fix(gate-bot): PnL colors, sync exchange TP/SL to plan display
Color floating PnL on position cards, mirror exchange stop/take prices in the grid and DB, and purge false external-close records on monitor relink.
This commit is contained in:
@@ -54,6 +54,7 @@ from form_submit_lib import check_duplicate_submit, submit_scope_add_key, submit
|
|||||||
from order_monitor_display_lib import (
|
from order_monitor_display_lib import (
|
||||||
apply_order_price_display_fields,
|
apply_order_price_display_fields,
|
||||||
enrich_order_display_fields,
|
enrich_order_display_fields,
|
||||||
|
tpsl_slot_trigger_price,
|
||||||
)
|
)
|
||||||
from journal_chart_lib import (
|
from journal_chart_lib import (
|
||||||
JOURNAL_CHART_DEFAULT_LIMIT,
|
JOURNAL_CHART_DEFAULT_LIMIT,
|
||||||
@@ -5799,7 +5800,6 @@ def api_price_snapshot():
|
|||||||
order_rows = conn.execute(
|
order_rows = conn.execute(
|
||||||
"SELECT id,symbol,exchange_symbol,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,margin_capital,leverage FROM order_monitors WHERE status='active'"
|
"SELECT id,symbol,exchange_symbol,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,margin_capital,leverage FROM order_monitors WHERE status='active'"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
conn.close()
|
|
||||||
|
|
||||||
symbol_set = set()
|
symbol_set = set()
|
||||||
for r in key_rows:
|
for r in key_rows:
|
||||||
@@ -5927,17 +5927,51 @@ def api_price_snapshot():
|
|||||||
except Exception:
|
except Exception:
|
||||||
exchange_tpsl = {"sl": None, "tp": None}
|
exchange_tpsl = {"sl": None, "tp": None}
|
||||||
payload["exchange_tpsl"] = exchange_tpsl
|
payload["exchange_tpsl"] = exchange_tpsl
|
||||||
|
live_sl = tpsl_slot_trigger_price(exchange_tpsl.get("sl"))
|
||||||
|
live_tp = tpsl_slot_trigger_price(exchange_tpsl.get("tp"))
|
||||||
|
disp_sl = live_sl if live_sl is not None else r["stop_loss"]
|
||||||
|
disp_tp = live_tp if live_tp is not None else r["take_profit"]
|
||||||
|
sym = r["symbol"]
|
||||||
|
payload["stop_loss_raw"] = disp_sl
|
||||||
|
payload["take_profit_raw"] = disp_tp
|
||||||
|
payload["stop_loss_display"] = (
|
||||||
|
format_price_for_symbol(sym, disp_sl) if disp_sl not in (None, "") else "—"
|
||||||
|
)
|
||||||
|
payload["take_profit_display"] = (
|
||||||
|
format_price_for_symbol(sym, disp_tp) if disp_tp not in (None, "") else "—"
|
||||||
|
)
|
||||||
apply_order_price_display_fields(
|
apply_order_price_display_fields(
|
||||||
payload,
|
payload,
|
||||||
direction=r["direction"],
|
direction=r["direction"],
|
||||||
entry_price=entry,
|
entry_price=entry,
|
||||||
initial_stop_loss=r["initial_stop_loss"],
|
initial_stop_loss=r["initial_stop_loss"],
|
||||||
stop_loss=r["stop_loss"],
|
stop_loss=disp_sl,
|
||||||
take_profit=r["take_profit"],
|
take_profit=disp_tp,
|
||||||
calc_rr_ratio_fn=calc_rr_ratio,
|
calc_rr_ratio_fn=calc_rr_ratio,
|
||||||
exchange_tpsl=exchange_tpsl,
|
exchange_tpsl=exchange_tpsl,
|
||||||
)
|
)
|
||||||
order_prices.append(payload)
|
order_prices.append(payload)
|
||||||
|
if live_sl is not None or live_tp is not None:
|
||||||
|
try:
|
||||||
|
cur_sl = float(r["stop_loss"] or 0)
|
||||||
|
cur_tp = float(r["take_profit"] or 0)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
cur_sl, cur_tp = 0.0, 0.0
|
||||||
|
new_sl = live_sl if live_sl is not None else cur_sl
|
||||||
|
new_tp = live_tp if live_tp is not None else cur_tp
|
||||||
|
if (live_sl is not None and abs(new_sl - cur_sl) > 1e-12) or (
|
||||||
|
live_tp is not None and abs(new_tp - cur_tp) > 1e-12
|
||||||
|
):
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE order_monitors SET stop_loss=?, take_profit=? WHERE id=?",
|
||||||
|
(new_sl, new_tp, int(r["id"])),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn.commit()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
conn.close()
|
||||||
|
|
||||||
from hub_position_metrics import build_position_marks_list
|
from hub_position_metrics import build_position_marks_list
|
||||||
|
|
||||||
@@ -6113,11 +6147,19 @@ def api_order_relink_orphan():
|
|||||||
"msg": "未找到可恢复的历史监控记录,请在中控核对持仓或联系管理员",
|
"msg": "未找到可恢复的历史监控记录,请在中控核对持仓或联系管理员",
|
||||||
}
|
}
|
||||||
), 404
|
), 404
|
||||||
|
opened_at = get_opened_at_value(row)
|
||||||
|
purged = conn.execute(
|
||||||
|
"DELETE FROM trade_records WHERE symbol=? AND direction=? AND opened_at=? AND result LIKE ?",
|
||||||
|
(symbol, direction, opened_at, "%外部平仓%"),
|
||||||
|
).rowcount
|
||||||
conn.execute("UPDATE order_monitors SET status='active' WHERE id=?", (int(row["id"]),))
|
conn.execute("UPDATE order_monitors SET status='active' WHERE id=?", (int(row["id"]),))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
oid = int(row["id"])
|
oid = int(row["id"])
|
||||||
conn.close()
|
conn.close()
|
||||||
return jsonify({"ok": True, "msg": "已恢复本地监控", "order_id": oid})
|
msg = "已恢复本地监控"
|
||||||
|
if purged:
|
||||||
|
msg += f"(已清除 {purged} 条误记的外部平仓记录)"
|
||||||
|
return jsonify({"ok": True, "msg": msg, "order_id": oid, "purged_trade_records": purged})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/order_defaults")
|
@app.route("/api/order_defaults")
|
||||||
|
|||||||
@@ -244,6 +244,9 @@
|
|||||||
.pos-value.price-up{color:#4cd97f}
|
.pos-value.price-up{color:#4cd97f}
|
||||||
.pos-value.price-down{color:#ff6666}
|
.pos-value.price-down{color:#ff6666}
|
||||||
.pos-value.price-flat{color:#e8ecf4}
|
.pos-value.price-flat{color:#e8ecf4}
|
||||||
|
.pos-value.pnl-profit{color:#4cd97f;font-weight:700}
|
||||||
|
.pos-value.pnl-loss{color:#ff6666;font-weight:700}
|
||||||
|
.pos-value.pnl-neutral{color:#cfd3ef;font-weight:600}
|
||||||
.pos-footer{display:flex;flex-wrap:wrap;gap:14px 18px;font-size:.75rem;color:#6d7689}
|
.pos-footer{display:flex;flex-wrap:wrap;gap:14px 18px;font-size:.75rem;color:#6d7689}
|
||||||
.pos-empty{padding:18px;text-align:center;color:#8892b0;font-size:.85rem;background:#141923;border:1px dashed #2a3348;border-radius:10px}
|
.pos-empty{padding:18px;text-align:center;color:#8892b0;font-size:.85rem;background:#141923;border:1px dashed #2a3348;border-radius:10px}
|
||||||
.pos-card-orphan{border-color:#6a5528;background:#1a1810}
|
.pos-card-orphan{border-color:#6a5528;background:#1a1810}
|
||||||
@@ -478,19 +481,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="pos-cell">
|
<div class="pos-cell">
|
||||||
<span class="pos-label">止损</span>
|
<span class="pos-label">止损</span>
|
||||||
{% if o.stop_loss %}
|
<span class="pos-value" id="order-plan-sl-{{ o.id }}">{{ price_fmt(o.symbol, o.stop_loss) if o.stop_loss else '—' }}</span>
|
||||||
<span class="pos-value">{{ price_fmt(o.symbol, o.stop_loss) }}</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="pos-value pos-val-dash">—</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="pos-cell">
|
<div class="pos-cell">
|
||||||
<span class="pos-label">止盈</span>
|
<span class="pos-label">止盈</span>
|
||||||
{% if o.take_profit %}
|
<span class="pos-value" id="order-plan-tp-{{ o.id }}">{{ price_fmt(o.symbol, o.take_profit) if o.take_profit else '—' }}</span>
|
||||||
<span class="pos-value">{{ price_fmt(o.symbol, o.take_profit) }}</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="pos-value pos-val-dash">—</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="pos-cell">
|
<div class="pos-cell">
|
||||||
<span class="pos-label">盈亏比</span>
|
<span class="pos-label">盈亏比</span>
|
||||||
@@ -1647,6 +1642,24 @@ function paintExchangeTpslRow(orderId, tpsl){
|
|||||||
if(slBtn) slBtn.disabled = !(data.sl && data.sl.order_id);
|
if(slBtn) slBtn.disabled = !(data.sl && data.sl.order_id);
|
||||||
if(tpBtn) tpBtn.disabled = !(data.tp && data.tp.order_id);
|
if(tpBtn) tpBtn.disabled = !(data.tp && data.tp.order_id);
|
||||||
}
|
}
|
||||||
|
function paintPlanTpslDisplay(orderId, snap){
|
||||||
|
if(!snap) return;
|
||||||
|
const card = document.getElementById(`order-row-${orderId}`);
|
||||||
|
const slEl = document.getElementById(`order-plan-sl-${orderId}`);
|
||||||
|
const tpEl = document.getElementById(`order-plan-tp-${orderId}`);
|
||||||
|
const rrEl = document.getElementById(`order-rr-${orderId}`);
|
||||||
|
const slDisp = snap.stop_loss_display;
|
||||||
|
const tpDisp = snap.take_profit_display;
|
||||||
|
if(slEl && slDisp) slEl.innerText = slDisp;
|
||||||
|
if(tpEl && tpDisp) tpEl.innerText = tpDisp;
|
||||||
|
if(card){
|
||||||
|
if(snap.stop_loss_raw != null && snap.stop_loss_raw !== "") card.setAttribute('data-plan-sl', formatPriceForInput(snap.stop_loss_raw));
|
||||||
|
else if(slDisp) card.setAttribute('data-plan-sl', slDisp);
|
||||||
|
if(snap.take_profit_raw != null && snap.take_profit_raw !== "") card.setAttribute('data-plan-tp', formatPriceForInput(snap.take_profit_raw));
|
||||||
|
else if(tpDisp) card.setAttribute('data-plan-tp', tpDisp);
|
||||||
|
}
|
||||||
|
if(rrEl && typeof snap.rr_ratio !== "undefined") rrEl.innerText = formatRrRatio(snap.rr_ratio);
|
||||||
|
}
|
||||||
function toggleTpslModalMode(){
|
function toggleTpslModalMode(){
|
||||||
const mode = (document.getElementById('tpsl-modal-mode')||{}).value || 'price';
|
const mode = (document.getElementById('tpsl-modal-mode')||{}).value || 'price';
|
||||||
const pct = mode === 'pct';
|
const pct = mode === 'pct';
|
||||||
@@ -1696,6 +1709,13 @@ function submitTpslEntrust(){
|
|||||||
alert(data.msg || '已提交');
|
alert(data.msg || '已提交');
|
||||||
closeTpslEntrustModal();
|
closeTpslEntrustModal();
|
||||||
if(data.exchange_tpsl) paintExchangeTpslRow(orderId, data.exchange_tpsl);
|
if(data.exchange_tpsl) paintExchangeTpslRow(orderId, data.exchange_tpsl);
|
||||||
|
paintPlanTpslDisplay(orderId, {
|
||||||
|
stop_loss_raw: data.stop_loss,
|
||||||
|
take_profit_raw: data.take_profit,
|
||||||
|
stop_loss_display: data.stop_loss != null ? formatPriceForInput(data.stop_loss) : null,
|
||||||
|
take_profit_display: data.take_profit != null ? formatPriceForInput(data.take_profit) : null,
|
||||||
|
rr_ratio: data.planned_rr,
|
||||||
|
});
|
||||||
refreshPriceSnapshotConditional();
|
refreshPriceSnapshotConditional();
|
||||||
}).catch(()=>alert('委托请求失败'));
|
}).catch(()=>alert('委托请求失败'));
|
||||||
};
|
};
|
||||||
@@ -1839,14 +1859,14 @@ function refreshPriceSnapshotConditional(){
|
|||||||
if(pnlEl){
|
if(pnlEl){
|
||||||
pnlEl.innerText = `${formatSigned(o.float_pnl, 2)}U (${formatSigned(o.float_pct, 2)}%)`;
|
pnlEl.innerText = `${formatSigned(o.float_pnl, 2)}U (${formatSigned(o.float_pct, 2)}%)`;
|
||||||
pnlEl.classList.remove("price-up","price-down","price-flat","pnl-profit","pnl-loss","pnl-neutral");
|
pnlEl.classList.remove("price-up","price-down","price-flat","pnl-profit","pnl-loss","pnl-neutral");
|
||||||
if(Number(o.float_pnl) > 0) pnlEl.classList.add("price-up");
|
const fp = Number(o.float_pnl);
|
||||||
else if(Number(o.float_pnl) < 0) pnlEl.classList.add("price-down");
|
if(fp > 0) pnlEl.classList.add("pnl-profit");
|
||||||
else pnlEl.classList.add("price-flat");
|
else if(fp < 0) pnlEl.classList.add("pnl-loss");
|
||||||
|
else pnlEl.classList.add("pnl-neutral");
|
||||||
}
|
}
|
||||||
const rrEl = document.getElementById(`order-rr-${o.id}`);
|
|
||||||
if(rrEl) rrEl.innerText = formatRrRatio(o.rr_ratio);
|
|
||||||
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
||||||
paintExchangeTpslRow(o.id, o.exchange_tpsl || {});
|
paintExchangeTpslRow(o.id, o.exchange_tpsl || {});
|
||||||
|
paintPlanTpslDisplay(o.id, o);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).catch(()=>{});
|
}).catch(()=>{});
|
||||||
|
|||||||
Reference in New Issue
Block a user