Add per-firm banned symbols feature

- lib/db.ts: new firm_banned_symbols table with getBannedSymbols,
  isSymbolBanned, and setBannedSymbol helpers
- app/api/firms/[id]/banned-symbols/route.ts: GET lists banned symbols,
  PATCH toggles a ban for a given symbol
- app/api/firms/route.ts: include bannedSymbols[] in firm list response
- app/firms/[id]/settings/page.tsx: Instruments section shows all
  globally-enabled symbols with a red toggle to ban/unban; banned
  symbols display a "SYMBOL BANNED" pill next to their name
- app/page.tsx: FirmRows shows "ES BANNED" (or current symbol) pill next
  to the firm name when the selected trade symbol is banned for that firm
- lib/auto-trade.ts: skip firms entirely when the trade symbol is banned
- types.ts: add bannedSymbols field to FirmConfig

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Senofy
2026-03-12 03:29:16 -05:00
co-authored by Claude Sonnet 4.6
parent d945e0038f
commit 6d3e8b6065
7 changed files with 150 additions and 3 deletions
+7 -1
View File
@@ -7,7 +7,7 @@
* signal but have since exited and are now eligible.
*/
import { getFirms } from './db';
import { getFirms, isSymbolBanned } from './db';
import { getClients } from './clients';
import { computeDailyTarget, POINT_VALUES } from './trading-logic';
import { getSetting } from './db';
@@ -100,6 +100,12 @@ export async function runTrade(action: 'Buy' | 'Sell' | 'Random', symbol: string
const client = clients.get(firm.id);
if (!client || client.accountList.length === 0) return;
// Skip this firm entirely if the symbol is banned for it
if (isSymbolBanned(firm.id, symbol)) {
console.log(`[auto-trade] ${firm.name}: ${symbol} is banned — skipping firm`);
return;
}
const firmConfig = mapFirmConfig(firm);
const contract = await client.findFrontMonthContract(symbol);
+27
View File
@@ -196,3 +196,30 @@ export function setInstrumentEnabled(symbol: string, enabled: boolean): boolean
const result = db.prepare('UPDATE instruments SET enabled = ? WHERE symbol = ?').run(enabled ? 1 : 0, symbol);
return result.changes > 0;
}
// ── Firm banned symbols ───────────────────────────────────────────────────────
db.exec(`
CREATE TABLE IF NOT EXISTS firm_banned_symbols (
firm_id INTEGER NOT NULL REFERENCES firms(id) ON DELETE CASCADE,
symbol TEXT NOT NULL,
PRIMARY KEY (firm_id, symbol)
);
`);
export function getBannedSymbols(firmId: number): string[] {
return (db.prepare('SELECT symbol FROM firm_banned_symbols WHERE firm_id = ? ORDER BY symbol').all(firmId) as { symbol: string }[])
.map((r) => r.symbol);
}
export function isSymbolBanned(firmId: number, symbol: string): boolean {
return !!db.prepare('SELECT 1 FROM firm_banned_symbols WHERE firm_id = ? AND symbol = ?').get(firmId, symbol);
}
export function setBannedSymbol(firmId: number, symbol: string, banned: boolean): void {
if (banned) {
db.prepare('INSERT OR IGNORE INTO firm_banned_symbols (firm_id, symbol) VALUES (?, ?)').run(firmId, symbol);
} else {
db.prepare('DELETE FROM firm_banned_symbols WHERE firm_id = ? AND symbol = ?').run(firmId, symbol);
}
}