2025-07-10 15:07:31 +08:00
|
|
|
|
{
|
|
|
|
|
|
"cells": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "code",
|
2025-07-28 14:36:58 +08:00
|
|
|
|
"execution_count": 6,
|
2025-07-10 15:07:31 +08:00
|
|
|
|
"id": "782ec73f",
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"ExecuteTime": {
|
2025-07-28 14:36:58 +08:00
|
|
|
|
"end_time": "2025-07-27T15:30:02.993254Z",
|
|
|
|
|
|
"start_time": "2025-07-27T15:30:02.962786Z"
|
2025-07-10 15:07:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-07-28 14:36:58 +08:00
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"The autoreload extension is already loaded. To reload it, use:\n",
|
|
|
|
|
|
" %reload_ext autoreload\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-07-10 15:07:31 +08:00
|
|
|
|
"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",
|
|
|
|
|
|
"from src.strategies.OpenTwoFactorStrategy import SimpleLimitBuyStrategyLong, SimpleLimitBuyStrategyShort, \\\n",
|
|
|
|
|
|
" SimpleLimitBuyStrategy\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"import builtins\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"%load_ext autoreload\n",
|
|
|
|
|
|
"%autoreload 2\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"origin_print = print\n"
|
2025-07-28 14:36:58 +08:00
|
|
|
|
]
|
2025-07-10 15:07:31 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "code",
|
2025-07-28 14:36:58 +08:00
|
|
|
|
"execution_count": 7,
|
2025-07-10 15:07:31 +08:00
|
|
|
|
"id": "76f9a2e9",
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"ExecuteTime": {
|
2025-07-28 14:36:58 +08:00
|
|
|
|
"end_time": "2025-07-27T15:30:03.036212Z",
|
|
|
|
|
|
"start_time": "2025-07-27T15:30:03.011916Z"
|
2025-07-10 15:07:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-07-28 14:36:58 +08:00
|
|
|
|
"outputs": [],
|
2025-07-10 15:07:31 +08:00
|
|
|
|
"source": [
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# --- 单个回测任务函数 ---\n",
|
|
|
|
|
|
"# 这个函数将在每个独立的进程中运行,因此它必须是自包含的\n",
|
|
|
|
|
|
"def run_single_backtest(\n",
|
|
|
|
|
|
" combination: Tuple[float, float], # 传入当前参数组合\n",
|
|
|
|
|
|
" common_config: Dict[str, Any] # 传入公共配置 (如数据路径, 初始资金等)\n",
|
|
|
|
|
|
") -> Optional[Dict[str, Any]]:\n",
|
|
|
|
|
|
" \"\"\"\n",
|
|
|
|
|
|
" 运行单个参数组合的回测任务。\n",
|
|
|
|
|
|
" 此函数将在一个独立的进程中执行。\n",
|
|
|
|
|
|
" \"\"\"\n",
|
|
|
|
|
|
" p1_value, p2_value = combination\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" # 从 common_config 中获取必要的配置\n",
|
|
|
|
|
|
" symbol = common_config['symbol']\n",
|
|
|
|
|
|
" data_path = common_config['data_path']\n",
|
|
|
|
|
|
" initial_capital = common_config['initial_capital']\n",
|
|
|
|
|
|
" slippage_rate = common_config['slippage_rate']\n",
|
|
|
|
|
|
" commission_rate = common_config['commission_rate']\n",
|
|
|
|
|
|
" start_time = common_config['start_time']\n",
|
|
|
|
|
|
" end_time = common_config['end_time']\n",
|
|
|
|
|
|
" roll_over_mode = common_config['roll_over_mode']\n",
|
|
|
|
|
|
" # bar_duration_seconds = common_config['bar_duration_seconds'] # 如果DataManager需要,可以再传\n",
|
|
|
|
|
|
" param1_name = common_config['param1_name']\n",
|
|
|
|
|
|
" param2_name = common_config['param2_name']\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" # 每个进程内部独立初始化 DataManager 和 BacktestEngine\n",
|
|
|
|
|
|
" # 确保每个进程有自己的数据副本和模拟状态\n",
|
|
|
|
|
|
" data_manager = DataManager(\n",
|
|
|
|
|
|
" file_path=data_path,\n",
|
|
|
|
|
|
" symbol=symbol,\n",
|
|
|
|
|
|
" # bar_duration_seconds=bar_duration_seconds, # 如果DataManager需要,根据数据文件路径推断或者额外参数传入\n",
|
|
|
|
|
|
" # start_date=start_time.date(), # DataManager 现在通过 file_path 和 symbol 处理数据\n",
|
|
|
|
|
|
" # end_date=end_time.date(),\n",
|
|
|
|
|
|
" )\n",
|
|
|
|
|
|
" # data_manager.load_data() # DataManager 内部加载数据\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" # 策略参数\n",
|
|
|
|
|
|
" strategy_parameters = {\n",
|
2025-07-28 14:36:58 +08:00
|
|
|
|
" 'main_symbol': common_config['main_symbol'],\n",
|
2025-07-10 15:07:31 +08:00
|
|
|
|
" 'trade_volume': 1,\n",
|
|
|
|
|
|
" param1_name: p1_value,\n",
|
|
|
|
|
|
" param2_name: p2_value,\n",
|
|
|
|
|
|
" 'max_position': 20,\n",
|
|
|
|
|
|
" 'enable_log': False, # 在网格搜索时通常关闭策略内部的详细日志\n",
|
2025-07-28 14:36:58 +08:00
|
|
|
|
" 'stop_loss_points': common_config['stop_loss_points'],\n",
|
2025-07-15 22:45:51 +08:00
|
|
|
|
" 'lag': common_config['lag']\n",
|
2025-07-10 15:07:31 +08:00
|
|
|
|
" }\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" # 打印当前进程正在处理的组合信息\n",
|
|
|
|
|
|
" # 注意:多进程打印会交错显示\n",
|
|
|
|
|
|
" # print(f\"--- 正在运行组合: {strategy_parameters} (PID: {multiprocessing.current_process().pid}) ---\")\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" try:\n",
|
|
|
|
|
|
" # 初始化回测引擎\n",
|
|
|
|
|
|
" engine = BacktestEngine(\n",
|
|
|
|
|
|
" data_manager=data_manager,\n",
|
|
|
|
|
|
" strategy_class=common_config['strategy'],\n",
|
|
|
|
|
|
" strategy_params=strategy_parameters,\n",
|
|
|
|
|
|
" initial_capital=initial_capital,\n",
|
|
|
|
|
|
" slippage_rate=slippage_rate,\n",
|
|
|
|
|
|
" commission_rate=commission_rate,\n",
|
|
|
|
|
|
" roll_over_mode=True, # 保持换月模式\n",
|
|
|
|
|
|
" start_time=common_config['start_time'],\n",
|
|
|
|
|
|
" end_time=common_config['end_time']\n",
|
|
|
|
|
|
" )\n",
|
|
|
|
|
|
" # 运行回测,传入时间范围\n",
|
|
|
|
|
|
" engine.run_backtest()\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" # 获取回测结果并分析\n",
|
|
|
|
|
|
" results = engine.get_backtest_results()\n",
|
|
|
|
|
|
" portfolio_snapshots = results[\"portfolio_snapshots\"]\n",
|
|
|
|
|
|
" trade_history = results[\"trade_history\"]\n",
|
|
|
|
|
|
" bars = results[\"all_bars\"]\n",
|
|
|
|
|
|
" initial_capital_result = results[\"initial_capital\"]\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" if portfolio_snapshots:\n",
|
|
|
|
|
|
" analyzer = ResultAnalyzer(portfolio_snapshots, trade_history, bars, initial_capital_result)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" # analyzer.generate_report()\n",
|
|
|
|
|
|
" # analyzer.plot_performance()\n",
|
|
|
|
|
|
" metrics = analyzer.calculate_all_metrics()\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" # 将当前组合的参数和性能指标存储起来\n",
|
|
|
|
|
|
" result_entry = {**strategy_parameters, **metrics}\n",
|
|
|
|
|
|
" return result_entry\n",
|
|
|
|
|
|
" else:\n",
|
|
|
|
|
|
" print(\n",
|
|
|
|
|
|
" f\" 组合 {strategy_parameters} 没有生成投资组合快照,无法进行结果分析。(PID: {multiprocessing.current_process().pid})\")\n",
|
|
|
|
|
|
" # 返回一个包含参数和默认0值的结果,以便追踪失败组合\n",
|
|
|
|
|
|
" return {**strategy_parameters, \"total_return\": 0.0, \"annualized_return\": 0.0, \"sharpe_ratio\": 0.0,\n",
|
|
|
|
|
|
" \"max_drawdown\": 0.0, \"error\": \"No portfolio snapshots\"}\n",
|
|
|
|
|
|
" except Exception as e:\n",
|
|
|
|
|
|
" import traceback\n",
|
|
|
|
|
|
" error_trace = traceback.format_exc()\n",
|
|
|
|
|
|
" print(\n",
|
|
|
|
|
|
" f\" 组合 {strategy_parameters} 运行失败: {e}\\n{error_trace} (PID: {multiprocessing.current_process().pid})\")\n",
|
|
|
|
|
|
" # 返回错误信息,以便后续处理\n",
|
|
|
|
|
|
" return {**strategy_parameters, \"error\": str(e), \"traceback\": error_trace}\n",
|
|
|
|
|
|
"\n"
|
2025-07-28 14:36:58 +08:00
|
|
|
|
]
|
2025-07-10 15:07:31 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "code",
|
2025-07-28 14:36:58 +08:00
|
|
|
|
"execution_count": 8,
|
2025-07-10 15:07:31 +08:00
|
|
|
|
"id": "c0984689",
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"ExecuteTime": {
|
2025-07-28 14:36:58 +08:00
|
|
|
|
"end_time": "2025-07-27T15:30:03.075967Z",
|
|
|
|
|
|
"start_time": "2025-07-27T15:30:03.054845Z"
|
2025-07-10 15:07:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-07-28 14:36:58 +08:00
|
|
|
|
"outputs": [],
|
2025-07-10 15:07:31 +08:00
|
|
|
|
"source": [
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"def slient_print(*args):\n",
|
|
|
|
|
|
" pass\n"
|
2025-07-28 14:36:58 +08:00
|
|
|
|
]
|
2025-07-10 15:07:31 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "code",
|
2025-07-28 14:36:58 +08:00
|
|
|
|
"execution_count": 9,
|
2025-07-10 15:07:31 +08:00
|
|
|
|
"id": "8b6d9f4cd97a863d",
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"ExecuteTime": {
|
2025-07-28 14:36:58 +08:00
|
|
|
|
"end_time": "2025-07-27T15:32:02.333202Z",
|
|
|
|
|
|
"start_time": "2025-07-27T15:30:03.095169Z"
|
2025-07-10 15:07:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-07-28 14:36:58 +08:00
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"总计 1581 种参数组合需要回测。\n",
|
|
|
|
|
|
"--- 启动多进程网格搜索,使用 15 个进程 ---\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"--- 网格搜索回测完毕 ---\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-07-10 15:07:31 +08:00
|
|
|
|
"source": [
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# --- 主执行块 ---\n",
|
|
|
|
|
|
"# 这是多进程代码的入口点,必须在 'if __name__ == \"__main__\":' 保护块中\n",
|
|
|
|
|
|
"# 确保 autoreload 启用 (在Jupyter Notebook中使用,纯Python脚本运行时可移除)\n",
|
|
|
|
|
|
"# %load_ext autoreload\n",
|
|
|
|
|
|
"# %autoreload 2\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# --- 全局配置 ---\n",
|
2025-07-15 22:45:51 +08:00
|
|
|
|
"# data_file_path = \"/mnt/d/PyProject/NewQuant/data/data/KQ_m@CZCE_MA/KQ_m@CZCE_MA_min60.csv\"\n",
|
2025-07-28 14:36:58 +08:00
|
|
|
|
"data_file_path = \"/mnt/d/PyProject/NewQuant/data/data/KQ_m@DCE_c/KQ_m@DCE_c_min60.csv\"\n",
|
|
|
|
|
|
"# data_file_path = \"/mnt/d/PyProject/NewQuant/data/data/KQ_m@CZCE_SR/KQ_m@CZCE_SR_min60.csv\"\n",
|
|
|
|
|
|
"# data_file_path = '/mnt/d/PyProject/NewQuant/data/data/KQ_m@CZCE_MA/KQ_m@CZCE_MA_min60.csv'\n",
|
|
|
|
|
|
"\n",
|
2025-07-15 22:45:51 +08:00
|
|
|
|
"\n",
|
2025-07-10 15:07:31 +08:00
|
|
|
|
"initial_capital = 100000.0\n",
|
|
|
|
|
|
"slippage_rate = 0.0000\n",
|
|
|
|
|
|
"commission_rate = 0.0001\n",
|
|
|
|
|
|
"global_config = {\n",
|
2025-07-28 14:36:58 +08:00
|
|
|
|
" 'symbol': 'KQ_m@DCE_c',\n",
|
2025-07-10 15:07:31 +08:00
|
|
|
|
"}\n",
|
|
|
|
|
|
"# 确保每个合约的tick_size在这里定义或获取\n",
|
|
|
|
|
|
"RB_TICK_SIZE = 1.0 # 螺纹钢的最小变动单位\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# --- 定义参数网格 ---\n",
|
|
|
|
|
|
"param1_name = \"range_factor\"\n",
|
|
|
|
|
|
"param1_values = generate_parameter_range(start=0, end=3, step=0.1)\n",
|
|
|
|
|
|
"param2_name = \"profit_factor\"\n",
|
2025-07-15 22:45:51 +08:00
|
|
|
|
"param2_values = generate_parameter_range(start=0, end=5, step=0.1)\n",
|
2025-07-10 15:07:31 +08:00
|
|
|
|
"optimization_metric = 'sharpe_ratio'\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",
|
2025-07-28 14:36:58 +08:00
|
|
|
|
" 'main_symbol': global_config['symbol'],\n",
|
2025-07-10 15:07:31 +08:00
|
|
|
|
" '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",
|
2025-07-28 14:36:58 +08:00
|
|
|
|
" 'start_time': datetime(2022, 1, 1), # 回测起始时间\n",
|
2025-07-10 15:07:31 +08:00
|
|
|
|
" '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",
|
2025-07-28 14:36:58 +08:00
|
|
|
|
" 'strategy': SimpleLimitBuyStrategyLong,\n",
|
2025-07-15 22:45:51 +08:00
|
|
|
|
" 'lag': 7,\n",
|
2025-07-28 14:36:58 +08:00
|
|
|
|
" 'stop_loss_points': 10\n",
|
2025-07-10 15:07:31 +08:00
|
|
|
|
"}\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# 确定要使用的进程数量 (通常是CPU核心数)\n",
|
|
|
|
|
|
"num_processes = int(multiprocessing.cpu_count() * 0.75)\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",
|
|
|
|
|
|
" # 使用 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--- 网格搜索回测完毕 ---\")"
|
2025-07-28 14:36:58 +08:00
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "code",
|
|
|
|
|
|
"execution_count": 10,
|
|
|
|
|
|
"id": "239e9ca0",
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"ExecuteTime": {
|
|
|
|
|
|
"end_time": "2025-07-27T16:25:37.029915Z",
|
|
|
|
|
|
"start_time": "2025-07-27T16:25:36.896025Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-07-10 15:07:31 +08:00
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
2025-07-28 14:36:58 +08:00
|
|
|
|
"<class 'pandas.core.frame.DataFrame'>\n",
|
|
|
|
|
|
"RangeIndex: 1581 entries, 0 to 1580\n",
|
|
|
|
|
|
"Data columns (total 40 columns):\n",
|
|
|
|
|
|
" # Column Non-Null Count Dtype \n",
|
|
|
|
|
|
"--- ------ -------------- ----- \n",
|
|
|
|
|
|
" 0 main_symbol 1581 non-null object \n",
|
|
|
|
|
|
" 1 trade_volume 1581 non-null int64 \n",
|
|
|
|
|
|
" 2 range_factor 1581 non-null float64\n",
|
|
|
|
|
|
" 3 profit_factor 1581 non-null float64\n",
|
|
|
|
|
|
" 4 max_position 1581 non-null int64 \n",
|
|
|
|
|
|
" 5 enable_log 1581 non-null bool \n",
|
|
|
|
|
|
" 6 stop_loss_points 1581 non-null int64 \n",
|
|
|
|
|
|
" 7 lag 1581 non-null int64 \n",
|
|
|
|
|
|
" 8 初始资金 1581 non-null float64\n",
|
|
|
|
|
|
" 9 最终资金 1581 non-null float64\n",
|
|
|
|
|
|
" 10 总收益率 1581 non-null float64\n",
|
|
|
|
|
|
" 11 年化收益率 1581 non-null float64\n",
|
|
|
|
|
|
" 12 最大回撤 1581 non-null float64\n",
|
|
|
|
|
|
" 13 夏普比率 1581 non-null float64\n",
|
|
|
|
|
|
" 14 卡玛比率 1581 non-null float64\n",
|
|
|
|
|
|
" 15 总交易次数 1581 non-null int64 \n",
|
|
|
|
|
|
" 16 交易成本 1581 non-null float64\n",
|
|
|
|
|
|
" 17 总实现盈亏 1581 non-null float64\n",
|
|
|
|
|
|
" 18 胜率 1581 non-null float64\n",
|
|
|
|
|
|
" 19 盈亏比 1581 non-null float64\n",
|
|
|
|
|
|
" 20 盈利交易次数 1581 non-null int64 \n",
|
|
|
|
|
|
" 21 亏损交易次数 1581 non-null int64 \n",
|
|
|
|
|
|
" 22 平均每次盈利 1581 non-null float64\n",
|
|
|
|
|
|
" 23 平均每次亏损 1581 non-null float64\n",
|
|
|
|
|
|
" 24 initial_capital 1581 non-null float64\n",
|
|
|
|
|
|
" 25 final_capital 1581 non-null float64\n",
|
|
|
|
|
|
" 26 total_return 1581 non-null float64\n",
|
|
|
|
|
|
" 27 annualized_return 1581 non-null float64\n",
|
|
|
|
|
|
" 28 max_drawdown 1581 non-null float64\n",
|
|
|
|
|
|
" 29 sharpe_ratio 1581 non-null float64\n",
|
|
|
|
|
|
" 30 calmar_ratio 1581 non-null float64\n",
|
|
|
|
|
|
" 31 total_trades 1581 non-null int64 \n",
|
|
|
|
|
|
" 32 transaction_costs 1581 non-null float64\n",
|
|
|
|
|
|
" 33 total_realized_pnl 1581 non-null float64\n",
|
|
|
|
|
|
" 34 win_rate 1581 non-null float64\n",
|
|
|
|
|
|
" 35 profit_loss_ratio 1581 non-null float64\n",
|
|
|
|
|
|
" 36 winning_trades_count 1581 non-null int64 \n",
|
|
|
|
|
|
" 37 losing_trades_count 1581 non-null int64 \n",
|
|
|
|
|
|
" 38 avg_profit_per_trade 1581 non-null float64\n",
|
|
|
|
|
|
" 39 avg_loss_per_trade 1581 non-null float64\n",
|
|
|
|
|
|
"dtypes: bool(1), float64(28), int64(10), object(1)\n",
|
|
|
|
|
|
"memory usage: 483.4+ KB\n",
|
|
|
|
|
|
"None\n",
|
2025-07-10 15:07:31 +08:00
|
|
|
|
"\n",
|
2025-07-28 14:36:58 +08:00
|
|
|
|
"--- 最优参数组合 (按sharpe_ratio) ---\n",
|
|
|
|
|
|
"main_symbol KQ_m@DCE_c\n",
|
|
|
|
|
|
"trade_volume 1\n",
|
|
|
|
|
|
"range_factor 1.2\n",
|
|
|
|
|
|
"profit_factor 0.0\n",
|
|
|
|
|
|
"max_position 20\n",
|
|
|
|
|
|
"enable_log False\n",
|
|
|
|
|
|
"stop_loss_points 10\n",
|
|
|
|
|
|
"lag 7\n",
|
|
|
|
|
|
"初始资金 100000.0\n",
|
|
|
|
|
|
"最终资金 99618.1047\n",
|
|
|
|
|
|
"总收益率 -0.003819\n",
|
|
|
|
|
|
"年化收益率 -0.001098\n",
|
|
|
|
|
|
"最大回撤 0.007545\n",
|
|
|
|
|
|
"夏普比率 -0.167818\n",
|
|
|
|
|
|
"卡玛比率 -0.145475\n",
|
|
|
|
|
|
"总交易次数 396\n",
|
|
|
|
|
|
"交易成本 107.1388\n",
|
|
|
|
|
|
"总实现盈亏 -134.0\n",
|
|
|
|
|
|
"胜率 0.388889\n",
|
|
|
|
|
|
"盈亏比 1.463388\n",
|
|
|
|
|
|
"盈利交易次数 77\n",
|
|
|
|
|
|
"亏损交易次数 121\n",
|
|
|
|
|
|
"平均每次盈利 23.571429\n",
|
|
|
|
|
|
"平均每次亏损 -16.107438\n",
|
|
|
|
|
|
"initial_capital 100000.0\n",
|
|
|
|
|
|
"final_capital 99618.1047\n",
|
|
|
|
|
|
"total_return -0.003819\n",
|
|
|
|
|
|
"annualized_return -0.001098\n",
|
|
|
|
|
|
"max_drawdown 0.007545\n",
|
|
|
|
|
|
"sharpe_ratio -0.167818\n",
|
|
|
|
|
|
"calmar_ratio -0.145475\n",
|
|
|
|
|
|
"total_trades 396\n",
|
|
|
|
|
|
"transaction_costs 107.1388\n",
|
|
|
|
|
|
"total_realized_pnl -134.0\n",
|
|
|
|
|
|
"win_rate 0.388889\n",
|
|
|
|
|
|
"profit_loss_ratio 1.463388\n",
|
|
|
|
|
|
"winning_trades_count 77\n",
|
|
|
|
|
|
"losing_trades_count 121\n",
|
|
|
|
|
|
"avg_profit_per_trade 23.571429\n",
|
|
|
|
|
|
"avg_loss_per_trade -16.107438\n",
|
|
|
|
|
|
"Name: 612, dtype: object\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"--- 动态网格搜索完成 ---\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"--- 最佳参数组合 ---\n",
|
|
|
|
|
|
" range_factor: 2.8\n",
|
|
|
|
|
|
" profit_factor: 0\n",
|
|
|
|
|
|
" sharpe_ratio: -0.0347\n",
|
|
|
|
|
|
"[0, 3.0, 0, 5.0]\n"
|
2025-07-10 15:07:31 +08:00
|
|
|
|
]
|
2025-07-28 14:36:58 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA7YAAAMWCAYAAADF5hp2AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsnXdYVMf3uN9tLEtZehdREQV7jb1X7L33EluiSayptqixpRoTY2I0tiSWGHvB3ntHFEGRovS6wAK79/fHysK6gGj0Y/L93deH53Hnzj0zc87MnZk7c+dIBEEQEBERERERERERERERERH5jyJ90xkQEREREREREREREREREfkniBNbERERERERERERERERkf804sRWRERERERERERERERE5D+NOLEVERERERERERERERER+U8jTmxFRERERERERERERERE/tOIE1sRERERERERERERERGR/zTixFZERERERERERERERETkP404sRURERERERERERERERH5TyNObEVERERERERERERERET+04gTWxGRUjJnzhwkEgkJCQlvOiv/XyHq/Z9x7NgxJBIJx44de9NZEXkO+/fvp1atWlhaWiKRSEhJSXnTWfr/losXL9K4cWOsra2RSCRcu3bN+Cx6VWRkZDBmzBjc3d2RSCS89957r0y2iIiIyP+PiBNbEZH/AHv37mXOnDlvOhsi/2JWrlzJ2rVr33Q2RF6SxMRE+vXrh0ql4vvvv2f9+vVYW1u/0jTOnDnDnDlz/ucTZr1ez7Jly/Dz80OlUuHr68uECRPIyMj4n+ajtOTm5tK3b1+SkpL46quvWL9+PT4+PkXGXbhwITt27HipdBYuXMjatWuZMGEC69evZ+jQof8g18Wn8bL5ExEREfmvIREEQXjTmRAR+S8wZ84c5s6dS3x8PM7Ozv/TtN955x2+//57/n9srm9S7/8lqlWrhrOzs9nKrF6vJycnBwsLC6RS8V3mv5X9+/cTGBjIoUOHaNu27WtJY9myZUyfPp0HDx5Qrly515JGUXz11Vd88MEH9OjRg8DAQCIiIti8eTNHjhz5n+ajtISEhBAQEMDq1asZM2aMMTwvL4+8vDwsLS2NYTY2NvTp0+elXio1bNgQuVzOqVOnXkW2i+Sf5E9ERETkv4b8TWdARERE5E0jCALZ2dmoVKo3nRXg1eZHKpWaDMT/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
|
|
|
|
|
|
"text/plain": [
|
|
|
|
|
|
"<Figure size 1000x800 with 2 Axes>"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"output_type": "display_data"
|
2025-07-10 15:07:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"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",
|
2025-07-28 14:36:58 +08:00
|
|
|
|
" (results_df['total_trades'] > 200) \n",
|
|
|
|
|
|
" &\n",
|
|
|
|
|
|
" (results_df['profit_factor'] < 3.)\n",
|
2025-07-15 22:45:51 +08:00
|
|
|
|
" ]\n",
|
2025-07-10 15:07:31 +08:00
|
|
|
|
" if len(normal_results) > 0:\n",
|
|
|
|
|
|
" best_result = normal_results.loc[(normal_results[optimization_metric].idxmax())]\n",
|
2025-07-15 22:45:51 +08:00
|
|
|
|
" print(f\"\\n--- 最优参数组合 (按{optimization_metric}) ---\")\n",
|
2025-07-10 15:07:31 +08:00
|
|
|
|
" 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",
|
2025-07-15 22:45:51 +08:00
|
|
|
|
" # print(f\"\\n{optimization_metric} 网格结果 (Pivoted):\")\n",
|
|
|
|
|
|
" # print(pivot_table.to_string())\n",
|
2025-07-10 15:07:31 +08:00
|
|
|
|
" 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没有生成任何网格搜索结果,无法进行分析。\")"
|
2025-07-28 14:36:58 +08:00
|
|
|
|
]
|
2025-07-10 15:07:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"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
|
|
|
|
|
|
}
|