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.

419 lines (346 loc) 23 kB
# Kestrel — founding architecture > A typed, token-efficient **language + runtime for agentic trading**. The slow > intelligence (an LLM agent, and the human) *authors*; the fast runtime *perceives* > and *executes* at millisecond latency. Named for the falcon that wind-hovers — > holding perfectly still with the sharpest eyes in the animal kingdom — and then > **stoops**, dropping onto prey faster than you can react. That is the whole product: > **see everything, then strike at speed.** **Status:** founding document (2026-07-11), reconciled with the design grill of the same day. Where wording differs, `CONTEXT.md` (the glossary) and `docs/adr/0001``0007` are canonical. Domain: **kestrel.markets**. Published to npm as **`kestrel.markets`** (MIT). --- ## 1. Why Kestrel exists — two hard requirements Kestrel did not come from a trading strategy. It came from two lived frustrations running LLM agents against live markets, and those two frustrations *are* the hard requirements. Everything else is downstream of them. 1. **Perception parity — the agent must SEE what the human sees.** Today a human stares at rich charts and candlesticks and then tries to *narrate* them to the agent in prose. That is absurd and lossy. Kestrel gives the agent the same rich, structured market picture the human has — as **one canonical truth rendered many ways**: a token-efficient screen (ASCII/unicode/markdown) for the agent's context, and a real chart (HTML) for the human. Same numbers, same moment, different Renderings of one Frame. 2. **Latency — the agent can NEVER be in the hot path.** By the time an agent reads a price, computes fair value, and submits a limit order, the market has moved and the order is stale. So the agent does not trade in the loop. It **pre-authors deterministic strategy** — a standing program the runtime fires at machine speed without waking anyone. Slow judgment, compiled into a fast reflex (*fire-then-inform*). Every design decision below is judged against these two. If a change makes the agent see less, or puts it back in the hot path, it is wrong regardless of any other merit. --- ## 2. The primitive — one language, four surfaces Kestrel is **one language with one lexical core** (ADR-0001): four statement kinds sharing the trigger algebra, series references, and price expressions. All four are readable by humans and agents, and token-efficient — they are what an LLM emits and consumes, so every token counts. View, Wake, and Plan are **standing** (authored → armed → versioned → superseded); Grade is **imperative** (a job that runs and returns). | surface | question it answers | lifecycle | |---|---|---| | **View** | *what should I see?* | standing | | **Wake** | *when should I look?* | standing | | **Plan** | *what may execute?* | standing | | **Grade** | *did it actually work?* | imperative | The canonical representation is a **typed TS object model**; text is a projection of it — `parse(text) → objects`, `print(objects) → canonical text`, byte-stable round-trip (ADR-0004). Every document is a **module** (ESM-like): its named statements are importable; a file can be a full pod, a single book, or a library of plan templates (ADR-0003). ### View — what should I see? A View is the standing spec: which panes, at what token budget. Each moment it materializes into a typed **Frame** — the instance carrying every value with its source — which renders to ASCII, unicode, markdown, JSON, or HTML. Same Frame, many Renderings; the agent's screen and the human's chart are two renderings of one Frame. A View selects panes; it never computes. ```kestrel VIEW open budget 1800 macro week quarter rv20 vol straddle expected-move tape skyline 5m vwap detector-strip levels registry chain fair realness ``` Every Frame carries a **kernel** bound to the *session*, not the View: an acting session (a book attached) always shows positions, resting orders, budget, wake reason — non-configurable; a spectator Frame (publication, research, historical charts) carries provenance only. You cannot configure away the acting kernel — only genuinely have nothing at stake. ### Wake — when should I look? A Wake is a standing subscription over the same trigger algebra Plans use, but it spends **attention** (tokens, wakes), never risk. Wakes are event-driven — never an arbitrary clock. A Wake names the View it delivers: the wake decides *when*, the View decides *what*. ```kestrel WAKE session-open WHEN phase open # from the instrument's session calendar DELIVER open MANDATORY WAKE fade-setup WHEN second failed-break of hod within 30m DELIVER execution PRIORITY 2 COALESCE hod-action BUDGET 12 wakes/day WAKE fast-move WHEN velocity(1m) > p99 # scale-relative: window × baseline DELIVER zoom-1m ``` **There is no absolute shock**: magnitude is judged at a window (`velocity(1m)`, `move(1w)`) against that window's own trailing baseline (`p99`, `3sigma`) — and the delivered View zooms to the wake's window. A **Scan** is a Wake over a *universe* rather than a coverage (all listed equities at `move(1d) > p99`) — wide and slow where coverage wakes are narrow and fast. One platform-default Wake exists in every acting session: the **staleness backstop** (if nothing woke you in N, wake anyway; not removable while holding positions). ### Plan — what may execute? A Plan is a standing, bounded-risk program: `trigger → actions → bracket → invalidation → TTL`. The runtime evaluates it every tick and **fires the pre-authorized action at the tick, then informs the author in parallel** (fire-then-inform). The plan — not the order — is the agent's unit of output. ```kestrel PLAN chase-urgent budget 0.2R ttl +30m regime {intraday: trend} USING signal SPX exec SPY 0dte WHEN spot crosses above hod AND velocity(1m) >= p95 DO buy 2 +1 C @ min(fair-3c, mid) peg esc fair 3m cap fair cancel-if under vwap EXIT under vwap held 60s @ fair esc mid 30s ``` The author's **belief is expressed as the shape of the plan** — which strikes, how far under `fair` it rests, its sizing, and whether adverse movement means RELOAD (buy more), INVALIDATE (thesis dead, ride the tail), or EXIT (get out). Belief is never a number the engine computes. Scope of Plan v1 — including inventory binding (`basis` anchor, held-leg quantifiers, on-arm trigger) and the reserved-but-refused `atomic` keyword — is ADR-0005. ### Grade — did it actually work? A Grade replays anything authored — a Plan, a Wake (attention efficiency), a View (did agents decide better through it), a regime tag (gated-beats-pooled), a Pod node (the PM's allocations) — across recorded sessions under a **named, versioned fill model**, and reports honest EV with counterfactuals built in (ADR-0006). ```kestrel GRADE plan chase-urgent OVER 2025-01..2026-06 FILL maker-v1 VS ungated BY regime.intraday, vehicle, lineage ``` **Names are data**: a recurring plan name is a lineage (one strategy, many authored instances); `BY name` / `BY lineage` are first-class grade dimensions, and clustering names with outcomes is how strategy families and sub-regimes *emerge* rather than being hand-defined. --- ## 3. The org model — pods all the way down The org is in the language (ADR-0002). The **Pod** is the recursive node: an allocating role (**PM**) + a risk envelope + children, where each child is a **Book** (the leaf — the only place positions live, run by a **Trader** under an assigned **Coverage**) or another Pod. Depth is unbounded; the org is **data** — a standing Kestrel document the PM revises intraday (spinning up five traders for the day is authoring five leaves). Humans always sit above the root: a pod is the largest thing that runs on its own. ```kestrel POD example-pod RISK day-loss 2R -> halt BOOK alpha budget 1.0R coverage SPY thesis "..." BOOK beta budget 0.5R coverage QQQ thesis "..." ``` Depth-independent invariants at every edge: **budgets nest** (child ⊆ parent), **authority only narrows downward**, **positions live at leaves only**, and **Risk (L0) outranks every node including the root**. Org facts are **path-scoped series** resolved lexically like ESM (`pnl` = my scope; `alpha.pnl` = my child; `children(any).drawdown` = a PM's wake operand). The typical shape: standing books at fast windows on core coverage, pod-level Scans wide-and-slow for discovery — a Scan fires, the PM evaluates, and may author a new leaf. ### The authority tiers — who acts, at what speed - **L2 — judgment (seconds–minutes).** The LLM agent and the human. Reads Frames, does research, forms a thesis, authors View/Wake/Plan, allocates (PM). Never touches the wire. - **L1 — the runtime (ms–1s).** Evaluates armed triggers every tick; fires pre-authorized actions at machine speed; wakes L2 *in parallel* with the fire. - **L0 — Risk (µs–ms).** Bounded-risk invariants. Clamps or vetoes any order; **may never open risk.** The backstop under every node of every pod. Intent flows down; veto flows up. L2 is where intelligence lives and it is too slow to trade; L1 is fast and deterministic and has no judgment; L0 keeps everyone bounded. --- ## 4. The module spine and its seams Each module is deep (a small interface over a lot of behavior) and meets its neighbors at a named seam. Adapters vary behind a seam; the interface does not. ``` L2 agents + humans (author: View / Wake / Plan / Pod) | intent ↓ ↑ inform (fire-then-inform) ┌──────────────────────┼───────────────────────────────────────────────┐ | Wake router Frame ── renderer ── (ascii|unicode|md|json|html) | | ↑ | | series registry ──────────────────┐ | Fields (attributed, | | (register once → every surface) | | watermarked) | | ↓ ↓ ↓ | | | triggers Grade panes canonical market state ── ExecutionFair | | | (one causal coord) (@fair, receipts) | ┌─────┴──────────────────────────────────────────────────┐ | | | DSL core (parse/print/eval · typed TS object model) | | | └─────┬──────────────────────────────────────────────────┘ | | ↓ | | rule engine (tick loop, fire-then-inform) ── Gate seam ──┐ | | | | | | L0 Risk envelope (bounded-risk clamps) | | └──────────┼──────────────────────────────────────────────────┼─────────────┘ ↓ ↓ Session {driver, clock, gate, bus, mode} adapters: broker (BYO/MCP) sim | paper | live = ONE path, different gate market-data feed ↑ lake (chDB / R2) event bus (typed, append-only, replay-byte-stable) ``` ### Canonical market state One causal market coordinate per underlying — spot, HOD/LOD, opening range, velocity, structural levels — sourced from the **deepest instrument**, health-gated. Never reconciles two estimates at runtime: the source hierarchy is fixed a priori; on feed failure it enters an explicit **degraded** mode that taints dependents and de-arms affected plans. Every derived number references one canonical series; nothing is recomputed per caller. (This kills the classic bug class where the screen's "authoritative" value disagrees with the level the engine fires on.) ### ExecutionFair — the `@fair` anchor The honest **price at which a market maker will fill you**, not the midpoint of a possibly-fictional book. Underlying-anchored intrinsic + a vol read backed out of the *liquid* quotes, floored at intrinsic. Receipt-gated (model version, fit quality, freshness) — it carries whether it can be trusted, not just a number. **The observed mid is a health signal (thin/dark-book fingerprint), never a value.** `@fair` resolves here. Belief is the author's; fair is the engine's. ### The DSL core Parses, prints, and evaluates all four surfaces into one typed object-model family (ADR-0004). Fails **closed**: any parse escape resolves to STAND_DOWN, never an unbounded order. One evaluator for price expressions across every surface — exactly one place knows what `fair`, `mid`, `lean(a,b,x)`, `peg`, `cap`, and `esc` mean. ### The rule engine The tick loop. Evaluates armed Wake/Plan triggers each tick over detectors and series computed causally from the stream it already owns (never a second market-data connection). On a confirmed trigger it fires through the **Gate** and wakes L2 in parallel. Deterministic and replayable: same input sequence → same event sequence. ### The series registry Anything with a name whose value changes over time registers **once** and becomes simultaneously: a trigger operand, a View pane, and a Grade column — by construction, one definition, no trigger/screen/grade skew. Two kinds of series, and only two: **market facts** (ambient per signal instrument, spectator-visible) and **org facts** (path-scoped in the pod tree, acting-sessions only). Registration is taste-free; replay decides what earns usage; an unresolvable series reads UNKNOWN and de-arms its statement with a logged reason. ### Frame + renderer The engine serializes a typed **Frame** of **Fields**. Every rendered number is a `Field { series_id, value, attribution, source_watermark }` — attributed (OBS / CALC / DETECTOR / MODEL / POLICY / UNKNOWN) and watermarked (instrument, engine sequence, model version). **The renderer is a pure function of the Frame; it invents no value** — it chooses only glyphs, layout, and token spend, parameterized by format and by the target model's tokenizer (which glyphs are cheap is an empirical, per-model fact). **Streaming renderings are append-only (ADR-0008):** the live tape is vertical — one row per candle, newest last — so a long-running agent's KV cache stays valid and each update costs O(new bars); 2D price×time grids are keyframe-only or human-facing. This is what makes the screen a trainable curriculum and satisfies requirement №1. ### Wake router Turns the trigger algebra into attention. Subscriptions carry attention budget, priority, and a delivered View — never risk. Past budget, wakes **coalesce and downgrade, never silently drop** (the squelch is visible in the kernel). **A Wake is not a Plan** — shared WHEN algebra, disjoint authority. ### Session The one object that owns a run: `{ driver, clock, gate, bus, mode }`, modes **sim | paper | live** as one code path with different gate adapters — honest grading by construction, and the single thing the API/CLI/MCP sit on. A Pod definition is a **class**; a run is an **instance** (ADR-0007): sim and paper instances are unbounded (thousands of parallel runs across versions is the normal shape of Grade); **the live instance is a singleton per pod lineage, platform-enforced**. Promotion is a document revision of the live pod, never a flag flip. ### Agent harness The L2 loop: Frame → LLM (+ research) → authored Kestrel → validated by the DSL core → armed. Parse failure routes to STAND_DOWN. This is where intelligence enters, and it is deliberately **outside** the hot path. ### Adapters (the edges) - **Broker execution** — BYO-broker: the client's own plans through the client's own account, via a broker adapter or broker MCP. Kestrel never holds custody. - **Market-data feed** — the live tick/quote source; colocated with the runtime. - **Lake** — chDB over S3-compatible object storage, native in TS/Bun. **Object storage is the only source of truth**; local disk is either an engine-managed filesystem cache (named, size-capped, LRU — repeated reads go local-fast) or an explicit dev/fixture root for creds-free tests — never a silent alternate data source. Read-only history for Grade and sim. ### Grade / fill model One **FillModel** seam returning a calibrated `p_fill`, an expected fill price, and a support flag — not a binary fill. Every plan *and* the structural null cross the same fill model, so strategies are compared on fills, not on fill assumptions. The fill model is itself calibrated against realized live fills and versioned; EVs across versions are not comparable and are stamped as such. --- ## 5. The stack — TS/Bun, top to bottom - **TypeScript on Bun**, everything: DSL core, rule engine, renderer, harness, API/CLI/MCP. chDB runs natively in TS — no second language anywhere. - **The ms rule engine is fine in TS.** This is millisecond-budget work (faster than an LLM round-trip), not microsecond HFT. The latency floor is network-to-exchange; language overhead is noise. Colocate runtime + feed + broker gateway. - **Surfaces:** HTTP/SSE (programs) · CLI (humans) · MCP (agents) — three faces of one runtime: same Frames, same grammar, same provenance ceiling. - **Validation:** the grammar and engine are proven against a privately-maintained golden corpus (grammar parses + recorded session replays) and property tests (round-trip, replay-byte-stability, fail-closed). Correctness is earned by tests, never assumed from provenance. --- ## 6. Non-negotiable invariants — enforce TYPES, not control flow A thing belongs in the platform iff violating it is *always* wrong. If two reasonable strategies could legitimately differ on it, it is a free dial, not a platform constraint. 1. **Bounded risk (bounded by default, fail-closed).** Every action has a *computable* `max_loss` (never unknown, never unbounded) **and** `size × max_loss ≤ budget`; enforced at the output boundary, fail-closed — an unknown or unbounded max_loss is refused (`no-bounded-envelope`). This alone forbids the always-wrong cases (a naked call and short equity are unbounded) while permitting a budgeted cash-secured put, so `no-uncovered-short` is an author-overridable **application default** in the Risk envelope, *not* a platform invariant — two reasonable strategies differ on it (this invariant's own membership test). Unbounded risk is reachable only by explicit willing-human enablement, never a silent default. See ADR-0005. 2. **Causality + fence.** No look-ahead. Same bus + same sequence ⇒ byte-identical Frame and byte-identical event stream. Replay-stability is a test, not a hope. 3. **Fair carries receipts; mid is never a price** — never a silent price anchor, never worth. (As a *book-state observation* in triggers — spread regime, dark-book detection — mid/marks are legitimate signal; the structural ban on mark operands applies to EXIT, where acting on a lying mark is the failure mode: ADR-0005.) 4. **Fail-closed parse → STAND_DOWN.** Unknown series read UNKNOWN and de-arm their statement with a logged reason — never crash, never silently false. 5. **Honest Grade under one named fill model**, counterfactuals built in. 6. **Provenance ceiling.** Authority is set by the authenticated channel; a document's declared provenance can only *narrow* it. Unvetted plans are capped. 7. **Org invariants at every edge:** budgets nest; authority narrows downward; positions at leaves only; **live is a singleton per pod**. 8. **The acting kernel is non-configurable.** No agent trades blind to its own inventory. --- ## 7. Platform vs application Kestrel is the **platform**: the language, the runtime, the honest judge — instrument-, strategy-, and edge-agnostic. **Applications** — actual books, coverages, armories of vetted plans, and the theses behind them — are built *on* Kestrel and are not part of it. The platform ships nothing that reveals any application: examples in docs and tests are illustrative, chosen to teach language features. Kestrel is **not** an algorithm, a predictive model, a signal service, or a broker-dealer; the intelligence is the author's (agent or human), and the platform's job is to let it see, act, and be judged honestly. --- ## 8. Build order (first milestones) Each milestone is graded against the acceptance suite before the next begins. 1. **Typed object model + DSL core** — all four surfaces + Pod/Book/USING/imports; parse/print round-trip; fail-closed. The language exists first. 2. **Canonical state + ExecutionFair + Frame** — the correctness floor everything reads. 3. **Rule engine + Session over a sim gate** — grade a recorded day end to end; byte-identical replay. 4. **Agent harness + Wake router** — close the Frame → LLM → plan loop; event-driven wakes. 5. **Paper gate** — live feed, simulated fills, same Session path. 6. **Surfaces** — HTTP/SSE, CLI, MCP over Session. 7. **Live broker adapter** — BYO-broker, colocated, human-gated promotion, singleton enforced. --- ## 9. Open questions (honestly unresolved) - **Near-expiry fair.** ExecutionFair degrades toward intrinsic when the near-money book goes dark close to expiry (all gamma, no time value, no liquid quotes to reconstruct from) — exactly where belief-vs-fair matters most. How much does the engine assert vs hand to the author? - **Multi-instrument graph.** Base case: one underlying + its tradeable projections; general case: an N-node instrument graph with signal/execution as per-plan roles. Deferred; types must not preclude it. - **Atomic multi-leg.** Named defined-risk structures need whole-structure preflight + an atomic execution adapter. v1 reserves the `atomic` keyword and refuses it loudly (ADR-0005) — never parse-and-leg silently. - **The advisory line.** BYO-broker dodges custody, not the investment-adviser line; auto-arming someone else's book is discretionary management. Live stays BYO-broker AND BYO-plan; anything more is a registered-entity question.