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 { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom'
import ConfigPanel from './components/ConfigPanel'
import ConfigGuide from './components/ConfigGuide'
import TradeList from './components/TradeList'
import StatsDashboard from './components/StatsDashboard'
import './App.css'
@ -24,6 +25,7 @@ function App() {
<Routes>
<Route path="/" element={<StatsDashboard />} />
<Route path="/config" element={<ConfigPanel />} />
<Route path="/config/guide" element={<ConfigGuide />} />
<Route path="/trades" element={<TradeList />} />
</Routes>
</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>: 36001小时</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>: 60010分钟</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>: 3005分钟</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>36001小时</p>
<p><strong>说明</strong>系统每隔多长时间扫描一次市场寻找交易机会</p>
<p><strong>影响</strong></p>
<ul>
<li>值越小扫描越频繁能更快捕捉市场波动但系统资源消耗增加</li>
<li>建议范围3005分钟- 36001小时</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.02%</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.022%</p>
<p><strong>说明</strong>交易对的最小波动率要求过滤掉波动过小的交易对</p>
<p><strong>影响</strong></p>
<ul>
<li>值越小允许更多交易对参与但可能包含波动不足的交易对</li>
<li>建议范围0.0151.5%- 0.0252.5%</li>
</ul>
</div>
<div className="param-item">
<h4>MIN_VOLUME_24H最小24小时成交量</h4>
<p><strong>单位</strong>USDT</p>
<p><strong>默认值</strong>100000001000</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.055%</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.3030%</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.033%</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.055%</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从36001800600300</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;
}
.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 {
margin-bottom: 0.5rem;
color: #2c3e50;
@ -128,6 +204,15 @@
font-size: 0.85rem;
color: #888;
font-style: italic;
display: flex;
align-items: center;
gap: 0.25rem;
cursor: help;
}
.help-icon {
font-size: 0.75rem;
opacity: 0.6;
}
.loading {

View File

@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { api } from '../services/api'
import './ConfigPanel.css'
@ -7,6 +8,43 @@ const ConfigPanel = () => {
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
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(() => {
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>
const configCategories = {
@ -63,10 +135,32 @@ const ConfigPanel = () => {
return (
<div className="config-panel">
<div className="config-header">
<h2>交易配置</h2>
<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">
<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>
{message && (
<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="false"></option>
</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>
)
}
@ -178,7 +277,12 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
<option key={opt} value={opt}>{opt}</option>
))}
</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>
)
}
@ -202,9 +306,33 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
className={isEditing ? 'editing' : ''}
/>
{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>
)
}
//
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