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.
361 lines (360 loc) • 19.8 kB
JavaScript
/**
* # session/grid — the Simulate config-comparison harness: buildGrid + CellAggregate + leaderboard (kestrel-5zl.8)
*
* THE POINT of Simulate: the SAME recorded tape graded under N configs → a GRID of graded
* {@link import("../blotter").Blotter}s → a pure comparison LEADERBOARD. This module is the two PURE
* derivations that turn a set of FINALIZED Blotters into that grid + table — no wall clock, no RNG
* (RUNTIME §0) — and it is a MEASUREMENT / reporting layer: it only READS the finalized record, and never
* writes a byte back onto the graded bus or the Blotter (zero golden / e2e churn), and stamps NO new schema
* field. It builds strictly on the run-identity layer (5zl.7 {@link cellOf}) + the config layer (5zl.6):
*
* 1. {@link buildGrid} — `(runs: readonly Blotter[]) → Grid`. Group the finalized Blotters by their 5zl.7
* {@link cellOf} CellKey (tape × fill_model × config-ConfigId). Runs sharing a CellKey are REPLICATES in
* one comparison cell; each replicate keeps a DISTINCT SimRunId, read straight off the finalized record
* as `blotter.session.bus.sha256` (which equals `SimRunId(gradedBus)` — the 5zl.7 pure-read
* reconciliation, so no raw bus is needed). Each cell is a {@link CellAggregate} over its replicate runs.
*
* 2. {@link CellAggregate} — the honest measurements across a cell's replicate runs, using the Session/day
* (one finalized Blotter = one run) as the INDEPENDENT EVIDENCE UNIT, and REPORTING UNCERTAINTY: every
* numeric axis is a {@link Dist} — `{ n, mean, min, max, stdev }` — a distribution/spread over the
* replicates, NEVER a single cherry-picked point (a single-run cell reports `n = 1`, spread `0` — still a
* distribution, never a bare scalar). The axes come straight off the Blotter: financial (EV / P&L), risk,
* process, fidelity, provider, cost — plus a {@link CertificationBreakdown}.
*
* 3. {@link leaderboard} — `(grid: Grid) → readonly LeaderboardRow[]`. A pure comparison table: ONE ROW PER
* CELL, the honest multi-axis measurements reported SIDE BY SIDE with per-axis uncertainty, and NO opaque
* omnibus score (the m9i principle: "report the financial, risk, process, attention, provider, and
* interface measurements WITHOUT an opaque omnibus score"). The axes are laid out; they are never blended
* into one number, and never sorted into a rank — the caller reads the axes it cares about.
*
* ## Determinism (RUNTIME §0 — a CI invariant)
* Both derivations are PURE and INPUT-ORDER-INSENSITIVE: the same runs in ANY order ⇒ a byte-identical grid
* and table. buildGrid sorts the cells by CellKey and each cell's replicates by SimRunId BEFORE any
* aggregation, so every reduction (mean, stdev, …) folds over a FIXED order — no order-dependent float drift.
*
* ## Honesty (the 5zl.8 mandate)
* The config axis of a cell is the FULL ConfigId (`session.config_id`, m9i.2 owner tweak) — a PROVISIONAL
* run keys by its own stamped ConfigId (or, absent any config identity, the NO_ENVELOPE sentinel), so it
* lands in its OWN cell, distinct from every differently-configured cell, and can NEVER be silently absorbed
* into a certified cell's numbers (a provisional and a certified run can only share a cell by sharing the
* ENTIRE config — and an identical config yields an identical completeness verdict). The cell's
* {@link CertificationBreakdown} carries the certified/provisional COUNTS and a WORST-CASE verdict
* (provisional if ANY replicate is provisional) — the projector's grade is surfaced, never laundered.
* Provider / cost read the run's `session.envelope`, absent (`null`) for a no-envelope / provisional cell —
* an honest gap, never a fabricated identity.
*/
import { InstanceRunId, SequenceCellKey, cellOf } from "./run-identity.js";
/** Summarize a fixed-order list of per-run measurements as a {@link Dist}. PURE — folds in the caller's
* (deterministic) order, so the same values always yield byte-identical floats. `values` is never empty (a
* cell always holds ≥ 1 run). Exported so other measurement layers (e.g. the percept-lab score log,
* kestrel-m9i.12) fold their replicate probes with the SAME semantics and their rows port into this grid. */
export function distOf(values) {
const n = values.length;
let sum = 0;
let min = values[0];
let max = values[0];
for (const v of values) {
sum += v;
if (v < min)
min = v;
if (v > max)
max = v;
}
const mean = sum / n;
let sq = 0;
for (const v of values)
sq += (v - mean) * (v - mean);
const stdev = Math.sqrt(sq / n);
return { n, mean, min, max, stdev };
}
/**
* The buy-and-hold P&L of an underlier over the horizon (kestrel-m9i.19): `qty × (close − open)`, where
* `open = prices[0]` and `close = prices[last]`. PURE + byte-stable (no wall clock, no RNG — RUNTIME §0): the
* interior prints don't matter, the hold is open → close and `qty` scales it linearly. FAIL-CLOSED — an
* underlier price the baseline needs but cannot resolve (an empty series, or a non-finite open / close / qty)
* is a typed refusal, NEVER a silent 0 (a silent 0-alpha would launder market direction as judgment).
*/
export function buyAndHoldBaseline(hold) {
const { symbol, prices, qty } = hold;
if (prices.length === 0) {
throw new Error(`grid: buyAndHoldBaseline cannot resolve an empty underlier price series for ${symbol} (fail-closed — never a silent 0-alpha)`);
}
const open = prices[0];
const close = prices[prices.length - 1];
if (!Number.isFinite(open) || !Number.isFinite(close)) {
throw new Error(`grid: buyAndHoldBaseline cannot resolve a non-finite underlier price for ${symbol} (open=${open}, close=${close}) (fail-closed — never a silent 0-alpha)`);
}
if (!Number.isFinite(qty)) {
throw new Error(`grid: buyAndHoldBaseline cannot resolve a non-finite qty for the ${symbol} hold (qty=${qty}) (fail-closed — never a silent 0-alpha)`);
}
return qty * (close - open);
}
// ─────────────────────────────────────────────────────────────────────────────
// buildGrid — group finalized Blotters by CellKey, aggregate each cell's replicates
// ─────────────────────────────────────────────────────────────────────────────
/** The SimRunId of a finalized run, read straight off the record (`session.bus.sha256`, which the projector
* already computed and equals `SimRunId(gradedBus)` — the 5zl.7 pure-read reconciliation). */
function runIdOf(blotter) {
return blotter.session.bus.sha256;
}
/** Aggregate one cell's already-sorted replicate runs into a {@link CellAggregate}. PURE. `runs` is
* non-empty and in a FIXED (SimRunId-sorted) order, so every reduction is deterministic. When a per-cell
* `hold` is supplied (kestrel-m9i.19), the financial axis additionally carries the buy-and-hold BASELINE +
* benchmark-relative ALPHA; without one the axis is byte-identical to today's (the field is omitted). */
function aggregateCell(cell, runs, hold) {
// Financial — the floor / expected totals and the support-partitioned expected, per run.
const claimExpected = (b, support) => {
const c = b.fill_claim.find((f) => f.support === support);
return c === undefined ? 0 : c.expected;
};
const financialBase = {
floor: distOf(runs.map((b) => b.totals.floor)),
expected: distOf(runs.map((b) => b.totals.expected)),
calibrated: distOf(runs.map((b) => claimExpected(b, "calibrated"))),
extrapolated: distOf(runs.map((b) => claimExpected(b, "extrapolated"))),
};
// The benchmark rides ALONGSIDE the absolute EV, computed ONLY when a per-cell hold is supplied. baseline =
// the buy-and-hold P&L of the cell underlier; alpha_i = expected_i − baseline (a Dist WITH uncertainty, so
// alpha.mean = cell EV − baseline, the 5zl.16.8 identity, over the SAME fixed replicate order as `expected`).
// FAIL-CLOSED: an unresolvable underlier price propagates buyAndHoldBaseline's typed refusal — never a
// silent 0-alpha. When absent, the `benchmark` field is OMITTED (not `undefined`), so an un-baselined cell
// is byte-identical to today's grid (no churn — existing goldens/e2e intact).
const financial = hold === undefined
? financialBase
: (() => {
const baseline = buyAndHoldBaseline(hold);
return {
...financialBase,
benchmark: {
symbol: hold.symbol,
baseline,
alpha: distOf(runs.map((b) => b.totals.expected - baseline)),
},
};
})();
// Risk — headline taint + the directional-cap disposition tallied over every order in every run.
let taintedRuns = 0;
const reasons = new Set();
const cap = { applied: 0, exempt: 0, na: 0 };
for (const b of runs) {
if (b.totals.headline.tainted)
taintedRuns += 1;
for (const r of b.totals.headline.reasons)
reasons.add(r);
for (const o of b.orders) {
if (o.directional_cap === "applied")
cap.applied += 1;
else if (o.directional_cap === "exempt")
cap.exempt += 1;
else if (o.directional_cap === "na")
cap.na += 1;
}
}
const risk = {
tainted_runs: taintedRuns,
headline_reasons: [...reasons].sort(),
directional_cap: cap,
};
// Process — orders / fills / reprices / plans, per run.
const process = {
orders: distOf(runs.map((b) => b.orders.length)),
fills: distOf(runs.map((b) => b.orders.filter((o) => o.filled).length)),
reprices: distOf(runs.map((b) => b.orders.reduce((a, o) => a + o.reprice_count, 0))),
plans: distOf(runs.map((b) => b.plans.length)),
};
// Fidelity — the level breakdown + the (cell-invariant) judge self-limitation.
const levels = {};
for (const b of runs) {
const lvl = b.session.fidelity.level;
levels[lvl] = (levels[lvl] ?? 0) + 1;
}
const fidelity = {
levels,
self_limitation: runs[0].session.fidelity.self_limitation,
};
// Provider / cost — the a57.14 envelope, invariant across the cell (the config axis of the CellKey). A
// no-envelope cell (a provisional / NO-LLM run) has neither — surfaced honestly as `null`, never fabricated.
const envelope = runs[0].session.envelope ?? null;
const provider = envelope;
const cost = envelope === null
? null
: {
input: distOf(runs.map((b) => b.session.envelope.token_accounting.input)),
output: distOf(runs.map((b) => b.session.envelope.token_accounting.output)),
thinking: distOf(runs.map((b) => b.session.envelope.token_accounting.thinking)),
};
// Certification — the projector's grade, surfaced with counts + a worst-case verdict.
let certified = 0;
let provisional = 0;
for (const b of runs) {
if (b.certification.verdict === "certified")
certified += 1;
else
provisional += 1;
}
const certification = {
certified,
provisional,
verdict: provisional > 0 ? "provisional" : "certified",
};
// Rankability — the DERIVED leaderboard-eligibility axis (kestrel-m9i.25), surfaced DISTINCTLY from
// certification: the rankable/not-rankable replicate counts + a WORST-CASE verdict (the cell is rankable
// ONLY if every replicate is rankable — one not-rankable replicate makes the cell not rankable).
let rankableRuns = 0;
let notRankableRuns = 0;
for (const b of runs) {
if (b.rankability.rankable)
rankableRuns += 1;
else
notRankableRuns += 1;
}
const rankable = {
rankable: rankableRuns,
not_rankable: notRankableRuns,
verdict: notRankableRuns === 0,
};
return {
cell,
runs: runs.length,
runIds: runs.map(runIdOf),
financial,
risk,
process,
fidelity,
provider,
cost,
certification,
rankable,
};
}
/**
* Group a set of FINALIZED Blotters into the config-comparison {@link Grid} (kestrel-5zl.8). Each run is keyed
* by its 5zl.7 {@link cellOf} CellKey (tape × fill_model × config-ConfigId); runs sharing a key are REPLICATES
* aggregated into one {@link CellAggregate} (each keeping a distinct SimRunId). PURE and
* INPUT-ORDER-INSENSITIVE: cells are sorted by CellKey and each cell's replicates by SimRunId before any
* aggregation, so the same runs in ANY order yield a byte-identical grid (RUNTIME §0). Reads finalized
* Blotters only — writes no bus/Blotter byte, stamps no schema field.
*
* ADDITIVE (kestrel-m9i.19): the optional 2nd arg supplies a per-cell buy-and-hold BASELINE. A cell whose
* CellKey appears in `opts.baselines` gets a `financial.benchmark` (the baseline + benchmark-relative alpha,
* ALONGSIDE absolute EV); every other cell — and the whole grid when no options are supplied — is BYTE-IDENTICAL
* to today's. The baseline touches neither CellKey nor SimRunId nor the leaderboard row shape. FAIL-CLOSED: an
* unresolvable supplied underlier price propagates {@link buyAndHoldBaseline}'s typed refusal (never a silent 0).
*/
export function buildGrid(runs, opts) {
const baselines = opts?.baselines;
const byCell = new Map();
for (const b of runs) {
const key = cellOf(b);
const bucket = byCell.get(key);
if (bucket === undefined)
byCell.set(key, [b]);
else
bucket.push(b);
}
const cells = [];
for (const key of [...byCell.keys()].sort()) {
const bucket = byCell.get(key);
// Sort replicates by SimRunId so every downstream reduction folds over a FIXED order (determinism).
const sorted = [...bucket].sort((a, b) => (runIdOf(a) < runIdOf(b) ? -1 : runIdOf(a) > runIdOf(b) ? 1 : 0));
cells.push(aggregateCell(key, sorted, baselines?.get(key)));
}
return { cells };
}
/**
* Project a {@link Grid} into the comparison LEADERBOARD (kestrel-5zl.8): ONE ROW PER CELL, the honest
* multi-axis measurements reported SIDE BY SIDE with per-axis uncertainty, and NO opaque omnibus score — the
* axes are laid out, never blended into one number and never sorted into a rank (the m9i principle). PURE and
* deterministic: the row order mirrors the grid's (CellKey-sorted), so the same grid always yields a
* byte-identical table.
*/
export function leaderboard(grid) {
return grid.cells.map((c) => ({
cell: c.cell,
runs: c.runs,
runIds: c.runIds,
financial: c.financial,
risk: c.risk,
process: c.process,
fidelity: c.fidelity,
provider: c.provider,
cost: c.cost,
certification: c.certification,
rankable: c.rankable,
}));
}
/**
* Fold the ORDERED per-session Blotters of a multi-session Instance into ONE {@link InstanceBlotter}
* (kestrel-5zl.16.8) — a PURE read of the finalized sequence (no wall clock, no RNG — RUNTIME §0; no bus /
* Blotter byte written, no schema field stamped). Where {@link buildGrid} folds order-INSENSITIVE REPLICATES
* into a distribution, this folds an order-SENSITIVE SEQUENCE into one CHAINED record — the ordering IS the
* semantics. Three things:
*
* 1. CHAINS per-session cash across the sequence: session N+1 OPENS from session N's carried cash
* (`openingCash[k] = closingCash[k-1] = Σ_{j<k} realized_j`, `realized_k = totals.floor`), exactly
* re-deriving the chain `carry.ts` threads (`carry.cash = prior.cash + settle.floorTotal`). Deterministic
* + byte-stable: the ledger folds in SEQUENCE order (no clock, no RNG). `grade.financial.cash` is the
* final running cash.
* 2. a sequence-level GRADE — the financial axis chained; the non-financial axes reuse the EXISTING grid
* axes ({@link aggregateCell}) over the sessions-as-replicate-runs, so a LENGTH-1 Instance's non-financial
* axes are BYTE-IDENTICAL to `buildGrid([bl]).cells[0]` (NO existing-grid churn). NO opaque omnibus score.
* 3. a benchmark-relative ALPHA hook — `alpha = instanceEV − baseline`, `instanceEV = Σ totals.expected`.
* alpha lives INSIDE the financial axis, one honest measure — never a collapsed single score.
*
* FAIL-CLOSED: an empty (unstitchable) sequence, or a missing / non-finite baseline, is a typed refusal —
* never a silent default (no silent 0-alpha).
*/
export function buildInstanceBlotter(orderedBlotters, opts) {
// Fail-closed — an empty sequence has no chain to open.
if (orderedBlotters.length === 0) {
throw new Error("grid: buildInstanceBlotter needs a non-empty session sequence (fail-closed)");
}
// Fail-closed — a missing / non-finite baseline is a typed refusal, never a silent 0-alpha default.
const baseline = opts?.baseline;
if (typeof baseline !== "number" || !Number.isFinite(baseline)) {
throw new Error("grid: buildInstanceBlotter needs a finite `baseline` benchmark (fail-closed — never a silent 0-alpha default)");
}
// (1) The chained-cash ledger — order-SENSITIVE, folded in SEQUENCE order (each session opens from the
// prior's carried cash). This re-derives, purely, the exact chain carry.ts threads (cash = prior.cash +
// settle.floorTotal, and settle.floorTotal === totals.floor).
const sessions = [];
let running = 0;
for (let k = 0; k < orderedBlotters.length; k++) {
const b = orderedBlotters[k];
const openingCash = running;
const realized = b.totals.floor;
const closingCash = openingCash + realized;
sessions.push({ ordinal: k, runId: runIdOf(b), openingCash, realized, closingCash });
running = closingCash;
}
// (3) The financial axis — chained cash + the benchmark-relative alpha hook.
const instanceEV = orderedBlotters.reduce((acc, b) => acc + b.totals.expected, 0);
const financial = {
cash: running, // the final running cash of the chain (Σ per-session realized floor)
instanceEV,
alpha: instanceEV - baseline,
baseline,
};
// (2) The non-financial axes — reuse the EXISTING grid aggregation over the sessions-as-replicate-runs, in
// the SAME SimRunId-sorted FIXED order buildGrid folds a cell in (so a length-1 Instance's non-financial
// axes are byte-identical to buildGrid([bl]).cells[0], and every reduction is deterministic — RUNTIME §0).
const sorted = [...orderedBlotters].sort((a, b) => (runIdOf(a) < runIdOf(b) ? -1 : runIdOf(a) > runIdOf(b) ? 1 : 0));
const sequenceCellKey = SequenceCellKey(orderedBlotters.map((b) => cellOf(b)));
const agg = aggregateCell(sequenceCellKey, sorted);
const grade = {
financial,
risk: agg.risk,
process: agg.process,
fidelity: agg.fidelity,
provider: agg.provider,
cost: agg.cost,
certification: agg.certification,
};
return {
// The 5zl.16.5 identity chain, read straight off the ordered finalized sequence.
instanceRunId: InstanceRunId(orderedBlotters.map((b) => runIdOf(b))),
sequenceCellKey,
sessions,
grade,
};
}