Files
NewQuant/futures_trading_strategies/SA/Spectral/search_SpectralTrendStrategy.ipynb

432 lines
111 KiB
Plaintext
Raw Normal View History

{
"cells": [
{
"cell_type": "code",
"id": "522f09ca7b3fe929",
"metadata": {
"ExecuteTime": {
"end_time": "2025-11-06T15:17:42.402991Z",
"start_time": "2025-11-06T15:17:42.369481Z"
}
},
"source": [
"import sys\n",
"\n",
"if '/mnt/d/PyProject/NewQuant/' not in sys.path:\n",
" sys.path.append('/mnt/d/PyProject/NewQuant/')\n",
"\n",
"%load_ext autoreload\n",
"%autoreload 2\n",
"\n"
],
"outputs": [],
"execution_count": 1
},
{
"cell_type": "code",
"id": "4f7e4b438cea750e",
"metadata": {
"ExecuteTime": {
"end_time": "2025-11-06T15:17:43.407501Z",
"start_time": "2025-11-06T15:17:42.408994Z"
}
},
"source": [
"import pandas as pd\n",
"from datetime import datetime\n",
"import itertools\n",
"from typing import Dict, Any, List, Tuple, Optional\n",
"import multiprocessing # 导入 multiprocessing 模块\n",
"import math # 保留 math 导入,因为您的策略内部可能需要用到数学函数\n",
"\n",
"# 导入所有必要的模块\n",
"# 请确保这些导入路径与您的项目结构相符\n",
"from src.analysis.grid_search_analyzer import GridSearchAnalyzer\n",
"from src.analysis.result_analyzer import ResultAnalyzer\n",
"from src.common_utils import generate_parameter_range\n",
"from src.data_manager import DataManager\n",
"from src.backtest_engine import BacktestEngine\n",
"# 导入策略类\n",
"\n",
"import builtins\n",
"\n",
"\n",
"origin_print = print\n",
"\n"
],
"outputs": [],
"execution_count": 2
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-11-06T15:17:43.431465Z",
"start_time": "2025-11-06T15:17:43.412037Z"
}
},
"cell_type": "code",
"source": [
"\n",
"def slient_print(*args):\n",
" pass\n"
],
"id": "f903fd2761d446cd",
"outputs": [],
"execution_count": 3
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-11-06T15:23:19.406712Z",
"start_time": "2025-11-06T15:17:43.436160Z"
}
},
"cell_type": "code",
"source": [
"\n",
"# --- 主执行块 ---\n",
"# 这是多进程代码的入口点,必须在 'if __name__ == \"__main__\":' 保护块中\n",
"# 确保 autoreload 启用 (在Jupyter Notebook中使用纯Python脚本运行时可移除)\n",
"# %load_ext autoreload\n",
"# %autoreload 2\n",
"\n",
"from src.strategies.TestStrategy.TestStrategy import AccelBreakoutStrategy\n",
"from src.strategies.RsiStrategy.utils import run_single_backtest\n",
"\n",
"# --- 全局配置 ---\n",
"data_file_path = \"D:/PyProject/NewQuant/data/data/KQ_m@CZCE_MA/KQ_m@CZCE_MA_min15.csv\"\n",
"\n",
"\n",
"initial_capital = 100000.0\n",
"slippage_rate = 0.0000\n",
"commission_rate = 0.0000\n",
"global_config = {\n",
" 'symbol': 'MA',\n",
"}\n",
"# 确保每个合约的tick_size在这里定义或获取\n",
"RB_TICK_SIZE = 1.0 # 螺纹钢的最小变动单位\n",
"\n",
"# --- 定义参数网格 ---\n",
"param1_name = \"breakout_period\"\n",
"param1_values = generate_parameter_range(start=5, end=105, step=10)\n",
"param2_name = \"trend_period\"\n",
"param2_values = generate_parameter_range(start=5, end=105, step=10)\n",
"# param2_name = \"dominance_multiplier\"\n",
"# param2_values = generate_parameter_range(start=0, end=5, step=0.5)\n",
"optimization_metric = 'total_return'\n",
"\n",
"# 生成所有参数组合\n",
"param_combinations = list(itertools.product(param1_values, param2_values))\n",
"total_combinations = len(param_combinations)\n",
"print(f\"总计 {total_combinations} 种参数组合需要回测。\")\n",
"\n",
"all_results: List[Dict[str, Any]] = []\n",
"grid_results: List[Dict[str, Any]] = []\n",
"\n",
"# 准备传递给每个子进程的公共配置字典\n",
"common_config_for_processes = {\n",
" 'main_symbol': global_config['symbol'],\n",
" 'symbol': global_config['symbol'],\n",
" 'data_path': data_file_path,\n",
" 'initial_capital': initial_capital,\n",
" 'slippage_rate': slippage_rate,\n",
" 'commission_rate': commission_rate,\n",
" 'start_time': datetime(2022, 1, 1), # 回测起始时间\n",
" 'end_time': datetime(2024, 6, 1), # 回测结束时间\n",
" 'roll_over_mode': True, # 保持换月模式\n",
" 'param1_name': param1_name,\n",
" 'param2_name': param2_name,\n",
" 'optimization_metric': optimization_metric,\n",
" 'strategy': AccelBreakoutStrategy,\n",
" 'order_direction': ['BUY', 'SELL'],\n",
" # 'hawkes_entry_percent': 0.9,\n",
" 'stop_loss_tick': 5\n",
"}\n",
"\n",
"# 确定要使用的进程数量 (通常是CPU核心数)\n",
"num_processes = int(multiprocessing.cpu_count() * 0.6)\n",
"if num_processes < 1:\n",
" num_processes = 1\n",
"\n",
"print(f\"--- 启动多进程网格搜索,使用 {num_processes} 个进程 ---\")\n",
"\n",
"builtins.print = slient_print\n",
"\n",
"# 创建一个进程池\n",
"with multiprocessing.Pool(processes=num_processes) as pool:\n",
" # 准备 run_single_backtest 函数的参数列表\n",
" # starmap 需要一个可迭代对象,其中每个元素是传递给目标函数的参数元组\n",
" args_for_starmap = [\n",
" (combo, common_config_for_processes) for combo in param_combinations\n",
" ]\n",
"\n",
" run_single_backtest(*args_for_starmap[0])\n",
"\n",
" # 使用 starmap() 来并行执行 run_single_backtest 函数\n",
" # starmap 是阻塞的,会等待所有任务完成并返回结果列表\n",
" for i, result_entry in enumerate(pool.starmap(run_single_backtest, args_for_starmap)):\n",
" if result_entry: # 确保结果不为空\n",
" all_results.append(result_entry)\n",
" # 仅将成功的(无错误的)结果添加到用于网格分析的列表中\n",
" if 'error' not in result_entry:\n",
" grid_results.append(\n",
" {\n",
" param1_name: result_entry.get(param1_name),\n",
" param2_name: result_entry.get(param2_name),\n",
" optimization_metric: result_entry.get(optimization_metric, 0.0),\n",
" }\n",
" )\n",
" else:\n",
" # 对于失败的组合,将其优化指标设置为一个特殊值,便于识别\n",
" grid_results.append(\n",
" {\n",
" param1_name: result_entry.get(param1_name),\n",
" param2_name: result_entry.get(param2_name),\n",
" optimization_metric: float('-inf'), # 用负无穷表示失败\n",
" 'error_message': result_entry['error']\n",
" }\n",
" )\n",
"\n",
"builtins.print = origin_print\n",
"print(\"\\n--- 网格搜索回测完毕 ---\")"
],
"id": "aed5938660e42b20",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"总计 121 种参数组合需要回测。\n",
"--- 启动多进程网格搜索,使用 12 个进程 ---\n",
"\n",
"--- 网格搜索回测完毕 ---\n"
]
}
],
"execution_count": 4
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-11-06T15:23:19.636729Z",
"start_time": "2025-11-06T15:23:19.420221Z"
}
},
"cell_type": "code",
"source": [
"\n",
"# --- 5. 后处理和最佳结果选择 ---\n",
"if all_results:\n",
" results_df = pd.DataFrame(all_results)\n",
" # print(\"\\n--- 所有回测结果汇总 ---\")\n",
" # # 确保打印时浮点数格式化\n",
" # pd.set_option('display.float_format', lambda x: '%.4f' % x)\n",
" # print(results_df.to_string())\n",
"\n",
" # 找到最佳组合 (排除有错误的)\n",
" # 过滤掉包含 'error' 键的行,或者 'error' 键的值不为空的行\n",
" # 同时确保优化指标是数值,并且不为无穷大\n",
" print(results_df.info())\n",
" successful_results_df = results_df[(pd.to_numeric(results_df[optimization_metric], errors='coerce').notna()) &\n",
" (pd.to_numeric(results_df[optimization_metric], errors='coerce') != float(\n",
" '-inf'))\n",
" ].copy() # 使用 .copy() 避免 SettingWithCopyWarning\n",
"\n",
" if not successful_results_df.empty and optimization_metric in successful_results_df.columns:\n",
" # 确保优化指标列是数值类型\n",
" successful_results_df[optimization_metric] = pd.to_numeric(successful_results_df[optimization_metric],\n",
" errors='coerce')\n",
"\n",
" if not successful_results_df.empty and optimization_metric in successful_results_df.columns:\n",
" # 过滤掉NaN值如果所有夏普比率都是NaN则可能没有有效结果\n",
" normal_results = successful_results_df[\n",
" (results_df['total_trades'] > 100)\n",
" # &\n",
" # (results_df['profit_factor'] < 3.)\n",
" ]\n",
" if len(normal_results) > 0:\n",
" best_result = normal_results.loc[(normal_results[optimization_metric].idxmax())]\n",
" print(f\"\\n--- 最优参数组合 (按{optimization_metric}) ---\")\n",
" print(best_result)\n",
" else:\n",
" print('ERROR!!!!!!!!!!!!!!!!!!!!')\n",
"\n",
" # 找到最大值的索引\n",
" # best_result = successful_results_df.loc[successful_results_df[optimization_metric].idxmax()]\n",
" # print(f\"\\n--- 最优参数组合 (按 {optimization_metric}) ---\")\n",
" # print(best_result)\n",
"\n",
" # 导出到CSV\n",
" output_filename = f\"grid_search_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv\"\n",
" # results_df.to_csv(output_filename, index=False, encoding='utf-8')\n",
" # print(f\"\\n所有结果已导出到: {output_filename}\")\n",
"\n",
" # 打印枢轴表\n",
" grid_df = pd.DataFrame(grid_results)\n",
" # 确保优化指标列是数值类型,非数值的(如 -inf在pandas中可能被正确处理\n",
" grid_df[optimization_metric] = pd.to_numeric(grid_df[optimization_metric], errors='coerce')\n",
"\n",
" pivot_table = grid_df.pivot_table(\n",
" index=param1_name, columns=param2_name, values=optimization_metric\n",
" )\n",
" # print(f\"\\n{optimization_metric} 网格结果 (Pivoted):\")\n",
" # print(pivot_table.to_string())\n",
" else:\n",
" print(f\"\\n没有成功的组合结果可供分析或优化指标 '{optimization_metric}' 不在结果中,或所有组合均失败。\")\n",
"else:\n",
" print(\"没有可用的回测结果。\")\n",
"print(\"\\n--- 动态网格搜索完成 ---\")\n",
"\n",
"# --- 6. 可视化 (依赖 GridSearchAnalyzer) ---\n",
"if grid_results:\n",
" grid_analyzer = GridSearchAnalyzer(grid_results, optimization_metric)\n",
" grid_analyzer.find_best_parameters() # 这会找到并打印最佳参数\n",
" grid_analyzer.plot_heatmap() # 这会绘制热力图\n",
"else:\n",
" print(\"\\n没有生成任何网格搜索结果无法进行分析。\")"
],
"id": "a1c18a2776fcaba2",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<class 'pandas.core.frame.DataFrame'>\n",
"RangeIndex: 121 entries, 0 to 120\n",
"Data columns (total 39 columns):\n",
" # Column Non-Null Count Dtype \n",
"--- ------ -------------- ----- \n",
" 0 main_symbol 121 non-null object \n",
" 1 trade_volume 121 non-null int64 \n",
" 2 breakout_period 121 non-null int64 \n",
" 3 trend_period 121 non-null int64 \n",
" 4 order_direction 121 non-null object \n",
" 5 enable_log 121 non-null bool \n",
" 6 初始资金 121 non-null float64\n",
" 7 最终资金 121 non-null float64\n",
" 8 总收益率 121 non-null float64\n",
" 9 年化收益率 121 non-null float64\n",
" 10 最大回撤 121 non-null float64\n",
" 11 夏普比率 121 non-null float64\n",
" 12 卡玛比率 121 non-null float64\n",
" 13 总交易次数 121 non-null int64 \n",
" 14 交易成本 121 non-null float64\n",
" 15 总实现盈亏 121 non-null float64\n",
" 16 胜率 121 non-null float64\n",
" 17 盈亏比 121 non-null float64\n",
" 18 盈利交易次数 121 non-null int64 \n",
" 19 亏损交易次数 121 non-null int64 \n",
" 20 平均每次盈利 121 non-null float64\n",
" 21 平均每次亏损 121 non-null float64\n",
" 22 initial_capital 121 non-null float64\n",
" 23 final_capital 121 non-null float64\n",
" 24 total_return 121 non-null float64\n",
" 25 annualized_return 121 non-null float64\n",
" 26 max_drawdown 121 non-null float64\n",
" 27 sharpe_ratio 121 non-null float64\n",
" 28 calmar_ratio 121 non-null float64\n",
" 29 sortino_ratio 121 non-null float64\n",
" 30 total_trades 121 non-null int64 \n",
" 31 transaction_costs 121 non-null float64\n",
" 32 total_realized_pnl 121 non-null float64\n",
" 33 win_rate 121 non-null float64\n",
" 34 profit_loss_ratio 121 non-null float64\n",
" 35 winning_trades_count 121 non-null int64 \n",
" 36 losing_trades_count 121 non-null int64 \n",
" 37 avg_profit_per_trade 121 non-null float64\n",
" 38 avg_loss_per_trade 121 non-null float64\n",
"dtypes: bool(1), float64(27), int64(9), object(2)\n",
"memory usage: 36.2+ KB\n",
"None\n",
"\n",
"--- 最优参数组合 (按total_return) ---\n",
"main_symbol MA\n",
"trade_volume 1\n",
"breakout_period 95\n",
"trend_period 65\n",
"order_direction [BUY, SELL]\n",
"enable_log False\n",
"初始资金 100000.0\n",
"最终资金 99453.0\n",
"总收益率 -0.00547\n",
"年化收益率 -0.001573\n",
"最大回撤 0.0111\n",
"夏普比率 -0.086754\n",
"卡玛比率 -0.141717\n",
"总交易次数 238\n",
"交易成本 0.0\n",
"总实现盈亏 -547.0\n",
"胜率 0.364407\n",
"盈亏比 1.441499\n",
"盈利交易次数 43\n",
"亏损交易次数 75\n",
"平均每次盈利 60.581395\n",
"平均每次亏损 -42.026667\n",
"initial_capital 100000.0\n",
"final_capital 99453.0\n",
"total_return -0.00547\n",
"annualized_return -0.001573\n",
"max_drawdown 0.0111\n",
"sharpe_ratio -0.086754\n",
"calmar_ratio -0.141717\n",
"sortino_ratio -0.10056\n",
"total_trades 238\n",
"transaction_costs 0.0\n",
"total_realized_pnl -547.0\n",
"win_rate 0.364407\n",
"profit_loss_ratio 1.441499\n",
"winning_trades_count 43\n",
"losing_trades_count 75\n",
"avg_profit_per_trade 60.581395\n",
"avg_loss_per_trade -42.026667\n",
"Name: 105, dtype: object\n",
"\n",
"--- 动态网格搜索完成 ---\n",
"\n",
"--- 最佳参数组合 ---\n",
" breakout_period: 95\n",
" trend_period: 65\n",
" total_return: -0.0055\n",
"[5, 105, 5, 105]\n"
]
},
{
"data": {
"text/plain": [
"<Figure size 1000x800 with 2 Axes>"
],
"image/png": "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
},
"metadata": {},
"output_type": "display_data",
"jetTransient": {
"display_id": null
}
}
],
"execution_count": 5
}
],
"metadata": {
"kernelspec": {
"display_name": "quant",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}