/**
 * # adapters/broker/ibkr/feed — the FEED FACE (kestrel-7o2.7)
 *
 * The {@link FeedSource} reference implementation over the ONE shared IB session: it opens
 * `reqMktData` subscriptions on the contracts the 7o2.6 layer resolved (the spot/equity underlier +
 * an option-chain slice) and turns the venue's tick stream into the **EXISTING** {@link BusEvent}
 * union — a `META` header, `TICK/SPOT` (`px`/`bid`/`ask`), `TICK/BOOK` (real `OptionQuote` legs +
 * `underlier_px`), and `TICK/HEARTBEAT` proof-of-life. No new event kind is invented, so
 * `SessionCore.step` folds this feed with zero changes and the deterministic core downstream is
 * untouched.
 *
 * It is MARKET DATA ONLY. There is no order path here — not in the module, and not even reachable
 * through the client: the narrow {@link IbFeedClient} surface declares nothing but `reqMktData` /
 * `cancelMktData` / `reqMarketDataType` and the event bus. Orders are 7o2.8 (paper) / 7o2.10 (live),
 * behind the human-signed arm envelope (7o2.9).
 *
 * ## The price doctrine (ARCHITECTURE §4, RUNTIME §4) — the heart of this face
 *
 * **The observed MID is a HEALTH SIGNAL, never a value and never a price this feed publishes.** An
 * option's book can be fictional: a maker quoting 1.00 × 3.00 on a contract with 5.10 of intrinsic is
 * not offering you 2.00, and a feed that carried that 2.00 forward as a number would have authored a
 * naked sale nobody wrote. So:
 *
 *  - `TICK/SPOT.px` is the **observed last trade** the venue actually printed. When no trade has
 *    printed, the feed emits **no SPOT at all** (fail-closed) — it never synthesizes a spot out of a
 *    quote mid.
 *  - `TICK/BOOK` legs carry the **observed sides verbatim** (`bid`/`ask`/`bsz`/`asz`), with a dark
 *    side as `null` — never `0`, never omitted — so a downstream reducer can tell a genuinely
 *    one-sided book (the market-maker-pull fingerprint of a real move) from a two-sided one.
 *  - `@fair` ({@link feedFair}) is the ENGINE's `executionFair` — underlying-anchored Black-76 backed
 *    out of the LIQUID two-sided quotes and **floored at intrinsic always** — and it carries a
 *    {@link FairReceipt}: the model VERSION, the FIT QUALITY (how many liquid strikes backed the
 *    surface, and the IV it priced at), and the FRESHNESS (`asof`). Belief is the author's; fair is
 *    the engine's, and it says how it was earned.
 *  - A **one-sided or dark book** yields `fair === null` and FAILS CLOSED to an **annotated** book
 *    value — a SELL degrades to its intrinsic floor (`fair=fallback(intrinsic;mid-dark)`, never
 *    naked), a BUY with no two-sided mid is genuinely unresolvable. The annotation ALWAYS names the
 *    fallback, so a silent mid can never slip through (RUNTIME §4).
 *  - The mid survives ONLY inside {@link QuoteHealth}: a spread WIDTH and a dimensionless spread
 *    REGIME (`tight | wide | one-sided | dark`). There is deliberately no `mid` field anywhere on
 *    this module's surface, so a caller cannot read one off it as a price even by accident.
 *
 * ## Staleness — the watermark (the rs4/8kc bug class, first-class here)
 *
 * A **frozen, re-printed quote does NOT advance `asOfSeq`.** IB will happily re-send an identical
 * bid/ask forever while the market underneath it is gone; a feed that treated each re-print as news
 * would keep a dead tape looking alive. So the watermark advances on a **value CHANGE**, never on a
 * print: a re-print increments a `reprints` counter (the frozen fingerprint) and refreshes only
 * `lastPrintTs` (the LINE is talking), while `asOfSeq`/`asOfTs` (the VALUE) stay pinned. Past the
 * staleness threshold the line is STALE and the feed's health goes **DEGRADED** with a logged reason
 * — never a silent one.
 *
 * That degradation has TEETH, and this is the part that matters: canonical state cannot un-know a
 * price it has already folded, so the frozen 445.10 sits in {@link CanonicalState} looking perfectly
 * alive and would happily fire an armed plan. {@link stalenessGatedProvider} wraps the real
 * {@link SeriesProvider} and, while the underlier line is dead, resolves **every market-side series to
 * UNKNOWN** rather than to its last-known value. Only a definite `true` fires (RUNTIME §3), so an
 * UNKNOWN operand taints every dependent trigger and the affected plans **cannot fire off a dead
 * feed**. A dead feed must never read as live.
 *
 * ## Determinism at the edge
 *
 * The IB client, the clock, the request-id minter and the deadline scheduler are all INJECTED (as the
 * transport and the contract layer inject them), so unit tests drive an in-memory double with no
 * socket and no timer. Events carry a monotone `seq` from 0 and the INJECTED `ts` — so the BusEvents
 * this edge produces are causally ordered and replay-stable, which is exactly what the deterministic
 * core downstream requires of them.
 */
