Drain stages keep the firm's consistency rule

A drain stage zeroed consistency, so the payout was planned (and the
account stopped trading) as soon as the min days were booked — even when
the cycle's biggest day still exceeded the firm's ratio (Topstep holds
its 40% through the drain). Carry stage.consistency into the effective
config and keep booking drain days until the cycle total covers
maxDay / consistency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-16 05:43:55 -05:00
co-authored by Claude Fable 5
parent 1bf8ba8693
commit 97947bffde
+18 -3
View File
@@ -3,7 +3,9 @@ export interface WithdrawalStage {
consistency: number; consistency: number;
minTradingDays: number; minTradingDays: number;
/** Spam-withdraw stage: no profit target — book minTradingDays qualifying /** Spam-withdraw stage: no profit target — book minTradingDays qualifying
* days of ~minDayPnL (tiny MNQ trades), then wait for the withdrawal. */ * days of ~minDayPnL (tiny MNQ trades), then wait for the withdrawal.
* The stage's consistency still applies: days keep booking until the
* cycle's biggest day fits inside that share of the cycle total. */
drain?: boolean; drain?: boolean;
/** Drain stages only: the per-day booked amount (default $5; randomized /** Drain stages only: the per-day booked amount (default $5; randomized
* the fixed $4–$15 half-dollar grid by drainDayAmount; this field is * the fixed $4–$15 half-dollar grid by drainDayAmount; this field is
@@ -61,7 +63,10 @@ export function resolveEffectiveConfig(
if (stage.drain === true) { if (stage.drain === true) {
return { return {
profitTarget: 0, profitTarget: 0,
consistency: 0, // The firm's consistency rule doesn't pause for the drain
// (Topstep still holds its ratio at payout), so carry the
// stage's value instead of zeroing it.
consistency: stage.consistency,
minTradingDays: stage.minTradingDays || minTradingDays, minTradingDays: stage.minTradingDays || minTradingDays,
// Drain amounts come from the fixed $4–$15 grid, so the // Drain amounts come from the fixed $4–$15 grid, so the
// qualifying bar sits at the grid floor — every draw counts. // qualifying bar sits at the grid floor — every draw counts.
@@ -796,7 +801,17 @@ export function computeDayPlan(
const qualifying = minDayPnL > 0 const qualifying = minDayPnL > 0
? priorDays.filter((d) => d.pnl >= minDayPnL) ? priorDays.filter((d) => d.pnl >= minDayPnL)
: priorDays.filter((d) => d.pnl !== 0); : priorDays.filter((d) => d.pnl !== 0);
const dayTarget = qualifying.length >= minTradingDays // The stage's consistency rule outlives the profit target: the cycle's
// biggest day must fit inside `consistency` of the cycle total, so the
// drain keeps booking days past the min-day count until the total
// covers maxDay / consistency.
let consistencyShort = false;
if (consistency > 0 && consistency < 1 && priorDays.length > 0) {
const maxDay = Math.max(0, ...priorDays.map((d) => d.pnl));
const total = priorDays.reduce((s, d) => s + d.pnl, 0);
consistencyShort = maxDay > 0 && total < round2(maxDay / consistency) - 0.005;
}
const dayTarget = qualifying.length >= minTradingDays && !consistencyShort
? 0 ? 0
: drainDayAmount(accountId, tradeDate); : drainDayAmount(accountId, tradeDate);
return { return {