Files
autofirmer-expanded/lib/master-protocol.ts
T
Brandon LiandClaude Fable 5 620ab54d42 Report each account's last traded trade date
Sessions roll at 5 PM CT, so after tonight's open a day booked on
tomorrow's trade date is already in the day count — lastDay is what lets
the master tell those accounts apart from ones that have not traded the
current session yet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-10 18:51:59 -05:00

163 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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<number, number>;
/** 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<string, number> }
| { 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 };