增加前端委托
This commit is contained in:
@@ -2750,6 +2750,199 @@ def cancel_gate_swap_trigger_orders(exchange_symbol):
|
||||
pass
|
||||
|
||||
|
||||
def _gate_list_trigger_open_orders(exchange_symbol):
|
||||
params = _gate_swap_trigger_order_params()
|
||||
try:
|
||||
return exchange.fetch_open_orders(exchange_symbol, params=params) or []
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _gate_order_trigger_price(order):
|
||||
for key in ("stopPrice", "triggerPrice", "price"):
|
||||
try:
|
||||
v = float(order.get(key) or 0)
|
||||
if v > 0:
|
||||
return v
|
||||
except Exception:
|
||||
pass
|
||||
info = order.get("info") or {}
|
||||
if isinstance(info, dict):
|
||||
trig = info.get("trigger")
|
||||
if isinstance(trig, dict):
|
||||
try:
|
||||
v = float(trig.get("price") or 0)
|
||||
if v > 0:
|
||||
return v
|
||||
except Exception:
|
||||
pass
|
||||
for key in ("trigger_price", "triggerPrice", "stopPrice", "price"):
|
||||
try:
|
||||
v = float(info.get(key) or 0)
|
||||
if v > 0:
|
||||
return v
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _gate_tpsl_role_from_order(order, direction):
|
||||
info = order.get("info") or {}
|
||||
if not isinstance(info, dict):
|
||||
info = {}
|
||||
ot = str(info.get("order_type") or info.get("orderType") or order.get("type") or "").lower()
|
||||
if "take" in ot and "profit" in ot:
|
||||
return "tp"
|
||||
if "stop" in ot and "loss" in ot:
|
||||
return "sl"
|
||||
trig = info.get("trigger")
|
||||
rule = None
|
||||
if isinstance(trig, dict) and trig.get("rule") is not None:
|
||||
try:
|
||||
rule = int(trig["rule"])
|
||||
except Exception:
|
||||
rule = None
|
||||
if rule is None:
|
||||
try:
|
||||
rule = int(info.get("rule"))
|
||||
except Exception:
|
||||
rule = None
|
||||
if rule is not None:
|
||||
if direction == "long":
|
||||
return "sl" if rule == 2 else ("tp" if rule == 1 else None)
|
||||
return "sl" if rule == 1 else ("tp" if rule == 2 else None)
|
||||
if order.get("stopLossPrice"):
|
||||
return "sl"
|
||||
if order.get("takeProfitPrice"):
|
||||
return "tp"
|
||||
typ = str(order.get("type") or "").upper()
|
||||
if "TAKE" in typ:
|
||||
return "tp"
|
||||
if "STOP" in typ:
|
||||
return "sl"
|
||||
return None
|
||||
|
||||
|
||||
def _gate_tpsl_slot_from_order(order, exchange_symbol):
|
||||
trig = _gate_order_trigger_price(order)
|
||||
try:
|
||||
amt = float(order.get("amount") or order.get("remaining") or 0)
|
||||
except Exception:
|
||||
amt = None
|
||||
if amt is not None and amt <= 0:
|
||||
amt = None
|
||||
oid = order.get("id")
|
||||
if oid is None and isinstance(order.get("info"), dict):
|
||||
oid = order["info"].get("id") or order["info"].get("order_id")
|
||||
disp = format_price_for_symbol(exchange_symbol, trig) if trig else "-"
|
||||
return {
|
||||
"order_id": str(oid) if oid is not None else "",
|
||||
"channel": "gate_trigger",
|
||||
"trigger_price": trig,
|
||||
"trigger_display": disp,
|
||||
"amount": amt,
|
||||
"type": str(order.get("type") or ""),
|
||||
}
|
||||
|
||||
|
||||
def fetch_exchange_tpsl_slots(exchange_symbol, direction, plan_sl=None, plan_tp=None):
|
||||
slots = {"sl": None, "tp": None}
|
||||
if not exchange_symbol:
|
||||
return slots
|
||||
ok, _ = ensure_exchange_live_ready()
|
||||
if not ok:
|
||||
return slots
|
||||
try:
|
||||
ensure_markets_loaded()
|
||||
ambiguous = []
|
||||
for order in _gate_list_trigger_open_orders(exchange_symbol):
|
||||
role = _gate_tpsl_role_from_order(order, direction)
|
||||
slot = _gate_tpsl_slot_from_order(order, exchange_symbol)
|
||||
if role in ("sl", "tp"):
|
||||
if slots[role] is None:
|
||||
slots[role] = slot
|
||||
continue
|
||||
ambiguous.append(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
|
||||
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:
|
||||
role = "sl"
|
||||
elif plan_tp_f is not None:
|
||||
role = "tp"
|
||||
else:
|
||||
continue
|
||||
if slots[role] is None:
|
||||
slots[role] = slot
|
||||
except Exception:
|
||||
pass
|
||||
return slots
|
||||
|
||||
|
||||
def cancel_gate_tpsl_slot(exchange_symbol, slot):
|
||||
if not slot or not exchange_symbol:
|
||||
return
|
||||
ensure_markets_loaded()
|
||||
oid = slot.get("order_id")
|
||||
if not oid:
|
||||
return
|
||||
params = _gate_swap_trigger_order_params()
|
||||
exchange.cancel_order(str(oid), exchange_symbol, params)
|
||||
|
||||
|
||||
def _resolve_tpsl_prices_for_manual(direction, live_price, sltp_mode, data):
|
||||
sltp_mode = (sltp_mode or "price").strip().lower()
|
||||
if sltp_mode == "pct":
|
||||
sl_pct = float(data.get("sl_pct") or 0)
|
||||
tp_pct = float(data.get("tp_pct") or 0)
|
||||
if sl_pct <= 0 or tp_pct <= 0:
|
||||
raise ValueError("百分比止盈止损须为正数")
|
||||
sl_ratio = sl_pct / 100.0
|
||||
tp_ratio = tp_pct / 100.0
|
||||
entry = float(live_price)
|
||||
if direction == "short":
|
||||
stop_loss = entry * (1 + sl_ratio)
|
||||
take_profit = entry * (1 - tp_ratio)
|
||||
else:
|
||||
stop_loss = entry * (1 - sl_ratio)
|
||||
take_profit = entry * (1 + tp_ratio)
|
||||
else:
|
||||
stop_loss = float(data.get("sl") or data.get("stop_loss") or 0)
|
||||
take_profit = float(data.get("tp") or data.get("take_profit") or data.get("tgt") or 0)
|
||||
if stop_loss <= 0 or take_profit <= 0:
|
||||
raise ValueError("止盈止损价格须大于 0")
|
||||
return stop_loss, take_profit
|
||||
|
||||
|
||||
def replace_active_monitor_tpsl_on_exchange(order_row, stop_loss, take_profit):
|
||||
ok, reason = ensure_exchange_live_ready()
|
||||
if not ok:
|
||||
raise RuntimeError(reason or "实盘未就绪")
|
||||
ex_sym = resolve_monitor_exchange_symbol(order_row)
|
||||
direction = order_row["direction"]
|
||||
cancel_gate_swap_trigger_orders(ex_sym)
|
||||
contracts = get_live_position_contracts(ex_sym, direction)
|
||||
if contracts is None or float(contracts) <= 0:
|
||||
raise ValueError("交易所当前无该方向持仓,无法挂止盈止损")
|
||||
amt = float(contracts)
|
||||
if amt <= 0:
|
||||
try:
|
||||
amt = float(order_row["order_amount"] or 0)
|
||||
except Exception:
|
||||
amt = 0
|
||||
if amt <= 0:
|
||||
raise ValueError("无法确定平仓数量")
|
||||
_gate_place_tp_sl_orders(ex_sym, direction, amt, float(stop_loss), float(take_profit))
|
||||
|
||||
|
||||
def extract_trade_price_from_order(order):
|
||||
if not order:
|
||||
return None
|
||||
@@ -4671,6 +4864,18 @@ def api_price_snapshot():
|
||||
except Exception:
|
||||
payload["price"] = px_for_fmt
|
||||
payload["price_display"] = px_disp
|
||||
if exchange_private_api_configured():
|
||||
try:
|
||||
payload["exchange_tpsl"] = fetch_exchange_tpsl_slots(
|
||||
ex_sym,
|
||||
r["direction"],
|
||||
plan_sl=r["stop_loss"],
|
||||
plan_tp=r["take_profit"],
|
||||
)
|
||||
except Exception:
|
||||
payload["exchange_tpsl"] = {"sl": None, "tp": None}
|
||||
else:
|
||||
payload["exchange_tpsl"] = {"sl": None, "tp": None}
|
||||
order_prices.append(payload)
|
||||
|
||||
return jsonify({
|
||||
@@ -4681,6 +4886,100 @@ def api_price_snapshot():
|
||||
})
|
||||
|
||||
|
||||
@app.route("/api/order/<int:order_id>/cancel_tpsl", methods=["POST"])
|
||||
@login_required
|
||||
def api_order_cancel_tpsl(order_id):
|
||||
data = request.get_json(silent=True) or {}
|
||||
role = (data.get("role") or "").strip().lower()
|
||||
if role not in ("sl", "tp"):
|
||||
return jsonify({"ok": False, "msg": "role 须为 sl 或 tp"}), 400
|
||||
conn = get_db()
|
||||
row = conn.execute(
|
||||
"SELECT * FROM order_monitors WHERE id=? AND status='active'",
|
||||
(order_id,),
|
||||
).fetchone()
|
||||
conn.close()
|
||||
if not row:
|
||||
return jsonify({"ok": False, "msg": "持仓不存在或已结束"}), 404
|
||||
ok, reason = ensure_exchange_live_ready()
|
||||
if not ok:
|
||||
return jsonify({"ok": False, "msg": reason}), 400
|
||||
ex_sym = resolve_monitor_exchange_symbol(row)
|
||||
slots = fetch_exchange_tpsl_slots(
|
||||
ex_sym, row["direction"], plan_sl=row["stop_loss"], plan_tp=row["take_profit"]
|
||||
)
|
||||
slot = slots.get(role)
|
||||
if not slot:
|
||||
return jsonify({"ok": False, "msg": f"交易所未找到{'止损' if role == 'sl' else '止盈'}委托"}), 404
|
||||
try:
|
||||
cancel_gate_tpsl_slot(ex_sym, slot)
|
||||
slots = fetch_exchange_tpsl_slots(
|
||||
ex_sym, row["direction"], plan_sl=row["stop_loss"], plan_tp=row["take_profit"]
|
||||
)
|
||||
return jsonify({"ok": True, "msg": "已撤单", "exchange_tpsl": slots})
|
||||
except Exception as e:
|
||||
return jsonify({"ok": False, "msg": friendly_exchange_error(e)}), 400
|
||||
|
||||
|
||||
@app.route("/api/order/<int:order_id>/place_tpsl", methods=["POST"])
|
||||
@login_required
|
||||
def api_order_place_tpsl(order_id):
|
||||
data = request.get_json(silent=True) or {}
|
||||
conn = get_db()
|
||||
row = conn.execute(
|
||||
"SELECT * FROM order_monitors WHERE id=? AND status='active'",
|
||||
(order_id,),
|
||||
).fetchone()
|
||||
if not row:
|
||||
conn.close()
|
||||
return jsonify({"ok": False, "msg": "持仓不存在或已结束"}), 404
|
||||
symbol = row["symbol"]
|
||||
direction = row["direction"]
|
||||
live_price = get_price(symbol)
|
||||
if live_price is None:
|
||||
conn.close()
|
||||
return jsonify({"ok": False, "msg": "获取交易所实时价格失败"}), 400
|
||||
try:
|
||||
sltp_mode = (data.get("sltp_mode") or "price").strip().lower()
|
||||
stop_loss, take_profit = _resolve_tpsl_prices_for_manual(direction, live_price, sltp_mode, data)
|
||||
except Exception as e:
|
||||
conn.close()
|
||||
return jsonify({"ok": False, "msg": str(e)}), 400
|
||||
planned_rr = calc_rr_ratio(direction, live_price, stop_loss, take_profit)
|
||||
if planned_rr is None or planned_rr < MANUAL_MIN_PLANNED_RR:
|
||||
conn.close()
|
||||
rr_txt = f"{planned_rr:.4f}" if planned_rr is not None else "无法计算"
|
||||
return jsonify(
|
||||
{
|
||||
"ok": False,
|
||||
"msg": f"计划盈亏比 {rr_txt}:1 低于最低要求 {MANUAL_MIN_PLANNED_RR}:1",
|
||||
}
|
||||
), 400
|
||||
try:
|
||||
replace_active_monitor_tpsl_on_exchange(row, stop_loss, take_profit)
|
||||
except Exception as e:
|
||||
conn.close()
|
||||
return jsonify({"ok": False, "msg": friendly_exchange_error(e)}), 400
|
||||
conn.execute(
|
||||
"UPDATE order_monitors SET stop_loss=?, take_profit=? WHERE id=?",
|
||||
(stop_loss, take_profit, order_id),
|
||||
)
|
||||
conn.commit()
|
||||
ex_sym = resolve_monitor_exchange_symbol(row)
|
||||
slots = fetch_exchange_tpsl_slots(ex_sym, direction, plan_sl=stop_loss, plan_tp=take_profit)
|
||||
conn.close()
|
||||
return jsonify(
|
||||
{
|
||||
"ok": True,
|
||||
"msg": "已先撤后挂止盈止损",
|
||||
"stop_loss": stop_loss,
|
||||
"take_profit": take_profit,
|
||||
"planned_rr": planned_rr,
|
||||
"exchange_tpsl": slots,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/api/symbol_liquidity_rank")
|
||||
@login_required
|
||||
def api_symbol_liquidity_rank():
|
||||
|
||||
Reference in New Issue
Block a user