from __future__ import annotations import time from typing import Any from ..models.db import Database, get_db class Ledger: def __init__(self, db: Database | None = None) -> None: self.db = db or get_db() def snapshot(self) -> dict[str, Any]: row = self.db.fetchone("SELECT * FROM ledger_meta WHERE id=1") assert row is not None return { "equity": float(row["equity"]), "available": float(row["available"]), "reserved": float(row["reserved"]), "updated_at_ms": int(row["updated_at_ms"]), } def apply_cash( self, amount: float, *, kind: str, group_id: str | None = None, note: str = "", allow_negative: bool = False, ) -> float: """amount>0 入账;amount<0 出账。返回余额。 LIVE 实盘成交后本地账本仅作镜像,须 allow_negative=True,避免「交易所已成交、本地拒记」导致卡仓。 """ now = int(time.time() * 1000) with self.db._lock: row = self.db._conn.execute("SELECT * FROM ledger_meta WHERE id=1").fetchone() assert row is not None equity = float(row["equity"]) + float(amount) available = float(row["available"]) + float(amount) if not allow_negative and available < -1e-9: raise RuntimeError("可用资金不足") self.db._conn.execute( "UPDATE ledger_meta SET equity=?, available=?, updated_at_ms=? WHERE id=1", (equity, available, now), ) self.db._conn.execute( "INSERT INTO ledger_entries(group_id, kind, amount, balance_after, note, ts_ms) VALUES (?,?,?,?,?,?)", (group_id, kind, float(amount), equity, note, now), ) self.db._conn.commit() return equity def reset_equity(self, amount: float, *, note: str = "重置模拟资金") -> float: """将权益与可用资金重置为 amount(reserved 清零)。须在无持仓时调用。""" now = int(time.time() * 1000) amt = float(amount) if amt < 0: raise ValueError("模拟资金不能为负") with self.db._lock: self.db._conn.execute( "UPDATE ledger_meta SET equity=?, available=?, reserved=0, updated_at_ms=? WHERE id=1", (amt, amt, now), ) self.db._conn.execute( "INSERT INTO ledger_entries(group_id, kind, amount, balance_after, note, ts_ms) VALUES (?,?,?,?,?,?)", (None, "reset", amt, amt, note, now), ) self.db._conn.commit() return amt def get_setting_float(self, key: str, default: float) -> float: v = self.db.get_setting(key) if v is None or v == "": return default try: return float(v) except ValueError: return default def get_setting_int(self, key: str, default: int) -> int: return int(self.get_setting_float(key, float(default))) def get_setting_str(self, key: str, default: str) -> str: v = self.db.get_setting(key) if v is None or v == "": return default return str(v) def get_setting_bool(self, key: str, default: bool) -> bool: v = self.db.get_setting(key) if v is None or v == "": return default return str(v).strip().lower() in ("1", "true", "yes", "on")