PerpForge
Get started

Concept · Survival & ruin

Bankruptcy

A (strategy, symbol) pair stops trading when its equity reaches or falls below a configured floor. This is the simulator's account-survival model: the moment that closes the gap between "the strategy stopped making money" and "the account that would have funded the strategy no longer exists."

Bankruptcy

A (strategy, symbol) pair stops trading when its equity reaches or falls below a configured floor. This is the simulator's account-survival model: the moment that closes the gap between "the strategy stopped making money" and "the account that would have funded the strategy no longer exists."

In plain English

A real trading account can run out of money. Once it does, no further trades happen — not because the strategy stopped firing signals, but because there is nothing left to size a position from. Before phase 121.1, the simulator did not enforce this: equity could be driven arbitrarily negative by accumulated losses, and the strategy would keep entering trades as long as MIN_POSITION_USD = $1 of margin could be carved out.

Bankruptcy is the missing constraint. After phase 121.1, each (strategy, symbol) pair carries an equityBankruptcyFloor. When a closing trade's debit drives the pair's equity to or below the floor, the pair is marked bankrupt — that trade is the last one for that pair, and every subsequent entry signal is recorded with fillStatus: 'account_bankrupt' instead of opening a position.

The default floor is $0 (true zero-equity bust). The wire contract also accepts a percent-of-initial-equity string (e.g. '20%'), which models psychological capitulation — a real trader typically stops adding margin long before equity literally hits zero.

UI note (2026-05-18): The Global Settings input in the frontend currently accepts dollars only — the percent-string form remains a valid wire shape but can only be set programmatically (e.g. via a direct PATCH /api/simulation-settings call) until a dedicated dollar/percent toggle is added to the UI.

When it triggers

Bankruptcy is a consequence of a normal exit signal, not a separate force-close path (D-09 / D-09a).

  1. A position is open. Price moves against it.
  2. A normal exit fires (condition, take_profit, stop_loss, flip, or liquidation).
  3. computePnl() resolves the trade's true PnL — the simulator does not clip the loss to land cleanly on the floor. The recorded finalEquity may slightly overshoot (sit just below the configured floor or even below zero).
  4. EquityLedger.apply(strategyId, symbol, netPnl) returns { newEquity, bankrupted: true } because the apply call crossed the floor.
  5. The triggering trade is tagged exit_reason='bankruptcy'.
  6. The pair is now bankrupt. Subsequent entry signals on this (strategyId, symbol) are written with fillStatus: 'account_bankrupt' and no position opens.

Cross-symbol open positions are not force-closed when one symbol busts (D-09b). Bankruptcy is keyed by (strategyId, symbol) exactly like the EquityLedger, mirroring the Phase 107 partitioning invariant. A strategy that trades BTC and ETH can go bust on BTC while ETH continues independently. Portfolio-level bust cascade is explicitly deferred.

What you see

  • Leaderboard: a "Bust" badge appears on bankrupt strategies with the 1-indexed trade number at which the bust occurred. This is visually distinct from "negative-PnL-but-still-trading" — a busted strategy has a hard stop, not just a drawdown.
  • Analytics: bankruptcyTradeIndex on the /api/analytics payload — number for busted pairs, null otherwise.
  • Final equity: finalEquity floors near (not exactly on) the bankruptcy threshold per the D-18 contract — slight overshoot is the trade's true realized PnL, which is the only honest number to record.
  • Trade record: the bust trade carries exit_reason='bankruptcy' in the existing enum; no new column was added (D-10 revised). The full enum is 'condition' | 'take_profit' | 'stop_loss' | 'flip' | 'liquidation' | 'bankruptcy'.

Why $10 min-order-size

minOrderSizeUsd defaults to $10 (D-05) — a behavioral floor that reflects fee+slippage break-even on majors, not venue mechanics. Orderly's per-symbol venue minimums are typically far lower (sub-dollar for BTC), but trading $1 of notional through a real account is not economically meaningful: bid/ask spread alone exceeds expected per-trade edge.

The simulator therefore enforces the fidelity-aligned floor rather than the venue-mechanic floor. When a strategy's computed position size resolves below minOrderSizeUsd, the entry signal is recorded with fillStatus: 'margin_insufficient' and no position opens. This is a different gate from bankruptcy (fillStatus: 'account_bankrupt') — the strategy might recover (equity grows back above the threshold) — but it shrinks the viable strategy count on $200-starting-equity backtests, which is the intended consequence. Per-symbol venue-driven minimums are deferred and would be a fidelity regression, not a future improvement.

What this fleet shows — the absence is the lesson

In the EMA-cross dossier the headline number is a zero: 0 bankruptcies across all 210 strategies, even at 50× and 100× leverage (a position-size multiplier that scales both profit and loss). That sounds reassuring. It is not — it is an artifact of position sizing.

Every row sizes each trade at 5% of current equity. So no single leveraged position can zero the account: a total loss on one trade removes 5% of what is left, not 100% of it. As losses compound, equity asymptotes toward zero — the deepest rows post −98% to −99.9% drawdown (peak-to-trough equity fall) — but it barely reaches zero, so the bankruptcy floor (default $0) is never crossed.

The trap, then, is reading "survived" as "did fine". The simulator's survival binary is generous: "didn't lose ≥50%". The real ruin signal in this dataset is the heavy_loss outcome — a deep, near-total wipe — and 110 of 210 rows land there. A strategy can be heavy_loss, down 98%, riding a 145-trade losing streak (see risk of ruin), and still never trip the bankruptcy gate. The gate didn't fire because the sizing rule kept a sliver of equity alive, not because the strategy was safe.

So in this fleet bankruptcy is the dog that didn't bark. The mechanism is real and modeled; the dataset just never exercises it, which is itself the teaching point — survival is not the same as success.

Cross-links

  • risk-of-ruin — the proxy metric (the max-drawdown-% screen) that estimated this hazard before the simulator modeled it directly.
  • min-order-size — the paused-not-dead counterpart: a per-trade economics floor that skips an entry without ending the account, where bankruptcy ends it for good.
  • simulator-fidelity — §4 ("Equity can go negative") was the original gap; this concept doc is the resolution.
  • liquidation — per-trade catastrophe (margin loss on a single position); bankruptcy is the account-level catastrophe that liquidation cascades produce when uncapped.
  • drawdown — peak-to-trough equity decline; drawdown that exceeds the floor is bankruptcy under the new contract.

Sources

  • .planning/phases/121.1-account-bankruptcy-modeling-and-realistic-margin-floor/121.1-CONTEXT.md (D-01 through D-23)
  • apps/backend/src/evaluation/equity-ledger.tsapply() returns { newEquity, bankrupted } and isBankrupt(strategyId, symbol)
  • apps/backend/src/simulation/config-manager.tsequityBankruptcyFloor and minOrderSizeUsd FIELDS descriptors (both 'rebacktest'-classified)
  • packages/shared/src/index.tsFillStatus enum extended with 'account_bankrupt'

Related concepts

See it in a real result →

Put it to the test

Does your idea have a real edge, or just a big number?

Spawn your variant, run it on the same engine, and read the edge-significance verdict — before you risk real money.

Test your own idea — free →Free account, no card