feat: add timed position close (1h/2h/4h) for key levels and live orders
Program monitors open positions and market-closes at deadline; UI shows label and countdown on instance and hub boards. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -341,6 +341,17 @@ html[data-theme="light"] .pos-meta-item::after {
|
||||
color: #b8c8d8 !important;
|
||||
}
|
||||
|
||||
.pos-time-close-meta {
|
||||
color: #8fc8ff;
|
||||
}
|
||||
.pos-time-close-meta .pos-time-close-cd {
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.key-time-close-wrap.is-disabled select,
|
||||
.order-time-close-wrap.is-disabled select {
|
||||
opacity: 0.55;
|
||||
}
|
||||
html[data-theme="light"] .pos-meta-on {
|
||||
color: #006e9a !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 时间平仓:表单开关 + 持仓倒计时。
|
||||
*/
|
||||
(function (global) {
|
||||
"use strict";
|
||||
|
||||
function pad2(n) {
|
||||
return n < 10 ? "0" + n : String(n);
|
||||
}
|
||||
|
||||
function formatCountdown(sec) {
|
||||
const s = Math.max(0, parseInt(sec, 10) || 0);
|
||||
const h = Math.floor(s / 3600);
|
||||
const m = Math.floor((s % 3600) / 60);
|
||||
const r = s % 60;
|
||||
return pad2(h) + ":" + pad2(m) + ":" + pad2(r);
|
||||
}
|
||||
|
||||
function bindTimeCloseForm(checkboxId, selectId, wrapId) {
|
||||
const cb = document.getElementById(checkboxId);
|
||||
const sel = document.getElementById(selectId);
|
||||
const wrap = wrapId ? document.getElementById(wrapId) : null;
|
||||
if (!cb || !sel) return;
|
||||
function sync() {
|
||||
const on = !!cb.checked;
|
||||
sel.disabled = !on;
|
||||
if (wrap) wrap.classList.toggle("is-disabled", !on);
|
||||
}
|
||||
cb.addEventListener("change", sync);
|
||||
sync();
|
||||
}
|
||||
|
||||
function paintOrderTimeClose(order) {
|
||||
if (!order || order.id == null) return;
|
||||
const wrap = document.getElementById("order-time-close-wrap-" + order.id);
|
||||
const cd = document.getElementById("order-time-close-cd-" + order.id);
|
||||
if (!wrap || !cd) return;
|
||||
const enabled = !!(order.time_close_enabled || order.time_close_at_ms);
|
||||
if (!enabled) {
|
||||
wrap.style.display = "none";
|
||||
return;
|
||||
}
|
||||
wrap.style.display = "";
|
||||
const hours = order.time_close_hours;
|
||||
const label = order.time_close_label || (hours ? "时间平仓 " + hours + "h" : "时间平仓");
|
||||
const labelEl = wrap.querySelector(".pos-time-close-label");
|
||||
if (labelEl) labelEl.textContent = label;
|
||||
let rem =
|
||||
order.time_close_remaining_sec != null
|
||||
? Number(order.time_close_remaining_sec)
|
||||
: null;
|
||||
if ((rem == null || !Number.isFinite(rem)) && order.time_close_at_ms) {
|
||||
rem = Math.max(0, Math.floor((Number(order.time_close_at_ms) - Date.now()) / 1000));
|
||||
}
|
||||
cd.textContent = Number.isFinite(rem) ? formatCountdown(rem) : "--:--:--";
|
||||
wrap.dataset.closeAtMs = order.time_close_at_ms ? String(order.time_close_at_ms) : "";
|
||||
}
|
||||
|
||||
function tickLocalCountdowns() {
|
||||
document.querySelectorAll("[data-close-at-ms]").forEach(function (wrap) {
|
||||
const closeAtRaw = wrap.dataset.closeAtMs || wrap.getAttribute("data-close-at-ms") || "";
|
||||
const cd = wrap.querySelector(".pos-time-close-cd");
|
||||
if (!cd) return;
|
||||
const closeAt = Number(closeAtRaw);
|
||||
if (!closeAt) return;
|
||||
const rem = Math.max(0, Math.floor((closeAt - Date.now()) / 1000));
|
||||
cd.textContent = formatCountdown(rem);
|
||||
});
|
||||
}
|
||||
|
||||
function paintOrders(orders) {
|
||||
(orders || []).forEach(paintOrderTimeClose);
|
||||
}
|
||||
|
||||
function syncKeyTimeCloseVisibility(show) {
|
||||
const wrap = document.getElementById("key-time-close-wrap");
|
||||
if (!wrap) return;
|
||||
wrap.style.display = show ? "inline-flex" : "none";
|
||||
}
|
||||
|
||||
global.TimeCloseUI = {
|
||||
bindTimeCloseForm: bindTimeCloseForm,
|
||||
paintOrderTimeClose: paintOrderTimeClose,
|
||||
paintOrders: paintOrders,
|
||||
tickLocalCountdowns: tickLocalCountdowns,
|
||||
syncKeyTimeCloseVisibility: syncKeyTimeCloseVisibility,
|
||||
formatCountdown: formatCountdown,
|
||||
};
|
||||
|
||||
if (!global.__timeCloseCountdownTimer) {
|
||||
global.__timeCloseCountdownTimer = setInterval(tickLocalCountdowns, 1000);
|
||||
}
|
||||
})(typeof window !== "undefined" ? window : globalThis);
|
||||
Reference in New Issue
Block a user