我的交易进程(当前账号 #{accountId})
@@ -985,7 +975,7 @@ const ConfigPanel = ({ currentUser }) => {
) : null}
{/* 账号密钥(当前账号;owner/admin 可改) */}
- {currentAccountMeta && String(currentAccountMeta?.status || 'active') === 'active' ? (
+ {currentAccountMeta && currentAccountMeta.status === 'active' ? (
账号密钥(当前账号)
@@ -1056,89 +1046,6 @@ const ConfigPanel = ({ currentUser }) => {
) : null}
- {/* 系统控制:清缓存 / 启停 / 重启(supervisor) */}
- {isAdmin ? (
-
-
-
系统控制
-
-
- {systemStatus?.running ? '运行中' : '未运行'}
-
- {systemStatus?.pid ? PID: {systemStatus.pid} : null}
- {systemStatus?.program ? 程序: {systemStatus.program} : null}
- {systemStatus?.meta?.requested_at ? 上次重启: {systemStatus.meta.requested_at} : null}
-
-
-
-
-
-
-
-
-
-
-
-
- 后端 {backendStatus?.running ? '运行中' : '未知'}
-
- {backendStatus?.pid ? PID: {backendStatus.pid} : null}
- {backendStatus?.meta?.requested_at ? 上次重启: {backendStatus.meta.requested_at} : null}
-
-
- 建议流程:先更新配置里的 Key → 点击“清除缓存” → 点击“重启交易系统”,确保不再使用旧账号下单。
-
-
- ) : null}
-
{/* 账号管理(超管) */}
{isAdmin ? (
@@ -1406,106 +1313,8 @@ const ConfigPanel = ({ currentUser }) => {
) : null}
- {/* 预设方案快速切换(仅管理员 + 全局策略账号:策略核心统一管理) */}
- {isAdmin && isGlobalStrategyAccount && currentAccountMeta && String(currentAccountMeta?.status || 'active') === 'active' ? (
-
-
-
快速切换方案
-
- 当前方案:
-
- {currentPreset ? presets[currentPreset].name : '自定义'}
-
-
-
-
-
怎么选更不迷糊
-
- -
- 先选入场机制:纯限价(更控频但可能撤单) 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.presetKeys
- .filter((k) => presets[k])
- .map((k) => {
- const preset = presets[k]
- const meta = presetUiMeta[k] || { group: g.key, tag: '' }
- return (
-
- )
- })}
-
-
- ))}
-
- )
- })()}
-
- ) : (
+ {/* 用户提示 */}
+ {!isAdmin && (
平台已开启“傻瓜化模式”:策略核心由管理员统一管理。你只需要配置密钥、充值余额,并调整少量风控参数(如最小/最大仓位、每日开仓次数等)。
@@ -1513,7 +1322,7 @@ const ConfigPanel = ({ currentUser }) => {
{/* 配置可行性检查提示 */}
- {currentAccountMeta && String(currentAccountMeta?.status || 'active') === 'active' && feasibilityCheck && (
+ {currentAccountMeta && currentAccountMeta.status === 'active' && feasibilityCheck && (
@@ -1639,7 +1448,7 @@ const ConfigPanel = ({ currentUser }) => {
)}
{/* 配置项(禁用account时不显示) */}
- {currentAccountMeta && String(currentAccountMeta?.status || 'active') === 'active' ? (
+ {currentAccountMeta && currentAccountMeta.status === 'active' ? (
Object.entries(configCategories).map(([category, label]) => (
{label}
@@ -1663,56 +1472,13 @@ const ConfigPanel = ({ currentUser }) => {
))
- ) : currentAccountMeta && String(currentAccountMeta?.status || 'active') === 'disabled' ? (
+ ) : currentAccountMeta && currentAccountMeta.status === 'disabled' ? (
当前账号已禁用,无法查看和修改配置
) : null}
- {showSnapshot && (
-
setShowSnapshot(false)} role="presentation">
-
e.stopPropagation()}>
-
-
-
当前整体配置快照
-
- 默认脱敏 BINANCE_API_KEY/SECRET。你可以选择明文后重新生成再复制/下载。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{snapshotText}
-
-
- )}
+ {/* 配置快照已移至全局配置页面 */}
)
}
diff --git a/frontend/src/components/GlobalConfig.jsx b/frontend/src/components/GlobalConfig.jsx
index fd50c40..33a900b 100644
--- a/frontend/src/components/GlobalConfig.jsx
+++ b/frontend/src/components/GlobalConfig.jsx
@@ -1,6 +1,8 @@
-import React, { useState, useEffect } from 'react'
-import { api } from '../services/api'
+import React, { useState, useEffect, useRef } from 'react'
+import { Link } from 'react-router-dom'
+import { api, getCurrentAccountId } from '../services/api'
import './GlobalConfig.css'
+import './ConfigPanel.css' // 复用 ConfigPanel 的样式
const GlobalConfig = ({ currentUser }) => {
const [users, setUsers] = useState([])
@@ -12,10 +14,191 @@ const GlobalConfig = ({ currentUser }) => {
const [showUserForm, setShowUserForm] = useState(false)
const [newUser, setNewUser] = useState({ username: '', password: '', role: 'user', status: 'active' })
const [editingUserId, setEditingUserId] = useState(null)
+
+ // 系统控制相关
+ const [systemStatus, setSystemStatus] = useState(null)
+ const [backendStatus, setBackendStatus] = useState(null)
+ const [systemBusy, setSystemBusy] = useState(false)
+
+ // 预设方案相关
+ const [configs, setConfigs] = useState({})
+ const [saving, setSaving] = useState(false)
+ const [currentAccountId, setCurrentAccountId] = useState(getCurrentAccountId())
+ const [configMeta, setConfigMeta] = useState(null)
+
+ // 配置快照相关
+ const [showSnapshot, setShowSnapshot] = useState(false)
+ const [snapshotText, setSnapshotText] = useState('')
+ const [snapshotIncludeSecrets, setSnapshotIncludeSecrets] = useState(false)
+ const [snapshotBusy, setSnapshotBusy] = useState(false)
+
+ const PCT_LIKE_KEYS = new Set([
+ 'LIMIT_ORDER_OFFSET_PCT',
+ 'ENTRY_MAX_DRIFT_PCT_TRENDING',
+ 'ENTRY_MAX_DRIFT_PCT_RANGING',
+ ])
+
+ const isAdmin = (currentUser?.role || '') === 'admin'
+ const globalStrategyAccountId = parseInt(String(configMeta?.global_strategy_account_id || '1'), 10) || 1
+ const isGlobalStrategyAccount = isAdmin && currentAccountId === globalStrategyAccountId
+
+ // 预设方案配置
+ const presets = {
+ swing: {
+ name: '波段回归(推荐)',
+ desc: '根治高频与追价:关闭智能入场,回归"纯限价 + 30分钟扫描 + 更高信号门槛"的低频波段。建议先跑20-30单再评估。',
+ configs: {
+ SCAN_INTERVAL: 1800,
+ TOP_N_SYMBOLS: 8,
+ MAX_POSITION_PERCENT: 2.0,
+ MAX_TOTAL_POSITION_PERCENT: 20.0,
+ MIN_POSITION_PERCENT: 0.0,
+ MIN_SIGNAL_STRENGTH: 8,
+ USE_TRAILING_STOP: false,
+ ATR_TAKE_PROFIT_MULTIPLIER: 4.5,
+ TAKE_PROFIT_PERCENT: 25.0,
+ MIN_HOLD_TIME_SEC: 1800,
+ SMART_ENTRY_ENABLED: false,
+ },
+ },
+ fill: {
+ name: '成交优先(更少漏单)',
+ desc: '优先解决"挂单NEW→超时撤单→没成交"的问题:解锁自动交易过滤 + 保守智能入场(限制追价步数与追价上限),在趋势强时允许可控的市价兜底。',
+ configs: {
+ SCAN_INTERVAL: 1800,
+ TOP_N_SYMBOLS: 6,
+ MIN_SIGNAL_STRENGTH: 7,
+ AUTO_TRADE_ONLY_TRENDING: false,
+ AUTO_TRADE_ALLOW_4H_NEUTRAL: true,
+ SMART_ENTRY_ENABLED: true,
+ LIMIT_ORDER_OFFSET_PCT: 0.1,
+ ENTRY_CONFIRM_TIMEOUT_SEC: 120,
+ ENTRY_CHASE_MAX_STEPS: 2,
+ ENTRY_STEP_WAIT_SEC: 20,
+ ENTRY_MARKET_FALLBACK_AFTER_SEC: 60,
+ ENTRY_MAX_DRIFT_PCT_TRENDING: 0.3,
+ ENTRY_MAX_DRIFT_PCT_RANGING: 0.15,
+ USE_TRAILING_STOP: false,
+ ATR_TAKE_PROFIT_MULTIPLIER: 4.5,
+ TAKE_PROFIT_PERCENT: 25.0,
+ MIN_HOLD_TIME_SEC: 1800,
+ },
+ },
+ strict: {
+ name: '精选低频(高胜率倾向)',
+ desc: '更偏"少单、质量优先":仅趋势行情自动交易 + 4H中性不自动下单 + 更高信号门槛。仍保持较贴近的限价偏移,减少"完全成交不了"。',
+ configs: {
+ SCAN_INTERVAL: 1800,
+ TOP_N_SYMBOLS: 6,
+ MIN_SIGNAL_STRENGTH: 8,
+ AUTO_TRADE_ONLY_TRENDING: true,
+ AUTO_TRADE_ALLOW_4H_NEUTRAL: false,
+ SMART_ENTRY_ENABLED: false,
+ LIMIT_ORDER_OFFSET_PCT: 0.1,
+ ENTRY_CONFIRM_TIMEOUT_SEC: 180,
+ USE_TRAILING_STOP: false,
+ ATR_TAKE_PROFIT_MULTIPLIER: 4.5,
+ TAKE_PROFIT_PERCENT: 25.0,
+ MIN_HOLD_TIME_SEC: 1800,
+ },
+ },
+ steady: {
+ name: '稳定出单(均衡收益/频率)',
+ desc: '在"会下单"的基础上略提高出单频率:更短扫描间隔 + 更宽松门槛 + 保守智能入场(追价受限),适合想要稳定有单但不想回到高频。',
+ configs: {
+ SCAN_INTERVAL: 900,
+ TOP_N_SYMBOLS: 8,
+ MIN_SIGNAL_STRENGTH: 6,
+ AUTO_TRADE_ONLY_TRENDING: false,
+ AUTO_TRADE_ALLOW_4H_NEUTRAL: true,
+ SMART_ENTRY_ENABLED: true,
+ LIMIT_ORDER_OFFSET_PCT: 0.12,
+ ENTRY_CONFIRM_TIMEOUT_SEC: 120,
+ ENTRY_CHASE_MAX_STEPS: 3,
+ ENTRY_STEP_WAIT_SEC: 15,
+ ENTRY_MARKET_FALLBACK_AFTER_SEC: 45,
+ ENTRY_MAX_DRIFT_PCT_TRENDING: 0.4,
+ ENTRY_MAX_DRIFT_PCT_RANGING: 0.2,
+ USE_TRAILING_STOP: false,
+ ATR_TAKE_PROFIT_MULTIPLIER: 4.5,
+ TAKE_PROFIT_PERCENT: 25.0,
+ MIN_HOLD_TIME_SEC: 1800,
+ },
+ },
+ conservative: {
+ name: '保守配置',
+ desc: '适合新手,风险较低,止损止盈较宽松,避免被正常波动触发',
+ configs: {
+ SCAN_INTERVAL: 900,
+ MIN_CHANGE_PERCENT: 2.0,
+ MIN_SIGNAL_STRENGTH: 5,
+ TOP_N_SYMBOLS: 10,
+ MAX_SCAN_SYMBOLS: 150,
+ MIN_VOLATILITY: 0.02,
+ STOP_LOSS_PERCENT: 10.0,
+ TAKE_PROFIT_PERCENT: 25.0,
+ MIN_STOP_LOSS_PRICE_PCT: 2.0,
+ MIN_TAKE_PROFIT_PRICE_PCT: 3.0,
+ USE_TRAILING_STOP: false,
+ ATR_TAKE_PROFIT_MULTIPLIER: 4.5,
+ MIN_HOLD_TIME_SEC: 1800
+ }
+ },
+ balanced: {
+ name: '平衡配置',
+ desc: '推荐使用,平衡频率和质量,止损止盈适中(盈亏比2.5:1)',
+ configs: {
+ SCAN_INTERVAL: 600,
+ MIN_CHANGE_PERCENT: 1.5,
+ MIN_SIGNAL_STRENGTH: 4,
+ TOP_N_SYMBOLS: 12,
+ MAX_SCAN_SYMBOLS: 250,
+ MIN_VOLATILITY: 0.018,
+ STOP_LOSS_PERCENT: 8.0,
+ TAKE_PROFIT_PERCENT: 25.0,
+ MIN_STOP_LOSS_PRICE_PCT: 2.0,
+ MIN_TAKE_PROFIT_PRICE_PCT: 3.0,
+ USE_TRAILING_STOP: false,
+ ATR_TAKE_PROFIT_MULTIPLIER: 4.5,
+ MIN_HOLD_TIME_SEC: 1800
+ }
+ },
+ aggressive: {
+ name: '激进高频',
+ desc: '晚间波动大时使用,交易频率高,止损较紧但止盈合理(盈亏比3:1)',
+ configs: {
+ SCAN_INTERVAL: 300,
+ MIN_CHANGE_PERCENT: 1.0,
+ MIN_SIGNAL_STRENGTH: 3,
+ TOP_N_SYMBOLS: 18,
+ MAX_SCAN_SYMBOLS: 350,
+ MIN_VOLATILITY: 0.015,
+ STOP_LOSS_PERCENT: 5.0,
+ TAKE_PROFIT_PERCENT: 25.0,
+ MIN_STOP_LOSS_PRICE_PCT: 1.5,
+ MIN_TAKE_PROFIT_PRICE_PCT: 2.0,
+ USE_TRAILING_STOP: false,
+ ATR_TAKE_PROFIT_MULTIPLIER: 4.5,
+ MIN_HOLD_TIME_SEC: 1800
+ }
+ }
+ }
useEffect(() => {
loadUsers()
loadAccounts()
+ if (isAdmin) {
+ loadConfigMeta()
+ loadConfigs()
+ loadSystemStatus()
+ loadBackendStatus()
+
+ const timer = setInterval(() => {
+ loadSystemStatus()
+ loadBackendStatus()
+ }, 3000)
+ return () => clearInterval(timer)
+ }
}, [])
const loadUsers = async () => {
@@ -38,6 +221,364 @@ const GlobalConfig = ({ currentUser }) => {
}
}
+ const loadConfigMeta = async () => {
+ try {
+ const m = await api.getConfigMeta()
+ setConfigMeta(m || null)
+ } catch (e) {
+ setConfigMeta(null)
+ }
+ }
+
+ const loadConfigs = async () => {
+ try {
+ const data = await api.getConfigs()
+ setConfigs(data)
+ } catch (error) {
+ console.error('Failed to load configs:', error)
+ }
+ }
+
+ const loadSystemStatus = async () => {
+ try {
+ const res = await api.getTradingSystemStatus()
+ setSystemStatus(res)
+ } catch (error) {
+ // 静默失败
+ }
+ }
+
+ const loadBackendStatus = async () => {
+ try {
+ const res = await api.getBackendStatus()
+ setBackendStatus(res)
+ } catch (error) {
+ // 静默失败
+ }
+ }
+
+ // 系统控制函数
+ const handleClearCache = async () => {
+ setSystemBusy(true)
+ setMessage('')
+ try {
+ const res = await api.clearSystemCache()
+ setMessage(res.message || '缓存已清理')
+ await loadConfigs()
+ await loadSystemStatus()
+ } catch (error) {
+ setMessage('清理缓存失败: ' + (error.message || '未知错误'))
+ } finally {
+ setSystemBusy(false)
+ }
+ }
+
+ const handleStartTrading = async () => {
+ setSystemBusy(true)
+ setMessage('')
+ try {
+ const res = await api.startTradingSystem()
+ setMessage(res.message || '交易系统已启动')
+ await loadSystemStatus()
+ } catch (error) {
+ setMessage('启动失败: ' + (error.message || '未知错误'))
+ } finally {
+ setSystemBusy(false)
+ }
+ }
+
+ const handleStopTrading = async () => {
+ setSystemBusy(true)
+ setMessage('')
+ try {
+ const res = await api.stopTradingSystem()
+ setMessage(res.message || '交易系统已停止')
+ await loadSystemStatus()
+ } catch (error) {
+ setMessage('停止失败: ' + (error.message || '未知错误'))
+ } finally {
+ setSystemBusy(false)
+ }
+ }
+
+ const handleRestartTrading = async () => {
+ setSystemBusy(true)
+ setMessage('')
+ try {
+ const res = await api.restartTradingSystem()
+ setMessage(res.message || '交易系统已重启')
+ await loadSystemStatus()
+ } catch (error) {
+ setMessage('重启失败: ' + (error.message || '未知错误'))
+ } finally {
+ setSystemBusy(false)
+ }
+ }
+
+ const handleRestartBackend = async () => {
+ if (!window.confirm('确定要重启后端服务吗?重启期间页面接口会短暂不可用(约 3-10 秒)。')) return
+ setSystemBusy(true)
+ setMessage('')
+ try {
+ const res = await api.restartBackend()
+ setMessage(res.message || '已发起后端重启')
+ setTimeout(() => {
+ loadBackendStatus()
+ }, 4000)
+ } catch (error) {
+ setMessage('重启后端失败: ' + (error.message || '未知错误'))
+ } finally {
+ setSystemBusy(false)
+ }
+ }
+
+ const handleRestartAllTrading = async () => {
+ if (!window.confirm('确定要重启【所有账号】的交易进程吗?这会让所有用户的交易服务短暂中断(约 3-10 秒),用于升级代码后统一生效。')) return
+ setSystemBusy(true)
+ setMessage('')
+ try {
+ const res = await api.restartAllTradingSystems({ prefix: 'auto_sys_acc', do_update: true })
+ setMessage(`已发起批量重启:共 ${res.count} 个,成功 ${res.ok},失败 ${res.failed}`)
+ } catch (e) {
+ setMessage('批量重启失败: ' + (e?.message || '未知错误'))
+ } finally {
+ setSystemBusy(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')) {
+ if (PCT_LIKE_KEYS.has(key)) {
+ currentValue = currentValue <= 0.05 ? currentValue * 100 : currentValue
+ } else {
+ 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) {
+ let type = 'number'
+ let category = 'risk'
+ if (typeof value === 'boolean') {
+ type = 'boolean'
+ category = 'strategy'
+ }
+ 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'
+ }
+ } 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')) && !PCT_LIKE_KEYS.has(key) ? value / 100 : value,
+ type,
+ category,
+ description: `预设方案配置项:${key}`
+ }
+ }
+ return {
+ key,
+ value: (key.includes('PERCENT') || key.includes('PCT')) && !PCT_LIKE_KEYS.has(key) ? 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 isSecretKey = (key) => {
+ return key === 'BINANCE_API_KEY' || key === 'BINANCE_API_SECRET'
+ }
+
+ const maskSecret = (val) => {
+ const s = val === null || val === undefined ? '' : String(val)
+ if (!s) return ''
+ if (s.length <= 8) return '****'
+ return `${s.slice(0, 4)}...${s.slice(-4)}`
+ }
+
+ const toDisplayValueForSnapshot = (key, value) => {
+ if (value === null || value === undefined) return value
+ if (typeof value === 'number' && (key.includes('PERCENT') || key.includes('PCT'))) {
+ if (PCT_LIKE_KEYS.has(key)) {
+ return value <= 0.05 ? value * 100 : value
+ }
+ return value < 1 ? value * 100 : value
+ }
+ return value
+ }
+
+ const buildConfigSnapshot = async (includeSecrets) => {
+ const data = await api.getConfigs()
+ const now = new Date()
+
+ const categoryMap = {
+ scan: '市场扫描',
+ position: '仓位控制',
+ risk: '风险控制',
+ strategy: '策略参数',
+ api: 'API配置',
+ }
+
+ const entries = Object.entries(data || {}).map(([key, cfg]) => {
+ const rawVal = cfg?.value
+ const valMasked = isSecretKey(key) && !includeSecrets ? maskSecret(rawVal) : rawVal
+ const displayVal = toDisplayValueForSnapshot(key, valMasked)
+ return {
+ key,
+ category: cfg?.category || '',
+ category_label: categoryMap[cfg?.category] || cfg?.category || '',
+ type: cfg?.type || '',
+ value: valMasked,
+ display_value: displayVal,
+ description: cfg?.description || '',
+ }
+ })
+
+ entries.sort((a, b) => {
+ const ca = a.category_label || a.category || ''
+ const cb = b.category_label || b.category || ''
+ if (ca !== cb) return ca.localeCompare(cb)
+ return a.key.localeCompare(b.key)
+ })
+
+ const snapshot = {
+ fetched_at: now.toISOString(),
+ note: 'display_value 对 PERCENT/PCT 做了百分比换算;敏感字段可选择脱敏/明文。',
+ preset_detected: detectCurrentPreset(),
+ system_status: systemStatus ? {
+ running: !!systemStatus.running,
+ pid: systemStatus.pid || null,
+ program: systemStatus.program || null,
+ state: systemStatus.state || null,
+ } : null,
+ configs: entries,
+ }
+
+ return JSON.stringify(snapshot, null, 2)
+ }
+
+ const openSnapshot = async (includeSecrets) => {
+ setSnapshotBusy(true)
+ setMessage('')
+ try {
+ const text = await buildConfigSnapshot(includeSecrets)
+ setSnapshotText(text)
+ setShowSnapshot(true)
+ } catch (e) {
+ setMessage('生成配置快照失败: ' + (e?.message || '未知错误'))
+ } finally {
+ setSnapshotBusy(false)
+ }
+ }
+
+ const copySnapshot = async () => {
+ try {
+ if (navigator?.clipboard?.writeText) {
+ await navigator.clipboard.writeText(snapshotText || '')
+ setMessage('已复制配置快照到剪贴板')
+ } else {
+ setMessage('当前浏览器不支持剪贴板 API,可手动全选复制')
+ }
+ } catch (e) {
+ setMessage('复制失败: ' + (e?.message || '未知错误'))
+ }
+ }
+
+ const downloadSnapshot = () => {
+ try {
+ const blob = new Blob([snapshotText || ''], { type: 'application/json;charset=utf-8' })
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement('a')
+ a.href = url
+ a.download = `config-snapshot-${new Date().toISOString().replace(/[:.]/g, '-')}.json`
+ document.body.appendChild(a)
+ a.click()
+ a.remove()
+ URL.revokeObjectURL(url)
+ } catch (e) {
+ setMessage('下载失败: ' + (e?.message || '未知错误'))
+ }
+ }
+
+ const currentPreset = detectCurrentPreset()
+
const handleCreateUser = async () => {
if (!newUser.username || !newUser.password) {
setMessage('用户名和密码不能为空')
@@ -112,11 +653,58 @@ const GlobalConfig = ({ currentUser }) => {
return
加载中...
}
+ 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 presetGroups = [
+ {
+ 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 (
-
全局配置
-
管理用户、账号和全局策略配置
+
+
+
全局配置
+
管理用户、账号和全局策略配置
+
+
+
+ 📖 配置说明
+
+
{message && (
@@ -125,6 +713,155 @@ const GlobalConfig = ({ currentUser }) => {
)}
+ {/* 系统控制 */}
+ {isAdmin && (
+
+
+
系统控制
+
+
+ {systemStatus?.running ? '运行中' : '未运行'}
+
+ {systemStatus?.pid ? PID: {systemStatus.pid} : null}
+ {systemStatus?.program ? 程序: {systemStatus.program} : null}
+ {systemStatus?.meta?.requested_at ? 上次重启: {systemStatus.meta.requested_at} : null}
+
+
+
+
+
+
+
+
+
+
+
+
+ 后端 {backendStatus?.running ? '运行中' : '未知'}
+
+ {backendStatus?.pid ? PID: {backendStatus.pid} : null}
+ {backendStatus?.meta?.requested_at ? 上次重启: {backendStatus.meta.requested_at} : null}
+
+
+ 建议流程:先更新配置里的 Key → 点击"清除缓存" → 点击"重启交易系统",确保不再使用旧账号下单。
+
+
+ )}
+
+ {/* 预设方案快速切换(仅管理员 + 全局策略账号) */}
+ {isAdmin && isGlobalStrategyAccount && (
+
+
+
快速切换方案
+
+ 当前方案:
+
+ {currentPreset ? presets[currentPreset].name : '自定义'}
+
+
+
+
+
怎么选更不迷糊
+
+ -
+ 先选入场机制:纯限价(更控频但可能撤单) vs 智能入场(更少漏单但需限制追价)。
+
+ -
+ 再看"会不会下单":如果你发现几乎不出单,优先把
AUTO_TRADE_ONLY_TRENDING 关掉、把 AUTO_TRADE_ALLOW_4H_NEUTRAL 打开。
+
+ -
+ 最后再微调:想更容易成交 → 调小
LIMIT_ORDER_OFFSET_PCT、调大 ENTRY_CONFIRM_TIMEOUT_SEC。
+
+
+
+
+
+ {presetGroups.map((g) => (
+
+
+
+ {g.presetKeys
+ .filter((k) => presets[k])
+ .map((k) => {
+ const preset = presets[k]
+ const meta = presetUiMeta[k] || { group: g.key, tag: '' }
+ return (
+
+ )
+ })}
+
+
+ ))}
+
+
+ )}
+
{/* 用户管理 */}
@@ -321,6 +1058,51 @@ const GlobalConfig = ({ currentUser }) => {
+
+ {/* 配置快照 Modal */}
+ {showSnapshot && (
+
setShowSnapshot(false)} role="presentation">
+
e.stopPropagation()}>
+
+
+
当前整体配置快照
+
+ 默认脱敏 BINANCE_API_KEY/SECRET。你可以选择明文后重新生成再复制/下载。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{snapshotText}
+
+
+ )}