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.

602 lines (601 loc) 37.2 kB
/** * # fill/engine — SimFillEngine, the pure resting-order state machine (RUNTIME §6–7) * * A deterministic, time-injected engine that holds resting orders, reassesses them against * book updates through a {@link FillModel}, and cash-settles the outcome at session close. * It owns no clock and no RNG on its default path (RUNTIME §0): every timestamp is passed in, * and the only randomness — an *optional* seeded sampler for Monte-Carlo realization — is * injected. **Same event sequence ⇒ same outcomes** (the determinism invariant). * * Lifecycle of one order: * * 1. {@link SimFillEngine.place} — the order rests; a `place` ORDER event is emitted. * 2. {@link SimFillEngine.cancel} — **effective immediately**; a cancelled order can never * later fill, no matter what the tape does afterward. * 3. {@link SimFillEngine.onBook} — every resting order on the updated leg is reassessed. A * strict-cross result is a **floor fill**: the order leaves the book into filled inventory * and a `fill` ORDER event is emitted. A hazard result accrues into the order's survival * product for expected-value accounting; the order keeps resting. * 4. {@link SimFillEngine.settle} — at the settle spot: filled inventory settles at * **intrinsic**; still-resting orders **expire worthless-unfilled** (no position, $0 * floor). The report carries both the floor outcome and the E[$] under `pFill` * (RUNTIME §6). * * Expected-value accounting: for each resting order the engine keeps a **survival** product * `S = Π(1 − pFillᵢ)`; its expected fill probability is `1 − S`. A floor fill pins that to * `1`. At settle, `floorPnl` counts only floor fills, while `expectedPnl` weights every fill's * settle P&L by its expected fill probability — so a single grade reports the conservative * floor and the calibrated expectation side by side. */ import { mulberry32 } from "../bus/index.js"; import { sha256 } from "../crypto/sha256.js"; import { intrinsic } from "../fair/index.js"; import { isCalibratedSupport } from "./model.js"; /** * The seeded per-episode RNG substream key (ADR-0016 §3): `hash(runSeed, model.version, order.ref, * episodeIndex) → uint32`, folded to a mulberry32 seed. sha256 (portable, deterministic — no wall * clock, no unseeded RNG, RUNTIME §0) over the canonical tuple string, top 32 bits parsed as the * seed. Adding `episodeIndex` to the prior fill model's `(seed, model.version, ref)` key gives each * dark↔lit episode under one ref its own independent, replay-stable draw (deviation D1); a book * observation that does NOT mint an episode consumes no substream and perturbs nothing. */ function episodeSeed(runSeed, modelVersion, ref, episodeIndex) { const h = sha256(`${runSeed}|${modelVersion}|${ref}|${episodeIndex}`); return parseInt(h.slice(0, 8), 16) >>> 0; } /** * The **reference re-mint cadence** for a resting episode-keyed order (ADR-0016 §episode-identity, * boundary **B4**, kestrel-9gu.13). A per-EPISODE hazard model (`MakerFairCalV1`) folds one Bernoulli * per resting *episode*, and episodes previously minted ONLY on placement/reprice/dark↔lit-flip — so a * STATIC resting order (no reprice, no regime flip) took a single draw all session and its cumulative * fill could never approach the calibrated ground truth (a patient at-fair BUY pinned at p≈0.25 rather * than saturating). B4 re-mints **one lit episode per `DT_REF` of continuous rest in the SAME regime**, * matching the upstream reference model's per-observation-at-DT_REF draw (its measured time-to-fill * median ≈ 0s makes the per-episode rate a per-`DT_REF`-window rate). * * It is keyed on the **injected tape clock** (`ts`), never wall time or observation COUNT: the number of * re-mints over a rest span is `floor(restMs / DT_REF)`, so a dense tape and a sparse tape covering the * same span mint the same episodes (density-independent — "a bus's book cadence is an artifact of * recording", ADR-0016 §Context). A re-look INSIDE a window mints nothing (the N1 anti-double-count core * is preserved: `windows = 0`). `5_000` ms = the reference model's DT_REF (5s). */ export const EPISODE_REMINT_DT_REF_MS = 5_000; /** * Once a resting order's survival product drops below this, its expected fill probability is ≥ `1 − eps` * (effectively certain) and further B4 re-mints of the same regime move neither the bankable expected * channel nor a still-open sampled draw meaningfully — so the catch-up loop STOPS folding (and stops * emitting per-episode hazard telemetry) at this floor. This bounds telemetry and work on a sparse-tape * catch-up (a single look after a long rest gap would otherwise mint thousands of saturated episodes) * while keeping the engine's survival byte-identical to what the bus telemetry re-derives (both stop at * the same deterministic threshold — ADR-0011 re-derivability holds). */ const EPISODE_SURVIVAL_SATURATION_EPS = 1e-9; /** * The declared default settle-mark staleness threshold (kestrel-xwf): a settle spot whose value was * last established more than this many ms before the settle instant grades STALE and taints the * headline. 5 minutes — comfortably coarser than the tape's event cadence and the 1-minute structural * wake grid (a live feed refreshes the mark many times per threshold), yet orders of magnitude finer * than the hours-frozen failure it exists to catch (2025-04-09: frozen from 13:01 through the close). */ export const SETTLE_MARK_STALE_AFTER_MS = 300_000; // ───────────────────────────────────────────────────────────────────────────── // SimFillEngine // ───────────────────────────────────────────────────────────────────────────── /** * The resting-order state machine. Construct one per book (or per session); drive it with * `place` / `cancel` / `onBook`, then `settle`. Every mutator returns the typed bus events it * produced (seq-less {@link NewBusEvent}s — the owner {@link ../bus/write.ts BusWriter} stamps * `seq`); the same events are also appended to the engine's cumulative {@link events} log. */ export class SimFillEngine { #model; #multiplier; #sampler; #staleMarkAfterMs; #resting = new Map(); #filled = []; #cancelled = new Set(); #seen = new Set(); #events = []; #carried = []; /** 9gu.9 stale-print gate: the last trade print SEEN per leg (`instrument|strike|right`), folded on * EVERY book look. `OptionQuote.last` is carried forward session-cumulatively by the tape converter * (99%+ of last-bearing leg-events on the recorded tapes are stale carries), so the trade-through rule * may fire only when the print CHANGED on this event — a fresh execution, never a morning print an * afternoon order could not have interacted with. The first sighting of a leg's print is inert (no * prior to diff against — conservative; the floor undercounts). The engine strips `last` from the leg * it hands the model unless the print is fresh, so all three judges get the gate for free. */ #lastPrint = new Map(); #settled = false; constructor(opts) { this.#model = opts.model; this.#multiplier = opts.multiplier ?? 1; this.#sampler = opts.sampler; this.#staleMarkAfterMs = opts.staleMarkAfterMs ?? SETTLE_MARK_STALE_AFTER_MS; } /** The cumulative typed bus events this engine has produced, in order. Read-only. */ get events() { return this.#events; } /** The refs of all currently-resting (placed, not cancelled, not floor-filled) orders. */ restingRefs() { return [...this.#resting.keys()]; } /** A resting order's current expected fill probability (`1 − survival`), or `undefined` if * no such order is resting. Exposed for grading/inspection. */ expectedFillProb(ref) { const st = this.#resting.get(ref); return st === undefined ? undefined : 1 - st.survival; } /** * Place a resting order. The `ref` must be unique across the engine's lifetime (a re-used * ref is refused loudly — fail-closed, RUNTIME §8). Emits a `place` ORDER event. Returns the * order's `ref`. */ place(order, ts) { if (this.#settled) throw new Error(`SimFillEngine: place after settle (${order.ref})`); if (this.#seen.has(order.ref)) { throw new Error(`SimFillEngine: duplicate order ref ${JSON.stringify(order.ref)}`); } this.#seen.add(order.ref); this.#resting.set(order.ref, { order, lastTs: ts, survival: 1, supportExtrapolated: false, sampledFilled: false, episodeIndex: 0, cadenceAnchorTs: ts, }); this.#emit("place", ts, order, { px: order.px }); return order.ref; } /** * Seed a **carried** position (kestrel-5zl.16.2 — the multi-session open). Re-opens a lot handed * over by the prior session's carry-mark settle at its carry-mark `basis`: emits a `place` then a * `fill` ORDER event (both at `basis`, at the session's open `ts`) so the GRADED bus is * self-consistent — the position appears as an opening fill the Blotter projects — and pushes it into * filled inventory, where {@link settle} marks it. Never reassessed by {@link onBook} (it is inventory, * not a resting order). `ref` must be unique across the engine's lifetime (fail-closed on reuse). No * caller seeds this on a length-1 sequence, so this path never runs for an existing single-session run. */ seedFilled(order, basis, ts) { if (this.#settled) throw new Error(`SimFillEngine: seedFilled after settle (${order.ref})`); if (this.#seen.has(order.ref)) { throw new Error(`SimFillEngine: duplicate order ref ${JSON.stringify(order.ref)}`); } this.#seen.add(order.ref); // The carried lot's basis IS its resting/fill price — `#outcome` measures P&L off `order.px`. const seeded = { ...order, px: basis }; this.#emit("place", ts, seeded, { px: basis }); this.#emit("fill", ts, seeded, { px: basis, reason: "carried" }); // A carried lot is a DEFINITE opening fill (realized before this session), so it is floor-class: // not a sampled draw (`viaSampled: false`), structurally `calibrated`, and — containment, ADR-0016 // §4 — present in the sampled channel too whenever the sampler is armed (a definite fill exists in // every channel; `survivalAtFill: 1` is the floor pin, unused for a non-sampled lot). this.#filled.push({ order: seeded, fillPx: basis, fillTs: ts, sampledFilled: this.#sampler !== undefined, viaSampled: false, support: "calibrated", survivalAtFill: 1, }); } /** The lots a carry-mark {@link settle} marked to close and let survive — the next session's opening * inventory (kestrel-5zl.16.3). Empty until (and unless) a `settle(…, { carry: true })` ran. */ carriedInventory() { return this.#carried; } /** * Cancel a resting order, **effective immediately**: it is removed from the book and can * never later fill. A no-op (no event) if the ref is not currently resting (already filled, * already cancelled, or unknown). Returns whether a resting order was removed. */ cancel(ref, ts) { const st = this.#resting.get(ref); if (st === undefined) return false; this.#resting.delete(ref); this.#cancelled.add(ref); this.#emit("cancel", ts, st.order, { reason: "cancelled" }); return true; } /** * Reassess every resting order on one leg against a new quote. `fair` is the engine's * ExecutionFair for the leg (omit when unbuildable — the model then degrades to the floor). * Floor (strict-cross) fills leave the book and emit a `fill` event; hazard results accrue * into the survival product and keep resting. Returns the events produced this call. */ onBook(instrument, leg, ts, fair) { if (this.#settled) throw new Error("SimFillEngine: onBook after settle"); const before = this.#events.length; // 9gu.9 stale-print gate (see #lastPrint): the trade-through arm may test `leg.last` ONLY when the // print CHANGED on this event — the tape carries `last` forward session-cumulatively, so an // unchanged value is a stale carry, not a fresh execution. The engine hands the model a leg with // `last` STRIPPED unless fresh (a pure per-look model cannot see across events; this memory is the // engine's). Folded on EVERY look — including looks matching no order — so freshness state is // global and an order placed mid-session is inert against the pre-placement print it first sees. const legKey = `${instrument}|${leg.strike}|${leg.right}`; const priorPrint = this.#lastPrint.get(legKey); const freshPrint = leg.last !== undefined && priorPrint !== undefined && leg.last !== priorPrint; if (leg.last !== undefined) this.#lastPrint.set(legKey, leg.last); let assessLeg = leg; if (!freshPrint && leg.last !== undefined) { const { last: _stale, ...rest } = leg; assessLeg = rest; } // Snapshot the matching resting orders first: a fill mutates the map mid-iteration, and // insertion order must be stable for determinism. const matches = []; for (const st of this.#resting.values()) { const o = st.order; if (o.instrument === instrument && o.strike === leg.strike && o.right === leg.right) { matches.push(st); } } for (const st of matches) { // 9gu.4 — exit-clock-at-entry-fill / causal sequencing (fail-closed). // An order is eligible ONLY against book events at or after its own placement (`lastTs` is // seeded to the placement ts). A book event that PREDATES the order — a rewound/stale tape, or, // structurally, a TP/exit sell weighed against a print BEFORE its entry fill — can never fill it // (selling before you own the contract once reversed a study verdict). In a monotone session // this never fires; the guard exists so an out-of-order look cannot manufacture a pre-entry exit. if (ts < st.lastTs) continue; const dt = Math.max(0, ts - st.lastTs); const view = { side: st.order.side, qty: st.order.qty, px: st.order.px, strike: st.order.strike, right: st.order.right, ...(st.order.moneyness !== undefined ? { moneyness: st.order.moneyness } : {}), ...(st.order.covered !== undefined ? { covered: st.order.covered } : {}), }; const res = this.#model.assess(view, assessLeg, fair === undefined ? { dtSinceLast: dt } : { fair, dtSinceLast: dt }); st.lastTs = ts; if (res.fill !== undefined) { // Floor fill: definite, at the resting price (strict-cross-FIRST, ADR-0016 §4). It short- // circuits the hazard, so the sampled channel can never MISS a fill the floor credited — // containment floor ⊆ sampled: a floor lot is marked `sampledFilled` whenever the sampler is // armed (every draw ∈ [0,1) satisfies draw < 1). Leaves the book into inventory. this.#resting.delete(st.order.ref); const floorSampled = this.#sampler !== undefined || st.sampledFilled; this.#filled.push({ order: st.order, fillPx: res.fill.px, fillTs: ts, sampledFilled: floorSampled, viaSampled: false, support: "calibrated", survivalAtFill: st.survival, }); this.#emit("fill", ts, st.order, { px: res.fill.px, reason: res.kind }); continue; } // Hazard accrual (no floor fill this look). Decide how many episodes to MINT for this order on // this look, then fold each one (ADR-0016 §episode-identity): // - Un-keyed models (the per-second hazard, no `episodeKey`): exactly ONE fold per look, as // before — the hazard already integrates `dtSinceLast`, so it takes no time-based re-mint. // - Keyed per-episode models (`MakerFairCalV1`): one fold on a regime boundary (B1 first look / // B3 dark↔lit flip), PLUS one fold per `DT_REF` window of continuous SAME-regime rest (B4, // kestrel-9gu.13). A same-regime re-look inside a window mints nothing (N1 anti-double-count: // `windows === 0`), so re-observation still never inflates the product. if (res.pFill > 0) { const keyed = res.episodeKey !== undefined; // `catchUp` = the same-regime B4 path (keyed AND the key is unchanged from the last fold). // Captured BEFORE the mint loop mutates `lastEpisodeKey` so the saturation guard below never // skips a STRUCTURAL boundary mint (B1/B3, which always folds its one episode). const catchUp = keyed && res.episodeKey === st.lastEpisodeKey; let mints; if (!keyed) { mints = 1; // un-keyed per-second hazard: one per-look fold (byte-identical to before) } else if (!catchUp) { // B1/B3 regime boundary: one structural mint, and (re)anchor the B4 cadence clock HERE so the // window count is measured from the instant this regime began, not from an earlier regime. mints = 1; st.cadenceAnchorTs = ts; } else { // B4: the same regime has persisted — mint one episode per COMPLETED `DT_REF` window since the // anchor. Keyed on the injected clock, so the count is density-independent; advance the anchor // by whole windows so it is chunk-invariant (a dense and a sparse tape over the same rest span // mint the same episodes — ADR-0016 §Context: book cadence is a recording artifact). const windows = Math.floor((ts - st.cadenceAnchorTs) / EPISODE_REMINT_DT_REF_MS); mints = windows; st.cadenceAnchorTs += windows * EPISODE_REMINT_DT_REF_MS; } for (let m = 0; m < mints; m++) { // Saturation floor (B4 catch-up only): once expected fill is effectively 1, further windows of // the same regime move nothing bankable and a still-open sampled draw would fill regardless — // so stop folding (and stop emitting per-episode telemetry) rather than mint unbounded // saturated episodes on a long-gap catch-up. The engine survival stays byte-identical to the // bus-re-derived one (both stop at this same deterministic threshold, ADR-0011). if (catchUp && st.survival < EPISODE_SURVIVAL_SATURATION_EPS) break; // Each mint consumes the next episode index and advances it — so successive mints under one // ref get unique substreams (0 at placement's first episode B1, then ++ on each subsequent // mint: a dark↔lit flip B3 or a DT_REF re-mint B4 for a keyed model; every fold for an // un-keyed one). A same-regime re-look inside a window (N1) mints nothing, so it neither // advances the index nor consumes a draw (ADR-0016 §1/§3). const mintIndex = st.episodeIndex; st.episodeIndex = mintIndex + 1; st.survival *= 1 - res.pFill; // 9gu.3 support: an assessment we actually fold into the survival product taints the order's // expected-$ as non-bankable unless it is calibrated. An unstated flag is treated // conservatively (not calibrated) — mirrors isCalibratedSupport's fail-closed default. if (!isCalibratedSupport(res.support)) st.supportExtrapolated = true; if (res.episodeKey !== undefined) st.lastEpisodeKey = res.episodeKey; // One draw per episode, consumed at mint off the (runSeed, model.version, ref, mintIndex) // substream (ADR-0016 §3). Extrapolated/dark episodes STILL draw (9gu.8 needs realized // branches for causal management) but their gains are unbankable (the support flag rides // the lot + telemetry, §5). let sampledThisMint = false; if (this.#sampler !== undefined && !st.sampledFilled) { const draw = mulberry32(episodeSeed(this.#sampler.runSeed, this.#model.version, st.order.ref, mintIndex))(); if (draw < res.pFill) { sampledThisMint = true; st.sampledFilled = true; } } // Per-episode hazard telemetry — episode-keyed models only (the per-episode judge). The // survival product Π(1 − pFill) re-derives from these bus records (ADR-0016 / ADR-0011). An // unstated support records as `extrapolated` (fail-closed, non-bankable — isCalibratedSupport // treats a missing flag conservatively). if (res.episodeKey !== undefined) { const epSupport = isCalibratedSupport(res.support) ? "calibrated" : "extrapolated"; this.#emitHazard(ts, st.order.ref, mintIndex, res.episodeKey, res.pFill, epSupport, sampledThisMint); } // A realized sampled draw is a CAUSAL fill: emit a real `fill` ORDER event at the resting // price and leave the book into a sampled lot (NOT a floor fill — floorFilled/floorPnl stay // 0). Fed back into Plan management downstream (kestrel-9gu.8.3). Removing the order ends the // mint loop for it this look. if (sampledThisMint) { this.#resting.delete(st.order.ref); this.#filled.push({ order: st.order, fillPx: st.order.px, fillTs: ts, sampledFilled: true, viaSampled: true, support: isCalibratedSupport(res.support) ? "calibrated" : "extrapolated", survivalAtFill: st.survival, }); this.#emit("fill", ts, st.order, { px: st.order.px, reason: "sampled" }); break; } } } } return this.#events.slice(before); } /** * Cash-settle at the close (RUNTIME §6). Filled inventory settles at **intrinsic** from the * settle spot; every still-resting order **expires worthless-unfilled** (no position, $0 * floor) and emits a `cancel` event annotated `expired-unfilled`. Idempotent: a second * settle finds an empty book and reports only the (already-final) filled inventory. * * The report carries both the floor outcome and the E[$] under `pFill`, plus the sampled * total when a seeded sampler was supplied. Determinism: outcomes are emitted filled-first * (fill order), then resting (placement order). * * **Carry-mark (the multi-session horizon design, kestrel-5zl.16.3).** With `{ carry: true }` — a NON-final session of a * multi-session Instance — held inventory is marked to the session-close spot and RECORDED to * {@link carriedInventory} with its basis re-baselined to that mark, so the position survives to the * next session. The realized P&L, the emitted ORDER/TELEMETRY bytes, and the returned report are * **byte-identical** to a plain intrinsic settle (mark == `intrinsic(settleSpot, …)`, the same * per-session mark-to-market); the ONLY difference is the additional (bus-silent) carried-lot * hand-off. The default / final settle (`carry` absent or `false`) is today's behaviour exactly — the * position dies at intrinsic — so a length-1 sequence is byte-for-byte unchanged. * * **`opts.mark` is the settle spot's source provenance (kestrel-xwf)**, threaded by the session * driver. FAIL-CLOSED: omitted/unstated provenance grades STALE (never a silently fresh-looking * mark), as does an as-of older than the declared threshold — the verdict rides the report's * `settleMark` AND the bus (a `settle_mark` TELEMETRY record) so the Blotter re-derives the * taint from bytes. A STALE mark applies `opts.mark.parity` — the session's closing put-call-parity * candidate — when present (`source: "parity"`, an ANNOTATED recovery: the book cash-settles * against the recovered spot, the staleness verdict unchanged) and is otherwise retained * `markUncertain` (`source: "stale"` — honestly refused, never fabricated). */ settle(settleSpot, ts, opts) { const carry = opts?.carry ?? false; const mark = opts?.mark; // Settle-mark provenance + fail-closed staleness verdict (kestrel-xwf), resolved FIRST because the // book cash-settles against the RESOLVED mark: the mark's as-of vs the settle instant, under the // declared threshold. Unstated provenance is STALE, never fresh-by-default. A STALE mark applies // the session's closing put-call-parity candidate when one was derivable (`source: "parity"` — an // ANNOTATED recovery, the staleness verdict unchanged) and is otherwise retained mark-uncertain // (`source: "stale"` — honestly refused, never fabricated). const asOfTs = mark?.asOfTs ?? null; const ageMs = asOfTs === null ? null : Math.max(0, ts - asOfTs); const stale = ageMs === null || ageMs > this.#staleMarkAfterMs; const parity = stale ? (mark?.parity ?? null) : null; const resolvedSpot = parity !== null ? parity.spot : settleSpot; const source = !stale ? "market" : parity !== null ? "parity" : "stale"; const staleness = ageMs === null ? "provenance unstated (no spot observation on the tape)" : `value last established ${ageMs}ms before settle (> ${this.#staleMarkAfterMs}ms threshold)`; const note = !stale ? undefined : parity !== null ? `settle mark ${settleSpot} is stale: ${staleness}; recovered via closing put-call parity at strike ${parity.strike} ⇒ ${parity.spot}` : `settle mark ${settleSpot} is stale: ${staleness}; no closing put-call parity derivable — mark-uncertain`; const settleMark = { px: resolvedSpot, asOfTs, lastObservedTs: mark?.lastObservedTs ?? null, ageMs, staleAfterMs: this.#staleMarkAfterMs, stale, source, markUncertain: stale && parity === null, ...(note !== undefined ? { note } : {}), }; const outcomes = []; let floorTotal = 0; let expectedTotal = 0; let sampledTotal = 0; // Filled inventory — settle at intrinsic. A strict-cross FLOOR lot is a structural price-priority // fact: `floorFilled`, expected-fill-prob pinned to `1`, `calibrated`. A `viaSampled` hazard-draw // lot is NOT a floor fill (floorFilled/floorPnl stay 0) — it contributes only to the sampled // channel; its expected channel is the accrued analytic `1 − survivalAtFill` on its own support // (extrapolated ⇒ unbankable, ADR-0016 §5). for (const lot of this.#filled) { const floorFilled = !lot.viaSampled; const expectedProb = lot.viaSampled ? 1 - lot.survivalAtFill : 1; const oc = this.#outcome(lot.order, resolvedSpot, floorFilled, floorFilled ? lot.fillPx : null, expectedProb, lot.support, lot.sampledFilled); outcomes.push(oc); floorTotal += oc.floorPnl; expectedTotal += oc.expectedPnl; if (oc.sampled !== undefined) sampledTotal += oc.sampled.pnl; // Carry-mark: the lot survives to the next session at its mark-to-close basis (the re-baseline // that lands the overnight gap in the NEXT session's P&L, not this one). Bus-silent — no event. if (carry) this.#carried.push({ order: { ...lot.order, px: oc.intrinsicAtSettle }, basis: oc.intrinsicAtSettle }); } // Still-resting — expire worthless-unfilled ($0 floor), but carry accrued E[$] + its support flag. for (const st of this.#resting.values()) { this.#emit("cancel", ts, st.order, { reason: "expired-unfilled" }); const support = st.supportExtrapolated ? "extrapolated" : "calibrated"; const oc = this.#outcome(st.order, resolvedSpot, false, null, 1 - st.survival, support, st.sampledFilled); outcomes.push(oc); floorTotal += oc.floorPnl; expectedTotal += oc.expectedPnl; if (oc.sampled !== undefined) sampledTotal += oc.sampled.pnl; } this.#resting.clear(); this.#settled = true; // Record the probabilistic accounting ON THE BUS (a57.1 slice 1, ADR-0011): one settle-outcome // TELEMETRY record per order, carrying the accrued expected-fill-prob, the 9gu.3 support flag, and // the unrounded floor/expected P&L — so the Blotter projector re-derives totals{floor,expected} + // orders[].support from bus bytes alone, with no engine-state scrape. Emitted AFTER the expired- // unfilled cancels above so every ORDER event keeps its exact seq/bytes (only these additive // TELEMETRY lines are new), in outcomes order (filled-first, then resting) for determinism. for (const oc of outcomes) this.#emitSettleTelemetry(oc, ts); // Commit the provenance ON THE BUS (ADR-0010: the Bus is the truth) — one settle_mark TELEMETRY // record per session, after the per-order settle records, so the projector folds the taint from // bus bytes alone. Emitted ONLY when the session settled at least one order: with no outcomes // there is no P&L to qualify, and a zero-documents run must stay a byte-identical pass-through // (RUNTIME §7 certification). Absent keys are OMITTED (byte-minimal), mirroring the payload. if (outcomes.length > 0) { this.#events.push({ ts, stream: "TELEMETRY", type: "settle_mark", px: settleMark.px, ...(settleMark.asOfTs !== null ? { as_of_ts: settleMark.asOfTs } : {}), ...(settleMark.lastObservedTs !== null ? { last_observed_ts: settleMark.lastObservedTs } : {}), ...(settleMark.ageMs !== null ? { age_ms: settleMark.ageMs } : {}), stale_after_ms: settleMark.staleAfterMs, stale: settleMark.stale, source: settleMark.source, mark_uncertain: settleMark.markUncertain, ...(settleMark.note !== undefined ? { note: settleMark.note } : {}), }); } const cal = this.#model.calibration; return { settleSpot: resolvedSpot, settleMark, fillModel: `${this.#model.name}/${this.#model.version}`, ...(cal !== undefined ? { hazardVersion: cal.version, calibrated: cal.calibrated } : {}), multiplier: this.#multiplier, outcomes, floorTotal, expectedTotal, ...(this.#sampler !== undefined ? { sampledTotal } : {}), }; } // ── internals ────────────────────────────────────────────────────────────── /** Build one order's settle outcome. `scaled` is the settle P&L of a *filled* position: * BUY earns `intrinsic − px`, SELL earns `px − intrinsic`, times qty × multiplier. */ #outcome(order, settleSpot, floorFilled, floorFillPx, expectedFillProb, support, sampledFilled) { const intr = intrinsic(settleSpot, order.strike, order.right); // fair's canonical (spot,strike,right) const perContract = order.side === "buy" ? intr - order.px : order.px - intr; const scaled = perContract * order.qty * this.#multiplier; return { ref: order.ref, ...(order.plan !== undefined ? { plan: order.plan } : {}), ...(order.plan_instance !== undefined ? { plan_instance: order.plan_instance } : {}), instrument: order.instrument, side: order.side, qty: order.qty, px: order.px, strike: order.strike, right: order.right, floorFilled, floorFillPx, expectedFillProb, support, intrinsicAtSettle: intr, floorPnl: floorFilled ? scaled : 0, expectedPnl: expectedFillProb * scaled, ...(this.#sampler !== undefined ? { sampled: { filled: sampledFilled, pnl: sampledFilled ? scaled : 0 } } : {}), }; } /** Emit one per-episode hazard TELEMETRY record (kestrel-9gu.8.2, ADR-0016) at an episode mint — * the folded per-episode `pFill` + support + (on a seeded run) whether its draw realized, so the * survival product `Π(1 − pFill)` and the sampled path re-derive from bus bytes (ADR-0011). * Observational: the engine never reads it back (determinism stable on replay). `sampled` is * omitted entirely when the sampled channel is off (no draw was taken). */ #emitHazard(ts, orderId, episodeIndex, episodeKey, pFill, support, sampled) { this.#events.push({ ts, stream: "TELEMETRY", type: "hazard", order_id: orderId, episode_index: episodeIndex, episode_key: episodeKey, p_fill: pFill, support, ...(this.#sampler !== undefined ? { sampled } : {}), }); } /** Emit one settle-outcome TELEMETRY record (a57.1 slice 1) onto the cumulative log — the accrued * probabilistic accounting the projector re-derives totals + support from. Observational: the * engine never reads it back, so it moves no order/fill/plan decision (determinism stable). The * unrounded `floor_pnl`/`expected_pnl` sum-then-round back to the report's realized/expected totals; * on a SEEDED run the sampled channel rides too (`sampled_filled`/`sampled_pnl`, kestrel-9gu.12) so * all three channels re-derive from bus bytes — an unseeded record is byte-identical to before. */ #emitSettleTelemetry(oc, ts) { this.#events.push({ ts, stream: "TELEMETRY", type: "settle", order_id: oc.ref, expected_fill_prob: oc.expectedFillProb, support: oc.support, floor_filled: oc.floorFilled, floor_pnl: oc.floorPnl, expected_pnl: oc.expectedPnl, ...(oc.sampled !== undefined ? { sampled_filled: oc.sampled.filled, sampled_pnl: oc.sampled.pnl } : {}), }); } /** Append a typed ORDER event to the cumulative log. */ #emit(action, ts, order, extra) { const payload = { order_id: order.ref, ...(order.plan !== undefined ? { plan: order.plan } : {}), ...(order.plan_instance !== undefined ? { plan_instance: order.plan_instance } : {}), instrument: order.instrument, side: order.side, qty: order.qty, strike: order.strike, right: order.right, ...extra, }; this.#events.push({ ts, stream: "ORDER", type: action, ...payload }); } }