/** * Wire protocol for the control link to the master dashboard. * * Mirror of the block at the bottom of `autotrader-master/types.ts`, which is * the source of truth. The two repos deploy independently, so change both * together — and only ever by adding, since an instance and a master can be * running different versions of this file at the same time. */ export interface AccountTypeStats { accountTypeId: string | null; label: string; accountSize: number; totalAccounts: number; accountsTraded: number; inTrade: number; /** Of the accounts that finished trading today (traded, no open position): * how many ended with the day's profit target hit, in profit but short of * the target, or at a realized loss (an account locked out at the daily * loss limit counts as a loss). Optional so an older master simply * ignores them. */ targetHit?: number; partial?: number; loss?: number; /** Live accounts by trading days completed toward the current target: * {daysTraded: accountCount}. Feeds the master's payout calendar, so * accounts that are dead, DLL-locked, or in the red are left out — the * calendar only projects accounts still on the winning path. Optional so * an older master simply ignores it. */ daysHist?: Record; /** One entry per live account (negative ones included, dead ones not): * its actual progress toward the current target, so the master can * simulate the real required profit instead of assuming the winning * path. A withdrawal resets the path, so days/maxDay/sum cover only the * current cycle (client.dailyPnL is already the active-cycle slice), * while profit stays the full equity profit. withdrawals > 0 tells the * master the trail locked long ago; target/consistency/minDays carry * this cycle's effective values from resolveEffectiveConfig. Optional so * an older master simply ignores it. */ payoutStates?: { days: number; profit: number; maxDay: number; sum: number; high: number; withdrawals?: number; target?: number; consistency?: number; minDays?: number; /** Trade date (YYYY-MM-DD) of the account's most recent traded day — * after the 5 PM CT roll a day booked on tomorrow's trade date is * already counted in `days`. */ lastDay?: string; }[]; } export interface FirmStats { firm: string; totalAccounts: number; accountsTraded: number; inTrade: number; /** Day outcomes, as on AccountTypeStats. */ targetHit?: number; partial?: number; loss?: number; /** Per account type, summing to the firm's own totals. Optional so a master * on an older build simply ignores it. */ accountTypes?: AccountTypeStats[]; } export interface SchedulerState { running: boolean; action: 'Buy' | 'Sell' | 'Auto'; symbol: string; lastRun: string | null; intervalSeconds: number; stopAfterAll?: boolean; /** Today's random session-open delay in minutes (1–20), drawn once per * Central-time day. Optional so an older master simply ignores it. */ sessionOpenDelayMin?: number; } export interface AutomationSummary { key: string; label: string; firm: string; inputs: { id: string; label: string; min: number; max: number; default: number }[]; } export interface InstanceCapabilities { symbols: string[]; automations: AutomationSummary[]; runnerOnline: boolean; } // ── Firm catalog ───────────────────────────────────────────────────────────── // Served by the master's public GET /api/firms/catalog and cached locally by // lib/firm-catalog.ts. Credentials are per-machine and absent from all of it. /** One account type as published by the master. * * The field names match `AccountConfig` in types.ts on purpose — that is what * lets the trading code consume a catalog account type wherever it used to * read a local account_configs row. */ export interface CatalogAccountType { id: string; firmId: string; label: string; prefix: string; accountSize: number; profitTarget: number; consistency: number; minDayPnL: number; minTradingDays: number; maxLoss: number; maxPositionSize: number; targetSameEquity: boolean; /** False = big days trade as one unsplit leg instead of random ≥$800 pieces. */ splitEnabled: boolean; /** True = CME holiday sessions count with the next full trading day, the way * firms that follow CME clearing report them (e.g. Apex: Sun/Mon/Tue around * a Monday holiday is ONE trading day, recorded on the Tuesday). */ combineCmeHolidays: boolean; withdrawalStages: { profit: number; consistency: number; minTradingDays: number }[]; /** ISO. Ties between two matching account types are broken by this. */ updatedAt: string; } export interface CatalogFirm { id: string; name: string; /** Which platform the firm's accounts trade on. Only 'tradovate' firms get * a client here; anything else is synced but sits idle until its trading * logic exists. Older masters omit the field — treated as 'tradovate'. */ platform: 'tradovate' | 'topstepx'; bannedSymbols: string[]; accountTypes: CatalogAccountType[]; updatedAt: string; } export interface FirmCatalogPayload { version: number; updatedAt: string; firms: CatalogFirm[]; } export interface FirmCatalogUnchanged { version: number; updatedAt: string; unchanged: true; } export type Command = | { kind: 'trade'; action: 'Buy' | 'Sell' | 'Auto'; symbol: string; stopAfterAll?: boolean } | { kind: 'stop-trading' } | { kind: 'autobuyer-run'; automationId: string; inputs?: Record } | { kind: 'report-now' }; export type MasterMessage = /** personId is the person's stable uuid; the instance stores it and * presents it on later hellos, which is what makes a rename follow the * person. */ | { type: 'welcome'; personId?: string } | { type: 'command'; id: string; command: Command }; export type InstanceMessage = | { type: 'hello'; instance: string; id?: string; capabilities: InstanceCapabilities } | { type: 'report'; firms: FirmStats[]; scheduler: SchedulerState; capabilities?: InstanceCapabilities } | { type: 'ack'; id: string; ok: boolean; result?: unknown; error?: string };