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, commit: bool = True, ) -> float: """amount>0 入账;amount<0 出账。返回余额。 LIVE 实盘成交后本地账本仅作镜像,须 allow_negative=True,避免「交易所已成交、本地拒记」导致卡仓。 commit=False:由调用方持锁并统一提交(与持仓/残留状态同事务)。 """ 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), ) if commit: self.db._conn.commit() if commit: try: from ..config import get_settings from .funds_wallets import SimFundsWallets if get_settings().is_sim: SimFundsWallets(self.db).mirror_cash(float(amount), kind=kind) except Exception: pass return equity def clear_trade_history(self) -> None: """清空交易记录与持仓痕迹(组/成交/残留/账本流水),仓位置 flat。""" now = int(time.time() * 1000) with self.db._lock: self.db._conn.execute("DELETE FROM fills") self.db._conn.execute("DELETE FROM residual_options") self.db._conn.execute("DELETE FROM groups") self.db._conn.execute("DELETE FROM ledger_entries") self.db._conn.execute( """UPDATE positions SET group_id=NULL, perp_side=NULL, perp_qty_eth=0, perp_entry_px=NULL, option_inst_id=NULL, option_side=NULL, option_qty_eth=0, option_qty_contracts=0, option_entry_px=NULL, entry_index_px=NULL, initial_premium=0, exit_target_usdt=NULL, status='flat' WHERE id=1""" ) self.db._conn.execute( """UPDATE strategy_state SET rounds_done=0, window_key=NULL, rest_until_ms=NULL, last_error=NULL, phase=CASE WHEN running=1 THEN phase ELSE 'idle' END, updated_at_ms=? WHERE id=1""", (now,), ) self.db._conn.execute( "DELETE FROM settings WHERE key=?", ("risk_last_k",) ) self.db._conn.commit() def reset_equity(self, amount: float, *, note: str = "重置模拟资金") -> float: """将权益与可用资金重置为 amount(reserved 清零),并清空交易记录。须在无持仓时调用。""" now = int(time.time() * 1000) amt = float(amount) if amt < 0: raise ValueError("模拟资金不能为负") self.clear_trade_history() 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() try: from ..config import get_settings from .funds_wallets import SimFundsWallets if get_settings().is_sim: SimFundsWallets(self.db).reset_from_equity(amt) except Exception: pass 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")