diff --git a/lib/trading-logic.ts b/lib/trading-logic.ts index 712f9d9..c38be9f 100644 --- a/lib/trading-logic.ts +++ b/lib/trading-logic.ts @@ -428,8 +428,14 @@ export function projectPerfectPath(opts: { const dd = Math.max(0, opts.payout?.drawdown ?? 0); const today = currentTradeDateCT(); + // A weekend "today" (Saturday, or Sunday before the evening open) has no + // session — the platform's realizedPnL is the LAST session's stale number, + // already booked in cycleDailyPnL under its own date. Adding it as a + // weekend row would fabricate an extra qualifying day. + const todayDow = new Date(`${today}T00:00:00Z`).getUTCDay(); + const todayHasSession = todayDow !== 0 && todayDow !== 6; let sim = opts.cycleDailyPnL.filter((d) => d.date !== today); - if (opts.realizedPnL !== 0) sim = [...sim, { date: today, pnl: opts.realizedPnL }]; + if (todayHasSession && opts.realizedPnL !== 0) sim = [...sim, { date: today, pnl: opts.realizedPnL }]; else sim = [...sim]; const funds = [...opts.fundTransactions]; let balance = opts.balance; @@ -479,8 +485,7 @@ export function projectPerfectPath(opts: { // Book the rest of today silently — the perfect path finishes today too. // Skipped when "today" is a weekend date (Saturday, or Friday evening // after the roll): there is no session to finish. - const todayDow = new Date(`${today}T00:00:00Z`).getUTCDay(); - if (todayDow !== 0 && todayDow !== 6) { + if (todayHasSession) { const eff = effectiveNow(); const plan = computeDayPlan( accountId, today, eff.profitTarget, eff.consistency, sim, eff.minDayPnL, @@ -497,9 +502,9 @@ export function projectPerfectPath(opts: { } } - // Fires the payout ON the given date — the day the cycle's final trading - // day books (or today, when the cycle already stands complete), the - // payout becomes requestable. The withdrawal takes the completed stage's + // Fires the payout ON the given date — the trading day the cycle's final + // required day books (the last traded day, when the cycle already stands + // complete), the payout becomes requestable after that session. The withdrawal takes the completed stage's // share of the EQUITY PROFIT (for a $0-start account that is the whole // balance), capped. Prop payouts come out of profit, never the funded // principal. Returns true when the projection should stop (no further @@ -527,9 +532,10 @@ export function projectPerfectPath(opts: { return true; }; - // Already complete right now — target hit and min days done? Then the - // payout is requestable TODAY, whatever day of the week it is, not on - // the next session. + // Already complete right now — target hit and min days done? The payout + // anchors to the trading day the cycle finished: the last traded day, + // requestable after that session's close. Never "today" — a projection + // computed over the weekend must not slide a Friday finish to Sunday. { const eff = effectiveNow(); const probe = nextTradingDate(today); @@ -539,7 +545,11 @@ export function projectPerfectPath(opts: { ); if (plan && plan.dayTarget <= 0 && eff.minTradingDays <= qualifyingDays(eff.minDayPnL, probe)) { const stage = 1 + funds.filter((f) => f.amount < 0).length; - if (finishCycle(today, 0, stage, eff.drain)) return out; + const traded = sim.filter((d) => d.pnl !== 0); + const doneOn = traded.length > 0 + ? traded.reduce((a, b) => (a.date > b.date ? a : b)).date + : today; + if (finishCycle(doneOn, 0, stage, eff.drain)) return out; } }