Files
autofirmer-expanded/types.ts
T
Brandon LiandClaude Opus 5.5 4add379c68 Dashboard: a stage's own daily loss limit shows its stop, its personal DLL and its min days
Since a withdrawal stage can carry its own daily loss limit, the engine
places that stop, but the dashboard and report still asked the type's base
limit whether the bot stops at all: with a base of 0 and no obfuscation
options the row showed no "stop $X", and a stopped-out red day read "Flat"
(and counted as a loss, not a personal DLL, in the report). botStopsActive
now takes the stage-resolved config. The state also carries the stage's
daily loss limit and min trading days, which the account page and the Days
column show instead of the type's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 14:15:02 -05:00

210 lines
10 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.
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;
}