From ff1a51a49e02aa347463257eccfab61c8c0cf0bd Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Sun, 20 Sep 2026 15:49:14 +0800
Subject: [PATCH] feat(station): 添加双开止损触发配置功能
---
src/main/resources/static/station-dashboard.html | 48 ++++++++++++++++++++++++++++++++++++++++--------
1 files changed, 40 insertions(+), 8 deletions(-)
diff --git a/src/main/resources/static/station-dashboard.html b/src/main/resources/static/station-dashboard.html
index 2f2f7d8..e694045 100644
--- a/src/main/resources/static/station-dashboard.html
+++ b/src/main/resources/static/station-dashboard.html
@@ -130,6 +130,10 @@
.btn-outline { background: transparent; border: 1px solid var(--border); color: var(--text); }
.btn:disabled { opacity: .4; cursor: not-allowed; }
+ /* ===== 手动控制面板 ===== */
+ .manual-actions { display: flex; flex-wrap: wrap; gap: 8px; }
+ .manual-actions .btn { padding: 7px 14px; }
+
/* ===== 内容区(Tabs) ===== */
.content { flex: 1; overflow-y: auto; padding: 16px 24px; }
.tabs {
@@ -365,8 +369,8 @@
<h2 style="color:var(--dim)">← 选择一个实例</h2>
</div>
<div class="btn-group">
- <button class="btn btn-success" id="btnStart" disabled onclick="showConfirm('start')">▶ 启动</button>
- <button class="btn btn-danger" id="btnStop" disabled onclick="showConfirm('stop')">⏹ 停止</button>
+ <button class="btn btn-success" id="btnStart" disabled onclick="showConfirm('start','启动')">▶ 启动</button>
+ <button class="btn btn-danger" id="btnStop" disabled onclick="showConfirm('stop','停止')">⏹ 停止</button>
</div>
</div>
@@ -391,6 +395,17 @@
<!-- 概览 Tab -->
<div class="tab-content active" data-tab="overview">
<div class="stats-grid" id="statsGrid"></div>
+ <div class="chart-container">
+ <h3>⚙️ 手动控制 <span style="font-size:11px;color:var(--dim);font-weight:400">(实时下发 MQ 指令,策略运行中可用)</span></h3>
+ <div class="manual-actions">
+ <button class="btn btn-outline" id="btnCancelLongTpSl" disabled onclick="showConfirm('cancel-long-tp-sl','取消多仓止盈止损')">取消多仓止盈止损</button>
+ <button class="btn btn-outline" id="btnCancelShortTpSl" disabled onclick="showConfirm('cancel-short-tp-sl','取消空仓止盈止损')">取消空仓止盈止损</button>
+ <button class="btn btn-outline" id="btnRebuildLongTpSl" disabled onclick="showConfirm('rebuild-long-tp-sl','生成多仓止盈止损')">生成多仓止盈止损</button>
+ <button class="btn btn-outline" id="btnRebuildShortTpSl" disabled onclick="showConfirm('rebuild-short-tp-sl','生成空仓止盈止损')">生成空仓止盈止损</button>
+ <button class="btn btn-danger" id="btnCloseLong" disabled onclick="showConfirm('close-long','多单平仓')">多单平仓</button>
+ <button class="btn btn-danger" id="btnCloseShort" disabled onclick="showConfirm('close-short','空单平仓')">空单平仓</button>
+ </div>
+ </div>
<div class="chart-container">
<h3>📈 盈亏趋势(最近 PNL_SNAPSHOT 事件)</h3>
<div class="pnl-summary" id="pnlSummary"></div>
@@ -1023,6 +1038,9 @@
addPositionStartThreshold: 1,
placeExcessTakeProfit: false,
priceDriveEnabled: true,
+ dualStopLossEnabled: false,
+ longStopLossPrice: '',
+ shortStopLossPrice: '',
};
async function loadConfig(md5) {
@@ -1069,6 +1087,10 @@
{ key: 'rounds', label: '运行轮数 (0=不限)', type: 'number', placeholder: '0', jtype: 'int', group: 'run', tip: '盈利重启达此轮数后停止' },
{ key: 'placeExcessTakeProfit', label: '超额止盈', type: 'select', options: [{ v: 'false', l: '关闭' }, { v: 'true', l: '开启' }], group: 'run' },
{ key: 'priceDriveEnabled', label: '价格驱动', type: 'select', options: [{ v: 'true', l: '开启' }, { v: 'false', l: '关闭' }], group: 'run' },
+
+ { key: 'dualStopLossEnabled', label: '双开止损触发', type: 'select', options: [{ v: 'false', l: '关闭' }, { v: 'true', l: '开启' }], group: 'dual-sl', tip: '开启后按下方多空止损触发价初始化双向止损单,任一触发后重挂另一侧止损' },
+ { key: 'longStopLossPrice', label: '多头止损触发价', type: 'number', step: 'any', placeholder: '如 2540', jtype: 'string', group: 'dual-sl', tip: '价格跌至该值时触发多头止损' },
+ { key: 'shortStopLossPrice', label: '空头止损触发价', type: 'number', step: 'any', placeholder: '如 2650', jtype: 'string', group: 'dual-sl', tip: '价格涨至该值时触发空头止损' },
];
// 分组定义(渲染顺序)
@@ -1076,6 +1098,7 @@
{ id: 'grid', name: '📐 网格参数' },
{ id: 'risk', name: '🛡️ 风控参数' },
{ id: 'addon', name: '📈 加仓 & 止损' },
+ { id: 'dual-sl', name: '🎯 双开止损触发' },
{ id: 'run', name: '⚡ 运行控制' },
];
@@ -1205,24 +1228,33 @@
function updateActionButtons(inst) {
const startBtn = $('btnStart');
const stopBtn = $('btnStop');
+ // 手动控制按钮(止盈止损/平仓):需策略运行中才有持仓/挂单可操作
+ const manualIds = ['btnCancelLongTpSl', 'btnCancelShortTpSl', 'btnRebuildLongTpSl',
+ 'btnRebuildShortTpSl', 'btnCloseLong', 'btnCloseShort'];
+ const setManual = (disabled) => {
+ manualIds.forEach(id => { const b = $(id); if (b) b.disabled = disabled; });
+ };
if (!inst) {
startBtn.disabled = true;
stopBtn.disabled = true;
+ setManual(true);
return;
}
const running = inst.state === 'ACTIVE' || inst.state === 'OPENING' || inst.state === 'WAITING_KLINE';
startBtn.disabled = running; // 运行中不可重复启动
stopBtn.disabled = !running; // 未运行不可停止
+ setManual(!running); // 仅运行中可下发手动指令
}
-function showConfirm(action) {
+function showConfirm(action, label) {
if (!selectedMd5) return;
- pendingAction = action;
- const verb = action === 'start' ? '启动' : '停止';
+ pendingAction = { path: action, label: label || action };
+ const verb = pendingAction.label;
+ const isDanger = action === 'stop' || action === 'close-long' || action === 'close-short';
$('modalTitle').textContent = `确认${verb}`;
- $('modalMsg').textContent = `确定要${verb}实例 ${selectedMd5.substring(0, 12)}... 的策略吗?`;
+ $('modalMsg').textContent = `确定要对实例 ${selectedMd5.substring(0, 12)}... 执行「${verb}」吗?`;
$('modalConfirmBtn').textContent = verb;
- $('modalConfirmBtn').className = 'btn ' + (action === 'start' ? 'btn-success' : 'btn-danger');
+ $('modalConfirmBtn').className = 'btn ' + (isDanger ? 'btn-danger' : 'btn-success');
$('modalOverlay').classList.add('show');
}
@@ -1272,7 +1304,7 @@
closeModal();
try {
- const msg = await api(API + '/' + action + '?apiKeyMd5=' + encodeURIComponent(selectedMd5), { method: 'POST' });
+ const msg = await api(API + '/' + action.path + '?apiKeyMd5=' + encodeURIComponent(selectedMd5), { method: 'POST' });
toast(msg || '指令已发送', 'success');
// 延迟刷新等待 ACK
setTimeout(refreshInstanceList, 2000);
--
Gitblit v1.9.1