import { EventName } from "@stoqey/ib";
import type { Contract } from "@stoqey/ib";
import { IbkrRequestError } from "./requests.ts";
import type { AtmCoverage, AtmPolicy } from "./surface-window.ts";
import type { IbkrContractDeps, IbkrOptionContract, IbkrQuotedContract, IbkrUnderlierContract, UnderlierSecType } from "./contract.ts";
import type { IbkrTransport, NowFn, Scheduler } from "./transport.ts";
import type { BusEvent, Mode, OptionQuote, Right, SessionPhase } from "../../../bus/index.ts";
import { EXEC_FAIR_MODEL } from "../../../fair/index.ts";
import type { FairReceipt } from "../../../fair/index.ts";
import type { FeedSource } from "../../broker.ts";
import { type SeriesProvider, type SeriesRegistry } from "../../../series/index.ts";
/** How long a line's VALUE may go unchanged before it is STALE. A quote that has not MOVED in this
 * long is not evidence of a calm market — it is a line we can no longer vouch for. */
export declare const DEFAULT_STALE_AFTER_MS = 15000;
/** How long one `reqMktData` may go completely unanswered before the subscription FAILS. A gateway
 * that never answers is a FAILED request, never an infinite wait (fail-closed). */
export declare const DEFAULT_SUBSCRIBE_TIMEOUT_MS = 10000;
/** Spread-to-mid ratio above which a two-sided book is a WIDE one — the dimensionless regime
 * boundary. A RATIO, never a price: the mid appears here only as a denominator. */
export declare const WIDE_SPREAD_RATIO = 0.15;
/** A listener over the IB event bus (the transport's convention). */
type IbListener = (...args: never[]) => void;
/**
 * The NARROW structural surface of the shared IB client the feed speaks through: the streaming
 * market-data requests and the event bus. It declares NO order method of any kind — the feed cannot
 * reach one even through the client. A test injects a double implementing exactly this; production
 * passes {@link IbkrTransport}'s ONE shared client via {@link feedClientOf} — never a second socket,
 * and never a second market-data connection.
 */
export interface IbFeedClient {
    /** Open a streaming market-data subscription for one contract. */
    reqMktData(reqId: number, contract: Contract, genericTickList: string | null, snapshot: boolean, regulatorySnapshot: boolean): unknown;
    /** Close a streaming market-data subscription. */
    cancelMktData(reqId: number): unknown;
    /** Choose the market-data flavour (1 real-time, 2 frozen, 3 delayed, 4 delayed-frozen). OPTIONAL:
     * a client that cannot switch simply omits it, and the feed never pretends it did. */
    reqMarketDataType?(marketDataType: number): unknown;
    on(event: EventName, listener: IbListener): unknown;
    removeListener(event: EventName, listener: IbListener): unknown;
}
/**
 * The ONE shared IB session, viewed through the READ-ONLY market-data surface (kestrel-7o2.7). The
 * transport hands out its single guarded client (throwing the typed connection error if the session
 * is not connected or has gone degraded, so a quote can never be read over a dead socket); this
 * re-views it as an {@link IbFeedClient}. The cast NARROWS toward the market-data requests — the real
 * `IBApi` is a structural superset, and this view can place nothing. There is never a second socket.
 */
