修复okx止盈止损
This commit is contained in:
+118
-11
@@ -37,13 +37,22 @@ from hub_web_auth import (
|
||||
)
|
||||
from url_public import browser_url, default_review_url, public_origin
|
||||
|
||||
try:
|
||||
from exchange_orders import symbols_match as _symbols_match
|
||||
except ImportError:
|
||||
|
||||
def _symbols_match(position_symbol: str, order_symbol: str) -> bool:
|
||||
a = (position_symbol or "").strip().upper()
|
||||
b = (order_symbol or "").strip().upper()
|
||||
return bool(a and b and a == b)
|
||||
|
||||
HUB_HOST = os.getenv("HUB_HOST", "0.0.0.0")
|
||||
HUB_PORT = int(os.getenv("HUB_PORT", "5100"))
|
||||
HUB_BRIDGE_TOKEN = (os.getenv("HUB_BRIDGE_TOKEN") or os.getenv("CONTROL_TOKEN") or "").strip()
|
||||
_trust_raw = (os.getenv("HUB_TRUST_LAN", "true") or "").strip().lower()
|
||||
HUB_TRUST_LAN = _trust_raw not in ("0", "false", "no", "off")
|
||||
DIR = Path(__file__).resolve().parent
|
||||
HUB_BUILD = "20260525-okx-tpsl"
|
||||
HUB_BUILD = "20260525-okx-tpsl2"
|
||||
HUB_AGENT_TIMEOUT = float(os.getenv("HUB_AGENT_TIMEOUT", "8"))
|
||||
HUB_FLASK_TIMEOUT = float(os.getenv("HUB_FLASK_TIMEOUT", "10"))
|
||||
_board_key_prices_raw = (os.getenv("HUB_BOARD_KEY_PRICES", "true") or "").strip().lower()
|
||||
@@ -352,33 +361,131 @@ def _flask_error_from_hub_mon(hub_mon: dict | None) -> str | None:
|
||||
)
|
||||
|
||||
|
||||
def _tpsl_slots_to_conditional_orders(exchange_tpsl: dict, symbol: str) -> list[dict]:
|
||||
"""将实例 price_snapshot 的 exchange_tpsl 转为中控条件单结构。"""
|
||||
out: list[dict] = []
|
||||
if not isinstance(exchange_tpsl, dict):
|
||||
return out
|
||||
for role, label in (("sl", "止损"), ("tp", "止盈")):
|
||||
slot = exchange_tpsl.get(role)
|
||||
if not isinstance(slot, dict):
|
||||
continue
|
||||
trig = slot.get("trigger_price")
|
||||
oid = slot.get("order_id")
|
||||
if trig is None or oid is None:
|
||||
continue
|
||||
try:
|
||||
trig_f = float(trig)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
out.append(
|
||||
{
|
||||
"id": str(oid),
|
||||
"symbol": symbol,
|
||||
"channel": "algo",
|
||||
"category": "conditional",
|
||||
"label": f"{label} {trig_f:g}",
|
||||
"trigger_price": trig_f,
|
||||
"amount": None,
|
||||
"status": "open",
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def _find_exchange_tpsl_for_position(
|
||||
symbol: str,
|
||||
side: str,
|
||||
order_prices: list,
|
||||
hub_orders: list,
|
||||
) -> dict | None:
|
||||
side_l = (side or "").lower()
|
||||
op_by_id = {
|
||||
op.get("id"): op
|
||||
for op in order_prices
|
||||
if isinstance(op, dict) and op.get("id") is not None
|
||||
}
|
||||
for o in hub_orders:
|
||||
if not isinstance(o, dict):
|
||||
continue
|
||||
o_sym = o.get("exchange_symbol") or o.get("symbol") or ""
|
||||
if not _symbols_match(symbol, o_sym):
|
||||
continue
|
||||
if (o.get("direction") or "").lower() != side_l:
|
||||
continue
|
||||
op = op_by_id.get(o.get("id"))
|
||||
if not isinstance(op, dict):
|
||||
continue
|
||||
et = op.get("exchange_tpsl")
|
||||
if isinstance(et, dict) and (et.get("sl") or et.get("tp")):
|
||||
return et
|
||||
for op in order_prices:
|
||||
if not isinstance(op, dict):
|
||||
continue
|
||||
if not _symbols_match(symbol, op.get("symbol") or ""):
|
||||
continue
|
||||
et = op.get("exchange_tpsl")
|
||||
if isinstance(et, dict) and (et.get("sl") or et.get("tp")):
|
||||
return et
|
||||
return None
|
||||
|
||||
|
||||
def _merge_flask_exchange_tpsl(agent_row: dict, snap: dict | None, hub_mon: dict | None) -> None:
|
||||
"""子代理挂单为空时,用实例 Flask 已算好的 exchange_tpsl 补全展示。"""
|
||||
ag = agent_row.get("agent")
|
||||
if not isinstance(ag, dict):
|
||||
return
|
||||
positions = ag.get("positions")
|
||||
if not isinstance(positions, list) or not positions:
|
||||
return
|
||||
if not isinstance(snap, dict):
|
||||
return
|
||||
order_prices = snap.get("order_prices") or []
|
||||
hub_orders = []
|
||||
if isinstance(hub_mon, dict):
|
||||
hub_orders = hub_mon.get("orders") or []
|
||||
for p in positions:
|
||||
if not isinstance(p, dict):
|
||||
continue
|
||||
sym = p.get("symbol") or ""
|
||||
side = p.get("side") or ""
|
||||
et = _find_exchange_tpsl_for_position(sym, side, order_prices, hub_orders)
|
||||
if not et:
|
||||
continue
|
||||
p["exchange_tpsl"] = et
|
||||
cond = p.get("conditional_orders") or []
|
||||
if not cond:
|
||||
p["conditional_orders"] = _tpsl_slots_to_conditional_orders(et, sym)
|
||||
|
||||
|
||||
async def _fetch_exchange_flask_bundle(
|
||||
client: httpx.AsyncClient, ex: dict
|
||||
) -> tuple[dict | None, dict | None, list | None]:
|
||||
"""单所 Flask:monitor / meta /(可选)price_snapshot 并行拉取。"""
|
||||
) -> tuple[dict | None, dict | None, list | None, dict | None]:
|
||||
"""单所 Flask:monitor / meta / price_snapshot(有 flask_url 时)并行拉取。"""
|
||||
caps = ex.get("capabilities") or []
|
||||
tasks = [
|
||||
_fetch_flask_json(client, ex, "/api/hub/monitor"),
|
||||
_fetch_flask_json(client, ex, "/api/hub/meta"),
|
||||
]
|
||||
want_prices = HUB_BOARD_KEY_PRICES and "key" in caps
|
||||
if want_prices:
|
||||
has_flask = bool((ex.get("flask_url") or "").strip())
|
||||
if has_flask:
|
||||
tasks.append(_fetch_flask_json(client, ex, "/api/price_snapshot"))
|
||||
results = await asyncio.gather(*tasks)
|
||||
hub_mon = results[0]
|
||||
meta = results[1]
|
||||
snap = results[2] if has_flask and len(results) > 2 else None
|
||||
key_prices = None
|
||||
if want_prices and len(results) > 2:
|
||||
snap = results[2]
|
||||
if isinstance(snap, dict):
|
||||
key_prices = snap.get("key_prices")
|
||||
return hub_mon, meta, key_prices
|
||||
want_prices = HUB_BOARD_KEY_PRICES and "key" in caps
|
||||
if want_prices and isinstance(snap, dict):
|
||||
key_prices = snap.get("key_prices")
|
||||
return hub_mon, meta, key_prices, snap if isinstance(snap, dict) else None
|
||||
|
||||
|
||||
async def _assemble_board_row(
|
||||
client: httpx.AsyncClient, ex: dict, agent_row: dict
|
||||
) -> dict:
|
||||
hub_mon, meta, key_prices = await _fetch_exchange_flask_bundle(client, ex)
|
||||
hub_mon, meta, key_prices, snap = await _fetch_exchange_flask_bundle(client, ex)
|
||||
_merge_flask_exchange_tpsl(agent_row, snap, hub_mon if isinstance(hub_mon, dict) else None)
|
||||
flask_ok = isinstance(hub_mon, dict) and hub_mon.get("ok") is not False
|
||||
raw_review = (ex.get("review_url") or "").strip()
|
||||
review_link = browser_url(raw_review) if raw_review else default_review_url(
|
||||
|
||||
Reference in New Issue
Block a user