From be7896cc25b78449ccde96abef52d3cffa9e7681 Mon Sep 17 00:00:00 2001 From: dekun Date: Thu, 2 Jul 2026 22:40:41 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20OKX=20=E6=8C=81=E4=BB=93=E5=BC=A0?= =?UTF-8?q?=E6=95=B0=E4=BC=98=E5=85=88=E8=AF=BB=20info.pos=EF=BC=8C?= =?UTF-8?q?=E6=BB=9A=E4=BB=93=E5=90=8E=E5=90=8C=E6=AD=A5=20order=5Famount?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- crypto_monitor_okx/app.py | 27 +++++++++++++---------- lib/hub/hub_position_metrics.py | 19 +++++++++------- lib/strategy/strategy_roll_monitor_lib.py | 12 ++++++++-- tests/test_hub_position_metrics.py | 15 +++++++++++++ 4 files changed, 51 insertions(+), 22 deletions(-) create mode 100644 tests/test_hub_position_metrics.py diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 1e66c47..e0be0bd 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -2817,18 +2817,20 @@ def exchange_private_api_configured(): def _position_row_effective_contracts(p): - info = p.get("info", {}) or {} - contracts = p.get("contracts") - if contracts is None: - raw_pos = info.get("pos") - try: - contracts = abs(float(raw_pos)) if raw_pos is not None else 0.0 - except Exception: - contracts = 0.0 - try: - return float(contracts) - except Exception: + """张数:OKX 以 info.pos 为准,再兜底 ccxt contracts 等(与 Binance/Gate 多字段一致)。""" + if not p: return 0.0 + info = p.get("info", {}) or {} + for val in (info.get("pos"), p.get("contracts"), info.get("positionAmt"), info.get("size")): + if val is None or val == "": + continue + try: + x = abs(float(val)) + if x > 0: + return x + except (TypeError, ValueError): + continue + return 0.0 def _position_matches_wanted_contract(exchange_symbol, position): @@ -6685,7 +6687,7 @@ def api_price_snapshot(): "SELECT id,symbol,monitor_type,direction,upper,lower,fib_entry_price,fib_stop_loss,fib_take_profit,fib_limit_order_id,created_at FROM key_monitors" ).fetchall() order_rows = conn.execute( - "SELECT id,symbol,exchange_symbol,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,margin_capital,leverage," + "SELECT id,symbol,exchange_symbol,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,margin_capital,leverage,order_amount," "time_close_enabled,time_close_hours,time_close_at_ms,opened_at_ms FROM order_monitors WHERE status='active'" ).fetchall() @@ -6878,6 +6880,7 @@ def api_price_snapshot(): "float_pnl": round(pnl, 2), "float_pct": pnl_pct, "plan_margin": round(margin, 2) if margin else None, + "order_amount": float(r["order_amount"]) if r["order_amount"] not in (None, "") else None, "exchange_initial_margin": None, "exchange_notional": None, "exchange_mark_price": None, diff --git a/lib/hub/hub_position_metrics.py b/lib/hub/hub_position_metrics.py index 93fb8bb..3852340 100644 --- a/lib/hub/hub_position_metrics.py +++ b/lib/hub/hub_position_metrics.py @@ -24,23 +24,26 @@ def _coerce_float(*values: Any) -> float | None: def position_contracts(p: dict[str, Any]) -> float: - raw = p.get("contracts") - if raw is not None: - try: - return float(raw) - except (TypeError, ValueError): - pass info = p.get("info") or {} if not isinstance(info, dict): info = {} - for k in ("positionAmt", "positionamt", "pos", "size"): + # OKX 等:info.pos 为交易所张数,优先于 ccxt contracts(加仓后后者可能滞后) + for k in ("pos", "positionAmt", "positionamt", "size"): if k in info: try: v = float(info[k]) if v != 0: - return v + return abs(v) except (TypeError, ValueError): pass + raw = p.get("contracts") + if raw is not None: + try: + v = float(raw) + if v != 0: + return abs(v) + except (TypeError, ValueError): + pass return 0.0 diff --git a/lib/strategy/strategy_roll_monitor_lib.py b/lib/strategy/strategy_roll_monitor_lib.py index 2990bef..64107a2 100644 --- a/lib/strategy/strategy_roll_monitor_lib.py +++ b/lib/strategy/strategy_roll_monitor_lib.py @@ -418,9 +418,17 @@ def _execute_pending_roll_leg( "UPDATE roll_groups SET leg_count=?, current_stop_loss=?, updated_at=? WHERE id=?", (filled + 1, sl, _now(cfg), gid), ) + live_qty = qty + float(amount) + try: + pos2 = cfg["get_position"](ex_sym, direction) or {} + q2 = float(pos2.get("contracts") or 0) + if q2 > 0: + live_qty = q2 + except Exception: + pass conn.execute( - "UPDATE order_monitors SET stop_loss=? WHERE id=? AND status='active'", - (sl, mon["id"]), + "UPDATE order_monitors SET stop_loss=?, order_amount=? WHERE id=? AND status='active'", + (sl, float(live_qty), mon["id"]), ) notify = cfg.get("send_wechat") diff --git a/tests/test_hub_position_metrics.py b/tests/test_hub_position_metrics.py new file mode 100644 index 0000000..679ecf5 --- /dev/null +++ b/tests/test_hub_position_metrics.py @@ -0,0 +1,15 @@ +from lib.hub.hub_position_metrics import position_contracts + + +def test_position_contracts_prefers_okx_info_pos_over_stale_ccxt(): + p = { + "contracts": 0.81, + "side": "short", + "info": {"pos": "-1.62", "posSide": "short"}, + } + assert position_contracts(p) == 1.62 + + +def test_position_contracts_falls_back_to_ccxt_contracts(): + p = {"contracts": 2.5, "info": {}} + assert position_contracts(p) == 2.5