From 2d18d92069dab14c8ea723f530634149aec445de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Mon, 19 Jan 2026 23:55:18 +0800 Subject: [PATCH] a --- frontend/src/components/ConfigPanel.css | 89 +++++++++++ frontend/src/components/ConfigPanel.jsx | 194 ++++++++++++++++++++---- 2 files changed, 252 insertions(+), 31 deletions(-) diff --git a/frontend/src/components/ConfigPanel.css b/frontend/src/components/ConfigPanel.css index fc282e7..8eaf12a 100644 --- a/frontend/src/components/ConfigPanel.css +++ b/frontend/src/components/ConfigPanel.css @@ -184,6 +184,66 @@ font-size: 1rem; } +.preset-guide { + background: #fff; + border: 1px solid #e9ecef; + border-radius: 10px; + padding: 0.9rem 1rem; + margin-bottom: 1rem; +} + +.preset-guide-title { + font-weight: 800; + color: #2c3e50; + margin-bottom: 0.5rem; + font-size: 0.95rem; +} + +.preset-guide-list { + margin: 0; + padding-left: 1.1rem; + color: #555; + font-size: 0.9rem; + line-height: 1.6; +} + +.preset-guide code { + background: #f5f7ff; + border: 1px solid #e7ecff; + padding: 0.05rem 0.35rem; + border-radius: 6px; + font-size: 0.85em; +} + +.preset-groups { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.preset-group { + background: rgba(255, 255, 255, 0.6); + border: 1px solid #e9ecef; + border-radius: 12px; + padding: 0.9rem; +} + +.preset-group-header { + margin-bottom: 0.75rem; +} + +.preset-group-title { + font-weight: 800; + color: #2c3e50; + margin-bottom: 0.25rem; +} + +.preset-group-desc { + color: #666; + font-size: 0.9rem; + line-height: 1.5; +} + .current-preset-status { display: flex; align-items: center; @@ -282,6 +342,35 @@ gap: 0.5rem; } +.preset-tag { + display: inline-flex; + align-items: center; + padding: 0.15rem 0.55rem; + border-radius: 999px; + font-size: 0.75rem; + font-weight: 800; + border: 1px solid transparent; + white-space: nowrap; +} + +.preset-tag--limit { + background: #f5f7ff; + color: #2b4cff; + border-color: #dfe6ff; +} + +.preset-tag--smart { + background: #e7f7ff; + color: #006c9c; + border-color: #cfefff; +} + +.preset-tag--legacy { + background: #fff7e6; + color: #a35b00; + border-color: #ffe2b5; +} + .active-indicator { color: #4CAF50; font-size: 1.2rem; diff --git a/frontend/src/components/ConfigPanel.jsx b/frontend/src/components/ConfigPanel.jsx index a12401e..1f058ec 100644 --- a/frontend/src/components/ConfigPanel.jsx +++ b/frontend/src/components/ConfigPanel.jsx @@ -14,6 +14,14 @@ const ConfigPanel = () => { const [backendStatus, setBackendStatus] = useState(null) const [systemBusy, setSystemBusy] = useState(false) + // “PCT”类配置里有少数是“百分比数值(<=1表示<=1%)”,而不是“0~1比例” + // 例如 LIMIT_ORDER_OFFSET_PCT=0.5 表示 0.5%(而不是 50%) + const PCT_LIKE_KEYS = new Set([ + 'LIMIT_ORDER_OFFSET_PCT', + 'ENTRY_MAX_DRIFT_PCT_TRENDING', + 'ENTRY_MAX_DRIFT_PCT_RANGING', + ]) + // 配置快照(用于整体分析/导出) const [showSnapshot, setShowSnapshot] = useState(false) const [snapshotText, setSnapshotText] = useState('') @@ -339,7 +347,12 @@ const ConfigPanel = () => { if (value === null || value === undefined) return value // 百分比/比例:尽量转成 “百分比值” if (typeof value === 'number' && (key.includes('PERCENT') || key.includes('PCT'))) { - // 存储一般是 0~1,小于 1 则乘 100;若已经是 >=1 则认为就是百分比 + // 兼容两种: + // - 常规 PERCENT/PCT:存储为 0~1(比例),展示为 0~100(%) + // - PCT_LIKE_KEYS:存储可能是 0.006(=0.6%) 或 0.6(=0.6%),展示统一为“百分比数值” + if (PCT_LIKE_KEYS.has(key)) { + return value <= 0.05 ? value * 100 : value + } return value < 1 ? value * 100 : value } return value @@ -468,7 +481,12 @@ const ConfigPanel = () => { // 获取当前值(处理百分比转换) let currentValue = currentConfig.value if (key.includes('PERCENT') || key.includes('PCT')) { - currentValue = currentValue * 100 + if (PCT_LIKE_KEYS.has(key)) { + // 兼容旧值:0.006(=0.6%) 或 0.6(=0.6%) + currentValue = currentValue <= 0.05 ? currentValue * 100 : currentValue + } else { + currentValue = currentValue * 100 + } } // 比较值(允许小的浮点数误差) @@ -509,10 +527,21 @@ const ConfigPanel = () => { type = 'boolean' category = 'strategy' } - if (key.includes('PERCENT') || key.includes('PCT')) { + if (key.startsWith('ENTRY_') || key.startsWith('SMART_ENTRY_') || key === 'SMART_ENTRY_ENABLED') { + type = typeof value === 'boolean' ? 'boolean' : 'number' + category = 'strategy' + } else if (key.startsWith('AUTO_TRADE_')) { + type = typeof value === 'boolean' ? 'boolean' : 'number' + category = 'strategy' + } else if (key === 'LIMIT_ORDER_OFFSET_PCT') { + type = 'number' + category = 'strategy' + } else if (key.includes('PERCENT') || key.includes('PCT')) { type = 'number' if (key.includes('STOP_LOSS') || key.includes('TAKE_PROFIT')) { category = 'risk' + } else if (key.includes('POSITION')) { + category = 'position' } else { category = 'scan' } @@ -524,17 +553,29 @@ const ConfigPanel = () => { category = 'scan' } + const detail = typeof getConfigDetail === 'function' ? getConfigDetail(key) : '' + const desc = + detail && typeof detail === 'string' && !detail.includes('暂无详细说明') + ? detail + : `预设方案配置项:${key}` + return { key, - value: (key.includes('PERCENT') || key.includes('PCT')) ? value / 100 : value, + value: + (key.includes('PERCENT') || key.includes('PCT')) && !PCT_LIKE_KEYS.has(key) + ? value / 100 + : value, type, category, - description: `预设方案配置项:${key}` + description: desc } } return { key, - value: (key.includes('PERCENT') || key.includes('PCT')) ? value / 100 : value, + value: + (key.includes('PERCENT') || key.includes('PCT')) && !PCT_LIKE_KEYS.has(key) + ? value / 100 + : value, type: config.type, category: config.category, description: config.description @@ -673,23 +714,92 @@ const ConfigPanel = () => { -
- {Object.entries(presets).map(([key, preset]) => ( - - ))} +
+
怎么选更不迷糊
+
    +
  • + 先选入场机制:纯限价(更控频但可能撤单) vs 智能入场(更少漏单但需限制追价)。 +
  • +
  • + 再看“会不会下单”:如果你发现几乎不出单,优先把 AUTO_TRADE_ONLY_TRENDING 关掉、把 AUTO_TRADE_ALLOW_4H_NEUTRAL 打开。 +
  • +
  • + 最后再微调:想更容易成交 → 调小 LIMIT_ORDER_OFFSET_PCT、调大 ENTRY_CONFIRM_TIMEOUT_SEC。 +
  • +
+ + {(() => { + const presetUiMeta = { + swing: { group: 'limit', tag: '纯限价' }, + strict: { group: 'limit', tag: '纯限价' }, + fill: { group: 'smart', tag: '智能入场' }, + steady: { group: 'smart', tag: '智能入场' }, + conservative: { group: 'legacy', tag: '传统' }, + balanced: { group: 'legacy', tag: '传统' }, + aggressive: { group: 'legacy', tag: '高频实验' }, + } + + const groups = [ + { + key: 'limit', + title: 'A. 纯限价(SMART_ENTRY_ENABLED=false)', + desc: '只下 1 次限价单,未在确认时间内成交就撤单跳过。更控频、更接近“波段”,但更容易出现 NEW→撤单。', + presetKeys: ['swing', 'strict'], + }, + { + key: 'smart', + title: 'B. 智能入场(SMART_ENTRY_ENABLED=true)', + desc: '限价回调 + 受限追价 +(趋势强时)可控市价兜底。更少漏单,但必须限制追价步数与偏离上限,避免回到高频追价。', + presetKeys: ['fill', 'steady'], + }, + { + key: 'legacy', + title: 'C. 传统 / 实验(不建议长期)', + desc: '这组更多用于对比或临时实验(频率更高/更容易过度交易),建议在稳定盈利前谨慎使用。', + presetKeys: ['conservative', 'balanced', 'aggressive'], + }, + ] + + return ( +
+ {groups.map((g) => ( +
+
+
{g.title}
+
{g.desc}
+
+
+ {g.presetKeys + .filter((k) => presets[k]) + .map((k) => { + const preset = presets[k] + const meta = presetUiMeta[k] || { group: g.key, tag: '' } + return ( + + ) + })} +
+
+ ))} +
+ ) + })()}
@@ -888,6 +998,13 @@ const ConfigPanel = () => { const ConfigItem = ({ label, config, onUpdate, disabled }) => { const isPercentKey = label.includes('PERCENT') || label.includes('PCT') + const PCT_LIKE_KEYS = new Set([ + 'LIMIT_ORDER_OFFSET_PCT', + 'ENTRY_MAX_DRIFT_PCT_TRENDING', + 'ENTRY_MAX_DRIFT_PCT_RANGING', + ]) + const isPctLike = PCT_LIKE_KEYS.has(label) + const isRatioPercentKey = isPercentKey && !isPctLike const formatPercent = (n) => { // 最多 4 位小数,去掉尾随 0(例如 2.3000 -> 2.3) @@ -905,7 +1022,13 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => { if (isNaN(numVal)) { return '' } - // 存储为 0~1,小于等于 1 则换算成百分比;异常旧值则原样展示 + // 两类: + // 1) 常规比例型(如 STOP_LOSS_PERCENT=0.08):展示为 8(%) + // 2) pct-like(如 LIMIT_ORDER_OFFSET_PCT=0.5 表示0.5%):展示为 0.5(%) + if (isPctLike) { + const pctNum = numVal <= 0.05 ? numVal * 100 : numVal + return formatPercent(pctNum) + } const percent = numVal <= 1 ? numVal * 100 : numVal return formatPercent(percent) } @@ -972,14 +1095,22 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => { } processedValue = numValue - // 百分比配置:前端统一按“百分比”输入(支持小数),存储为 0~1 + // 百分比配置 if (isPercentKey) { - // 限制范围 0-100 - if (processedValue < 0 || processedValue > 100) { - setLocalValue(getInitialDisplayValue(value)) - return + if (isPctLike) { + // pct-like:值本身就是“百分比数值”(<=1表示<=1%),不做 /100 + if (processedValue < 0 || processedValue > 1) { + setLocalValue(getInitialDisplayValue(value)) + return + } + } else { + // 常规比例型:前端按“百分比”输入(0~100),存储为 0~1 + if (processedValue < 0 || processedValue > 100) { + setLocalValue(getInitialDisplayValue(value)) + return + } + processedValue = processedValue / 100 } - processedValue = processedValue / 100 } } else if (config.type === 'boolean') { processedValue = localValue === 'true' || localValue === true @@ -1133,7 +1264,8 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => { // 如果是百分比配置,限制输入范围(0-100) if (isPercentKey) { const numValue = parseFloat(newValue) - if (newValue !== '' && !isNaN(numValue) && (numValue < 0 || numValue > 100)) { + const maxAllowed = isPctLike ? 1 : 100 + if (newValue !== '' && !isNaN(numValue) && (numValue < 0 || numValue > maxAllowed)) { // 超出范围,不更新 return } @@ -1197,7 +1329,7 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => { }} disabled={disabled} className={isEditing ? 'editing' : ''} - placeholder={isPercentKey ? '输入百分比(可小数)' : '输入数值'} + placeholder={isPercentKey ? (isPctLike ? '输入0~1(表示0%~1%)' : '输入百分比(可小数)') : '输入数值'} /> {isPercentKey && %} {isEditing && (