Vest: the payout projection runs on Vest's day, and stages count each payout

projectPerfectPath takes the raw trade date (default: the Central one):
the reporter passes its tradeDateFor day and the account page the one
/api/state now gives each account. From 5 PM CT to 20:00 ET it took
tomorrow as today while every input was Vest's, so the day's claim moved
a day late, the next day's stage-1 payout dropped, and an open Vest-day
row counted twice.

Without same-day stages, the stage comes from the client's payout list
(payoutDays, one per payout) when it has read one, not the negative fund
rows: Vest books a day's two claims as one row, so the engine, claimDue,
the stage label and the projection stayed a stage behind the report.
StageInputs.payouts carries the count; a client with no list counts rows
as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-10-27 15:37:44 -05:00
co-authored by Claude Opus 5.5
parent 032ba1ca9c
commit 4a8e027056
7 changed files with 130 additions and 20 deletions
+22 -1
View File
@@ -54,7 +54,7 @@ const { putVestSession } = await import('../lib/vest-session.ts');
const { botOpenedPositions, loadAccountMeta, saveAccountMeta } = await import('../lib/db.ts');
const { vestDayOf, vestAccountId, VestApiError, VEST_STATUS_ACTIVE } = await import('../lib/vest-helpers.ts');
const { encodeContractName } = await import('../lib/dxtrade-helpers.ts');
const { tradeDateFor } = await import('../lib/trading-logic.ts');
const { tradeDateFor, stageInputsFor, resolveEffectiveConfig } = await import('../lib/trading-logic.ts');
const { isClientReady } = await import('../lib/platform-client.ts');
const iso = (s) => Date.parse(s);
@@ -396,3 +396,24 @@ test("the client's Vest day turns with its numbers, and a sync that stalls stops
client.disconnect();
}
});
test('two payouts on one Vest day share its fund row, and still count two toward the stage', async () => {
clockAt('2026-10-27T23:00:00Z');
const client = await syncingClient(unanswered);
try {
// $1,500 then $3,000 taken on Vest day 10-27: Vest's two WITHDRAWs, one row.
client.withdrawals.set(A, [{ at: iso('2026-10-27T15:00:00Z'), amount: -1500 }, { at: iso('2026-10-27T19:00:00Z'), amount: -3000 }]);
client.fundTransactions[A] = [{ date: '2026-10-20', amount: 50000 }, { date: '2026-10-27', amount: -4500 }];
assert.deepEqual(client.payoutDays(A), ['2026-10-27', '2026-10-27']);
// A type whose stages don't reset each day trades stage 3's $2,000, not stage 2's again.
const stages = [{ profit: 3000, consistency: 0, minTradingDays: 0 }, { profit: 2000, consistency: 0, minTradingDays: 0 }];
const inputs = stageInputsFor(client, A, { stagesSameDay: false });
assert.equal(inputs.payouts, 2);
assert.equal(resolveEffectiveConfig(1500, 0, 0, false, stages, 0, client.fundTransactions[A], 0, inputs).profitTarget, 2000);
// Before its history is read: one per negative fund row.
client.historyLoadedIds.delete(A);
assert.equal(stageInputsFor(client, A, {}).payouts, undefined);
} finally {
client.disconnect();
}
});