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.

174 lines 11.2 kB
/** * # fair — ExecutionFair: the honest `@fair` anchor (ARCHITECTURE §4, RUNTIME §4) * * ExecutionFair is the price at which a market maker will actually **fill** you — not the * midpoint of a possibly-fictional book. It is **underlying-anchored intrinsic + a vol read * backed out of the *liquid* quotes, floored at intrinsic, and receipt-gated**: the value * carries whether it can be trusted (model version, how many liquid strikes backed it, the * IV it was priced at), not just a number. The observed mid is a health signal (thin/dark * book fingerprint), never a value. * * This module is **pure** — no I/O, no clock, no RNG (RUNTIME §0). It is the one place that * knows how `fair` is computed; every surface that resolves `@fair` reads through here. * * ## The contract (RUNTIME §4) * `executionFair(...)` returns `{ value, receipt }` or **`null`**. It returns `null` in * exactly two cases — **no usable underlying**, or **zero liquid strikes** to back a vol out * of. `null` is *not* an error: it is the signal for the price resolver to fall back to an * **annotated** book value (`fair=fallback(mid)` for a BUY, `fair=fallback(max(mid, * intrinsic))` for a SELL) — a silent mid is forbidden. This module never invents that * fallback; it reports honestly that fair is unbuildable and lets the caller annotate. * * When it *does* return a value, that value is **always floored at intrinsic** — the price a * maker will never sell you below (RUNTIME §4 SELL floor, and the honest lower bound on any * option's worth). * * ## Freshness is NOT this module's job * A pure valuation cannot know whether the quotes it priced are stale — staleness is a fact * about the clock and the book's last update, and this module reads neither. So it does not * gate on freshness; it **carries** the observation time instead. The caller may inject an * `asof` timestamp (epoch ms) on {@link ExecutionFairInput}; it is stamped verbatim onto the * {@link FairReceipt} as {@link FairReceipt.asof}. **Applying** a staleness gate (comparing * `asof` against `now`, de-arming or annotating a too-old fair) is the **engine's** job, * downstream — the receipt merely makes the input observable so that gate can exist. Absent * with a reason, now documented. */ import type { OptionQuote, Right } from "../bus/types.ts"; import { type ForwardSource } from "./surface.ts"; export { black76, impliedVol, intrinsic, tauYearsToClose, erf, normCdf, MS_PER_YEAR, type Black76Params, type ImpliedVolParams, } from "./black76.ts"; export { buildSurface, impliedForward, interpIv, FORWARD_NO_ARB_BAND, type ForwardSource, PARITY_TOTAL_VOL_TOLERANCE, type ImpliedForward, type IvPoint, } from "./surface.ts"; /** `@fair` for a spot instrument (equity/crypto, ADR-0017): its own two-sided quote mid with a * receipt (`exec-fair-quote-v1`), or `null` when one-sided/dark — never Black-76. */ export { executionFairSpot, EXEC_FAIR_QUOTE_MODEL, type SpotFairInput, type SpotFairResult, type SpotFairReceipt, } from "./spot.ts"; /** The versioned model tag stamped on every receipt. EVs across model versions are not * comparable and are distinguished by this string (ARCHITECTURE §4). */ export declare const EXEC_FAIR_MODEL: "exec-fair-b76-v1"; /** The trust envelope carried alongside a fair value — it says how the number was earned. */ export interface FairReceipt { /** Versioned model identity. */ readonly model: typeof EXEC_FAIR_MODEL; /** How many liquid strikes backed the vol surface (0 ⇒ no fair; the whole result is null). */ readonly nLiquid: number; /** The implied vol the value was priced at (interpolated to the requested strike). */ readonly ivAtStrike: number; /** The FORWARD the value was priced at — derived by put-call parity, NOT assumed equal to * spot (ADR-0037 §2, kestrel-ukwz). Carried so an outsider can recompute the number: a fair * whose forward is invisible cannot be audited, and the forward is the single largest term in * the price. */ readonly forward: number; /** `"parity"` (derived from the option market) or `"spot"` (the known-degraded fallback). */ readonly forwardSource: ForwardSource; /** The strike parity was read at. Present iff `forwardSource === "parity"`. */ readonly forwardStrike?: number; /** `forward − underlyingSpot` — the carry/dividend/clock-desync wedge the option market itself * implies. Present iff `forwardSource === "parity"`. */ readonly forwardResidual?: number; /** The machine-readable reason the parity derivation failed, present iff * `forwardSource === "spot"`. This is what makes the degraded forward LOUD: the pricing seam * names it in the price annotation and the live-broker wall gates on it (ADR-0037 §6/§7) — a * spot forward is never silently substituted. */ readonly forwardDegraded?: string; /** The WORST call-vs-put IV disagreement across the surface points backing this value — the * fit-quality signal `nLiquid` never was (kestrel-ku99). (`nLiquid` counts strikes, and counts * them *higher* the more legs a corrupt forward splits apart, so it reads healthiest exactly * when the surface is least trustworthy — ADR-0037 §6.) Absent when no strike had both sides * liquid — parity was then unobservable, and claiming `0` would assert an agreement never * checked. */ readonly parityResidual?: number; /** The observation timestamp (epoch ms) the caller injected on {@link ExecutionFairInput.asof}, * carried through verbatim. Absent when the caller supplied none. This module never gates on * it — freshness gating is the engine's job downstream; the receipt only makes `asof` * observable (see the module header). */ readonly asof?: number; } /** A resolved fair value with its receipt. */ export interface FairResult { readonly value: number; readonly receipt: FairReceipt; } /** Why no fair was served. Two KINDS, because they are two different facts: * - `unbuildable` — no fair could be computed at all (no underlying / no liquid strikes). The * caller's annotated book fallback is the ordinary, expected path (RUNTIME §4). * - `tainted` — a fair WAS computed and then REFUSED as untrustworthy. Same fallback, but this * is an active corruption signal and the reason belongs in the audit trail. */ export type FairRefusalKind = "unbuildable" | "tainted"; /** The machine-readable refusal reasons — a closed set, so a downstream gate can switch on them * rather than parse prose. */ export type FairRefusalReason = /** `underlyingSpot` is not a finite positive number. */ "no-underlying" /** Zero liquid strikes produced a vol point. */ | "no-liquid-strikes" /** The surface could not be read at the requested strike (defensive; unreachable when * `nLiquid > 0`). */ | "no-surface-at-strike" /** The valued leg's own book is real+fresh and the value landed ABOVE its ask (ADR-0037 §3). */ | "fair-above-fresh-ask" /** The valued leg's own book is real+fresh and the value landed BELOW its bid (ADR-0037 §3). */ | "fair-below-fresh-bid"; /** A refusal to serve a fair value, always naming a machine-readable reason. */ export interface FairRefusal { readonly refused: FairRefusalKind; readonly reason: FairRefusalReason; } /** Either a fair value with its receipt, or a reasoned refusal. */ export type FairOutcome = FairResult | FairRefusal; /** Narrow a {@link FairOutcome} to the refusal arm. */ export declare function isFairRefusal(o: FairOutcome): o is FairRefusal; /** The inputs to a single ExecutionFair valuation. `liquidQuotes` is the chain slice the vol * surface is built from — two-sided legs are kept, one-sided/dark legs ignored (their mid is * a health signal, never a price). */ export interface ExecutionFairInput { /** The underlier SPOT. It is **not** the forward — the forward is derived from the option * market by put-call parity (ADR-0037 §2); spot is the intrinsic-floor anchor, the ATM-ness * yardstick, and the degraded fallback. Non-finite/≤0 ⇒ `null` result. */ readonly underlyingSpot: number; /** The strike being valued. */ readonly strike: number; /** Call or put. */ readonly right: Right; /** Time to expiry in years (injected; `<= 0` prices to intrinsic). */ readonly tauYears: number; /** The chain slice to back the vol surface out of. */ readonly liquidQuotes: readonly OptionQuote[]; /** Optional injected observation timestamp (epoch ms) of the quotes being valued. The pure * valuation does **not** gate on it — it is carried onto {@link FairReceipt.asof} so a * downstream freshness gate (the engine's job) can compare it against `now`. Time is always * injected, never read from a clock (RUNTIME §0). */ readonly asof?: number; /** The VALUED leg's own bid, for the fair-vs-book bound (kestrel-ku99, ADR-0037 §3). * `null`/absent ⇒ dark ⇒ no bound. Distinct from {@link ExecutionFairInput.liquidQuotes}, which * is the chain the surface is built from; this is the one leg being priced. */ readonly legBid?: number | null; /** The VALUED leg's own ask. `null`/absent ⇒ dark ⇒ no bound. */ readonly legAsk?: number | null; /** The caller's verdict on whether the valued leg's book is FRESH — injected, because this * module reads no clock (ADR-0037 §4: freshness is the engine's job). `true` ⇒ the book is * real-time enough to be authoritative, so a value outside it is refused as tainted. * Absent/`false` ⇒ the book carries no such authority and the bound does not apply AT ALL * (ADR-0037 §3: a stale/wide/dark/index-lagging book is a diagnostic the underlying-anchored * surface legitimately overrides — never a clamp). */ readonly bookFresh?: boolean; } /** * Resolve `@fair` for one option, reporting a machine-readable reason when it refuses. * * The forward is READ from the options by parity (kestrel-ukwz, ADR-0037 §2) and used for BOTH * the IV inversion and the final pricing; the intrinsic floor stays on spot. A parity strike * whose call/put IVs disagree contributes no vol point (kestrel-ku99). A value outside the valued * leg's own real+fresh book is REFUSED, never clamped (ADR-0037 §3/§5). * * {@link executionFair} is the reason-free wrapper over this; prefer THIS when you can put the * reason in an audit trail. */ export declare function executionFairOutcome(input: ExecutionFairInput): FairOutcome; /** * Resolve `@fair` for one option, or `null` when none can be served. * * The reason-free wrapper over {@link executionFairOutcome} — `null` collapses every refusal * (unbuildable AND tainted) into the one signal the caller already handles: fall back to an * annotated book value (RUNTIME §4). Callers that can carry the reason into an audit trail * should call {@link executionFairOutcome} instead; `src/engine/pricing.ts` does. */ export declare function executionFair(input: ExecutionFairInput): FairResult | null; //# sourceMappingURL=index.d.ts.map