From 186b2f24245f9692a17dccea575126e3421b8679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Tue, 13 Jan 2026 20:08:11 +0800 Subject: [PATCH] a --- backend/api/main.py | 3 +- backend/api/routes/stats.py | 32 ++++- frontend/src/components/StatsDashboard.css | 10 ++ frontend/src/components/StatsDashboard.jsx | 21 ++- trading_system/config.py | 153 ++++++++++++--------- trading_system/strategy.py | 13 +- 6 files changed, 154 insertions(+), 78 deletions(-) diff --git a/backend/api/main.py b/backend/api/main.py index b87ce61..8e43049 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -3,7 +3,7 @@ FastAPI应用主入口 """ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from api.routes import config, trades, stats, dashboard +from api.routes import config, trades, stats, dashboard, account import os from pathlib import Path @@ -45,6 +45,7 @@ app.include_router(config.router, prefix="/api/config", tags=["配置管理"]) app.include_router(trades.router, prefix="/api/trades", tags=["交易记录"]) app.include_router(stats.router, prefix="/api/stats", tags=["统计分析"]) app.include_router(dashboard.router, prefix="/api/dashboard", tags=["仪表板"]) +app.include_router(account.router, prefix="/api/account", tags=["账户数据"]) @app.get("/") diff --git a/backend/api/routes/stats.py b/backend/api/routes/stats.py index 39e23f8..0c57079 100644 --- a/backend/api/routes/stats.py +++ b/backend/api/routes/stats.py @@ -40,12 +40,29 @@ async def get_performance_stats(days: int = Query(7, ge=1, le=365)): async def get_dashboard_data(): """获取仪表板数据""" try: - # 最近的账户快照 - snapshots = AccountSnapshot.get_recent(1) - latest_snapshot = snapshots[0] if snapshots else None + account_data = None - # 最近的交易 - recent_trades = Trade.get_all(status='open')[:10] + # 优先尝试获取实时账户数据 + try: + from api.routes.account import get_realtime_account_data + account_data = await get_realtime_account_data() + except Exception as e: + logger.warning(f"获取实时账户数据失败,使用数据库快照: {e}") + # 回退到数据库快照 + snapshots = AccountSnapshot.get_recent(1) + account_data = snapshots[0] if snapshots else None + + # 获取持仓数据(优先实时,回退到数据库) + open_trades = [] + try: + from api.routes.account import get_realtime_positions + positions = await get_realtime_positions() + # 转换为前端需要的格式 + open_trades = positions + except Exception as e: + logger.warning(f"获取实时持仓失败,使用数据库记录: {e}") + # 回退到数据库记录 + open_trades = Trade.get_all(status='open')[:10] # 最近的扫描记录 recent_scans = MarketScan.get_recent(10) @@ -54,10 +71,11 @@ async def get_dashboard_data(): recent_signals = TradingSignal.get_recent(20) return { - "account": latest_snapshot, - "open_trades": recent_trades, + "account": account_data, + "open_trades": open_trades, "recent_scans": recent_scans, "recent_signals": recent_signals } except Exception as e: + logger.error(f"获取仪表板数据失败: {e}", exc_info=True) raise HTTPException(status_code=500, detail=str(e)) diff --git a/frontend/src/components/StatsDashboard.css b/frontend/src/components/StatsDashboard.css index 80d0308..c36d607 100644 --- a/frontend/src/components/StatsDashboard.css +++ b/frontend/src/components/StatsDashboard.css @@ -75,6 +75,16 @@ padding: 0.75rem; background: white; border-radius: 4px; + gap: 1rem; +} + +.trade-info { + display: flex; + flex-direction: column; + gap: 0.25rem; + font-size: 0.85rem; + color: #666; + flex: 1; } .trade-symbol { diff --git a/frontend/src/components/StatsDashboard.jsx b/frontend/src/components/StatsDashboard.jsx index 5266aa9..52b29b1 100644 --- a/frontend/src/components/StatsDashboard.jsx +++ b/frontend/src/components/StatsDashboard.jsx @@ -70,14 +70,27 @@ const StatsDashboard = () => {

当前持仓

{openTrades.length > 0 ? (
- {openTrades.map(trade => ( -
+ {openTrades.map((trade, index) => ( +
{trade.symbol}
{trade.side}
-
- {parseFloat(trade.pnl).toFixed(2)} USDT +
+
数量: {parseFloat(trade.quantity || 0).toFixed(4)}
+
入场价: {parseFloat(trade.entry_price || 0).toFixed(4)}
+ {trade.mark_price && ( +
标记价: {parseFloat(trade.mark_price).toFixed(4)}
+ )} + {trade.leverage && ( +
杠杆: {trade.leverage}x
+ )} +
+
= 0 ? 'positive' : 'negative'}`}> + {parseFloat(trade.pnl || 0).toFixed(2)} USDT + {trade.pnl_percent !== undefined && ( + ({parseFloat(trade.pnl_percent).toFixed(2)}%) + )}
))} diff --git a/trading_system/config.py b/trading_system/config.py index 04283ca..e9e6499 100644 --- a/trading_system/config.py +++ b/trading_system/config.py @@ -1,86 +1,111 @@ """ 配置文件 - API密钥和交易参数配置 支持从数据库读取配置(优先),回退到环境变量和默认值 +支持动态重载配置 """ import os from typing import Optional # 尝试从数据库加载配置 USE_DB_CONFIG = False -try: - import sys - from pathlib import Path - # 从trading_system目录向上两级到项目根目录,然后找backend - project_root = Path(__file__).parent.parent - backend_path = project_root / 'backend' - if backend_path.exists(): - sys.path.insert(0, str(backend_path)) - from config_manager import config_manager - USE_DB_CONFIG = True - else: +_config_manager = None + +def _init_config_manager(): + """初始化配置管理器""" + global USE_DB_CONFIG, _config_manager + if _config_manager is not None: + return _config_manager + + try: + import sys + from pathlib import Path + # 从trading_system目录向上两级到项目根目录,然后找backend + project_root = Path(__file__).parent.parent + backend_path = project_root / 'backend' + if backend_path.exists(): + sys.path.insert(0, str(backend_path)) + from config_manager import config_manager + _config_manager = config_manager + USE_DB_CONFIG = True + return config_manager + else: + USE_DB_CONFIG = False + return None + except Exception as e: USE_DB_CONFIG = False -except Exception: - USE_DB_CONFIG = False + return None -# 币安API配置(优先从数据库,回退到环境变量和默认值) -if USE_DB_CONFIG: - BINANCE_API_KEY: Optional[str] = config_manager.get('BINANCE_API_KEY') or os.getenv('BINANCE_API_KEY', 'pMEXSgISMgpUIpGjyhikMXWQ7K7cCs1FFATyIvNIwWrUIQegoipVBskPUoUuvaVN') - BINANCE_API_SECRET: Optional[str] = config_manager.get('BINANCE_API_SECRET') or os.getenv('BINANCE_API_SECRET', 'RklItVtBCjGV40mIquoSj78xlTGkdUxz0AFyTnsnuzSBfx776VG0S2Vw5BRLRRg2') - USE_TESTNET: bool = config_manager.get('USE_TESTNET', False) if config_manager.get('USE_TESTNET') is not None else os.getenv('USE_TESTNET', 'False').lower() == 'true' -else: - BINANCE_API_KEY: Optional[str] = os.getenv('BINANCE_API_KEY', 'pMEXSgISMgpUIpGjyhikMXWQ7K7cCs1FFATyIvNIwWrUIQegoipVBskPUoUuvaVN') - BINANCE_API_SECRET: Optional[str] = os.getenv('BINANCE_API_SECRET', 'RklItVtBCjGV40mIquoSj78xlTGkdUxz0AFyTnsnuzSBfx776VG0S2Vw5BRLRRg2') - USE_TESTNET: bool = os.getenv('USE_TESTNET', 'False').lower() == 'true' +# 初始化配置管理器 +_init_config_manager() -# 交易参数配置(优先从数据库读取) -if USE_DB_CONFIG: - TRADING_CONFIG = config_manager.get_trading_config() -else: - TRADING_CONFIG = { - # 仓位控制 - 'MAX_POSITION_PERCENT': 0.05, # 单笔最大仓位:账户余额的5% - 'MAX_TOTAL_POSITION_PERCENT': 0.30, # 总仓位上限:账户余额的30% - 'MIN_POSITION_PERCENT': 0.01, # 单笔最小仓位:账户余额的1% - - # 涨跌幅阈值 - 'MIN_CHANGE_PERCENT': 2.0, # 最小涨跌幅阈值:2% - 'TOP_N_SYMBOLS': 10, # 选择前N个货币对 - - # 风险控制 - 'STOP_LOSS_PERCENT': 0.03, # 止损:3% - 'TAKE_PROFIT_PERCENT': 0.05, # 止盈:5% - - # 市场扫描(1小时主周期) - 'SCAN_INTERVAL': 3600, # 扫描间隔:1小时(秒) - 'KLINE_INTERVAL': '1h', # K线周期:1小时 - 'PRIMARY_INTERVAL': '1h', # 主周期:1小时 - 'CONFIRM_INTERVAL': '4h', # 确认周期:4小时 - 'ENTRY_INTERVAL': '15m', # 入场周期:15分钟 - - # 过滤条件 - 'MIN_VOLUME_24H': 10000000, # 最小24小时成交量:1000万USDT - 'MIN_VOLATILITY': 0.02, # 最小波动率:2% - - # 高胜率策略参数 - 'MIN_SIGNAL_STRENGTH': 5, # 最小信号强度(0-10),越高越严格,胜率越高 - 'LEVERAGE': 10, # 杠杆倍数 - 'USE_TRAILING_STOP': True, # 是否使用移动止损 - 'TRAILING_STOP_ACTIVATION': 0.01, # 移动止损激活阈值(盈利1%后激活) - 'TRAILING_STOP_PROTECT': 0.01, # 移动止损保护利润(保护1%利润) - - # Unicorn WebSocket配置 - 'USE_UNICORN_WEBSOCKET': True, # 是否使用Unicorn WebSocket(高性能实时数据流) - } +def _get_config_value(key, default=None): + """获取配置值(支持动态重载)""" + if _config_manager: + _config_manager.reload() # 每次获取配置时重新加载 + return _config_manager.get(key, default) or os.getenv(key, default) + return os.getenv(key, default) -# 如果使用数据库配置,确保包含所有必要的默认值 -if USE_DB_CONFIG: - defaults = { +def _get_trading_config(): + """获取交易配置(支持动态重载)""" + if _config_manager: + _config_manager.reload() # 每次获取配置时重新加载 + return _config_manager.get_trading_config() + # 回退到默认配置 + return { + 'MAX_POSITION_PERCENT': 0.05, + 'MAX_TOTAL_POSITION_PERCENT': 0.30, + 'MIN_POSITION_PERCENT': 0.01, + 'MIN_CHANGE_PERCENT': 2.0, + 'TOP_N_SYMBOLS': 10, + 'STOP_LOSS_PERCENT': 0.03, + 'TAKE_PROFIT_PERCENT': 0.05, 'SCAN_INTERVAL': 3600, 'KLINE_INTERVAL': '1h', 'PRIMARY_INTERVAL': '1h', 'CONFIRM_INTERVAL': '4h', 'ENTRY_INTERVAL': '15m', + 'MIN_VOLUME_24H': 10000000, + 'MIN_VOLATILITY': 0.02, + 'MIN_SIGNAL_STRENGTH': 5, + 'LEVERAGE': 10, + 'USE_TRAILING_STOP': True, + 'TRAILING_STOP_ACTIVATION': 0.01, + 'TRAILING_STOP_PROTECT': 0.01, + 'USE_UNICORN_WEBSOCKET': True, } + +# 币安API配置(优先从数据库,回退到环境变量和默认值) +BINANCE_API_KEY: Optional[str] = _get_config_value('BINANCE_API_KEY', 'pMEXSgISMgpUIpGjyhikMXWQ7K7cCs1FFATyIvNIwWrUIQegoipVBskPUoUuvaVN') +BINANCE_API_SECRET: Optional[str] = _get_config_value('BINANCE_API_SECRET', 'RklItVtBCjGV40mIquoSj78xlTGkdUxz0AFyTnsnuzSBfx776VG0S2Vw5BRLRRg2') +USE_TESTNET: bool = _get_config_value('USE_TESTNET', False) if _get_config_value('USE_TESTNET') is not None else os.getenv('USE_TESTNET', 'False').lower() == 'true' + +# 交易参数配置(优先从数据库读取,支持动态重载) +TRADING_CONFIG = _get_trading_config() + +# 确保包含所有必要的默认值 +defaults = { + 'SCAN_INTERVAL': 3600, + 'KLINE_INTERVAL': '1h', + 'PRIMARY_INTERVAL': '1h', + 'CONFIRM_INTERVAL': '4h', + 'ENTRY_INTERVAL': '15m', +} +for key, value in defaults.items(): + if key not in TRADING_CONFIG: + TRADING_CONFIG[key] = value + +# 提供一个函数来重新加载配置 +def reload_config(): + """重新加载配置(供外部调用)""" + global TRADING_CONFIG, BINANCE_API_KEY, BINANCE_API_SECRET, USE_TESTNET, _config_manager + _init_config_manager() # 重新初始化配置管理器 + if _config_manager: + _config_manager.reload() + BINANCE_API_KEY = _get_config_value('BINANCE_API_KEY', BINANCE_API_KEY) + BINANCE_API_SECRET = _get_config_value('BINANCE_API_SECRET', BINANCE_API_SECRET) + USE_TESTNET = _get_config_value('USE_TESTNET', False) if _get_config_value('USE_TESTNET') is not None else os.getenv('USE_TESTNET', 'False').lower() == 'true' + TRADING_CONFIG = _get_trading_config() + # 确保默认值 for key, value in defaults.items(): if key not in TRADING_CONFIG: TRADING_CONFIG[key] = value diff --git a/trading_system/strategy.py b/trading_system/strategy.py index 1a567d8..2de33a3 100644 --- a/trading_system/strategy.py +++ b/trading_system/strategy.py @@ -175,9 +175,18 @@ class TradingStrategy: except Exception as e: logger.debug(f"记录账户快照失败: {e}") + # 重新加载配置(确保使用最新配置) + try: + if hasattr(config, 'reload_config'): + config.reload_config() + logger.info("配置已重新加载,将使用最新配置进行下次扫描") + except Exception as e: + logger.debug(f"重新加载配置失败: {e}") + # 等待下次扫描 - logger.info(f"等待 {config.TRADING_CONFIG['SCAN_INTERVAL']} 秒后进行下次扫描...") - await asyncio.sleep(config.TRADING_CONFIG['SCAN_INTERVAL']) + scan_interval = config.TRADING_CONFIG.get('SCAN_INTERVAL', 3600) + logger.info(f"等待 {scan_interval} 秒后进行下次扫描...") + await asyncio.sleep(scan_interval) except Exception as e: logger.error(f"策略执行出错: {e}", exc_info=True)