From b00bce96509b9f772b20dbd550940783542f34b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Thu, 15 Jan 2026 14:50:04 +0800 Subject: [PATCH] a --- backend/api/routes/stats.py | 28 ++++++++- backend/config_manager.py | 2 + backend/database/init.sql | 4 +- frontend/src/components/StatsDashboard.css | 25 ++++++++ frontend/src/components/StatsDashboard.jsx | 45 +++++++++++++++ trading_system/binance_client.py | 38 ++++++++++++- trading_system/config.py | 4 +- trading_system/risk_manager.py | 66 ++++++++++++++++++++++ trading_system/strategy.py | 13 ++++- 9 files changed, 218 insertions(+), 7 deletions(-) diff --git a/backend/api/routes/stats.py b/backend/api/routes/stats.py index 682952f..e87d04f 100644 --- a/backend/api/routes/stats.py +++ b/backend/api/routes/stats.py @@ -154,11 +154,37 @@ async def get_dashboard_data(): except Exception as e: logger.error(f"获取交易信号失败: {e}") + # 计算仓位占比信息 + position_stats = None + if account_data: + try: + from database.models import TradingConfig + total_balance = float(account_data.get('total_balance', 0)) + total_position_value = float(account_data.get('total_position_value', 0)) + max_total_position_percent = float(TradingConfig.get_value('MAX_TOTAL_POSITION_PERCENT', 0.30)) + + # 当前仓位占比 + current_position_percent = (total_position_value / total_balance * 100) if total_balance > 0 else 0 + + # 最大仓位量(根据配置的最大占比计算) + max_position_value = total_balance * max_total_position_percent + + position_stats = { + "current_position_percent": round(current_position_percent, 2), + "max_position_percent": round(max_total_position_percent * 100, 2), + "max_position_value": round(max_position_value, 2), + "total_balance": round(total_balance, 2), + "total_position_value": round(total_position_value, 2) + } + except Exception as e: + logger.warning(f"计算仓位占比信息失败: {e}") + result = { "account": account_data, "open_trades": open_trades, "recent_scans": recent_scans, - "recent_signals": recent_signals + "recent_signals": recent_signals, + "position_stats": position_stats } # 如果有错误,在响应中包含错误信息(但不影响返回) diff --git a/backend/config_manager.py b/backend/config_manager.py index 46bf084..54e391e 100644 --- a/backend/config_manager.py +++ b/backend/config_manager.py @@ -138,6 +138,8 @@ class ConfigManager: # 高胜率策略参数 'MIN_SIGNAL_STRENGTH': self.get('MIN_SIGNAL_STRENGTH', 5), 'LEVERAGE': self.get('LEVERAGE', 10), + 'USE_DYNAMIC_LEVERAGE': self.get('USE_DYNAMIC_LEVERAGE', True), + 'MAX_LEVERAGE': self.get('MAX_LEVERAGE', 20), 'USE_TRAILING_STOP': self.get('USE_TRAILING_STOP', True), 'TRAILING_STOP_ACTIVATION': self.get('TRAILING_STOP_ACTIVATION', 0.01), 'TRAILING_STOP_PROTECT': self.get('TRAILING_STOP_PROTECT', 0.01), diff --git a/backend/database/init.sql b/backend/database/init.sql index 4a46658..76a4df3 100644 --- a/backend/database/init.sql +++ b/backend/database/init.sql @@ -157,7 +157,9 @@ INSERT INTO `trading_config` (`config_key`, `config_value`, `config_type`, `cate -- 高胜率策略参数 ('MIN_SIGNAL_STRENGTH', '7', 'number', 'strategy', '最小信号强度(0-10),已提升至7以提高入场质量'), -('LEVERAGE', '10', 'number', 'strategy', '杠杆倍数'), +('LEVERAGE', '10', 'number', 'strategy', '基础杠杆倍数'), +('USE_DYNAMIC_LEVERAGE', 'true', 'boolean', 'strategy', '是否启用动态杠杆(根据信号强度调整杠杆倍数)'), +('MAX_LEVERAGE', '20', 'number', 'strategy', '最大杠杆倍数(动态杠杆上限)'), ('USE_TRAILING_STOP', 'true', 'boolean', 'strategy', '是否使用移动止损'), ('TRAILING_STOP_ACTIVATION', '0.01', 'number', 'strategy', '移动止损激活阈值(盈利1%后激活)'), ('TRAILING_STOP_PROTECT', '0.01', 'number', 'strategy', '移动止损保护利润(保护1%利润)'), diff --git a/frontend/src/components/StatsDashboard.css b/frontend/src/components/StatsDashboard.css index 140db5c..b3487d7 100644 --- a/frontend/src/components/StatsDashboard.css +++ b/frontend/src/components/StatsDashboard.css @@ -95,6 +95,31 @@ color: #2c3e50; font-size: 0.95rem; text-align: right; + display: flex; + align-items: center; + gap: 0.5rem; + flex-direction: column; +} + +@media (min-width: 768px) { + .info-item .value { + flex-direction: row; + } +} + +.position-bar-container { + width: 100px; + height: 8px; + background-color: #e9ecef; + border-radius: 4px; + overflow: hidden; + display: inline-block; +} + +.position-bar { + height: 100%; + display: block; + transition: width 0.3s ease; } @media (min-width: 768px) { diff --git a/frontend/src/components/StatsDashboard.jsx b/frontend/src/components/StatsDashboard.jsx index 89b7bba..4bebb21 100644 --- a/frontend/src/components/StatsDashboard.jsx +++ b/frontend/src/components/StatsDashboard.jsx @@ -106,6 +106,51 @@ const StatsDashboard = () => { )} + {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 ? ( diff --git a/trading_system/binance_client.py b/trading_system/binance_client.py index d27b9d7..4a1344a 100644 --- a/trading_system/binance_client.py +++ b/trading_system/binance_client.py @@ -489,11 +489,26 @@ class BinanceClient: if min_notional is None or min_notional == 0: min_notional = 5.0 + # 获取交易对支持的最大杠杆倍数 + # 币安API的exchange_info中可能没有直接的leverageBracket信息 + # 我们尝试从leverageBracket获取,如果没有则使用默认值 + max_leverage_supported = 125 # 币安合约默认最大杠杆 + + # 尝试从leverageBracket获取(如果存在) + if s.get('leverageBracket') and len(s.get('leverageBracket', [])) > 0: + max_leverage_supported = s['leverageBracket'][0].get('maxLeverage', 125) + else: + # 如果leverageBracket不存在,尝试通过futures_leverage_bracket API获取 + # 但为了不增加API调用,这里先使用默认值125 + # 实际使用时会在设置杠杆时检查,如果失败会自动降低 + max_leverage_supported = 125 + info = { 'quantityPrecision': quantity_precision, 'minQty': min_qty or 0, 'stepSize': step_size or 0, - 'minNotional': min_notional + 'minNotional': min_notional, + 'maxLeverage': int(max_leverage_supported) # 交易对支持的最大杠杆 } # 写入 Redis 缓存(TTL: 1小时) @@ -675,6 +690,7 @@ class BinanceClient: async def set_leverage(self, symbol: str, leverage: int = 10) -> bool: """ 设置杠杆倍数 + 如果设置失败(比如超过交易对支持的最大杠杆),会自动降低杠杆重试 Args: symbol: 交易对 @@ -688,7 +704,25 @@ class BinanceClient: logger.info(f"设置杠杆成功: {symbol} {leverage}x") return True except BinanceAPIException as e: - logger.error(f"设置杠杆失败: {e}") + error_msg = str(e).lower() + # 如果错误信息包含杠杆相关的内容,尝试降低杠杆 + if 'leverage' in error_msg or 'invalid' in error_msg: + # 尝试降低杠杆(每次降低5倍,最低到1倍) + for reduced_leverage in range(leverage - 5, 0, -5): + if reduced_leverage < 1: + reduced_leverage = 1 + try: + await self.client.futures_change_leverage(symbol=symbol, leverage=reduced_leverage) + logger.warning( + f"{symbol} 杠杆 {leverage}x 设置失败,已自动降低为 {reduced_leverage}x " + f"(原因: {e})" + ) + return True + except BinanceAPIException: + if reduced_leverage <= 1: + break + continue + logger.error(f"设置杠杆失败: {symbol} {leverage}x, 错误: {e}") return False def get_realtime_price(self, symbol: str) -> Optional[float]: diff --git a/trading_system/config.py b/trading_system/config.py index 0b1788e..d3da953 100644 --- a/trading_system/config.py +++ b/trading_system/config.py @@ -178,7 +178,9 @@ def _get_trading_config(): 'MIN_VOLUME_24H': 10000000, 'MIN_VOLATILITY': 0.02, 'MIN_SIGNAL_STRENGTH': 7, # 提升至7以提高入场质量,减少假信号 - 'LEVERAGE': 10, + 'LEVERAGE': 10, # 基础杠杆倍数 + 'USE_DYNAMIC_LEVERAGE': True, # 是否启用动态杠杆(根据信号强度调整) + 'MAX_LEVERAGE': 20, # 最大杠杆倍数(动态杠杆上限) 'USE_TRAILING_STOP': True, 'TRAILING_STOP_ACTIVATION': 0.01, 'TRAILING_STOP_PROTECT': 0.01, diff --git a/trading_system/risk_manager.py b/trading_system/risk_manager.py index 7a8c521..55dc773 100644 --- a/trading_system/risk_manager.py +++ b/trading_system/risk_manager.py @@ -504,3 +504,69 @@ class RiskManager: return entry_price * (1 + take_profit_percent) else: # 做空,止盈价低于入场价 return entry_price * (1 - take_profit_percent) + + async def calculate_dynamic_leverage(self, signal_strength: int, symbol: str = None) -> int: + """ + 根据信号强度计算动态杠杆倍数 + 信号强度越高,杠杆倍数越高,以最大化收益 + 同时检查交易对支持的最大杠杆限制 + + Args: + signal_strength: 信号强度 (0-10) + symbol: 交易对符号(可选,用于检查交易对的最大杠杆限制) + + Returns: + 杠杆倍数 + """ + # 获取配置参数 + use_dynamic_leverage = self.config.get('USE_DYNAMIC_LEVERAGE', True) + base_leverage = self.config.get('LEVERAGE', 10) + max_leverage = self.config.get('MAX_LEVERAGE', 20) + min_signal_strength = self.config.get('MIN_SIGNAL_STRENGTH', 7) + + # 如果未启用动态杠杆,返回基础杠杆 + if not use_dynamic_leverage: + final_leverage = int(base_leverage) + else: + # 如果信号强度低于最小要求,使用基础杠杆 + if signal_strength < min_signal_strength: + final_leverage = int(base_leverage) + else: + # 计算动态杠杆:信号强度越高,杠杆越高 + # 公式:杠杆 = 基础杠杆 + (信号强度 - 最小信号强度) * (最大杠杆 - 基础杠杆) / (10 - 最小信号强度) + signal_range = 10 - min_signal_strength # 信号强度范围 + leverage_range = max_leverage - base_leverage # 杠杆范围 + + if signal_range > 0: + # 计算信号强度超出最小值的比例 + strength_above_min = signal_strength - min_signal_strength + leverage_increase = (strength_above_min / signal_range) * leverage_range + dynamic_leverage = base_leverage + leverage_increase + else: + dynamic_leverage = base_leverage + + # 确保杠杆在合理范围内(不超过配置的最大杠杆) + final_leverage = max(int(base_leverage), min(int(dynamic_leverage), int(max_leverage))) + + # 如果提供了交易对符号,检查交易对支持的最大杠杆限制 + if symbol: + try: + symbol_info = await self.client.get_symbol_info(symbol) + if symbol_info and 'maxLeverage' in symbol_info: + symbol_max_leverage = symbol_info['maxLeverage'] + if final_leverage > symbol_max_leverage: + logger.warning( + f"{symbol} 交易对最大杠杆限制为 {symbol_max_leverage}x, " + f"计算杠杆 {final_leverage}x 超过限制,调整为 {symbol_max_leverage}x" + ) + final_leverage = symbol_max_leverage + except Exception as e: + logger.warning(f"获取 {symbol} 交易对杠杆限制失败: {e},使用计算值 {final_leverage}x") + + logger.info( + f"动态杠杆计算: 信号强度={signal_strength}/10, " + f"基础杠杆={base_leverage}x, 计算杠杆={final_leverage}x" + + (f", 交易对={symbol}" if symbol else "") + ) + + return final_leverage diff --git a/trading_system/strategy.py b/trading_system/strategy.py index 0453608..4c5cfa3 100644 --- a/trading_system/strategy.py +++ b/trading_system/strategy.py @@ -148,17 +148,26 @@ class TradingStrategy: trade_direction = trade_signal['direction'] entry_reason = trade_signal['reason'] + signal_strength = trade_signal.get('strength', 0) logger.info( f"{symbol} 交易信号: {trade_direction} | " f"原因: {entry_reason} | " - f"信号强度: {trade_signal.get('strength', 0)}/10" + f"信号强度: {signal_strength}/10" + ) + + # 根据信号强度计算动态杠杆(高质量信号使用更高杠杆) + # 同时检查交易对支持的最大杠杆限制 + dynamic_leverage = await self.risk_manager.calculate_dynamic_leverage(signal_strength, symbol) + logger.info( + f"{symbol} 使用动态杠杆: {dynamic_leverage}x " + f"(信号强度: {signal_strength}/10)" ) # 开仓(使用改进的仓位管理) position = await self.position_manager.open_position( symbol=symbol, change_percent=change_percent, - leverage=config.TRADING_CONFIG.get('LEVERAGE', 10), + leverage=dynamic_leverage, trade_direction=trade_direction, entry_reason=entry_reason, atr=symbol_info.get('atr'),