Payout projection: payoutable same-equity accounts loop without explicit stages

A payoutable account with no withdrawal stages (e.g. MFFU same-equity)
now withdraws its share and re-targets the same equity each cycle instead
of stopping after the first payout. Non-payoutable evals still stop at
one completion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-12 19:04:22 -05:00
co-authored by Claude Opus 4.8
parent 70eab33d1b
commit a6a79bba7e
+10 -1
View File
@@ -380,6 +380,10 @@ export function projectPerfectPath(opts: {
profitTarget: number; consistency: number; minDayPnL: number;
minTradingDays: number; accountSize: number; targetSameEquity: boolean;
withdrawalStages: WithdrawalStage[]; splitEnabled: boolean;
/** True = completing a cycle pays out and the account keeps going
* (withdraw, reset, re-target). False = it just PASSES once (an
* eval) and the projection stops. */
payoutable?: boolean;
};
/** Current-cycle daily P&L (account.dailyPnL). */
cycleDailyPnL: { date: string; pnl: number }[];
@@ -491,12 +495,17 @@ export function projectPerfectPath(opts: {
// balance), capped. Prop payouts come out of profit, never the funded
// principal. Returns true when the projection should stop (no further
// stages).
// A cycle that completes keeps looping when the account has withdrawal
// stages OR is simply payoutable — a same-equity payout account (no
// explicit stages) withdraws its share and re-targets the same equity,
// over and over. Only a non-payoutable eval stops after one completion.
const loops = cfg.withdrawalStages.length > 0 || cfg.payoutable === true;
const finishCycle = (onDate: string, dayPnl: number, stage: number, drain: boolean): boolean => {
const { pct, cap } = stagePayout();
const rawGross = round2(Math.max(0, balance - cfg.accountSize) * pct);
const gross = cap > 0 ? Math.min(rawGross, round2(cap)) : rawGross;
const net = round2(gross * profitSplit);
if (cfg.withdrawalStages.length > 0 && withdrawals < maxWithdrawals && gross > 1) {
if (loops && withdrawals < maxWithdrawals && gross > 1) {
funds.push({ date: onDate, amount: -gross });
balance = round2(balance - gross);
sim = []; // withdrawal excludes its day — fresh cycle