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:
co-authored by
Claude Opus 5.5
parent
acb8879575
commit
a7e01b0138
+65
-17
@@ -11,15 +11,15 @@
|
||||
* POST keeps those working unchanged.
|
||||
*/
|
||||
|
||||
import { getSetting } from './db';
|
||||
import { getSetting, loadEndedCycleStart } from './db';
|
||||
import { pushReport } from './master-link';
|
||||
import { getBuildVersion } from './version';
|
||||
import { getClients } from './clients';
|
||||
import { getTradableFirms } from './firm-catalog';
|
||||
import { getAccountConfig } from './account-type-resolver';
|
||||
import { getSchedulerStatus } from './auto-trade';
|
||||
import { combineCmeSessions, computeDayPlan, currentTradeDateCT, cycleCompletion, LEG_COMPLETION_EPSILON, projectPerfectPath, resolveEffectiveConfig } from './trading-logic';
|
||||
import type { AccountTypeStats } from './master-protocol';
|
||||
import { combineCmeSessions, computeDayPlan, currentTradeDateCT, cycleCompletion, endedCycleCompletion, LEG_COMPLETION_EPSILON, projectPerfectPath, resolveEffectiveConfig } from './trading-logic';
|
||||
import type { AccountTypeStats, FirmStats } from './master-protocol';
|
||||
import { historyFresh } from './platform-client';
|
||||
|
||||
function collectFirmStats() {
|
||||
@@ -31,13 +31,23 @@ function collectFirmStats() {
|
||||
// One raw trade date for the whole report: the combined views below roll
|
||||
// it forward, the completion check needs it as is.
|
||||
const rawToday = currentTradeDateCT();
|
||||
// projectPerfectPath's `loops` test: a type whose completed cycle keeps
|
||||
// going pays out (a funded account), one that stops just passes.
|
||||
const loops = (cfg: { payoutable?: boolean; withdrawalStages: unknown[] }): boolean =>
|
||||
cfg.payoutable === true || cfg.withdrawalStages.length > 0;
|
||||
const completionKind = (cfg: Parameters<typeof loops>[0], drain: boolean): 'pass' | 'payout' | 'drain' =>
|
||||
(drain ? 'drain' : loops(cfg) ? 'payout' : 'pass');
|
||||
|
||||
return firms.map((f) => {
|
||||
const client = clients.get(f.id);
|
||||
if (!client || client.accountList.length === 0) {
|
||||
return {
|
||||
firm: f.name, totalAccounts: 0, accountsTraded: 0, inTrade: 0,
|
||||
targetHit: 0, partial: 0, loss: 0, personalDll: 0, completed: 0, accountTypes: [],
|
||||
targetHit: 0, partial: 0, loss: 0, personalDll: 0, completed: 0,
|
||||
// Empty only once synced — before that, a firm whose accounts
|
||||
// haven't arrived would read as every saved account erased.
|
||||
...(client?.syncComplete ? { accounts: [] } : {}),
|
||||
accountTypes: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -49,6 +59,10 @@ function collectFirmStats() {
|
||||
let lossCount = 0;
|
||||
let personalDllCount = 0;
|
||||
let completedCount = 0;
|
||||
// The firm's whole broker list, dead accounts included — the master
|
||||
// tells an account the firm erased from one still listed, and spots
|
||||
// a newly arrived funded account.
|
||||
const accounts: NonNullable<FirmStats['accounts']> = [];
|
||||
|
||||
// Per account type, so the master can say what the accounts still to
|
||||
// trade actually are. Keyed by account type id; unassigned accounts
|
||||
@@ -78,11 +92,12 @@ function collectFirmStats() {
|
||||
for (const acc of client.accountList) {
|
||||
const cash = client.accountCashBalances[acc.id] ?? { amount: 0, realizedPnL: 0 };
|
||||
const autoLiqThreshold = client.autoLiqThresholds[acc.id] ?? 0;
|
||||
const cfg = getAccountConfig(acc.id).accountType;
|
||||
accounts.push({ name: acc.name, size: cfg?.accountSize ?? 0, payoutable: cfg ? loops(cfg) : null });
|
||||
// 0 = no floor; negative floors are reconstructed TopstepX MLLs.
|
||||
const isDead = autoLiqThreshold !== 0 && cash.amount <= autoLiqThreshold;
|
||||
if (isDead) continue;
|
||||
|
||||
const cfg = getAccountConfig(acc.id).accountType;
|
||||
const slice = bucket(cfg);
|
||||
|
||||
totalAccounts++;
|
||||
@@ -205,6 +220,41 @@ function collectFirmStats() {
|
||||
});
|
||||
}
|
||||
|
||||
// Only once the account's history has loaded — right after a
|
||||
// restart the missing rows and withdrawals would read as a fresh
|
||||
// stage-1 cycle.
|
||||
const historyLoaded = client.dailyPnL[acc.id] !== undefined
|
||||
&& client.fundTransactions?.[acc.id] !== undefined;
|
||||
|
||||
// Catch-up for the cycle a fund transaction just ended: if it stood
|
||||
// complete, name it, since its final day may never have been
|
||||
// reported (a guard held it back, or this instance was offline).
|
||||
// Named only — it isn't today's outcome, so no counter moves. Ahead
|
||||
// of the in-trade check: the new cycle trading doesn't hide it.
|
||||
if (cfg && effective && historyLoaded && historyFresh(client, acc)) {
|
||||
const ended = endedCycleCompletion({
|
||||
accountId: acc.id,
|
||||
rawTradeDate: rawToday,
|
||||
fullDailyPnL: client.fullDailyPnL[acc.id] ?? [],
|
||||
cycleDailyPnL: client.dailyPnL[acc.id],
|
||||
fundTransactions: fundTxns,
|
||||
lastFundDate: client.lastFundDates?.[acc.id] ?? null,
|
||||
endedCycleStart: loadEndedCycleStart(acc.id),
|
||||
rawRealizedPnL: cash.realizedPnL,
|
||||
equityProfit: cash.amount - cfg.accountSize,
|
||||
cfg,
|
||||
});
|
||||
if (ended) {
|
||||
(slice.completedAccounts ??= []).push({
|
||||
name: acc.name,
|
||||
date: ended.date,
|
||||
stage: ended.stage,
|
||||
kind: completionKind(cfg, ended.drain),
|
||||
cycle: ended.cycle,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (client.positions[acc.id]) {
|
||||
inTrade++;
|
||||
accountsTraded++;
|
||||
@@ -215,12 +265,8 @@ function collectFirmStats() {
|
||||
|
||||
// Did today finish the cycle? Checked ahead of the lockout branch:
|
||||
// firms lock a passed account (Tradovate liquidate-only, TopstepX
|
||||
// canTrade=false), and a pass is no loss. Only once the account's
|
||||
// history has loaded — right after a restart the missing rows and
|
||||
// withdrawals would read as a fresh stage-1 cycle — and a 'today'
|
||||
// only on freshly fetched rows, since the master keeps it.
|
||||
const historyLoaded = client.dailyPnL[acc.id] !== undefined
|
||||
&& client.fundTransactions?.[acc.id] !== undefined;
|
||||
// canTrade=false), and a pass is no loss. A 'today' only on
|
||||
// freshly fetched rows, since the master keeps it.
|
||||
if (cfg && combined && effective && historyLoaded) {
|
||||
const completion = cycleCompletion({
|
||||
accountId: acc.id,
|
||||
@@ -243,15 +289,14 @@ function collectFirmStats() {
|
||||
dayEntry.targetHit++;
|
||||
dayEntry.completed = (dayEntry.completed ?? 0) + 1;
|
||||
}
|
||||
// Same test as projectPerfectPath's `loops`: a cycle that
|
||||
// keeps going pays out, one that stops just passes.
|
||||
const kind = effective.drain ? 'drain'
|
||||
: (cfg.payoutable === true || cfg.withdrawalStages.length > 0) ? 'payout' : 'pass';
|
||||
(slice.completedAccounts ??= []).push({
|
||||
name: acc.name,
|
||||
date: combined.tradeDate,
|
||||
stage: 1 + fundTxns.filter((f) => f.amount < 0).length,
|
||||
kind,
|
||||
kind: completionKind(cfg, effective.drain),
|
||||
// The fund transaction that started this cycle — the
|
||||
// catch-up names the same cycle by the same key.
|
||||
cycle: client.lastFundDates?.[acc.id] ?? '',
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -322,7 +367,10 @@ function collectFirmStats() {
|
||||
return {
|
||||
firm: f.name, totalAccounts, accountsTraded, inTrade,
|
||||
targetHit: targetHitCount, partial: partialCount, loss: lossCount,
|
||||
personalDll: personalDllCount, completed: completedCount, accountTypes,
|
||||
personalDll: personalDllCount, completed: completedCount,
|
||||
// Unknown until the firm has synced, not empty.
|
||||
...(client.syncComplete ? { accounts } : {}),
|
||||
accountTypes,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user