/** * Persistence — a single JSON file written atomically (tmp + rename). No native * deps, survives restarts, and the exact shape a future `/api/*` endpoint would * serve to make the site's Performance + fills honest. Swap for SQLite later * without touching callers (the engine only talks to getState()/save()). */ import { readFileSync, writeFileSync, mkdirSync, renameSync, existsSync } from 'node:fs' import { dirname } from 'node:path' import { CONFIG } from './config.ts' import type { Direction } from '../src/lib/signals.ts' export type Side = Exclude export type Position = { id: string coin: string dir: Side entry: number notional: number // USD exposure (= equity * size% * leverage) tp: number sl: number tpPct: number slPct: number confidence: number reason: string openedAt: number } export type Trade = Position & { exit: number closedAt: number pnlPct: number // direction-adjusted price move, % pnlUsd: number // notional * pnlPct outcome: 'tp' | 'sl' holdMin: number } export type SignalLog = { t: number coin: string decision: Direction confidence: number price: number net: number reason: string } export type State = { startedAt: number mode: string equity: number positions: Position[] closed: Trade[] signals: SignalLog[] equityCurve: { t: number; equity: number }[] } let state: State | null = null function fresh(): State { return { startedAt: Date.now(), mode: CONFIG.mode, equity: CONFIG.equityStart, positions: [], closed: [], signals: [], equityCurve: [{ t: Date.now(), equity: CONFIG.equityStart }], } } /** Load persisted state, or start fresh. Idempotent. */ export function load(): State { if (state) return state try { if (existsSync(CONFIG.dataFile)) { const raw = JSON.parse(readFileSync(CONFIG.dataFile, 'utf8')) as State state = { ...fresh(), ...raw } return state } } catch (e) { console.error('[store] load failed, starting fresh:', (e as Error).message) } state = fresh() return state } export function getState(): State { return state ?? load() } /** Atomic write: serialise to `.tmp`, then rename over the real file. */ export function save(): void { const st = getState() try { mkdirSync(dirname(CONFIG.dataFile), { recursive: true }) const tmp = `${CONFIG.dataFile}.tmp` writeFileSync(tmp, JSON.stringify(st, null, 2)) renameSync(tmp, CONFIG.dataFile) } catch (e) { console.error('[store] save failed:', (e as Error).message) } }