Vest: projected days trade the next stage after each payout, as the bot does
runClaims claims a same-day type's target and trades its next stage the same day on the room the claim freed, but the projection paid out once a day, so Vest's payout calendar showed $1,500 a day instead of $1,500 then $3,000. Each payout now counts toward its own date, and the same date is projected again on the next stage, once through the stages a day (the last one repeats). A cycle that stood complete before today counts toward today, when it is claimed. Other types project as before. The account page sums a date's cycles, cuts after the second payout day, and labels a same-day type's past days from that day's own payout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e33e890a83
commit
266100b65c
+25
-20
@@ -600,7 +600,7 @@ export default function AccountPage() {
|
||||
// books exactly its plan, cycles complete, and the configured withdrawal
|
||||
// fires the day a cycle finishes. Same planner the trade path runs, so the
|
||||
// numbers (bumps and drain amounts included) are what would actually be
|
||||
// aimed for. Shown up to TWO payouts ahead.
|
||||
// aimed for. Shown up to TWO payout days ahead.
|
||||
const rawProjection = (cfg && !isDead) ? projectPerfectPath({
|
||||
accountId: account.id,
|
||||
cfg,
|
||||
@@ -610,7 +610,8 @@ export default function AccountPage() {
|
||||
realizedPnL: account.realizedPnL,
|
||||
// A type whose stages reset each day: its state stage counts today's payouts.
|
||||
stageInputs: { stagesSameDay: cfg.stagesSameDay === true, sameDayWithdrawals: account.stage - 1 },
|
||||
maxWithdrawals: 2,
|
||||
// Such a type pays out once per stage on each of those days.
|
||||
maxWithdrawals: cfg.stagesSameDay === true ? 2 * (1 + cfg.withdrawalStages.length) : 2,
|
||||
payout: {
|
||||
withdrawalPct: cfg.withdrawalPct,
|
||||
payoutCap: cfg.payoutCap,
|
||||
@@ -626,23 +627,21 @@ export default function AccountPage() {
|
||||
: undefined,
|
||||
},
|
||||
}) : [];
|
||||
// Cut the projection at the second payout — days beyond it are noise here.
|
||||
const secondPayoutIdx = (() => {
|
||||
let seen = 0;
|
||||
for (let i = 0; i < rawProjection.length; i++) {
|
||||
const p = rawProjection[i];
|
||||
if (p.withdrawal !== undefined || p.payoutReady) {
|
||||
seen++;
|
||||
if (seen >= 2 || p.payoutReady) return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
})();
|
||||
const projection = secondPayoutIdx >= 0 ? rawProjection.slice(0, secondPayoutIdx + 1) : rawProjection;
|
||||
// Cut the projection after the second payout day — days beyond it are
|
||||
// noise here. The path's dates never go back, and a payoutReady entry is
|
||||
// always its last.
|
||||
const payoutDays = [...new Set(rawProjection.filter((p) => p.withdrawal !== undefined || p.payoutReady).map((p) => p.date))];
|
||||
const projection = payoutDays.length >= 2 ? rawProjection.filter((p) => p.date <= payoutDays[1]) : rawProjection;
|
||||
// A payout day is the cycle's FINAL trading day — it can carry both the
|
||||
// day's booked P&L and the withdrawal.
|
||||
const projPnlMap = new Map(projection.filter((p) => p.pnl !== 0).map((p) => [p.date, p.pnl]));
|
||||
const projFundMap = new Map(projection.filter((p) => p.withdrawal !== undefined).map((p) => [p.date, p.withdrawal!]));
|
||||
// day's booked P&L and the withdrawal. A same-day type's day can hold
|
||||
// several cycles: their P&L and withdrawals add up, and the day shows
|
||||
// the last stage it traded.
|
||||
const projPnlMap = new Map<string, number>();
|
||||
const projFundMap = new Map<string, number>();
|
||||
for (const p of projection) {
|
||||
if (p.pnl !== 0) projPnlMap.set(p.date, Math.round(((projPnlMap.get(p.date) ?? 0) + p.pnl) * 100) / 100);
|
||||
if (p.withdrawal !== undefined) projFundMap.set(p.date, Math.round(((projFundMap.get(p.date) ?? 0) + p.withdrawal) * 100) / 100);
|
||||
}
|
||||
const projStageMap = new Map(projection.map((p) => [p.date, p.stage]));
|
||||
/** Cumulative probability of the perfect path surviving through each projected day. */
|
||||
const projProbMap = new Map(projection.map((p) => [p.date, p.prob]));
|
||||
@@ -657,14 +656,20 @@ export default function AccountPage() {
|
||||
];
|
||||
const calendarMonths = [...new Set(allCalendarDates.map((d) => d.slice(0, 7)))].sort();
|
||||
|
||||
// Build stage map: stage increments after each withdrawal (negative fund txn)
|
||||
// Build stage map: stage increments after each withdrawal (negative fund txn).
|
||||
// A type whose stages reset each day starts every day on stage 1 and
|
||||
// trades its next stage after a payout that day, as the projected days
|
||||
// show — one fund row a day holds all of that day's payouts, so a day
|
||||
// with one reads stage 2.
|
||||
const sortedWithdrawalDates = fundTransactions
|
||||
.filter((f) => f.amount < 0)
|
||||
.map((f) => f.date)
|
||||
.sort();
|
||||
const stageMap = new Map<string, number>();
|
||||
for (const d of displayPnL) {
|
||||
const stage = sortedWithdrawalDates.filter((wd) => wd < d.date).length + 1;
|
||||
const stage = cfg?.stagesSameDay === true
|
||||
? sortedWithdrawalDates.filter((wd) => wd === d.date).length + 1
|
||||
: sortedWithdrawalDates.filter((wd) => wd < d.date).length + 1;
|
||||
stageMap.set(d.date, stage);
|
||||
}
|
||||
|
||||
|
||||
+23
-6
@@ -534,15 +534,23 @@ export function projectPerfectPath(opts: {
|
||||
};
|
||||
|
||||
// A type whose stages reset each day (Vest) counts only the payouts made
|
||||
// since its day began: today's real ones, and none on a projected day —
|
||||
// the path pays out at most once a day, as the day's last entry, so
|
||||
// every projected day trades the base stage.
|
||||
// since its day began: today's real ones plus the path's own on that
|
||||
// date, so every projected day starts on the base stage.
|
||||
const sameDay = opts.stageInputs?.stagesSameDay === true;
|
||||
/** The path's own payouts, by the date they are claimed on. */
|
||||
const paidOn = new Map<string, number>();
|
||||
/** Payouts before the stage traded on `day`: that day's own for a
|
||||
* same-day type, else every withdrawal so far. */
|
||||
const paidBefore = (day: string): number => (sameDay
|
||||
? (day === today ? Math.max(0, Math.floor(opts.stageInputs?.sameDayWithdrawals ?? 0)) : 0)
|
||||
? (day === today ? Math.max(0, Math.floor(opts.stageInputs?.sameDayWithdrawals ?? 0)) : 0) + (paidOn.get(day) ?? 0)
|
||||
: funds.filter((f) => f.amount < 0).length);
|
||||
/** A same-day type trades its next stage on the date a payout lands
|
||||
* (runClaims claims, then trades again on the room the claim freed) —
|
||||
* once through its stages a day: the last one repeats, and the path
|
||||
* stops there. */
|
||||
const tradesAgain = (day: string): boolean => sameDay
|
||||
&& (paidOn.get(day) ?? 0) > 0
|
||||
&& paidBefore(day) <= cfg.withdrawalStages.length;
|
||||
const effectiveOn = (day: string) => resolveEffectiveConfig(
|
||||
cfg.profitTarget, cfg.consistency, cfg.minTradingDays, cfg.targetSameEquity,
|
||||
cfg.withdrawalStages, 0, funds, cfg.minDayPnL,
|
||||
@@ -604,6 +612,9 @@ export function projectPerfectPath(opts: {
|
||||
balance = round2(balance - gross);
|
||||
sim = []; // withdrawal excludes its day — fresh cycle
|
||||
withdrawals++;
|
||||
// A cycle that stood complete before today is claimed today.
|
||||
const claimDay = onDate < today ? today : onDate;
|
||||
paidOn.set(claimDay, (paidOn.get(claimDay) ?? 0) + 1);
|
||||
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;
|
||||
@@ -633,9 +644,13 @@ export function projectPerfectPath(opts: {
|
||||
}
|
||||
}
|
||||
|
||||
// A same-day type's next stage trades on the date its payout landed,
|
||||
// today's included; anything else moves on a day.
|
||||
let date = today;
|
||||
for (let step = 0; step < horizon; step++) {
|
||||
date = nextDay(date);
|
||||
let again = todayHasSession && tradesAgain(today);
|
||||
for (let step = 0; again || step < horizon;) {
|
||||
if (again) again = false;
|
||||
else { date = nextDay(date); step++; }
|
||||
const eff = effectiveOn(date);
|
||||
const stage = 1 + paidBefore(date);
|
||||
const plan = computeDayPlan(
|
||||
@@ -650,6 +665,7 @@ export function projectPerfectPath(opts: {
|
||||
// The cycle completed mid-projection with nothing left to trade —
|
||||
// the pending payout lands on this day.
|
||||
if (finishCycle(date, 0, stage, eff.drain)) break;
|
||||
again = tradesAgain(date);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -673,6 +689,7 @@ export function projectPerfectPath(opts: {
|
||||
&& eff2.minTradingDays <= qualifyingDays(eff2.minDayPnL, nd);
|
||||
if (doneAfter) {
|
||||
if (finishCycle(date, pnl, stage, eff.drain)) break;
|
||||
again = tradesAgain(date);
|
||||
} else {
|
||||
out.push({ date, pnl, stage, drain: eff.drain, prob });
|
||||
}
|
||||
|
||||
@@ -1157,7 +1157,7 @@ const vestFunded = {
|
||||
};
|
||||
const claimedBefore = [{ date: '2026-10-20', amount: 50000 }, { date: '2026-10-25', amount: -1500 }];
|
||||
|
||||
test('projectPerfectPath: a same-day type trades each projected day from the base stage', () => {
|
||||
test('projectPerfectPath: a same-day type trades each projected day from the base stage, then its next stages that day', () => {
|
||||
const opts = {
|
||||
accountId: 7, cfg: vestFunded, cycleDailyPnL: [], fundTransactions: claimedBefore,
|
||||
balance: 50000, realizedPnL: 0, payout: { withdrawalPct: 1 }, now: cdt('2026-10-27T12:00:00'),
|
||||
@@ -1165,14 +1165,34 @@ test('projectPerfectPath: a same-day type trades each projected day from the bas
|
||||
const firstOf = (path, n) => path.slice(0, n).map((d) => [d.date, d.stage, d.withdrawal]);
|
||||
|
||||
// An earlier day's claim doesn't count: today and every day after it
|
||||
// make $1,500 on stage 1 and claim it.
|
||||
// make $1,500 on stage 1 and claim it, then $3,000 on stage 2 and claim
|
||||
// that — once through the stages a day.
|
||||
const fresh = projectPerfectPath({ ...opts, stageInputs: { dailyLossLimit: 1229, stagesSameDay: true, sameDayWithdrawals: 0 } });
|
||||
assert.deepEqual(firstOf(fresh, 3), [['2026-10-27', 1, -1500], ['2026-10-28', 1, -1500], ['2026-10-29', 1, -1500]]);
|
||||
assert.ok(fresh.every((d) => d.stage === 1));
|
||||
assert.deepEqual(firstOf(fresh, 5), [
|
||||
['2026-10-27', 1, -1500], ['2026-10-27', 2, -3000],
|
||||
['2026-10-28', 1, -1500], ['2026-10-28', 2, -3000],
|
||||
['2026-10-29', 1, -1500],
|
||||
]);
|
||||
|
||||
// Claimed once already today: today's cycle is stage 2's $3,000, tomorrow $1,500 again.
|
||||
// Claimed once already today: today's cycle is stage 2's $3,000, tomorrow $1,500 and $3,000 again.
|
||||
const claimedToday = projectPerfectPath({ ...opts, stageInputs: { dailyLossLimit: 1229, stagesSameDay: true, sameDayWithdrawals: 1 } });
|
||||
assert.deepEqual(firstOf(claimedToday, 2), [['2026-10-27', 2, -3000], ['2026-10-28', 1, -1500]]);
|
||||
assert.deepEqual(firstOf(claimedToday, 3), [['2026-10-27', 2, -3000], ['2026-10-28', 1, -1500], ['2026-10-28', 2, -3000]]);
|
||||
|
||||
// A cycle that stood complete yesterday is claimed today, so today trades stage 2.
|
||||
const unclaimed = projectPerfectPath({
|
||||
...opts, cycleDailyPnL: [{ date: '2026-10-26', pnl: 1500 }], balance: 51500,
|
||||
stageInputs: { dailyLossLimit: 1229, stagesSameDay: true, sameDayWithdrawals: 0 },
|
||||
});
|
||||
assert.deepEqual(firstOf(unclaimed, 3), [['2026-10-26', 1, -1500], ['2026-10-27', 2, -3000], ['2026-10-28', 1, -1500]]);
|
||||
|
||||
// Two listed stages: three payouts a day, and the day after starts over.
|
||||
const twoStages = projectPerfectPath({
|
||||
...opts, cfg: { ...vestFunded, withdrawalStages: [...vestFunded.withdrawalStages, { profit: 2000, consistency: 0, minTradingDays: 0 }] },
|
||||
maxWithdrawals: 10, stageInputs: { stagesSameDay: true, sameDayWithdrawals: 0 },
|
||||
});
|
||||
assert.deepEqual(firstOf(twoStages, 4).map(([date, stage]) => [date, stage]), [
|
||||
['2026-10-27', 1], ['2026-10-27', 2], ['2026-10-27', 3], ['2026-10-28', 1],
|
||||
]);
|
||||
|
||||
// Any other type counts every withdrawal, as it always has.
|
||||
const lifetime = projectPerfectPath(opts);
|
||||
|
||||
Reference in New Issue
Block a user