Files
crypto_monitor/manual_trading_hub/exchange_orders.py
T
2026-05-24 07:35:48 +08:00

356 lines
11 KiB
Python

"""
中控子代理:拉取交易所挂单/条件单并规范化展示;撤销单笔或按合约批量撤销。
"""
from __future__ import annotations
from typing import Any
def _coerce_float(*values) -> float | None:
for v in values:
if v is None or v == "":
continue
try:
return float(v)
except (TypeError, ValueError):
continue
return None
def symbols_match(position_symbol: str, order_symbol: str) -> bool:
a = (position_symbol or "").strip().upper()
b = (order_symbol or "").strip().upper()
if not a or not b:
return False
if a == b:
return True
for suf in (":USDT", "/USDT:USDT", "/USDT"):
a2 = a.replace(suf, "")
b2 = b.replace(suf, "")
if f"{a2}/USDT" == b or f"{a2}/USDT:USDT" == b:
return True
if f"{b2}/USDT" == a or f"{b2}/USDT:USDT" == a:
return True
if a2 == b2:
return True
return False
def _order_type_str(order: dict) -> str:
info = order.get("info") or {}
if isinstance(info, dict):
for key in ("orderType", "type", "origType", "algoType", "ordType"):
val = info.get(key)
if val:
return str(val).upper()
return str(order.get("type") or "").upper()
def _is_conditional_type(typ: str) -> bool:
t = (typ or "").upper()
if not t:
return False
keys = ("STOP", "TAKE_PROFIT", "TRAIL", "TRIGGER", "CONDITIONAL")
return any(k in t for k in keys)
def _order_label(typ: str, side: str, reduce_only: bool | None) -> str:
t = (typ or "").upper()
side_l = (side or "").lower()
parts = []
if "TAKE_PROFIT" in t:
parts.append("止盈")
elif "STOP" in t:
parts.append("止损")
elif "LIMIT" in t:
parts.append("限价")
elif "MARKET" in t:
parts.append("市价")
else:
parts.append(typ or "委托")
if side_l == "buy":
parts.append("买入")
elif side_l == "sell":
parts.append("卖出")
if reduce_only:
parts.append("·只减仓")
return " ".join(parts)
def _normalize_raw_order(order: dict, *, channel: str) -> dict[str, Any] | None:
if not isinstance(order, dict):
return None
info = order.get("info") or {}
if not isinstance(info, dict):
info = {}
oid = order.get("id") or info.get("algoId") or info.get("orderId") or info.get("ordId")
if oid is None:
return None
sym = str(order.get("symbol") or info.get("symbol") or "")
typ = _order_type_str(order)
side = str(order.get("side") or info.get("side") or "").lower()
reduce_only = order.get("reduceOnly")
if reduce_only is None:
reduce_only = info.get("reduceOnly")
try:
reduce_only = bool(reduce_only) if reduce_only is not None else None
except (TypeError, ValueError):
reduce_only = None
trig = _coerce_float(
order.get("stopPrice"),
order.get("triggerPrice"),
info.get("triggerPrice"),
info.get("stopPrice"),
info.get("slTriggerPx"),
info.get("tpTriggerPx"),
)
price = _coerce_float(order.get("price"), info.get("price"))
amt = _coerce_float(order.get("amount"), order.get("remaining"), info.get("quantity"), info.get("origQty"))
category = "conditional" if _is_conditional_type(typ) or channel == "algo" else "limit"
return {
"id": str(oid),
"symbol": sym,
"channel": channel,
"category": category,
"label": _order_label(typ, side, reduce_only),
"type": typ,
"side": side,
"amount": amt,
"trigger_price": trig,
"price": price,
"reduce_only": reduce_only,
"status": str(order.get("status") or info.get("status") or "open"),
}
def _binance_list(ex: Any, symbol: str | None) -> list[dict]:
ex.load_markets()
out: list[dict] = []
symbols: list[str] = []
if symbol:
try:
symbols = [ex.market(symbol)["symbol"]]
except Exception:
symbols = [symbol]
else:
symbols = []
try:
for p in ex.fetch_positions() or []:
sym = p.get("symbol")
if sym:
symbols.append(sym)
except Exception:
pass
if symbol and not symbols:
symbols = [symbol]
def collect(ex_sym: str) -> None:
market = ex.market(ex_sym)
contract_id = market.get("id")
try:
for o in ex.fetch_open_orders(ex_sym) or []:
item = dict(o)
item["_channel"] = "regular"
n = _normalize_raw_order(item, channel="regular")
if n:
out.append(n)
except Exception:
pass
try:
if contract_id and hasattr(ex, "fapiPrivateGetOpenAlgoOrders"):
raw = ex.fapiPrivateGetOpenAlgoOrders({"symbol": contract_id})
items = raw if isinstance(raw, list) else (raw.get("orders") or raw.get("data") or [])
for info in items or []:
if not isinstance(info, dict):
continue
wrapped = {
"id": info.get("algoId") or info.get("orderId"),
"symbol": ex_sym,
"info": info,
"type": info.get("orderType") or info.get("type"),
"side": (info.get("side") or "").lower(),
"amount": info.get("quantity") or info.get("origQty"),
"stopPrice": info.get("triggerPrice") or info.get("stopPrice"),
"reduceOnly": info.get("reduceOnly"),
}
n = _normalize_raw_order(wrapped, channel="algo")
if n:
out.append(n)
except Exception:
pass
if symbols:
seen = set()
for s in symbols:
if s in seen:
continue
seen.add(s)
collect(s)
return out
def _okx_list(ex: Any, symbol: str | None) -> list[dict]:
ex.load_markets()
out: list[dict] = []
symbols: list[str] = []
if symbol:
try:
symbols = [ex.market(symbol)["symbol"]]
except Exception:
symbols = [symbol]
else:
try:
for p in ex.fetch_positions() or []:
sym = p.get("symbol")
if sym:
symbols.append(sym)
except Exception:
pass
if symbol and not symbols:
symbols = [symbol]
seen = set()
for sym in symbols:
if sym in seen:
continue
seen.add(sym)
try:
for o in ex.fetch_open_orders(sym) or []:
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)
except Exception:
pass
return out
def _gate_trigger_params(ex: Any) -> dict:
p = {"type": "swap", "trigger": True}
try:
ex.load_unified_status()
if ex.options.get("unifiedAccount"):
p["unifiedAccount"] = True
except Exception:
pass
return p
def _gate_list(ex: Any, symbol: str | None) -> list[dict]:
ex.load_markets()
out: list[dict] = []
symbols: list[str] = []
if symbol:
try:
symbols = [ex.market(symbol)["symbol"]]
except Exception:
symbols = [symbol]
else:
try:
for p in ex.fetch_positions() or []:
sym = p.get("symbol")
if sym:
symbols.append(sym)
except Exception:
pass
if symbol and not symbols:
symbols = [symbol]
trig_params = _gate_trigger_params(ex)
seen = set()
for sym in symbols:
if sym in seen:
continue
seen.add(sym)
try:
for o in ex.fetch_open_orders(sym) or []:
n = _normalize_raw_order(dict(o), channel="regular")
if n:
out.append(n)
except Exception:
pass
try:
for o in ex.fetch_open_orders(sym, params=trig_params) or []:
item = dict(o)
item["type"] = item.get("type") or "trigger"
n = _normalize_raw_order(item, channel="algo")
if n:
out.append(n)
except Exception:
pass
return out
def list_open_orders(ex: Any, exchange_kind: str, symbol: str | None = None) -> list[dict]:
kind = (exchange_kind or "binance").lower()
if kind == "binance":
orders = _binance_list(ex, symbol)
elif kind == "okx":
orders = _okx_list(ex, symbol)
else:
orders = _gate_list(ex, symbol)
if symbol:
orders = [o for o in orders if symbols_match(symbol, o.get("symbol") or "")]
# 去重 id+channel
seen: set[tuple[str, str]] = set()
uniq: list[dict] = []
for o in orders:
key = (o["id"], o["channel"])
if key in seen:
continue
seen.add(key)
uniq.append(o)
return uniq
def attach_orders_to_positions(positions: list[dict], orders: list[dict]) -> None:
for p in positions:
sym = p.get("symbol") or ""
matched = [o for o in orders if symbols_match(sym, o.get("symbol") or "")]
p["conditional_orders"] = [o for o in matched if o.get("category") == "conditional"]
p["regular_orders"] = [o for o in matched if o.get("category") != "conditional"]
def cancel_order(
ex: Any,
exchange_kind: str,
symbol: str,
order_id: str,
channel: str = "regular",
) -> None:
kind = (exchange_kind or "binance").lower()
ex.load_markets()
market = ex.market(symbol)
unified = market["symbol"]
ch = (channel or "regular").lower()
if kind == "binance" and ch == "algo":
contract_id = market.get("id")
if contract_id and hasattr(ex, "fapiPrivateDeleteAlgoOrder"):
ex.fapiPrivateDeleteAlgoOrder({"symbol": contract_id, "algoId": str(order_id)})
return
params = None
if kind == "gate" and ch == "algo":
params = _gate_trigger_params(ex)
ex.cancel_order(str(order_id), unified, params)
def cancel_orders_for_symbol(
ex: Any,
exchange_kind: str,
symbol: str,
*,
scope: str = "all",
) -> int:
"""scope: all | conditional | limit"""
orders = list_open_orders(ex, exchange_kind, symbol)
if scope == "conditional":
orders = [o for o in orders if o.get("category") == "conditional"]
elif scope == "limit":
orders = [o for o in orders if o.get("category") != "conditional"]
n = 0
for o in orders:
try:
cancel_order(ex, exchange_kind, symbol, o["id"], o.get("channel") or "regular")
n += 1
except Exception:
pass
return n