| | |
| | | |
| | | <!-- 事件日志 Tab --> |
| | | <div class="tab-content" data-tab="events"> |
| | | <div class="events-panel" style="margin-bottom:16px"> |
| | | <div class="events-header"> |
| | | <h3>🎯 每轮止损次数</h3> |
| | | <button class="btn btn-outline" onclick="refreshRoundStopLoss()" style="font-size:11px;padding:3px 10px">🔄 刷新</button> |
| | | </div> |
| | | <div style="max-height:300px;overflow-y:auto"> |
| | | <table class="events-table"> |
| | | <thead> |
| | | <tr> |
| | | <th style="width:120px">轮次</th> |
| | | <th style="width:120px">多仓止损</th> |
| | | <th>空仓止损</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody id="roundStopLossBody"></tbody> |
| | | </table> |
| | | </div> |
| | | </div> |
| | | <div class="events-panel"> |
| | | <div class="events-header"> |
| | | <h3>📋 最近 100 条事件</h3> |
| | |
| | | // 加载事件(内部会渲染事件表格 + PNL 图表) |
| | | await loadEvents(md5); |
| | | |
| | | // 加载每轮止损次数 |
| | | loadRoundStopLoss(md5); |
| | | |
| | | // 事件加载完成后重算统计卡:账户总权益依赖最新 PNL_SNAPSHOT |
| | | if (selectedMd5 === md5 && instances[md5]) { |
| | | renderStats(instances[md5]); |
| | |
| | | }).join(''); |
| | | |
| | | renderPnlChart(); |
| | | } |
| | | |
| | | // ==================== 每轮止损次数 ==================== |
| | | async function loadRoundStopLoss(md5) { |
| | | try { |
| | | const rows = await api(API + '/round-stop-loss?apiKeyMd5=' + encodeURIComponent(md5)); |
| | | renderRoundStopLoss(rows || []); |
| | | } catch (e) { |
| | | renderRoundStopLoss([]); |
| | | } |
| | | } |
| | | |
| | | function renderRoundStopLoss(rows) { |
| | | const tbody = $('roundStopLossBody'); |
| | | if (!tbody) return; |
| | | if (!rows || rows.length === 0) { |
| | | tbody.innerHTML = `<tr><td colspan="3" style="text-align:center;padding:20px;color:var(--dim)">暂无止损记录</td></tr>`; |
| | | return; |
| | | } |
| | | tbody.innerHTML = rows.map(r => { |
| | | const roundLabel = (r.roundNum === null || r.roundNum === undefined) ? '进行中' : ('第 ' + r.roundNum + ' 轮'); |
| | | return ` |
| | | <tr> |
| | | <td>${esc(roundLabel)}</td> |
| | | <td>${r.longLossCount ?? 0}</td> |
| | | <td>${r.shortLossCount ?? 0}</td> |
| | | </tr>`; |
| | | }).join(''); |
| | | } |
| | | |
| | | function refreshRoundStopLoss() { |
| | | if (selectedMd5) loadRoundStopLoss(selectedMd5); |
| | | } |
| | | |
| | | // ==================== PNL 图表 ==================== |
| | |
| | | addPositionQuantity: 1, |
| | | maxPositionPerSide: 0, |
| | | addPositionStartThreshold: 1, |
| | | placeExcessTakeProfit: false, |
| | | placeExcessTakeProfit: 'off', |
| | | priceDriveEnabled: true, |
| | | dualStopLossEnabled: false, |
| | | longStopLossPrice: '', |
| | | shortStopLossPrice: '', |
| | | tpTriggerOppositeAddEnabled: false, |
| | | tpTriggerOppositeAddQuantity: 0, |
| | | baseTakeProfitPerGrid: 0, |
| | | baseTakeProfitGridCount: 0, |
| | | }; |
| | | |
| | | async function loadConfig(md5) { |
| | |
| | | { key: 'quantity', label: '每次下单张数', type: 'number', placeholder: '2', jtype: 'string', group: 'grid' }, |
| | | { key: 'maxPositionSize', label: '最大持仓张数', type: 'number', placeholder: '4', jtype: 'int', group: 'grid' }, |
| | | { key: 'takeProfitGridSpan', label: '止盈网格跨度', type: 'number', placeholder: '2', jtype: 'int', group: 'grid' }, |
| | | { key: 'baseTakeProfitPerGrid', label: '单格止盈张数', type: 'number', step: 'any', placeholder: '0', jtype: 'decimal', group: 'grid', tip: '基础仓位止盈:每个网格挂的止盈张数(支持小数)' }, |
| | | { key: 'baseTakeProfitGridCount', label: '止盈网格数量', type: 'number', placeholder: '0', jtype: 'int', group: 'grid', tip: '基础仓位止盈:挂止盈的网格数量' }, |
| | | |
| | | { key: 'expectedProfit', label: '预期收益 (USDT)', type: 'number', step: '0.01', placeholder: '0.15', jtype: 'decimal', group: 'risk' }, |
| | | { key: 'maxLoss', label: '最大亏损 (USDT)', type: 'number', step: '0.01', placeholder: '1.5', jtype: 'decimal', group: 'risk' }, |
| | |
| | | { key: 'addPositionQuantity', label: '加仓数量 (张)', type: 'number', placeholder: '1', jtype: 'int', group: 'addon' }, |
| | | { key: 'maxPositionPerSide', label: '单边最大仓位 (0=不限)', type: 'number', placeholder: '0', jtype: 'int', group: 'addon' }, |
| | | { key: 'addPositionStartThreshold', label: '加仓启动阈值 (次)', type: 'number', placeholder: '1', jtype: 'int', group: 'addon', tip: '前 N 次止损不触发加仓' }, |
| | | { key: 'tpTriggerOppositeAddEnabled', label: '止盈触发对向加仓', type: 'select', options: [{ v: 'false', l: '关闭' }, { v: 'true', l: '开启' }], group: 'addon', tip: '止盈单成交后触发对向加仓' }, |
| | | { key: 'tpTriggerOppositeAddQuantity', label: '对向加仓挂单数量 (张)', type: 'number', placeholder: '0', jtype: 'int', group: 'addon', tip: '止盈成交后在对向挂出的加仓张数' }, |
| | | |
| | | { 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: 'placeExcessTakeProfit', label: '止盈模式', type: 'select', options: [{ v: 'off', l: '关闭' }, { v: 'concentrated', l: '集中止盈' }, { v: 'dispersed', l: '分散止盈' }], group: 'run', tip: '超出基础仓位的加仓部分如何挂止盈' }, |
| | | { 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: '开启后按下方多空止损触发价初始化双向止损单,任一触发后重挂另一侧止损' }, |