import type { FirmPlatform } from './lib/master-protocol'; import type { KeptRulesWindow } from './lib/kept-rules'; /** When one of an account type's old rule sets applies: accounts whose rules * date is inside [openedAt, closedAt) keep those rules (openedAt null = no * lower bound). `id` is the kept set's own, never an account type id. */ export type { KeptRulesWindow }; export interface AccountConfig { /** Master's account type id — what a manual assignment pins to. */ id: string; label: string; prefix: string; profitTarget: number; consistency: number; minDayPnL: number; minTradingDays: number; accountSize: number; maxLoss: number; // static max drawdown below the starting size (0 = none); with a hard daily loss, decides last stands maxPositionSize: number; // 0 = no limit; positive = max contracts per trade targetSameEquity: boolean; // if true, reduce profitTarget by priorProfit after withdrawal splitEnabled: boolean; // false = big days trade as one unsplit leg instead of random ≥$500 pieces minLegs: number; // big days (> $1,700) must carve at least this many legs (0 = however the carve falls) firstDayStop: boolean; // first cycle day carries a $1,000 account stop - books red and ends minDayStop: boolean; // min-day trades carry a 2x-target stop; next day after a loss rides stopless combineCmeHolidays: boolean; // true = CME holiday sessions count with the next full trading day (Apex-style) withdrawalStages: { profit: number; consistency: number; minTradingDays: number; drain?: boolean; minDayPnL?: number; withdrawalPct?: number; payoutCap?: number }[]; // per-stage fresh profit + consistency (empty = use base values); drain = spam-withdraw stage /** Payout model, from the master's payout settings (absent on old masters): * withdraw withdrawalPct of balance (capped at payoutCap) per cycle, * receive profitSplit of the gross; trailingDrawdown feeds the ruin * probabilities. payoutable=false means completing the target just * PASSES the account (an eval) rather than paying out. */ payoutable?: boolean; withdrawalPct?: number; payoutCap?: number; profitSplit?: number; trailingDrawdown?: number; /** Hard daily loss limit in dollars (0/absent = none): every leg carries a * stop that keeps the firm's day inside it, less a reserve and fees. */ dailyLossLimit?: number; /** The firm trades every calendar day, weekends and CME holidays included. */ tradesEveryDay?: boolean; } /** An account type as the firm listing (/api/firms) carries it: the current * rules, plus the windows of the old rule sets it keeps, so the dashboard can * preview which rules a rules date lands on. */ export interface FirmAccountType extends AccountConfig { /** Sorted by closedAt; empty when every account is on the current rules. */ kept: KeptRulesWindow[]; } export interface FirmConfig { /** Master's firm id (uuid). */ id: string; firm: string; /** Which platform the firm's accounts trade on — decides both which client * drives it and which credential fields the dashboard asks for. */ platform: FirmPlatform; /** Blank when this machine has no credentials for the firm. */ username: string; /** True once the platform's credentials are stored locally — a username + * password (Tradovate, DXTrade) or a TopstepX API key. Firms without them * are shown greyed out with an inline credential form. */ hasCredentials: boolean; /** What the firm trades (lib/instruments.ts): CME roots for futures * firms, its own symbols for DXtrade. */ instruments: string[]; /** 'master' = the catalog's list; 'legacy' = a master from before the * lists, read the old way (this machine's enabled set less bans). */ instrumentsSource: 'master' | 'legacy'; /** The firm's account types, straight from the catalog. Doubles as the * option list for a manual account-type override. */ accounts: FirmAccountType[]; catalogUpdatedAt: string; } export interface AccountState { id: number; name: string; active: boolean; amount: number; realizedPnL: number; daysTraded: number; positionDirection: 'long' | 'short' | null; /** Balance floor from Tradovate's auto-liquidation profile (0 = not set) */ autoLiqThreshold: number; /** Sum of all historical daily P&L entries */ totalProfit: number; /** True when today's realizedPnL has met or exceeded the computed daily target */ targetHit: boolean; /** True when a stop-enabled account booked its day red via its own stop — * the personal DLL. It stands down until the next session's make-back. */ personalDll: boolean; /** True when today's trading finished the current cycle — profit target, * consistency and minimum days all met (cycleCompletion 'today'). Only * the final day; the firm may lock or remove the account afterwards. */ completed: boolean; /** Current stage = 1 + number of withdrawals (negative fund transactions). Stage 1 = fresh account. */ stage: number; /** True when the current stage is a drain stage (spam-withdraw mode). */ stageDrain: boolean; /** Next trading day's target, computed server-side. null when account is dead or config is missing. */ dailyTarget: { amount: number; path: 'first_day' | 'normal_day' | 'reduced_day' } | null; /** Dollars of price movement the next trade may lose before its stop * fires — the tighter of the obfuscation stop and the hard daily-loss * stop, before fees. null = the next trade rides stopless (options off, * big non-first day, or a make-back day after a loss, with no daily * loss limit). */ nextStop: number | null; /** What the firm's day may still lose before a hard daily-loss breach. * null = no daily loss limit (or its day-start balance isn't known yet). */ dllRoom: number | null; /** DXtrade: how the next leg would be sized and how long it is expected to * hold, from today's measured volatility of the last-picked instrument. * null on other platforms, or before the day's first pick. */ holdEstimate: { hours: number; symbol: string; notional: number; /** Notional ÷ account size. */ multiplier: number; volatilityPerDay: number; winProbability: number | null; /** The same leg with no commission, spread or swap. */ winProbabilityNoFees: number | null; /** Expected swap a winning leg pays, added to its take-profit. */ swapReserve: number; /** The broker's leverage on the symbol (5 = 5:1); null when unstated. */ leverage: number | null; /** Expected spread, commissions and swap for the leg. */ cost: number; } | null; /** The next leg rides without a daily-loss stop: a stop-out would leave * the account too close to its max-drawdown floor to trade on. */ lastStand: boolean; /** Actual profit target to hit — max(stage profit target, consistency realTarget = maxDay/consistency). */ effectiveProfitTarget: number | null; /** The current stage's hard daily loss limit (0 = none) and minimum * trading days — a withdrawal stage may set its own. null = unassigned. */ effectiveDailyLossLimit: number | null; effectiveMinTradingDays: number | null; /** Current cycle daily P&L — used for target calculations. */ dailyPnL: { date: string; pnl: number }[]; /** Full P&L history across all cycles — used for calendar and equity curve display. */ fullDailyPnL: { date: string; pnl: number }[]; /** Fund transactions (deposits/withdrawals) — used by the calendar and equity curve. */ fundTransactions: { date: string; amount: number }[]; /** Master account type this account resolved to. null = Unassigned. */ accountTypeId: string | null; accountTypeLabel: string | null; assignmentSource: 'manual' | 'auto' | 'unassigned'; /** The resolved config inlined, so client components need no second lookup. */ accountType: AccountConfig | null; /** Size from the initial funding transaction; null until reports return. */ inferredAccountSize: number | null; /** True when more than one account type matched and updatedAt decided it. */ accountTypeAmbiguous: boolean; /** ISO. Decides which of the type's rule sets the account trades on: one * dated before a firm's rule change keeps the old rules. Defaults to when * the account started (broker's first deposit / registration), else when * this machine first saw it. null = no assignment row yet. */ rulesDate: string | null; /** True when the rules date was set by hand — broker evidence never moves it. */ rulesDateManual: boolean; /** The old rule set the account trades on; null = the type's current * rules. accountType above already carries these rules. */ keptRules: (KeptRulesWindow & { /** Rule fields where the kept set differs from the type's current * rules — raw values; the dashboard formats them. */ differs: { field: string; kept: unknown; now: unknown }[]; }) | null; } export interface FirmState { /** Master's firm id — join state to config on this, not on the name, which * changes the moment someone renames a firm on the master. */ firmId: string; firm: string; connected: boolean; accounts: AccountState[]; } /** One recorded position (GET /api/trades) — Vest's position history plus * what this instance did itself. */ export interface TradeView { positionId: string; accountId: number; accountName: string; symbol: string; side: 'long' | 'short'; quantity: number; openPrice: number | null; closePrice: number | null; /** Realized P&L before fees and funding. */ pnl: number | null; fee: number | null; funding: number | null; /** pnl − fee + funding; null while open (or unknown). */ net: number | null; openedAt: number; closedAt: number | null; /** null: opened before this instance began marking its own trades. */ openedBy: 'bot' | 'outside' | null; /** null while open; else 'bot', 'outside' (closed by hand or another * tool), or the platform's own reason (stop_loss, take_profit, * liquidation, …; user_requested from before marking began). */ closedBy: string | null; }