39875112a0
Python bot (bot/alpaclaudia): alpaca-py client, wheel strategy (CSP + covered calls) plus equity trailing stops, risk gates (cash buffer, cost-basis guard, per-symbol concentration cap), SQLite state log, Typer CLI (tick/loop/status/ report/dump-state), Discord daily report, pytest suite. Next.js 14 dashboard (dashboard/): read-only — reads the bot's SQLite directly and pulls live account/positions/orders from Alpaca. KPIs, equity chart, positions, bot-intents audit table, and orders table. Dark UI with Tailwind. systemd/: user-unit templates for the polling loop and the post-close report timer. docs/STRATEGY.md: wheel mechanics, risk invariants, later candidates. Defaults to BOT_MODE=dry — nothing is submitted to Alpaca until explicitly enabled in .env. ALPACA_ENV=paper by default; flipping to live requires an explicit second guard.
73 lines
2.0 KiB
TypeScript
73 lines
2.0 KiB
TypeScript
import Database from "better-sqlite3";
|
|
import path from "node:path";
|
|
|
|
type Row = Record<string, unknown>;
|
|
|
|
let _db: Database.Database | null = null;
|
|
|
|
function dbPath(): string {
|
|
const raw = process.env.ALPACLAUDIA_DB || "../data/alpaclaudia.db";
|
|
return path.isAbsolute(raw) ? raw : path.resolve(process.cwd(), raw);
|
|
}
|
|
|
|
function getDb(): Database.Database | null {
|
|
if (_db) return _db;
|
|
try {
|
|
_db = new Database(dbPath(), { readonly: true, fileMustExist: true });
|
|
return _db;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function recentTicks(limit = 300): Row[] {
|
|
const db = getDb();
|
|
if (!db) return [];
|
|
return db
|
|
.prepare(`SELECT id, ts, equity, cash, buying_power, mode FROM ticks ORDER BY id DESC LIMIT ?`)
|
|
.all(limit) as Row[];
|
|
}
|
|
|
|
export function recentIntents(limit = 200): Row[] {
|
|
const db = getDb();
|
|
if (!db) return [];
|
|
return db
|
|
.prepare(
|
|
`SELECT id, ts, strategy, symbol, side, qty, order_type, limit_price,
|
|
trail_percent, details_json, submitted, alpaca_order_id, status
|
|
FROM order_intents ORDER BY id DESC LIMIT ?`
|
|
)
|
|
.all(limit) as Row[];
|
|
}
|
|
|
|
export function intentsSince(iso: string): Row[] {
|
|
const db = getDb();
|
|
if (!db) return [];
|
|
return db
|
|
.prepare(
|
|
`SELECT id, ts, strategy, symbol, side, qty, order_type, limit_price,
|
|
trail_percent, details_json, submitted, alpaca_order_id, status
|
|
FROM order_intents WHERE ts >= ? ORDER BY id DESC`
|
|
)
|
|
.all(iso) as Row[];
|
|
}
|
|
|
|
export function recentEvents(limit = 100): Row[] {
|
|
const db = getDb();
|
|
if (!db) return [];
|
|
return db
|
|
.prepare(`SELECT id, ts, kind, payload_json FROM events ORDER BY id DESC LIMIT ?`)
|
|
.all(limit) as Row[];
|
|
}
|
|
|
|
export function dbStatus(): { ok: boolean; path: string; error?: string } {
|
|
const p = dbPath();
|
|
try {
|
|
const db = getDb();
|
|
if (!db) return { ok: false, path: p, error: "could not open db" };
|
|
return { ok: true, path: p };
|
|
} catch (e: unknown) {
|
|
return { ok: false, path: p, error: e instanceof Error ? e.message : String(e) };
|
|
}
|
|
}
|