diff --git a/lib/master-protocol.ts b/lib/master-protocol.ts index 212e922..52a19b2 100644 --- a/lib/master-protocol.ts +++ b/lib/master-protocol.ts @@ -14,6 +14,13 @@ export interface AccountTypeStats { totalAccounts: number; accountsTraded: number; inTrade: number; + /** Of the accounts that finished trading today (traded, no open position): + * how many ended with the day's profit target hit, in profit but short of + * the target, or at a realized loss. Optional so an older master simply + * ignores them. */ + targetHit?: number; + partial?: number; + loss?: number; } export interface FirmStats { @@ -21,6 +28,10 @@ export interface FirmStats { totalAccounts: number; accountsTraded: number; inTrade: number; + /** Day outcomes, as on AccountTypeStats. */ + targetHit?: number; + partial?: number; + loss?: number; /** Per account type, summing to the firm's own totals. Optional so a master * on an older build simply ignores it. */ accountTypes?: AccountTypeStats[]; diff --git a/lib/reporter.ts b/lib/reporter.ts index 3eac01a..7001c6e 100644 --- a/lib/reporter.ts +++ b/lib/reporter.ts @@ -30,12 +30,18 @@ function collectFirmStats() { 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, accountTypes: [] }; + return { + firm: f.name, totalAccounts: 0, accountsTraded: 0, inTrade: 0, + targetHit: 0, partial: 0, loss: 0, accountTypes: [], + }; } let totalAccounts = 0; let accountsTraded = 0; let inTrade = 0; + let targetHitCount = 0; + let partialCount = 0; + let lossCount = 0; // Per account type, so the master can say what the accounts still to // trade actually are. Keyed by account type id; unassigned accounts @@ -53,6 +59,9 @@ function collectFirmStats() { totalAccounts: 0, accountsTraded: 0, inTrade: 0, + targetHit: 0, + partial: 0, + loss: 0, }; byType.set(key, entry); } @@ -95,6 +104,18 @@ function collectFirmStats() { if (targetHit || cash.realizedPnL !== 0) { accountsTraded++; slice.accountsTraded++; + // A grind-day fill (dayTarget 0) can flag targetHit on a + // negative day — a loss is still a loss, so red wins. + if (cash.realizedPnL < 0) { + lossCount++; + slice.loss = (slice.loss ?? 0) + 1; + } else if (targetHit) { + targetHitCount++; + slice.targetHit = (slice.targetHit ?? 0) + 1; + } else { + partialCount++; + slice.partial = (slice.partial ?? 0) + 1; + } } } } @@ -106,7 +127,10 @@ function collectFirmStats() { (b.totalAccounts - b.accountsTraded) - (a.totalAccounts - a.accountsTraded) || a.label.localeCompare(b.label)); - return { firm: f.name, totalAccounts, accountsTraded, inTrade, accountTypes }; + return { + firm: f.name, totalAccounts, accountsTraded, inTrade, + targetHit: targetHitCount, partial: partialCount, loss: lossCount, accountTypes, + }; }); }