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.
47 lines (42 loc) • 2.51 kB
text/typescript
/**
* # session/specs — the driver's PURE session-header helpers (META → instruments), factored light
*
* `requireMeta` / `resolveSpecs` / `specFromMeta` are the sim driver's own header rules: find the
* one META a well-formed bus opens with, and resolve the session's {@link InstrumentSpec}s from it.
* They were born inside `src/session/sim.ts` and are re-exported from there unchanged; this module
* exists so a consumer that must stay OFF the heavy session graph — the CLI's `validate --arm`
* context derivation (kestrel-elu9), which is LIGHT/node-runnable — can reuse the EXACT derivation
* a real run performs instead of re-implementing a copy that could drift (AGENTS.md: validation
* must match execution). Type-only imports; zero runtime dependencies; no `bun:*`, no IO, no clock.
*/
import type { BusEvent, MetaEvent, SessionInstrument } from "../bus/types.ts";
import type { InstrumentSpec } from "../engine/plans.ts";
/** A default {@link InstrumentSpec} from a bus META instrument. Contract facts are not on the v1
* META, so they are DERIVED from the asset class: an `option-underlier` (SPX, SPY, …) gets the
* standard listed-option shape — tick `0.01`, strike step `1`, **multiplier `100`** (premiums and
* P&L are per-contract-×-100 dollars) — while a non-option underlier keeps multiplier `1`. An
* explicit `instruments` override on `RunSimOptions` still wins. */
export function specFromMeta(si: SessionInstrument): InstrumentSpec {
const isOption = si.assetClass === "option-underlier";
return {
symbol: si.symbol,
tickSize: 0.01,
strikeStep: 1,
multiplier: isOption ? 100 : 1,
...(si.role !== undefined ? { role: si.role } : {}),
};
}
/** Resolve the session's instrument specs from options + META, failing closed on none. */
export function resolveSpecs(meta: MetaEvent, override?: readonly InstrumentSpec[]): InstrumentSpec[] {
const specs = override !== undefined ? [...override] : meta.instruments.map(specFromMeta);
if (specs.length === 0) throw new Error("session: no instruments (fail-closed, RUNTIME §8)");
return specs;
}
/** Find the single META header, failing closed on its absence (RUNTIME §8). */
export function requireMeta(events: readonly BusEvent[]): MetaEvent {
const meta = events.find((e): e is MetaEvent => e.stream === "META");
if (meta === undefined) {
throw new Error("session: no META header in bus (a well-formed bus opens with exactly one META, RUNTIME §8)");
}
return meta;
}