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.
153 lines (152 loc) • 10 kB
JavaScript
/**
* # fill/guarded-intent — the **GuardedIntent** owner (kestrel-vav2, arch-review C1b)
*
* ONE interface for the directional far-OTM guard, so the BOUNDED-RISK / never-naked property is
* testable THROUGH the seam that enforces it rather than across three functions in two modules.
* A {@link GuardedIntent} is a leg's guard verdict: which wing it sits in, whether it is a covering
* wing, and whether the classification had to fail closed. It concentrates three decisions that
* previously sat apart (the engine classified, the engine resolved spot, the fill model capped):
*
* 1. **Classification** — the coarse {@link Moneyness} bucket (strike vs the decision-time
* underlier), and the ADR-0017 spot-leg case (no strike/right ⇒ no wing; the far-OTM guard is an
* option concept with no spot analogue).
* 2. **Decision-time spot resolution** — the underlier is pulled through the caller's
* {@link GuardIntentInput.resolveSpot} thunk **at the moment of the decision**, and ONLY when an
* option leg actually needs classifying. The owner decides *when* spot is needed; the caller only
* says *how* to get it (the engine's `#spotFor` path). No clock, no RNG — pure given the thunk.
* 3. **The far-OTM cap decision** — {@link guardedPfill}, the ONE place a symmetric maker `p_fill`
* is bent by side × wing. {@link MakerFairV1} (and the calibrated sibling) consume a GuardedIntent
* through this function on both of their hazard paths.
*
* The fail-closed clause lives here too and cannot be bypassed by a caller: a **SELL** whose spot is
* unresolvable is classified `deep_otm` + standalone (the conservative cap) and flagged
* {@link GuardedIntent.unresolved} so the caller LOGS the fork — never a silent default, never a
* silent bypass (RUNTIME §8). A **BUY** carries no bounded-risk leak (the guard only ever UNLOCKS a
* far-OTM bid), so an unresolvable BUY takes the symmetric path unflagged.
*
* Everything here is pure (no wall clock, no RNG — RUNTIME §0).
*/
/**
* The coarse **moneyness bucket boundaries** — GENERIC market-convention cutoffs (NOT fitted
* calibration constants; they carry no strategy edge). A leg within {@link MONEYNESS_ATM_BAND} of
* spot is at-the-money; a leg at least {@link MONEYNESS_DEEP_OTM_FRAC} out of the money is the
* `deep_otm` far wing the directional guard keys on. These are the ONLY end-to-end definition of
* "far-OTM" (a genuine fork, kestrel-9gu.6): the conservative default is a generous wing so a
* standalone far-OTM short is caught by the cap rather than slipping through. Tuning them is a
* data-versioned change, but they are deliberately round and public — a moneyness bucket boundary
* is a market convention, not a private threshold.
*/
export const MONEYNESS_ATM_BAND = 0.02;
/** @see MONEYNESS_ATM_BAND — the far-OTM wing boundary (fraction of spot out of the money). */
export const MONEYNESS_DEEP_OTM_FRAC = 0.15;
/**
* Classify a leg's coarse {@link Moneyness} from the decision-time underlier — strike vs spot, by
* right. Pure. Returns `undefined` when spot is unresolvable (`undefined` / non-positive): the
* caller must NOT read that as near-the-money (which would silently bypass the directional guard) —
* it fails closed to the conservative wing (see {@link guardIntent}, kestrel-9gu.6).
*
* The OTM fraction is signed toward out-of-the-money: `(strike − spot)/spot` for a CALL, its mirror
* for a PUT. `≥ MONEYNESS_DEEP_OTM_FRAC` ⇒ `deep_otm`; `> MONEYNESS_ATM_BAND` ⇒ `otm`; within the
* band ⇒ `atm`; below ⇒ `itm`.
*/
export function classifyMoneyness(spot, strike, right) {
if (spot === undefined || !Number.isFinite(spot) || spot <= 0)
return undefined;
const otmFrac = right === "C" ? (strike - spot) / spot : (spot - strike) / spot;
if (otmFrac >= MONEYNESS_DEEP_OTM_FRAC)
return "deep_otm";
if (otmFrac > MONEYNESS_ATM_BAND)
return "otm";
if (otmFrac >= -MONEYNESS_ATM_BAND)
return "atm";
return "itm";
}
/**
* Author a {@link GuardedIntent} for a leg at the moment of the decision (kestrel-9gu.6 /
* kestrel-vav2) — the ONE place a leg's wing, covered-state and fail-closed fork are decided, so the
* far-OTM standalone SELL cap, the covered-wing exemption, and the BUY crossing unlock all run END
* TO END through the fill model. Pure given `resolveSpot` (no clock/RNG).
*
* - **Spot leg** (no strike/right, ADR-0017) — no wing: the far-OTM guard is an option concept with
* no spot analogue, so no classification is attempted and spot is never resolved.
* - **BUY** — the guard only ever UNLOCKS a far-OTM bid (crossing-print), never a bounded-risk leak,
* so an unresolvable spot is safe: classify when we can, else take the symmetric path (no
* moneyness). `covered` is not meaningful for a buy.
* - **SELL** — the engine's never-naked boundary authors only single-leg closes of a held long, which
* are STANDALONE offers in the fill-model sense (`covered: false`) — a genuine multi-leg
* combo-anchored short is not authored here (a FORK; if one is ever authored it must set
* `covered: true` explicitly). If spot is unresolvable we cannot classify the wing, so we **fail
* closed** to the conservative far-OTM cap (`deep_otm`, standalone) and flag it — NEVER a silent
* default and never a silent bypass (BOUNDED-RISK / never-naked, RUNTIME §8).
*/
export function guardIntent(input) {
const { side, strike, right } = input;
if (strike === undefined || right === undefined)
return { side, unresolved: false };
const m = classifyMoneyness(input.resolveSpot(), strike, right);
if (side === "buy") {
return m === undefined ? { side, unresolved: false } : { side, moneyness: m, unresolved: false };
}
if (m === undefined)
return { side, moneyness: "deep_otm", covered: false, unresolved: true };
return { side, moneyness: m, covered: false, unresolved: false };
}
/** Read the {@link GuardedIntent} back off a resting order — the fill side of the seam. The verdict
* was reached at submission ({@link guardIntent}) and threaded onto the order; the fill model never
* re-classifies (it has no decision-time underlier, and a second classification is a second truth).
* An order carrying no `moneyness` is UNCLASSIFIED ⇒ the symmetric path. `unresolved` is the
* author's log-worthy fork and is not re-litigated here, so it reads `false`: the fail-closed
* verdict is already baked into `moneyness`/`covered`. */
export function guardedIntentOf(order) {
return {
side: order.side,
...(order.moneyness !== undefined ? { moneyness: order.moneyness } : {}),
...(order.covered !== undefined ? { covered: order.covered } : {}),
unresolved: false,
};
}
// ─────────────────────────────────────────────────────────────────────────────
// The far-OTM cap decision
// ─────────────────────────────────────────────────────────────────────────────
/**
* The far-OTM SELL cap. Resting an OFFER to SELL far-OTM premium is
* **fantasy** — nobody lifts the worthless wing on a thin book, so the honest hazard is ~no-fill.
* We **do not hard-zero it** (a probability of exactly 0 is brittle and un-gradable, and a rare
* lift is not literally impossible); we **floor it** to this small positive value — so far below a
* typical fill threshold that any standalone far-OTM short DROPS the package under complete-fill-only
* (fail-closed in practice), yet the cell stays gradable. */
export const SELL_FAR_OTM_CAP = 0.01;
/**
* The far-OTM BUY crossing-print multiplier. Resting a penny BID to
* BUY cheap far-OTM is **real**: holders of near-worthless OTM options DUMP to any bid into expiry,
* and their market SELL crosses our resting bid at our price. The dump flow concentrates at the bid,
* so the buy-side far-OTM leg fills MORE than the symmetric touch implied — a modest (>1) unlock,
* clamped to 1. NOT invented high — calibrated up from the live crossing-print ledger. */
export const BUY_FAR_OTM_CROSSING = 1.5;
/** The wing bucket where the SELL-side maker fill is fantasy and the BUY-side is the real edge. */
function isFarOtm(moneyness) {
return moneyness === "deep_otm";
}
/**
* Apply a {@link GuardedIntent}'s directional asymmetry to a **symmetric** maker hazard `pSym` —
* the ONE far-OTM cap decision, and the seam the never-naked property is enforced at. The guard
* fires ONLY in the far-OTM wing; near-the-money (itm/atm/otm) and an **unclassified** leg
* (`moneyness` absent) are returned UNCHANGED — the bounded short-premium that SELLS near strikes is
* preserved, and callers that want the guard MUST author a classified GuardedIntent.
*
* - BUY far-OTM → `× BUY_FAR_OTM_CROSSING`, clamped to 1 (the real crossing-print dump-flow fill).
* - SELL far-OTM **standalone** → floored/capped to `SELL_FAR_OTM_CAP` ≈ no-fill (the no-lifter fantasy).
* - SELL far-OTM **covered** (covering wing of a defined-risk package) → UNCHANGED (fills as a combo,
* anchored by its near leg; penalizing it would silently kill every defined-risk spread).
*
* Pure. `covered` defaults to standalone. Never hard-zeros: the cap is a positive floor.
*/
export function guardedPfill(pSym, guarded) {
if (!isFarOtm(guarded.moneyness))
return pSym;
if (guarded.side === "buy")
return Math.min(1, pSym * BUY_FAR_OTM_CROSSING);
if (guarded.covered === true)
return pSym; // covering wing of a defined-risk package — not fantasy.
return Math.min(pSym, SELL_FAR_OTM_CAP); // standalone far-OTM short: fail-closed to ~no-fill.
}