840e88daad
Key monitors use 5m close triggers with WeChat alerts and box/convergence auto orders; add pending-order worker, structured WeChat notify, AI settings/messages, session clock, CTP margin sizing, and dual-layer position limits. Co-authored-by: Cursor <cursoragent@cursor.com>
184 lines
7.0 KiB
JavaScript
184 lines
7.0 KiB
JavaScript
/* 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, '<')
|
|
.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 = '<div class="trend-summary">' + esc(summary) + '</div>';
|
|
if (detail) {
|
|
html += '<div class="trend-detail">' + esc(detail) + '</div>';
|
|
}
|
|
if (rows.length) {
|
|
html += '<table class="strategy-preview-table"><thead><tr>' +
|
|
'<th>档位</th><th>触发/参考价</th><th>手数</th><th>加仓后均价</th>' +
|
|
'<th>止盈盈利(元)</th><th>止损(元)</th><th>盈亏比</th>' +
|
|
'</tr></thead><tbody>';
|
|
rows.forEach(function (row) {
|
|
html += '<tr><td>' + esc(row.level) + '</td>' +
|
|
'<td>' + fmtNum(row.price) + '</td>' +
|
|
'<td>' + fmtNum(row.lots) + '</td>' +
|
|
'<td>' + fmtNum(row.avg_after) + '</td>' +
|
|
'<td>' + fmtNum(row.profit_at_tp) + '</td>' +
|
|
'<td>' + fmtNum(row.loss_at_sl) + '</td>' +
|
|
'<td>' + fmtNum(row.rr_ratio) + '</td></tr>';
|
|
});
|
|
html += '</tbody></table>';
|
|
} else {
|
|
html += '<div class="trend-detail">目标手数 ' + fmtNum(plan.target_lots) +
|
|
' · 首仓 ' + fmtNum(plan.first_lots) + ' 手</div>';
|
|
}
|
|
return html;
|
|
}
|
|
|
|
function formatRoll(preview) {
|
|
if (!preview) return '';
|
|
var lines = [];
|
|
if (preview.add_lots != null) lines.push('加仓手数:' + preview.add_lots);
|
|
if (preview.new_stop_loss != null) lines.push('新止损:' + preview.new_stop_loss);
|
|
if (preview.total_lots != null) lines.push('合计手数:' + preview.total_lots);
|
|
if (preview.worst_loss != null) lines.push('最坏亏损:' + preview.worst_loss + ' 元');
|
|
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.message) lines.push(preview.message);
|
|
return lines.length ? lines.join('\n') : JSON.stringify(preview, null, 2);
|
|
}
|
|
|
|
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');
|
|
if (btnRollP && rollForm) {
|
|
btnRollP.addEventListener('click', function () {
|
|
btnRollP.disabled = true;
|
|
jsonPost('/api/strategy/roll/preview', formData(rollForm)).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 () {
|
|
btnRollE.disabled = true;
|
|
btnRollE.textContent = '执行中…';
|
|
jsonPost('/api/strategy/roll/execute', formData(rollForm)).then(function (d) {
|
|
if (!d.ok) { alert(d.error); return; }
|
|
location.reload();
|
|
}).finally(function () {
|
|
btnRollE.disabled = false;
|
|
btnRollE.textContent = '执行滚仓';
|
|
});
|
|
});
|
|
}
|
|
|
|
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();
|
|
});
|
|
});
|
|
}
|
|
})();
|