Vest: a claim names the completed cycle it ends, a day's second and one after the reset too

Two of the cycles a claim ends never got a completion entry. A Vest
day's second cycle is one: the auto-claim at the next tick beats the 30 s
report, and the payout books into that day's fund row, so the latest fund
date never moves for the catch-up. A cycle claimed right after the 20:00
ET reset is the other: the reset puts it back on stage 1, and all of its
P&L sits in the previous claim's day row, so endedCycleCompletion reads
none of it.

Before a claim goes out, claimedCycle (trading-logic) checks whether the
cycle stands complete: cycleCompletion 'today' or 'before' on the
client's numbers. It keys the cycle the way the final day would
(completionPlace on the payout list), so the master dedupes the two.
afterClaim keeps the result in account_meta. The reporter names each kept
cycle for the catch-up's week. It lists them last in the slice, newest
first, under the master's per-slice cap, and adds no counter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-10-27 16:08:17 -05:00
co-authored by Claude Opus 5.5
parent 4a8e027056
commit e02838409b
7 changed files with 348 additions and 15 deletions
+61
View File
@@ -1413,3 +1413,64 @@ test('endedCycleCompletion: a same-day type\'s ended cycle is judged on its day\
// Any other type: judged on stage 2's $3,000, as before.
assert.equal(endedCycleCompletion({ ...opts, cfg: vestFunded }), null);
});
import { claimedCycle, withinEndedCycleWindow } from '../lib/trading-logic.ts';
test('claimedCycle: a claim names the complete cycle it ends, where the final day and the catch-up can\'t', () => {
const cfg = { ...vestFunded, stagesSameDay: true, dailyLossLimit: 1500 };
const activated = { date: '2026-10-20', amount: 50000 };
/** A Vest client's numbers for account 7, just before a claim. */
const client = (o) => ({
accountCashBalances: { 7: o.cash }, dailyPnL: { 7: o.cycle ?? [] }, fundTransactions: { 7: o.rows },
lastFundDates: { 7: o.last }, priorProfit: { 7: 0 },
tradeDate: () => o.day, sameDayClaims: () => o.today, payoutDays: o.payouts && (() => o.payouts),
});
// Vest day 10-27, 6 PM ET: claim 1 took stage 1's $1,500 at 3 PM, and
// stage 2 has made its $3,000 since. The auto-claim at the next tick
// beats the 30 s report, and its payout books into 10-27's row: the
// latest fund date never moves for the catch-up.
const claimedOnce = [activated, { date: '2026-10-27', amount: -1500 }];
const second = { cash: { amount: 53000, realizedPnL: 3000 }, rows: claimedOnce, last: '2026-10-27', day: '2026-10-27', today: 1, payouts: ['2026-10-27'] };
assert.deepEqual(claimedCycle(client(second), 7, cfg, cdt('2026-10-27T17:00:00')),
{ date: '2026-10-27', stage: 2, drain: false, cycle: '2026-10-27', cycleSeq: 0 });
// A Claim now short of stage 2's $3,000 names nothing.
assert.equal(claimedCycle(client({ ...second, cash: { amount: 51000, realizedPnL: 1000 } }), 7, cfg, cdt('2026-10-27T17:00:00')), null);
// The day's third cycle, after that claim, has a key of its own.
const twice = [activated, { date: '2026-10-27', amount: -4500 }];
assert.deepEqual(claimedCycle(client({ ...second, rows: twice, today: 2, payouts: ['2026-10-27', '2026-10-27'] }), 7, cfg, cdt('2026-10-27T17:00:00')),
{ date: '2026-10-27', stage: 3, drain: false, cycle: '2026-10-27', cycleSeq: 1 });
// Stage 2 made only $1,500 of its $3,000 by the 20:00 ET reset, which
// puts the account back on stage 1's $1,500: claimed at the first tick,
// 8:00:30 PM ET.
const afterReset = { cash: { amount: 51500, realizedPnL: 0 }, rows: claimedOnce, last: '2026-10-27', day: '2026-10-28', today: 0, payouts: ['2026-10-27'] };
assert.deepEqual(claimedCycle(client(afterReset), 7, cfg, cdt('2026-10-27T19:00:30')),
{ date: '2026-10-28', stage: 2, drain: false, cycle: '2026-10-27', cycleSeq: 0 });
// The catch-up after it reads none of that cycle's P&L, all in 10-27's
// row, which the cycle before owns.
assert.equal(endedCycleCompletion({
accountId: 7, rawTradeDate: '2026-10-28', fullDailyPnL: [{ date: '2026-10-27', pnl: 3000 }], cycleDailyPnL: [],
fundTransactions: [...claimedOnce, { date: '2026-10-28', amount: -1500 }], lastFundDate: '2026-10-28',
endedCycleStart: { fundDate: '2026-10-27', includesDay: false }, rawRealizedPnL: 0, equityProfit: 0, cfg, now: cdt('2026-10-27T19:10:00'),
}), null);
// Where the catch-up does name the cycle — its P&L on a day of its own —
// the claim names it by the same key, so the master keeps one.
const crossDay = { cash: { amount: 51500, realizedPnL: 1500 }, rows: claimedBefore, last: '2026-10-25', day: '2026-10-26', today: 0, payouts: ['2026-10-25'] };
assert.deepEqual(claimedCycle(client(crossDay), 7, cfg, cdt('2026-10-26T12:00:00')),
{ date: '2026-10-26', stage: 2, drain: false, cycle: '2026-10-25', cycleSeq: 0 });
// Without a payout list, one payout per negative fund row.
assert.equal(claimedCycle(client({ ...crossDay, payouts: undefined }), 7, cfg, cdt('2026-10-26T12:00:00')).stage, 2);
// Numbers not read yet: nothing.
assert.equal(claimedCycle({ ...client(crossDay), accountCashBalances: {} }, 7, cfg, cdt('2026-10-26T12:00:00')), null);
assert.equal(claimedCycle({ ...client(crossDay), fundTransactions: {} }, 7, cfg, cdt('2026-10-26T12:00:00')), null);
});
test('withinEndedCycleWindow: a week after the date, and never for one that doesn\'t parse', () => {
assert.equal(withinEndedCycleWindow('2026-10-21', '2026-10-28'), true);
assert.equal(withinEndedCycleWindow('2026-10-28', '2026-10-28'), true);
assert.equal(withinEndedCycleWindow('2026-10-20', '2026-10-28'), false);
assert.equal(withinEndedCycleWindow('', '2026-10-28'), false);
assert.equal(withinEndedCycleWindow('10/27', '2026-10-28'), false);
});