Payout projection: a completed cycle is requestable today, not next session
When target and min trading days are already satisfied, the payout fires on the current trade date instead of being pushed to the next session — so an account that has already hit its target with its days in shows the payout as requestable now. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
bf56a76a6d
commit
70eab33d1b
+45
-28
@@ -484,6 +484,47 @@ 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
|
||||||
|
// 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
|
||||||
|
// stages).
|
||||||
|
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) {
|
||||||
|
funds.push({ date: onDate, amount: -gross });
|
||||||
|
balance = round2(balance - gross);
|
||||||
|
sim = []; // withdrawal excludes its day — fresh cycle
|
||||||
|
withdrawals++;
|
||||||
|
high = Math.max(high, dd); // a payout locks the trail at breakeven
|
||||||
|
out.push({ date: onDate, pnl: dayPnl, stage, drain, withdrawal: -gross, prob, gross, net });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.push({ date: onDate, pnl: dayPnl, stage, drain, payoutReady: true, prob, gross, net });
|
||||||
|
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.
|
||||||
|
{
|
||||||
|
const eff = effectiveNow();
|
||||||
|
const probe = nextTradingDate(today);
|
||||||
|
const plan = computeDayPlan(
|
||||||
|
accountId, probe, eff.profitTarget, eff.consistency, sim, eff.minDayPnL,
|
||||||
|
eff.minTradingDays, balance - cfg.accountSize, 0, cfg.splitEnabled, eff.drain,
|
||||||
|
);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let date = today;
|
let date = today;
|
||||||
for (let step = 0; step < horizon; step++) {
|
for (let step = 0; step < horizon; step++) {
|
||||||
date = nextTradingDate(date);
|
date = nextTradingDate(date);
|
||||||
@@ -497,34 +538,10 @@ export function projectPerfectPath(opts: {
|
|||||||
|
|
||||||
const daysNeeded = eff.minTradingDays > qualifyingDays(eff.minDayPnL, date);
|
const daysNeeded = eff.minTradingDays > qualifyingDays(eff.minDayPnL, date);
|
||||||
|
|
||||||
// Fires the payout ON the given date — the day the cycle's final
|
|
||||||
// trading day books, the payout becomes requestable. 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 stages).
|
|
||||||
const finishCycle = (dayPnl: 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) {
|
|
||||||
funds.push({ date, amount: -gross });
|
|
||||||
balance = round2(balance - gross);
|
|
||||||
sim = []; // withdrawal excludes its day — fresh cycle
|
|
||||||
withdrawals++;
|
|
||||||
high = Math.max(high, dd); // a payout locks the trail at breakeven
|
|
||||||
out.push({ date, pnl: dayPnl, stage, drain, withdrawal: -gross, prob, gross, net });
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
out.push({ date, pnl: dayPnl, stage, drain, payoutReady: true, prob, gross, net });
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (plan.dayTarget <= 0 && !daysNeeded) {
|
if (plan.dayTarget <= 0 && !daysNeeded) {
|
||||||
// The cycle was already complete before this day — the pending
|
// The cycle completed mid-projection with nothing left to trade —
|
||||||
// payout lands on the first projected day.
|
// the pending payout lands on this day.
|
||||||
if (finishCycle(0, eff.drain)) break;
|
if (finishCycle(date, 0, stage, eff.drain)) break;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,7 +564,7 @@ export function projectPerfectPath(opts: {
|
|||||||
&& plan2.dayTarget <= 0
|
&& plan2.dayTarget <= 0
|
||||||
&& eff2.minTradingDays <= qualifyingDays(eff2.minDayPnL, nd);
|
&& eff2.minTradingDays <= qualifyingDays(eff2.minDayPnL, nd);
|
||||||
if (doneAfter) {
|
if (doneAfter) {
|
||||||
if (finishCycle(pnl, eff.drain)) break;
|
if (finishCycle(date, pnl, stage, eff.drain)) break;
|
||||||
} else {
|
} else {
|
||||||
out.push({ date, pnl, stage, drain: eff.drain, prob });
|
out.push({ date, pnl, stage, drain: eff.drain, prob });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user