diff --git a/app/accounts/[id]/page.tsx b/app/accounts/[id]/page.tsx index 1a89859..e076541 100644 --- a/app/accounts/[id]/page.tsx +++ b/app/accounts/[id]/page.tsx @@ -592,7 +592,7 @@ export default function AccountPage() {

Account Blown

- Balance ${fmt(account.amount)} has breached the Tradovate auto-liquidation floor + Balance ${fmt(account.amount)} has breached the auto-liquidation floor {liqFloor != null ? ` of $${fmt(liqFloor)}` : ''}.

diff --git a/lib/topstepx-class.ts b/lib/topstepx-class.ts index 03070d2..bb16541 100644 --- a/lib/topstepx-class.ts +++ b/lib/topstepx-class.ts @@ -584,12 +584,24 @@ export class TopstepXClient implements PlatformClient { * accounts are skipped: the platform has already retired them. */ private updateMllFloor(account: PlatformAccount): void { - if (!account.active) { this.autoLiqThresholds[account.id] = 0; return; } + // Deliberately computed for canTrade=false accounts too: when Topstep + // liquidates a breach and disables the account, the floor must SURVIVE + // so the dashboards show it as blown (☠), not merely inactive. Retired + // accounts without an assigned type fall out below via dd = 0. const dd = getAccountConfig(account.id).accountType?.trailingDrawdown ?? 0; const history = this.fullDailyPnL[account.id]; const cash = this.accountCashBalances[account.id]; if (!(dd > 0) || !history || !cash) { this.autoLiqThresholds[account.id] = 0; return; } + // Only move the floor on SETTLED books: never mid-trade (the fill + // hasn't landed — the dead check must not fire off half-processed + // state), and never while the balance disagrees with the books by + // more than fee noise (a fill or fund event is still being absorbed; + // reconcileFunds or the next sync settles it). The previous floor + // stands until then, so `balance ≤ floor` can only come true off a + // landed, booked fill. + if (this.positions[account.id]) return; + const today = currentTradeDateCT(); const fees = this.dailyFees[account.id] ?? new Map(); @@ -608,6 +620,9 @@ export class TopstepXClient implements PlatformClient { const booked = dates.reduce((s, d) => s + events.get(d)!, 0); const todayFund = (this.fundTransactions[account.id] ?? []).find((f) => f.date === today)?.amount ?? 0; const residual = Math.round((cash.amount - cash.realizedPnL - todayFund - booked) * 100) / 100; + // Books not settled (an unabsorbed fill or an unbooked fund event) — + // keep the previous floor rather than skewing the curve with it. + if (Math.abs(residual) >= 200) return; const totalFees = dates.reduce((s, d) => s + (fees.get(d) ?? 0), 0); let bal = 0;