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.

83 lines (74 loc) 3.24 kB
/** * [NEEDS-LIVE-GATEWAY] Owner smoke for the IB Gateway TWS-socket transport (kestrel-7o2.5). * * This is the acceptance the OWNER runs against a RUNNING IB PAPER gateway. It is NOT a unit test * and is NEVER exercised by `bun test` (it lives under `src/`, not `*.test.ts`, and only runs when * invoked directly with the env gate set), so CI stays green with no gateway. * * It PLACES NO ORDERS. It only: connects, completes the handshake, prints the REDACTED account id + * the server time, shows a live heartbeat, and then waits for the operator to KILL the gateway to * demonstrate the fail-closed degraded-on-kill behavior (status flips `degraded`, use throws the * typed error). Ctrl-C or a detected degradation exits cleanly. * * Run (paper only): * KESTREL_IBKR_SMOKE=1 KESTREL_IBKR_MODE=paper KESTREL_IBKR_PORT=4002 \ * bun run src/adapters/broker/ibkr/smoke.ts */ import { describeIbkrConfig, resolveIbkrConfig } from "./config.ts"; import { loadEnvFallback } from "../../../cli/credentials.ts"; import { IbkrTransport } from "./transport.ts"; async function runSmoke(): Promise<void> { const config = resolveIbkrConfig(); // eslint-disable-next-line no-console console.log(`[smoke] resolving transport → ${describeIbkrConfig(config)} (PLACES NO ORDERS)`); const transport = new IbkrTransport(config, { heartbeatIntervalMs: 3_000, heartbeatStalenessMs: 12_000, log: (line) => console.log(`[smoke] ${line}`), }); const status = await transport.connect(); console.log( `[smoke] connected: account=${status.account} serverTime=${ status.serverTime === undefined ? "?" : new Date(status.serverTime).toISOString() } heartbeat=${status.lastHeartbeat ?? "?"}`, ); console.log("[smoke] heartbeat is live. Now KILL the IB Gateway to prove degraded-on-kill…"); await new Promise<void>((resolve) => { const timer = setInterval(() => { transport.pulse(); const s = transport.status(); console.log( `[smoke] status connected=${s.connected} degraded=${s.degraded} serverTime=${ s.serverTime === undefined ? "?" : new Date(s.serverTime).toISOString() }`, ); if (s.degraded) { console.log(`[smoke] DEGRADED (fail-closed): ${s.reason}`); try { transport.client(); // must throw the typed IbkrConnectionError } catch (e) { console.log(`[smoke] use-after-degrade threw as required: ${(e as Error).name}: ${(e as Error).message}`); } clearInterval(timer); resolve(); } }, 3_000); }); transport.disconnect(); console.log("[smoke] done — transport closed cleanly. No orders were placed."); } if (import.meta.main) { loadEnvFallback(); // KESTREL_IBKR_* identifiers from the shared secrets home (OSS-ADR-0054) if (process.env.KESTREL_IBKR_SMOKE !== "1") { console.log( "[smoke] skipped: set KESTREL_IBKR_SMOKE=1 (and run a paper IB Gateway) to exercise this. Places no orders.", ); } else { runSmoke().catch((e: unknown) => { const err = e as Error; console.error(`[smoke] FAILED (fail-closed): ${err.name}: ${err.message}`); process.exitCode = 1; }); } } export { runSmoke };