Files
mt5_python_ea_suite/backtest.py
T

160 lines
6.9 KiB
Python
Raw Normal View History

2025-07-25 17:35:01 +08:00
import pandas as pd
2025-08-11 18:06:53 +08:00
from trade_logger import trade_logger
from risk_management import RiskController
from config import SIGNAL_THRESHOLDS
from logger import logger
2025-07-25 17:35:01 +08:00
class BacktestEngine:
def __init__(self, df):
"""
df: 包含历史k线的DataFrame,至少包括open, high, low, close字段
"""
self.df = df
def run_strategy(self, strategy):
"""
执行策略的run_backtest,得到信号序列
"""
2025-08-11 18:06:53 +08:00
signals = strategy.run_backtest(self.df)
# 记录策略信号统计
buy_count = (signals == 1).sum()
sell_count = (signals == -1).sum()
neutral_count = (signals == 0).sum()
logger.info(f"策略 {strategy.__class__.__module__} 信号统计 - 买入: {buy_count}, 卖出: {sell_count}, 中性: {neutral_count}")
return signals
2025-07-25 17:35:01 +08:00
2025-08-11 18:06:53 +08:00
def combine_signals(self, signals_list, weights):
2025-07-25 17:35:01 +08:00
"""
多策略信号加权合成,并根据阈值生成最终信号
返回合成信号序列
"""
df_signals = pd.concat(signals_list, axis=1).fillna(0)
weighted_signals = df_signals * weights
combined = weighted_signals.sum(axis=1)
def apply_threshold(score):
2025-08-11 18:06:53 +08:00
if score > SIGNAL_THRESHOLDS["buy_threshold"]:
2025-07-25 17:35:01 +08:00
return 1
2025-08-11 18:06:53 +08:00
elif score < SIGNAL_THRESHOLDS["sell_threshold"]:
2025-07-25 17:35:01 +08:00
return -1
else:
return 0
combined_signal = combined.apply(apply_threshold)
2025-08-11 18:06:53 +08:00
# 记录信号统计
buy_signals = (combined_signal == 1).sum()
sell_signals = (combined_signal == -1).sum()
neutral_signals = (combined_signal == 0).sum()
logger.info(f"信号统计 - 买入: {buy_signals}, 卖出: {sell_signals}, 中性: {neutral_signals}")
2025-07-25 17:35:01 +08:00
return combined_signal
def calc_returns(self, signals):
"""
根据信号计算策略回测收益率(简化版)
"""
df = self.df.copy()
df['signal'] = signals.shift(1).fillna(0) # 防止未来函数
df['returns'] = df['close'].pct_change()
df['strategy_returns'] = df['signal'] * df['returns']
cum_ret = (1 + df['strategy_returns']).cumprod() - 1
return cum_ret
2025-08-11 18:06:53 +08:00
def calc_returns_with_trades(self, signals, symbol="XAUUSD"):
"""
根据信号计算策略回测收益率,并记录每一笔交易
使用RiskController进行风险管理,防止频繁交易
"""
df = self.df.copy()
df['signal'] = signals.shift(1).fillna(0) # 防止未来函数
# 初始化风险管理器
risk_controller = RiskController()
# 记录交易
in_position = False
position_direction = None
position_price = 0
position_time = None
last_trade_time = None
min_trade_interval = 0 # 取消最小交易间隔限制
for i in range(1, len(df)):
current_signal = df['signal'].iloc[i]
current_price = df['close'].iloc[i]
current_time = df.index[i] if hasattr(df.index, 'to_pydatetime') else i
# 取消交易间隔限制检查
can_trade = True
# 定期打印进度(每1000根K线)
if i % 1000 == 0:
logger.info(f"回测进度: {i}/{len(df)-1}")
# 开仓信号
if current_signal != 0 and not in_position and can_trade:
direction = 'buy' if current_signal == 1 else 'sell'
# 检查风险管理器是否允许交易
if risk_controller.should_allow_trade(direction):
trade_logger.open_position(symbol, direction, current_price, current_time, f"signal_{current_signal}")
in_position = True
position_direction = direction
position_price = current_price
position_time = current_time
last_trade_time = current_time
# 更新RiskController的持仓信息
position_type = "long" if direction == 'buy' else "short"
risk_controller.update_position_entry(current_price, position_type)
# 持仓时检查风险管理条件(无论信号如何)
if in_position:
# 使用RiskController检查风险管理条件
risk_action, risk_reason = risk_controller.check_risk_management(current_price)
if risk_action != "none":
trade_logger.close_position(symbol, current_price, current_time, f"risk_management_{risk_action}")
risk_controller.execute_risk_action(risk_action, risk_reason)
in_position = False
continue # 跳过后续信号处理
# 中性信号处理
elif current_signal == 0 and in_position:
trade_logger.close_position(symbol, current_price, current_time, "signal_to_neutral")
in_position = False
# 反向信号(取消交易间隔限制)
elif current_signal != 0 and in_position and can_trade:
# 只有当信号明显反向时才平仓并反向
if (current_signal == 1 and position_direction == 'sell') or \
(current_signal == -1 and position_direction == 'buy'):
# 先平仓
trade_logger.close_position(symbol, current_price, current_time, "signal_reverse")
in_position = False
# 立即开新仓(取消等待间隔)
direction = 'buy' if current_signal == 1 else 'sell'
if risk_controller.should_allow_trade(direction):
trade_logger.open_position(symbol, direction, current_price, current_time, f"signal_{current_signal}")
in_position = True
position_direction = direction
position_price = current_price
position_time = current_time
last_trade_time = current_time
# 更新RiskController的持仓信息
position_type = "long" if direction == 'buy' else "short"
risk_controller.update_position_entry(current_price, position_type)
# 持仓到最后
elif in_position and i == len(df) - 1:
trade_logger.close_position(symbol, current_price, current_time, "end_of_backtest")
in_position = False
# 计算收益率
df['returns'] = df['close'].pct_change()
df['strategy_returns'] = df['signal'] * df['returns']
cum_ret = (1 + df['strategy_returns']).cumprod() - 1
return cum_ret