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>
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
"""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,
|
||||
}
|
||||
@@ -50,7 +50,15 @@ class Ledger:
|
||||
(group_id, kind, float(amount), equity, note, now),
|
||||
)
|
||||
self.db._conn.commit()
|
||||
return equity
|
||||
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 清零)。须在无持仓时调用。"""
|
||||
@@ -68,7 +76,15 @@ class Ledger:
|
||||
(None, "reset", amt, amt, note, now),
|
||||
)
|
||||
self.db._conn.commit()
|
||||
return amt
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user