+132
-76
@@ -1658,67 +1658,60 @@ def _exchange_tpsl_from_hub_order(hub_orders: list, symbol: str, side: str) -> d
|
||||
return None
|
||||
|
||||
|
||||
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
|
||||
def _order_price_op_indexes(order_prices: list) -> tuple[dict, list]:
|
||||
"""price_snapshot order_prices:id 可能为 int/str,需双键索引。"""
|
||||
by_id: dict = {}
|
||||
flat: list = []
|
||||
for op in order_prices:
|
||||
if not isinstance(op, dict):
|
||||
continue
|
||||
if not _symbols_match(symbol, op.get("symbol") or ""):
|
||||
flat.append(op)
|
||||
oid = op.get("id")
|
||||
if oid is None:
|
||||
continue
|
||||
et = op.get("exchange_tpsl")
|
||||
if isinstance(et, dict) and (et.get("sl") or et.get("tp")):
|
||||
return et
|
||||
by_id[oid] = op
|
||||
by_id[str(oid)] = op
|
||||
try:
|
||||
by_id[int(oid)] = op
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
return by_id, flat
|
||||
|
||||
|
||||
def _match_order_price_op(
|
||||
order_row: dict,
|
||||
by_id: dict,
|
||||
order_prices: list,
|
||||
) -> dict | None:
|
||||
if not isinstance(order_row, dict):
|
||||
return None
|
||||
oid = order_row.get("id")
|
||||
if oid is not None:
|
||||
for key in (oid, str(oid)):
|
||||
op = by_id.get(key)
|
||||
if isinstance(op, dict):
|
||||
return op
|
||||
try:
|
||||
op = by_id.get(int(oid))
|
||||
if isinstance(op, dict):
|
||||
return op
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
sym = order_row.get("exchange_symbol") or order_row.get("symbol") or ""
|
||||
direction = (order_row.get("direction") or "").lower()
|
||||
for op in order_prices:
|
||||
if not isinstance(op, dict):
|
||||
continue
|
||||
if not _symbols_match(sym, op.get("symbol") or ""):
|
||||
continue
|
||||
op_dir = (op.get("direction") or "").lower()
|
||||
if direction and op_dir and direction != op_dir:
|
||||
continue
|
||||
return op
|
||||
return None
|
||||
|
||||
|
||||
def _merge_flask_order_price_fields(hub_mon: dict | None, snap: dict | None) -> None:
|
||||
"""将 price_snapshot 中的快照盈亏比、已保本状态合并进 hub_monitor.orders。"""
|
||||
if not isinstance(hub_mon, dict) or not isinstance(snap, dict):
|
||||
return
|
||||
order_prices = snap.get("order_prices") or []
|
||||
op_by_id = {
|
||||
op.get("id"): op
|
||||
for op in order_prices
|
||||
if isinstance(op, dict) and op.get("id") is not None
|
||||
}
|
||||
orders = hub_mon.get("orders") or []
|
||||
if not isinstance(orders, list):
|
||||
return
|
||||
for o in orders:
|
||||
if not isinstance(o, dict):
|
||||
continue
|
||||
op = op_by_id.get(o.get("id"))
|
||||
if not isinstance(op, dict):
|
||||
continue
|
||||
if op.get("rr_ratio") is not None:
|
||||
o["rr_ratio"] = op["rr_ratio"]
|
||||
if "sl_breakeven_secured" in op:
|
||||
o["sl_breakeven_secured"] = bool(op["sl_breakeven_secured"])
|
||||
for key in (
|
||||
_ORDER_PRICE_MERGE_KEYS = (
|
||||
"stop_loss",
|
||||
"take_profit",
|
||||
"stop_loss_display",
|
||||
@@ -1734,13 +1727,81 @@ def _merge_flask_order_price_fields(hub_mon: dict | None, snap: dict | None) ->
|
||||
"time_close_label",
|
||||
"time_close_countdown",
|
||||
"time_close_remaining_sec",
|
||||
):
|
||||
if key in op and op[key] not in (None, ""):
|
||||
o[key] = op[key]
|
||||
)
|
||||
|
||||
|
||||
def _apply_order_price_op_fields(target: dict, op: dict) -> None:
|
||||
if not isinstance(target, dict) or not isinstance(op, dict):
|
||||
return
|
||||
if op.get("rr_ratio") is not None:
|
||||
target["rr_ratio"] = op["rr_ratio"]
|
||||
if "sl_breakeven_secured" in op:
|
||||
target["sl_breakeven_secured"] = bool(op["sl_breakeven_secured"])
|
||||
for key in _ORDER_PRICE_MERGE_KEYS:
|
||||
if key not in op:
|
||||
continue
|
||||
val = op[key]
|
||||
if key == "latest_risk_amount":
|
||||
if val is not None and val != "":
|
||||
target[key] = val
|
||||
continue
|
||||
if val not in (None, ""):
|
||||
target[key] = val
|
||||
|
||||
|
||||
def _find_exchange_tpsl_for_position(
|
||||
symbol: str,
|
||||
side: str,
|
||||
order_prices: list,
|
||||
hub_orders: list,
|
||||
) -> dict | None:
|
||||
side_l = (side or "").lower()
|
||||
by_id, flat = _order_price_op_indexes(order_prices)
|
||||
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 = _match_order_price_op(o, by_id, flat)
|
||||
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 flat:
|
||||
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_order_price_fields(hub_mon: dict | None, snap: dict | None) -> None:
|
||||
"""将 price_snapshot 中的快照盈亏比、已保本状态合并进 hub_monitor.orders。"""
|
||||
if not isinstance(hub_mon, dict) or not isinstance(snap, dict):
|
||||
return
|
||||
order_prices = snap.get("order_prices") or []
|
||||
by_id, flat = _order_price_op_indexes(order_prices)
|
||||
orders = hub_mon.get("orders") or []
|
||||
if not isinstance(orders, list):
|
||||
return
|
||||
for o in orders:
|
||||
if not isinstance(o, dict):
|
||||
continue
|
||||
op = _match_order_price_op(o, by_id, flat)
|
||||
if not isinstance(op, dict):
|
||||
continue
|
||||
_apply_order_price_op_fields(o, op)
|
||||
|
||||
|
||||
def _merge_flask_position_breakeven(agent_row: dict, snap: dict | None, hub_mon: dict | None) -> None:
|
||||
"""将 price_snapshot 的已保本状态同步到 agent 持仓,供中控首页表格展示。"""
|
||||
"""将 price_snapshot 的已保本、最新风险、保证金等同步到 agent 持仓。"""
|
||||
ag = agent_row.get("agent")
|
||||
if not isinstance(ag, dict) or not isinstance(snap, dict):
|
||||
return
|
||||
@@ -1748,14 +1809,10 @@ def _merge_flask_position_breakeven(agent_row: dict, snap: dict | None, hub_mon:
|
||||
if not isinstance(positions, list) or not positions:
|
||||
return
|
||||
order_prices = snap.get("order_prices") or []
|
||||
by_id, flat = _order_price_op_indexes(order_prices)
|
||||
hub_orders = []
|
||||
if isinstance(hub_mon, dict):
|
||||
hub_orders = hub_mon.get("orders") or []
|
||||
op_by_id = {
|
||||
op.get("id"): op
|
||||
for op in order_prices
|
||||
if isinstance(op, dict) and op.get("id") is not None
|
||||
}
|
||||
for p in positions:
|
||||
if not isinstance(p, dict):
|
||||
continue
|
||||
@@ -1770,18 +1827,22 @@ def _merge_flask_position_breakeven(agent_row: dict, snap: dict | None, hub_mon:
|
||||
continue
|
||||
if (o.get("direction") or "").lower() != side:
|
||||
continue
|
||||
matched = op_by_id.get(o.get("id"))
|
||||
matched = _match_order_price_op(o, by_id, flat)
|
||||
if isinstance(matched, dict):
|
||||
break
|
||||
if o.get("latest_risk_amount") is not None or o.get("exchange_initial_margin") is not None:
|
||||
matched = o
|
||||
break
|
||||
if matched is None:
|
||||
for op in order_prices:
|
||||
for op in flat:
|
||||
if not isinstance(op, dict):
|
||||
continue
|
||||
if not _symbols_match(sym, op.get("symbol") or ""):
|
||||
continue
|
||||
matched = op
|
||||
break
|
||||
if isinstance(matched, dict) and "sl_breakeven_secured" in matched:
|
||||
p["sl_breakeven_secured"] = bool(matched["sl_breakeven_secured"])
|
||||
if isinstance(matched, dict):
|
||||
_apply_order_price_op_fields(p, matched)
|
||||
|
||||
|
||||
def _agent_position_has_mark(p: dict) -> bool:
|
||||
@@ -1809,10 +1870,10 @@ def _find_matched_order_price_op(
|
||||
p: dict,
|
||||
order_prices: list,
|
||||
hub_orders: list,
|
||||
op_by_id: dict,
|
||||
) -> dict | None:
|
||||
sym = p.get("symbol") or ""
|
||||
side = (p.get("side") or "").lower()
|
||||
by_id, flat = _order_price_op_indexes(order_prices)
|
||||
for o in hub_orders:
|
||||
if not isinstance(o, dict):
|
||||
continue
|
||||
@@ -1821,11 +1882,11 @@ def _find_matched_order_price_op(
|
||||
continue
|
||||
if (o.get("direction") or "").lower() != side:
|
||||
continue
|
||||
matched = op_by_id.get(o.get("id"))
|
||||
matched = _match_order_price_op(o, by_id, flat)
|
||||
if isinstance(matched, dict):
|
||||
return matched
|
||||
break
|
||||
for op in order_prices:
|
||||
for op in flat:
|
||||
if not isinstance(op, dict):
|
||||
continue
|
||||
if not _symbols_match(sym, op.get("symbol") or ""):
|
||||
@@ -1848,15 +1909,10 @@ def _merge_flask_position_mark_price(
|
||||
hub_orders = []
|
||||
if isinstance(hub_mon, dict):
|
||||
hub_orders = hub_mon.get("orders") or []
|
||||
op_by_id = {
|
||||
op.get("id"): op
|
||||
for op in order_prices
|
||||
if isinstance(op, dict) and op.get("id") is not None
|
||||
}
|
||||
for p in positions:
|
||||
if not isinstance(p, dict) or _agent_position_has_mark(p):
|
||||
continue
|
||||
matched = _find_matched_order_price_op(p, order_prices, hub_orders, op_by_id)
|
||||
matched = _find_matched_order_price_op(p, order_prices, hub_orders)
|
||||
if isinstance(matched, dict):
|
||||
_apply_agent_mark_price(
|
||||
p,
|
||||
|
||||
@@ -706,11 +706,17 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveTrendSizingFooter(mo, trendPlan, isTrend) {
|
||||
function resolveTrendSizingFooter(mo, trendPlan, isTrend, pos) {
|
||||
const m = mo || {};
|
||||
const p = pos || {};
|
||||
if (!isTrend || !trendPlan || !trendPlan.id) {
|
||||
return {
|
||||
margin: m.exchange_initial_margin ?? m.plan_margin ?? null,
|
||||
margin:
|
||||
m.exchange_initial_margin ??
|
||||
p.exchange_initial_margin ??
|
||||
m.plan_margin ??
|
||||
p.plan_margin ??
|
||||
null,
|
||||
leverage: m.leverage,
|
||||
planBase: m.margin_capital,
|
||||
positionRatio: m.position_ratio,
|
||||
@@ -788,15 +794,59 @@
|
||||
hubHoldDurationTimer = setInterval(tickHubHoldDurations, 1000);
|
||||
}
|
||||
|
||||
function formatLatestRiskMeta(mo, trendPlan) {
|
||||
function estimateLatestRiskUsdt(side, entry, sl, pos, mo) {
|
||||
const e = Number(entry);
|
||||
const s = Number(sl);
|
||||
if (!Number.isFinite(e) || !Number.isFinite(s) || e <= 0) return null;
|
||||
const sd = (side || "long").toLowerCase();
|
||||
const rf = sd === "short" ? (s - e) / e : (e - s) / e;
|
||||
if (!Number.isFinite(rf)) return null;
|
||||
if (rf <= 0) return 0;
|
||||
const m = mo || {};
|
||||
const p = pos || {};
|
||||
let notional = Number(p.notional_usdt);
|
||||
if (!Number.isFinite(notional) || notional <= 0) {
|
||||
notional = Number(m.exchange_notional);
|
||||
}
|
||||
if (!Number.isFinite(notional) || notional <= 0) {
|
||||
const mc = Number(m.margin_capital);
|
||||
const lev = Number(m.leverage);
|
||||
if (Number.isFinite(mc) && mc > 0 && Number.isFinite(lev) && lev > 0) {
|
||||
notional = mc * lev;
|
||||
}
|
||||
}
|
||||
if (!Number.isFinite(notional) || notional <= 0) {
|
||||
const c = Math.abs(Number(p.contracts));
|
||||
const cs = Number(p.contract_size);
|
||||
const mult = Number.isFinite(cs) && cs > 0 ? cs : 1;
|
||||
const px = Number(p.mark_price);
|
||||
const mark = Number.isFinite(px) && px > 0 ? px : e;
|
||||
if (Number.isFinite(c) && c > 0) notional = c * mult * mark;
|
||||
}
|
||||
if (!Number.isFinite(notional) || notional <= 0) return null;
|
||||
return Math.round(notional * rf * 100) / 100;
|
||||
}
|
||||
|
||||
function formatLatestRiskMeta(mo, trendPlan, pos, tpsl) {
|
||||
const m = mo || {};
|
||||
const t = trendPlan || {};
|
||||
const v =
|
||||
let v =
|
||||
m.latest_risk_amount != null && m.latest_risk_amount !== ""
|
||||
? Number(m.latest_risk_amount)
|
||||
: pos && pos.latest_risk_amount != null && pos.latest_risk_amount !== ""
|
||||
? Number(pos.latest_risk_amount)
|
||||
: t.latest_risk_amount != null && t.latest_risk_amount !== ""
|
||||
? Number(t.latest_risk_amount)
|
||||
: null;
|
||||
if ((v == null || !Number.isFinite(v)) && tpsl && pos) {
|
||||
v = estimateLatestRiskUsdt(
|
||||
pos.side || m.direction,
|
||||
tpsl.entry,
|
||||
tpsl.sl,
|
||||
pos,
|
||||
m
|
||||
);
|
||||
}
|
||||
if (v != null && Number.isFinite(v)) {
|
||||
return `最新风险: ${fmt(v, 2)}U`;
|
||||
}
|
||||
@@ -2837,7 +2887,7 @@
|
||||
const upnl = resolveTrendFloatingPnl(pos, trendPlan);
|
||||
const pnlFmt = formatFloatingPnlText(upnl, pos.notional_usdt);
|
||||
const pnlText = pnlFmt.text;
|
||||
const sizingFoot = resolveTrendSizingFooter(mo, trendPlan, isTrend);
|
||||
const sizingFoot = resolveTrendSizingFooter(mo, trendPlan, isTrend, pos);
|
||||
const openMeta = resolvePositionOpenMeta(mo, trendPlan, isTrend);
|
||||
const marginText =
|
||||
sizingFoot.margin != null && sizingFoot.margin !== "" && Number.isFinite(Number(sizingFoot.margin))
|
||||
@@ -2855,7 +2905,7 @@
|
||||
meta.push(monitorOrderSourceHtml(mo, trendPlan));
|
||||
const riskLine = formatMonitorRiskMeta(mo, trendPlan);
|
||||
if (riskLine) meta.push(riskLine);
|
||||
const latestRiskLine = formatLatestRiskMeta(mo, trendPlan);
|
||||
const latestRiskLine = formatLatestRiskMeta(mo, trendPlan, pos, tpsl);
|
||||
if (latestRiskLine) meta.push(latestRiskLine);
|
||||
if (trendPlan && trendPlan.id) {
|
||||
const zone =
|
||||
@@ -2875,7 +2925,7 @@
|
||||
else meta.push("风格: —");
|
||||
const riskLine = formatMonitorRiskMeta(mo, trendPlan);
|
||||
if (riskLine) meta.push(riskLine);
|
||||
const latestRiskLine = formatLatestRiskMeta(mo, trendPlan);
|
||||
const latestRiskLine = formatLatestRiskMeta(mo, trendPlan, pos, tpsl);
|
||||
if (latestRiskLine) meta.push(latestRiskLine);
|
||||
const beOn = mo.breakeven_enabled === 1 || mo.breakeven_enabled === true;
|
||||
meta.push(
|
||||
|
||||
Reference in New Issue
Block a user