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.

109 lines (104 loc) 3.69 kB
// src/adapters/broker/ibkr/errors.ts class IbkrContractError extends Error { name = "IbkrContractError"; failure; reason; code; candidates; constructor(failure, reason, options = {}) { super(`IBKR contract resolution refused [${failure}]: ${reason} (fail-closed; STAND_DOWN)`, options.cause === undefined ? undefined : { cause: options.cause }); this.failure = failure; this.reason = reason; this.code = options.code; this.candidates = options.candidates; } } class IbkrConfigError extends Error { name = "IbkrConfigError"; constructor(message, options = {}) { super(message, options.cause === undefined ? undefined : { cause: options.cause }); } } class IbkrConnectionError extends Error { name = "IbkrConnectionError"; failure; code; constructor(failure, message, options = {}) { super(message, options.cause === undefined ? undefined : { cause: options.cause }); this.failure = failure; this.code = options.code; } } // src/adapters/broker/ibkr/config.ts var IBKR_DEFAULT_HOST = "127.0.0.1"; var IBKR_DEFAULT_PORT = 4002; var IBKR_DEFAULT_CLIENT_ID = 0; var IBKR_DEFAULT_MODE = "paper"; var nonEmpty = (v) => { if (v === undefined) return; const t = v.trim(); return t.length > 0 ? t : undefined; }; function resolveInt(label, override, raw, fallback) { if (override !== undefined) { if (!Number.isInteger(override)) { throw new IbkrConfigError(`${label} override must be an integer (got ${JSON.stringify(override)})`); } return override; } const s = nonEmpty(raw); if (s === undefined) return fallback; const n = Number(s); if (!Number.isInteger(n)) { throw new IbkrConfigError(`${label} must be an integer (got ${JSON.stringify(s)}) — fail-closed`); } return n; } function resolveMode(override, raw) { const v = override ?? nonEmpty(raw); if (v === undefined) return IBKR_DEFAULT_MODE; if (v !== "paper" && v !== "live") { throw new IbkrConfigError(`KESTREL_IBKR_MODE must be "paper" or "live" (got ${JSON.stringify(v)}) — fail-closed`); } return v; } function resolveIbkrConfig(input = {}) { const env = input.env ?? process.env; const host = nonEmpty(input.host) ?? nonEmpty(env.KESTREL_IBKR_HOST) ?? IBKR_DEFAULT_HOST; const port = resolveInt("KESTREL_IBKR_PORT", input.port, env.KESTREL_IBKR_PORT, IBKR_DEFAULT_PORT); const clientId = resolveInt("KESTREL_IBKR_CLIENT_ID", input.clientId, env.KESTREL_IBKR_CLIENT_ID, IBKR_DEFAULT_CLIENT_ID); const account = nonEmpty(input.account) ?? nonEmpty(env.KESTREL_IBKR_ACCOUNT); const mode = resolveMode(input.mode, env.KESTREL_IBKR_MODE); return { host, port, clientId, account, mode }; } var IBKR_PAPER_ACCOUNT = /^D[UF]\d+$/; var IBKR_LIVE_ACCOUNT = /^U\d+$/; function classifyIbkrAccount(account) { if (account === undefined) return "unknown"; const a = account.trim(); if (IBKR_PAPER_ACCOUNT.test(a)) return "paper"; if (IBKR_LIVE_ACCOUNT.test(a)) return "live"; return "unknown"; } var IBKR_LIVE_PORTS = new Map([ [4001, "IB Gateway live"], [7496, "TWS live"] ]); function redactAccount(account) { if (account === undefined) return "(none)"; const a = account.trim(); if (a.length <= 3) return "***"; return `${a[0]}***${a.slice(-2)}`; } function describeIbkrConfig(config) { return `IBKR[${config.mode}] ${config.host}:${config.port} clientId=${config.clientId} account=${redactAccount(config.account)}`; } export { IbkrContractError, IbkrConnectionError, IBKR_DEFAULT_HOST, IBKR_DEFAULT_PORT, IBKR_DEFAULT_CLIENT_ID, IBKR_DEFAULT_MODE, resolveIbkrConfig, classifyIbkrAccount, IBKR_LIVE_PORTS, redactAccount, describeIbkrConfig };