diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index 9400274..b66e908 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -7224,6 +7224,15 @@ def api_price_snapshot(): except Exception: all_swap_positions = [] + from lib.hub.price_snapshot_lib import resolve_order_snapshot_price, seed_prices_from_positions + + seed_prices_from_positions( + prices, + order_rows, + all_swap_positions, + resolve_ex_sym_fn=resolve_monitor_exchange_symbol, + ) + key_prices = [] for r in key_rows: is_fib = is_fib_key_monitor_type(r["monitor_type"]) @@ -7364,8 +7373,6 @@ def api_price_snapshot(): }) order_prices = [] - from lib.hub.price_snapshot_lib import resolve_order_snapshot_price - for r in order_rows: margin = float(r["margin_capital"] or 0) leverage = float(r["leverage"] or 0) @@ -7403,7 +7410,11 @@ def api_price_snapshot(): if ex_metrics.get("notional") is not None: payload["exchange_notional"] = ex_metrics["notional"] if ex_metrics.get("mark_price") is not None: - payload["exchange_mark_price"] = ex_metrics["mark_price"] + mp = ex_metrics["mark_price"] + payload["exchange_mark_price"] = mp + payload["exchange_mark_price_display"] = format_price_for_symbol( + r["symbol"], mp + ) if ex_metrics.get("unrealized_pnl") is not None: payload["float_pnl"] = round(float(ex_metrics["unrealized_pnl"]), 2) payload["pnl_source"] = "exchange" @@ -7429,6 +7440,9 @@ def api_price_snapshot(): except Exception: payload["price"] = px_for_fmt payload["price_display"] = px_disp + if payload.get("exchange_mark_price") is None: + payload["exchange_mark_price"] = px_for_fmt + payload["exchange_mark_price_display"] = px_disp else: payload["price"] = None payload["price_display"] = "-" @@ -8288,6 +8302,8 @@ def add_order(): conn.close() flash(style_err) return redirect("/trade") + if entry_model: + trade_style = "trend" available_usdt = get_available_trading_usdt() live_price = get_price(symbol) if live_price is None: diff --git a/lib/hub/hub_bridge.py b/lib/hub/hub_bridge.py index d0925ab..0091529 100644 --- a/lib/hub/hub_bridge.py +++ b/lib/hub/hub_bridge.py @@ -527,6 +527,12 @@ def register_hub_routes(app): od = apply_order_monitor_source_labels(od) except Exception: pass + try: + from lib.trade.entry_model_lib import enrich_entry_model_display + + enrich_entry_model_display(od) + except Exception: + pass orders.append(od) trends = [] if c.get("has_trend"): diff --git a/lib/hub/price_snapshot_lib.py b/lib/hub/price_snapshot_lib.py index 8550ca1..1c6731c 100644 --- a/lib/hub/price_snapshot_lib.py +++ b/lib/hub/price_snapshot_lib.py @@ -1,7 +1,7 @@ """price_snapshot 共用:订单行情价兜底,避免 get_price 失败时整单不入 order_prices。""" from __future__ import annotations -from typing import Any, Callable, Mapping, Optional +from typing import Any, Callable, Mapping, Optional, Sequence from lib.hub.hub_position_metrics import parse_position_mark_price @@ -73,3 +73,52 @@ def resolve_order_snapshot_price( except (TypeError, ValueError): pass return None + + +def seed_prices_from_positions( + prices: dict[str, float], + order_rows: Sequence[Any], + all_positions: Sequence[dict[str, Any]], + *, + resolve_ex_sym_fn: Callable[[Any], str], +) -> None: + """用持仓标记价补全 prices 字典(symbol 与 order_monitors 行对齐)。""" + if not all_positions or not order_rows: + return + try: + from lib.hub.hub_symbol_lib import symbols_match + except Exception: + symbols_match = None + for r in order_rows: + try: + sym = str(r["symbol"] or "").strip() + except (KeyError, TypeError, IndexError): + sym = "" + if not sym or sym in prices: + continue + try: + ex_sym = resolve_ex_sym_fn(r) + except Exception: + ex_sym = sym + try: + direction = str(r["direction"] or "long").lower() + except (KeyError, TypeError, IndexError): + direction = "long" + for p in all_positions: + if not isinstance(p, dict): + continue + ps = p.get("symbol") or "" + if not ps: + continue + matched = ps == sym or ps == ex_sym + if not matched and symbols_match is not None: + matched = symbols_match(sym, ps) or symbols_match(ex_sym, ps) + if not matched: + continue + side = (p.get("side") or "").lower() + if side and side != direction: + continue + mp = parse_position_mark_price(p) + if mp is not None and mp > 0: + prices[sym] = float(mp) + break diff --git a/lib/instance/templates/embed_boot_scripts.html b/lib/instance/templates/embed_boot_scripts.html index bf6e31f..09a8009 100644 --- a/lib/instance/templates/embed_boot_scripts.html +++ b/lib/instance/templates/embed_boot_scripts.html @@ -891,6 +891,38 @@ function paintPriceTrend(el, key, value){ lastPriceMap[key] = value; } +function formatOrderMarkDisplay(o){ + const row = o || {}; + const markRaw = row.exchange_mark_price; + const markNum = Number(markRaw); + const hasMark = markRaw !== null && markRaw !== undefined && markRaw !== "" && Number.isFinite(markNum) && markNum > 0; + if(hasMark){ + if(row.exchange_mark_price_display && row.exchange_mark_price_display !== "-") return row.exchange_mark_price_display; + return markNum.toFixed(6); + } + if(row.price_display && row.price_display !== "-") return row.price_display; + const px = Number(row.price); + return Number.isFinite(px) && px > 0 ? px.toFixed(6) : "-"; +} +function paintOrderMarkAndPnl(orderId, o){ + const pEl = document.getElementById(`order-price-${orderId}`); + if(pEl){ + const disp = formatOrderMarkDisplay(o); + pEl.innerText = disp; + const markNum = Number(o && o.exchange_mark_price); + const pxNum = Number.isFinite(markNum) && markNum > 0 ? markNum : Number(o && o.price); + paintPriceTrend(pEl, `o-${orderId}`, Number.isFinite(pxNum) ? pxNum : null); + } + const pnlEl = document.getElementById(`order-pnl-${orderId}`); + if(pnlEl){ + pnlEl.innerText = `${formatSigned(o.float_pnl, 2)}U (${formatSigned(o.float_pct, 2)}%)`; + pnlEl.classList.remove("price-up","price-down","price-flat"); + if(Number(o.float_pnl) > 0) pnlEl.classList.add("price-up"); + else if(Number(o.float_pnl) < 0) pnlEl.classList.add("price-down"); + else pnlEl.classList.add("price-flat"); + } +} + function refreshPriceSnapshot(){ fetch("/api/price_snapshot").then(r=>r.json()).then(data=>{ const updatedEl = document.getElementById("price-last-updated"); @@ -925,22 +957,7 @@ function refreshPriceSnapshot(){ } }); (data.order_prices || []).forEach(o=>{ - const pEl = document.getElementById(`order-price-${o.id}`); - if(pEl){ - const hasMark = (()=>{ const x = o.exchange_mark_price; if(x===null||x===undefined||x==="")return false; const n=Number(x); return !Number.isNaN(n); })(); - let disp = ""; - if(hasMark && o.exchange_mark_price_display){ - disp = o.exchange_mark_price_display; - } else if(o.price_display){ - disp = o.price_display; - } else { - const px = hasMark ? Number(o.exchange_mark_price) : Number(o.price); - disp = Number.isFinite(px) ? px.toFixed(6) : "-"; - } - pEl.innerText = disp; - const pxNum = hasMark ? Number(o.exchange_mark_price) : Number(o.price); - paintPriceTrend(pEl, `o-${o.id}`, Number.isFinite(pxNum) ? pxNum : px); - } + paintOrderMarkAndPnl(o.id, o); const exM = document.getElementById(`order-ex-margin-${o.id}`); if(exM){ const mv = o.exchange_initial_margin; @@ -952,14 +969,6 @@ function refreshPriceSnapshot(){ exM.innerText = (prc === 0) ? "无仓数据" : "-"; } } - const pnlEl = document.getElementById(`order-pnl-${o.id}`); - if(pnlEl){ - pnlEl.innerText = `${formatSigned(o.float_pnl, 2)}U (${formatSigned(o.float_pct, 2)}%)`; - pnlEl.classList.remove("price-up","price-down","price-flat"); - if(Number(o.float_pnl) > 0) pnlEl.classList.add("price-up"); - else if(Number(o.float_pnl) < 0) pnlEl.classList.add("price-down"); - else pnlEl.classList.add("price-flat"); - } const rrEl = document.getElementById(`order-rr-${o.id}`); if(rrEl){ const rr = o.display_rr_ratio != null && o.display_rr_ratio !== "" ? o.display_rr_ratio : o.rr_ratio; @@ -1225,19 +1234,10 @@ function refreshPriceSnapshotConditional(){ if(typeof paintKeyMonitorSummary === "function") paintKeyMonitorSummary(k.id, k); }); } - if(page === "trade"){ + const tradePage = page === "trade" || !!document.querySelector("[id^='order-price-']"); + if(tradePage){ (data.order_prices || []).forEach(o=>{ - const pEl = document.getElementById(`order-price-${o.id}`); - if(pEl){ - const hasMark = (()=>{ const x = o.exchange_mark_price; if(x===null||x===undefined||x==="")return false; const n=Number(x); return !Number.isNaN(n); })(); - let disp = ""; - if(hasMark && o.exchange_mark_price_display) disp = o.exchange_mark_price_display; - else if(o.price_display) disp = o.price_display; - else { const px = hasMark ? Number(o.exchange_mark_price) : Number(o.price); disp = Number.isFinite(px) ? px.toFixed(6) : "-"; } - pEl.innerText = disp; - const pxNum = hasMark ? Number(o.exchange_mark_price) : Number(o.price); - paintPriceTrend(pEl, `o-${o.id}`, Number.isFinite(pxNum) ? pxNum : px); - } + paintOrderMarkAndPnl(o.id, o); const exM = document.getElementById(`order-ex-margin-${o.id}`); if(exM){ const mv = o.exchange_initial_margin; @@ -1245,14 +1245,6 @@ function refreshPriceSnapshotConditional(){ if(!Number.isNaN(mn)) exM.innerText = `${mn.toFixed(2)}U`; else { const prc = (typeof data.positions_raw_count === "number") ? data.positions_raw_count : null; exM.innerText = (prc === 0) ? "无仓数据" : "-"; } } - const pnlEl = document.getElementById(`order-pnl-${o.id}`); - if(pnlEl){ - pnlEl.innerText = `${formatSigned(o.float_pnl, 2)}U (${formatSigned(o.float_pct, 2)}%)`; - pnlEl.classList.remove("price-up","price-down","price-flat"); - if(Number(o.float_pnl) > 0) pnlEl.classList.add("price-up"); - else if(Number(o.float_pnl) < 0) pnlEl.classList.add("price-down"); - else pnlEl.classList.add("price-flat"); - } const rrEl = document.getElementById(`order-rr-${o.id}`); if(rrEl) rrEl.innerText = formatRrRatio(o.rr_ratio); paintBreakevenBadge(o.id, o.sl_breakeven_secured); diff --git a/lib/instance/templates/embed_page_fragment.html b/lib/instance/templates/embed_page_fragment.html index 0517eec..c71989d 100644 --- a/lib/instance/templates/embed_page_fragment.html +++ b/lib/instance/templates/embed_page_fragment.html @@ -111,7 +111,7 @@
来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %} - {% if o.entry_model_label %}开仓: {{ o.entry_model_label }}{% else %}风格: {{ '波段单' if o.trade_style == 'swing' else '趋势单' }}{% endif %} + {% if o.entry_model_label %}开仓: {{ o.entry_model_label }}{% elif intraday_discipline %}开仓: —{% else %}风格: {{ '波段单' if o.trade_style == 'swing' else '趋势单' }}{% endif %} 风险: {% if position_sizing_mode == 'full_margin' %}{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% else %}{{ o.risk_percent or '-' }}%≈{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% endif %} diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index fdf7604..570c199 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -224,7 +224,7 @@
来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %} - {% if o.entry_model_label %}开仓: {{ o.entry_model_label }}{% else %}风格: {{ '波段单' if o.trade_style == 'swing' else '趋势单' }}{% endif %} + {% if o.entry_model_label %}开仓: {{ o.entry_model_label }}{% elif intraday_discipline %}开仓: —{% else %}风格: {{ '波段单' if o.trade_style == 'swing' else '趋势单' }}{% endif %} 风险: {% if position_sizing_mode == 'full_margin' %}{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% else %}{{ o.risk_percent or '-' }}%≈{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% endif %} diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index bf02ded..de33960 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -1112,6 +1112,17 @@ return mt || "下单监控"; } + function monitorEntryStyleHtml(mo, intradayDiscipline) { + const o = mo || {}; + if (o.entry_model_label) return `开仓: ${esc(o.entry_model_label)}`; + if (intradayDiscipline) return "开仓: —"; + const ts = String(o.trade_style || "").toLowerCase(); + if (ts === "swing") return "风格: 波段单"; + if (ts === "trend") return "风格: 趋势单"; + if (o.trade_style) return `风格: ${esc(o.trade_style)}`; + return "风格: —"; + } + function monitorOrderSourceHtml(mo, trendPlan) { if (isTrendContext(mo, trendPlan)) { return `来源: ${esc(monitorOrderSourceLabel(mo, trendPlan))}`; @@ -3073,8 +3084,7 @@ meta.push(`移动保本:关`); } else if (mo.monitor_type || mo.key_signal_type || mo.trend_plan_id) { meta.push(monitorOrderSourceHtml(mo, trendPlan)); - if (mo.trade_style) meta.push(`风格: ${esc(mo.trade_style)}`); - else meta.push("风格: —"); + meta.push(monitorEntryStyleHtml(mo, intraday)); const riskLine = formatMonitorRiskMeta(mo, trendPlan); if (riskLine) meta.push(riskLine); const latestRiskLine = formatLatestRiskMeta(mo, trendPlan, pos, tpsl); @@ -3188,7 +3198,7 @@ const fcBadge = o.force_close_enabled ? forceCloseSymbolBadgeHtml(o) : ""; return `
#${esc(o.id)} · ${esc(o.symbol || o.exchange_symbol)} ${tcBadge}${fcBadge} · ${renderDirectionHtml(o.direction)}
-
触发 ${fmtSymbolPrice(o.trigger_price, sym, tickMap)} · SL ${fmtSymbolPrice(o.stop_loss, sym, tickMap)} · TP ${fmtSymbolPrice(o.take_profit, sym, tickMap)} · ${esc(o.trade_style || o.monitor_type || "下单监控")}
+
触发 ${fmtSymbolPrice(o.trigger_price, sym, tickMap)} · SL ${fmtSymbolPrice(o.stop_loss, sym, tickMap)} · TP ${fmtSymbolPrice(o.take_profit, sym, tickMap)} · ${esc(o.entry_model_label || o.trade_style || o.monitor_type || "下单监控")}
`; }) .join("");