/* Copyright (c) 2025-2026 马建军. All rights reserved. * 专有软件 — 未经授权禁止复制、传播、转售。 * 详见 LICENSE.zh-CN.txt */ (function () { var trendPayload = null; function jsonPost(url, body) { return fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) }).then(function (r) { return r.json(); }); } function formData(form) { var fd = new FormData(form); var o = {}; fd.forEach(function (v, k) { o[k] = v; }); return o; } function esc(s) { return String(s == null ? '' : s) .replace(/&/g, '&') .replace(//g, '>'); } function showPreview(el, content, ok, isHtml) { if (!el) return; if (!content) { el.hidden = true; el.textContent = ''; el.innerHTML = ''; return; } el.hidden = false; el.style.color = ok === false ? 'var(--loss)' : ''; if (isHtml) { el.innerHTML = content; } else { el.innerHTML = ''; el.textContent = content; } } function fmtNum(v) { if (v == null || v === '') return '—'; return String(v); } function renderTrendPlanHtml(plan) { if (!plan) return ''; var summary = plan.summary_line || ( (plan.symbol_name || plan.symbol || '') + ' ' + (plan.direction_label || '') + ' ' + (plan.period_label || '') ); var detail = plan.detail_line || ''; var rows = plan.preview_rows || []; var html = '
' + esc(summary) + '
'; if (detail) { html += '
' + esc(detail) + '
'; } if (rows.length) { html += '' + '' + '' + ''; rows.forEach(function (row) { html += '' + '' + '' + '' + '' + '' + ''; }); html += '
档位触发/参考价手数加仓后均价止盈盈利(元)止损(元)盈亏比
' + esc(row.level) + '' + fmtNum(row.price) + '' + fmtNum(row.lots) + '' + fmtNum(row.avg_after) + '' + fmtNum(row.profit_at_tp) + '' + fmtNum(row.loss_at_sl) + '' + fmtNum(row.rr_ratio) + '
'; } else { html += '
目标手数 ' + fmtNum(plan.target_lots) + ' · 首仓 ' + fmtNum(plan.first_lots) + ' 手
'; } return html; } function formatRoll(preview) { if (!preview) return ''; var lines = []; if (preview.add_mode_label) lines.push('方式:' + preview.add_mode_label); if (preview.add_lots != null) lines.push('加仓手数:' + preview.add_lots); if (preview.qty_after != null) lines.push('合计手数:' + preview.qty_after); if (preview.avg_entry_after != null) lines.push('加仓后均价:' + preview.avg_entry_after); if (preview.new_stop_loss != null) lines.push('新止损:' + preview.new_stop_loss); if (preview.trigger_price != null) lines.push('触发价:' + preview.trigger_price); if (preview.risk_budget != null) lines.push('风险预算(固定金额):' + preview.risk_budget + ' 元'); if (preview.loss_at_sl != null) lines.push('打到止损亏损:' + preview.loss_at_sl + ' 元'); if (preview.reward_at_tp != null) lines.push('止盈盈利:' + preview.reward_at_tp + ' 元'); if (preview.margin_usage_pct != null) { lines.push('滚仓后保证金占用:' + preview.margin_usage_pct + '%'); } if (preview.margin_cap_note) lines.push(preview.margin_cap_note); if (preview.is_pending) lines.push('(提交后为程序监控,触价后自动市价加仓)'); return lines.length ? lines.join('\n') : JSON.stringify(preview, null, 2); } function syncRollModeUi() { var modeEl = document.getElementById('roll-add-mode'); var breakEl = document.getElementById('roll-break-price'); var execHint = document.getElementById('roll-exec-hint'); var btnExec = document.getElementById('btn-roll-exec'); if (!modeEl) return; var mode = modeEl.value || 'market'; var isBreak = mode === 'breakout'; if (breakEl) { breakEl.hidden = !isBreak; breakEl.required = isBreak; } if (execHint) execHint.hidden = false; if (btnExec) { btnExec.textContent = mode === 'market' ? '执行滚仓' : '提交监控'; } } function syncRollRiskHint() { /* 固定金额由服务端渲染在 #roll-risk-budget,切换监控单无需变更 */ } var rollCountdownTimer = null; function startRollCountdown(btn, payload) { var sec = 10; btn.disabled = true; function tick() { btn.textContent = '确认执行 (' + sec + 's)'; if (sec <= 0) { clearInterval(rollCountdownTimer); rollCountdownTimer = null; jsonPost('/api/strategy/roll/execute', payload).then(function (d) { if (!d.ok) { alert(d.error || d.message || '失败'); btn.disabled = false; btn.textContent = '执行滚仓'; return; } alert(d.message || '已提交'); location.reload(); }).catch(function () { btn.disabled = false; btn.textContent = '执行滚仓'; }); return; } sec -= 1; } tick(); rollCountdownTimer = setInterval(tick, 1000); } var trendForm = document.getElementById('trend-form'); var btnPreview = document.getElementById('btn-trend-preview'); var btnExec = document.getElementById('btn-trend-exec'); var previewEl = document.getElementById('trend-preview'); if (btnPreview && trendForm) { btnPreview.addEventListener('click', function () { btnPreview.disabled = true; jsonPost('/api/strategy/trend/preview', formData(trendForm)).then(function (d) { if (!d.ok) { showPreview(previewEl, d.error || '预览失败', false, false); btnExec.hidden = true; return; } trendPayload = formData(trendForm); showPreview(previewEl, renderTrendPlanHtml(d.plan), true, true); btnExec.hidden = false; }).finally(function () { btnPreview.disabled = false; }); }); } if (btnExec) { btnExec.addEventListener('click', function () { if (!trendPayload) return; btnExec.disabled = true; btnExec.textContent = '执行中…'; jsonPost('/api/strategy/trend/execute', trendPayload).then(function (d) { if (!d.ok) { alert(d.error); return; } location.reload(); }).finally(function () { btnExec.disabled = false; btnExec.textContent = '确认执行首仓'; }); }); } var rollForm = document.getElementById('roll-form'); var btnRollP = document.getElementById('btn-roll-preview'); var btnRollE = document.getElementById('btn-roll-exec'); var rollPrev = document.getElementById('roll-preview'); var rollPayload = null; var rollMonitorSel = document.getElementById('roll-monitor-select'); var rollModeSel = document.getElementById('roll-add-mode'); if (rollModeSel) rollModeSel.addEventListener('change', syncRollModeUi); if (rollMonitorSel) rollMonitorSel.addEventListener('change', syncRollRiskHint); syncRollModeUi(); syncRollRiskHint(); if (btnRollP && rollForm) { btnRollP.addEventListener('click', function () { btnRollP.disabled = true; rollPayload = formData(rollForm); jsonPost('/api/strategy/roll/preview', rollPayload).then(function (d) { if (!d.ok) { showPreview(rollPrev, d.error, false, false); btnRollE.hidden = true; return; } showPreview(rollPrev, formatRoll(d.preview), true, false); btnRollE.hidden = false; }).finally(function () { btnRollP.disabled = false; }); }); } if (btnRollE && rollForm) { btnRollE.addEventListener('click', function () { var payload = rollPayload || formData(rollForm); var mode = (payload.add_mode || 'market'); if (mode === 'market') { if (!confirm('确认执行市价滚仓?')) return; startRollCountdown(btnRollE, payload); return; } btnRollE.disabled = true; btnRollE.textContent = '提交中…'; jsonPost('/api/strategy/roll/execute', payload).then(function (d) { if (!d.ok) { alert(d.error || '失败'); return; } alert(d.message || '已提交监控'); location.reload(); }).finally(function () { btnRollE.disabled = false; syncRollModeUi(); }); }); } document.querySelectorAll('.roll-cancel-leg').forEach(function (btn) { btn.addEventListener('click', function () { var legId = btn.getAttribute('data-leg-id'); if (!legId || !confirm('删除该监控腿?')) return; jsonPost('/api/strategy/roll/cancel/' + legId, {}).then(function (d) { if (!d.ok) { alert(d.error); return; } location.reload(); }); }); }); var btnStop = document.getElementById('btn-trend-stop'); if (btnStop) { btnStop.addEventListener('click', function () { var pid = document.querySelector('#trend-stop-form input[name=plan_id]'); jsonPost('/api/strategy/trend/stop', { plan_id: pid ? pid.value : 0 }).then(function (d) { if (!d.ok) { alert(d.error); return; } location.reload(); }); }); } })();