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.
132 lines (131 loc) • 7.4 kB
JavaScript
/**
* # grade/headline — the three-channel P&L contract + the sampled qualification gate (kestrel-9gu.12)
*
* One session's P&L is reported as **three channels side by side** (RUNTIME §6; fill-realism forensics
* 2026-07-13), never one number pretending to be all of them:
*
* • **floor** — realized $ under the deterministic strict-cross floor. The honest LOWER bound: only
* definite, price-priority fills count. Calibration-free, bankable, byte-stable.
* • **expected** — E[$] under the survival product `1 − Π(1 − pFill)` (ADR-0016). The analytic
* expectation, banked per-cell by support (`bankableEv`, 9gu.3/ADR-0014) — an honest BOUND beside
* the realization, never itself the headline (an expectation is not an outcome).
* • **sampled** — the seeded causal realization (one Bernoulli per resting episode, ADR-0016 §3),
* present ONLY on a `fillSeed` run. One replicate of the ensemble.
*
* **The headline is SELECTED, not assumed** (owner rule, kestrel-9gu.12 / 9gu.8.6 / 9gu.8 AC#7):
* strict-cross floor stays the headline UNTIL the sampled channel passes its qualification gate —
* calibration + causality + fidelity, recorded as a {@link SampledQualificationClaim} — and even a
* qualified run's sampled channel is REFUSED the headline when any of its realizations rests on
* extrapolated support (the `bankableEv` doctrine: never an uncalibrated headline). The gate FAILS
* CLOSED: no claim on record ⇒ not qualified ⇒ floor headline, with the reasons on the record.
*
* The EXPECTED channel is never the headline: it is an expectation (a bound), and headline candidates
* are realizations only — `floor` (deterministic) or `sampled` (seeded), per the owner-approved
* fill-stack contract.
*
* Both graded surfaces — the {@link ../session/sim.ts EpisodeReport} and the {@link ../blotter Blotter}
* — select through this ONE module (one policy, never forked), each assembling its
* {@link SampledGateEvidence} from its own substrate (the settle report in-process; bus bytes in the
* projector). Pure and deterministic throughout: fixed reason order, no wall clock, no RNG (RUNTIME §0).
*/
/**
* The qualification-gate predicate (kestrel-9gu.12 seam for kestrel-9gu.8.6). The sampled channel
* qualifies as headline iff ALL of:
*
* 1. the channel actually ran (`sampledArmed` — a seeded run);
* 2. a {@link SampledQualificationClaim} is on the record with ALL THREE legs passed — calibration,
* causality, fidelity — and a non-empty `reference` naming where the approval is recorded;
* 3. no sampled realization rests on extrapolated support (`bankableEv` — never an uncalibrated
* headline, ADR-0016 §5).
*
* FAIL-CLOSED DEFAULT: as of kestrel-9gu.12 no qualification study has passed (the study is
* kestrel-9gu.8.6), so no run carries a claim and every headline resolves to the strict-cross floor —
* exactly the owner-recorded sequencing (9gu.8 AC#7). Pure; reasons are emitted in the fixed order
* above so the verdict is byte-stable.
*/
export function sampledQualified(e) {
const reasons = [];
if (!e.sampledArmed) {
reasons.push("sampled channel off (no fillSeed) — nothing to qualify (fail-closed, ADR-0016 §5)");
}
const q = e.qualification;
if (q === undefined) {
reasons.push("no sampled-channel qualification on record — calibration + causality + fidelity study not passed (kestrel-9gu.8.6; 9gu.8 AC#7, fail-closed)");
}
else {
// Fixed leg order: calibration, causality, fidelity — byte-stable reasons.
if (!q.calibration)
reasons.push("qualification leg 'calibration' not passed (kestrel-9gu.8.6)");
if (!q.causality)
reasons.push("qualification leg 'causality' not passed (kestrel-9gu.8.6)");
if (!q.fidelity)
reasons.push("qualification leg 'fidelity' not passed (kestrel-9gu.8.6)");
if (q.reference.length === 0) {
reasons.push("qualification claim carries no reference — approval provenance unstated (fail-closed)");
}
}
if (e.sampledExtrapolated) {
reasons.push("a sampled realization rests on extrapolated support — never an uncalibrated headline (bankableEv, ADR-0016 §5)");
}
return { qualified: reasons.length === 0, reasons };
}
/**
* Select the headline channel (kestrel-9gu.12): `sampled` iff the gate passed AND the channel is
* present; otherwise `floor`. `taintReasons` are the session-level accounting taints (a mark-uncertain
* settle taints all three channels' headline — the xwf composition) — they NEVER change the selection,
* only stamp it, so a tainted number always renders with its taint attached rather than being silently
* replaced by a different number. Pure and byte-stable.
*/
export function selectHeadline(channels, gate, taintReasons) {
const useSampled = gate.qualified && channels.sampled !== undefined;
return {
channel: useSampled ? "sampled" : "floor",
usd: useSampled ? channels.sampled : channels.floor,
gate,
tainted: taintReasons.length > 0,
reasons: [...taintReasons],
};
}
/**
* The never-naked taint (kestrel-22j.4). Scan the filled legs in CAUSAL order and watch the running
* net position per key: if it ever crosses below zero the run legged into a **net-naked short** — an
* uncovered short (unbounded on a call / short equity), the exact state the bounded-risk / never-naked
* non-negotiable forbids. Such a run can never headline as a clean win, however positive its P&L, so
* the crossing is returned as a taint reason (one per key, in first-cross order — byte-stable) for
* {@link selectHeadline} to stamp on the headline. An empty result ⇔ the book stayed long-or-flat
* throughout. Pure; no wall clock, no RNG (RUNTIME §0).
*/
export function nakedShortTaint(legs) {
const net = new Map();
const flagged = new Set();
const reasons = [];
for (const leg of legs) {
const after = (net.get(leg.key) ?? 0) + (leg.side === "buy" ? leg.qty : -leg.qty);
net.set(leg.key, after);
if (after < 0 && !flagged.has(leg.key)) {
flagged.add(leg.key);
reasons.push(`net-naked short at ${leg.key}: filled position went to ${after} (an uncovered short) — never-naked forbids this from headlining as a clean win (kestrel-22j.4)`);
}
}
return reasons;
}
/**
* Parse an UNTRUSTED value (a config field / META bytes) into a {@link SampledQualificationClaim},
* failing closed to `undefined` on ANY malformation — a missing leg, a non-boolean, an empty or
* non-string reference. A malformed claim is treated exactly like no claim (the gate then refuses),
* never coerced onto a passing shape (RUNTIME §8).
*/
export function readSampledQualificationClaim(raw) {
if (typeof raw !== "object" || raw === null)
return undefined;
const r = raw;
if (typeof r.calibration !== "boolean")
return undefined;
if (typeof r.causality !== "boolean")
return undefined;
if (typeof r.fidelity !== "boolean")
return undefined;
if (typeof r.reference !== "string" || r.reference.length === 0)
return undefined;
return { calibration: r.calibration, causality: r.causality, fidelity: r.fidelity, reference: r.reference };
}