Reporter: catch up completed cycles on reset, report each firm's account list

When a fund transaction starts a new cycle (the days revert to 0), the
cycle it ended is checked against the completion rule for a week after and
named if it stood complete — its final day may never have been reported.
Only for a cycle this instance saw begin: the clients now note where the
ended cycle started as they move last_fund_date forward. Completions carry
the cycle they belong to, and each firm reports its whole account list so
the master can tell erased accounts and spot new funded ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-23 13:08:16 -05:00
co-authored by Claude Opus 5.5
parent acb8879575
commit a7e01b0138
6 changed files with 279 additions and 49 deletions
+14 -1
View File
@@ -25,7 +25,7 @@ import * as signalR from '@microsoft/signalr';
import { currentTradeDateCT } from './trading-logic';
import { getCachedContract } from './contract-resolver';
import { signalrWebSocket } from './outbound-proxy';
import { saveDailyPnL, loadDailyPnL, loadAccountMeta, saveAccountMeta, loadFundTransactions, saveFundTransactions } from './db';
import { saveDailyPnL, loadDailyPnL, loadAccountMeta, saveAccountMeta, loadFundTransactions, saveFundTransactions, saveEndedCycleStart } from './db';
import { getAccountConfig } from './account-type-resolver';
import type { PlatformClient, PlatformAccount, PlatformContract } from './platform-client';
@@ -563,9 +563,22 @@ export class TopstepXClient implements PlatformClient {
const txns = this.fundTransactions[account.id] ?? [];
const funds = txns.reduce((s, f) => s + f.amount, 0);
const delta = Math.round((cash.amount - startBal - tradePnL - funds) * 100) / 100;
// The first check on this instance (or since a wipe) only has the
// fetch window to explain the balance with: a lump it books is unseen
// P&L and past payouts, not the end of a cycle this instance watched.
const firstCheck = loadAccountMeta(account.id, 'funds_reconciled') === null;
if (firstCheck) saveAccountMeta(account.id, 'funds_reconciled', today);
if (Math.abs(delta) < 200) return;
console.log(`[topstepx] ${account.name}: balance ${cash.amount} vs books ${Math.round((tradePnL + funds) * 100) / 100} — recording ${delta < 0 ? 'withdrawal' : 'deposit'} of ${delta}`);
// A new cycle starts today: note where the one it ends began, by
// applyCycleSlice's rule for that date's own row, for the reporter's
// catch-up. A second event today doesn't start another cycle.
const prevFundDate = this.lastFundDates[account.id] ?? null;
if (!firstCheck && prevFundDate !== today) {
const prevAmt = prevFundDate ? (txns.find((f) => f.date === prevFundDate)?.amount ?? null) : null;
saveEndedCycleStart(account.id, { fundDate: prevFundDate ?? '', includesDay: !(prevAmt !== null && prevAmt < 0) });
}
// Same-day events merge into one row (the table keys on account+date),
// so add today's delta onto whatever is already booked for today.
const existingToday = txns.find((f) => f.date === today)?.amount ?? 0;