Files
autofirmer-expanded/lib/master-protocol.ts
T
Brandon LiandClaude Opus 5.5 acb8879575 Reporter: name the accounts whose final day completed their cycle
cycleCompletion() says whether today's P&L finished the current cycle
(profit target, consistency and minimum days all met) — projectPerfectPath's
completion probe run without and with today. The reporter counts those as
`completed` (a subset of targetHit) and sends their names, so the master
can keep an account a firm erases after it passes. Checked ahead of the
lockout branch: a firm locking a passed account is no DLL loss. The local
dashboard shows them with a purple Completed pill.

Because the master keeps a completion for good, doubtful reads stay
'none': weekends, a Monday with a weekend-dated row, the first minutes
after the 5 PM roll, and history that hasn't freshly loaded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 12:28:41 -05:00

235 lines
11 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;
/** Of the finished accounts, how many booked the day via their own
* obfuscation stop (personal DLL) rather than a genuine loss. */
personalDll?: number;
/** Of targetHit: accounts whose day finished the current cycle — profit
* target, consistency and minimum days all met once today's P&L booked
* (the final day). Optional: older instances omit it. */
completed?: number;
/** The accounts behind `completed`, by name, so the master can keep them
* after a firm erases a passed account. date = the trade date it
* completed on; stage = 1 + withdrawals taken (its meaning depends on the
* account type); kind: 'pass' = an eval passing, 'payout' = a funded
* cycle ready to withdraw, 'drain' = a drain stage's days booked.
* Optional: older instances omit it. */
completedAccounts?: { name: string; date: string; stage: number; kind: 'pass' | 'payout' | 'drain' }[];
/** 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>;
/** Live accounts by the trading day they are on today (1 = first day of
* the current cycle, counting traded days before today's trade date),
* with how many of them hit today's target; completed = how many of those
* hits finished the cycle. Lets the master show "Day 1 3/5 · Day 2 1/4".
* Optional: older instances omit it. */
dayStats?: Record<number, { total: number; targetHit: number; completed?: 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;
/** This account's projected payout chain on the perfect path: one
* entry per future withdrawal, with the gross amount
* (min(withdrawalPct × balance, cap)), the net received
* (gross × profitSplit), and prob = the CUMULATIVE probability of
* reaching that payout under the fair-walk ruin model. Optional so
* an older master simply ignores it. */
futurePayouts?: {
date: string; gross: number; net: number; prob: number;
stage: number; drain?: boolean;
}[];
}[];
}
export interface FirmStats {
firm: string;
totalAccounts: number;
accountsTraded: number;
inTrade: number;
/** Day outcomes, as on AccountTypeStats. */
targetHit?: number;
partial?: number;
loss?: number;
personalDll?: number;
/** Of targetHit: accounts that finished their cycle today. Optional. */
completed?: 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;
}
/** The build this instance is running — its checkout's HEAD commit. The
* commit time is what orders two different versions. Optional in every
* message so an older instance or master simply ignores it. */
export interface BuildVersion {
commit: string;
time: string;
}
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 ≥$500 pieces. */
splitEnabled: boolean;
/** Big days (> the $1,700 split threshold) must carve at least this many
* legs (0 = however the carve falls). Smaller days always trade as one
* leg; clamped to what ≥$500 pieces make possible. */
minLegs: number;
/** Obfuscation, part 1: the first trading day of a cycle carries a $1,000
* account stop — the day books red and ends instead of riding. */
firstDayStop: boolean;
/** Obfuscation, part 2: min-day (small) trades carry a 2×-target stop; a
* stopped-out day books, and the next day rides stopless. */
minDayStop: 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;
/** Per-stage config after the Nth withdrawal; the last stage repeats.
* drain=true marks a spam-withdraw stage: no profit target — book
* minTradingDays qualifying days of ~minDayPnL (default $5, randomized
* $5–$15, traded as tiny MNQ legs), then wait for the next withdrawal.
* withdrawalPct/payoutCap override the type's payout defaults per stage. */
withdrawalStages: { profit: number; consistency: number; minTradingDays: number; drain?: boolean; minDayPnL?: number; withdrawalPct?: number; payoutCap?: number }[];
/** Payout-projection settings, owned by the master's payout calendar but
* consumed here to compute each account's future payout chain. Optional:
* an older master doesn't send them. */
payoutable?: boolean;
/** Trailing drawdown in dollars for ruin-model probabilities. 0 = unset. */
trailingDrawdown?: number;
/** Legacy static payout estimate — superseded by the computed chain. */
expectedPayout?: number;
/** Share of the balance withdrawn when a cycle completes (default 0.5). */
withdrawalPct?: number;
/** Cap on the gross withdrawal in dollars. 0 = no cap. */
payoutCap?: number;
/** Share of the gross the person actually receives (default 0.9). */
profitSplit?: 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' }
/** Push a fresh settings/credentials backup right now. */
| { kind: 'backup-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; version?: BuildVersion }
| { type: 'report'; firms: FirmStats[]; scheduler: SchedulerState; capabilities?: InstanceCapabilities; version?: BuildVersion }
| { type: 'ack'; id: string; ok: boolean; result?: unknown; error?: string };