feat: 盈亏计算改为账户余额% + 拖尾止损绝对值扣除

- _calculate_pnl_pct: 从金价涨跌%改为账户余额%
- _get_contract_size: 新增缓存合约规格方法
- 硬止损: 从金价%乘法改为账户$→点数→价格转换
- 拖尾止损: peak*(1-retrace) 改为 peak-retrace(绝对值扣除)
- 统一 _record_closed_trade/_calculate_unrealized_pnl 使用缓存合约规格
- RISK_CONFIG: 止损-1.5%/止盈+3.0%/拖尾激活+2.0%/回撤1.0%(均为账户%)
This commit is contained in:
silencesdg
2026-05-14 17:14:50 +08:00
parent 430376eb61
commit 9b0eeb521e
5 changed files with 195 additions and 97 deletions
+1
View File
@@ -0,0 +1 @@
2026-05-14T14:19:14.539006
+82 -81
View File
@@ -135,54 +135,55 @@ GENETIC_OPTIMIZER_CONFIG = {
# 信号阈值配置(优化器结果 2026-05-10,5万根M1数据) # 信号阈值配置(优化器结果 2026-05-10,5万根M1数据)
SIGNAL_THRESHOLDS = { SIGNAL_THRESHOLDS = {
"buy_threshold": 1.344, "buy_threshold": 1.0315663601353515,
"sell_threshold": -2.980 "sell_threshold": -2.312867704316165
} }
# 风险管理参数(优化器结果 2026-05-10,5万根M1数据) # 风险管理参数(优化器结果 2026-05-10,5万根M1数据)
RISK_CONFIG = { RISK_CONFIG = {
"stop_loss_pct": -0.046, # ★ 以下百分比均为账户余额%,非金价涨跌%
"profit_retracement_pct": 0.030, # 例:余额$1682-1.5% = -$25 ≈ 25点金价(0.01手)
"min_profit_for_trailing": 0.03, "stop_loss_pct": -0.015, # -1.5% 账户
"take_profit_pct": 0.246, "profit_retracement_pct": 0.01, # 1.0% 账户(拖尾回撤容忍)
"min_profit_for_trailing": 0.02, # +2.0% 后激活拖尾
"take_profit_pct": 0.03, # +3.0% 账户
"max_daily_loss": -0.3, "max_daily_loss": -0.3,
"max_holding_minutes": 133, "max_holding_minutes": 133,
"min_profit_for_time_exit": 0.010, "min_profit_for_time_exit": 0.010,
"cooldown_bars": 30, "cooldown_bars": 30,
# ★ 硬止损倍率:MT5 服务器端 SL/TP = 软止损 × 倍率(兜底,仅 EA 挂掉时触发) # ★ 硬止损倍率:MT5 服务器端 SL/TP = 软止损 × 倍率(兜底,仅 EA 挂掉时触发)
# 硬止损必须比软止损宽(倍率>1),否则会抢先触发导致拖尾失效 "hard_sl_multiplier": 1.5, # 硬 SL = 软 SL × 1.5-1.5%→-2.25%账户)
"hard_sl_multiplier": 1.5, # 硬 SL = 软 SL × 1.5(例如软-4.6%→硬-6.9% "hard_tp_multiplier": 1.3, # 硬 TP = 软 TP × 1.3+3.0%→+3.9%账户
"hard_tp_multiplier": 1.3, # 硬 TP = 软 TP × 1.3(例如软+24.6%→硬+32.0%
} }
# 市场状态分析参数 # 市场状态分析参数
MARKET_STATE_CONFIG = { MARKET_STATE_CONFIG = {
"trend_period": 24, "trend_period": 83,
"retracement_tolerance": 0.425, "retracement_tolerance": 0.15567164907853615,
"volume_period": 21, "volume_period": 29,
"volume_ma_period": 12 "volume_ma_period": 23
} }
# 策略参数配置(优化器结果 2026-05-10,5万根M1数据) # 策略参数配置(优化器结果 2026-05-10,5万根M1数据)
STRATEGY_CONFIG = { STRATEGY_CONFIG = {
"ma_cross": { "ma_cross": {
"short_window": 12, "short_window": 13,
"long_window": 30 "long_window": 17
}, },
"rsi": { "rsi": {
"period": 21, "period": 30,
"overbought": 75, "overbought": 68,
"oversold": 26 "oversold": 32
}, },
"bollinger": { "bollinger": {
"period": 20, "period": 20,
"std_dev": 2.162 "std_dev": 2.4663422649047333
}, },
"macd": { "macd": {
"fast_ema": 16, "fast_ema": 14,
"slow_ema": 34, "slow_ema": 24,
"signal_period": 12 "signal_period": 9
}, },
"mean_reversion": { "mean_reversion": {
"period": 29, "period": 29,
@@ -190,7 +191,7 @@ STRATEGY_CONFIG = {
}, },
"momentum_breakout": { "momentum_breakout": {
"period": 15, "period": 15,
"momentum_period": 17 "momentum_period": 13
}, },
"kdj": { "kdj": {
"period": 21 "period": 21
@@ -199,96 +200,96 @@ STRATEGY_CONFIG = {
"period": 42 "period": 42
}, },
"daily_breakout": { "daily_breakout": {
"bars_count": 746 "bars_count": 2008
}, },
"wave_theory": { "wave_theory": {
"ema_short": 3, "ema_short": 3,
"ema_medium": 16, "ema_medium": 11,
"ema_long": 26, "ema_long": 46,
"wave_period": 32, "wave_period": 11,
"range_period": 30, "range_period": 35,
"adx_period": 23, "adx_period": 20,
"momentum_period": 10, "momentum_period": 10,
"range_threshold": 0.002, "range_threshold": 0.01,
"adx_threshold": 23 "adx_threshold": 21
} }
} }
# 市场趋势判断权重配置 # 市场趋势判断权重配置
TREND_INDICATOR_WEIGHTS = { TREND_INDICATOR_WEIGHTS = {
"price_breakout": -0.0949, "price_breakout": 0.1504199442999585,
"volume_confirmation": 0.5277, "volume_confirmation": 0.12782205952949638,
"momentum oscillator": 0.3677, "momentum oscillator": 0.3677,
"moving_average": 0.1506 "moving_average": 0.4092273363154768
} }
# 趋势判断阈值 # 趋势判断阈值
TREND_THRESHOLDS = { TREND_THRESHOLDS = {
"strong_trend": 0.4182, "strong_trend": 0.7940886082643032,
"weak_trend": 0.2164, "weak_trend": 0.4565953163045441,
"volume_spike": 1.5843, "volume_spike": 2.7329673335105396,
"oversold": 24, "oversold": 32,
"overbought": 80 "overbought": 68
} }
# 动态权重配置(优化器结果 2026-05-10,5万根M1数据) # 动态权重配置(优化器结果 2026-05-10,5万根M1数据)
DEFAULT_WEIGHTS = { DEFAULT_WEIGHTS = {
"ma_cross": 0.809, "ma_cross": 0.32048324544087786,
"rsi": 1.141, "rsi": 1.906009222972592,
"bollinger": 0.389, "bollinger": 0.9232444456662969,
"mean_reversion": 1.106, "mean_reversion": 1.1649306516234597,
"momentum_breakout": 0.147, "momentum_breakout": 0.8769099086178171,
"macd": 1.181, "macd": 0.748930985323352,
"kdj": 1.525, "kdj": 1.4647470623067596,
"turtle": 0.559, "turtle": 1.637931136008917,
"daily_breakout": 1.846, "daily_breakout": 1.478598649389487,
"wave_theory": 1.361 "wave_theory": 0.818283180489803
} }
# 市场状态策略权重配置 # 市场状态策略权重配置
MARKET_STATE_WEIGHTS = { MARKET_STATE_WEIGHTS = {
"uptrend": { "uptrend": {
"ma_cross": 1.50, "ma_cross": 0.32048324544087786,
"momentum_breakout": 1.20, "momentum_breakout": 0.8769099086178171,
"turtle": 0.25, "turtle": 1.637931136008917,
"macd": 0.35, "macd": 0.748930985323352,
"daily_breakout": 1.50, "daily_breakout": 1.478598649389487,
"rsi": 1.00, "rsi": 1.906009222972592,
"bollinger": 1.00, "bollinger": 0.9232444456662969,
"kdj": 0.40, "kdj": 1.4647470623067596,
"mean_reversion": 0.80, "mean_reversion": 1.1649306516234597,
"wave_theory": 0.20 "wave_theory": 0.818283180489803
}, },
"downtrend": { "downtrend": {
"ma_cross": 1.50, "ma_cross": 0.32048324544087786,
"momentum_breakout": 1.20, "momentum_breakout": 0.8769099086178171,
"turtle": 0.25, "turtle": 1.637931136008917,
"macd": 0.35, "macd": 0.748930985323352,
"daily_breakout": 1.50, "daily_breakout": 1.478598649389487,
"rsi": 1.00, "rsi": 1.906009222972592,
"bollinger": 1.00, "bollinger": 0.9232444456662969,
"kdj": 0.40, "kdj": 1.4647470623067596,
"mean_reversion": 0.80, "mean_reversion": 1.1649306516234597,
"wave_theory": 0.20 "wave_theory": 0.818283180489803
}, },
"ranging": { "ranging": {
"rsi": 1.60, "rsi": 1.906009222972592,
"bollinger": 1.70, "bollinger": 0.9232444456662969,
"mean_reversion": 1.50, "mean_reversion": 1.1649306516234597,
"kdj": 1.00, "kdj": 1.4647470623067596,
"wave_theory": 0.50, "wave_theory": 0.818283180489803,
"ma_cross": 0.70, "ma_cross": 0.32048324544087786,
"macd": 0.20, "macd": 0.748930985323352,
"turtle": 0.10, "turtle": 1.637931136008917,
"momentum_breakout": 0.50, "momentum_breakout": 0.8769099086178171,
"daily_breakout": 0.90 "daily_breakout": 1.478598649389487
}, },
"none": DEFAULT_WEIGHTS "none": DEFAULT_WEIGHTS
} }
# 市场趋势置信度阈值配置 # 市场趋势置信度阈值配置
CONFIDENCE_THRESHOLDS = { CONFIDENCE_THRESHOLDS = {
"high_confidence": 0.8474, "high_confidence": 0.6813641209473742,
"medium_confidence": 0.4964 "medium_confidence": 0.6336441706563001
} }
+2 -1
View File
@@ -58,7 +58,8 @@ class TrailingStopRule(BaseExitRule):
if ctx.peak_profit_pct <= min_profit: if ctx.peak_profit_pct <= min_profit:
return "none", "" return "none", ""
stop_level = ctx.peak_profit_pct * (1 - retracement_pct) # ★ 回撤从峰值绝对值扣除(账户%,非相对%):峰值+2.0%回撤1.0%→止损在+1.0%
stop_level = ctx.peak_profit_pct - retracement_pct
if ctx.current_profit_pct <= stop_level: if ctx.current_profit_pct <= stop_level:
return "close", ( return "close", (
f"追踪止损触发 " f"追踪止损触发 "
+35 -15
View File
@@ -189,18 +189,27 @@ class PositionManager:
return False return False
# ★ 计算 MT5 硬止损/硬止盈(兜底安全网) # ★ 计算 MT5 硬止损/硬止盈(兜底安全网)
# 公式:账户% × 余额 × 倍率 → 美元 → 金价点数 → 价格
hard_sl_price = None hard_sl_price = None
hard_tp_price = None hard_tp_price = None
contract_size = self._get_contract_size()
points_per_dollar = 1.0 / (position_volume * contract_size) if position_volume > 0 else 0
sl_dollars = abs(self.stop_loss_pct) * self.total_equity * self.hard_sl_mult # 正数
tp_dollars = abs(self.take_profit_pct) * self.total_equity * self.hard_tp_mult
sl_points = sl_dollars * points_per_dollar
tp_points = tp_dollars * points_per_dollar
if direction == 'buy': if direction == 'buy':
if self.hard_sl_mult > 0: if self.hard_sl_mult > 0:
hard_sl_price = round(execution_price * (1 + self.stop_loss_pct * self.hard_sl_mult), 2) hard_sl_price = round(execution_price - sl_points, 2)
if self.hard_tp_mult > 0: if self.hard_tp_mult > 0:
hard_tp_price = round(execution_price * (1 + self.take_profit_pct * self.hard_tp_mult), 2) hard_tp_price = round(execution_price + tp_points, 2)
else: else:
if self.hard_sl_mult > 0: if self.hard_sl_mult > 0:
hard_sl_price = round(execution_price * (1 - self.stop_loss_pct * self.hard_sl_mult), 2) hard_sl_price = round(execution_price + sl_points, 2)
if self.hard_tp_mult > 0: if self.hard_tp_mult > 0:
hard_tp_price = round(execution_price * (1 - self.take_profit_pct * self.hard_tp_mult), 2) hard_tp_price = round(execution_price - tp_points, 2)
order_result = self.data_provider.send_order( order_result = self.data_provider.send_order(
self.symbol, direction, position_volume, self.symbol, direction, position_volume,
@@ -309,25 +318,39 @@ class PositionManager:
# ── 盈亏计算 ── # ── 盈亏计算 ──
def _get_contract_size(self) -> float:
"""获取合约规格(缓存避免重复API调用)"""
if not hasattr(self, '_cached_contract_size'):
symbol_info = self.data_provider.get_symbol_info(self.symbol)
self._cached_contract_size = (
symbol_info['trade_contract_size']
if symbol_info and isinstance(symbol_info, dict)
else 100
) if symbol_info else 100
return self._cached_contract_size
def _calculate_pnl_pct(self, position, current_price_value): def _calculate_pnl_pct(self, position, current_price_value):
"""★ 账户余额百分比盈亏(非金价涨跌%"""
entry_price = position['entry_price'] entry_price = position['entry_price']
quantity = position.get('quantity', 0.01)
contract_size = self._get_contract_size()
if position['position_type'] == 'long': if position['position_type'] == 'long':
return (current_price_value - entry_price) / entry_price if entry_price != 0 else 0.0 dollar_pnl = (current_price_value - entry_price) * quantity * contract_size
else: else:
return (entry_price - current_price_value) / entry_price if entry_price != 0 else 0.0 dollar_pnl = (entry_price - current_price_value) * quantity * contract_size
return dollar_pnl / self.total_equity if self.total_equity > 0 else 0.0
def _calculate_unrealized_pnl(self) -> float: def _calculate_unrealized_pnl(self) -> float:
"""★ 新增:计算所有持仓的浮动盈亏""" """计算所有持仓的浮动盈亏"""
if not self.positions: if not self.positions:
return 0.0 return 0.0
current_price_data = self.data_provider.get_current_price(self.symbol) current_price_data = self.data_provider.get_current_price(self.symbol)
if not current_price_data: if not current_price_data:
return 0.0 return 0.0
current_price = current_price_data['last'] current_price = current_price_data['last']
symbol_info = self.data_provider.get_symbol_info(self.symbol) contract_size = self._get_contract_size()
contract_size = (symbol_info['trade_contract_size']
if symbol_info and isinstance(symbol_info, dict)
else 100) if symbol_info else 100
total = 0.0 total = 0.0
for pos in self.positions: for pos in self.positions:
if pos['position_type'] == 'long': if pos['position_type'] == 'long':
@@ -338,10 +361,7 @@ class PositionManager:
return total return total
def _record_closed_trade(self, position, close_price, close_reason): def _record_closed_trade(self, position, close_price, close_reason):
symbol_info = self.data_provider.get_symbol_info(position['symbol']) contract_size = self._get_contract_size()
contract_size = (symbol_info['trade_contract_size']
if symbol_info and isinstance(symbol_info, dict)
else 100) if symbol_info else 100
if position['position_type'] == 'long': if position['position_type'] == 'long':
pnl = (close_price - position['entry_price']) * position['quantity'] * contract_size pnl = (close_price - position['entry_price']) * position['quantity'] * contract_size
else: else:
+75
View File
@@ -0,0 +1,75 @@
优化完成时间: 2026-05-14 14:19:14.534049
最佳适应度: 169.69
最佳参数:
ma_cross_short_window: 13
ma_cross_long_window: 17
rsi_period: 7
rsi_overbought: 73
rsi_oversold: 27
bollinger_period: 24
bollinger_std_dev: 1.9616407387901151
macd_fast_ema: 14
macd_slow_ema: 24
macd_signal_period: 9
mean_reversion_period: 30
mean_reversion_std_dev: 2.4663422649047333
momentum_breakout_period: 50
momentum_breakout_momentum_period: 27
kdj_period: 12
turtle_period: 30
wave_ema_short: 3
wave_ema_medium: 11
wave_ema_long: 46
wave_period: 11
wave_range_period: 35
wave_adx_period: 20
wave_momentum_period: 13
wave_range_threshold: 0.01
wave_adx_threshold: 21
daily_breakout_bars_count: 2008
buy_threshold: 1.0315663601353515
sell_threshold: -2.312867704316165
stop_loss_pct: -0.028495655271807224
profit_retracement_pct: 0.07523134661548606
min_profit_for_trailing: 0.005
take_profit_pct: 0.3970073007719148
max_holding_minutes: 156
min_profit_for_time_exit: 0.006397809476304411
weight_MACrossStrategy: 0.32048324544087786
weight_RSIStrategy: 1.906009222972592
weight_BollingerStrategy: 0.9232444456662969
weight_MeanReversionStrategy: 1.1649306516234597
weight_MomentumBreakoutStrategy: 0.8769099086178171
weight_MACDStrategy: 0.748930985323352
weight_KDJStrategy: 1.4647470623067596
weight_TurtleStrategy: 1.637931136008917
weight_DailyBreakoutStrategy: 1.478598649389487
weight_WaveTheoryStrategy: 0.818283180489803
market_trend_period: 83
market_retracement_tolerance: 0.15567164907853615
market_volume_period: 29
market_volume_ma_period: 23
trend_price_breakout_weight: 0.1504199442999585
trend_volume_confirmation_weight: 0.12782205952949638
trend_momentum_oscillator_weight: 0.3645053274270841
trend_moving_average_weight: 0.4092273363154768
trend_strong_threshold: 0.7940886082643032
trend_weak_threshold: 0.4565953163045441
trend_volume_spike: 2.7329673335105396
trend_oversold: 32
trend_overbought: 68
confidence_high: 0.6813641209473742
confidence_medium: 0.6336441706563001
代数统计:
代数 1: 最佳=166.40, 平均=33.80
代数 2: 最佳=116.73, 平均=50.63
代数 3: 最佳=104.87, 平均=55.76
代数 4: 最佳=147.56, 平均=74.03
代数 5: 最佳=141.85, 平均=57.90
代数 6: 最佳=141.85, 平均=70.85
代数 7: 最佳=154.87, 平均=85.11
代数 8: 最佳=169.69, 平均=94.60
代数 9: 最佳=169.69, 平均=99.20
代数 10: 最佳=169.69, 平均=112.85