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
JavaScript
// 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 };