kestrel.markets
Version:
A typed, token-efficient language + runtime for agentic trading: agents author bounded plans, the runtime fires them at the tick. CLI + typed library + MCP server.
128 lines • 8.93 kB
TypeScript
/**
* # series/provider — resolve a `SeriesRef` to a value (RUNTIME §2)
*
* The seam the trigger evaluator (and, later, panes and grade columns) reads through. A
* {@link SeriesProvider} answers three questions about the *market* and delegates *org*
* facts to an injected {@link OrgFacts}:
*
* - {@link SeriesProvider.resolve} — a `SeriesRef` (from `src/lang`) → a {@link Resolved}
* value (`number | string | UNKNOWN`). **Market facts** (`spot`, `vwap`, `hod`, `lod`,
* `prior_close`, `or_high`, `or_low`, and the windowed `velocity/move/range`) come from
* {@link CanonicalState} + its window engine; everything else is a path-scoped **org
* fact** resolved by the injected {@link OrgFacts} (the engine supplies the real one in a
* later milestone; a test fake ships here).
* - {@link SeriesProvider.baseline} — a windowed series' trailing `p*`/`sigma` (the portable
* "big", CONTEXT: Window). UNKNOWN unless the series is windowed, tracked, and warm.
* - {@link SeriesProvider.phase} — the session phase, for `phase open` triggers.
*
* Detector-driven predicates (`failed-break`), fill telemetry, and the session calendar are
* inputs from *other* modules; they are OPTIONAL hooks on the interface, and a provider that
* does not supply them makes those triggers read UNKNOWN with a logged reason (fail-closed,
* absent-with-reason — RUNTIME §8). Nothing here ever throws or reads silently false.
*
* All time is injected: `resolve` and the detector/calendar hooks take `now` explicitly
* (RUNTIME §0) — the provider holds no clock.
*/
import type { AtWindow, Baseline, FillEvent, PathSegment, SeriesRef, StructuralEvent, UntilWindow } from "../lang/index.ts";
import type { SessionPhase } from "../bus/index.ts";
import { type Resolved, type TriState, type Unknown } from "./types.ts";
/** The injected non-market hooks a canonical provider serves through (RUNTIME §2/§3): the session
* calendar (`time HH:MM` / `ttl-at`) and own-fill telemetry. Absent hooks read UNKNOWN
* (fail-closed, absent-with-reason) exactly as the OPTIONAL {@link SeriesProvider} methods
* specify — the session (which owns the ET calendar and the fill book) supplies the real ones. */
export interface CanonicalProviderHooks {
/** Minutes-since-ET-midnight for `now` (drives `time HH:MM` windows + absolute `ttl 16:00`). */
timeOfDayMinutes?(now: number): number | Unknown;
/** A fill-lifecycle event (`filled`/`rejected`/`cancelled`) as a trigger, from the session book. */
fillEvent?(ev: FillEvent): TriState;
/** The `{until,at}` thesis temporal-envelope firing decision (`inner until 15:30` / `inner at 15:30`,
* kestrel-rtf), given the envelope node, the evaluator-computed tri-state of its `inner`, and `now`.
* Absent ⇒ UNKNOWN (fail-closed, absent-with-reason) — the concrete firing semantics are specified by
* a future runtime ADR and wired through this seam. */
temporalEnvelope?(env: UntilWindow | AtWindow, inner: TriState, now: number): TriState;
}
import type { CanonicalState } from "./state.ts";
import { type SeriesRegistry } from "./registry.ts";
/** The injected resolver for **org facts** — path-scoped series that exist only in an
* acting session (`pnl`, `alpha.pnl`, `fills.avg_px`, `plan(chase-urgent).state`,
* `children(any).drawdown`). The engine supplies the real, book-backed implementation in a
* later milestone; it must return {@link UNKNOWN} for an unresolvable path (absent book,
* unknown name) and **never throw** — an unresolvable series de-arms its statement, it does
* not crash (RUNTIME §8). */
export interface OrgFacts {
resolve(segments: readonly PathSegment[]): Resolved;
/** OPTIONAL: resolve a **quantified** org path (`children(any|all).<fact>`) to the *vector* of
* per-member numeric values, so the trigger can fold the authored comparator per member (∃/∀)
* instead of collapsing to a comparator-blind scalar. UNKNOWN when the path is genuinely
* unresolvable (no member has published the fact) — and, like {@link resolve}, this is the point
* that logs the fail-closed de-arm reason (RUNTIME §8). Absent ⇒ the caller reads UNKNOWN. */
quantify?(segments: readonly PathSegment[]): readonly number[] | Unknown;
}
/** The market side of series resolution + the optional detector/fill/calendar hooks. */
export interface SeriesProvider {
/** A `SeriesRef` → its current value at `now`, or UNKNOWN. */
resolve(ref: SeriesRef, now: number): Resolved;
/** The trailing baseline (`p*`/`sigma`) of a windowed series, or UNKNOWN if it is not a
* windowed metric, is untracked, or is not yet warm. */
baseline(ref: SeriesRef, stat: Baseline): number | Unknown;
/** The current session phase, or UNKNOWN before the first phase-bearing heartbeat. */
phase(): SessionPhase | Unknown;
/** OPTIONAL: a detector-driven structural event (`failed-break of HOD`). Absent ⇒ the
* trigger reads UNKNOWN. */
structural?(ev: StructuralEvent, now: number): TriState;
/** OPTIONAL: a fill-lifecycle event (`filled`, `rejected`). Absent ⇒ UNKNOWN. */
fillEvent?(ev: FillEvent): TriState;
/** OPTIONAL: minutes-since-session-midnight for `time HH:MM..HH:MM` triggers. Absent ⇒
* UNKNOWN. */
timeOfDayMinutes?(now: number): number | Unknown;
/** OPTIONAL: the per-member value vector for a **quantified** org operand
* (`children(any|all).<fact>`), so a comparison folds the authored comparator per member (∃/∀)
* rather than against a comparator-blind scalar. UNKNOWN when unresolvable. Absent ⇒ UNKNOWN. */
quantify?(ref: SeriesRef, now: number): readonly number[] | Unknown;
/** OPTIONAL: the `{until,at}` thesis temporal-envelope firing decision (`inner until 15:30`,
* kestrel-rtf) — given the envelope node, the evaluator-computed tri-state of its `inner` (whose
* per-node causal memory the evaluator owns, so the hook cannot re-derive it), and `now`. Absent ⇒
* the envelope reads UNKNOWN (fail-closed, absent-with-reason), exactly like an absent
* `structural`/`fillEvent` hook — the capability is DISCLOSED as absent rather than a silent
* hardcoded never-fire. The concrete firing semantics are specified by a future runtime ADR and
* wired through this seam. */
temporalEnvelope?(env: UntilWindow | AtWindow, inner: TriState, now: number): TriState;
}
export declare class CanonicalSeriesProvider implements SeriesProvider {
private readonly state;
private readonly org;
private readonly hooks;
/** The series phonebook the market-vs-org routing dials (CONTEXT: Registry). Defaults to the
* process-wide platform phonebook; the market side is static, so the default is deterministic. */
private readonly registry;
constructor(state: CanonicalState, org: OrgFacts, hooks?: CanonicalProviderHooks,
/** The series phonebook the market-vs-org routing dials (CONTEXT: Registry). Defaults to the
* process-wide platform phonebook; the market side is static, so the default is deterministic. */
registry?: SeriesRegistry);
/** The session-calendar hook (`time HH:MM` / `ttl-at`); UNKNOWN when the session injects none. */
timeOfDayMinutes(now: number): number | Unknown;
/** The own-fill telemetry hook; UNKNOWN when the session injects none (absent-with-reason). */
fillEvent(ev: FillEvent): TriState;
/** The `{until,at}` temporal-envelope hook (kestrel-rtf); UNKNOWN when the session injects none
* (absent-with-reason) — the firing semantics are wired by a future runtime ADR. */
temporalEnvelope(env: UntilWindow | AtWindow, inner: TriState, now: number): TriState;
resolve(ref: SeriesRef, now: number): Resolved;
/** A quantified org operand folds per member (∃/∀) — delegate to the org backing's vector
* resolution; absent (or a market provider without org quantification) ⇒ UNKNOWN (fail-closed). */
quantify(ref: SeriesRef, _now: number): readonly number[] | Unknown;
baseline(ref: SeriesRef, stat: Baseline): number | Unknown;
phase(): SessionPhase | Unknown;
private marketScalar;
}
/** A path key for a segment list: `plan(chase).state`, `children(any).drawdown`, `pnl`.
* The canonical string form used by {@link FakeOrgFacts} and handy for logging. */
export declare function pathKey(segments: readonly PathSegment[]): string;
/** A map-backed {@link OrgFacts} for tests (the engine ships the real one). Unknown paths
* resolve to UNKNOWN — exactly the contract the real seam must honor. */
export declare class FakeOrgFacts implements OrgFacts {
private readonly facts;
constructor(facts?: ReadonlyMap<string, Resolved>);
static of(entries: Readonly<Record<string, Resolved>>): FakeOrgFacts;
resolve(segments: readonly PathSegment[]): Resolved;
}
//# sourceMappingURL=provider.d.ts.map