export declare function feedClientOf(transport: IbkrTransport): IbFeedClient;
/** Why the FEED itself failed (kestrel-7o2.7) — distinct from a single refused subscription
 * ({@link IbkrRequestError}) and from a dead socket (`IbkrConnectionError`). A feed with no underlier
 * has no tape at all: there is no `underlier_px` to quote a book against and no spot to drive
 * canonical state, so it fails CLOSED rather than emitting a chain floating in a vacuum. */
export declare class IbkrFeedError extends Error {
    readonly name = "IbkrFeedError";
    readonly reason: string;
    constructor(reason: string, options?: {
        cause?: unknown;
    });
}
/** The spread REGIME of an observed book — the mid's ONLY legitimate role (a fingerprint, never a
 * price). `dark` = both sides gone; `one-sided` = a maker pulled one side (the real-move fingerprint);
 * `wide` = two-sided but the spread is a large fraction of the book (a book you cannot trust to a
 * cent); `tight` = a book worth reading. */
export type SpreadRegime = "tight" | "wide" | "one-sided" | "dark";
/**
 * The health of one observed book (kestrel-7o2.7). This is where — and ONLY where — the observed mid
 * is allowed to matter, and even here it never escapes as a number: it appears solely as the
 * denominator of a dimensionless {@link SpreadRegime}. There is deliberately NO `mid` field: a caller
 * cannot lift a mid off this surface and price against it, because the surface does not carry one.
 */
export interface QuoteHealth {
    /** Both sides present ⇒ a real, two-sided book. */
    readonly twoSided: boolean;
    /** Both sides gone ⇒ dark. */
    readonly dark: boolean;
    /** The spread WIDTH in dollars (`ask − bid`) — an observed distance, not a price. `null` unless
     * the book is two-sided. */
    readonly spread: number | null;
    /** The dimensionless spread regime — the fingerprint the mid is reduced to. */
    readonly spreadRegime: SpreadRegime;
}
/**
 * The fail-closed ANNOTATED book value a caller falls back to when `@fair` is unbuildable (RUNTIME
 * §4). The `annotation` ALWAYS names the fallback — that is the whole point: **a silent mid is
 * forbidden**, so a number that did not come from the model arrives wearing a label that says so.
 * `px` is `null` when there is no honest number at all (a BUY into a dark book) — an annotated
 * refusal, never a guess.
 */
export interface FairFallback {
    readonly px: number | null;
    readonly annotation: string;
}
/**
 * `@fair` for one option leg (kestrel-7o2.7) — the engine's value, with its receipt, plus the honest
 * fail-closed alternative when it cannot be built. Exactly one of `value` / `fallback` is present.
 * There is no `mid` on this object, and there never will be.
 */
