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 <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-12 08:42:33 +08:00
parent 04fd84757c
commit 551c86264a
8 changed files with 70 additions and 11 deletions
+10 -2
View File
@@ -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,
+5 -4
View File
@@ -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"}
@@ -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
@@ -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);
+3 -2
View File
@@ -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);
@@ -39,7 +39,7 @@
</div>
<div class="stat-strip-item">
<div class="label">期权交易账户</div>
<div class="value" id="options-trading-usdc" data-funds-field="options-trading-usdc">{% if options_trading_usdc is not none %}{{ funds_fmt(options_trading_usdc) }} USDC{% else %}—{% endif %}</div>
<div class="value" id="options-trading-usdc" data-funds-field="options-trading-usdc">{{ options_funding_label(options_trading_usdc, options_trading_usdt) }}</div>
</div>
{% endif %}
<div class="stat-strip-item stat-strip-item--pnl">
+10
View File
@@ -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__":
+35
View File
@@ -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()