larp-quant-agent/worker/store.ts
zefiroff f3b533b032
All checks were successful
deploy / deploy (push) Successful in 7s
Quant Agent — multi-agent quant terminal on Hyperliquid
Live HL data → in-browser TA → 4-agent committee → LONG/SHORT/SKIP verdict.
Terminal grid command bar + interactive agent-workflow graph. Paper-trading
worker reuses the browser brain (dry-run + Telegram). Forgejo static deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 19:00:13 +04:00

103 lines
2.5 KiB
TypeScript

/**
* 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<Direction, 'SKIP'>
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 `<file>.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)
}
}