import React, { useState, useEffect } from 'react' import { api } from '../services/api' import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts' import './StatsDashboard.css' const StatsDashboard = () => { const [dashboardData, setDashboardData] = useState(null) const [loading, setLoading] = useState(true) const [closingSymbol, setClosingSymbol] = useState(null) const [message, setMessage] = useState('') const [tradingConfig, setTradingConfig] = useState(null) useEffect(() => { loadDashboard() loadTradingConfig() const interval = setInterval(() => { loadDashboard() loadTradingConfig() // 同时刷新配置 }, 30000) // 每30秒刷新 return () => clearInterval(interval) }, []) const loadTradingConfig = async () => { try { const configs = await api.getConfigs() setTradingConfig(configs) } catch (error) { console.error('Failed to load trading config:', error) } } const loadDashboard = async () => { try { const data = await api.getDashboard() setDashboardData(data) // 如果dashboard数据中包含配置,也更新配置状态 if (data.trading_config) { setTradingConfig(data.trading_config) } } catch (error) { console.error('Failed to load dashboard:', error) } finally { setLoading(false) } } const handleClosePosition = async (symbol) => { if (!window.confirm(`确定要平仓 ${symbol} 吗?`)) { return } setClosingSymbol(symbol) setMessage('') try { console.log(`开始平仓 ${symbol}...`) const result = await api.closePosition(symbol) console.log('平仓结果:', result) setMessage(result.message || `${symbol} 平仓成功`) // 立即刷新数据 await loadDashboard() // 3秒后清除消息 setTimeout(() => { setMessage('') }, 3000) } catch (error) { console.error('Close position error:', error) const errorMessage = error.message || error.toString() || '平仓失败,请检查网络连接或后端服务' setMessage(`平仓失败: ${errorMessage}`) // 错误消息5秒后清除 setTimeout(() => { setMessage('') }, 5000) } finally { setClosingSymbol(null) } } const handleSyncPositions = async () => { if (!window.confirm('确定要同步持仓状态吗?这将检查币安实际持仓并更新数据库状态。')) { return } setMessage('正在同步持仓状态...') try { const result = await api.syncPositions() console.log('同步结果:', result) let message = result.message || '同步完成' if (result.updated_to_closed > 0) { message += `,已更新 ${result.updated_to_closed} 条记录为已平仓` } if (result.missing_in_db && result.missing_in_db.length > 0) { message += `,发现 ${result.missing_in_db.length} 个币安持仓在数据库中没有记录` } setMessage(message) // 立即刷新数据 await loadDashboard() // 5秒后清除消息 setTimeout(() => { setMessage('') }, 5000) } catch (error) { console.error('Sync positions error:', error) const errorMessage = error.message || error.toString() || '同步失败,请检查网络连接或后端服务' setMessage(`同步失败: ${errorMessage}`) // 错误消息5秒后清除 setTimeout(() => { setMessage('') }, 5000) } } if (loading) return
加载中...
const account = dashboardData?.account const openTrades = dashboardData?.open_trades || [] return (

仪表板

{message && (
{message}
)}

账户信息

{account ? (
总余额: {parseFloat(account.total_balance).toFixed(2)} USDT
可用余额: {parseFloat(account.available_balance).toFixed(2)} USDT
总仓位: {parseFloat(account.total_position_value).toFixed(2)} USDT
总盈亏: = 0 ? 'positive' : 'negative'}`}> {parseFloat(account.total_pnl).toFixed(2)} USDT
持仓数量: {account.open_positions}
) : (
暂无数据
)}
{dashboardData?.position_stats && (

仓位占比

当前仓位占比: {dashboardData.position_stats.current_position_percent}% dashboardData.position_stats.max_position_percent * 0.8 ? '#ff6b6b' : dashboardData.position_stats.current_position_percent > dashboardData.position_stats.max_position_percent * 0.6 ? '#ffa500' : '#51cf66' }} />
最大仓位占比: {dashboardData.position_stats.max_position_percent}%
最大仓位量: {dashboardData.position_stats.max_position_value.toFixed(2)} USDT
已用仓位: {dashboardData.position_stats.total_position_value.toFixed(2)} USDT
可用仓位: {(dashboardData.position_stats.max_position_value - dashboardData.position_stats.total_position_value).toFixed(2)} USDT
)}

当前持仓

{openTrades.length > 0 ? (
{openTrades.map((trade, index) => { // 计算价格涨跌比例、止损比例、止盈比例 const entryPrice = parseFloat(trade.entry_price || 0) const markPrice = parseFloat(trade.mark_price || entryPrice) const side = trade.side || 'BUY' const quantity = parseFloat(trade.quantity || 0) // 价格涨跌比例(当前价格相对于入场价) let priceChangePercent = 0 if (entryPrice > 0) { if (side === 'BUY') { priceChangePercent = ((markPrice - entryPrice) / entryPrice) * 100 } else { priceChangePercent = ((entryPrice - markPrice) / entryPrice) * 100 } } // 计算保证金(用于基于保证金的止损止盈) const entryValue = trade.entry_value_usdt !== undefined ? parseFloat(trade.entry_value_usdt) : (quantity * entryPrice) const leverage = parseFloat(trade.leverage || 10) const margin = leverage > 0 ? entryValue / leverage : entryValue // 从配置获取止损止盈比例(相对于保证金) const configSource = dashboardData?.trading_config || tradingConfig const stopLossConfig = configSource?.STOP_LOSS_PERCENT const takeProfitConfig = configSource?.TAKE_PROFIT_PERCENT // 配置值是小数形式(0.08表示8%),相对于保证金 let stopLossPercentMargin = 0.08 // 默认8%(相对于保证金,更宽松) let takeProfitPercentMargin = 0.15 // 默认15%(相对于保证金,给趋势更多空间) if (stopLossConfig) { const configValue = stopLossConfig.value if (typeof configValue === 'number') { stopLossPercentMargin = configValue } else { const parsed = parseFloat(configValue) if (!isNaN(parsed)) { stopLossPercentMargin = parsed } } } if (takeProfitConfig) { const configValue = takeProfitConfig.value if (typeof configValue === 'number') { takeProfitPercentMargin = configValue } else { const parsed = parseFloat(configValue) if (!isNaN(parsed)) { takeProfitPercentMargin = parsed } } } // 计算止损止盈金额(相对于保证金) const stopLossAmount = margin * stopLossPercentMargin const takeProfitAmount = margin * takeProfitPercentMargin // 计算止损价和止盈价(基于保证金金额) // 优先使用后端返回的止损止盈价格,如果没有则自己计算 let stopLossPrice = 0 let takeProfitPrice = 0 if (trade.stop_loss_price && trade.take_profit_price) { // 使用后端返回的止损止盈价格(如果可用) stopLossPrice = parseFloat(trade.stop_loss_price) takeProfitPrice = parseFloat(trade.take_profit_price) } else { // 自己计算止损止盈价格 // 止损金额 = (开仓价 - 止损价) × 数量 或 (止损价 - 开仓价) × 数量 if (side === 'BUY') { // 做多:止损价 = 开仓价 - (止损金额 / 数量) stopLossPrice = entryPrice - (stopLossAmount / quantity) // 做多:止盈价 = 开仓价 + (止盈金额 / 数量) takeProfitPrice = entryPrice + (takeProfitAmount / quantity) } else { // 做空:止损价 = 开仓价 + (止损金额 / 数量) stopLossPrice = entryPrice + (stopLossAmount / quantity) // 做空:止盈价 = 开仓价 - (止盈金额 / 数量) takeProfitPrice = entryPrice - (takeProfitAmount / quantity) } } // 计算止损比例和止盈比例(相对于保证金,用于显示) const stopLossPercent = stopLossPercentMargin * 100 // 相对于保证金 const takeProfitPercent = takeProfitPercentMargin * 100 // 相对于保证金 // 也计算价格百分比(用于参考) const stopLossPercentPrice = side === 'BUY' ? ((entryPrice - stopLossPrice) / entryPrice) * 100 : ((stopLossPrice - entryPrice) / entryPrice) * 100 const takeProfitPercentPrice = side === 'BUY' ? ((takeProfitPrice - entryPrice) / entryPrice) * 100 : ((entryPrice - takeProfitPrice) / entryPrice) * 100 // 格式化开仓时间为具体的年月日时分秒 const formatEntryTime = (timeStr) => { if (!timeStr) return null try { const date = new Date(timeStr) const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') const hours = String(date.getHours()).padStart(2, '0') const minutes = String(date.getMinutes()).padStart(2, '0') const seconds = String(date.getSeconds()).padStart(2, '0') return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` } catch (e) { return timeStr } } return (
{trade.symbol}
{trade.side}
数量: {parseFloat(trade.quantity || 0).toFixed(4)}
保证金: {margin >= 0.01 ? margin.toFixed(2) : margin.toFixed(4)} USDT
入场价: {entryPrice.toFixed(4)}
{trade.mark_price && (
标记价: {markPrice.toFixed(4)}
)} {trade.leverage && (
杠杆: {trade.leverage}x
)} {/* 价格涨跌比例 */}
= 0 ? 'positive' : 'negative'}`}> 价格涨跌: {priceChangePercent >= 0 ? '+' : ''}{priceChangePercent.toFixed(2)}%
{/* 止损止盈比例 */} {trade.entry_time && (
开仓时间: {formatEntryTime(trade.entry_time)}
)}
止损: -{stopLossPercent.toFixed(2)}% (of margin) (价: {stopLossPrice.toFixed(4)}) (金额: -{stopLossAmount >= 0.01 ? stopLossAmount.toFixed(2) : stopLossAmount.toFixed(4)} USDT)
止盈: +{takeProfitPercent.toFixed(2)}% (of margin) (价: {takeProfitPrice.toFixed(4)}) (金额: +{takeProfitAmount >= 0.01 ? takeProfitAmount.toFixed(2) : takeProfitAmount.toFixed(4)} USDT)
= 0 ? 'positive' : 'negative'}`}> {parseFloat(trade.pnl || 0).toFixed(2)} USDT {trade.pnl_percent !== undefined && ( ({parseFloat(trade.pnl_percent).toFixed(2)}%) )}
) })}
) : (
暂无持仓
)}
) } export default StatsDashboard