实现单品种连续多合约回测

This commit is contained in:
2025-06-19 15:28:26 +08:00
parent ce5ad27bab
commit 355e451aac
6 changed files with 2016 additions and 22 deletions

View File

@@ -20,6 +20,7 @@ class BacktestEngine:
data_manager: DataManager,
strategy_class: Type[Strategy],
strategy_params: Dict[str, Any],
current_segment_symbol: str,
initial_capital: float = 100000.0,
slippage_rate: float = 0.0001,
commission_rate: float = 0.0002):
@@ -41,6 +42,7 @@ class BacktestEngine:
commission_rate=commission_rate
)
self.context = BacktestContext(self.data_manager, self.simulator)
self.current_segment_symbol = current_segment_symbol
# 实例化策略
self.strategy = strategy_class(self.context, **strategy_params)
@@ -107,6 +109,8 @@ class BacktestEngine:
self.portfolio_snapshots.append(snapshot)
self.all_bars.append(current_bar)
last_processed_bar = current_bar
# 记录交易历史(从模拟器获取)
# 简化处理每次获取模拟器中的所有交易历史并更新引擎的trade_history
# 更好的做法是模拟器提供一个方法,返回自上次查询以来的新增交易
@@ -117,7 +121,25 @@ class BacktestEngine:
# 这里可以做一个增量获取,或者简单地在循环结束后统一获取
# 目前我们在执行模拟器中已经将成交记录在了 trade_log 中,所以这里不用重复记录,
# 而是等到回测结束后再统一获取。
pass # 不在此处记录 self.trade_history
# 不在此处记录 self.trade_history
print("\n--- 回测片段结束,检查并平仓所有持仓 ---")
if last_processed_bar: # 确保至少有一根Bar被处理过
positions_to_close = self.simulator.get_current_positions()
for symbol_held, quantity in positions_to_close.items():
if quantity != 0:
print(f"[{last_processed_bar.datetime}] 回测结束平仓: 平仓 {symbol_held} ({quantity} 手) @ {last_processed_bar.close:.2f}")
direction = "SELL" if quantity > 0 else "BUY"
volume = abs(quantity)
# 使用当前合约的最后一根Bar的价格进行平仓
# 注意这里假设平仓的symbol_held就是当前segment的symbol
# 如果策略可能同时持有其他旧合约的仓位(多主力同时持有),这里需要更复杂的逻辑来获取正确的平仓价格
# 但在主力合约切换场景下,通常只持有当前主力合约的仓位。
rollover_order = Order(symbol=symbol_held, direction=direction, volume=volume, price_type="MARKET")
self.simulator.send_order(rollover_order, current_bar=last_processed_bar)
else:
print("没有处理任何Bar无需平仓。")
# 回测结束后,获取所有交易记录
self.trade_history = self.simulator.get_trade_history()
@@ -135,4 +157,10 @@ class BacktestEngine:
"trade_history": self.trade_history,
"initial_capital": self.simulator.initial_capital, # 或 self.initial_capital
"all_bars": self.all_bars
}
}
def get_simulator(self) -> ExecutionSimulator: # <--- 新增的方法
"""
返回引擎内部的 ExecutionSimulator 实例,以便外部可以访问和修改其状态。
"""
return self.simulator

View File

@@ -236,7 +236,7 @@ class ExecutionSimulator:
float: 当前的投资组合总价值。
"""
total_value = self.cash
# 在单品种场景下,我们假设 self.positions 最多只包含一个品种
# 并且这个品种就是 current_bar.symbol 所代表的品种
symbol_in_position = list(self.positions.keys())[0] if self.positions else None
@@ -246,7 +246,7 @@ class ExecutionSimulator:
# 持仓市值 = 数量 * 当前市场价格 (current_bar.close)
# 无论多头(quantity > 0)还是空头(quantity < 0),这个计算都是正确的
total_value += quantity * current_bar.close
# 您也可以选择在这里打印调试信息
# print(f" DEBUG Portfolio Value Calculation: Cash={self.cash:.2f}, "
# f"Position for {symbol_in_position}: {quantity} @ {current_bar.close:.2f}, "
@@ -254,7 +254,7 @@ class ExecutionSimulator:
# 如果没有持仓或者持仓品种与当前Bar品种不符 (理论上单品种不会发生)
# 那么 total_value 依然是 self.cash
return total_value
def get_current_positions(self) -> Dict[str, int]:
@@ -268,3 +268,22 @@ class ExecutionSimulator:
返回所有成交记录的副本。
"""
return self.trade_log.copy()
def reset(self, new_initial_capital: float = None, new_initial_positions: Dict[str, int] = None) -> None:
"""
重置模拟器状态到新的初始条件。
可以在总回测开始时调用,或在合约切换时调整资金和持仓。
"""
print("ExecutionSimulator: 重置状态。")
self.cash = new_initial_capital if new_initial_capital is not None else self.initial_capital
self.positions = new_initial_positions.copy() if new_initial_positions is not None else {}
self.trade_history = []
self.current_orders = {}
def clear_trade_history(self) -> None:
"""
清空当前模拟器的交易历史。
在每个合约片段结束时调用,以便我们只收集当前片段的交易记录。
"""
print("ExecutionSimulator: 清空交易历史。")
self.trade_history = []

View File

@@ -101,10 +101,10 @@ class SimpleLimitBuyStrategy(Strategy):
order = Order(
id=order_id,
symbol=self.symbol,
direction="SELL",
direction="BUY",
volume=trade_volume,
price_type="LIMIT",
limit_price=limit_price,
price_type="MARKET",
# limit_price=limit_price,
submitted_time=bar.datetime
)