auto_trade_sys/frontend/src/components/ConfigPanel.jsx
薇薇安 004472bbd4 a
2026-01-17 18:58:53 +08:00

734 lines
31 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { api } from '../services/api'
import './ConfigPanel.css'
const ConfigPanel = () => {
const [configs, setConfigs] = useState({})
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
const [message, setMessage] = useState('')
const [feasibilityCheck, setFeasibilityCheck] = useState(null)
const [checkingFeasibility, setCheckingFeasibility] = useState(false)
// 预设方案配置
// 注意百分比配置使用整数形式如8.0表示8%在应用时会转换为小数0.08
const presets = {
conservative: {
name: '保守配置',
desc: '适合新手,风险较低,止损止盈较宽松,避免被正常波动触发',
configs: {
SCAN_INTERVAL: 900,
MIN_CHANGE_PERCENT: 2.0, // 2%
MIN_SIGNAL_STRENGTH: 5,
TOP_N_SYMBOLS: 10,
MAX_SCAN_SYMBOLS: 150,
MIN_VOLATILITY: 0.02, // 保持小数形式(波动率)
STOP_LOSS_PERCENT: 10.0, // 10%(相对于保证金,更宽松)
TAKE_PROFIT_PERCENT: 20.0, // 20%(相对于保证金,给趋势更多空间)
MIN_STOP_LOSS_PRICE_PCT: 2.0, // 2%最小价格变动保护
MIN_TAKE_PROFIT_PRICE_PCT: 3.0 // 3%最小价格变动保护
}
},
balanced: {
name: '平衡配置',
desc: '推荐使用平衡频率和质量止损止盈适中盈亏比2.5:1',
configs: {
SCAN_INTERVAL: 600,
MIN_CHANGE_PERCENT: 1.5, // 1.5%
MIN_SIGNAL_STRENGTH: 4,
TOP_N_SYMBOLS: 12,
MAX_SCAN_SYMBOLS: 250,
MIN_VOLATILITY: 0.018, // 保持小数形式(波动率)
STOP_LOSS_PERCENT: 8.0, // 8%(相对于保证金,默认值)
TAKE_PROFIT_PERCENT: 20.0, // 20%相对于保证金盈亏比2.5:1提高收益
MIN_STOP_LOSS_PRICE_PCT: 2.0, // 2%最小价格变动保护
MIN_TAKE_PROFIT_PRICE_PCT: 3.0 // 3%最小价格变动保护
}
},
aggressive: {
name: '激进高频',
desc: '晚间波动大时使用交易频率高止损较紧但止盈合理盈亏比3:1',
configs: {
SCAN_INTERVAL: 300,
MIN_CHANGE_PERCENT: 1.0, // 1%
MIN_SIGNAL_STRENGTH: 3,
TOP_N_SYMBOLS: 18,
MAX_SCAN_SYMBOLS: 350,
MIN_VOLATILITY: 0.015, // 保持小数形式(波动率)
STOP_LOSS_PERCENT: 5.0, // 5%(相对于保证金,较紧)
TAKE_PROFIT_PERCENT: 15.0, // 15%相对于保证金盈亏比3:1能捕捉更大趋势
MIN_STOP_LOSS_PRICE_PCT: 1.5, // 1.5%最小价格变动保护
MIN_TAKE_PROFIT_PRICE_PCT: 2.0 // 2%最小价格变动保护
}
}
}
useEffect(() => {
loadConfigs()
checkFeasibility()
}, [])
const checkFeasibility = async () => {
setCheckingFeasibility(true)
try {
const result = await api.checkConfigFeasibility()
setFeasibilityCheck(result)
} catch (error) {
console.error('检查配置可行性失败:', error)
// 静默失败,不显示错误
} finally {
setCheckingFeasibility(false)
}
}
const loadConfigs = async () => {
try {
const data = await api.getConfigs()
setConfigs(data)
} catch (error) {
console.error('Failed to load configs:', error)
setMessage('加载配置失败: ' + error.message)
} finally {
setLoading(false)
}
}
const handleUpdate = async (key, value, type, category) => {
setSaving(true)
setMessage('')
try {
const response = await api.updateConfig(key, {
value,
type,
category
})
setMessage(response.message || '配置已更新')
if (response.note) {
setTimeout(() => {
setMessage(response.note)
}, 2000)
}
// 重新加载配置
await loadConfigs()
// 如果更新的是与可行性相关的配置,重新检查可行性
if (['MIN_MARGIN_USDT', 'MIN_POSITION_PERCENT', 'MAX_POSITION_PERCENT', 'LEVERAGE'].includes(key)) {
await checkFeasibility()
}
} catch (error) {
const errorMsg = error.message || '更新失败'
setMessage('更新失败: ' + errorMsg)
console.error('Config update error:', error)
} finally {
setSaving(false)
}
}
// 检测当前配置匹配哪个预设方案
const detectCurrentPreset = () => {
if (!configs || Object.keys(configs).length === 0) return null
for (const [presetKey, preset] of Object.entries(presets)) {
let match = true
for (const [key, expectedValue] of Object.entries(preset.configs)) {
const currentConfig = configs[key]
if (!currentConfig) {
match = false
break
}
// 获取当前值(处理百分比转换)
let currentValue = currentConfig.value
if (key.includes('PERCENT') || key.includes('PCT')) {
currentValue = currentValue * 100
}
// 比较值(允许小的浮点数误差)
if (typeof expectedValue === 'number' && typeof currentValue === 'number') {
if (Math.abs(currentValue - expectedValue) > 0.01) {
match = false
break
}
} else if (currentValue !== expectedValue) {
match = false
break
}
}
if (match) {
return presetKey
}
}
return null
}
const applyPreset = async (presetKey) => {
const preset = presets[presetKey]
if (!preset) return
setSaving(true)
setMessage('')
try {
const configItems = Object.entries(preset.configs).map(([key, value]) => {
const config = configs[key]
if (!config) {
// 如果配置项不存在,尝试创建(用于新增的配置项)
// 根据key判断类型和分类
let type = 'number'
let category = 'risk'
if (key.includes('PERCENT') || key.includes('PCT')) {
type = 'number'
if (key.includes('STOP_LOSS') || key.includes('TAKE_PROFIT')) {
category = 'risk'
} else {
category = 'scan'
}
} else if (key === 'MIN_VOLATILITY') {
type = 'number'
category = 'scan'
} else if (typeof value === 'number') {
type = 'number'
category = 'scan'
}
return {
key,
value: (key.includes('PERCENT') || key.includes('PCT')) ? value / 100 : value,
type,
category,
description: `预设方案配置项:${key}`
}
}
return {
key,
value: (key.includes('PERCENT') || key.includes('PCT')) ? value / 100 : value,
type: config.type,
category: config.category,
description: config.description
}
}).filter(Boolean)
const response = await api.updateConfigsBatch(configItems)
setMessage(response.message || `已应用${preset.name}`)
if (response.note) {
setTimeout(() => {
setMessage(response.note)
}, 2000)
}
await loadConfigs()
} catch (error) {
setMessage('应用预设失败: ' + error.message)
} finally {
setSaving(false)
}
}
const currentPreset = detectCurrentPreset()
if (loading) return <div className="loading">加载中...</div>
const configCategories = {
'scan': '市场扫描',
'position': '仓位控制',
'risk': '风险控制',
'strategy': '策略参数',
'api': 'API配置'
}
return (
<div className="config-panel">
<div className="config-header">
<div className="header-top">
<h2>交易配置</h2>
<Link to="/config/guide" className="guide-link">📖 配置说明</Link>
</div>
<div className="config-info">
<p>修改配置后交易系统将在下次扫描时自动使用新配置</p>
</div>
{/* 预设方案快速切换 */}
<div className="preset-section">
<div className="preset-header">
<h3>快速切换方案</h3>
<div className="current-preset-status">
<span className="status-label">当前方案</span>
<span className={`status-badge ${currentPreset ? 'preset' : 'custom'}`}>
{currentPreset ? presets[currentPreset].name : '自定义'}
</span>
</div>
</div>
<div className="preset-buttons">
{Object.entries(presets).map(([key, preset]) => (
<button
key={key}
className={`preset-btn ${currentPreset === key ? 'active' : ''}`}
onClick={() => applyPreset(key)}
disabled={saving}
title={preset.desc}
>
<div className="preset-name">
{preset.name}
{currentPreset === key && <span className="active-indicator"></span>}
</div>
<div className="preset-desc">{preset.desc}</div>
</button>
))}
</div>
</div>
</div>
{/* 配置可行性检查提示 */}
{feasibilityCheck && (
<div className={`feasibility-check ${feasibilityCheck.feasible ? 'feasible' : 'infeasible'}`}>
<div className="feasibility-header">
<h4>
{feasibilityCheck.feasible ? '✓ 配置可行' : '⚠ 配置冲突'}
</h4>
<button
onClick={checkFeasibility}
disabled={checkingFeasibility}
className="refresh-btn"
title="重新检查"
>
{checkingFeasibility ? '检查中...' : '🔄 刷新'}
</button>
</div>
<div className="feasibility-info">
<p>
账户余额: <strong>{feasibilityCheck.account_balance?.toFixed(2) || 'N/A'}</strong> USDT |
杠杆: <strong>{feasibilityCheck.leverage}x</strong> |
最小保证金: <strong>{feasibilityCheck.current_config?.min_margin_usdt?.toFixed(2) || 'N/A'}</strong> USDT
</p>
{!feasibilityCheck.feasible && (
<p className="warning-text">
需要仓位价值: <strong>{feasibilityCheck.calculated_values?.required_position_percent?.toFixed(1)}%</strong> |
最大允许: <strong>{feasibilityCheck.calculated_values?.max_allowed_position_percent?.toFixed(1)}%</strong>
</p>
)}
</div>
{feasibilityCheck.suggestions && feasibilityCheck.suggestions.length > 0 && (
<div className="feasibility-suggestions">
<h5>建议方案</h5>
{feasibilityCheck.suggestions.map((suggestion, index) => (
<div key={index} className="suggestion-item">
<div className="suggestion-header">
<strong>{suggestion.title}</strong>
</div>
<p className="suggestion-desc">{suggestion.description}</p>
{suggestion.config_key && suggestion.suggested_value !== null && (
<button
className="apply-suggestion-btn"
onClick={async () => {
try {
await api.updateConfig(suggestion.config_key, {
value: suggestion.suggested_value,
type: 'number',
category: 'position'
})
setMessage(`已应用建议: ${suggestion.title}`)
await loadConfigs()
await checkFeasibility()
} catch (error) {
setMessage('应用建议失败: ' + error.message)
}
}}
>
应用此建议
</button>
)}
</div>
))}
</div>
)}
</div>
)}
{message && (
<div className={`message ${message.includes('失败') || message.includes('错误') ? 'error' : 'success'}`}>
{message}
</div>
)}
{Object.entries(configCategories).map(([category, label]) => (
<section key={category} className="config-section">
<h3>{label}</h3>
<div className="config-grid">
{Object.entries(configs)
.filter(([key, config]) => config.category === category)
.map(([key, config]) => (
<ConfigItem
key={key}
label={key}
config={config}
onUpdate={(value) => handleUpdate(key, value, config.type, config.category)}
disabled={saving}
/>
))}
</div>
</section>
))}
</div>
)
}
const ConfigItem = ({ label, config, onUpdate, disabled }) => {
// 初始化显示值:百分比配置转换为整数形式显示
const getInitialDisplayValue = (val) => {
if (config.type === 'number' && label.includes('PERCENT')) {
if (val === null || val === undefined || val === '') {
return ''
}
const numVal = typeof val === 'string' ? parseFloat(val) : val
if (isNaN(numVal)) {
return ''
}
// 如果是小数形式0.05转换为整数显示5
if (numVal < 1) {
return Math.round(numVal * 100)
}
// 如果已经是整数形式5直接显示
return numVal
}
return val === null || val === undefined ? '' : val
}
const [value, setValue] = useState(config.value)
const [localValue, setLocalValue] = useState(getInitialDisplayValue(config.value))
const [isEditing, setIsEditing] = useState(false)
const [showDetail, setShowDetail] = useState(false)
useEffect(() => {
setValue(config.value)
// 当配置值更新时,重置编辑状态和本地值
setIsEditing(false)
setLocalValue(getInitialDisplayValue(config.value))
}, [config.value])
const handleChange = (newValue) => {
setLocalValue(newValue)
setIsEditing(true)
}
const handleSave = () => {
setIsEditing(false)
// 处理localValue可能是字符串的情况
let processedValue = localValue
if (config.type === 'number') {
// 如果是空字符串,恢复原值
if (localValue === '' || localValue === null || localValue === undefined) {
setLocalValue(value)
return
}
const numValue = typeof localValue === 'string' ? parseFloat(localValue) : localValue
if (isNaN(numValue)) {
// 如果输入无效,恢复原值
const restoreValue = label.includes('PERCENT')
? (typeof value === 'number' && value < 1 ? Math.round(value * 100) : value)
: value
setLocalValue(restoreValue)
return
}
processedValue = numValue
// 百分比配置需要转换用户输入的是整数如5需要转换为小数0.05
if (label.includes('PERCENT')) {
// 用户输入的是整数形式如5表示5%需要转换为小数0.05
// 如果输入值大于等于1说明是百分比形式需要除以100
// 如果输入值小于1说明已经是小数形式直接使用
if (processedValue >= 1) {
processedValue = processedValue / 100
}
// 如果小于1说明已经是小数形式直接使用
}
} else if (config.type === 'boolean') {
processedValue = localValue === 'true' || localValue === true
}
// 只有当值真正发生变化时才保存
if (processedValue !== value) {
onUpdate(processedValue)
} else {
// 值没变化,但需要更新显示值
setValue(processedValue)
}
}
const handleBlur = () => {
// 移动端不自动保存,需要点击保存按钮
if (window.innerWidth > 768) {
handleSave()
}
}
const handleKeyPress = (e) => {
if (e.key === 'Enter') {
handleSave()
}
}
// 显示值百分比配置显示为整数如5其他保持原样
// 处理localValue可能是字符串的情况
const getDisplayValue = () => {
if (config.type === 'number' && label.includes('PERCENT')) {
if (localValue === '' || localValue === null || localValue === undefined) {
return ''
}
const numValue = typeof localValue === 'string' ? parseFloat(localValue) : localValue
if (isNaN(numValue)) {
return ''
}
// localValue已经是显示值整数形式直接返回
return numValue
}
return localValue === null || localValue === undefined ? '' : localValue
}
const displayValue = getDisplayValue()
if (config.type === 'boolean') {
return (
<div className="config-item">
<div className="config-item-header">
<label>{label}</label>
{config.description && (
<button
className="help-btn"
onClick={() => setShowDetail(!showDetail)}
type="button"
>
{showDetail ? '收起' : '说明'}
</button>
)}
</div>
{showDetail && (
<div className="config-detail-popup">
{getConfigDetail(label)}
</div>
)}
<select
value={localValue ? 'true' : 'false'}
onChange={(e) => {
handleChange(e.target.value)
// 布尔值立即保存
onUpdate(e.target.value === 'true')
}}
disabled={disabled}
>
<option value="true"></option>
<option value="false"></option>
</select>
{config.description && (
<span className="description">
{config.description}
</span>
)}
</div>
)
}
if (label.includes('INTERVAL') && ['PRIMARY_INTERVAL', 'CONFIRM_INTERVAL', 'ENTRY_INTERVAL', 'KLINE_INTERVAL'].includes(label)) {
const options = ['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '1d']
return (
<div className="config-item">
<div className="config-item-header">
<label>{label}</label>
{config.description && (
<button
className="help-btn"
onClick={() => setShowDetail(!showDetail)}
type="button"
>
{showDetail ? '收起' : '说明'}
</button>
)}
</div>
{showDetail && (
<div className="config-detail-popup">
{getConfigDetail(label)}
</div>
)}
<select
value={localValue}
onChange={(e) => {
handleChange(e.target.value)
// 下拉框立即保存
onUpdate(e.target.value)
}}
disabled={disabled}
>
{options.map(opt => (
<option key={opt} value={opt}>{opt}</option>
))}
</select>
{config.description && (
<span className="description">
{config.description}
</span>
)}
</div>
)
}
return (
<div className="config-item">
<div className="config-item-header">
<label>{label}</label>
{config.description && (
<button
className="help-btn"
onClick={() => setShowDetail(!showDetail)}
type="button"
>
{showDetail ? '收起' : '说明'}
</button>
)}
</div>
{showDetail && (
<div className="config-detail-popup">
{getConfigDetail(label)}
</div>
)}
<div className="config-input-wrapper">
<input
type="text"
inputMode={config.type === 'number' ? 'decimal' : 'text'}
value={label.includes('PERCENT')
? (displayValue === '' || displayValue === null || displayValue === undefined ? '' : String(displayValue))
: (localValue === '' || localValue === null || localValue === undefined ? '' : String(localValue))}
onChange={(e) => {
// 使用文本输入避免number类型的自动补0问题
let newValue = e.target.value
// 对于数字类型,只允许数字、小数点和负号
if (config.type === 'number') {
// 允许空字符串、数字、小数点和负号
const validPattern = /^-?\d*\.?\d*$/
if (newValue !== '' && !validPattern.test(newValue)) {
// 无效输入,不更新
return
}
// 如果是百分比配置限制输入范围0-100
if (label.includes('PERCENT')) {
const numValue = parseFloat(newValue)
if (newValue !== '' && !isNaN(numValue) && (numValue < 0 || numValue > 100)) {
// 超出范围,不更新
return
}
}
}
// 更新本地值
if (config.type === 'number' && label.includes('PERCENT')) {
// 百分比配置:保持数字形式(整数),允许空值
if (newValue === '') {
handleChange('')
} else {
const numValue = parseFloat(newValue)
if (!isNaN(numValue)) {
handleChange(numValue)
}
// 如果无效,不更新(保持原值)
}
} else if (config.type === 'number') {
// 其他数字配置:保持数字形式,允许空值
if (newValue === '') {
handleChange('')
} else {
const numValue = parseFloat(newValue)
if (!isNaN(numValue)) {
handleChange(numValue)
}
// 如果无效,不更新(保持原值)
}
} else {
handleChange(newValue)
}
}}
onBlur={handleBlur}
onKeyPress={handleKeyPress}
onKeyDown={(e) => {
// 允许删除、退格、方向键等
if (['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(e.key)) {
return
}
// 对于数字输入,只允许数字、小数点和负号
if (config.type === 'number') {
const validKeys = /^[0-9.-]$/
if (!validKeys.test(e.key) && !['Enter', 'Escape'].includes(e.key)) {
e.preventDefault()
}
}
}}
disabled={disabled}
className={isEditing ? 'editing' : ''}
placeholder={label.includes('PERCENT') ? '输入百分比' : '输入数值'}
/>
{label.includes('PERCENT') && <span className="percent-suffix">%</span>}
{isEditing && (
<button
className="save-btn"
onClick={handleSave}
disabled={disabled}
type="button"
>
保存
</button>
)}
</div>
{isEditing && window.innerWidth > 768 && <span className="edit-hint">按Enter保存</span>}
{config.description && (
<span className="description">
{config.description}
</span>
)}
</div>
)
}
// 配置项详细说明
const getConfigDetail = (key) => {
const details = {
// 市场扫描参数
'SCAN_INTERVAL': '扫描间隔。系统每隔多长时间扫描一次市场寻找交易机会。值越小扫描越频繁能更快捕捉波动但会增加API请求和系统负载。建议保守策略3600秒(1小时)平衡策略600秒(10分钟)激进策略300秒(5分钟)。晚间波动大时可降低到300-600秒。',
'MIN_CHANGE_PERCENT': '最小涨跌幅阈值(%。只有24小时涨跌幅达到此值的交易对才会被考虑交易。值越小捕捉机会越多但可能包含噪音和假信号。值越大只捕捉大幅波动信号质量更高但机会更少。建议保守策略2.0-3.0%平衡策略1.5-2.0%激进策略1.0-1.5%。',
'MIN_SIGNAL_STRENGTH': '最小信号强度0-10。技术指标综合评分只有达到此强度的信号才会执行交易。值越小交易机会越多但信号质量可能下降胜率降低。值越大只执行高质量信号胜率更高但机会更少。建议保守策略5-7平衡策略4-5激进策略3-4。',
'TOP_N_SYMBOLS': '每次扫描后处理的交易对数量。从符合条件的交易对中选择涨跌幅最大的前N个进行详细分析。值越大机会越多但计算量增加API请求增多。建议保守策略8-10个平衡策略12-15个激进策略15-20个。',
'MAX_SCAN_SYMBOLS': '扫描的最大交易对数量0表示扫描所有。限制每次扫描时处理的交易对总数减少API请求和计算量。值越小扫描越快但可能错过一些机会。值越大覆盖更全面但API请求和计算量增加。建议保守策略100-200个平衡策略200-300个激进策略300-500个。设置为0会扫描所有交易对约500+个)。',
'MIN_VOLATILITY': '最小波动率小数形式如0.02表示2%。过滤掉波动率低于此值的交易对确保只交易有足够波动的币种。值越小允许更多交易对但可能包含波动不足的币种。值越大只交易高波动币种但可能错过一些机会。建议0.015-0.0251.5%-2.5%)。',
'MIN_VOLUME_24H': '最小24小时成交量USDT。过滤掉24小时交易量低于此值的交易对确保只交易流动性好的币种避免滑点和流动性风险。建议≥500万USDT主流币种可设置1000万以上。',
'KLINE_INTERVAL': 'K线数据周期。获取K线数据的基础周期影响技术指标的计算粒度。周期越短反应越快但可能包含更多噪音。周期越长信号更平滑但反应较慢。建议5m-1h通常与PRIMARY_INTERVAL保持一致。',
'PRIMARY_INTERVAL': '主周期。策略分析的主要时间周期用于计算技术指标RSI、MACD、布林带等。决定策略主要关注的市场趋势级别。周期越短反应越快周期越长趋势更明确。建议保守策略4h-1d平衡策略1h-4h激进策略15m-1h。',
'CONFIRM_INTERVAL': '确认周期。用于确认交易信号的更长时间周期增加信号的可靠性减少假信号。通常比主周期更长。建议保守策略1d平衡策略4h-1d激进策略1h-4h。',
'ENTRY_INTERVAL': '入场周期。用于精确入场的短时间周期优化入场点提高单笔交易的盈亏比。通常比主周期更短。建议保守策略1h平衡策略15m-30m激进策略5m-15m。',
// 仓位控制参数
'MAX_POSITION_PERCENT': '单笔最大仓位账户余额的百分比如0.05表示5%。单笔交易允许的最大仓位大小。值越大单笔金额越大潜在收益和风险都增加。注意币安要求最小名义价值5 USDT如果账户余额较小系统会自动调整。建议保守策略3-5%平衡策略5-7%激进策略7-10%。',
'MAX_TOTAL_POSITION_PERCENT': '总仓位上限账户余额的百分比如0.30表示30%。所有持仓的总价值不能超过账户余额的百分比防止过度交易和风险集中。值越大可以同时持有更多仓位但风险集中度增加。建议保守策略20-30%平衡策略30-40%激进策略40-50%。',
'MIN_POSITION_PERCENT': '单笔最小仓位账户余额的百分比如0.01表示1%。单笔交易允许的最小仓位大小避免交易过小的仓位减少手续费影响。建议1-2%。',
// 风险控制参数
'STOP_LOSS_PERCENT': '止损百分比如0.08表示8%相对于保证金。当亏损达到此百分比时自动平仓止损限制单笔交易的最大亏损。值越小止损更严格单笔损失更小但可能被正常波动触发。值越大允许更大的回撤但单笔损失可能较大。建议保守策略10-15%平衡策略8-10%激进策略5-8%。注意止损应该小于止盈建议盈亏比至少1:1.5。系统会结合最小价格变动保护,取更宽松的一个。',
'TAKE_PROFIT_PERCENT': '止盈百分比如0.15表示15%相对于保证金。当盈利达到此百分比时自动平仓止盈锁定利润。值越大目标利润更高但可能错过及时止盈的机会持仓时间更长。值越小能更快锁定利润但可能错过更大的趋势。建议保守策略20-30%平衡策略15-20%激进策略10-15%。注意应该大于止损建议盈亏比至少1:1.5。系统会结合最小价格变动保护,取更宽松的一个。',
'MIN_STOP_LOSS_PRICE_PCT': '最小止损价格变动百分比如0.02表示2%。防止止损过紧即使基于保证金的止损更紧也会使用至少此百分比的价格变动。建议保守策略2-3%平衡策略2%激进策略1.5-2%。',
'MIN_TAKE_PROFIT_PRICE_PCT': '最小止盈价格变动百分比如0.03表示3%。防止止盈过紧即使基于保证金的止盈更紧也会使用至少此百分比的价格变动。建议保守策略3-4%平衡策略3%激进策略2-3%。',
// 策略参数
'LEVERAGE': '交易杠杆倍数。放大资金利用率同时放大收益和风险。杠杆越高相同仓位下需要的保证金越少但风险越大。建议保守策略5-10倍平衡策略10倍激进策略10-15倍。注意高杠杆会增加爆仓风险请谨慎使用。',
'USE_TRAILING_STOP': '是否启用移动止损true/false。启用后当盈利达到激活阈值时止损会自动跟踪价格保护利润。适合趋势行情可以捕捉更大的利润空间。建议平衡和激进策略启用保守策略可关闭。',
'TRAILING_STOP_ACTIVATION': '移动止损激活阈值百分比如0.01表示1%。当盈利达到此百分比时移动止损开始跟踪价格将止损移至成本价保本。值越小激活越早更早保护利润但可能过早退出。值越大激活越晚给价格更多波动空间。建议1-2%。',
'TRAILING_STOP_PROTECT': '移动止损保护利润百分比如0.01表示1%。当价格从最高点回撤达到此百分比时触发止损平仓锁定利润。值越小保护更严格能锁定更多利润但可能过早退出。值越大允许更大回撤可能捕捉更大趋势但利润可能回吐。建议1-2%。',
// API配置
'BINANCE_API_KEY': '币安API密钥。用于访问币安账户的凭证需要启用"合约交易"权限。请妥善保管,不要泄露。',
'BINANCE_API_SECRET': '币安API密钥。与API Key配对使用用于签名验证。请妥善保管不要泄露。',
'USE_TESTNET': '是否使用币安测试网true/false。true表示使用测试网模拟交易无真实资金false表示使用生产网真实交易。建议测试时使用true正式交易使用false。'
}
return details[key] || '暂无详细说明,请参考配置说明文档'
}
export default ConfigPanel