This commit is contained in:
薇薇安 2026-01-15 14:50:04 +08:00
parent 81526052e7
commit b00bce9650
9 changed files with 218 additions and 7 deletions

View File

@ -154,11 +154,37 @@ async def get_dashboard_data():
except Exception as e: except Exception as e:
logger.error(f"获取交易信号失败: {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 = { result = {
"account": account_data, "account": account_data,
"open_trades": open_trades, "open_trades": open_trades,
"recent_scans": recent_scans, "recent_scans": recent_scans,
"recent_signals": recent_signals "recent_signals": recent_signals,
"position_stats": position_stats
} }
# 如果有错误,在响应中包含错误信息(但不影响返回) # 如果有错误,在响应中包含错误信息(但不影响返回)

View File

@ -138,6 +138,8 @@ class ConfigManager:
# 高胜率策略参数 # 高胜率策略参数
'MIN_SIGNAL_STRENGTH': self.get('MIN_SIGNAL_STRENGTH', 5), 'MIN_SIGNAL_STRENGTH': self.get('MIN_SIGNAL_STRENGTH', 5),
'LEVERAGE': self.get('LEVERAGE', 10), '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), 'USE_TRAILING_STOP': self.get('USE_TRAILING_STOP', True),
'TRAILING_STOP_ACTIVATION': self.get('TRAILING_STOP_ACTIVATION', 0.01), 'TRAILING_STOP_ACTIVATION': self.get('TRAILING_STOP_ACTIVATION', 0.01),
'TRAILING_STOP_PROTECT': self.get('TRAILING_STOP_PROTECT', 0.01), 'TRAILING_STOP_PROTECT': self.get('TRAILING_STOP_PROTECT', 0.01),

View File

