diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py
index 2d26f1f..ed6adec 100644
--- a/crypto_monitor_binance/app.py
+++ b/crypto_monitor_binance/app.py
@@ -7024,6 +7024,9 @@ def api_price_snapshot():
symbol=r["symbol"],
)
apply_time_close_to_payload(payload, r)
+ payload["opened_at"] = r["opened_at"] if "opened_at" in r.keys() else None
+ open_ms = r["opened_at_ms"] if "opened_at_ms" in r.keys() else None
+ payload["opened_at_ms"] = int(open_ms) if open_ms not in (None, "") else None
new_sl, new_tp, changed = order_monitor_tpsl_needs_sync(
r["stop_loss"], r["take_profit"], exchange_tpsl
)
diff --git a/crypto_monitor_binance/templates/index.html b/crypto_monitor_binance/templates/index.html
index 4f642d1..b2dca61 100644
--- a/crypto_monitor_binance/templates/index.html
+++ b/crypto_monitor_binance/templates/index.html
@@ -467,6 +467,8 @@
计划基数: {{ funds_fmt(o.margin_capital) if o.margin_capital is not none else '-' }}U
杠杆: {{ o.leverage or '-' }}x
仓位占比: {{ o.position_ratio if o.position_ratio is not none else '-' }}%
+ 开仓时间: {{ (o.opened_at or '-')[:16] }}
+ 持仓时长: —
交易所止盈止损
@@ -2194,10 +2196,41 @@ function refreshPriceSnapshotConditional(){
paintExchangeTpslRow(o.id, o.exchange_tpsl || {});
paintPlanTpslDisplay(o.id, o);
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
+ const holdEl = document.getElementById(`order-hold-duration-${o.id}`);
+ if(holdEl && o.opened_at_ms != null && o.opened_at_ms !== ""){
+ holdEl.setAttribute("data-order-opened-ms", String(o.opened_at_ms));
+ }
});
+ tickOrderHoldDurations();
}
}).catch(()=>{});
}
+function formatLiveHoldDurationFromMs(openedMs, nowMs){
+ if(openedMs == null || openedMs === "" || !Number.isFinite(Number(openedMs))) return "—";
+ const ms = Number(openedMs);
+ const now = (nowMs != null) ? nowMs : Date.now();
+ let sec = Math.floor((now - ms) / 1000);
+ if(sec < 0) sec = 0;
+ if(sec <= 0) return "0分钟";
+ const d = Math.floor(sec / 86400); sec %= 86400;
+ const h = Math.floor(sec / 3600); sec %= 3600;
+ const m = Math.floor(sec / 60);
+ const parts = [];
+ if(d) parts.push(`${d}天`);
+ if(h) parts.push(`${h}小时`);
+ if(m || !parts.length) parts.push(`${m}分钟`);
+ return parts.join("");
+}
+function tickOrderHoldDurations(){
+ const now = Date.now();
+ document.querySelectorAll(".order-hold-duration[data-order-opened-ms]").forEach(el=>{
+ const ms = Number(el.getAttribute("data-order-opened-ms"));
+ if(!Number.isFinite(ms) || ms <= 0) return;
+ el.textContent = formatLiveHoldDurationFromMs(ms, now);
+ });
+}
+setInterval(tickOrderHoldDurations, 1000);
+tickOrderHoldDurations();
setInterval(refreshPriceSnapshotConditional, {{ price_refresh_seconds * 1000 }});