From 551c86264a27b37e5c8aa6edf72549b310e135ab Mon Sep 17 00:00:00 2001 From: dekun Date: Sun, 12 Jul 2026 08:42:33 +0800 Subject: [PATCH] Fix OKX spot swap tgtCcy and show options trading USDT in header. OKX requires quote_ccy/base_ccy for market orders; include trading_usdt in balance display and total funds. Co-authored-by: Cursor --- crypto_monitor_okx/app.py | 12 +++++-- lib/exchange/okx_options_lib.py | 9 ++--- lib/instance/instance_embed_context_lib.py | 3 ++ .../templates/embed_boot_scripts.html | 5 +-- lib/instance/templates/index.html | 5 +-- .../templates/instance_header_stats.html | 2 +- tests/test_instance_header_stats_lib.py | 10 ++++++ tests/test_okx_spot_swap.py | 35 +++++++++++++++++++ 8 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 tests/test_okx_spot_swap.py diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 224e49d..473ad55 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -6528,6 +6528,7 @@ def render_main_page(page="trade", embed_mode=None): options_trading_usdc = None options_funding_usdc = None options_funding_usdt = None + options_trading_usdt = None if ( OKX_OPTIONS_ENABLED and exchange_options.apiKey @@ -6536,13 +6537,14 @@ def render_main_page(page="trade", embed_mode=None): try: from lib.exchange.okx_options_lib import options_header_balances - options_trading_usdc, options_funding_usdc, options_funding_usdt = options_header_balances( + options_trading_usdc, options_funding_usdc, options_funding_usdt, options_trading_usdt = options_header_balances( exchange_options ) except Exception: options_trading_usdc = None options_funding_usdc = None options_funding_usdt = None + options_trading_usdt = None recommended_capital = get_recommended_capital(current_capital) key_list = ( conn.execute("SELECT * FROM key_monitors").fetchall() if plan.key_list else [] @@ -6675,10 +6677,12 @@ def render_main_page(page="trade", embed_mode=None): options_trading_usdc, options_funding_usdc, options_funding_usdt, + options_trading_usdt, ), options_funding_usdc=options_funding_usdc, options_funding_usdt=options_funding_usdt, options_trading_usdc=options_trading_usdc, + options_trading_usdt=options_trading_usdt, trading_day=trading_day, daily_start_capital=DAILY_START_CAPITAL, current_capital=current_capital, @@ -6887,11 +6891,12 @@ def api_account_snapshot(): options_trading_usdc = None options_funding_usdc = None options_funding_usdt = None + options_trading_usdt = None if OKX_OPTIONS_ENABLED and exchange_options.apiKey: try: from lib.exchange.okx_options_lib import options_header_balances - options_trading_usdc, options_funding_usdc, options_funding_usdt = options_header_balances( + options_trading_usdc, options_funding_usdc, options_funding_usdt, options_trading_usdt = options_header_balances( exchange_options, force=force_refresh, ) @@ -6899,6 +6904,7 @@ def api_account_snapshot(): options_trading_usdc = None options_funding_usdc = None options_funding_usdt = None + options_trading_usdt = None recommended_capital = get_recommended_capital(current_capital) from lib.strategy.strategy_trade_labels import count_position_limit_active_monitors @@ -6954,12 +6960,14 @@ def api_account_snapshot(): "options_funding_usdc": options_funding_usdc, "options_funding_usdt": options_funding_usdt, "options_trading_usdc": options_trading_usdc, + "options_trading_usdt": options_trading_usdt, "total_funds": total_funds_usdt( funding_usdt, current_capital, options_trading_usdc, options_funding_usdc, options_funding_usdt, + options_trading_usdt, ), "available_trading_usdt": round(available_trading_usdt, FUNDS_DECIMALS) if available_trading_usdt is not None else None, "unrealized_pnl": unrealized_pnl, diff --git a/lib/exchange/okx_options_lib.py b/lib/exchange/okx_options_lib.py index 8efc2f8..9445876 100644 --- a/lib/exchange/okx_options_lib.py +++ b/lib/exchange/okx_options_lib.py @@ -446,8 +446,8 @@ def options_header_balances( ex: ccxt.okx, *, force: bool = False, -) -> tuple[float | None, float | None, float | None]: - """顶栏三格:交易 USDC,资金 USDC,资金 USDT(单次拉取 + 缓存).""" +) -> tuple[float | None, float | None, float | None, float | None]: + """顶栏四格:交易 USDC/USDT,资金 USDC/USDT(单次拉取 + 缓存).""" bal = fetch_options_balances(ex, force=force) def _round(v: Any) -> float | None: @@ -462,6 +462,7 @@ def options_header_balances( _round(bal.get("trading_usdc")), _round(bal.get("funding_usdc")), _round(bal.get("funding_usdt")), + _round(bal.get("trading_usdt")), ) @@ -1110,7 +1111,7 @@ def spot_market_swap_usdt_usdc( "side": "buy", "ordType": "market", "sz": str(amount), - "tgtCcy": "quote", + "tgtCcy": "quote_ccy", } elif d == "usdc_to_usdt": body = { @@ -1119,7 +1120,7 @@ def spot_market_swap_usdt_usdc( "side": "sell", "ordType": "market", "sz": str(amount), - "tgtCcy": "base", + "tgtCcy": "base_ccy", } else: return {"ok": False, "msg": "direction 须为 usdt_to_usdc 或 usdc_to_usdt"} diff --git a/lib/instance/instance_embed_context_lib.py b/lib/instance/instance_embed_context_lib.py index 9a19d38..e184a61 100644 --- a/lib/instance/instance_embed_context_lib.py +++ b/lib/instance/instance_embed_context_lib.py @@ -103,6 +103,7 @@ def total_funds_usdt( options_trading_usdc: float | None = None, options_funding_usdc: float | None = None, options_funding_usdt: float | None = None, + options_trading_usdt: float | None = None, ) -> float | None: if funding_usdt is None: return None @@ -114,6 +115,8 @@ def total_funds_usdt( total += float(options_funding_usdt) if options_trading_usdc is not None: total += float(options_trading_usdc) + if options_trading_usdt is not None: + total += float(options_trading_usdt) return round(total, 2) except (TypeError, ValueError): return None diff --git a/lib/instance/templates/embed_boot_scripts.html b/lib/instance/templates/embed_boot_scripts.html index 03645ae..195e934 100644 --- a/lib/instance/templates/embed_boot_scripts.html +++ b/lib/instance/templates/embed_boot_scripts.html @@ -1168,8 +1168,9 @@ function applyAccountSnapshot(data){ const optFunding = formatOptionsFundingLabel(data.options_funding_usdc, data.options_funding_usdt); if(optFunding !== "—") setFundsFieldText("options-funding-usdc", optFunding); } - if(data.options_trading_usdc != null && data.options_trading_usdc !== ""){ - setFundsFieldText("options-trading-usdc", `${Number(data.options_trading_usdc).toFixed(2)} USDC`); + if(data.options_trading_usdc != null || data.options_trading_usdt != null){ + const optTrading = formatOptionsFundingLabel(data.options_trading_usdc, data.options_trading_usdt); + if(optTrading !== "—") setFundsFieldText("options-trading-usdc", optTrading); } if(typeof data.unrealized_pnl !== "undefined"){ updateRealtimePnl(data.unrealized_pnl); diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index 8b29f30..3bfa6da 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -1813,8 +1813,9 @@ function applyAccountSnapshot(data){ const optFunding = formatOptionsFundingLabel(data.options_funding_usdc, data.options_funding_usdt); if(optFunding !== "—") setFundsFieldText("options-funding-usdc", optFunding); } - if(data.options_trading_usdc != null && data.options_trading_usdc !== ""){ - setFundsFieldText("options-trading-usdc", `${Number(data.options_trading_usdc).toFixed(2)} USDC`); + if(data.options_trading_usdc != null || data.options_trading_usdt != null){ + const optTrading = formatOptionsFundingLabel(data.options_trading_usdc, data.options_trading_usdt); + if(optTrading !== "—") setFundsFieldText("options-trading-usdc", optTrading); } if(typeof data.unrealized_pnl !== "undefined"){ updateRealtimePnl(data.unrealized_pnl); diff --git a/lib/instance/templates/instance_header_stats.html b/lib/instance/templates/instance_header_stats.html index 32aa2d0..86b3416 100644 --- a/lib/instance/templates/instance_header_stats.html +++ b/lib/instance/templates/instance_header_stats.html @@ -39,7 +39,7 @@
期权交易账户
-
{% if options_trading_usdc is not none %}{{ funds_fmt(options_trading_usdc) }} USDC{% else %}—{% endif %}
+
{{ options_funding_label(options_trading_usdc, options_trading_usdt) }}
{% endif %}
diff --git a/tests/test_instance_header_stats_lib.py b/tests/test_instance_header_stats_lib.py index 4fd4b55..b5168cd 100644 --- a/tests/test_instance_header_stats_lib.py +++ b/tests/test_instance_header_stats_lib.py @@ -4,6 +4,7 @@ from __future__ import annotations import unittest from lib.instance.instance_embed_context_lib import ( + options_funding_label, profit_loss_ratio_from_averages, profit_loss_ratio_from_trades, total_funds_usdt, @@ -27,6 +28,15 @@ class TestHeaderStatsLib(unittest.TestCase): def test_total_funds_usdt(self): self.assertEqual(total_funds_usdt(100.5, 59.27), 159.77) self.assertIsNone(total_funds_usdt(None, 10)) + self.assertEqual( + total_funds_usdt(100, 50, options_trading_usdc=0.2, options_trading_usdt=10), + 160.2, + ) + + def test_options_funding_label(self): + self.assertEqual(options_funding_label(1.5, 10), "1.50 USDC · 10.00 USDT") + self.assertEqual(options_funding_label(None, 10), "10.00 USDT") + self.assertEqual(options_funding_label(None, None), "—") if __name__ == "__main__": diff --git a/tests/test_okx_spot_swap.py b/tests/test_okx_spot_swap.py new file mode 100644 index 0000000..7b7e72f --- /dev/null +++ b/tests/test_okx_spot_swap.py @@ -0,0 +1,35 @@ +"""OKX USDT/USDC 现货市价兑换参数.""" +from __future__ import annotations + +import unittest +from unittest.mock import MagicMock + +from lib.exchange.okx_options_lib import spot_market_swap_usdt_usdc + + +class TestOkxSpotSwap(unittest.TestCase): + def test_usdt_to_usdc_uses_quote_ccy(self): + ex = MagicMock() + ex.private_post_trade_order.return_value = { + "data": [{"sCode": "0", "ordId": "1"}], + } + result = spot_market_swap_usdt_usdc(ex, direction="usdt_to_usdc", amount=10) + self.assertTrue(result["ok"]) + body = ex.private_post_trade_order.call_args[0][0] + self.assertEqual(body["tgtCcy"], "quote_ccy") + self.assertEqual(body["side"], "buy") + + def test_usdc_to_usdt_uses_base_ccy(self): + ex = MagicMock() + ex.private_post_trade_order.return_value = { + "data": [{"sCode": "0", "ordId": "2"}], + } + result = spot_market_swap_usdt_usdc(ex, direction="usdc_to_usdt", amount=5) + self.assertTrue(result["ok"]) + body = ex.private_post_trade_order.call_args[0][0] + self.assertEqual(body["tgtCcy"], "base_ccy") + self.assertEqual(body["side"], "sell") + + +if __name__ == "__main__": + unittest.main()