Files
eth_hedge_sim/backend/app/sim/ledger.py
T
dekun 930e6c26c5 Clear trade history when resetting sim equity.
Deleting groups/fills/residuals and resetting strategy counters keeps funds reset a clean slate.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 14:49:51 +08:00

146 lines
5.6 KiB
Python

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")