fix: OKX 持仓张数优先读 info.pos,滚仓后同步 order_amount
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+15
-12
@@ -2817,18 +2817,20 @@ def exchange_private_api_configured():
|
|||||||
|
|
||||||
|
|
||||||
def _position_row_effective_contracts(p):
|
def _position_row_effective_contracts(p):
|
||||||
info = p.get("info", {}) or {}
|
"""张数:OKX 以 info.pos 为准,再兜底 ccxt contracts 等(与 Binance/Gate 多字段一致)。"""
|
||||||
contracts = p.get("contracts")
|
if not p:
|
||||||
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:
|
|
||||||
return 0.0
|
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):
|
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"
|
"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()
|
).fetchall()
|
||||||
order_rows = conn.execute(
|
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'"
|
"time_close_enabled,time_close_hours,time_close_at_ms,opened_at_ms FROM order_monitors WHERE status='active'"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
|
||||||
@@ -6878,6 +6880,7 @@ def api_price_snapshot():
|
|||||||
"float_pnl": round(pnl, 2),
|
"float_pnl": round(pnl, 2),
|
||||||
"float_pct": pnl_pct,
|
"float_pct": pnl_pct,
|
||||||
"plan_margin": round(margin, 2) if margin else None,
|
"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_initial_margin": None,
|
||||||
"exchange_notional": None,
|
"exchange_notional": None,
|
||||||
"exchange_mark_price": None,
|
"exchange_mark_price": None,
|
||||||
|
|||||||
@@ -24,23 +24,26 @@ def _coerce_float(*values: Any) -> float | None:
|
|||||||
|
|
||||||
|
|
||||||
def position_contracts(p: dict[str, Any]) -> float:
|
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 {}
|
info = p.get("info") or {}
|
||||||
if not isinstance(info, dict):
|
if not isinstance(info, dict):
|
||||||
info = {}
|
info = {}
|
||||||
for k in ("positionAmt", "positionamt", "pos", "size"):
|
# OKX 等:info.pos 为交易所张数,优先于 ccxt contracts(加仓后后者可能滞后)
|
||||||
|
for k in ("pos", "positionAmt", "positionamt", "size"):
|
||||||
if k in info:
|
if k in info:
|
||||||
try:
|
try:
|
||||||
v = float(info[k])
|
v = float(info[k])
|
||||||
if v != 0:
|
if v != 0:
|
||||||
return v
|
return abs(v)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
pass
|
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
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -418,9 +418,17 @@ def _execute_pending_roll_leg(
|
|||||||
"UPDATE roll_groups SET leg_count=?, current_stop_loss=?, updated_at=? WHERE id=?",
|
"UPDATE roll_groups SET leg_count=?, current_stop_loss=?, updated_at=? WHERE id=?",
|
||||||
(filled + 1, sl, _now(cfg), gid),
|
(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(
|
conn.execute(
|
||||||
"UPDATE order_monitors SET stop_loss=? WHERE id=? AND status='active'",
|
"UPDATE order_monitors SET stop_loss=?, order_amount=? WHERE id=? AND status='active'",
|
||||||
(sl, mon["id"]),
|
(sl, float(live_qty), mon["id"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
notify = cfg.get("send_wechat")
|
notify = cfg.get("send_wechat")
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user