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