From 480e5b1d204f214eec63ffde70d811a9db3742f4 Mon Sep 17 00:00:00 2001 From: dekun Date: Wed, 27 May 2026 06:02:19 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9okx=E6=8C=82=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crypto_monitor_okx/app.py | 75 ++++++++++++++++++++++----- manual_trading_hub/exchange_orders.py | 35 ++++++++++--- 2 files changed, 90 insertions(+), 20 deletions(-) diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 2ba4d7d..ca70766 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -2481,21 +2481,44 @@ def cancel_okx_swap_open_orders(exchange_symbol): def _okx_place_tp_sl_orders(exchange_symbol, direction, amount, stop_loss, take_profit): + """ + 为已有持仓挂条件止盈/止损(算法单)。 + 勿在同一笔 reduce-only 市价单上同时带 stopLoss+takeProfit,OKX/ccxt 可能当成立即全平。 + """ ensure_markets_loaded() close_side = "sell" if direction == "long" else "buy" amt = float(exchange.amount_to_precision(exchange_symbol, float(amount))) if amt <= 0: raise RuntimeError("止盈止损:可平数量经精度舍入后为 0") - params = build_okx_order_params(direction, reduce_only=True) - params["stopLoss"] = { - "triggerPrice": _okx_algo_trigger_price_str(exchange_symbol, stop_loss), - "type": "market", - } - params["takeProfit"] = { - "triggerPrice": _okx_algo_trigger_price_str(exchange_symbol, take_profit), - "type": "market", - } - exchange.create_order(exchange_symbol, "market", close_side, amt, None, params) + base = build_okx_order_params(direction, reduce_only=True) + sl_px = float(stop_loss) + tp_px = float(take_profit) + last_err = None + for attempt in range(6): + try: + exchange.create_order( + exchange_symbol, + "market", + close_side, + amt, + None, + {**base, "stopLossPrice": sl_px}, + ) + time.sleep(0.05) + exchange.create_order( + exchange_symbol, + "market", + close_side, + amt, + None, + {**base, "takeProfitPrice": tp_px}, + ) + return + except Exception as e: + last_err = e + cancel_okx_swap_open_orders(exchange_symbol) + time.sleep(0.2 * (attempt + 1)) + raise RuntimeError(f"OKX 未接受止盈/止损条件单:{last_err}") @@ -3756,7 +3779,15 @@ def fib_limit_order_status(exchange_symbol, order_id): return "unknown" -def place_fib_limit_order(exchange_symbol, direction, amount, leverage, limit_price): +def place_fib_limit_order( + exchange_symbol, + direction, + amount, + leverage, + limit_price, + stop_loss=None, + take_profit=None, +): ensure_markets_loaded() exchange.set_leverage(leverage, exchange_symbol) side = "buy" if direction == "long" else "sell" @@ -3764,6 +3795,15 @@ def place_fib_limit_order(exchange_symbol, direction, amount, leverage, limit_pr if price is None or price <= 0: raise ValueError("挂单价无效") params = build_okx_order_params(direction, reduce_only=False) + if stop_loss and take_profit: + params["attachAlgoOrds"] = [ + { + "tpTriggerPx": _okx_algo_trigger_price_str(exchange_symbol, take_profit), + "tpOrdPx": "-1", + "slTriggerPx": _okx_algo_trigger_price_str(exchange_symbol, stop_loss), + "slOrdPx": "-1", + } + ] return exchange.create_order(exchange_symbol, "limit", side, amount, price, params) @@ -3933,8 +3973,13 @@ def _finalize_fib_key_fill(conn, row): return tpsl_attached = False try: - _okx_place_tp_sl_orders(ex_sym, direction, amount, sl, tp) - tpsl_attached = True + slots = fetch_exchange_tpsl_slots(ex_sym, direction, plan_sl=sl, plan_tp=tp) + if slots.get("sl") and slots.get("tp"): + tpsl_attached = True + else: + _okx_place_tp_sl_orders(ex_sym, direction, amount, sl, tp) + slots2 = fetch_exchange_tpsl_slots(ex_sym, direction, plan_sl=sl, plan_tp=tp) + tpsl_attached = bool(slots2.get("sl") and slots2.get("tp")) except Exception as e: msg = ( f"# ❌ {symbol} 斐波成交后挂 TP/SL 失败\n" @@ -4079,7 +4124,9 @@ def _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px, br ) try: amount, _ = prepare_order_amount(ex_sym, margin_capital, leverage, entry) - order_resp = place_fib_limit_order(ex_sym, direction_sel, amount, leverage, entry) + order_resp = place_fib_limit_order( + ex_sym, direction_sel, amount, leverage, entry, stop_loss=sl, take_profit=tp + ) oid = str(order_resp.get("id") or "") if not oid: return False, "交易所未返回限价单 ID" diff --git a/manual_trading_hub/exchange_orders.py b/manual_trading_hub/exchange_orders.py index 3983a20..ca3b4f7 100644 --- a/manual_trading_hub/exchange_orders.py +++ b/manual_trading_hub/exchange_orders.py @@ -514,12 +514,35 @@ def _okx_place_tp_sl( amt = float(ex.amount_to_precision(symbol, float(amount))) if amt <= 0: raise RuntimeError("止盈止损:可平数量经精度舍入后为 0") - params = _okx_order_params(direction, reduce_only=True, pos_mode=pos_mode, td_mode=td_mode) - sl_s = ex.price_to_precision(symbol, float(stop_loss)) - tp_s = ex.price_to_precision(symbol, float(take_profit)) - params["stopLoss"] = {"triggerPrice": sl_s, "type": "market"} - params["takeProfit"] = {"triggerPrice": tp_s, "type": "market"} - ex.create_order(symbol, "market", close_side, amt, None, params) + base = _okx_order_params(direction, reduce_only=True, pos_mode=pos_mode, td_mode=td_mode) + sl_px = float(stop_loss) + tp_px = float(take_profit) + last_err: Exception | None = None + for attempt in range(6): + try: + ex.create_order( + symbol, + "market", + close_side, + amt, + None, + {**base, "stopLossPrice": sl_px}, + ) + time.sleep(0.05) + ex.create_order( + symbol, + "market", + close_side, + amt, + None, + {**base, "takeProfitPrice": tp_px}, + ) + return + except Exception as e: + last_err = e + cancel_orders_for_symbol(ex, "okx", symbol, scope="conditional") + time.sleep(0.2 * (attempt + 1)) + raise RuntimeError(f"OKX 未接受止盈/止损条件单:{last_err}") def _gate_tpsl_env() -> tuple[bool, int, int, str]: