// Checks for the account status the reporter sends the master's ledger // (lib/platform-client.ts accountStatus and the helpers around it). // // npm run check // // .mjs on purpose: Next's tsconfig includes every .ts file, and a .ts test // importing another .ts by extension would trip the build's type check. Node // strips the types from lib/platform-client.ts at import time (its only // imports are `import type`). import { test } from 'node:test'; import assert from 'node:assert/strict'; import { accountStatus, accountStatusFields, brokerFacts, emptyFirmStats, firmStatusFields, legacyDead, REPORT_STATUS_VERSION, SYNC_STALE_MS, } from '../lib/platform-client.ts'; const FLOORS = { negative: -2000, zero: 0, positive: 48_000 }; const OFFSETS = { above: 1500, at: 0, below: -1500 }; /** Every case of {cash present/missing, floor <0/0/>0, cash above/at/below the * floor, active, history ready}. */ function* matrix() { for (const [floorName, floor] of Object.entries(FLOORS)) { for (const [offsetName, offset] of Object.entries(OFFSETS)) { for (const missing of [false, true]) { for (const active of [true, false]) { for (const historyReady of [true, false]) { const cash = missing ? undefined : floor + offset; yield { label: `${floorName} floor, cash ${missing ? 'missing' : offsetName}, active ${active}, history ${historyReady}`, cash, floor, active, historyReady }; } } } } } } test('legacyDead is the test reporter.ts:114 used before', () => { for (const c of matrix()) { const cashOrZero = c.cash ?? 0; // Copied from reporter.ts at 8747dd6: autoLiqThreshold !== 0 && cash.amount <= autoLiqThreshold assert.equal(legacyDead(cashOrZero, c.floor), c.floor !== 0 && cashOrZero <= c.floor, c.label); } }); test('dead is exactly the legacy test whenever there is a balance', () => { let cases = 0; for (const c of matrix()) { if (c.cash === undefined) continue; cases++; const { status } = accountStatus(c); assert.equal(status === 'dead', legacyDead(c.cash, c.floor), c.label); } assert.equal(cases, 36); // A balance of exactly 0 is a balance, not a missing one. assert.deepEqual(accountStatus({ cash: 0, floor: 48_000, active: true, historyReady: true }), { status: 'dead' }); assert.deepEqual(accountStatus({ cash: 0, floor: 0, active: true, historyReady: true }), { status: 'live' }); }); test('a missing balance is unknown, never dead and never 0', () => { for (const c of matrix()) { if (c.cash !== undefined) continue; assert.deepEqual(accountStatus(c), { status: 'unknown', reason: 'cash_missing' }, c.label); } assert.deepEqual(accountStatus({ cash: null, floor: 48_000, active: true, historyReady: true }), { status: 'unknown', reason: 'cash_missing' }); }); test('the legacy invariant: the old dead count = dead + cash-missing with a positive floor', () => { for (const c of matrix()) { const { status, reason } = accountStatus(c); const oldDead = legacyDead(c.cash ?? 0, c.floor); const newDead = status === 'dead' || (status === 'unknown' && reason === 'cash_missing' && c.floor > 0); assert.equal(newDead, oldDead, c.label); } }); test('priority: dead, then history pending, then locked, then live', () => { const base = { cash: 50_000, floor: 48_000, active: true, historyReady: true }; assert.deepEqual(accountStatus(base), { status: 'live' }); assert.deepEqual(accountStatus({ ...base, active: false }), { status: 'locked', reason: 'inactive' }); assert.deepEqual(accountStatus({ ...base, active: false, inactiveReason: 'liquidate_only' }), { status: 'locked', reason: 'liquidate_only' }); assert.deepEqual(accountStatus({ ...base, active: false, historyReady: false }), { status: 'history_pending' }); assert.deepEqual(accountStatus({ ...base, cash: 47_000, active: false, historyReady: false }), { status: 'dead' }); assert.deepEqual(accountStatus({ ...base, cash: 47_000, deadReason: 'terminal:TERMINATED' }), { status: 'dead', reason: 'terminal:TERMINATED' }); // A floor of 0 is no floor: never dead, whatever the balance. assert.equal(accountStatus({ ...base, cash: -10, floor: 0 }).status, 'live'); }); test('broker facts per platform', () => { assert.deepEqual(brokerFacts('tradovate', { active: true, closed: false, archived: false, restricted: false }), { inactiveReason: 'liquidate_only' }); assert.deepEqual(brokerFacts('tradovate', { active: false, closed: true, archived: true, restricted: false }), { inactiveReason: 'liquidate_only', brokerStatus: 'closed,archived' }); assert.deepEqual(brokerFacts('topstepx', { active: true }), { inactiveReason: 'cannot_trade' }); assert.deepEqual(brokerFacts('topstepx', { active: false }), { inactiveReason: 'cannot_trade', brokerStatus: 'canTrade:false' }); const registered = Date.parse('2026-10-03T03:00:00Z'); assert.deepEqual(brokerFacts('dxtrade', { active: true }, { status: 'FULL_TRADING', registrationTime: registered }), { inactiveReason: 'status:FULL_TRADING', deadReason: 'terminal:FULL_TRADING', registeredAt: registered }); assert.deepEqual(brokerFacts('dxtrade', { active: false }, { status: 'CLOSE_ONLY', registrationTime: NaN }), { inactiveReason: 'close_only', deadReason: 'terminal:CLOSE_ONLY', brokerStatus: 'CLOSE_ONLY' }); assert.deepEqual(brokerFacts('dxtrade', { active: false }, undefined), { inactiveReason: 'inactive' }); }); test('a DXtrade account the firm terminated reads dead with the terminal status', () => { // dxtrade-class.ts sets the floor to the balance for DEAD_STATUSES. const facts = brokerFacts('dxtrade', { active: false }, { status: 'TERMINATED', registrationTime: NaN }); const fields = accountStatusFields({ cash: 94_210.5, floor: 94_210.5, active: false, historyReady: true, accountTypeId: null, fundTransactions: [], ...facts }); assert.equal(fields.status, 'dead'); assert.equal(fields.reason, 'terminal:TERMINATED'); assert.equal(fields.brokerStatus, 'TERMINATED'); // A CLOSE_ONLY account is inactive (floor 0 there), not dead. const closeOnly = accountStatusFields({ cash: 94_210.5, floor: 0, active: false, historyReady: true, accountTypeId: 't', fundTransactions: [], ...brokerFacts('dxtrade', { active: false }, { status: 'CLOSE_ONLY', registrationTime: NaN }) }); assert.deepEqual([closeOnly.status, closeOnly.reason], ['locked', 'close_only']); }); test('per-account fields: defaults and nulls are left out', () => { const fields = accountStatusFields({ cash: undefined, floor: 0, active: true, historyReady: false, accountTypeId: null, fundTransactions: undefined, inactiveReason: 'liquidate_only' }); assert.deepEqual(fields, { status: 'unknown', reason: 'cash_missing' }); const live = accountStatusFields({ cash: 50_123.456, floor: 47_623.451, active: true, historyReady: true, accountTypeId: 'cc9d7145-f85c-4b61-9e90-b7091b6f7dcc', fundTransactions: [{ date: '2026-09-30', amount: -1000 }, { date: '2026-09-25', amount: 50_000 }, { date: '2026-10-02', amount: 25 }], inactiveReason: 'liquidate_only', }); assert.deepEqual(live, { status: 'live', balance: 50_123.46, floor: 47_623.45, accountTypeId: 'cc9d7145-f85c-4b61-9e90-b7091b6f7dcc', firstFundDate: '2026-09-25', positiveFunds: 2, }); // Fund history loaded with no positive rows: a count of 0, no date. const none = accountStatusFields({ cash: 10, floor: 0, active: true, historyReady: true, accountTypeId: 't', fundTransactions: [] }); assert.deepEqual([none.positiveFunds, 'firstFundDate' in none], [0, false]); }); test('DXtrade: the registration date (Chicago) stands in for the first fund date', () => { const registeredAt = Date.parse('2026-10-03T03:00:00Z'); // Oct 2, 22:00 CDT const input = { cash: 100_000, floor: 0, active: true, historyReady: true, accountTypeId: 't', registeredAt }; assert.equal(accountStatusFields({ ...input, fundTransactions: [] }).firstFundDate, '2026-10-02'); // A real fund row wins; and nothing until the fund history has loaded. assert.equal(accountStatusFields({ ...input, fundTransactions: [{ date: '2026-10-05', amount: 100_000 }] }).firstFundDate, '2026-10-05'); assert.equal('firstFundDate' in accountStatusFields({ ...input, fundTransactions: undefined }), false); // A time no Date can hold is no date — and must not throw. for (const bad of [1.7e18, -1e16, NaN]) { assert.equal('registeredAt' in brokerFacts('dxtrade', { active: true }, { status: 'FULL_TRADING', registrationTime: bad }), false); assert.equal('firstFundDate' in accountStatusFields({ ...input, registeredAt: bad, fundTransactions: [] }), false); } assert.deepEqual(firmStatusFields('f', true, 1e17), { statusVersion: 1, firmId: 'f' }); }); test('firm-level fields: statusVersion and firmId always, syncedAt once synced', () => { const at = Date.parse('2026-10-06T14:00:00Z'); assert.deepEqual(firmStatusFields('f-1', true, at), { statusVersion: 1, firmId: 'f-1', syncedAt: '2026-10-06T14:00:00.000Z' }); assert.deepEqual(firmStatusFields('f-1', true, 0), { statusVersion: 1, firmId: 'f-1' }); assert.deepEqual(firmStatusFields('f-1', false, at), { statusVersion: 1, firmId: 'f-1' }); assert.equal(REPORT_STATUS_VERSION, 1); assert.equal(SYNC_STALE_MS, 5 * 60 * 1000); }); test('the no-accounts branch keeps every existing key and value', () => { const firm = { id: '71016d02-752f-4078-9e24-e74c9d75d1c8', name: 'Tradeify' }; // What reporter.ts sent before (its :46-56 branch), for each client state. const legacy = (client) => ({ firm: firm.name, totalAccounts: 0, accountsTraded: 0, inTrade: 0, targetHit: 0, partial: 0, loss: 0, personalDll: 0, completed: 0, ...(client?.syncComplete ? { accounts: [] } : {}), accountTypes: [], }); const at = Date.parse('2026-10-06T14:00:00Z'); const states = [undefined, { syncComplete: false, lastSyncAt: 0 }, { syncComplete: false, lastSyncAt: at }, { syncComplete: true, lastSyncAt: 0 }, { syncComplete: true, lastSyncAt: at }]; for (const client of states) { const got = emptyFirmStats(firm, client); const old = legacy(client); for (const [key, value] of Object.entries(old)) assert.deepEqual(got[key], value, `${JSON.stringify(client)}: ${key}`); const added = Object.keys(got).filter((k) => !(k in old)).sort(); const synced = client?.syncComplete === true; assert.equal(got.statusVersion, 1); assert.equal(got.firmId, firm.id); assert.equal('accounts' in got, synced); assert.equal('syncedAt' in got, synced && client.lastSyncAt > 0); assert.deepEqual(added, ['firmId', 'statusVersion', ...(got.syncedAt ? ['syncedAt'] : [])].sort()); // The old keys stay first and in their order: only new keys follow. assert.deepEqual(Object.keys(got).slice(0, Object.keys(old).length), Object.keys(old)); } }); // ── Size: 4 firms × 500 accounts against the master socket's 1 MB frame ────── const TYPE_ID = 'f5ad1527-8bd7-4ca0-9e35-bcadf78e52e9'; const pad = (n, w) => String(n).padStart(w, '0'); /** The entry reporter.ts sent before, with a realistic 14-character name. */ function legacyEntry(i) { return { name: `TDFY${pad(i, 10)}`, size: 150_000, payoutable: false, withdrawals: 2 }; } /** Every new field populated, worst realistic case per platform. */ function fullFields(platform, i) { const balance = 149_823.45 - (i % 97); if (platform === 'tradovate') { return accountStatusFields({ cash: balance, floor: 147_500.12, active: false, historyReady: true, accountTypeId: TYPE_ID, fundTransactions: [{ date: '2026-09-25', amount: 150_000 }, { date: '2026-10-01', amount: 25 }], ...brokerFacts('tradovate', { active: false, closed: true, archived: true, restricted: true }) }); } if (platform === 'dxtrade') { return accountStatusFields({ cash: balance, floor: balance, active: false, historyReady: true, accountTypeId: TYPE_ID, fundTransactions: [{ date: '2026-09-25', amount: 150_000 }], ...brokerFacts('dxtrade', { active: false }, { status: 'TERMINATED', registrationTime: Date.parse('2026-09-25T15:00:00Z') }) }); } return accountStatusFields({ cash: balance, floor: 147_500.12, active: false, historyReady: true, accountTypeId: TYPE_ID, fundTransactions: [{ date: '2026-09-25', amount: 150_000 }], ...brokerFacts('topstepx', { active: false }) }); } /** A typical live account: no reason, no broker status. */ function typicalFields(i) { return accountStatusFields({ cash: 50_123.45 + (i % 89), floor: 47_623.45, active: true, historyReady: true, accountTypeId: TYPE_ID, fundTransactions: [{ date: '2026-09-25', amount: 50_000 }], ...brokerFacts('tradovate', { active: true, closed: false, archived: false, restricted: false }) }); } const added = (fields, i) => JSON.stringify({ ...legacyEntry(i), ...fields }).length - JSON.stringify(legacyEntry(i)).length; const PLATFORMS = ['tradovate', 'tradovate', 'topstepx', 'dxtrade']; const ALL_FIELDS = ['status', 'reason', 'balance', 'floor', 'accountTypeId', 'firstFundDate', 'positiveFunds', 'brokerStatus']; test('size: the new fields add < 230 bytes per fully populated account, < 180 per typical live one', () => { let worst = 0; for (const platform of new Set(PLATFORMS)) { assert.deepEqual(Object.keys(fullFields(platform, 0)).sort(), [...ALL_FIELDS].sort(), `${platform} entry is fully populated`); assert.equal(fullFields(platform, 0).accountTypeId.length, 36); for (let i = 0; i < 500; i++) worst = Math.max(worst, added(fullFields(platform, i), i)); } assert.deepEqual(Object.keys(typicalFields(0)).sort(), ['accountTypeId', 'balance', 'firstFundDate', 'floor', 'positiveFunds', 'status']); let typical = 0; for (let i = 0; i < 500; i++) typical = Math.max(typical, added(typicalFields(i), i)); console.log(` # new fields per account: ${worst} B fully populated (worst platform), ${typical} B typical live`); assert.ok(worst < 230, `fully populated adds ${worst} B`); assert.ok(typical < 180, `typical live adds ${typical} B`); }); test('size: 2000 fully populated accounts < 600 KB, the whole report body < 900 KB', () => { const firms = PLATFORMS.map((platform, f) => ({ firm: `Firm ${f}`, totalAccounts: 500, accountsTraded: 500, inTrade: 0, targetHit: 0, partial: 0, loss: 500, personalDll: 0, completed: 0, accounts: Array.from({ length: 500 }, (_, i) => ({ ...legacyEntry(f * 500 + i), ...fullFields(platform, i) })), accountTypes: [{ accountTypeId: TYPE_ID, label: '150K Eval', accountSize: 150_000, totalAccounts: 500, accountsTraded: 500, inTrade: 0, targetHit: 0, partial: 0, loss: 500 }], ...firmStatusFields(`00000000-0000-4000-8000-00000000000${f}`, true, Date.parse('2026-10-06T14:00:00Z')), })); const accountsBytes = JSON.stringify(firms.flatMap((f) => f.accounts)).length; const body = JSON.stringify({ instance: 'Someone', id: '00000000-0000-4000-8000-000000000000', firms, scheduler: { running: true, lastTicks: [] }, version: '8747dd6', updater: null, }).length; console.log(` # 2000 accounts: ${accountsBytes} B; report body: ${body} B`); assert.ok(accountsBytes < 600_000, `accounts serialize to ${accountsBytes} B`); assert.ok(body < 900_000, `report body is ${body} B`); });