Add trailing BE to SL/TP dialog and speed up position refresh.

Use modal for monitor upsert with trailing checkbox, refresh CTP tick every second, and push full snapshot after orders.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-29 09:32:56 +08:00
parent d366344b0f
commit fd2dba22fd
4 changed files with 144 additions and 21 deletions
+93 -14
View File
@@ -1061,16 +1061,18 @@
var dirBadge = row.direction_label || (row.direction === 'long' ? '做多' : '做空');
var openT = (row.open_time || '').replace('T', ' ').slice(0, 16);
var closeAllowed = row.close_allowed !== false && isTradingSession;
var slTpBtn = (!row.stop_loss && !row.take_profit && row.can_close) ?
var slTpBtn = (!row.stop_loss && !row.take_profit && !row.trailing_be && row.can_close) ?
'<button type="button" class="pos-dismiss-btn pos-sl-btn" data-sl-tp="' +
encodeURIComponent(JSON.stringify({
symbol_code: row.symbol_code, direction: row.direction,
lots: row.lots, entry_price: row.entry_price, monitor_id: row.monitor_id || null
lots: row.lots, entry_price: row.entry_price, monitor_id: row.monitor_id || null,
trailing_be: !!row.trailing_be
})) + '">设置止盈止损</button>' : '';
var editPayload = encodeURIComponent(JSON.stringify({
symbol_code: row.symbol_code, direction: row.direction,
lots: row.lots, entry_price: row.entry_price, monitor_id: row.monitor_id || null,
stop_loss: row.stop_loss, take_profit: row.take_profit
stop_loss: row.stop_loss, take_profit: row.take_profit,
trailing_be: !!row.trailing_be
}));
var entrustBtn = row.can_close ?
'<button type="button" class="pos-order-btn pos-entrust-btn" data-edit-sl-tp="' + editPayload + '">委托</button>' : '';
@@ -1172,16 +1174,66 @@
});
}
function promptStopTakeProfit(payload, btn, btnLabel) {
btnLabel = btnLabel || '设置止盈止损';
var slDefault = payload.stop_loss != null && payload.stop_loss !== '' ? String(payload.stop_loss) : '';
var tpDefault = payload.take_profit != null && payload.take_profit !== '' ? String(payload.take_profit) : '';
var slRaw = prompt('止损价(可留空)', slDefault);
if (slRaw === null) return;
var tpRaw = prompt('止盈价(可留空)', tpDefault);
if (tpRaw === null) return;
var sl = slRaw.trim() ? parseFloat(slRaw) : null;
var tp = tpRaw.trim() ? parseFloat(tpRaw) : null;
var slTpModalState = { payload: null, btn: null, btnLabel: '设置止盈止损' };
function syncSlTpModalTrailingUi() {
var trailingEl = document.getElementById('sl-tp-modal-trailing');
var tpWrap = document.getElementById('sl-tp-modal-tp-wrap');
var hint = document.getElementById('sl-tp-modal-trailing-hint');
var on = !!(trailingEl && trailingEl.checked);
if (tpWrap) tpWrap.hidden = on;
if (hint) hint.hidden = !on;
if (on) {
var tpInput = document.getElementById('sl-tp-modal-tp');
if (tpInput) tpInput.value = '';
}
}
function closeSlTpModal() {
var mask = document.getElementById('sl-tp-modal');
if (mask) mask.classList.remove('show');
slTpModalState.payload = null;
slTpModalState.btn = null;
}
function openSlTpModal(payload, btn, btnLabel) {
var mask = document.getElementById('sl-tp-modal');
var title = document.getElementById('sl-tp-modal-title');
var slInput = document.getElementById('sl-tp-modal-sl');
var tpInput = document.getElementById('sl-tp-modal-tp');
var trailingEl = document.getElementById('sl-tp-modal-trailing');
if (!mask || !slInput) return;
slTpModalState.payload = payload;
slTpModalState.btn = btn || null;
slTpModalState.btnLabel = btnLabel || '设置止盈止损';
if (title) title.textContent = slTpModalState.btnLabel;
slInput.value = payload.stop_loss != null && payload.stop_loss !== '' ? String(payload.stop_loss) : '';
if (tpInput) {
tpInput.value = payload.take_profit != null && payload.take_profit !== '' ? String(payload.take_profit) : '';
}
if (trailingEl) trailingEl.checked = !!payload.trailing_be;
syncSlTpModalTrailingUi();
mask.classList.add('show');
slInput.focus();
}
function saveSlTpModal() {
var payload = slTpModalState.payload;
if (!payload) return;
var btn = slTpModalState.btn;
var btnLabel = slTpModalState.btnLabel;
var slInput = document.getElementById('sl-tp-modal-sl');
var tpInput = document.getElementById('sl-tp-modal-tp');
var trailingEl = document.getElementById('sl-tp-modal-trailing');
var trailingOn = !!(trailingEl && trailingEl.checked);
var slRaw = slInput && slInput.value ? slInput.value.trim() : '';
var tpRaw = trailingOn ? '' : (tpInput && tpInput.value ? tpInput.value.trim() : '');
var sl = slRaw ? parseFloat(slRaw) : null;
var tp = tpRaw ? parseFloat(tpRaw) : null;
if (trailingOn && (sl == null || isNaN(sl))) {
alert('移动保本须填写止损价');
return;
}
if (sl == null && tp == null) {
alert('请至少填写止损或止盈');
return;
@@ -1190,6 +1242,8 @@
btn.disabled = true;
btn.textContent = '保存中…';
}
var saveBtn = document.getElementById('sl-tp-modal-save');
if (saveBtn) saveBtn.disabled = true;
fetch('/api/trading/monitor/upsert', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -1201,7 +1255,8 @@
entry_price: payload.entry_price,
monitor_id: payload.monitor_id || null,
stop_loss: sl,
take_profit: tp
take_profit: tp,
trailing_be: trailingOn
})
})
.then(function (r) {
@@ -1214,6 +1269,7 @@
})
.then(function (d) {
if (!d.ok) throw new Error(d.error || '保存失败');
closeSlTpModal();
pollPositions();
})
.catch(function (e) {
@@ -1224,9 +1280,31 @@
btn.disabled = false;
btn.textContent = btnLabel;
}
})
.finally(function () {
if (saveBtn) saveBtn.disabled = false;
});
}
function bindSlTpModal() {
var mask = document.getElementById('sl-tp-modal');
var trailingEl = document.getElementById('sl-tp-modal-trailing');
var cancelBtn = document.getElementById('sl-tp-modal-cancel');
var saveBtn = document.getElementById('sl-tp-modal-save');
if (trailingEl) trailingEl.addEventListener('change', syncSlTpModalTrailingUi);
if (cancelBtn) cancelBtn.addEventListener('click', closeSlTpModal);
if (saveBtn) saveBtn.addEventListener('click', saveSlTpModal);
if (mask) {
mask.addEventListener('click', function (e) {
if (e.target === mask) closeSlTpModal();
});
}
}
function promptStopTakeProfit(payload, btn, btnLabel) {
openSlTpModal(payload, btn, btnLabel || '设置止盈止损');
}
function bindSlTpButtons(root) {
if (!root) return;
root.querySelectorAll('[data-sl-tp]').forEach(function (btn) {
@@ -1754,6 +1832,7 @@
}
pollPositions();
connectPositionStream();
bindSlTpModal();
initCtpOnLoad();
connectRecommendStream();
initRecommendSortControls();