实现单品种连续多合约回测
This commit is contained in:
@@ -160,7 +160,7 @@ def collect_and_save_tqsdk_data_stream(
|
||||
save_folder = os.path.join(output_dir, safe_symbol)
|
||||
os.makedirs(save_folder, exist_ok=True)
|
||||
|
||||
file_name = f"{safe_symbol}_{freq_folder}_{start_date_str.replace('-', '')}_{end_date_str.replace('-', '')}_{freq}.csv"
|
||||
file_name = f"{safe_symbol}_{freq}.csv"
|
||||
file_path = os.path.join(save_folder, file_name)
|
||||
|
||||
df.to_csv(file_path, index=True)
|
||||
@@ -190,10 +190,10 @@ if __name__ == "__main__":
|
||||
# 示例1: 在回测模式下获取沪深300指数主连的日线数据 (用于历史回测)
|
||||
# 这种方式适合获取相对较短或中等长度的历史K线数据。
|
||||
df_if_backtest_daily = collect_and_save_tqsdk_data_stream(
|
||||
symbol="SHFE.rb2501",
|
||||
freq="min1",
|
||||
start_date_str="2024-09-01",
|
||||
end_date_str="2024-12-01",
|
||||
symbol="SHFE.rb2410",
|
||||
freq="min60",
|
||||
start_date_str="2024-05-01",
|
||||
end_date_str="2024-09-01",
|
||||
mode="backtest", # 指定为回测模式
|
||||
tq_user=TQ_USER_NAME,
|
||||
tq_pwd=TQ_PASSWORD
|
||||
|
||||
23
main.ipynb
23
main.ipynb
@@ -2,7 +2,7 @@
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": 3,
|
||||
"id": "initial_id",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -19,7 +19,7 @@
|
||||
"The autoreload extension is already loaded. To reload it, use:\n",
|
||||
" %reload_ext autoreload\n",
|
||||
"初始化数据管理器...\n",
|
||||
"数据加载成功: /mnt/d/PyProject/NewQuant/data/data/SHFE_rb2501/SHFE_rb2501_m60_20240901_20241201_min60.csv\n",
|
||||
"数据加载成功: /mnt/d/PyProject/NewQuant/data/data/SHFE_rb2501/SHFE_rb2501_min60.csv\n",
|
||||
"数据范围从 2024-08-30 14:00:00 到 2024-11-29 21:00:00\n",
|
||||
"总计 404 条记录。\n",
|
||||
"\n",
|
||||
@@ -444,9 +444,12 @@
|
||||
"[2024-11-29 14:00:00] Strategy processing Bar. Current close price: 3318.00. Current Portfolio Value: 99973.05\n",
|
||||
"[2024-11-29 21:00:00] Strategy processing Bar. Current close price: 3301.00. Current Portfolio Value: 99990.05\n",
|
||||
"Bar 对象流生成完毕。\n",
|
||||
"\n",
|
||||
"--- 回测片段结束,检查并平仓所有持仓 ---\n",
|
||||
"[2024-11-29 21:00:00] 回测结束平仓: 平仓 SHFE_rb2501 (-1 手) @ 3301.00。\n",
|
||||
"--- 回测结束 ---\n",
|
||||
"总计处理了 404 根K线。\n",
|
||||
"总计发生了 1 笔交易。\n",
|
||||
"总计发生了 2 笔交易。\n",
|
||||
"\n",
|
||||
"回测运行完毕。\n",
|
||||
"\n",
|
||||
@@ -462,11 +465,12 @@
|
||||
"最大回撤 : 0.63%\n",
|
||||
"夏普比率 : -0.02\n",
|
||||
"卡玛比率 : -0.04\n",
|
||||
"总交易次数 : 1\n",
|
||||
"交易成本 : 0.66\n",
|
||||
"总交易次数 : 2\n",
|
||||
"交易成本 : 1.32\n",
|
||||
"\n",
|
||||
"--- 部分交易明细 (最近5笔) ---\n",
|
||||
" 2024-08-30 21:00:00 | SELL | SHFE_rb2501 | Vol: 1 | Price: 3291.70 | Commission: 0.66\n",
|
||||
" 2024-11-29 21:00:00 | BUY | SHFE_rb2501 | Vol: 1 | Price: 3304.30 | Commission: 0.66\n",
|
||||
"正在绘制绩效图表...\n"
|
||||
]
|
||||
},
|
||||
@@ -512,7 +516,7 @@
|
||||
"def main():\n",
|
||||
" # --- 配置参数 ---\n",
|
||||
" # 获取当前脚本所在目录,假设数据文件在项目根目录下的 data 文件夹内\n",
|
||||
" data_file_path = '/mnt/d/PyProject/NewQuant/data/data/SHFE_rb2501/SHFE_rb2501_m60_20240901_20241201_min60.csv'\n",
|
||||
" data_file_path = '/mnt/d/PyProject/NewQuant/data/data/SHFE_rb2501/SHFE_rb2501_min60.csv'\n",
|
||||
"\n",
|
||||
" initial_capital = 100000.0\n",
|
||||
" slippage_rate = 0.001 # 假设每笔交易0.1%的滑点\n",
|
||||
@@ -536,6 +540,7 @@
|
||||
" engine = BacktestEngine(\n",
|
||||
" data_manager=data_manager,\n",
|
||||
" strategy_class=SimpleLimitBuyStrategy,\n",
|
||||
" current_segment_symbol='SHFE_rb2501',\n",
|
||||
" strategy_params=strategy_parameters,\n",
|
||||
" initial_capital=initial_capital,\n",
|
||||
" slippage_rate=slippage_rate,\n",
|
||||
@@ -582,7 +587,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"execution_count": 4,
|
||||
"id": "9dd93e564f0e2b55",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -597,7 +602,7 @@
|
||||
"'/home/liaozhaorun/.fonts/simhei.ttf'"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@@ -611,7 +616,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"execution_count": 5,
|
||||
"id": "a14196c49af33461",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
|
||||
1942
main_multi.ipynb
Normal file
1942
main_multi.ipynb
Normal file
File diff suppressed because one or more lines are too long
@@ -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()
|
||||
@@ -136,3 +158,9 @@ class BacktestEngine:
|
||||
"initial_capital": self.simulator.initial_capital, # 或 self.initial_capital
|
||||
"all_bars": self.all_bars
|
||||
}
|
||||
|
||||
def get_simulator(self) -> ExecutionSimulator: # <--- 新增的方法
|
||||
"""
|
||||
返回引擎内部的 ExecutionSimulator 实例,以便外部可以访问和修改其状态。
|
||||
"""
|
||||
return self.simulator
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user