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.
78 lines (77 loc) • 4.13 kB
JavaScript
/**
* # blotter/protocol-view — the pure engine → PROTOCOL Blotter projection (kestrel-b0li).
*
* The ONE source of truth for turning a rich engine {@link EngineBlotter} into the generic public
* PROTOCOL {@link Blotter} the {@link grade} Judge scores — un-dropping the risk-honest `totals` +
* `fill_claim` (kestrel-h314 D2) so both metric families are outsider-recomputable from a published
* Blotter. Extracted here from `sdk/local.ts` (which re-exports it) into a LIGHT module — it depends
* only on the blotter types + the dependency-free protocol types, never on `lang`/`session`/`catalog`
* — so the node-only `certify` verb can grade a re-projection (`published numbers == grade(reproject(tape))`,
* OSS-ADR-0051) without dragging the engine drive into the npx-light import graph.
*
* Honest + deterministic: no wall clock (the settle instant is the settle-mark's `as_of_ts` or the
* latest plan-lifecycle ts), no RNG, no invented arithmetic — only the accounting the engine record
* already carries, reduced to its public verdict.
*/
/** The settle instant as an ISO-8601 string — from the settle-mark's `as_of_ts` when the graded bus
* carries one, else the latest plan-lifecycle ts (deterministic; never a wall clock). */
function settledAtOf(b) {
const asOf = b.totals.settle_mark?.as_of_ts;
if (typeof asOf === "number")
return new Date(asOf).toISOString();
let maxTs = 0;
for (const p of b.plans)
for (const s of p.lifecycle)
if (s.ts > maxTs)
maxTs = s.ts;
return new Date(maxTs).toISOString();
}
/**
* Project the rich engine {@link EngineBlotter}'s totals into the verdict-only PROTOCOL {@link Totals}
* (kestrel-h314 D2). UN-DROPS the risk-honest accounting the engine record already carries — byte-stable,
* NOT invented arithmetic — so the risk-honest metrics are outsider-recomputable from the published Blotter.
* The engine's licensed raw settle mark is REDUCED to its verdict here (D3): the raw vendor spot +
* observation timestamps do NOT cross onto the public artifact.
*/
function toProtocolTotals(b) {
const t = b.totals;
const sm = t.settle_mark;
const settle_mark = sm === undefined ? undefined : { stale: sm.stale, source: sm.source, mark_uncertain: sm.mark_uncertain, note: sm.note };
return {
floor: t.floor,
expected: t.expected,
...(t.sampled !== undefined ? { sampled: t.sampled } : {}),
headline: {
channel: t.headline.channel,
usd: t.headline.usd,
gate: { qualified: t.headline.gate.qualified, reasons: t.headline.gate.reasons },
tainted: t.headline.tainted,
reasons: t.headline.reasons,
},
...(settle_mark !== undefined ? { settle_mark } : {}),
};
}
/** Project the engine `fill_claim` slices onto the wire {@link FillClaim} (kestrel-h314 D2): drops the
* per-slice `orders` count (not needed to recompute bankable EV), keeps the SIGNED `expected`/`gain`/`loss`.
* Deterministic, in the fixed `[calibrated, extrapolated]` order the engine already emits. */
function toProtocolFillClaim(b) {
return b.fill_claim.map((c) => ({ support: c.support, expected: c.expected, gain: c.gain, loss: c.loss }));
}
/**
* Project the rich engine {@link EngineBlotter} into the PROTOCOL {@link Blotter} (the generic public
* view). Honest + deterministic. Carries the deterministic identity + the fully-determined settlement;
* the generic order/fill/position arrays are empty (the engine record lacks the generic legs a faithful
* public Instrument/Order needs; never fabricated). Additionally carries the un-dropped `totals` +
* `fill_claim`, so the risk-honest metric family is outsider-recomputable by the {@link grade} Judge.
*/
export function toProtocolBlotter(sessionId, b) {
return {
sessionId,
orders: [],
fills: [],
positions: [],
settlement: { realized: b.totals.floor, asset: "usd", settledAt: settledAtOf(b) },
totals: toProtocolTotals(b),
fill_claim: toProtocolFillClaim(b),
};
}