Vest: a claim counts on the fresh day only once Vest dates it there, and a day's second cycle gets its own key

A claim's stage time is now taken before the request goes out (sentAt):
Vest dates it no earlier, so a claim answered just after 20:00 ET no longer
puts the fresh Vest day on stage 2 before the history read shows its
WITHDRAW. One Vest dates after the turn reads as the day before until then,
the cautious way round. Claims saved without it count from their answer.

Completions count one per payout on both paths (completionPlace): the
catch-up's stage no longer counts a Vest day's two claims as one, and a
cycle a day's second claim started carries cycleSeq, so it can be told from
that day's first. The master ignores it until it keys on it; old masters and
old instances are unchanged.

bookHistory's claim bookkeeping (WITHDRAWs, lapse, fund rows, the balance
the books check expects) is now the pure readClaims, tested on the case
that held an account for good: a claim that never went in, read an hour on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-10-27 14:39:13 -05:00
co-authored by Claude Opus 5.5
parent 9fe7fc0ea3
commit 35512cd504
9 changed files with 245 additions and 79 deletions
+33
View File
@@ -715,6 +715,39 @@ test('an every-day firm can finish its cycle on a Saturday', () => {
assert.equal(cycleCompletion(opts), 'none');
});
import { completionPlace } from '../lib/trading-logic.ts';
test('completionPlace: each payout counts, and a day\'s second cycle has a key of its own', () => {
// Vest: activated on the 20th; claims c1 and c2 on the 27th share one
// fund row; c3 on the 28th.
const activated = { date: '2026-10-20', amount: 50000 };
// Cycle A, the first, completes on the 27th ahead of any claim.
assert.deepEqual(completionPlace({ payoutDays: [], fundTransactions: [activated], cycle: '2026-10-20' }), { stage: 1, cycleSeq: 0 });
// c1 starts cycle B, which completes the same day.
const b = completionPlace({ payoutDays: ['2026-10-27'], fundTransactions: [activated, { date: '2026-10-27', amount: -2250 }], cycle: '2026-10-27' });
assert.deepEqual(b, { stage: 2, cycleSeq: 0 });
// c2 starts cycle C on the same fund row, which completes on the 28th:
// payout 3, and not B's key — the master would fold C into B.
const both = [activated, { date: '2026-10-27', amount: -3750 }];
const c = completionPlace({ payoutDays: ['2026-10-27', '2026-10-27'], fundTransactions: both, cycle: '2026-10-27' });
assert.deepEqual(c, { stage: 3, cycleSeq: 1 });
// C caught up after c3 ended it: the same stage and key as its final day.
assert.deepEqual(completionPlace({
payoutDays: ['2026-10-27', '2026-10-27', '2026-10-28'], fundTransactions: [...both, { date: '2026-10-28', amount: -1500 }],
cycle: '2026-10-27', end: '2026-10-28',
}), c);
// One fund row per payout (every other platform): 1 + the rows before, no
// cycleSeq — as the stage always read.
const rows = [{ date: '2026-09-01', amount: -500 }, { date: '2026-09-15', amount: -800 }];
const days = rows.map((f) => f.date);
assert.deepEqual(completionPlace({ payoutDays: days, fundTransactions: rows, cycle: '2026-09-15' }), { stage: 3, cycleSeq: 0 });
assert.deepEqual(completionPlace({ payoutDays: days, fundTransactions: rows, cycle: '2026-09-01', end: '2026-09-15' }), { stage: 2, cycleSeq: 0 });
// The account's first cycle; and a claim on the day it was activated,
// whose deposit row names the cycle it started.
assert.deepEqual(completionPlace({ payoutDays: [], fundTransactions: [], cycle: '' }), { stage: 1, cycleSeq: 0 });
assert.deepEqual(completionPlace({ payoutDays: ['2026-10-20'], fundTransactions: [{ date: '2026-10-20', amount: 47750 }], cycle: '2026-10-20' }), { stage: 2, cycleSeq: 1 });
});
// ── DXTrade helpers (lib/dxtrade-helpers.ts) ─────────────────────────────────
import {