UNPKG

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.

196 lines (171 loc) 8.42 kB
/** * # vocab — the single source for every closed lexical vocabulary * * Each runtime table below is DERIVED from a closed string-union in {@link ./ast.ts}, so the * vocabulary the parser recognizes and the printer emits can never silently drift from the * type that defines the language. Every table is a `Record<Union, …>`: removing a union * member leaves an excess key here (a compile error at the table), and adding a member leaves * a missing key here (also a compile error at the table). The human-facing ordered lists * (`ANCHOR_NAMES`, `TIME_UNITS_LIST`) come from `Object.keys` of those Records — insertion * order is stable for non-integer keys — so "expected one of …" error strings are * single-sourced too, never a hand-retyped third copy. * * `parse.ts` and `print.ts` consume this module; neither keeps a private copy (ADR-0004: the * two projections must agree, and the surest way is to read the same table). */ import type { AnchorName, Citation, ClockStop, CompareOp, FillEvent, GradeSubject, ProvenanceTier, Standing, TimeHeldStop, TimeUnit, } from "./ast.ts"; /** The keys of a closed-vocabulary table, in insertion order, typed back to the union. */ function keysOf<T extends string>(table: Record<T, unknown>): readonly T[] { return Object.keys(table) as T[]; } // ── Time units (TimeUnit) ────────────────────────────────────────────────────── const TIME_UNIT_TABLE: Record<TimeUnit, true> = { s: true, m: true, h: true, d: true, w: true, mo: true, q: true, y: true, }; /** Ordered for the "a time unit (…)" / "expected one of …" parse messages. */ export const TIME_UNITS_LIST = keysOf(TIME_UNIT_TABLE); export const TIME_UNITS: ReadonlySet<string> = new Set(TIME_UNITS_LIST); // ── Ordinal-literal prefixes (SessionOrdinal / ExpiryOrdinal — Train 1B, ADR-0041 §1) ─────────── // // The two single-letter prefix words that open a tight ordinal literal in a pane-argument position: // `d-<n>` (a SessionOrdinal — `d-0` current session, `d-1` prior…) and `e-<n>` (an ExpiryOrdinal — // `e-0` nearest expiry…). Date-blind RELATIVE addresses (never a date — same class as wake ordinals), // each with ONE canonical printed form so `print(parse(text))` stays byte-stable (ADR-0004). The // parser assigns the SUPERSORT from the prefix with zero catalog knowledge (the `wf` rung); the // catalog `ParamSlot` refines it (SessionOrdinal / ExpiryOrdinal) at the `mat` rung. export const ORDINAL_PREFIX = "d" as const; export const EXPIRY_PREFIX = "e" as const; // ── Price anchors (AnchorName) ───────────────────────────────────────────────── const ANCHOR_TABLE: Record<AnchorName, true> = { fair: true, intrinsic: true, basis: true, bid: true, ask: true, mid: true, last: true, join: true, improve: true, stub: true, // Emergent anchor (ADR-0030 / kestrel-ipc): admitting `spot` here auto-updates the parser's // accepted set (ANCHORS) AND the "expected a price: an anchor (…)" message (ANCHOR_NAMES) — // a single-sourced, strict-superset relaxation. spot: true, }; /** Ordered for the "an anchor (fair, intrinsic, …)" parse message. */ export const ANCHOR_NAMES = keysOf(ANCHOR_TABLE); export const ANCHORS: ReadonlySet<string> = new Set(ANCHOR_NAMES); // ── Exit time-stop keywords (TimeHeldStop / ClockStop) — the 0DTE time-exit surface ── /** The lead keyword for each 0DTE time-exit stop node, single-sourced so the parser that reads * it (`parse.ts`) and the printer that emits it (`print.ts`) — and the WATCHER grammar overlay * that references `clockET` (`scripts/gen-grammar.ts`) — all draw the SAME spelling from one * table, never a hand-retyped copy (ADR-0004; the file's single-source thesis). Derived from the * {@link TimeHeldStop}/{@link ClockStop} `kind` union so adding/removing a stop is a compile error * here. `held` is the same spelling the `break-hold` postfix uses; `clockET` is the new token * this axis adds (it mirrors the watcher's `atClockET` wake vocabulary). */ const TIME_STOP_TABLE: Record<TimeHeldStop["kind"] | ClockStop["kind"], string> = { "held-stop": "held", "clock-stop": "clockET", }; export const TIME_STOP_KEYWORDS: Readonly<Record<TimeHeldStop["kind"] | ClockStop["kind"], string>> = TIME_STOP_TABLE; /** `held` — the lead keyword of a `TimeHeldStop` (`EXIT held 90m`). */ export const HELD_STOP_KEYWORD = TIME_STOP_TABLE["held-stop"]; /** `clockET` — the lead keyword of a `ClockStop` (`EXIT clockET 15:40`). The NEW vocab token. */ export const CLOCK_STOP_KEYWORD = TIME_STOP_TABLE["clock-stop"]; // ── Fill-lifecycle events (FillEvent["event"]) ───────────────────────────────── const FILL_EVENT_TABLE: Record<FillEvent["event"], true> = { filled: true, "partial-fill": true, unfilled: true, rejected: true, cancelled: true, }; export const FILL_EVENTS: ReadonlySet<string> = new Set(keysOf(FILL_EVENT_TABLE)); // ── Standing lifecycle (Standing) ────────────────────────────────────────────── const STANDING_TABLE: Record<Standing, true> = { authored: true, armed: true, versioned: true, superseded: true, }; export const STANDINGS: ReadonlySet<string> = new Set(keysOf(STANDING_TABLE)); // ── Grade subjects (GradeSubject["what"]) ────────────────────────────────────── const GRADE_WHAT_TABLE: Record<GradeSubject["what"], true> = { plan: true, wake: true, view: true, pod: true, tag: true, }; export const GRADE_WHATS: ReadonlySet<string> = new Set(keysOf(GRADE_WHAT_TABLE)); // ── Citation content-hash algorithms (Citation["algo"]) — the `because` clause ─ /** The closed set of content-hash algorithms the `because` citation accepts (kestrel-rtf). * Derived from the {@link Citation} union so the lexed `sha256:<64 hex>` token shape and the * type can never drift; reuses the repo's ubiquitous sha256 digest shape (no second scheme). */ const CITATION_ALGO_TABLE: Record<Citation["algo"], true> = { sha256: true, }; export const CITATION_ALGOS: ReadonlySet<string> = new Set(keysOf(CITATION_ALGO_TABLE)); // ── Provenance tiers + authority rank (ProvenanceTier) ───────────────────────── /** Authority ordering for the provenance ceiling: a higher rank is more authority. The * Record over `ProvenanceTier` doubles as the closed-vocabulary table — membership is its * key set, so the rank map and the tier vocabulary can never diverge. */ export const PROV_RANK: Readonly<Record<ProvenanceTier, number>> = { unvetted: 0, candidate: 1, vetted: 2, }; export const PROV_TIERS: ReadonlySet<string> = new Set(keysOf(PROV_RANK)); // ── Comparison operators (CompareOp) — ONE bidirectional map ─────────────────── /** The canonical direction the printer emits (op → punctuation). `Record<CompareOp, …>` * keeps it exhaustive over the union. */ export const CMP_TO_PUNCT: Readonly<Record<CompareOp, string>> = { gt: ">", ge: ">=", lt: "<", le: "<=", eq: "==", ne: "!=", }; /** The parser's inverse (punctuation → op), derived mechanically from `CMP_TO_PUNCT` so the * two directions are one table, not two hand-kept copies that could disagree. */ export const CMP_FROM_PUNCT: Readonly<Record<string, CompareOp>> = Object.fromEntries( (Object.entries(CMP_TO_PUNCT) as [CompareOp, string][]).map(([op, punct]) => [punct, op] as const), ); // ── Ordinals — ONE list (parser matches words, printer emits them) ───────────── /** 1-based ordinals; index 0 (`zeroth`) is a slot filler so `ORDINALS[n]` lines up with the * cardinal `n` (the printer refuses n < 1, the parser matches from index 1). */ export const ORDINALS: readonly string[] = [ "zeroth", "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth", ];