export interface FeedFair {
    /** The ExecutionFair value — Black-76 at the interpolated IV, FLOORED AT INTRINSIC. `null` when
     * unbuildable (no usable underlier, or a book with zero liquid strikes to back a vol out of). */
    readonly value: number | null;
    /** The trust envelope: model VERSION + FIT QUALITY (`nLiquid`, `ivAtStrike`) + FRESHNESS (`asof`).
     * `null` exactly when `value` is — a receipt is never fabricated for a number that does not exist. */
    readonly receipt: FairReceipt | null;
    /** The annotated book value when `value` is `null`; `null` when fair resolved. */
    readonly fallback: FairFallback | null;
    /** The observed book's health — the mid's only role. */
    readonly health: QuoteHealth;
    /**
     * THE FIT QUALITY THE RECEIPT COULD NOT EXPRESS (kestrel-7o2.19): does the surface this value came
     * off actually KNOW WHERE THE MONEY IS? `nLiquid` is a COUNT, not a LOCATION — five deep-ITM strikes
     * are five liquid strikes and zero evidence about an at-the-money price. Always present, whether or
     * not a value resolved.
     */
    readonly coverage: AtmCoverage;
    /** Non-`null` exactly when a value is returned off a surface that does NOT cover the money (the
     * `taint` {@link AtmPolicy}) — the number then travels WEARING the reason it cannot be trusted.
     * `null` when the surface covers the money, and `null` when the feed failed closed instead (there is
     * then no number to taint, and the fallback's own annotation carries the reason). */
    readonly taint: string | null;
}
/** The inputs to one `@fair` resolution off a recorded/observed book. Pure: no clock, no socket. */
export interface FeedFairInput {
    /** The OBSERVED underlier price (a real print). `null` ⇒ no usable underlying ⇒ fair is unbuildable
     * (never a spot synthesized out of a quote). */
    readonly underlierPx: number | null;
    readonly strike: number;
    readonly right: Right;
    /** The side decides the fallback SHAPE: a SELL degrades to its intrinsic floor (never naked); a BUY
     * with no two-sided mid is genuinely unresolvable. */
    readonly side: "buy" | "sell";
    /** Time to expiry in YEARS — injected, never read off a clock (RUNTIME §0). */
    readonly tauYears: number;
    /** The chain slice the vol surface is backed out of. One-sided/dark legs contribute nothing (their
     * mid is a health signal, never a price) — that is `buildSurface`'s own rule, reused verbatim. */
    readonly legs: readonly OptionQuote[];
    /** The observation time of the quotes being valued (epoch ms) — stamped onto the receipt. */
    readonly asof: number;
    /** What "NEAR the money" means, as a fraction of spot (default {@link DEFAULT_ATM_BAND_FRACTION}) —
     * a named, injectable parameter, never a magic number at a call site. */
    readonly bandFraction?: number | undefined;
    /** What to do when the surface does NOT know the money (default `fail-closed`). */
    readonly atmPolicy?: AtmPolicy | undefined;
}
/**
 * Resolve `@fair` for one option leg off an observed two-sided book (kestrel-7o2.7).
 *
 * Delegates the VALUE to the engine's own {@link executionFair} — this module does not own a second
 * pricing model, and it does not re-price. It owns exactly the fail-closed half the price doctrine
 * demands: when fair is unbuildable, produce the **annotated** book value (mirroring
 * `engine/pricing.ts` `resolveFair` field-for-field, including its annotation strings — those strings
 * ARE the audit trail RUNTIME §4 requires), and reduce the observed mid to a health signal so it can
 * never be mistaken for a price.
 *
 * **AND IT REFUSES TO VOUCH FOR A SURFACE THAT DOES NOT KNOW THE MONEY (kestrel-7o2.19).** `fair` is
 * receipt-gated — it CARRIES WHETHER IT CAN BE TRUSTED (ARCHITECTURE §4) — and a receipt reporting
 * `nLiquid=5` off five deep-ITM strikes while the money sits eleven points higher is a receipt that
 * does not know it is lying. So {@link atmCoverage} is measured on EVERY resolution, off the same legs
 * and the same liquidity rule the surface itself uses; a surface with no liquid strike NEAR the money
 * (or none BRACKETING it, which makes an at-the-money read a flat extrapolation of a wing's IV) either
 * FAILS CLOSED to the annotated fallback — the default — or returns its value TAINTED. Never a
 * confident number off a surface that does not know the money.
 */
