81e51236c0
Co-authored-by: Cursor <cursoragent@cursor.com>
214 lines
7.4 KiB
Python
214 lines
7.4 KiB
Python
"""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,
|
||
}
|