projectPerfectPath and endedCycleCompletion counted every withdrawal ever made, so a Vest funded account that had claimed on any earlier day was projected at stage 2's $3,000 forever (labelled stage 3, 4, ...), and a day's stage-1 cycle that the auto-claim ended before the reporter saw it was judged against $3,000 and never reported. The projection now takes the stage inputs: a type whose stages reset each day trades today on today's payouts and every projected day from the base stage. The catch-up judges a same-day type's ended cycle on its day's base stage; the stage it reports stays the payout's number among the account's withdrawals, which the master clears the entry by. Other types project exactly as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
213 lines
10 KiB
TypeScript
213 lines
10 KiB
TypeScript
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 stage is picked by the payouts since the firm's last daily reset,
|
||
* and every new day starts back at the base stage (Vest). */
|
||
stagesSameDay?: boolean;
|
||
/** 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;
|
||
}
|