export declare function feedFair(input: FeedFairInput): FeedFair;
/** The feed's overall verdict on itself. `DEGRADED` is never silent — it always carries a reason. */
export type FeedHealth = "LIVE" | "DEGRADED";
/**
 * The staleness watermark of ONE subscribed line (kestrel-7o2.7). The distinction the whole bead
 * turns on lives here: `lastPrintTs` is when the LINE last spoke (IB re-sent something, anything),
 * while `asOfSeq`/`asOfTs` are when the VALUE last CHANGED. A frozen line re-printing the same
 * bid/ask forever keeps `lastPrintTs` moving and `asOfSeq` PINNED — which is exactly how a dead tape
 * is caught rendering as a live one.
 */
export interface FeedSeriesWatermark {
    /** `SPY` for the underlier, `SPY|445C` for an option leg. */
    readonly key: string;
    readonly kind: "spot" | "option";
    /** The emitted `seq` at which this line's VALUE last CHANGED. A frozen re-print does NOT advance
     * it. `null` before the line has ever carried a value. */
    readonly asOfSeq: number | null;
    /** The injected-clock `ts` at which this line's VALUE last CHANGED. */
    readonly asOfTs: number | null;
    /** The injected-clock `ts` at which IB last PRINTED anything on this line, changed or not — proof
     * the line is talking, which is NOT proof the market is moving. */
    readonly lastPrintTs: number | null;
    /** Identical re-prints since the last value CHANGE — the frozen-quote fingerprint. */
    readonly reprints: number;
    /** `now − asOfTs` — how old the VALUE is. `null` before the first value. */
    readonly ageMs: number | null;
    /** `ageMs > staleAfterMs`, or no value has ever landed ⇒ STALE (fail-closed on absence). */
    readonly stale: boolean;
    /** Both sides gone. */
    readonly dark: boolean;
    /** A refused subscription (a request-scoped IB error / a deadline lapse) — the line is dead by
     * refusal rather than by silence. */
    readonly failed: boolean;
}
/** The feed's staleness watermark (kestrel-7o2.7) — the canonical freshness truth a consumer gates
 * on. Read it, do not infer it: canonical state cannot tell you a price is stale, only what it last
 * saw. */
