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.
148 lines • 10.2 kB
TypeScript
/**
* # 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).
*/
import type { Right } from "../bus/index.ts";
/** Coarse moneyness of the resting leg vs the decision-time underlier — the axis the
* DIRECTIONAL fill guard keys on.
* `deep_otm` is the far wing where a resting OFFER has no lifter (fantasy) while a resting BID is
* a real crossing-print fill. Caller-supplied; **absent ⇒ the symmetric legacy path** (the guard
* cannot fire on an unclassified leg). */
export type Moneyness = "itm" | "atm" | "otm" | "deep_otm";
/**
* 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 declare const MONEYNESS_ATM_BAND = 0.02;
/** @see MONEYNESS_ATM_BAND — the far-OTM wing boundary (fraction of spot out of the money). */
export declare 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 declare function classifyMoneyness(spot: number | undefined, strike: number, right: Right): Moneyness | undefined;
/**
* A leg's directional-guard verdict — the ONE value the engine authors and the fill model consumes.
* Produced by {@link guardIntent} at submission (from the authoring side) or read back off a resting
* order by {@link guardedIntentOf} (from the fill side); applied by {@link guardedPfill}.
*
* `moneyness` absent ⇒ the leg is UNCLASSIFIED and takes the symmetric path (a spot leg, or a BUY
* whose spot was unresolvable) — the guard cannot fire. `covered` is meaningful only on a SELL.
* `unresolved` is `true` ONLY on a SELL that had to fail closed to the conservative wing, so the
* caller can log the fork.
*/
export interface GuardedIntent {
readonly side: "buy" | "sell";
readonly moneyness?: Moneyness;
readonly covered?: boolean;
readonly unresolved: boolean;
}
/** What {@link guardIntent} needs to reach a verdict: the leg, and HOW to resolve the underlier.
* `strike`/`right` are BOTH present for an option leg and BOTH absent for a spot leg (ADR-0017). */
export interface GuardIntentInput {
readonly side: "buy" | "sell";
/** The option strike; ABSENT for a spot/equity leg (ADR-0017). */
readonly strike?: number;
/** The option right; ABSENT for a spot/equity leg (ADR-0017). */
readonly right?: Right;
/** The decision-time underlier resolver — called by the owner at most once, and ONLY when an
* option leg needs classifying (a spot leg never resolves spot). `undefined` ⇒ unresolvable. */
readonly resolveSpot: () => number | undefined;
}
/**
* 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 declare function guardIntent(input: GuardIntentInput): GuardedIntent;
/** 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 declare function guardedIntentOf(order: {
readonly side: "buy" | "sell";
readonly moneyness?: Moneyness | undefined;
readonly covered?: boolean | undefined;
}): GuardedIntent;
/**
* 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 declare 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 declare const BUY_FAR_OTM_CROSSING = 1.5;
/**
* 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 declare function guardedPfill(pSym: number, guarded: GuardedIntent): number;
//# sourceMappingURL=guarded-intent.d.ts.map