Add auto-trade scheduler with batch locking, commission gross-up, and sync gate

- Auto-trade scheduler fires every 60s; uses Promise.allSettled batch so no new trades fire while any position from the current batch is open
- Commission gross-up: read entryCommission from cash.realizedPnL after fill (fallback 2.5×contracts), grossTarget = target + 2×entryCommission
- Sync gate: TradovateClient.syncComplete flag; scheduler skips tick until every client finishes initial position/balance sync
- Contracts formula changed to Math.ceil so $1500 target = 2 contracts
- Removed all fee caching (perContractFees, recentFills, fillFee handler) from tradovate-class.ts
- Removed firm_fees table, getFirmFees, upsertFirmFee from db.ts
- Deleted instrument-configs API routes; removed Fees UI from firm settings page
- /api/instruments returns full {symbol, enabled}[] objects; dashboard filters to enabled-only for trade selector
- Added auto-trade, debug, orders, settings, and trade API routes
- Instrument selector on dashboard now driven by enabled instruments from DB

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Senofy
2026-03-09 03:31:47 -05:00
co-authored by Claude Sonnet 4.6
parent 532c2e2279
commit b2a1bdd1c3
19 changed files with 1050 additions and 218 deletions
+17 -36
View File
@@ -23,14 +23,6 @@ db.exec(`
min_trading_days INTEGER NOT NULL DEFAULT 5
);
CREATE TABLE IF NOT EXISTS firm_fees (
firm_id INTEGER NOT NULL REFERENCES firms(id) ON DELETE CASCADE,
symbol TEXT NOT NULL,
allin_fee REAL NOT NULL DEFAULT 0,
roundtrip_fee REAL NOT NULL DEFAULT 0,
PRIMARY KEY (firm_id, symbol)
);
CREATE TABLE IF NOT EXISTS instruments (
symbol TEXT PRIMARY KEY,
enabled INTEGER NOT NULL DEFAULT 1
@@ -98,13 +90,6 @@ export interface FirmWithAccounts extends FirmRow {
accounts: AccountConfigRow[];
}
export interface FirmFee {
firmId: number;
symbol: string;
allinFee: number;
roundtripFee: number;
}
// ── Firms ───────────────────────────────────────────────────────────────────
export function getFirms(): FirmWithAccounts[] {
@@ -174,30 +159,26 @@ export function updateAccountConfig(id: number, data: {
return result.changes > 0;
}
// ── Firm Fees ────────────────────────────────────────────────────────────────
// ── Settings ────────────────────────────────────────────────────────────────
export function getFirmFees(firmId: number): FirmFee[] {
return (db.prepare('SELECT firm_id, symbol, allin_fee, roundtrip_fee FROM firm_fees WHERE firm_id = ? ORDER BY symbol').all(firmId) as {
firm_id: number;
symbol: string;
allin_fee: number;
roundtrip_fee: number;
}[]).map((r) => ({
firmId: r.firm_id,
symbol: r.symbol,
allinFee: r.allin_fee,
roundtripFee: r.roundtrip_fee,
}));
db.exec(`
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
`);
// Seed defaults if missing
const seedSetting = db.prepare(`INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)`);
seedSetting.run('max_concurrent_accounts', '5');
export function getSetting(key: string): string | null {
const row = db.prepare('SELECT value FROM settings WHERE key = ?').get(key) as { value: string } | undefined;
return row?.value ?? null;
}
export function upsertFirmFee(firmId: number, symbol: string, allinFee: number, roundtripFee: number): void {
db.prepare(`
INSERT INTO firm_fees (firm_id, symbol, allin_fee, roundtrip_fee)
VALUES (?, ?, ?, ?)
ON CONFLICT(firm_id, symbol) DO UPDATE SET
allin_fee = excluded.allin_fee,
roundtrip_fee = excluded.roundtrip_fee
`).run(firmId, symbol, allinFee, roundtripFee);
export function setSetting(key: string, value: string): void {
db.prepare(`INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(key, value);
}
// ── Instruments ──────────────────────────────────────────────────────────────