diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py index 8dcb5b4..7fc485c 100644 --- a/crypto_monitor_binance/app.py +++ b/crypto_monitor_binance/app.py @@ -7498,6 +7498,13 @@ def api_price_snapshot(): exchange_tpsl=exchange_tpsl, format_price_fn=format_price_for_symbol, symbol=r["symbol"], + margin_capital=margin, + leverage=leverage, + exchange_notional=ex_metrics.get("notional") if ex_metrics else None, + contracts=abs(_position_row_effective_contracts(prow)) if prow else None, + contract_size=float(get_contract_size(r["symbol"])) if r["symbol"] else 1.0, + mark_price=ex_metrics.get("mark_price") if ex_metrics else price, + funds_decimals=FUNDS_DECIMALS, ) apply_time_close_to_payload(payload, r) payload["opened_at"] = r["opened_at"] if "opened_at" in r.keys() else None @@ -7617,6 +7624,32 @@ def api_order_place_tpsl(order_id): conn.commit() ex_sym = resolve_monitor_exchange_symbol(row) slots = fetch_exchange_tpsl_slots(ex_sym, direction) + prow = None + ex_metrics = None + if exchange_private_api_configured(): + try: + rows = exchange.fetch_positions([ex_sym]) or exchange.fetch_positions() or [] + prow = _select_live_position_row(rows, ex_sym, direction) + if prow: + ex_metrics = parse_ccxt_position_metrics(prow, order_leverage=row["leverage"]) + except Exception: + pass + from lib.trade.order_monitor_display_lib import enrich_active_monitor_tpsl_json + + display_extra = enrich_active_monitor_tpsl_json( + row, + stop_loss, + take_profit, + slots, + position_row=prow, + exchange_notional=ex_metrics.get("notional") if ex_metrics else None, + contract_size=float(get_contract_size(symbol)) if symbol else 1.0, + mark_price=live_price, + calc_rr_ratio_fn=calc_rr_ratio, + format_price_fn=format_price_for_symbol, + symbol=symbol, + funds_decimals=FUNDS_DECIMALS, + ) conn.close() return jsonify( { @@ -7626,6 +7659,7 @@ def api_order_place_tpsl(order_id): "take_profit": take_profit, "planned_rr": planned_rr, "exchange_tpsl": slots, + **display_extra, } ) diff --git a/crypto_monitor_binance/templates/index.html b/crypto_monitor_binance/templates/index.html index dc9d10e..b27d408 100644 --- a/crypto_monitor_binance/templates/index.html +++ b/crypto_monitor_binance/templates/index.html @@ -462,6 +462,7 @@ 来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %} 风格: {{ o.trade_style or 'trend' }} 风险: {% 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 %} + {% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %} @@ -484,6 +485,10 @@ 盈亏比 {% if o.rr_ratio is not none %}{{ '%g'|format(o.rr_ratio) }}:1{% else %}-:1{% endif %} +
+ 张数 + {% if o.order_amount is not none %}{{ '%g'|format(o.order_amount) }}{% else %}—{% endif %} +
标记价 - @@ -1696,6 +1701,13 @@ function submitTpslEntrust(){ alert(data.msg || '已提交'); closeTpslEntrustModal(); if(data.exchange_tpsl) paintExchangeTpslRow(orderId, data.exchange_tpsl); + paintPlanTpslDisplay(orderId, data); + paintLatestRiskDisplay(orderId, data); + const rrEl = document.getElementById(`order-rr-${orderId}`); + if(rrEl){ + const rr = data.display_rr_ratio != null && data.display_rr_ratio !== "" ? data.display_rr_ratio : data.planned_rr; + rrEl.innerText = formatRrRatio(rr); + } refreshPriceSnapshotConditional(); }).catch(()=>alert('委托请求失败')); } @@ -1763,6 +1775,25 @@ function paintPlanTpslDisplay(orderId, snap){ else if(tpDisp) card.setAttribute("data-plan-tp", tpDisp); } } +function paintLatestRiskDisplay(orderId, snap){ + const wrap = document.getElementById(`order-latest-risk-wrap-${orderId}`); + if(!wrap) return; + const v = snap && snap.latest_risk_amount; + const n = v != null && v !== "" ? Number(v) : NaN; + if(Number.isFinite(n)){ + wrap.style.display = "inline-flex"; + wrap.textContent = `最新风险: ${n.toFixed(2)}U`; + } else { + wrap.style.display = "none"; + } +} +function paintContractsDisplay(orderId, snap){ + const el = document.getElementById(`order-contracts-${orderId}`); + if(!el || !snap) return; + const v = snap.contracts != null && snap.contracts !== "" ? snap.contracts : snap.order_amount; + const n = v != null && v !== "" ? Number(v) : NaN; + el.innerText = Number.isFinite(n) ? String(parseFloat(n.toFixed(4))) : "—"; +} function paintPriceTrend(el, key, value){ if(!el) return; @@ -1886,8 +1917,11 @@ function refreshPriceSnapshot(){ } const rrEl = document.getElementById(`order-rr-${o.id}`); if(rrEl){ - rrEl.innerText = formatRrRatio(o.rr_ratio); + const rr = o.display_rr_ratio != null && o.display_rr_ratio !== "" ? o.display_rr_ratio : o.rr_ratio; + rrEl.innerText = formatRrRatio(rr); } + paintLatestRiskDisplay(o.id, o); + paintContractsDisplay(o.id, o); paintBreakevenBadge(o.id, o.sl_breakeven_secured); if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl); paintPlanTpslDisplay(o.id, o); diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index 6b84d96..1fc39ce 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -7391,6 +7391,13 @@ def api_price_snapshot(): exchange_tpsl=exchange_tpsl, format_price_fn=format_price_for_symbol, symbol=r["symbol"], + margin_capital=margin, + leverage=leverage, + exchange_notional=ex_metrics.get("notional") if ex_metrics else None, + contracts=abs(_position_row_effective_contracts(prow)) if prow else None, + contract_size=float(get_contract_size(r["symbol"])) if r["symbol"] else 1.0, + mark_price=ex_metrics.get("mark_price") if ex_metrics else price, + funds_decimals=FUNDS_DECIMALS, ) apply_time_close_to_payload(payload, r) payload["opened_at"] = r["opened_at"] if "opened_at" in r.keys() else None @@ -7512,6 +7519,32 @@ def api_order_place_tpsl(order_id): conn.commit() ex_sym = resolve_monitor_exchange_symbol(row) slots = fetch_exchange_tpsl_slots(ex_sym, direction, plan_sl=stop_loss, plan_tp=take_profit) + prow = None + ex_metrics = None + if exchange_private_api_configured(): + try: + rows = exchange.fetch_positions([ex_sym]) or exchange.fetch_positions() or [] + prow = _select_live_position_row(rows, ex_sym, direction) + if prow: + ex_metrics = parse_ccxt_position_metrics(prow, order_leverage=row["leverage"]) + except Exception: + pass + from lib.trade.order_monitor_display_lib import enrich_active_monitor_tpsl_json + + display_extra = enrich_active_monitor_tpsl_json( + row, + stop_loss, + take_profit, + slots, + position_row=prow, + exchange_notional=ex_metrics.get("notional") if ex_metrics else None, + contract_size=float(get_contract_size(symbol)) if symbol else 1.0, + mark_price=live_price, + calc_rr_ratio_fn=calc_rr_ratio, + format_price_fn=format_price_for_symbol, + symbol=symbol, + funds_decimals=FUNDS_DECIMALS, + ) conn.close() return jsonify( { @@ -7521,6 +7554,7 @@ def api_order_place_tpsl(order_id): "take_profit": take_profit, "planned_rr": planned_rr, "exchange_tpsl": slots, + **display_extra, } ) diff --git a/crypto_monitor_gate/templates/index.html b/crypto_monitor_gate/templates/index.html index b617f78..018a03d 100644 --- a/crypto_monitor_gate/templates/index.html +++ b/crypto_monitor_gate/templates/index.html @@ -429,6 +429,7 @@ 来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %} 风格: {{ o.trade_style or 'trend' }} 风险: {% 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 %} + {% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %} @@ -451,6 +452,10 @@ 盈亏比 {% if o.rr_ratio is not none %}{{ '%g'|format(o.rr_ratio) }}:1{% else %}-:1{% endif %}
+
+ 张数 + {% if o.order_amount is not none %}{{ '%g'|format(o.order_amount) }}{% else %}—{% endif %} +
标记价 - @@ -1663,6 +1668,13 @@ function submitTpslEntrust(){ alert(data.msg || '已提交'); closeTpslEntrustModal(); if(data.exchange_tpsl) paintExchangeTpslRow(orderId, data.exchange_tpsl); + paintPlanTpslDisplay(orderId, data); + paintLatestRiskDisplay(orderId, data); + const rrEl = document.getElementById(`order-rr-${orderId}`); + if(rrEl){ + const rr = data.display_rr_ratio != null && data.display_rr_ratio !== "" ? data.display_rr_ratio : data.planned_rr; + rrEl.innerText = formatRrRatio(rr); + } refreshPriceSnapshotConditional(); }).catch(()=>alert('委托请求失败')); } @@ -1730,6 +1742,25 @@ function paintPlanTpslDisplay(orderId, snap){ else if(tpDisp) card.setAttribute("data-plan-tp", tpDisp); } } +function paintLatestRiskDisplay(orderId, snap){ + const wrap = document.getElementById(`order-latest-risk-wrap-${orderId}`); + if(!wrap) return; + const v = snap && snap.latest_risk_amount; + const n = v != null && v !== "" ? Number(v) : NaN; + if(Number.isFinite(n)){ + wrap.style.display = "inline-flex"; + wrap.textContent = `最新风险: ${n.toFixed(2)}U`; + } else { + wrap.style.display = "none"; + } +} +function paintContractsDisplay(orderId, snap){ + const el = document.getElementById(`order-contracts-${orderId}`); + if(!el || !snap) return; + const v = snap.contracts != null && snap.contracts !== "" ? snap.contracts : snap.order_amount; + const n = v != null && v !== "" ? Number(v) : NaN; + el.innerText = Number.isFinite(n) ? String(parseFloat(n.toFixed(4))) : "—"; +} function paintPriceTrend(el, key, value){ if(!el) return; @@ -1813,8 +1844,11 @@ function refreshPriceSnapshot(){ } const rrEl = document.getElementById(`order-rr-${o.id}`); if(rrEl){ - rrEl.innerText = formatRrRatio(o.rr_ratio); + const rr = o.display_rr_ratio != null && o.display_rr_ratio !== "" ? o.display_rr_ratio : o.rr_ratio; + rrEl.innerText = formatRrRatio(rr); } + paintLatestRiskDisplay(o.id, o); + paintContractsDisplay(o.id, o); paintBreakevenBadge(o.id, o.sl_breakeven_secured); if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl); paintPlanTpslDisplay(o.id, o); diff --git a/crypto_monitor_gate_bot/app.py b/crypto_monitor_gate_bot/app.py index 8b9f158..9b78732 100644 --- a/crypto_monitor_gate_bot/app.py +++ b/crypto_monitor_gate_bot/app.py @@ -7387,6 +7387,13 @@ def api_price_snapshot(): exchange_tpsl=exchange_tpsl, format_price_fn=format_price_for_symbol, symbol=r["symbol"], + margin_capital=margin, + leverage=leverage, + exchange_notional=ex_metrics.get("notional") if ex_metrics else None, + contracts=abs(_position_row_effective_contracts(prow)) if prow else None, + contract_size=float(get_contract_size(r["symbol"])) if r["symbol"] else 1.0, + mark_price=ex_metrics.get("mark_price") if ex_metrics else price, + funds_decimals=FUNDS_DECIMALS, ) apply_time_close_to_payload(payload, r) payload["opened_at"] = r["opened_at"] if "opened_at" in r.keys() else None @@ -7508,6 +7515,32 @@ def api_order_place_tpsl(order_id): conn.commit() ex_sym = resolve_monitor_exchange_symbol(row) slots = fetch_exchange_tpsl_slots(ex_sym, direction, plan_sl=stop_loss, plan_tp=take_profit) + prow = None + ex_metrics = None + if exchange_private_api_configured(): + try: + rows = exchange.fetch_positions([ex_sym]) or exchange.fetch_positions() or [] + prow = _select_live_position_row(rows, ex_sym, direction) + if prow: + ex_metrics = parse_ccxt_position_metrics(prow, order_leverage=row["leverage"]) + except Exception: + pass + from lib.trade.order_monitor_display_lib import enrich_active_monitor_tpsl_json + + display_extra = enrich_active_monitor_tpsl_json( + row, + stop_loss, + take_profit, + slots, + position_row=prow, + exchange_notional=ex_metrics.get("notional") if ex_metrics else None, + contract_size=float(get_contract_size(symbol)) if symbol else 1.0, + mark_price=live_price, + calc_rr_ratio_fn=calc_rr_ratio, + format_price_fn=format_price_for_symbol, + symbol=symbol, + funds_decimals=FUNDS_DECIMALS, + ) conn.close() return jsonify( { @@ -7517,6 +7550,7 @@ def api_order_place_tpsl(order_id): "take_profit": take_profit, "planned_rr": planned_rr, "exchange_tpsl": slots, + **display_extra, } ) diff --git a/crypto_monitor_gate_bot/templates/index.html b/crypto_monitor_gate_bot/templates/index.html index b617f78..018a03d 100644 --- a/crypto_monitor_gate_bot/templates/index.html +++ b/crypto_monitor_gate_bot/templates/index.html @@ -429,6 +429,7 @@ 来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %} 风格: {{ o.trade_style or 'trend' }} 风险: {% 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 %} + {% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %} @@ -451,6 +452,10 @@ 盈亏比 {% if o.rr_ratio is not none %}{{ '%g'|format(o.rr_ratio) }}:1{% else %}-:1{% endif %}
+
+ 张数 + {% if o.order_amount is not none %}{{ '%g'|format(o.order_amount) }}{% else %}—{% endif %} +
标记价 - @@ -1663,6 +1668,13 @@ function submitTpslEntrust(){ alert(data.msg || '已提交'); closeTpslEntrustModal(); if(data.exchange_tpsl) paintExchangeTpslRow(orderId, data.exchange_tpsl); + paintPlanTpslDisplay(orderId, data); + paintLatestRiskDisplay(orderId, data); + const rrEl = document.getElementById(`order-rr-${orderId}`); + if(rrEl){ + const rr = data.display_rr_ratio != null && data.display_rr_ratio !== "" ? data.display_rr_ratio : data.planned_rr; + rrEl.innerText = formatRrRatio(rr); + } refreshPriceSnapshotConditional(); }).catch(()=>alert('委托请求失败')); } @@ -1730,6 +1742,25 @@ function paintPlanTpslDisplay(orderId, snap){ else if(tpDisp) card.setAttribute("data-plan-tp", tpDisp); } } +function paintLatestRiskDisplay(orderId, snap){ + const wrap = document.getElementById(`order-latest-risk-wrap-${orderId}`); + if(!wrap) return; + const v = snap && snap.latest_risk_amount; + const n = v != null && v !== "" ? Number(v) : NaN; + if(Number.isFinite(n)){ + wrap.style.display = "inline-flex"; + wrap.textContent = `最新风险: ${n.toFixed(2)}U`; + } else { + wrap.style.display = "none"; + } +} +function paintContractsDisplay(orderId, snap){ + const el = document.getElementById(`order-contracts-${orderId}`); + if(!el || !snap) return; + const v = snap.contracts != null && snap.contracts !== "" ? snap.contracts : snap.order_amount; + const n = v != null && v !== "" ? Number(v) : NaN; + el.innerText = Number.isFinite(n) ? String(parseFloat(n.toFixed(4))) : "—"; +} function paintPriceTrend(el, key, value){ if(!el) return; @@ -1813,8 +1844,11 @@ function refreshPriceSnapshot(){ } const rrEl = document.getElementById(`order-rr-${o.id}`); if(rrEl){ - rrEl.innerText = formatRrRatio(o.rr_ratio); + const rr = o.display_rr_ratio != null && o.display_rr_ratio !== "" ? o.display_rr_ratio : o.rr_ratio; + rrEl.innerText = formatRrRatio(rr); } + paintLatestRiskDisplay(o.id, o); + paintContractsDisplay(o.id, o); paintBreakevenBadge(o.id, o.sl_breakeven_secured); if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl); paintPlanTpslDisplay(o.id, o); diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 7aed8da..1e66c47 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -6931,6 +6931,13 @@ def api_price_snapshot(): exchange_tpsl=exchange_tpsl, format_price_fn=format_price_for_symbol, symbol=r["symbol"], + margin_capital=margin, + leverage=leverage, + exchange_notional=ex_metrics.get("notional") if ex_metrics else None, + contracts=abs(_position_row_effective_contracts(prow)) if prow else None, + contract_size=float(get_contract_size(r["symbol"])) if r["symbol"] else 1.0, + mark_price=ex_metrics.get("mark_price") if ex_metrics else price, + funds_decimals=FUNDS_DECIMALS, ) apply_time_close_to_payload(payload, r) payload["opened_at"] = r["opened_at"] if "opened_at" in r.keys() else None @@ -7361,6 +7368,32 @@ def api_order_place_tpsl(order_id): conn.commit() ex_sym = resolve_monitor_exchange_symbol(row) slots = fetch_exchange_tpsl_slots(ex_sym, direction, plan_sl=stop_loss, plan_tp=take_profit) + prow = None + ex_metrics = None + if exchange_private_api_configured(): + try: + rows = exchange.fetch_positions([ex_sym]) or exchange.fetch_positions() or [] + prow = _select_live_position_row(rows, ex_sym, direction) + if prow: + ex_metrics = parse_ccxt_position_metrics(prow, order_leverage=row["leverage"]) + except Exception: + pass + from lib.trade.order_monitor_display_lib import enrich_active_monitor_tpsl_json + + display_extra = enrich_active_monitor_tpsl_json( + row, + stop_loss, + take_profit, + slots, + position_row=prow, + exchange_notional=ex_metrics.get("notional") if ex_metrics else None, + contract_size=float(get_contract_size(symbol)) if symbol else 1.0, + mark_price=live_price, + calc_rr_ratio_fn=calc_rr_ratio, + format_price_fn=format_price_for_symbol, + symbol=symbol, + funds_decimals=FUNDS_DECIMALS, + ) conn.close() return jsonify( { @@ -7370,6 +7403,7 @@ def api_order_place_tpsl(order_id): "take_profit": take_profit, "planned_rr": planned_rr, "exchange_tpsl": slots, + **display_extra, } ) diff --git a/crypto_monitor_okx/templates/index.html b/crypto_monitor_okx/templates/index.html index be479e3..c667cd8 100644 --- a/crypto_monitor_okx/templates/index.html +++ b/crypto_monitor_okx/templates/index.html @@ -458,6 +458,7 @@ 来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %} 风格: {{ o.trade_style or 'trend' }} 风险: {% 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 %} + {% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %} @@ -480,6 +481,10 @@ 盈亏比 {% if o.rr_ratio is not none %}{{ '%g'|format(o.rr_ratio) }}:1{% else %}-:1{% endif %}
+
+ 张数 + {% if o.order_amount is not none %}{{ '%g'|format(o.order_amount) }}{% else %}—{% endif %} +
标记价 - @@ -1692,6 +1697,13 @@ function submitTpslEntrust(){ alert(data.msg || '已提交'); closeTpslEntrustModal(); if(data.exchange_tpsl) paintExchangeTpslRow(orderId, data.exchange_tpsl); + paintPlanTpslDisplay(orderId, data); + paintLatestRiskDisplay(orderId, data); + const rrEl = document.getElementById(`order-rr-${orderId}`); + if(rrEl){ + const rr = data.display_rr_ratio != null && data.display_rr_ratio !== "" ? data.display_rr_ratio : data.planned_rr; + rrEl.innerText = formatRrRatio(rr); + } refreshPriceSnapshotConditional(); }).catch(()=>alert('委托请求失败')); } @@ -1759,6 +1771,25 @@ function paintPlanTpslDisplay(orderId, snap){ else if(tpDisp) card.setAttribute("data-plan-tp", tpDisp); } } +function paintLatestRiskDisplay(orderId, snap){ + const wrap = document.getElementById(`order-latest-risk-wrap-${orderId}`); + if(!wrap) return; + const v = snap && snap.latest_risk_amount; + const n = v != null && v !== "" ? Number(v) : NaN; + if(Number.isFinite(n)){ + wrap.style.display = "inline-flex"; + wrap.textContent = `最新风险: ${n.toFixed(2)}U`; + } else { + wrap.style.display = "none"; + } +} +function paintContractsDisplay(orderId, snap){ + const el = document.getElementById(`order-contracts-${orderId}`); + if(!el || !snap) return; + const v = snap.contracts != null && snap.contracts !== "" ? snap.contracts : snap.order_amount; + const n = v != null && v !== "" ? Number(v) : NaN; + el.innerText = Number.isFinite(n) ? String(parseFloat(n.toFixed(4))) : "—"; +} function paintPriceTrend(el, key, value){ if(!el) return; @@ -1842,8 +1873,11 @@ function refreshPriceSnapshot(){ } const rrEl = document.getElementById(`order-rr-${o.id}`); if(rrEl){ - rrEl.innerText = formatRrRatio(o.rr_ratio); + const rr = o.display_rr_ratio != null && o.display_rr_ratio !== "" ? o.display_rr_ratio : o.rr_ratio; + rrEl.innerText = formatRrRatio(rr); } + paintLatestRiskDisplay(o.id, o); + paintContractsDisplay(o.id, o); paintBreakevenBadge(o.id, o.sl_breakeven_secured); if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl); paintPlanTpslDisplay(o.id, o); diff --git a/lib/instance/templates/embed_boot_scripts.html b/lib/instance/templates/embed_boot_scripts.html index fb51acf..31eb2ae 100644 --- a/lib/instance/templates/embed_boot_scripts.html +++ b/lib/instance/templates/embed_boot_scripts.html @@ -873,6 +873,13 @@ function submitTpslEntrust(){ alert(data.msg || '已提交'); closeTpslEntrustModal(); if(data.exchange_tpsl) paintExchangeTpslRow(orderId, data.exchange_tpsl); + paintPlanTpslDisplay(orderId, data); + paintLatestRiskDisplay(orderId, data); + const rrEl = document.getElementById(`order-rr-${orderId}`); + if(rrEl){ + const rr = data.display_rr_ratio != null && data.display_rr_ratio !== "" ? data.display_rr_ratio : data.planned_rr; + rrEl.innerText = formatRrRatio(rr); + } refreshPriceSnapshotConditional(); }).catch(()=>alert('委托请求失败')); } @@ -944,6 +951,25 @@ function paintPlanTpslDisplay(orderId, snap){ else if(tpDisp) card.setAttribute("data-plan-tp", tpDisp); } } +function paintLatestRiskDisplay(orderId, snap){ + const wrap = document.getElementById(`order-latest-risk-wrap-${orderId}`); + if(!wrap) return; + const v = snap && snap.latest_risk_amount; + const n = v != null && v !== "" ? Number(v) : NaN; + if(Number.isFinite(n)){ + wrap.style.display = "inline-flex"; + wrap.textContent = `最新风险: ${n.toFixed(2)}U`; + } else { + wrap.style.display = "none"; + } +} +function paintContractsDisplay(orderId, snap){ + const el = document.getElementById(`order-contracts-${orderId}`); + if(!el || !snap) return; + const v = snap.contracts != null && snap.contracts !== "" ? snap.contracts : snap.order_amount; + const n = v != null && v !== "" ? Number(v) : NaN; + el.innerText = Number.isFinite(n) ? String(parseFloat(n.toFixed(4))) : "—"; +} function paintPriceTrend(el, key, value){ if(!el) return; @@ -1027,8 +1053,11 @@ function refreshPriceSnapshot(){ } const rrEl = document.getElementById(`order-rr-${o.id}`); if(rrEl){ - rrEl.innerText = formatRrRatio(o.rr_ratio); + const rr = o.display_rr_ratio != null && o.display_rr_ratio !== "" ? o.display_rr_ratio : o.rr_ratio; + rrEl.innerText = formatRrRatio(rr); } + paintLatestRiskDisplay(o.id, o); + paintContractsDisplay(o.id, o); paintBreakevenBadge(o.id, o.sl_breakeven_secured); if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl); paintPlanTpslDisplay(o.id, o); diff --git a/lib/instance/templates/embed_page_fragment.html b/lib/instance/templates/embed_page_fragment.html index 6f030dd..8845da7 100644 --- a/lib/instance/templates/embed_page_fragment.html +++ b/lib/instance/templates/embed_page_fragment.html @@ -109,6 +109,7 @@ 来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %} 风格: {{ o.trade_style or 'trend' }} 风险: {% 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 %} + {% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %} @@ -131,6 +132,10 @@ 盈亏比 {% if o.rr_ratio is not none %}{{ '%g'|format(o.rr_ratio) }}:1{% else %}-:1{% endif %}
+
+ 张数 + {% if o.order_amount is not none %}{{ '%g'|format(o.order_amount) }}{% else %}—{% endif %} +
标记价 - diff --git a/lib/trade/order_monitor_display_lib.py b/lib/trade/order_monitor_display_lib.py index 13643e8..7059b61 100644 --- a/lib/trade/order_monitor_display_lib.py +++ b/lib/trade/order_monitor_display_lib.py @@ -175,6 +175,66 @@ def resolve_live_tpsl_prices( return disp_sl, disp_tp, ex_sl, ex_tp +def calc_risk_fraction(direction: str, entry_price: Any, stop_loss: Any) -> Optional[float]: + """|入场-止损|/入场;盈利侧止损返回 0。""" + entry = _positive_float(entry_price) + sl = _positive_float(stop_loss) + if entry is None or sl is None: + return None + d = (direction or "long").strip().lower() + if d == "short": + risk = sl - entry + else: + risk = entry - sl + if risk <= 0: + return 0.0 + return risk / entry + + +def calc_latest_risk_amount( + direction: str, + entry_price: Any, + stop_loss: Any, + *, + margin_capital: Any = None, + leverage: Any = None, + exchange_notional: Any = None, + contracts: Any = None, + contract_size: Any = None, + mark_price: Any = None, + funds_decimals: int = 2, +) -> Optional[float]: + """按当前止损与持仓名义价值估算最新风险(U)。""" + rf = calc_risk_fraction(direction, entry_price, stop_loss) + if rf is None: + return None + if rf <= 0: + return 0.0 + notional = _positive_float(exchange_notional) + if notional is None: + try: + mc = float(margin_capital or 0) + lev = float(leverage or 0) + if mc > 0 and lev > 0: + notional = mc * lev + except (TypeError, ValueError): + pass + if notional is None: + try: + c = abs(float(contracts or 0)) + cs = float(contract_size or 1) + if cs <= 0: + cs = 1.0 + px = _positive_float(mark_price) or _positive_float(entry_price) + if c > 0 and px is not None: + notional = c * cs * px + except (TypeError, ValueError): + pass + if notional is None or notional <= 0: + return None + return round(notional * rf, funds_decimals) + + def order_monitor_tpsl_needs_sync( plan_sl: Any, plan_tp: Any, @@ -210,8 +270,17 @@ def apply_order_price_display_fields( exchange_tpsl: Any = None, format_price_fn: Optional[Callable[[Any, Any], str]] = None, symbol: Any = None, + margin_capital: Any = None, + leverage: Any = None, + exchange_notional: Any = None, + contracts: Any = None, + contract_size: Any = None, + mark_price: Any = None, + funds_decimals: int = 2, ) -> dict[str, Any]: disp_sl, disp_tp, _, _ = resolve_live_tpsl_prices(stop_loss, take_profit, exchange_tpsl) + payload["stop_loss_raw"] = _positive_float(stop_loss) + payload["take_profit_raw"] = _positive_float(take_profit) payload["rr_ratio"] = snapshot_rr( calc_rr_ratio_fn, direction, @@ -231,6 +300,25 @@ def apply_order_price_display_fields( ) else: payload["display_rr_ratio"] = None + if contracts is not None: + try: + c = abs(float(contracts)) + if c > 0: + payload["contracts"] = c + except (TypeError, ValueError): + pass + payload["latest_risk_amount"] = calc_latest_risk_amount( + direction, + entry_price, + disp_sl if disp_sl is not None else stop_loss, + margin_capital=margin_capital, + leverage=leverage, + exchange_notional=exchange_notional, + contracts=payload.get("contracts") if payload.get("contracts") is not None else contracts, + contract_size=contract_size, + mark_price=mark_price, + funds_decimals=funds_decimals, + ) if format_price_fn is not None and symbol is not None: payload["stop_loss_display"] = ( format_price_fn(symbol, disp_sl) if disp_sl is not None else "—" @@ -239,3 +327,67 @@ def apply_order_price_display_fields( format_price_fn(symbol, disp_tp) if disp_tp is not None else "—" ) return payload + + +def enrich_active_monitor_tpsl_json( + row: Any, + stop_loss: Any, + take_profit: Any, + exchange_tpsl: Any, + *, + position_row: Any = None, + exchange_notional: Any = None, + contracts: Any = None, + contract_size: float = 1.0, + mark_price: Any = None, + calc_rr_ratio_fn: Callable[..., Optional[float]], + format_price_fn: Optional[Callable[[Any, Any], str]] = None, + symbol: Any = None, + funds_decimals: int = 2, +) -> dict[str, Any]: + """place_tpsl 响应:展示用 TP/SL、最新风险、当前盈亏比。""" + def _row_val(key: str, default=None): + try: + if hasattr(row, "keys") and key in row.keys(): + return row[key] + except Exception: + pass + if isinstance(row, dict): + return row.get(key, default) + return default + + direction = _row_val("direction") or "long" + entry = _row_val("trigger_price") + init_sl = _row_val("initial_stop_loss") + margin = _row_val("margin_capital") + leverage = _row_val("leverage") + if position_row is not None: + from lib.hub.hub_position_metrics import position_contracts + + live_c = position_contracts(position_row) + if abs(live_c) >= 1e-12: + contracts = abs(live_c) + payload: dict[str, Any] = { + "stop_loss": stop_loss, + "take_profit": take_profit, + } + apply_order_price_display_fields( + payload, + direction=direction, + entry_price=entry, + initial_stop_loss=init_sl, + stop_loss=stop_loss, + take_profit=take_profit, + calc_rr_ratio_fn=calc_rr_ratio_fn, + exchange_tpsl=exchange_tpsl, + format_price_fn=format_price_fn, + symbol=symbol or _row_val("symbol"), + margin_capital=margin, + leverage=leverage, + exchange_notional=exchange_notional, + contracts=contracts, + contract_size=contract_size, + mark_price=mark_price, + funds_decimals=funds_decimals, + ) + return payload diff --git a/manual_trading_hub/hub.py b/manual_trading_hub/hub.py index 73afb58..a42d4dc 100644 --- a/manual_trading_hub/hub.py +++ b/manual_trading_hub/hub.py @@ -1724,6 +1724,8 @@ def _merge_flask_order_price_fields(hub_mon: dict | None, snap: dict | None) -> "stop_loss_display", "take_profit_display", "display_rr_ratio", + "latest_risk_amount", + "contracts", "exchange_initial_margin", "plan_margin", "time_close_enabled", diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index cf015ab..40d190b 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -788,6 +788,21 @@ hubHoldDurationTimer = setInterval(tickHubHoldDurations, 1000); } + function formatLatestRiskMeta(mo, trendPlan) { + const m = mo || {}; + const t = trendPlan || {}; + const v = + m.latest_risk_amount != null && m.latest_risk_amount !== "" + ? Number(m.latest_risk_amount) + : t.latest_risk_amount != null && t.latest_risk_amount !== "" + ? Number(t.latest_risk_amount) + : null; + if (v != null && Number.isFinite(v)) { + return `最新风险: ${fmt(v, 2)}U`; + } + return null; + } + function formatMonitorRiskMeta(mo, trendPlan) { const m = mo || {}; const t = trendPlan || {}; @@ -2840,6 +2855,8 @@ meta.push(monitorOrderSourceHtml(mo, trendPlan)); const riskLine = formatMonitorRiskMeta(mo, trendPlan); if (riskLine) meta.push(riskLine); + const latestRiskLine = formatLatestRiskMeta(mo, trendPlan); + if (latestRiskLine) meta.push(latestRiskLine); if (trendPlan && trendPlan.id) { const zone = trendPlan.add_upper_display || @@ -2858,6 +2875,8 @@ else meta.push("风格: —"); const riskLine = formatMonitorRiskMeta(mo, trendPlan); if (riskLine) meta.push(riskLine); + const latestRiskLine = formatLatestRiskMeta(mo, trendPlan); + if (latestRiskLine) meta.push(latestRiskLine); const beOn = mo.breakeven_enabled === 1 || mo.breakeven_enabled === true; meta.push( `移动保本:${beOn ? "开" : "关"}` diff --git a/tests/test_order_monitor_display_lib.py b/tests/test_order_monitor_display_lib.py index 67c828d..90d96e6 100644 --- a/tests/test_order_monitor_display_lib.py +++ b/tests/test_order_monitor_display_lib.py @@ -1,5 +1,7 @@ from lib.trade.order_monitor_display_lib import ( apply_order_price_display_fields, + calc_latest_risk_amount, + calc_risk_fraction, is_sl_breakeven_secured, monitor_open_stop_loss, order_monitor_tpsl_needs_sync, @@ -95,8 +97,27 @@ def test_apply_order_price_display_fields_live_sl(): exchange_tpsl={"sl": {"trigger_price": 1661}, "tp": {"trigger_price": 1647.65}}, format_price_fn=lambda _s, v: f"{v:.2f}", symbol="ETH/USDT:USDT", + margin_capital=100, + leverage=10, + exchange_notional=1000, ) assert payload["stop_loss"] == 1661 assert payload["stop_loss_display"] == "1661.00" assert payload["sl_breakeven_secured"] is True assert payload["rr_ratio"] is not None + assert payload["latest_risk_amount"] is not None + assert payload["latest_risk_amount"] >= 0 + + +def test_calc_latest_risk_amount_long(): + rf = calc_risk_fraction("long", 100, 95) + assert rf is not None and abs(rf - 0.05) < 1e-9 + risk = calc_latest_risk_amount( + "long", 100, 95, exchange_notional=1000, funds_decimals=2 + ) + assert risk == 50.0 + + +def test_calc_latest_risk_amount_profit_side_stop(): + risk = calc_latest_risk_amount("long", 100, 101, exchange_notional=1000) + assert risk == 0.0