diff --git a/lib/sim/hooks.py b/lib/sim/hooks.py index 90d8e69..f85e37c 100644 --- a/lib/sim/hooks.py +++ b/lib/sim/hooks.py @@ -136,6 +136,36 @@ def apply_sim_hooks(app_module: Any) -> None: app_module.place_exchange_order = place_exchange_order app_module.close_exchange_order = close_exchange_order app_module.get_live_position_contracts = get_live_position_contracts + + _orig_xfer = getattr(app_module, "execute_transfer_usdt", None) + if callable(_orig_xfer): + + def execute_transfer_usdt(amount, from_account, to_account): + if is_sim_mode(app_module.get_db): + from lib.sim.wallets_lib import SimWallets, normalize_sim_account + + try: + result = SimWallets(app_module.get_db).transfer( + ccy="USDT", + amount=float(amount), + from_account=normalize_sim_account(from_account), + to_account=normalize_sim_account(to_account), + ) + except Exception as e: + return False, str(e), None + if not result.get("ok"): + return False, result.get("detail") or result.get("msg") or "划转失败", None + try: + from lib.instance.instance_live_push_lib import notify_instance_balance_changed + + notify_instance_balance_changed() + except Exception: + pass + return True, "sim 划转成功", result + return _orig_xfer(amount, from_account, to_account) + + app_module.execute_transfer_usdt = execute_transfer_usdt + app_module._sim_hooks_applied = True _patch_okx_options_lib(app_module) @@ -154,6 +184,8 @@ def _patch_okx_options_lib(app_module: Any) -> None: _orig_fetch_pos = opt_lib.fetch_option_positions _orig_ready = opt_lib.options_api_ready _orig_fetch_bal = opt_lib.fetch_options_balances + _orig_transfer = opt_lib.transfer_ccy + _orig_swap = opt_lib.spot_market_swap_usdt_usdc def options_header_balances(ex, *, force: bool = False): try: @@ -169,19 +201,30 @@ def _patch_okx_options_lib(app_module: Any) -> None: pass return _orig_header(ex, force=force) - def fetch_options_balances(ex, *, force: bool = False): + def fetch_options_balances( + ex, *, force: bool = False, scope: str = "main", sub_acct: str = "" + ): try: if _GET_DB is not None and is_sim_mode(_GET_DB): w = broker().balances_header() + fu = float(w["funding_usdt"]) + fc = float(w["funding_usdc"]) + tu = float(w["trading_usdt"]) + tc = float(w["trading_usdc"]) return { - "trading_usdc": float(w["trading_usdc"]), - "funding_usdc": float(w["funding_usdc"]), - "funding_usdt": float(w["funding_usdt"]), - "trading_usdt": float(w["trading_usdt"]), + "scope": "main", + "funding_usdt": fu, + "funding_usdc": fc, + "trading_usdt": tu, + "trading_usdc": tc, + "funding_usdt_avail": fu, + "funding_usdc_avail": fc, + "trading_usdt_avail": tu, + "trading_usdc_avail": tc, } except Exception: pass - return _orig_fetch_bal(ex, force=force) + return _orig_fetch_bal(ex, force=force, scope=scope, sub_acct=sub_acct) def options_api_ready(ex): try: @@ -191,6 +234,72 @@ def _patch_okx_options_lib(app_module: Any) -> None: pass return _orig_ready(ex) + def transfer_ccy(ex, ccy, amount, from_acct, to_acct): + try: + if _GET_DB is not None and is_sim_mode(_GET_DB): + from lib.sim.wallets_lib import SimWallets, normalize_sim_account + + result = SimWallets(_GET_DB).transfer( + ccy=str(ccy or "USDC"), + amount=float(amount), + from_account=normalize_sim_account(from_acct), + to_account=normalize_sim_account(to_acct), + ) + if not result.get("ok"): + return { + "ok": False, + "msg": result.get("detail") or result.get("msg") or "划转失败", + } + try: + from lib.instance.instance_live_push_lib import notify_instance_balance_changed + + notify_instance_balance_changed() + except Exception: + pass + return {"ok": True, "msg": "sim 划转成功", "sim": True, **result} + except Exception as e: + return {"ok": False, "msg": str(e)} + return _orig_transfer(ex, ccy, amount, from_acct, to_acct) + + def spot_market_swap_usdt_usdc(ex, *, direction: str = "usdt_to_usdc", amount: float = 0): + try: + if _GET_DB is not None and is_sim_mode(_GET_DB): + from lib.sim.wallets_lib import SimWallets + + d = (direction or "usdt_to_usdc").strip().lower() + if d == "usdc_to_usdt": + from_ccy, to_ccy = "USDC", "USDT" + else: + from_ccy, to_ccy = "USDT", "USDC" + result = SimWallets(_GET_DB).convert( + from_ccy=from_ccy, + to_ccy=to_ccy, + amount=float(amount), + account="funding", + ) + if not result.get("ok"): + result = SimWallets(_GET_DB).convert( + from_ccy=from_ccy, + to_ccy=to_ccy, + amount=float(amount), + account="trading", + ) + if not result.get("ok"): + return { + "ok": False, + "msg": result.get("detail") or result.get("msg") or "兑换失败", + } + try: + from lib.instance.instance_live_push_lib import notify_instance_balance_changed + + notify_instance_balance_changed() + except Exception: + pass + return {"ok": True, "msg": "sim 兑换成功(1:1)", "sim": True, **result} + except Exception as e: + return {"ok": False, "msg": str(e)} + return _orig_swap(ex, direction=direction, amount=amount) + def fetch_option_order(ex, *, inst_id: str, ord_id: str): oid = str(ord_id or "") if oid.startswith("sim-"): @@ -243,6 +352,8 @@ def _patch_okx_options_lib(app_module: Any) -> None: opt_lib.options_header_balances = options_header_balances opt_lib.fetch_options_balances = fetch_options_balances opt_lib.options_api_ready = options_api_ready + opt_lib.transfer_ccy = transfer_ccy + opt_lib.spot_market_swap_usdt_usdc = spot_market_swap_usdt_usdc opt_lib.fetch_option_order = fetch_option_order opt_lib.wait_option_order_full_fill = wait_option_order_full_fill opt_lib.fetch_option_positions = fetch_option_positions @@ -279,6 +390,22 @@ def patch_options_cfg(cfg: dict[str, Any]) -> dict[str, Any]: if not callable(get_db): return cfg set_get_db(get_db) + + # 与 apply_sim_hooks 对齐: cfg 里可能仍是 import 时的旧引用 + import lib.exchange.okx_options_lib as opt_lib + + for key in ( + "transfer_ccy", + "spot_market_swap_usdt_usdc", + "options_api_ready", + "fetch_options_balances", + "fetch_option_positions", + "fetch_option_order", + "wait_option_order_full_fill", + ): + if key in cfg and hasattr(opt_lib, key): + cfg[key] = getattr(opt_lib, key) + live_limit = cfg.get("place_option_limit_order") live_market = cfg.get("place_option_market_order") if callable(live_limit) and callable(live_market): diff --git a/lib/sim/wallets_lib.py b/lib/sim/wallets_lib.py index d5f3436..94d0672 100644 --- a/lib/sim/wallets_lib.py +++ b/lib/sim/wallets_lib.py @@ -21,6 +21,16 @@ _ACCT_MAP = { } +def normalize_sim_account(account: str | None) -> str: + """统一账户别名: swap/unified → trading; spot → funding.""" + a = (account or "").strip().lower() + if a in ("trading", "swap", "unified", "18"): + return "trading" + if a in ("funding", "spot", "6"): + return "funding" + return a + + class InsufficientFunds(RuntimeError): pass @@ -172,8 +182,8 @@ class SimWallets: if amt <= 0: return {"ok": False, "detail": "划转金额须大于 0"} ccy_l = (ccy or "USDT").strip().lower() - fa = (from_account or "").strip().lower() - ta = (to_account or "").strip().lower() + fa = normalize_sim_account(from_account) + ta = normalize_sim_account(to_account) if fa not in ("funding", "trading") or ta not in ("funding", "trading"): return {"ok": False, "detail": "账户仅支持 funding / trading"} if fa == ta: