Files
autofirmer-expanded/lib/db.ts
T
SenofyandClaude Sonnet 4.6 563554c58f Remove hardcoded firm credentials from DB seed
Plaintext usernames and passwords for Alpha and TakeProfitTrader have
been removed from the startup seed block. Firms must now be added
manually through the UI or API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 13:52:25 -05:00

199 lines
7.5 KiB
TypeScript

import Database from 'better-sqlite3';
import path from 'path';
const db = new Database(path.join(process.cwd(), 'autotrader.sqlite'));
db.pragma('foreign_keys = ON');
db.exec(`
CREATE TABLE IF NOT EXISTS firms (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
username TEXT NOT NULL,
password TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS account_configs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
firm_id INTEGER NOT NULL REFERENCES firms(id) ON DELETE CASCADE,
prefix TEXT NOT NULL,
profit_target REAL NOT NULL DEFAULT 3000,
consistency REAL NOT NULL DEFAULT 0.5,
min_day_pnl REAL NOT NULL DEFAULT -999,
min_trading_days INTEGER NOT NULL DEFAULT 5
);
CREATE TABLE IF NOT EXISTS instruments (
symbol TEXT PRIMARY KEY,
enabled INTEGER NOT NULL DEFAULT 1
);
`);
// Migration: add account_size column if it doesn't exist yet
try {
db.exec('ALTER TABLE account_configs ADD COLUMN account_size REAL NOT NULL DEFAULT 50000');
} catch {
// Column already exists
}
// Migration: add max_loss column (0 = no max loss limit)
try {
db.exec('ALTER TABLE account_configs ADD COLUMN max_loss REAL NOT NULL DEFAULT 0');
} catch {
// Column already exists
}
// Migration: add max_position_size column (0 = no limit)
try {
db.exec('ALTER TABLE account_configs ADD COLUMN max_position_size INTEGER NOT NULL DEFAULT 0');
} catch {
// Column already exists
}
// ── Interfaces ─────────────────────────────────────────────────────────────
export interface AccountConfigRow {
id: number;
firm_id: number;
prefix: string;
profit_target: number;
consistency: number;
min_day_pnl: number;
min_trading_days: number;
account_size: number;
max_loss: number;
max_position_size: number;
}
export interface FirmRow {
id: number;
name: string;
username: string;
password: string;
}
export interface FirmWithAccounts extends FirmRow {
accounts: AccountConfigRow[];
}
// ── Firms ───────────────────────────────────────────────────────────────────
export function getFirms(): FirmWithAccounts[] {
const firms = db.prepare('SELECT * FROM firms ORDER BY id').all() as FirmRow[];
const getAccounts = db.prepare('SELECT * FROM account_configs WHERE firm_id = ? ORDER BY id');
return firms.map((firm) => ({
...firm,
accounts: getAccounts.all(firm.id) as AccountConfigRow[],
}));
}
export function getFirmById(id: number): FirmWithAccounts | undefined {
const firm = db.prepare('SELECT * FROM firms WHERE id = ?').get(id) as FirmRow | undefined;
if (!firm) return undefined;
const accounts = db.prepare('SELECT * FROM account_configs WHERE firm_id = ? ORDER BY id').all(id) as AccountConfigRow[];
return { ...firm, accounts };
}
export function createFirm(name: string, username: string, password: string): FirmRow {
const stmt = db.prepare('INSERT INTO firms (name, username, password) VALUES (?, ?, ?)');
const result = stmt.run(name, username, password);
return db.prepare('SELECT * FROM firms WHERE id = ?').get(result.lastInsertRowid) as FirmRow;
}
export function deleteFirm(id: number): boolean {
const result = db.prepare('DELETE FROM firms WHERE id = ?').run(id);
return result.changes > 0;
}
// ── Account Configs ─────────────────────────────────────────────────────────
export function createAccountConfig(firmId: number, data: {
prefix: string;
profitTarget: number;
consistency: number;
minDayPnL: number;
minTradingDays: number;
accountSize: number;
maxLoss: number;
maxPositionSize: number;
}): AccountConfigRow {
const stmt = db.prepare(
'INSERT INTO account_configs (firm_id, prefix, profit_target, consistency, min_day_pnl, min_trading_days, account_size, max_loss, max_position_size) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'
);
const result = stmt.run(firmId, data.prefix, data.profitTarget, data.consistency, data.minDayPnL, data.minTradingDays, data.accountSize, data.maxLoss, data.maxPositionSize);
return db.prepare('SELECT * FROM account_configs WHERE id = ?').get(result.lastInsertRowid) as AccountConfigRow;
}
export function deleteAccountConfig(id: number): boolean {
const result = db.prepare('DELETE FROM account_configs WHERE id = ?').run(id);
return result.changes > 0;
}
export function updateAccountConfig(id: number, data: {
prefix: string;
profitTarget: number;
consistency: number;
minDayPnL: number;
minTradingDays: number;
accountSize: number;
maxLoss: number;
maxPositionSize: number;
}): boolean {
const result = db.prepare(`
UPDATE account_configs
SET prefix = ?, profit_target = ?, consistency = ?, min_day_pnl = ?, min_trading_days = ?, account_size = ?, max_loss = ?, max_position_size = ?
WHERE id = ?
`).run(data.prefix, data.profitTarget, data.consistency, data.minDayPnL, data.minTradingDays, data.accountSize, data.maxLoss, data.maxPositionSize, id);
return result.changes > 0;
}
// ── Settings ─────────────────────────────────────────────────────────────────
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 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 ──────────────────────────────────────────────────────────────
const SYMBOLS = ['NQ','MNQ','ES','MES','YM','MYM','RTY','M2K','GC','MGC','SI','CL','MCL','NG','ZB','ZN','ZF','6E','6J','6B'];
// Seed instruments table if empty
const instrCount = (db.prepare('SELECT COUNT(*) as count FROM instruments').get() as { count: number }).count;
if (instrCount === 0) {
const ins = db.prepare('INSERT INTO instruments (symbol, enabled) VALUES (?, 1)');
for (const s of SYMBOLS) ins.run(s);
}
export interface InstrumentRow {
symbol: string;
enabled: boolean;
}
export function getInstruments(): InstrumentRow[] {
return (db.prepare('SELECT symbol, enabled FROM instruments ORDER BY symbol').all() as { symbol: string; enabled: number }[])
.map((r) => ({ symbol: r.symbol, enabled: r.enabled === 1 }));
}
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;
}