diff --git a/lib/dxtrade-class.ts b/lib/dxtrade-class.ts index 621c924..2feb741 100644 --- a/lib/dxtrade-class.ts +++ b/lib/dxtrade-class.ts @@ -25,7 +25,8 @@ * - Cash transactions (SETTLEMENT, COMMISSION, FINANCING, DEPOSIT, * WITHDRAWAL…) are the books: daily P&L, fund transactions, today's * realized P&L and the balance at the firm's 22:00 UTC reset all come from - * them. + * them. Each is dated by the firm's day (firmDayOf), which in winter rolls + * an hour before our 5 PM CT trade date. * - No auto-liquidation profile: autoLiqThresholds stays 0; a breached * account shows as accountStatus ≠ FULL_TRADING → active=false. */ @@ -44,7 +45,7 @@ import type { PlatformClient, PlatformAccount, PlatformContract, ClientHealth, P import { OrderNotPlacedError, isOrderNotPlaced, errorMessage } from './platform-client'; import { dxAccountId, accountDisplayName, accountCodeOf, encodeContractName, decodeContractName, roundToStep, - lastUtcReset, ctTradeDayStart, timeOf, parseDurationMs, ciNum, isFundType, signedFundValue, newOrderCode, + lastUtcReset, ctTradeDayStart, firmDayOf, timeOf, parseDurationMs, ciNum, isFundType, signedFundValue, newOrderCode, inTradingHours, realizedVolPerHour, scoreCandidate, candidateRejection, parseTransfers, marginRateOf, } from './dxtrade-helpers'; import type { DxTxn } from './dxtrade-helpers'; @@ -614,7 +615,13 @@ export class DXTradeClient implements PlatformClient { const orders = new Map(); let txns: DxTxn[] = []; const now = new Date(); - const windowStart = new Date(Math.min(ctTradeDayStart(now).getTime(), lastUtcReset(now, FIRM_RESET_UTC_HOUR).getTime()) - 5 * 60_000); + // From the firm's day that today's trade date names (in winter it + // began an hour before 5 PM CT) and the current reset, whichever + // is earlier. + const windowStart = new Date(Math.min( + lastUtcReset(ctTradeDayStart(now), FIRM_RESET_UTC_HOUR).getTime(), + lastUtcReset(now, FIRM_RESET_UTC_HOUR).getTime(), + ) - 5 * 60_000); if (codes.length > 0) { try { for (const m of await this.perAccounts>('/accounts/metrics', codes, (d) => (d as { metrics?: unknown[] })?.metrics ?? [])) { @@ -659,7 +666,7 @@ export class DXTradeClient implements PlatformClient { this.recentTxns.set(acc.id, map); let realized = 0; for (const t of map.values()) { - if (!isFundType(t.type) && currentTradeDateCT(new Date(t.time)) === today) realized += t.value; + if (!isFundType(t.type) && firmDayOf(new Date(t.time), FIRM_RESET_UTC_HOUR) === today) realized += t.value; } this.accountCashBalances[acc.id] = { amount: balance, realizedPnL: round2(realized), equity: ciNum(m, 'equity'), availableFunds: ciNum(m, 'availableFunds'), @@ -913,7 +920,7 @@ export class DXTradeClient implements PlatformClient { const daily = new Map(); const funds = new Map(); for (const t of mine) { - const date = currentTradeDateCT(new Date(t.time)); + const date = firmDayOf(new Date(t.time), FIRM_RESET_UTC_HOUR); if (isFundType(t.type)) { funds.set(date, (funds.get(date) ?? 0) + signedFundValue(t.type, t.value)); continue; diff --git a/lib/dxtrade-helpers.ts b/lib/dxtrade-helpers.ts index a0f631e..19a2f01 100644 --- a/lib/dxtrade-helpers.ts +++ b/lib/dxtrade-helpers.ts @@ -91,6 +91,15 @@ export function lastUtcReset(now: Date, hourUtc = 22): Date { return reset; } +/** The trading day the firm books a moment to: the day that began at the + * reset before it, labelled by the calendar date that follows — the label + * currentTradeDateCT gives the same day. In summer the two roll together + * (22:00 UTC = 5 PM CDT); in winter the firm's rolls an hour earlier, and a + * close in that hour belongs to the new day, as the firm counts it. */ +export function firmDayOf(at: Date, hourUtc = 22): string { + return new Date(at.getTime() + (24 - hourUtc) * 3_600_000).toISOString().slice(0, 10); +} + /** When the current Central trade date began (5 PM CT) — the window today's * realized P&L covers, matching currentTradeDateCT. */ export function ctTradeDayStart(now: Date): Date { diff --git a/scripts/check-trading-logic.mjs b/scripts/check-trading-logic.mjs index a060d74..42540e3 100644 --- a/scripts/check-trading-logic.mjs +++ b/scripts/check-trading-logic.mjs @@ -13,6 +13,7 @@ import { botStopsActive, sizeNotional, isNoTradeTime, isPastSessionClose, nextTradingDate, effectiveCombine, DEFAULT_TARGET_HOLD_HOURS, projectPerfectPath, cycleCompletion, DLL_RESERVE_FRACTION, DLL_MIN_STOP, notionalCap, expectedSwapToWin, MARGIN_HEADROOM, dllReserve, DLL_SLIPPAGE_FRACTION, maxLossRoom, lastStand, breachDistance, LAST_STAND_FRACTION, planLegSize, rankForTarget, + currentTradeDateCT, } from '../lib/trading-logic.ts'; // Deterministic PRNG so a failure reproduces. @@ -502,7 +503,7 @@ test('an every-day firm can finish its cycle on a Saturday', () => { import { dxAccountId, accountDisplayName, accountCodeOf, encodeContractName, decodeContractName, roundToStep, - lastUtcReset, ctTradeDayStart, parseDurationMs, ciNum, signedFundValue, inTradingHours, + lastUtcReset, ctTradeDayStart, firmDayOf, parseDurationMs, ciNum, signedFundValue, inTradingHours, realizedVolPerHour, scoreCandidate, candidateRejection, MIN_VOL_CANDLES, parseTransfers, marginRateOf, } from '../lib/dxtrade-helpers.ts'; @@ -573,6 +574,19 @@ test('the firm reset and the CT trade-date start', () => { assert.equal(ctTradeDayStart(cst('2027-01-12T10:00:00')).toISOString(), '2027-01-11T23:00:00.000Z'); }); +test('the firm\'s day: the same label as the CT trade date, rolling at its reset', () => { + // Summer: the reset is 5 PM CDT, so the two agree on both sides of it. + assert.equal(firmDayOf(new Date('2026-09-26T21:59:00Z')), '2026-09-26'); + assert.equal(firmDayOf(new Date('2026-09-26T22:00:00Z')), '2026-09-27'); + assert.equal(firmDayOf(new Date('2026-09-26T22:00:00Z')), currentTradeDateCT(new Date('2026-09-26T22:00:00Z'))); + // Winter: a close at 4:30 PM CST is the firm's next day, still ours. + const close = cst('2027-01-12T16:30:00'); + assert.equal(firmDayOf(close), '2027-01-13'); + assert.equal(currentTradeDateCT(close), '2027-01-12'); + assert.equal(firmDayOf(cst('2027-01-12T15:59:00')), '2027-01-12'); + assert.equal(firmDayOf(cst('2027-01-12T17:30:00')), currentTradeDateCT(cst('2027-01-12T17:30:00'))); +}); + test('loose parsing', () => { assert.equal(parseDurationMs('PT30M'), 30 * 60_000); assert.equal(parseDurationMs('01:05:08'), (3600 + 300 + 8) * 1000);