Add master dashboard reporter and settings UI
- New reporter module that pushes firm stats (total accounts, accounts traded, in trade) to a configurable master dashboard every 30 seconds - Add Instance Name and Dashboard URL fields to the settings page - Register master_dashboard_url and instance_name in settings API - Seed default (empty) values for new settings in db Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
15ef2d7adc
commit
f112118090
+103
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Reporter module — pushes aggregated state to the master dashboard.
|
||||
*
|
||||
* Every 30 seconds, collects firm-level stats + scheduler state and POSTs
|
||||
* to the configured master_dashboard_url. Silently skips if not configured.
|
||||
*/
|
||||
|
||||
import { getSetting } from './db';
|
||||
import { getFirms } from './db';
|
||||
import { getClients } from './clients';
|
||||
import { getSchedulerStatus } from './auto-trade';
|
||||
import { computeDailyTarget } from './trading-logic';
|
||||
import type { AccountConfigRow } from './db';
|
||||
|
||||
function getAccountConfig(name: string, accounts: AccountConfigRow[]): AccountConfigRow | undefined {
|
||||
return [...accounts]
|
||||
.sort((a, b) => b.prefix.length - a.prefix.length)
|
||||
.find((a) => name.startsWith(a.prefix));
|
||||
}
|
||||
|
||||
function collectFirmStats() {
|
||||
const firms = getFirms();
|
||||
const clients = getClients();
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
let totalAccounts = 0;
|
||||
let accountsTraded = 0;
|
||||
let inTrade = 0;
|
||||
|
||||
for (const acc of client.accountList) {
|
||||
const cash = client.accountCashBalances[acc.id] ?? { amount: 0, realizedPnL: 0 };
|
||||
const autoLiqThreshold = client.autoLiqThresholds[acc.id] ?? 0;
|
||||
const isDead = autoLiqThreshold > 0 && cash.amount <= autoLiqThreshold;
|
||||
if (isDead) continue;
|
||||
|
||||
totalAccounts++;
|
||||
|
||||
if (client.positions[acc.id]) {
|
||||
inTrade++;
|
||||
accountsTraded++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if target was hit today
|
||||
const cfg = getAccountConfig(acc.name, f.accounts);
|
||||
if (cfg) {
|
||||
const dailyPnL = client.dailyPnL[acc.id] ?? [];
|
||||
const totalProfit = dailyPnL.reduce((sum: number, d: { pnl: number }) => sum + d.pnl, 0);
|
||||
const target = computeDailyTarget(cfg.profit_target, cfg.consistency, totalProfit, dailyPnL, cfg.min_day_pnl, cfg.min_trading_days);
|
||||
const targetHit =
|
||||
(target.amount === 0 && Math.abs(cash.realizedPnL) > 0 && (client.daysTraded[acc.id] ?? 0) <= cfg.min_trading_days) ||
|
||||
(cash.realizedPnL >= target.amount);
|
||||
|
||||
if (targetHit || cash.realizedPnL !== 0) {
|
||||
accountsTraded++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { firm: f.name, totalAccounts, accountsTraded, inTrade };
|
||||
});
|
||||
}
|
||||
|
||||
// HMR-safe global to avoid duplicate intervals
|
||||
const _g = globalThis as typeof globalThis & { __reporterInterval?: ReturnType<typeof setInterval> };
|
||||
|
||||
export function startReporter() {
|
||||
if (_g.__reporterInterval) {
|
||||
clearInterval(_g.__reporterInterval);
|
||||
}
|
||||
|
||||
const report = async () => {
|
||||
const url = getSetting('master_dashboard_url');
|
||||
const instanceName = getSetting('instance_name');
|
||||
|
||||
if (!url || !instanceName) return;
|
||||
|
||||
try {
|
||||
const firms = collectFirmStats();
|
||||
const scheduler = getSchedulerStatus();
|
||||
|
||||
await fetch(`${url}/api/report`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ instance: instanceName, firms, scheduler }),
|
||||
});
|
||||
} catch {
|
||||
// Master might be down — silently continue
|
||||
}
|
||||
};
|
||||
|
||||
_g.__reporterInterval = setInterval(report, 30_000);
|
||||
|
||||
// Fire first report after a short delay to let clients sync
|
||||
setTimeout(report, 10_000);
|
||||
|
||||
console.log('[reporter] started — reporting every 30s');
|
||||
}
|
||||
Reference in New Issue
Block a user