export interface FeedWatermark {
    /** The injected clock at the moment the watermark was read. */
    readonly now: number;
    /** The last `seq` the feed emitted. */
    readonly seq: number;
    readonly staleAfterMs: number;
    readonly health: FeedHealth;
    /** The logged reason for DEGRADED, or `null` while LIVE. Never a silent degrade. */
    readonly reason: string | null;
    readonly series: readonly FeedSeriesWatermark[];
    /** Is the UNDERLIER line stale/dead? It is the canonical coordinate every market series hangs off,
     * so its death taints all of them. */
    readonly underlierStale: boolean;
}
export interface IbkrFeedConfig {
    /** The underlier symbol (generic tickers only in anything shipped — ARCHITECTURE §7). */
    readonly instrument: string;
    /** The session header's opaque calendar token (`YYYY-MM-DD`). */
    readonly sessionDate: string;
    /** The Kestrel mode this tape is stamped with. */
    readonly mode: Mode;
    /** The chain's expiry, carried onto every `TICK/BOOK` (the venue's own `YYYYMMDD`, or a tag). */
    readonly expiry?: string | undefined;
    /** Time to expiry in YEARS for `@fair` — INJECTED, never derived from a clock (RUNTIME §0). */
    readonly tauYears?: number | undefined;
    /** How long a line's VALUE may go unchanged before it is STALE (default {@link DEFAULT_STALE_AFTER_MS}). */
    readonly staleAfterMs?: number | undefined;
    /** How long one subscription may go unanswered before it FAILS (default {@link DEFAULT_SUBSCRIBE_TIMEOUT_MS}). */
    readonly subscribeTimeoutMs?: number | undefined;
    /** What "NEAR the money" means for the fit-quality receipt, as a fraction of spot (default
     * {@link DEFAULT_ATM_BAND_FRACTION}) — a named, injectable parameter (kestrel-7o2.19). */
    readonly atmBandFraction?: number | undefined;
    /** What `@fair` does when the surface does NOT know the money: FAIL CLOSED (`fair => null` + the
     * annotated fallback — the default) or return the value TAINTED. Never a silent confident number. */
    readonly atmPolicy?: AtmPolicy | undefined;
    /** The session phase stamped on the proof-of-life heartbeats, when the caller pins one. */
    readonly phase?: SessionPhase | undefined;
    /** IB market-data flavour (1 real-time, 3 delayed). Absent ⇒ the gateway's own default is left
     * alone — the feed never silently downgrades a caller to delayed data. */
    readonly marketDataType?: number | undefined;
    /** The session account — used ONLY to stamp a REDACTED marker on diagnostics. */
    readonly account?: string | undefined;
}
export interface IbkrFeedDeps {
    /** The shared IB client — the transport's ONE session in production ({@link feedClientOf}). */
    readonly client: IbFeedClient;
    /** The resolved UNDERLIER contract (kestrel-7o2.6) — an equity/ETF (`STK`) or an INDEX (`IND`,
     * the underlier of a cash-settled index option, kestrel-7o2.24). The gateway's OWN definition,
     * never hand-rolled: the feed does not resolve identities, it subscribes to resolved ones. Typed as
     * the UNDERLIER union, not the TRADABLE one — a feed quotes an index, it never orders one. */
    readonly underlier: IbkrUnderlierContract;
    /** The resolved OPTION legs (kestrel-7o2.6). Empty/absent ⇒ an equity-only tape: no `TICK/BOOK` is
     * ever emitted, which is a first-class shape on this bus. */
    readonly legs?: readonly IbkrOptionContract[] | undefined;
    /** The injected clock (epoch ms). */
    readonly now: NowFn;
    /** Mints a unique IB request id per subscription — a counter, never an RNG. */
    readonly nextReqId?: (() => number) | undefined;
    /** The deadline scheduler — injected so tests trip it by hand (no real timer, no flake). */
    readonly scheduler?: Scheduler | undefined;
    /** Redacted-diagnostic sink (default no-op). NOTE: failures never depend on this — that was the
     * 7o2.16 gap. A refused subscription reaches the CALLER through {@link FeedSubscription.ready} and
     * {@link FeedOpenResult.failed}, whether or not anyone is listening to the log. */
    readonly log?: ((line: string) => void) | undefined;
}
/** The lifecycle of one `reqMktData` subscription. */
export type SubscriptionState = "pending" | "live" | "failed";
/**
 * One market-data subscription on the shared socket (kestrel-7o2.7). {@link ready} is the
 * caller-visible channel the 7o2.16 follow-up demanded: it RESOLVES on the line's first print and
 * REJECTS with a typed {@link IbkrRequestError} when the gateway refuses the request (IB 200 / 321 /
 * 354 / 10197 …) or when the deadline lapses. It never simply hangs.
 */
export interface FeedSubscription {
    readonly reqId: number;
    /** `SPY` for the underlier, `SPY|445C` for an option leg. */
    readonly key: string;
    readonly kind: "spot" | "option";
    readonly contract: IbkrQuotedContract;
    readonly state: SubscriptionState;
    /** The typed refusal, once this subscription has failed. `null` otherwise. */
    readonly error: IbkrRequestError | null;
    /** Resolves on the first print; REJECTS on a request-scoped refusal or a deadline lapse. */
    readonly ready: Promise<void>;
}
/** What {@link IbkrFeed.open} reports. Failures are SURFACED, never swallowed — but a refused LEG is
 * not a refused feed: the leg is dropped (it will never be quoted as a phantom) and the tape carries
 * on, while a refused UNDERLIER throws {@link IbkrFeedError} because there is then no tape at all. */
