Anchor payouts to the trading day the cycle finishes, not the report day

A completed cycle's payout now lands on its last traded day (requestable
after that session's close) instead of whatever day the projection ran —
a Friday finish viewed over the weekend stayed pinned to Sunday. Also
stop inserting the platform's stale weekend realizedPnL as a Sat/Sun
P&L row, which fabricated an extra qualifying day and made accounts one
day short look already complete.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-13 14:39:14 -05:00
co-authored by Claude Fable 5
parent 1996937ef2
commit 9597db08f7
+20 -10
View File
@@ -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;
}
}