Files
eth_hedge_sim/backend/app/sim/funds_wallets.py
T
dekun 5c3bd4b654 Add OKX-style funds bar with USDT/USDC convert and transfer.
SIM uses multi-wallet balances; LIVE hits OKX asset/account APIs and spot USDC-USDT swap. Funds strip hides sim labeling.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 17:06:13 +08:00

203 lines
7.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""SIM 多账户资金:资金/交易 USDT + 期权资金/交易 USDC(对齐 OKX 展示)。"""
from __future__ import annotations
import time
from typing import Any
from ..models.db import Database, get_db
WALLET_KEYS = (
"funding_usdt",
"trading_usdt",
"options_funding_usdc",
"options_trading_usdc",
"options_funding_usdt",
"options_trading_usdt",
)
# 划转账户映射
_ACCT_MAP = {
("funding", "usdt"): "funding_usdt",
("trading", "usdt"): "trading_usdt",
("options_funding", "usdc"): "options_funding_usdc",
("options_trading", "usdc"): "options_trading_usdc",
("options_funding", "usdt"): "options_funding_usdt",
("options_trading", "usdt"): "options_trading_usdt",
# 简化别名:funding/trading + usdc → 期权侧
("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 total_usdt_equiv(self, snap: dict[str, float] | None = None) -> float:
"""USDC 按 1:1 计入总资金(与 crypto_monitor 一致)。"""
s = snap or self.snapshot()
return round(sum(float(s.get(k) or 0) for k in WALLET_KEYS), 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=?, options_trading_usdt=?, updated_at_ms=?
WHERE id=1""",
(
snap["funding_usdt"],
snap["trading_usdt"],
snap["options_funding_usdc"],
snap["options_trading_usdc"],
snap["options_funding_usdt"],
snap["options_trading_usdt"],
now,
),
)
return snap
def mirror_cash(self, amount: float, *, kind: str) -> None:
"""策略账本变动时镜像到对应钱包(SIM)。"""
amt = float(amount)
if abs(amt) < 1e-12:
return
snap = self.snapshot()
k = (kind or "").lower()
if "option" in k:
key = "options_trading_usdc"
elif "perp" in k or "funding" in k:
key = "trading_usdt"
else:
key = "trading_usdt"
snap[key] = float(snap.get(key) or 0) + amt
# 允许短暂为负(与 ledger allow_negative 对齐时由调用方保证);展示侧夹到合理范围不在此做
self._set(**snap)
def sync_ledger_equity(self) -> float:
"""兑换/划转后把总权益同步到 ledger_meta(不重置钱包分配)。"""
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]:
"""
SIM 兑换(默认在「资金账户」内 USDT↔USDC,对齐 crypto_monitor 主路径)。
direction: usdt_to_usdc | usdc_to_usdt
rate: 1 USDC = rate USDT(默认 1.0
"""
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.snapshot(),
"total_usdt_equiv": total,
}
def transfer(
self,
*,
ccy: str,
amount: float,
from_account: str,
to_account: str,
) -> dict[str, Any]:
"""SIM 划转:funding/trading/options_funding/options_trading × USDT|USDC。"""
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()
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": "账户须为 funding/trading/options_funding/options_trading,币种 USDT|USDC",
}
snap = self.snapshot()
src_bal = float(snap[src_key])
if amt > src_bal + 1e-9:
return {"ok": False, "detail": f"{src_key} 余额不足(可用 {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.snapshot(),
"total_usdt_equiv": total,
}