Fix mark/PnL display fallback and intraday entry labels
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
</div>
|
||||
<div class="pos-meta">
|
||||
<span class="pos-meta-item">来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %}</span>
|
||||
<span class="pos-meta-item">{% if o.entry_model_label %}开仓: {{ o.entry_model_label }}{% else %}风格: {{ '波段单' if o.trade_style == 'swing' else '趋势单' }}{% endif %}</span>
|
||||
<span class="pos-meta-item">{% if o.entry_model_label %}开仓: {{ o.entry_model_label }}{% elif intraday_discipline %}开仓: —{% else %}风格: {{ '波段单' if o.trade_style == 'swing' else '趋势单' }}{% endif %}</span>
|
||||
<span class="pos-meta-item">风险: {% 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 %}</span>
|
||||
<span class="pos-meta-item" id="order-latest-risk-wrap-{{ o.id }}" style="display:none">最新风险: —</span>
|
||||
<span class="pos-meta-item {% if not intraday_discipline %}{% if o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}{% endif %}">
|
||||
|
||||
@@ -224,7 +224,7 @@
|
||||
</div>
|
||||
<div class="pos-meta">
|
||||
<span class="pos-meta-item">来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %}</span>
|
||||
<span class="pos-meta-item">{% if o.entry_model_label %}开仓: {{ o.entry_model_label }}{% else %}风格: {{ '波段单' if o.trade_style == 'swing' else '趋势单' }}{% endif %}</span>
|
||||
<span class="pos-meta-item">{% if o.entry_model_label %}开仓: {{ o.entry_model_label }}{% elif intraday_discipline %}开仓: —{% else %}风格: {{ '波段单' if o.trade_style == 'swing' else '趋势单' }}{% endif %}</span>
|
||||
<span class="pos-meta-item">风险: {% 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 %}</span>
|
||||
<span class="pos-meta-item" id="order-latest-risk-wrap-{{ o.id }}" style="display:none">最新风险: —</span>
|
||||
<span class="pos-meta-item {% if not intraday_discipline %}{% if o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}{% endif %}">
|
||||
|
||||
@@ -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(`<span class="pos-meta-off">移动保本:关</span>`);
|
||||
} 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 `<div class="hub-mini-card">
|
||||
<div class="hub-mini-title">#${esc(o.id)} · ${esc(o.symbol || o.exchange_symbol)} ${tcBadge}${fcBadge} · ${renderDirectionHtml(o.direction)}</div>
|
||||
<div class="hub-mini-line">触发 ${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 || "下单监控")}</div>
|
||||
<div class="hub-mini-line">触发 ${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 || "下单监控")}</div>
|
||||
</div>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
Reference in New Issue
Block a user