Account calendar: reach probabilities, 2-payout horizon, outline cells, same-day payouts, real drawdown floors
- projectPerfectPath books each cycle's payout ON its final trading day and anchors ruin probabilities to the platform's real liquidation floor (Tradovate auto-liq) when reported, else the configured trailing drawdown with the maxLoss/$2,000 fallback - Account calendar shows the cumulative reach % on every projected day, caps the projection at two payouts ahead, and renders potential days as dashed outlines instead of shaded cells Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
52d51afebb
commit
9b2d823210
+51
-22
@@ -398,6 +398,11 @@ export function projectPerfectPath(opts: {
|
||||
drawdown?: number;
|
||||
/** Current cycle's end-of-day equity-profit high-water mark. */
|
||||
startHigh?: number;
|
||||
/** The account's REAL current liquidation floor on the equity-profit
|
||||
* scale (platform floor − account size), when the platform reports
|
||||
* one (Tradovate auto-liq). The modeled trail never sits below it,
|
||||
* so today's room is exact and only tightens going forward. */
|
||||
startThreshold?: number;
|
||||
};
|
||||
}): ProjectedDay[] {
|
||||
const { accountId, cfg } = opts;
|
||||
@@ -424,10 +429,16 @@ export function projectPerfectPath(opts: {
|
||||
// telescope, so a whole day survives with exactly room/(room + target).
|
||||
let high = Math.max(0, opts.payout?.startHigh ?? 0, balance - cfg.accountSize);
|
||||
if (funds.some((f) => f.amount < 0)) high = Math.max(high, dd);
|
||||
// The platform's real floor (when reported) is the baseline the modeled
|
||||
// trail can never sit below — a floor that already trailed higher than
|
||||
// our history explains stays exact.
|
||||
const startThreshold = opts.payout?.startThreshold;
|
||||
let prob = 1;
|
||||
const dayProb = (dayTarget: number): number => {
|
||||
if (dd <= 0 || dayTarget <= 0) return 1;
|
||||
const threshold = high >= dd ? 0 : high - dd;
|
||||
if (dayTarget <= 0) return 1;
|
||||
if (dd <= 0 && startThreshold === undefined) return 1;
|
||||
const modeled = dd > 0 ? (high >= dd ? 0 : high - dd) : -Infinity;
|
||||
const threshold = Math.max(modeled, startThreshold ?? -Infinity);
|
||||
const room = (balance - cfg.accountSize) - threshold;
|
||||
if (room <= 0) return 0;
|
||||
return room / (room + dayTarget);
|
||||
@@ -485,32 +496,35 @@ export function projectPerfectPath(opts: {
|
||||
|
||||
const daysNeeded = eff.minTradingDays > qualifyingDays(eff.minDayPnL, date);
|
||||
|
||||
if (plan.dayTarget <= 0 && !daysNeeded) {
|
||||
// Cycle complete — nothing left to trade. 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.
|
||||
// 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 withdrawal = -gross;
|
||||
if (cfg.withdrawalStages.length > 0 && withdrawals < maxWithdrawals && withdrawal < -1) {
|
||||
funds.push({ date, amount: withdrawal });
|
||||
balance = round2(balance + withdrawal);
|
||||
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: 0, stage, drain: eff.drain, withdrawal, prob,
|
||||
gross, net: round2(gross * profitSplit),
|
||||
});
|
||||
continue;
|
||||
out.push({ date, pnl: dayPnl, stage, drain, withdrawal: -gross, prob, gross, net });
|
||||
return false;
|
||||
}
|
||||
out.push({
|
||||
date, pnl: 0, stage, drain: eff.drain, payoutReady: true, prob,
|
||||
gross, net: round2(gross * profitSplit),
|
||||
});
|
||||
break;
|
||||
out.push({ date, pnl: dayPnl, stage, drain, payoutReady: true, prob, gross, net });
|
||||
return true;
|
||||
};
|
||||
|
||||
if (plan.dayTarget <= 0 && !daysNeeded) {
|
||||
// The cycle was already complete before this day — the pending
|
||||
// payout lands on the first projected day.
|
||||
if (finishCycle(0, eff.drain)) break;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extra days (min-day 0, days still owed) book a token dollar so the
|
||||
@@ -520,7 +534,22 @@ export function projectPerfectPath(opts: {
|
||||
sim.push({ date, pnl });
|
||||
balance = round2(balance + pnl);
|
||||
high = Math.max(high, balance - cfg.accountSize);
|
||||
out.push({ date, pnl, stage, drain: eff.drain, prob });
|
||||
|
||||
// Did this day complete the cycle? Then it IS the payout day.
|
||||
const nd = nextTradingDate(date);
|
||||
const eff2 = effectiveNow();
|
||||
const plan2 = computeDayPlan(
|
||||
accountId, nd, eff2.profitTarget, eff2.consistency, sim, eff2.minDayPnL,
|
||||
eff2.minTradingDays, balance - cfg.accountSize, 0, cfg.splitEnabled, eff2.drain,
|
||||
);
|
||||
const doneAfter = plan2 !== null
|
||||
&& plan2.dayTarget <= 0
|
||||
&& eff2.minTradingDays <= qualifyingDays(eff2.minDayPnL, nd);
|
||||
if (doneAfter) {
|
||||
if (finishCycle(pnl, eff.drain)) break;
|
||||
} else {
|
||||
out.push({ date, pnl, stage, drain: eff.drain, prob });
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
|
||||
Reference in New Issue
Block a user