export interface FeedOpenResult {
    readonly underlier: IbkrUnderlierContract;
    /** The legs that actually subscribed. */
    readonly legs: readonly IbkrOptionContract[];
    /** Every per-subscription refusal, surfaced to the caller. */
    readonly failed: readonly IbkrRequestError[];
}
/** What a `@fair` is asked for. */
export interface FeedFairQuery {
    readonly strike: number;
    readonly right: Right;
    readonly side: "buy" | "sell";
}
/**
 * The IBKR feed face (kestrel-7o2.7) — a {@link FeedSource} producing the EXISTING {@link BusEvent}
 * union off the ONE shared IB session. Market data only; it exposes no order path.
 */
export interface IbkrFeed extends FeedSource {
    /** Emit `META`, open every subscription, and settle. Never hangs: each subscription carries its own
     * deadline, and a refusal surfaces as a typed error rather than silence. */
    open(): Promise<FeedOpenResult>;
    /** Fold whatever IB has said since the last pump into BusEvents. A value CHANGE produces a
     * `TICK/SPOT` / `TICK/BOOK`; a quiet interval produces a `TICK/HEARTBEAT` (proof of life on a quiet
     * tape) — and a frozen re-print produces the heartbeat too, because a re-print is not news. */
    pump(): void;
    /** The bus events produced so far, in order — the {@link FeedSource} face. Re-iterable and
     * byte-stable (`META` first, then causally-ordered ticks). */
    events(): Iterable<BusEvent>;
    /** The events produced since the previous drain (the live loop's incremental read). */
    drain(): readonly BusEvent[];
    /** The staleness watermark — read it rather than inferring freshness from canonical state. */
    watermark(): FeedWatermark;
    /** `@fair` for one leg off the CURRENT observed book, with its receipt (or the annotated
     * fail-closed fallback). */
    fair(query: FeedFairQuery): FeedFair;
    /** Every subscription, live and failed. */
    subscriptions(): readonly FeedSubscription[];
    /** Close every subscription and detach every listener from the shared socket. Idempotent. */
    close(): void;
}
/**
 * Build the IBKR feed face (kestrel-7o2.7) — a {@link FeedSource} over the ONE shared IB session,
 * producing the EXISTING {@link BusEvent} union. Market data only: no order path exists here, and the
 * narrow client surface it speaks through declares none.
 */
export declare function ibkrFeed(cfg: IbkrFeedConfig, deps: IbkrFeedDeps): IbkrFeed;
/** What the gate needs of a feed — just its watermark. (Injected as an interface so the gate is
 * testable against any freshness source, and so nothing here depends on a socket.) */
export interface WatermarkSource {
    watermark(): FeedWatermark;
}
/**
 * Wrap a {@link SeriesProvider} so that a DEAD FEED can never read as a live one (kestrel-7o2.7).
 *
 * The problem this exists to solve is not hypothetical, and it cannot be fixed inside canonical
 * state: `CanonicalState` folds every SPOT it is given and then *knows* that price forever. When the
 * line freezes — IB re-printing an identical quote while the market underneath it is gone — the last
 * value sits there looking perfectly healthy. An armed plan reading `spot` gets a number, the trigger
 * evaluates `true`, and the engine fires off a tape that died minutes ago. The frozen-feed forensics
 * (kestrel-rs4/8kc) are exactly this.
 *
 * So freshness is applied at the READ, from the one place that actually knows it — the feed's
 * {@link FeedWatermark}. While the feed is DEGRADED, every **market-side** series (the registry's own
 * market-vs-org routing decides which: `spot`, `vwap`, `hod`/`lod`, the opening range, every windowed
 * metric) resolves to **UNKNOWN** instead of to its last-known value. Only a definite `true` fires
 * (RUNTIME §3), so an UNKNOWN operand propagates through the trigger algebra and the affected plans
 * simply cannot fire. The taint is precise: **org** facts (the session's own bookkeeping — `pnl`, a
 * plan's state) are not market observations and are passed through untouched; the calendar is not a
 * market observation either.
 *
 * Every refusal is LOGGED with its reason (once per series while degraded — a reason, never a storm).
 */
