feat: 修改委托后展示最新风险,四所持仓卡增加张数

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-02 22:08:27 +08:00
parent 9d9d0af31e
commit 687a34474d
14 changed files with 505 additions and 5 deletions
+34
View File
@@ -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,
}
)
+35 -1
View File
@@ -462,6 +462,7 @@
<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">风格: {{ o.trade_style or 'trend' }}</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 o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
{% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %}
</span>
@@ -484,6 +485,10 @@
<span class="pos-label">盈亏比</span>
<span class="pos-value" id="order-rr-{{ o.id }}">{% if o.rr_ratio is not none %}{{ '%g'|format(o.rr_ratio) }}:1{% else %}-:1{% endif %}</span>
</div>
<div class="pos-cell">
<span class="pos-label">张数</span>
<span class="pos-value" id="order-contracts-{{ o.id }}">{% if o.order_amount is not none %}{{ '%g'|format(o.order_amount) }}{% else %}—{% endif %}</span>
</div>
<div class="pos-cell">
<span class="pos-label">标记价</span>
<span class="pos-value" id="order-price-{{ o.id }}">-</span>
@@ -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);
+34
View File
@@ -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,
}
)
+35 -1
View File
@@ -429,6 +429,7 @@
<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">风格: {{ o.trade_style or 'trend' }}</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 o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
{% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %}
</span>
@@ -451,6 +452,10 @@
<span class="pos-label">盈亏比</span>
<span class="pos-value" id="order-rr-{{ o.id }}">{% if o.rr_ratio is not none %}{{ '%g'|format(o.rr_ratio) }}:1{% else %}-:1{% endif %}</span>
</div>
<div class="pos-cell">
<span class="pos-label">张数</span>
<span class="pos-value" id="order-contracts-{{ o.id }}">{% if o.order_amount is not none %}{{ '%g'|format(o.order_amount) }}{% else %}—{% endif %}</span>
</div>
<div class="pos-cell">
<span class="pos-label">标记价</span>
<span class="pos-value" id="order-price-{{ o.id }}">-</span>
@@ -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);
+34
View File
@@ -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,
}
)
+35 -1
View File
@@ -429,6 +429,7 @@
<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">风格: {{ o.trade_style or 'trend' }}</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 o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
{% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %}
</span>
@@ -451,6 +452,10 @@
<span class="pos-label">盈亏比</span>
<span class="pos-value" id="order-rr-{{ o.id }}">{% if o.rr_ratio is not none %}{{ '%g'|format(o.rr_ratio) }}:1{% else %}-:1{% endif %}</span>
</div>
<div class="pos-cell">
<span class="pos-label">张数</span>
<span class="pos-value" id="order-contracts-{{ o.id }}">{% if o.order_amount is not none %}{{ '%g'|format(o.order_amount) }}{% else %}—{% endif %}</span>
</div>
<div class="pos-cell">
<span class="pos-label">标记价</span>
<span class="pos-value" id="order-price-{{ o.id }}">-</span>
@@ -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);
+34
View File
@@ -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,
}
)
+35 -1
View File
@@ -458,6 +458,7 @@
<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">风格: {{ o.trade_style or 'trend' }}</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 o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
{% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %}
</span>
@@ -480,6 +481,10 @@
<span class="pos-label">盈亏比</span>
<span class="pos-value" id="order-rr-{{ o.id }}">{% if o.rr_ratio is not none %}{{ '%g'|format(o.rr_ratio) }}:1{% else %}-:1{% endif %}</span>
</div>
<div class="pos-cell">
<span class="pos-label">张数</span>
<span class="pos-value" id="order-contracts-{{ o.id }}">{% if o.order_amount is not none %}{{ '%g'|format(o.order_amount) }}{% else %}—{% endif %}</span>
</div>
<div class="pos-cell">
<span class="pos-label">标记价</span>
<span class="pos-value" id="order-price-{{ o.id }}">-</span>
@@ -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);
+30 -1
View File
@@ -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);
@@ -109,6 +109,7 @@
<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">风格: {{ o.trade_style or 'trend' }}</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 o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
{% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %}
</span>
@@ -131,6 +132,10 @@
<span class="pos-label">盈亏比</span>
<span class="pos-value" id="order-rr-{{ o.id }}">{% if o.rr_ratio is not none %}{{ '%g'|format(o.rr_ratio) }}:1{% else %}-:1{% endif %}</span>
</div>
<div class="pos-cell">
<span class="pos-label">张数</span>
<span class="pos-value" id="order-contracts-{{ o.id }}">{% if o.order_amount is not none %}{{ '%g'|format(o.order_amount) }}{% else %}—{% endif %}</span>
</div>
<div class="pos-cell">
<span class="pos-label">标记价</span>
<span class="pos-value" id="order-price-{{ o.id }}">-</span>
+152
View File
@@ -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
+2
View File
@@ -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",
+19
View File
@@ -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(
`<span class="${beOn ? "pos-meta-on" : "pos-meta-off"}">移动保本:${beOn ? "开" : "关"}</span>`
+21
View File
@@ -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