a
This commit is contained in:
parent
81526052e7
commit
b00bce9650
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
# 如果有错误,在响应中包含错误信息(但不影响返回)
|
# 如果有错误,在响应中包含错误信息(但不影响返回)
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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%利润)'),
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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 ? (
|
||||||
|
|
|
||||||
|
|
@ -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]:
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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'),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user