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'),