export declare function stalenessGatedProvider(inner: SeriesProvider, feed: WatermarkSource, opts?: {
    registry?: SeriesRegistry | undefined;
    log?: ((line: string) => void) | undefined;
}): SeriesProvider;
/** Which underlier + chain slice a feed wants to watch. */
export interface FeedContractQuery {
    readonly symbol: string;
    /** The chain expiry (`YYYYMMDD`). Absent ⇒ an equity-only tape (no legs are resolved at all — an
     * expiry is NEVER guessed, because "the nearest one" is a chain nobody asked for). */
    readonly expiry?: string | undefined;
    /**
     * The OBSERVED underlier price the strike window is CENTRED on — WHERE THE MONEY IS. **Required
     * whenever `expiry` is present** (kestrel-7o2.19): a chain slice picked without it is not a slice
     * of the market, it is a slice of the grid, and a grid whose middle is not the money hands the agent
     * five deep-ITM calls and five worthless puts (the live defect: 741–745 at a spot of 751.94).
     */
    readonly spot?: number | undefined;
    /** How many LISTED strikes to take EACH SIDE of the ATM one (default
     * {@link DEFAULT_SURFACE_HALF_WIDTH} = 4 ⇒ 9 strikes ⇒ 18 two-sided legs — the order path's own
     * window). A named, injectable parameter; never a magic number. */
    readonly halfWidth?: number | undefined;
    /** Pin a trading class (the only sanctioned way to break a two-class ambiguity). */
    readonly tradingClass?: string | undefined;
    /**
     * What the UNDERLIER is (kestrel-7o2.24): an equity/ETF (`STK`, the default — what every caller has
     * always meant) or an INDEX (`IND`, e.g. the NDX/XND class of cash-settled index options, whose
     * underlier is a published number and not an ETF).
     *
     * NEVER inferred from the symbol. An adapter that sniffed "this looks index-y" would be guessing an
     * identity, which is the one thing this layer exists not to do. Stating `STK` for an index symbol
     * does not silently mis-resolve: the gateway simply has no `STK` definition for it and the lookup
     * refuses `unresolvable`, loudly.
     */
    readonly underlierSecType?: UnderlierSecType | undefined;
}
/**
 * Resolve the contracts a feed subscribes to, by DRIVING the 7o2.6 contract layer (kestrel-7o2.7).
 * Nothing about identity resolution is re-implemented here: the equity comes from
 * {@link resolveUnderlier} (an equity/ETF or, since kestrel-7o2.24, an INDEX), the listed strikes
 * from {@link listOptionStrikes} (the venue's truth for THAT expiry, not the union across expiries),
 * and each leg from {@link resolveContract}. An
 * ambiguous or unresolvable identity raises the 7o2.6 typed refusal and reaches STAND_DOWN — the feed
 * never subscribes to a guessed contract.
 *
 * The WINDOW is {@link surfaceWindow} — the ONE rule (kestrel-7o2.19), the same one the order path
 * selects its vol surface with. It is centred on the ATM strike **the venue LISTS for this expiry**,
 * read from {@link listOptionStrikes} rather than from the chain's UNION grid: `reqSecDefOptParams`
 * returns the union of strikes across every expiry, and a strike in the union may simply not be listed
 * on the expiry asked for — selecting off it invents an identity the venue does not list.
 *
 * @throws {IbkrFeedError} when an option chain is asked for with no observed spot (fail-closed: no
 * money, no window).
 */
export declare function resolveFeedContracts(query: FeedContractQuery, deps: IbkrContractDeps): Promise<{
    underlier: IbkrUnderlierContract;
    legs: readonly IbkrOptionContract[];
}>;
export { EXEC_FAIR_MODEL };
//# sourceMappingURL=feed.d.ts.map