@ -157,7 +157,9 @@ INSERT INTO `trading_config` (`config_key`, `config_value`, `config_type`, `cate
-- 高胜率策略参数 -- 高胜率策略参数
('MIN_SIGNAL_STRENGTH', '7', 'number', 'strategy', '最小信号强度0-10已提升至7以提高入场质量'), ('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', '是否使用移动止损'), ('USE_TRAILING_STOP', 'true', 'boolean', 'strategy', '是否使用移动止损'),
('TRAILING_STOP_ACTIVATION', '0.01', 'number', 'strategy', '移动止损激活阈值盈利1%后激活)'), ('TRAILING_STOP_ACTIVATION', '0.01', 'number', 'strategy', '移动止损激活阈值盈利1%后激活)'),
('TRAILING_STOP_PROTECT', '0.01', 'number', 'strategy', '移动止损保护利润保护1%利润)'), ('TRAILING_STOP_PROTECT', '0.01', 'number', 'strategy', '移动止损保护利润保护1%利润)'),

View File

@ -95,6 +95,31 @@
color: #2c3e50; color: #2c3e50;
font-size: 0.95rem; font-size: 0.95rem;
text-align: right; 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) { @media (min-width: 768px) {

View File

@ -106,6 +106,51 @@ const StatsDashboard = () => {
)} )}
</div> </div>
{dashboardData?.position_stats && (
<div className="dashboard-card">
<h3>仓位占比</h3>
<div className="account-info">
<div className="info-item">
<span className="label">当前仓位占比:</span>
<span className="value">
<span>{dashboardData.position_stats.current_position_percent}%</span>
<span className="position-bar-container">
<span
className="position-bar"
style={{
width: `${Math.min((dashboardData.position_stats.current_position_percent / dashboardData.position_stats.max_position_percent) * 100, 100)}%`,
backgroundColor: 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'
}}
/>
</span>
</span>
</div>
<div className="info-item">
<span className="label">最大仓位占比:</span>
<span className="value">{dashboardData.position_stats.max_position_percent}%</span>
</div>
<div className="info-item">
<span className="label">最大仓位量:</span>
<span className="value">{dashboardData.position_stats.max_position_value.toFixed(2)} USDT</span>
</div>
<div className="info-item">
<span className="label">已用仓位:</span>
<span className="value">{dashboardData.position_stats.total_position_value.toFixed(2)} USDT</span>
</div>
<div className="info-item">
<span className="label">可用仓位:</span>
<span className="value">
{(dashboardData.position_stats.max_position_value - dashboardData.position_stats.total_position_value).toFixed(2)} USDT
</span>
</div>
</div>
</div>
)}
<div className="dashboard-card"> <div className="dashboard-card">
<h3>当前持仓</h3> <h3>当前持仓</h3>
{openTrades.length > 0 ? ( {openTrades.length > 0 ? (

View File

@ -489,11 +489,26 @@ class BinanceClient:
if min_notional is None or min_notional == 0: if min_notional is None or min_notional == 0:
min_notional = 5.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 = { info = {
'quantityPrecision': quantity_precision, 'quantityPrecision': quantity_precision,
'minQty': min_qty or 0, 'minQty': min_qty or 0,
'stepSize': step_size or 0, 'stepSize': step_size or 0,
'minNotional': min_notional 'minNotional': min_notional,
'maxLeverage': int(max_leverage_supported) # 交易对支持的最大杠杆
} }
# 写入 Redis 缓存TTL: 1小时 # 写入 Redis 缓存TTL: 1小时
@ -675,6 +690,7 @@ class BinanceClient:
async def set_leverage(self, symbol: str, leverage: int = 10) -> bool: async def set_leverage(self, symbol: str, leverage: int = 10) -> bool:
""" """
设置杠杆倍数 设置杠杆倍数
如果设置失败比如超过交易对支持的最大杠杆会自动降低杠杆重试
Args: Args:
symbol: 交易对 symbol: 交易对
@ -688,7 +704,25 @@ class BinanceClient:
logger.info(f"设置杠杆成功: {symbol} {leverage}x") logger.info(f"设置杠杆成功: {symbol} {leverage}x")
return True return True
except BinanceAPIException as e: 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 return False
def get_realtime_price(self, symbol: str) -> Optional[float]: def get_realtime_price(self, symbol: str) -> Optional[float]:

View File

@ -178,7 +178,9 @@ def _get_trading_config():
'MIN_VOLUME_24H': 10000000, 'MIN_VOLUME_24H': 10000000,
'MIN_VOLATILITY': 0.02, 'MIN_VOLATILITY': 0.02,
'MIN_SIGNAL_STRENGTH': 7, # 提升至7以提高入场质量减少假信号 'MIN_SIGNAL_STRENGTH': 7, # 提升至7以提高入场质量减少假信号
'LEVERAGE': 10, 'LEVERAGE': 10, # 基础杠杆倍数
'USE_DYNAMIC_LEVERAGE': True, # 是否启用动态杠杆(根据信号强度调整)
'MAX_LEVERAGE': 20, # 最大杠杆倍数(动态杠杆上限)
'USE_TRAILING_STOP': True, 'USE_TRAILING_STOP': True,
'TRAILING_STOP_ACTIVATION': 0.01, 'TRAILING_STOP_ACTIVATION': 0.01,
'TRAILING_STOP_PROTECT': 0.01, 'TRAILING_STOP_PROTECT': 0.01,

View File

@ -504,3 +504,69 @@ class RiskManager:
return entry_price * (1 + take_profit_percent) return entry_price * (1 + take_profit_percent)
else: # 做空,止盈价低于入场价 else: # 做空,止盈价低于入场价
return entry_price * (1 - take_profit_percent) 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

View File

@ -148,17 +148,26 @@ class TradingStrategy:
trade_direction = trade_signal['direction'] trade_direction = trade_signal['direction']
entry_reason = trade_signal['reason'] entry_reason = trade_signal['reason']
signal_strength = trade_signal.get('strength', 0)
logger.info( logger.info(
f"{symbol} 交易信号: {trade_direction} | " f"{symbol} 交易信号: {trade_direction} | "
f"原因: {entry_reason} | " 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( position = await self.position_manager.open_position(
symbol=symbol, symbol=symbol,
change_percent=change_percent, change_percent=change_percent,
leverage=config.TRADING_CONFIG.get('LEVERAGE', 10), leverage=dynamic_leverage,
trade_direction=trade_direction, trade_direction=trade_direction,
entry_reason=entry_reason, entry_reason=entry_reason,
atr=symbol_info.get('atr'), atr=symbol_info.get('atr'),