修复okx止盈止损
This commit is contained in:
+77
-46
@@ -45,6 +45,7 @@ from fib_key_monitor_lib import (
|
||||
key_signal_type_for_trade_record,
|
||||
stored_key_signal_type,
|
||||
)
|
||||
from okx_orders_lib import fetch_okx_all_open_orders
|
||||
from key_sl_tp_lib import (
|
||||
breakeven_enabled_from_row,
|
||||
normalize_sl_tp_mode,
|
||||
@@ -2467,7 +2468,7 @@ def cancel_okx_swap_open_orders(exchange_symbol):
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
for o in exchange.fetch_open_orders(exchange_symbol) or []:
|
||||
for o in fetch_okx_all_open_orders(exchange, exchange_symbol):
|
||||
oid = o.get("id")
|
||||
if oid is None:
|
||||
continue
|
||||
@@ -2616,26 +2617,52 @@ def _resolve_tpsl_prices_for_manual(direction, live_price, sltp_mode, data):
|
||||
return stop_loss, take_profit
|
||||
|
||||
|
||||
def _okx_tpsl_slot_from_order(order, exchange_symbol):
|
||||
def _okx_tpsl_slot_build(exchange_symbol, order_id, trigger_price, order_type=""):
|
||||
if trigger_price is None or order_id is None:
|
||||
return None
|
||||
sym = exchange_symbol.replace(":USDT", "").replace("/USDT:USDT", "")
|
||||
return {
|
||||
"order_id": str(order_id),
|
||||
"trigger_price": float(trigger_price),
|
||||
"trigger_display": format_price_for_symbol(sym, trigger_price),
|
||||
"type": str(order_type or ""),
|
||||
}
|
||||
|
||||
|
||||
def _okx_tpsl_slots_from_order(order, exchange_symbol):
|
||||
"""从单笔 OKX 订单解析 SL/TP(算法单常同时带 slTriggerPx 与 tpTriggerPx)。"""
|
||||
if not isinstance(order, dict):
|
||||
return None, None
|
||||
info = order.get("info") or {}
|
||||
if not isinstance(info, dict):
|
||||
info = {}
|
||||
oid = order.get("id") or info.get("algoId") or info.get("ordId")
|
||||
trig = _coerce_float(
|
||||
info.get("slTriggerPx"),
|
||||
info.get("tpTriggerPx"),
|
||||
if oid is None:
|
||||
return None, None
|
||||
ord_type = str(order.get("type") or info.get("ordType") or "")
|
||||
sl_px = _coerce_float(
|
||||
order.get("stopLossPrice"),
|
||||
info.get("slTriggerPx"),
|
||||
info.get("slOrdPx"),
|
||||
)
|
||||
tp_px = _coerce_float(
|
||||
order.get("takeProfitPrice"),
|
||||
info.get("tpTriggerPx"),
|
||||
info.get("tpOrdPx"),
|
||||
)
|
||||
sl_slot = _okx_tpsl_slot_build(exchange_symbol, oid, sl_px, ord_type) if sl_px is not None else None
|
||||
tp_slot = _okx_tpsl_slot_build(exchange_symbol, oid, tp_px, ord_type) if tp_px is not None else None
|
||||
if sl_slot or tp_slot:
|
||||
return sl_slot, tp_slot
|
||||
trig = _coerce_float(
|
||||
info.get("triggerPx"),
|
||||
order.get("triggerPrice"),
|
||||
order.get("stopPrice"),
|
||||
)
|
||||
if trig is None:
|
||||
return None
|
||||
return {
|
||||
"order_id": str(oid) if oid is not None else None,
|
||||
"trigger_price": float(trig),
|
||||
"trigger_display": format_price_for_symbol(
|
||||
exchange_symbol.replace(":USDT", "").replace("/USDT:USDT", ""),
|
||||
trig,
|
||||
),
|
||||
"type": str(order.get("type") or info.get("ordType") or ""),
|
||||
}
|
||||
return None, None
|
||||
one = _okx_tpsl_slot_build(exchange_symbol, oid, trig, ord_type)
|
||||
return one, None
|
||||
|
||||
|
||||
def fetch_exchange_tpsl_slots(exchange_symbol, direction, plan_sl=None, plan_tp=None):
|
||||
@@ -2647,35 +2674,18 @@ def fetch_exchange_tpsl_slots(exchange_symbol, direction, plan_sl=None, plan_tp=
|
||||
return slots
|
||||
try:
|
||||
ensure_markets_loaded()
|
||||
ambiguous = []
|
||||
for order in exchange.fetch_open_orders(exchange_symbol) or []:
|
||||
slot = _okx_tpsl_slot_from_order(order, exchange_symbol)
|
||||
if not slot or not slot.get("order_id"):
|
||||
continue
|
||||
trig = slot.get("trigger_price")
|
||||
if plan_sl is not None and plan_tp is not None:
|
||||
try:
|
||||
role = "sl" if abs(trig - float(plan_sl)) <= abs(trig - float(plan_tp)) else "tp"
|
||||
except Exception:
|
||||
role = None
|
||||
elif plan_sl is not None:
|
||||
role = "sl"
|
||||
elif plan_tp is not None:
|
||||
role = "tp"
|
||||
else:
|
||||
ambiguous.append(slot)
|
||||
continue
|
||||
if role in ("sl", "tp") and slots[role] is None:
|
||||
slots[role] = slot
|
||||
for slot in ambiguous:
|
||||
trig = slot.get("trigger_price")
|
||||
if trig is None:
|
||||
continue
|
||||
try:
|
||||
plan_sl_f = float(plan_sl) if plan_sl is not None else None
|
||||
plan_tp_f = float(plan_tp) if plan_tp is not None else None
|
||||
except Exception:
|
||||
plan_sl_f = plan_tp_f = None
|
||||
plan_sl_f = plan_tp_f = None
|
||||
try:
|
||||
if plan_sl is not None:
|
||||
plan_sl_f = float(plan_sl)
|
||||
if plan_tp is not None:
|
||||
plan_tp_f = float(plan_tp)
|
||||
except Exception:
|
||||
plan_sl_f = plan_tp_f = None
|
||||
|
||||
def assign_role(trig, slot):
|
||||
if trig is None or slot is None:
|
||||
return
|
||||
if plan_sl_f is not None and plan_tp_f is not None:
|
||||
role = "sl" if abs(trig - plan_sl_f) <= abs(trig - plan_tp_f) else "tp"
|
||||
elif plan_sl_f is not None:
|
||||
@@ -2683,9 +2693,30 @@ def fetch_exchange_tpsl_slots(exchange_symbol, direction, plan_sl=None, plan_tp=
|
||||
elif plan_tp_f is not None:
|
||||
role = "tp"
|
||||
else:
|
||||
continue
|
||||
return
|
||||
if slots[role] is None:
|
||||
slots[role] = slot
|
||||
|
||||
for order in fetch_okx_all_open_orders(exchange, exchange_symbol):
|
||||
sl_slot, tp_slot = _okx_tpsl_slots_from_order(order, exchange_symbol)
|
||||
if sl_slot and slots["sl"] is None:
|
||||
slots["sl"] = sl_slot
|
||||
if tp_slot and slots["tp"] is None:
|
||||
slots["tp"] = tp_slot
|
||||
if sl_slot or tp_slot:
|
||||
continue
|
||||
info = order.get("info") or {}
|
||||
oid = order.get("id") or info.get("algoId")
|
||||
trig = _coerce_float(info.get("triggerPx"), order.get("triggerPrice"))
|
||||
if oid is None or trig is None:
|
||||
continue
|
||||
slot = _okx_tpsl_slot_build(
|
||||
exchange_symbol,
|
||||
oid,
|
||||
trig,
|
||||
str(order.get("type") or info.get("ordType") or ""),
|
||||
)
|
||||
assign_role(trig, slot)
|
||||
except Exception:
|
||||
pass
|
||||
return slots
|
||||
@@ -3717,7 +3748,7 @@ def fib_limit_order_status(exchange_symbol, order_id):
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
for o in exchange.fetch_open_orders(exchange_symbol) or []:
|
||||
for o in fetch_okx_all_open_orders(exchange, exchange_symbol):
|
||||
if str(o.get("id")) == oid:
|
||||
return "open"
|
||||
except Exception:
|
||||
|
||||
@@ -7,6 +7,8 @@ import os
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from okx_orders_lib import fetch_okx_all_open_orders
|
||||
|
||||
|
||||
def _coerce_float(*values) -> float | None:
|
||||
for v in values:
|
||||
@@ -98,23 +100,33 @@ def _normalize_raw_order(order: dict, *, channel: str) -> dict[str, Any] | None:
|
||||
reduce_only = bool(reduce_only) if reduce_only is not None else None
|
||||
except (TypeError, ValueError):
|
||||
reduce_only = None
|
||||
sl_trig = _coerce_float(info.get("slTriggerPx"), order.get("stopLossPrice"))
|
||||
tp_trig = _coerce_float(info.get("tpTriggerPx"), order.get("takeProfitPrice"))
|
||||
trig = _coerce_float(
|
||||
order.get("stopPrice"),
|
||||
order.get("triggerPrice"),
|
||||
info.get("triggerPrice"),
|
||||
info.get("stopPrice"),
|
||||
info.get("slTriggerPx"),
|
||||
info.get("tpTriggerPx"),
|
||||
info.get("triggerPx"),
|
||||
sl_trig,
|
||||
tp_trig,
|
||||
)
|
||||
price = _coerce_float(order.get("price"), info.get("price"))
|
||||
amt = _coerce_float(order.get("amount"), order.get("remaining"), info.get("quantity"), info.get("origQty"))
|
||||
price = _coerce_float(order.get("price"), info.get("price"), info.get("ordPx"))
|
||||
amt = _coerce_float(order.get("amount"), order.get("remaining"), info.get("quantity"), info.get("origQty"), info.get("sz"))
|
||||
category = "conditional" if _is_conditional_type(typ) or channel == "algo" else "limit"
|
||||
label = _order_label(typ, side, reduce_only)
|
||||
if sl_trig is not None and tp_trig is not None:
|
||||
label = f"止盈止损 SL={sl_trig:g} TP={tp_trig:g}"
|
||||
elif sl_trig is not None:
|
||||
label = f"止损 {sl_trig:g}"
|
||||
elif tp_trig is not None:
|
||||
label = f"止盈 {tp_trig:g}"
|
||||
return {
|
||||
"id": str(oid),
|
||||
"symbol": sym,
|
||||
"channel": channel,
|
||||
"category": category,
|
||||
"label": _order_label(typ, side, reduce_only),
|
||||
"label": label,
|
||||
"type": typ,
|
||||
"side": side,
|
||||
"amount": amt,
|
||||
@@ -210,17 +222,19 @@ def _okx_list(ex: Any, symbol: str | None) -> list[dict]:
|
||||
pass
|
||||
if symbol and not symbols:
|
||||
symbols = [symbol]
|
||||
seen = set()
|
||||
seen: set[tuple[str, str]] = set()
|
||||
for sym in symbols:
|
||||
if sym in seen:
|
||||
continue
|
||||
seen.add(sym)
|
||||
try:
|
||||
for o in ex.fetch_open_orders(sym) or []:
|
||||
for o in fetch_okx_all_open_orders(ex, sym):
|
||||
ch = "algo" if _is_conditional_type(_order_type_str(o)) else "regular"
|
||||
n = _normalize_raw_order(dict(o), channel=ch)
|
||||
if n:
|
||||
out.append(n)
|
||||
if not n:
|
||||
continue
|
||||
key = (n["id"], n.get("channel") or ch)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
out.append(n)
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
OKX 挂单聚合:普通委托 + 算法单(conditional / oco / trigger)。
|
||||
交易所 App「止盈止损」页多为 orders-algo-pending,仅 fetch_open_orders 默认拿不到。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def _order_dedupe_key(order: dict) -> str:
|
||||
info = order.get("info") or {}
|
||||
if not isinstance(info, dict):
|
||||
info = {}
|
||||
return str(order.get("id") or info.get("algoId") or info.get("ordId") or "")
|
||||
|
||||
|
||||
def fetch_okx_all_open_orders(ex, exchange_symbol: str) -> list[dict]:
|
||||
"""合并 OKX 普通挂单与算法挂单(去重)。"""
|
||||
if not exchange_symbol:
|
||||
return []
|
||||
ex.load_markets()
|
||||
sym = exchange_symbol
|
||||
try:
|
||||
sym = ex.market(exchange_symbol)["symbol"]
|
||||
except Exception:
|
||||
pass
|
||||
seen: set[str] = set()
|
||||
out: list[dict] = []
|
||||
|
||||
def add_batch(batch: list | None) -> None:
|
||||
for o in batch or []:
|
||||
if not isinstance(o, dict):
|
||||
continue
|
||||
k = _order_dedupe_key(o)
|
||||
if not k or k in seen:
|
||||
continue
|
||||
seen.add(k)
|
||||
out.append(o)
|
||||
|
||||
try:
|
||||
add_batch(ex.fetch_open_orders(sym))
|
||||
except Exception:
|
||||
pass
|
||||
for params in (
|
||||
{"ordType": "conditional"},
|
||||
{"ordType": "oco"},
|
||||
{"trigger": True},
|
||||
):
|
||||
try:
|
||||
add_batch(ex.fetch_open_orders(sym, params=dict(params)))
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
Reference in New Issue
Block a user