a
This commit is contained in:
parent
81526052e7
commit
b00bce9650
|
|
@ -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
|
||||
}
|
||||
|
||||
# 如果有错误,在响应中包含错误信息(但不影响返回)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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%利润)'),
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -106,6 +106,51 @@ const StatsDashboard = () => {
|
|||
)}
|
||||
</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">
|
||||
<h3>当前持仓</h3>
|
||||
{openTrades.length > 0 ? (
|
||||
|
|
|
|||
|
|
@ -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]:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user