This commit is contained in:
薇薇安 2026-01-13 23:12:23 +08:00
parent d9830f395b
commit 42a6f2c1f3
6 changed files with 3051 additions and 4 deletions

2374
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom' import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom'
import ConfigPanel from './components/ConfigPanel' import ConfigPanel from './components/ConfigPanel'
import ConfigGuide from './components/ConfigGuide'
import TradeList from './components/TradeList' import TradeList from './components/TradeList'
import StatsDashboard from './components/StatsDashboard' import StatsDashboard from './components/StatsDashboard'
import './App.css' import './App.css'
@ -24,6 +25,7 @@ function App() {
<Routes> <Routes>
<Route path="/" element={<StatsDashboard />} /> <Route path="/" element={<StatsDashboard />} />
<Route path="/config" element={<ConfigPanel />} /> <Route path="/config" element={<ConfigPanel />} />
<Route path="/config/guide" element={<ConfigGuide />} />
<Route path="/trades" element={<TradeList />} /> <Route path="/trades" element={<TradeList />} />
</Routes> </Routes>
</main> </main>

View File

@ -0,0 +1,164 @@
.config-guide {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.guide-header {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 2px solid #eee;
}
.back-link {
display: inline-block;
margin-bottom: 1rem;
color: #2196F3;
text-decoration: none;
font-size: 0.9rem;
transition: color 0.3s;
}
.back-link:hover {
color: #1976D2;
text-decoration: underline;
}
.guide-header h1 {
margin: 0;
color: #2c3e50;
}
.guide-content {
line-height: 1.8;
}
.guide-section {
margin-bottom: 3rem;
}
.guide-section h2 {
color: #34495e;
border-left: 4px solid #2196F3;
padding-left: 1rem;
margin-bottom: 1.5rem;
}
.preset-card {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.preset-card h3 {
color: #2c3e50;
margin-top: 0;
margin-bottom: 0.5rem;
}
.preset-desc {
color: #666;
font-style: italic;
margin-bottom: 1rem;
}
.preset-params {
background: white;
padding: 1rem;
border-radius: 4px;
margin-bottom: 1rem;
}
.preset-params ul {
margin: 0;
padding-left: 1.5rem;
}
.preset-params li {
margin-bottom: 0.5rem;
}
.preset-effect {
background: #e7f3ff;
padding: 1rem;
border-radius: 4px;
border-left: 3px solid #2196F3;
color: #1976D2;
}
.preset-warning {
background: #fff3cd;
padding: 1rem;
border-radius: 4px;
border-left: 3px solid #ffc107;
color: #856404;
margin-top: 1rem;
}
.config-detail {
margin-bottom: 2rem;
}
.config-detail h3 {
color: #34495e;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid #dee2e6;
}
.param-item {
background: #f8f9fa;
padding: 1.5rem;
border-radius: 8px;
margin-bottom: 1.5rem;
border-left: 4px solid #6c757d;
}
.param-item h4 {
color: #2c3e50;
margin-top: 0;
margin-bottom: 0.75rem;
}
.param-item p {
margin: 0.5rem 0;
}
.param-item ul,
.param-item ol {
margin: 0.5rem 0;
padding-left: 1.5rem;
}
.param-item li {
margin-bottom: 0.5rem;
}
.tips {
background: #f8f9fa;
padding: 1.5rem;
border-radius: 8px;
margin-bottom: 1.5rem;
border-left: 4px solid #28a745;
}
.tips h3 {
color: #2c3e50;
margin-top: 0;
margin-bottom: 1rem;
}
.tips ul,
.tips ol {
margin: 0.5rem 0;
padding-left: 1.5rem;
}
.tips li {
margin-bottom: 0.5rem;
}

View File

@ -0,0 +1,294 @@
import React from 'react'
import { Link } from 'react-router-dom'
import './ConfigGuide.css'
const ConfigGuide = () => {
return (
<div className="config-guide">
<div className="guide-header">
<Link to="/config" className="back-link">← 返回配置页面</Link>
<h1>交易配置说明文档</h1>
</div>
<div className="guide-content">
<section className="guide-section">
<h2>一、预设方案说明</h2>
<div className="preset-card">
<h3>方案1:保守配置(默认)</h3>
<p className="preset-desc">适合新手或稳健型交易者,风险较低,交易频率适中</p>
<div className="preset-params">
<ul>
<li><strong>扫描间隔</strong>: 3600秒(1小时)</li>
<li><strong>最小涨跌幅</strong>: 2.0%</li>
<li><strong>信号强度</strong>: 5/10</li>
<li><strong>处理交易对</strong>: 10个</li>
</ul>
</div>
<div className="preset-effect">
<strong>效果:</strong>每小时扫描一次,只捕捉2%以上的波动,信号质量高,胜率较高但交易机会较少
</div>
</div>
<div className="preset-card">
<h3>方案2:平衡配置(推荐)</h3>
<p className="preset-desc">平衡交易频率和信号质量,适合大多数交易者</p>
<div className="preset-params">
<ul>
<li><strong>扫描间隔</strong>: 600秒(10分钟)</li>
<li><strong>最小涨跌幅</strong>: 1.5%</li>
<li><strong>信号强度</strong>: 4/10</li>
<li><strong>处理交易对</strong>: 12个</li>
</ul>
</div>
<div className="preset-effect">
<strong>效果:</strong>10分钟扫描一次,捕捉1.5%以上的波动,交易机会增加,信号质量仍然较高
</div>
</div>
<div className="preset-card">
<h3>方案3:激进高频配置</h3>
<p className="preset-desc">适合晚间波动大时使用,交易频率高,需要密切监控</p>
<div className="preset-params">
<ul>
<li><strong>扫描间隔</strong>: 300秒(5分钟)</li>
<li><strong>最小涨跌幅</strong>: 1.0%</li>
<li><strong>信号强度</strong>: 3/10</li>
<li><strong>处理交易对</strong>: 20个</li>
</ul>
</div>
<div className="preset-effect">
<strong>效果:</strong>5分钟扫描一次,捕捉1%以上的波动,交易机会大幅增加,但需要监控胜率和手续费
</div>
<div className="preset-warning">
⚠️ <strong>风险提示:</strong>高频交易会增加手续费成本,建议在波动大的时段使用,并密切监控胜率
</div>
</div>
</section>
<section className="guide-section">
<h2>二、配置参数详细说明</h2>
<div className="config-detail">
<h3>市场扫描参数</h3>
<div className="param-item">
<h4>SCAN_INTERVAL(扫描间隔)</h4>
<p><strong>单位:</strong>秒</p>
<p><strong>默认值:</strong>3600(1小时)</p>
<p><strong>说明:</strong>系统每隔多长时间扫描一次市场,寻找交易机会</p>
<p><strong>影响:</strong></p>
<ul>
<li>值越小,扫描越频繁,能更快捕捉市场波动,但系统资源消耗增加</li>
<li>建议范围:300(5分钟)- 3600(1小时)</li>
<li>晚间波动大时建议:300-600秒</li>
<li>白天平稳时段建议:1800-3600秒</li>
</ul>
</div>
<div className="param-item">
<h4>MIN_CHANGE_PERCENT(最小涨跌幅阈值)</h4>
<p><strong>单位:</strong>百分比(前端显示为%,存储为小数)</p>
<p><strong>默认值:</strong>2.0(2%)</p>
<p><strong>说明:</strong>只有涨跌幅达到此阈值的交易对才会被考虑交易</p>
<p><strong>影响:</strong></p>
<ul>
<li>值越小,捕捉的交易机会越多,但可能包含更多噪音</li>
<li>值越大,只捕捉大幅波动,信号质量更高但机会更少</li>
<li>建议范围:1.0% - 3.0%</li>
<li>高频交易建议:1.0-1.5%</li>
<li>稳健交易建议:2.0-3.0%</li>
</ul>
</div>
<div className="param-item">
<h4>MIN_SIGNAL_STRENGTH(最小信号强度)</h4>
<p><strong>单位:</strong>0-10的整数</p>
<p><strong>默认值:</strong>5</p>
<p><strong>说明:</strong>技术指标综合评分,只有达到此强度的信号才会执行交易</p>
<p><strong>影响:</strong></p>
<ul>
<li>值越小,交易机会越多,但信号质量可能下降</li>
<li>值越大,只执行高质量信号,胜率更高但机会更少</li>
<li>建议范围:3-7</li>
<li>激进策略建议:3-4</li>
<li>稳健策略建议:5-7</li>
</ul>
</div>
<div className="param-item">
<h4>TOP_N_SYMBOLS(处理交易对数量)</h4>
<p><strong>单位:</strong>个</p>
<p><strong>默认值:</strong>10</p>
<p><strong>说明:</strong>每次扫描后,处理涨跌幅最大的前N个交易对</p>
<p><strong>影响:</strong></p>
<ul>
<li>值越大,处理的交易对越多,交易机会增加</li>
<li>但系统计算量增加,可能影响响应速度</li>
<li>建议范围:10-20</li>
</ul>
</div>
<div className="param-item">
<h4>MIN_VOLATILITY(最小波动率)</h4>
<p><strong>单位:</strong>百分比(小数形式)</p>
<p><strong>默认值:</strong>0.02(2%)</p>
<p><strong>说明:</strong>交易对的最小波动率要求,过滤掉波动过小的交易对</p>
<p><strong>影响:</strong></p>
<ul>
<li>值越小,允许更多交易对参与,但可能包含波动不足的交易对</li>
<li>建议范围:0.015(1.5%)- 0.025(2.5%)</li>
</ul>
</div>
<div className="param-item">
<h4>MIN_VOLUME_24H(最小24小时成交量)</h4>
<p><strong>单位:</strong>USDT</p>
<p><strong>默认值:</strong>10000000(1000万)</p>
<p><strong>说明:</strong>过滤掉成交量过小的交易对,确保流动性</p>
<p><strong>影响:</strong></p>
<ul>
<li>值越小,允许更多交易对,但可能包含流动性差的交易对</li>
<li>建议不要低于500万USDT</li>
</ul>
</div>
</div>
<div className="config-detail">
<h3>时间周期参数</h3>
<div className="param-item">
<h4>PRIMARY_INTERVAL(主周期)</h4>
<p><strong>默认值:</strong>1h</p>
<p><strong>说明:</strong>用于计算技术指标的主要时间周期</p>
<p><strong>选项:</strong>1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 1d</p>
<p><strong>影响:</strong></p>
<ul>
<li>周期越短,反应越快,但信号可能更频繁变化</li>
<li>周期越长,信号更稳定,但反应较慢</li>
<li>高频交易建议:15m-30m</li>
<li>稳健交易建议:1h-4h</li>
</ul>
</div>
<div className="param-item">
<h4>ENTRY_INTERVAL(入场周期)</h4>
<p><strong>默认值:</strong>15m</p>
<p><strong>说明:</strong>用于确定入场时机的K线周期</p>
<p><strong>影响:</strong></p>
<ul>
<li>周期越短,入场时机更精确,但可能产生更多假信号</li>
<li>建议:5m-15m</li>
</ul>
</div>
</div>
<div className="config-detail">
<h3>仓位控制参数</h3>
<div className="param-item">
<h4>MAX_POSITION_PERCENT(单笔最大仓位)</h4>
<p><strong>单位:</strong>账户余额的百分比</p>
<p><strong>默认值:</strong>0.05(5%)</p>
<p><strong>说明:</strong>单笔交易最多使用账户余额的百分比</p>
<p><strong>影响:</strong></p>
<ul>
<li>值越大,单笔交易金额越大,潜在收益和风险都增加</li>
<li>建议范围:3%-10%</li>
</ul>
</div>
<div className="param-item">
<h4>MAX_TOTAL_POSITION_PERCENT(总仓位上限)</h4>
<p><strong>单位:</strong>账户余额的百分比</p>
<p><strong>默认值:</strong>0.30(30%)</p>
<p><strong>说明:</strong>所有持仓的总价值不能超过账户余额的百分比</p>
<p><strong>影响:</strong></p>
<ul>
<li>值越大,可以同时持有更多仓位,但风险集中度增加</li>
<li>建议范围:20%-50%</li>
</ul>
</div>
</div>
<div className="config-detail">
<h3>风险控制参数</h3>
<div className="param-item">
<h4>STOP_LOSS_PERCENT(止损百分比)</h4>
<p><strong>单位:</strong>百分比</p>
<p><strong>默认值:</strong>0.03(3%)</p>
<p><strong>说明:</strong>当亏损达到此百分比时自动平仓止损</p>
<p><strong>影响:</strong></p>
<ul>
<li>值越小,止损更严格,单笔损失更小但可能被正常波动触发</li>
<li>建议范围:2%-5%</li>
</ul>
</div>
<div className="param-item">
<h4>TAKE_PROFIT_PERCENT(止盈百分比)</h4>
<p><strong>单位:</strong>百分比</p>
<p><strong>默认值:</strong>0.05(5%)</p>
<p><strong>说明:</strong>当盈利达到此百分比时自动平仓止盈</p>
<p><strong>影响:</strong></p>
<ul>
<li>值越大,目标利润更高,但可能错过及时止盈的机会</li>
<li>建议范围:3%-8%</li>
</ul>
</div>
</div>
</section>
<section className="guide-section">
<h2>三、使用建议</h2>
<div className="tips">
<h3>时段差异化配置</h3>
<p>建议在不同时段使用不同配置:</p>
<ul>
<li><strong>晚间波动时段(20:00-02:00 UTC+8)</strong>:使用激进高频配置,捕捉更多机会</li>
<li><strong>白天平稳时段</strong>:使用保守或平衡配置,确保信号质量</li>
</ul>
</div>
<div className="tips">
<h3>渐进式调整</h3>
<p>不要一次性大幅调整所有参数,建议:</p>
<ol>
<li>先调整SCAN_INTERVAL(从3600→1800→600→300)</li>
<li>观察1-2天效果</li>
<li>再调整MIN_CHANGE_PERCENT和MIN_SIGNAL_STRENGTH</li>
<li>根据实际表现微调</li>
</ol>
</div>
<div className="tips">
<h3>监控指标</h3>
<p>修改配置后,建议监控:</p>
<ul>
<li><strong>胜率</strong>:如果下降明显,需要提高MIN_SIGNAL_STRENGTH</li>
<li><strong>总盈亏</strong>:确保增加的交易次数带来正收益</li>
<li><strong>手续费</strong>:高频交易会增加手续费成本</li>
<li><strong>系统负载</strong>:确保服务器能承受更频繁的扫描</li>
</ul>
</div>
</section>
<section className="guide-section">
<h2>四、配置生效时间</h2>
<p>配置修改后:</p>
<ul>
<li>立即保存到数据库</li>
<li>交易系统会在<strong>下次扫描时</strong>自动重新加载配置</li>
<li>如果SCAN_INTERVAL是3600秒,最多等待1小时</li>
<li>如果改为300秒,最多等待5分钟</li>
<li>无需重启交易系统</li>
</ul>
</section>
</div>
</div>
)
}
export default ConfigGuide

View File

@ -9,6 +9,82 @@
margin-bottom: 2rem; margin-bottom: 2rem;
} }
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.guide-link {
color: #2196F3;
text-decoration: none;
font-size: 0.9rem;
padding: 0.5rem 1rem;
border: 1px solid #2196F3;
border-radius: 4px;
transition: all 0.3s;
}
.guide-link:hover {
background: #2196F3;
color: white;
}
.preset-section {
background: #f8f9fa;
padding: 1.5rem;
border-radius: 8px;
margin-top: 1.5rem;
}
.preset-section h3 {
margin: 0 0 1rem 0;
color: #34495e;
font-size: 1rem;
}
.preset-buttons {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.preset-btn {
flex: 1;
min-width: 200px;
padding: 1rem;
border: 2px solid #dee2e6;
background: white;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
text-align: left;
}
.preset-btn:hover:not(:disabled) {
border-color: #2196F3;
background: #e7f3ff;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.preset-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.preset-name {
font-weight: bold;
color: #2c3e50;
margin-bottom: 0.25rem;
}
.preset-desc {
font-size: 0.85rem;
color: #666;
}
.config-panel h2 { .config-panel h2 {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
color: #2c3e50; color: #2c3e50;
@ -128,6 +204,15 @@
font-size: 0.85rem; font-size: 0.85rem;
color: #888; color: #888;
font-style: italic; font-style: italic;
display: flex;
align-items: center;
gap: 0.25rem;
cursor: help;
}
.help-icon {
font-size: 0.75rem;
opacity: 0.6;
} }
.loading { .loading {

View File

@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { api } from '../services/api' import { api } from '../services/api'
import './ConfigPanel.css' import './ConfigPanel.css'
@ -8,6 +9,43 @@ const ConfigPanel = () => {
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const [message, setMessage] = useState('') const [message, setMessage] = useState('')
// 预设方案配置
const presets = {
conservative: {
name: '保守配置',
desc: '适合新手,风险较低,交易频率适中',
configs: {
SCAN_INTERVAL: 3600,
MIN_CHANGE_PERCENT: 2.0,
MIN_SIGNAL_STRENGTH: 5,
TOP_N_SYMBOLS: 10,
MIN_VOLATILITY: 0.02
}
},
balanced: {
name: '平衡配置',
desc: '推荐使用,平衡频率和质量',
configs: {
SCAN_INTERVAL: 600,
MIN_CHANGE_PERCENT: 1.5,
MIN_SIGNAL_STRENGTH: 4,
TOP_N_SYMBOLS: 12,
MIN_VOLATILITY: 0.018
}
},
aggressive: {
name: '激进高频',
desc: '晚间波动大时使用,交易频率高',
configs: {
SCAN_INTERVAL: 300,
MIN_CHANGE_PERCENT: 1.0,
MIN_SIGNAL_STRENGTH: 3,
TOP_N_SYMBOLS: 20,
MIN_VOLATILITY: 0.015
}
}
}
useEffect(() => { useEffect(() => {
loadConfigs() loadConfigs()
}, []) }, [])
@ -50,6 +88,40 @@ const ConfigPanel = () => {
} }
} }
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) return null
return {
key,
value: key.includes('PERCENT') ? 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)
}
}
if (loading) return <div className="loading">加载中...</div> if (loading) return <div className="loading">加载中...</div>
const configCategories = { const configCategories = {
@ -63,10 +135,32 @@ const ConfigPanel = () => {
return ( return (
<div className="config-panel"> <div className="config-panel">
<div className="config-header"> <div className="config-header">
<div className="header-top">
<h2>交易配置</h2> <h2>交易配置</h2>
<Link to="/config/guide" className="guide-link">📖 配置说明</Link>
</div>
<div className="config-info"> <div className="config-info">
<p>修改配置后,交易系统将在下次扫描时自动使用新配置</p> <p>修改配置后,交易系统将在下次扫描时自动使用新配置</p>
</div> </div>
{/* 预设方案快速切换 */}
<div className="preset-section">
<h3>快速切换方案</h3>
<div className="preset-buttons">
{Object.entries(presets).map(([key, preset]) => (
<button
key={key}
className="preset-btn"
onClick={() => applyPreset(key)}
disabled={saving}
title={preset.desc}
>
<div className="preset-name">{preset.name}</div>
<div className="preset-desc">{preset.desc}</div>
</button>
))}
</div>
</div>
</div> </div>
{message && ( {message && (
<div className={`message ${message.includes('失败') || message.includes('错误') ? 'error' : 'success'}`}> <div className={`message ${message.includes('失败') || message.includes('错误') ? 'error' : 'success'}`}>
@ -155,7 +249,12 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
<option value="true">是</option> <option value="true">是</option>
<option value="false">否</option> <option value="false">否</option>
</select> </select>
{config.description && <span className="description">{config.description}</span>} {config.description && (
<span className="description" title={getConfigDetail(label)}>
{config.description}
<span className="help-icon" title="悬停查看详细说明">ℹ️</span>
</span>
)}
</div> </div>
) )
} }
@ -178,7 +277,12 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
<option key={opt} value={opt}>{opt}</option> <option key={opt} value={opt}>{opt}</option>
))} ))}
</select> </select>
{config.description && <span className="description">{config.description}</span>} {config.description && (
<span className="description" title={getConfigDetail(label)}>
{config.description}
<span className="help-icon" title="悬停查看详细说明">ℹ️</span>
</span>
)}
</div> </div>
) )
} }
@ -202,9 +306,33 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
className={isEditing ? 'editing' : ''} className={isEditing ? 'editing' : ''}
/> />
{isEditing && <span className="edit-hint">按Enter保存</span>} {isEditing && <span className="edit-hint">按Enter保存</span>}
{config.description && <span className="description">{config.description}</span>} {config.description && (
<span className="description" title={getConfigDetail(label)}>
{config.description}
<span className="help-icon" title="悬停查看详细说明">ℹ️</span>
</span>
)}
</div> </div>
) )
} }
// 配置项详细说明
const getConfigDetail = (key) => {
const details = {
'SCAN_INTERVAL': '扫描间隔(秒)。值越小扫描越频繁,能更快捕捉波动,但资源消耗增加。建议:300-3600秒',
'MIN_CHANGE_PERCENT': '最小涨跌幅阈值(%)。值越小捕捉机会越多,但可能包含噪音。建议:1.0%-3.0%',
'MIN_SIGNAL_STRENGTH': '最小信号强度(0-10)。值越小交易机会越多,但信号质量可能下降。建议:3-7',
'TOP_N_SYMBOLS': '每次处理的交易对数量。值越大机会越多,但计算量增加。建议:10-20',
'MIN_VOLATILITY': '最小波动率(小数)。值越小允许更多交易对。建议:0.015-0.025',
'MIN_VOLUME_24H': '最小24小时成交量(USDT)。过滤流动性差的交易对。建议:≥500万',
'PRIMARY_INTERVAL': '主周期。用于计算技术指标。周期越短反应越快。建议:15m-1h',
'ENTRY_INTERVAL': '入场周期。用于确定入场时机。建议:5m-15m',
'MAX_POSITION_PERCENT': '单笔最大仓位(账户余额%)。值越大单笔金额越大。建议:3%-10%',
'MAX_TOTAL_POSITION_PERCENT': '总仓位上限(账户余额%)。值越大可同时持有更多仓位。建议:20%-50%',
'STOP_LOSS_PERCENT': '止损百分比。值越小止损更严格。建议:2%-5%',
'TAKE_PROFIT_PERCENT': '止盈百分比。值越大目标利润更高。建议:3%-8%'
}
return details[key] || '暂无详细说明'
}
export default ConfigPanel export default ConfigPanel