feat(binance): recover live position when local monitor was lost
Detect exchange positions without active order_monitors, show a recover banner on the trade page, and reactivate stopped monitors with optional TP/SL restore via API. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -402,6 +402,7 @@
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2 style="margin-bottom:8px">实时持仓</h2>
|
||||
<div id="orphan-position-recover" class="orphan-recover-banner" style="display:none;margin-bottom:10px;padding:10px 12px;background:#2a2210;border:1px solid #6b5420;border-radius:6px;font-size:.9rem;color:#e8d5a8"></div>
|
||||
<div class="panel-scroll pos-list pos-list-live">
|
||||
{% for o in order %}
|
||||
<div class="pos-card" id="order-row-{{ o.id }}"
|
||||
@@ -1866,6 +1867,46 @@ function paintPriceTrend(el, key, value){
|
||||
lastPriceMap[key] = value;
|
||||
}
|
||||
|
||||
function renderOrphanRecoverBanner(orphans){
|
||||
const el = document.getElementById("orphan-position-recover");
|
||||
if(!el) return;
|
||||
const liveCards = document.querySelectorAll(".pos-list-live .pos-card");
|
||||
if(liveCards.length > 0 || !orphans || !orphans.length){
|
||||
el.style.display = "none";
|
||||
el.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
const o = orphans[0];
|
||||
const dir = o.direction === "short" ? "空" : "多";
|
||||
const mid = o.recoverable_monitor_id;
|
||||
let html = `检测到交易所仍有 <strong>${o.symbol}</strong> ${dir}仓,但本地监控已中断(误同步时可能无交易记录)。`;
|
||||
if(mid){
|
||||
const tpslHint = (o.plan_stop_loss && o.plan_take_profit) ? "并挂止盈止损" : "";
|
||||
html += ` <button type="button" class="pos-entrust-btn" onclick="recoverLivePosition(${mid})">恢复监控${tpslHint}</button>`;
|
||||
} else {
|
||||
html += " 未找到可恢复的监控记录,需在服务器数据库处理。";
|
||||
}
|
||||
el.innerHTML = html;
|
||||
el.style.display = "block";
|
||||
}
|
||||
|
||||
async function recoverLivePosition(monitorId){
|
||||
const withTpsl = confirm("确认恢复本地实时监控?若原计划有止盈止损,将尝试重新挂到交易所。");
|
||||
if(!withTpsl) return;
|
||||
try{
|
||||
const res = await fetch("/api/recover_live_position", {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify({monitor_id: monitorId, place_tpsl: true})
|
||||
});
|
||||
const data = await res.json();
|
||||
alert(data.msg || (data.ok ? "已恢复" : "失败"));
|
||||
if(data.ok) location.reload();
|
||||
}catch(e){
|
||||
alert("恢复失败:" + e);
|
||||
}
|
||||
}
|
||||
|
||||
function refreshPriceSnapshot(){
|
||||
fetch("/api/price_snapshot").then(r=>r.json()).then(data=>{
|
||||
const updatedEl = document.getElementById("price-last-updated");
|
||||
@@ -1941,6 +1982,7 @@ function refreshPriceSnapshot(){
|
||||
paintPlanTpslDisplay(o.id, o);
|
||||
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
||||
});
|
||||
renderOrphanRecoverBanner(data.orphan_live_positions);
|
||||
}).catch(()=>{});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user