"""SIM 资金钱包:资金账户 / 交易账户 × USDT|USDC(对齐 OKX,无期权分账户)。""" from __future__ import annotations import time from typing import Any from ..models.db import Database, get_db # DB 列名(历史兼容:USDC 存在 options_*_usdc 列,语义为资金/交易账户 USDC) WALLET_KEYS = ( "funding_usdt", "trading_usdt", "options_funding_usdc", # = funding_usdc "options_trading_usdc", # = trading_usdc "options_funding_usdt", # 废弃,恒为 0 "options_trading_usdt", # 废弃,恒为 0 ) _ACCT_MAP = { ("funding", "usdt"): "funding_usdt", ("trading", "usdt"): "trading_usdt", ("funding", "usdc"): "options_funding_usdc", ("trading", "usdc"): "options_trading_usdc", } def _now_ms() -> int: return int(time.time() * 1000) class SimFundsWallets: def __init__(self, db: Database | None = None) -> None: self.db = db or get_db() def snapshot(self) -> dict[str, float]: row = self.db.fetchone("SELECT * FROM funds_wallets WHERE id=1") if row is None: return {k: 0.0 for k in WALLET_KEYS} return {k: float(row[k] or 0) for k in WALLET_KEYS} def view(self) -> dict[str, float]: """对外口径:funding/trading × usdt/usdc。""" s = self.snapshot() return { "funding_usdt": float(s["funding_usdt"]), "trading_usdt": float(s["trading_usdt"]), "funding_usdc": float(s["options_funding_usdc"]), "trading_usdc": float(s["options_trading_usdc"]), } def total_usdt_equiv(self, snap: dict[str, float] | None = None) -> float: """USDC 按 1:1 计入总资金。""" if snap is None: v = self.view() elif "funding_usdc" in snap: v = snap else: v = { "funding_usdt": float(snap.get("funding_usdt") or 0), "trading_usdt": float(snap.get("trading_usdt") or 0), "funding_usdc": float(snap.get("options_funding_usdc") or 0), "trading_usdc": float(snap.get("options_trading_usdc") or 0), } return round( float(v.get("funding_usdt") or 0) + float(v.get("trading_usdt") or 0) + float(v.get("funding_usdc") or 0) + float(v.get("trading_usdc") or 0), 8, ) def reset_from_equity(self, equity: float) -> dict[str, float]: """重置:全部放入资金账户 USDT。""" amt = max(0.0, float(equity)) now = _now_ms() self.db.execute( """UPDATE funds_wallets SET funding_usdt=?, trading_usdt=0, options_funding_usdc=0, options_trading_usdc=0, options_funding_usdt=0, options_trading_usdt=0, updated_at_ms=? WHERE id=1""", (amt, now), ) return self.snapshot() def _set(self, **kwargs: float) -> dict[str, float]: snap = self.snapshot() for k, v in kwargs.items(): if k in WALLET_KEYS: snap[k] = float(v) now = _now_ms() self.db.execute( """UPDATE funds_wallets SET funding_usdt=?, trading_usdt=?, options_funding_usdc=?, options_trading_usdc=?, options_funding_usdt=0, options_trading_usdt=0, updated_at_ms=? WHERE id=1""", ( snap["funding_usdt"], snap["trading_usdt"], snap["options_funding_usdc"], snap["options_trading_usdc"], now, ), ) return snap def mirror_cash(self, amount: float, *, kind: str) -> None: """策略账本变动镜像到交易账户(永续 USDT / 期权 USDC)。""" amt = float(amount) if abs(amt) < 1e-12: return snap = self.snapshot() k = (kind or "").lower() if "option" in k: key = "options_trading_usdc" else: key = "trading_usdt" snap[key] = float(snap.get(key) or 0) + amt self._set(**snap) def sync_ledger_equity(self) -> float: total = self.total_usdt_equiv() now = _now_ms() self.db.execute( "UPDATE ledger_meta SET equity=?, available=?, updated_at_ms=? WHERE id=1", (total, total, now), ) return total def convert( self, *, direction: str, amount: float, rate: float = 1.0, ) -> dict[str, Any]: """资金账户内 USDT↔USDC 兑换。""" amt = float(amount) if amt <= 0: return {"ok": False, "detail": "数量须大于 0"} r = float(rate) if rate and rate > 0 else 1.0 d = (direction or "").strip().lower() snap = self.snapshot() if d == "usdt_to_usdc": src = float(snap["funding_usdt"]) if amt > src + 1e-9: return {"ok": False, "detail": f"资金账户 USDT 不足(可用 {src:.4f})"} usdc = amt / r snap["funding_usdt"] = src - amt snap["options_funding_usdc"] = float(snap["options_funding_usdc"]) + usdc elif d == "usdc_to_usdt": src = float(snap["options_funding_usdc"]) if amt > src + 1e-9: return {"ok": False, "detail": f"资金账户 USDC 不足(可用 {src:.4f})"} usdt = amt * r snap["options_funding_usdc"] = src - amt snap["funding_usdt"] = float(snap["funding_usdt"]) + usdt else: return {"ok": False, "detail": "direction 须为 usdt_to_usdc 或 usdc_to_usdt"} self._set(**snap) total = self.sync_ledger_equity() return { "ok": True, "detail": "converted", "direction": d, "amount": amt, "rate": r, "wallets": self.view(), "total_usdt_equiv": total, } def transfer( self, *, ccy: str, amount: float, from_account: str, to_account: str, ) -> dict[str, Any]: """仅资金账户 ↔ 交易账户。""" amt = float(amount) if amt <= 0: return {"ok": False, "detail": "划转金额须大于 0"} ccy_l = (ccy or "USDC").strip().lower() fa = (from_account or "").strip().lower() ta = (to_account or "").strip().lower() allowed = {"funding", "trading"} if fa not in allowed or ta not in allowed: return {"ok": False, "detail": "账户仅支持 funding / trading"} if fa == ta: return {"ok": False, "detail": "来源与目标账户不能相同"} src_key = _ACCT_MAP.get((fa, ccy_l)) dst_key = _ACCT_MAP.get((ta, ccy_l)) if not src_key or not dst_key: return {"ok": False, "detail": "币种须为 USDT 或 USDC"} snap = self.snapshot() src_bal = float(snap[src_key]) if amt > src_bal + 1e-9: return {"ok": False, "detail": f"余额不足(可用 {src_bal:.4f})"} snap[src_key] = src_bal - amt snap[dst_key] = float(snap[dst_key]) + amt self._set(**snap) total = self.sync_ledger_equity() return { "ok": True, "detail": "transferred", "ccy": ccy_l.upper(), "amount": amt, "from": fa, "to": ta, "wallets": self.view(), "total_usdt_equiv": total, }