469e7a258a
Co-authored-by: Cursor <cursoragent@cursor.com>
116 lines
4.2 KiB
Python
116 lines
4.2 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 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()
|
|
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")
|