25 次代碼提交 b84313cbed ... b0aa1c2b28

作者 SHA1 備註 提交日期
  maxfeng b0aa1c2b28 优化K线图工具,增强历史交易记录分析功能,提升复盘效率。 2 月之前
  maxfeng 939c115711 新增根据历史交易记录画K线图的工具,用于复盘 2 月之前
  maxfeng 0fb22438b5 MAPatternStrategy_v002.py 2 月之前
  maxfeng c748edd74f 更新.gitignore以忽略data/source目录中的Excel文件,并在analysis_chart.py中增强期权分析功能,支持标注指定价格点的损益,优化绘图和报告输出逻辑,提升分析的灵活性与可读性。 2 月之前
  maxfeng 8ff9ca0027 更新.gitignore以忽略data目录中的所有文件,并在MAPatternStrategy_v002.py中新增均线跟踪止盈恢复检查功能,增强日志输出以便于调试和监控开仓及止损止盈条件。 2 月之前
  maxfeng d1bef407ba 增强了止盈止损部分的日志,检查是否有问题 2 月之前
  maxfeng 4154e1ef53 更新期货保证金爬取工具,新增爬取和更新功能,支持数据备份与验证,优化策略代码中的保证金配置。同时调整均线形态交易策略的保证金参数,提升策略的灵活性与风险控制。 3 月之前
  maxfeng 9d050d36d3 均线形态改成顺势 3 月之前
  maxfeng 0485278464 更新下跳涨回策略文档,新增251024版本的缺失开仓、错误开仓、良好开仓、问题及计划部分,详细记录了相关交易情况和策略调整,提升了文档的完整性与可操作性。 3 月之前
  maxfeng d6c25caef3 更新均线形态交易策略 v002,新增极端趋势过滤器以提高交易信号的准确性,并优化开仓逻辑。调整开盘价差阈值,支持三种策略模式,增强了策略的灵活性与风险控制。同时更新README文档,详细说明策略逻辑与参数配置,提升可操作性。 3 月之前
  maxfeng 19ab6c3b91 新增均线形态交易策略 v002。将在这个基础上进一步优化下跳涨回和上跳跌回的相关策略 3 月之前
  maxfeng b5ee1f1905 新增均线形态交易策略 v001,结合均线走势与K线形态进行期货交易。实现了开盘时均线检查、开盘价差判断、动态止损与止盈策略,并支持自动换月移仓。完善了策略参数配置与日志输出,增强了策略的可操作性与灵活性。 3 月之前
  maxfeng 52f622f5eb 为长上影线的蜡烛形态修改了开仓和止盈策略: 3 月之前
  maxfeng 5d575f7dde 更新期货左侧交易策略(带网格和对冲)v001,完善底仓左侧多头、网格多头和空头对冲的逻辑,新增待成交限价单跟踪功能,优化开仓和止盈检查时间点。同时更新README文档,详细说明各策略组件的开仓、止盈和止损条件,增强了策略的可操作性和灵活性。 3 月之前
  maxfeng ce5b484798 新增期货左侧交易策略(带网格和对冲)v001,包含底仓左侧多头、网格多头和空头对冲三个策略组件。更新README文档详细说明策略逻辑、配置参数及使用方法,增强了合约自动切换、持仓状态管理和多品种支持功能。 3 月之前
  maxfeng 5e1500d497 更新期货网格交易分析工具,新增对冲策略配置及相关逻辑,包括对冲头寸的建立、止盈、止损和波动率过滤功能。同时调整了基础头寸与对冲头寸的盈亏计算逻辑,增强了对冲策略的统计分析能力,确保在不同市场条件下的有效性。 4 月之前
  maxfeng d3692b041b 在原有'future_grid_trading_analysis.py'的基础上构建了一个新版本,这个版本不使用对冲策略对冲网格交易时可能的风险。 4 月之前
  maxfeng e10c631fec 新增移动平均线过滤器以优化网格对冲策略,避免在强下跌趋势中进行对冲入场。同时扩展了历史数据获取逻辑,确保MA计算所需数据的充足性,并更新了相关配置和日志输出。调整了基础头寸交易逻辑,确保在价格低于网格水平时主动开仓。 4 月之前
  maxfeng edea28be61 新增期货左侧交易+网格交易+网格对冲交易研究分析工具,支持主力合约自动切换、强制平仓和重新建仓逻辑。更新README文档以详细说明功能特点、使用方法及配置参数,同时增加了交易记录的退出原因字段,增强了对比分析能力。 4 月之前
  maxfeng 2b7e0def4c 新增烛台影线形态反向交易策略及相关分析工具,包含完整的策略逻辑、参数配置和交易信号生成。同时更新README文档以说明新功能和使用方法,增强了对未来价格走势的预测能力。 4 月之前
  maxfeng 723bc03a37 新增期货合约5日移动平均线大幅偏离后是否反弹的分析工具,包含完整的分析流程和配置参数,支持数据收集、距离计算、阈值确定及信号检测与分析。同时新增VSCode配置文件以支持Python环境管理。 4 月之前
  maxfeng 2acf267fae 新增期货市场技术形态规律分析工具,包含综合分析类和配置参数,支持大幅下跌后K线形态识别及后续走势分析。同时新增主力合约跟踪工具,提供主力合约历史变化获取功能,并更新README文档以说明新功能和使用方法。 4 月之前
  maxfeng 5554ee46ab 更新了一般牛差的逻辑,但是还没有完全完成 4 月之前
  maxfeng 1172c858f9 解决了不同交易所的问题 5 月之前
  maxfeng 56148011b3 调整了日志和合约到期移仓日期限制 5 月之前

+ 14 - 0
.cursor/rules/intro.mdc

@@ -0,0 +1,14 @@
+---
+alwaysApply: true
+---
+
+# 概要
+这是一个策略仓库,不负责实际运行,所有代码都是在聚宽等线上平台运行的。
+
+# 目录简介
+ - 'API'是官方API的介绍文档
+ - 'Lib'则是所有策略存放的目录,其他策略的实现逻辑可用于参考
+
+# 重要信息
+ - 一般情况下是一个文件一整套逻辑,而不是将多个文件串联起来的传统编程套路
+ - 所以一般是专注于具体一个文件的内部逻辑,偶尔可以参考相同类型目录下的其他代码的实现逻辑

+ 3 - 1
.gitignore

@@ -6,4 +6,6 @@ resources/part_api.html
 
 *.csv
 
-data/
+data/*
+data/source/Input.xlsx
+data/source/Output.xlsx

+ 5 - 0
.vscode/settings.json

@@ -0,0 +1,5 @@
+{
+    "python-envs.defaultEnvManager": "ms-python.python:conda",
+    "python-envs.defaultPackageManager": "ms-python.python:conda",
+    "python-envs.pythonProjects": []
+}

+ 174 - 0
Lib/Options/README_ETF_MA_Analysis.md

@@ -0,0 +1,174 @@
+# ETF月线级别均线分析工具
+
+## 概述
+
+基于 `deep_itm_bull_spread_strategy.py` 的ETF获取逻辑,创建了一个专门的ETF价格与移动平均线距离分析工具。该工具用于分析ETF月收盘价与移动平均线的距离关系。
+
+## 功能特性
+
+### 1. ETF数据获取
+- **支持的ETF**:
+  - 50ETF (510050.XSHG) - 上证50ETF
+  - 300ETF (510300.XSHG) - 沪深300ETF  
+  - 创业板ETF (159915.XSHE) - 创业板ETF
+
+### 2. 技术分析
+- **移动平均线计算**:5日、10日、20日均线
+- **月线级别分析**:将日线数据转换为月线数据
+- **距离计算**:计算月收盘价与各均线的绝对距离和百分比距离
+
+### 3. 数据可视化
+- 生成距离趋势图表
+- 显示每个ETF的距离变化曲线
+- 包含统计信息标注
+
+### 4. 数据导出
+- 导出详细的CSV分析结果
+- 包含价格、均线、距离等完整数据
+
+## 文件说明
+
+### 主要文件
+- `etf_monthly_ma_analysis.py` - 完整的分析工具(包含分析功能和直接运行代码)
+- `README_ETF_MA_Analysis.md` - 本说明文档
+
+### 生成文件
+运行后会生成以下文件:
+- `ETF月线距离分析_开始日期_结束日期.png` - 分析图表
+- `ETF_月线分析_ETF名称_开始日期_结束日期.csv` - 每个ETF的详细数据
+
+## 使用方法
+
+### 直接运行(推荐)
+
+```bash
+# 在聚宽环境中直接运行
+python etf_monthly_ma_analysis.py
+```
+
+文件中已经预设了分析参数,直接运行即可开始分析。如需修改时间范围,编辑文件底部的参数:
+
+```python
+# 修改这里的参数来自定义分析
+analyzer = ETFMonthlyMAAnalysis(
+    start_date='2024-01-01',  # 修改开始日期
+    end_date='2024-10-31'     # 修改结束日期
+)
+```
+
+### 自定义使用
+
+如果需要在其他脚本中使用,可以导入并自定义:
+
+```python
+from etf_monthly_ma_analysis import ETFMonthlyMAAnalysis
+
+# 创建分析器
+analyzer = ETFMonthlyMAAnalysis(
+    start_date='2024-06-01', 
+    end_date='2024-12-31'
+)
+
+# 运行完整分析
+analyzer.run_complete_analysis(
+    save_chart=True,  # 保存图表
+    export_csv=True   # 导出CSV
+)
+```
+
+## 输出示例
+
+### 控制台输出
+```
+==========================================
+详细分析结果
+==========================================
+
+50ETF 分析结果:
+------------------------------
+MA5:
+  平均距离: 0.0234 元
+  距离标准差: 0.0456 元
+  平均百分比距离: 1.23%
+  高于均线次数: 6
+  低于均线次数: 4
+  高于均线比例: 60.0%
+
+MA10:
+  平均距离: 0.0156 元
+  ...
+```
+
+### 图表输出
+- 多子图显示每个ETF的距离趋势
+- 包含统计信息文本框
+- 支持保存为PNG文件
+
+### CSV数据格式
+| 年月 | close | MA5 | MA10 | MA20 | Distance_MA5 | Distance_MA5_Pct | ... |
+|------|-------|-----|------|------|--------------|------------------|-----|
+| 2024-01 | 2.845 | 2.834 | 2.821 | 2.808 | 0.011 | 0.39% | ... |
+| 2024-02 | 2.892 | 2.856 | 2.843 | 2.835 | 0.036 | 1.26% | ... |
+
+## 配置参数
+
+### 时间范围
+```python
+analyzer = ETFMonthlyMAAnalysis(
+    start_date='2024-01-01',  # 开始日期
+    end_date='2024-12-31'     # 结束日期
+)
+```
+
+### 移动平均线周期
+可在类初始化时修改 `self.ma_periods = [5, 10, 20]`
+
+### ETF符号
+可在类初始化时修改 `self.etf_symbols` 字典
+
+## 技术原理
+
+### 数据获取
+- 使用聚宽的 `get_price()` 函数获取ETF日线数据
+- 包含开盘价、最高价、最低价、收盘价、成交量
+
+### 月线转换
+- 使用 pandas 的 `resample('M')` 方法
+- 月开盘价:取月内第一个交易日开盘价
+- 月收盘价:取月内最后一个交易日收盘价
+- 月最高/最低价:取月内极值
+
+### 距离计算
+- **绝对距离**:`距离 = 月收盘价 - 移动平均线`
+- **百分比距离**:`百分比距离 = (月收盘价 - 移动平均线) / 移动平均线 × 100%`
+
+### 统计分析
+- 计算距离的均值、标准差、最大值、最小值
+- 统计正向/负向距离的次数
+- 计算高于均线的比例
+
+## 注意事项
+
+1. **环境要求**:需要在聚宽(JoinQuant)环境中运行
+2. **数据依赖**:依赖聚宽的 `jqdata` 包和 `get_price()` 函数
+3. **时间范围**:建议选择至少6个月以上的时间范围以获得有意义的分析结果
+4. **市场假期**:工具会自动处理交易日历,跳过市场假期
+
+## 应用场景
+
+1. **趋势分析**:观察ETF价格相对于均线的长期趋势
+2. **买卖时机**:通过距离分析判断潜在的买卖机会
+3. **风险评估**:通过距离的波动性评估风险
+4. **策略回测**:为期权策略提供基础的技术分析数据
+
+## 扩展功能
+
+可以基于此工具进一步开发:
+- 添加更多技术指标(RSI、MACD等)
+- 增加更多均线周期
+- 添加自动化交易信号生成
+- 集成到现有的期权策略中
+
+## 版本历史
+
+- v1.0 - 初始版本,基于深度实值牛差策略的ETF获取逻辑

+ 81 - 10
Lib/Options/analysis_chart.py

@@ -5,7 +5,7 @@ from statistics import mean
 import os
 from datetime import datetime
 
-def analyze_options(*options):
+def analyze_options(*options, mark_prices=None):
     """
     统一的期权分析方法
 
@@ -17,6 +17,8 @@ def analyze_options(*options):
     premium: 权利金
     strike_price: 行权价
     quantity: 数量
+    mark_prices: 可选,需要标注的标的价格列表,如 [1.35, 1.52]
+                 可以作为关键字参数或最后一个位置参数传递
 
     示例:
     # 单个期权
@@ -24,17 +26,34 @@ def analyze_options(*options):
 
     # 期权组合
     analyze_options(('buy', 'call', 0.08, 2.9, 1), ('sell', 'call', 0.03, 3.1, 1))
+
+    # 标注指定价格点(关键字参数方式)
+    analyze_options(('buy', 'call', 0.05, 3.0, 1), mark_prices=[2.8, 3.2])
+    
+    # 标注指定价格点(位置参数方式)
+    analyze_options(('buy', 'call', 0.05, 3.0, 1), [2.8, 3.2])
     """
 
     if not options:
         raise ValueError("请至少提供一个期权")
 
+    # 检查最后一个参数是否是 mark_prices(列表类型且不是期权格式)
+    actual_options = list(options)
+    if (mark_prices is None and len(actual_options) > 0 and 
+        isinstance(actual_options[-1], list) and 
+        len(actual_options[-1]) != 5):
+        # 最后一个参数是列表且不是期权格式,将其视为 mark_prices
+        mark_prices = actual_options.pop()
+    
+    if not actual_options:
+        raise ValueError("请至少提供一个期权")
+
     # 解析期权数据
     option_list = []
     all_strikes = []
 
-    for i, opt in enumerate(options):
-        if len(opt) != 5:
+    for i, opt in enumerate(actual_options):
+        if not isinstance(opt, (tuple, list)) or len(opt) != 5:
             raise ValueError(f"期权{i+1}格式错误,应为(direction, option_type, premium, strike_price, quantity)")
 
         direction, option_type, premium, strike_price, quantity = opt
@@ -76,10 +95,10 @@ def analyze_options(*options):
         results['combined'] = combined_profits
 
     # 绘制图表
-    _plot_results(results, option_list, prices)
+    _plot_results(results, option_list, prices, mark_prices=mark_prices)
 
     # 打印分析报告
-    _print_report(results, option_list, prices)
+    _print_report(results, option_list, prices, mark_prices=mark_prices)
 
     return pd.DataFrame(results)
 
@@ -123,7 +142,7 @@ def _calculate_profit(option, price):
     return 0
 
 
-def _plot_results(results, option_list, prices):
+def _plot_results(results, option_list, prices, mark_prices=None):
     """绘制分析图表"""
     plt.figure(figsize=(14, 10))
     plt.rcParams['axes.unicode_minus'] = False
@@ -151,9 +170,17 @@ def _plot_results(results, option_list, prices):
 
     # 找到并标注关键点(盈亏平衡点、最大收益/损失边界点)
     if 'combined' in results:
-        _mark_key_points(results['combined'], prices, 'Portfolio')
+        profits_for_marking = results['combined']
+        _mark_key_points(profits_for_marking, prices, 'Portfolio')
     elif len(option_list) == 1:
-        _mark_key_points(results['opt1'], prices, 'Option')
+        profits_for_marking = results['opt1']
+        _mark_key_points(profits_for_marking, prices, 'Option')
+    else:
+        profits_for_marking = None
+
+    # 标注指定的价格点
+    if mark_prices and profits_for_marking is not None:
+        _mark_specified_prices(mark_prices, profits_for_marking, prices, option_list)
 
     plt.xlabel('Assest Price', fontsize=12)
     plt.ylabel('Profit/Loss', fontsize=12)
@@ -226,6 +253,40 @@ def _mark_key_points(profits, prices, label_prefix):
                     fontsize=10, color='magenta', weight='bold')
 
 
+def _mark_specified_prices(mark_prices, profits, prices, option_list):
+    """标注指定的标的价格点及其对应的损益"""
+    if not mark_prices:
+        return
+    
+    # 计算每个指定价格对应的损益
+    for mark_price in mark_prices:
+        # 找到最接近的价格索引
+        price_idx = np.argmin(np.abs(prices - mark_price))
+        actual_price = prices[price_idx]
+        profit_value = profits[price_idx]
+        
+        # 绘制标记点(使用黄色星形标记)
+        plt.plot(actual_price, profit_value, '*', color='gold', markersize=15, 
+                markeredgecolor='darkorange', markeredgewidth=2, zorder=5)
+        
+        # 添加标注
+        price_range = prices.max() - prices.min()
+        profit_range = max(profits) - min(profits) if len(profits) > 0 else 1
+        
+        # 根据损益值的正负决定标注位置
+        if profit_value >= 0:
+            y_offset = profit_range * 0.15
+        else:
+            y_offset = -profit_range * 0.15
+        
+        plt.annotate(f'Price: {actual_price:.4f}\nP/L: {profit_value:.4f}',
+                    xy=(actual_price, profit_value),
+                    xytext=(actual_price + price_range * 0.05, profit_value + y_offset),
+                    arrowprops=dict(arrowstyle='->', color='darkorange', lw=1.5),
+                    fontsize=10, color='darkorange', weight='bold',
+                    bbox=dict(boxstyle='round,pad=0.5', facecolor='wheat', alpha=0.8))
+
+
 def _find_boundary_points(profits, prices, extreme_value, _extreme_type):
     """找到最大收益或最大损失的边界点"""
     boundary_points = []
@@ -268,7 +329,7 @@ def _find_boundary_points(profits, prices, extreme_value, _extreme_type):
     return boundary_points
 
 
-def _print_report(results, option_list, prices):
+def _print_report(results, option_list, prices, mark_prices=None):
     """打印分析报告"""
     print("=" * 60)
     print("期权分析报告")
@@ -314,6 +375,16 @@ def _print_report(results, option_list, prices):
     else:
         print("无盈亏平衡点")
 
+    # 打印指定价格点的损益信息
+    if mark_prices:
+        print(f"\n【指定价格点分析】")
+        for mark_price in mark_prices:
+            # 找到最接近的价格索引
+            price_idx = np.argmin(np.abs(prices - mark_price))
+            actual_price = prices[price_idx]
+            profit_value = profits[price_idx]
+            print(f"标的价格: {actual_price:.4f} -> 损益: {profit_value:.4f} (一单: {profit_value * 10000:.2f}元)")
+
     print("=" * 60)
 
-analyze_options(('sell', 'call', 0.066, 3.0, 1), ('buy', 'call', 0.2017, 2.8, 1))
+analyze_options(('buy', 'put', 0.1636, 1.55, 2), ('sell', 'put', 0.084, 1.45, 1), [1.314, 1.35, 1.52])

+ 67 - 32
Lib/Options/deep_itm_bull_spread_strategy.py

@@ -34,8 +34,8 @@ class DeepITMBullSpreadStrategy:
             'min_days_to_expiry': 15,  # 最少开仓日期(距离到期日)
             'call_time_value_threshold': 0.015,  # 买购时间价值阈值(README中为0.015)
             'put_close_premium_threshold': 0.005,  # 卖购平仓权利金阈值(README中为50单位,转换为0.005)
-            'max_days_before_expiry': 7,  # 合约到期移仓日期最大(交易日)
-            'min_days_before_expiry': 2,  # 合约到期移仓日期最小(交易日)
+            'max_days_before_expiry': 3,  # 合约到期移仓日期最大(交易日)
+            'min_days_before_expiry': 1,  # 合约到期移仓日期最小(交易日)
             'add_position_threshold': {'510300': 0.2, '510050': 0.1, '159915': 0.15},  # 加仓窗口阈值
             'max_add_positions': 2,  # 加仓次数上限
             'max_profit_close_threshold': 0.83
@@ -62,12 +62,12 @@ class DeepITMBullSpreadStrategy:
         self.month_split = list(self.trade_days.resample('M', label='left').mean().index) + [pd.to_datetime(end_date)]
         
         # 调试输出:显示月份分割点
-        print(f"Month split 计算结果 ({len(self.month_split)}个分割点):")
+        # print(f"Month split 计算结果 ({len(self.month_split)}个分割点):")
         for i, split_date in enumerate(self.month_split):
-            print(f"  索引{i}: {split_date.strftime('%Y-%m-%d')}")
+            # print(f"  索引{i}: {split_date.strftime('%Y-%m-%d')}")
             if i < len(self.month_split) - 1:
                 next_split = self.month_split[i + 1]
-                print(f"    月份{i}覆盖范围: {split_date.strftime('%Y-%m-%d')} 到 {next_split.strftime('%Y-%m-%d')}")
+                # print(f"    月份{i}覆盖范围: {split_date.strftime('%Y-%m-%d')} 到 {next_split.strftime('%Y-%m-%d')}")
         
         # 用于存储前一日ETF价格,计算当日涨幅
         self.previous_etf_price = None
@@ -112,6 +112,16 @@ class DeepITMBullSpreadStrategy:
         """获取指定月份的期权合约信息"""
         underlying_code = self.get_underlying_code()
         
+        # 根据标的ETF代码动态确定交易所代码
+        if underlying_code in ['510050', '510300']:
+            exchange_code = 'XSHG'  # 上海交易所
+        elif underlying_code in ['159915']:
+            exchange_code = 'XSHE'  # 深圳交易所
+        else:
+            exchange_code = 'XSHG'  # 默认上海交易所
+        
+        print(f"  期权筛选: 标的代码={underlying_code}, 交易所={exchange_code}")
+        
         # 基于自然月份计算期权筛选范围
         query_date = trade_date.date() if hasattr(trade_date, 'date') else trade_date
         current_year = query_date.year
@@ -145,7 +155,7 @@ class DeepITMBullSpreadStrategy:
             else:
                 end_date = pd.to_datetime(f'{target_year}-{target_month + 1:02d}-01').date() - pd.Timedelta(days=1)
             
-            print(f"  月份索引{month_idx}({target_year}-{target_month:02d})期权筛选范围: {start_date} 到 {end_date}")
+            # print(f"  月份索引{month_idx}({target_year}-{target_month:02d})期权筛选范围: {start_date} 到 {end_date}")
         
         q_contract_info = query(
             opt.OPT_CONTRACT_INFO.code,
@@ -156,7 +166,7 @@ class DeepITMBullSpreadStrategy:
                   opt.OPT_CONTRACT_INFO.list_date
                  ).filter(
                      opt.OPT_CONTRACT_INFO.contract_type == contract_type,
-            opt.OPT_CONTRACT_INFO.exchange_code == 'XSHG',
+            opt.OPT_CONTRACT_INFO.exchange_code == exchange_code,
             opt.OPT_CONTRACT_INFO.last_trade_date >= start_date,
             opt.OPT_CONTRACT_INFO.last_trade_date <= end_date,
             opt.OPT_CONTRACT_INFO.list_date < query_date
@@ -175,17 +185,19 @@ class DeepITMBullSpreadStrategy:
     
     def get_monthly_option_candidates(self, trade_date, month_idx, silent=False):
         """获取指定月份的所有认购期权候选信息
-        返回: (contract_info, month_info) - contract_info为期权列表,month_info为月份信息
+        返回: (contract_info, month_info, failure_reason) - contract_info为期权列表,month_info为月份信息,failure_reason为失败原因(成功时为None)
         """
-        if not silent:
-            print(f"{trade_date.strftime('%Y-%m-%d')} 获取月份索引 {month_idx} 的认购期权候选信息")
+        # if not silent:
+        #     print(f"{trade_date.strftime('%Y-%m-%d')} 获取月份索引 {month_idx} 的认购期权候选信息")
         
         # 获取期权合约信息
         contract_info = self.get_option_contracts(trade_date, month_idx, 'CO')
+        # print(f"contract_info: {contract_info}")
         
         if contract_info.empty:
+            failure_reason = "无可用认购期权合约"
             if not silent:
-                print(f"  月份索引 {month_idx} 无可用认购期权合约")
+                print(f"  月份索引 {month_idx} {failure_reason}")
                 # 增加调试信息,显示查询的时间范围
                 if month_idx < len(self.month_split) - 1:
                     start_date = self.month_split[month_idx].date() if hasattr(self.month_split[month_idx], 'date') else self.month_split[month_idx]
@@ -194,7 +206,7 @@ class DeepITMBullSpreadStrategy:
                     print(f"    查询日期: {trade_date}")
                     print(f"    标的代码: {self.get_underlying_code()}")
                     print(f"    month_split总长度: {len(self.month_split)}")
-            return None, None
+            return None, None, failure_reason
         
         # 获取月份信息
         underlying_code = self.get_underlying_code()
@@ -204,8 +216,13 @@ class DeepITMBullSpreadStrategy:
         query_date = trade_date.date() if hasattr(trade_date, 'date') else trade_date
         candidates = []
         
-        if not silent:
-            print(f"  查询到 {len(contract_info)} 个认购期权合约,开始获取价格信息:")
+        # 统计失败原因
+        expiry_rejected_count = 0
+        price_missing_count = 0
+        price_error_count = 0
+        
+        # if not silent:
+        #     print(f"  查询到 {len(contract_info)} 个认购期权合约,开始获取价格信息:")
         
         for idx, contract in contract_info.iterrows():
             # 检查到期日
@@ -214,6 +231,7 @@ class DeepITMBullSpreadStrategy:
             days_to_expiry = len(get_trade_days(trade_date_obj, expiry_date.date())) - 1
             
             if days_to_expiry < min_days_to_expiry:
+                expiry_rejected_count += 1
                 # if not silent:
                 #     print(f"    行权价 {contract['exercise_price']:.3f}: 到期时间不足 ({days_to_expiry} < {min_days_to_expiry})")
                 continue
@@ -226,6 +244,7 @@ class DeepITMBullSpreadStrategy:
                 )
                 price_result = opt.run_query(q_price)
                 if price_result.empty:
+                    price_missing_count += 1
                     if not silent:
                         print(f"    行权价 {contract['exercise_price']:.3f}: 无价格数据")
                     continue
@@ -242,18 +261,34 @@ class DeepITMBullSpreadStrategy:
                 }
                 candidates.append(candidate)
                 
-                # if not silent:
-                #     print(f"    行权价 {contract['exercise_price']:.3f}: 期权价格 {option_price:.4f}, 剩余天数 {days_to_expiry}")
+                if not silent:
+                    print(f"    行权价 {contract['exercise_price']:.3f}: 期权价格 {option_price:.4f}, 剩余天数 {days_to_expiry}")
                     
             except Exception as e:
+                price_error_count += 1
                 if not silent:
                     print(f"    行权价 {contract['exercise_price']:.3f}: 价格查询失败 ({str(e)})")
                 continue
         
         if not candidates:
-            if not silent:
-                print(f"  月份索引 {month_idx} 无符合基本条件的期权候选")
-            return None, None
+            # 构建详细的失败原因
+            total_contracts = len(contract_info)
+            failure_parts = []
+            if expiry_rejected_count > 0:
+                failure_parts.append(f"到期时间不足{expiry_rejected_count}个")
+            if price_missing_count > 0:
+                failure_parts.append(f"无价格数据{price_missing_count}个")
+            if price_error_count > 0:
+                failure_parts.append(f"价格查询失败{price_error_count}个")
+            
+            if failure_parts:
+                failure_reason = f"共{total_contracts}个期权合约,但均不符合条件:" + "、".join(failure_parts)
+            else:
+                failure_reason = f"共{total_contracts}个期权合约,但均不符合基本条件"
+            
+            # if not silent:
+            #     print(f"  月份索引 {month_idx} 无符合基本条件的期权候选")
+            return None, None, failure_reason
         
         month_info = {
             'month_idx': month_idx,
@@ -266,7 +301,7 @@ class DeepITMBullSpreadStrategy:
             print(f"  成功获取 {len(candidates)} 个有效期权候选")
             # print(f"  月份索引 {month_idx} 的认购期权候选信息: {candidates}")
         
-        return candidates, month_info
+        return candidates, month_info, None
     
     def select_sell_call_from_candidates(self, candidates, etf_price, min_premium, silent=False):
         """从候选期权中选择卖购期权(虚值期权)
@@ -398,9 +433,9 @@ class DeepITMBullSpreadStrategy:
             print(f"  权利金阈值: {min_premium:.4f},时间价值阈值: {time_value_threshold:.4f}")
         
         # 1. 获取月份期权候选
-        candidates, month_info = self.get_monthly_option_candidates(trade_date, month_idx, silent)
+        candidates, month_info, failure_reason = self.get_monthly_option_candidates(trade_date, month_idx, silent)
         if not candidates:
-            reason = f"{month_type}无可用期权候选"
+            reason = f"{month_type}期权候选获取失败:{failure_reason}"
             if not silent:
                 print(f"  {month_type}牛差策略失败:{reason}")
             return None, None, reason
@@ -438,8 +473,6 @@ class DeepITMBullSpreadStrategy:
         
         return buy_call, sell_call, None
     
-
-    
     def calculate_bull_spread_profit(self, buy_call, sell_call, contract_size=None):
         """计算牛差组合的盈利情况"""
         if contract_size is None:
@@ -470,6 +503,7 @@ class DeepITMBullSpreadStrategy:
         
         # 安全获取合约数量
         contract_size = self.get_safe_contract_size()
+        print(f"合约数量: {contract_size}")
         
         # 计算盈利信息
         profit_info = self.calculate_bull_spread_profit(buy_call, sell_call, contract_size)
@@ -521,9 +555,6 @@ class DeepITMBullSpreadStrategy:
     def try_bull_spread_strategy(self, trade_date, etf_price, month_idx, position_type='main', silent=False, save_to_csv=True):
         """尝试牛差策略:深度实值买购+卖购期权"""
         
-        if not silent:
-            print(f"{trade_date.strftime('%Y-%m-%d')} 开始尝试牛差策略")
-        
         # 1. 先尝试当月牛差策略
         buy_call, sell_call, reason = self.try_bull_spread_for_month(trade_date, etf_price, month_idx, is_current_month=True, silent=silent)
         
@@ -588,6 +619,10 @@ class DeepITMBullSpreadStrategy:
             if trade_date >= month_date:
                 month_idx = i
         
+        # 先输出开始尝试的日志,确保顺序正确
+        if not silent:
+            print(f"{trade_date.strftime('%Y-%m-%d')} 开始尝试牛差策略")
+        
         # 尝试牛差策略(深度实值买购+卖购)
         result, reason = self.try_bull_spread_strategy(trade_date, etf_price, month_idx, position_type, silent, save_to_csv)
         if result is not None:
@@ -1239,9 +1274,9 @@ class StrategyConfig:
     def __init__(self):
         # ETF标的配置字典
         self.etf_symbols = {
-            '50ETF': '510050.XSHG'    # 上证50ETF
+            # '50ETF': '510050.XSHG',    # 上证50ETF
             # '300ETF': '510300.XSHG',   # 沪深300ETF
-            # '创业板ETF': '159915.XSHE'    # 创业板ETF
+            '创业板ETF': '159915.XSHE'    # 创业板ETF
         }
         
         # 时间范围配置
@@ -1892,8 +1927,8 @@ class MultiUnderlyingBullSpreadManager:
                             
                             print(f"  {etf_code}: 开仓主仓位({strategy_desc}),ETF价格: {etf_price:.4f}, 合约数量: {contract_size}张, {profit_desc}")
                             daily_has_activity = True
-                        else:
-                            print(f"  {etf_code}: 开仓失败,无法找到合适的期权组合,ETF价格: {etf_price:.4f}")
+                        # else:
+                        #     print(f"  {etf_code}: 开仓失败,无法找到合适的期权组合,ETF价格: {etf_price:.4f}")
                     
                     # 检查是否需要加仓
                     elif strategy.should_add_position(trade_date, etf_price):
@@ -1994,7 +2029,7 @@ def run_deep_itm_bull_spread_example():
     # 可以自定义配置
     config.time_config = {
         'start_date': '2025-06-15',
-        'end_date': '2025-08-15'
+        'end_date': '2025-08-25'
     }
     
     config.capital_config.update({

+ 426 - 0
Lib/Options/etf_monthly_ma_analysis.py

@@ -0,0 +1,426 @@
+# ETF月线级别均线分析工具
+# 功能:
+# 1. 获取50ETF、300ETF、创业板ETF的价格数据
+# 2. 计算月线级别的5、10、20日均线
+# 3. 计算当月收盘价与各均线的距离
+# 4. 生成可视化图表和数据导出
+
+import jqdata
+from jqdata import *
+import pandas as pd
+import numpy as np
+import matplotlib.pyplot as plt
+import datetime
+from typing import Dict, List, Tuple
+
+# 设置中文字体
+plt.rcParams['font.sans-serif'] = ['SimHei']
+plt.rcParams['axes.unicode_minus'] = False
+
+class ETFMonthlyMAAnalysis:
+    """ETF月线级别均线分析器"""
+    
+    def __init__(self, start_date='2024-01-01', end_date='2024-12-31'):
+        """
+        初始化分析器
+        :param start_date: 开始日期
+        :param end_date: 结束日期
+        """
+        self.start_date = start_date
+        self.end_date = end_date
+        
+        # ETF配置 - 基于现有策略的符号定义
+        self.etf_symbols = {
+            '50ETF': '510050.XSHG',      # 上证50ETF
+            '300ETF': '510300.XSHG',     # 沪深300ETF (用户提到的500ETF应该是指这个)
+            '创业板ETF': '159915.XSHE'    # 创业板ETF
+        }
+        
+        # 移动平均线配置
+        self.ma_periods = [5, 10, 20]  # 5日、10日、20日均线
+        
+        # 存储数据
+        self.price_data = {}
+        self.monthly_data = {}
+        self.analysis_results = {}
+        
+        print(f"初始化ETF月线分析器")
+        print(f"分析时间范围: {start_date} 到 {end_date}")
+        print(f"分析ETF: {list(self.etf_symbols.keys())}")
+        print(f"均线周期: {self.ma_periods}日")
+    
+    def fetch_etf_prices(self) -> Dict[str, pd.DataFrame]:
+        """
+        获取所有ETF的价格数据
+        返回: {etf_name: price_dataframe}
+        """
+        print(f"\n开始获取ETF价格数据...")
+        
+        for etf_name, symbol in self.etf_symbols.items():
+            try:
+                print(f"  获取{etf_name}({symbol})价格数据...")
+                
+                # 使用现有策略的get_price方法获取数据
+                price_data = get_price(
+                    symbol, 
+                    start_date=self.start_date, 
+                    end_date=self.end_date, 
+                    fields=['close', 'open', 'high', 'low', 'volume'],
+                    frequency='daily'
+                )
+                
+                if price_data.empty:
+                    print(f"    警告: {etf_name}无价格数据")
+                    continue
+                
+                # 确保索引为日期格式
+                if not isinstance(price_data.index, pd.DatetimeIndex):
+                    price_data.index = pd.to_datetime(price_data.index)
+                
+                self.price_data[etf_name] = price_data
+                
+                print(f"    成功获取{etf_name}数据: {len(price_data)}条记录")
+                print(f"    数据范围: {price_data.index[0].strftime('%Y-%m-%d')} 到 {price_data.index[-1].strftime('%Y-%m-%d')}")
+                print(f"    价格范围: {price_data['close'].min():.3f} - {price_data['close'].max():.3f}")
+                
+            except Exception as e:
+                print(f"    错误: 获取{etf_name}价格数据失败 - {e}")
+                continue
+        
+        print(f"\nETF价格数据获取完成,成功获取{len(self.price_data)}个ETF的数据")
+        return self.price_data
+    
+    def calculate_monthly_ma(self) -> Dict[str, pd.DataFrame]:
+        """
+        计算月线级别的移动平均线
+        返回: {etf_name: monthly_data_with_ma}
+        """
+        print(f"\n开始计算月线级别的移动平均线...")
+        
+        for etf_name, price_df in self.price_data.items():
+            try:
+                print(f"  处理{etf_name}的月线数据...")
+                
+                # 转换为月线数据 - 取每月最后一个交易日的数据
+                monthly_df = price_df.resample('M').agg({
+                    'open': 'first',    # 月初开盘价
+                    'high': 'max',      # 月内最高价
+                    'low': 'min',       # 月内最低价
+                    'close': 'last',    # 月末收盘价
+                    'volume': 'sum'     # 月成交量
+                })
+                
+                # 计算移动平均线
+                for period in self.ma_periods:
+                    ma_column = f'MA{period}'
+                    monthly_df[ma_column] = monthly_df['close'].rolling(window=period).mean()
+                    print(f"    计算{period}日均线完成")
+                
+                # 计算当月收盘价与各均线的距离
+                for period in self.ma_periods:
+                    ma_column = f'MA{period}'
+                    distance_column = f'Distance_MA{period}'
+                    monthly_df[distance_column] = monthly_df['close'] - monthly_df[ma_column]
+                    
+                    # 计算距离的百分比
+                    pct_distance_column = f'Distance_MA{period}_Pct'
+                    monthly_df[pct_distance_column] = (monthly_df['close'] - monthly_df[ma_column]) / monthly_df[ma_column] * 100
+                
+                # 添加月份标识
+                monthly_df['年月'] = monthly_df.index.strftime('%Y-%m')
+                
+                self.monthly_data[etf_name] = monthly_df
+                
+                print(f"    {etf_name}月线数据计算完成: {len(monthly_df)}个月")
+                
+                # 显示最近几个月的数据示例
+                if len(monthly_df) > 0:
+                    latest_data = monthly_df.tail(3)
+                    print(f"    最近数据示例:")
+                    for idx, row in latest_data.iterrows():
+                        month_str = idx.strftime('%Y-%m')
+                        close_price = row['close']
+                        ma5_dist = row.get('Distance_MA5', np.nan)
+                        ma10_dist = row.get('Distance_MA10', np.nan)
+                        ma20_dist = row.get('Distance_MA20', np.nan)
+                        print(f"      {month_str}: 收盘{close_price:.3f}, 距MA5={ma5_dist:.3f}, 距MA10={ma10_dist:.3f}, 距MA20={ma20_dist:.3f}")
+                
+            except Exception as e:
+                print(f"    错误: 处理{etf_name}月线数据失败 - {e}")
+                continue
+        
+        print(f"\n月线移动平均线计算完成,处理了{len(self.monthly_data)}个ETF")
+        return self.monthly_data
+    
+    def analyze_distance_statistics(self) -> Dict[str, Dict]:
+        """
+        分析距离统计信息
+        返回: {etf_name: statistics}
+        """
+        print(f"\n开始分析距离统计信息...")
+        
+        for etf_name, monthly_df in self.monthly_data.items():
+            try:
+                print(f"  分析{etf_name}的距离统计...")
+                
+                stats = {}
+                
+                # 对每个均线周期进行统计
+                for period in self.ma_periods:
+                    distance_col = f'Distance_MA{period}'
+                    pct_distance_col = f'Distance_MA{period}_Pct'
+                    
+                    if distance_col in monthly_df.columns:
+                        distance_data = monthly_df[distance_col].dropna()
+                        pct_distance_data = monthly_df[pct_distance_col].dropna()
+                        
+                        if len(distance_data) > 0:
+                            stats[f'MA{period}'] = {
+                                '绝对距离_均值': distance_data.mean(),
+                                '绝对距离_标准差': distance_data.std(),
+                                '绝对距离_最大值': distance_data.max(),
+                                '绝对距离_最小值': distance_data.min(),
+                                '百分比距离_均值': pct_distance_data.mean(),
+                                '百分比距离_标准差': pct_distance_data.std(),
+                                '百分比距离_最大值': pct_distance_data.max(),
+                                '百分比距离_最小值': pct_distance_data.min(),
+                                '正向距离次数': (distance_data > 0).sum(),
+                                '负向距离次数': (distance_data < 0).sum(),
+                                '数据点总数': len(distance_data)
+                            }
+                            
+                            print(f"    MA{period}统计: 均值={stats[f'MA{period}']['绝对距离_均值']:.3f}, "
+                                  f"正向{stats[f'MA{period}']['正向距离次数']}次, "
+                                  f"负向{stats[f'MA{period}']['负向距离次数']}次")
+                
+                self.analysis_results[etf_name] = stats
+                
+            except Exception as e:
+                print(f"    错误: 分析{etf_name}距离统计失败 - {e}")
+                continue
+        
+        print(f"\n距离统计分析完成")
+        return self.analysis_results
+    
+    def plot_distance_chart(self, save_path: str = None):
+        """
+        绘制距离图表
+        :param save_path: 保存路径,如果为None则直接显示
+        """
+        print(f"\n开始绘制距离图表...")
+        
+        # 设置图表布局:每个ETF一个子图
+        n_etfs = len(self.monthly_data)
+        if n_etfs == 0:
+            print("没有数据可以绘制")
+            return
+        
+        fig, axes = plt.subplots(n_etfs, 1, figsize=(14, 6 * n_etfs))
+        if n_etfs == 1:
+            axes = [axes]  # 确保axes是列表
+        
+        # 颜色配置
+        colors = ['red', 'blue', 'green']
+        ma_labels = [f'距MA{period}日线' for period in self.ma_periods]
+        
+        for idx, (etf_name, monthly_df) in enumerate(self.monthly_data.items()):
+            ax = axes[idx]
+            
+            # 准备数据
+            months = monthly_df.index
+            month_labels = [dt.strftime('%Y-%m') for dt in months]
+            
+            # 绘制距离线
+            for i, period in enumerate(self.ma_periods):
+                distance_col = f'Distance_MA{period}'
+                if distance_col in monthly_df.columns:
+                    distance_data = monthly_df[distance_col]
+                    ax.plot(months, distance_data, 
+                           label=ma_labels[i], 
+                           color=colors[i], 
+                           linewidth=2, 
+                           marker='o', 
+                           markersize=4)
+            
+            # 添加零线
+            ax.axhline(y=0, color='black', linestyle='--', alpha=0.5, linewidth=1)
+            
+            # 设置标题和标签
+            ax.set_title(f'{etf_name} 月收盘价与移动平均线距离', fontsize=14, fontweight='bold')
+            ax.set_xlabel('月份', fontsize=12)
+            ax.set_ylabel('距离 (元)', fontsize=12)
+            ax.legend(fontsize=10)
+            ax.grid(True, alpha=0.3)
+            
+            # 设置x轴标签
+            if len(months) > 12:
+                # 如果月份太多,只显示部分标签
+                step = max(1, len(months) // 12)
+                ax.set_xticks(months[::step])
+                ax.set_xticklabels([dt.strftime('%Y-%m') for dt in months[::step]], rotation=45)
+            else:
+                ax.set_xticklabels(month_labels, rotation=45)
+            
+            # 添加统计信息文本
+            if etf_name in self.analysis_results:
+                stats_text = []
+                for period in self.ma_periods:
+                    ma_key = f'MA{period}'
+                    if ma_key in self.analysis_results[etf_name]:
+                        mean_dist = self.analysis_results[etf_name][ma_key]['绝对距离_均值']
+                        positive_count = self.analysis_results[etf_name][ma_key]['正向距离次数']
+                        negative_count = self.analysis_results[etf_name][ma_key]['负向距离次数']
+                        stats_text.append(f'MA{period}: 均值{mean_dist:.3f}, 正{positive_count}负{negative_count}')
+                
+                if stats_text:
+                    ax.text(0.02, 0.98, '\n'.join(stats_text), 
+                           transform=ax.transAxes, 
+                           fontsize=9, 
+                           verticalalignment='top',
+                           bbox=dict(boxstyle='round,pad=0.3', facecolor='lightgray', alpha=0.8))
+        
+        plt.tight_layout()
+        
+        if save_path:
+            plt.savefig(save_path, dpi=300, bbox_inches='tight')
+            print(f"图表已保存到: {save_path}")
+        else:
+            plt.show()
+        
+        print("距离图表绘制完成")
+    
+    def export_results_to_csv(self, output_dir: str = "./"):
+        """
+        导出结果到CSV文件
+        :param output_dir: 输出目录
+        """
+        print(f"\n开始导出结果到CSV文件...")
+        
+        for etf_name, monthly_df in self.monthly_data.items():
+            try:
+                # 准备导出数据
+                export_df = monthly_df.copy()
+                
+                # 重新排列列的顺序,把重要信息放在前面
+                column_order = ['年月', 'close']
+                
+                # 添加均线列
+                for period in self.ma_periods:
+                    column_order.append(f'MA{period}')
+                
+                # 添加距离列
+                for period in self.ma_periods:
+                    column_order.append(f'Distance_MA{period}')
+                    column_order.append(f'Distance_MA{period}_Pct')
+                
+                # 添加其他列
+                other_columns = [col for col in export_df.columns if col not in column_order]
+                column_order.extend(other_columns)
+                
+                # 重新排序并导出
+                export_df = export_df.reindex(columns=column_order)
+                
+                # 构建文件名
+                filename = f"{output_dir}/ETF_月线分析_{etf_name}_{self.start_date}_{self.end_date}.csv"
+                export_df.to_csv(filename, encoding='utf-8-sig', index=True)
+                
+                print(f"  {etf_name}数据已导出到: {filename}")
+                
+            except Exception as e:
+                print(f"  错误: 导出{etf_name}数据失败 - {e}")
+        
+        print("CSV导出完成")
+    
+    def run_complete_analysis(self, save_chart: bool = True, export_csv: bool = True):
+        """
+        运行完整的分析流程
+        :param save_chart: 是否保存图表
+        :param export_csv: 是否导出CSV
+        """
+        print("="*60)
+        print("开始ETF月线级别均线距离分析")
+        print("="*60)
+        
+        try:
+            # 1. 获取ETF价格数据
+            print(f"\n步骤1: 获取ETF价格数据")
+            self.fetch_etf_prices()
+            
+            if not self.price_data:
+                print("错误: 没有成功获取任何ETF价格数据")
+                return
+            
+            # 2. 计算月线均线
+            print(f"\n步骤2: 计算月线级别移动平均线")
+            self.calculate_monthly_ma()
+            
+            if not self.monthly_data:
+                print("错误: 没有成功计算任何月线数据")
+                return
+            
+            # 3. 分析距离统计
+            print(f"\n步骤3: 分析距离统计信息")
+            self.analyze_distance_statistics()
+            
+            # 4. 绘制图表
+            print(f"\n步骤4: 绘制距离图表")
+            if save_chart:
+                chart_filename = f"ETF月线距离分析_{self.start_date}_{self.end_date}.png"
+                self.plot_distance_chart(save_path=chart_filename)
+            else:
+                self.plot_distance_chart()
+            
+            # 5. 导出CSV
+            if export_csv:
+                print(f"\n步骤5: 导出分析结果")
+                self.export_results_to_csv()
+            
+            # 6. 输出总结
+            print(f"\n" + "="*60)
+            print("分析总结")
+            print("="*60)
+            
+            for etf_name, stats in self.analysis_results.items():
+                print(f"\n{etf_name}分析结果:")
+                for ma_period, stat_data in stats.items():
+                    print(f"  {ma_period}:")
+                    print(f"    平均距离: {stat_data['绝对距离_均值']:.3f} 元")
+                    print(f"    平均距离百分比: {stat_data['百分比距离_均值']:.2f}%")
+                    print(f"    正向/负向比例: {stat_data['正向距离次数']}/{stat_data['负向距离次数']}")
+            
+            print(f"\n分析完成! 总共处理了{len(self.monthly_data)}个ETF,{sum(len(df) for df in self.monthly_data.values())}个月度数据点")
+            
+        except Exception as e:
+            print(f"分析过程中发生错误: {e}")
+            import traceback
+            traceback.print_exc()
+
+# =============================================================================
+# 直接运行分析 - 可以修改下面的参数来自定义分析
+# =============================================================================
+
+print("="*60)
+print("ETF月线级别均线距离分析工具")
+print("="*60)
+print("功能:分析ETF价格与5、10、20日均线的距离关系")
+print("ETF标的:50ETF、300ETF、创业板ETF")
+print()
+
+# 分析参数配置(可根据需要修改)
+START_DATE = '2022-01-01'  # 开始日期
+END_DATE = '2024-12-31'    # 结束日期
+
+print(f"分析时间范围: {START_DATE} 到 {END_DATE}")
+print()
+
+# 创建分析器实例
+analyzer = ETFMonthlyMAAnalysis(
+    start_date=START_DATE, 
+    end_date=END_DATE
+)
+
+# 运行完整分析
+analyzer.run_complete_analysis(
+    save_chart=False,  # 保存图表到文件
+    export_csv=False   # 导出CSV数据
+)

+ 5 - 0
Lib/fund/README.md

@@ -211,3 +211,8 @@
 - 保持统一的缩进和列表样式
 - 使用标题区分不同层级的内容
 
+## Kmeans
+https://www.joinquant.com/algorithm/index/edit?algorithmId=efadbad9b1c9b57c556faca968310a6f&backtest=0
+Lib/Options/etf_monthly_ma_analysis.py
+
+配套研究:https://www.joinquant.com/research?target=research&url=/user/75474983526/notebooks/Funds/Kmeans.ipynb

+ 108 - 0
Lib/fund/kmeans.py

@@ -0,0 +1,108 @@
+# 克隆自聚宽文章:https://www.joinquant.com/post/61157
+# 标题:致敬市场(13)——K-means 指数增强
+# 作者:Gyro^.^
+
+import numpy as np
+import pandas as pd
+import datetime as dt
+from sklearn.cluster import KMeans
+
+def initialize(context):
+    # setting system
+    log.set_level('order', 'error')
+    set_option('use_real_price', True)
+    set_option('avoid_future_data', True)
+    run_monthly(iUpdate, 1, 'before_open')
+    run_daily(iTrader, 'every_bar')
+    run_daily(iReport, 'after_close')
+
+def iUpdate(context):
+    # paramers
+    nday = 243
+    n_cluster = 24
+    n_class = 5
+    # all funds
+    dt_now = context.current_dt.date()
+    all_fund = get_all_securities('fund', dt_now)
+    # onlist 1-year
+    dt_1y = dt_now - dt.timedelta(days=365)
+    funds = all_fund[all_fund.start_date < dt_1y].index.tolist()
+    # filter, liquity
+    hm = history(nday, '1d', 'money', funds).mean()
+    funds = hm[hm > 1e6].index.tolist()
+    # history return
+    h = history(nday, '1d', 'close', funds).dropna(axis=1)
+    r = np.log(h).diff()[1:]
+    # annual return
+    ar = 100*nday*r.mean()
+    # K-means
+    cluster = KMeans(n_clusters=n_cluster).fit(r.T)
+    # labels
+    c = pd.Series(cluster.labels_, r.columns)
+    # choice
+    df = pd.DataFrame(columns=['Cluster', 'Return', 'Name'])
+    for k in range(n_cluster):
+        # class
+        choice = []
+        for s in c.index:
+            if c[s] == k:
+                choice.append(s) 
+        log.info(k, len(choice))
+        # high return
+        cr = ar[choice].sort_values(ascending=False).head(n_class)
+        for s in cr.index:
+            if s in context.portfolio.positions:
+                df.loc[s] = [k, cr[s], get_security_info(s).display_name]
+                break
+        else:
+            s = cr.index[0]
+            df.loc[s] = [k, cr[s], get_security_info(s).display_name]
+    # report
+    pd.set_option('display.max_rows', None)
+    log.info('Funds', len(df), '\n', df)
+    # results
+    g.funds = df.index.tolist()
+    g.position_size = 0.95/n_cluster * context.portfolio.total_value
+
+def iTrader(context):
+    # load data
+    choice  = g.funds
+    position_size = g.position_size
+    lm_value = 0.7*position_size
+    hm_value = 1.5*position_size
+    cash_size = 0.05*context.portfolio.total_value
+    cdata = get_current_data()
+    # sell
+    for s in context.portfolio.positions:
+        if cdata[s].paused or \
+            cdata[s].last_price >= cdata[s].high_limit or cdata[s].last_price <= cdata[s].low_limit:
+            continue # 过滤三停
+        if s not in choice:
+            log.info('sell', s, cdata[s].name)
+            order_target(s, 0, MarketOrderStyle(0.99*cdata[s].last_price))
+    # buy
+    for s in choice:
+        if cdata[s].paused or \
+            cdata[s].last_price >= cdata[s].high_limit or cdata[s].last_price <= cdata[s].low_limit:
+            continue # 过滤三停
+        if context.portfolio.available_cash < cash_size:
+            break # 现金耗尽
+        if s not in context.portfolio.positions:
+            log.info('buy', s, cdata[s].name)
+            order_target_value(s, position_size, MarketOrderStyle(1.01*cdata[s].last_price))
+
+def iReport(context):
+    # table of positions
+    ptable = pd.DataFrame(columns=['amount', 'value', 'weight', 'name'])
+    tvalue = context.portfolio.total_value
+    cdata = get_current_data()
+    for s in context.portfolio.positions:
+        ps = context.portfolio.positions[s]
+        ptable.loc[s] = [ps.total_amount, int(ps.value), 100*ps.value/tvalue, cdata[s].name]
+    ptable = ptable.sort_values(by='weight', ascending=False)
+    # report portfolio
+    pd.set_option('display.max_rows', None)
+    log.info('  positions', len(ptable), '\n', ptable.head())
+    log.info('  total value %.2f, cash %.2f', \
+        context.portfolio.total_value/10000, context.portfolio.available_cash/10000)
+# end

+ 1339 - 0
Lib/future/CandlestickHatchReverseStrategy_v001.py

@@ -0,0 +1,1339 @@
+# 导入函数库
+from jqdata import *
+from jqdata import finance
+import pandas as pd
+import numpy as np
+from datetime import date, datetime, timedelta
+import re
+
+# 烛台影线形态反向交易策略 v001
+# 基于烛台形态检测,按照影线相反方向进行交易的策略
+
+# 设置以便完整打印 DataFrame
+pd.set_option('display.max_rows', None)
+pd.set_option('display.max_columns', None)
+pd.set_option('display.width', None)
+pd.set_option('display.max_colwidth', 20)
+
+## 初始化函数,设定基准等等
+def initialize(context):
+    # 设定沪深300作为基准
+    set_benchmark('000300.XSHG')
+    # 开启动态复权模式(真实价格)
+    set_option('use_real_price', True)
+    # 输出内容到日志
+    log.info('烛台影线形态反向交易策略初始化开始')
+
+    ### 期货相关设定 ###
+    # 设定账户为金融账户
+    set_subportfolios([SubPortfolioConfig(cash=context.portfolio.starting_cash, type='index_futures')])
+    # 期货类每笔交易时的手续费是: 买入时万分之0.23,卖出时万分之0.23,平今仓为万分之23
+    set_order_cost(OrderCost(open_commission=0.000023, close_commission=0.000023, close_today_commission=0.0023), type='index_futures')
+    
+    # 设置期货交易的滑点
+    set_slippage(StepRelatedSlippage(2))
+    
+    # 初始化全局变量
+    g.usage_percentage = 0.8  # 最大资金使用比例
+    g.max_margin_per_position = 20000  # 单个标的最大持仓保证金(元)
+    
+    # 烛台形态检测参数(与研究文件保持一致)
+    g.hatch_to_body_ratio = 1.2  # 影线与实体长度比率阈值
+    g.opposite_hatch_ratio = 0.5  # 相反方向影线与实体长度比率阈值
+    g.historical_days = 365  # 历史数据天数,用于计算平均实体长度阈值(与研究文件保持一致)
+    
+    # 全局阈值缓存(每月更新一次)
+    g.monthly_body_threshold_cache = {}  # 存储每月计算的实体长度阈值
+    g.last_threshold_update_month = None  # 记录上次更新阈值的月份
+    
+    # 止损止盈策略参数
+    g.fixed_stop_loss_rate = 0.01  # 固定止损比率(1%)
+    g.trailing_stop_thresholds = [0.05, 0.10]  # 动态追踪止损触发条件(5%, 10%)
+    g.trailing_stop_rates = [0.02, 0.03, 0.04]  # 动态追踪止损比率(2%, 3%, 4%)
+    
+    # 开仓方向配置参数
+    g.reverse_direction_symbols = ['JD']  # 使用反向逻辑的品种列表(上影线做空,下影线做多)
+    # 不在此列表中的品种使用正向逻辑(上影线做多,下影线做空)
+    
+    # 期货品种完整配置字典
+    g.futures_config = {
+        # 贵金属
+        'AU': {'has_night_session': True, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 1000},
+        'AG': {'has_night_session': True, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 15},
+        
+        # 有色金属
+        'CU': {'has_night_session': True, 'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5},
+        'AL': {'has_night_session': True, 'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5},
+        'ZN': {'has_night_session': True, 'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5},
+        'PB': {'has_night_session': True, 'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5},
+        'NI': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1},
+        'SN': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1},
+        'SS': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5},
+        
+        # 黑色系
+        'RB': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10},
+        'HC': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10},
+        'I': {'has_night_session': True, 'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 100},
+        'JM': {'has_night_session': True, 'margin_rate': {'long': 0.22, 'short': 0.22}, 'multiplier': 100},
+        'J': {'has_night_session': True, 'margin_rate': {'long': 0.22, 'short': 0.22}, 'multiplier': 60},
+        
+        # 能源化工
+        'SP': {'has_night_session': True, 'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 10},
+        'FU': {'has_night_session': True, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 10},
+        'BU': {'has_night_session': True, 'margin_rate': {'long': 0.04, 'short': 0.04}, 'multiplier': 10},
+        'RU': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        'BR': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5},
+        'SC': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1000},
+        'NR': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10},
+        'LU': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 10},
+        'LC': {'has_night_session': False, 'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 1},
+        
+        # 化工
+        'FG': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20},
+        'TA': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 5},
+        'MA': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        'SA': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20},
+        'L': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5},
+        'V': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5},
+        'EG': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        'PP': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5},
+        'EB': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 5},
+        'PG': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20},
+        
+        # 农产品
+        'RM': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        'OI': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        'CF': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 5},
+        'SR': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        'PF': {'has_night_session': True, 'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 5},
+        'C': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10},
+        'CS': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10},
+        'CY': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5},
+        'A': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10},
+        'B': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        'M': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10},
+        'Y': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        'P': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        
+        # 无夜盘品种
+        'IF': {'has_night_session': False, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 300},
+        'IH': {'has_night_session': False, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 300},
+        'IC': {'has_night_session': False, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 200},
+        'IM': {'has_night_session': False, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 200},
+        'AP': {'has_night_session': False, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 10},
+        'CJ': {'has_night_session': False, 'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5},
+        'PK': {'has_night_session': False, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 5},
+        'JD': {'has_night_session': False, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10},
+        'LH': {'has_night_session': False, 'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 16}
+    }
+    
+    # 策略品种选择策略配置
+    # 方案1:全品种策略 - 考虑所有配置的期货品种
+    # g.strategy_focus_symbols = []  # 空列表表示考虑所有品种
+    
+    # 方案2:精选品种策略 - 只交易流动性较好的特定品种(如需使用请取消下行注释)
+    g.strategy_focus_symbols = ['RM', 'CJ', 'CY', 'JD', 'L', 'LC', 'SF', 'SI']
+    
+    log.info(f"品种选择策略: {'全品种策略(覆盖所有配置品种)' if not g.strategy_focus_symbols else '精选品种策略(' + str(len(g.strategy_focus_symbols)) + '个品种)'}")
+    
+    # 输出开仓方向配置信息
+    log.info("=" * 50)
+    log.info("开仓方向配置:")
+    log.info(f"反向逻辑品种: {g.reverse_direction_symbols} (上影线做空,下影线做多)")
+    log.info("正向逻辑品种: 所有其他品种 (上影线做多,下影线做空)")
+    log.info("=" * 50)
+    
+    # 交易记录和数据存储
+    g.trade_history = {}
+    g.candlestick_signals = {}  # 存储烛台形态信号
+    g.daily_data_cache = {}  # 存储历史日线数据缓存
+    g.minute_data_cache = {}  # 存储今日分钟数据缓存
+    g.body_threshold_cache = {}  # 存储实体长度阈值缓存
+    
+    # 保证金比例管理
+    g.margin_rate_history = {}  # 保证金比例变化历史记录
+    g.today_trades = []  # 当日交易记录
+    
+    # 定时任务设置
+    # 每月第1个交易日更新阈值
+    run_monthly(monthly_update_thresholds, 1, 'before_open', reference_security='IF1808.CCFX')
+    
+    # 夜盘开始 - 仅止损止盈检查
+    run_daily(main_trading_stop_only, time='21:05:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='21:35:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='22:05:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='22:35:00', reference_security='IF1808.CCFX')
+    
+    # 日盘开始 - 仅止损止盈检查
+    run_daily(main_trading_stop_only, time='09:05:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='09:35:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='10:05:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='10:35:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='11:05:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='11:25:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='13:35:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='14:05:00', reference_security='IF1808.CCFX')
+    
+    # 收盘前 - 14:55进行完整交易检查,14:35仅止损止盈
+    run_daily(main_trading_stop_only, time='14:35:00', reference_security='IF1808.CCFX')
+    
+    # 完整交易检查 - 仅在14:55执行(任务1, 2, 3, 4, 5, 6, 7)
+    run_daily(main_trading_complete, time='14:55:00', reference_security='IF1808.CCFX')
+    
+    # 收盘后
+    run_daily(after_market_close, time='15:30:00', reference_security='IF1808.CCFX')
+
+############################ 主程序执行函数 ###################################
+
+def monthly_update_thresholds(context):
+    """每月更新实体长度阈值"""
+    log.info("=" * 60)
+    log.info("每月阈值更新开始")
+    log.info("=" * 60)
+    
+    current_month = context.current_dt.strftime('%Y-%m')
+    
+    # 检查是否需要更新
+    if g.last_threshold_update_month == current_month:
+        log.info(f"本月阈值已更新,跳过更新")
+        return
+    
+    # 获取所有需要计算阈值的品种
+    focus_symbols = g.strategy_focus_symbols if g.strategy_focus_symbols else list(g.futures_config.keys())
+    
+    updated_count = 0
+    for symbol in focus_symbols:
+        try:
+            # 获取主力合约
+            dominant_future = get_dominant_future(symbol)
+            if not dominant_future:
+                continue
+            
+            # 获取365天历史数据
+            data = attribute_history(dominant_future, g.historical_days, '1d', 
+                                   ['open', 'close', 'high', 'low', 'volume'], 
+                                   df=True)
+            
+            if data is not None and len(data) > 30:  # 确保有足够数据
+                # 计算实体长度阈值
+                data['body_length'] = abs(data['close'] - data['open'])
+                body_threshold = data['body_length'].mean()
+                
+                # 存储到全局阈值缓存
+                g.monthly_body_threshold_cache[dominant_future] = body_threshold
+                updated_count += 1
+                
+                log.info(f"更新 {symbol}({dominant_future}) 实体长度阈值: {body_threshold:.4f}")
+            
+        except Exception as e:
+            log.warning(f"更新{symbol}阈值时出错: {str(e)}")
+            continue
+    
+    # 更新最后更新月份
+    g.last_threshold_update_month = current_month
+    
+    log.info(f"阈值更新完成,共更新 {updated_count} 个品种")
+    log.info("=" * 60)
+
+def main_trading_stop_only(context):
+    """仅进行止损止盈检查的交易时段"""
+    # 只进行止损止盈检查
+    task_7_check_stop_loss_profit(context)
+
+def main_trading_complete(context):
+    """14:55 完整交易检查 - 执行任务1, 2, 3, 4, 5, 6, 7"""
+    log.info("=" * 60)
+    log.info("14:55 烛台影线形态反向交易策略 - 完整交易检查")
+    log.info("=" * 60)
+    
+    # 任务1: 获取所有可交易品种
+    task_1_get_tradable_futures(context)
+    
+    # 任务2: 获取历史数据并处理阈值(包括单独计算缺失阈值)
+    task_2_load_historical_data_and_thresholds(context)
+    
+    # 任务3: 获取今日分钟数据
+    task_3_update_realtime_data(context)
+    
+    # 任务4: 检测烛台形态
+    task_4_detect_candlestick_patterns(context)
+    
+    # 任务5: 检查开仓条件(简化版)
+    filtered_signals = task_5_check_opening_conditions(context)
+
+    # 任务6: 执行交易
+    if filtered_signals:
+        task_6_execute_trades(context, filtered_signals)
+    
+    # 任务7: 检查止损止盈
+    task_7_check_stop_loss_profit(context)
+    
+    # 任务8: 检查换月移仓
+    task_8_check_position_switch(context)
+
+############################ 核心任务函数 ###################################
+
+def task_1_get_tradable_futures(context):
+    """任务1: 获取所有可交易品种(分白天和晚上)"""
+    log.info("执行任务1: 获取可交易品种")
+    
+    current_time = str(context.current_dt.time())[:2]
+    
+    # 从策略关注列表中筛选可交易品种
+    focus_symbols = g.strategy_focus_symbols if g.strategy_focus_symbols else list(g.futures_config.keys())
+    
+    potential_icon_list = []
+    night_session_symbols = []
+    day_only_symbols = []
+    
+    # 预先分类所有品种
+    for symbol in focus_symbols:
+        if get_futures_config(symbol, 'has_night_session', False):
+            night_session_symbols.append(symbol)
+        else:
+            day_only_symbols.append(symbol)
+    
+    if current_time in ('21', '22'):
+        # 夜盘时间:只考虑有夜盘的品种
+        potential_icon_list = night_session_symbols[:]
+        log.info(f"夜盘时间,有夜盘交易的品种: {len(night_session_symbols)}个")
+        log.info(f"夜盘可交易品种: {potential_icon_list[:10]}{'...' if len(potential_icon_list) > 10 else ''}")  # 显示前10个,避免日志过长
+    else:
+        # 日盘时间:所有品种都可以交易(包括有夜盘的和只有日盘的)
+        potential_icon_list = focus_symbols[:]
+        log.info(f"日盘时间,全部品种: {len(focus_symbols)}个(含夜盘品种: {len(night_session_symbols)}个,日盘专属品种: {len(day_only_symbols)}个)")
+        if day_only_symbols:
+            log.info(f"日盘专属品种: {day_only_symbols}")
+        log.info(f"日盘可交易品种: {potential_icon_list[:10]}{'...' if len(potential_icon_list) > 10 else ''}")  # 显示前10个,避免日志过长
+    
+    potential_future_list = []
+    for symbol in potential_icon_list:
+        dominant_future = get_dominant_future(symbol)
+        if dominant_future:
+            potential_future_list.append(dominant_future)
+    
+    # 过滤掉已有持仓的品种
+    existing_positions = set(g.trade_history.keys())
+    potential_future_list = [f for f in potential_future_list if not check_symbol_prefix_match(f, existing_positions)]
+    
+    # 存储到全局变量
+    g.tradable_futures = potential_future_list
+    log.info(f"最终可交易期货品种数量: {len(potential_future_list)}")
+    
+    return potential_future_list
+
+def task_2_load_historical_data_and_thresholds(context):
+    """任务2: 获取历史数据并处理阈值(包括单独计算缺失阈值)"""
+    log.info("执行任务2: 加载历史数据并处理阈值")
+    
+    if not hasattr(g, 'tradable_futures'):
+        return
+    
+    for future_code in g.tradable_futures:
+        try:
+            # 获取30天历史数据(用于分钟数据合并)
+            data = attribute_history(future_code, 30, '1d', 
+                                   ['open', 'close', 'high', 'low', 'volume'], 
+                                   df=True)
+            
+            if data is not None and len(data) > 0:
+                # 排除今天的数据
+                today = context.current_dt.date()
+                data = data[data.index.date < today]
+                g.daily_data_cache[future_code] = data
+                
+                # 处理阈值:优先使用月度阈值,缺失时单独计算
+                if future_code in g.monthly_body_threshold_cache:
+                    # 使用月度阈值缓存
+                    body_threshold = g.monthly_body_threshold_cache[future_code]
+                    g.body_threshold_cache[future_code] = body_threshold
+                    # log.info(f"已缓存 {future_code} 历史数据 {len(data)} 条,使用月度阈值: {body_threshold:.4f}")
+                else:
+                    # 单独计算缺失的阈值
+                    log.info(f"{future_code} 未找到月度阈值,开始单独计算")
+                    body_threshold = calculate_individual_threshold(context, future_code)
+                    if body_threshold is not None:
+                        g.body_threshold_cache[future_code] = body_threshold
+                        log.info(f"已为 {future_code} 单独计算阈值: {body_threshold:.4f}")
+                    else:
+                        log.warning(f"{future_code} 无法计算阈值,跳过")
+            
+        except Exception as e:
+            log.warning(f"加载{future_code}历史数据时出错: {str(e)}")
+            continue
+    
+    log.info(f"历史数据缓存完成,共缓存 {len(g.daily_data_cache)} 个品种")
+
+def calculate_individual_threshold(context, future_code):
+    """为单个工具计算实体长度阈值并自动缓存到月度阈值中"""
+    try:
+        log.info(f"为 {future_code} 单独计算365天历史阈值")
+        
+        # 获取365天历史数据
+        data = attribute_history(future_code, g.historical_days, '1d', 
+                               ['open', 'close', 'high', 'low', 'volume'], 
+                               df=True)
+        
+        if data is not None and len(data) > 30:  # 确保有足够数据
+            # 排除今天的数据
+            today = context.current_dt.date()
+            data = data[data.index.date < today]
+            
+            # 计算实体长度阈值
+            data['body_length'] = abs(data['close'] - data['open'])
+            body_threshold = data['body_length'].mean()
+            
+            # 🚀 优化1:将计算结果自动添加到月度阈值缓存中
+            g.monthly_body_threshold_cache[future_code] = body_threshold
+            log.info(f"✅ {future_code} 单独计算完成,阈值: {body_threshold:.4f},已添加到月度缓存")
+            log.info(f"当前月度缓存包含 {len(g.monthly_body_threshold_cache)} 个品种阈值")
+            
+            return body_threshold
+        else:
+            log.warning(f"{future_code} 历史数据不足,无法计算阈值")
+            return None
+            
+    except Exception as e:
+        log.warning(f"为 {future_code} 单独计算阈值时出错: {str(e)}")
+        return None
+
+def task_3_update_realtime_data(context):
+    """任务3: 获取今日分钟数据"""
+    # log.info("执行任务3: 更新实时数据")
+    
+    # 收集需要更新数据的品种
+    update_symbols = set()
+    
+    # 添加可交易品种
+    if hasattr(g, 'tradable_futures') and g.tradable_futures:
+        update_symbols.update(g.tradable_futures)
+    
+    # 添加持仓品种(用于止损止盈)
+    if hasattr(g, 'trade_history') and g.trade_history:
+        update_symbols.update(g.trade_history.keys())
+    
+    if not update_symbols:
+        return
+    
+    for future_code in update_symbols:
+        try:
+            # 获取今日分钟数据
+            minute_data = get_today_minute_data(context, future_code)
+            if minute_data is None:
+                continue
+            
+            # 获取历史数据
+            historical_data = g.daily_data_cache.get(future_code)
+            if historical_data is None:
+                # 为持仓品种临时获取历史数据
+                try:
+                    data = attribute_history(future_code, g.historical_days, '1d', 
+                                           ['open', 'close', 'high', 'low', 'volume'], 
+                                           df=True)
+                    if data is not None and len(data) > 0:
+                        today = context.current_dt.date()
+                        data = data[data.index.date < today]
+                        
+                        # 计算实体长度阈值
+                        data['body_length'] = abs(data['close'] - data['open'])
+                        body_threshold = data['body_length'].mean()
+                        
+                        g.daily_data_cache[future_code] = data
+                        g.body_threshold_cache[future_code] = body_threshold
+                        historical_data = data
+                        log.info(f"为持仓品种 {future_code} 临时获取历史数据 {len(data)} 条")
+                except Exception as e:
+                    log.warning(f"获取{future_code}历史数据失败: {str(e)}")
+                    continue
+            
+            if historical_data is None:
+                continue
+            
+            # 将今日数据合并为日线数据
+            today_data = aggregate_minute_to_daily(minute_data)
+            if today_data is not None:
+                # 合并历史数据和今日数据
+                combined_data = pd.concat([historical_data, today_data], sort=False)
+                g.minute_data_cache[future_code] = combined_data
+            
+        except Exception as e:
+            log.warning(f"更新{future_code}实时数据时出错: {str(e)}")
+            continue
+
+def task_4_detect_candlestick_patterns(context):
+    """任务4: 检测烛台形态"""
+    # log.info("执行任务4: 检测烛台形态")
+    
+    candlestick_signals = []
+    
+    # 获取已持仓的品种列表
+    existing_positions = set(g.trade_history.keys())
+    
+    for future_code, data in g.minute_data_cache.items():
+        try:
+            # 检查是否已有相似持仓,如果有则跳过分析
+            if check_symbol_prefix_match(future_code, existing_positions):
+                continue
+            
+            # 获取实体长度阈值
+            body_threshold = g.body_threshold_cache.get(future_code)
+            if body_threshold is None:
+                continue
+            
+            # 检查最新的烛台形态
+            latest_pattern = check_latest_candlestick_pattern(data, future_code, body_threshold)
+            if latest_pattern:
+                candlestick_signals.append(latest_pattern)
+                log.info(f"{future_code} 发现烛台形态: {latest_pattern['hatch_direction']}影线")
+                
+        except Exception as e:
+            log.warning(f"检测{future_code}烛台形态时出错: {str(e)}")
+            continue
+    
+    # 存储到全局变量
+    g.candlestick_signals = candlestick_signals
+    if len(candlestick_signals) > 0:
+        log.info(f"烛台形态检测完成,发现信号 {len(candlestick_signals)} 个")
+    
+    return candlestick_signals
+
+def task_5_check_opening_conditions(context):
+    """任务5: 检查开仓条件(简化版)"""
+    log.info("执行任务5: 检查开仓条件(简化版)")
+    
+    if not hasattr(g, 'candlestick_signals') or not g.candlestick_signals:
+        log.info("没有检测到烛台信号")
+        return []
+    
+    log.info(f"检测到 {len(g.candlestick_signals)} 个烛台信号,开始筛选")
+    
+    filtered_signals = []
+    
+    for i, signal in enumerate(g.candlestick_signals):
+        log.info(f"处理信号 {i+1}: {signal['symbol']} {signal['hatch_direction']}影线")
+        
+        # 仅检查是否已有相似持仓(避免重复开仓同一品种)
+        if check_symbol_prefix_match(signal['symbol'], set(g.trade_history.keys())):
+            log.info(f"{signal['symbol']} 已有相似持仓,跳过")
+            continue
+        
+        # 简化版:只要形态满足条件就接受,无额外验证
+        filtered_signals.append(signal)
+        log.info(f"{signal['symbol']} 烛台形态满足条件,接受交易信号")
+    
+    log.info(f"开仓条件检查完成,满足条件 {len(filtered_signals)} 个")
+    return filtered_signals
+
+def task_6_execute_trades(context, filtered_signals):
+    """任务6: 执行交易(简化版)"""
+    log.info("执行任务6: 执行交易(简化版)")
+    
+    if not filtered_signals:
+        log.info("没有满足条件的交易信号")
+        return
+    
+    log.info(f"开始执行 {len(filtered_signals)} 个交易信号")
+    
+    for i, signal in enumerate(filtered_signals):
+        symbol = signal['symbol']
+        log.info(f"执行交易 {i+1}: {symbol} {signal['hatch_direction']}影线形态")
+        
+        # 使用可配置的开仓方向逻辑
+        direction, logic_description = determine_trading_direction(symbol, signal['hatch_direction'])
+        
+        log.info(f"{symbol} 交易方向: {direction} ({logic_description})")
+        
+        try:
+            # 计算目标手数(优化版:直接返回手数)
+            target_hands = calculate_target_hands(context, symbol, direction)
+            log.info(f"计算目标手数: {symbol} {direction} 目标手数: {target_hands}")
+            
+            if target_hands > 0:
+                # 执行开仓
+                log.info(f"开始下单: {symbol} {direction} 目标手数: {target_hands}")
+                success = open_position(context, symbol, target_hands, direction, signal)
+                
+                if success:
+                    actual_margin = g.trade_history[symbol]['actual_margin']
+                    actual_price = g.trade_history[symbol]['entry_price']
+                    log.info(f"✅ 成功开仓 {symbol} {direction}, 成交价格: {actual_price:.2f}, 目标手数: {target_hands}, 实际保证金: {actual_margin:.0f}")
+                else:
+                    log.warning(f"❌ 开仓失败 {symbol} {direction}")
+            else:
+                log.warning(f"{symbol} 计算目标手数为0,跳过")
+        except Exception as e:
+            log.warning(f"{symbol} 交易执行出错: {str(e)}")
+            continue
+    
+    log.info("交易执行完成")
+
+def task_8_check_position_switch(context):
+    """任务8: 检查换月移仓"""
+    # log.info("执行任务8: 检查换月移仓")
+    
+    switch_result = position_auto_switch(context)
+    if switch_result:
+        log.info(f"执行了 {len(switch_result)} 次移仓换月")
+        for result in switch_result:
+            log.info(f"移仓: {result['before']} -> {result['after']}")
+
+def task_7_check_stop_loss_profit(context):
+    """任务7: 检查止损止盈(带交易时间验证)"""
+    # log.info("执行任务7: 检查止损止盈")
+    
+    # 获取当前时间
+    current_time = context.current_dt.strftime('%H:%M')
+    current_hour = int(current_time[:2])
+    
+    # 判断是否为夜盘时间(21:00-23:00 和 00:00-02:30)
+    is_night_session = (current_hour >= 21) or (current_hour <= 2)
+    
+    # 遍历所有持仓进行止损止盈检查
+    subportfolio = context.subportfolios[0]
+    long_positions = list(subportfolio.long_positions.values())
+    short_positions = list(subportfolio.short_positions.values())
+    
+    closed_count = 0
+    skipped_count = 0
+    
+    for position in long_positions + short_positions:
+        security = position.security
+        underlying_symbol = security.split('.')[0][:-4]
+        
+        # 检查交易时间适配性
+        has_night_session = get_futures_config(underlying_symbol, 'has_night_session', False)
+        
+        # 如果是夜盘时间,但品种不支持夜盘交易,则跳过
+        if is_night_session and not has_night_session:
+            skipped_count += 1
+            # log.info(f"跳过夜盘时间的日间品种: {underlying_symbol} ({current_time})")
+            continue
+        
+        # 执行止损止盈检查
+        if check_stop_loss_profit(context, position):
+            closed_count += 1
+    
+    if closed_count > 0:
+        log.info(f"执行了 {closed_count} 次止损止盈")
+    
+    if skipped_count > 0:
+        log.info(f"夜盘时间跳过 {skipped_count} 个日间品种的止损止盈检查")
+
+############################ 数据处理辅助函数 ###################################
+
+def check_has_night_session(underlying_symbol):
+    """检查品种是否有夜盘"""
+    return get_futures_config(underlying_symbol, 'has_night_session', False)
+
+def get_today_minute_data(context, future_code):
+    """获取今日分钟数据"""
+    try:
+        # 判断该品种是否有夜盘
+        underlying_symbol = future_code.split('.')[0][:-4]
+        has_night_session = check_has_night_session(underlying_symbol)
+        
+        end_time = context.current_dt
+        
+        # 获取足够的历史分钟数据
+        minute_data = attribute_history(future_code, 
+                                      count=800,  # 获取足够多的数据
+                                      unit='1m',
+                                      fields=['open', 'close', 'high', 'low', 'volume'],
+                                      df=True)
+        
+        if minute_data is None or len(minute_data) == 0:
+            return None
+        
+        # 提取所有日期(年月日维度)
+        minute_data['date'] = minute_data.index.date
+        unique_dates = sorted(minute_data['date'].unique())
+        
+        if has_night_session:
+            # 有夜盘的品种:需要找到前一交易日的21:00作为今日开盘起点
+            today_date = end_time.date()
+            
+            # 找到今天之前的最后一个交易日
+            previous_trading_dates = [d for d in unique_dates if d < today_date]
+            if not previous_trading_dates:
+                return minute_data
+            
+            previous_trading_date = max(previous_trading_dates)
+            
+            # 找到前一交易日21:00:00的数据作为开盘起点
+            previous_day_data = minute_data[minute_data['date'] == previous_trading_date]
+            night_21_data = previous_day_data[previous_day_data.index.hour == 21]
+            
+            if len(night_21_data) > 0:
+                # 从前一交易日21:00开始的所有数据
+                start_time = night_21_data.index[0]  # 21:00:00的时间点
+                filtered_data = minute_data[minute_data.index >= start_time]
+                return filtered_data.drop(columns=['date'])
+            else:
+                # 备选方案:使用今天9:00开始的数据
+                today_data = minute_data[minute_data['date'] == today_date]
+                day_9_data = today_data[today_data.index.hour >= 9]
+                if len(day_9_data) > 0:
+                    return day_9_data.drop(columns=['date'])
+                else:
+                    return minute_data.drop(columns=['date'])
+        else:
+            # 没有夜盘的品种:从今天9:00:00开始
+            today_date = end_time.date()
+            today_data = minute_data[minute_data['date'] == today_date]
+            
+            # 找到今天9:00:00开始的数据
+            day_9_data = today_data[today_data.index.hour >= 9]
+            
+            if len(day_9_data) > 0:
+                return day_9_data.drop(columns=['date'])
+            else:
+                return today_data.drop(columns=['date']) if len(today_data) > 0 else minute_data.drop(columns=['date'])
+        
+    except Exception as e:
+        log.warning(f"获取{future_code}今日分钟数据时出错: {str(e)}")
+    
+    return None
+
+def aggregate_minute_to_daily(minute_data):
+    """将分钟数据聚合为日数据"""
+    try:
+        if minute_data is None or len(minute_data) == 0:
+            return None
+        
+        # 获取今日日期(使用最后一条数据的日期作为今日日期)
+        today_date = minute_data.index[-1].date()
+        
+        # 聚合为日数据
+        daily_data = pd.DataFrame({
+            'open': [minute_data['open'].iloc[0]],  # 今日交易开始时的开盘价
+            'close': [minute_data['close'].iloc[-1]],  # 当前收盘价,实时更新
+            'high': [minute_data['high'].max()],
+            'low': [minute_data['low'].min()],
+            'volume': [minute_data['volume'].sum()]
+        }, index=[pd.Timestamp(today_date)])
+        
+        return daily_data
+        
+    except Exception as e:
+        log.warning(f"聚合分钟数据时出错: {str(e)}")
+        return None
+
+def check_latest_candlestick_pattern(data, future_code, body_threshold):
+    """检查最新的烛台形态"""
+    if len(data) < 1:
+        return None
+        
+    # 获取最新一天的数据
+    today = data.iloc[-1]
+    
+    # 检查烛台形态
+    pattern_result = check_candlestick_pattern_single_day(today, body_threshold)
+    if not pattern_result:
+        return None
+    
+    return {
+        'symbol': future_code,
+        'date': today.name,
+        'hatch_direction': pattern_result['hatch_direction'],
+        'open': today['open'],
+        'close': today['close'],
+        'high': today['high'],
+        'low': today['low'],
+        'body_length': pattern_result['body_length'],
+        'hatch_length': pattern_result['hatch_length']
+    }
+
+def check_candlestick_pattern_single_day(row, body_threshold):
+    """检查单日烛台形态是否符合影线条件"""
+    open_price = row['open']
+    close_price = row['close']
+    high_price = row['high']
+    low_price = row['low']
+    
+    # 计算实体长度
+    body_length = abs(close_price - open_price)
+    
+    # 检查实体长度是否满足阈值
+    if body_length < body_threshold:
+        return None
+    
+    # 计算影线长度
+    upper_hatch = high_price - max(open_price, close_price)
+    lower_hatch = min(open_price, close_price) - low_price
+    
+    # 检查影线条件
+    hatch_direction = None
+    hatch_length = 0
+    
+    # 检查上影线条件:上影线长度符合要求 且 下影线长度小于实体长度的一半
+    if (upper_hatch >= g.hatch_to_body_ratio * body_length and 
+        lower_hatch < g.opposite_hatch_ratio * body_length):
+        hatch_direction = 'up'
+        hatch_length = upper_hatch
+    # 检查下影线条件:下影线长度符合要求 且 上影线长度小于实体长度的一半
+    elif (lower_hatch >= g.hatch_to_body_ratio * body_length and 
+          upper_hatch < g.opposite_hatch_ratio * body_length):
+        hatch_direction = 'down'
+        hatch_length = lower_hatch
+    
+    # 如果满足影线条件,返回形态信息
+    if hatch_direction is not None:
+        return {
+            'hatch_direction': hatch_direction,
+            'body_length': body_length,
+            'hatch_length': hatch_length
+        }
+    
+    return None
+
+############################ 动态保证金率调整函数 ###################################
+
+def detect_and_update_margin_rates(context, symbol, actual_hands, cash_change, direction):
+    """
+    检测并更新保证金率配置
+    
+    参数:
+    symbol: 期货合约代码
+    actual_hands: 实际成交手数
+    cash_change: 实际资金变化(保证金使用)
+    direction: 交易方向
+    """
+    try:
+        underlying_symbol = symbol.split('.')[0][:-4]
+        
+        if underlying_symbol not in g.futures_config:
+            return
+        
+        current_price = get_current_data()[symbol].last_price
+        multiplier = get_multiplier(underlying_symbol)
+        
+        # 计算实际保证金率
+        contract_value = current_price * multiplier * actual_hands
+        actual_margin_rate = cash_change / contract_value if contract_value > 0 else 0
+        
+        # 获取配置中的保证金率
+        config_margin_rate = g.futures_config[underlying_symbol]['margin_rate'][direction]
+        
+        # 计算差异百分比
+        rate_diff_percentage = abs(actual_margin_rate - config_margin_rate) / config_margin_rate * 100 if config_margin_rate > 0 else 100
+        
+        # 如果差异超过10%,更新配置
+        if rate_diff_percentage > 10:
+            log.info(f"🔍 检测到{underlying_symbol}保证金率差异: 配置={config_margin_rate:.3f}, 实际={actual_margin_rate:.3f}, 差异={rate_diff_percentage:.1f}%")
+            
+            # 更新配置
+            g.futures_config[underlying_symbol]['margin_rate'][direction] = actual_margin_rate
+            
+            # 如果是双向更新(多空保证金率通常相同)
+            if g.futures_config[underlying_symbol]['margin_rate']['long'] == g.futures_config[underlying_symbol]['margin_rate']['short']:
+                g.futures_config[underlying_symbol]['margin_rate']['long'] = actual_margin_rate
+                g.futures_config[underlying_symbol]['margin_rate']['short'] = actual_margin_rate
+                log.info(f"✅ 已更新{underlying_symbol}保证金率为 {actual_margin_rate:.3f} (双向)")
+            else:
+                log.info(f"✅ 已更新{underlying_symbol} {direction}保证金率为 {actual_margin_rate:.3f}")
+                
+    except Exception as e:
+        log.warning(f"检测保证金率时出错 {symbol}: {str(e)}")
+
+############################ 机会性仓位调整函数 ###################################
+
+def opportunistic_position_increase(context, symbol, direction, signal):
+    """
+    机会性仓位调整:在保证金使用量低于限制时自动增加仓位
+    
+    参数:
+    symbol: 期货合约代码
+    direction: 交易方向
+    signal: 交易信号
+    """
+    try:
+        if symbol not in g.trade_history:
+            return False
+            
+        underlying_symbol = symbol.split('.')[0][:-4]
+        current_price = get_current_data()[symbol].last_price
+        margin_rate = get_margin_rate(underlying_symbol, direction)
+        multiplier = get_multiplier(underlying_symbol)
+        
+        # 获取当前持仓信息
+        current_actual_margin = g.trade_history[symbol]['actual_margin']
+        current_hands = g.trade_history[symbol]['actual_hands']
+        
+        # 计算剩余保证金容量
+        max_margin = g.max_margin_per_position
+        remaining_margin_capacity = max_margin - current_actual_margin
+        
+        # 检查是否有足够的剩余容量增加至少1手
+        single_hand_margin = current_price * multiplier * margin_rate
+        
+        if remaining_margin_capacity >= single_hand_margin:
+            # 计算可以增加的最大手数
+            additional_hands = int(remaining_margin_capacity / single_hand_margin)
+            
+            # 同时考虑可用资金限制
+            available_cash = context.portfolio.available_cash * g.usage_percentage
+            max_hands_by_cash = int(available_cash / single_hand_margin)
+            
+            # 取较小值
+            additional_hands = min(additional_hands, max_hands_by_cash)
+            
+            if additional_hands >= 1:
+                log.info(f"🚀 机会性增仓机会: {symbol} 当前{current_hands}手(保证金:{current_actual_margin:.0f}), 可增加{additional_hands}手")
+                
+                # 计算新的目标手数
+                new_target_hands = current_hands + additional_hands
+                
+                # 执行增仓
+                order = order_target(symbol, new_target_hands, side=direction)
+                
+                if order and order.filled > 0:
+                    # 计算实际增仓使用的保证金
+                    cash_after = context.portfolio.available_cash
+                    additional_margin_used = order.filled * single_hand_margin  # 估算使用的保证金
+                    
+                    # 更新交易记录
+                    new_total_hands = current_hands + order.filled
+                    new_total_margin = current_actual_margin + additional_margin_used
+                    
+                    g.trade_history[symbol]['actual_hands'] = new_total_hands
+                    g.trade_history[symbol]['target_hands'] = new_target_hands
+                    g.trade_history[symbol]['actual_margin'] = new_total_margin
+                    
+                    log.info(f"✅ 成功增仓 {symbol} {order.filled}手, 总手数: {new_total_hands}手, 总保证金: {new_total_margin:.0f}")
+                    
+                    # 记录增仓交易
+                    g.today_trades.append({
+                        'security': symbol,
+                        'underlying_symbol': underlying_symbol,
+                        'direction': direction,
+                        'order_amount': order.filled,
+                        'order_price': order.avg_cost if order.avg_cost else order.price,
+                        'cash_change': additional_margin_used,
+                        'time': context.current_dt,
+                        'trade_type': 'increase'  # 标记为增仓
+                    })
+                    
+                    return True
+                    
+        return False
+        
+    except Exception as e:
+        log.warning(f"机会性增仓时出错 {symbol}: {str(e)}")
+        return False
+
+############################ 交易执行函数 ###################################
+
+def open_position(context, security, target_hands, direction, signal):
+    """开仓(优化版:使用order_target按手数开仓)"""
+    try:
+        # 记录交易前的可用资金
+        cash_before = context.portfolio.available_cash
+        
+        # 使用order_target按手数开仓,自动处理持仓差额
+        order = order_target(security, target_hands, side=direction)
+        
+        if order is not None and order.filled > 0:
+            # 记录交易后的可用资金
+            cash_after = context.portfolio.available_cash
+            
+            # 计算实际资金变化
+            cash_change = cash_before - cash_after
+            
+            # 获取订单价格和数量
+            order_price = order.avg_cost if order.avg_cost else order.price
+            order_amount = order.filled
+            
+            # 记录当日交易
+            underlying_symbol = security.split('.')[0][:-4]
+            g.today_trades.append({
+                'security': security,
+                'underlying_symbol': underlying_symbol,
+                'direction': direction,
+                'order_amount': order_amount,
+                'order_price': order_price,
+                'cash_change': cash_change,
+                'time': context.current_dt
+            })
+            
+            # 记录交易信息,包含止损止盈相关信息
+            g.trade_history[security] = {
+                'entry_price': order_price,
+                'target_hands': target_hands,
+                'actual_hands': order_amount,
+                'actual_margin': cash_change,
+                'direction': direction,
+                'entry_time': context.current_dt,
+                'signal_info': signal,
+                'max_profit': 0.0,  # 初始化最大利润
+                'max_profit_price': order_price  # 记录最大利润时的价格
+            }
+            
+            # 🚀 优化2:动态保证金率调整
+            detect_and_update_margin_rates(context, security, order_amount, cash_change, direction)
+            
+            # 🚀 优化3:机会性仓位调整
+            opportunistic_position_increase(context, security, direction, signal)
+            
+            return True
+            
+    except Exception as e:
+        log.warning(f"开仓失败 {security}: {str(e)}")
+    
+    return False
+
+def close_position(context, security, direction):
+    """平仓(优化版:使用order_target平仓到0手)"""
+    try:
+        # 使用order_target平仓到0手,自动处理持仓清零
+        order = order_target(security, 0, side=direction)
+        
+        if order is not None and order.filled > 0:
+            underlying_symbol = security.split('.')[0][:-4]
+            
+            # 记录当日交易(平仓)
+            g.today_trades.append({
+                'security': security,
+                'underlying_symbol': underlying_symbol,
+                'direction': direction,
+                'order_amount': -order.filled,  # 负数表示平仓
+                'order_price': order.avg_cost if order.avg_cost else order.price,
+                'cash_change': 0,  # 平仓不计算保证金变化
+                'time': context.current_dt
+            })
+            
+            log.info(f"平仓成功 - 品种: {underlying_symbol}, 平仓手数: {order.filled}")
+            
+            # 从交易历史中移除
+            if security in g.trade_history:
+                del g.trade_history[security]
+            return True
+            
+    except Exception as e:
+        log.warning(f"平仓失败 {security}: {str(e)}")
+    
+    return False
+
+def check_stop_loss_profit(context, position):
+    """检查止损止盈"""
+    security = position.security
+    
+    if security not in g.trade_history:
+        return False
+    
+    trade_info = g.trade_history[security]
+    direction = trade_info['direction']
+    entry_price = trade_info['entry_price']
+    current_price = position.price
+    
+    # 计算当前盈亏比率
+    if direction == 'long':
+        profit_rate = (current_price - entry_price) / entry_price
+    else:
+        profit_rate = (entry_price - current_price) / entry_price
+    
+    # 更新最大利润记录
+    if profit_rate > trade_info['max_profit']:
+        trade_info['max_profit'] = profit_rate
+        trade_info['max_profit_price'] = current_price
+        g.trade_history[security] = trade_info
+    
+    # 检查固定止损
+    if profit_rate <= -g.fixed_stop_loss_rate:
+        log.info(f"触发固定止损 {security} {direction}, 当前亏损率: {profit_rate:.3%}, 成本价: {entry_price:.2f}, 当前价格: {current_price:.2f}")
+        close_position(context, security, direction)
+        return True
+    
+    # 检查动态追踪止盈
+    max_profit = trade_info['max_profit']
+    if max_profit > 0:
+        # 确定追踪止损比率
+        if max_profit <= g.trailing_stop_thresholds[0]:  # ≤5%
+            trailing_rate = g.trailing_stop_rates[0]  # 1%
+        elif max_profit <= g.trailing_stop_thresholds[1]:  # 5%-10%
+            trailing_rate = g.trailing_stop_rates[1]  # 2%
+        else:  # >10%
+            trailing_rate = g.trailing_stop_rates[2]  # 3%
+        
+        # 检查是否触发追踪止损
+        profit_drawdown = max_profit - profit_rate
+        if profit_drawdown >= trailing_rate:
+            log.info(f"触发动态追踪止损 {security} {direction}")
+            log.info(f"最大利润: {max_profit:.3%}, 当前利润: {profit_rate:.3%}, 回撤: {profit_drawdown:.3%}, 触发阈值: {trailing_rate:.3%}")
+            close_position(context, security, direction)
+            return True
+    
+    return False
+
+############################ 辅助函数 ###################################
+
+def determine_trading_direction(symbol, hatch_direction):
+    """
+    确定开仓方向(支持可配置的正向/反向逻辑)
+    
+    参数:
+    symbol: 期货合约代码 (如 'JD2510.XDCE')
+    hatch_direction: 影线方向 ('up' 或 'down')
+    
+    返回:
+    (direction, logic_description): 交易方向和逻辑说明的元组
+    """
+    # 提取基础品种代码
+    underlying_symbol = symbol.split('.')[0][:-4]  # 如 'JD2510.XDCE' -> 'JD'
+    
+    # 检查是否在反向逻辑列表中
+    use_reverse_logic = underlying_symbol in g.reverse_direction_symbols
+    
+    if use_reverse_logic:
+        # 反向逻辑:上影线做空,下影线做多(当前逻辑)
+        if hatch_direction == 'up':
+            direction = 'short'
+            logic_description = f"反向逻辑:{underlying_symbol}上影线做空"
+        else:
+            direction = 'long'
+            logic_description = f"反向逻辑:{underlying_symbol}下影线做多"
+    else:
+        # 正向逻辑:上影线做多,下影线做空(新逻辑)
+        if hatch_direction == 'up':
+            direction = 'long'
+            logic_description = f"正向逻辑:{underlying_symbol}上影线做多"
+        else:
+            direction = 'short'
+            logic_description = f"正向逻辑:{underlying_symbol}下影线做空"
+    
+    return direction, logic_description
+
+def get_futures_config(underlying_symbol, config_key=None, default_value=None):
+    """获取期货品种配置信息的辅助函数"""
+    if underlying_symbol not in g.futures_config:
+        if config_key and default_value is not None:
+            return default_value
+        return {}
+    
+    if config_key is None:
+        return g.futures_config[underlying_symbol]
+    
+    return g.futures_config[underlying_symbol].get(config_key, default_value)
+
+def get_margin_rate(underlying_symbol, direction, default_rate=0.10):
+    """获取保证金比例的辅助函数"""
+    return g.futures_config.get(underlying_symbol, {}).get('margin_rate', {}).get(direction, default_rate)
+
+def get_multiplier(underlying_symbol, default_multiplier=10):
+    """获取合约乘数的辅助函数"""
+    return g.futures_config.get(underlying_symbol, {}).get('multiplier', default_multiplier)
+
+def calculate_target_hands(context, security, direction):
+    """计算目标开仓手数(优化版:直接返回手数用于order_target)"""
+    current_price = get_current_data()[security].last_price
+    underlying_symbol = security.split('.')[0][:-4]
+    
+    # 使用保证金比例
+    margin_rate = get_margin_rate(underlying_symbol, direction)
+    multiplier = get_multiplier(underlying_symbol)
+    
+    # 计算单手保证金
+    single_hand_margin = current_price * multiplier * margin_rate
+    
+    # 还要考虑可用资金限制
+    available_cash = context.portfolio.available_cash * g.usage_percentage
+    
+    # 根据单个标的最大持仓保证金限制计算开仓数量
+    max_margin = g.max_margin_per_position
+    
+    if single_hand_margin <= max_margin:
+        # 如果单手保证金不超过最大限制,计算最大可开仓手数
+        max_hands = int(max_margin / single_hand_margin)
+        max_hands_by_cash = int(available_cash / single_hand_margin)
+        
+        # 取两者较小值
+        actual_hands = min(max_hands, max_hands_by_cash)
+        
+        # 确保至少开1手
+        actual_hands = max(1, actual_hands)
+        
+        # 实际保证金
+        actual_margin = single_hand_margin * actual_hands
+        
+        log.info(f"单手保证金: {single_hand_margin:.0f}, 目标开仓手数: {actual_hands}, 预计保证金: {actual_margin:.0f}")
+        
+        # 直接返回手数,用于order_target()
+        return actual_hands
+    else:
+        # 如果单手保证金超过最大限制,默认开仓1手
+        actual_hands = 1
+        actual_margin = single_hand_margin * actual_hands
+        
+        log.info(f"单手保证金: {single_hand_margin:.0f} 超过最大限制: {max_margin}, 默认开仓1手, 预计保证金: {actual_margin:.0f}")
+        
+        # 直接返回手数,用于order_target()
+        return actual_hands
+
+def check_sufficient_capital(context, symbol):
+    """检查资金是否充足"""
+    try:
+        current_price = get_current_data()[symbol].last_price
+        underlying_symbol = symbol.split('.')[0][:-4]
+        
+        margin_rate = get_margin_rate(underlying_symbol, 'long')
+        multiplier = get_multiplier(underlying_symbol)
+        
+        # 计算单手保证金
+        single_hand_margin = current_price * multiplier * margin_rate
+        
+        # 使用实际可用资金(考虑资金使用比例)
+        available_cash = context.portfolio.available_cash * g.usage_percentage
+        
+        # 检查是否有足够资金开仓至少1手
+        return available_cash >= single_hand_margin
+    except:
+        return False
+
+def check_price_and_liquidity(signal):
+    """检查价格合理性和流动性"""
+    try:
+        # 简单的合理性检查
+        symbol = signal['symbol']
+        current_data = get_current_data()[symbol]
+        
+        log.info(f"{symbol} 价格检查 - 当前价: {current_data.last_price:.2f}, 涨停: {current_data.high_limit:.2f}, 跌停: {current_data.low_limit:.2f}, 成交量: {current_data.volume}")
+        
+        # 检查是否处于涨跌停
+        if current_data.last_price <= current_data.low_limit:
+            log.warning(f"{symbol} 价格触及跌停板 ({current_data.last_price:.2f} <= {current_data.low_limit:.2f}),跳过")
+            return False
+            
+        if current_data.last_price >= current_data.high_limit:
+            log.warning(f"{symbol} 价格触及涨停板 ({current_data.last_price:.2f} >= {current_data.high_limit:.2f}),跳过")
+            return False
+        
+        # 检查成交量是否充足
+        if current_data.volume <= 0:
+            log.warning(f"{symbol} 无成交量 ({current_data.volume}),跳过")
+            return False
+        
+        log.info(f"{symbol} 价格和流动性检查通过")
+        return True
+    except Exception as e:
+        log.warning(f"{signal['symbol']} 价格检查时出错: {str(e)}")
+        return False
+
+def check_symbol_prefix_match(symbol, hold_symbols):
+    """检查是否有相似的持仓品种"""
+    symbol_prefix = symbol[:-9]
+    
+    for hold_symbol in hold_symbols:
+        hold_symbol_prefix = hold_symbol[:-9]
+        if symbol_prefix == hold_symbol_prefix:
+            return True
+    return False
+
+def after_market_close(context):
+    """收盘后运行函数"""
+    log.info(str('函数运行时间(after_market_close):'+str(context.current_dt.time())))
+    
+    # 只有当天有交易时才打印统计信息
+    if g.today_trades:
+        print_daily_trading_summary(context)
+        
+        # 清空当日交易记录
+        g.today_trades = []
+    
+    log.info('##############################################################')
+
+def print_daily_trading_summary(context):
+    """打印当日交易汇总"""
+    if not g.today_trades:
+        return
+    
+    log.info("\n=== 当日交易汇总 ===")
+    total_margin = 0
+    
+    for trade in g.today_trades:
+        if trade['order_amount'] > 0:  # 开仓
+            log.info(f"开仓 {trade['underlying_symbol']} {trade['direction']} {trade['order_amount']}手 "
+                  f"价格:{trade['order_price']:.2f} 保证金:{trade['cash_change']:.0f}")
+            total_margin += trade['cash_change']
+        else:  # 平仓
+            log.info(f"平仓 {trade['underlying_symbol']} {trade['direction']} {abs(trade['order_amount'])}手 "
+                  f"价格:{trade['order_price']:.2f}")
+    
+    log.info(f"当日保证金占用: {total_margin:.0f}")
+    log.info("==================\n")
+
+########################## 自动移仓换月函数 #################################
+def position_auto_switch(context, pindex=0, switch_func=None, callback=None):
+    """
+    期货自动移仓换月。默认使用市价单进行开平仓。
+    """
+    import re
+    subportfolio = context.subportfolios[pindex]
+    symbols = set(subportfolio.long_positions.keys()) | set(subportfolio.short_positions.keys())
+    switch_result = []
+    for symbol in symbols:
+        match = re.match(r"(?P<underlying_symbol>[A-Z]{1,})", symbol)
+        if not match:
+            raise ValueError("未知期货标的: {}".format(symbol))
+        else:
+            dominant = get_dominant_future(match.groupdict()["underlying_symbol"])
+            cur = get_current_data()
+            symbol_last_price = cur[symbol].last_price
+            dominant_last_price = cur[dominant].last_price
+            log.info(f'当前持仓合约: {symbol}, 当前主力合约: {dominant}')
+            
+            if dominant > symbol:
+                for positions_ in (subportfolio.long_positions, subportfolio.short_positions):
+                    if symbol not in positions_.keys():
+                        continue
+                    else :
+                        p = positions_[symbol]
+
+                    if switch_func is not None:
+                        switch_func(context, pindex, p, dominant)
+                    else:
+                        amount = p.total_amount
+                        # 跌停不能开空和平多,涨停不能开多和平空。
+                        if p.side == "long":
+                            symbol_low_limit = cur[symbol].low_limit
+                            dominant_high_limit = cur[dominant].high_limit
+                            if symbol_last_price <= symbol_low_limit:
+                                log.warning("标的{}跌停,无法平仓。移仓换月取消。".format(symbol))
+                                continue
+                            elif dominant_last_price >= dominant_high_limit:
+                                log.warning("标的{}涨停,无法开仓。移仓换月取消。".format(dominant))
+                                continue
+                            else:
+                                log.info("进行移仓换月: ({0},long) -> ({1},long)".format(symbol, dominant))
+                                order_old = order_target(symbol, 0, side='long')
+                                if order_old != None and order_old.filled > 0:
+                                    order_new = order_target(dominant, amount, side='long')
+                                    if order_new != None and order_new.filled > 0:
+                                        switch_result.append({"before": symbol, "after": dominant, "side": "long"})
+                                        # 换月中的买卖都成功了,则增加新的记录去掉旧的记录
+                                        g.trade_history[dominant] = g.trade_history[symbol]
+                                        del g.trade_history[symbol]
+                                    else:
+                                        log.warning("标的{}交易失败,无法开仓。移仓换月失败。".format(dominant))
+                        if p.side == "short":
+                            symbol_high_limit = cur[symbol].high_limit
+                            dominant_low_limit = cur[dominant].low_limit
+                            if symbol_last_price >= symbol_high_limit:
+                                log.warning("标的{}涨停,无法平仓。移仓换月取消。".format(symbol))
+                                continue
+                            elif dominant_last_price <= dominant_low_limit:
+                                log.warning("标的{}跌停,无法开仓。移仓换月取消。".format(dominant))
+                                continue
+                            else:
+                                log.info("进行移仓换月: ({0},short) -> ({1},short)".format(symbol, dominant))
+                                order_old = order_target(symbol, 0, side='short')
+                                if order_old != None and order_old.filled > 0:
+                                    order_new = order_target(dominant, amount, side='short')
+                                    if order_new != None and order_new.filled > 0:
+                                        switch_result.append({"before": symbol, "after": dominant, "side": "short"})
+                                        # 换月中的买卖都成功了,则增加新的记录去掉旧的记录
+                                        g.trade_history[dominant] = g.trade_history[symbol]
+                                        del g.trade_history[symbol]
+                                    else:
+                                        log.warning("标的{}交易失败,无法开仓。移仓换月失败。".format(dominant))
+                        if callback:
+                            callback(context, pindex, p, dominant)
+    return switch_result

+ 1500 - 0
Lib/future/CandlestickHatchReverseStrategy_v002.py

@@ -0,0 +1,1500 @@
+# 导入函数库
+from jqdata import *
+from jqdata import finance
+import pandas as pd
+import numpy as np
+from datetime import date, datetime, timedelta
+import re
+
+# 烛台影线形态反向交易策略 v002
+# 基于烛台形态检测,采用两阶段仓位管理的交易策略
+# 第一阶段:同时开立多空头寸(市场中性)
+# 第二阶段:亏损方平仓,保留盈利方
+# 第三阶段:对剩余头寸应用标准止盈止损逻辑
+
+# 设置以便完整打印 DataFrame
+pd.set_option('display.max_rows', None)
+pd.set_option('display.max_columns', None)
+pd.set_option('display.width', None)
+pd.set_option('display.max_colwidth', 20)
+
+## 初始化函数,设定基准等等
+def initialize(context):
+    # 设定沪深300作为基准
+    set_benchmark('000300.XSHG')
+    # 开启动态复权模式(真实价格)
+    set_option('use_real_price', True)
+    # 输出内容到日志
+    log.info('=' * 60)
+    log.info('烛台影线形态交易策略 v002 初始化开始')
+    log.info('策略类型: 两阶段仓位管理策略')
+    log.info('=' * 60)
+
+    ### 期货相关设定 ###
+    # 设定账户为金融账户
+    set_subportfolios([SubPortfolioConfig(cash=context.portfolio.starting_cash, type='index_futures')])
+    # 期货类每笔交易时的手续费是: 买入时万分之0.23,卖出时万分之0.23,平今仓为万分之23
+    set_order_cost(OrderCost(open_commission=0.000023, close_commission=0.000023, close_today_commission=0.0023), type='index_futures')
+    
+    # 设置期货交易的滑点
+    set_slippage(StepRelatedSlippage(2))
+    
+    # 初始化全局变量
+    g.usage_percentage = 0.8  # 最大资金使用比例
+    g.max_margin_per_position = 20000  # 单个标的最大持仓保证金(元)
+    
+    # 烛台形态检测参数(与研究文件保持一致)
+    g.hatch_to_body_ratio = 1.2  # 影线与实体长度比率阈值
+    g.opposite_hatch_ratio = 0.5  # 相反方向影线与实体长度比率阈值
+    g.historical_days = 365  # 历史数据天数,用于计算平均实体长度阈值(与研究文件保持一致)
+    
+    # 全局阈值缓存(每月更新一次)
+    g.monthly_body_threshold_cache = {}  # 存储每月计算的实体长度阈值
+    g.last_threshold_update_month = None  # 记录上次更新阈值的月份
+    
+    # 止损止盈策略参数
+    g.fixed_stop_loss_rate = 0.01  # 固定止损比率(1%)
+    g.trailing_stop_thresholds = [0.05, 0.10]  # 动态追踪止损触发条件(5%, 10%)
+    g.trailing_stop_rates = [0.01, 0.02, 0.03]  # 动态追踪止损比率(1%, 2%, 3%)
+    
+    # 两阶段仓位管理参数
+    g.phase2_loss_threshold = 0.015  # 第二阶段亏损触发阈值(3%)- 当任一方向亏损达到此比例时平仓该方向
+    
+    # 输出两阶段策略参数
+    log.info("两阶段仓位管理参数:")
+    log.info(f"  第一阶段: 同时开立多空头寸(市场中性)")
+    log.info(f"  第二阶段触发条件: 亏损方达到 {g.phase2_loss_threshold:.1%} 时平仓,保留盈利方")
+    log.info(f"  第三阶段: 对剩余头寸应用标准止盈止损逻辑")
+    log.info(f"    - 固定止损: {g.fixed_stop_loss_rate:.1%}")
+    log.info(f"    - 动态追踪止损: {g.trailing_stop_rates}")
+    
+    # 开仓方向配置参数(v002版本已移除方向判断逻辑,保留变量以兼容其他代码)
+    g.reverse_direction_symbols = []  # v002不再使用方向判断,同时开立多空头寸
+    
+    # 期货品种完整配置字典
+    g.futures_config = {
+        # 贵金属
+        'AU': {'has_night_session': True, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 1000},
+        'AG': {'has_night_session': True, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 15},
+        
+        # 有色金属
+        'CU': {'has_night_session': True, 'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5},
+        'AL': {'has_night_session': True, 'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5},
+        'ZN': {'has_night_session': True, 'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5},
+        'PB': {'has_night_session': True, 'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5},
+        'NI': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1},
+        'SN': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1},
+        'SS': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5},
+        
+        # 黑色系
+        'RB': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10},
+        'HC': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10},
+        'I': {'has_night_session': True, 'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 100},
+        'JM': {'has_night_session': True, 'margin_rate': {'long': 0.22, 'short': 0.22}, 'multiplier': 100},
+        'J': {'has_night_session': True, 'margin_rate': {'long': 0.22, 'short': 0.22}, 'multiplier': 60},
+        
+        # 能源化工
+        'SP': {'has_night_session': True, 'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 10},
+        'FU': {'has_night_session': True, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 10},
+        'BU': {'has_night_session': True, 'margin_rate': {'long': 0.04, 'short': 0.04}, 'multiplier': 10},
+        'RU': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        'BR': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5},
+        'SC': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1000},
+        'NR': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10},
+        'LU': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 10},
+        'LC': {'has_night_session': False, 'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 1},
+        
+        # 化工
+        'FG': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20},
+        'TA': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 5},
+        'MA': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        'SA': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20},
+        'L': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5},
+        'V': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5},
+        'EG': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        'PP': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5},
+        'EB': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 5},
+        'PG': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20},
+        
+        # 农产品
+        'RM': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        'OI': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        'CF': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 5},
+        'SR': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        'PF': {'has_night_session': True, 'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 5},
+        'C': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10},
+        'CS': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10},
+        'CY': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5},
+        'A': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10},
+        'B': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        'M': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10},
+        'Y': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        'P': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10},
+        
+        # 无夜盘品种
+        'IF': {'has_night_session': False, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 300},
+        'IH': {'has_night_session': False, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 300},
+        'IC': {'has_night_session': False, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 200},
+        'IM': {'has_night_session': False, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 200},
+        'AP': {'has_night_session': False, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 10},
+        'CJ': {'has_night_session': False, 'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5},
+        'PK': {'has_night_session': False, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 5},
+        'JD': {'has_night_session': False, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10},
+        'LH': {'has_night_session': False, 'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 16}
+    }
+    
+    # 策略品种选择策略配置
+    # 方案1:全品种策略 - 考虑所有配置的期货品种
+    # g.strategy_focus_symbols = []  # 空列表表示考虑所有品种
+    
+    # 方案2:精选品种策略 - 只交易流动性较好的特定品种(如需使用请取消下行注释)
+    g.strategy_focus_symbols = ['RM', 'CJ', 'CY', 'JD', 'L', 'LC', 'SF', 'SI']
+    
+    log.info(f"品种选择策略: {'全品种策略(覆盖所有配置品种)' if not g.strategy_focus_symbols else '精选品种策略(' + str(len(g.strategy_focus_symbols)) + '个品种)'}")
+    
+    # 交易记录和数据存储
+    g.trade_history = {}
+    g.candlestick_signals = {}  # 存储烛台形态信号
+    g.daily_data_cache = {}  # 存储历史日线数据缓存
+    g.minute_data_cache = {}  # 存储今日分钟数据缓存
+    g.body_threshold_cache = {}  # 存储实体长度阈值缓存
+    
+    # 保证金比例管理
+    g.margin_rate_history = {}  # 保证金比例变化历史记录
+    g.today_trades = []  # 当日交易记录
+    
+    # 定时任务设置
+    # 每月第1个交易日更新阈值
+    run_monthly(monthly_update_thresholds, 1, 'before_open', reference_security='IF1808.CCFX')
+    
+    # 夜盘开始 - 仅止损止盈检查
+    run_daily(main_trading_stop_only, time='21:05:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='21:35:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='22:05:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='22:35:00', reference_security='IF1808.CCFX')
+    
+    # 日盘开始 - 仅止损止盈检查
+    run_daily(main_trading_stop_only, time='09:05:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='09:35:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='10:05:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='10:35:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='11:05:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='11:25:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='13:35:00', reference_security='IF1808.CCFX')
+    run_daily(main_trading_stop_only, time='14:05:00', reference_security='IF1808.CCFX')
+    
+    # 收盘前 - 14:55进行完整交易检查,14:35仅止损止盈
+    run_daily(main_trading_stop_only, time='14:35:00', reference_security='IF1808.CCFX')
+    
+    # 完整交易检查 - 仅在14:55执行(任务1, 2, 3, 4, 5, 6, 7)
+    run_daily(main_trading_complete, time='14:55:00', reference_security='IF1808.CCFX')
+    
+    # 收盘后
+    run_daily(after_market_close, time='15:30:00', reference_security='IF1808.CCFX')
+
+############################ 主程序执行函数 ###################################
+
+def monthly_update_thresholds(context):
+    """每月更新实体长度阈值"""
+    log.info("=" * 60)
+    log.info("每月阈值更新开始")
+    log.info("=" * 60)
+    
+    current_month = context.current_dt.strftime('%Y-%m')
+    
+    # 检查是否需要更新
+    if g.last_threshold_update_month == current_month:
+        log.info(f"本月阈值已更新,跳过更新")
+        return
+    
+    # 获取所有需要计算阈值的品种
+    focus_symbols = g.strategy_focus_symbols if g.strategy_focus_symbols else list(g.futures_config.keys())
+    
+    updated_count = 0
+    for symbol in focus_symbols:
+        try:
+            # 获取主力合约
+            dominant_future = get_dominant_future(symbol)
+            if not dominant_future:
+                continue
+            
+            # 获取365天历史数据
+            data = attribute_history(dominant_future, g.historical_days, '1d', 
+                                   ['open', 'close', 'high', 'low', 'volume'], 
+                                   df=True)
+            
+            if data is not None and len(data) > 30:  # 确保有足够数据
+                # 计算实体长度阈值
+                data['body_length'] = abs(data['close'] - data['open'])
+                body_threshold = data['body_length'].mean()
+                
+                # 存储到全局阈值缓存
+                g.monthly_body_threshold_cache[dominant_future] = body_threshold
+                updated_count += 1
+                
+                log.info(f"更新 {symbol}({dominant_future}) 实体长度阈值: {body_threshold:.4f}")
+            
+        except Exception as e:
+            log.warning(f"更新{symbol}阈值时出错: {str(e)}")
+            continue
+    
+    # 更新最后更新月份
+    g.last_threshold_update_month = current_month
+    
+    log.info(f"阈值更新完成,共更新 {updated_count} 个品种")
+    log.info("=" * 60)
+
+def main_trading_stop_only(context):
+    """仅进行止损止盈检查的交易时段"""
+    # 只进行止损止盈检查
+    task_7_check_stop_loss_profit(context)
+
+def main_trading_complete(context):
+    """14:55 完整交易检查 - 执行任务1, 2, 3, 4, 5, 6, 7"""
+    log.info("=" * 60)
+    log.info("14:55 烛台影线形态反向交易策略 - 完整交易检查")
+    log.info("=" * 60)
+    
+    # 任务1: 获取所有可交易品种
+    task_1_get_tradable_futures(context)
+    
+    # 任务2: 获取历史数据并处理阈值(包括单独计算缺失阈值)
+    task_2_load_historical_data_and_thresholds(context)
+    
+    # 任务3: 获取今日分钟数据
+    task_3_update_realtime_data(context)
+    
+    # 任务4: 检测烛台形态
+    task_4_detect_candlestick_patterns(context)
+    
+    # 任务5: 检查开仓条件(简化版)
+    filtered_signals = task_5_check_opening_conditions(context)
+
+    # 任务6: 执行交易
+    if filtered_signals:
+        task_6_execute_trades(context, filtered_signals)
+    
+    # 任务7: 检查止损止盈
+    task_7_check_stop_loss_profit(context)
+    
+    # 任务8: 检查换月移仓
+    task_8_check_position_switch(context)
+
+############################ 核心任务函数 ###################################
+
+def task_1_get_tradable_futures(context):
+    """任务1: 获取所有可交易品种(分白天和晚上)"""
+    log.info("执行任务1: 获取可交易品种")
+    
+    current_time = str(context.current_dt.time())[:2]
+    
+    # 从策略关注列表中筛选可交易品种
+    focus_symbols = g.strategy_focus_symbols if g.strategy_focus_symbols else list(g.futures_config.keys())
+    
+    potential_icon_list = []
+    night_session_symbols = []
+    day_only_symbols = []
+    
+    # 预先分类所有品种
+    for symbol in focus_symbols:
+        if get_futures_config(symbol, 'has_night_session', False):
+            night_session_symbols.append(symbol)
+        else:
+            day_only_symbols.append(symbol)
+    
+    if current_time in ('21', '22'):
+        # 夜盘时间:只考虑有夜盘的品种
+        potential_icon_list = night_session_symbols[:]
+        log.info(f"夜盘时间,有夜盘交易的品种: {len(night_session_symbols)}个")
+        log.info(f"夜盘可交易品种: {potential_icon_list[:10]}{'...' if len(potential_icon_list) > 10 else ''}")  # 显示前10个,避免日志过长
+    else:
+        # 日盘时间:所有品种都可以交易(包括有夜盘的和只有日盘的)
+        potential_icon_list = focus_symbols[:]
+        log.info(f"日盘时间,全部品种: {len(focus_symbols)}个(含夜盘品种: {len(night_session_symbols)}个,日盘专属品种: {len(day_only_symbols)}个)")
+        if day_only_symbols:
+            log.info(f"日盘专属品种: {day_only_symbols}")
+        log.info(f"日盘可交易品种: {potential_icon_list[:10]}{'...' if len(potential_icon_list) > 10 else ''}")  # 显示前10个,避免日志过长
+    
+    potential_future_list = []
+    for symbol in potential_icon_list:
+        dominant_future = get_dominant_future(symbol)
+        if dominant_future:
+            potential_future_list.append(dominant_future)
+    
+    # 过滤掉已有持仓的品种
+    existing_positions = set(g.trade_history.keys())
+    potential_future_list = [f for f in potential_future_list if not check_symbol_prefix_match(f, existing_positions)]
+    
+    # 存储到全局变量
+    g.tradable_futures = potential_future_list
+    log.info(f"最终可交易期货品种数量: {len(potential_future_list)}")
+    
+    return potential_future_list
+
+def task_2_load_historical_data_and_thresholds(context):
+    """任务2: 获取历史数据并处理阈值(包括单独计算缺失阈值)"""
+    log.info("执行任务2: 加载历史数据并处理阈值")
+    
+    if not hasattr(g, 'tradable_futures'):
+        return
+    
+    for future_code in g.tradable_futures:
+        try:
+            # 获取30天历史数据(用于分钟数据合并)
+            data = attribute_history(future_code, 30, '1d', 
+                                   ['open', 'close', 'high', 'low', 'volume'], 
+                                   df=True)
+            
+            if data is not None and len(data) > 0:
+                # 排除今天的数据
+                today = context.current_dt.date()
+                data = data[data.index.date < today]
+                g.daily_data_cache[future_code] = data
+                
+                # 处理阈值:优先使用月度阈值,缺失时单独计算
+                if future_code in g.monthly_body_threshold_cache:
+                    # 使用月度阈值缓存
+                    body_threshold = g.monthly_body_threshold_cache[future_code]
+                    g.body_threshold_cache[future_code] = body_threshold
+                    # log.info(f"已缓存 {future_code} 历史数据 {len(data)} 条,使用月度阈值: {body_threshold:.4f}")
+                else:
+                    # 单独计算缺失的阈值
+                    log.info(f"{future_code} 未找到月度阈值,开始单独计算")
+                    body_threshold = calculate_individual_threshold(context, future_code)
+                    if body_threshold is not None:
+                        g.body_threshold_cache[future_code] = body_threshold
+                        log.info(f"已为 {future_code} 单独计算阈值: {body_threshold:.4f}")
+                    else:
+                        log.warning(f"{future_code} 无法计算阈值,跳过")
+            
+        except Exception as e:
+            log.warning(f"加载{future_code}历史数据时出错: {str(e)}")
+            continue
+    
+    log.info(f"历史数据缓存完成,共缓存 {len(g.daily_data_cache)} 个品种")
+
+def calculate_individual_threshold(context, future_code):
+    """为单个工具计算实体长度阈值并自动缓存到月度阈值中"""
+    try:
+        log.info(f"为 {future_code} 单独计算365天历史阈值")
+        
+        # 获取365天历史数据
+        data = attribute_history(future_code, g.historical_days, '1d', 
+                               ['open', 'close', 'high', 'low', 'volume'], 
+                               df=True)
+        
+        if data is not None and len(data) > 30:  # 确保有足够数据
+            # 排除今天的数据
+            today = context.current_dt.date()
+            data = data[data.index.date < today]
+            
+            # 计算实体长度阈值
+            data['body_length'] = abs(data['close'] - data['open'])
+            body_threshold = data['body_length'].mean()
+            
+            # 🚀 优化1:将计算结果自动添加到月度阈值缓存中
+            g.monthly_body_threshold_cache[future_code] = body_threshold
+            log.info(f"✅ {future_code} 单独计算完成,阈值: {body_threshold:.4f},已添加到月度缓存")
+            log.info(f"当前月度缓存包含 {len(g.monthly_body_threshold_cache)} 个品种阈值")
+            
+            return body_threshold
+        else:
+            log.warning(f"{future_code} 历史数据不足,无法计算阈值")
+            return None
+            
+    except Exception as e:
+        log.warning(f"为 {future_code} 单独计算阈值时出错: {str(e)}")
+        return None
+
+def task_3_update_realtime_data(context):
+    """任务3: 获取今日分钟数据"""
+    # log.info("执行任务3: 更新实时数据")
+    
+    # 收集需要更新数据的品种
+    update_symbols = set()
+    
+    # 添加可交易品种
+    if hasattr(g, 'tradable_futures') and g.tradable_futures:
+        update_symbols.update(g.tradable_futures)
+    
+    # 添加持仓品种(用于止损止盈)- 去掉_long/_short后缀
+    if hasattr(g, 'trade_history') and g.trade_history:
+        for key in g.trade_history.keys():
+            # 去掉可能的_long或_short后缀
+            clean_symbol = key.replace('_long', '').replace('_short', '')
+            update_symbols.add(clean_symbol)
+    
+    if not update_symbols:
+        return
+    
+    for future_code in update_symbols:
+        try:
+            # 获取今日分钟数据
+            minute_data = get_today_minute_data(context, future_code)
+            if minute_data is None:
+                continue
+            
+            # 获取历史数据
+            historical_data = g.daily_data_cache.get(future_code)
+            if historical_data is None:
+                # 为持仓品种临时获取历史数据
+                try:
+                    data = attribute_history(future_code, g.historical_days, '1d', 
+                                           ['open', 'close', 'high', 'low', 'volume'], 
+                                           df=True)
+                    if data is not None and len(data) > 0:
+                        today = context.current_dt.date()
+                        data = data[data.index.date < today]
+                        
+                        # 计算实体长度阈值
+                        data['body_length'] = abs(data['close'] - data['open'])
+                        body_threshold = data['body_length'].mean()
+                        
+                        g.daily_data_cache[future_code] = data
+                        g.body_threshold_cache[future_code] = body_threshold
+                        historical_data = data
+                        log.info(f"为持仓品种 {future_code} 临时获取历史数据 {len(data)} 条")
+                except Exception as e:
+                    log.warning(f"获取{future_code}历史数据失败: {str(e)}")
+                    continue
+            
+            if historical_data is None:
+                continue
+            
+            # 将今日数据合并为日线数据
+            today_data = aggregate_minute_to_daily(minute_data)
+            if today_data is not None:
+                # 合并历史数据和今日数据
+                combined_data = pd.concat([historical_data, today_data], sort=False)
+                g.minute_data_cache[future_code] = combined_data
+            
+        except Exception as e:
+            log.warning(f"更新{future_code}实时数据时出错: {str(e)}")
+            continue
+
+def task_4_detect_candlestick_patterns(context):
+    """任务4: 检测烛台形态"""
+    # log.info("执行任务4: 检测烛台形态")
+    
+    candlestick_signals = []
+    
+    # 获取已持仓的品种列表
+    existing_positions = set(g.trade_history.keys())
+    
+    for future_code, data in g.minute_data_cache.items():
+        try:
+            # 检查是否已有相似持仓,如果有则跳过分析
+            if check_symbol_prefix_match(future_code, existing_positions):
+                continue
+            
+            # 获取实体长度阈值
+            body_threshold = g.body_threshold_cache.get(future_code)
+            if body_threshold is None:
+                continue
+            
+            # 检查最新的烛台形态
+            latest_pattern = check_latest_candlestick_pattern(data, future_code, body_threshold)
+            if latest_pattern:
+                candlestick_signals.append(latest_pattern)
+                log.info(f"{future_code} 发现烛台形态: {latest_pattern['hatch_direction']}影线")
+                
+        except Exception as e:
+            log.warning(f"检测{future_code}烛台形态时出错: {str(e)}")
+            continue
+    
+    # 存储到全局变量
+    g.candlestick_signals = candlestick_signals
+    if len(candlestick_signals) > 0:
+        log.info(f"烛台形态检测完成,发现信号 {len(candlestick_signals)} 个")
+    
+    return candlestick_signals
+
+def task_5_check_opening_conditions(context):
+    """任务5: 检查开仓条件(简化版)"""
+    log.info("执行任务5: 检查开仓条件(简化版)")
+    
+    if not hasattr(g, 'candlestick_signals') or not g.candlestick_signals:
+        log.info("没有检测到烛台信号")
+        return []
+    
+    log.info(f"检测到 {len(g.candlestick_signals)} 个烛台信号,开始筛选")
+    
+    filtered_signals = []
+    
+    for i, signal in enumerate(g.candlestick_signals):
+        log.info(f"处理信号 {i+1}: {signal['symbol']} {signal['hatch_direction']}影线")
+        
+        # 仅检查是否已有相似持仓(避免重复开仓同一品种)
+        if check_symbol_prefix_match(signal['symbol'], set(g.trade_history.keys())):
+            log.info(f"{signal['symbol']} 已有相似持仓,跳过")
+            continue
+        
+        # 简化版:只要形态满足条件就接受,无额外验证
+        filtered_signals.append(signal)
+        log.info(f"{signal['symbol']} 烛台形态满足条件,接受交易信号")
+    
+    log.info(f"开仓条件检查完成,满足条件 {len(filtered_signals)} 个")
+    return filtered_signals
+
+def task_6_execute_trades(context, filtered_signals):
+    """任务6: 执行交易(两阶段策略版 - 同时开立多空头寸)"""
+    log.info("执行任务6: 执行交易(两阶段策略版)")
+    
+    if not filtered_signals:
+        log.info("没有满足条件的交易信号")
+        return
+    
+    log.info(f"开始执行 {len(filtered_signals)} 个交易信号")
+    
+    for i, signal in enumerate(filtered_signals):
+        symbol = signal['symbol']
+        log.info(f"执行交易 {i+1}: {symbol} {signal['hatch_direction']}影线形态")
+        log.info(f"{symbol} 第一阶段: 同时开立多空头寸(市场中性)")
+        
+        try:
+            # 计算目标手数(使用long方向计算,多空手数相同)
+            target_hands = calculate_target_hands(context, symbol, 'long')
+            log.info(f"计算目标手数: {symbol} 多空各 {target_hands} 手")
+            
+            if target_hands > 0:
+                # 同时开立多头和空头头寸
+                log.info(f"开始同时下单: {symbol} 多头 {target_hands}手 + 空头 {target_hands}手")
+                success = open_dual_position(context, symbol, target_hands, signal)
+                
+                if success:
+                    log.info(f"✅ 成功开立双向头寸 {symbol}, 多空各 {target_hands} 手(第一阶段)")
+                else:
+                    log.warning(f"❌ 双向开仓失败 {symbol}")
+            else:
+                log.warning(f"{symbol} 计算目标手数为0,跳过")
+        except Exception as e:
+            log.warning(f"{symbol} 交易执行出错: {str(e)}")
+            continue
+    
+    log.info("交易执行完成")
+
+def task_8_check_position_switch(context):
+    """任务8: 检查换月移仓"""
+    # log.info("执行任务8: 检查换月移仓")
+    
+    switch_result = position_auto_switch(context)
+    if switch_result:
+        log.info(f"执行了 {len(switch_result)} 次移仓换月")
+        for result in switch_result:
+            log.info(f"移仓: {result['before']} -> {result['after']}")
+
+def task_7_check_stop_loss_profit(context):
+    """任务7: 检查止损止盈(带交易时间验证)"""
+    # log.info("执行任务7: 检查止损止盈")
+    
+    # 获取当前时间
+    current_time = context.current_dt.strftime('%H:%M')
+    current_hour = int(current_time[:2])
+    
+    # 判断是否为夜盘时间(21:00-23:00 和 00:00-02:30)
+    is_night_session = (current_hour >= 21) or (current_hour <= 2)
+    
+    # 遍历所有持仓进行止损止盈检查
+    subportfolio = context.subportfolios[0]
+    long_positions = list(subportfolio.long_positions.values())
+    short_positions = list(subportfolio.short_positions.values())
+    
+    closed_count = 0
+    skipped_count = 0
+    
+    for position in long_positions + short_positions:
+        security = position.security
+        underlying_symbol = security.split('.')[0][:-4]
+        
+        # 检查交易时间适配性
+        has_night_session = get_futures_config(underlying_symbol, 'has_night_session', False)
+        
+        # 如果是夜盘时间,但品种不支持夜盘交易,则跳过
+        if is_night_session and not has_night_session:
+            skipped_count += 1
+            # log.info(f"跳过夜盘时间的日间品种: {underlying_symbol} ({current_time})")
+            continue
+        
+        # 执行止损止盈检查
+        if check_stop_loss_profit(context, position):
+            closed_count += 1
+    
+    if closed_count > 0:
+        log.info(f"执行了 {closed_count} 次止损止盈")
+    
+    if skipped_count > 0:
+        log.info(f"夜盘时间跳过 {skipped_count} 个日间品种的止损止盈检查")
+
+############################ 数据处理辅助函数 ###################################
+
+def check_has_night_session(underlying_symbol):
+    """检查品种是否有夜盘"""
+    return get_futures_config(underlying_symbol, 'has_night_session', False)
+
+def get_today_minute_data(context, future_code):
+    """获取今日分钟数据"""
+    try:
+        # 判断该品种是否有夜盘
+        underlying_symbol = future_code.split('.')[0][:-4]
+        has_night_session = check_has_night_session(underlying_symbol)
+        
+        end_time = context.current_dt
+        
+        # 获取足够的历史分钟数据
+        minute_data = attribute_history(future_code, 
+                                      count=800,  # 获取足够多的数据
+                                      unit='1m',
+                                      fields=['open', 'close', 'high', 'low', 'volume'],
+                                      df=True)
+        
+        if minute_data is None or len(minute_data) == 0:
+            return None
+        
+        # 提取所有日期(年月日维度)
+        minute_data['date'] = minute_data.index.date
+        unique_dates = sorted(minute_data['date'].unique())
+        
+        if has_night_session:
+            # 有夜盘的品种:需要找到前一交易日的21:00作为今日开盘起点
+            today_date = end_time.date()
+            
+            # 找到今天之前的最后一个交易日
+            previous_trading_dates = [d for d in unique_dates if d < today_date]
+            if not previous_trading_dates:
+                return minute_data
+            
+            previous_trading_date = max(previous_trading_dates)
+            
+            # 找到前一交易日21:00:00的数据作为开盘起点
+            previous_day_data = minute_data[minute_data['date'] == previous_trading_date]
+            night_21_data = previous_day_data[previous_day_data.index.hour == 21]
+            
+            if len(night_21_data) > 0:
+                # 从前一交易日21:00开始的所有数据
+                start_time = night_21_data.index[0]  # 21:00:00的时间点
+                filtered_data = minute_data[minute_data.index >= start_time]
+                return filtered_data.drop(columns=['date'])
+            else:
+                # 备选方案:使用今天9:00开始的数据
+                today_data = minute_data[minute_data['date'] == today_date]
+                day_9_data = today_data[today_data.index.hour >= 9]
+                if len(day_9_data) > 0:
+                    return day_9_data.drop(columns=['date'])
+                else:
+                    return minute_data.drop(columns=['date'])
+        else:
+            # 没有夜盘的品种:从今天9:00:00开始
+            today_date = end_time.date()
+            today_data = minute_data[minute_data['date'] == today_date]
+            
+            # 找到今天9:00:00开始的数据
+            day_9_data = today_data[today_data.index.hour >= 9]
+            
+            if len(day_9_data) > 0:
+                return day_9_data.drop(columns=['date'])
+            else:
+                return today_data.drop(columns=['date']) if len(today_data) > 0 else minute_data.drop(columns=['date'])
+        
+    except Exception as e:
+        log.warning(f"获取{future_code}今日分钟数据时出错: {str(e)}")
+    
+    return None
+
+def aggregate_minute_to_daily(minute_data):
+    """将分钟数据聚合为日数据"""
+    try:
+        if minute_data is None or len(minute_data) == 0:
+            return None
+        
+        # 获取今日日期(使用最后一条数据的日期作为今日日期)
+        today_date = minute_data.index[-1].date()
+        
+        # 聚合为日数据
+        daily_data = pd.DataFrame({
+            'open': [minute_data['open'].iloc[0]],  # 今日交易开始时的开盘价
+            'close': [minute_data['close'].iloc[-1]],  # 当前收盘价,实时更新
+            'high': [minute_data['high'].max()],
+            'low': [minute_data['low'].min()],
+            'volume': [minute_data['volume'].sum()]
+        }, index=[pd.Timestamp(today_date)])
+        
+        return daily_data
+        
+    except Exception as e:
+        log.warning(f"聚合分钟数据时出错: {str(e)}")
+        return None
+
+def check_latest_candlestick_pattern(data, future_code, body_threshold):
+    """检查最新的烛台形态"""
+    if len(data) < 1:
+        return None
+        
+    # 获取最新一天的数据
+    today = data.iloc[-1]
+    
+    # 检查烛台形态
+    pattern_result = check_candlestick_pattern_single_day(today, body_threshold)
+    if not pattern_result:
+        return None
+    
+    return {
+        'symbol': future_code,
+        'date': today.name,
+        'hatch_direction': pattern_result['hatch_direction'],
+        'open': today['open'],
+        'close': today['close'],
+        'high': today['high'],
+        'low': today['low'],
+        'body_length': pattern_result['body_length'],
+        'hatch_length': pattern_result['hatch_length']
+    }
+
+def check_candlestick_pattern_single_day(row, body_threshold):
+    """检查单日烛台形态是否符合影线条件"""
+    open_price = row['open']
+    close_price = row['close']
+    high_price = row['high']
+    low_price = row['low']
+    
+    # 计算实体长度
+    body_length = abs(close_price - open_price)
+    
+    # 检查实体长度是否满足阈值
+    if body_length < body_threshold:
+        return None
+    
+    # 计算影线长度
+    upper_hatch = high_price - max(open_price, close_price)
+    lower_hatch = min(open_price, close_price) - low_price
+    
+    # 检查影线条件
+    hatch_direction = None
+    hatch_length = 0
+    
+    # 检查上影线条件:上影线长度符合要求 且 下影线长度小于实体长度的一半
+    if (upper_hatch >= g.hatch_to_body_ratio * body_length and 
+        lower_hatch < g.opposite_hatch_ratio * body_length):
+        hatch_direction = 'up'
+        hatch_length = upper_hatch
+    # 检查下影线条件:下影线长度符合要求 且 上影线长度小于实体长度的一半
+    elif (lower_hatch >= g.hatch_to_body_ratio * body_length and 
+          upper_hatch < g.opposite_hatch_ratio * body_length):
+        hatch_direction = 'down'
+        hatch_length = lower_hatch
+    
+    # 如果满足影线条件,返回形态信息
+    if hatch_direction is not None:
+        return {
+            'hatch_direction': hatch_direction,
+            'body_length': body_length,
+            'hatch_length': hatch_length
+        }
+    
+    return None
+
+############################ 动态保证金率调整函数 ###################################
+
+def detect_and_update_margin_rates(context, symbol, actual_hands, cash_change, direction):
+    """
+    检测并更新保证金率配置
+    
+    参数:
+    symbol: 期货合约代码
+    actual_hands: 实际成交手数
+    cash_change: 实际资金变化(保证金使用)
+    direction: 交易方向
+    """
+    try:
+        underlying_symbol = symbol.split('.')[0][:-4]
+        
+        if underlying_symbol not in g.futures_config:
+            return
+        
+        current_price = get_current_data()[symbol].last_price
+        multiplier = get_multiplier(underlying_symbol)
+        
+        # 计算实际保证金率
+        contract_value = current_price * multiplier * actual_hands
+        actual_margin_rate = cash_change / contract_value if contract_value > 0 else 0
+        
+        # 获取配置中的保证金率
+        config_margin_rate = g.futures_config[underlying_symbol]['margin_rate'][direction]
+        
+        # 计算差异百分比
+        rate_diff_percentage = abs(actual_margin_rate - config_margin_rate) / config_margin_rate * 100 if config_margin_rate > 0 else 100
+        
+        # 如果差异超过10%,更新配置
+        if rate_diff_percentage > 10:
+            log.info(f"🔍 检测到{underlying_symbol}保证金率差异: 配置={config_margin_rate:.3f}, 实际={actual_margin_rate:.3f}, 差异={rate_diff_percentage:.1f}%")
+            
+            # 更新配置
+            g.futures_config[underlying_symbol]['margin_rate'][direction] = actual_margin_rate
+            
+            # 如果是双向更新(多空保证金率通常相同)
+            if g.futures_config[underlying_symbol]['margin_rate']['long'] == g.futures_config[underlying_symbol]['margin_rate']['short']:
+                g.futures_config[underlying_symbol]['margin_rate']['long'] = actual_margin_rate
+                g.futures_config[underlying_symbol]['margin_rate']['short'] = actual_margin_rate
+                log.info(f"✅ 已更新{underlying_symbol}保证金率为 {actual_margin_rate:.3f} (双向)")
+            else:
+                log.info(f"✅ 已更新{underlying_symbol} {direction}保证金率为 {actual_margin_rate:.3f}")
+                
+    except Exception as e:
+        log.warning(f"检测保证金率时出错 {symbol}: {str(e)}")
+
+############################ 机会性仓位调整函数 ###################################
+
+def opportunistic_position_increase(context, symbol, direction, signal):
+    """
+    机会性仓位调整:在保证金使用量低于限制时自动增加仓位
+    
+    参数:
+    symbol: 期货合约代码
+    direction: 交易方向
+    signal: 交易信号
+    """
+    try:
+        if symbol not in g.trade_history:
+            return False
+            
+        underlying_symbol = symbol.split('.')[0][:-4]
+        current_price = get_current_data()[symbol].last_price
+        margin_rate = get_margin_rate(underlying_symbol, direction)
+        multiplier = get_multiplier(underlying_symbol)
+        
+        # 获取当前持仓信息
+        current_actual_margin = g.trade_history[symbol]['actual_margin']
+        current_hands = g.trade_history[symbol]['actual_hands']
+        
+        # 计算剩余保证金容量
+        max_margin = g.max_margin_per_position
+        remaining_margin_capacity = max_margin - current_actual_margin
+        
+        # 检查是否有足够的剩余容量增加至少1手
+        single_hand_margin = current_price * multiplier * margin_rate
+        
+        if remaining_margin_capacity >= single_hand_margin:
+            # 计算可以增加的最大手数
+            additional_hands = int(remaining_margin_capacity / single_hand_margin)
+            
+            # 同时考虑可用资金限制
+            available_cash = context.portfolio.available_cash * g.usage_percentage
+            max_hands_by_cash = int(available_cash / single_hand_margin)
+            
+            # 取较小值
+            additional_hands = min(additional_hands, max_hands_by_cash)
+            
+            if additional_hands >= 1:
+                log.info(f"🚀 机会性增仓机会: {symbol} 当前{current_hands}手(保证金:{current_actual_margin:.0f}), 可增加{additional_hands}手")
+                
+                # 计算新的目标手数
+                new_target_hands = current_hands + additional_hands
+                
+                # 执行增仓
+                order = order_target(symbol, new_target_hands, side=direction)
+                
+                if order and order.filled > 0:
+                    # 计算实际增仓使用的保证金
+                    cash_after = context.portfolio.available_cash
+                    additional_margin_used = order.filled * single_hand_margin  # 估算使用的保证金
+                    
+                    # 更新交易记录
+                    new_total_hands = current_hands + order.filled
+                    new_total_margin = current_actual_margin + additional_margin_used
+                    
+                    g.trade_history[symbol]['actual_hands'] = new_total_hands
+                    g.trade_history[symbol]['target_hands'] = new_target_hands
+                    g.trade_history[symbol]['actual_margin'] = new_total_margin
+                    
+                    log.info(f"✅ 成功增仓 {symbol} {order.filled}手, 总手数: {new_total_hands}手, 总保证金: {new_total_margin:.0f}")
+                    
+                    # 记录增仓交易
+                    g.today_trades.append({
+                        'security': symbol,
+                        'underlying_symbol': underlying_symbol,
+                        'direction': direction,
+                        'order_amount': order.filled,
+                        'order_price': order.avg_cost if order.avg_cost else order.price,
+                        'cash_change': additional_margin_used,
+                        'time': context.current_dt,
+                        'trade_type': 'increase'  # 标记为增仓
+                    })
+                    
+                    return True
+                    
+        return False
+        
+    except Exception as e:
+        log.warning(f"机会性增仓时出错 {symbol}: {str(e)}")
+        return False
+
+############################ 交易执行函数 ###################################
+
+def open_dual_position(context, security, target_hands, signal):
+    """同时开立多空头寸(第一阶段)"""
+    try:
+        underlying_symbol = security.split('.')[0][:-4]
+        
+        # 记录交易前的可用资金
+        cash_before = context.portfolio.available_cash
+        
+        # 先开多头
+        log.info(f"开立多头头寸: {security} {target_hands}手")
+        order_long = order_target(security, target_hands, side='long')
+        
+        if order_long is None or order_long.filled == 0:
+            log.warning(f"多头开仓失败: {security}")
+            return False
+        
+        # 记录多头开仓后的资金
+        cash_after_long = context.portfolio.available_cash
+        cash_change_long = cash_before - cash_after_long
+        
+        # 再开空头
+        log.info(f"开立空头头寸: {security} {target_hands}手")
+        order_short = order_target(security, target_hands, side='short')
+        
+        if order_short is None or order_short.filled == 0:
+            log.warning(f"空头开仓失败: {security},需要平掉多头")
+            # 如果空头开仓失败,平掉已开的多头
+            close_position(context, security, 'long')
+            return False
+        
+        # 记录空头开仓后的资金
+        cash_after_short = context.portfolio.available_cash
+        cash_change_short = cash_after_long - cash_after_short
+        
+        # 获取订单价格和数量
+        long_price = order_long.avg_cost if order_long.avg_cost else order_long.price
+        short_price = order_short.avg_cost if order_short.avg_cost else order_short.price
+        long_amount = order_long.filled
+        short_amount = order_short.filled
+        
+        # 记录当日交易
+        g.today_trades.append({
+            'security': security,
+            'underlying_symbol': underlying_symbol,
+            'direction': 'long',
+            'order_amount': long_amount,
+            'order_price': long_price,
+            'cash_change': cash_change_long,
+            'time': context.current_dt,
+            'phase': 1
+        })
+        
+        g.today_trades.append({
+            'security': security,
+            'underlying_symbol': underlying_symbol,
+            'direction': 'short',
+            'order_amount': short_amount,
+            'order_price': short_price,
+            'cash_change': cash_change_short,
+            'time': context.current_dt,
+            'phase': 1
+        })
+        
+        # 生成唯一的配对ID
+        pair_id = f"{security}_{context.current_dt.strftime('%Y%m%d_%H%M%S')}"
+        
+        # 记录多头交易信息
+        long_key = f"{security}_long"
+        g.trade_history[long_key] = {
+            'entry_price': long_price,
+            'target_hands': target_hands,
+            'actual_hands': long_amount,
+            'actual_margin': cash_change_long,
+            'direction': 'long',
+            'entry_time': context.current_dt,
+            'signal_info': signal,
+            'max_profit': 0.0,
+            'max_profit_price': long_price,
+            'phase': 1,  # 第一阶段:双向持仓
+            'pair_id': pair_id,  # 配对ID
+            'paired_position': f"{security}_short"  # 配对的另一方
+        }
+        
+        # 记录空头交易信息
+        short_key = f"{security}_short"
+        g.trade_history[short_key] = {
+            'entry_price': short_price,
+            'target_hands': target_hands,
+            'actual_hands': short_amount,
+            'actual_margin': cash_change_short,
+            'direction': 'short',
+            'entry_time': context.current_dt,
+            'signal_info': signal,
+            'max_profit': 0.0,
+            'max_profit_price': short_price,
+            'phase': 1,  # 第一阶段:双向持仓
+            'pair_id': pair_id,  # 配对ID
+            'paired_position': f"{security}_long"  # 配对的另一方
+        }
+        
+        log.info(f"成功开立双向头寸 - 多头: {long_amount}手@{long_price:.2f}(保证金:{cash_change_long:.0f}), "
+                f"空头: {short_amount}手@{short_price:.2f}(保证金:{cash_change_short:.0f})")
+        
+        return True
+        
+    except Exception as e:
+        log.warning(f"双向开仓失败 {security}: {str(e)}")
+        return False
+
+def open_position(context, security, target_hands, direction, signal):
+    """开仓(优化版:使用order_target按手数开仓)"""
+    try:
+        # 记录交易前的可用资金
+        cash_before = context.portfolio.available_cash
+        
+        # 使用order_target按手数开仓,自动处理持仓差额
+        order = order_target(security, target_hands, side=direction)
+        
+        if order is not None and order.filled > 0:
+            # 记录交易后的可用资金
+            cash_after = context.portfolio.available_cash
+            
+            # 计算实际资金变化
+            cash_change = cash_before - cash_after
+            
+            # 获取订单价格和数量
+            order_price = order.avg_cost if order.avg_cost else order.price
+            order_amount = order.filled
+            
+            # 记录当日交易
+            underlying_symbol = security.split('.')[0][:-4]
+            g.today_trades.append({
+                'security': security,
+                'underlying_symbol': underlying_symbol,
+                'direction': direction,
+                'order_amount': order_amount,
+                'order_price': order_price,
+                'cash_change': cash_change,
+                'time': context.current_dt
+            })
+            
+            # 记录交易信息,包含止损止盈相关信息
+            g.trade_history[security] = {
+                'entry_price': order_price,
+                'target_hands': target_hands,
+                'actual_hands': order_amount,
+                'actual_margin': cash_change,
+                'direction': direction,
+                'entry_time': context.current_dt,
+                'signal_info': signal,
+                'max_profit': 0.0,  # 初始化最大利润
+                'max_profit_price': order_price  # 记录最大利润时的价格
+            }
+            
+            # 🚀 优化2:动态保证金率调整
+            detect_and_update_margin_rates(context, security, order_amount, cash_change, direction)
+            
+            # 🚀 优化3:机会性仓位调整
+            opportunistic_position_increase(context, security, direction, signal)
+            
+            return True
+            
+    except Exception as e:
+        log.warning(f"开仓失败 {security}: {str(e)}")
+    
+    return False
+
+def close_position(context, security, direction):
+    """平仓(优化版:使用order_target平仓到0手)"""
+    try:
+        # 使用order_target平仓到0手,自动处理持仓清零
+        order = order_target(security, 0, side=direction)
+        
+        if order is not None and order.filled > 0:
+            underlying_symbol = security.split('.')[0][:-4]
+            
+            # 记录当日交易(平仓)
+            g.today_trades.append({
+                'security': security,
+                'underlying_symbol': underlying_symbol,
+                'direction': direction,
+                'order_amount': -order.filled,  # 负数表示平仓
+                'order_price': order.avg_cost if order.avg_cost else order.price,
+                'cash_change': 0,  # 平仓不计算保证金变化
+                'time': context.current_dt
+            })
+            
+            log.info(f"平仓成功 - 品种: {underlying_symbol}, 平仓手数: {order.filled}")
+            
+            # 从交易历史中移除
+            if security in g.trade_history:
+                del g.trade_history[security]
+            return True
+            
+    except Exception as e:
+        log.warning(f"平仓失败 {security}: {str(e)}")
+    
+    return False
+
+def check_stop_loss_profit(context, position):
+    """检查止损止盈(支持两阶段策略)"""
+    security = position.security
+    
+    # 尝试匹配trade_history中的key(可能是原始security或带_long/_short后缀的)
+    trade_key = None
+    if security in g.trade_history:
+        trade_key = security
+    else:
+        # 检查是否有带方向后缀的key
+        for key in g.trade_history.keys():
+            if key.startswith(security + '_'):
+                if g.trade_history[key]['direction'] == position.side:
+                    trade_key = key
+                    break
+    
+    if trade_key is None:
+        return False
+    
+    trade_info = g.trade_history[trade_key]
+    direction = trade_info['direction']
+    entry_price = trade_info['entry_price']
+    current_price = position.price
+    phase = trade_info.get('phase', 3)  # 默认第三阶段(兼容旧数据)
+    
+    # 计算当前盈亏比率
+    if direction == 'long':
+        profit_rate = (current_price - entry_price) / entry_price
+    else:
+        profit_rate = (entry_price - current_price) / entry_price
+    
+    # 更新最大利润记录
+    if profit_rate > trade_info['max_profit']:
+        trade_info['max_profit'] = profit_rate
+        trade_info['max_profit_price'] = current_price
+        g.trade_history[trade_key] = trade_info
+    
+    # 第一阶段:双向持仓,检查是否有一方亏损达到阈值
+    if phase == 1:
+        # 只有亏损达到阈值才平仓
+        if profit_rate <= -g.phase2_loss_threshold:
+            log.info(f"【第二阶段触发】{security} {direction} 亏损达到阈值")
+            log.info(f"亏损率: {profit_rate:.3%}, 触发阈值: {-g.phase2_loss_threshold:.3%}, 成本价: {entry_price:.2f}, 当前价格: {current_price:.2f}")
+            
+            # 平仓亏损方向
+            close_position(context, security, direction)
+            
+            # 更新配对的另一方进入第三阶段
+            paired_key = trade_info.get('paired_position')
+            if paired_key and paired_key in g.trade_history:
+                g.trade_history[paired_key]['phase'] = 3
+                log.info(f"【进入第三阶段】{paired_key} 继续持有,应用标准止盈止损逻辑")
+            
+            return True
+        
+        # 第一阶段不进行其他止损止盈检查
+        return False
+    
+    # 第三阶段:单向持仓,应用原有的标准止盈止损逻辑
+    # 检查固定止损
+    if profit_rate <= -g.fixed_stop_loss_rate:
+        log.info(f"【第三阶段】触发固定止损 {security} {direction}, 当前亏损率: {profit_rate:.3%}, 成本价: {entry_price:.2f}, 当前价格: {current_price:.2f}")
+        close_position(context, security, direction)
+        return True
+    
+    # 检查动态追踪止盈
+    max_profit = trade_info['max_profit']
+    if max_profit > 0:
+        # 确定追踪止损比率
+        if max_profit <= g.trailing_stop_thresholds[0]:  # ≤5%
+            trailing_rate = g.trailing_stop_rates[0]  # 2%
+        elif max_profit <= g.trailing_stop_thresholds[1]:  # 5%-10%
+            trailing_rate = g.trailing_stop_rates[1]  # 3%
+        else:  # >10%
+            trailing_rate = g.trailing_stop_rates[2]  # 4%
+        
+        # 检查是否触发追踪止损
+        profit_drawdown = max_profit - profit_rate
+        if profit_drawdown >= trailing_rate:
+            log.info(f"【第三阶段】触发动态追踪止损 {security} {direction}")
+            log.info(f"最大利润: {max_profit:.3%}, 当前利润: {profit_rate:.3%}, 回撤: {profit_drawdown:.3%}, 触发阈值: {trailing_rate:.3%}")
+            close_position(context, security, direction)
+            return True
+    
+    return False
+
+############################ 辅助函数 ###################################
+
+def determine_trading_direction(symbol, hatch_direction):
+    """
+    确定开仓方向(支持可配置的正向/反向逻辑)
+    
+    参数:
+    symbol: 期货合约代码 (如 'JD2510.XDCE')
+    hatch_direction: 影线方向 ('up' 或 'down')
+    
+    返回:
+    (direction, logic_description): 交易方向和逻辑说明的元组
+    """
+    # 提取基础品种代码
+    underlying_symbol = symbol.split('.')[0][:-4]  # 如 'JD2510.XDCE' -> 'JD'
+    
+    # 检查是否在反向逻辑列表中
+    use_reverse_logic = underlying_symbol in g.reverse_direction_symbols
+    
+    if use_reverse_logic:
+        # 反向逻辑:上影线做空,下影线做多(当前逻辑)
+        if hatch_direction == 'up':
+            direction = 'short'
+            logic_description = f"反向逻辑:{underlying_symbol}上影线做空"
+        else:
+            direction = 'long'
+            logic_description = f"反向逻辑:{underlying_symbol}下影线做多"
+    else:
+        # 正向逻辑:上影线做多,下影线做空(新逻辑)
+        if hatch_direction == 'up':
+            direction = 'long'
+            logic_description = f"正向逻辑:{underlying_symbol}上影线做多"
+        else:
+            direction = 'short'
+            logic_description = f"正向逻辑:{underlying_symbol}下影线做空"
+    
+    return direction, logic_description
+
+def get_futures_config(underlying_symbol, config_key=None, default_value=None):
+    """获取期货品种配置信息的辅助函数"""
+    if underlying_symbol not in g.futures_config:
+        if config_key and default_value is not None:
+            return default_value
+        return {}
+    
+    if config_key is None:
+        return g.futures_config[underlying_symbol]
+    
+    return g.futures_config[underlying_symbol].get(config_key, default_value)
+
+def get_margin_rate(underlying_symbol, direction, default_rate=0.10):
+    """获取保证金比例的辅助函数"""
+    return g.futures_config.get(underlying_symbol, {}).get('margin_rate', {}).get(direction, default_rate)
+
+def get_multiplier(underlying_symbol, default_multiplier=10):
+    """获取合约乘数的辅助函数"""
+    return g.futures_config.get(underlying_symbol, {}).get('multiplier', default_multiplier)
+
+def calculate_target_hands(context, security, direction):
+    """计算目标开仓手数(优化版:直接返回手数用于order_target)"""
+    current_price = get_current_data()[security].last_price
+    underlying_symbol = security.split('.')[0][:-4]
+    
+    # 使用保证金比例
+    margin_rate = get_margin_rate(underlying_symbol, direction)
+    multiplier = get_multiplier(underlying_symbol)
+    
+    # 计算单手保证金
+    single_hand_margin = current_price * multiplier * margin_rate
+    
+    # 还要考虑可用资金限制
+    available_cash = context.portfolio.available_cash * g.usage_percentage
+    
+    # 根据单个标的最大持仓保证金限制计算开仓数量
+    max_margin = g.max_margin_per_position
+    
+    if single_hand_margin <= max_margin:
+        # 如果单手保证金不超过最大限制,计算最大可开仓手数
+        max_hands = int(max_margin / single_hand_margin)
+        max_hands_by_cash = int(available_cash / single_hand_margin)
+        
+        # 取两者较小值
+        actual_hands = min(max_hands, max_hands_by_cash)
+        
+        # 确保至少开1手
+        actual_hands = max(1, actual_hands)
+        
+        # 实际保证金
+        actual_margin = single_hand_margin * actual_hands
+        
+        log.info(f"单手保证金: {single_hand_margin:.0f}, 目标开仓手数: {actual_hands}, 预计保证金: {actual_margin:.0f}")
+        
+        # 直接返回手数,用于order_target()
+        return actual_hands
+    else:
+        # 如果单手保证金超过最大限制,默认开仓1手
+        actual_hands = 1
+        actual_margin = single_hand_margin * actual_hands
+        
+        log.info(f"单手保证金: {single_hand_margin:.0f} 超过最大限制: {max_margin}, 默认开仓1手, 预计保证金: {actual_margin:.0f}")
+        
+        # 直接返回手数,用于order_target()
+        return actual_hands
+
+def check_sufficient_capital(context, symbol):
+    """检查资金是否充足"""
+    try:
+        current_price = get_current_data()[symbol].last_price
+        underlying_symbol = symbol.split('.')[0][:-4]
+        
+        margin_rate = get_margin_rate(underlying_symbol, 'long')
+        multiplier = get_multiplier(underlying_symbol)
+        
+        # 计算单手保证金
+        single_hand_margin = current_price * multiplier * margin_rate
+        
+        # 使用实际可用资金(考虑资金使用比例)
+        available_cash = context.portfolio.available_cash * g.usage_percentage
+        
+        # 检查是否有足够资金开仓至少1手
+        return available_cash >= single_hand_margin
+    except:
+        return False
+
+def check_price_and_liquidity(signal):
+    """检查价格合理性和流动性"""
+    try:
+        # 简单的合理性检查
+        symbol = signal['symbol']
+        current_data = get_current_data()[symbol]
+        
+        log.info(f"{symbol} 价格检查 - 当前价: {current_data.last_price:.2f}, 涨停: {current_data.high_limit:.2f}, 跌停: {current_data.low_limit:.2f}, 成交量: {current_data.volume}")
+        
+        # 检查是否处于涨跌停
+        if current_data.last_price <= current_data.low_limit:
+            log.warning(f"{symbol} 价格触及跌停板 ({current_data.last_price:.2f} <= {current_data.low_limit:.2f}),跳过")
+            return False
+            
+        if current_data.last_price >= current_data.high_limit:
+            log.warning(f"{symbol} 价格触及涨停板 ({current_data.last_price:.2f} >= {current_data.high_limit:.2f}),跳过")
+            return False
+        
+        # 检查成交量是否充足
+        if current_data.volume <= 0:
+            log.warning(f"{symbol} 无成交量 ({current_data.volume}),跳过")
+            return False
+        
+        log.info(f"{symbol} 价格和流动性检查通过")
+        return True
+    except Exception as e:
+        log.warning(f"{signal['symbol']} 价格检查时出错: {str(e)}")
+        return False
+
+def check_symbol_prefix_match(symbol, hold_symbols):
+    """检查是否有相似的持仓品种(支持带_long/_short后缀的key)"""
+    symbol_prefix = symbol[:-9]
+    
+    for hold_symbol in hold_symbols:
+        # 移除可能的_long或_short后缀
+        clean_hold_symbol = hold_symbol.replace('_long', '').replace('_short', '')
+        hold_symbol_prefix = clean_hold_symbol[:-9] if len(clean_hold_symbol) > 9 else clean_hold_symbol
+        
+        if symbol_prefix == hold_symbol_prefix:
+            return True
+    return False
+
+def after_market_close(context):
+    """收盘后运行函数"""
+    log.info(str('函数运行时间(after_market_close):'+str(context.current_dt.time())))
+    
+    # 只有当天有交易时才打印统计信息
+    if g.today_trades:
+        print_daily_trading_summary(context)
+        
+        # 清空当日交易记录
+        g.today_trades = []
+    
+    log.info('##############################################################')
+
+def print_daily_trading_summary(context):
+    """打印当日交易汇总"""
+    if not g.today_trades:
+        return
+    
+    log.info("\n=== 当日交易汇总 ===")
+    total_margin = 0
+    
+    for trade in g.today_trades:
+        if trade['order_amount'] > 0:  # 开仓
+            log.info(f"开仓 {trade['underlying_symbol']} {trade['direction']} {trade['order_amount']}手 "
+                  f"价格:{trade['order_price']:.2f} 保证金:{trade['cash_change']:.0f}")
+            total_margin += trade['cash_change']
+        else:  # 平仓
+            log.info(f"平仓 {trade['underlying_symbol']} {trade['direction']} {abs(trade['order_amount'])}手 "
+                  f"价格:{trade['order_price']:.2f}")
+    
+    log.info(f"当日保证金占用: {total_margin:.0f}")
+    log.info("==================\n")
+
+########################## 自动移仓换月函数 #################################
+def position_auto_switch(context, pindex=0, switch_func=None, callback=None):
+    """
+    期货自动移仓换月。默认使用市价单进行开平仓。
+    """
+    import re
+    subportfolio = context.subportfolios[pindex]
+    symbols = set(subportfolio.long_positions.keys()) | set(subportfolio.short_positions.keys())
+    switch_result = []
+    for symbol in symbols:
+        match = re.match(r"(?P<underlying_symbol>[A-Z]{1,})", symbol)
+        if not match:
+            raise ValueError("未知期货标的: {}".format(symbol))
+        else:
+            dominant = get_dominant_future(match.groupdict()["underlying_symbol"])
+            cur = get_current_data()
+            symbol_last_price = cur[symbol].last_price
+            dominant_last_price = cur[dominant].last_price
+            log.info(f'当前持仓合约: {symbol}, 当前主力合约: {dominant}')
+            
+            if dominant > symbol:
+                for positions_ in (subportfolio.long_positions, subportfolio.short_positions):
+                    if symbol not in positions_.keys():
+                        continue
+                    else :
+                        p = positions_[symbol]
+
+                    if switch_func is not None:
+                        switch_func(context, pindex, p, dominant)
+                    else:
+                        amount = p.total_amount
+                        # 跌停不能开空和平多,涨停不能开多和平空。
+                        if p.side == "long":
+                            symbol_low_limit = cur[symbol].low_limit
+                            dominant_high_limit = cur[dominant].high_limit
+                            if symbol_last_price <= symbol_low_limit:
+                                log.warning("标的{}跌停,无法平仓。移仓换月取消。".format(symbol))
+                                continue
+                            elif dominant_last_price >= dominant_high_limit:
+                                log.warning("标的{}涨停,无法开仓。移仓换月取消。".format(dominant))
+                                continue
+                            else:
+                                log.info("进行移仓换月: ({0},long) -> ({1},long)".format(symbol, dominant))
+                                order_old = order_target(symbol, 0, side='long')
+                                if order_old != None and order_old.filled > 0:
+                                    order_new = order_target(dominant, amount, side='long')
+                                    if order_new != None and order_new.filled > 0:
+                                        switch_result.append({"before": symbol, "after": dominant, "side": "long"})
+                                        # 换月中的买卖都成功了,则增加新的记录去掉旧的记录
+                                        # 检查是否有带_long后缀的key
+                                        old_key = f"{symbol}_long" if f"{symbol}_long" in g.trade_history else symbol
+                                        new_key = f"{dominant}_long" if "_long" in old_key else dominant
+                                        if old_key in g.trade_history:
+                                            g.trade_history[new_key] = g.trade_history[old_key]
+                                            del g.trade_history[old_key]
+                                    else:
+                                        log.warning("标的{}交易失败,无法开仓。移仓换月失败。".format(dominant))
+                        if p.side == "short":
+                            symbol_high_limit = cur[symbol].high_limit
+                            dominant_low_limit = cur[dominant].low_limit
+                            if symbol_last_price >= symbol_high_limit:
+                                log.warning("标的{}涨停,无法平仓。移仓换月取消。".format(symbol))
+                                continue
+                            elif dominant_last_price <= dominant_low_limit:
+                                log.warning("标的{}跌停,无法开仓。移仓换月取消。".format(dominant))
+                                continue
+                            else:
+                                log.info("进行移仓换月: ({0},short) -> ({1},short)".format(symbol, dominant))
+                                order_old = order_target(symbol, 0, side='short')
+                                if order_old != None and order_old.filled > 0:
+                                    order_new = order_target(dominant, amount, side='short')
+                                    if order_new != None and order_new.filled > 0:
+                                        switch_result.append({"before": symbol, "after": dominant, "side": "short"})
+                                        # 换月中的买卖都成功了,则增加新的记录去掉旧的记录
+                                        # 检查是否有带_short后缀的key
+                                        old_key = f"{symbol}_short" if f"{symbol}_short" in g.trade_history else symbol
+                                        new_key = f"{dominant}_short" if "_short" in old_key else dominant
+                                        if old_key in g.trade_history:
+                                            g.trade_history[new_key] = g.trade_history[old_key]
+                                            del g.trade_history[old_key]
+                                    else:
+                                        log.warning("标的{}交易失败,无法开仓。移仓换月失败。".format(dominant))
+                        if callback:
+                            callback(context, pindex, p, dominant)
+    return switch_result

+ 1556 - 0
Lib/future/FutureLeftSide_v001.py

@@ -0,0 +1,1556 @@
+# 导入函数库
+from jqdata import *
+from jqdata import finance
+import pandas as pd
+import numpy as np
+from datetime import date, datetime, timedelta, time
+import re
+
+# 期货左侧交易策略(带网格和对冲)v001
+# 基于网格交易理念,结合左侧底仓建仓、网格交易和空头对冲三个策略组件
+# 
+# 交易逻辑分离:
+# 1. 建仓逻辑:在开盘时执行(夜盘品种21:05,日盘品种09:05)
+#    - 底仓左侧多头:档位价格>=当前价用市价单,否则挂最高两档限价单
+#    - 网格多头:目标价格>=当前价用市价单,否则挂最高两档限价单
+#    - 空头对冲:永远使用限价单
+# 2. 平仓逻辑:在所有时间点执行(check_stop_profit_loss函数),包括止盈和止损
+
+# 设置以便完整打印 DataFrame
+pd.set_option('display.max_rows', None)
+pd.set_option('display.max_columns', None)
+pd.set_option('display.width', None)
+pd.set_option('display.max_colwidth', 20)
+
+## 初始化函数,设定基准等等
+def initialize(context):
+    # 设定沪深300作为基准
+    set_benchmark('000300.XSHG')
+    # 开启动态复权模式(真实价格)
+    set_option('use_real_price', True)
+    # 输出内容到日志
+    log.info('期货左侧交易策略(带网格和对冲)初始化开始')
+
+    ### 期货相关设定 ###
+    # 设定账户为金融账户
+    set_subportfolios([SubPortfolioConfig(cash=context.portfolio.starting_cash, type='index_futures')])
+    # 期货类每笔交易时的手续费是: 买入时万分之0.23,卖出时万分之0.23,平今仓为万分之23
+    set_order_cost(OrderCost(open_commission=0.000023, close_commission=0.000023, close_today_commission=0.0023), type='index_futures')
+    
+    # 设置期货交易的滑点
+    set_slippage(StepRelatedSlippage(2))
+    
+    # ==================== 策略组件开关配置 ====================
+    g.enable_grid_long = True  # 是否启用网格多头策略组件
+    g.enable_hedge_short = True  # 是否启用空头对冲策略组件
+    # 注意:底仓左侧多头始终启用
+    
+    # ==================== 资金管理配置 ====================
+    g.usage_percentage = 0.8  # 最大资金使用比例
+    g.max_margin_per_position = 20000  # 单个标的最大持仓保证金(元)
+    
+    # ==================== 交易品种配置 ====================
+    # 期货品种完整配置字典
+    g.futures_config = {
+        # 贵金属(夜盘品种)
+        'AU': {'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 1000, 'trading_start_time': '21:00'},
+        'AG': {'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 15, 'trading_start_time': '21:00'},
+        
+        # 有色金属(夜盘品种)
+        'CU': {'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'AL': {'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'ZN': {'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'PB': {'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'NI': {'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1, 'trading_start_time': '21:00'},
+        'SN': {'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1, 'trading_start_time': '21:00'},
+        'SS': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        
+        # 黑色系(夜盘品种)
+        'RB': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'HC': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'I': {'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 100, 'trading_start_time': '21:00'},
+        'JM': {'margin_rate': {'long': 0.22, 'short': 0.22}, 'multiplier': 100, 'trading_start_time': '21:00'},
+        'J': {'margin_rate': {'long': 0.22, 'short': 0.22}, 'multiplier': 60, 'trading_start_time': '21:00'},
+        
+        # 能源化工(夜盘品种)
+        'SP': {'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'FU': {'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'BU': {'margin_rate': {'long': 0.04, 'short': 0.04}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'RU': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'BR': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'SC': {'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1000, 'trading_start_time': '21:00'},
+        'NR': {'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'LU': {'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'LC': {'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 1, 'trading_start_time': '09:00'},
+        
+        # 化工(夜盘品种)
+        'FG': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20, 'trading_start_time': '21:00'},
+        'TA': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'MA': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'SA': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20, 'trading_start_time': '21:00'},
+        'L': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'V': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'EG': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'PP': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'EB': {'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'PG': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20, 'trading_start_time': '21:00'},
+        
+        # 农产品(夜盘品种)
+        'RM': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'OI': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'CF': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'SR': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'PF': {'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'C': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'CS': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'CY': {'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'A': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'B': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'M': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'Y': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'P': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        
+        # 股指期货(日盘品种,9:30开始交易)
+        'IF': {'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 300, 'trading_start_time': '09:30'},
+        'IH': {'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 300, 'trading_start_time': '09:30'},
+        'IC': {'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 200, 'trading_start_time': '09:30'},
+        'IM': {'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 200, 'trading_start_time': '09:30'},
+        'TL': {'margin_rate': {'long': 0.01, 'short': 0.01}, 'multiplier': 10000, 'trading_start_time': '09:30'},
+        
+        # 其他日盘品种(9:00开始交易)
+        'AP': {'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 10, 'trading_start_time': '09:00'},
+        'CJ': {'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5, 'trading_start_time': '09:00'},
+        'PK': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 5, 'trading_start_time': '09:00'},
+        'JD': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '09:00'},
+        'LH': {'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 16, 'trading_start_time': '09:00'},
+        'UR': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20, 'trading_start_time': '09:00'}
+    }
+    
+    # 策略品种选择配置
+    g.strategy_focus_symbols = ['LH']  # 交易品种列表
+    
+    # ==================== 底仓左侧多头配置 ====================
+    # 价格-数量网格:价格越低,买入手数越多
+    g.base_position_grid = {
+        'TL': {118: 1, 117: 1, 116: 1, 115: 1, 114: 2, 113: 2},
+        'SA': {1400: 4, 1300: 6, 1200: 8, 1100: 12, 1000: 14, 900: 16},
+        'M': {2800: 4, 2750: 6, 2700: 8, 2650: 12, 2600: 14, 2550: 16},
+        'UR': {1750: 4, 1700: 6, 1650: 8, 1600: 12, 1550: 14, 1500: 16},
+        'LH': {13000: 1, 12500: 1, 12000: 1, 11500: 1, 11000: 2},
+    }
+    
+    # 底仓退出价格(止盈)
+    g.base_position_exit_price = {
+        'TL': 121,
+        'SA': 1550,
+        'M': 3800,
+        'UR': 2400,
+        'LH': 14600,
+    }
+    
+    # ==================== 网格多头配置 ====================
+    g.grid_config = {
+        'TL': {
+            'start_price': 118,      # 网格开始价格
+            'grid_size': 1,          # 网格大小
+            'quantity_per_grid': 1,  # 每网格数量
+            'exit_grid_size': 1      # 退出网格大小
+        },
+        'SA': {
+            'start_price': 1250, # 开始价格
+            'grid_size': 50, # 网格大小
+            'quantity_per_grid': 5, # 每网格数量
+            'exit_grid_size': 50 # 退出网格大小
+        },
+        'M': {
+            'start_price': 2800,
+            'grid_size': 100,
+            'quantity_per_grid': 10,
+            'exit_grid_size': 100
+        },
+        'UR': {
+            'start_price': 1800,
+            'grid_size': 50,
+            'quantity_per_grid': 10,
+            'exit_grid_size': 50
+        },
+        'LH': {
+            'start_price': 13500,
+            'grid_size': 500,
+            'quantity_per_grid': 1,
+            'exit_grid_size': 500
+        },
+    }
+    
+    # ==================== 空头对冲配置 ====================
+    g.hedge_stoploss_pct = 0.02          # 止损百分比(1%)
+    g.hedge_level1_recovery_pct = 0.30   # 一级止盈回升百分比(30%)
+    g.hedge_level2_recovery_pct = 0.60   # 二级止盈回升百分比(60%)
+    g.hedge_cost_area_pct = 0.02         # 成本区域百分比(2%)
+    
+    # ==================== 全局变量初始化 ====================
+    # 持仓跟踪
+    g.base_positions = {}      # 底仓持仓 {symbol: {level_key: {'entry_price': price, 'quantity': qty, 'entry_date': date}}}
+    g.grid_positions = {}      # 网格持仓 {symbol: [{'entry_price': price, 'quantity': qty, 'target_price': target_price, 'entry_date': date}]}
+    g.hedge_positions = {}     # 对冲持仓 {symbol: {level_key: {'entry_price': price, 'quantity': qty, 'entry_date': date}}}
+    
+    # 底仓待成交限价单跟踪(问题2修复)
+    g.base_pending_orders = {} # {symbol: {level_key: {'order_id': xxx, 'price': xxx, 'quantity': xxx, 'submit_time': xxx}}}
+    
+    # 网格状态跟踪
+    g.grid_buy_levels = {}     # 网格已建仓目标价集合 {symbol: set()}
+    
+    # 对冲状态跟踪
+    g.hedge_state = {}         # {symbol: {'max_profit': 0, 'max_profit_price': 0, ...}}
+    
+    # 订单策略信息记录
+    g.order_strategy_info = {} # {order_id: {'strategy_type': '策略类型', 'target_price': 价格}}
+    
+    # ==================== 定时任务设置 ====================
+    # 日盘止损止盈检查(每个时间点)
+    run_daily(check_open_positions, time='09:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_profit_loss, time='10:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_profit_loss, time='10:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_profit_loss, time='11:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_profit_loss, time='13:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_profit_loss, time='14:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_profit_loss, time='14:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_profit_loss, time='14:55:00', reference_security='IF1808.CCFX')
+    
+    # 建仓逻辑(开盘时执行:夜盘21:05,日盘09:05)
+    run_daily(check_open_positions, time='21:05:00', reference_security='IF1808.CCFX')  # 夜盘品种
+    run_daily(check_open_positions, time='09:05:00', reference_security='IF1808.CCFX')  # 日盘品种
+    
+    # 夜盘止损止盈检查(仅对有夜盘的品种)
+    run_daily(check_stop_profit_loss, time='21:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_profit_loss, time='21:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_profit_loss, time='22:05:00', reference_security='IF1808.CCFX')
+    
+    # 收盘后
+    run_daily(after_market_close, time='15:30:00', reference_security='IF1808.CCFX')
+    
+    log.info("=" * 50)
+    log.info("策略组件配置:")
+    log.info(f"  底仓左侧多头: 始终启用")
+    log.info(f"  网格多头策略: {'启用' if g.enable_grid_long else '禁用'}")
+    log.info(f"  空头对冲策略: {'启用' if g.enable_hedge_short else '禁用'}")
+    log.info(f"交易品种: {g.strategy_focus_symbols}")
+    log.info("=" * 50)
+
+############################ 主程序执行函数 ###################################
+
+def check_open_positions(context):
+    """建仓检查函数(仅在14:35执行)"""
+    log.info("=" * 60)
+    log.info(f"执行建仓检查逻辑 - 时间: {context.current_dt}")
+    log.info("=" * 60)
+
+    # 先检查换月移仓
+    switch_success, switch_failed = position_auto_switch(context)
+    if switch_failed:
+        log.info("检测到移仓换月失败,跳过后续建仓检查")
+        for failed in switch_failed:
+            log.info(f"换月失败详情: {failed}")
+        return
+    
+    # 获取当前时间
+    current_time = str(context.current_dt.time())[:2]
+    
+    # 筛选可交易品种(根据交易开始时间判断是否为夜盘品种)
+    tradable_symbols = []
+    for symbol in g.strategy_focus_symbols:
+        is_night_symbol = is_night_session_symbol(symbol)
+        if current_time in ('21', '22'):
+            # 夜盘时间:只考虑夜盘品种
+            if is_night_symbol:
+                tradable_symbols.append(symbol)
+        else:
+            # 日盘时间:所有品种都可以交易
+            tradable_symbols.append(symbol)
+    
+    if not tradable_symbols:
+        log.info("当前时段无可交易品种")
+        return
+    
+    log.info(f"可交易品种: {tradable_symbols}")
+    
+    # 对每个品种执行建仓检查
+    for symbol in tradable_symbols:
+        try:
+            # 获取主力合约
+            dominant_future = get_dominant_future(symbol)
+            if not dominant_future:
+                log.warning(f"{symbol} 未找到主力合约")
+                continue
+            
+            # 获取当前价格
+            current_data = get_current_data()[dominant_future]
+            current_price = current_data.last_price
+            
+            log.info(f"建仓检查 - 品种: {symbol}, 主力合约: {dominant_future}, 当前价格: {current_price}")
+            
+            # 策略组件1: 底仓左侧多头建仓(始终启用)
+            check_base_position_open(context, symbol, dominant_future, current_price)
+            
+            # 策略组件2: 网格多头建仓(可配置)
+            if g.enable_grid_long:
+                check_grid_trading_open(context, symbol, dominant_future, current_price)
+            
+            # 策略组件3: 空头对冲建仓(可配置)
+            if g.enable_hedge_short:
+                check_hedge_position_open(context, symbol, dominant_future, current_price)
+            
+        except Exception as e:
+            log.warning(f"建仓检查处理{symbol}时出错: {str(e)}")
+            continue
+
+def check_stop_profit_loss(context):
+    """止损止盈检查函数(所有时间点执行)"""
+    # log.info("=" * 60)
+    # log.info(f"执行止损止盈检查 - 时间: {context.current_dt}")
+    # log.info("=" * 60)
+
+    # 先检查换月移仓
+    switch_success, switch_failed = position_auto_switch(context)
+    if switch_failed:
+        log.info("检测到移仓换月失败,跳过后续止盈止损检查")
+        for failed in switch_failed:
+            log.info(f"换月失败详情: {failed}")
+        return
+    
+    # 获取当前时间
+    current_dt = context.current_dt
+    current_time = current_dt.time()
+    current_time_str = current_dt.strftime("%H:%M:%S")
+    in_night_session = current_time >= time(21, 0) or current_time < time(2, 30)
+    in_day_session = time(9, 0) <= current_time <= time(15, 0)
+    
+    # 筛选可交易品种(根据交易开始时间判断是否为夜盘品种)
+    tradable_symbols = []
+    for symbol in g.strategy_focus_symbols:
+        is_night_symbol = is_night_session_symbol(symbol)
+        if not is_night_symbol:
+            if in_night_session:
+                log.info(f"{symbol} 为日盘合约,当前夜间时段 {current_time_str} 跳过止损止盈检查")
+                continue
+            if not in_day_session:
+                log.info(f"{symbol} 为日盘合约,当前非交易时段 {current_time_str} 跳过止损止盈检查")
+                continue
+        else:
+            if in_night_session:
+                log.info(f"{symbol} 夜盘合约在夜间时段 {current_time_str} 执行止损止盈检查")
+            elif not in_day_session:
+                log.info(f"{symbol} 夜盘合约当前非活跃时段 {current_time_str} 仍执行止损止盈检查以覆盖全天风险")
+        tradable_symbols.append(symbol)
+    
+    if not tradable_symbols:
+        return
+    
+    # 对每个品种执行止损止盈检查
+    for symbol in tradable_symbols:
+        try:
+            # 获取主力合约
+            dominant_future = get_dominant_future(symbol)
+            if not dominant_future:
+                continue
+            
+            # 获取当前价格
+            current_data = get_current_data()[dominant_future]
+            current_price = current_data.last_price
+            
+            # 策略组件1: 底仓左侧多头止盈(始终启用)
+            check_base_position_close(context, symbol, dominant_future, current_price)
+            
+            # 策略组件2: 网格多头止盈(可配置)
+            if g.enable_grid_long:
+                check_grid_trading_close(context, symbol, dominant_future, current_price)
+                check_grid_trading_open(context, symbol, dominant_future, current_price)
+            
+            # 策略组件3: 空头对冲止损止盈(可配置)
+            if g.enable_hedge_short:
+                check_hedge_position_close(context, symbol, dominant_future, current_price)
+                check_hedge_position_open(context, symbol, dominant_future, current_price)
+            
+        except Exception as e:
+            log.warning(f"止损止盈检查处理{symbol}时出错: {str(e)}")
+            continue
+
+############################ 策略组件1: 底仓左侧多头 ###################################
+
+def update_base_pending_orders(context, symbol, dominant_future):
+    """更新底仓待成交限价单状态
+    
+    检查待成交限价单的状态:
+    - 如果订单已完全成交,移到 g.base_positions
+    - 如果订单被取消或失败,从 g.base_pending_orders 中删除
+    - 如果订单部分成交或仍在挂单,保持在 g.base_pending_orders 中
+    
+    Args:
+        context: 上下文对象
+        symbol: 品种代码
+        dominant_future: 主力合约
+    """
+    if dominant_future not in g.base_pending_orders:
+        return
+    
+    if not g.base_pending_orders[dominant_future]:
+        return
+    
+    # 获取所有订单
+    try:
+        all_orders = get_orders()
+    except Exception as e:
+        log.warning(f"更新待成交限价单失败: 无法获取订单信息 - {str(e)}")
+        return
+    
+    levels_to_remove = []
+    levels_to_add_to_positions = []
+    
+    for level_key, pending_info in g.base_pending_orders[dominant_future].items():
+        order_id = pending_info.get('order_id')
+        if not order_id or order_id not in all_orders:
+            # 订单不存在,可能已被系统清理,从待成交列表中移除
+            log.info(f"底仓限价单 {level_key} 订单 {order_id} 不存在于订单列表,移除待成交记录")
+            levels_to_remove.append(level_key)
+            continue
+        
+        order_obj = all_orders[order_id]
+        status = str(order_obj.status).lower()
+        filled_amount = getattr(order_obj, 'filled', 0) or 0
+        order_amount = getattr(order_obj, 'amount', 0) or 0
+        
+        # 检查订单状态
+        if status in ('filled', 'held'):
+            # 订单已完全成交
+            actual_entry_price = pending_info['price']  # 限价单成交价等于限价
+            levels_to_add_to_positions.append((level_key, {
+                'entry_price': actual_entry_price,
+                'quantity': pending_info['quantity'],
+                'entry_date': pending_info['submit_time']
+            }))
+            levels_to_remove.append(level_key)
+            log.info(f"底仓限价单 {level_key} 已成交,订单 {order_id},移至持仓记录")
+        elif status in ('canceled', 'rejected'):
+            # 订单已取消或被拒绝
+            levels_to_remove.append(level_key)
+            log.info(f"底仓限价单 {level_key} 已{status},订单 {order_id},从待成交列表移除")
+        elif status in ('open', 'pending', 'new', 'part_filled'):
+            # 订单仍在等待成交或部分成交,保持在待成交列表
+            log.info(f"底仓限价单 {level_key} 状态 {status},已成交 {filled_amount}/{order_amount} 手,继续等待")
+        else:
+            # 未知状态,记录日志
+            log.warning(f"底仓限价单 {level_key} 订单 {order_id} 状态未知: {status}")
+    
+    # 将已成交的订单移到持仓记录
+    for level_key, position_info in levels_to_add_to_positions:
+        g.base_positions[dominant_future][level_key] = position_info
+    
+    # 从待成交列表中移除
+    for level_key in levels_to_remove:
+        del g.base_pending_orders[dominant_future][level_key]
+
+def check_base_position_open(context, symbol, dominant_future, current_price):
+    """检查底仓左侧多头建仓
+    
+    逻辑:
+    1. 遍历所有未建仓的价格档位
+    2. 如果档位价格 >= 当前价格:使用市价单立即成交
+    3. 如果档位价格 < 当前价格:记录到待挂限价单列表
+    4. 从待挂限价单列表中选择最高的两个档位挂限价单
+    """
+    
+    # 时间检查:判断是否已达到该品种的交易开始时间
+    if not is_trading_time_reached(context, symbol):
+        log.info(f"底仓建仓跳过: {symbol} 未达到交易开始时间")
+        return
+    
+    # 检查是否有底仓配置
+    if symbol not in g.base_position_grid:
+        return
+    
+    grid = g.base_position_grid[symbol]
+    
+    # 初始化持仓记录
+    if dominant_future not in g.base_positions:
+        g.base_positions[dominant_future] = {}
+    
+    # 初始化待成交限价单记录(问题2修复)
+    if dominant_future not in g.base_pending_orders:
+        g.base_pending_orders[dominant_future] = {}
+    
+    # 问题2修复:检查待成交限价单的状态,将已成交的订单移到持仓记录
+    update_base_pending_orders(context, symbol, dominant_future)
+    
+    log.info(f"底仓建仓检查: {symbol} 当前价格 {current_price}")
+    log.info(f"底仓持仓: {g.base_positions[dominant_future]}")
+    log.info(f"底仓待成交限价单: {g.base_pending_orders[dominant_future]}")
+    
+    # 收集未建仓的档位
+    market_order_levels = []  # 需要市价单的档位
+    limit_order_levels = []   # 需要限价单的档位
+    
+    for price_level, quantity in sorted(grid.items(), reverse=True):
+        level_key = f"{price_level}"
+        
+        # 跳过已建仓的档位
+        if level_key in g.base_positions[dominant_future]:
+            continue
+        
+        # 问题2修复:跳过已有待成交限价单的档位
+        if level_key in g.base_pending_orders[dominant_future]:
+            log.info(f"  档位 {price_level} 已有待成交限价单,跳过")
+            continue
+        
+        # 问题1修复:过滤价格过低的档位,避免无效挂单
+        # 当档位价格 < 当前价格的95%时,跳过该档位(相当于当前价格 > 档位价格 * 1.05)
+        if price_level < current_price * 0.95:
+            log.info(f"  档位 {price_level} 过低(< 当前价 {current_price} * 0.95 = {current_price * 0.95:.2f}),跳过挂单")
+            continue
+        
+        # 判断使用市价单还是限价单
+        if price_level >= current_price:
+            # 档位价格 >= 当前价格,使用市价单
+            market_order_levels.append((price_level, quantity))
+            log.info(f"  档位 {price_level}({quantity}手)>= 当前价 {current_price},将使用市价单")
+        else:
+            # 档位价格 < 当前价格,加入限价单候选
+            limit_order_levels.append((price_level, quantity))
+    
+    # 执行市价单
+    for price_level, quantity in market_order_levels:
+        level_key = f"{price_level}"
+        strategy_info = {'strategy_type': '基础左侧', 'target_price': price_level}
+        _, success = open_position(context, dominant_future, quantity, 'long', 
+                              f'底仓市价建仓(目标档位{price_level})', 
+                              strategy_info=strategy_info)
+        if success:
+            # 获取实际成交价格(从订单记录中获取)
+            actual_price = current_price  # 市价单按当前价记录
+            g.base_positions[dominant_future][level_key] = {
+                'entry_price': actual_price,
+                'quantity': quantity,
+                'entry_date': context.current_dt
+            }
+            log.info(f"底仓市价单成交: {symbol} 目标档位 {price_level}, 实际价格 {actual_price}, 数量 {quantity}")
+    
+    # 执行限价单(固定挂两个档位)
+    if limit_order_levels:
+        # 选择价格最高的两个档位
+        top_two_levels = sorted(limit_order_levels, reverse=True)[:2]
+        log.info(f"  准备挂限价单,候选档位: {[p for p, q in limit_order_levels]}, 选择最高两个: {[p for p, q in top_two_levels]}")
+        
+        for price_level, quantity in top_two_levels:
+            level_key = f"{price_level}"
+            strategy_info = {'strategy_type': '基础左侧', 'target_price': price_level}
+            order_id, success = open_position(context, dominant_future, quantity, 'long', 
+                                  f'底仓限价建仓@{price_level}', 
+                                  limit_price=price_level,
+                                  strategy_info=strategy_info)
+            if success:
+                # 问题2修复:限价单提交成功后,记录到待成交列表,而不是直接记录到持仓
+                g.base_pending_orders[dominant_future][level_key] = {
+                    'order_id': order_id,
+                    'price': price_level,
+                    'quantity': quantity,
+                    'submit_time': context.current_dt
+                }
+                log.info(f"底仓限价单挂单: {symbol} 档位 {price_level}, 数量 {quantity}, order_id={order_id}, 待成交确认")
+
+def check_base_position_close(context, symbol, dominant_future, current_price):
+    """检查底仓左侧多头平仓(止盈)"""
+    
+    # 时间检查:判断是否已达到该品种的交易开始时间
+    if not is_trading_time_reached(context, symbol):
+        log.info(f"底仓止盈跳过: {symbol} 未达到交易开始时间")
+        return
+    
+    # 检查是否有底仓配置
+    if symbol not in g.base_position_exit_price:
+        return
+    
+    exit_price = g.base_position_exit_price.get(symbol)
+    
+    # 检查退出条件(止盈)
+    if exit_price and current_price >= exit_price:
+        # 检查是否有持仓需要平仓
+        subportfolio = context.subportfolios[0]
+        if dominant_future in subportfolio.long_positions:
+            position = subportfolio.long_positions[dominant_future]
+            if position.total_amount > 0:
+                log.info(f"底仓触发止盈: {symbol} 当前价格 {current_price} >= 退出价格 {exit_price}")
+                close_position(context, dominant_future, position.total_amount, 'long', '底仓止盈')
+                if dominant_future in g.base_positions:
+                    g.base_positions[dominant_future] = {}
+                return
+
+############################ 策略组件2: 网格多头策略 ###################################
+
+def check_grid_trading_open(context, symbol, dominant_future, current_price):
+    """检查网格多头建仓
+    
+    逻辑:
+    1. 找出所有应该买入但未买入的网格层级
+    2. 对于每个层级的目标价格:
+       - 如果目标价格 >= 当前价格:使用市价单
+       - 如果目标价格 < 当前价格:记录到待挂限价单列表
+    3. 从待挂限价单列表中选择价格最高的两个层级挂限价单
+    """
+    
+    # 时间检查:判断是否已达到该品种的交易开始时间
+    if not is_trading_time_reached(context, symbol):
+        log.info(f"网格建仓跳过: {symbol} 未达到交易开始时间")
+        return
+    
+    # 检查是否有网格配置
+    if symbol not in g.grid_config:
+        return
+    
+    config = g.grid_config[symbol]
+    start_price = config['start_price']
+    grid_size = config['grid_size']
+    quantity_per_grid = config['quantity_per_grid']
+    
+    # 初始化网格持仓记录
+    if dominant_future not in g.grid_positions:
+        g.grid_positions[dominant_future] = []
+    if dominant_future not in g.grid_buy_levels:
+        g.grid_buy_levels[dominant_future] = set()
+    
+    # 只在价格低于起始价格时才执行网格交易
+    if current_price >= start_price:
+        return
+    
+    log.info(f"网格建仓检查: {symbol} 当前价格 {current_price}, 起始价格 {start_price}")
+    
+    # 计算当前价格对应的最低网格档位数量
+    max_grid_index = int((start_price - current_price) / grid_size)
+    
+    # 收集需要建仓的网格档位
+    market_order_grids = []  # 需要市价单的网格
+    limit_order_grids = []   # 需要限价单的网格
+    
+    # 遍历所有应该触发的网格档位(从0到max_grid_index)
+    for grid_index in range(max_grid_index + 1):
+        # 计算该档位的目标买入价格
+        target_price = start_price - grid_index * grid_size
+
+        # 跳过已经买入的目标价
+        if target_price in g.grid_buy_levels[dominant_future]:
+            continue
+
+        grid_info = (grid_index, target_price)
+        
+        # 判断使用市价单还是限价单
+        if target_price >= current_price:
+            # 目标价格 >= 当前价格,使用市价单
+            market_order_grids.append(grid_info)
+            log.info(f"  网格目标价 {target_price} >= 当前价 {current_price},将使用市价单 (档位索引 {grid_index})")
+        else:
+            # 目标价格 < 当前价格,加入限价单候选
+            limit_order_grids.append(grid_info)
+    
+    # 执行市价单
+    for _, target_price in market_order_grids:
+        strategy_info = {'strategy_type': '网格', 'target_price': target_price}
+        _, success = open_position(context, dominant_future, quantity_per_grid, 'long', 
+                              f'网格市价买入(目标价{target_price})',
+                              strategy_info=strategy_info)
+        if success:
+            # 市价单按实际成交价记录(这里用当前价近似)
+            g.grid_positions[dominant_future].append({
+                'entry_price': current_price,
+                'quantity': quantity_per_grid,
+                'target_price': target_price,
+                'entry_date': context.current_dt
+            })
+            g.grid_buy_levels[dominant_future].add(target_price)
+            log.info(f"网格市价单成交: {symbol} 目标价 {target_price}, 实际价 {current_price}, 数量 {quantity_per_grid}")
+    
+    # 执行限价单(固定挂两个层级)
+    if limit_order_grids:
+        # 选择价格最高的两个目标价(档位数越小价格越高)
+        top_two_grids = sorted(limit_order_grids, key=lambda x: x[1], reverse=True)[:2]
+        log.info(f"  准备挂限价单,候选目标价: {[(l, p) for l, p in limit_order_grids]}, 选择最高两个: {[(l, p) for l, p in top_two_grids]}")
+
+        for _, target_price in top_two_grids:
+            strategy_info = {'strategy_type': '网格', 'target_price': target_price}
+            _, success = open_position(context, dominant_future, quantity_per_grid, 'long', 
+                                  f'网格限价买入@目标价{target_price}', 
+                                  limit_price=target_price,
+                                  strategy_info=strategy_info)
+            if success:
+                g.grid_positions[dominant_future].append({
+                    'entry_price': target_price,  # 限价单按委托价记录
+                    'quantity': quantity_per_grid,
+                    'target_price': target_price,
+                    'entry_date': context.current_dt
+                })
+                g.grid_buy_levels[dominant_future].add(target_price)
+                log.info(f"网格限价单挂单: {symbol} 目标价 {target_price}, 数量 {quantity_per_grid}")
+
+def check_grid_trading_close(context, symbol, dominant_future, current_price):
+    """检查网格多头平仓(止盈)"""
+    
+    # 时间检查:判断是否已达到该品种的交易开始时间
+    if not is_trading_time_reached(context, symbol):
+        log.info(f"网格止盈跳过: {symbol} 未达到交易开始时间")
+        return
+    
+    # 检查是否有网格配置
+    if symbol not in g.grid_config:
+        return
+    
+    config = g.grid_config[symbol]
+    exit_grid_size = config['exit_grid_size']
+    
+    # 初始化网格持仓记录
+    if dominant_future not in g.grid_positions:
+        g.grid_positions[dominant_future] = []
+    if dominant_future not in g.grid_buy_levels:
+        g.grid_buy_levels[dominant_future] = set()
+    
+    # 检查卖出条件:对每个持仓检查是否达到退出条件
+    positions_to_remove = []
+    for i, position in enumerate(g.grid_positions[dominant_future]):
+        target_price = position['target_price']
+        exit_price = target_price + exit_grid_size
+        if current_price >= exit_price:
+            # 执行网格卖出
+            success = close_position(context, dominant_future, position['quantity'], 'long', 
+                                   f'网格卖出@目标价{target_price}')
+            if success:
+                profit_per_unit = current_price - position['entry_price']
+                log.info(f"网格卖出: {symbol} 目标价 {target_price}, 计划退出价 {exit_price}, "
+                        f"买入价 {position['entry_price']}, 卖出价 {current_price}, "
+                        f"盈利 {profit_per_unit * position['quantity'] * get_multiplier(symbol)}")
+                positions_to_remove.append(i)
+                # 移除该网格目标价标记,允许重新买入
+                if target_price in g.grid_buy_levels[dominant_future]:
+                    g.grid_buy_levels[dominant_future].remove(target_price)
+    
+    # 移除已平仓的网格持仓记录
+    for i in reversed(positions_to_remove):
+        g.grid_positions[dominant_future].pop(i)
+
+############################ 策略组件3: 空头对冲策略 ###################################
+
+def check_hedge_position_open(context, symbol, dominant_future, current_price):
+    """检查空头对冲建仓
+    
+    对冲逻辑:
+    1. 对冲数量只对应基础左侧持仓,不包括网格持仓
+    2. 对冲永远使用限价单,价格为各档位的目标价格
+    3. 针对每个基础左侧档位分别建立对应的空头对冲单
+    4. **关键约束**:只对今天开立的左侧持仓建立对冲
+    """
+    
+    # 时间检查:判断是否已达到该品种的交易开始时间
+    if not is_trading_time_reached(context, symbol):
+        log.info(f"对冲建仓跳过: {symbol} 未达到交易开始时间")
+        return
+    
+    # 初始化对冲状态
+    if dominant_future not in g.hedge_state:
+        g.hedge_state[dominant_future] = {
+            'max_profit': 0,
+            'max_profit_price': 0,
+            'profit_stage': 0  # 0: 未盈利, 1: 一级盈利, 2: 二级盈利
+        }
+    
+    # 初始化对冲持仓记录
+    if dominant_future not in g.hedge_positions:
+        g.hedge_positions[dominant_future] = {}
+    
+    # 检查是否有底仓持仓(作为建立对冲的条件)
+    if dominant_future not in g.base_positions or not g.base_positions[dominant_future]:
+        log.info(f"对冲建仓检查: {symbol} 当前无基础左侧持仓,无需对冲")
+        return
+    
+    # **关键检查**:筛选今天开立的左侧持仓
+    today_date = context.current_dt.date()
+    today_opened_positions = {}
+    
+    for level_key, position_info in g.base_positions[dominant_future].items():
+        entry_date = position_info.get('entry_date')
+        if entry_date is None:
+            log.warning(f"对冲建仓检查: {symbol} 档位 {level_key} 缺少entry_date,跳过")
+            continue
+        
+        # 将entry_date转换为date对象进行比较
+        if hasattr(entry_date, 'date'):
+            entry_date_obj = entry_date.date()
+        elif hasattr(entry_date, 'to_pydatetime'):
+            entry_date_obj = entry_date.to_pydatetime().date()
+        else:
+            entry_date_obj = entry_date
+        
+        # 只记录今天开立的持仓
+        if entry_date_obj == today_date:
+            today_opened_positions[level_key] = position_info
+            log.info(f"对冲建仓检查: {symbol} 档位 {level_key} 今天开立,可建立对冲")
+        else:
+            log.info(f"对冲建仓检查: {symbol} 档位 {level_key} 开立日期 {entry_date_obj} ≠ 今天 {today_date},跳过对冲")
+    
+    # **关键判断**:如果今天没有开立任何左侧持仓,完全跳过对冲逻辑
+    if not today_opened_positions:
+        # log.info(f"对冲建仓检查: {symbol} 今天未开立任何左侧持仓,跳过所有对冲建仓逻辑")
+        return
+    
+    log.info(f"对冲建仓检查: {symbol} 当前价格 {current_price}, 今天开立的左侧持仓档位: {list(today_opened_positions.keys())}")
+
+    # 收集对应基础左侧订单的成交情况,仅在订单成交后才建立对冲
+    level_fill_status = {}
+    order_strategy_info_map = getattr(g, 'order_strategy_info', {}) if hasattr(g, 'order_strategy_info') else {}
+    try:
+        all_orders = get_orders()
+    except Exception as e:
+        log.warning(f"对冲建仓检查: {symbol} 获取订单信息失败,等待下次检查。错误: {str(e)}")
+        all_orders = {}
+
+    if all_orders:
+        for order_id, order_obj in all_orders.items():
+            if order_id not in order_strategy_info_map:
+                continue
+            strategy_info = order_strategy_info_map[order_id]
+            if strategy_info.get('strategy_type') != '基础左侧':
+                continue
+            if getattr(order_obj, 'security', None) != dominant_future:
+                continue
+            target_price_key = f"{strategy_info.get('target_price')}"
+            if target_price_key not in today_opened_positions:
+                continue
+            status_value = getattr(order_obj, 'status', '')
+            status_str = str(status_value)
+            status_lower = status_str.lower()
+            filled_amount = getattr(order_obj, 'filled', 0) or 0
+            order_amount = getattr(order_obj, 'amount', 0) or 0
+            info = level_fill_status.setdefault(target_price_key, {
+                'filled': 0,
+                'amount': 0,
+                'pending': False,
+                'waiting': False,
+                'statuses': set()
+            })
+            info['filled'] += filled_amount
+            info['amount'] += order_amount
+            info['statuses'].add(status_str)
+            if status_lower in ('open', 'part_filled', 'pending', 'submitted'):
+                info['pending'] = True
+            if status_lower in ('open', 'submitted') and filled_amount == 0:
+                info['waiting'] = True
+
+    # 只针对今天开立的基础左侧档位建立对应的对冲单
+    for level_key, position_info in today_opened_positions.items():
+        # 跳过已经建立对冲的档位
+        if level_key in g.hedge_positions[dominant_future]:
+            log.info(f"  对冲档位 {level_key}: 已存在对冲持仓,跳过")
+            continue
+
+        fill_info = level_fill_status.get(level_key)
+        if not fill_info:
+            log.info(f"  对冲档位 {level_key}: 底仓订单未找到成交记录,等待成交后再建立对冲")
+            continue
+
+        required_quantity = position_info['quantity']
+        filled_quantity = fill_info.get('filled', 0)
+
+        status_list = sorted(fill_info.get('statuses'))
+
+        if fill_info.get('waiting', False):
+            log.info(f"  对冲档位 {level_key}: 底仓限价单仍在挂单等待,状态集合 {status_list}")
+            continue
+
+        if filled_quantity < required_quantity:
+            # log.info(f"  对冲档位 {level_key}: 底仓订单成交量不足 (成交 {filled_quantity} 手 / 目标 {required_quantity} 手,状态集合 {status_list}),跳过对冲")
+            continue
+        
+        target_price = level_key # 目标价格本质就是对冲档位
+        quantity = position_info['quantity']
+        
+        log.info(f"  对冲档位 {level_key}: 目标价格 {target_price}, 数量 {quantity}手(对应今天开立的左侧持仓)")
+        
+        # 建立空头对冲(永远使用限价单,价格为档位目标价)
+        strategy_info = {'strategy_type': '对冲', 'target_price': target_price}
+        _, success = open_position(context, dominant_future, quantity, 'short', 
+                              f'建立空头对冲@{target_price}', 
+                              limit_price=target_price,
+                              strategy_info=strategy_info)
+        if success:
+            try:
+                entry_price_value = float(target_price)
+            except (TypeError, ValueError):
+                log.error(f"对冲建仓记录失败: 档位 {level_key} 目标价无法转换为浮点数 -> {target_price}")
+                entry_price_value = target_price
+            g.hedge_positions[dominant_future][level_key] = {
+                'entry_price': entry_price_value,
+                'quantity': quantity,
+                'entry_date': context.current_dt
+            }
+            log.info(f"对冲限价单挂单: {symbol} 档位 {level_key}, 价格 {target_price}, 数量 {quantity}手")
+
+def check_hedge_position_close(context, symbol, dominant_future, current_price):
+    """检查空头对冲平仓(止损止盈)
+    
+    对冲平仓逻辑:
+    1. 分别检查每个档位的对冲持仓
+    2. 每个档位独立计算盈亏和止损止盈条件
+    """
+    
+    # 时间检查:判断是否已达到该品种的交易开始时间
+    if not is_trading_time_reached(context, symbol):
+        log.info(f"对冲止损止盈跳过: {symbol} 未达到交易开始时间")
+        return
+    
+    # 检查是否已有对冲持仓记录
+    if dominant_future not in g.hedge_positions or not g.hedge_positions[dominant_future]:
+        return
+    
+    # 检查实际空头持仓
+    subportfolio = context.subportfolios[0]
+    has_hedge_position = dominant_future in subportfolio.short_positions and \
+                        subportfolio.short_positions[dominant_future].total_amount > 0
+    
+    if not has_hedge_position:
+        return
+    
+    # 问题1修复:获取当前日期,用于过滤当日新开仓的对冲头寸
+    today_date = context.current_dt.date()
+    
+    # 遍历每个档位的对冲持仓
+    levels_to_remove = []
+    for level_key, hedge_info in g.hedge_positions[dominant_future].items():
+        # 问题1修复:检查对冲头寸的开仓日期
+        entry_date = hedge_info.get('entry_date')
+        if entry_date is not None:
+            # 将entry_date转换为date对象进行比较
+            if hasattr(entry_date, 'date'):
+                entry_date_obj = entry_date.date()
+            elif hasattr(entry_date, 'to_pydatetime'):
+                entry_date_obj = entry_date.to_pydatetime().date()
+            else:
+                entry_date_obj = entry_date
+            
+            # 如果是当日新开仓的对冲头寸,跳过止损止盈检查
+            if entry_date_obj == today_date:
+                log.info(f"对冲止损止盈检查: {symbol} 档位 {level_key} 于今日开仓,跳过当日止损止盈检查")
+                continue
+        
+        try:
+            entry_price = float(hedge_info['entry_price'])
+        except (TypeError, ValueError):
+            log.error(f"对冲止损止盈计算失败: 档位 {level_key} 入场价无法转换为浮点数 -> {hedge_info.get('entry_price')}")
+            continue
+        quantity = hedge_info['quantity']
+        
+        # 计算当前盈亏率(空头:入场价格 > 当前价格为盈利)
+        profit_rate = (entry_price - current_price) / entry_price
+        
+        # 更新最大盈利
+        if profit_rate > g.hedge_state[dominant_future]['max_profit']:
+            g.hedge_state[dominant_future]['max_profit'] = profit_rate
+            g.hedge_state[dominant_future]['max_profit_price'] = current_price
+            
+            # 更新盈利阶段
+            if profit_rate >= g.hedge_level2_recovery_pct:
+                g.hedge_state[dominant_future]['profit_stage'] = 2
+            elif profit_rate >= g.hedge_level1_recovery_pct:
+                g.hedge_state[dominant_future]['profit_stage'] = 1
+        
+        # 检查止损条件
+        if profit_rate <= -g.hedge_stoploss_pct:
+            log.info(f"对冲触发止损: {symbol} 档位 {level_key}, 入场价 {entry_price}, 亏损率 {profit_rate:.2%}")
+            close_position(context, dominant_future, quantity, 'short', f'对冲止损@档位{level_key}')
+            levels_to_remove.append(level_key)
+            continue
+        
+        # 检查成本区域止盈条件
+        if abs(profit_rate) <= g.hedge_cost_area_pct:
+            profit_stage = g.hedge_state[dominant_future]['profit_stage']
+            if profit_stage >= 1:  # 曾经达到过一级或二级盈利
+                log.info(f"对冲触发成本区域止盈: {symbol} 档位 {level_key}, 当前盈利 {profit_rate:.2%} "
+                        f"在成本区域内,曾达到盈利阶段 {profit_stage}")
+                close_position(context, dominant_future, quantity, 'short', f'对冲成本区域止盈@档位{level_key}')
+                levels_to_remove.append(level_key)
+                continue
+    
+    # 移除已平仓的档位
+    for level_key in levels_to_remove:
+        del g.hedge_positions[dominant_future][level_key]
+    
+    # 如果所有对冲持仓都已平仓,重置对冲状态
+    if not g.hedge_positions[dominant_future]:
+        g.hedge_state[dominant_future] = {'max_profit': 0, 'max_profit_price': 0, 'profit_stage': 0}
+
+############################ 交易执行函数 ###################################
+
+def open_position(context, security, quantity, direction, reason='', limit_price=None, strategy_info=None):
+    """开仓
+    
+    Args:
+        context: 上下文对象
+        security: 合约代码
+        quantity: 开仓数量
+        direction: 方向 ('long' 或 'short')
+        reason: 开仓原因
+        limit_price: 限价单价格,None表示使用市价单
+        strategy_info: 策略信息字典 {'strategy_type': '策略类型', 'target_price': 目标价格}
+        
+    Returns:
+        tuple: (order_id, success) - 订单ID和是否成功
+        
+    注:订单提交后filled=0,实际成交信息需要在收盘后通过get_trades()获取
+    """
+    try:
+        order_type = "限价单" if limit_price is not None else "市价单"
+        price_info = f"@{limit_price}" if limit_price is not None else ""
+        log.info(f"提交开仓订单: {security} {direction} {quantity}手 {order_type}{price_info} - {reason}")
+        
+        # 使用order按手数开仓(避免order_target取消之前的未完成订单)
+        # 根据是否有限价选择订单类型
+        if limit_price is not None:
+            order_obj = order(security, quantity, LimitOrderStyle(limit_price), side=direction)
+        else:
+            order_obj = order(security, quantity, side=direction)
+        
+        if order_obj is not None:
+            log.info(f"订单已提交: order_id={order_obj.order_id}, 状态={order_obj.status}, "
+                    f"已成交={order_obj.filled}/{quantity}手")
+            
+            # 保存策略信息到订单记录中,用于收盘后的汇总显示
+            if strategy_info:
+                if not hasattr(g, 'order_strategy_info'):
+                    g.order_strategy_info = {}
+                g.order_strategy_info[order_obj.order_id] = strategy_info
+                log.info(f"订单策略信息已记录: {strategy_info}")
+            
+            return order_obj.order_id, True
+        else:
+            log.warning(f"订单提交失败: {security} {direction} {quantity}手")
+            return None, False
+            
+    except Exception as e:
+        log.warning(f"开仓异常 {security}: {str(e)}")
+    
+    return None, False
+
+def close_position(context, security, quantity, direction, reason=''):
+    """平仓
+    
+    注:平仓使用order_target是安全的,因为通常不会有多个平仓订单同时进行
+    订单提交后filled=0,实际成交信息需要在收盘后通过get_trades()获取
+    """
+    try:
+        log.info(f"提交平仓订单: {security} {direction} {quantity}手 - {reason}")
+        
+        # 使用order_target平仓
+        subportfolio = context.subportfolios[0]
+        current_position = 0
+        if direction == 'long' and security in subportfolio.long_positions:
+            current_position = subportfolio.long_positions[security].total_amount
+        elif direction == 'short' and security in subportfolio.short_positions:
+            current_position = subportfolio.short_positions[security].total_amount
+        
+        target_quantity = max(0, current_position - quantity)
+        order_obj = order_target(security, target_quantity, side=direction)
+        
+        if order_obj is not None:
+            log.info(f"平仓订单已提交: order_id={order_obj.order_id}, 状态={order_obj.status}, "
+                    f"已成交={order_obj.filled}/{quantity}手")
+            return True
+        else:
+            log.warning(f"平仓订单提交失败: {security} {direction} {quantity}手")
+            return False
+            
+    except Exception as e:
+        log.warning(f"平仓异常 {security}: {str(e)}")
+    
+    return False
+
+############################ 辅助函数 ###################################
+
+def get_futures_config(underlying_symbol, config_key=None, default_value=None):
+    """获取期货品种配置信息的辅助函数"""
+    if underlying_symbol not in g.futures_config:
+        if config_key and default_value is not None:
+            return default_value
+        return {}
+    
+    if config_key is None:
+        return g.futures_config[underlying_symbol]
+    
+    return g.futures_config[underlying_symbol].get(config_key, default_value)
+
+def is_night_session_symbol(symbol):
+    """判断品种是否为夜盘品种
+    
+    Args:
+        symbol: 期货品种代码(如'IF'、'AU'等)
+    
+    Returns:
+        bool: True表示夜盘品种,False表示日盘品种
+    """
+    trading_start_time = get_futures_config(symbol, 'trading_start_time', '09:00')
+    
+    # 解析配置的时间
+    try:
+        start_hour, _ = map(int, trading_start_time.split(':'))
+        # 如果开始时间 >= 21:00,则为夜盘品种
+        return start_hour >= 21
+    except (ValueError, AttributeError):
+        log.warning(f"{symbol} 交易开始时间配置格式错误: {trading_start_time},默认判断为日盘品种")
+        return False
+
+def is_trading_time_reached(context, symbol):
+    """判断当前时间是否已达到该品种的交易开始时间
+    
+    Args:
+        context: 上下文对象
+        symbol: 期货品种代码(如'IF'、'AU'等)
+    
+    Returns:
+        bool: True表示已达到交易时间,False表示未达到
+    """
+    # 获取配置的交易开始时间
+    trading_start_time = get_futures_config(symbol, 'trading_start_time', '09:05')
+    
+    # 解析配置的时间(格式为 "HH:MM")
+    try:
+        start_hour, start_minute = map(int, trading_start_time.split(':'))
+        configured_start_time = time(start_hour, start_minute)
+    except (ValueError, AttributeError):
+        log.warning(f"{symbol} 交易开始时间配置格式错误: {trading_start_time},默认使用09:05")
+        configured_start_time = time(9, 5)
+    
+    # 获取当前时间
+    current_time = context.current_dt.time()
+    
+    # 判断逻辑:
+    # 1. 如果配置时间是夜盘时间(>=21:00),且当前时间也在夜盘时段(>=21:00 或 <03:00),直接比较
+    # 2. 如果配置时间是日盘时间(<21:00),且当前时间也在日盘时段(09:00-15:00),直接比较
+    # 3. 其他情况返回False(例如日盘品种在夜盘时段)
+    
+    is_night_start = configured_start_time >= time(21, 0)
+    is_current_night = current_time >= time(21, 0) or current_time < time(3, 0)
+    is_current_day = time(9, 0) <= current_time <= time(15, 0)
+    
+    if is_night_start:
+        # 配置为夜盘开始时间
+        if is_current_night:
+            # 当前也在夜盘时段
+            if current_time >= time(21, 0):
+                # 当前在21:00之后
+                result = current_time >= configured_start_time
+            else:
+                # 当前在凌晨(已经过了配置的开始时间)
+                result = True
+            # log.debug(f"[时间检查] {symbol} 配置夜盘开始 {trading_start_time}, 当前时间 {current_time.strftime('%H:%M:%S')}, 判断结果: {result}")
+            return result
+        elif is_current_day:
+            # 夜盘品种在日盘时段,允许交易
+            # log.debug(f"[时间检查] {symbol} 夜盘品种在日盘时段 {current_time.strftime('%H:%M:%S')}, 允许交易")
+            return True
+        else:
+            # 其他时段不允许交易
+            # log.debug(f"[时间检查] {symbol} 夜盘品种在非交易时段 {current_time.strftime('%H:%M:%S')}, 不允许交易")
+            return False
+    else:
+        # 配置为日盘开始时间
+        if is_current_night:
+            # 日盘品种在夜盘时段,不允许交易
+            # log.debug(f"[时间检查] {symbol} 日盘品种在夜盘时段 {current_time.strftime('%H:%M:%S')}, 不允许交易")
+            return False
+        elif is_current_day:
+            # 当前在日盘时段,比较时间
+            result = current_time >= configured_start_time
+            # log.debug(f"[时间检查] {symbol} 配置日盘开始 {trading_start_time}, 当前时间 {current_time.strftime('%H:%M:%S')}, 判断结果: {result}")
+            return result
+        else:
+            # 其他时段(如午休、收盘后)不允许建仓,但允许止盈止损
+            # log.debug(f"[时间检查] {symbol} 日盘品种在非开盘时段 {current_time.strftime('%H:%M:%S')}, 不允许交易")
+            return False
+
+def get_margin_rate(underlying_symbol, direction, default_rate=0.10):
+    """获取保证金比例的辅助函数"""
+    return g.futures_config.get(underlying_symbol, {}).get('margin_rate', {}).get(direction, default_rate)
+
+def get_multiplier(underlying_symbol, default_multiplier=10):
+    """获取合约乘数的辅助函数"""
+    return g.futures_config.get(underlying_symbol, {}).get('multiplier', default_multiplier)
+
+def after_market_close(context):
+    """收盘后运行函数
+    
+    使用get_trades()获取当日所有成交记录
+    使用get_open_orders()获取所有未成交订单
+    """
+    log.info(str('函数运行时间(after_market_close):'+str(context.current_dt.time())))
+    
+    # 获取当日所有成交记录
+    trades = get_trades()
+    
+    # 获取当日所有未完成订单
+    open_orders = get_open_orders()
+    
+    # 打印交易汇总
+    print_daily_trading_summary(context, trades, open_orders)
+    
+    log.info('##############################################################')
+
+def print_daily_trading_summary(context, trades, open_orders):
+    """打印当日交易汇总
+    
+    Args:
+        context: 上下文对象
+        trades: 当日成交记录字典 {trade_id: UserTrade对象}
+        open_orders: 未完成订单字典 {order_id: Order对象}
+        
+    注:UserTrade对象只有基本属性(trade_id, order_id, time, amount, price)
+        需要通过order_id关联订单来获取完整信息
+    """
+    log.info("=" * 60)
+    log.info("当日交易汇总")
+    log.info("=" * 60)
+    current_data = {}
+    try:
+        current_data = get_current_data()
+    except Exception as e:
+        log.warning(f"获取当前行情数据失败: {str(e)}")
+        current_data = {}
+
+    def format_datetime_value(dt_value):
+        if hasattr(dt_value, 'to_pydatetime'):
+            dt_value = dt_value.to_pydatetime()
+        if isinstance(dt_value, datetime):
+            return dt_value.strftime('%Y-%m-%d %H:%M:%S')
+        if isinstance(dt_value, date):
+            return datetime.combine(dt_value, datetime.min.time()).strftime('%Y-%m-%d %H:%M:%S')
+        return str(dt_value) if dt_value is not None else ''
+    
+    # 统计成交记录
+    if trades:
+        log.info(f"\n【成交记录】共 {len(trades)} 笔")
+        log.info("-" * 60)
+        
+        # 获取所有订单(包括已完成和未完成的)用于关联成交记录
+        all_orders = get_orders()
+        
+        for trade_id, trade in trades.items():
+            # 从trade获取基本信息
+            security = None
+            
+            # 尝试通过order_id获取订单信息
+            order_info = None
+            if hasattr(trade, 'order_id') and trade.order_id in all_orders:
+                order_info = all_orders[trade.order_id]
+                security = order_info.security
+            
+            # 如果无法获取security,跳过这条记录
+            trade_time = getattr(trade, 'time', None)
+            trade_datetime_str = format_datetime_value(trade_time)
+
+            if not security:
+                log.info(f"成交记录 trade_id={trade_id}: {trade.amount}手 @{trade.price:.2f} "
+                        f"成交日期时间:{trade_datetime_str} (无法获取详细信息)")
+                continue
+            
+            underlying_symbol = security.split('.')[0][:-4]
+            dominant_code = None
+            if hasattr(g, 'dominant_contracts'):
+                dominant_code = g.dominant_contracts.get(underlying_symbol)
+            display_symbol = dominant_code if dominant_code else security.split('.')[0]
+            
+            # 从订单获取action和side信息
+            if order_info:
+                action_str = "开仓" if order_info.action == "open" else "平仓"
+                side_str = "多" if order_info.side == "long" else "空"
+                
+                # 获取策略信息
+                strategy_str = ""
+                if hasattr(g, 'order_strategy_info') and trade.order_id in g.order_strategy_info:
+                    strategy_info = g.order_strategy_info[trade.order_id]
+                    strategy_type = strategy_info.get('strategy_type', '未知')
+                    target_price = strategy_info.get('target_price', 0)
+                    strategy_str = f", 策略:{strategy_type}, 策略对应价格:{target_price}"
+                
+                # 获取当前价格
+                try:
+                    current_price = current_data[security].last_price
+                except Exception:
+                    current_price = 0
+                
+                log.info(f"{action_str}{side_str}: {display_symbol}, "
+                            f"数量:{trade.amount}手, 成交价:{trade.price:.2f}{strategy_str}, "
+                            f"标的价格:{current_price:.2f}, "
+                            f"成交日期时间:{trade_datetime_str}")
+            else:
+                log.info(f"成交: {underlying_symbol} {trade.amount}手 @{trade.price:.2f} "
+                        f"成交日期时间:{trade_datetime_str}")
+        
+        log.info("-" * 60)
+    else:
+        log.info("\n【成交记录】无")
+    
+    # 统计未完成订单
+    if open_orders:
+        log.info(f"\n【未完成订单】共 {len(open_orders)} 个")
+        log.info("-" * 60)
+        
+        for order_id, order_obj in open_orders.items():
+            dominant_code = None
+            try:
+                underlying_symbol = order_obj.security.split('.')[0][:-4]
+                full_symbol = order_obj.security.split('.')[0]
+                if hasattr(g, 'dominant_contracts'):
+                    dominant_code = g.dominant_contracts.get(underlying_symbol)
+            except Exception:
+                underlying_symbol = order_obj.security
+            display_symbol = dominant_code if dominant_code else full_symbol
+            action_str = "开仓" if order_obj.action == "open" else "平仓"
+            side_str = "多" if order_obj.side == "long" else "空"
+            
+            # 判断订单类型
+            order_type = "市价单"
+            limit_price_str = ""
+            if hasattr(order_obj, 'style') and order_obj.style:
+                if hasattr(order_obj.style, 'limit_price') and order_obj.style.limit_price > 0:
+                    order_type = "限价单"
+                    limit_price_str = f" @{order_obj.style.limit_price:.2f}"
+            
+            log.info(f"{action_str}{side_str}: {display_symbol} "
+                    f"{order_obj.amount}手 {order_type}{limit_price_str} "
+                    f"状态:{order_obj.status}")
+        
+        log.info("-" * 60)
+    else:
+        log.info("\n【未完成订单】无")
+    
+    # 打印持仓信息
+    log.info("\n【当前持仓】")
+    log.info("-" * 60)
+    subportfolio = context.subportfolios[0]
+    holdings_logged = False
+    
+    for security, position in subportfolio.long_positions.items():
+        if position.total_amount <= 0:
+            continue
+        underlying_symbol = security.split('.')[0][:-4]
+        full_symbol = security.split('.')[0]
+        dominant_code = None
+        if hasattr(g, 'dominant_contracts'):
+            dominant_code = g.dominant_contracts.get(underlying_symbol)
+        display_symbol = dominant_code if dominant_code else full_symbol
+        try:
+            current_price = current_data[security].last_price
+        except Exception:
+            current_price = 0
+        symbol_logged = False
+        base_entries = g.base_positions.get(security, {}) if hasattr(g, 'base_positions') else {}
+        for level_key, entry_info in sorted(base_entries.items()):
+            quantity = entry_info.get('quantity', 0)
+            if quantity <= 0:
+                continue
+            entry_price = float(entry_info.get('entry_price', 0))
+            entry_date = entry_info.get('entry_date')
+            log.info(f"标的种类: {display_symbol}, 数量:{quantity}手, 成交价:{entry_price:.2f}, "
+                    f"策略:基础左侧, 策略对应价格:{level_key}, 当前标的价格:{current_price:.2f}, "
+                    f"成交日期时间: {format_datetime_value(entry_date)}")
+            symbol_logged = True
+            holdings_logged = True
+        grid_entries = g.grid_positions.get(security, []) if hasattr(g, 'grid_positions') else []
+        for entry_info in sorted(grid_entries, key=lambda x: x.get('target_price', 0), reverse=True):
+            quantity = entry_info.get('quantity', 0)
+            if quantity <= 0:
+                continue
+            entry_price = float(entry_info.get('entry_price', 0))
+            target_price = entry_info.get('target_price', 0)
+            entry_date = entry_info.get('entry_date')
+            log.info(f"标的种类: {display_symbol}, 数量:{quantity}手, 成交价:{entry_price:.2f}, "
+                    f"策略:网格, 策略对应价格:{target_price}, 当前标的价格:{current_price:.2f}, "
+                    f"成交日期时间: {format_datetime_value(entry_date)}")
+            symbol_logged = True
+            holdings_logged = True
+        if not symbol_logged:
+            log.info(f"标的种类: {display_symbol}, 数量:{position.total_amount}手, 成交价:{position.avg_cost:.2f}, "
+                    f"策略:未分类, 策略对应价格:--, 当前标的价格:{current_price:.2f}, 成交日期时间: --")
+            holdings_logged = True
+    
+    for security, position in subportfolio.short_positions.items():
+        if position.total_amount <= 0:
+            continue
+        underlying_symbol = security.split('.')[0][:-4]
+        dominant_code = None
+        if hasattr(g, 'dominant_contracts'):
+            dominant_code = g.dominant_contracts.get(underlying_symbol)
+        display_symbol = dominant_code if dominant_code else full_symbol
+        try:
+            current_price = current_data[security].last_price
+        except Exception:
+            current_price = 0
+        symbol_logged = False
+        hedge_entries = g.hedge_positions.get(security, {}) if hasattr(g, 'hedge_positions') else {}
+        for level_key, entry_info in sorted(hedge_entries.items()):
+            quantity = entry_info.get('quantity', 0)
+            if quantity <= 0:
+                continue
+            entry_price = float(entry_info.get('entry_price', 0))
+            entry_date = entry_info.get('entry_date')
+            log.info(f"标的种类: {display_symbol}, 数量:{quantity}手, 成交价:{entry_price:.2f}, "
+                    f"策略:对冲, 策略对应价格:{level_key}, 当前标的价格:{current_price:.2f}, "
+                    f"成交日期时间: {format_datetime_value(entry_date)}")
+            symbol_logged = True
+            holdings_logged = True
+        if not symbol_logged:
+            log.info(f"标的种类: {display_symbol}, 数量:{position.total_amount}手, 成交价:{position.avg_cost:.2f}, "
+                    f"策略:未分类(空), 策略对应价格:--, 当前标的价格:{current_price:.2f}, 成交日期时间: --")
+            holdings_logged = True
+    
+    if not holdings_logged:
+        log.info("无持仓")
+    
+    log.info("-" * 60)
+    log.info(f"账户总资产: {context.portfolio.total_value:.2f}")
+    log.info(f"可用资金: {context.portfolio.available_cash:.2f}")
+    log.info("=" * 60)
+
+########################## 自动移仓换月函数 #################################
+def position_auto_switch(context, pindex=0, switch_func=None, callback=None):
+    """
+    期货自动移仓换月。默认使用市价单进行开平仓。
+    """
+    import re
+    subportfolio = context.subportfolios[pindex]
+    symbols = set(subportfolio.long_positions.keys()) | set(subportfolio.short_positions.keys())
+    switch_result = []
+    switch_failed = []
+    for symbol in symbols:
+        match = re.match(r"(?P<underlying_symbol>[A-Z]{1,})", symbol)
+        if not match:
+            raise ValueError("未知期货标的: {}".format(symbol))
+        else:
+            underlying_symbol = match.groupdict()["underlying_symbol"]
+            
+            # 时间检查:判断是否已达到该品种的交易开始时间
+            if not is_trading_time_reached(context, underlying_symbol):
+                log.info(f"换月操作跳过: {underlying_symbol} 未达到交易开始时间")
+                continue
+            
+            dominant = get_dominant_future(underlying_symbol)
+            cur = get_current_data()
+            symbol_last_price = cur[symbol].last_price
+            dominant_last_price = cur[dominant].last_price
+            # log.debug(f'检查换月中,当前持仓合约: {symbol}, 当前主力合约: {dominant}')
+            
+            if dominant > symbol:
+                for positions_ in (subportfolio.long_positions, subportfolio.short_positions):
+                    if symbol not in positions_.keys():
+                        continue
+                    else :
+                        p = positions_[symbol]
+
+                    if switch_func is not None:
+                        switch_func(context, pindex, p, dominant)
+                    else:
+                        amount = p.total_amount
+                        # 跌停不能开空和平多,涨停不能开多和平空。
+                        if p.side == "long":
+                            symbol_low_limit = cur[symbol].low_limit
+                            dominant_high_limit = cur[dominant].high_limit
+                            if symbol_last_price <= symbol_low_limit:
+                                log.warning("标的{}跌停,无法平仓。移仓换月取消。".format(symbol))
+                                switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "limit_close_failed"})
+                                break
+                            elif dominant_last_price >= dominant_high_limit:
+                                log.warning("标的{}涨停,无法开仓。移仓换月取消。".format(dominant))
+                                switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "limit_open_failed"})
+                                break
+                            else:
+                                log.info("进行移仓换月: ({0},long) -> ({1},long)".format(symbol, dominant))
+                                order_old = order_target(symbol, 0, side='long')
+                                if order_old != None and order_old.filled > 0:
+                                    order_new = order_target(dominant, amount, side='long')
+                                    if order_new != None and order_new.filled > 0:
+                                        switch_result.append({"before": symbol, "after": dominant, "side": "long"})
+                                        # 换月成功,更新持仓记录
+                                        update_positions_after_switch(symbol, dominant, 'long')
+                                    else:
+                                        log.warning("标的{}交易失败,无法开仓。移仓换月失败。".format(dominant))
+                                        switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "open_order_failed"})
+                                        break
+                                else:
+                                    log.info("标的{}平仓委托未成交,移仓换月失败,等待下次重试。".format(symbol))
+                                    switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "close_order_not_filled"})
+                                    break
+                        if switch_failed:
+                            break
+                        if p.side == "short":
+                            symbol_high_limit = cur[symbol].high_limit
+                            dominant_low_limit = cur[dominant].low_limit
+                            if symbol_last_price >= symbol_high_limit:
+                                log.warning("标的{}涨停,无法平仓。移仓换月取消。".format(symbol))
+                                switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "limit_close_failed"})
+                                break
+                            elif dominant_last_price <= dominant_low_limit:
+                                log.warning("标的{}跌停,无法开仓。移仓换月取消。".format(dominant))
+                                switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "limit_open_failed"})
+                                break
+                            else:
+                                log.info("进行移仓换月: ({0},short) -> ({1},short)".format(symbol, dominant))
+                                order_old = order_target(symbol, 0, side='short')
+                                if order_old != None and order_old.filled > 0:
+                                    order_new = order_target(dominant, amount, side='short')
+                                    if order_new != None and order_new.filled > 0:
+                                        switch_result.append({"before": symbol, "after": dominant, "side": "short"})
+                                        # 换月成功,更新持仓记录
+                                        update_positions_after_switch(symbol, dominant, 'short')
+                                    else:
+                                        log.warning("标的{}交易失败,无法开仓。移仓换月失败。".format(dominant))
+                                        switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "open_order_failed"})
+                                        break
+                                else:
+                                    log.info("标的{}平仓委托未成交,移仓换月失败,等待下次重试。".format(symbol))
+                                    switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "close_order_not_filled"})
+                                    break
+                        if callback:
+                            callback(context, pindex, p, dominant)
+                        if switch_failed:
+                            break
+                if switch_failed:
+                    break
+        if switch_failed:
+            break
+    return switch_result, switch_failed
+
+def update_positions_after_switch(old_symbol, new_symbol, side):
+    """合约切换后更新持仓记录"""
+    # 更新底仓持仓记录
+    if old_symbol in g.base_positions:
+        g.base_positions[new_symbol] = g.base_positions[old_symbol]
+        del g.base_positions[old_symbol]
+        log.info(f"底仓持仓记录更新: {old_symbol} -> {new_symbol}")
+    
+    # 更新底仓待成交限价单记录(问题2修复)
+    if old_symbol in g.base_pending_orders:
+        g.base_pending_orders[new_symbol] = g.base_pending_orders[old_symbol]
+        del g.base_pending_orders[old_symbol]
+        log.info(f"底仓待成交限价单记录更新: {old_symbol} -> {new_symbol}")
+    
+    # 更新网格持仓记录
+    if old_symbol in g.grid_positions:
+        g.grid_positions[new_symbol] = g.grid_positions[old_symbol]
+        del g.grid_positions[old_symbol]
+        log.info(f"网格持仓记录更新: {old_symbol} -> {new_symbol}")
+    
+    if old_symbol in g.grid_buy_levels:
+        g.grid_buy_levels[new_symbol] = g.grid_buy_levels[old_symbol]
+        del g.grid_buy_levels[old_symbol]
+    
+    # 更新对冲持仓记录
+    if old_symbol in g.hedge_positions:
+        g.hedge_positions[new_symbol] = g.hedge_positions[old_symbol]
+        del g.hedge_positions[old_symbol]
+        log.info(f"对冲持仓记录更新: {old_symbol} -> {new_symbol}")
+    
+    if old_symbol in g.hedge_state:
+        g.hedge_state[new_symbol] = g.hedge_state[old_symbol]
+        del g.hedge_state[old_symbol]
+

+ 1163 - 0
Lib/future/MAPatternStrategy_v001.py

@@ -0,0 +1,1163 @@
+# 导入函数库
+from jqdata import *
+from jqdata import finance
+import pandas as pd
+import numpy as np
+from datetime import date, datetime, timedelta, time
+import re
+
+# 顺势交易策略 v001
+# 基于均线走势(前提条件)+ K线形态(开盘价差、当天价差)的期货交易策略
+#
+# 核心逻辑:
+# 1. 开盘时检查均线走势(MA30<=MA20<=MA10<=MA5为多头,反之为空头)
+# 2. 检查开盘价差是否符合方向要求(多头>=0.5%,空头<=-0.5%)
+# 3. 14:35和14:55检查当天价差(多头>0,空头<0),满足条件则开仓
+# 4. 应用固定止损和动态追踪止盈
+# 5. 自动换月移仓
+
+# 设置以便完整打印 DataFrame
+pd.set_option('display.max_rows', None)
+pd.set_option('display.max_columns', None)
+pd.set_option('display.width', None)
+pd.set_option('display.max_colwidth', 20)
+
+## 初始化函数,设定基准等等
+def initialize(context):
+    # 设定沪深300作为基准
+    set_benchmark('000300.XSHG')
+    # 开启动态复权模式(真实价格)
+    set_option('use_real_price', True)
+    # 输出内容到日志
+    log.info('=' * 60)
+    log.info('均线形态交易策略 v001 初始化开始')
+    log.info('策略类型: 均线走势 + K线形态')
+    log.info('=' * 60)
+
+    ### 期货相关设定 ###
+    # 设定账户为金融账户
+    set_subportfolios([SubPortfolioConfig(cash=context.portfolio.starting_cash, type='index_futures')])
+    # 期货类每笔交易时的手续费是: 买入时万分之0.23,卖出时万分之0.23,平今仓为万分之23
+    set_order_cost(OrderCost(open_commission=0.000023, close_commission=0.000023, close_today_commission=0.0023), type='index_futures')
+    
+    # 设置期货交易的滑点
+    set_slippage(StepRelatedSlippage(2))
+    
+    # 初始化全局变量
+    g.usage_percentage = 0.8  # 最大资金使用比例
+    g.max_margin_per_position = 20000  # 单个标的最大持仓保证金(元)
+    
+    # 均线策略参数
+    g.ma_periods = [5, 10, 20, 30]  # 均线周期
+    g.ma_historical_days = 60  # 获取历史数据天数(确保足够计算MA30)
+    g.ma_open_gap_threshold = 0.002  # 方案1开盘价差阈值(0.2%)
+    g.ma_pattern_lookback_days = 10  # 历史均线模式一致性检查的天数
+    g.ma_pattern_consistency_threshold = 0.8  # 历史均线模式一致性阈值(80%)
+    g.check_intraday_spread = False  # 是否检查日内价差(True: 检查, False: 跳过)
+    g.ma_proximity_min_threshold = 8  # MA5与MA10贴近计数和的最低阈值
+    
+    # 均线价差策略方案选择
+    g.ma_gap_strategy_mode = 2  # 策略模式选择(1: 原方案, 2: 新方案)
+    g.ma_open_gap_threshold2 = 0.002  # 方案2开盘价差阈值(0.2%)
+    g.ma_intraday_threshold_scheme2 = 0.005  # 方案2日内变化阈值(0.5%)
+    
+    # 止损止盈策略参数
+    g.fixed_stop_loss_rate = 0.01  # 固定止损比率(1%)
+    g.ma_offset_ratio_normal = 0.003  # 均线跟踪止盈常规偏移量(0.3%)
+    g.ma_offset_ratio_close = 0.01  # 均线跟踪止盈收盘前偏移量(1%)
+    g.days_for_adjustment = 4  # 持仓天数调整阈值
+    
+    # 输出策略参数
+    log.info("均线形态策略参数:")
+    log.info(f"  均线周期: {g.ma_periods}")
+    log.info(f"  策略模式: 方案{g.ma_gap_strategy_mode}")
+    log.info(f"  方案1开盘价差阈值: {g.ma_open_gap_threshold:.1%}")
+    log.info(f"  方案2开盘价差阈值: {g.ma_open_gap_threshold2:.1%}")
+    log.info(f"  方案2日内变化阈值: {g.ma_intraday_threshold_scheme2:.1%}")
+    log.info(f"  历史均线模式检查天数: {g.ma_pattern_lookback_days}天")
+    log.info(f"  历史均线模式一致性阈值: {g.ma_pattern_consistency_threshold:.1%}")
+    log.info(f"  均线贴近计数阈值: {g.ma_proximity_min_threshold}")
+    log.info(f"  是否检查日内价差: {g.check_intraday_spread}")
+    log.info(f"  固定止损: {g.fixed_stop_loss_rate:.1%}")
+    log.info(f"  均线跟踪止盈常规偏移: {g.ma_offset_ratio_normal:.1%}")
+    log.info(f"  均线跟踪止盈收盘前偏移: {g.ma_offset_ratio_close:.1%}")
+    log.info(f"  持仓天数调整阈值: {g.days_for_adjustment}天")
+    
+    # 期货品种完整配置字典
+    g.futures_config = {
+        # 贵金属
+        'AU': {'has_night_session': True, 'margin_rate': {'long': 0.21, 'short': 0.21}, 'multiplier': 1000, 'trading_start_time': '21:00'},
+        'AG': {'has_night_session': True, 'margin_rate': {'long': 0.22, 'short': 0.22}, 'multiplier': 15, 'trading_start_time': '21:00'},
+        
+        # 有色金属
+        'CU': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'AL': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'ZN': {'has_night_session': True, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'PB': {'has_night_session': True, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'NI': {'has_night_session': True, 'margin_rate': {'long': 0.16, 'short': 0.16}, 'multiplier': 1, 'trading_start_time': '21:00'},
+        'SN': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 1, 'trading_start_time': '21:00'},
+        'SS': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        
+        # 黑色系
+        'RB': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'HC': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'I': {'has_night_session': True, 'margin_rate': {'long': 0.16, 'short': 0.16}, 'multiplier': 100, 'trading_start_time': '21:00'},
+        'JM': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 100, 'trading_start_time': '21:00'},
+        'J': {'has_night_session': True, 'margin_rate': {'long': 0.25, 'short': 0.25}, 'multiplier': 60, 'trading_start_time': '21:00'},
+        
+        # 能源化工
+        'SP': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'FU': {'has_night_session': True, 'margin_rate': {'long': 0.16, 'short': 0.16}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'BU': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'RU': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'BR': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'SC': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 1000, 'trading_start_time': '21:00'},
+        'NR': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'LU': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'LC': {'has_night_session': False, 'margin_rate': {'long': 0.16, 'short': 0.16}, 'multiplier': 1, 'trading_start_time': '09:00'},
+        
+        # 化工
+        'FG': {'has_night_session': True, 'margin_rate': {'long': 0.16, 'short': 0.16}, 'multiplier': 20, 'trading_start_time': '21:00'},
+        'TA': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'MA': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'SA': {'has_night_session': True, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 20, 'trading_start_time': '21:00'},
+        'L': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'V': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'EG': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'PP': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'EB': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'PG': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 20, 'trading_start_time': '21:00'},
+        
+        # 农产品
+        'RM': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'OI': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'CF': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'SR': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'PF': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'C': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'CS': {'has_night_session': True, 'margin_rate': {'long': 0.11, 'short': 0.11}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'CY': {'has_night_session': True, 'margin_rate': {'long': 0.11, 'short': 0.11}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'A': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'B': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'M': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'Y': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'P': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        
+        # 无夜盘品种
+        'IF': {'has_night_session': False, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 300, 'trading_start_time': '09:30'},
+        'IH': {'has_night_session': False, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 300, 'trading_start_time': '09:30'},
+        'IC': {'has_night_session': False, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 200, 'trading_start_time': '09:30'},
+        'IM': {'has_night_session': False, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 200, 'trading_start_time': '09:30'},
+        'AP': {'has_night_session': False, 'margin_rate': {'long': 0.16, 'short': 0.16}, 'multiplier': 10, 'trading_start_time': '09:00'},
+        'CJ': {'has_night_session': False, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 5, 'trading_start_time': '09:00'},
+        'PK': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 5, 'trading_start_time': '09:00'},
+        'JD': {'has_night_session': False, 'margin_rate': {'long': 0.11, 'short': 0.11}, 'multiplier': 10, 'trading_start_time': '09:00'},
+        'LH': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 16, 'trading_start_time': '09:00'},
+        'T': {'has_night_session': False, 'margin_rate': {'long': 0.03, 'short': 0.03}, 'multiplier': 1000000, 'trading_start_time': '09:30'},
+        'PS': {'has_night_session': False, 'margin_rate': {'long': 0.16, 'short': 0.16}, 'multiplier': 3, 'trading_start_time': '09:00'},
+        'UR': {'has_night_session': False, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 20, 'trading_start_time': '09:00'},
+        'MO': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 100, 'trading_start_time': '21:00'},
+        'LF': {'has_night_session': False, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1, 'trading_start_time': '09:30'},
+        'HO': {'has_night_session': False, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 100, 'trading_start_time': '09:30'},
+        'LR': {'has_night_session': True, 'margin_rate': {'long': 0.21, 'short': 0.21}, 'multiplier': 1, 'trading_start_time': '21:00'},
+        'LG': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 1, 'trading_start_time': '21:00'},
+        'FB': {'has_night_session': True, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 1, 'trading_start_time': '21:00'},
+        'PX': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1, 'trading_start_time': '21:00'},
+        'PM': {'has_night_session': True, 'margin_rate': {'long': 0.2, 'short': 0.2}, 'multiplier': 1, 'trading_start_time': '21:00'},
+        'EC': {'has_night_session': False, 'margin_rate': {'long': 0.23, 'short': 0.23}, 'multiplier': 50, 'trading_start_time': '09:00'},
+        'RR': {'has_night_session': True, 'margin_rate': {'long': 0.11, 'short': 0.11}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'OP': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 40, 'trading_start_time': '09:00'},
+        'IO': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 1, 'trading_start_time': '21:00'},
+        'BC': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'WH': {'has_night_session': False, 'margin_rate': {'long': 0.2, 'short': 0.2}, 'multiplier': 20, 'trading_start_time': '09:00'},
+        'SH': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 30, 'trading_start_time': '21:00'},
+        'RI': {'has_night_session': False, 'margin_rate': {'long': 0.21, 'short': 0.21}, 'multiplier': 20, 'trading_start_time': '09:00'},
+        'TS': {'has_night_session': False, 'margin_rate': {'long': 0.015, 'short': 0.015}, 'multiplier': 2000000, 'trading_start_time': '09:30'},
+        'JR': {'has_night_session': False, 'margin_rate': {'long': 0.21, 'short': 0.21}, 'multiplier': 20, 'trading_start_time': '09:00'},
+        'AD': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '09:00'},
+        'BB': {'has_night_session': False, 'margin_rate': {'long': 0.19, 'short': 0.19}, 'multiplier': 500, 'trading_start_time': '09:00'},
+        'PL': {'has_night_session': False, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 20, 'trading_start_time': '09:00'},
+        'RS': {'has_night_session': False, 'margin_rate': {'long': 0.26, 'short': 0.26}, 'multiplier': 10, 'trading_start_time': '09:00'},
+        'SI': {'has_night_session': False, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '09:00'},
+        'ZC': {'has_night_session': True, 'margin_rate': {'long': 0.56, 'short': 0.56}, 'multiplier': 100, 'trading_start_time': '21:00'},
+        'SM': {'has_night_session': False, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '09:00'},
+        'AO': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 20, 'trading_start_time': '21:00'},
+        'TL': {'has_night_session': False, 'margin_rate': {'long': 0.045, 'short': 0.045}, 'multiplier': 1000000, 'trading_start_time': '09:00'},
+        'SF': {'has_night_session': False, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 5, 'trading_start_time': '09:00'},
+        'WR': {'has_night_session': False, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 10, 'trading_start_time': '09:00'},
+        'PR': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 15, 'trading_start_time': '21:00'},
+        'TF': {'has_night_session': False, 'margin_rate': {'long': 0.022, 'short': 0.022}, 'multiplier': 1000000, 'trading_start_time': '09:00'},
+        'VF': {'has_night_session': False, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1, 'trading_start_time': '09:00'},
+        'BZ': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 30, 'trading_start_time': '09:00'},
+    }
+    
+    # 策略品种选择策略配置
+    # 方案1:全品种策略 - 考虑所有配置的期货品种
+    g.strategy_focus_symbols = ['IC', 'LH']  # 空列表表示考虑所有品种
+    
+    # 方案2:精选品种策略 - 只交易流动性较好的特定品种(如需使用请取消下行注释)
+    # g.strategy_focus_symbols = ['RM', 'CJ', 'CY', 'JD', 'L', 'LC', 'SF', 'SI']
+    
+    log.info(f"品种选择策略: {'全品种策略(覆盖所有配置品种)' if not g.strategy_focus_symbols else '精选品种策略(' + str(len(g.strategy_focus_symbols)) + '个品种)'}")
+    
+    # 交易记录和数据存储
+    g.trade_history = {}  # 持仓记录 {symbol: {'entry_price': xxx, 'direction': xxx, ...}}
+    g.daily_ma_candidates = {}  # 通过均线和开盘价差检查的候选品种 {symbol: {'direction': 'long'/'short', 'open_price': xxx, ...}}
+    g.today_trades = []  # 当日交易记录
+    g.excluded_contracts = {}  # 每日排除的合约缓存 {dominant_future: {'reason': 'ma_trend'/'open_gap', 'trading_day': xxx}}
+    g.ma_checked_underlyings = {}  # 记录各品种在交易日的均线检查状态 {symbol: trading_day}
+    g.last_ma_trading_day = None  # 最近一次均线检查所属交易日
+    
+    # 定时任务设置
+    # 夜盘开始(21:05) - 均线和开盘价差检查
+    run_daily(check_ma_trend_and_open_gap, time='21:05:00', reference_security='IF1808.CCFX')
+    
+    # 日盘开始 - 均线和开盘价差检查
+    run_daily(check_ma_trend_and_open_gap, time='09:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_ma_trend_and_open_gap, time='09:35:00', reference_security='IF1808.CCFX')
+    
+    # 盘中价差检查和开仓(14:35和14:55)
+    run_daily(check_intraday_price_diff, time='14:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_intraday_price_diff, time='14:55:00', reference_security='IF1808.CCFX')
+    
+    # 夜盘止损止盈检查
+    run_daily(check_stop_loss_profit, time='21:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='21:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='22:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='22:35:00', reference_security='IF1808.CCFX')
+    
+    # 日盘止损止盈检查
+    run_daily(check_stop_loss_profit, time='09:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='09:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='10:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='10:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='11:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='11:25:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='13:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='14:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='14:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='14:55:00', reference_security='IF1808.CCFX')
+    
+    # 收盘后
+    run_daily(after_market_close, time='15:30:00', reference_security='IF1808.CCFX')
+    
+    log.info('=' * 60)
+
+############################ 主程序执行函数 ###################################
+
+def get_current_trading_day(current_dt):
+    """根据当前时间推断对应的期货交易日"""
+    current_date = current_dt.date()
+    current_time = current_dt.time()
+
+    trade_days = get_trade_days(end_date=current_date, count=1)
+    if trade_days and trade_days[0] == current_date:
+        trading_day = current_date
+    else:
+        next_days = get_trade_days(start_date=current_date, count=1)
+        trading_day = next_days[0] if next_days else current_date
+
+    if current_time >= time(20, 59):
+        next_trade_days = get_trade_days(start_date=trading_day, count=2)
+        if len(next_trade_days) >= 2:
+            return next_trade_days[1]
+        if len(next_trade_days) == 1:
+            return next_trade_days[0]
+    return trading_day
+
+
+def normalize_trade_day_value(value):
+    """将交易日对象统一转换为 datetime.date"""
+    if isinstance(value, date) and not isinstance(value, datetime):
+        return value
+    if isinstance(value, datetime):
+        return value.date()
+    if hasattr(value, 'to_pydatetime'):
+        return value.to_pydatetime().date()
+    try:
+        return pd.Timestamp(value).date()
+    except Exception:
+        return value
+
+
+def check_ma_trend_and_open_gap(context):
+    """阶段一:开盘时均线走势和开盘价差检查(一天一次)"""
+    log.info("=" * 60)
+    current_trading_day = get_current_trading_day(context.current_dt)
+    log.info(f"执行均线走势和开盘价差检查 - 时间: {context.current_dt}, 交易日: {current_trading_day}")
+    log.info("=" * 60)
+    
+    # 先检查换月移仓
+    position_auto_switch(context)
+    
+    # 检查是否进入新交易日,必要时清空缓存
+    if g.last_ma_trading_day != current_trading_day:
+        if g.excluded_contracts:
+            log.info(f"交易日切换至 {current_trading_day},清空上一交易日的排除缓存")
+        g.excluded_contracts = {}
+        g.ma_checked_underlyings = {}
+        g.last_ma_trading_day = current_trading_day
+
+    # 获取当前时间
+    current_time = str(context.current_dt.time())[:5]  # HH:MM格式
+    
+    # 筛选可交易品种(根据交易开始时间判断)
+    focus_symbols = g.strategy_focus_symbols if g.strategy_focus_symbols else list(g.futures_config.keys())
+    tradable_symbols = []
+    
+    # 根据当前时间确定可交易的时段
+    # 21:05 -> 仅接受21:00开盘的合约
+    # 09:05 -> 接受09:00或21:00开盘的合约
+    # 09:35 -> 接受所有时段(21:00, 09:00, 09:30)的合约
+    for symbol in focus_symbols:
+        trading_start_time = get_futures_config(symbol, 'trading_start_time', '09:05')
+        should_trade = False
+        
+        if current_time == '21:05':
+            # 夜盘开盘:仅接受21:00开盘的品种
+            should_trade = trading_start_time.startswith('21:00')
+        elif current_time == '09:05':
+            # 日盘早盘:接受21:00和09:00开盘的品种
+            should_trade = trading_start_time.startswith('21:00') or trading_start_time.startswith('09:00')
+        elif current_time == '09:35':
+            # 日盘晚开:接受所有品种(21:00, 09:00, 09:30)
+            should_trade = True
+        
+        if should_trade:
+            tradable_symbols.append(symbol)
+    
+    if not tradable_symbols:
+        log.info(f"当前时间 {current_time} 无品种开盘,跳过检查")
+        return
+    
+    log.info(f"当前时间 {current_time} 开盘品种: {tradable_symbols}")
+    
+    # 对每个品种执行均线和开盘价差检查
+    for symbol in tradable_symbols:
+        if g.ma_checked_underlyings.get(symbol) == current_trading_day:
+            log.info(f"{symbol} 已在交易日 {current_trading_day} 完成均线检查,跳过本次执行")
+            continue
+
+        try:
+            g.ma_checked_underlyings[symbol] = current_trading_day
+            # 获取主力合约
+            dominant_future = get_dominant_future(symbol)
+            # log.debug(f"{symbol} 主力合约: {dominant_future}")
+            if not dominant_future:
+                log.info(f"{symbol} 未找到主力合约,跳过")
+                continue
+            
+            # 检查是否在排除缓存中(当日已检查过但不符合条件)
+            if dominant_future in g.excluded_contracts:
+                excluded_info = g.excluded_contracts[dominant_future]
+                if excluded_info['trading_day'] == current_trading_day:
+                    # log.debug(f"{symbol} 在排除缓存中(原因: {excluded_info['reason']}),跳过")
+                    continue
+                else:
+                    # 新的一天,从缓存中移除(会在after_market_close统一清理,这里也做兜底)
+                    del g.excluded_contracts[dominant_future]
+            
+            # 检查是否已有持仓
+            if check_symbol_prefix_match(dominant_future, set(g.trade_history.keys())):
+                log.info(f"{symbol} 已有持仓,跳过")
+                continue
+            
+            # 获取历史数据(需要足够计算MA30)
+            # 使用get_price获取数据,可以正确处理夜盘品种
+            # 注意:historical_data最后一行是昨天的数据,不包含今天的数据
+            historical_data = get_price(dominant_future, end_date=context.current_dt, 
+                                       frequency='1d', fields=['open', 'close', 'high', 'low'], 
+                                       count=g.ma_historical_days)
+            
+            if historical_data is None or len(historical_data) < max(g.ma_periods):
+                log.info(f"{symbol} 历史数据不足,跳过")
+                continue
+
+            previous_trade_days = get_trade_days(end_date=current_trading_day, count=2)
+            previous_trade_days = [normalize_trade_day_value(d) for d in previous_trade_days]
+            previous_trading_day = None
+            if len(previous_trade_days) >= 2:
+                previous_trading_day = previous_trade_days[-2]
+            elif len(previous_trade_days) == 1 and previous_trade_days[0] < current_trading_day:
+                previous_trading_day = previous_trade_days[0]
+
+            if previous_trading_day is None:
+                log.info(f"{symbol} 无法确定前一交易日,跳过")
+                continue
+
+            historical_dates = historical_data.index.date
+            match_indices = np.where(historical_dates == previous_trading_day)[0]
+
+            if len(match_indices) == 0:
+                earlier_indices = np.where(historical_dates < previous_trading_day)[0]
+                if len(earlier_indices) == 0:
+                    log.info(f"{symbol} 历史数据缺少 {previous_trading_day} 之前的记录,跳过")
+                    continue
+                match_indices = [earlier_indices[-1]]
+
+            data_upto_yesterday = historical_data.iloc[:match_indices[-1] + 1]
+            # log.debug(f"data_upto_yesterday: {data_upto_yesterday}")
+            yesterday_data = data_upto_yesterday.iloc[-1]
+            yesterday_close = yesterday_data['close']
+            
+            # 获取今天的开盘价(使用get_current_data API)
+            current_data = get_current_data()[dominant_future]
+            today_open = current_data.day_open
+            
+            # log.info(f"  历史数据时间范围: {historical_data.index[0]} 至 {historical_data.index[-1]}")
+            
+            # 计算昨天的均线值(使用截至前一交易日的数据)
+            ma_values = calculate_ma_values(data_upto_yesterday, g.ma_periods)
+            ma_proximity_counts = calculate_ma_proximity_counts(data_upto_yesterday, g.ma_periods, g.ma_pattern_lookback_days)
+            
+            log.info(f"{symbol}({dominant_future}) 均线检查:")
+            # log.debug(f"yesterday_data: {yesterday_data}")
+            # log.info(f"  昨收: {yesterday_close:.2f}, 今开: {today_open:.2f}")
+            # log.info(f"  昨日均线 - MA5: {ma_values['MA5']:.2f}, MA10: {ma_values['MA10']:.2f}, "
+            #         f"MA20: {ma_values['MA20']:.2f}, MA30: {ma_values['MA30']:.2f}")
+            log.info(f"  均线贴近统计: {ma_proximity_counts}")
+            proximity_sum = ma_proximity_counts.get('MA5', 0) + ma_proximity_counts.get('MA10', 0)
+            if proximity_sum < g.ma_proximity_min_threshold:
+                log.info(f"  {symbol}({dominant_future}) ✗ 均线贴近计数不足,MA5+MA10={proximity_sum} < {g.ma_proximity_min_threshold},跳过")
+                g.excluded_contracts[dominant_future] = {
+                    'reason': 'ma_proximity',
+                    'trading_day': current_trading_day
+                }
+                continue
+            
+            # 判断均线走势(使用新的灵活模式检查)
+            direction = None
+            if check_ma_pattern(ma_values, 'long'):
+                direction = 'long'
+                # log.info(f"  {symbol}({dominant_future}) 均线走势判断: 多头排列")
+            elif check_ma_pattern(ma_values, 'short'):
+                direction = 'short'
+                # log.info(f"  {symbol}({dominant_future}) 均线走势判断: 空头排列")
+            else:
+                # log.info(f"  均线走势判断: 不符合多头或空头排列,跳过")
+                # 将不符合条件的合约加入排除缓存
+                g.excluded_contracts[dominant_future] = {
+                    'reason': 'ma_trend',
+                    'trading_day': current_trading_day
+                }
+                continue
+            
+            # 检查历史均线模式一致性
+            consistency_passed, consistency_ratio = check_historical_ma_pattern_consistency(
+                historical_data, direction, g.ma_pattern_lookback_days, g.ma_pattern_consistency_threshold
+            )
+            
+            if not consistency_passed:
+                log.info(f"  {symbol}({dominant_future}) ✗ 历史均线模式一致性不足 "
+                        f"({consistency_ratio:.1%} < {g.ma_pattern_consistency_threshold:.1%}),跳过")
+                g.excluded_contracts[dominant_future] = {
+                    'reason': 'ma_consistency',
+                    'trading_day': current_trading_day
+                }
+                continue
+            else:
+                log.info(f"  {symbol}({dominant_future}) ✓ 历史均线模式一致性检查通过 "
+                        f"({consistency_ratio:.1%} >= {g.ma_pattern_consistency_threshold:.1%})")
+            
+            # 计算开盘价差比例
+            open_gap_ratio = (today_open - yesterday_close) / yesterday_close
+            
+            log.info(f"  开盘价差检查: 昨收 {yesterday_close:.2f}, 今开 {today_open:.2f}, "
+                    f"价差比例 {open_gap_ratio:.2%}")
+            
+            # 检查开盘价差是否符合方向要求
+            gap_check_passed = False
+            
+            if g.ma_gap_strategy_mode == 1:
+                # 方案1:多头检查上跳,空头检查下跳
+                if direction == 'long' and open_gap_ratio >= g.ma_open_gap_threshold:
+                    log.info(f"  {symbol}({dominant_future}) ✓ 方案1多头开盘价差检查通过 ({open_gap_ratio:.2%} >= {g.ma_open_gap_threshold:.2%})")
+                    gap_check_passed = True
+                elif direction == 'short' and open_gap_ratio <= -g.ma_open_gap_threshold:
+                    log.info(f"  {symbol}({dominant_future}) ✓ 方案1空头开盘价差检查通过 ({open_gap_ratio:.2%} <= {-g.ma_open_gap_threshold:.2%})")
+                    gap_check_passed = True
+            elif g.ma_gap_strategy_mode == 2:
+                # 方案2:多头检查下跳,空头检查上跳
+                if direction == 'long' and open_gap_ratio <= -g.ma_open_gap_threshold2:
+                    log.info(f"  {symbol}({dominant_future}) ✓ 方案2多头开盘价差检查通过 ({open_gap_ratio:.2%} <= {-g.ma_open_gap_threshold2:.2%})")
+                    gap_check_passed = True
+                elif direction == 'short' and open_gap_ratio >= g.ma_open_gap_threshold2:
+                    log.info(f"  {symbol}({dominant_future}) ✓ 方案2空头开盘价差检查通过 ({open_gap_ratio:.2%} >= {g.ma_open_gap_threshold2:.2%})")
+                    gap_check_passed = True
+            
+            if not gap_check_passed:
+                # log.info(f"  ✗ 开盘价差不符合方案{g.ma_gap_strategy_mode} {direction}方向要求,跳过")
+                # 将不符合条件的合约加入排除缓存
+                g.excluded_contracts[dominant_future] = {
+                    'reason': 'open_gap',
+                    'trading_day': current_trading_day
+                }
+                continue
+            
+            # 将通过检查的品种加入候选列表
+            g.daily_ma_candidates[dominant_future] = {
+                'symbol': symbol,
+                'direction': direction,
+                'open_price': today_open,
+                'yesterday_close': yesterday_close,
+                'ma_values': ma_values
+            }
+            
+            log.info(f"  ✓✓ {symbol} 通过均线和开盘价差检查,加入候选列表")
+            
+        except Exception as e:
+            g.ma_checked_underlyings.pop(symbol, None)
+            log.warning(f"{symbol} 检查时出错: {str(e)}")
+            continue
+    
+    log.info(f"候选列表更新完成,当前候选品种: {list(g.daily_ma_candidates.keys())}")
+    log.info("=" * 60)
+
+def check_intraday_price_diff(context):
+    """阶段二:盘中价差检查和开仓(14:35和14:55)"""
+    log.info("=" * 60)
+    log.info(f"执行当天价差检查和开仓逻辑 - 时间: {context.current_dt}")
+    log.info("=" * 60)
+    
+    # 先检查换月移仓
+    position_auto_switch(context)
+    
+    if not g.daily_ma_candidates:
+        log.info("当前无候选品种,跳过")
+        return
+    
+    log.info(f"候选品种数量: {len(g.daily_ma_candidates)}")
+    
+    # 遍历候选品种
+    candidates_to_remove = []
+    
+    for dominant_future, candidate_info in g.daily_ma_candidates.items():
+        try:
+            symbol = candidate_info['symbol']
+            direction = candidate_info['direction']
+            open_price = candidate_info['open_price']
+            
+            # 再次检查是否已有持仓
+            if check_symbol_prefix_match(dominant_future, set(g.trade_history.keys())):
+                log.info(f"{symbol} 已有持仓,从候选列表移除")
+                candidates_to_remove.append(dominant_future)
+                continue
+            
+            # 获取当前价格
+            current_data = get_current_data()[dominant_future]
+            current_price = current_data.last_price
+            
+            # 计算当天价差
+            intraday_diff = current_price - open_price
+            intraday_diff_ratio = intraday_diff / open_price  # 计算相对变化比例
+            
+            log.info(f"{symbol}({dominant_future}) 当天价差检查:")
+            log.info(f"  方向: {direction}, 开盘价: {open_price:.2f}, 当前价: {current_price:.2f}, "
+                    f"当天价差: {intraday_diff:.2f}, 变化比例: {intraday_diff_ratio:.2%}")
+            
+            # 判断是否满足开仓条件
+            should_open = False
+            
+            if g.ma_gap_strategy_mode == 1:
+                # 方案1:根据参数决定是否检查日内价差
+                if not g.check_intraday_spread:
+                    # 跳过日内价差检查,直接允许开仓
+                    log.info(f"  方案1跳过日内价差检查(check_intraday_spread=False)")
+                    should_open = True
+                elif direction == 'long' and intraday_diff > 0:
+                    log.info(f"  ✓ 方案1多头当天价差检查通过 ({intraday_diff:.2f} > 0)")
+                    should_open = True
+                elif direction == 'short' and intraday_diff < 0:
+                    log.info(f"  ✓ 方案1空头当天价差检查通过 ({intraday_diff:.2f} < 0)")
+                    should_open = True
+                else:
+                    log.info(f"  ✗ 方案1当天价差不符合{direction}方向要求")
+            elif g.ma_gap_strategy_mode == 2:
+                # 方案2:强制检查日内变化,使用专用阈值
+                if direction == 'long' and intraday_diff_ratio >= g.ma_intraday_threshold_scheme2:
+                    log.info(f"  ✓ 方案2多头日内变化检查通过 ({intraday_diff_ratio:.2%} >= {g.ma_intraday_threshold_scheme2:.2%})")
+                    should_open = True
+                elif direction == 'short' and intraday_diff_ratio <= -g.ma_intraday_threshold_scheme2:
+                    log.info(f"  ✓ 方案2空头日内变化检查通过 ({intraday_diff_ratio:.2%} <= {-g.ma_intraday_threshold_scheme2:.2%})")
+                    should_open = True
+                else:
+                    log.info(f"  ✗ 方案2日内变化不符合{direction}方向要求(阈值: ±{g.ma_intraday_threshold_scheme2:.2%})")
+            
+            if should_open:
+                # 执行开仓
+                log.info(f"  准备开仓: {symbol} {direction}")
+                target_hands = calculate_target_hands(context, dominant_future, direction)
+                
+                if target_hands > 0:
+                    success = open_position(context, dominant_future, target_hands, direction, 
+                                          f'均线形态开仓')
+                    if success:
+                        log.info(f"  ✓✓ {symbol} 开仓成功,从候选列表移除")
+                        candidates_to_remove.append(dominant_future)
+                    else:
+                        log.warning(f"  ✗ {symbol} 开仓失败")
+                else:
+                    log.warning(f"  ✗ {symbol} 计算目标手数为0,跳过开仓")
+                    
+        except Exception as e:
+            log.warning(f"{dominant_future} 处理时出错: {str(e)}")
+            continue
+    
+    # 从候选列表中移除已开仓的品种
+    for future in candidates_to_remove:
+        if future in g.daily_ma_candidates:
+            del g.daily_ma_candidates[future]
+    
+    log.info(f"剩余候选品种: {list(g.daily_ma_candidates.keys())}")
+    log.info("=" * 60)
+
+def check_stop_loss_profit(context):
+    """阶段三:止损止盈检查(所有时间点)"""
+    # 先检查换月移仓
+    position_auto_switch(context)
+    
+    # 获取当前时间
+    current_time = str(context.current_dt.time())[:2]
+    
+    # 判断是否为夜盘时间
+    is_night_session = (current_time in ['21', '22', '23', '00', '01', '02'])
+    
+    # 遍历所有持仓进行止损止盈检查
+    subportfolio = context.subportfolios[0]
+    long_positions = list(subportfolio.long_positions.values())
+    short_positions = list(subportfolio.short_positions.values())
+    
+    closed_count = 0
+    skipped_count = 0
+    
+    for position in long_positions + short_positions:
+        security = position.security
+        underlying_symbol = security.split('.')[0][:-4]
+        
+        # 检查交易时间适配性
+        has_night_session = get_futures_config(underlying_symbol, 'has_night_session', False)
+        
+        # 如果是夜盘时间,但品种不支持夜盘交易,则跳过
+        if is_night_session and not has_night_session:
+            skipped_count += 1
+            continue
+        
+        # 执行止损止盈检查
+        if check_position_stop_loss_profit(context, position):
+            closed_count += 1
+    
+    if closed_count > 0:
+        log.info(f"执行了 {closed_count} 次止损止盈")
+    
+    if skipped_count > 0:
+        log.info(f"夜盘时间跳过 {skipped_count} 个日间品种的止损止盈检查")
+
+def check_position_stop_loss_profit(context, position):
+    """检查单个持仓的止损止盈"""
+    security = position.security
+    
+    if security not in g.trade_history:
+        return False
+    
+    trade_info = g.trade_history[security]
+    direction = trade_info['direction']
+    entry_price = trade_info['entry_price']
+    entry_time = trade_info['entry_time']
+    current_price = position.price
+    
+    # 计算当前盈亏比率
+    if direction == 'long':
+        profit_rate = (current_price - entry_price) / entry_price
+    else:
+        profit_rate = (entry_price - current_price) / entry_price
+    
+    # 检查固定止损
+    if profit_rate <= -g.fixed_stop_loss_rate:
+        log.info(f"触发固定止损 {security} {direction}, 当前亏损率: {profit_rate:.3%}, "
+                f"成本价: {entry_price:.2f}, 当前价格: {current_price:.2f}")
+        close_position(context, security, direction)
+        return True
+    
+    # 检查是否启用均线跟踪止盈
+    if not trade_info.get('ma_trailing_enabled', True):
+        return False
+
+    # 检查均线跟踪止盈
+    # 获取持仓天数
+    entry_date = entry_time.date()
+    current_date = context.current_dt.date()
+    all_trade_days = get_all_trade_days()
+    holding_days = sum((entry_date <= d <= current_date) for d in all_trade_days)
+    
+    # 计算变化率
+    today_price = get_current_data()[security].last_price
+    avg_daily_change_rate = calculate_average_daily_change_rate(security)
+    historical_data = attribute_history(security, 1, '1d', ['close'])
+    yesterday_close = historical_data['close'].iloc[-1]
+    today_change_rate = abs((today_price - yesterday_close) / yesterday_close)
+    
+    # 根据时间判断使用的偏移量
+    current_time = context.current_dt.time()
+    target_time = datetime.strptime('14:55:00', '%H:%M:%S').time()
+    if current_time > target_time:
+        offset_ratio = g.ma_offset_ratio_close
+    else:
+        offset_ratio = g.ma_offset_ratio_normal
+    
+    # 选择止损均线
+    close_line = None
+    if today_change_rate >= 1.5 * avg_daily_change_rate:
+        close_line = 'ma5'  # 波动剧烈时用短周期
+    elif holding_days <= g.days_for_adjustment:
+        close_line = 'ma5'  # 持仓初期用短周期
+    else:
+        close_line = 'ma5' if today_change_rate >= 1.2 * avg_daily_change_rate else 'ma10'
+    
+    # 计算实时均线值
+    ma_values = calculate_realtime_ma_values(security, [5, 10])
+    ma_value = ma_values[close_line]
+    
+    # 应用偏移量
+    if direction == 'long':
+        adjusted_ma_value = ma_value * (1 - offset_ratio)
+    else:
+        adjusted_ma_value = ma_value * (1 + offset_ratio)
+    
+    # 判断是否触发均线止损
+    if (direction == 'long' and today_price < adjusted_ma_value) or \
+       (direction == 'short' and today_price > adjusted_ma_value):
+        log.info(f"触发均线跟踪止盈 {security} {direction}, 止损均线: {close_line}, "
+                f"均线值: {ma_value:.2f}, 调整后: {adjusted_ma_value:.2f}, "
+                f"当前价: {today_price:.2f}, 持仓天数: {holding_days}")
+        close_position(context, security, direction)
+        return True
+    
+    return False
+
+############################ 核心辅助函数 ###################################
+
+def calculate_ma_values(data, periods):
+    """计算均线值
+    
+    Args:
+        data: DataFrame,包含'close'列的历史数据(最后一行是最新的数据)
+        periods: list,均线周期列表,如[5, 10, 20, 30]
+    
+    Returns:
+        dict: {'MA5': value, 'MA10': value, 'MA20': value, 'MA30': value}
+        返回最后一行(最新日期)的各周期均线值
+    """
+    ma_values = {}
+    
+    for period in periods:
+        if len(data) >= period:
+            # 计算最后period天的均线值
+            ma_values[f'MA{period}'] = data['close'].iloc[-period:].mean()
+        else:
+            ma_values[f'MA{period}'] = None
+    
+    return ma_values
+
+
+def calculate_ma_proximity_counts(data, periods, lookback_days):
+    """统计近 lookback_days 天收盘价贴近各均线的次数"""
+    proximity_counts = {f'MA{period}': 0 for period in periods}
+
+    if len(data) < lookback_days:
+        return proximity_counts
+
+    closes = data['close'].iloc[-lookback_days:]
+    ma_series = {
+        period: data['close'].rolling(window=period).mean().iloc[-lookback_days:]
+        for period in periods
+    }
+
+    for idx, close_price in enumerate(closes):
+        min_diff = None
+        closest_period = None
+
+        for period in periods:
+            ma_value = ma_series[period].iloc[idx]
+            if pd.isna(ma_value):
+                continue
+            diff = abs(close_price - ma_value)
+            if min_diff is None or diff < min_diff:
+                min_diff = diff
+                closest_period = period
+
+        if closest_period is not None:
+            proximity_counts[f'MA{closest_period}'] += 1
+
+    return proximity_counts
+
+
+def check_ma_pattern(ma_values, direction):
+    """检查均线排列模式是否符合方向要求
+    
+    Args:
+        ma_values: dict,包含MA5, MA10, MA20, MA30的均线值
+        direction: str,'long'或'short'
+    
+    Returns:
+        bool: 是否符合均线排列要求
+    """
+    ma5 = ma_values['MA5']
+    ma10 = ma_values['MA10']
+    ma20 = ma_values['MA20']
+    ma30 = ma_values['MA30']
+    
+    if direction == 'long':
+        # 多头模式:MA30 <= MA20 <= MA10 <= MA5 或 MA30 <= MA20 <= MA5 <= MA10
+        pattern1 = (ma30 <= ma20 <= ma10 <= ma5)
+        pattern2 = (ma30 <= ma20 <= ma5 <= ma10)
+        return pattern1 or pattern2
+    elif direction == 'short':
+        # 空头模式:MA10 <= MA5 <= MA20 <= MA30 或 MA5 <= MA10 <= MA20 <= MA30
+        pattern1 = (ma10 <= ma5 <= ma20 <= ma30)
+        pattern2 = (ma5 <= ma10 <= ma20 <= ma30)
+        return pattern1 or pattern2
+    else:
+        return False
+
+def check_historical_ma_pattern_consistency(historical_data, direction, lookback_days, consistency_threshold):
+    """检查历史均线模式的一致性
+    
+    Args:
+        historical_data: DataFrame,包含足够天数的历史数据
+        direction: str,'long'或'short'
+        lookback_days: int,检查过去多少天
+        consistency_threshold: float,一致性阈值(0-1之间)
+    
+    Returns:
+        tuple: (bool, float) - (是否通过一致性检查, 实际一致性比例)
+    """
+    if len(historical_data) < max(g.ma_periods) + lookback_days:
+        # 历史数据不足
+        return False, 0.0
+    
+    match_count = 0
+    total_count = lookback_days
+    
+    # 检查过去lookback_days天的均线模式
+    for i in range(lookback_days):
+        # 获取倒数第(i+1)天的数据(i=0时是昨天,i=1时是前天,依此类推)
+        end_idx = -(i + 1)
+        if end_idx == -1:
+            data_slice = historical_data
+        else:
+            data_slice = historical_data.iloc[:end_idx]
+        
+        # 计算该天的均线值
+        ma_values = calculate_ma_values(data_slice, g.ma_periods)
+        
+        # 检查是否符合模式
+        if check_ma_pattern(ma_values, direction):
+            match_count += 1
+    
+    consistency_ratio = match_count / total_count
+    passed = consistency_ratio >= consistency_threshold
+    
+    return passed, consistency_ratio
+
+############################ 交易执行函数 ###################################
+
+def open_position(context, security, target_hands, direction, reason=''):
+    """开仓"""
+    try:
+        # 记录交易前的可用资金
+        cash_before = context.portfolio.available_cash
+        
+        # 使用order_target按手数开仓
+        order = order_target(security, target_hands, side=direction)
+        
+        if order is not None and order.filled > 0:
+            # 记录交易后的可用资金
+            cash_after = context.portfolio.available_cash
+            
+            # 计算实际资金变化
+            cash_change = cash_before - cash_after
+            
+            # 获取订单价格和数量
+            order_price = order.avg_cost if order.avg_cost else order.price
+            order_amount = order.filled
+            
+            # 记录当日交易
+            underlying_symbol = security.split('.')[0][:-4]
+            g.today_trades.append({
+                'security': security,
+                'underlying_symbol': underlying_symbol,
+                'direction': direction,
+                'order_amount': order_amount,
+                'order_price': order_price,
+                'cash_change': cash_change,
+                'time': context.current_dt
+            })
+            
+            # 记录交易信息
+            g.trade_history[security] = {
+                'entry_price': order_price,
+                'target_hands': target_hands,
+                'actual_hands': order_amount,
+                'actual_margin': cash_change,
+                'direction': direction,
+                'entry_time': context.current_dt
+            }
+
+            ma_trailing_enabled = True
+            if direction == 'long':
+                ma_values_at_entry = calculate_realtime_ma_values(security, [5])
+                ma5_value = ma_values_at_entry.get('ma5')
+                if ma5_value is not None and order_price < ma5_value:
+                    ma_trailing_enabled = False
+                    log.info(f"禁用均线跟踪止盈: {security} {direction}, 开仓价 {order_price:.2f} < MA5 {ma5_value:.2f}")
+
+            g.trade_history[security]['ma_trailing_enabled'] = ma_trailing_enabled
+            
+            log.info(f"开仓成功: {security} {direction} {order_amount}手 @{order_price:.2f}, "
+                    f"保证金: {cash_change:.0f}, 原因: {reason}")
+            
+            return True
+            
+    except Exception as e:
+        log.warning(f"开仓失败 {security}: {str(e)}")
+    
+    return False
+
+def close_position(context, security, direction):
+    """平仓"""
+    try:
+        # 使用order_target平仓到0手
+        order = order_target(security, 0, side=direction)
+        
+        if order is not None and order.filled > 0:
+            underlying_symbol = security.split('.')[0][:-4]
+            
+            # 记录当日交易(平仓)
+            g.today_trades.append({
+                'security': security,
+                'underlying_symbol': underlying_symbol,
+                'direction': direction,
+                'order_amount': -order.filled,
+                'order_price': order.avg_cost if order.avg_cost else order.price,
+                'cash_change': 0,
+                'time': context.current_dt
+            })
+            
+            log.info(f"平仓成功: {underlying_symbol} {direction} {order.filled}手")
+            
+            # 从交易历史中移除
+            if security in g.trade_history:
+                del g.trade_history[security]
+            return True
+            
+    except Exception as e:
+        log.warning(f"平仓失败 {security}: {str(e)}")
+    
+    return False
+
+############################ 辅助函数 ###################################
+
+def get_futures_config(underlying_symbol, config_key=None, default_value=None):
+    """获取期货品种配置信息的辅助函数"""
+    if underlying_symbol not in g.futures_config:
+        if config_key and default_value is not None:
+            return default_value
+        return {}
+    
+    if config_key is None:
+        return g.futures_config[underlying_symbol]
+    
+    return g.futures_config[underlying_symbol].get(config_key, default_value)
+
+def get_margin_rate(underlying_symbol, direction, default_rate=0.10):
+    """获取保证金比例的辅助函数"""
+    return g.futures_config.get(underlying_symbol, {}).get('margin_rate', {}).get(direction, default_rate)
+
+def get_multiplier(underlying_symbol, default_multiplier=10):
+    """获取合约乘数的辅助函数"""
+    return g.futures_config.get(underlying_symbol, {}).get('multiplier', default_multiplier)
+
+def calculate_target_hands(context, security, direction):
+    """计算目标开仓手数"""
+    current_price = get_current_data()[security].last_price
+    underlying_symbol = security.split('.')[0][:-4]
+    
+    # 使用保证金比例
+    margin_rate = get_margin_rate(underlying_symbol, direction)
+    multiplier = get_multiplier(underlying_symbol)
+    
+    # 计算单手保证金
+    single_hand_margin = current_price * multiplier * margin_rate
+    
+    # 还要考虑可用资金限制
+    available_cash = context.portfolio.available_cash * g.usage_percentage
+    
+    # 根据单个标的最大持仓保证金限制计算开仓数量
+    max_margin = g.max_margin_per_position
+    
+    if single_hand_margin <= max_margin:
+        # 如果单手保证金不超过最大限制,计算最大可开仓手数
+        max_hands = int(max_margin / single_hand_margin)
+        max_hands_by_cash = int(available_cash / single_hand_margin)
+        
+        # 取两者较小值
+        actual_hands = min(max_hands, max_hands_by_cash)
+        
+        # 确保至少开1手
+        actual_hands = max(1, actual_hands)
+        
+        log.info(f"单手保证金: {single_hand_margin:.0f}, 目标开仓手数: {actual_hands}")
+        
+        return actual_hands
+    else:
+        # 如果单手保证金超过最大限制,默认开仓1手
+        actual_hands = 1
+        
+        log.info(f"单手保证金: {single_hand_margin:.0f} 超过最大限制: {max_margin}, 默认开仓1手")
+        
+        return actual_hands
+
+def check_symbol_prefix_match(symbol, hold_symbols):
+    """检查是否有相似的持仓品种"""
+    symbol_prefix = symbol[:-9]
+    
+    for hold_symbol in hold_symbols:
+        hold_symbol_prefix = hold_symbol[:-9] if len(hold_symbol) > 9 else hold_symbol
+        
+        if symbol_prefix == hold_symbol_prefix:
+            return True
+    return False
+
+def calculate_average_daily_change_rate(security, days=30):
+    """计算日均变化率"""
+    historical_data = attribute_history(security, days + 1, '1d', ['close'])
+    daily_change_rates = abs(historical_data['close'].pct_change()).iloc[1:]
+    return daily_change_rates.mean()
+
+def calculate_realtime_ma_values(security, ma_periods):
+    """计算包含当前价格的实时均线值"""
+    historical_data = attribute_history(security, max(ma_periods), '1d', ['close'])
+    today_price = get_current_data()[security].last_price
+    close_prices = historical_data['close'].tolist() + [today_price]
+    ma_values = {f'ma{period}': sum(close_prices[-period:]) / period for period in ma_periods}
+    return ma_values
+
+def after_market_close(context):
+    """收盘后运行函数"""
+    log.info(str('函数运行时间(after_market_close):'+str(context.current_dt.time())))
+    
+    # 清空候选列表(每天重新检查)
+    g.daily_ma_candidates = {}
+    
+    # 清空排除缓存(每天重新检查)
+    excluded_count = len(g.excluded_contracts)
+    if excluded_count > 0:
+        log.info(f"清空排除缓存,共 {excluded_count} 个合约")
+        g.excluded_contracts = {}
+    
+    # 只有当天有交易时才打印统计信息
+    if g.today_trades:
+        print_daily_trading_summary(context)
+        
+        # 清空当日交易记录
+        g.today_trades = []
+    
+    log.info('##############################################################')
+
+def print_daily_trading_summary(context):
+    """打印当日交易汇总"""
+    if not g.today_trades:
+        return
+    
+    log.info("\n=== 当日交易汇总 ===")
+    total_margin = 0
+    
+    for trade in g.today_trades:
+        if trade['order_amount'] > 0:  # 开仓
+            log.info(f"开仓 {trade['underlying_symbol']} {trade['direction']} {trade['order_amount']}手 "
+                  f"价格:{trade['order_price']:.2f} 保证金:{trade['cash_change']:.0f}")
+            total_margin += trade['cash_change']
+        else:  # 平仓
+            log.info(f"平仓 {trade['underlying_symbol']} {trade['direction']} {abs(trade['order_amount'])}手 "
+                  f"价格:{trade['order_price']:.2f}")
+    
+    log.info(f"当日保证金占用: {total_margin:.0f}")
+    log.info("==================\n")
+
+########################## 自动移仓换月函数 #################################
+def position_auto_switch(context, pindex=0, switch_func=None, callback=None):
+    """期货自动移仓换月"""
+    import re
+    subportfolio = context.subportfolios[pindex]
+    symbols = set(subportfolio.long_positions.keys()) | set(subportfolio.short_positions.keys())
+    switch_result = []
+    for symbol in symbols:
+        match = re.match(r"(?P<underlying_symbol>[A-Z]{1,})", symbol)
+        if not match:
+            raise ValueError("未知期货标的: {}".format(symbol))
+        else:
+            dominant = get_dominant_future(match.groupdict()["underlying_symbol"])
+            cur = get_current_data()
+            symbol_last_price = cur[symbol].last_price
+            dominant_last_price = cur[dominant].last_price
+            
+            if dominant > symbol:
+                for positions_ in (subportfolio.long_positions, subportfolio.short_positions):
+                    if symbol not in positions_.keys():
+                        continue
+                    else :
+                        p = positions_[symbol]
+
+                    if switch_func is not None:
+                        switch_func(context, pindex, p, dominant)
+                    else:
+                        amount = p.total_amount
+                        # 跌停不能开空和平多,涨停不能开多和平空
+                        if p.side == "long":
+                            symbol_low_limit = cur[symbol].low_limit
+                            dominant_high_limit = cur[dominant].high_limit
+                            if symbol_last_price <= symbol_low_limit:
+                                log.warning("标的{}跌停,无法平仓。移仓换月取消。".format(symbol))
+                                continue
+                            elif dominant_last_price >= dominant_high_limit:
+                                log.warning("标的{}涨停,无法开仓。移仓换月取消。".format(dominant))
+                                continue
+                            else:
+                                log.info("进行移仓换月: ({0},long) -> ({1},long)".format(symbol, dominant))
+                                order_old = order_target(symbol, 0, side='long')
+                                if order_old != None and order_old.filled > 0:
+                                    order_new = order_target(dominant, amount, side='long')
+                                    if order_new != None and order_new.filled > 0:
+                                        switch_result.append({"before": symbol, "after": dominant, "side": "long"})
+                                        # 换月成功,更新交易记录
+                                        if symbol in g.trade_history:
+                                            g.trade_history[dominant] = g.trade_history[symbol]
+                                            del g.trade_history[symbol]
+                                    else:
+                                        log.warning("标的{}交易失败,无法开仓。移仓换月失败。".format(dominant))
+                        if p.side == "short":
+                            symbol_high_limit = cur[symbol].high_limit
+                            dominant_low_limit = cur[dominant].low_limit
+                            if symbol_last_price >= symbol_high_limit:
+                                log.warning("标的{}涨停,无法平仓。移仓换月取消。".format(symbol))
+                                continue
+                            elif dominant_last_price <= dominant_low_limit:
+                                log.warning("标的{}跌停,无法开仓。移仓换月取消。".format(dominant))
+                                continue
+                            else:
+                                log.info("进行移仓换月: ({0},short) -> ({1},short)".format(symbol, dominant))
+                                order_old = order_target(symbol, 0, side='short')
+                                if order_old != None and order_old.filled > 0:
+                                    order_new = order_target(dominant, amount, side='short')
+                                    if order_new != None and order_new.filled > 0:
+                                        switch_result.append({"before": symbol, "after": dominant, "side": "short"})
+                                        # 换月成功,更新交易记录
+                                        if symbol in g.trade_history:
+                                            g.trade_history[dominant] = g.trade_history[symbol]
+                                            del g.trade_history[symbol]
+                                    else:
+                                        log.warning("标的{}交易失败,无法开仓。移仓换月失败。".format(dominant))
+                        if callback:
+                            callback(context, pindex, p, dominant)
+    return switch_result
+

+ 1127 - 0
Lib/future/MAPatternStrategy_v001.py.bak

@@ -0,0 +1,1127 @@
+# 导入函数库
+from jqdata import *
+from jqdata import finance
+import pandas as pd
+import numpy as np
+from datetime import date, datetime, timedelta, time
+import re
+
+# 顺势交易策略 v001
+# 基于均线走势(前提条件)+ K线形态(开盘价差、当天价差)的期货交易策略
+#
+# 核心逻辑:
+# 1. 开盘时检查均线走势(MA30<=MA20<=MA10<=MA5为多头,反之为空头)
+# 2. 检查开盘价差是否符合方向要求(多头>=0.5%,空头<=-0.5%)
+# 3. 14:35和14:55检查当天价差(多头>0,空头<0),满足条件则开仓
+# 4. 应用固定止损和动态追踪止盈
+# 5. 自动换月移仓
+
+# 设置以便完整打印 DataFrame
+pd.set_option('display.max_rows', None)
+pd.set_option('display.max_columns', None)
+pd.set_option('display.width', None)
+pd.set_option('display.max_colwidth', 20)
+
+## 初始化函数,设定基准等等
+def initialize(context):
+    # 设定沪深300作为基准
+    set_benchmark('000300.XSHG')
+    # 开启动态复权模式(真实价格)
+    set_option('use_real_price', True)
+    # 输出内容到日志
+    log.info('=' * 60)
+    log.info('均线形态交易策略 v001 初始化开始')
+    log.info('策略类型: 均线走势 + K线形态')
+    log.info('=' * 60)
+
+    ### 期货相关设定 ###
+    # 设定账户为金融账户
+    set_subportfolios([SubPortfolioConfig(cash=context.portfolio.starting_cash, type='index_futures')])
+    # 期货类每笔交易时的手续费是: 买入时万分之0.23,卖出时万分之0.23,平今仓为万分之23
+    set_order_cost(OrderCost(open_commission=0.000023, close_commission=0.000023, close_today_commission=0.0023), type='index_futures')
+    
+    # 设置期货交易的滑点
+    set_slippage(StepRelatedSlippage(2))
+    
+    # 初始化全局变量
+    g.usage_percentage = 0.8  # 最大资金使用比例
+    g.max_margin_per_position = 20000  # 单个标的最大持仓保证金(元)
+    
+    # 均线策略参数
+    g.ma_periods = [5, 10, 20, 30]  # 均线周期
+    g.ma_historical_days = 60  # 获取历史数据天数(确保足够计算MA30)
+    g.ma_open_gap_threshold = 0.002  # 方案1开盘价差阈值(0.2%)
+    g.ma_pattern_lookback_days = 10  # 历史均线模式一致性检查的天数
+    g.ma_pattern_consistency_threshold = 0.8  # 历史均线模式一致性阈值(80%)
+    g.check_intraday_spread = False  # 是否检查日内价差(True: 检查, False: 跳过)
+    g.ma_proximity_min_threshold = 8  # MA5与MA10贴近计数和的最低阈值
+    
+    # 均线价差策略方案选择
+    g.ma_gap_strategy_mode = 2  # 策略模式选择(1: 原方案, 2: 新方案)
+    g.ma_open_gap_threshold2 = 0.002  # 方案2开盘价差阈值(0.2%)
+    g.ma_intraday_threshold_scheme2 = 0.005  # 方案2日内变化阈值(0.5%)
+    
+    # 止损止盈策略参数
+    g.fixed_stop_loss_rate = 0.01  # 固定止损比率(1%)
+    g.ma_offset_ratio_normal = 0.003  # 均线跟踪止盈常规偏移量(0.3%)
+    g.ma_offset_ratio_close = 0.01  # 均线跟踪止盈收盘前偏移量(1%)
+    g.days_for_adjustment = 4  # 持仓天数调整阈值
+    
+    # 输出策略参数
+    log.info("均线形态策略参数:")
+    log.info(f"  均线周期: {g.ma_periods}")
+    log.info(f"  策略模式: 方案{g.ma_gap_strategy_mode}")
+    log.info(f"  方案1开盘价差阈值: {g.ma_open_gap_threshold:.1%}")
+    log.info(f"  方案2开盘价差阈值: {g.ma_open_gap_threshold2:.1%}")
+    log.info(f"  方案2日内变化阈值: {g.ma_intraday_threshold_scheme2:.1%}")
+    log.info(f"  历史均线模式检查天数: {g.ma_pattern_lookback_days}天")
+    log.info(f"  历史均线模式一致性阈值: {g.ma_pattern_consistency_threshold:.1%}")
+    log.info(f"  均线贴近计数阈值: {g.ma_proximity_min_threshold}")
+    log.info(f"  是否检查日内价差: {g.check_intraday_spread}")
+    log.info(f"  固定止损: {g.fixed_stop_loss_rate:.1%}")
+    log.info(f"  均线跟踪止盈常规偏移: {g.ma_offset_ratio_normal:.1%}")
+    log.info(f"  均线跟踪止盈收盘前偏移: {g.ma_offset_ratio_close:.1%}")
+    log.info(f"  持仓天数调整阈值: {g.days_for_adjustment}天")
+    
+    # 期货品种完整配置字典
+    g.futures_config = {
+        # 贵金属
+        'AU': {'has_night_session': True, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 1000, 'trading_start_time': '21:00'},
+        'AG': {'has_night_session': True, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 15, 'trading_start_time': '21:00'},
+        
+        # 有色金属
+        'CU': {'has_night_session': True, 'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'AL': {'has_night_session': True, 'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'ZN': {'has_night_session': True, 'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'PB': {'has_night_session': True, 'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'NI': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1, 'trading_start_time': '21:00'},
+        'SN': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1, 'trading_start_time': '21:00'},
+        'SS': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        
+        # 黑色系
+        'RB': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'HC': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'I': {'has_night_session': True, 'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 100, 'trading_start_time': '21:00'},
+        'JM': {'has_night_session': True, 'margin_rate': {'long': 0.22, 'short': 0.22}, 'multiplier': 100, 'trading_start_time': '21:00'},
+        'J': {'has_night_session': True, 'margin_rate': {'long': 0.22, 'short': 0.22}, 'multiplier': 60, 'trading_start_time': '21:00'},
+        
+        # 能源化工
+        'SP': {'has_night_session': True, 'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'FU': {'has_night_session': True, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'BU': {'has_night_session': True, 'margin_rate': {'long': 0.04, 'short': 0.04}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'RU': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'BR': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'SC': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1000, 'trading_start_time': '21:00'},
+        'NR': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'LU': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'LC': {'has_night_session': False, 'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 1, 'trading_start_time': '09:00'},
+        
+        # 化工
+        'FG': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20, 'trading_start_time': '21:00'},
+        'TA': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'MA': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'SA': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20, 'trading_start_time': '21:00'},
+        'L': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'V': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'EG': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'PP': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'EB': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'PG': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20, 'trading_start_time': '21:00'},
+        
+        # 农产品
+        'RM': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'OI': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'CF': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'SR': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'PF': {'has_night_session': True, 'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'C': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'CS': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'CY': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'A': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'B': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'M': {'has_night_session': True, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'Y': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'P': {'has_night_session': True, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        
+        # 无夜盘品种
+        'IF': {'has_night_session': False, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 300, 'trading_start_time': '09:30'},
+        'IH': {'has_night_session': False, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 300, 'trading_start_time': '09:30'},
+        'IC': {'has_night_session': False, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 200, 'trading_start_time': '09:30'},
+        'IM': {'has_night_session': False, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 200, 'trading_start_time': '09:30'},
+        'AP': {'has_night_session': False, 'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 10, 'trading_start_time': '09:00'},
+        'CJ': {'has_night_session': False, 'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5, 'trading_start_time': '09:00'},
+        'PK': {'has_night_session': False, 'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 5, 'trading_start_time': '09:00'},
+        'JD': {'has_night_session': False, 'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '09:00'},
+        'LH': {'has_night_session': False, 'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 16, 'trading_start_time': '09:00'}
+    }
+    
+    # 策略品种选择策略配置
+    # 方案1:全品种策略 - 考虑所有配置的期货品种
+    g.strategy_focus_symbols = ['IC', 'LH']  # 空列表表示考虑所有品种
+    
+    # 方案2:精选品种策略 - 只交易流动性较好的特定品种(如需使用请取消下行注释)
+    # g.strategy_focus_symbols = ['RM', 'CJ', 'CY', 'JD', 'L', 'LC', 'SF', 'SI']
+    
+    log.info(f"品种选择策略: {'全品种策略(覆盖所有配置品种)' if not g.strategy_focus_symbols else '精选品种策略(' + str(len(g.strategy_focus_symbols)) + '个品种)'}")
+    
+    # 交易记录和数据存储
+    g.trade_history = {}  # 持仓记录 {symbol: {'entry_price': xxx, 'direction': xxx, ...}}
+    g.daily_ma_candidates = {}  # 通过均线和开盘价差检查的候选品种 {symbol: {'direction': 'long'/'short', 'open_price': xxx, ...}}
+    g.today_trades = []  # 当日交易记录
+    g.excluded_contracts = {}  # 每日排除的合约缓存 {dominant_future: {'reason': 'ma_trend'/'open_gap', 'trading_day': xxx}}
+    g.ma_checked_underlyings = {}  # 记录各品种在交易日的均线检查状态 {symbol: trading_day}
+    g.last_ma_trading_day = None  # 最近一次均线检查所属交易日
+    
+    # 定时任务设置
+    # 夜盘开始(21:05) - 均线和开盘价差检查
+    run_daily(check_ma_trend_and_open_gap, time='21:05:00', reference_security='IF1808.CCFX')
+    
+    # 日盘开始 - 均线和开盘价差检查
+    run_daily(check_ma_trend_and_open_gap, time='09:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_ma_trend_and_open_gap, time='09:35:00', reference_security='IF1808.CCFX')
+    
+    # 盘中价差检查和开仓(14:35和14:55)
+    run_daily(check_intraday_price_diff, time='14:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_intraday_price_diff, time='14:55:00', reference_security='IF1808.CCFX')
+    
+    # 夜盘止损止盈检查
+    run_daily(check_stop_loss_profit, time='21:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='21:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='22:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='22:35:00', reference_security='IF1808.CCFX')
+    
+    # 日盘止损止盈检查
+    run_daily(check_stop_loss_profit, time='09:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='09:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='10:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='10:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='11:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='11:25:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='13:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='14:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='14:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_stop_loss_profit, time='14:55:00', reference_security='IF1808.CCFX')
+    
+    # 收盘后
+    run_daily(after_market_close, time='15:30:00', reference_security='IF1808.CCFX')
+    
+    log.info('=' * 60)
+
+############################ 主程序执行函数 ###################################
+
+def get_current_trading_day(current_dt):
+    """根据当前时间推断对应的期货交易日"""
+    current_date = current_dt.date()
+    current_time = current_dt.time()
+
+    trade_days = get_trade_days(end_date=current_date, count=1)
+    if trade_days and trade_days[0] == current_date:
+        trading_day = current_date
+    else:
+        next_days = get_trade_days(start_date=current_date, count=1)
+        trading_day = next_days[0] if next_days else current_date
+
+    if current_time >= time(20, 59):
+        next_trade_days = get_trade_days(start_date=trading_day, count=2)
+        if len(next_trade_days) >= 2:
+            return next_trade_days[1]
+        if len(next_trade_days) == 1:
+            return next_trade_days[0]
+    return trading_day
+
+
+def normalize_trade_day_value(value):
+    """将交易日对象统一转换为 datetime.date"""
+    if isinstance(value, date) and not isinstance(value, datetime):
+        return value
+    if isinstance(value, datetime):
+        return value.date()
+    if hasattr(value, 'to_pydatetime'):
+        return value.to_pydatetime().date()
+    try:
+        return pd.Timestamp(value).date()
+    except Exception:
+        return value
+
+
+def check_ma_trend_and_open_gap(context):
+    """阶段一:开盘时均线走势和开盘价差检查(一天一次)"""
+    log.info("=" * 60)
+    current_trading_day = get_current_trading_day(context.current_dt)
+    log.info(f"执行均线走势和开盘价差检查 - 时间: {context.current_dt}, 交易日: {current_trading_day}")
+    log.info("=" * 60)
+    
+    # 先检查换月移仓
+    position_auto_switch(context)
+    
+    # 检查是否进入新交易日,必要时清空缓存
+    if g.last_ma_trading_day != current_trading_day:
+        if g.excluded_contracts:
+            log.info(f"交易日切换至 {current_trading_day},清空上一交易日的排除缓存")
+        g.excluded_contracts = {}
+        g.ma_checked_underlyings = {}
+        g.last_ma_trading_day = current_trading_day
+
+    # 获取当前时间
+    current_time = str(context.current_dt.time())[:5]  # HH:MM格式
+    
+    # 筛选可交易品种(根据交易开始时间判断)
+    focus_symbols = g.strategy_focus_symbols if g.strategy_focus_symbols else list(g.futures_config.keys())
+    tradable_symbols = []
+    
+    # 根据当前时间确定可交易的时段
+    # 21:05 -> 仅接受21:00开盘的合约
+    # 09:05 -> 接受09:00或21:00开盘的合约
+    # 09:35 -> 接受所有时段(21:00, 09:00, 09:30)的合约
+    for symbol in focus_symbols:
+        trading_start_time = get_futures_config(symbol, 'trading_start_time', '09:05')
+        should_trade = False
+        
+        if current_time == '21:05':
+            # 夜盘开盘:仅接受21:00开盘的品种
+            should_trade = trading_start_time.startswith('21:00')
+        elif current_time == '09:05':
+            # 日盘早盘:接受21:00和09:00开盘的品种
+            should_trade = trading_start_time.startswith('21:00') or trading_start_time.startswith('09:00')
+        elif current_time == '09:35':
+            # 日盘晚开:接受所有品种(21:00, 09:00, 09:30)
+            should_trade = True
+        
+        if should_trade:
+            tradable_symbols.append(symbol)
+    
+    if not tradable_symbols:
+        log.info(f"当前时间 {current_time} 无品种开盘,跳过检查")
+        return
+    
+    log.info(f"当前时间 {current_time} 开盘品种: {tradable_symbols}")
+    
+    # 对每个品种执行均线和开盘价差检查
+    for symbol in tradable_symbols:
+        if g.ma_checked_underlyings.get(symbol) == current_trading_day:
+            log.info(f"{symbol} 已在交易日 {current_trading_day} 完成均线检查,跳过本次执行")
+            continue
+
+        try:
+            g.ma_checked_underlyings[symbol] = current_trading_day
+            # 获取主力合约
+            dominant_future = get_dominant_future(symbol)
+            # log.debug(f"{symbol} 主力合约: {dominant_future}")
+            if not dominant_future:
+                log.info(f"{symbol} 未找到主力合约,跳过")
+                continue
+            
+            # 检查是否在排除缓存中(当日已检查过但不符合条件)
+            if dominant_future in g.excluded_contracts:
+                excluded_info = g.excluded_contracts[dominant_future]
+                if excluded_info['trading_day'] == current_trading_day:
+                    # log.debug(f"{symbol} 在排除缓存中(原因: {excluded_info['reason']}),跳过")
+                    continue
+                else:
+                    # 新的一天,从缓存中移除(会在after_market_close统一清理,这里也做兜底)
+                    del g.excluded_contracts[dominant_future]
+            
+            # 检查是否已有持仓
+            if check_symbol_prefix_match(dominant_future, set(g.trade_history.keys())):
+                log.info(f"{symbol} 已有持仓,跳过")
+                continue
+            
+            # 获取历史数据(需要足够计算MA30)
+            # 使用get_price获取数据,可以正确处理夜盘品种
+            # 注意:historical_data最后一行是昨天的数据,不包含今天的数据
+            historical_data = get_price(dominant_future, end_date=context.current_dt, 
+                                       frequency='1d', fields=['open', 'close', 'high', 'low'], 
+                                       count=g.ma_historical_days)
+            
+            if historical_data is None or len(historical_data) < max(g.ma_periods):
+                log.info(f"{symbol} 历史数据不足,跳过")
+                continue
+
+            previous_trade_days = get_trade_days(end_date=current_trading_day, count=2)
+            previous_trade_days = [normalize_trade_day_value(d) for d in previous_trade_days]
+            previous_trading_day = None
+            if len(previous_trade_days) >= 2:
+                previous_trading_day = previous_trade_days[-2]
+            elif len(previous_trade_days) == 1 and previous_trade_days[0] < current_trading_day:
+                previous_trading_day = previous_trade_days[0]
+
+            if previous_trading_day is None:
+                log.info(f"{symbol} 无法确定前一交易日,跳过")
+                continue
+
+            historical_dates = historical_data.index.date
+            match_indices = np.where(historical_dates == previous_trading_day)[0]
+
+            if len(match_indices) == 0:
+                earlier_indices = np.where(historical_dates < previous_trading_day)[0]
+                if len(earlier_indices) == 0:
+                    log.info(f"{symbol} 历史数据缺少 {previous_trading_day} 之前的记录,跳过")
+                    continue
+                match_indices = [earlier_indices[-1]]
+
+            data_upto_yesterday = historical_data.iloc[:match_indices[-1] + 1]
+            # log.debug(f"data_upto_yesterday: {data_upto_yesterday}")
+            yesterday_data = data_upto_yesterday.iloc[-1]
+            yesterday_close = yesterday_data['close']
+            
+            # 获取今天的开盘价(使用get_current_data API)
+            current_data = get_current_data()[dominant_future]
+            today_open = current_data.day_open
+            
+            # log.info(f"  历史数据时间范围: {historical_data.index[0]} 至 {historical_data.index[-1]}")
+            
+            # 计算昨天的均线值(使用截至前一交易日的数据)
+            ma_values = calculate_ma_values(data_upto_yesterday, g.ma_periods)
+            ma_proximity_counts = calculate_ma_proximity_counts(data_upto_yesterday, g.ma_periods, g.ma_pattern_lookback_days)
+            
+            log.info(f"{symbol}({dominant_future}) 均线检查:")
+            # log.debug(f"yesterday_data: {yesterday_data}")
+            # log.info(f"  昨收: {yesterday_close:.2f}, 今开: {today_open:.2f}")
+            # log.info(f"  昨日均线 - MA5: {ma_values['MA5']:.2f}, MA10: {ma_values['MA10']:.2f}, "
+            #         f"MA20: {ma_values['MA20']:.2f}, MA30: {ma_values['MA30']:.2f}")
+            log.info(f"  均线贴近统计: {ma_proximity_counts}")
+            proximity_sum = ma_proximity_counts.get('MA5', 0) + ma_proximity_counts.get('MA10', 0)
+            if proximity_sum < g.ma_proximity_min_threshold:
+                log.info(f"  {symbol}({dominant_future}) ✗ 均线贴近计数不足,MA5+MA10={proximity_sum} < {g.ma_proximity_min_threshold},跳过")
+                g.excluded_contracts[dominant_future] = {
+                    'reason': 'ma_proximity',
+                    'trading_day': current_trading_day
+                }
+                continue
+            
+            # 判断均线走势(使用新的灵活模式检查)
+            direction = None
+            if check_ma_pattern(ma_values, 'long'):
+                direction = 'long'
+                # log.info(f"  {symbol}({dominant_future}) 均线走势判断: 多头排列")
+            elif check_ma_pattern(ma_values, 'short'):
+                direction = 'short'
+                # log.info(f"  {symbol}({dominant_future}) 均线走势判断: 空头排列")
+            else:
+                # log.info(f"  均线走势判断: 不符合多头或空头排列,跳过")
+                # 将不符合条件的合约加入排除缓存
+                g.excluded_contracts[dominant_future] = {
+                    'reason': 'ma_trend',
+                    'trading_day': current_trading_day
+                }
+                continue
+            
+            # 检查历史均线模式一致性
+            consistency_passed, consistency_ratio = check_historical_ma_pattern_consistency(
+                historical_data, direction, g.ma_pattern_lookback_days, g.ma_pattern_consistency_threshold
+            )
+            
+            if not consistency_passed:
+                log.info(f"  {symbol}({dominant_future}) ✗ 历史均线模式一致性不足 "
+                        f"({consistency_ratio:.1%} < {g.ma_pattern_consistency_threshold:.1%}),跳过")
+                g.excluded_contracts[dominant_future] = {
+                    'reason': 'ma_consistency',
+                    'trading_day': current_trading_day
+                }
+                continue
+            else:
+                log.info(f"  {symbol}({dominant_future}) ✓ 历史均线模式一致性检查通过 "
+                        f"({consistency_ratio:.1%} >= {g.ma_pattern_consistency_threshold:.1%})")
+            
+            # 计算开盘价差比例
+            open_gap_ratio = (today_open - yesterday_close) / yesterday_close
+            
+            log.info(f"  开盘价差检查: 昨收 {yesterday_close:.2f}, 今开 {today_open:.2f}, "
+                    f"价差比例 {open_gap_ratio:.2%}")
+            
+            # 检查开盘价差是否符合方向要求
+            gap_check_passed = False
+            
+            if g.ma_gap_strategy_mode == 1:
+                # 方案1:多头检查上跳,空头检查下跳
+                if direction == 'long' and open_gap_ratio >= g.ma_open_gap_threshold:
+                    log.info(f"  {symbol}({dominant_future}) ✓ 方案1多头开盘价差检查通过 ({open_gap_ratio:.2%} >= {g.ma_open_gap_threshold:.2%})")
+                    gap_check_passed = True
+                elif direction == 'short' and open_gap_ratio <= -g.ma_open_gap_threshold:
+                    log.info(f"  {symbol}({dominant_future}) ✓ 方案1空头开盘价差检查通过 ({open_gap_ratio:.2%} <= {-g.ma_open_gap_threshold:.2%})")
+                    gap_check_passed = True
+            elif g.ma_gap_strategy_mode == 2:
+                # 方案2:多头检查下跳,空头检查上跳
+                if direction == 'long' and open_gap_ratio <= -g.ma_open_gap_threshold2:
+                    log.info(f"  {symbol}({dominant_future}) ✓ 方案2多头开盘价差检查通过 ({open_gap_ratio:.2%} <= {-g.ma_open_gap_threshold2:.2%})")
+                    gap_check_passed = True
+                elif direction == 'short' and open_gap_ratio >= g.ma_open_gap_threshold2:
+                    log.info(f"  {symbol}({dominant_future}) ✓ 方案2空头开盘价差检查通过 ({open_gap_ratio:.2%} >= {g.ma_open_gap_threshold2:.2%})")
+                    gap_check_passed = True
+            
+            if not gap_check_passed:
+                # log.info(f"  ✗ 开盘价差不符合方案{g.ma_gap_strategy_mode} {direction}方向要求,跳过")
+                # 将不符合条件的合约加入排除缓存
+                g.excluded_contracts[dominant_future] = {
+                    'reason': 'open_gap',
+                    'trading_day': current_trading_day
+                }
+                continue
+            
+            # 将通过检查的品种加入候选列表
+            g.daily_ma_candidates[dominant_future] = {
+                'symbol': symbol,
+                'direction': direction,
+                'open_price': today_open,
+                'yesterday_close': yesterday_close,
+                'ma_values': ma_values
+            }
+            
+            log.info(f"  ✓✓ {symbol} 通过均线和开盘价差检查,加入候选列表")
+            
+        except Exception as e:
+            g.ma_checked_underlyings.pop(symbol, None)
+            log.warning(f"{symbol} 检查时出错: {str(e)}")
+            continue
+    
+    log.info(f"候选列表更新完成,当前候选品种: {list(g.daily_ma_candidates.keys())}")
+    log.info("=" * 60)
+
+def check_intraday_price_diff(context):
+    """阶段二:盘中价差检查和开仓(14:35和14:55)"""
+    log.info("=" * 60)
+    log.info(f"执行当天价差检查和开仓逻辑 - 时间: {context.current_dt}")
+    log.info("=" * 60)
+    
+    # 先检查换月移仓
+    position_auto_switch(context)
+    
+    if not g.daily_ma_candidates:
+        log.info("当前无候选品种,跳过")
+        return
+    
+    log.info(f"候选品种数量: {len(g.daily_ma_candidates)}")
+    
+    # 遍历候选品种
+    candidates_to_remove = []
+    
+    for dominant_future, candidate_info in g.daily_ma_candidates.items():
+        try:
+            symbol = candidate_info['symbol']
+            direction = candidate_info['direction']
+            open_price = candidate_info['open_price']
+            
+            # 再次检查是否已有持仓
+            if check_symbol_prefix_match(dominant_future, set(g.trade_history.keys())):
+                log.info(f"{symbol} 已有持仓,从候选列表移除")
+                candidates_to_remove.append(dominant_future)
+                continue
+            
+            # 获取当前价格
+            current_data = get_current_data()[dominant_future]
+            current_price = current_data.last_price
+            
+            # 计算当天价差
+            intraday_diff = current_price - open_price
+            intraday_diff_ratio = intraday_diff / open_price  # 计算相对变化比例
+            
+            log.info(f"{symbol}({dominant_future}) 当天价差检查:")
+            log.info(f"  方向: {direction}, 开盘价: {open_price:.2f}, 当前价: {current_price:.2f}, "
+                    f"当天价差: {intraday_diff:.2f}, 变化比例: {intraday_diff_ratio:.2%}")
+            
+            # 判断是否满足开仓条件
+            should_open = False
+            
+            if g.ma_gap_strategy_mode == 1:
+                # 方案1:根据参数决定是否检查日内价差
+                if not g.check_intraday_spread:
+                    # 跳过日内价差检查,直接允许开仓
+                    log.info(f"  方案1跳过日内价差检查(check_intraday_spread=False)")
+                    should_open = True
+                elif direction == 'long' and intraday_diff > 0:
+                    log.info(f"  ✓ 方案1多头当天价差检查通过 ({intraday_diff:.2f} > 0)")
+                    should_open = True
+                elif direction == 'short' and intraday_diff < 0:
+                    log.info(f"  ✓ 方案1空头当天价差检查通过 ({intraday_diff:.2f} < 0)")
+                    should_open = True
+                else:
+                    log.info(f"  ✗ 方案1当天价差不符合{direction}方向要求")
+            elif g.ma_gap_strategy_mode == 2:
+                # 方案2:强制检查日内变化,使用专用阈值
+                if direction == 'long' and intraday_diff_ratio >= g.ma_intraday_threshold_scheme2:
+                    log.info(f"  ✓ 方案2多头日内变化检查通过 ({intraday_diff_ratio:.2%} >= {g.ma_intraday_threshold_scheme2:.2%})")
+                    should_open = True
+                elif direction == 'short' and intraday_diff_ratio <= -g.ma_intraday_threshold_scheme2:
+                    log.info(f"  ✓ 方案2空头日内变化检查通过 ({intraday_diff_ratio:.2%} <= {-g.ma_intraday_threshold_scheme2:.2%})")
+                    should_open = True
+                else:
+                    log.info(f"  ✗ 方案2日内变化不符合{direction}方向要求(阈值: ±{g.ma_intraday_threshold_scheme2:.2%})")
+            
+            if should_open:
+                # 执行开仓
+                log.info(f"  准备开仓: {symbol} {direction}")
+                target_hands = calculate_target_hands(context, dominant_future, direction)
+                
+                if target_hands > 0:
+                    success = open_position(context, dominant_future, target_hands, direction, 
+                                          f'均线形态开仓')
+                    if success:
+                        log.info(f"  ✓✓ {symbol} 开仓成功,从候选列表移除")
+                        candidates_to_remove.append(dominant_future)
+                    else:
+                        log.warning(f"  ✗ {symbol} 开仓失败")
+                else:
+                    log.warning(f"  ✗ {symbol} 计算目标手数为0,跳过开仓")
+                    
+        except Exception as e:
+            log.warning(f"{dominant_future} 处理时出错: {str(e)}")
+            continue
+    
+    # 从候选列表中移除已开仓的品种
+    for future in candidates_to_remove:
+        if future in g.daily_ma_candidates:
+            del g.daily_ma_candidates[future]
+    
+    log.info(f"剩余候选品种: {list(g.daily_ma_candidates.keys())}")
+    log.info("=" * 60)
+
+def check_stop_loss_profit(context):
+    """阶段三:止损止盈检查(所有时间点)"""
+    # 先检查换月移仓
+    position_auto_switch(context)
+    
+    # 获取当前时间
+    current_time = str(context.current_dt.time())[:2]
+    
+    # 判断是否为夜盘时间
+    is_night_session = (current_time in ['21', '22', '23', '00', '01', '02'])
+    
+    # 遍历所有持仓进行止损止盈检查
+    subportfolio = context.subportfolios[0]
+    long_positions = list(subportfolio.long_positions.values())
+    short_positions = list(subportfolio.short_positions.values())
+    
+    closed_count = 0
+    skipped_count = 0
+    
+    for position in long_positions + short_positions:
+        security = position.security
+        underlying_symbol = security.split('.')[0][:-4]
+        
+        # 检查交易时间适配性
+        has_night_session = get_futures_config(underlying_symbol, 'has_night_session', False)
+        
+        # 如果是夜盘时间,但品种不支持夜盘交易,则跳过
+        if is_night_session and not has_night_session:
+            skipped_count += 1
+            continue
+        
+        # 执行止损止盈检查
+        if check_position_stop_loss_profit(context, position):
+            closed_count += 1
+    
+    if closed_count > 0:
+        log.info(f"执行了 {closed_count} 次止损止盈")
+    
+    if skipped_count > 0:
+        log.info(f"夜盘时间跳过 {skipped_count} 个日间品种的止损止盈检查")
+
+def check_position_stop_loss_profit(context, position):
+    """检查单个持仓的止损止盈"""
+    security = position.security
+    
+    if security not in g.trade_history:
+        return False
+    
+    trade_info = g.trade_history[security]
+    direction = trade_info['direction']
+    entry_price = trade_info['entry_price']
+    entry_time = trade_info['entry_time']
+    current_price = position.price
+    
+    # 计算当前盈亏比率
+    if direction == 'long':
+        profit_rate = (current_price - entry_price) / entry_price
+    else:
+        profit_rate = (entry_price - current_price) / entry_price
+    
+    # 检查固定止损
+    if profit_rate <= -g.fixed_stop_loss_rate:
+        log.info(f"触发固定止损 {security} {direction}, 当前亏损率: {profit_rate:.3%}, "
+                f"成本价: {entry_price:.2f}, 当前价格: {current_price:.2f}")
+        close_position(context, security, direction)
+        return True
+    
+    # 检查是否启用均线跟踪止盈
+    if not trade_info.get('ma_trailing_enabled', True):
+        return False
+
+    # 检查均线跟踪止盈
+    # 获取持仓天数
+    entry_date = entry_time.date()
+    current_date = context.current_dt.date()
+    all_trade_days = get_all_trade_days()
+    holding_days = sum((entry_date <= d <= current_date) for d in all_trade_days)
+    
+    # 计算变化率
+    today_price = get_current_data()[security].last_price
+    avg_daily_change_rate = calculate_average_daily_change_rate(security)
+    historical_data = attribute_history(security, 1, '1d', ['close'])
+    yesterday_close = historical_data['close'].iloc[-1]
+    today_change_rate = abs((today_price - yesterday_close) / yesterday_close)
+    
+    # 根据时间判断使用的偏移量
+    current_time = context.current_dt.time()
+    target_time = datetime.strptime('14:55:00', '%H:%M:%S').time()
+    if current_time > target_time:
+        offset_ratio = g.ma_offset_ratio_close
+    else:
+        offset_ratio = g.ma_offset_ratio_normal
+    
+    # 选择止损均线
+    close_line = None
+    if today_change_rate >= 1.5 * avg_daily_change_rate:
+        close_line = 'ma5'  # 波动剧烈时用短周期
+    elif holding_days <= g.days_for_adjustment:
+        close_line = 'ma5'  # 持仓初期用短周期
+    else:
+        close_line = 'ma5' if today_change_rate >= 1.2 * avg_daily_change_rate else 'ma10'
+    
+    # 计算实时均线值
+    ma_values = calculate_realtime_ma_values(security, [5, 10])
+    ma_value = ma_values[close_line]
+    
+    # 应用偏移量
+    if direction == 'long':
+        adjusted_ma_value = ma_value * (1 - offset_ratio)
+    else:
+        adjusted_ma_value = ma_value * (1 + offset_ratio)
+    
+    # 判断是否触发均线止损
+    if (direction == 'long' and today_price < adjusted_ma_value) or \
+       (direction == 'short' and today_price > adjusted_ma_value):
+        log.info(f"触发均线跟踪止盈 {security} {direction}, 止损均线: {close_line}, "
+                f"均线值: {ma_value:.2f}, 调整后: {adjusted_ma_value:.2f}, "
+                f"当前价: {today_price:.2f}, 持仓天数: {holding_days}")
+        close_position(context, security, direction)
+        return True
+    
+    return False
+
+############################ 核心辅助函数 ###################################
+
+def calculate_ma_values(data, periods):
+    """计算均线值
+    
+    Args:
+        data: DataFrame,包含'close'列的历史数据(最后一行是最新的数据)
+        periods: list,均线周期列表,如[5, 10, 20, 30]
+    
+    Returns:
+        dict: {'MA5': value, 'MA10': value, 'MA20': value, 'MA30': value}
+        返回最后一行(最新日期)的各周期均线值
+    """
+    ma_values = {}
+    
+    for period in periods:
+        if len(data) >= period:
+            # 计算最后period天的均线值
+            ma_values[f'MA{period}'] = data['close'].iloc[-period:].mean()
+        else:
+            ma_values[f'MA{period}'] = None
+    
+    return ma_values
+
+
+def calculate_ma_proximity_counts(data, periods, lookback_days):
+    """统计近 lookback_days 天收盘价贴近各均线的次数"""
+    proximity_counts = {f'MA{period}': 0 for period in periods}
+
+    if len(data) < lookback_days:
+        return proximity_counts
+
+    closes = data['close'].iloc[-lookback_days:]
+    ma_series = {
+        period: data['close'].rolling(window=period).mean().iloc[-lookback_days:]
+        for period in periods
+    }
+
+    for idx, close_price in enumerate(closes):
+        min_diff = None
+        closest_period = None
+
+        for period in periods:
+            ma_value = ma_series[period].iloc[idx]
+            if pd.isna(ma_value):
+                continue
+            diff = abs(close_price - ma_value)
+            if min_diff is None or diff < min_diff:
+                min_diff = diff
+                closest_period = period
+
+        if closest_period is not None:
+            proximity_counts[f'MA{closest_period}'] += 1
+
+    return proximity_counts
+
+
+def check_ma_pattern(ma_values, direction):
+    """检查均线排列模式是否符合方向要求
+    
+    Args:
+        ma_values: dict,包含MA5, MA10, MA20, MA30的均线值
+        direction: str,'long'或'short'
+    
+    Returns:
+        bool: 是否符合均线排列要求
+    """
+    ma5 = ma_values['MA5']
+    ma10 = ma_values['MA10']
+    ma20 = ma_values['MA20']
+    ma30 = ma_values['MA30']
+    
+    if direction == 'long':
+        # 多头模式:MA30 <= MA20 <= MA10 <= MA5 或 MA30 <= MA20 <= MA5 <= MA10
+        pattern1 = (ma30 <= ma20 <= ma10 <= ma5)
+        pattern2 = (ma30 <= ma20 <= ma5 <= ma10)
+        return pattern1 or pattern2
+    elif direction == 'short':
+        # 空头模式:MA10 <= MA5 <= MA20 <= MA30 或 MA5 <= MA10 <= MA20 <= MA30
+        pattern1 = (ma10 <= ma5 <= ma20 <= ma30)
+        pattern2 = (ma5 <= ma10 <= ma20 <= ma30)
+        return pattern1 or pattern2
+    else:
+        return False
+
+def check_historical_ma_pattern_consistency(historical_data, direction, lookback_days, consistency_threshold):
+    """检查历史均线模式的一致性
+    
+    Args:
+        historical_data: DataFrame,包含足够天数的历史数据
+        direction: str,'long'或'short'
+        lookback_days: int,检查过去多少天
+        consistency_threshold: float,一致性阈值(0-1之间)
+    
+    Returns:
+        tuple: (bool, float) - (是否通过一致性检查, 实际一致性比例)
+    """
+    if len(historical_data) < max(g.ma_periods) + lookback_days:
+        # 历史数据不足
+        return False, 0.0
+    
+    match_count = 0
+    total_count = lookback_days
+    
+    # 检查过去lookback_days天的均线模式
+    for i in range(lookback_days):
+        # 获取倒数第(i+1)天的数据(i=0时是昨天,i=1时是前天,依此类推)
+        end_idx = -(i + 1)
+        if end_idx == -1:
+            data_slice = historical_data
+        else:
+            data_slice = historical_data.iloc[:end_idx]
+        
+        # 计算该天的均线值
+        ma_values = calculate_ma_values(data_slice, g.ma_periods)
+        
+        # 检查是否符合模式
+        if check_ma_pattern(ma_values, direction):
+            match_count += 1
+    
+    consistency_ratio = match_count / total_count
+    passed = consistency_ratio >= consistency_threshold
+    
+    return passed, consistency_ratio
+
+############################ 交易执行函数 ###################################
+
+def open_position(context, security, target_hands, direction, reason=''):
+    """开仓"""
+    try:
+        # 记录交易前的可用资金
+        cash_before = context.portfolio.available_cash
+        
+        # 使用order_target按手数开仓
+        order = order_target(security, target_hands, side=direction)
+        
+        if order is not None and order.filled > 0:
+            # 记录交易后的可用资金
+            cash_after = context.portfolio.available_cash
+            
+            # 计算实际资金变化
+            cash_change = cash_before - cash_after
+            
+            # 获取订单价格和数量
+            order_price = order.avg_cost if order.avg_cost else order.price
+            order_amount = order.filled
+            
+            # 记录当日交易
+            underlying_symbol = security.split('.')[0][:-4]
+            g.today_trades.append({
+                'security': security,
+                'underlying_symbol': underlying_symbol,
+                'direction': direction,
+                'order_amount': order_amount,
+                'order_price': order_price,
+                'cash_change': cash_change,
+                'time': context.current_dt
+            })
+            
+            # 记录交易信息
+            g.trade_history[security] = {
+                'entry_price': order_price,
+                'target_hands': target_hands,
+                'actual_hands': order_amount,
+                'actual_margin': cash_change,
+                'direction': direction,
+                'entry_time': context.current_dt
+            }
+
+            ma_trailing_enabled = True
+            if direction == 'long':
+                ma_values_at_entry = calculate_realtime_ma_values(security, [5])
+                ma5_value = ma_values_at_entry.get('ma5')
+                if ma5_value is not None and order_price < ma5_value:
+                    ma_trailing_enabled = False
+                    log.info(f"禁用均线跟踪止盈: {security} {direction}, 开仓价 {order_price:.2f} < MA5 {ma5_value:.2f}")
+
+            g.trade_history[security]['ma_trailing_enabled'] = ma_trailing_enabled
+            
+            log.info(f"开仓成功: {security} {direction} {order_amount}手 @{order_price:.2f}, "
+                    f"保证金: {cash_change:.0f}, 原因: {reason}")
+            
+            return True
+            
+    except Exception as e:
+        log.warning(f"开仓失败 {security}: {str(e)}")
+    
+    return False
+
+def close_position(context, security, direction):
+    """平仓"""
+    try:
+        # 使用order_target平仓到0手
+        order = order_target(security, 0, side=direction)
+        
+        if order is not None and order.filled > 0:
+            underlying_symbol = security.split('.')[0][:-4]
+            
+            # 记录当日交易(平仓)
+            g.today_trades.append({
+                'security': security,
+                'underlying_symbol': underlying_symbol,
+                'direction': direction,
+                'order_amount': -order.filled,
+                'order_price': order.avg_cost if order.avg_cost else order.price,
+                'cash_change': 0,
+                'time': context.current_dt
+            })
+            
+            log.info(f"平仓成功: {underlying_symbol} {direction} {order.filled}手")
+            
+            # 从交易历史中移除
+            if security in g.trade_history:
+                del g.trade_history[security]
+            return True
+            
+    except Exception as e:
+        log.warning(f"平仓失败 {security}: {str(e)}")
+    
+    return False
+
+############################ 辅助函数 ###################################
+
+def get_futures_config(underlying_symbol, config_key=None, default_value=None):
+    """获取期货品种配置信息的辅助函数"""
+    if underlying_symbol not in g.futures_config:
+        if config_key and default_value is not None:
+            return default_value
+        return {}
+    
+    if config_key is None:
+        return g.futures_config[underlying_symbol]
+    
+    return g.futures_config[underlying_symbol].get(config_key, default_value)
+
+def get_margin_rate(underlying_symbol, direction, default_rate=0.10):
+    """获取保证金比例的辅助函数"""
+    return g.futures_config.get(underlying_symbol, {}).get('margin_rate', {}).get(direction, default_rate)
+
+def get_multiplier(underlying_symbol, default_multiplier=10):
+    """获取合约乘数的辅助函数"""
+    return g.futures_config.get(underlying_symbol, {}).get('multiplier', default_multiplier)
+
+def calculate_target_hands(context, security, direction):
+    """计算目标开仓手数"""
+    current_price = get_current_data()[security].last_price
+    underlying_symbol = security.split('.')[0][:-4]
+    
+    # 使用保证金比例
+    margin_rate = get_margin_rate(underlying_symbol, direction)
+    multiplier = get_multiplier(underlying_symbol)
+    
+    # 计算单手保证金
+    single_hand_margin = current_price * multiplier * margin_rate
+    
+    # 还要考虑可用资金限制
+    available_cash = context.portfolio.available_cash * g.usage_percentage
+    
+    # 根据单个标的最大持仓保证金限制计算开仓数量
+    max_margin = g.max_margin_per_position
+    
+    if single_hand_margin <= max_margin:
+        # 如果单手保证金不超过最大限制,计算最大可开仓手数
+        max_hands = int(max_margin / single_hand_margin)
+        max_hands_by_cash = int(available_cash / single_hand_margin)
+        
+        # 取两者较小值
+        actual_hands = min(max_hands, max_hands_by_cash)
+        
+        # 确保至少开1手
+        actual_hands = max(1, actual_hands)
+        
+        log.info(f"单手保证金: {single_hand_margin:.0f}, 目标开仓手数: {actual_hands}")
+        
+        return actual_hands
+    else:
+        # 如果单手保证金超过最大限制,默认开仓1手
+        actual_hands = 1
+        
+        log.info(f"单手保证金: {single_hand_margin:.0f} 超过最大限制: {max_margin}, 默认开仓1手")
+        
+        return actual_hands
+
+def check_symbol_prefix_match(symbol, hold_symbols):
+    """检查是否有相似的持仓品种"""
+    symbol_prefix = symbol[:-9]
+    
+    for hold_symbol in hold_symbols:
+        hold_symbol_prefix = hold_symbol[:-9] if len(hold_symbol) > 9 else hold_symbol
+        
+        if symbol_prefix == hold_symbol_prefix:
+            return True
+    return False
+
+def calculate_average_daily_change_rate(security, days=30):
+    """计算日均变化率"""
+    historical_data = attribute_history(security, days + 1, '1d', ['close'])
+    daily_change_rates = abs(historical_data['close'].pct_change()).iloc[1:]
+    return daily_change_rates.mean()
+
+def calculate_realtime_ma_values(security, ma_periods):
+    """计算包含当前价格的实时均线值"""
+    historical_data = attribute_history(security, max(ma_periods), '1d', ['close'])
+    today_price = get_current_data()[security].last_price
+    close_prices = historical_data['close'].tolist() + [today_price]
+    ma_values = {f'ma{period}': sum(close_prices[-period:]) / period for period in ma_periods}
+    return ma_values
+
+def after_market_close(context):
+    """收盘后运行函数"""
+    log.info(str('函数运行时间(after_market_close):'+str(context.current_dt.time())))
+    
+    # 清空候选列表(每天重新检查)
+    g.daily_ma_candidates = {}
+    
+    # 清空排除缓存(每天重新检查)
+    excluded_count = len(g.excluded_contracts)
+    if excluded_count > 0:
+        log.info(f"清空排除缓存,共 {excluded_count} 个合约")
+        g.excluded_contracts = {}
+    
+    # 只有当天有交易时才打印统计信息
+    if g.today_trades:
+        print_daily_trading_summary(context)
+        
+        # 清空当日交易记录
+        g.today_trades = []
+    
+    log.info('##############################################################')
+
+def print_daily_trading_summary(context):
+    """打印当日交易汇总"""
+    if not g.today_trades:
+        return
+    
+    log.info("\n=== 当日交易汇总 ===")
+    total_margin = 0
+    
+    for trade in g.today_trades:
+        if trade['order_amount'] > 0:  # 开仓
+            log.info(f"开仓 {trade['underlying_symbol']} {trade['direction']} {trade['order_amount']}手 "
+                  f"价格:{trade['order_price']:.2f} 保证金:{trade['cash_change']:.0f}")
+            total_margin += trade['cash_change']
+        else:  # 平仓
+            log.info(f"平仓 {trade['underlying_symbol']} {trade['direction']} {abs(trade['order_amount'])}手 "
+                  f"价格:{trade['order_price']:.2f}")
+    
+    log.info(f"当日保证金占用: {total_margin:.0f}")
+    log.info("==================\n")
+
+########################## 自动移仓换月函数 #################################
+def position_auto_switch(context, pindex=0, switch_func=None, callback=None):
+    """期货自动移仓换月"""
+    import re
+    subportfolio = context.subportfolios[pindex]
+    symbols = set(subportfolio.long_positions.keys()) | set(subportfolio.short_positions.keys())
+    switch_result = []
+    for symbol in symbols:
+        match = re.match(r"(?P<underlying_symbol>[A-Z]{1,})", symbol)
+        if not match:
+            raise ValueError("未知期货标的: {}".format(symbol))
+        else:
+            dominant = get_dominant_future(match.groupdict()["underlying_symbol"])
+            cur = get_current_data()
+            symbol_last_price = cur[symbol].last_price
+            dominant_last_price = cur[dominant].last_price
+            
+            if dominant > symbol:
+                for positions_ in (subportfolio.long_positions, subportfolio.short_positions):
+                    if symbol not in positions_.keys():
+                        continue
+                    else :
+                        p = positions_[symbol]
+
+                    if switch_func is not None:
+                        switch_func(context, pindex, p, dominant)
+                    else:
+                        amount = p.total_amount
+                        # 跌停不能开空和平多,涨停不能开多和平空
+                        if p.side == "long":
+                            symbol_low_limit = cur[symbol].low_limit
+                            dominant_high_limit = cur[dominant].high_limit
+                            if symbol_last_price <= symbol_low_limit:
+                                log.warning("标的{}跌停,无法平仓。移仓换月取消。".format(symbol))
+                                continue
+                            elif dominant_last_price >= dominant_high_limit:
+                                log.warning("标的{}涨停,无法开仓。移仓换月取消。".format(dominant))
+                                continue
+                            else:
+                                log.info("进行移仓换月: ({0},long) -> ({1},long)".format(symbol, dominant))
+                                order_old = order_target(symbol, 0, side='long')
+                                if order_old != None and order_old.filled > 0:
+                                    order_new = order_target(dominant, amount, side='long')
+                                    if order_new != None and order_new.filled > 0:
+                                        switch_result.append({"before": symbol, "after": dominant, "side": "long"})
+                                        # 换月成功,更新交易记录
+                                        if symbol in g.trade_history:
+                                            g.trade_history[dominant] = g.trade_history[symbol]
+                                            del g.trade_history[symbol]
+                                    else:
+                                        log.warning("标的{}交易失败,无法开仓。移仓换月失败。".format(dominant))
+                        if p.side == "short":
+                            symbol_high_limit = cur[symbol].high_limit
+                            dominant_low_limit = cur[dominant].low_limit
+                            if symbol_last_price >= symbol_high_limit:
+                                log.warning("标的{}涨停,无法平仓。移仓换月取消。".format(symbol))
+                                continue
+                            elif dominant_last_price <= dominant_low_limit:
+                                log.warning("标的{}跌停,无法开仓。移仓换月取消。".format(dominant))
+                                continue
+                            else:
+                                log.info("进行移仓换月: ({0},short) -> ({1},short)".format(symbol, dominant))
+                                order_old = order_target(symbol, 0, side='short')
+                                if order_old != None and order_old.filled > 0:
+                                    order_new = order_target(dominant, amount, side='short')
+                                    if order_new != None and order_new.filled > 0:
+                                        switch_result.append({"before": symbol, "after": dominant, "side": "short"})
+                                        # 换月成功,更新交易记录
+                                        if symbol in g.trade_history:
+                                            g.trade_history[dominant] = g.trade_history[symbol]
+                                            del g.trade_history[symbol]
+                                    else:
+                                        log.warning("标的{}交易失败,无法开仓。移仓换月失败。".format(dominant))
+                        if callback:
+                            callback(context, pindex, p, dominant)
+    return switch_result
+

+ 1450 - 0
Lib/future/MAPatternStrategy_v002.py

@@ -0,0 +1,1450 @@
+# 导入函数库
+from jqdata import *
+from jqdata import finance
+import pandas as pd
+import numpy as np
+import math
+from datetime import date, datetime, timedelta, time
+import re
+
+# 顺势交易策略 v001
+# 基于均线走势(前提条件)+ K线形态(开盘价差、当天价差)的期货交易策略
+#
+# 核心逻辑:
+# 1. 开盘时检查均线走势(MA30<=MA20<=MA10<=MA5为多头,反之为空头)
+# 2. 检查开盘价差是否符合方向要求(多头>=0.5%,空头<=-0.5%)
+# 3. 14:35和14:55检查当天价差(多头>0,空头<0),满足条件则开仓
+# 4. 应用固定止损和动态追踪止盈
+# 5. 自动换月移仓
+
+# 设置以便完整打印 DataFrame
+pd.set_option('display.max_rows', None)
+pd.set_option('display.max_columns', None)
+pd.set_option('display.width', None)
+pd.set_option('display.max_colwidth', 20)
+
+## 初始化函数,设定基准等等
+def initialize(context):
+    # 设定沪深300作为基准
+    set_benchmark('000300.XSHG')
+    # 开启动态复权模式(真实价格)
+    set_option('use_real_price', True)
+    # 输出内容到日志
+    log.info('=' * 60)
+    log.info('均线形态交易策略 v001 初始化开始')
+    log.info('策略类型: 均线走势 + K线形态')
+    log.info('=' * 60)
+
+    ### 期货相关设定 ###
+    # 设定账户为金融账户
+    set_subportfolios([SubPortfolioConfig(cash=context.portfolio.starting_cash, type='index_futures')])
+    # 期货类每笔交易时的手续费是: 买入时万分之0.23,卖出时万分之0.23,平今仓为万分之23
+    set_order_cost(OrderCost(open_commission=0.000023, close_commission=0.000023, close_today_commission=0.0023), type='index_futures')
+    
+    # 设置期货交易的滑点
+    set_slippage(StepRelatedSlippage(2))
+    
+    # 初始化全局变量
+    g.usage_percentage = 0.8  # 最大资金使用比例
+    g.max_margin_per_position = 20000  # 单个标的最大持仓保证金(元)
+    
+    # 均线策略参数
+    g.ma_periods = [5, 10, 20, 30]  # 均线周期
+    g.ma_historical_days = 60  # 获取历史数据天数(确保足够计算MA30)
+    g.ma_open_gap_threshold = 0.001  # 方案1开盘价差阈值(0.2%)
+    g.ma_pattern_lookback_days = 10  # 历史均线模式一致性检查的天数
+    g.ma_pattern_consistency_threshold = 0.8  # 历史均线模式一致性阈值(80%)
+    g.check_intraday_spread = False  # 是否检查日内价差(True: 检查, False: 跳过)
+    g.ma_proximity_min_threshold = 8  # MA5与MA10贴近计数和的最低阈值
+    g.ma_pattern_extreme_days_threshold = 4  # 极端趋势天数阈值
+    g.ma_distribution_lookback_days = 5  # MA5分布过滤回溯天数
+    g.ma_distribution_min_ratio = 0.4  # MA5分布满足比例阈值
+    g.enable_ma_distribution_filter = True  # 是否启用MA5分布过滤
+    g.ma_cross_threshold = 1  # 均线穿越数量阈值
+    
+    # 均线价差策略方案选择
+    g.ma_gap_strategy_mode = 3  # 策略模式选择(1: 原方案, 2: 新方案, 3: 方案3)
+    g.ma_open_gap_threshold2 = 0.001  # 方案2开盘价差阈值(0.2%)
+    g.ma_intraday_threshold_scheme2 = 0.005  # 方案2日内变化阈值(0.5%)
+    
+    # 止损止盈策略参数
+    g.fixed_stop_loss_rate = 0.01  # 固定止损比率(1%)
+    g.ma_offset_ratio_normal = 0.003  # 均线跟踪止盈常规偏移量(0.3%)
+    g.ma_offset_ratio_close = 0.01  # 均线跟踪止盈收盘前偏移量(1%)
+    g.days_for_adjustment = 4  # 持仓天数调整阈值
+    
+    # 输出策略参数
+    log.info("均线形态策略参数:")
+    log.info(f"  均线周期: {g.ma_periods}")
+    log.info(f"  策略模式: 方案{g.ma_gap_strategy_mode}")
+    log.info(f"  方案1开盘价差阈值: {g.ma_open_gap_threshold:.1%}")
+    log.info(f"  方案2开盘价差阈值: {g.ma_open_gap_threshold2:.1%}")
+    log.info(f"  方案2日内变化阈值: {g.ma_intraday_threshold_scheme2:.1%}")
+    log.info(f"  历史均线模式检查天数: {g.ma_pattern_lookback_days}天")
+    log.info(f"  历史均线模式一致性阈值: {g.ma_pattern_consistency_threshold:.1%}")
+    log.info(f"  极端趋势天数阈值: {g.ma_pattern_extreme_days_threshold}")
+    log.info(f"  均线贴近计数阈值: {g.ma_proximity_min_threshold}")
+    log.info(f"  MA5分布过滤天数: {g.ma_distribution_lookback_days}")
+    log.info(f"  MA5分布最低比例: {g.ma_distribution_min_ratio:.0%}")
+    log.info(f"  启用MA5分布过滤: {g.enable_ma_distribution_filter}")
+    log.info(f"  是否检查日内价差: {g.check_intraday_spread}")
+    log.info(f"  均线穿越阈值: {g.ma_cross_threshold}")
+    log.info(f"  固定止损: {g.fixed_stop_loss_rate:.1%}")
+    log.info(f"  均线跟踪止盈常规偏移: {g.ma_offset_ratio_normal:.1%}")
+    log.info(f"  均线跟踪止盈收盘前偏移: {g.ma_offset_ratio_close:.1%}")
+    log.info(f"  持仓天数调整阈值: {g.days_for_adjustment}天")
+    
+    # 期货品种完整配置字典
+    g.futures_config = {
+        # 贵金属
+        'AU': {'has_night_session': True, 'margin_rate': {'long': 0.21, 'short': 0.21}, 'multiplier': 1000, 'trading_start_time': '21:00'},
+        'AG': {'has_night_session': True, 'margin_rate': {'long': 0.22, 'short': 0.22}, 'multiplier': 15, 'trading_start_time': '21:00'},
+        
+        # 有色金属
+        'CU': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'AL': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'ZN': {'has_night_session': True, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'PB': {'has_night_session': True, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'NI': {'has_night_session': True, 'margin_rate': {'long': 0.16, 'short': 0.16}, 'multiplier': 1, 'trading_start_time': '21:00'},
+        'SN': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 1, 'trading_start_time': '21:00'},
+        'SS': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        
+        # 黑色系
+        'RB': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'HC': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'I': {'has_night_session': True, 'margin_rate': {'long': 0.16, 'short': 0.16}, 'multiplier': 100, 'trading_start_time': '21:00'},
+        'JM': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 100, 'trading_start_time': '21:00'},
+        'J': {'has_night_session': True, 'margin_rate': {'long': 0.25, 'short': 0.25}, 'multiplier': 60, 'trading_start_time': '21:00'},
+        
+        # 能源化工
+        'SP': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'FU': {'has_night_session': True, 'margin_rate': {'long': 0.16, 'short': 0.16}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'BU': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'RU': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'BR': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'SC': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 1000, 'trading_start_time': '21:00'},
+        'NR': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'LU': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'LC': {'has_night_session': False, 'margin_rate': {'long': 0.16, 'short': 0.16}, 'multiplier': 1, 'trading_start_time': '09:00'},
+        
+        # 化工
+        'FG': {'has_night_session': True, 'margin_rate': {'long': 0.16, 'short': 0.16}, 'multiplier': 20, 'trading_start_time': '21:00'},
+        'TA': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'MA': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'SA': {'has_night_session': True, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 20, 'trading_start_time': '21:00'},
+        'L': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'V': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'EG': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'PP': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'EB': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'PG': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 20, 'trading_start_time': '21:00'},
+        'PX': {'has_night_session': True, 'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        
+        # 农产品
+        'RM': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'OI': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'CF': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'SR': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'PF': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'C': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'CS': {'has_night_session': True, 'margin_rate': {'long': 0.11, 'short': 0.11}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'CY': {'has_night_session': True, 'margin_rate': {'long': 0.11, 'short': 0.11}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'A': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'B': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'M': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'Y': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'P': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        
+        # 无夜盘品种
+        'IF': {'has_night_session': False, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 300, 'trading_start_time': '09:30'},
+        'IH': {'has_night_session': False, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 300, 'trading_start_time': '09:30'},
+        'IC': {'has_night_session': False, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 200, 'trading_start_time': '09:30'},
+        'IM': {'has_night_session': False, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 200, 'trading_start_time': '09:30'},
+        'AP': {'has_night_session': False, 'margin_rate': {'long': 0.16, 'short': 0.16}, 'multiplier': 10, 'trading_start_time': '09:00'},
+        'CJ': {'has_night_session': False, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 5, 'trading_start_time': '09:00'},
+        'PK': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 5, 'trading_start_time': '09:00'},
+        'JD': {'has_night_session': False, 'margin_rate': {'long': 0.11, 'short': 0.11}, 'multiplier': 10, 'trading_start_time': '09:00'},
+        'LH': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 16, 'trading_start_time': '09:00'},
+        'T': {'has_night_session': False, 'margin_rate': {'long': 0.03, 'short': 0.03}, 'multiplier': 1000000, 'trading_start_time': '09:30'},
+        'PS': {'has_night_session': False, 'margin_rate': {'long': 0.16, 'short': 0.16}, 'multiplier': 3, 'trading_start_time': '09:00'},
+        'UR': {'has_night_session': False, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 20, 'trading_start_time': '09:00'},
+        'MO': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 100, 'trading_start_time': '21:00'},
+        # 'LF': {'has_night_session': False, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1, 'trading_start_time': '09:30'},
+        'HO': {'has_night_session': False, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 100, 'trading_start_time': '09:30'},
+        'LR': {'has_night_session': True, 'margin_rate': {'long': 0.21, 'short': 0.21}, 'multiplier': 20, 'trading_start_time': '21:00'},
+        'LG': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 90, 'trading_start_time': '21:00'},
+        # 'FB': {'has_night_session': True, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'PM': {'has_night_session': True, 'margin_rate': {'long': 0.2, 'short': 0.2}, 'multiplier': 50, 'trading_start_time': '21:00'},
+        'EC': {'has_night_session': False, 'margin_rate': {'long': 0.23, 'short': 0.23}, 'multiplier': 50, 'trading_start_time': '09:00'},
+        # 'RR': {'has_night_session': True, 'margin_rate': {'long': 0.11, 'short': 0.11}, 'multiplier': 10, 'trading_start_time': '21:00'},
+        'OP': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 40, 'trading_start_time': '09:00'},
+        # 'IO': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 1, 'trading_start_time': '21:00'},
+        'BC': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '21:00'},
+        'WH': {'has_night_session': False, 'margin_rate': {'long': 0.2, 'short': 0.2}, 'multiplier': 20, 'trading_start_time': '09:00'},
+        'SH': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 30, 'trading_start_time': '21:00'},
+        'RI': {'has_night_session': False, 'margin_rate': {'long': 0.21, 'short': 0.21}, 'multiplier': 20, 'trading_start_time': '09:00'},
+        'TS': {'has_night_session': False, 'margin_rate': {'long': 0.015, 'short': 0.015}, 'multiplier': 2000000, 'trading_start_time': '09:30'},
+        'JR': {'has_night_session': False, 'margin_rate': {'long': 0.21, 'short': 0.21}, 'multiplier': 20, 'trading_start_time': '09:00'},
+        'AD': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '09:00'},
+        # 'BB': {'has_night_session': False, 'margin_rate': {'long': 0.19, 'short': 0.19}, 'multiplier': 500, 'trading_start_time': '09:00'},
+        'PL': {'has_night_session': False, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 20, 'trading_start_time': '09:00'},
+        'RS': {'has_night_session': False, 'margin_rate': {'long': 0.26, 'short': 0.26}, 'multiplier': 10, 'trading_start_time': '09:00'},
+        'SI': {'has_night_session': False, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '09:00'},
+        'ZC': {'has_night_session': True, 'margin_rate': {'long': 0.56, 'short': 0.56}, 'multiplier': 100, 'trading_start_time': '21:00'},
+        'SM': {'has_night_session': False, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '09:00'},
+        'AO': {'has_night_session': True, 'margin_rate': {'long': 0.17, 'short': 0.17}, 'multiplier': 20, 'trading_start_time': '21:00'},
+        'TL': {'has_night_session': False, 'margin_rate': {'long': 0.045, 'short': 0.045}, 'multiplier': 1000000, 'trading_start_time': '09:00'},
+        'SF': {'has_night_session': False, 'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 5, 'trading_start_time': '09:00'},
+        # 'WR': {'has_night_session': False, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 10, 'trading_start_time': '09:00'},
+        'PR': {'has_night_session': True, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 15, 'trading_start_time': '21:00'},
+        'TF': {'has_night_session': False, 'margin_rate': {'long': 0.022, 'short': 0.022}, 'multiplier': 1000000, 'trading_start_time': '09:00'},
+        # 'VF': {'has_night_session': False, 'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1, 'trading_start_time': '09:00'},
+        'BZ': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 30, 'trading_start_time': '09:00'},
+    }
+    
+    # 策略品种选择策略配置
+    # 方案1:全品种策略 - 考虑所有配置的期货品种
+    g.strategy_focus_symbols = []  # 空列表表示考虑所有品种
+    
+    # 方案2:精选品种策略 - 只交易流动性较好的特定品种(如需使用请取消下行注释)
+    # g.strategy_focus_symbols = ['RM', 'CJ', 'CY', 'JD', 'L', 'LC', 'SF', 'SI']
+    
+    log.info(f"品种选择策略: {'全品种策略(覆盖所有配置品种)' if not g.strategy_focus_symbols else '精选品种策略(' + str(len(g.strategy_focus_symbols)) + '个品种)'}")
+    
+    # 交易记录和数据存储
+    g.trade_history = {}  # 持仓记录 {symbol: {'entry_price': xxx, 'direction': xxx, ...}}
+    g.daily_ma_candidates = {}  # 通过均线和开盘价差检查的候选品种 {symbol: {'direction': 'long'/'short', 'open_price': xxx, ...}}
+    g.today_trades = []  # 当日交易记录
+    g.excluded_contracts = {}  # 每日排除的合约缓存 {dominant_future: {'reason': 'ma_trend'/'open_gap', 'trading_day': xxx}}
+    g.ma_checked_underlyings = {}  # 记录各品种在交易日的均线检查状态 {symbol: trading_day}
+    g.last_ma_trading_day = None  # 最近一次均线检查所属交易日
+    
+    # 定时任务设置
+    # 夜盘开始(21:05) - 均线和开盘价差检查
+    run_daily(check_ma_trend_and_open_gap, time='21:05:00', reference_security='IF1808.CCFX')
+    
+    # 日盘开始 - 均线和开盘价差检查
+    run_daily(check_ma_trend_and_open_gap, time='09:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_ma_trend_and_open_gap, time='09:35:00', reference_security='IF1808.CCFX')
+    
+    # 夜盘开仓和止损止盈检查
+    run_daily(check_open_and_stop, time='21:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_open_and_stop, time='21:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_open_and_stop, time='22:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_open_and_stop, time='22:35:00', reference_security='IF1808.CCFX')
+    
+    # 日盘开仓和止损止盈检查
+    run_daily(check_open_and_stop, time='09:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_open_and_stop, time='09:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_open_and_stop, time='10:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_open_and_stop, time='10:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_open_and_stop, time='11:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_open_and_stop, time='11:25:00', reference_security='IF1808.CCFX')
+    run_daily(check_open_and_stop, time='13:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_open_and_stop, time='14:05:00', reference_security='IF1808.CCFX')
+    run_daily(check_open_and_stop, time='14:35:00', reference_security='IF1808.CCFX')
+    run_daily(check_open_and_stop, time='14:55:00', reference_security='IF1808.CCFX')
+    run_daily(check_ma_trailing_reactivation, time='14:55:00', reference_security='IF1808.CCFX')
+    
+    # 收盘后
+    run_daily(after_market_close, time='15:30:00', reference_security='IF1808.CCFX')
+    
+    log.info('=' * 60)
+
+############################ 主程序执行函数 ###################################
+
+def get_current_trading_day(current_dt):
+    """根据当前时间推断对应的期货交易日"""
+    current_date = current_dt.date()
+    current_time = current_dt.time()
+
+    trade_days = get_trade_days(end_date=current_date, count=1)
+    if trade_days and trade_days[0] == current_date:
+        trading_day = current_date
+    else:
+        next_days = get_trade_days(start_date=current_date, count=1)
+        trading_day = next_days[0] if next_days else current_date
+
+    if current_time >= time(20, 59):
+        next_trade_days = get_trade_days(start_date=trading_day, count=2)
+        if len(next_trade_days) >= 2:
+            return next_trade_days[1]
+        if len(next_trade_days) == 1:
+            return next_trade_days[0]
+    return trading_day
+
+
+def normalize_trade_day_value(value):
+    """将交易日对象统一转换为 datetime.date"""
+    if isinstance(value, date) and not isinstance(value, datetime):
+        return value
+    if isinstance(value, datetime):
+        return value.date()
+    if hasattr(value, 'to_pydatetime'):
+        return value.to_pydatetime().date()
+    try:
+        return pd.Timestamp(value).date()
+    except Exception:
+        return value
+
+
+def check_ma_trend_and_open_gap(context):
+    """阶段一:开盘时均线走势和开盘价差检查(一天一次)"""
+    log.info("=" * 60)
+    current_trading_day = get_current_trading_day(context.current_dt)
+    log.info(f"执行均线走势和开盘价差检查 - 时间: {context.current_dt}, 交易日: {current_trading_day}")
+    log.info("=" * 60)
+    
+    # 换月移仓检查(在所有部分之前)
+    position_auto_switch(context)
+    
+    # ==================== 第一部分:基础数据获取 ====================
+    
+    # 步骤1:交易日检查和缓存清理
+    if g.last_ma_trading_day != current_trading_day:
+        if g.excluded_contracts:
+            log.info(f"交易日切换至 {current_trading_day},清空上一交易日的排除缓存")
+        g.excluded_contracts = {}
+        g.ma_checked_underlyings = {}
+        g.last_ma_trading_day = current_trading_day
+
+    # 步骤2:获取当前时间和筛选可交易品种
+    current_time = str(context.current_dt.time())[:5]  # HH:MM格式
+    focus_symbols = g.strategy_focus_symbols if g.strategy_focus_symbols else list(g.futures_config.keys())
+    tradable_symbols = []
+    
+    # 根据当前时间确定可交易的时段
+    # 21:05 -> 仅接受21:00开盘的合约
+    # 09:05 -> 接受09:00或21:00开盘的合约
+    # 09:35 -> 接受所有时段(21:00, 09:00, 09:30)的合约
+    for symbol in focus_symbols:
+        trading_start_time = get_futures_config(symbol, 'trading_start_time', '09:05')
+        should_trade = False
+        
+        if current_time == '21:05':
+            should_trade = trading_start_time.startswith('21:00')
+        elif current_time == '09:05':
+            should_trade = trading_start_time.startswith('21:00') or trading_start_time.startswith('09:00')
+        elif current_time == '09:35':
+            should_trade = True
+        
+        if should_trade:
+            tradable_symbols.append(symbol)
+    
+    if not tradable_symbols:
+        log.info(f"当前时间 {current_time} 无品种开盘,跳过检查")
+        return
+    
+    log.info(f"当前时间 {current_time} 开盘品种: {tradable_symbols}")
+    
+    # 步骤3:对每个品种循环处理
+    for symbol in tradable_symbols:
+        # 步骤3.1:检查是否已处理过
+        if g.ma_checked_underlyings.get(symbol) == current_trading_day:
+            log.info(f"{symbol} 已在交易日 {current_trading_day} 完成均线检查,跳过本次执行")
+            continue
+
+        try:
+            g.ma_checked_underlyings[symbol] = current_trading_day
+            
+            # 步骤3.2:获取主力合约
+            dominant_future = get_dominant_future(symbol)
+            if not dominant_future:
+                log.info(f"{symbol} 未找到主力合约,跳过")
+                continue
+            
+            # 步骤3.3:检查排除缓存
+            if dominant_future in g.excluded_contracts:
+                excluded_info = g.excluded_contracts[dominant_future]
+                if excluded_info['trading_day'] == current_trading_day:
+                    continue
+                else:
+                    # 新的一天,从缓存中移除(会在after_market_close统一清理,这里也做兜底)
+                    del g.excluded_contracts[dominant_future]
+            
+            # 步骤3.4:检查是否已有持仓
+            if check_symbol_prefix_match(dominant_future, set(g.trade_history.keys())):
+                log.info(f"{symbol} 已有持仓,跳过")
+                continue
+            
+            # 步骤3.5:获取历史数据和前一交易日数据(合并优化)
+            # 获取历史数据(需要足够计算MA30)
+            historical_data = get_price(dominant_future, end_date=context.current_dt, 
+                                       frequency='1d', fields=['open', 'close', 'high', 'low'], 
+                                       count=g.ma_historical_days)
+            
+            if historical_data is None or len(historical_data) < max(g.ma_periods):
+                log.info(f"{symbol} 历史数据不足,跳过")
+                continue
+
+            # 获取前一交易日并在历史数据中匹配
+            previous_trade_days = get_trade_days(end_date=current_trading_day, count=2)
+            previous_trade_days = [normalize_trade_day_value(d) for d in previous_trade_days]
+            previous_trading_day = None
+            if len(previous_trade_days) >= 2:
+                previous_trading_day = previous_trade_days[-2]
+            elif len(previous_trade_days) == 1 and previous_trade_days[0] < current_trading_day:
+                previous_trading_day = previous_trade_days[0]
+
+            if previous_trading_day is None:
+                log.info(f"{symbol} 无法确定前一交易日,跳过")
+                continue
+
+            # 在历史数据中匹配前一交易日
+            historical_dates = historical_data.index.date
+            match_indices = np.where(historical_dates == previous_trading_day)[0]
+            if len(match_indices) == 0:
+                earlier_indices = np.where(historical_dates < previous_trading_day)[0]
+                if len(earlier_indices) == 0:
+                    log.info(f"{symbol} 历史数据缺少 {previous_trading_day} 之前的记录,跳过")
+                    continue
+                match_indices = [earlier_indices[-1]]
+
+            # 提取截至前一交易日的数据,并一次性提取所有需要的字段
+            data_upto_yesterday = historical_data.iloc[:match_indices[-1] + 1]
+            yesterday_data = data_upto_yesterday.iloc[-1]
+            yesterday_close = yesterday_data['close']
+            yesterday_open = yesterday_data['open']
+            
+            # 步骤3.6:获取当前价格数据
+            current_data = get_current_data()[dominant_future]
+            today_open = current_data.day_open
+            
+            # ==================== 第二部分:核心指标计算 ====================
+            
+            # 步骤4:计算均线相关指标(合并优化)
+            ma_values = calculate_ma_values(data_upto_yesterday, g.ma_periods)
+            ma_proximity_counts = calculate_ma_proximity_counts(data_upto_yesterday, g.ma_periods, g.ma_pattern_lookback_days)
+            
+            log.info(f"{symbol}({dominant_future}) 均线检查:")
+            log.info(f"  均线贴近统计: {ma_proximity_counts}")
+            
+            # 检查均线贴近计数
+            proximity_sum = ma_proximity_counts.get('MA5', 0) + ma_proximity_counts.get('MA10', 0)
+            if proximity_sum < g.ma_proximity_min_threshold:
+                log.info(f"  {symbol}({dominant_future}) ✗ 均线贴近计数不足,MA5+MA10={proximity_sum} < {g.ma_proximity_min_threshold},跳过")
+                add_to_excluded_contracts(dominant_future, 'ma_proximity', current_trading_day)
+                continue
+            
+            # 步骤5:计算极端趋势天数
+            extreme_above_count, extreme_below_count = calculate_extreme_trend_days(
+                data_upto_yesterday,
+                g.ma_periods,
+                g.ma_pattern_lookback_days
+            )
+            extreme_total = extreme_above_count + extreme_below_count
+            min_extreme = min(extreme_above_count, extreme_below_count)
+            filter_threshold = max(2, g.ma_pattern_extreme_days_threshold)
+            log.info(
+                f"  极端趋势天数统计: 收盘在所有均线上方 {extreme_above_count} 天, 收盘在所有均线下方 {extreme_below_count} 天, "
+                f"合计 {extreme_total} 天, min(A,B)={min_extreme} (过滤阈值: {filter_threshold})"
+            )
+            if extreme_above_count > 0 and extreme_below_count > 0 and min_extreme >= filter_threshold:
+                log.info(
+                    f"  {symbol}({dominant_future}) ✗ 极端趋势多空同时出现且 min(A,B)={min_extreme} ≥ {filter_threshold},跳过"
+                )
+                add_to_excluded_contracts(dominant_future, 'ma_extreme_trend', current_trading_day)
+                continue
+
+            # 步骤6:判断均线走势
+            direction = None
+            if check_ma_pattern(ma_values, 'long'):
+                direction = 'long'
+            elif check_ma_pattern(ma_values, 'short'):
+                direction = 'short'
+            else:
+                add_to_excluded_contracts(dominant_future, 'ma_trend', current_trading_day)
+                continue
+            
+            # 步骤7:检查MA5分布过滤
+            if g.enable_ma_distribution_filter:
+                distribution_passed, distribution_stats = check_ma5_distribution_filter(
+                    data_upto_yesterday,
+                    g.ma_distribution_lookback_days,
+                    direction,
+                    g.ma_distribution_min_ratio
+                )
+                log.info(
+                    f"  MA5分布过滤: 方向 {direction}, 有效天数 "
+                    f"{distribution_stats['valid_days']}/{distribution_stats['lookback_days']},"
+                    f"满足天数 {distribution_stats['qualified_days']}/{distribution_stats['required_days']}"
+                )
+                if not distribution_passed:
+                    insufficiency = distribution_stats['valid_days'] < distribution_stats['lookback_days']
+                    reason = "有效数据不足" if insufficiency else "满足天数不足"
+                    log.info(
+                        f"  {symbol}({dominant_future}) ✗ MA5分布过滤未通过({reason})"
+                    )
+                    add_to_excluded_contracts(dominant_future, 'ma5_distribution', current_trading_day)
+                    continue
+            
+            # 步骤8:检查历史均线模式一致性
+            consistency_passed, consistency_ratio = check_historical_ma_pattern_consistency(
+                historical_data, direction, g.ma_pattern_lookback_days, g.ma_pattern_consistency_threshold
+            )
+            
+            if not consistency_passed:
+                log.info(f"  {symbol}({dominant_future}) ✗ 历史均线模式一致性不足 "
+                        f"({consistency_ratio:.1%} < {g.ma_pattern_consistency_threshold:.1%}),跳过")
+                add_to_excluded_contracts(dominant_future, 'ma_consistency', current_trading_day)
+                continue
+            else:
+                log.info(f"  {symbol}({dominant_future}) ✓ 历史均线模式一致性检查通过 "
+                        f"({consistency_ratio:.1%} >= {g.ma_pattern_consistency_threshold:.1%})")
+            
+            # 步骤9:计算开盘价差并检查(合并优化)
+            open_gap_ratio = (today_open - yesterday_close) / yesterday_close
+            log.info(f"  开盘价差检查: 昨收 {yesterday_close:.2f}, 今开 {today_open:.2f}, "
+                    f"价差比例 {open_gap_ratio:.2%}")
+            
+            # 检查开盘价差是否符合方向要求
+            gap_check_passed = False
+            if g.ma_gap_strategy_mode == 1:
+                # 方案1:多头检查上跳,空头检查下跳
+                if direction == 'long' and open_gap_ratio >= g.ma_open_gap_threshold:
+                    log.info(f"  {symbol}({dominant_future}) ✓ 方案1多头开盘价差检查通过 ({open_gap_ratio:.2%} >= {g.ma_open_gap_threshold:.2%})")
+                    gap_check_passed = True
+                elif direction == 'short' and open_gap_ratio <= -g.ma_open_gap_threshold:
+                    log.info(f"  {symbol}({dominant_future}) ✓ 方案1空头开盘价差检查通过 ({open_gap_ratio:.2%} <= {-g.ma_open_gap_threshold:.2%})")
+                    gap_check_passed = True
+            elif g.ma_gap_strategy_mode == 2 or g.ma_gap_strategy_mode == 3:
+                # 方案2和方案3:多头检查下跳,空头检查上跳
+                if direction == 'long' and open_gap_ratio <= -g.ma_open_gap_threshold2:
+                    log.info(f"  {symbol}({dominant_future}) ✓ 方案{g.ma_gap_strategy_mode}多头开盘价差检查通过 ({open_gap_ratio:.2%} <= {-g.ma_open_gap_threshold2:.2%})")
+                    gap_check_passed = True
+                elif direction == 'short' and open_gap_ratio >= g.ma_open_gap_threshold2:
+                    log.info(f"  {symbol}({dominant_future}) ✓ 方案{g.ma_gap_strategy_mode}空头开盘价差检查通过 ({open_gap_ratio:.2%} >= {g.ma_open_gap_threshold2:.2%})")
+                    gap_check_passed = True
+            
+            if not gap_check_passed:
+                add_to_excluded_contracts(dominant_future, 'open_gap', current_trading_day)
+                continue
+            
+            # 步骤10:将通过检查的品种加入候选列表
+            g.daily_ma_candidates[dominant_future] = {
+                'symbol': symbol,
+                'direction': direction,
+                'open_price': today_open,
+                'yesterday_close': yesterday_close,
+                'yesterday_open': yesterday_open,
+                'ma_values': ma_values
+            }
+            
+            log.info(f"  ✓✓ {symbol} 通过均线和开盘价差检查,加入候选列表")
+            
+        except Exception as e:
+            g.ma_checked_underlyings.pop(symbol, None)
+            log.warning(f"{symbol} 检查时出错: {str(e)}")
+            continue
+    
+    log.info(f"候选列表更新完成,当前候选品种: {list(g.daily_ma_candidates.keys())}")
+    log.info("=" * 60)
+
+def check_open_and_stop(context):
+    """统一的开仓和止损止盈检查函数"""
+    # 先检查换月移仓
+    log.info("=" * 60)
+    current_trading_day = get_current_trading_day(context.current_dt)
+    log.info(f"执行开仓和止损止盈检查 - 时间: {context.current_dt}, 交易日: {current_trading_day}")
+    log.info("=" * 60)
+    log.info(f"先检查换月:")
+    position_auto_switch(context)
+    
+    # 获取当前时间
+    current_time = str(context.current_dt.time())[:2]
+    
+    # 判断是否为夜盘时间
+    is_night_session = (current_time in ['21', '22', '23', '00', '01', '02'])
+    
+    # 第一步:检查开仓条件
+    log.info(f"检查开仓条件:")
+    if g.daily_ma_candidates:
+        log.info("=" * 60)
+        log.info(f"执行开仓检查 - 时间: {context.current_dt}, 候选品种数量: {len(g.daily_ma_candidates)}")
+        
+        # 遍历候选品种
+        candidates_to_remove = []
+        
+        for dominant_future, candidate_info in g.daily_ma_candidates.items():
+            try:
+                symbol = candidate_info['symbol']
+                direction = candidate_info['direction']
+                open_price = candidate_info['open_price']
+                yesterday_close = candidate_info.get('yesterday_close')
+                yesterday_open = candidate_info.get('yesterday_open')
+                
+                # 检查是否已有持仓
+                if check_symbol_prefix_match(dominant_future, set(g.trade_history.keys())):
+                    log.info(f"{symbol} 已有持仓,从候选列表移除")
+                    candidates_to_remove.append(dominant_future)
+                    continue
+                
+                # 获取当前价格
+                current_data = get_current_data()[dominant_future]
+                current_price = current_data.last_price
+                
+                # 计算当天价差
+                intraday_diff = current_price - open_price
+                intraday_diff_ratio = intraday_diff / open_price
+                
+                log.info(f"{symbol}({dominant_future}) 开仓条件检查:")
+                log.info(f"  方向: {direction}, 开盘价: {open_price:.2f}, 当前价: {current_price:.2f}, "
+                        f"当天价差: {intraday_diff:.2f}, 变化比例: {intraday_diff_ratio:.2%}")
+                
+                # 判断是否满足开仓条件
+                should_open = False
+                
+                if g.ma_gap_strategy_mode == 1:
+                    # 方案1:根据参数决定是否检查日内价差
+                    if not g.check_intraday_spread:
+                        log.info(f"  方案1跳过日内价差检查(check_intraday_spread=False)")
+                        should_open = True
+                    elif direction == 'long' and intraday_diff > 0:
+                        log.info(f"  ✓ 方案1多头当天价差检查通过 ({intraday_diff:.2f} > 0)")
+                        should_open = True
+                    elif direction == 'short' and intraday_diff < 0:
+                        log.info(f"  ✓ 方案1空头当天价差检查通过 ({intraday_diff:.2f} < 0)")
+                        should_open = True
+                    else:
+                        log.info(f"  ✗ 方案1当天价差不符合{direction}方向要求")
+                        
+                elif g.ma_gap_strategy_mode == 2:
+                    # 方案2:强制检查日内变化,使用专用阈值
+                    if direction == 'long' and intraday_diff_ratio >= g.ma_intraday_threshold_scheme2:
+                        log.info(f"  ✓ 方案2多头日内变化检查通过 ({intraday_diff_ratio:.2%} >= {g.ma_intraday_threshold_scheme2:.2%})")
+                        should_open = True
+                    elif direction == 'short' and intraday_diff_ratio <= -g.ma_intraday_threshold_scheme2:
+                        log.info(f"  ✓ 方案2空头日内变化检查通过 ({intraday_diff_ratio:.2%} <= {-g.ma_intraday_threshold_scheme2:.2%})")
+                        should_open = True
+                    else:
+                        log.info(f"  ✗ 方案2日内变化不符合{direction}方向要求(阈值: ±{g.ma_intraday_threshold_scheme2:.2%})")
+                        
+                elif g.ma_gap_strategy_mode == 3:
+                    # 方案3:下跳后上涨(多头)或上跳后下跌(空头),并检查当前价格与前一日开盘收盘均值的关系
+                    if yesterday_open is not None and yesterday_close is not None:
+                        prev_day_avg = (yesterday_open + yesterday_close) / 2
+                        log.debug(f"  前一日开盘价: {yesterday_open:.2f}, 前一日收盘价: {yesterday_close:.2f}, 前一日开盘收盘均值: {prev_day_avg:.2f}")
+                        
+                        if direction == 'long':
+                            # 多头:当前价格 >= 前一日开盘收盘均值
+                            if current_price >= prev_day_avg:
+                                log.info(f"  ✓ 方案3多头入场条件通过: 当前价 {current_price:.2f} >= 前日均值 {prev_day_avg:.2f}")
+                                should_open = True
+                            else:
+                                log.info(f"  ✗ 方案3多头入场条件未通过: 当前价 {current_price:.2f} < 前日均值 {prev_day_avg:.2f}")
+                        elif direction == 'short':
+                            # 空头:当前价格 <= 前一日开盘收盘均值
+                            if current_price <= prev_day_avg:
+                                log.info(f"  ✓ 方案3空头入场条件通过: 当前价 {current_price:.2f} <= 前日均值 {prev_day_avg:.2f}")
+                                should_open = True
+                            else:
+                                log.info(f"  ✗ 方案3空头入场条件未通过: 当前价 {current_price:.2f} > 前日均值 {prev_day_avg:.2f}")
+                    else:
+                        log.info(f"  ✗ 方案3缺少前一日开盘或收盘价数据")
+                
+                if should_open:
+                    ma_values = candidate_info.get('ma_values') or {}
+                    cross_score = calculate_ma_cross_score(open_price, current_price, ma_values, direction)
+                    log.info(f"  均线穿越得分: {cross_score}, 阈值: {g.ma_cross_threshold}")
+                    if cross_score < g.ma_cross_threshold:
+                        log.info(f"  ✗ 均线穿越得分不足,跳过开仓")
+                        continue
+                    # 执行开仓
+                    log.info(f"  准备开仓: {symbol} {direction}")
+                    target_hands = calculate_target_hands(context, dominant_future, direction)
+                    
+                    if target_hands > 0:
+                        success = open_position(context, dominant_future, target_hands, direction, 
+                                              f'均线形态开仓')
+                        if success:
+                            log.info(f"  ✓✓ {symbol} 开仓成功,从候选列表移除")
+                            candidates_to_remove.append(dominant_future)
+                        else:
+                            log.warning(f"  ✗ {symbol} 开仓失败")
+                    else:
+                        log.warning(f"  ✗ {symbol} 计算目标手数为0,跳过开仓")
+                        
+            except Exception as e:
+                log.warning(f"{dominant_future} 处理时出错: {str(e)}")
+                continue
+        
+        # 从候选列表中移除已开仓的品种
+        for future in candidates_to_remove:
+            if future in g.daily_ma_candidates:
+                del g.daily_ma_candidates[future]
+        
+        log.info(f"剩余候选品种: {list(g.daily_ma_candidates.keys())}")
+        log.info("=" * 60)
+    
+    # 第二步:检查止损止盈
+    log.info(f"检查止损止盈条件:")
+    subportfolio = context.subportfolios[0]
+    long_positions = list(subportfolio.long_positions.values())
+    short_positions = list(subportfolio.short_positions.values())
+    
+    closed_count = 0
+    skipped_count = 0
+    
+    for position in long_positions + short_positions:
+        security = position.security
+        underlying_symbol = security.split('.')[0][:-4]
+        
+        # 检查交易时间适配性
+        has_night_session = get_futures_config(underlying_symbol, 'has_night_session', False)
+        
+        # 如果是夜盘时间,但品种不支持夜盘交易,则跳过
+        if is_night_session and not has_night_session:
+            skipped_count += 1
+            continue
+        
+        # 执行止损止盈检查
+        if check_position_stop_loss_profit(context, position):
+            closed_count += 1
+    
+    if closed_count > 0:
+        log.info(f"执行了 {closed_count} 次止损止盈")
+    
+    if skipped_count > 0:
+        log.info(f"夜盘时间跳过 {skipped_count} 个日间品种的止损止盈检查")
+
+
+def check_ma_trailing_reactivation(context):
+    """检查是否需要恢复均线跟踪止盈"""
+    subportfolio = context.subportfolios[0]
+    positions = list(subportfolio.long_positions.values()) + list(subportfolio.short_positions.values())
+    
+    if not positions:
+        return
+    
+    reenabled_count = 0
+    current_data = get_current_data()
+    
+    for position in positions:
+        security = position.security
+        trade_info = g.trade_history.get(security)
+        
+        if not trade_info or trade_info.get('ma_trailing_enabled', True):
+            continue
+        
+        direction = trade_info['direction']
+        ma_values = calculate_realtime_ma_values(security, [5])
+        ma5_value = ma_values.get('ma5')
+        
+        if ma5_value is None or security not in current_data:
+            continue
+        
+        today_price = current_data[security].last_price
+        
+        if direction == 'long' and today_price > ma5_value:
+            trade_info['ma_trailing_enabled'] = True
+            reenabled_count += 1
+            log.info(f"恢复均线跟踪止盈: {security} {direction}, 当前价 {today_price:.2f} > MA5 {ma5_value:.2f}")
+        elif direction == 'short' and today_price < ma5_value:
+            trade_info['ma_trailing_enabled'] = True
+            reenabled_count += 1
+            log.info(f"恢复均线跟踪止盈: {security} {direction}, 当前价 {today_price:.2f} < MA5 {ma5_value:.2f}")
+    
+    if reenabled_count > 0:
+        log.info(f"恢复均线跟踪止盈持仓数量: {reenabled_count}")
+
+
+def check_position_stop_loss_profit(context, position):
+    """检查单个持仓的止损止盈"""
+    log.info(f"检查持仓: {position.security}")
+    security = position.security
+    
+    if security not in g.trade_history:
+        return False
+    
+    trade_info = g.trade_history[security]
+    direction = trade_info['direction']
+    entry_price = trade_info['entry_price']
+    entry_time = trade_info['entry_time']
+    entry_trading_day = trade_info.get('entry_trading_day')
+    if entry_trading_day is None:
+        entry_trading_day = get_current_trading_day(entry_time)
+        trade_info['entry_trading_day'] = entry_trading_day
+    if entry_trading_day is not None:
+        entry_trading_day = normalize_trade_day_value(entry_trading_day)
+    current_trading_day = normalize_trade_day_value(get_current_trading_day(context.current_dt))
+    current_price = position.price
+    
+    # 计算当前盈亏比率
+    if direction == 'long':
+        profit_rate = (current_price - entry_price) / entry_price
+    else:
+        profit_rate = (entry_price - current_price) / entry_price
+    
+    # 检查固定止损
+    log.info("=" * 60)
+    log.info(f"检查固定止损:")
+    log.info("=" * 60)
+    if profit_rate <= -g.fixed_stop_loss_rate:
+        log.info(f"触发固定止损 {security} {direction}, 当前亏损率: {profit_rate:.3%}, "
+                f"成本价: {entry_price:.2f}, 当前价格: {current_price:.2f}")
+        close_position(context, security, direction)
+        return True
+    else:
+        log.debug(f"未触发固定止损 {security} {direction}, 当前亏损率: {profit_rate:.3%}, "
+                f"成本价: {entry_price:.2f}, 当前价格: {current_price:.2f}")
+
+    if entry_trading_day is not None and entry_trading_day == current_trading_day:
+        log.info(f"{security} 建仓交易日内跳过动态止盈检查")
+        return False
+
+    # 检查是否启用均线跟踪止盈
+    log.info("=" * 60)
+    log.info(f"检查是否启用均线跟踪止盈:")
+    log.info("=" * 60)
+    if not trade_info.get('ma_trailing_enabled', True):
+        return False
+
+    # 检查均线跟踪止盈
+    # 获取持仓天数
+    entry_date = entry_time.date()
+    current_date = context.current_dt.date()
+    all_trade_days = get_all_trade_days()
+    holding_days = sum((entry_date <= d <= current_date) for d in all_trade_days)
+    
+    # 计算变化率
+    today_price = get_current_data()[security].last_price
+    avg_daily_change_rate = calculate_average_daily_change_rate(security)
+    historical_data = attribute_history(security, 1, '1d', ['close'])
+    yesterday_close = historical_data['close'].iloc[-1]
+    today_change_rate = abs((today_price - yesterday_close) / yesterday_close)
+    
+    # 根据时间判断使用的偏移量
+    current_time = context.current_dt.time()
+    target_time = datetime.strptime('14:55:00', '%H:%M:%S').time()
+    if current_time > target_time:
+        offset_ratio = g.ma_offset_ratio_close
+        log.debug(f"当前时间是:{current_time},使用偏移量: {offset_ratio:.3%}")
+    else:
+        offset_ratio = g.ma_offset_ratio_normal
+        log.debug(f"当前时间是:{current_time},使用偏移量: {offset_ratio:.3%}")
+    # 选择止损均线
+    close_line = None
+    if today_change_rate >= 1.5 * avg_daily_change_rate:
+        close_line = 'ma5'  # 波动剧烈时用短周期
+    elif holding_days <= g.days_for_adjustment:
+        close_line = 'ma5'  # 持仓初期用短周期
+    else:
+        close_line = 'ma5' if today_change_rate >= 1.2 * avg_daily_change_rate else 'ma10'
+    
+    # 计算实时均线值
+    ma_values = calculate_realtime_ma_values(security, [5, 10])
+    ma_value = ma_values[close_line]
+    
+    # 应用偏移量
+    if direction == 'long':
+        adjusted_ma_value = ma_value * (1 - offset_ratio)
+    else:
+        adjusted_ma_value = ma_value * (1 + offset_ratio)
+    
+    # 判断是否触发均线止损
+    if (direction == 'long' and today_price < adjusted_ma_value) or \
+       (direction == 'short' and today_price > adjusted_ma_value):
+        log.info(f"触发均线跟踪止盈 {security} {direction}, 止损均线: {close_line}, "
+                f"均线值: {ma_value:.2f}, 调整后: {adjusted_ma_value:.2f}, "
+                f"当前价: {today_price:.2f}, 持仓天数: {holding_days}")
+        close_position(context, security, direction)
+        return True
+    else:
+        log.debug(f"未触发均线跟踪止盈 {security} {direction}, 止损均线: {close_line}, "
+                f"均线值: {ma_value:.2f}, 调整后: {adjusted_ma_value:.2f}, "
+                f"当前价: {today_price:.2f}, 持仓天数: {holding_days}")
+    
+    return False
+
+############################ 核心辅助函数 ###################################
+
+def calculate_ma_values(data, periods):
+    """计算均线值
+    
+    Args:
+        data: DataFrame,包含'close'列的历史数据(最后一行是最新的数据)
+        periods: list,均线周期列表,如[5, 10, 20, 30]
+    
+    Returns:
+        dict: {'MA5': value, 'MA10': value, 'MA20': value, 'MA30': value}
+        返回最后一行(最新日期)的各周期均线值
+    """
+    ma_values = {}
+    
+    for period in periods:
+        if len(data) >= period:
+            # 计算最后period天的均线值
+            ma_values[f'MA{period}'] = data['close'].iloc[-period:].mean()
+        else:
+            ma_values[f'MA{period}'] = None
+    
+    return ma_values
+
+
+def calculate_ma_cross_score(open_price, current_price, ma_values, direction):
+    """根据开盘价与当前价统计多周期均线穿越得分"""
+    if not ma_values:
+        return 0
+    assert direction in ('long', 'short')
+    score = 0
+    for period in g.ma_periods:
+        key = f'MA{period}'
+        ma_value = ma_values.get(key)
+        if ma_value is None:
+            continue
+        cross_up = open_price < ma_value and current_price > ma_value
+        cross_down = open_price > ma_value and current_price < ma_value
+        if not (cross_up or cross_down):
+            continue
+        if direction == 'long':
+            delta = 1 if cross_up else -1
+        else:
+            delta = -1 if cross_up else 1
+        score += delta
+        log.debug(
+            f"  均线穿越[{key}] - 开盘 {open_price:.2f}, 当前 {current_price:.2f}, "
+            f"均线 {ma_value:.2f}, 方向 {direction}, 增量 {delta}, 当前得分 {score}"
+        )
+    return score
+
+
+def calculate_ma_proximity_counts(data, periods, lookback_days):
+    """统计近 lookback_days 天收盘价贴近各均线的次数"""
+    proximity_counts = {f'MA{period}': 0 for period in periods}
+
+    if len(data) < lookback_days:
+        return proximity_counts
+
+    closes = data['close'].iloc[-lookback_days:]
+    ma_series = {
+        period: data['close'].rolling(window=period).mean().iloc[-lookback_days:]
+        for period in periods
+    }
+
+    for idx, close_price in enumerate(closes):
+        min_diff = None
+        closest_period = None
+
+        for period in periods:
+            ma_value = ma_series[period].iloc[idx]
+            if pd.isna(ma_value):
+                continue
+            diff = abs(close_price - ma_value)
+            if min_diff is None or diff < min_diff:
+                min_diff = diff
+                closest_period = period
+
+        if closest_period is not None:
+            proximity_counts[f'MA{closest_period}'] += 1
+
+    return proximity_counts
+
+
+def calculate_extreme_trend_days(data, periods, lookback_days):
+    """统计过去 lookback_days 天收盘价相对所有均线的极端趋势天数"""
+    if len(data) < lookback_days:
+        return 0, 0
+
+    recent_closes = data['close'].iloc[-lookback_days:]
+    ma_series = {
+        period: data['close'].rolling(window=period).mean().iloc[-lookback_days:]
+        for period in periods
+    }
+
+    above_count = 0
+    below_count = 0
+
+    for idx, close_price in enumerate(recent_closes):
+        ma_values = []
+        valid = True
+
+        for period in periods:
+            ma_value = ma_series[period].iloc[idx]
+            if pd.isna(ma_value):
+                valid = False
+                break
+            ma_values.append(ma_value)
+
+        if not valid or not ma_values:
+            continue
+
+        if all(close_price > ma_value for ma_value in ma_values):
+            above_count += 1
+        elif all(close_price < ma_value for ma_value in ma_values):
+            below_count += 1
+
+    return above_count, below_count
+
+
+def check_ma5_distribution_filter(data, lookback_days, direction, min_ratio):
+    """检查近 lookback_days 天收盘价相对于MA5的分布情况"""
+    stats = {
+        'lookback_days': lookback_days,
+        'valid_days': 0,
+        'qualified_days': 0,
+        'required_days': max(0, math.ceil(lookback_days * min_ratio))
+    }
+
+    if lookback_days <= 0:
+        return True, stats
+
+    if len(data) < max(lookback_days, 5):
+        return False, stats
+
+    recent_closes = data['close'].iloc[-lookback_days:]
+    ma5_series = data['close'].rolling(window=5).mean().iloc[-lookback_days:]
+
+    for close_price, ma5_value in zip(recent_closes, ma5_series):
+        if pd.isna(ma5_value):
+            continue
+        stats['valid_days'] += 1
+        if direction == 'long' and close_price < ma5_value:
+            stats['qualified_days'] += 1
+        elif direction == 'short' and close_price > ma5_value:
+            stats['qualified_days'] += 1
+
+    if stats['valid_days'] < lookback_days:
+        return False, stats
+
+    return stats['qualified_days'] >= stats['required_days'], stats
+
+
+def check_ma_pattern(ma_values, direction):
+    """检查均线排列模式是否符合方向要求
+    
+    Args:
+        ma_values: dict,包含MA5, MA10, MA20, MA30的均线值
+        direction: str,'long'或'short'
+    
+    Returns:
+        bool: 是否符合均线排列要求
+    """
+    ma5 = ma_values['MA5']
+    ma10 = ma_values['MA10']
+    ma20 = ma_values['MA20']
+    ma30 = ma_values['MA30']
+    
+    if direction == 'long':
+        # 多头模式:MA30 <= MA20 <= MA10 <= MA5 或 MA30 <= MA20 <= MA5 <= MA10
+        # 或者:MA20 <= MA30 <= MA10 <= MA5 或 MA20 <= MA30 <= MA5 <= MA10
+        pattern1 = (ma30 <= ma20 <= ma10 <= ma5)
+        pattern2 = (ma30 <= ma20 <= ma5 <= ma10)
+        pattern3 = (ma20 <= ma30 <= ma10 <= ma5)
+        pattern4 = (ma20 <= ma30 <= ma5 <= ma10)
+        return pattern1 or pattern2 or pattern3 or pattern4
+    elif direction == 'short':
+        # 空头模式:MA10 <= MA5 <= MA20 <= MA30 或 MA5 <= MA10 <= MA20 <= MA30
+        # 或者:MA10 <= MA5 <= MA30 <= MA20 或 MA5 <= MA10 <= MA30 <= MA20
+        pattern1 = (ma10 <= ma5 <= ma20 <= ma30)
+        pattern2 = (ma5 <= ma10 <= ma20 <= ma30)
+        pattern3 = (ma10 <= ma5 <= ma30 <= ma20)
+        pattern4 = (ma5 <= ma10 <= ma30 <= ma20)
+        return pattern1 or pattern2 or pattern3 or pattern4
+    else:
+        return False
+
+def check_historical_ma_pattern_consistency(historical_data, direction, lookback_days, consistency_threshold):
+    """检查历史均线模式的一致性
+    
+    Args:
+        historical_data: DataFrame,包含足够天数的历史数据
+        direction: str,'long'或'short'
+        lookback_days: int,检查过去多少天
+        consistency_threshold: float,一致性阈值(0-1之间)
+    
+    Returns:
+        tuple: (bool, float) - (是否通过一致性检查, 实际一致性比例)
+    """
+    if len(historical_data) < max(g.ma_periods) + lookback_days:
+        # 历史数据不足
+        return False, 0.0
+    
+    match_count = 0
+    total_count = lookback_days
+    # log.debug(f"历史均线模式一致性检查: {direction}, 检查过去{lookback_days}天的数据")
+    # log.debug(f"历史数据: {historical_data}")
+    
+    # 检查过去lookback_days天的均线模式
+    for i in range(lookback_days):
+        # 获取倒数第(i+1)天的数据(i=0时是昨天,i=1时是前天,依此类推)
+        end_idx = -(i + 1)
+        # 获取这一天的具体日期
+        date = historical_data.index[end_idx].date()
+        # 获取到该天(包括该天)为止的所有数据
+        if i == 0:
+            data_slice = historical_data
+        else:
+            data_slice = historical_data.iloc[:-i]
+        
+        # 计算该天的均线值
+        # log.debug(f"对于倒数第{i+1}天,end_idx: {end_idx},日期: {date},计算均线值: {data_slice}")
+        ma_values = calculate_ma_values(data_slice, g.ma_periods)
+        # log.debug(f"end_idx: {end_idx},日期: {date},倒数第{i+1}天的均线值: {ma_values}")
+        
+        # 检查是否符合模式
+        if check_ma_pattern(ma_values, direction):
+            match_count += 1
+            # log.debug(f"日期: {date},对于倒数第{i+1}天,历史均线模式一致性检查: {direction} 符合模式")
+        # else:
+            # log.debug(f"日期: {date},对于倒数第{i+1}天,历史均线模式一致性检查: {direction} 不符合模式")
+    
+    consistency_ratio = match_count / total_count
+    passed = consistency_ratio >= consistency_threshold
+    
+    return passed, consistency_ratio
+
+############################ 交易执行函数 ###################################
+
+def open_position(context, security, target_hands, direction, reason=''):
+    """开仓"""
+    try:
+        # 记录交易前的可用资金
+        cash_before = context.portfolio.available_cash
+        
+        # 使用order_target按手数开仓
+        order = order_target(security, target_hands, side=direction)
+        
+        if order is not None and order.filled > 0:
+            # 记录交易后的可用资金
+            cash_after = context.portfolio.available_cash
+            
+            # 计算实际资金变化
+            cash_change = cash_before - cash_after
+            
+            # 获取订单价格和数量
+            order_price = order.avg_cost if order.avg_cost else order.price
+            order_amount = order.filled
+            
+            # 记录当日交易
+            underlying_symbol = security.split('.')[0][:-4]
+            g.today_trades.append({
+                'security': security,
+                'underlying_symbol': underlying_symbol,
+                'direction': direction,
+                'order_amount': order_amount,
+                'order_price': order_price,
+                'cash_change': cash_change,
+                'time': context.current_dt
+            })
+            
+            # 记录交易信息
+            entry_trading_day = get_current_trading_day(context.current_dt)
+            g.trade_history[security] = {
+                'entry_price': order_price,
+                'target_hands': target_hands,
+                'actual_hands': order_amount,
+                'actual_margin': cash_change,
+                'direction': direction,
+                'entry_time': context.current_dt,
+                'entry_trading_day': entry_trading_day
+            }
+
+            ma_trailing_enabled = True
+            ma_values_at_entry = calculate_realtime_ma_values(security, [5])
+            ma5_value = ma_values_at_entry.get('ma5')
+            if ma5_value is not None:
+                if direction == 'long' and order_price < ma5_value:
+                    ma_trailing_enabled = False
+                    log.info(f"禁用均线跟踪止盈: {security} {direction}, 开仓价 {order_price:.2f} < MA5 {ma5_value:.2f}")
+                elif direction == 'short' and order_price > ma5_value:
+                    ma_trailing_enabled = False
+                    log.info(f"禁用均线跟踪止盈: {security} {direction}, 开仓价 {order_price:.2f} > MA5 {ma5_value:.2f}")
+
+            g.trade_history[security]['ma_trailing_enabled'] = ma_trailing_enabled
+            
+            log.info(f"开仓成功: {security} {direction} {order_amount}手 @{order_price:.2f}, "
+                    f"保证金: {cash_change:.0f}, 原因: {reason}")
+            
+            return True
+            
+    except Exception as e:
+        log.warning(f"开仓失败 {security}: {str(e)}")
+    
+    return False
+
+def close_position(context, security, direction):
+    """平仓"""
+    try:
+        # 使用order_target平仓到0手
+        order = order_target(security, 0, side=direction)
+        
+        if order is not None and order.filled > 0:
+            underlying_symbol = security.split('.')[0][:-4]
+            
+            # 记录当日交易(平仓)
+            g.today_trades.append({
+                'security': security,
+                'underlying_symbol': underlying_symbol,
+                'direction': direction,
+                'order_amount': -order.filled,
+                'order_price': order.avg_cost if order.avg_cost else order.price,
+                'cash_change': 0,
+                'time': context.current_dt
+            })
+            
+            log.info(f"平仓成功: {underlying_symbol} {direction} {order.filled}手")
+            
+            # 从交易历史中移除
+            if security in g.trade_history:
+                del g.trade_history[security]
+            return True
+            
+    except Exception as e:
+        log.warning(f"平仓失败 {security}: {str(e)}")
+    
+    return False
+
+############################ 辅助函数 ###################################
+
+def get_futures_config(underlying_symbol, config_key=None, default_value=None):
+    """获取期货品种配置信息的辅助函数"""
+    if underlying_symbol not in g.futures_config:
+        if config_key and default_value is not None:
+            return default_value
+        return {}
+    
+    if config_key is None:
+        return g.futures_config[underlying_symbol]
+    
+    return g.futures_config[underlying_symbol].get(config_key, default_value)
+
+def get_margin_rate(underlying_symbol, direction, default_rate=0.10):
+    """获取保证金比例的辅助函数"""
+    return g.futures_config.get(underlying_symbol, {}).get('margin_rate', {}).get(direction, default_rate)
+
+def get_multiplier(underlying_symbol, default_multiplier=10):
+    """获取合约乘数的辅助函数"""
+    return g.futures_config.get(underlying_symbol, {}).get('multiplier', default_multiplier)
+
+def add_to_excluded_contracts(dominant_future, reason, current_trading_day):
+    """将合约添加到排除缓存"""
+    g.excluded_contracts[dominant_future] = {
+        'reason': reason,
+        'trading_day': current_trading_day
+    }
+
+def has_reached_trading_start(current_dt, trading_start_time_str, has_night_session=False):
+    """判断当前是否已到达合约允许交易的起始时间"""
+    if not trading_start_time_str:
+        return True
+
+    try:
+        hour, minute = [int(part) for part in trading_start_time_str.split(':')[:2]]
+    except Exception:
+        return True
+
+    start_time = time(hour, minute)
+    current_time = current_dt.time()
+
+    if has_night_session:
+        if current_time >= start_time:
+            return True
+        if current_time < time(12, 0):
+            return True
+        if time(8, 30) <= current_time <= time(15, 30):
+            return True
+        return False
+
+    if current_time < start_time:
+        return False
+    if current_time >= time(20, 0):
+        return False
+    return True
+
+def calculate_target_hands(context, security, direction):
+    """计算目标开仓手数"""
+    current_price = get_current_data()[security].last_price
+    underlying_symbol = security.split('.')[0][:-4]
+    
+    # 使用保证金比例
+    margin_rate = get_margin_rate(underlying_symbol, direction)
+    multiplier = get_multiplier(underlying_symbol)
+    
+    # 计算单手保证金
+    log.debug(f"计算单手保证金: {current_price:.2f} * {multiplier:.2f} * {margin_rate:.2f} = {current_price * multiplier * margin_rate:.2f}")
+    single_hand_margin = current_price * multiplier * margin_rate
+    
+    # 还要考虑可用资金限制
+    available_cash = context.portfolio.available_cash * g.usage_percentage
+    
+    # 根据单个标的最大持仓保证金限制计算开仓数量
+    max_margin = g.max_margin_per_position
+    
+    if single_hand_margin <= max_margin:
+        # 如果单手保证金不超过最大限制,计算最大可开仓手数
+        max_hands = int(max_margin / single_hand_margin)
+        max_hands_by_cash = int(available_cash / single_hand_margin)
+        
+        # 取两者较小值
+        actual_hands = min(max_hands, max_hands_by_cash)
+        
+        # 确保至少开1手
+        actual_hands = max(1, actual_hands)
+        
+        log.info(f"单手保证金: {single_hand_margin:.0f}, 目标开仓手数: {actual_hands}")
+        
+        return actual_hands
+    else:
+        # 如果单手保证金超过最大限制,默认开仓1手
+        actual_hands = 1
+        
+        log.info(f"单手保证金: {single_hand_margin:.0f} 超过最大限制: {max_margin}, 默认开仓1手")
+        
+        return actual_hands
+
+def check_symbol_prefix_match(symbol, hold_symbols):
+    """检查是否有相似的持仓品种"""
+    symbol_prefix = symbol[:-9]
+    
+    for hold_symbol in hold_symbols:
+        hold_symbol_prefix = hold_symbol[:-9] if len(hold_symbol) > 9 else hold_symbol
+        
+        if symbol_prefix == hold_symbol_prefix:
+            return True
+    return False
+
+def calculate_average_daily_change_rate(security, days=30):
+    """计算日均变化率"""
+    historical_data = attribute_history(security, days + 1, '1d', ['close'])
+    daily_change_rates = abs(historical_data['close'].pct_change()).iloc[1:]
+    return daily_change_rates.mean()
+
+def calculate_realtime_ma_values(security, ma_periods):
+    """计算包含当前价格的实时均线值"""
+    historical_data = attribute_history(security, max(ma_periods), '1d', ['close'])
+    today_price = get_current_data()[security].last_price
+    close_prices = historical_data['close'].tolist() + [today_price]
+    ma_values = {f'ma{period}': sum(close_prices[-period:]) / period for period in ma_periods}
+    return ma_values
+
+def after_market_close(context):
+    """收盘后运行函数"""
+    log.info(str('函数运行时间(after_market_close):'+str(context.current_dt.time())))
+    
+    # 清空候选列表(每天重新检查)
+    g.daily_ma_candidates = {}
+    
+    # 清空排除缓存(每天重新检查)
+    excluded_count = len(g.excluded_contracts)
+    if excluded_count > 0:
+        log.info(f"清空排除缓存,共 {excluded_count} 个合约")
+        g.excluded_contracts = {}
+    
+    # 只有当天有交易时才打印统计信息
+    if g.today_trades:
+        print_daily_trading_summary(context)
+        
+        # 清空当日交易记录
+        g.today_trades = []
+    
+    log.info('##############################################################')
+
+def print_daily_trading_summary(context):
+    """打印当日交易汇总"""
+    if not g.today_trades:
+        return
+    
+    log.info("\n=== 当日交易汇总 ===")
+    total_margin = 0
+    
+    for trade in g.today_trades:
+        if trade['order_amount'] > 0:  # 开仓
+            log.info(f"开仓 {trade['underlying_symbol']} {trade['direction']} {trade['order_amount']}手 "
+                  f"价格:{trade['order_price']:.2f} 保证金:{trade['cash_change']:.0f}")
+            total_margin += trade['cash_change']
+        else:  # 平仓
+            log.info(f"平仓 {trade['underlying_symbol']} {trade['direction']} {abs(trade['order_amount'])}手 "
+                  f"价格:{trade['order_price']:.2f}")
+    
+    log.info(f"当日保证金占用: {total_margin:.0f}")
+    log.info("==================\n")
+
+########################## 自动移仓换月函数 #################################
+def position_auto_switch(context, pindex=0, switch_func=None, callback=None):
+    """期货自动移仓换月"""
+    import re
+    subportfolio = context.subportfolios[pindex]
+    symbols = set(subportfolio.long_positions.keys()) | set(subportfolio.short_positions.keys())
+    switch_result = []
+    for symbol in symbols:
+        match = re.match(r"(?P<underlying_symbol>[A-Z]{1,})", symbol)
+        if not match:
+            raise ValueError("未知期货标的: {}".format(symbol))
+        else:
+            underlying_symbol = match.groupdict()["underlying_symbol"]
+            trading_start = get_futures_config(underlying_symbol, 'trading_start_time', None)
+            has_night_session = get_futures_config(underlying_symbol, 'has_night_session', False)
+            # log.debug(f"移仓换月: {symbol}, 交易开始时间: {trading_start}, 夜盘: {has_night_session}")
+            if trading_start and not has_reached_trading_start(context.current_dt, trading_start, has_night_session):
+                # log.info("{} 当前时间 {} 未到达交易开始时间 {} (夜盘:{} ),跳过移仓".format(
+                #     symbol,
+                #     context.current_dt.strftime('%H:%M:%S'),
+                #     trading_start,
+                #     has_night_session
+                # ))
+                continue
+            dominant = get_dominant_future(underlying_symbol)
+            cur = get_current_data()
+            symbol_last_price = cur[symbol].last_price
+            dominant_last_price = cur[dominant].last_price
+            
+            if dominant > symbol:
+                for positions_ in (subportfolio.long_positions, subportfolio.short_positions):
+                    if symbol not in positions_.keys():
+                        continue
+                    else :
+                        p = positions_[symbol]
+
+                    if switch_func is not None:
+                        switch_func(context, pindex, p, dominant)
+                    else:
+                        amount = p.total_amount
+                        # 跌停不能开空和平多,涨停不能开多和平空
+                        if p.side == "long":
+                            symbol_low_limit = cur[symbol].low_limit
+                            dominant_high_limit = cur[dominant].high_limit
+                            if symbol_last_price <= symbol_low_limit:
+                                log.warning("标的{}跌停,无法平仓。移仓换月取消。".format(symbol))
+                                continue
+                            elif dominant_last_price >= dominant_high_limit:
+                                log.warning("标的{}涨停,无法开仓。移仓换月取消。".format(dominant))
+                                continue
+                            else:
+                                log.info("进行移仓换月: ({0},long) -> ({1},long)".format(symbol, dominant))
+                                order_old = order_target(symbol, 0, side='long')
+                                if order_old != None and order_old.filled > 0:
+                                    order_new = order_target(dominant, amount, side='long')
+                                    if order_new != None and order_new.filled > 0:
+                                        switch_result.append({"before": symbol, "after": dominant, "side": "long"})
+                                        # 换月成功,更新交易记录
+                                        if symbol in g.trade_history:
+                                            g.trade_history[dominant] = g.trade_history[symbol]
+                                            del g.trade_history[symbol]
+                                    else:
+                                        log.warning("标的{}交易失败,无法开仓。移仓换月失败。".format(dominant))
+                        if p.side == "short":
+                            symbol_high_limit = cur[symbol].high_limit
+                            dominant_low_limit = cur[dominant].low_limit
+                            if symbol_last_price >= symbol_high_limit:
+                                log.warning("标的{}涨停,无法平仓。移仓换月取消。".format(symbol))
+                                continue
+                            elif dominant_last_price <= dominant_low_limit:
+                                log.warning("标的{}跌停,无法开仓。移仓换月取消。".format(dominant))
+                                continue
+                            else:
+                                log.info("进行移仓换月: ({0},short) -> ({1},short)".format(symbol, dominant))
+                                order_old = order_target(symbol, 0, side='short')
+                                if order_old != None and order_old.filled > 0:
+                                    order_new = order_target(dominant, amount, side='short')
+                                    if order_new != None and order_new.filled > 0:
+                                        switch_result.append({"before": symbol, "after": dominant, "side": "short"})
+                                        # 换月成功,更新交易记录
+                                        if symbol in g.trade_history:
+                                            g.trade_history[dominant] = g.trade_history[symbol]
+                                            del g.trade_history[symbol]
+                                    else:
+                                        log.warning("标的{}交易失败,无法开仓。移仓换月失败。".format(dominant))
+                        if callback:
+                            callback(context, pindex, p, dominant)
+    return switch_result
+

+ 237 - 0
Lib/future/MAPatternStrategy_v002_核心逻辑.md

@@ -0,0 +1,237 @@
+# 顺势交易策略 v002 核心逻辑详解
+
+## 概述
+本策略基于均线走势(前提条件)+ K线形态(跳空、价格验证)的期货交易策略,通过两阶段筛选和执行机制进行交易。
+
+---
+
+## 开仓逻辑结构总览
+
+开仓验证包含两个独立阶段,每个阶段都有明确的检查项:
+
+### 第一阶段:均线走势和跳空检查
+**位置**:`check_ma_trend_and_open_gap`函数
+**目的**:筛选出符合均线趋势要求且满足跳空条件的品种
+**检查内容**:
+1. 均线贴近度
+2. 极端趋势过滤
+3. 均线排列模式
+4. 历史模式一致性
+5. **跳空方向检查**(必选)- 趋势跟随或逆势操作
+6. **跳空幅度检查**(可选)- 是否达到阈值
+
+### 第二阶段:最终价格验证和开仓
+**位置**:`check_open_and_stop`函数
+**目的**:对候选品种进行最终价格验证,确认开仓时机
+**检查内容**:
+- **策略1**:可选的日内价差检查
+- **策略2**:强制的日内变化阈值检查
+- **策略3**:价格回归到前日开盘收盘均值检查
+
+---
+
+## 详细逻辑说明
+
+开仓验证包含两个独立阶段,每个阶段都有明确的检查项:
+
+---
+
+### 第一阶段:均线走势和跳空检查(函数:check_ma_trend_and_open_gap)
+
+#### 执行时间点
+- **夜盘开盘**:21:05:00(仅检查21:00开盘的品种)
+- **日盘早盘**:09:05:00(检查21:00和09:00开盘的品种)
+- **日盘晚开**:09:35:00(检查所有品种,包括09:30开盘的)
+
+#### 筛选条件(必须全部满足)
+
+**1. 均线贴近度检查**
+   - 统计过去10天收盘价贴近各均线的次数
+   - MA5贴近次数 + MA10贴近次数 ≥ 8次(`g.ma_proximity_min_threshold = 8`)
+   - 贴近定义为:收盘价距离某条均线最近的均线
+
+**2. 极端趋势过滤**
+   - 统计过去10天收盘价在所有均线上方/下方的天数
+   - 过滤条件:min(上方天数, 下方天数) ≥ 4天(`g.ma_pattern_extreme_days_threshold = 4`)
+   - 如果多空极端趋势同时出现且都达到阈值,则跳过该品种
+
+**3. 均线排列模式检查**
+   - **多头模式**:满足以下任一模式
+     - MA30 ≤ MA20 ≤ MA10 ≤ MA5
+     - MA30 ≤ MA20 ≤ MA5 ≤ MA10
+   - **空头模式**:满足以下任一模式
+     - MA10 ≤ MA5 ≤ MA20 ≤ MA30
+     - MA5 ≤ MA10 ≤ MA20 ≤ MA30
+
+**4. 历史均线模式一致性检查**
+   - 检查过去10天的均线模式一致性
+   - 一致性比例 ≥ 80%(`g.ma_pattern_consistency_threshold = 0.8`)
+   - 即10天中至少有8天符合当前方向的均线排列
+
+**5. 跳空方向检查**(第一部分,必选)
+   - **策略1 - 趋势跟随**(`g.ma_gap_strategy_mode = 1`):
+     - 看涨趋势:必须向上跳空(开盘价 > 昨收价)
+     - 看跌趋势:必须向下跳空(开盘价 < 昨收价)
+   - **策略2 - 逆势操作**(`g.ma_gap_strategy_mode = 2`):
+     - 看涨趋势:必须向下跳空(开盘价 < 昨收价)
+     - 看跌趋势:必须向上跳空(开盘价 > 昨收价)
+   - **策略3 - 逆势操作**(`g.ma_gap_strategy_mode = 3`):
+     - 看涨趋势:必须向下跳空(开盘价 < 昨收价)
+     - 看跌趋势:必须向上跳空(开盘价 > 昨收价)
+
+**6. 跳空幅度检查**(第二部分,可选)
+   - **选项A**(`g.check_gap_magnitude = True`):检查跳空幅度与阈值比较
+     - 策略1:`|开盘价差比例| >= 0.2%`(`g.ma_open_gap_threshold = 0.002`)
+     - 策略2/3:`|开盘价差比例| >= 0.2%`(`g.ma_open_gap_threshold2 = 0.002`)
+   - **选项B**(`g.check_gap_magnitude = False`):不验证跳空幅度,只要方向正确即可
+
+#### 通过条件后的处理
+- 将品种加入候选列表 `g.daily_ma_candidates`
+- 记录方向、开盘价、昨收价、前一日开盘价、均线值等信息
+
+---
+
+### 第二阶段:最终价格验证和开仓执行(函数:check_open_and_stop)
+
+#### 执行时间点
+**夜盘**:21:05, 21:35, 22:05, 22:35
+**日盘**:09:05, 09:35, 10:05, 10:35, 11:05, 11:25, 13:35, 14:05, 14:35, 14:55
+
+#### 开仓前最后一道关卡:最终价格验证
+
+在每个时间点,对候选列表中的品种进行最终价格验证检查:
+
+**策略1 - 趋势跟随**(`g.ma_gap_strategy_mode = 1`):
+   - **选项A**(`g.check_intraday_spread = False`):
+     - 跳过日内价差检查,直接通过验证
+   - **选项B**(`g.check_intraday_spread = True`):
+     - 看涨趋势:当天价差 > 0(当前价 > 开盘价)
+     - 看跌趋势:当天价差 < 0(当前价 < 开盘价)
+
+**策略2 - 逆势操作+强制阈值**(`g.ma_gap_strategy_mode = 2`):
+   - 看涨趋势:当天变化比例 ≥ +0.5%(`g.ma_intraday_threshold_scheme2 = 0.005`)
+   - 看跌趋势:当天变化比例 ≤ -0.5%
+   - 说明:确保价格在下跳后有足够的反弹幅度
+
+**策略3 - 逆势操作+价格回归**(`g.ma_gap_strategy_mode = 3`):
+   - 看涨趋势:当前价格 ≥ (前一日开盘价 + 前一日收盘价) / 2
+   - 看跌趋势:当前价格 ≤ (前一日开盘价 + 前一日收盘价) / 2
+   - 说明:确保价格在下跳后回归到前一日开盘收盘均值附近
+
+#### 开仓执行流程
+1. 计算目标手数:
+   - 单手保证金 = 当前价 × 合约乘数 × 保证金比例
+   - 最大开仓手数 = min(最大保证金限制/单手保证金, 可用资金×80%/单手保证金)
+   - 单个标的最大持仓保证金限制:20,000元(`g.max_margin_per_position = 20000`)
+2. 执行开仓并记录交易信息
+3. 从候选列表中移除已开仓品种
+
+#### 止损止盈检查
+在完成开仓检查后,同时检查所有持仓的止损止盈条件(详见下方止损止盈逻辑部分)
+
+---
+
+## 止损逻辑
+
+### 执行时间点
+与开仓检查同步执行(第二阶段的所有时间点)
+
+### 止损条件
+1. **固定止损**
+   - 亏损比例 ≤ -1%(`g.fixed_stop_loss_rate = 0.01`)
+   - 计算公式:
+     - 多头:`(当前价 - 开仓价) / 开仓价`
+     - 空头:`(开仓价 - 当前价) / 开仓价`
+   - 触发后立即平仓,防止损失扩大
+
+2. **交易时间适配性检查**
+   - 夜盘时间只检查支持夜盘交易的品种
+   - 无夜盘品种在夜盘时间跳过止损检查
+
+---
+
+## 止盈逻辑
+
+### 执行时间点
+与开仓检查和止损检查同步执行(第二阶段的所有时间点)
+**建仓交易日内跳过动态止盈检查**
+
+### 止盈策略:均线跟踪止盈
+#### 启用条件
+- 持仓超过建仓交易日
+- 均线跟踪功能启用(`ma_trailing_enabled = True`)
+- 特殊情况:多头开仓价 < MA5时禁用均线跟踪
+
+#### 动态止盈参数选择
+1. **时间相关偏移量**:
+   - 14:55之后:偏移量 1%(`g.ma_offset_ratio_close = 0.01`)
+   - 其他时间:偏移量 0.3%(`g.ma_offset_ratio_normal = 0.003`)
+
+2. **均线选择逻辑**:
+   - 持仓天数 ≤ 4天(`g.days_for_adjustment = 4`):使用MA5
+   - 持仓天数 > 4天:
+     - 当日变化率 ≥ 1.2倍日均变化率:使用MA5
+     - 当日变化率 < 1.2倍日均变化率:使用MA10
+   - 波动剧烈时(当日变化率 ≥ 1.5倍日均变化率):强制使用MA5
+
+#### 止盈触发条件
+1. **计算调整后均线值**:
+   - 多头:调整后均线值 = 均线值 × (1 - 偏移量)
+   - 空头:调整后均线值 = 均线值 × (1 + 偏移量)
+
+2. **触发条件**:
+   - 多头:当前价 < 调整后均线值
+   - 空头:当前价 > 调整后均线值
+
+3. **平仓执行**:
+   - 触发条件后立即平仓
+   - 记录止盈原因:使用的均线、均线值、调整后值、当前价、持仓天数
+
+---
+
+## 其他重要机制
+
+### 自动换月移仓
+- 在每次策略执行前检查
+- 当主力合约发生变化时自动移仓
+- 考虑涨跌停板限制,避免极端情况下的移仓失败
+
+### 缓存机制
+- **排除缓存**:记录当日不符合条件的合约,避免重复检查
+- **均线检查缓存**:记录每个品种在交易日的均线检查状态
+- **候选列表**:存储通过第一阶段检查的候选品种
+
+### 风险控制参数
+- 最大资金使用比例:80%(`g.usage_percentage = 0.8`)
+- 单个标的最大持仓保证金:20,000元
+- 固定止损比例:1%
+- 均线贴近度最低要求:8次
+- 极端趋势过滤阈值:4天
+- 历史一致性要求:80%
+
+### 策略参数配置
+
+**基础参数**
+- 均线周期:[5, 10, 20, 30]
+- 历史数据天数:60天(确保足够计算MA30)
+- 历史均线模式检查天数:10天
+
+**三种策略模式**(`g.ma_gap_strategy_mode`)
+
+**策略1 - 趋势跟随**:
+- 跳空方向:与趋势一致(看涨上跳/看跌下跳)
+- 跳空幅度:可选检查(`g.check_gap_magnitude`)
+- 最终价格验证:日内价差可选检查(`g.check_intraday_spread`)
+- 适用场景:追随趋势方向的强势突破
+
+**策略2 - 逆势操作+强制阈值**:
+- 跳空方向:与趋势相反(看涨下跳/看跌上跳)
+- 跳空幅度:可选检查(`g.check_gap_magnitude`)
+- 最终价格验证:强制日内变化阈值≥0.5%(`g.ma_intraday_threshold_scheme2`)
+- 适用场景:捕捉回调后的强力反弹
+
+**策略3 - 逆势操作+价格回归**:
+- 跳空方向:与趋势相反(看涨下跳/看跌上跳)
+- 跳空幅度:可选检查(`g.check_gap_magnitude`)
+- 最终价格验证:价格回归到前日开盘收盘均值
+- 适用场景:捕捉回调后的均值回归行情

+ 283 - 2
Lib/future/README.md

@@ -6,8 +6,88 @@
 1. 期货交易里的`order_target_value`里的`value`建议用保证金的金额,而不是实际价格
 2. 期货交易里的`order_target_value`里的`side`建议用`long`或`short`,而不能为空
 
-## 沪深300期货蜘蛛网策略
+## MAPatternStrategy_v002 均线形态策略
+
+### 策略概览
+该策略围绕主力合约的多周期移动平均线(MA5、MA10、MA20、MA30)构建信号,首先判断趋势方向,再结合价差与日内表现决定是否开仓。策略采用`calculate_extreme_trend_days`作为极端波动过滤器,并配合固定止损和动态均线追踪止盈控制风险。默认仅交易`['IF', 'LH', 'AG', 'IC', 'B', 'EG']`等流动性较好的品种。
+
+### 趋势识别与开仓条件
+- **趋势方向(多头/空头)**:
+  - 多头排列:满足`MA30 ≤ MA20 ≤ MA10 ≤ MA5`或`MA30 ≤ MA20 ≤ MA5 ≤ MA10`。
+  - 空头排列:满足`MA10 ≤ MA5 ≤ MA20 ≤ MA30`或`MA5 ≤ MA10 ≤ MA20 ≤ MA30`。
+  - 判断逻辑由函数`check_ma_pattern`实现,是开仓前提。
+- **历史一致性检查**:
+  - 参数:`g.ma_pattern_lookback_days = 10`,`g.ma_pattern_consistency_threshold = 0.8`。
+  - 若过去10个交易日中有至少80%的天数符合当前趋势方向,则认为趋势稳定。
+- **极端趋势过滤器**:
+  - `calculate_extreme_trend_days`会统计过去10个交易日中,收盘价高于所有均线的天数A和低于所有均线的天数B。
+  - 当A和B都大于0且`min(A,B) ≥ max(2, g.ma_pattern_extreme_days_threshold)`(默认阈值4)时,视为多空急速转换,过滤该标的,不进入候选列表。
+- **MA5分布过滤**:
+  - 参数:`g.ma_distribution_lookback_days = 5`,`g.ma_distribution_min_ratio = 0.4`,`g.enable_ma_distribution_filter = True`。
+  - 多头要求最近5日中至少40%的交易日收盘价低于MA5;空头要求至少40%交易日收盘价高于MA5。
+- **开盘价差要求**:
+  - 策略模式由`g.ma_gap_strategy_mode`决定(默认2)。
+  - 方案1:多头需上跳≥`g.ma_open_gap_threshold`(默认0.002),空头需下跳≤`-g.ma_open_gap_threshold`。
+  - 方案2:多头需下跳≤`-g.ma_open_gap_threshold2`(默认0.002),空头需上跳≥`g.ma_open_gap_threshold2`。
+- **日内价差检查**:
+  - 方案1:若`g.check_intraday_spread = False`,则忽略日内价差;为True时,多头要求当日涨幅>0,空头要求跌幅<0。
+  - 方案2:必须满足日内相对变化绝对值≥`g.ma_intraday_threshold_scheme2`(默认0.005)。
+- **资金与手数**:
+  - 资金占用比例:`g.usage_percentage = 0.8`。
+  - 单品种保证金上限:`g.max_margin_per_position = 20000`。
+  - 手数依据保证金乘数和当前价格计算,确保至少1手。
+
+### 止损逻辑
+- **参数**:`g.fixed_stop_loss_rate = 0.01`。
+- **形式**:基于入场价的百分比亏损,当浮亏达到1%即触发止损,无需额外条件。
+- **触发流程**:每次定时任务执行`check_stop_loss_profit`时,计算当前价相对入场价的收益率(多头:`(现价-入场价)/入场价`,空头取反)。若收益率≤-1%,调用`close_position`平仓并记录日志。
+
+### 止盈逻辑
+- **均线追踪止盈**:根据持仓天数与波动程度选择参考均线,并使用偏移量形成动态出场线。
+  - 常规偏移:`g.ma_offset_ratio_normal = 0.003`。
+  - 收盘前偏移:`g.ma_offset_ratio_close = 0.01`。
+  - 进入交易日内不启用追踪止盈,以避免噪音。
+- **参考均线选择**:
+  - 若当日波动显著(涨跌幅≥1.5倍平均波动)或持仓未超过`g.days_for_adjustment = 4`天,则使用MA5。
+  - 否则根据波动大小在MA5与MA10之间切换。
+- **触发条件**:
+  - 多头:当前价 < 参考均线 × (1 - 偏移量)。
+  - 空头:当前价 > 参考均线 × (1 + 偏移量)。
+  - 满足条件即平仓,并在日志中记录触发原因与参数。
+
+### 关键参数一览
+| 参数名 | 默认值 | 含义 |
+| --- | --- | --- |
+| `g.ma_periods` | `[5, 10, 20, 30]` | 均线组合 |
+| `g.ma_pattern_lookback_days` | `10` | 历史趋势一致性统计天数 |
+| `g.ma_pattern_consistency_threshold` | `0.8` | 趋势一致性最小比例 |
+| `g.ma_pattern_extreme_days_threshold` | `4` | 极端趋势过滤阈值(min(A,B))|
+| `g.ma_gap_strategy_mode` | `2` | 开盘价差方案(1或2)|
+| `g.ma_open_gap_threshold` | `0.002` | 方案1开盘价差阈值 |
+| `g.ma_open_gap_threshold2` | `0.002` | 方案2开盘价差阈值 |
+| `g.ma_intraday_threshold_scheme2` | `0.005` | 方案2日内变化阈值 |
+| `g.ma_distribution_lookback_days` | `5` | MA5分布过滤回溯天数 |
+| `g.ma_distribution_min_ratio` | `0.4` | MA5分布满足比例阈值 |
+| `g.enable_ma_distribution_filter` | `True` | 是否启用MA5分布过滤 |
+| `g.fixed_stop_loss_rate` | `0.01` | 固定止损比例 |
+| `g.ma_offset_ratio_normal` | `0.003` | 常规追踪止盈偏移 |
+| `g.ma_offset_ratio_close` | `0.01` | 收盘前追踪止盈偏移 |
+| `g.days_for_adjustment` | `4` | 常规追踪止盈切换阈值 |
+| `g.usage_percentage` | `0.8` | 资金使用比例 |
+| `g.max_margin_per_position` | `20000` | 单品种保证金上限 |
+
+### 运行时序
+- 21:05、09:05、09:35:执行`check_ma_trend_and_open_gap`,依次检查趋势、极端过滤、开盘价差并更新候选列表。
+- 14:35、14:55:执行`check_intraday_price_diff`,根据日内表现决定是否开仓。
+- 多个时间点执行`check_stop_loss_profit`,同时负责止损、止盈与换月检查。
+
+### 日志与排错
+- 每次筛选都会输出趋势状态、均线贴近统计、极端趋势天数、开盘价差等详细日志。
+- 被过滤的合约会记录在`g.excluded_contracts`,原因包括`ma_extreme_trend`、`ma_trend`、`ma5_distribution`、`open_gap`等,便于复盘。
+
+---
 
+## 沪深300期货蜘蛛网策略
 ### 核心思路
 该策略基于期货主力合约的持仓数据进行交易,通过监控主力多空持仓变化来判断市场方向。策略重点关注机构持仓变动,通过多空持仓增量的对比来进行交易决策。
 
@@ -362,4 +442,205 @@ g.stop_ratios = [0.0025, 0.005, 0.01, 0.02] # 止损比例
 - 考虑交易成本:
   - 手续费:按照不同品种设置
   - 滑点:按照品种特性设置
-  - 保证金:按照交易所要求设置
+  - 保证金:按照交易所要求设置
+
+## 烛台影线形态反向交易策略 v001
+
+### 核心思路
+该策略基于烛台形态检测,专门识别带有明显影线的K线形态,并按照影线的相反方向进行交易。这是一种基于"结论1"逻辑的反向交易策略,即认为影线代表市场的假突破,应该按相反方向进行交易。
+
+1. [策略来源](https://www.joinquant.com)/ [本地](Lib\future\CandlestickHatchReverseStrategy_v001.py)
+2. 策略名称:烛台影线形态反向交易策略
+3. 实现理念:基于烛台形态的反向交易思路
+
+### 主要特点
+1. 烛台形态识别(上影线/下影线)
+2. 反向交易逻辑(影线相反方向)
+3. 固定止损保护(可配置)
+4. 动态追踪止盈机制
+5. 自动合约换月功能
+6. 支持日内和夜盘交易
+
+### 具体策略逻辑
+
+#### 1. 交易品种
+- 交易品种:流动性较好的商品期货和金融期货
+- 默认关注品种:RM、RB、AU、CU、M、Y、CF、SR
+- 夜盘品种:金属、能源、农产品等
+- 日盘品种:其他商品、股指期货等
+- 交易合约:主力合约
+
+#### 2. 形态识别
+
+##### 2.1 烛台形态条件
+- 实体长度阈值:使用历史30天平均实体长度作为基准
+- 影线与实体比率:影线长度 >= 1.2 × 实体长度(**g.hatch_to_body_ratio**)
+- 相反影线限制:相反方向影线 < 0.5 × 实体长度(**g.opposite_hatch_ratio**)
+
+##### 2.2 形态类型
+- **上影线形态**:
+  - 上影线长度 >= 1.2 × 实体长度
+  - 下影线长度 < 0.5 × 实体长度
+  - 交易方向:做空(反向交易)
+- **下影线形态**:
+  - 下影线长度 >= 1.2 × 实体长度
+  - 上影线长度 < 0.5 × 实体长度
+  - 交易方向:做多(反向交易)
+
+#### 3. 仓位管理
+
+##### 3.1 开仓规则
+- 计算开仓数量:
+  ```python
+  单手保证金 = 当前价格 × 合约乘数 × 保证金比率
+  可用资金 = 账户资金 × 资金使用比例(80%)
+  开仓手数 = min(最大保证金限制 / 单手保证金, 可用资金 / 单手保证金)
+  ```
+- 开仓限制:
+  1. 检查涨跌停限制
+  2. 考虑保证金要求(**g.max_margin_per_position = 20000**)
+  3. 检查流动性条件
+  4. 防止同品种重复开仓
+
+##### 3.2 持仓调整
+- 移仓换月条件:
+  1. 主力合约更换时自动执行
+  2. 确保新旧合约可交易
+  3. 保持原有持仓方向和止损止盈记录
+- 移仓流程:
+  1. 平掉旧合约
+  2. 开仓新合约
+  3. 更新交易记录
+
+#### 4. 风险控制
+
+##### 4.1 止损机制
+- **固定止损**:
+  - 固定止损率:1%(**g.fixed_stop_loss_rate = 0.01**)
+  - 当亏损达到1%时立即平仓
+  - 适用于所有交易,无条件执行
+
+##### 4.2 动态追踪止盈
+- **分级追踪止盈**:根据已实现的最大利润设置不同的追踪止损比率
+  - 若最大利润 ≤ 5%:使用1%追踪止损(**g.trailing_stop_rates[0] = 0.01**)
+  - 若最大利润在5%-10%之间:使用2%追踪止损(**g.trailing_stop_rates[1] = 0.02**)
+  - 若最大利润 > 10%:使用3%追踪止损(**g.trailing_stop_rates[2] = 0.03**)
+- **追踪机制**:
+  - 实时记录持仓的最大利润
+  - 当利润回撤超过对应阈值时触发止盈
+  - 例如:最大利润8%,当前利润5%,回撤3% > 2%,触发止盈
+
+##### 4.2.1 详细止盈规则
+| 最大利润水平 | 追踪止损率 | 触发条件 | 说明 |
+|-------------|----------|---------|------|
+| ≤ 5% | 1% | 利润回撤 ≥ 1% | 小额利润,保守止盈 |
+| 5%-10% | 2% | 利润回撤 ≥ 2% | 中等利润,适度止盈 |
+| > 10% | 3% | 利润回撤 ≥ 3% | 高额利润,宽松止盈 |
+
+##### 4.3 配置参数
+```python
+g.fixed_stop_loss_rate = 0.01  # 固定止损比率(1%)
+g.trailing_stop_thresholds = [0.05, 0.10]  # 动态追踪止损触发条件(5%, 10%)
+g.trailing_stop_rates = [0.01, 0.02, 0.03]  # 动态追踪止损比率(1%, 2%, 3%)
+g.hatch_to_body_ratio = 1.2  # 影线与实体长度比率阈值
+g.opposite_hatch_ratio = 0.5  # 相反方向影线与实体长度比率阈值
+```
+
+#### 5. 交易执行
+
+##### 5.1 交易时间
+- 日盘:09:05开始交易
+- 夜盘:21:05开始交易
+- 14:55最后一次检查
+
+##### 5.2 核心交易步骤
+1. **任务1**: 获取所有可交易品种(分白天和晚上)
+2. **任务2**: 获取历史数据并计算实体长度阈值(基于30天历史数据)
+3. **任务3**: 获取今日分钟数据并聚合为日数据
+4. **任务4**: 检测烛台影线形态
+5. **任务5**: 检查开仓条件(价格合理性、流动性等)
+6. **任务6**: 执行交易(按影线相反方向开仓)
+7. **任务7**: 检查止损止盈
+8. **任务8**: 检查换月移仓
+
+##### 5.3 时间点任务分配
+- **夜盘开始**:
+  - 21:05:任务1, 2, 3, 4, 5, 6, 7(完整流程)
+  - 21:35, 22:05, 22:35:任务3, 4, 5, 6, 7(常规检查)
+- **日盘开始**:
+  - 09:05:任务1, 2, 3, 4, 5, 6, 7(完整流程)
+  - 09:35, 10:05, 10:35, 11:05, 11:25, 13:35, 14:05:任务3, 4, 5, 6, 7(常规检查)
+- **收盘前**:
+  - 14:35, 14:55:任务3, 4, 5, 6, 7, 8(包含移仓检查)
+
+#### 6. 策略特色
+
+##### 6.1 反向交易逻辑
+- **上影线 → 做空**:认为上影线表示上方压力强劲,价格可能下跌
+- **下影线 → 做多**:认为下影线表示下方支撑强劲,价格可能上涨
+- 基于"影线代表假突破"的交易理念
+
+##### 6.2 智能风控系统
+- 固定止损保护本金
+- 分级追踪止盈锁定利润
+- 根据盈利水平动态调整止盈策略
+- 自动合约换月避免交割风险
+
+##### 6.3 品种选择策略
+- 重点关注流动性好的主流品种
+- 支持夜盘和日盘品种
+- 避免同品种重复开仓
+- 动态获取主力合约
+
+### 交易规则
+- 采用异步报单模式
+- 使用真实价格
+- 考虑交易成本:
+  - 开仓手续费:0.0023%
+  - 平仓手续费:0.0023%  
+  - 平今手续费:0.23%
+  - 滑点:2个价格单位
+- 保证金比例:按品种设置(4%-22%不等)
+- 最大单品种保证金:20000元
+
+## 期货左侧交易策略(带网格和对冲)v001
+`FutureLeftSide_v001.py` 将仓位拆分为底仓左侧多头、网格多头和空头对冲三大组件,所有任务在执行前会优先尝试移仓换月,确保逻辑仅在可交易时间与主力合约下运行。
+
+### 底仓左侧多头
+
+- **开仓条件**
+  - 达到品种配置的开盘时间(`is_trading_time_reached`)。
+  - 遍历 `g.base_position_grid` 未成交档位:档位价 ≥ 当前价立即市价买入;档位价 < 当前价仅保留价格最高的两个档位挂限价单。
+  - 仅对尚未记录的档位下单,避免重复委托。
+- **止盈 / 止损条件**
+  - 市价 ≥ `g.base_position_exit_price[symbol]` 时一次性平掉底仓多头。
+  - 策略未设置固定止损,风险由网格和对冲组件共同覆盖。
+- **检查时间点**
+  - 开仓检查:09:05(日盘)、09:35(日盘金融类标的)、21:05(夜盘)。
+  - 止盈检查:09:35、10:05、10:35、11:05、13:35、14:05、14:35、14:55、21:05、21:35、22:05。
+
+### 网格多头(可选)
+
+- **开仓条件**
+  - 达到交易时间且当前价低于 `start_price`。
+  - 计算应触发的网格层级:目标价 ≥ 当前价使用市价单,其余只选择最高两个目标价挂限价单。
+  - 成交后在 `g.grid_buy_levels` 中标记目标价,防止重复下单。
+- **止盈 / 止损条件**
+  - 市价 ≥ `target_price + exit_grid_size` 时对应仓位全部卖出并解除标记。
+  - 未额外设定止损。
+- **检查时间点**
+  - 开仓检查:09:05(日盘)、09:35(日盘金融类标的)、21:05(夜盘)。
+  - 止盈检查:与底仓止盈相同的全部时间点。
+
+### 空头对冲(可选)
+
+- **开仓条件**
+  - 当天存在已成交的底仓档位(依据订单状态与成交量确认)。
+  - 对每个符合条件的档位按档位价挂空单,数量等于底仓手数,仅首次成功时记录。
+- **止盈 / 止损条件**
+  - 固定止损:收益率 ≤ -1% 时立即平仓。
+  - 盈利回撤止盈:当前收益距离历史最大收益回撤 ≥ 0.3% 时平仓。
+  - 成本区域止盈:达到盈利阶段后,收益回落至 ±2% 区间内触发。
+- **检查时间点**
+  - 开仓检查:09:05(日盘)、09:35(日盘金融类标的)、21:05(夜盘) 以及全部止盈检查时间点(用于补挂漏单)。
+  - 止盈检查:与网格、底仓相同的全部时间点。

+ 65 - 0
Lib/future/Upgrade_log.md

@@ -0,0 +1,65 @@
+# MAPatternStrategy
+
+### 下跳涨回的策略251022版本
+
+#### Missing Open
+1. 20250926, EG2601, 上跳幅度太小只有0.19%
+2. 20250902, AU2512, 因为均线之前是纠缠的
+3. 20250911, IC2511, 下跳幅度太小只有0.1%
+
+#### Open
+1. 20250902, CJ2601, fail, 上涨趋势,但是低于MA20,上涨幅度不大在前一天长阴线的下半部分
+2. 20250909, SP2511, fail, 很难避免,可能是因为在支撑位附近有个小反弹
+3. 20250911, IF2509, fail, 上涨后期了
+4. 20250911, IC2509, fail, 开仓太晚,如果最晚是涨过前三天的高点就好了
+5. 20250924, CJ2601, fail, 下跌趋势,上跳跌回的收盘价都没有低过前一天开盘价和收盘价之间的高点
+6. 20251015, CU2511, fail, 上涨后期了,形态没毛病,可以开的早一点
+7. 20251016, CU2511, fail, 如果是长阳线后的下跳涨回得超过前一天收盘价;如果是长阴线后的上跳跌回得超过前一天收盘价
+
+#### Good
+1. 20250916, LH2511, big
+2. 20250923, AG2512, big
+3. 20250924, IF2510, medium, 可以更早点,高过前一天开盘价和收盘价之间的低点就可以
+4. 20250924, IC2510, medium, 可以更早点,高过前一天开盘价和收盘价之间的低点就可以
+5. 20250926, B2511, tiny, 可以更早点,高过前一天开盘价和收盘价之间的低点就可以
+6. 20250930, EG2601, medium
+
+#### Issue
+1. 20250917, EB2510, 为什么刚开仓就平仓?-solved
+2. 20250924, CJ2601, 为什么刚开仓就平仓?-solved
+3. 20250930, LH2511, 为什么要在夜盘平仓?-solved
+4. 20251010, ZN2511, 为什么刚开仓就平仓?-solved
+5. 20251013, ZN2511, 为什么刚开仓就平仓?-solved
+6. 20251013, IM2510, 为什么还能开仓?-solved
+7. 20250930, LH2511, 为什么会平仓-solved
+
+### 下跳涨回的策略251024版本
+#### Missing Open
+1. 20250926, AU2512
+
+#### Bad Open
+1. 20250902, AP2510
+2. 20250902, CJ2601, 很难避免
+3. 20250919, IC2510, 为什么会开仓
+4. 20251017, IF2511, 很难避免
+5. 20250909, IC2509, 很难避免
+
+#### Good Open
+1. 20250916, EG2601
+2. 20250911, IF2509, 整体在10K和20K之间,前一天十字星,后一天下跳涨回长阳包前,且保住了前一天的最高价
+3. LH2511
+4. 20250924, IF2510, 当天穿越了5K,10K,20K,前一天小实体长下影,当天下跳涨回长阳包前,且保住了前一天的最高价
+5. IC2510
+
+#### Issue
+1. 20251014, AU2512, 为什么会开仓
+2. 20251016, IC2510, 为什么会开仓
+3. 20250901, SC2510, 是否可以不开仓
+4. 20250901, OI2601, 是否可以不开仓
+5. 20250901, JD2510, 是否可以不开仓
+6. 20250901, P2601, 20250910止盈止损是否可以优化
+7. 20250904, SP2511, 为什么会开仓
+8. 20250904, CS2511, 是否可以不开仓
+
+#### Plan
+1. 对于下跳涨回和上跳跌回是否可以根据跳的位置分成两档然后设置不同的开仓条件。针对下跳涨回,如果跳到昨日平均值以上,那么应该涨过昨日的开盘价和收盘价较大值,如果跳到昨日平均值以下

+ 486 - 0
Lib/future/kline_reconstruction.py

@@ -0,0 +1,486 @@
+# K线复原工具
+# 用于从交易记录CSV文件中提取开仓记录,获取对应的K线数据并绘制包含均线的K线图
+
+from jqdata import *
+import pandas as pd
+import numpy as np
+import matplotlib.pyplot as plt
+import matplotlib.patches as patches
+from datetime import datetime, timedelta, date
+import re
+import os
+import warnings
+warnings.filterwarnings('ignore')
+
+# 中文字体设置(虽然图片内文字用英文,但保留设置以防需要)
+plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']
+plt.rcParams['axes.unicode_minus'] = False
+
+
+def _get_current_directory():
+    """
+    获取当前文件所在目录,兼容 Jupyter notebook 环境
+    
+    返回:
+        str: 当前目录路径
+    """
+    try:
+        # 在普通 Python 脚本中,使用 __file__
+        current_dir = os.path.dirname(os.path.abspath(__file__))
+    except NameError:
+        # 在 Jupyter notebook 环境中,__file__ 不存在,使用当前工作目录
+        current_dir = os.getcwd()
+        # 如果当前目录不是 future 目录,尝试查找
+        if not os.path.exists(os.path.join(current_dir, 'transaction.csv')):
+            # 尝试查找 future 目录
+            if 'future' not in current_dir:
+                # 尝试向上查找 future 目录
+                parent_dir = os.path.dirname(current_dir)
+                future_dir = os.path.join(parent_dir, 'future')
+                if os.path.exists(os.path.join(future_dir, 'transaction.csv')):
+                    current_dir = future_dir
+    return current_dir
+
+
+def read_and_filter_open_positions(csv_path):
+    """
+    读取CSV文件并筛选出开仓记录
+    
+    参数:
+        csv_path (str): CSV文件路径
+    
+    返回:
+        pandas.DataFrame: 包含开仓记录的DataFrame
+    """
+    try:
+        df = pd.read_csv(csv_path, encoding='utf-8-sig')
+        
+        # 筛选交易类型第一个字符为"开"的行
+        open_positions = df[df['交易类型'].str[0] == '开'].copy()
+        
+        print(f"从CSV文件中读取到 {len(df)} 条记录")
+        print(f"筛选出 {len(open_positions)} 条开仓记录")
+        
+        return open_positions
+    except Exception as e:
+        print(f"读取CSV文件时出错: {str(e)}")
+        return pd.DataFrame()
+
+
+def extract_contract_code_and_date(row):
+    """
+    从标的列提取合约编号,从日期列提取日期
+    
+    参数:
+        row (pandas.Series): DataFrame的一行数据
+    
+    返回:
+        tuple: (contract_code, trade_date) 或 (None, None) 如果提取失败
+    """
+    try:
+        # 提取合约编号:从"标的"列中提取括号内的内容
+        target_str = str(row['标的'])
+        match = re.search(r'\(([^)]+)\)', target_str)
+        if match:
+            contract_code = match.group(1)
+        else:
+            print(f"无法从标的 '{target_str}' 中提取合约编号")
+            return None, None
+        
+        # 提取日期
+        date_str = str(row['日期'])
+        try:
+            trade_date = datetime.strptime(date_str, '%Y-%m-%d').date()
+        except:
+            print(f"日期格式错误: {date_str}")
+            return None, None
+        
+        return contract_code, trade_date
+    except Exception as e:
+        print(f"提取合约编号和日期时出错: {str(e)}")
+        return None, None
+
+
+def calculate_trade_days_range(trade_date, days_before=60, days_after=10):
+    """
+    计算交易日范围:往前days_before个交易日,往后days_after个交易日
+    
+    参数:
+        trade_date (date): 开仓日期
+        days_before (int): 往前交易日数量,默认60
+        days_after (int): 往后交易日数量,默认10
+    
+    返回:
+        tuple: (start_date, end_date) 或 (None, None) 如果计算失败
+    """
+    try:
+        # 往前找:从trade_date往前找days_before个交易日
+        # get_trade_days(end_date=trade_date, count=n) 返回包括trade_date在内的n个交易日
+        # 所以需要count=days_before+1,第一个就是days_before个交易日前的日期
+        trade_days_before = get_trade_days(end_date=trade_date, count=days_before + 1)
+        if len(trade_days_before) < days_before + 1:
+            print(f"无法获取足够的往前交易日,只获取到 {len(trade_days_before)} 个")
+            return None, None
+        
+        start_date = trade_days_before[0].date()
+        
+        # 往后找:从trade_date往后找days_after个交易日
+        # get_trade_days(start_date=trade_date, count=n) 返回包括trade_date在内的n个交易日
+        # 所以需要count=days_after+1,最后一个就是days_after个交易日后的日期
+        trade_days_after = get_trade_days(start_date=trade_date, count=days_after + 1)
+        if len(trade_days_after) < days_after + 1:
+            print(f"无法获取足够的往后交易日,只获取到 {len(trade_days_after)} 个")
+            return None, None
+        
+        end_date = trade_days_after[-1].date()
+        
+        return start_date, end_date
+    except Exception as e:
+        print(f"计算交易日范围时出错: {str(e)}")
+        return None, None
+
+
+def get_kline_data(contract_code, start_date, end_date):
+    """
+    获取指定合约在时间范围内的K线数据
+    
+    参数:
+        contract_code (str): 合约编号,如 'JD2502.XDCE'
+        start_date (date): 开始日期
+        end_date (date): 结束日期
+    
+    返回:
+        pandas.DataFrame: 包含OHLC数据的DataFrame,如果获取失败返回None
+    """
+    try:
+        # 使用get_price获取K线数据
+        price_data = get_price(
+            contract_code,
+            start_date=start_date,
+            end_date=end_date,
+            frequency='1d',
+            fields=['open', 'close', 'high', 'low']
+        )
+        
+        if price_data is None or len(price_data) == 0:
+            print(f"未获取到 {contract_code} 在 {start_date} 至 {end_date} 的数据")
+            return None
+        
+        return price_data
+    except Exception as e:
+        print(f"获取K线数据时出错: {str(e)}")
+        return None
+
+
+def calculate_moving_averages(data):
+    """
+    计算5K, 10K, 20K, 30K均线
+    
+    参数:
+        data (pandas.DataFrame): 包含close列的DataFrame
+    
+    返回:
+        pandas.DataFrame: 添加了均线列的DataFrame
+    """
+    data = data.copy()
+    
+    # 计算均线
+    data['ma5'] = data['close'].rolling(window=5).mean()
+    data['ma10'] = data['close'].rolling(window=10).mean()
+    data['ma20'] = data['close'].rolling(window=20).mean()
+    data['ma30'] = data['close'].rolling(window=30).mean()
+    
+    return data
+
+
+def filter_data_with_ma(data):
+    """
+    过滤掉任何一条均线为空的日期
+    
+    参数:
+        data (pandas.DataFrame): 包含均线列的DataFrame
+    
+    返回:
+        pandas.DataFrame: 过滤后的DataFrame
+    """
+    # 过滤掉任何一条均线为空的日期
+    filtered_data = data.dropna(subset=['ma5', 'ma10', 'ma20', 'ma30'])
+    
+    return filtered_data
+
+
+def plot_kline_chart(data, contract_code, trade_date, save_path):
+    """
+    绘制K线图(包含均线和开仓日期标注)
+    
+    参数:
+        data (pandas.DataFrame): 包含OHLC和均线数据的DataFrame
+        contract_code (str): 合约编号
+        trade_date (date): 开仓日期
+        save_path (str): 保存路径
+    """
+    try:
+        # 创建图表
+        fig, ax = plt.subplots(figsize=(16, 10))
+        
+        # 准备数据
+        dates = data.index
+        opens = data['open']
+        highs = data['high']
+        lows = data['low']
+        closes = data['close']
+        
+        # 找到开仓日期在数据中的位置
+        trade_date_idx = None
+        for i, date_idx in enumerate(dates):
+            if isinstance(date_idx, date):
+                if date_idx == trade_date:
+                    trade_date_idx = i
+                    break
+            elif isinstance(date_idx, datetime):
+                if date_idx.date() == trade_date:
+                    trade_date_idx = i
+                    break
+        
+        # 绘制K线
+        for i in range(len(data)):
+            date_idx = dates[i]
+            open_price = opens.iloc[i]
+            high_price = highs.iloc[i]
+            low_price = lows.iloc[i]
+            close_price = closes.iloc[i]
+            
+            # K线颜色:红涨绿跌
+            color = 'red' if close_price > open_price else 'green'
+            edge_color = 'darkred' if close_price > open_price else 'darkgreen'
+            
+            # 绘制影线(最高价到最低价的竖线)
+            ax.plot([i, i], [low_price, high_price], color='black', linewidth=1)
+            
+            # 绘制实体(开盘价到收盘价的矩形)
+            body_height = abs(close_price - open_price)
+            if body_height == 0:
+                body_height = 0.01  # 避免高度为0
+            bottom = min(open_price, close_price)
+            
+            # 使用矩形绘制K线实体
+            rect = patches.Rectangle((i-0.4, bottom), 0.8, body_height,
+                                   linewidth=1, edgecolor=edge_color, 
+                                   facecolor=color, alpha=0.8)
+            ax.add_patch(rect)
+        
+        # 绘制均线
+        ax.plot(range(len(data)), data['ma5'], label='MA5', color='blue', linewidth=1.5, alpha=0.8)
+        ax.plot(range(len(data)), data['ma10'], label='MA10', color='orange', linewidth=1.5, alpha=0.8)
+        ax.plot(range(len(data)), data['ma20'], label='MA20', color='purple', linewidth=1.5, alpha=0.8)
+        ax.plot(range(len(data)), data['ma30'], label='MA30', color='brown', linewidth=1.5, alpha=0.8)
+        
+        # 标注开仓日期位置
+        if trade_date_idx is not None:
+            trade_price = closes.iloc[trade_date_idx]
+            ax.plot(trade_date_idx, trade_price, marker='*', markersize=15, 
+                   color='yellow', markeredgecolor='black', markeredgewidth=1.5,
+                   label='Open Position', zorder=10)
+            # 添加垂直线
+            ax.axvline(x=trade_date_idx, color='yellow', linestyle='--', 
+                      linewidth=2, alpha=0.7, zorder=5)
+        
+        # 设置图表标题和标签(使用英文)
+        contract_simple = contract_code.split('.')[0]  # 提取合约编号的简约部分
+        ax.set_title(f'{contract_simple} ({contract_code}) K-Line Chart\n'
+                   f'Period: {dates[0].strftime("%Y-%m-%d")} to {dates[-1].strftime("%Y-%m-%d")} '
+                   f'({len(data)} bars)', 
+                   fontsize=14, fontweight='bold', pad=20)
+        
+        ax.set_xlabel('Time', fontsize=12)
+        ax.set_ylabel('Price', fontsize=12)
+        ax.grid(True, alpha=0.3)
+        ax.legend(loc='upper left', fontsize=10)
+        
+        # 设置x轴标签
+        step = max(1, len(data) // 10)  # 显示约10个时间标签
+        tick_positions = range(0, len(data), step)
+        tick_labels = []
+        for pos in tick_positions:
+            date_val = dates[pos]
+            if isinstance(date_val, date):
+                tick_labels.append(date_val.strftime('%Y-%m-%d'))
+            elif isinstance(date_val, datetime):
+                tick_labels.append(date_val.strftime('%Y-%m-%d'))
+            else:
+                tick_labels.append(str(date_val))
+        
+        ax.set_xticks(tick_positions)
+        ax.set_xticklabels(tick_labels, rotation=45, ha='right')
+        
+        # 添加统计信息(使用英文)
+        max_price = highs.max()
+        min_price = lows.min()
+        latest_close = closes.iloc[-1]
+        first_close = closes.iloc[0]
+        total_change = (latest_close - first_close) / first_close * 100
+        
+        stats_text = (f'High: {max_price:.2f}\n'
+                    f'Low: {min_price:.2f}\n'
+                    f'Latest Close: {latest_close:.2f}\n'
+                    f'Total Change: {total_change:+.2f}%')
+        
+        ax.text(0.02, 0.98, stats_text, transform=ax.transAxes, 
+               verticalalignment='top', bbox=dict(boxstyle='round', 
+               facecolor='wheat', alpha=0.8), fontsize=10)
+        
+        # 调整布局并保存
+        plt.tight_layout()
+        plt.savefig(save_path, dpi=150, bbox_inches='tight')
+        plt.close()
+        
+        print(f"K线图已保存到: {save_path}")
+        
+    except Exception as e:
+        print(f"绘制K线图时出错: {str(e)}")
+        raise
+
+
+def reconstruct_kline_from_transactions(csv_path=None, output_dir=None):
+    """
+    主函数:从交易记录中复原K线图
+    
+    参数:
+        csv_path (str): CSV文件路径,默认为 'Lib/future/transaction.csv'
+        output_dir (str): 输出目录,默认为 'Lib/future/K'
+    """
+    # 设置默认路径
+    if csv_path is None:
+        # 获取当前文件所在目录
+        # 在 Jupyter notebook 中,__file__ 不存在,使用当前工作目录
+        try:
+            current_dir = os.path.dirname(os.path.abspath(__file__))
+        except NameError:
+            # 在 Jupyter notebook 环境中,使用当前工作目录
+            current_dir = os.getcwd()
+            # 如果当前目录不是 future 目录,尝试查找
+            if not os.path.exists(os.path.join(current_dir, 'transaction.csv')):
+                # 尝试查找 future 目录
+                if 'future' in current_dir:
+                    pass  # 已经在 future 目录中
+                else:
+                    # 尝试向上查找 future 目录
+                    parent_dir = os.path.dirname(current_dir)
+                    future_dir = os.path.join(parent_dir, 'future')
+                    if os.path.exists(os.path.join(future_dir, 'transaction.csv')):
+                        current_dir = future_dir
+        csv_path = os.path.join(current_dir, 'transaction.csv')
+    
+    if output_dir is None:
+        # 获取当前文件所在目录
+        try:
+            current_dir = os.path.dirname(os.path.abspath(__file__))
+        except NameError:
+            # 在 Jupyter notebook 环境中,使用当前工作目录
+            current_dir = os.getcwd()
+            # 如果当前目录不是 future 目录,尝试查找
+            if not os.path.exists(os.path.join(current_dir, 'transaction.csv')):
+                # 尝试查找 future 目录
+                if 'future' in current_dir:
+                    pass  # 已经在 future 目录中
+                else:
+                    # 尝试向上查找 future 目录
+                    parent_dir = os.path.dirname(current_dir)
+                    future_dir = os.path.join(parent_dir, 'future')
+                    if os.path.exists(os.path.join(future_dir, 'transaction.csv')):
+                        current_dir = future_dir
+        output_dir = os.path.join(current_dir, 'K')
+    
+    # 确保输出目录存在
+    os.makedirs(output_dir, exist_ok=True)
+    print(f"输出目录: {output_dir}")
+    
+    # 1. 读取和筛选开仓记录
+    print("\n=== 步骤1: 读取和筛选开仓记录 ===")
+    open_positions = read_and_filter_open_positions(csv_path)
+    
+    if len(open_positions) == 0:
+        print("未找到开仓记录,退出")
+        return
+    
+    # 2. 处理每条开仓记录
+    print(f"\n=== 步骤2: 处理 {len(open_positions)} 条开仓记录 ===")
+    success_count = 0
+    fail_count = 0
+    
+    for idx, row in open_positions.iterrows():
+        print(f"\n--- 处理第 {idx + 1}/{len(open_positions)} 条记录 ---")
+        
+        try:
+            # 提取合约编号和日期
+            contract_code, trade_date = extract_contract_code_and_date(row)
+            if contract_code is None or trade_date is None:
+                print(f"跳过:无法提取合约编号或日期")
+                fail_count += 1
+                continue
+            
+            print(f"合约编号: {contract_code}, 开仓日期: {trade_date}")
+            
+            # 计算交易日范围
+            start_date, end_date = calculate_trade_days_range(trade_date, days_before=60, days_after=10)
+            if start_date is None or end_date is None:
+                print(f"跳过:无法计算交易日范围")
+                fail_count += 1
+                continue
+            
+            print(f"数据范围: {start_date} 至 {end_date}")
+            
+            # 获取K线数据
+            kline_data = get_kline_data(contract_code, start_date, end_date)
+            if kline_data is None or len(kline_data) == 0:
+                print(f"跳过:无法获取K线数据")
+                fail_count += 1
+                continue
+            
+            print(f"获取到 {len(kline_data)} 条K线数据")
+            
+            # 计算均线
+            kline_data = calculate_moving_averages(kline_data)
+            
+            # 过滤数据
+            filtered_data = filter_data_with_ma(kline_data)
+            if len(filtered_data) == 0:
+                print(f"跳过:过滤后无有效数据")
+                fail_count += 1
+                continue
+            
+            print(f"过滤后剩余 {len(filtered_data)} 条有效数据")
+            
+            # 生成文件名
+            contract_simple = contract_code.split('.')[0]  # 提取合约编号的简约部分
+            filename = f"{contract_simple}_{trade_date.strftime('%Y%m%d')}.png"
+            save_path = os.path.join(output_dir, filename)
+            
+            # 绘制K线图
+            plot_kline_chart(filtered_data, contract_code, trade_date, save_path)
+            
+            success_count += 1
+            print(f"✓ 成功处理")
+            
+        except Exception as e:
+            print(f"✗ 处理时出错: {str(e)}")
+            fail_count += 1
+            continue
+    
+    # 输出统计信息
+    print(f"\n=== 处理完成 ===")
+    print(f"成功: {success_count} 条")
+    print(f"失败: {fail_count} 条")
+    print(f"总计: {len(open_positions)} 条")
+
+
+# 使用示例
+if __name__ == "__main__":
+    print("=" * 60)
+    print("K线复原工具")
+    print("=" * 60)
+    
+    reconstruct_kline_from_transactions()
+    
+    print("\n=== 完成 ===")
+

+ 613 - 0
Lib/research/README.md

@@ -0,0 +1,613 @@
+# 期货市场研究算法库
+
+## 概述
+本目录包含期货市场的各种研究算法和分析工具,用于量化分析期货市场的技术指标、价格走势和交易策略。所有代码基于聚宽API开发,适用于聚宽在线研究平台。
+
+## 算法文件结构
+
+### 1. 期货多均线穿越分析 (`future_ma_cross_analysis.py`)
+
+#### 功能描述
+分析期货合约的多均线穿越事件,识别同时穿越至少3条均线的K线,并分析后续收益表现。
+
+#### 主要特点
+- **多均线系统**: 使用MA5、MA10、MA20、MA30四条移动平均线
+- **穿越识别**: 精确判断K线实体上穿或下穿至少3条均线
+- **后续跟踪**: 分析穿越后5日、10日、20日、30日的收益率表现
+- **批量处理**: 自动分析所有期货主力合约
+
+#### 核心算法
+```python
+def check_multi_ma_cross(row):
+    """检查单日K线是否向上或向下穿越了至少3条均线"""
+    # 条件:开盘价在均线一侧,收盘价在均线另一侧
+    # 上穿:开盘价 < 均线 且 收盘价 > 均线
+    # 下穿:开盘价 > 均线 且 收盘价 < 均线
+```
+
+#### 输出结果
+- CSV文件:包含所有穿越事件的详细信息和后续收益率
+- 统计报告:穿越方向分布、收益率统计、品种统计等
+
+### 2. 期货市场整体变化趋势分析 (`future_all_change_trend.py`)
+
+#### 功能描述
+分析期货市场整体的价格变化趋势,计算每日市场平均变化率和正负变化分布。
+
+#### 主要特点
+- **市场全貌**: 分析所有期货品种的价格变化
+- **趋势识别**: 计算滚动平均值识别市场趋势
+- **异常检测**: 使用标准差检测异常市场波动
+- **可视化**: 生成趋势图表展示市场变化规律
+
+#### 核心算法
+```python
+def process_data(df, std_dev_multiplier=3):
+    """处理数据并过滤异常值"""
+    # 计算每日平均变化率
+    # 统计正负变化数量
+    # 计算滚动平均值
+    # 过滤异常值
+```
+
+#### 输出结果
+- 每日市场平均变化率
+- 正负变化品种数量统计
+- 滚动平均值和趋势图表
+- 异常市场行为检测报告
+
+### 3. 期货技术形态规律分析 (`future_pattern_comprehensive.py`)
+
+#### 功能描述
+**核心研究项目** - 研究期货品种在大幅下跌后出现特定K线形态的规律,并分析后续走势表现。这是一个综合性的技术形态分析工具。
+
+#### 主要特点
+- **全自动化**: 从数据获取到结果输出全程自动化
+- **多维度筛选**: 结合回撤、K线形态、时间位置等条件
+- **量化验证**: 通过10周跟踪验证形态有效性
+- **可视化展示**: 自动生成K线图标记技术形态
+- **综合集成**: 单文件包含配置、分析、测试全部功能
+
+#### 文件结构
+```
+第一部分:分析配置参数 (AnalysisConfig类)
+第二部分:技术形态分析器主类 (FuturePatternAnalyzer类)  
+第三部分:测试和演示代码 (PatternAnalysisTests类)
+第四部分:主程序入口 (main函数)
+```
+
+#### 分析流程
+1. **构建期货品种数据**: 创建80+个期货品种的8888主连合约代码
+2. **筛选大幅下跌品种**: 识别最大回撤>30%的期货品种
+3. **获取主力合约映射**: 获取下跌期间的主力合约变化
+4. **获取周线K线数据**: 扩展时间范围的周线OHLCV数据
+5. **识别目标K线形态**: 十字星+长上影线组合形态识别
+6. **分析后续10周走势**: 量化跟踪后续收益率表现
+7. **生成可视化图表**: K线图展示和统计报告
+8. **保存分析结果**: 完整的CSV数据文件输出
+
+#### 技术形态条件
+- **条件A**: 开盘价与收盘价差异 ≤ 1% (十字星特征)
+- **条件B**: 最高价相对开盘价涨幅 ≥ 3% (长上影线特征)
+- **位置条件**: 形态出现在大幅下跌期间或附近
+
+#### 配置参数
+```python
+# 时间设置
+START_DATE = '2022-01-01'  # 建议至少2年数据
+END_DATE = '2024-12-31'
+
+# 筛选条件
+MIN_DRAWDOWN_THRESHOLD = 0.30  # 30%最小回撤
+KLINE_CONDITIONS = {
+    'max_open_close_diff': 0.01,  # 1%开收差异
+    'min_high_open_gain': 0.03,   # 3%最高涨幅
+}
+
+# 分析参数
+FOLLOW_UP_WEEKS = 10           # 后续跟踪10周
+SUCCESS_RETURN_THRESHOLD = 0.05 # 5%成功率阈值
+```
+
+#### 支持的期货品种
+- **大商所(XDCE)**: A、B、C、I、J、JM、L、M、P、PP、V、Y等
+- **郑商所(XZCE)**: AP、CF、FG、MA、RM、SR、TA、ZC等
+- **上期所(XSGE)**: AG、AL、AU、CU、RB、RU、ZN等
+- **中金所(CCFX)**: IC、IF、IH、T、TF、TS
+- **广期所(GFEX)**: SI、LC
+- **上海国际能源(XINE)**: SC、BC、LU、NR
+
+#### 使用方法
+```python
+# 基本使用 - 完整分析
+results = run_pattern_analysis(mode='full')
+
+# 快速测试
+results = run_pattern_analysis(mode='quick')
+
+# 自定义参数测试
+results = run_pattern_analysis(mode='custom')
+
+# 逐步功能测试
+run_pattern_analysis(mode='step')
+
+# 直接使用分析器类
+analyzer = FuturePatternAnalyzer('2022-01-01', '2024-12-31')
+results = analyzer.run_full_analysis()
+```
+
+#### 输出文件
+- `future_major_decline_YYYYMMDD_HHMMSS.csv` - 大幅下跌期货品种
+- `future_kline_patterns_YYYYMMDD_HHMMSS.csv` - 识别的K线形态
+- `future_pattern_performance_YYYYMMDD_HHMMSS.csv` - 后续走势分析
+- `future_pattern_summary_YYYYMMDD_HHMMSS.csv` - 统计汇总
+- `kline_pattern_{品种}_{日期}.png` - K线图表
+
+#### 性能统计示例
+```
+=== 期货技术形态规律分析报告 ===
+总分析期货品种数: 85
+大幅下跌品种数: 12
+识别出的目标形态数: 8
+完成后续走势分析数: 8
+
+=== 后续走势统计分析 ===
+10周内最高收益率统计:
+  平均值: 15.32%
+  正收益比例: 7/8 (87.5%)
+
+=== 交易成功率分析 ===
+成功率: 75.0%
+```
+
+### 4. 期货网格交易研究分析 (`future_grid_trading_analysis.py`)
+
+#### 功能描述
+研究期货网格交易策略在不同配置下的表现,支持多种交易场景的对比分析。该工具实现了完整的网格交易系统,**核心功能包括主力合约自动切换、强制平仓和重新建仓逻辑**。
+
+#### 主要特点
+- **主力合约自动监控**: 每日检测主力合约变化并自动处理切换
+- **强制平仓机制**: 合约切换时立即平掉旧合约所有头寸
+- **智能重新建仓**: 根据价格条件在新合约中重新建立头寸
+- **多策略支持**: 基础头寸、网格交易、网格对冲、组合策略
+- **限价订单模拟**: 精确的网格触发价格模拟
+- **风险控制**: 支持止损百分比设置
+- **完整交易记录**: 记录所有交易包括合约切换引起的强制平仓
+
+#### 主力合约切换逻辑
+
+**1. 主力合约监控与选择**
+```python
+# 合约选择算法
+def select_contracts():
+    """
+    1. 首先获取商品的主导合约
+    2. 如果主导合约在可用列表中,选择它
+    3. 如果主导合约不在列表中,选择未来到期日期最近且晚于主导合约的合约
+    例如:主导合约SA2507.XZCE -> 选择SA2509.XZCE而不是SA2601.XZCE
+    """
+```
+
+**2. 合约切换与强制平仓**
+- 每日自动检测主力合约变化
+- 当主力合约发生变更时,立即平掉旧合约中的所有头寸
+- 即使常规交易退出条件未被触发,这种强制平仓也同样适用
+- 合约切换构成独立于常规交易逻辑的强制退出条件
+
+**3. 智能重新建仓规则**
+
+**基础头寸交易重新建仓**:
+- 计算前一合约中所有基础头寸交易的总交易量
+- 以当日收盘价开立相同总数量的新多头头寸
+- 保持与之前相同的退出条件
+
+**网格交易头寸重新建仓**:
+- 将当前价格与之前的网格开仓价格进行比较
+- 仅在当前价格低于原始网格开仓价格时重新建仓
+- 保持原始退出价格不变
+
+合约切换示例(SA2509→SA2601,之前网格头寸为1250和1200):
+- 若当前价格=1220:开立1个头寸(仅1250网格符合条件),退出价格仍为1300
+- 若当前价格=1180:开立2个头寸(1250和1200网格均符合条件),退出价格分别仍为1300和1250
+- 若当前价格=1130:开立3个头寸(包括未开立的1150网格),退出价格分别为1200、1250和1300
+
+**网格对冲交易头寸重新建仓**:
+- 应用与网格交易相同的逻辑,但条件相反
+- 仅在当前价格高于原始网格开仓价格时重新建仓
+- 保持原始退出价格不变
+
+#### 四种交易策略
+
+**1. 基础头寸交易**
+- 配置:价格-数量网格(如SA: {1400: 10, 1300: 10, ...})
+- 逻辑:在指定价格水平开立多头头寸
+- 退出:统一退出价格,无止损
+
+**2. 网格交易策略**
+- 参数:start_price、grid_size、quantity_per_grid、exit_grid_size
+- 逻辑:从start_price开始,价格下降grid_size时买入quantity_per_grid
+- 退出:价格从入场价格上涨exit_grid_size时退出
+- 示例:1250入场→1350退出;1200入场→1300退出
+
+**3. 网格对冲策略**
+- 增强:在网格交易基础上增加止损百分比(如2%)
+- 入场:grid_price + grid_size(如1300入场)
+- 获利:entry_price - grid_size(如1300→1250获利)
+- 止损:entry_price × (1 + stop_loss_percentage)(如1300×1.02=1326止损)
+
+**4. 组合策略**
+- 同时运行网格交易和网格对冲策略
+- 综合评估两种策略的协同效果
+
+#### 配置参数
+```python
+class GridTradingConfig:
+    # 时间范围
+    START_DATE = datetime.datetime(2025, 6, 1)
+    END_DATE = datetime.datetime(2025, 9, 20)
+    
+    # 核心商品配置
+    CORE_COMMODITIES = {
+        'SA': ['SA2509.XZCE', 'SA2601.XZCE'],  # 纯碱
+        'MA': ['MA2501.XZCE', 'MA2505.XZCE'],  # 甲醇
+        'AU': ['AU2412.XSGE', 'AU2502.XSGE'],  # 黄金
+        'AG': ['AG2412.XSGE', 'AG2502.XSGE'],  # 白银
+        'RU': ['RU2501.XSGE', 'RU2505.XSGE'],  # 橡胶
+    }
+    
+    # 基础头寸网格
+    BASE_POSITION_GRID = {
+        'SA': {1400: 10, 1300: 10, 1200: 10, 1100: 10, 1000: 10, 900: 10},
+        'MA': {2500: 8, 2400: 8, 2300: 8, 2200: 8, 2100: 8, 2000: 8},
+        # ...
+    }
+    
+    # 网格交易配置
+    GRID_TRADING_CONFIG = {
+        'SA': {
+            'start_price': 1250,
+            'grid_size': 50,
+            'quantity_per_grid': 5,
+            'exit_grid_size': 50
+        },
+        # ...
+    }
+    
+    # 网格对冲配置
+    GRID_HEDGE_CONFIG = {
+        'stop_loss_percentage': 0.02,  # 2%止损
+        'enable_hedge_strategy': True
+    }
+```
+
+#### 使用方法
+```python
+# 基本使用
+results = run_grid_trading_analysis()
+
+# 自定义配置
+class CustomConfig(GridTradingConfig):
+    START_DATE = datetime.datetime(2025, 1, 1)
+    END_DATE = datetime.datetime(2025, 12, 31)
+    GRID_HEDGE_CONFIG = {
+        'stop_loss_percentage': 0.015,  # 1.5%止损
+        'enable_hedge_strategy': True
+    }
+
+results = run_grid_trading_analysis(CustomConfig)
+```
+
+#### 输出结果
+- **交易记录CSV**: `grid_trading_records_YYYYMMDD_HHMMSS.csv`
+  - 包含所有四种策略的详细交易记录
+  - 字段:commodity, contract, strategy, entry_date, exit_date, entry_price, exit_price, quantity, profit_loss, profit_loss_pct, days_held, **exit_reason**
+  - exit_reason字段标识退出原因:target_reached(达到目标)、contract_switch(合约切换)、stop_loss(止损)、profit_target(获利了结)
+- **性能统计CSV**: `grid_trading_performance_YYYYMMDD_HHMMSS.csv`
+  - 四种策略的完整性能指标
+  - 字段:total_trades, win_rate, total_profit_loss, avg_profit_loss, max_profit, max_loss, profit_factor等
+
+#### 性能指标
+- **总交易次数**: 各策略的交易笔数
+- **胜率**: 盈利交易占比
+- **总盈亏**: 累计盈亏金额
+- **盈亏比**: 总盈利/总亏损比值
+- **最大盈利/亏损**: 单笔最大盈亏
+- **平均持有天数**: 平均交易持有期
+
+#### 对比分析示例
+```
+=== 四种交易场景对比分析 ===
+策略名称      总交易次数  胜率    总盈亏      平均盈亏    盈亏比
+基础头寸交易      45     73.33%   1250.50     27.79     2.15
+网格交易策略      32     68.75%    890.25     27.82     1.87
+网格对冲策略      28     64.29%    650.75     23.24     1.54
+组合策略          60     66.67%   1541.00     25.68     1.78
+```
+
+### 5. 期货烛台影线形态分析 (`future_candlestick_hatch_analysis.py`)
+
+#### 功能描述
+分析期货合约中带有明显影线的K线,识别具备预测价值的烛台形态,并分析它们对未来价格走势的预测能力。
+
+#### 主要特点
+- **形态识别**: 识别带有明显影线且实体长度符合要求的K线
+- **双向过滤**: 主影线长度必须超过实体1.2倍,相反影线必须小于实体0.5倍
+- **交易信号**: 上影线做空信号,下影线做多信号
+- **未来跟踪**: 分析形态出现后N个交易日的价格表现
+- **主力合约**: 自动识别和使用主力合约数据
+
+#### 核心算法
+```python
+# 形态识别条件
+def check_hatch_pattern(row, body_threshold, hatch_ratio=1.2, opposite_ratio=0.5):
+    """检查K线是否符合影线形态条件"""
+    body_length = abs(close - open)
+    upper_hatch = high - max(open, close)
+    lower_hatch = min(open, close) - low
+    
+    # 条件1: 实体长度 >= 历史平均实体长度
+    # 条件2: 主影线长度 >= 1.2 × 实体长度  
+    # 条件3: 相反影线长度 < 0.5 × 实体长度
+```
+
+#### 交易逻辑
+- **上影线形态**: 做空信号
+  - 收益计算:(收盘价 - 未来最高价) / 收盘价
+  - 预期:价格向影线相反方向(下跌)移动
+- **下影线形态**: 做多信号  
+  - 收益计算:(未来最高价 - 收盘价) / 收盘价
+  - 预期:价格向影线相反方向(上涨)移动
+
+#### 配置参数
+```python
+class CandlestickHatchConfig:
+    ANALYSIS_START_DATE = datetime.datetime(2025, 8, 1)   # 分析开始日期
+    ANALYSIS_END_DATE = datetime.datetime(2025, 8, 20)    # 分析结束日期
+    HISTORICAL_DAYS = 365                                 # 历史数据回溯期
+    FORWARD_DAYS = 15                                     # 前向分析期
+    FUTURE_MONITOR_DAYS = 10                              # 未来表现跟踪期
+    HATCH_TO_BODY_RATIO = 1.2                            # 影线与实体长度比率阈值
+    OPPOSITE_HATCH_RATIO = 0.5                           # 相反影线与实体长度比率阈值
+    MAX_REVENUE_THRESHOLD = 0.02                         # 最大盈利阈值
+    MAX_LOSS_THRESHOLD = 0.005                           # 最大亏损阈值
+    MAX_ANALYSIS_CONTRACTS = 10                          # 最大分析合约数
+    # 交易仓位管理参数
+    INITIAL_TRADE_AMOUNT = 20000                         # 初始交易金额
+    LOSS_RATE = 0.01                                    # 亏损交易的亏损率
+    PROFIT_DRAWDOWN_RATE = 0.02                         # 盈利交易的回撤率
+```
+
+#### 数据结构
+```python
+# 主力合约数据结构 (dict1)
+contract_data = {
+    'L2601.XDCE': [开始日期, 结束日期],
+    'A2505.XDCE': [开始日期, 结束日期]
+}
+
+# 影线形态数据结构 (dict2)
+pattern_data = {
+    'L2601.XDCE': {
+        datetime.date(2025, 8, 5): {
+            'date': '2025-08-05',
+            'high': 6520.0, 'low': 6450.0, 'open': 6480.0, 'close': 6500.0,
+            'body_len': 20.0, 'hatch_dire': 'up', 'hatch_len': 50.0,
+            'max_revenue_date': '2025-08-08', 'max_revenue': 0.0234,
+            'min_revenue_date': '2025-08-06', 'min_revenue': -0.0156,
+            'conclusion': 'true',  # 形态有效性判断
+            'body_direction': 'up',  # 实体方向
+            'direction_consistency': 'false'  # 方向一致性
+        }
+    }
+}
+```
+
+#### 形态有效性判断(增强版)
+系统自动为每个识别的形态计算`conclusion`字段,判断逻辑:
+- **有效 (true)**:满足以下任一条件且无早期过度损失
+  1. 最大盈利日期 < 最大亏损日期 且 最大盈利 > 2%
+  2. 最大盈利日期 > 最大亏损日期 且 最大盈利 > 2% 且 最大亏损 < 0.5%
+- **增强条件**:如果在最大收益日期之前的任何一天,损失超过了最大亏损阈值(0.5%),则标记为无效
+- **无效 (false)**:不满足上述条件或存在早期过度损失
+
+#### 新增分析字段
+- **body_direction**: 烛台实体方向
+  - 'up': 收盘价 > 开盘价(阳线)
+  - 'down': 收盘价 < 开盘价(阴线) 
+  - 'flat': 收盘价 = 开盘价(十字星)
+- **direction_consistency**: 实体与影线方向一致性
+  - 'true': 实体方向与影线方向相反(符合预期)
+  - 'false': 实体方向与影线方向不相反
+- **动态仓位管理**: 基于交易结果的资金管理
+  - 初始交易金额:20,000(可配置)
+  - 亏损交易:按1%亏损率调整资金(可配置)
+  - 盈利交易:按最大利润减去2%回撤率调整资金(可配置)
+- **移动平均线分析**: 形态形成日的技术指标
+  - MA5、MA10、MA20、MA30:5日、10日、20日、30日移动平均线
+  - trend_strength:趋势强度评估(强趋势/中趋势/弱趋势/震荡/数据不足)
+- **hatch/body比率**: 影线与实体长度的比率,用于量化形态强度
+
+#### 使用方法
+```python
+# 基本使用
+results = run_candlestick_hatch_analysis()
+
+# 自定义配置
+class CustomConfig(CandlestickHatchConfig):
+    HATCH_TO_BODY_RATIO = 1.5       # 更严格的影线要求
+    OPPOSITE_HATCH_RATIO = 0.3      # 更严格的相反影线要求
+    MAX_REVENUE_THRESHOLD = 0.03    # 更高的盈利阈值要求
+    MAX_LOSS_THRESHOLD = 0.003      # 更低的亏损容忍度
+    FUTURE_MONITOR_DAYS = 15        # 更长的跟踪期
+
+results = run_candlestick_hatch_analysis(CustomConfig)
+```
+
+#### 输出结果
+- **CSV文件**: `future_candlestick_hatch_analysis_YYYYMMDD_HHMMSS.csv`
+- **包含字段**(按输出顺序): 
+  - contract_code:合约代码
+  - date:形态日期
+  - conclusion:有效性判断(true/false)
+  - trend_strength:趋势强度评估
+  - body_direction:实体方向
+  - direction_consistency:方向一致性
+  - body_len:实体长度
+  - hatch_len:影线长度  
+  - hatch/body:影线与实体长度比率
+  - close:收盘价
+  - open:开盘价
+  - high:最高价
+  - low:最低价
+  - ma5:5日移动平均线
+  - ma10:10日移动平均线
+  - ma20:20日移动平均线
+  - ma30:30日移动平均线
+  - max_revenue:最大收益率
+  - max_revenue_date:最大收益日期
+  - min_revenue:最大亏损率
+  - min_revenue_date:最大亏损日期
+  - profit_loss:单笔盈亏
+  - remaining_amount:剩余资金
+  - trade_amount:交易金额
+- **投资组合汇总**: 整体交易表现统计,包括成功率、总盈亏、总回报率等
+
+#### 统计指标
+- **检测形态总数**:识别出的符合条件的影线形态数量
+- **有效形态数**:conclusion为'true'的形态数量
+- **有效率**:有效形态占总形态的百分比
+
+#### 分析流程
+1. **构建主力合约数据结构**: 获取分析期间的主力合约变化
+2. **收集扩展数据**: 获取历史和未来的OHLC数据
+3. **计算实体长度阈值**: 使用历史数据计算平均实体长度
+4. **识别影线形态**: 筛选符合双重条件的K线
+5. **分析未来表现**: 跟踪形态后N天的价格走势
+6. **计算有效性判断**: 根据收益时序关系评估形态预测能力
+7. **生成分析结果**: 输出包含有效性判断的完整CSV数据文件
+
+## 技术架构
+
+### 数据源
+- **聚宽API**: 使用jqdata获取期货价格数据
+- **数据频率**: 支持日线和周线数据
+- **数据字段**: OHLCV (开高低收量)
+
+### 核心库依赖
+```python
+import pandas as pd      # 数据处理
+import numpy as np       # 数值计算  
+from jqdata import *     # 聚宽API
+import matplotlib.pyplot as plt  # 图表绘制
+```
+
+### 代码规范
+- **函数命名**: 使用下划线分隔的描述性命名
+- **类设计**: 面向对象设计,功能模块化
+- **错误处理**: 完善的异常捕获和日志记录
+- **配置管理**: 集中的参数配置和管理
+- **文档注释**: 详细的函数和类文档字符串
+
+## 使用建议
+
+### 1. 运行环境
+- **推荐平台**: 聚宽在线研究环境
+- **Python版本**: Python 3.6+
+- **内存要求**: 建议至少2GB内存
+- **数据权限**: 需要聚宽期货数据访问权限
+
+### 2. 最佳实践
+- **时间范围**: 建议使用至少2年的历史数据进行分析
+- **品种选择**: 可通过配置文件筛选活跃品种
+- **参数调优**: 根据研究目标调整筛选阈值
+- **结果验证**: 结合基本面分析验证技术分析结果
+
+### 3. 性能优化
+- **批量处理**: 使用并行处理提升大规模数据分析速度
+- **内存管理**: 及时释放不需要的数据对象
+- **缓存策略**: 对重复查询的数据进行缓存
+- **测试模式**: 使用测试模式快速验证算法逻辑
+
+## 更新日志
+
+### v1.6 (2025-09)
+- **重大更新**: 期货网格交易研究分析工具增加主力合约切换功能
+- **核心新功能**:
+  - **主力合约自动监控**:每日检测主力合约变化并自动处理切换
+  - **强制平仓机制**:合约切换时立即平掉旧合约所有头寸
+  - **智能重新建仓**:根据价格条件在新合约中重新建立头寸
+  - **完整交易记录**:记录所有交易包括合约切换引起的强制平仓
+- **增强功能**:
+  - 智能合约选择算法:基于主力合约的智能选择逻辑
+  - 四种交易策略支持:基础头寸、网格交易、网格对冲、组合策略
+  - 精确限价订单模拟:准确的网格触发价格计算
+  - 风险控制机制:支持止损百分比设置
+  - 性能对比分析:四种策略并排性能统计
+- **重新建仓规则**:
+  - 基础头寸:以收盘价重建相同总量的多头头寸
+  - 网格交易:仅在当前价格低于原始网格开仓价格时重新建仓
+  - 网格对冲:仅在当前价格高于原始网格开仓价格时重新建仓
+- **输出增强**: 交易记录增加exit_reason字段,标识退出原因(target_reached/contract_switch/stop_loss/profit_target)
+
+### v1.5 (2025-09)
+- **增强**: 期货烛台影线形态分析算法重大更新
+- **新增功能**:
+  - 动态仓位管理:基于交易结果的资金调整机制
+  - 移动平均线分析:增加MA5/MA10/MA20/MA30技术指标
+  - 趋势强度评估:6项比较的量化趋势分析
+  - 增强结论逻辑:早期过度损失检测机制
+- **字段优化**: 
+  - 移除重复的hatch_dire列
+  - 新增hatch/body比率列
+  - 按指定顺序重排CSV输出字段
+- **投资组合管理**: 完整的交易结果汇总和表现分析
+
+### v1.4 (2025-09)
+- **新增**: 期货烛台影线形态分析算法 (`future_candlestick_hatch_analysis.py`)
+- **算法特点**: 双向过滤条件、主力合约自动识别、未来表现跟踪
+- **配置优化**: 新增相反影线比率阈值等可配置参数
+- **文档完善**: 更新README文档包含新算法说明
+
+### v1.3 (2025-09)
+- **重构**: 将期货技术形态分析整合为单一综合文件
+- **优化**: 改进代码结构,增强在线平台适配性
+- **新增**: 添加多种测试模式和配置选项
+- **完善**: 统一的README文档和算法说明
+
+### v1.2 (2025-06)
+- **新增**: 期货技术形态规律分析算法
+- **扩展**: 支持更多期货品种和交易所
+- **优化**: 改进可视化效果和报告格式
+
+### v1.1 (2024-12)
+- **新增**: 多均线穿越分析功能
+- **优化**: 改进数据处理效率
+- **修复**: 修复部分品种数据获取问题
+
+### v1.0 (2024-06)
+- **初始版本**: 基础的期货市场趋势分析
+- **核心功能**: 市场整体变化趋势分析
+
+## 研究成果
+
+### 学术价值
+- 提供了期货市场技术分析的量化研究框架
+- 验证了特定K线形态在期货市场的有效性
+- 建立了系统性的期货品种分析方法论
+
+### 实用价值
+- 为量化交易策略提供技术分析工具
+- 辅助投资决策和风险管理
+- 支持期货市场的深度研究和分析
+
+### 未来发展
+- 集成机器学习模型提升预测准确性
+- 扩展更多技术指标和形态识别
+- 建立实时分析和预警系统
+- 开发策略回测和评估框架
+
+---
+**维护团队**: jukuan研究团队  
+**最后更新**: 2025年9月  
+**技术支持**: 基于聚宽平台开发,需要相应API权限  
+**使用许可**: 仅供研究和学习使用

+ 1481 - 0
Lib/research/future_candlestick_hatch_analysis.py

@@ -0,0 +1,1481 @@
+"""
+期货烛台影线形态分析
+研究期货合约中带有明显影线的K线对未来价格走势的预测能力
+
+本程序实现完整的分析流程:
+1. 数据结构设置 - 获取主力期货合约数据
+2. 数据收集 - 扩展时间范围获取OHLC数据
+3. 实体长度计算 - 计算历史平均实体长度作为阈值
+4. 影线形态识别 - 识别满足条件的K线形态
+5. 未来表现分析 - 分析影线方向与价格走势的关系
+6. 结果输出 - 生成影线形态分析结果CSV
+
+注:程序使用动态获取的主力合约进行分析,确保分析结果基于真实的交易活动
+
+作者: jukuan研究团队
+日期: 2025-09
+适用平台: 聚宽在线研究平台
+"""
+
+import pandas as pd
+import numpy as np
+from jqdata import *
+import datetime
+import warnings
+warnings.filterwarnings('ignore')
+
+# =====================================================================================
+# 分析配置参数 - 集中配置部分
+# =====================================================================================
+
+class CandlestickHatchConfig:
+    """期货烛台影线形态分析配置参数"""
+    
+    # ==================== 时间范围设置 ====================
+    ANALYSIS_START_DATE = datetime.datetime(2025, 8, 1)  # 分析开始日期
+    ANALYSIS_END_DATE = datetime.datetime(2025, 8, 20)   # 分析结束日期
+    
+    # ==================== 时间扩展参数 ====================
+    HISTORICAL_DAYS = 365      # 历史数据回溯期:优势期前天数
+    FORWARD_DAYS = 15          # 正向分析期:优势期后天数
+    FUTURE_MONITOR_DAYS = 15   # 未来表现跟踪期:交易日数
+    
+    # ==================== 影线形态参数 ====================
+    HATCH_TO_BODY_RATIO = 1.2  # 影线与实体长度比率阈值
+    OPPOSITE_HATCH_RATIO = 0.5  # 相反方向影线与实体长度比率阈值
+    
+    # ==================== 形态验证阈值 ====================
+    MAX_REVENUE_THRESHOLD = 0.02   # 最大盈利阈值
+    MAX_LOSS_THRESHOLD = 0.01     # 最大亏损阈值
+    
+    # ==================== 分析规模控制 ====================
+    MAX_ANALYSIS_CONTRACTS = -1  # 最大分析合约数量限制
+    
+    # ==================== 输出设置 ====================
+    OUTPUT_ENCODING = 'utf-8-sig'  # 输出文件编码格式
+    VERBOSE_LOGGING = True          # 是否打印详细日志
+    
+    # ==================== 交易仓位管理 ====================
+    INITIAL_TRADE_AMOUNT = 20000    # 单笔交易初始金额
+    LOSS_RATE = 0.01               # 亏损交易的亏损率(可配置)
+    PROFIT_DRAWDOWN_RATE = 0.02    # 盈利交易的回撤率(可配置)
+    
+    @classmethod
+    def print_config(cls):
+        """打印当前配置信息"""
+        print("=== 期货烛台影线形态分析配置 ===")
+        print(f"分析时间范围: {cls.ANALYSIS_START_DATE.strftime('%Y-%m-%d')} 至 {cls.ANALYSIS_END_DATE.strftime('%Y-%m-%d')}")
+        print(f"历史数据回溯期: {cls.HISTORICAL_DAYS}天")
+        print(f"前向分析期: {cls.FORWARD_DAYS}天")
+        print(f"未来表现跟踪期: {cls.FUTURE_MONITOR_DAYS}个交易日")
+        print(f"影线与实体长度比率阈值: {cls.HATCH_TO_BODY_RATIO}")
+        print(f"相反方向影线比率阈值: {cls.OPPOSITE_HATCH_RATIO}")
+        print(f"最大盈利阈值: {cls.MAX_REVENUE_THRESHOLD}")
+        print(f"最大亏损阈值: {cls.MAX_LOSS_THRESHOLD}")
+        print(f"最大分析合约数: {cls.MAX_ANALYSIS_CONTRACTS}")
+        print(f"合约类型: 主力合约")
+        print(f"详细日志: {'开启' if cls.VERBOSE_LOGGING else '关闭'}")
+        print(f"初始交易金额: {cls.INITIAL_TRADE_AMOUNT}")
+        print(f"亏损交易亏损率: {cls.LOSS_RATE}")
+        print(f"盈利交易回撤率: {cls.PROFIT_DRAWDOWN_RATE}")
+        print("=" * 50)
+
+class FutureCandlestickHatchAnalyzer:
+    """期货烛台影线形态分析器"""
+    
+    def __init__(self, config=None):
+        """初始化分析器"""
+        # 使用配置类参数
+        if config is None:
+            config = CandlestickHatchConfig
+        
+        self.config = config
+        self.historical_days = config.HISTORICAL_DAYS
+        self.forward_days = config.FORWARD_DAYS  
+        self.future_monitor_days = config.FUTURE_MONITOR_DAYS
+        self.hatch_to_body_ratio = config.HATCH_TO_BODY_RATIO
+        self.opposite_hatch_ratio = config.OPPOSITE_HATCH_RATIO
+        self.max_revenue_threshold = config.MAX_REVENUE_THRESHOLD
+        self.max_loss_threshold = config.MAX_LOSS_THRESHOLD
+        self.analysis_start_date = config.ANALYSIS_START_DATE
+        self.analysis_end_date = config.ANALYSIS_END_DATE
+        self.max_analysis_contracts = config.MAX_ANALYSIS_CONTRACTS
+        self.output_encoding = config.OUTPUT_ENCODING
+        self.verbose_logging = config.VERBOSE_LOGGING
+        
+        # 交易仓位管理参数
+        self.initial_trade_amount = config.INITIAL_TRADE_AMOUNT
+        self.loss_rate = config.LOSS_RATE
+        self.profit_drawdown_rate = config.PROFIT_DRAWDOWN_RATE
+        
+        # 期货品种与交易所的映射关系
+        self.exchange_map = {
+            # 大连商品交易所 (XDCE)
+            'A': 'XDCE', 'B': 'XDCE', 'C': 'XDCE', 'CS': 'XDCE', 
+            'I': 'XDCE', 'J': 'XDCE', 'JD': 'XDCE', 'JM': 'XDCE', 'L': 'XDCE',
+            'M': 'XDCE', 'P': 'XDCE', 'PP': 'XDCE', 'PG': 'XDCE', 'RR': 'XDCE',
+            'V': 'XDCE', 'Y': 'XDCE', 'EB': 'XDCE', 'EG': 'XDCE', 'LH': 'XDCE',
+            
+            # 郑州商品交易所 (XZCE)
+            'AP': 'XZCE', 'CF': 'XZCE', 'CY': 'XZCE', 'FG': 'XZCE', 'JR': 'XZCE',
+            'LR': 'XZCE', 'MA': 'XZCE', 'OI': 'XZCE', 'PM': 'XZCE', 'RI': 'XZCE',
+            'RM': 'XZCE', 'RS': 'XZCE', 'SF': 'XZCE', 'SM': 'XZCE', 'SR': 'XZCE',
+            'TA': 'XZCE', 'WH': 'XZCE', 'ZC': 'XZCE', 'CJ': 'XZCE', 
+            'ME': 'XZCE', 'PF': 'XZCE', 'PK': 'XZCE', 'RO': 'XZCE', 'SA': 'XZCE',
+            'TC': 'XZCE', 'UR': 'XZCE', 'WS': 'XZCE', 'WT': 'XZCE',
+            
+            # 上海期货交易所 (XSGE)
+            'AG': 'XSGE', 'AL': 'XSGE', 'AU': 'XSGE', 'BU': 'XSGE', 'CU': 'XSGE',
+            'FU': 'XSGE', 'HC': 'XSGE', 'NI': 'XSGE', 'PB': 'XSGE', 'RB': 'XSGE',
+            'RU': 'XSGE', 'SN': 'XSGE', 'SP': 'XSGE', 'SS': 'XSGE', 'WR': 'XSGE',
+            'ZN': 'XSGE',
+            
+            # 上海国际能源交易中心 (XINE)
+            'BC': 'XINE', 'LU': 'XINE', 'NR': 'XINE', 'SC': 'XINE',
+            
+            # 中金所 (CCFX)
+            'IC': 'CCFX', 'IF': 'CCFX', 'IH': 'CCFX', 'T': 'CCFX', 'TF': 'CCFX', 'TS': 'CCFX',
+            
+            # 广期所 (GFEX)
+            'SI': 'GFEX', 'LC': 'GFEX', 'PS': 'GFEX'
+        }
+        
+        # 存储结果的字典
+        self.contract_data = {}  # 主合约数据结构 - dict1
+        self.hatch_patterns = {}  # 影线形态数据 - dict2
+        
+        # 投资组合管理
+        self.portfolio_results = []  # 存储所有交易结果
+        
+        if self.verbose_logging:
+            print("初始化期货烛台影线形态分析器")
+            print(f"配置参数 - 历史数据回溯期: {self.historical_days}天, 前向分析期: {self.forward_days}天")
+            print(f"影线与实体长度比率阈值: {self.hatch_to_body_ratio}, 相反影线比率阈值: {self.opposite_hatch_ratio}")
+            print(f"最大盈利阈值: {self.max_revenue_threshold}, 最大亏损阈值: {self.max_loss_threshold}")
+            print(f"监控窗口: {self.future_monitor_days}个交易日")
+            print(f"最大分析合约数: {self.max_analysis_contracts}")
+            print(f"合约类型: 主力合约")
+    
+    def _is_trading_day(self, check_date):
+        """
+        检查是否为交易日
+        
+        参数:
+            check_date (date): 要检查的日期
+        
+        返回:
+            bool: 是否为交易日
+        """
+        try:
+            # 使用聚宽API检查交易日
+            trade_days = get_trade_days(end_date=check_date, count=1)
+            if len(trade_days) > 0:
+                return trade_days[0].date() == check_date
+            return False
+        except:
+            # 如果API调用失败,简单判断是否为工作日
+            return check_date.weekday() < 5
+    
+    def _get_symbol_dominant_history(self, symbol, start_date, end_date):
+        """
+        获取单个标的的主力合约历史变化
+        
+        参数:
+            symbol (str): 标的编码
+            start_date (date): 开始日期
+            end_date (date): 结束日期
+        
+        返回:
+            list: 主力合约记录列表
+        """
+        results = []
+        current_date = start_date
+        current_dominant = None
+        period_start = None
+        
+        # if self.verbose_logging:
+        #     print(f"  获取 {symbol} 主力合约历史...")
+        
+        # 按日遍历时间范围
+        while current_date <= end_date:
+            
+            # 跳过非交易日
+            if not self._is_trading_day(current_date):
+                current_date += datetime.timedelta(days=1)
+                continue
+            
+            try:
+                # 获取当日主力合约
+                dominant_contract = get_dominant_future(symbol, current_date)
+                
+                if dominant_contract is None:
+                    current_date += datetime.timedelta(days=1)
+                    continue
+                    
+                # 如果是新的主力合约
+                if dominant_contract != current_dominant:
+                    
+                    # 如果不是第一个合约,结束上一个合约的记录
+                    if current_dominant is not None and period_start is not None:
+                        results.append({
+                            'symbol': symbol,
+                            'dominant_contract': current_dominant,
+                            'start_date': period_start,
+                            'end_date': current_date - datetime.timedelta(days=1)
+                        })
+                    
+                    # 开始新合约的记录
+                    current_dominant = dominant_contract
+                    period_start = current_date
+                    
+                    # if self.verbose_logging:
+                    #     print(f"    {current_date}: 主力合约变更为 {dominant_contract}")
+                
+            except Exception as e:
+                if self.verbose_logging:
+                    print(f"    获取 {current_date} 的主力合约时出错: {str(e)}")
+            
+            current_date += datetime.timedelta(days=1)
+        
+        # 处理最后一个合约
+        if current_dominant is not None and period_start is not None:
+            results.append({
+                'symbol': symbol,
+                'dominant_contract': current_dominant,
+                'start_date': period_start,
+                'end_date': end_date
+            })
+        
+        return results
+    
+    def get_dominant_contracts_history(self, symbol_list, start_date, end_date):
+        """
+        获取指定时间范围内多个标的的主力合约历史变化
+        
+        参数:
+            symbol_list (list): 标的编码列表
+            start_date (date): 开始日期
+            end_date (date): 结束日期
+        
+        返回:
+            pandas.DataFrame: 包含主力合约变化历史的数据框
+        """
+        # if self.verbose_logging:
+        #     print(f"获取主力合约数据...")
+        #     print(f"标的列表: {symbol_list}")
+        #     print(f"时间范围: {start_date} 至 {end_date}")
+        
+        # 存储所有结果的列表
+        all_results = []
+        
+        # 逐个处理每个标的
+        for symbol in symbol_list:
+            # if self.verbose_logging:
+            #     print(f"\n处理标的: {symbol}")
+            
+            try:
+                # 获取该标的在指定时间范围内的主力合约历史
+                symbol_results = self._get_symbol_dominant_history(symbol, start_date, end_date)
+                all_results.extend(symbol_results)
+                
+            except Exception as e:
+                if self.verbose_logging:
+                    print(f"处理标的 {symbol} 时发生错误: {str(e)}")
+                continue
+        
+        # 将结果转换为DataFrame
+        if all_results:
+            result_df = pd.DataFrame(all_results)
+            result_df = result_df.sort_values(['symbol', 'start_date']).reset_index(drop=True)
+            
+            if self.verbose_logging:
+                print(f"\n成功获取 {len(result_df)} 条主力合约记录")
+            return result_df
+        else:
+            if self.verbose_logging:
+                print("\n未获取到任何主力合约数据")
+            return pd.DataFrame(columns=['symbol', 'dominant_contract', 'start_date', 'end_date'])
+
+    def build_contract_structure(self):
+        """
+        构建期货主力合约数据结构 (dict1)
+        返回结构:Key=完整合约代码, Value=[start_date, end_date]
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤1: 构建期货主力合约数据结构 ===")
+        
+        # 分析时间范围
+        analysis_start = self.analysis_start_date
+        analysis_end = self.analysis_end_date
+        
+        if self.verbose_logging:
+            print(f"分析时间范围: {analysis_start.strftime('%Y-%m-%d')} 至 {analysis_end.strftime('%Y-%m-%d')}")
+            print(f"合约类型: 主力合约")
+        
+        # 构建主力合约数据结构
+        contract_dict = self._build_dominant_contract_structure(analysis_start, analysis_end)
+        
+        self.contract_data = contract_dict
+        if self.verbose_logging:
+            print(f"构建完成,共{len(contract_dict)}个期货合约")
+        return contract_dict
+    
+    def _build_dominant_contract_structure(self, analysis_start, analysis_end):
+        """构建基于主力合约的数据结构"""
+        if self.verbose_logging:
+            print("使用主力合约获取逻辑...")
+        
+        # 获取要分析的期货品种列表
+        if self.max_analysis_contracts > 0:
+            symbol_list = list(self.exchange_map.keys())[:self.max_analysis_contracts]
+        else:
+            symbol_list = list(self.exchange_map.keys())
+        
+        # 获取主力合约历史数据
+        dominant_df = self.get_dominant_contracts_history(
+            symbol_list, 
+            analysis_start.date(), 
+            analysis_end.date()
+        )
+        
+        contract_dict = {}
+        
+        if not dominant_df.empty:
+            # 将DataFrame转换为字典结构
+            for _, row in dominant_df.iterrows():
+                contract_code = row['dominant_contract']
+                start_date = row['start_date'] if isinstance(row['start_date'], datetime.date) else row['start_date'].date()
+                end_date = row['end_date'] if isinstance(row['end_date'], datetime.date) else row['end_date'].date()
+                
+                contract_dict[contract_code] = [start_date, end_date]
+                
+                # if self.verbose_logging:
+                #     print(f"  添加主力合约: {contract_code} ({start_date} 至 {end_date})")
+        
+        return contract_dict
+    
+    def collect_extended_data(self):
+        """
+        数据收集:为每个合约收集扩展时间范围的数据
+        扩展期限:从(start_date-365天)到(end_date+15天)
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤2: 收集扩展时间范围的数据 ===")
+        
+        extended_data = {}
+        
+        for contract_code, period in self.contract_data.items():
+            start_date, end_date = period
+            
+            # 计算扩展期限
+            start_date_2 = start_date - datetime.timedelta(days=self.historical_days)
+            end_date_2 = end_date + datetime.timedelta(days=self.forward_days)
+            
+            # if self.verbose_logging:
+            #     print(f"获取 {contract_code} 数据")
+            #     print(f"  原始期间: {start_date} 至 {end_date}")
+            #     print(f"  扩展期间: {start_date_2} 至 {end_date_2}")
+            
+            try:
+                # 获取日线数据
+                data = get_price(
+                    contract_code,
+                    start_date=start_date_2,
+                    end_date=end_date_2,
+                    frequency='daily',
+                    fields=['open', 'close', 'high', 'low', 'volume'],
+                    skip_paused=False,
+                    panel=False
+                )
+                
+                if data is not None and len(data) > 0:
+                    extended_data[contract_code] = {
+                        'data': data,
+                        'original_start': start_date,
+                        'original_end': end_date,
+                        'extended_start': start_date_2,
+                        'extended_end': end_date_2
+                    }
+                    
+                #     if self.verbose_logging:
+                #         print(f"  成功获取{len(data)}条数据记录")
+                # else:
+                #     if self.verbose_logging:
+                #         print(f"  未获取到数据")
+                    
+            except Exception as e:
+                if self.verbose_logging:
+                    print(f"  获取数据失败: {str(e)}")
+                continue
+        
+        if self.verbose_logging:
+            print(f"数据收集完成,共{len(extended_data)}个合约有效数据")
+        return extended_data
+    
+    def calculate_body_thresholds(self, extended_data):
+        """
+        计算实体长度阈值:使用历史数据计算平均实体长度
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤3: 计算实体长度阈值 ===")
+        
+        threshold_data = {}
+        
+        for contract_code, contract_info in extended_data.items():
+            data = contract_info['data']
+            start_date = contract_info['original_start']
+            start_date_2 = contract_info['extended_start']
+            
+            # if self.verbose_logging:
+            #     print(f"计算 {contract_code} 的实体长度阈值")
+            #     print(f"  历史期间: {start_date_2} 至 {start_date}")
+            
+            # 筛选历史数据:date >= start_date_2 且 date < start_date
+            historical_mask = (data.index.date >= start_date_2) & (data.index.date < start_date)
+            historical_data = data[historical_mask]
+            
+            if len(historical_data) == 0:
+                if self.verbose_logging:
+                    print(f"  历史数据无效,跳过")
+                continue
+            
+            # 计算实体长度
+            historical_data = historical_data.copy()
+            historical_data['body_length'] = abs(historical_data['close'] - historical_data['open'])
+            
+            # 计算平均实体长度作为阈值
+            body_threshold = historical_data['body_length'].mean()
+            
+            threshold_data[contract_code] = {
+                'contract_info': contract_info,
+                'body_threshold': body_threshold,
+                'historical_days': len(historical_data)
+            }
+            
+            # if self.verbose_logging:
+            #     print(f"  实体长度阈值: {body_threshold:.4f} (基于{len(historical_data)}个历史交易日)")
+        
+        if self.verbose_logging:
+            print("实体长度阈值计算完成")
+        return threshold_data
+    
+    def identify_hatch_patterns(self, threshold_data):
+        """
+        影线形态识别:扫描符合条件的K线
+        条件:实体长度 >= 阈值 且 影线长度 >= 1.2 × 实体长度
+        """
+        if self.verbose_logging:
+            print(f"\n=== 步骤4: 识别影线形态 ===")
+        
+        pattern_dict = {}  # dict2结构
+        
+        for contract_code, threshold_info in threshold_data.items():
+            contract_info = threshold_info['contract_info']
+            data = contract_info['data']
+            body_threshold = threshold_info['body_threshold']
+            start_date = contract_info['original_start']  # 使用原始开始日期
+            end_date = contract_info['original_end']
+            
+            # if self.verbose_logging:
+            #     print(f"分析 {contract_code} 的影线形态")
+            #     print(f"  扫描期间: {start_date} 至 {end_date}")
+            #     print(f"  实体长度阈值: {body_threshold:.4f}")
+            
+            # 筛选扫描数据:从start_date到end_date(含)
+            scan_mask = (data.index.date >= start_date) & (data.index.date <= end_date)
+            scan_data = data[scan_mask]
+            
+            contract_patterns = {}
+            pattern_count = 0
+            
+            for idx, (signal_date, row) in enumerate(scan_data.iterrows()):
+                # 计算K线各项数值
+                open_price = row['open']
+                close_price = row['close']
+                high_price = row['high']
+                low_price = row['low']
+                
+                # 计算实体长度
+                body_length = abs(close_price - open_price)
+                
+                # 检查实体长度是否满足阈值
+                if body_length < body_threshold:
+                    continue
+                
+                # 计算影线长度
+                upper_hatch = high_price - max(open_price, close_price)
+                lower_hatch = min(open_price, close_price) - low_price
+                
+                # 检查影线条件
+                hatch_direction = None
+                hatch_length = 0
+                
+                # 检查上影线条件:上影线长度符合要求 且 下影线长度小于实体长度的一半
+                if (upper_hatch >= self.hatch_to_body_ratio * body_length and 
+                    lower_hatch < self.opposite_hatch_ratio * body_length):
+                    hatch_direction = 'up'
+                    hatch_length = upper_hatch
+                # 检查下影线条件:下影线长度符合要求 且 上影线长度小于实体长度的一半
+                elif (lower_hatch >= self.hatch_to_body_ratio * body_length and 
+                      upper_hatch < self.opposite_hatch_ratio * body_length):
+                    hatch_direction = 'down'
+                    hatch_length = lower_hatch
+                
+                # 如果满足影线条件,记录形态
+                if hatch_direction is not None:
+                    pattern_count += 1
+                    
+                    # dict3结构
+                    pattern_info = {
+                        'date': signal_date.strftime('%Y-%m-%d'),
+                        'high': float(high_price),
+                        'low': float(low_price),
+                        'open': float(open_price),
+                        'close': float(close_price),  # 标准收盘价
+                        'body_len': float(body_length),
+                        'hatch_dire': hatch_direction,
+                        'hatch_len': float(hatch_length)
+                    }
+                    
+                    contract_patterns[signal_date] = pattern_info
+                    
+                    # if self.verbose_logging:
+                    #     opposite_hatch_len = lower_hatch if hatch_direction == 'up' else upper_hatch
+                        # print(f"    发现形态: {signal_date.strftime('%Y-%m-%d')} {hatch_direction}影线 实体:{body_length:.4f} 影线:{hatch_length:.4f} 相反影线:{opposite_hatch_len:.4f}")
+            
+            if contract_patterns:
+                pattern_dict[contract_code] = contract_patterns
+            #     if self.verbose_logging:
+            #         print(f"  检测到{pattern_count}个有效影线形态")
+            # else:
+            #     if self.verbose_logging:
+            #         print(f"  未检测到有效影线形态")
+        
+        # if self.verbose_logging:
+        #     total_patterns = sum(len(patterns) for patterns in pattern_dict.values())
+        #     print(f"\n影线形态识别完成,共{total_patterns}个形态")
+        
+        self.hatch_patterns = pattern_dict
+        return pattern_dict
+    
+    def analyze_future_performance(self, threshold_data, pattern_dict):
+        """
+        未来表现分析:分析影线形态对未来价格走势的预测能力
+        交易方向:上影线做空,下影线做多
+        """
+        if self.verbose_logging:
+            print(f"\n=== 步骤5: 分析未来表现 ===")
+        
+        enhanced_pattern_dict = {}
+        
+        for contract_code, contract_patterns in pattern_dict.items():
+            # 获取对应的数据信息
+            contract_info = threshold_data[contract_code]['contract_info']
+            data = contract_info['data']
+            end_date_2 = contract_info['extended_end']
+            
+            if self.verbose_logging:
+                print(f"\n分析 {contract_code} 的未来表现")
+                print(f"  监控窗口: {self.future_monitor_days}个交易日")
+            
+            enhanced_patterns = {}
+            
+            for signal_date, pattern_info in contract_patterns.items():
+                hatch_direction = pattern_info['hatch_dire']
+                standard_close = pattern_info['close']  # 标准收盘价
+                print(f"形态日期:{signal_date.strftime('%Y-%m-%d')}, 标准收盘价: {standard_close}")
+                
+                # 在数据中找到信号日期的位置
+                signal_idx = None
+                for idx, (date, row) in enumerate(data.iterrows()):
+                    if date.date() == signal_date.date():
+                        signal_idx = idx
+                        break
+                
+                if signal_idx is None:
+                    if self.verbose_logging:
+                        print(f"    未找到信号日期 {signal_date.strftime('%Y-%m-%d')} 在数据中")
+                    continue
+                
+                # 分析未来N天的表现
+                future_analysis = self._analyze_future_n_days(
+                    data, signal_idx, hatch_direction, standard_close, 
+                    self.future_monitor_days, end_date_2
+                )
+                
+                # 计算最大收益和最大风险(原逻辑:与影线方向相反交易)
+                max_revenue_info, min_revenue_info = self._calculate_max_revenue_risk(future_analysis, hatch_direction, standard_close)
+                
+                # 计算最大收益和最大风险(新逻辑:与影线方向相同交易)
+                max_revenue_info_2, min_revenue_info_2 = self._calculate_max_revenue_risk_2(future_analysis, hatch_direction, standard_close)
+                
+                # if self.verbose_logging:
+                #     print(f"      原逻辑最大收益: {max_revenue_info['revenue']:.4f} ({max_revenue_info['date']})")
+                #     print(f"      原逻辑最大亏损: {min_revenue_info['revenue']:.4f} ({min_revenue_info['date']})")
+                #     print(f"      新逻辑最大收益: {max_revenue_info_2['revenue']:.4f} ({max_revenue_info_2['date']})")
+                #     print(f"      新逻辑最大亏损: {min_revenue_info_2['revenue']:.4f} ({min_revenue_info_2['date']})")
+
+                # 计算conclusion字段(增强版,包含日损失检查)
+                conclusion = self._calculate_conclusion(max_revenue_info, min_revenue_info, future_analysis, hatch_direction, standard_close)
+                
+                # 计算conclusion2字段(新逻辑:与影线方向相同交易)
+                conclusion_2 = self._calculate_conclusion_2(max_revenue_info_2, min_revenue_info_2, future_analysis, hatch_direction, standard_close)
+                
+                # 计算新增字段
+                body_direction, direction_consistency = self._calculate_direction_fields(pattern_info, hatch_direction)
+                
+                # 计算影线与实体长度比率
+                hatch_to_body_ratio = pattern_info['hatch_len'] / pattern_info['body_len'] if pattern_info['body_len'] > 0 else 0
+                
+                # 计算移动平均线
+                ma_values = self._calculate_moving_averages(data, signal_idx)
+                
+                # 计算基于收盘价与5K移动平均线关系的价格方向
+                price_direction = self._calculate_price_direction(standard_close, ma_values['ma5'])
+                
+                # 计算趋势强度
+                trend_strength = self._calculate_trend_strength(ma_values)
+                
+                # 动态仓位计算
+                remaining_amount = self._calculate_dynamic_position(
+                    conclusion, max_revenue_info['revenue'], self.initial_trade_amount
+                )
+                
+                # 记录到投资组合(包含两种交易逻辑)
+                self._add_portfolio_trade(
+                    contract_code, 
+                    signal_date.strftime('%Y-%m-%d'),
+                    conclusion,
+                    conclusion_2,
+                    max_revenue_info['revenue'],
+                    max_revenue_info_2['revenue'],
+                    self.initial_trade_amount,
+                    remaining_amount
+                )
+                
+                # 增强pattern_info
+                enhanced_pattern = pattern_info.copy()
+                enhanced_pattern.update({
+                    'max_revenue_date': max_revenue_info['date'],
+                    'max_revenue': max_revenue_info['revenue'],
+                    'min_revenue_date': min_revenue_info['date'],
+                    'min_revenue': min_revenue_info['revenue'],
+                    'max_revenue_date2': max_revenue_info_2['date'],
+                    'max_revenue2': max_revenue_info_2['revenue'],
+                    'min_revenue_date2': min_revenue_info_2['date'],
+                    'min_revenue2': min_revenue_info_2['revenue'],
+                    'conclusion': conclusion,
+                    'conclusion2': conclusion_2,
+                    'body_direction': body_direction,
+                    'direction_consistency': direction_consistency,
+                    'price_direction': price_direction,  # 新增:基于收盘价与5K移动平均线关系的价格方向
+                    'trade_amount': float(self.initial_trade_amount),
+                    'remaining_amount': float(remaining_amount),
+                    'profit_loss': float(remaining_amount - self.initial_trade_amount),
+                    'hatch_body_ratio': float(hatch_to_body_ratio),
+                    'ma5': ma_values['ma5'],
+                    'ma10': ma_values['ma10'],
+                    'ma20': ma_values['ma20'],
+                    'ma30': ma_values['ma30'],
+                    'trend_strength': trend_strength
+                })
+                
+                enhanced_patterns[signal_date] = enhanced_pattern
+                
+                if self.verbose_logging:
+                    print(f"    {signal_date.strftime('%Y-%m-%d')} {hatch_direction}影线:")
+                    print(f"      原逻辑 - 最大收益: {max_revenue_info['revenue']:.4f} ({max_revenue_info['date']})")
+                    print(f"      原逻辑 - 最大亏损: {min_revenue_info['revenue']:.4f} ({min_revenue_info['date']})")
+                    print(f"      原逻辑 - 结论: {conclusion}")
+                    print(f"      新逻辑 - 最大收益: {max_revenue_info_2['revenue']:.4f} ({max_revenue_info_2['date']})")
+                    print(f"      新逻辑 - 最大亏损: {min_revenue_info_2['revenue']:.4f} ({min_revenue_info_2['date']})")
+                    print(f"      新逻辑 - 结论: {conclusion_2}")
+                    print(f"      实体方向: {body_direction}")
+                    print(f"      方向一致性: {direction_consistency}")
+                    print(f"      价格方向: {price_direction} (收盘价vs5K线)")
+            
+            if enhanced_patterns:
+                enhanced_pattern_dict[contract_code] = enhanced_patterns
+                if self.verbose_logging:
+                    print(f"  完成{len(enhanced_patterns)}个形态的未来表现分析")
+        
+        if self.verbose_logging:
+            total_patterns = sum(len(patterns) for patterns in enhanced_pattern_dict.values())
+            print(f"\n未来表现分析完成,共{total_patterns}个形态")
+        
+        return enhanced_pattern_dict
+    
+    def _analyze_future_n_days(self, data, signal_idx, hatch_direction, standard_close, monitor_days, end_date_2):
+        """分析信号后未来N天的价格表现"""
+        future_prices = []
+        data_list = list(data.iterrows())
+        
+        for i in range(1, monitor_days + 1):
+            future_idx = signal_idx + i
+            if future_idx < len(data_list):
+                future_date, future_row = data_list[future_idx]
+                
+                # 检查是否超出扩展结束日期
+                if future_date.date() > end_date_2:
+                    break
+                
+                future_prices.append({
+                    'date': future_date.strftime('%Y-%m-%d'),
+                    'high': float(future_row['high']),
+                    'low': float(future_row['low'])
+                })
+            else:
+                break
+        
+        return future_prices
+    
+    def _calculate_max_revenue_risk(self, future_analysis, hatch_direction, standard_close):
+        """计算最大收益和最大风险(原逻辑:与影线方向相反交易)"""
+        if not future_analysis:
+            return {'date': None, 'revenue': 0.0}, {'date': None, 'revenue': 0.0}
+        
+        max_revenue = float('-inf')
+        min_revenue = float('inf')
+        max_revenue_date = None
+        min_revenue_date = None
+        
+        for day_data in future_analysis:
+            date = day_data['date']
+            high = day_data['high']
+            low = day_data['low']
+            
+            if hatch_direction == 'up':
+                # 上影线做空:收益 = (标准收盘价 - 最低价) / 标准收盘价,风险 = (标准收盘价 - 最高价) / 标准收盘价
+                revenue = (standard_close - low) / standard_close
+                risk = (standard_close - high) / standard_close
+            else:
+                # 下影线做多:收益 = (最高价 - 标准收盘价) / 标准收盘价,风险 = (最低价 - 标准收盘价) / 标准收盘价
+                revenue = (high - standard_close) / standard_close
+                risk = (low - standard_close) / standard_close
+            
+            # 更新最大收益
+            if revenue > max_revenue:
+                max_revenue = round(revenue, 4)
+                max_revenue_date = date
+            
+            # 更新最大风险(最大亏损,即最小收益的相反数)
+            if risk < min_revenue:
+                min_revenue = round(risk, 4)
+                min_revenue_date = date
+        
+        return {'date': max_revenue_date, 'revenue': float(max_revenue)}, {'date': min_revenue_date, 'revenue': float(min_revenue)}
+    
+    def _calculate_max_revenue_risk_2(self, future_analysis, hatch_direction, standard_close):
+        """计算最大收益和最大风险(新逻辑:与影线方向相同交易)"""
+        if not future_analysis:
+            return {'date': None, 'revenue': 0.0}, {'date': None, 'revenue': 0.0}
+        
+        max_revenue_2 = float('-inf')
+        min_revenue_2 = float('inf')
+        max_revenue_date_2 = None
+        min_revenue_date_2 = None
+        
+        for day_data in future_analysis:
+            date = day_data['date']
+            high = day_data['high']
+            low = day_data['low']
+            
+            if hatch_direction == 'up':
+                # 上影线做多:收益 = (最高价 - 标准收盘价) / 标准收盘价,风险 = (最低价 - 标准收盘价) / 标准收盘价
+                revenue_2 = (high - standard_close) / standard_close
+                risk_2 = (low - standard_close) / standard_close
+            else:
+                # 下影线做空:收益 = (标准收盘价 - 最低价) / 标准收盘价,风险 = (标准收盘价 - 最高价) / 标准收盘价
+                revenue_2 = (standard_close - low) / standard_close
+                risk_2 = (standard_close - high) / standard_close
+            
+            # 更新最大收益
+            if revenue_2 > max_revenue_2:
+                max_revenue_2 = round(revenue_2, 4)
+                max_revenue_date_2 = date
+            
+            # 更新最大风险(最大亏损,即最小收益的相反数)
+            if risk_2 < min_revenue_2:
+                min_revenue_2 = round(risk_2, 4)
+                min_revenue_date_2 = date
+        
+        return {'date': max_revenue_date_2, 'revenue': float(max_revenue_2)}, {'date': min_revenue_date_2, 'revenue': float(min_revenue_2)}
+    
+    def _calculate_conclusion(self, max_revenue_info, min_revenue_info, future_analysis, hatch_direction, standard_close):
+        """
+        计算形态的结论:是否为有效的预测信号(增强版)
+        
+        逻辑:
+        - 若满足以下条件则返回 "true":
+        1. (最大盈利日期 < 最大亏损日期)且(最大盈利 > max_revenue_threshold)
+        2. 或者(最大盈利日期 > 最大亏损日期)且(最大盈利 > max_revenue_threshold)且(最大亏损 < max_loss_threshold)
+        - 增强条件:如果在最大收益日期之前的任何一天,损失超过了MAX_LOSS_THRESHOLD,则返回 "false"
+        - 否则返回 "false"
+        """
+        max_revenue_date = max_revenue_info['date']
+        max_revenue = max_revenue_info['revenue']
+        min_revenue_date = min_revenue_info['date']
+        min_revenue = min_revenue_info['revenue']
+        
+        # 如果日期为空,返回false
+        if max_revenue_date is None or min_revenue_date is None:
+            return "false"
+        
+        # 转换日期字符串为datetime对象进行比较
+        try:
+            from datetime import datetime
+            max_revenue_dt = datetime.strptime(max_revenue_date, '%Y-%m-%d')
+            min_revenue_dt = datetime.strptime(min_revenue_date, '%Y-%m-%d')
+        except:
+            return "false"
+        
+        # 增强检查:检查最大收益日期之前是否有任何一天的损失超过阈值
+        if future_analysis and self._check_early_excessive_loss(future_analysis, max_revenue_date, hatch_direction, standard_close):
+            return "false"
+        
+        # 条件1:(最大盈利日期 < 最大亏损日期)且(最大盈利 > 阈值)
+        condition1 = (max_revenue_dt < min_revenue_dt) and (max_revenue > self.max_revenue_threshold)
+        
+        # 条件2:(最大盈利日期 > 最大亏损日期)且(最大盈利 > 阈值)且(最大亏损 < 阈值)
+        condition2 = (max_revenue_dt > min_revenue_dt) and (max_revenue > self.max_revenue_threshold) and (abs(min_revenue) < self.max_loss_threshold)
+        
+        return "true" if (condition1 or condition2) else "false"
+    
+    def _check_early_excessive_loss(self, future_analysis, max_revenue_date, hatch_direction, standard_close):
+        """
+        检查在最大收益日期之前是否有任何一天的损失超过阈值(原逻辑:与影线方向相反交易)
+        
+        参数:
+            future_analysis: 未来价格分析数据
+            max_revenue_date: 最大收益日期
+            hatch_direction: 影线方向
+            standard_close: 标准收盘价
+            
+        返回:
+            bool: True表示存在早期过度损失
+        """
+        from datetime import datetime
+        try:
+            max_revenue_dt = datetime.strptime(max_revenue_date, '%Y-%m-%d')
+        except:
+            return False
+        
+        for day_data in future_analysis:
+            day_date_str = day_data['date']
+            try:
+                day_dt = datetime.strptime(day_date_str, '%Y-%m-%d')
+                
+                # 只检查最大收益日期之前的日期
+                if day_dt >= max_revenue_dt:
+                    continue
+                
+                high = day_data['high']
+                low = day_data['low']
+                
+                if hatch_direction == 'up':
+                    # 上影线做空:亏损 = (标准收盘价 - 最高价) / 标准收盘价,当价格上涨时为负值(亏损)
+                    loss_value = (standard_close - high) / standard_close
+                else:
+                    # 下影线做多:亏损 = (最低价 - 标准收盘价) / 标准收盘价,当价格下跌时为负值(亏损)
+                    loss_value = (low - standard_close) / standard_close
+                
+                # 检查亏损是否超过阈值:亏损为负值,阈值为正值,所以检查 abs(亏损) > 阈值
+                if loss_value < 0 and abs(loss_value) > self.max_loss_threshold:
+                    return True
+                    
+            except:
+                continue
+        
+        return False
+    
+    def _calculate_conclusion_2(self, max_revenue_info_2, min_revenue_info_2, future_analysis, hatch_direction, standard_close):
+        """
+        计算形态的结论2:是否为有效的预测信号(新逻辑:与影线方向相同交易)
+        
+        逻辑:
+        - 若满足以下条件则返回 "true":
+        1. (最大盈利日期 < 最大亏损日期)且(最大盈利 > max_revenue_threshold)
+        2. 或者(最大盈利日期 > 最大亏损日期)且(最大盈利 > max_revenue_threshold)且(最大亏损 < max_loss_threshold)
+        - 增强条件:如果在最大收益日期之前的任何一天,损失超过了MAX_LOSS_THRESHOLD,则返回 "false"
+        - 否则返回 "false"
+        """
+        max_revenue_date_2 = max_revenue_info_2['date']
+        max_revenue_2 = max_revenue_info_2['revenue']
+        min_revenue_date_2 = min_revenue_info_2['date']
+        min_revenue_2 = min_revenue_info_2['revenue']
+        
+        # 如果日期为空,返回false
+        if max_revenue_date_2 is None or min_revenue_date_2 is None:
+            return "false"
+        
+        # 转换日期字符串为datetime对象进行比较
+        try:
+            from datetime import datetime
+            max_revenue_dt_2 = datetime.strptime(max_revenue_date_2, '%Y-%m-%d')
+            min_revenue_dt_2 = datetime.strptime(min_revenue_date_2, '%Y-%m-%d')
+        except:
+            return "false"
+        
+        # 增强检查:检查最大收益日期之前是否有任何一天的损失超过阈值(新逻辑)
+        if future_analysis and self._check_early_excessive_loss_2(future_analysis, max_revenue_date_2, hatch_direction, standard_close):
+            return "false"
+        
+        # 条件1:(最大盈利日期 < 最大亏损日期)且(最大盈利 > 阈值)
+        condition1 = (max_revenue_dt_2 < min_revenue_dt_2) and (max_revenue_2 > self.max_revenue_threshold)
+        
+        # 条件2:(最大盈利日期 > 最大亏损日期)且(最大盈利 > 阈值)且(最大亏损 < 阈值)
+        condition2 = (max_revenue_dt_2 > min_revenue_dt_2) and (max_revenue_2 > self.max_revenue_threshold) and (abs(min_revenue_2) < self.max_loss_threshold)
+        
+        return "true" if (condition1 or condition2) else "false"
+    
+    def _check_early_excessive_loss_2(self, future_analysis, max_revenue_date_2, hatch_direction, standard_close):
+        """
+        检查在最大收益日期之前是否有任何一天的损失超过阈值(新逻辑:与影线方向相同交易)
+        
+        参数:
+            future_analysis: 未来价格分析数据
+            max_revenue_date_2: 最大收益日期
+            hatch_direction: 影线方向
+            standard_close: 标准收盘价
+            
+        返回:
+            bool: True表示存在早期过度损失
+        """
+        from datetime import datetime
+        try:
+            max_revenue_dt_2 = datetime.strptime(max_revenue_date_2, '%Y-%m-%d')
+        except:
+            return False
+        
+        for day_data in future_analysis:
+            day_date_str = day_data['date']
+            try:
+                day_dt = datetime.strptime(day_date_str, '%Y-%m-%d')
+                
+                # 只检查最大收益日期之前的日期
+                if day_dt >= max_revenue_dt_2:
+                    continue
+                
+                high = day_data['high']
+                low = day_data['low']
+                
+                if hatch_direction == 'up':
+                    # 上影线做多:亏损 = (最低价 - 标准收盘价) / 标准收盘价,当价格下跌时为负值(亏损)
+                    loss_value = (low - standard_close) / standard_close
+                else:
+                    # 下影线做空:亏损 = (标准收盘价 - 最高价) / 标准收盘价,当价格上涨时为负值(亏损)
+                    loss_value = (standard_close - high) / standard_close
+                
+                # 检查亏损是否超过阈值:亏损为负值,阈值为正值,所以检查 abs(亏损) > 阈值
+                if loss_value < 0 and abs(loss_value) > self.max_loss_threshold:
+                    return True
+                    
+            except:
+                continue
+        
+        return False
+    
+    def _calculate_direction_fields(self, pattern_info, hatch_direction):
+        """
+        计算body_direction和direction_consistency字段
+        
+        参数:
+            pattern_info: K线形态信息
+            hatch_direction: 影线方向 ('up' 或 'down')
+        
+        返回:
+            tuple: (body_direction, direction_consistency)
+        """
+        open_price = pattern_info['open']
+        close_price = pattern_info['close']
+        
+        # 计算body_direction:实体方向
+        if close_price > open_price:
+            body_direction = 'up'
+        elif close_price < open_price:
+            body_direction = 'down'
+        else:
+            body_direction = 'flat'  # 十字星情况
+        
+        # 计算direction_consistency:检查实体方向与影线方向是否相反
+        if body_direction == 'flat':
+            direction_consistency = 'false'  # 十字星情况无法判断一致性
+        elif (body_direction == 'up' and hatch_direction == 'down') or \
+             (body_direction == 'down' and hatch_direction == 'up'):
+            direction_consistency = 'true'   # 相反,符合预期
+        else:
+            direction_consistency = 'false'  # 不相反
+            
+        return body_direction, direction_consistency
+    
+    def _calculate_price_direction(self, close_price, ma5_value):
+        """
+        计算基于收盘价与5K移动平均线关系的价格方向
+        
+        参数:
+            close_price: 形态形成日的收盘价
+            ma5_value: 5日移动平均线价格
+            
+        返回:
+            str: 价格方向分类 ("上涨" 或 "下跌")
+        """
+        if ma5_value is None:
+            return "数据不足"  # 如果MA5数据不足,返回数据不足
+        
+        if close_price >= ma5_value:
+            return "上涨"
+        else:
+            return "下跌"
+    
+    def _calculate_dynamic_position(self, conclusion, max_revenue, trade_amount):
+        """
+        计算动态仓位调整后的剩余资金
+        
+        参数:
+            conclusion: 交易结论 ('true' 为正确, 'false' 为错误)
+            max_revenue: 最大盈利率
+            trade_amount: 当前交易金额
+            
+        返回:
+            float: 调整后的剩余资金
+        """
+        if conclusion == "false":  # 亏损交易(错误结果)
+            remaining_amount = trade_amount * (1 - self.loss_rate)
+            if self.verbose_logging:
+                print(f"      亏损交易,剩余资金: {remaining_amount:.2f} (亏损率: {self.loss_rate})")
+        else:  # 盈利交易(正确结果)
+            remaining_amount = trade_amount * (1 + max_revenue - self.profit_drawdown_rate)
+            if self.verbose_logging:
+                print(f"      盈利交易,剩余资金: {remaining_amount:.2f} (最大利润: {max_revenue:.4f}, 回撤率: {self.profit_drawdown_rate})")
+        
+        return remaining_amount
+    
+    def _add_portfolio_trade(self, contract_code, date, conclusion, conclusion_2, max_revenue, max_revenue_2, trade_amount, remaining_amount):
+        """
+        添加交易记录到投资组合(支持两种交易逻辑)
+        
+        参数:
+            contract_code: 合约代码
+            date: 交易日期
+            conclusion: 原逻辑交易结论
+            conclusion_2: 新逻辑交易结论
+            max_revenue: 原逻辑最大盈利率
+            max_revenue_2: 新逻辑最大盈利率
+            trade_amount: 交易金额
+            remaining_amount: 剩余金额(基于原逻辑计算)
+        """
+        # 计算新逻辑的剩余金额
+        remaining_amount_2 = self._calculate_dynamic_position(
+            conclusion_2, max_revenue_2, trade_amount
+        )
+        
+        trade_record = {
+            'contract_code': contract_code,
+            'date': date,
+            'conclusion': conclusion,
+            'conclusion2': conclusion_2,
+            'max_revenue': max_revenue,
+            'max_revenue2': max_revenue_2,
+            'trade_amount': trade_amount,
+            'remaining_amount': remaining_amount,
+            'remaining_amount2': remaining_amount_2,
+            'profit_loss': remaining_amount - trade_amount,
+            'profit_loss2': remaining_amount_2 - trade_amount
+        }
+        self.portfolio_results.append(trade_record)
+    
+    def _calculate_moving_averages(self, data, signal_idx):
+        """
+        计算形态形成日的移动平均线
+        
+        参数:
+            data: 价格数据DataFrame
+            signal_idx: 信号日期在数据中的索引
+            
+        返回:
+            dict: 包含各移动平均线的字典
+        """
+        if signal_idx < 29:  # 至少需要30天数据来计算30日移动平均
+            return {'ma5': None, 'ma10': None, 'ma20': None, 'ma30': None}
+        
+        # 获取到信号日期为止的收盘价数据
+        data_list = list(data.iterrows())
+        closes = []
+        for i in range(max(0, signal_idx - 29), signal_idx + 1):  # 取30天数据
+            if i < len(data_list):
+                _, row = data_list[i]
+                closes.append(row['close'])
+        
+        if len(closes) < 30:
+            return {'ma5': None, 'ma10': None, 'ma20': None, 'ma30': None}
+        
+        # 计算移动平均线(使用最后的N天数据)
+        ma5 = sum(closes[-5:]) / 5 if len(closes) >= 5 else None
+        ma10 = sum(closes[-10:]) / 10 if len(closes) >= 10 else None
+        ma20 = sum(closes[-20:]) / 20 if len(closes) >= 20 else None
+        ma30 = sum(closes[-30:]) / 30 if len(closes) >= 30 else None
+        
+        return {
+            'ma5': round(ma5, 2) if ma5 is not None else None,
+            'ma10': round(ma10, 2) if ma10 is not None else None,
+            'ma20': round(ma20, 2) if ma20 is not None else None,
+            'ma30': round(ma30, 2) if ma30 is not None else None
+        }
+    
+    def _calculate_trend_strength(self, ma_values):
+        """
+        计算趋势强度评估
+        
+        参数:
+            ma_values: 移动平均线字典
+            
+        返回:
+            str: 趋势强度分类
+        """
+        ma5 = ma_values['ma5']
+        ma10 = ma_values['ma10']
+        ma20 = ma_values['ma20']
+        ma30 = ma_values['ma30']
+        
+        # 如果有任何移动平均线为空,返回"数据不足"
+        if None in [ma5, ma10, ma20, ma30]:
+            return "数据不足"
+        
+        # 进行6项趋势比较
+        comparisons = [
+            ma5 >= ma10,  # 5K≥10K
+            ma10 >= ma20,  # 10K≥20K
+            ma20 >= ma30,  # 20K≥30K
+            ma5 >= ma20,   # 5K≥20K
+            ma10 >= ma30,  # 10K≥30K
+            ma5 >= ma30    # 5K≥30K
+        ]
+        
+        # 计算上涨趋势的数量
+        uptrend_count = sum(comparisons)
+        
+        # 根据上涨趋势数量进行分类
+        if uptrend_count == 6 or uptrend_count == 0:
+            return "强趋势"
+        elif uptrend_count == 3:
+            return "震荡"
+        elif uptrend_count == 5 or uptrend_count == 1:
+            return "中趋势"
+        elif uptrend_count == 4 or uptrend_count == 2:
+            return "弱趋势"
+        else:
+            return "未知"  # 理论上不应该到这里
+    
+    def calculate_portfolio_summary(self):
+        """
+        计算投资组合整体表现汇总(支持两种交易逻辑对比)
+        
+        返回:
+            dict: 投资组合汇总信息
+        """
+        if not self.portfolio_results:
+            return {
+                'total_trades': 0,
+                'logic1': {
+                    'successful_trades': 0,
+                    'failed_trades': 0,
+                    'success_rate': 0.0,
+                    'total_profit_loss': 0.0,
+                    'final_capital': self.initial_trade_amount,
+                    'total_return': 0.0
+                },
+                'logic2': {
+                    'successful_trades': 0,
+                    'failed_trades': 0,
+                    'success_rate': 0.0,
+                    'total_profit_loss': 0.0,
+                    'final_capital': self.initial_trade_amount,
+                    'total_return': 0.0
+                }
+            }
+        
+        total_trades = len(self.portfolio_results)
+        initial_capital = total_trades * self.initial_trade_amount
+        
+        # 原逻辑(与影线方向相反交易)统计
+        successful_trades_1 = sum(1 for trade in self.portfolio_results if trade['conclusion'] == 'true')
+        failed_trades_1 = total_trades - successful_trades_1
+        success_rate_1 = (successful_trades_1 / total_trades) * 100 if total_trades > 0 else 0
+        total_profit_loss_1 = sum(trade['profit_loss'] for trade in self.portfolio_results)
+        final_capital_1 = initial_capital + total_profit_loss_1
+        total_return_1 = (total_profit_loss_1 / initial_capital * 100) if initial_capital > 0 else 0
+        
+        # 新逻辑(与影线方向相同交易)统计
+        successful_trades_2 = sum(1 for trade in self.portfolio_results if trade['conclusion2'] == 'true')
+        failed_trades_2 = total_trades - successful_trades_2
+        success_rate_2 = (successful_trades_2 / total_trades) * 100 if total_trades > 0 else 0
+        total_profit_loss_2 = sum(trade['profit_loss2'] for trade in self.portfolio_results)
+        final_capital_2 = initial_capital + total_profit_loss_2
+        total_return_2 = (total_profit_loss_2 / initial_capital * 100) if initial_capital > 0 else 0
+        
+        portfolio_summary = {
+            'total_trades': total_trades,
+            'initial_capital': initial_capital,
+            'logic1': {
+                'name': '原逻辑(与影线方向相反交易)',
+                'successful_trades': successful_trades_1,
+                'failed_trades': failed_trades_1,
+                'success_rate': round(success_rate_1, 2),
+                'total_profit_loss': round(total_profit_loss_1, 2),
+                'final_capital': round(final_capital_1, 2),
+                'total_return': round(total_return_1, 2)
+            },
+            'logic2': {
+                'name': '新逻辑(与影线方向相同交易)',
+                'successful_trades': successful_trades_2,
+                'failed_trades': failed_trades_2,
+                'success_rate': round(success_rate_2, 2),
+                'total_profit_loss': round(total_profit_loss_2, 2),
+                'final_capital': round(final_capital_2, 2),
+                'total_return': round(total_return_2, 2)
+            }
+        }
+        
+        if self.verbose_logging:
+            print(f"\n=== 投资组合双向交易逻辑对比分析 ===")
+            print(f"总交易次数: {total_trades}")
+            print(f"初始资本: {initial_capital:.2f}")
+            print(f"\n--- 原逻辑(与影线方向相反交易)---")
+            print(f"成功交易: {successful_trades_1}")
+            print(f"失败交易: {failed_trades_1}")
+            print(f"成功率: {success_rate_1:.2f}%")
+            print(f"总盈亏: {total_profit_loss_1:.2f}")
+            print(f"最终资本: {final_capital_1:.2f}")
+            print(f"总回报率: {total_return_1:.2f}%")
+            print(f"\n--- 新逻辑(与影线方向相同交易)---")
+            print(f"成功交易: {successful_trades_2}")
+            print(f"失败交易: {failed_trades_2}")
+            print(f"成功率: {success_rate_2:.2f}%")
+            print(f"总盈亏: {total_profit_loss_2:.2f}")
+            print(f"最终资本: {final_capital_2:.2f}")
+            print(f"总回报率: {total_return_2:.2f}%")
+            print(f"\n--- 逻辑对比 ---")
+            print(f"成功率差异: {success_rate_2 - success_rate_1:.2f}% (新逻辑 - 原逻辑)")
+            print(f"回报率差异: {total_return_2 - total_return_1:.2f}% (新逻辑 - 原逻辑)")
+        
+        return portfolio_summary
+    
+    def generate_csv_output(self, enhanced_pattern_dict):
+        """
+        生成CSV输出文件
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤6: 生成CSV输出文件 ===")
+        
+        # 准备CSV数据(按指定顺序排列字段)
+        csv_data = []
+        
+        for contract_code, patterns in enhanced_pattern_dict.items():
+            for signal_date, pattern_info in patterns.items():
+                csv_data.append({
+                    # 按更新的字段顺序:contract_code, date, conclusion, conclusion2, body_direction, direction_consistency, price_direction, body_len, hatch_len, hatch/body, close, open, high, low, max_revenue, max_revenue_date, min_revenue, min_revenue_date, max_revenue2, max_revenue_date2, min_revenue2, min_revenue_date2, profit_loss, remaining_amount, trade_amount
+                    'contract_code': contract_code,
+                    'date': pattern_info['date'],
+                    'conclusion': pattern_info['conclusion'],
+                    'conclusion2': pattern_info['conclusion2'],
+                    'body_direction': pattern_info['body_direction'],
+                    'direction_consistency': pattern_info['direction_consistency'],
+                    'price_direction': pattern_info['price_direction'],  # 新增:基于收盘价与5K移动平均线关系的价格方向
+                    'body_len': pattern_info['body_len'],
+                    'hatch_len': pattern_info['hatch_len'],
+                    'hatch/body': pattern_info['hatch_body_ratio'],
+                    'close': pattern_info['close'],
+                    'open': pattern_info['open'],
+                    'high': pattern_info['high'],
+                    'low': pattern_info['low'],
+                    'max_revenue': pattern_info['max_revenue'],
+                    'max_revenue_date': pattern_info['max_revenue_date'],
+                    'min_revenue': pattern_info['min_revenue'],
+                    'min_revenue_date': pattern_info['min_revenue_date'],
+                    'max_revenue2': pattern_info['max_revenue2'],
+                    'max_revenue_date2': pattern_info['max_revenue_date2'],
+                    'min_revenue2': pattern_info['min_revenue2'],
+                    'min_revenue_date2': pattern_info['min_revenue_date2'],
+                    'profit_loss': pattern_info['profit_loss'],
+                    'remaining_amount': pattern_info['remaining_amount'],
+                    'trade_amount': pattern_info['trade_amount']
+                })
+        
+        if csv_data:
+            # 创建DataFrame并保存为CSV,严格按照用户指定的列顺序
+            df = pd.DataFrame(csv_data)
+            
+            # 定义更新的确切列顺序(包含新的price_direction字段)
+            column_order = [
+                'contract_code', 'date', 'conclusion', 'conclusion2', 'body_direction', 
+                'direction_consistency', 'price_direction', 'body_len', 'hatch_len', 'hatch/body', 
+                'close', 'open', 'high', 'low', 'max_revenue', 'max_revenue_date', 
+                'min_revenue', 'min_revenue_date', 'max_revenue2', 'max_revenue_date2', 
+                'min_revenue2', 'min_revenue_date2', 'profit_loss', 'remaining_amount', 'trade_amount'
+            ]
+            
+            # 重新排列DataFrame的列顺序
+            df = df[column_order]
+            
+            timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
+            filename = f'future_candlestick_hatch_analysis_{timestamp}.csv'
+            df.to_csv(filename, index=False, encoding=self.output_encoding)
+            
+            if self.verbose_logging:
+                print(f"分析结果已保存至: {filename}")
+                print(f"总计 {len(csv_data)} 条记录")
+            
+            return filename
+        else:
+            if self.verbose_logging:
+                print("没有数据可以输出")
+            return None
+    
+    def run_complete_analysis(self):
+        """执行完整的分析流程"""
+        if self.verbose_logging:
+            print("开始执行期货烛台影线形态分析")
+            print("=" * 60)
+        
+        try:
+            # 步骤1: 构建合约数据结构
+            contract_structure = self.build_contract_structure()
+            if not contract_structure:
+                if self.verbose_logging:
+                    print("未获取到有效合约,分析终止")
+                return None
+            
+            # 步骤2: 收集扩展数据
+            extended_data = self.collect_extended_data()
+            if not extended_data:
+                if self.verbose_logging:
+                    print("未获取到有效数据,分析终止")
+                return None
+            
+            # 步骤3: 计算实体长度阈值
+            threshold_data = self.calculate_body_thresholds(extended_data)
+            if not threshold_data:
+                if self.verbose_logging:
+                    print("未能计算有效阈值,分析终止")
+                return None
+            
+            # 步骤4: 识别影线形态
+            pattern_dict = self.identify_hatch_patterns(threshold_data)
+            if not pattern_dict:
+                if self.verbose_logging:
+                    print("未检测到有效影线形态,分析终止")
+                return None
+            
+            # 步骤5: 分析未来表现
+            enhanced_pattern_dict = self.analyze_future_performance(threshold_data, pattern_dict)
+            
+            # 步骤6: 生成CSV输出
+            output_filename = self.generate_csv_output(enhanced_pattern_dict)
+            
+            # 步骤7: 计算投资组合汇总
+            portfolio_summary = self.calculate_portfolio_summary()
+            
+            # 分析汇总
+            total_contracts = len(contract_structure)
+            valid_contracts = len(threshold_data)
+            total_patterns = sum(len(patterns) for patterns in enhanced_pattern_dict.values())
+            
+            # 计算模式有效性指标
+            effective_patterns = 0
+            for contract_patterns in enhanced_pattern_dict.values():
+                for pattern_info in contract_patterns.values():
+                    if pattern_info.get('conclusion') == 'true':
+                        effective_patterns += 1
+            
+            effective_rate = (effective_patterns / total_patterns * 100) if total_patterns > 0 else 0
+            
+            if self.verbose_logging:
+                print("\n" + "=" * 60)
+                print("分析完成汇总:")
+                print(f"总合约数: {total_contracts}")
+                print(f"有效合约数: {valid_contracts}")
+                print(f"检测形态数: {total_patterns}")
+                print(f"有效形态数: {effective_patterns}")
+                print(f"有效率: {effective_rate:.1f}%")
+                print(f"输出文件: {output_filename}")
+            
+            return {
+                'contract_structure': contract_structure,
+                'threshold_data': threshold_data,
+                'pattern_results': enhanced_pattern_dict,
+                'output_filename': output_filename,
+                'portfolio_summary': portfolio_summary,
+                'summary': {
+                    'total_contracts': total_contracts,
+                    'valid_contracts': valid_contracts,
+                    'total_patterns': total_patterns,
+                    'effective_patterns': effective_patterns,
+                    'effective_rate': effective_rate
+                }
+            }
+            
+        except Exception as e:
+            if self.verbose_logging:
+                print(f"分析过程中出现错误: {str(e)}")
+                import traceback
+                traceback.print_exc()
+            return None
+
+
+# =====================================================================================
+# 主程序入口
+# =====================================================================================
+
+def run_candlestick_hatch_analysis(config=None):
+    """运行期货烛台影线形态分析"""
+    if config is None:
+        config = CandlestickHatchConfig
+    
+    # 打印配置信息
+    config.print_config()
+    
+    # 创建分析器并运行
+    analyzer = FutureCandlestickHatchAnalyzer(config)
+    results = analyzer.run_complete_analysis()
+    return results
+
+# 执行分析
+if __name__ == "__main__":
+    print("期货烛台影线形态双向交易分析工具")
+    print("研究带有明显影线的K线对未来价格走势的预测能力")
+    print("双向交易逻辑对比测试:")
+    print("  原逻辑:上影线做空,下影线做多(与影线方向相反交易)")
+    print("  新逻辑:上影线做多,下影线做空(与影线方向相同交易)")
+    print("适用于聚宽在线研究平台")
+    
+    results = run_candlestick_hatch_analysis()
+    
+    if results:
+        print("\n✅ 分析执行成功!")
+        summary = results['summary']
+        portfolio = results['portfolio_summary']
+        print(f"📊 结果摘要:")
+        print(f"   - 分析合约数: {summary['total_contracts']}")
+        print(f"   - 有效数据合约: {summary['valid_contracts']}")
+        print(f"   - 检测形态总数: {summary['total_patterns']}")
+        print(f"   - 有效形态数: {summary['effective_patterns']}")
+        print(f"   - 有效率: {summary['effective_rate']:.1f}%")
+        print(f"   - 输出文件: {results['output_filename']}")
+        print(f"\n💰 双向交易逻辑对比分析:")
+        print(f"   - 总交易次数: {portfolio['total_trades']}")
+        print(f"   - 初始资本: {portfolio['initial_capital']:.2f}")
+        print(f"\n   📈 原逻辑(与影线方向相反交易):")
+        print(f"       成功率: {portfolio['logic1']['success_rate']:.2f}%")
+        print(f"       总盈亏: {portfolio['logic1']['total_profit_loss']:.2f}")
+        print(f"       总回报率: {portfolio['logic1']['total_return']:.2f}%")
+        print(f"\n   📉 新逻辑(与影线方向相同交易):")
+        print(f"       成功率: {portfolio['logic2']['success_rate']:.2f}%")
+        print(f"       总盈亏: {portfolio['logic2']['total_profit_loss']:.2f}")
+        print(f"       总回报率: {portfolio['logic2']['total_return']:.2f}%")
+        print(f"\n   🔍 逻辑对比:")
+        success_diff = portfolio['logic2']['success_rate'] - portfolio['logic1']['success_rate']
+        return_diff = portfolio['logic2']['total_return'] - portfolio['logic1']['total_return']
+        print(f"       成功率差异: {success_diff:+.2f}% (新逻辑 - 原逻辑)")
+        print(f"       回报率差异: {return_diff:+.2f}% (新逻辑 - 原逻辑)")
+    else:
+        print("\n❌ 分析执行失败,请检查错误信息")

+ 3213 - 0
Lib/research/future_grid_trading_analysis.py

@@ -0,0 +1,3213 @@
+"""
+期货网格交易研究分析工具(带主力合约切换)
+研究期货网格交易策略在不同配置下的表现,支持多种交易场景的对比分析
+核心功能包括主力合约自动切换、强制平仓和重新建仓逻辑
+
+本程序实现完整的网格交易分析流程:
+1. 主力合约监控与切换 - 自动检测主力合约变化并处理切换
+2. 合约选择逻辑 - 基于主力合约选择算法
+3. 基础头寸交易 - 价格-数量网格配置,支持合约切换时重新建仓
+4. 网格交易策略 - 限价订单网格买入卖出,合约切换时根据价格条件重新建仓
+5. 网格对冲策略 - 带止损的增强网格交易,合约切换时根据价格条件重新建仓
+6. 移动平均线过滤器 - 基于MA趋势过滤对冲入场,避免强下跌趋势中的对冲
+7. 统计分析对比 - 四种交易场景性能分析
+
+主要特点:
+- 主力合约自动监控:每日检测主力合约变化
+- 强制平仓机制:合约切换时立即平掉旧合约所有头寸
+- 智能重新建仓:根据价格条件在新合约中重新建立头寸
+- MA趋势过滤:检测强下跌趋势(30K<20K<10K<5K或30K<20K<5K<10K),阻止对冲入场
+- 智能数据扩展:当MA计算所需数据不足时,自动扩展历史数据范围并缓存
+- 标准期货盈亏计算:使用正确的合约倍数和期货盈亏公式
+- 最终持仓结算:分析期结束时对所有未平仓头寸进行市值计价
+- 完整交易记录:记录所有交易包括合约切换引起的强制平仓
+
+期货盈亏计算公式:
+- 多头:(出场价格 - 入场价格) × 合约倍数 × 数量
+- 空头:(入场价格 - 出场价格) × 合约倍数 × 数量
+
+注:程序支持多个核心商品同时分析,生成详细的交易记录和统计报告
+
+作者: jukuan研究团队
+日期: 2025-09
+适用平台: 聚宽在线研究平台
+"""
+
+import pandas as pd
+import numpy as np
+from jqdata import *
+import datetime
+import warnings
+warnings.filterwarnings('ignore')
+
+# =====================================================================================
+# 分析配置参数 - 集中配置部分
+# =====================================================================================
+
+class GridTradingConfig:
+    """期货网格交易分析配置参数"""
+    
+    # ==================== 时间范围设置 ====================
+    START_DATE = datetime.datetime(2024, 11, 7)   # 分析开始日期
+    END_DATE = datetime.datetime(2025, 9, 19)    # 分析结束日期(修正为9月19日避免数据缺失)
+    
+    # ==================== 期货合约倍数配置 ====================
+    FUTURES_MULTIPLIER = {
+        # 贵金属
+        'AU': 1000,  # 黄金
+        'AG': 15,    # 白银
+        
+        # 有色金属
+        'CU': 5, 'AL': 5, 'ZN': 5, 'PB': 5, 'NI': 1, 'SN': 1, 'SS': 5,
+        
+        # 黑色系
+        'RB': 10, 'HC': 10, 'I': 100, 'JM': 100, 'J': 60,
+        
+        # 能源化工
+        'SP': 10, 'FU': 10, 'BU': 10, 'RU': 10, 'BR': 5, 'SC': 1000,
+        'NR': 10, 'LU': 10, 'LC': 1,
+        
+        # 化工
+        'FG': 20, 'TA': 5, 'MA': 10, 'SA': 20, 'L': 5, 'V': 5, 'EG': 10,
+        'PP': 5, 'EB': 5, 'PG': 20, 'UR': 20,
+        
+        # 农产品
+        'RM': 10, 'OI': 10, 'CF': 5, 'SR': 10, 'PF': 5, 'C': 10, 'CS': 10,
+        'CY': 5, 'A': 10, 'B': 10, 'M': 10, 'Y': 10, 'P': 10,
+        
+        # 股指期货
+        'IF': 300, 'IH': 300, 'IC': 200, 'IM': 200, 'TL': 10000,
+        
+        # 其他
+        'AP': 10, 'CJ': 5, 'PK': 5, 'JD': 10, 'LH': 16
+    }
+    
+    # ==================== 核心商品配置 ====================
+    CORE_COMMODITIES = {
+        # 'SA': ['SA2501.XZCE', 'SA2505.XZCE', 'SA2509.XZCE', 'SA2601.XZCE'],  # 纯碱
+        # 'M': ['M2501.XDCE', 'M2505.XDCE', 'M2509.XDCE', 'M2605.XDCE'],  # 豆粕
+        'UR': ['UR2501.XZCE', 'UR2505.XZCE', 'UR2509.XZCE', 'UR2601.XZCE'],  # 尿素
+        # 'LH': ['LH2501.XDCE', 'LH2505.XDCE', 'LH2509.XDCE', 'LH2601.XDCE'],  # 生猪
+        # 'TL': ['TL2503.CCFX', 'TL2506.CCFX', 'TL2509.CCFX', 'TL2512.CCFX']  # 30年期国债
+    }
+    
+    # ==================== 合约切换配置 ====================
+    REQUIRED_TRADING_DAYS = 30  # 合约切换前需要的最少有效交易日数
+    
+    # ==================== 基础头寸交易配置 ====================
+    BASE_POSITION_GRID = {
+        'SA': {1400: 4, 1300: 6, 1200: 8, 1100: 12, 1000: 14, 900: 16},
+        'M': {2800: 4, 2750: 6, 2700: 8, 2650: 12, 2600: 14, 2550: 16},
+        'UR': {1750: 4, 1700: 6, 1650: 8, 1600: 12, 1550: 14, 1500: 16},
+        'LH': {13000: 1, 12500: 1, 12000: 1, 11500: 1, 11000: 2},
+        'TL': {118: 1, 117: 1, 116: 1, 115: 1, 114: 2, 113: 2},
+    }
+    
+    # 统一退出价格(无止损)
+    BASE_POSITION_EXIT_PRICE = {
+        'SA': 1500,
+        'M': 3800,
+        'UR': 2400,
+        'LH': 20000,
+        'TL': 121,
+    }
+    
+    # ==================== 网格交易配置 ====================
+    GRID_TRADING_CONFIG = {
+        'SA': {
+            'start_price': 1250, # 开始价格
+            'grid_size': 50, # 网格大小
+            'quantity_per_grid': 5, # 每网格数量
+            'exit_grid_size': 50, # 退出网格大小
+            'hedge_stop_price': 1100 # 对冲停止价格(低于此价格不触发对冲)
+        },
+        'M': {
+            'start_price': 2800,
+            'grid_size': 100,
+            'quantity_per_grid': 10,
+            'exit_grid_size': 100,
+            'hedge_stop_price': 2400
+        },
+        'UR': {
+            'start_price': 1800,
+            'grid_size': 50,
+            'quantity_per_grid': 10,
+            'exit_grid_size': 50,
+            'hedge_stop_price': 1600
+        },
+        'LH': {
+            'start_price': 13500,
+            'grid_size': 500,
+            'quantity_per_grid': 1,
+            'exit_grid_size': 500,
+            'hedge_stop_price': 12000
+        },
+        'TL': {
+            'start_price': 118,
+            'grid_size': 1,
+            'quantity_per_grid': 1,
+            'exit_grid_size': 1,
+            'hedge_stop_price': 113  # 对冲停止价格(低于113不触发对冲)
+        },
+    }
+    
+    # ==================== 网格对冲策略配置 ====================
+    GRID_HEDGE_CONFIG = {
+        'stop_loss_percentage': 0.01,  # 2%止损百分比
+        'enable_hedge_strategy': True,  # 是否启用网格对冲策略
+        'use_ma_filter': True           # 是否启用移动平均线过滤器(阻止特定趋势下的对冲入场)
+    }
+    
+    # ==================== 输出设置 ====================
+    OUTPUT_ENCODING = 'utf-8-sig'  # 输出文件编码格式
+    VERBOSE_LOGGING = True          # 是否打印详细日志
+    
+    @classmethod
+    def print_config(cls):
+        """打印当前配置信息"""
+        print("=== 期货网格交易分析配置 ===")
+        print(f"分析时间范围: {cls.START_DATE.strftime('%Y-%m-%d')} 至 {cls.END_DATE.strftime('%Y-%m-%d')}")
+        print(f"核心商品数量: {len(cls.CORE_COMMODITIES)}")
+        print("核心商品列表:")
+        for commodity, contracts in cls.CORE_COMMODITIES.items():
+            print(f"  {commodity}: {contracts}")
+        print(f"\n网格交易配置:")
+        for commodity, config in cls.GRID_TRADING_CONFIG.items():
+            hedge_stop = config.get('hedge_stop_price', '未设置')
+            print(f"  {commodity}: 起始价{config['start_price']}, 网格大小{config['grid_size']}, "
+                  f"对冲停止价{hedge_stop}")
+        print(f"\n网格对冲策略: {'启用' if cls.GRID_HEDGE_CONFIG['enable_hedge_strategy'] else '禁用'}")
+        print(f"止损百分比: {cls.GRID_HEDGE_CONFIG['stop_loss_percentage']*100}%")
+        print(f"MA过滤器: {'启用' if cls.GRID_HEDGE_CONFIG.get('use_ma_filter', False) else '禁用'}")
+        print(f"详细日志: {'开启' if cls.VERBOSE_LOGGING else '关闭'}")
+        print("=" * 50)
+
+class FutureGridTradingAnalyzer:
+    """期货网格交易分析器"""
+    
+    def __init__(self, config=None):
+        """初始化分析器"""
+        if config is None:
+            config = GridTradingConfig
+        
+        self.config = config
+        self.start_date = config.START_DATE
+        self.end_date = config.END_DATE
+        self.core_commodities = config.CORE_COMMODITIES
+        self.base_position_grid = config.BASE_POSITION_GRID
+        self.base_position_exit_price = config.BASE_POSITION_EXIT_PRICE
+        self.grid_trading_config = config.GRID_TRADING_CONFIG
+        self.grid_hedge_config = config.GRID_HEDGE_CONFIG
+        self.verbose_logging = config.VERBOSE_LOGGING
+        self.output_encoding = config.OUTPUT_ENCODING
+        
+        # 存储结果的字典
+        self.selected_contracts = {}  # 选中的合约
+        self.price_data = {}         # 价格数据
+        self.dominant_contract_history = {}  # 主力合约历史变化
+        self.active_positions = {    # 当前活跃头寸跟踪
+            'base_position': {},
+            'grid_trading': {},
+            'grid_hedge': {}
+        }
+        self.trading_results = {     # 四种交易场景的结果
+            'base_position': [],
+            'grid_trading': [],
+            'grid_hedge': [],
+            'combined': []
+        }
+        
+        # MA过滤器扩展数据缓存(避免重复获取)
+        self.ma_extended_data_cache = {}
+        
+        if self.verbose_logging:
+            print("初始化期货网格交易分析器")
+            print(f"核心商品: {list(self.core_commodities.keys())}")
+            print(f"分析期间: {self.start_date.strftime('%Y-%m-%d')} - {self.end_date.strftime('%Y-%m-%d')}")
+    
+    def select_contracts(self):
+        """
+        合约选择逻辑
+        1. 首先获取商品的主导合约
+        2. 如果主导合约在可用列表中,选择它
+        3. 如果主导合约不在列表中,选择未来到期日期最近且晚于主导合约的合约
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤1: 合约选择逻辑 ===")
+        
+        for commodity, available_contracts in self.core_commodities.items():
+            if self.verbose_logging:
+                print(f"\n处理商品: {commodity}")
+                print(f"可用合约: {available_contracts}")
+            
+            try:
+                # 获取商品的主导合约
+                dominant_contract = get_dominant_future(commodity, self.start_date.date())
+                
+                if self.verbose_logging:
+                    print(f"主导合约: {dominant_contract}")
+                
+                if dominant_contract in available_contracts:
+                    # 主导合约在可用列表中,直接选择
+                    selected_contract = dominant_contract
+                    if self.verbose_logging:
+                        print(f"选择主导合约: {selected_contract}")
+                else:
+                    # 主导合约不在列表中,选择最近的未来合约
+                    selected_contract = self._select_nearest_future_contract(
+                        commodity, dominant_contract, available_contracts
+                    )
+                    if self.verbose_logging:
+                        print(f"选择最近的未来合约: {selected_contract}")
+                
+                self.selected_contracts[commodity] = selected_contract
+                
+            except Exception as e:
+                if self.verbose_logging:
+                    print(f"获取{commodity}主导合约失败: {str(e)}")
+                # 默认选择第一个可用合约
+                self.selected_contracts[commodity] = available_contracts[0]
+                if self.verbose_logging:
+                    print(f"默认选择第一个合约: {available_contracts[0]}")
+        
+        if self.verbose_logging:
+            print(f"\n合约选择完成,共选择{len(self.selected_contracts)}个合约")
+            for commodity, contract in self.selected_contracts.items():
+                print(f"  {commodity}: {contract}")
+        
+        return self.selected_contracts
+    
+    def _select_nearest_future_contract(self, commodity, dominant_contract, available_contracts):
+        """选择最近的未来到期合约"""
+        if not dominant_contract:
+            return available_contracts[0]
+        
+        # 解析主导合约的到期月份
+        try:
+            # 提取合约代码中的月份信息 (例如 SA2507 -> 2507)
+            dominant_year_month = dominant_contract.split('.')[0][-4:]  # 取最后4位
+            dominant_year = int(dominant_year_month[:2]) + 2000  # 假设是21世纪
+            dominant_month = int(dominant_year_month[2:])
+        except:
+            return available_contracts[0]
+        
+        # 找到最近的未来合约
+        best_contract = available_contracts[0]
+        best_diff = float('inf')
+        
+        for contract in available_contracts:
+            try:
+                contract_year_month = contract.split('.')[0][-4:]
+                contract_year = int(contract_year_month[:2]) + 2000
+                contract_month = int(contract_year_month[2:])
+                
+                # 计算月份差异
+                contract_total_months = contract_year * 12 + contract_month
+                dominant_total_months = dominant_year * 12 + dominant_month
+                
+                # 只选择晚于主导合约的合约
+                if contract_total_months > dominant_total_months:
+                    diff = contract_total_months - dominant_total_months
+                    if diff < best_diff:
+                        best_diff = diff
+                        best_contract = contract
+            except:
+                continue
+        
+        return best_contract
+    
+    def _get_futures_multiplier(self, commodity):
+        """获取期货合约倍数"""
+        return self.config.FUTURES_MULTIPLIER.get(commodity, 10)  # 默认倍数为10
+    
+    def _calculate_futures_pnl(self, entry_price, exit_price, quantity, commodity, is_long=True):
+        """
+        计算期货盈亏
+        
+        参数:
+            entry_price: 入场价格
+            exit_price: 出场价格
+            quantity: 数量(手数)
+            commodity: 商品代码
+            is_long: 是否多头,True为多头,False为空头
+            
+        返回:
+            实际盈亏金额
+        """
+        multiplier = self._get_futures_multiplier(commodity)
+        
+        if is_long:
+            # 多头:(出场价格 - 入场价格) × 合约倍数 × 数量
+            pnl = (exit_price - entry_price) * multiplier * quantity
+        else:
+            # 空头:(入场价格 - 出场价格) × 合约倍数 × 数量
+            pnl = (entry_price - exit_price) * multiplier * quantity
+        
+        return pnl
+    
+    def build_dominant_contract_history(self):
+        """
+        构建主力合约历史变化记录
+        为每个商品在整个分析期间构建主力合约变化的时间序列
+        只有当合约真正发生变化时才记录为合约切换
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤2:构建主力合约历史变化记录 ===")
+        
+        for commodity in self.core_commodities.keys():
+            if self.verbose_logging:
+                print(f"构建 {commodity} 主力合约历史...")
+            
+            contract_history = []
+            current_date = self.start_date.date()
+            end_date = self.end_date.date()
+            current_selected_contract = None  # 跟踪选择的合约而不是主力合约
+            
+            while current_date <= end_date:
+                # 跳过非交易日
+                if current_date.weekday() >= 5:  # 周六周日
+                    current_date += datetime.timedelta(days=1)
+                    continue
+                
+                try:
+                    # 获取当日主力合约
+                    dominant_contract = get_dominant_future(commodity, current_date)
+                    # print(f"日期: {current_date}, 主力合约: {dominant_contract}")
+                    selected_contract = self._match_to_available_contract(commodity, dominant_contract)
+                    
+                    # 只有当选择的合约真正发生变化时才记录
+                    if selected_contract != current_selected_contract:
+                        contract_history.append({
+                            'date': current_date,
+                            'dominant_contract': dominant_contract,
+                            'selected_contract': selected_contract,
+                            'is_initial': current_selected_contract is None  # 标记是否为初始合约
+                        })
+                        
+                        if self.verbose_logging:
+                            if current_selected_contract is None:
+                                print(f"  {current_date}: 初始合约设置为 {selected_contract}")
+                            else:
+                                print(f"  {current_date}: 合约切换 {current_selected_contract} -> {selected_contract}")
+                        
+                        current_selected_contract = selected_contract
+                
+                except Exception as e:
+                    if self.verbose_logging:
+                        print(f"  获取 {current_date} 的主力合约时出错: {str(e)}")
+                
+                current_date += datetime.timedelta(days=1)
+            
+            self.dominant_contract_history[commodity] = contract_history
+        
+        if self.verbose_logging:
+            total_changes = sum(len(history) for history in self.dominant_contract_history.values())
+            actual_switches = sum(
+                sum(1 for change in history if not change.get('is_initial', False))
+                for history in self.dominant_contract_history.values()
+            )
+            initial_setups = total_changes - actual_switches
+            print(f"主力合约历史构建完成,共 {total_changes} 次记录({initial_setups} 次初始设置,{actual_switches} 次真实切换)")
+        
+        return self.dominant_contract_history
+    
+    def _match_to_available_contract(self, commodity, dominant_contract):
+        """将主力合约匹配到可用合约列表"""
+        available_contracts = self.core_commodities.get(commodity, [])
+        
+        if dominant_contract in available_contracts:
+            return dominant_contract
+        else:
+            return self._select_nearest_future_contract(commodity, dominant_contract, available_contracts)
+    
+    def collect_price_data(self):
+        """收集所有可能用到的合约价格数据(优化日期范围)"""
+        if self.verbose_logging:
+            print("\n=== 步骤3: 收集价格数据(优化日期范围) ===")
+        
+        # 清除之前的调整建议
+        if hasattr(self, 'adjustment_suggestions'):
+            self.adjustment_suggestions = []
+        
+        # 为每个商品创建数据存储结构
+        for commodity in self.core_commodities.keys():
+            print(f'收集{commodity}的价格数据:')
+            self.price_data[commodity] = {}
+            
+            # 根据主力合约历史确定每个合约的数据获取范围
+            contract_date_ranges = self._determine_contract_date_ranges(commodity)
+            
+            for contract, date_range in contract_date_ranges.items():
+                start_date, end_date = date_range
+                
+                if self.verbose_logging:
+                    print(f"获取 {contract} 价格数据...")
+                    print(f"  优化日期范围: {start_date} 至 {end_date}")
+                
+                try:
+                    # 获取价格数据(使用优化的日期范围)
+                    data = get_price(
+                        contract,
+                        start_date=start_date,
+                        end_date=end_date,
+                        frequency='daily',
+                        fields=['open', 'close', 'high', 'low', 'volume'],
+                        skip_paused=False,
+                        panel=False
+                    )
+                    
+                    if data is not None and len(data) > 0:
+                        # print(f"第一条有数据的日期是: {data.index[0].date()},数据是: {data.iloc[0]}")
+                        # print(f"最后一条有数据的日期是: {data.index[-1].date()}, 数据是: {data.iloc[-1]}")
+                        self.price_data[commodity][contract] = data
+
+                        # 检查这个数据里有多少条空值数据
+                        empty_data = data[data.isna().any(axis=1)]
+                        
+                        # 检查有效交易日数据并收集调整建议
+                        adjustment_info = self._check_thirty_day_trading_data(commodity, contract, data, start_date, end_date)
+                        if adjustment_info and adjustment_info.get('needs_adjustment'):
+                            # 暂存调整建议,稍后统一处理
+                            if not hasattr(self, 'adjustment_suggestions'):
+                                self.adjustment_suggestions = []
+                            self.adjustment_suggestions.append(adjustment_info)
+                        
+                        if self.verbose_logging:
+                            print(f"  ✅ 成功获取{len(data)}条数据记录")
+                            print(f"  空值数据: {len(empty_data)}条")
+                            print(f"  价格范围: {data['low'].min():.2f} - {data['high'].max():.2f}")
+                            print(f"  数据日期范围: {data.index[0].date()} 至 {data.index[-1].date()}")
+                    else:
+                        if self.verbose_logging:
+                            print(f"  ⚠️  未获取到{contract}的数据")
+                        
+                        # 如果优化日期范围没有数据,尝试使用更宽泛的日期范围
+                        if self.verbose_logging:
+                            print(f"  尝试使用更宽泛的日期范围获取数据...")
+                        
+                        try:
+                            fallback_data = get_price(
+                                contract,
+                                start_date=self.start_date,
+                                end_date=self.end_date,
+                                frequency='daily',
+                                fields=['open', 'close', 'high', 'low', 'volume'],
+                                skip_paused=False,
+                                panel=False
+                            )
+                            
+                            if fallback_data is not None and len(fallback_data) > 0:
+                                self.price_data[commodity][contract] = fallback_data
+                                
+                                # 检查有效交易日数据并收集调整建议(回退方案)
+                                adjustment_info = self._check_thirty_day_trading_data(commodity, contract, fallback_data, self.start_date, self.end_date)
+                                if adjustment_info and adjustment_info.get('needs_adjustment'):
+                                    if not hasattr(self, 'adjustment_suggestions'):
+                                        self.adjustment_suggestions = []
+                                    self.adjustment_suggestions.append(adjustment_info)
+                                
+                                if self.verbose_logging:
+                                    print(f"  ✅ 回退方案成功获取{len(fallback_data)}条数据记录")
+                                    print(f"  数据日期范围: {fallback_data.index[0].date()} 至 {fallback_data.index[-1].date()}")
+                            else:
+                                if self.verbose_logging:
+                                    print(f"  ❌ 回退方案也未获取到{contract}的数据")
+                        except Exception as fallback_e:
+                            if self.verbose_logging:
+                                print(f"  ❌ 回退方案出错: {str(fallback_e)}")
+                        
+                except Exception as e:
+                    if self.verbose_logging:
+                        print(f"  ❌ 获取{contract}数据时出错: {str(e)}")
+                    continue
+        
+        # 处理动态调整建议
+        if hasattr(self, 'adjustment_suggestions') and self.adjustment_suggestions:
+            self._apply_dynamic_adjustments()
+        
+        if self.verbose_logging:
+            total_contracts = sum(len(contracts) for contracts in self.price_data.values())
+            print(f"价格数据收集完成,共{total_contracts}个合约")
+        
+        return self.price_data
+    
+    def _determine_contract_date_ranges(self, commodity):
+        """
+        根据主力合约历史确定每个合约的最优数据获取日期范围
+        """
+        contract_ranges = {}
+        
+        if commodity not in self.dominant_contract_history:
+            # 如果没有主力合约历史,使用全范围
+            for contract in self.core_commodities[commodity]:
+                contract_ranges[contract] = (self.start_date, self.end_date)
+            return contract_ranges
+        
+        contract_history = self.dominant_contract_history[commodity]
+        
+        # 分析每个合约的活跃期间
+        for contract in self.core_commodities[commodity]:
+            contract_start = self.start_date
+            contract_end = self.end_date
+            
+            # 查找该合约在主力合约历史中的使用时间段
+            for i, history_record in enumerate(contract_history):
+                if history_record['selected_contract'] == contract:
+                    # 该合约开始使用的日期
+                    if history_record.get('is_initial', False):
+                        # 初始设置的合约,从分析开始日期或历史记录日期开始
+                        contract_start = max(self.start_date.date(), history_record['date'])
+                    else:
+                        # 切换到的合约,从切换日期开始
+                        contract_start = history_record['date']
+                    
+                    # 查找该合约结束使用的日期
+                    for j in range(i + 1, len(contract_history)):
+                        next_record = contract_history[j]
+                        if next_record['selected_contract'] != contract:
+                            # 找到下一次切换,该合约在此日期结束使用
+                            contract_end = next_record['date']
+                            break
+                    else:
+                        # 该合约一直使用到分析结束
+                        contract_end = self.end_date.date()
+                    
+                    break
+            
+            # 转换为datetime格式并添加缓冲区
+            if isinstance(contract_start, datetime.date):
+                contract_start = datetime.datetime.combine(contract_start, datetime.time.min)
+            if isinstance(contract_end, datetime.date):
+                contract_end = datetime.datetime.combine(contract_end, datetime.time.max)
+            
+            # 添加缓冲期以确保有足够的历史数据满足最低交易日要求
+            # 使用REQUIRED_TRADING_DAYS作为缓冲,保证数据充足性
+            contract_start_buffered = contract_start - datetime.timedelta(days=self.config.REQUIRED_TRADING_DAYS)
+            contract_end_buffered = contract_end # + datetime.timedelta(days=self.config.REQUIRED_TRADING_DAYS)
+            
+            # 确保不超出总体分析范围
+            contract_start_final = max(contract_start_buffered, self.start_date)
+            contract_end_final = min(contract_end_buffered, self.end_date)
+            
+            contract_ranges[contract] = (contract_start_final, contract_end_final)
+            
+            if self.verbose_logging:
+                print(f"  {contract}: {contract_start_final.date()} 至 {contract_end_final.date()}")
+        
+        return contract_ranges
+    
+    def _check_thirty_day_trading_data(self, commodity, contract, data, start_date, end_date):
+        """
+        检查合约是否有足够的有效交易日数据并进行动态调整
+        返回调整建议信息
+        """
+        if data is None or len(data) == 0:
+            print(f"    ⚠️  {contract}: 无价格数据")
+            return None
+        
+        required_days = self.config.REQUIRED_TRADING_DAYS
+        
+        # 检查空值数据
+        empty_data = data[data.isna().any(axis=1)]
+        empty_count = len(empty_data)
+        
+        # 过滤出非空的收盘价数据
+        valid_close_data = data['close'].dropna()
+        valid_count = len(valid_close_data)
+        
+        print(f"    📊 {contract}: 有效收盘价数据共{valid_count}天")
+        
+        adjustment_info = {
+            'contract': contract,
+            'commodity': commodity,
+            'empty_count': empty_count,
+            'valid_count': valid_count,
+            'required_days': required_days,
+            'needs_adjustment': False,
+            'suggested_switch_date': None
+        }
+        
+        # 检查是否有空值数据且需要调整
+        if empty_count > 0:
+            print(f"    ⚠️  {contract}: 检测到{empty_count}条空值数据")
+            
+            if valid_count >= required_days:
+                # 找到第N个有效收盘价的日期
+                nth_date = valid_close_data.index[required_days - 1]  # 索引从0开始
+                nth_price = valid_close_data.iloc[required_days - 1]
+                
+                print(f"    📍 {contract}: 第{required_days}个有效收盘价日期为{nth_date.date()},价格{nth_price:.2f}")
+                
+                # 检查当前切换日期是否需要调整
+                if commodity in self.dominant_contract_history:
+                    for history_record in self.dominant_contract_history[commodity]:
+                        if (history_record['selected_contract'] == contract and 
+                            not history_record.get('is_initial', False)):
+                            current_switch_date = history_record['date']
+                            
+                            # 转换日期格式进行比较
+                            if isinstance(current_switch_date, datetime.date):
+                                current_switch_datetime = datetime.datetime.combine(current_switch_date, datetime.time.min)
+                            else:
+                                current_switch_datetime = current_switch_date
+                            
+                            if nth_date > current_switch_datetime:
+                                print(f"    ❌ {contract}: 切换日期过早(当前:{current_switch_date}),建议调整至{nth_date.date()}")
+                                adjustment_info.update({
+                                    'needs_adjustment': True,
+                                    'suggested_switch_date': nth_date.date(),
+                                    'current_switch_date': current_switch_date
+                                })
+                            else:
+                                print(f"    ✅ {contract}: 切换日期{current_switch_date}合理,在第{required_days}个有效交易日之后")
+                            break
+            else:
+                print(f"    ❌ {contract}: 有效交易日不足{required_days}天(仅{valid_count}天),不符合切换要求")
+                adjustment_info['needs_adjustment'] = True
+        else:
+            # 没有空值数据,检查是否有足够的交易日
+            if valid_count >= required_days:
+                nth_date = valid_close_data.index[required_days - 1]
+                nth_price = valid_close_data.iloc[required_days - 1]
+                print(f"    ✅ {contract}: 第{required_days}个有效收盘价日期为{nth_date.date()},价格{nth_price:.2f}")
+            else:
+                print(f"    ❌ {contract}: 有效交易日不足{required_days}天(仅{valid_count}天)")
+                
+        return adjustment_info
+    
+    def _apply_dynamic_adjustments(self):
+        """应用动态调整建议,更新合约切换日期并重新获取数据"""
+        if self.verbose_logging:
+            print(f"\n=== 应用动态调整建议(共{len(self.adjustment_suggestions)}个) ===")
+        
+        adjustments_applied = []
+        
+        for suggestion in self.adjustment_suggestions:
+            if suggestion.get('suggested_switch_date'):
+                commodity = suggestion['commodity']
+                contract = suggestion['contract']
+                new_switch_date = suggestion['suggested_switch_date']
+                
+                print(f"📅 调整{commodity}的{contract}切换日期至{new_switch_date}")
+                
+                # 更新合约历史
+                if self._update_contract_switch_date(commodity, contract, new_switch_date):
+                    adjustments_applied.append(suggestion)
+        
+        # 如果有调整,重新获取相关的价格数据
+        if adjustments_applied:
+            print(f"✅ 完成{len(adjustments_applied)}个调整,重新获取相关价格数据")
+            self._refresh_price_data_for_adjustments(adjustments_applied)
+    
+    def _update_contract_switch_date(self, commodity, contract, new_switch_date):
+        """更新指定合约的切换日期"""
+        if commodity not in self.dominant_contract_history:
+            return False
+        
+        # 查找并更新对应的历史记录
+        for history_record in self.dominant_contract_history[commodity]:
+            if (history_record['selected_contract'] == contract and 
+                not history_record.get('is_initial', False)):
+                old_date = history_record['date']
+                history_record['date'] = new_switch_date
+                print(f"    📝 {contract}: 切换日期从{old_date}更新为{new_switch_date}")
+                return True
+        
+        return False
+    
+    def _refresh_price_data_for_adjustments(self, adjustments):
+        """为调整的合约重新获取价格数据"""
+        affected_commodities = set()
+        
+        for adjustment in adjustments:
+            commodity = adjustment['commodity']
+            affected_commodities.add(commodity)
+        
+        for commodity in affected_commodities:
+            print(f"🔄 重新获取{commodity}的价格数据...")
+            
+            # 重新计算日期范围
+            contract_date_ranges = self._determine_contract_date_ranges(commodity)
+            
+            # 重新获取每个合约的数据
+            for contract, date_range in contract_date_ranges.items():
+                start_date, end_date = date_range
+                
+                try:
+                    # 获取价格数据(使用新的日期范围)
+                    data = get_price(
+                        contract,
+                        start_date=start_date,
+                        end_date=end_date,
+                        frequency='daily',
+                        fields=['open', 'close', 'high', 'low', 'volume'],
+                        skip_paused=False,
+                        panel=False
+                    )
+                    
+                    if data is not None and len(data) > 0:
+                        self.price_data[commodity][contract] = data
+                        
+                        # 检查调整后的数据
+                        empty_data = data[data.isna().any(axis=1)]
+                        empty_count = len(empty_data)
+                        
+                        print(f"    ✅ {contract}: 重新获取{len(data)}条数据记录,空值{empty_count}条")
+                        
+                        if empty_count == 0:
+                            print(f"    🎉 {contract}: 空值数据已消除")
+                        
+                except Exception as e:
+                    print(f"    ❌ 重新获取{contract}数据时出错: {str(e)}")
+
+    def simulate_with_contract_switching(self):
+        """
+        模拟带有主力合约切换逻辑的交易
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤3: 带合约切换的交易模拟 ===")
+            
+            # 打印对冲配置信息
+            if self.grid_hedge_config['enable_hedge_strategy']:
+                print("\n网格对冲策略配置:")
+                for commodity, config in self.grid_trading_config.items():
+                    start_price = config['start_price']
+                    hedge_stop_price = config.get('hedge_stop_price', 0)
+                    grid_size = config['grid_size']
+                    exit_grid_size = config['exit_grid_size']
+                    
+                    # 计算对冲范围
+                    hedge_levels = []
+                    current_grid = start_price
+                    while current_grid >= hedge_stop_price and current_grid > 0:
+                        hedge_entry = current_grid + exit_grid_size
+                        hedge_levels.append(hedge_entry)
+                        current_grid -= grid_size
+                    
+                    print(f"  {commodity}: 对冲范围 {min(hedge_levels)}-{max(hedge_levels)} "
+                          f"(对应主网格 {hedge_stop_price}-{start_price}), "
+                          f"共{len(hedge_levels)}个对冲级别")
+        
+        # 按日期顺序处理所有交易日
+        current_date = self.start_date.date()
+        end_date = self.end_date.date()
+        
+        while current_date <= end_date:
+            # 跳过非交易日
+            if current_date.weekday() >= 5:
+                current_date += datetime.timedelta(days=1)
+                continue
+            
+            # 检查每个商品的主力合约切换
+            for commodity in self.core_commodities.keys():
+                self._check_and_handle_contract_switch(commodity, current_date)
+            
+            # 处理正常的交易逻辑
+            self._process_daily_trading(current_date)
+            
+            current_date += datetime.timedelta(days=1)
+        
+        # 在交易循环结束后,计算所有未平仓头寸的最终盈亏
+        self._calculate_final_positions_pnl()
+        
+        if self.verbose_logging:
+            print("带合约切换的交易模拟完成")
+    
+    def _calculate_final_positions_pnl(self):
+        """
+        计算分析期结束时所有未平仓头寸的最终盈亏
+        将这些盈亏作为最终交易记录加入结果中
+        """
+        if self.verbose_logging:
+            print("\n=== 计算最终持仓盈亏 ===")
+        
+        final_date = self.end_date.date()
+        final_pnl_records = []
+        
+        # 添加诊断信息
+        if self.verbose_logging:
+            print(f"分析结束日期: {final_date}")
+            print(f"活跃头寸概览:")
+            for strategy_name in ['base_position', 'grid_trading', 'grid_hedge']:
+                strategy_positions = self.active_positions.get(strategy_name, {})
+                total_positions = 0
+                open_positions = 0
+                for commodity, positions in strategy_positions.items():
+                    commodity_total = len(positions)
+                    commodity_open = sum(1 for p in positions.values() if p['status'] == 'open')
+                    total_positions += commodity_total
+                    open_positions += commodity_open
+                    if commodity_total > 0:
+                        print(f"  {strategy_name} - {commodity}: 总计 {commodity_total} 个头寸, 未平仓 {commodity_open} 个")
+                        
+                        # 详细列出所有头寸信息
+                        print(f"    详细头寸列表:")
+                        for pos_id, pos_info in positions.items():
+                            status = pos_info.get('status', 'Unknown')
+                            entry_price = pos_info.get('entry_price', 'N/A')
+                            contract = pos_info.get('contract', 'N/A')
+                            entry_date = pos_info.get('entry_date', 'N/A')
+                            quantity = pos_info.get('quantity', 'N/A')
+                            print(f"      {pos_id}: 状态={status}, 合约={contract}, 开仓价格={entry_price}, 日期={entry_date}, 数量={quantity}")
+                        
+                        print(f"  {strategy_name} 策略总计: {open_positions}/{total_positions} 个未平仓头寸")
+                        
+                        # 验证头寸计数的准确性
+                        actual_count = len(positions)
+                        open_count_verify = len([p for p in positions.values() if p.get('status') == 'open'])
+                        
+                        if actual_count != commodity_total or open_count_verify != commodity_open:
+                            print(f"    ⚠️  计数不匹配!实际头寸数: {actual_count}, 预期: {commodity_total}; 实际未平仓: {open_count_verify}, 预期: {commodity_open}")
+                        
+                        # 检查是否有重复的开仓价格(同一合约同一状态)
+                        open_positions_by_price = {}
+                        for pos_id, pos_info in positions.items():
+                            if pos_info.get('status') == 'open':
+                                price = pos_info.get('entry_price')
+                                contract = pos_info.get('contract')
+                                key = f"{contract}_{price}"
+                                if key not in open_positions_by_price:
+                                    open_positions_by_price[key] = []
+                                open_positions_by_price[key].append(pos_id)
+                        
+                        # for key, pos_ids in open_positions_by_price.items():
+                        #     if len(pos_ids) > 1:
+                        #         print(f"    ⚠️  发现重复的未平仓头寸: {key} -> {pos_ids}")
+                
+                print(f"  {strategy_name} 策略总计: {open_positions}/{total_positions} 个未平仓头寸")
+        
+        for strategy_name in ['base_position', 'grid_trading', 'grid_hedge']:
+            strategy_positions = self.active_positions.get(strategy_name, {})
+            
+            for commodity, positions in strategy_positions.items():
+                # 获取当前合约和最终价格
+                current_contract = self._get_current_contract(commodity, final_date)
+                if not current_contract:
+                    if self.verbose_logging:
+                        print(f"  警告: 无法确定 {commodity} 在 {final_date} 的当前合约")
+                    continue
+                
+                final_price = self._get_price_on_date(commodity, current_contract, final_date, 'close')
+                if final_price is None:
+                    if self.verbose_logging:
+                        print(f"  警告: 无法获取 {commodity} {current_contract} 在 {final_date} 的价格")
+                    continue
+                
+                if self.verbose_logging and len(positions) > 0:
+                    print(f"  {commodity} {strategy_name}: 当前合约 {current_contract}, 结算价格 {final_price:.2f}")
+                
+                for position_id, position in positions.items():
+                    if self.verbose_logging:
+                        print(f"    检查头寸 {position_id}: 状态={position['status']}, 合约={position['contract']}, 开仓价格={position.get('entry_price', 'N/A')}")
+                    
+                    if position['status'] == 'open' and position['contract'] == current_contract:
+                        if self.verbose_logging:
+                            print(f"      匹配头寸进行结算: {position_id}")
+                            print(f"      头寸详情: 开仓日期={position.get('entry_date', 'N/A')}, 开仓价格={position['entry_price']}, 数量={position.get('quantity', 'N/A')}")
+                        # 计算最终盈亏
+                        if strategy_name == 'grid_hedge':
+                            # 网格对冲是做空
+                            profit_loss = self._calculate_futures_pnl(
+                                position['entry_price'], final_price, position['quantity'], commodity, is_long=False
+                            )
+                        else:
+                            # 基础头寸和网格交易都是做多
+                            profit_loss = self._calculate_futures_pnl(
+                                position['entry_price'], final_price, position['quantity'], commodity, is_long=True
+                            )
+                        
+                        profit_loss_pct = (final_price - position['entry_price']) / position['entry_price']
+                        if strategy_name == 'grid_hedge':
+                            profit_loss_pct = (position['entry_price'] - final_price) / position['entry_price']
+                        
+                        # 计算持有天数
+                        entry_date = datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()
+                        days_held = (final_date - entry_date).days
+                        
+                        # 创建最终持仓盈亏记录
+                        final_record = {
+                            'commodity': commodity,
+                            'contract': current_contract,
+                            'strategy': strategy_name,
+                            'entry_date': position['entry_date'],
+                            'exit_date': final_date.strftime('%Y-%m-%d'),
+                            'entry_price': position['entry_price'],
+                            'exit_price': final_price,
+                            'quantity': position['quantity'],
+                            'profit_loss': profit_loss,
+                            'profit_loss_pct': profit_loss_pct,
+                            'days_held': days_held,
+                            'exit_reason': 'final_settlement'
+                        }
+                        
+                        if self.verbose_logging:
+                            print(f"      创建最终结算记录: 头寸ID={position_id}, 开仓价格={position['entry_price']}, 结算价格={final_price:.2f}")
+                        
+                        final_pnl_records.append(final_record)
+                        
+                        # 将头寸标记为已平仓
+                        self.active_positions[strategy_name][commodity][position_id]['status'] = 'final_settled'
+                        
+                        if self.verbose_logging:
+                            print(f"  {commodity} {strategy_name} 最终结算: {position['entry_price']} -> {final_price:.2f}, 盈亏: {profit_loss:.2f}")
+        
+        # 将最终盈亏记录添加到交易结果中
+        for record in final_pnl_records:
+            strategy_name = record['strategy']
+            self.trading_results[strategy_name].append(record)
+        
+        # 更新组合策略结果
+        combined_final_records = []
+        for record in final_pnl_records:
+            if record['strategy'] in ['grid_trading', 'grid_hedge']:
+                combined_record = record.copy()
+                combined_record['strategy'] = f"combined_{record['strategy']}"
+                combined_final_records.append(combined_record)
+        
+        self.trading_results['combined'].extend(combined_final_records)
+        
+        if self.verbose_logging:
+            total_final_records = len(final_pnl_records)
+            total_final_pnl = sum(record['profit_loss'] for record in final_pnl_records)
+            print(f"最终持仓结算完成,共 {total_final_records} 个头寸,总未实现盈亏: {total_final_pnl:.2f}")
+            
+            # 显示所有最终结算记录的详情
+            if final_pnl_records:
+                print(f"最终结算记录详情:")
+                for i, record in enumerate(final_pnl_records, 1):
+                    print(f"  {i}. {record['commodity']} {record['strategy']}: {record['entry_price']} -> {record['exit_price']:.2f}, 盈亏: {record['profit_loss']:.2f}, 合约: {record['contract']}")
+    
+    def _check_and_handle_contract_switch(self, commodity, current_date):
+        """
+        检查并处理主力合约切换
+        只有真正的合约切换才会触发平仓和重新建仓,初始设置不会
+        """
+        if commodity not in self.dominant_contract_history:
+            return
+        
+        # 检查当天是否有合约变化
+        contract_changes = self.dominant_contract_history[commodity]
+        for change in contract_changes:
+            if change['date'] == current_date:
+                # 检查是否为初始合约设置
+                if change.get('is_initial', False):
+                    # 初始合约设置,不需要平仓和重新建仓,只需要启动正常交易逻辑
+                    if self.verbose_logging:
+                        print(f"\n{current_date}: {commodity} 初始合约设置为 {change['selected_contract']}")
+                    return
+                
+                # 真正的合约切换
+                old_contract = self._get_current_contract(commodity, current_date - datetime.timedelta(days=1))
+                new_contract = change['selected_contract']
+                
+                if self.verbose_logging:
+                    print(f"\n{current_date}: {commodity} 合约切换 {old_contract} -> {new_contract}")
+                
+                # 平掉旧合约的所有头寸
+                self._close_all_positions_on_switch(commodity, old_contract, current_date)
+                
+                # 在新合约中重新建仓
+                self._reestablish_positions_in_new_contract(commodity, new_contract, current_date)
+                
+                break
+    
+    def _get_current_contract(self, commodity, date):
+        """获取指定日期的当前合约"""
+        if commodity not in self.dominant_contract_history:
+            return None
+        
+        contract_changes = self.dominant_contract_history[commodity]
+        current_contract = None
+        
+        for change in contract_changes:
+            if change['date'] <= date:
+                current_contract = change['selected_contract']
+            else:
+                break
+        
+        return current_contract
+    
+    def _close_all_positions_on_switch(self, commodity, old_contract, switch_date):
+        """
+        在合约切换时平掉旧合约的所有头寸
+        """
+        if self.verbose_logging:
+            print(f"  平掉 {old_contract} 的所有头寸")
+        
+        # 获取当日收盘价
+        close_price = self._get_price_on_date(commodity, old_contract, switch_date, 'close')
+        if close_price is None:
+            if self.verbose_logging:
+                print(f"    无法获取 {switch_date} 的价格数据,跳过平仓")
+            return
+        
+        # 平掉基础头寸交易的头寸
+        if commodity in self.active_positions['base_position']:
+            positions = self.active_positions['base_position'][commodity].copy()
+            for position_id, position in positions.items():
+                if position['contract'] == old_contract and position['status'] == 'open':
+                    # 使用正确的期货盈亏计算公式(基础头寸都是多头)
+                    profit_loss = self._calculate_futures_pnl(
+                        position['entry_price'], close_price, position['quantity'], commodity, is_long=True
+                    )
+                    profit_loss_pct = (close_price - position['entry_price']) / position['entry_price']
+                    
+                    trade_record = {
+                        'commodity': commodity,
+                        'contract': old_contract,
+                        'strategy': 'base_position',
+                        'entry_date': position['entry_date'],
+                        'exit_date': switch_date.strftime('%Y-%m-%d'),
+                        'entry_price': position['entry_price'],
+                        'exit_price': close_price,
+                        'quantity': position['quantity'],
+                        'profit_loss': profit_loss,
+                        'profit_loss_pct': profit_loss_pct,
+                        'days_held': (switch_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
+                        'exit_reason': 'contract_switch'
+                    }
+                    
+                    self.trading_results['base_position'].append(trade_record)
+                    self.active_positions['base_position'][commodity][position_id]['status'] = 'closed'
+                    self.active_positions['base_position'][commodity][position_id]['close_reason'] = 'contract_switch'
+                    
+                    if self.verbose_logging:
+                        print(f"    基础头寸平仓: {position['entry_price']} -> {close_price:.2f}, 盈亏: {profit_loss:.2f}")
+        
+        # 平掉网格交易的头寸
+        if commodity in self.active_positions['grid_trading']:
+            positions = self.active_positions['grid_trading'][commodity].copy()
+            for position_id, position in positions.items():
+                if position['contract'] == old_contract and position['status'] == 'open':
+                    # 使用正确的期货盈亏计算公式(网格交易都是多头)
+                    profit_loss = self._calculate_futures_pnl(
+                        position['entry_price'], close_price, position['quantity'], commodity, is_long=True
+                    )
+                    profit_loss_pct = (close_price - position['entry_price']) / position['entry_price']
+                    
+                    trade_record = {
+                        'commodity': commodity,
+                        'contract': old_contract,
+                        'strategy': 'grid_trading',
+                        'entry_date': position['entry_date'],
+                        'exit_date': switch_date.strftime('%Y-%m-%d'),
+                        'entry_price': position['entry_price'],
+                        'exit_price': close_price,
+                        'quantity': position['quantity'],
+                        'profit_loss': profit_loss,
+                        'profit_loss_pct': profit_loss_pct,
+                        'days_held': (switch_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
+                        'exit_reason': 'contract_switch'
+                    }
+                    
+                    self.trading_results['grid_trading'].append(trade_record)
+                    self.active_positions['grid_trading'][commodity][position_id]['status'] = 'closed'
+                    self.active_positions['grid_trading'][commodity][position_id]['close_reason'] = 'contract_switch'
+                    
+                    if self.verbose_logging:
+                        print(f"    网格头寸平仓: {position['entry_price']} -> {close_price:.2f}, 盈亏: {profit_loss:.2f}")
+        
+        # 平掉网格对冲的头寸
+        if commodity in self.active_positions['grid_hedge']:
+            positions = self.active_positions['grid_hedge'][commodity].copy()
+            for position_id, position in positions.items():
+                if position['contract'] == old_contract and position['status'] == 'open':
+                    # 使用正确的期货盈亏计算公式(网格对冲是做空)
+                    profit_loss = self._calculate_futures_pnl(
+                        position['entry_price'], close_price, position['quantity'], commodity, is_long=False
+                    )
+                    profit_loss_pct = (position['entry_price'] - close_price) / position['entry_price']
+                    
+                    trade_record = {
+                        'commodity': commodity,
+                        'contract': old_contract,
+                        'strategy': 'grid_hedge',
+                        'entry_date': position['entry_date'],
+                        'exit_date': switch_date.strftime('%Y-%m-%d'),
+                        'entry_price': position['entry_price'],
+                        'exit_price': close_price,
+                        'quantity': position['quantity'],
+                        'profit_loss': profit_loss,
+                        'profit_loss_pct': profit_loss_pct,
+                        'days_held': (switch_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
+                        'exit_reason': 'contract_switch'
+                    }
+                    
+                    self.trading_results['grid_hedge'].append(trade_record)
+                    self.active_positions['grid_hedge'][commodity][position_id]['status'] = 'closed'
+                    self.active_positions['grid_hedge'][commodity][position_id]['close_reason'] = 'contract_switch'
+                    
+                    if self.verbose_logging:
+                        print(f"    对冲头寸平仓: {position['entry_price']} -> {close_price:.2f}, 盈亏: {profit_loss:.2f}")
+    
+    def _reestablish_positions_in_new_contract(self, commodity, new_contract, switch_date):
+        """
+        在新合约中重新建仓
+        """
+        if self.verbose_logging:
+            print(f"  在 {new_contract} 中重新建仓")
+        
+        # 获取当日收盘价
+        close_price = self._get_price_on_date(commodity, new_contract, switch_date, 'close')
+        if close_price is None:
+            if self.verbose_logging:
+                print(f"    无法获取 {switch_date} 的价格数据,跳过重新建仓")
+            return
+        
+        # 基础头寸交易重新建仓
+        self._reestablish_base_positions(commodity, new_contract, close_price, switch_date)
+        
+        # 网格交易重新建仓
+        self._reestablish_grid_positions(commodity, new_contract, close_price, switch_date)
+        
+        # 网格对冲重新建仓
+        self._reestablish_hedge_positions(commodity, new_contract, close_price, switch_date)
+    
+    def _reestablish_base_positions(self, commodity, new_contract, close_price, switch_date):
+        """重新建立基础头寸"""
+        if commodity not in self.base_position_grid:
+            return
+        
+        # 获取之前被平掉的基础头寸信息(按价格水平记录)
+        closed_positions = {}  # price_level -> quantity
+        if commodity in self.active_positions['base_position']:
+            for position in self.active_positions['base_position'][commodity].values():
+                if position['status'] == 'closed' and 'contract_switch' in position.get('close_reason', ''):
+                    # 只处理因合约切换而平掉的头寸
+                    original_price = position.get('original_price_level', position['entry_price'])
+                    if original_price not in closed_positions:
+                        closed_positions[original_price] = 0
+                    closed_positions[original_price] += position['quantity']
+                    
+                    if self.verbose_logging:
+                        print(f"    发现需重建的基础头寸: {original_price}水平 {position['quantity']}手 (原合约: {position['contract']})")
+        
+        # 根据当前价格和原始价格水平重建头寸
+        price_grid = self.base_position_grid[commodity]
+        reestablish_count = 0
+        
+        for target_price, configured_quantity in price_grid.items():
+            # 只有当目标价格大于等于当前价格时才重建头寸
+            # 这确保了只重建"应该持有"的价格水平头寸
+            if target_price >= close_price:
+                # 检查是否有该价格水平的平仓头寸需要重建
+                if target_price in closed_positions:
+                    quantity_to_reestablish = closed_positions[target_price]
+                    if self.verbose_logging:
+                        print(f"    重建条件检查: {target_price} >= {close_price:.2f} ✓ (重建原有平仓头寸)")
+                else:
+                    # 当前价格低于目标价格,应该建立该价格水平的头寸
+                    quantity_to_reestablish = configured_quantity
+                    if self.verbose_logging:
+                        print(f"    重建条件检查: {target_price} >= {close_price:.2f} ✓ (建立新头寸)")
+            else:
+                # 当前价格高于目标价格,不重建
+                if self.verbose_logging:
+                    print(f"    重建条件检查: {target_price} >= {close_price:.2f} ✗ (跳过重建)")
+                continue
+            
+            if quantity_to_reestablish > 0:
+                position_id = f"{commodity}_{new_contract}_{switch_date}_base_reestablish_{target_price}"
+                
+                if commodity not in self.active_positions['base_position']:
+                    self.active_positions['base_position'][commodity] = {}
+                
+                self.active_positions['base_position'][commodity][position_id] = {
+                    'contract': new_contract,
+                    'entry_date': switch_date.strftime('%Y-%m-%d'),
+                    'entry_price': close_price,  # 实际成交价格
+                    'original_price_level': target_price,  # 原始价格水平
+                    'quantity': quantity_to_reestablish,
+                    'status': 'open',
+                    'exit_target': self.base_position_exit_price.get(commodity)
+                }
+                
+                if self.verbose_logging:
+                    print(f"      创建重建头寸: {position_id}")
+                    print(f"      实际成交价格: {close_price}, 原始价格水平: {target_price}, 数量: {quantity_to_reestablish}")
+                
+                reestablish_count += quantity_to_reestablish
+                
+                if self.verbose_logging:
+                    print(f"    重建基础头寸 {target_price}水平: {quantity_to_reestablish} 手 @ {close_price:.2f}")
+        
+        if reestablish_count > 0 and self.verbose_logging:
+            print(f"    基础头寸重建完成,总计: {reestablish_count} 手")
+    
+    def _reestablish_grid_positions(self, commodity, new_contract, close_price, switch_date):
+        """重新建立网格交易头寸"""
+        if commodity not in self.grid_trading_config:
+            return
+        
+        config = self.grid_trading_config[commodity]
+        grid_size = config['grid_size']
+        quantity_per_grid = config['quantity_per_grid']
+        exit_grid_size = config['exit_grid_size']
+        
+        # 获取之前的网格头寸信息
+        previous_grid_levels = set()
+        if commodity in self.active_positions['grid_trading']:
+            for position in self.active_positions['grid_trading'][commodity].values():
+                if position['status'] == 'closed' and 'contract_switch' in position.get('close_reason', ''):
+                    # 只处理因合约切换而平掉的头寸
+                    previous_grid_levels.add(position['entry_price'])
+                    
+                    if self.verbose_logging:
+                        print(f"    发现需重建的网格头寸: {position['entry_price']}水平 {position['quantity']}手")
+        
+        # 仅在原始网格开仓价格大于等于当前价格时重新建仓
+        # 这确保了只重建"应该持有"的网格水平头寸
+        reestablish_count = 0
+        for grid_level in previous_grid_levels:
+            if grid_level >= close_price:
+                if self.verbose_logging:
+                    print(f"    网格重建条件检查: {grid_level} >= {close_price:.2f} ✓ (重建网格头寸)")
+                position_id = f"{commodity}_{new_contract}_{switch_date}_grid_{grid_level}"
+                
+                if commodity not in self.active_positions['grid_trading']:
+                    self.active_positions['grid_trading'][commodity] = {}
+                
+                self.active_positions['grid_trading'][commodity][position_id] = {
+                    'contract': new_contract,
+                    'entry_date': switch_date.strftime('%Y-%m-%d'),
+                    'entry_price': close_price,
+                    'original_grid_level': grid_level,
+                    'quantity': quantity_per_grid,
+                    'status': 'open',
+                    'exit_target': grid_level + exit_grid_size  # 保持原始退出价格
+                }
+                
+                reestablish_count += 1
+            else:
+                if self.verbose_logging:
+                    print(f"    网格重建条件检查: {grid_level} >= {close_price:.2f} ✗ (跳过重建)")
+                continue
+        
+        # 同时检查是否需要开立新的网格头寸(价格更低的情况)
+        start_price = config['start_price']
+        current_level = start_price
+        while current_level > close_price:
+            current_level -= grid_size
+            if current_level not in previous_grid_levels and current_level > 0:
+                # 这是一个新的网格水平
+                position_id = f"{commodity}_{new_contract}_{switch_date}_grid_new_{current_level}"
+                
+                if commodity not in self.active_positions['grid_trading']:
+                    self.active_positions['grid_trading'][commodity] = {}
+                
+                self.active_positions['grid_trading'][commodity][position_id] = {
+                    'contract': new_contract,
+                    'entry_date': switch_date.strftime('%Y-%m-%d'),
+                    'entry_price': close_price,
+                    'original_grid_level': current_level,
+                    'quantity': quantity_per_grid,
+                    'status': 'open',
+                    'exit_target': current_level + exit_grid_size
+                }
+                
+                reestablish_count += 1
+        
+        if self.verbose_logging and reestablish_count > 0:
+            print(f"    重建网格头寸: {reestablish_count} 个网格 @ {close_price:.2f}")
+    
+    def _reestablish_hedge_positions(self, commodity, new_contract, close_price, switch_date):
+        """重新建立网格对冲头寸"""
+        if not self.grid_hedge_config['enable_hedge_strategy'] or commodity not in self.grid_trading_config:
+            return
+        
+        config = self.grid_trading_config[commodity]
+        grid_size = config['grid_size']
+        quantity_per_grid = config['quantity_per_grid']
+        stop_loss_pct = self.grid_hedge_config['stop_loss_percentage']
+        
+        # 获取之前的对冲头寸信息
+        previous_hedge_levels = set()
+        if commodity in self.active_positions['grid_hedge']:
+            for position in self.active_positions['grid_hedge'][commodity].values():
+                if position['status'] == 'closed' and 'contract_switch' in position.get('close_reason', ''):
+                    # 只处理因合约切换而平掉的头寸
+                    previous_hedge_levels.add(position['entry_price'])
+                    
+                    if self.verbose_logging:
+                        print(f"    发现需重建的对冲头寸: {position['entry_price']}水平 {position['quantity']}手")
+        
+        # 仅在当前价格高于原始对冲开仓价格时重新建仓
+        # 对冲是做空策略,只有价格高于对冲水平时才适合重建做空头寸
+        reestablish_count = 0
+        for hedge_level in previous_hedge_levels:
+            if close_price > hedge_level:
+                if self.verbose_logging:
+                    print(f"    对冲重建条件检查: {close_price:.2f} > {hedge_level} ✓ (重建对冲头寸)")
+                position_id = f"{commodity}_{new_contract}_{switch_date}_hedge_{hedge_level}"
+                
+                if commodity not in self.active_positions['grid_hedge']:
+                    self.active_positions['grid_hedge'][commodity] = {}
+                
+                self.active_positions['grid_hedge'][commodity][position_id] = {
+                    'contract': new_contract,
+                    'entry_date': switch_date.strftime('%Y-%m-%d'),
+                    'entry_price': close_price,
+                    'original_grid_level': hedge_level,
+                    'quantity': quantity_per_grid,
+                    'status': 'open',
+                    'profit_target': hedge_level - grid_size,  # 保持原始退出价格
+                    'stop_loss': close_price * (1 + stop_loss_pct)
+                }
+                
+                reestablish_count += 1
+            else:
+                if self.verbose_logging:
+                    print(f"    对冲重建条件检查: {close_price:.2f} > {hedge_level} ✗ (跳过重建)")
+                continue
+        
+        if self.verbose_logging and reestablish_count > 0:
+            print(f"    重建对冲头寸: {reestablish_count} 个网格 @ {close_price:.2f}")
+    
+    def _get_price_on_date(self, commodity, contract, date, price_type='close'):
+        """获取指定日期和合约的价格(增强NaN问题诊断)"""
+        if commodity not in self.price_data or contract not in self.price_data[commodity]:
+            if self.verbose_logging:
+                print(f"  ❌ 价格数据不存在: {commodity} -> {contract}")
+            return None
+        
+        price_data = self.price_data[commodity][contract]
+        
+        # 找到日期对应的价格
+        target_date = date if isinstance(date, datetime.date) else date.date()
+        
+        for idx, row in price_data.iterrows():
+            if idx.date() == target_date:
+                price_value = row[price_type]
+                
+                if self.verbose_logging:
+                    print(f'{price_type}的价格是: {price_value}')
+                
+                # 如果价格为NaN,进行详细诊断
+                if pd.isna(price_value):
+                    self._diagnose_nan_price_issue(commodity, contract, target_date, price_type, row)
+                    return None
+                else:
+                    return price_value
+        
+        # 如果没有找到精确日期,尝试查找最近的交易日
+        if self.verbose_logging:
+            print(f"  ⚠️  未找到 {contract} 在 {target_date} 的数据,尝试查找最近交易日...")
+        
+        return self._get_nearest_trading_day_price(commodity, contract, target_date, price_type)
+    
+    def _diagnose_nan_price_issue(self, commodity, contract, date, price_type, row):
+        """诊断NaN价格问题的根本原因"""
+        if self.verbose_logging:
+            print(f"  🔍 NaN价格问题诊断: {commodity} {contract} {date}")
+            print(f"    目标价格类型: {price_type}")
+            print(f"    该日所有价格数据: 开盘={row['open']}, 收盘={row['close']}, 最高={row['high']}, 最低={row['low']}, 成交量={row['volume']}")
+            
+            # 检查是否所有价格都是NaN
+            price_fields = ['open', 'close', 'high', 'low']
+            nan_fields = [field for field in price_fields if pd.isna(row[field])]
+            valid_fields = [field for field in price_fields if not pd.isna(row[field])]
+            
+            if len(nan_fields) == len(price_fields):
+                print(f"    ❌ 所有价格字段都为NaN - 可能该合约在此日期未开始交易")
+            else:
+                print(f"    ⚠️  部分价格字段为NaN: {nan_fields}")
+                print(f"    ✅ 有效价格字段: {valid_fields}")
+                
+                # 如果有有效价格,尝试使用替代方案
+                if valid_fields:
+                    fallback_price = row[valid_fields[0]]
+                    print(f"    💡 建议使用替代价格: {valid_fields[0]} = {fallback_price}")
+            
+            # 检查成交量是否为0或NaN
+            if pd.isna(row['volume']) or row['volume'] == 0:
+                print(f"    ⚠️  成交量异常: {row['volume']} - 可能该合约在此日期无交易活动")
+            
+            # 检查是否是合约刚上市的情况
+            price_data = self.price_data[commodity][contract]
+            first_valid_date = None
+            for idx, data_row in price_data.iterrows():
+                if not pd.isna(data_row['close']):
+                    first_valid_date = idx.date()
+                    break
+            
+            if first_valid_date and date < first_valid_date:
+                print(f"    🔍 合约首次有效交易日: {first_valid_date} (查询日期 {date} 早于首次交易日)")
+                print(f"    💡 建议: 合约 {contract} 在 {date} 可能尚未开始交易")
+            
+            # 提供解决建议
+            print(f"    📋 解决建议:")
+            print(f"      1. 检查合约 {contract} 的上市日期")
+            print(f"      2. 验证合约代码是否正确")
+            print(f"      3. 考虑调整合约切换日期")
+            if valid_fields:
+                print(f"      4. 临时使用替代价格: {valid_fields[0]} = {row[valid_fields[0]]}")
+    
+    def _get_nearest_trading_day_price(self, commodity, contract, target_date, price_type):
+        """获取最近交易日的价格"""
+        price_data = self.price_data[commodity][contract]
+        
+        # 查找最近的交易日(前后5天范围内)
+        search_range = 5
+        for offset in range(1, search_range + 1):
+            # 先查找之后的日期
+            future_date = target_date + datetime.timedelta(days=offset)
+            for idx, row in price_data.iterrows():
+                if idx.date() == future_date:
+                    price_value = row[price_type]
+                    if not pd.isna(price_value):
+                        if self.verbose_logging:
+                            print(f"  ✅ 使用后续交易日 {future_date} 的价格: {price_value}")
+                        return price_value
+                    break
+            
+            # 再查找之前的日期
+            past_date = target_date - datetime.timedelta(days=offset)
+            for idx, row in price_data.iterrows():
+                if idx.date() == past_date:
+                    price_value = row[price_type]
+                    if not pd.isna(price_value):
+                        if self.verbose_logging:
+                            print(f"  ✅ 使用前期交易日 {past_date} 的价格: {price_value}")
+                        return price_value
+                    break
+        
+        if self.verbose_logging:
+            print(f"  ❌ 在 {search_range} 天范围内未找到有效的 {price_type} 价格")
+        return None
+    
+    def _process_daily_trading(self, current_date):
+        """处理每日的正常交易逻辑"""
+        for commodity in self.core_commodities.keys():
+            current_contract = self._get_current_contract(commodity, current_date)
+            if not current_contract:
+                continue
+            
+            # 获取当日价格数据
+            daily_prices = self._get_daily_prices(commodity, current_contract, current_date)
+            if not daily_prices:
+                continue
+            
+            # 检查基础头寸入场和退出机会
+            self._check_base_position_trading(commodity, current_contract, current_date, daily_prices)
+            
+            # 检查网格交易入场和退出机会
+            self._check_grid_trading(commodity, current_contract, current_date, daily_prices)
+            
+            # 检查网格对冲入场和退出机会
+            self._check_grid_hedge_trading(commodity, current_contract, current_date, daily_prices)
+    
+    def _get_daily_prices(self, commodity, contract, date):
+        """获取指定日期的价格数据"""
+        if commodity not in self.price_data or contract not in self.price_data[commodity]:
+            return None
+        
+        price_data = self.price_data[commodity][contract]
+        
+        for idx, row in price_data.iterrows():
+            if idx.date() == date:
+                return {
+                    'open': row['open'],
+                    'close': row['close'],
+                    'high': row['high'],
+                    'low': row['low'],
+                    'volume': row['volume']
+                }
+        
+        return None
+    
+    def _check_base_position_trading(self, commodity, contract, current_date, daily_prices):
+        """检查基础头寸交易机会
+        
+        逻辑:每日主动检查所有网格水平
+        - 如果当前收盘价低于某个网格水平价格
+        - 且该网格水平没有未平仓头寸
+        - 则以当日收盘价在该网格水平开仓
+        """
+        if commodity not in self.base_position_grid:
+            return
+        
+        # 检查入场机会 - 遍历所有网格水平
+        price_grid = self.base_position_grid[commodity]
+        current_close_price = daily_prices['close']
+        
+        for entry_price, quantity in price_grid.items():
+            # 检查是否已经有这个价格水平的头寸
+            position_exists = False
+            if commodity in self.active_positions['base_position']:
+                for position in self.active_positions['base_position'][commodity].values():
+                    if (position['contract'] == contract and position['status'] == 'open'):
+                        # 检查原始价格水平
+                        position_price_level = position.get('original_price_level', position['entry_price'])
+                        if position_price_level == entry_price:
+                            position_exists = True
+                            break
+            
+            # 主动开仓逻辑:当前价格低于网格水平价格 且 该水平没有头寸
+            if not position_exists and current_close_price < entry_price:
+                # 以当日收盘价建立头寸
+                position_id = f"{commodity}_{contract}_{current_date}_base_{entry_price}"
+                if commodity not in self.active_positions['base_position']:
+                    self.active_positions['base_position'][commodity] = {}
+                
+                self.active_positions['base_position'][commodity][position_id] = {
+                    'contract': contract,
+                    'entry_date': current_date.strftime('%Y-%m-%d'),
+                    'entry_price': current_close_price,  # 使用收盘价作为入场价格
+                    'original_price_level': entry_price,  # 记录原始网格水平
+                    'quantity': quantity,
+                    'status': 'open',
+                    'exit_target': self.base_position_exit_price.get(commodity)
+                }
+                
+                if self.verbose_logging:
+                    print(f"  {current_date}: {commodity} 基础头寸入场 @ 网格{entry_price} (实际价格: {current_close_price:.2f}), 数量: {quantity},当天收盘价{current_close_price:.2f}低于网格价格{entry_price}")
+        
+        # 检查退出机会
+        exit_target = self.base_position_exit_price.get(commodity)
+        if exit_target and commodity in self.active_positions['base_position']:
+            positions_to_close = []
+            for position_id, position in self.active_positions['base_position'][commodity].items():
+                if position['contract'] == contract and position['status'] == 'open' and daily_prices['high'] >= exit_target:
+                    positions_to_close.append(position_id)
+            
+            for position_id in positions_to_close:
+                position = self.active_positions['base_position'][commodity][position_id]
+                exit_price = min(exit_target, daily_prices['high'])
+                
+                # 使用正确的期货盈亏计算公式(基础头寸都是多头)
+                profit_loss = self._calculate_futures_pnl(
+                    position['entry_price'], exit_price, position['quantity'], commodity, is_long=True
+                )
+                profit_loss_pct = (exit_price - position['entry_price']) / position['entry_price']
+                
+                trade_record = {
+                    'commodity': commodity,
+                    'contract': contract,
+                    'strategy': 'base_position',
+                    'entry_date': position['entry_date'],
+                    'exit_date': current_date.strftime('%Y-%m-%d'),
+                    'entry_price': position['entry_price'],
+                    'exit_price': exit_price,
+                    'quantity': position['quantity'],
+                    'profit_loss': profit_loss,
+                    'profit_loss_pct': profit_loss_pct,
+                    'days_held': (current_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
+                    'exit_reason': 'target_reached'
+                }
+                
+                self.trading_results['base_position'].append(trade_record)
+                self.active_positions['base_position'][commodity][position_id]['status'] = 'closed'
+                
+                if self.verbose_logging:
+                    print(f"  {current_date}: {commodity} 基础头寸退出 {position['entry_price']} -> {exit_price:.2f}, 盈亏: {profit_loss:.2f}")
+    
+    def _check_grid_trading(self, commodity, contract, current_date, daily_prices):
+        """检查网格交易机会"""
+        if commodity not in self.grid_trading_config:
+            return
+        
+        config = self.grid_trading_config[commodity]
+        start_price = config['start_price']
+        grid_size = config['grid_size']
+        quantity_per_grid = config['quantity_per_grid']
+        exit_grid_size = config['exit_grid_size']
+        
+        # 检查入场机会
+        current_level = start_price
+        while current_level > daily_prices['low'] and current_level > 0:
+            position_exists = False
+            if commodity in self.active_positions['grid_trading']:
+                for position in self.active_positions['grid_trading'][commodity].values():
+                    if (position['contract'] == contract and 
+                        position.get('original_grid_level', position['entry_price']) == current_level and 
+                        position['status'] == 'open'):
+                        position_exists = True
+                        break
+            
+            if not position_exists and daily_prices['low'] <= current_level <= daily_prices['high']:
+                position_id = f"{commodity}_{contract}_{current_date}_grid_{current_level}"
+                if commodity not in self.active_positions['grid_trading']:
+                    self.active_positions['grid_trading'][commodity] = {}
+                
+                self.active_positions['grid_trading'][commodity][position_id] = {
+                    'contract': contract,
+                    'entry_date': current_date.strftime('%Y-%m-%d'),
+                    'entry_price': current_level,
+                    'original_grid_level': current_level,
+                    'quantity': quantity_per_grid,
+                    'status': 'open',
+                    'exit_target': current_level + exit_grid_size
+                }
+                
+                if self.verbose_logging:
+                    print(f"  {current_date}: {commodity} 网格入场 @ {current_level},数量:{quantity_per_grid},当天最低价为{daily_prices['low']},最高价为{daily_prices['high']}")
+            
+            current_level -= grid_size
+        
+        # 检查退出机会
+        if commodity in self.active_positions['grid_trading']:
+            positions_to_close = []
+            for position_id, position in self.active_positions['grid_trading'][commodity].items():
+                if position['contract'] == contract and position['status'] == 'open':
+                    if daily_prices['high'] >= position['exit_target']:
+                        positions_to_close.append(position_id)
+            
+            for position_id in positions_to_close:
+                position = self.active_positions['grid_trading'][commodity][position_id]
+                exit_price = position['exit_target']
+                
+                # 使用正确的期货盈亏计算公式(网格交易都是多头)
+                profit_loss = self._calculate_futures_pnl(
+                    position['entry_price'], exit_price, position['quantity'], commodity, is_long=True
+                )
+                profit_loss_pct = (exit_price - position['entry_price']) / position['entry_price']
+                
+                trade_record = {
+                    'commodity': commodity,
+                    'contract': contract,
+                    'strategy': 'grid_trading',
+                    'entry_date': position['entry_date'],
+                    'exit_date': current_date.strftime('%Y-%m-%d'),
+                    'entry_price': position['entry_price'],
+                    'exit_price': exit_price,
+                    'quantity': position['quantity'],
+                    'profit_loss': profit_loss,
+                    'profit_loss_pct': profit_loss_pct,
+                    'days_held': (current_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
+                    'exit_reason': 'target_reached'
+                }
+                
+                self.trading_results['grid_trading'].append(trade_record)
+                self.active_positions['grid_trading'][commodity][position_id]['status'] = 'closed'
+                
+                if self.verbose_logging:
+                    print(f"  {current_date}: {commodity} 网格退出 {position['entry_price']} -> {exit_price:.2f}, 盈亏: {profit_loss:.2f} ({profit_loss_pct:.2%})")
+    
+    def _extend_price_data_for_ma(self, commodity, contract, current_date, required_days=30):
+        """扩展价格数据以满足MA计算需求
+        
+        参数:
+            commodity: 商品代码
+            contract: 合约代码
+            current_date: 当前日期
+            required_days: 所需的最少数据天数
+        
+        返回:
+            扩展后的价格数据DataFrame,如果获取失败则返回None
+        """
+        cache_key = f"{commodity}_{contract}"
+        
+        # 检查缓存
+        if cache_key in self.ma_extended_data_cache:
+            cached_data = self.ma_extended_data_cache[cache_key]
+            target_date = current_date if isinstance(current_date, datetime.date) else current_date.date()
+            historical_data = cached_data[cached_data.index.date <= target_date]
+            
+            if len(historical_data) >= required_days:
+                if self.verbose_logging:
+                    print(f"  ℹ️  MA过滤器:使用缓存的扩展数据,共{len(historical_data)}天")
+                return historical_data
+        
+        # 获取现有数据
+        if commodity not in self.price_data or contract not in self.price_data[commodity]:
+            return None
+        
+        existing_data = self.price_data[commodity][contract]
+        target_date = current_date if isinstance(current_date, datetime.date) else current_date.date()
+        existing_historical = existing_data[existing_data.index.date <= target_date]
+        
+        if len(existing_historical) >= required_days:
+            return existing_historical
+        
+        # 数据不足,需要扩展获取
+        existing_count = len(existing_historical)
+        shortage = required_days - existing_count
+        
+        if self.verbose_logging:
+            print(f"  📊 MA过滤器:数据不足,开始扩展获取(当前{existing_count}天,需要{required_days}天,缺少{shortage}天)")
+        
+        # 计算扩展的开始日期:从现有数据最早日期往前推至少30个交易日
+        if len(existing_data) > 0:
+            earliest_date = existing_data.index.min().date()
+        else:
+            earliest_date = target_date
+        
+        # 往前推60个自然日(约等于40-45个交易日,提供充足缓冲)
+        extended_start_date = earliest_date - datetime.timedelta(days=60)
+        extended_end_date = target_date
+        
+        if self.verbose_logging:
+            print(f"  📊 扩展日期范围: {extended_start_date} 至 {extended_end_date}")
+        
+        try:
+            # 获取扩展的价格数据
+            extended_data = get_price(
+                contract,
+                start_date=extended_start_date,
+                end_date=extended_end_date,
+                frequency='daily',
+                fields=['open', 'close', 'high', 'low', 'volume'],
+                skip_paused=False,
+                panel=False
+            )
+            
+            if extended_data is None or len(extended_data) == 0:
+                if self.verbose_logging:
+                    print(f"  ⚠️  扩展数据获取失败:未获取到数据")
+                return existing_historical
+            
+            # 合并数据:将扩展数据与现有数据合并,保留所有日期的数据
+            # 使用concat合并,然后去重并按日期排序
+            combined_data = pd.concat([existing_data, extended_data])
+            combined_data = combined_data[~combined_data.index.duplicated(keep='first')]
+            combined_data = combined_data.sort_index()
+            
+            # 过滤到当前日期(仅用于返回给MA计算)
+            combined_historical = combined_data[combined_data.index.date <= target_date]
+            
+            if self.verbose_logging:
+                print(f"  ✅ 扩展数据获取成功:从{len(existing_historical)}天扩展到{len(combined_historical)}天")
+                print(f"     合并后完整数据范围:{combined_data.index.min().date()} 至 {combined_data.index.max().date()}(共{len(combined_data)}天)")
+            
+            # 缓存扩展后的完整数据
+            self.ma_extended_data_cache[cache_key] = combined_data
+            
+            # 更新主price_data,保留原有数据和新扩展的数据
+            self.price_data[commodity][contract] = combined_data
+            
+            return combined_historical
+            
+        except Exception as e:
+            if self.verbose_logging:
+                print(f"  ⚠️  扩展数据获取异常:{str(e)}")
+            return existing_historical
+    
+    def _should_block_hedge_by_ma_filter(self, commodity, contract, current_date):
+        """检查是否应该通过移动平均线过滤器阻止对冲入场
+        
+        返回值:
+            True: 应该阻止对冲入场(满足以下趋势之一)
+            False: 允许对冲入场
+        
+        阻止条件(强下跌趋势):
+            1. 30K < 20K < 10K < 5K(所有均线呈下降顺序)
+            2. 30K < 20K < 5K < 10K(5K与10K交叉的下跌趋势)
+        """
+        try:
+            # 获取价格数据
+            if commodity not in self.price_data or contract not in self.price_data[commodity]:
+                if self.verbose_logging:
+                    print(f"  ⚠️  MA过滤器:无法获取 {commodity} {contract} 的价格数据")
+                return False  # 没有数据时不阻止
+            
+            price_data = self.price_data[commodity][contract]
+            
+            # 获取当前日期及之前的数据(确保有足够的历史数据来计算30日均线)
+            target_date = current_date if isinstance(current_date, datetime.date) else current_date.date()
+            
+            # 过滤出当前日期及之前的数据
+            historical_data = price_data[price_data.index.date <= target_date]
+            
+            # 如果数据不足,尝试扩展获取
+            if len(historical_data) < 30:
+                if self.verbose_logging:
+                    print(f"  ⚠️  MA过滤器:{commodity} {contract} 历史数据不足(需要30天,实际{len(historical_data)}天)")
+                
+                # 尝试扩展数据获取
+                extended_data = self._extend_price_data_for_ma(commodity, contract, current_date, required_days=30)
+                
+                if extended_data is not None and len(extended_data) >= 30:
+                    historical_data = extended_data
+                    if self.verbose_logging:
+                        print(f"  ✅ 扩展数据成功,现在有{len(historical_data)}天数据,继续MA过滤器检查")
+                else:
+                    if self.verbose_logging:
+                        print(f"  ⚠️  扩展数据失败,数据仍不足,阻止对冲入场")
+                    return True  # 扩展失败,数据仍不足时阻止
+            
+            # 使用收盘价计算移动平均线
+            close_prices = historical_data['close']
+            
+            # 计算最近的移动平均线值
+            ma5 = close_prices.iloc[-5:].mean()
+            ma10 = close_prices.iloc[-10:].mean()
+            ma20 = close_prices.iloc[-20:].mean()
+            ma30 = close_prices.iloc[-30:].mean()
+            
+            # 检查是否为NaN
+            if pd.isna(ma5) or pd.isna(ma10) or pd.isna(ma20) or pd.isna(ma30):
+                if self.verbose_logging:
+                    print(f"  ⚠️  MA过滤器:{commodity} {contract} 移动平均线计算结果包含NaN")
+                return True  # 计算失败时阻止
+            
+            # 检查阻止条件
+            # 条件1:30K < 20K < 10K < 5K(所有均线呈下降顺序的强上涨趋势)
+            condition1 = (ma30 < ma20) and (ma20 < ma10) and (ma10 < ma5)
+            
+            # 条件2:30K < 20K < 5K < 10K(5K与10K交叉的上涨趋势)
+            condition2 = (ma30 < ma20) and (ma20 < ma5) and (ma5 < ma10)
+            
+            should_block = condition1 or condition2
+            
+            if self.verbose_logging and should_block:
+                print(f"  🚫 MA过滤器触发:{commodity} {contract} 检测到强上涨趋势,阻止对冲入场")
+                print(f"     MA5={ma5:.2f}, MA10={ma10:.2f}, MA20={ma20:.2f}, MA30={ma30:.2f}")
+                if condition1:
+                    print(f"     触发条件1: 30K < 20K < 10K < 5K")
+                if condition2:
+                    print(f"     触发条件2: 30K < 20K < 5K < 10K")
+            
+            return should_block
+            
+        except Exception as e:
+            if self.verbose_logging:
+                print(f"  ⚠️  MA过滤器异常:{commodity} {contract} - {str(e)}")
+            return True  # 异常时阻止
+    
+    def _check_grid_hedge_trading(self, commodity, contract, current_date, daily_prices):
+        """检查网格对冲交易机会
+        
+        逻辑:独立的对冲网格系统,不依赖主网格交易执行
+        - 基于主网格结构创建独立的对冲网格
+        - 对冲网格水平 = 主网格水平 + exit_grid_size
+        - 当价格突破网格水平时(如118→119),立即触发对冲空头头寸
+        - 对冲范围受 hedge_stop_price 限制,低于该价格不触发对冲
+        
+        示例:start_price=118, hedge_stop_price=113, exit_grid_size=1
+        - 主网格水平:118, 117, 116, 115, 114, 113, 112, ...
+        - 对冲触发价格:119, 118, 117, 116, 115, 114(对应主网格113-118)
+        - 低于113的主网格不触发对冲
+        """
+        if not self.grid_hedge_config['enable_hedge_strategy'] or commodity not in self.grid_trading_config:
+            return
+        
+        config = self.grid_trading_config[commodity]
+        start_price = config['start_price']
+        grid_size = config['grid_size']
+        quantity_per_grid = config['quantity_per_grid']
+        exit_grid_size = config['exit_grid_size']
+        hedge_stop_price = config.get('hedge_stop_price', 0)  # 对冲停止价格,默认为0(不限制)
+        stop_loss_pct = self.grid_hedge_config['stop_loss_percentage']
+        
+        # 检查入场机会(独立对冲网格系统)
+        # 从start_price开始,向下遍历所有可能的网格级别
+        # 只有在 hedge_stop_price 及以上的网格级别才会触发对冲
+        current_grid_level = start_price
+        while current_grid_level >= hedge_stop_price and current_grid_level > 0:
+            # 计算该网格级别对应的对冲开仓价格(独立触发,不依赖主网格交易)
+            hedge_entry_level = current_grid_level + exit_grid_size
+            
+            # 检查是否已经有这个对冲级别的头寸
+            position_exists = False
+            if commodity in self.active_positions['grid_hedge']:
+                for position in self.active_positions['grid_hedge'][commodity].values():
+                    if (position['contract'] == contract and 
+                        position.get('original_grid_level', position['entry_price']) == hedge_entry_level and 
+                        position['status'] == 'open'):
+                        position_exists = True
+                        break
+            
+            # 当价格触及对冲级别时独立开仓(不需要等待主网格交易)
+            if not position_exists and daily_prices['low'] <= hedge_entry_level <= daily_prices['high']:
+                # 检查MA过滤器:如果启用且检测到强上涨趋势,则阻止对冲入场
+                if self.grid_hedge_config.get('use_ma_filter', False):
+                    if self._should_block_hedge_by_ma_filter(commodity, contract, current_date):
+                        if self.verbose_logging:
+                            print(f"  {current_date}: {commodity} 对冲入场被MA过滤器阻止 @ {hedge_entry_level}")
+                        # 跳过此次对冲入场
+                        current_grid_level -= grid_size
+                        continue
+                
+                position_id = f"{commodity}_{contract}_{current_date}_hedge_{hedge_entry_level}"
+                if commodity not in self.active_positions['grid_hedge']:
+                    self.active_positions['grid_hedge'][commodity] = {}
+                
+                self.active_positions['grid_hedge'][commodity][position_id] = {
+                    'contract': contract,
+                    'entry_date': current_date.strftime('%Y-%m-%d'),
+                    'entry_price': hedge_entry_level,
+                    'original_grid_level': hedge_entry_level,
+                    'base_grid_level': current_grid_level,  # 记录对应的基础网格级别
+                    'quantity': quantity_per_grid,
+                    'status': 'open',
+                    'profit_target': hedge_entry_level - grid_size,  # 对冲平仓目标:回到原网格级别
+                    'stop_loss': hedge_entry_level * (1 + stop_loss_pct)
+                }
+                
+                if self.verbose_logging:
+                    print(f"  {current_date}: {commodity} 对冲入场 @ {hedge_entry_level}(对应网格级别{current_grid_level}),数量:{quantity_per_grid},目标:{hedge_entry_level - grid_size},当天价格区间[{daily_prices['low']}, {daily_prices['high']}]")
+            
+            current_grid_level -= grid_size
+        
+        # 检查退出机会
+        if commodity in self.active_positions['grid_hedge']:
+            positions_to_close = []
+            for position_id, position in self.active_positions['grid_hedge'][commodity].items():
+                if position['contract'] == contract and position['status'] == 'open':
+                    if daily_prices['low'] <= position['profit_target']:
+                        positions_to_close.append((position_id, position['profit_target'], 'profit_target'))
+                    elif daily_prices['high'] >= position['stop_loss']:
+                        positions_to_close.append((position_id, position['stop_loss'], 'stop_loss'))
+            
+            for position_id, exit_price, exit_reason in positions_to_close:
+                position = self.active_positions['grid_hedge'][commodity][position_id]
+                
+                # 使用正确的期货盈亏计算公式(网格对冲是做空)
+                profit_loss = self._calculate_futures_pnl(
+                    position['entry_price'], exit_price, position['quantity'], commodity, is_long=False
+                )
+                profit_loss_pct = (position['entry_price'] - exit_price) / position['entry_price']
+                
+                trade_record = {
+                    'commodity': commodity,
+                    'contract': contract,
+                    'strategy': 'grid_hedge',
+                    'entry_date': position['entry_date'],
+                    'exit_date': current_date.strftime('%Y-%m-%d'),
+                    'entry_price': position['entry_price'],
+                    'exit_price': exit_price,
+                    'quantity': position['quantity'],
+                    'profit_loss': profit_loss,
+                    'profit_loss_pct': profit_loss_pct,
+                    'days_held': (current_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
+                    'exit_reason': exit_reason
+                }
+                
+                self.trading_results['grid_hedge'].append(trade_record)
+                self.active_positions['grid_hedge'][commodity][position_id]['status'] = 'closed'
+                
+                if self.verbose_logging:
+                    print(f"  {current_date}: {commodity} 对冲退出 {position['entry_price']} -> {exit_price:.2f} ({exit_reason}),数量:{position['quantity']},当天最低价为{daily_prices['low']},最高价为{daily_prices['high']}")
+    
+    def simulate_base_position_trading(self):
+        """
+        模拟基础头寸交易
+        为每种商品配置价格-数量网格,以指定价格水平和数量开立多头头寸
+        所有头寸使用统一的退出价格(无止损)
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤3: 基础头寸交易模拟 ===")
+        
+        base_position_results = []
+        
+        for commodity in self.price_data.keys():
+            if commodity not in self.base_position_grid:
+                continue
+                
+            price_grid = self.base_position_grid[commodity]
+            exit_price = self.base_position_exit_price[commodity]
+            price_data = self.price_data[commodity]['data']
+            contract = self.price_data[commodity]['contract']
+            
+            if self.verbose_logging:
+                print(f"\n分析 {commodity} ({contract}) 基础头寸交易")
+                print(f"价格网格: {price_grid}")
+                print(f"退出价格: {exit_price}")
+            
+            # 遍历每个价格水平
+            for entry_price, quantity in price_grid.items():
+                # 查找触发入场的日期
+                entry_dates = []
+                for date, row in price_data.iterrows():
+                    if row['low'] <= entry_price <= row['high']:
+                        entry_dates.append(date)
+                
+                if not entry_dates:
+                    continue
+                
+                # 使用第一个触发日期作为入场点
+                entry_date = entry_dates[0]
+                
+                # 查找退出点
+                exit_date = None
+                exit_price_actual = exit_price
+                
+                # 在入场后查找价格达到退出价格的日期
+                for date, row in price_data.iterrows():
+                    if date > entry_date and row['high'] >= exit_price:
+                        exit_date = date
+                        exit_price_actual = min(exit_price, row['high'])
+                        break
+                
+                # 如果没有达到退出价格,使用最后一日的收盘价退出
+                if exit_date is None:
+                    exit_date = price_data.index[-1]
+                    exit_price_actual = price_data.iloc[-1]['close']
+                
+                # 计算盈亏
+                profit_loss = (exit_price_actual - entry_price) * quantity
+                profit_loss_pct = (exit_price_actual - entry_price) / entry_price
+                
+                trade_record = {
+                    'commodity': commodity,
+                    'contract': contract,
+                    'strategy': 'base_position',
+                    'entry_date': entry_date.strftime('%Y-%m-%d'),
+                    'exit_date': exit_date.strftime('%Y-%m-%d'),
+                    'entry_price': entry_price,
+                    'exit_price': exit_price_actual,
+                    'quantity': quantity,
+                    'profit_loss': profit_loss,
+                    'profit_loss_pct': profit_loss_pct,
+                    'days_held': (exit_date - entry_date).days
+                }
+                
+                base_position_results.append(trade_record)
+                
+                if self.verbose_logging:
+                    print(f"  入场: {entry_date.strftime('%Y-%m-%d')} @ {entry_price}, 数量: {quantity}")
+                    print(f"  出场: {exit_date.strftime('%Y-%m-%d')} @ {exit_price_actual:.2f}")
+                    print(f"  盈亏: {profit_loss:.2f} ({profit_loss_pct:.2%})")
+        
+        self.trading_results['base_position'] = base_position_results
+        
+        if self.verbose_logging:
+            print(f"\n基础头寸交易模拟完成,共{len(base_position_results)}笔交易")
+        
+        return base_position_results
+    
+    def simulate_grid_trading(self):
+        """
+        模拟网格交易策略
+        从start_price开始,每次价格下降grid_size时买入quantity_per_grid
+        当价格从入场价格上涨exit_grid_size时退出
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤4: 网格交易策略模拟 ===")
+        
+        grid_trading_results = []
+        
+        for commodity in self.price_data.keys():
+            if commodity not in self.grid_trading_config:
+                continue
+                
+            config = self.grid_trading_config[commodity]
+            start_price = config['start_price']
+            grid_size = config['grid_size']
+            quantity_per_grid = config['quantity_per_grid']
+            exit_grid_size = config['exit_grid_size']
+            
+            price_data = self.price_data[commodity]['data']
+            contract = self.price_data[commodity]['contract']
+            
+            if self.verbose_logging:
+                print(f"\n分析 {commodity} ({contract}) 网格交易")
+                print(f"起始价格: {start_price}, 网格大小: {grid_size}")
+                print(f"每网格数量: {quantity_per_grid}, 退出网格大小: {exit_grid_size}")
+            
+            # 生成网格价格水平
+            grid_levels = []
+            current_level = start_price
+            min_price = price_data['low'].min()
+            
+            while current_level > min_price:
+                grid_levels.append(current_level)
+                current_level -= grid_size
+            
+            # 模拟每个网格水平的交易
+            for entry_price in grid_levels:
+                exit_price = entry_price + exit_grid_size
+                
+                # 查找入场机会
+                entry_date = None
+                for date, row in price_data.iterrows():
+                    if row['low'] <= entry_price <= row['high']:
+                        entry_date = date
+                        break
+                
+                if entry_date is None:
+                    continue
+                
+                # 查找退出机会
+                exit_date = None
+                exit_price_actual = exit_price
+                
+                for date, row in price_data.iterrows():
+                    if date > entry_date and row['high'] >= exit_price:
+                        exit_date = date
+                        exit_price_actual = exit_price
+                        break
+                
+                # 如果没有达到退出价格,使用最后一日收盘价
+                if exit_date is None:
+                    exit_date = price_data.index[-1]
+                    exit_price_actual = price_data.iloc[-1]['close']
+                
+                # 计算盈亏
+                profit_loss = (exit_price_actual - entry_price) * quantity_per_grid
+                profit_loss_pct = (exit_price_actual - entry_price) / entry_price
+                
+                trade_record = {
+                    'commodity': commodity,
+                    'contract': contract,
+                    'strategy': 'grid_trading',
+                    'entry_date': entry_date.strftime('%Y-%m-%d'),
+                    'exit_date': exit_date.strftime('%Y-%m-%d'),
+                    'entry_price': entry_price,
+                    'exit_price': exit_price_actual,
+                    'quantity': quantity_per_grid,
+                    'profit_loss': profit_loss,
+                    'profit_loss_pct': profit_loss_pct,
+                    'days_held': (exit_date - entry_date).days
+                }
+                
+                grid_trading_results.append(trade_record)
+                
+                if self.verbose_logging:
+                    print(f"  网格 {entry_price}: {entry_date.strftime('%Y-%m-%d')} -> {exit_date.strftime('%Y-%m-%d')}")
+                    print(f"    盈亏: {profit_loss:.2f} ({profit_loss_pct:.2%})")
+        
+        self.trading_results['grid_trading'] = grid_trading_results
+        
+        if self.verbose_logging:
+            print(f"\n网格交易模拟完成,共{len(grid_trading_results)}笔交易")
+        
+        return grid_trading_results
+    
+    def simulate_grid_hedge_strategy(self):
+        """
+        模拟网格对冲策略
+        类似于网格交易,但有额外的止损百分比
+        """
+        if not self.grid_hedge_config['enable_hedge_strategy']:
+            if self.verbose_logging:
+                print("\n网格对冲策略已禁用,跳过模拟")
+            return []
+        
+        if self.verbose_logging:
+            print("\n=== 步骤5: 网格对冲策略模拟 ===")
+        
+        grid_hedge_results = []
+        stop_loss_pct = self.grid_hedge_config['stop_loss_percentage']
+        
+        for commodity in self.price_data.keys():
+            if commodity not in self.grid_trading_config:
+                continue
+                
+            config = self.grid_trading_config[commodity]
+            start_price = config['start_price']
+            grid_size = config['grid_size']
+            quantity_per_grid = config['quantity_per_grid']
+            exit_grid_size = config['exit_grid_size']
+            
+            price_data = self.price_data[commodity]['data']
+            contract = self.price_data[commodity]['contract']
+            
+            if self.verbose_logging:
+                print(f"\n分析 {commodity} ({contract}) 网格对冲策略")
+                print(f"起始价格: {start_price}, 网格大小: {grid_size}")
+                print(f"止损百分比: {stop_loss_pct:.1%}")
+            
+            # 生成网格价格水平(对冲策略:在start_price + grid_size入场)
+            grid_levels = []
+            current_level = start_price + grid_size
+            max_price = price_data['high'].max()
+            
+            while current_level < max_price:
+                grid_levels.append(current_level)
+                current_level += grid_size
+            
+            # 模拟每个网格水平的对冲交易
+            for entry_price in grid_levels:
+                profit_target = entry_price - grid_size  # 获利了结
+                stop_loss = entry_price * (1 + stop_loss_pct)  # 止损
+                
+                # 查找入场机会
+                entry_date = None
+                for date, row in price_data.iterrows():
+                    if row['low'] <= entry_price <= row['high']:
+                        entry_date = date
+                        break
+                
+                if entry_date is None:
+                    continue
+                
+                # 查找退出机会(获利或止损)
+                exit_date = None
+                exit_price_actual = None
+                exit_reason = 'hold'
+                
+                for date, row in price_data.iterrows():
+                    if date <= entry_date:
+                        continue
+                    
+                    # 检查止损
+                    if row['high'] >= stop_loss:
+                        exit_date = date
+                        exit_price_actual = stop_loss
+                        exit_reason = 'stop_loss'
+                        break
+                    
+                    # 检查获利了结
+                    if row['low'] <= profit_target:
+                        exit_date = date
+                        exit_price_actual = profit_target
+                        exit_reason = 'profit_target'
+                        break
+                
+                # 如果没有触发退出条件,使用最后一日收盘价
+                if exit_date is None:
+                    exit_date = price_data.index[-1]
+                    exit_price_actual = price_data.iloc[-1]['close']
+                    exit_reason = 'end_of_data'
+                
+                # 计算盈亏(做空,所以是entry_price - exit_price)
+                profit_loss = (entry_price - exit_price_actual) * quantity_per_grid
+                profit_loss_pct = (entry_price - exit_price_actual) / entry_price
+                
+                trade_record = {
+                    'commodity': commodity,
+                    'contract': contract,
+                    'strategy': 'grid_hedge',
+                    'entry_date': entry_date.strftime('%Y-%m-%d'),
+                    'exit_date': exit_date.strftime('%Y-%m-%d'),
+                    'entry_price': entry_price,
+                    'exit_price': exit_price_actual,
+                    'quantity': quantity_per_grid,
+                    'profit_loss': profit_loss,
+                    'profit_loss_pct': profit_loss_pct,
+                    'days_held': (exit_date - entry_date).days,
+                    'exit_reason': exit_reason
+                }
+                
+                grid_hedge_results.append(trade_record)
+                
+                if self.verbose_logging:
+                    print(f"  对冲网格 {entry_price}: {entry_date.strftime('%Y-%m-%d')} -> {exit_date.strftime('%Y-%m-%d')}")
+                    print(f"    退出原因: {exit_reason}, 盈亏: {profit_loss:.2f} ({profit_loss_pct:.2%})")
+        
+        self.trading_results['grid_hedge'] = grid_hedge_results
+        
+        if self.verbose_logging:
+            print(f"\n网格对冲策略模拟完成,共{len(grid_hedge_results)}笔交易")
+        
+        return grid_hedge_results
+    
+    def simulate_combined_strategy(self):
+        """
+        模拟组合策略:网格交易 + 网格对冲策略
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤5: 组合策略模拟 ===")
+        
+        # 简单将网格交易和网格对冲的结果合并
+        grid_results = self.trading_results['grid_trading']
+        hedge_results = self.trading_results['grid_hedge']
+        
+        combined_results = []
+        
+        # 添加网格交易结果
+        for record in grid_results:
+            combined_record = record.copy()
+            combined_record['strategy'] = 'combined_grid'
+            combined_results.append(combined_record)
+        
+        # 添加网格对冲结果
+        for record in hedge_results:
+            combined_record = record.copy()
+            combined_record['strategy'] = 'combined_hedge'
+            combined_results.append(combined_record)
+        
+        self.trading_results['combined'] = combined_results
+        
+        if self.verbose_logging:
+            print(f"组合策略模拟完成,共{len(combined_results)}笔交易")
+            print(f"  其中网格交易: {len(grid_results)}笔")
+            print(f"  其中网格对冲: {len(hedge_results)}笔")
+        
+        return combined_results
+    
+    def calculate_performance_statistics(self):
+        """
+        计算多品种多级聚合的性能统计
+        包括品种-策略级、品种级、策略级和总体级统计
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤6: 多级性能统计分析 ===")
+        
+        # 多级统计结构
+        performance_stats = {
+            'by_commodity_strategy': {},  # 品种-策略级统计
+            'by_commodity': {},           # 品种级汇总
+            'by_strategy': {},            # 策略级汇总  
+            'overall': {}                 # 总体汇总
+        }
+        
+        if self.verbose_logging:
+            print("\n--- 第一级:品种-策略级统计 ---")
+        
+        # 第一步:计算品种-策略级统计
+        for strategy_name, results in self.trading_results.items():
+            if strategy_name not in performance_stats['by_commodity_strategy']:
+                performance_stats['by_commodity_strategy'][strategy_name] = {}
+            
+            # 按品种分组交易结果
+            commodity_results = {}
+            for result in results:
+                commodity = result['commodity']
+                if commodity not in commodity_results:
+                    commodity_results[commodity] = []
+                commodity_results[commodity].append(result)
+            
+            # 为每个品种计算统计
+            for commodity in self.core_commodities.keys():
+                comm_results = commodity_results.get(commodity, [])
+                stats = self._calculate_single_strategy_stats(strategy_name, comm_results, commodity)
+                performance_stats['by_commodity_strategy'][strategy_name][commodity] = stats
+                
+                if self.verbose_logging:
+                    print(f"\n{commodity}-{strategy_name} 策略统计:")
+                    self._print_strategy_stats(stats)
+        
+        if self.verbose_logging:
+            print("\n--- 第二级:品种级汇总统计 ---")
+        
+        # 第二步:计算品种级汇总统计
+        for commodity in self.core_commodities.keys():
+            commodity_stats = self._calculate_commodity_summary(commodity, performance_stats['by_commodity_strategy'])
+            performance_stats['by_commodity'][commodity] = commodity_stats
+            
+            if self.verbose_logging:
+                print(f"\n{commodity} 品种汇总统计:")
+                self._print_strategy_stats(commodity_stats)
+        
+        if self.verbose_logging:
+            print("\n--- 第三级:策略级汇总统计 ---")
+        
+        # 第三步:计算策略级汇总统计
+        for strategy_name in self.trading_results.keys():
+            strategy_stats = self._calculate_strategy_summary(strategy_name, performance_stats['by_commodity_strategy'])
+            performance_stats['by_strategy'][strategy_name] = strategy_stats
+            
+            if self.verbose_logging:
+                print(f"\n{strategy_name} 策略汇总统计:")
+                self._print_strategy_stats(strategy_stats)
+        
+        if self.verbose_logging:
+            print("\n--- 第四级:整体汇总统计 ---")
+        
+        # 第四步:计算总体统计
+        overall_stats = self._calculate_overall_summary(performance_stats['by_strategy'])
+        performance_stats['overall'] = overall_stats
+        
+        if self.verbose_logging:
+            print(f"\n整体汇总统计:")
+            self._print_strategy_stats(overall_stats)
+        
+        return performance_stats
+    
+    def _calculate_single_strategy_stats(self, strategy_name, results, commodity):
+        """计算单个策略在特定品种下的统计数据"""
+        # 计算已平仓交易的盈亏
+        closed_profit_loss = sum(r['profit_loss'] for r in results) if results else 0.0
+        
+        # 计算未平仓头寸的未实现盈亏(特定品种)
+        unrealized_profit_loss = self._calculate_unrealized_pnl_for_commodity(strategy_name, commodity)
+        
+        # 总盈亏 = 已实现盈亏 + 未实现盈亏
+        total_profit_loss = closed_profit_loss + unrealized_profit_loss
+        
+        if not results and unrealized_profit_loss == 0:
+            return {
+                'total_trades': 0,
+                'open_positions': 0,
+                'profitable_trades': 0,
+                'losing_trades': 0,
+                'win_rate': 0.0,
+                'closed_profit_loss': 0.0,
+                'unrealized_profit_loss': 0.0,
+                'total_profit_loss': 0.0,
+                'avg_profit_loss': 0.0,
+                'avg_profit_loss_pct': 0.0,
+                'max_profit': 0.0,
+                'max_loss': 0.0,
+                'avg_holding_days': 0.0,
+                'profit_factor': 0.0
+            }
+        
+        # 基本统计
+        total_trades = len(results)
+        open_positions = self._count_open_positions_for_commodity(strategy_name, commodity)
+        profitable_trades = sum(1 for r in results if r['profit_loss'] > 0)
+        losing_trades = sum(1 for r in results if r['profit_loss'] < 0)
+        win_rate = profitable_trades / total_trades if total_trades > 0 else 0
+        
+        # 平均盈亏(基于已平仓交易)
+        avg_profit_loss = closed_profit_loss / total_trades if total_trades > 0 else 0
+        avg_profit_loss_pct = sum(r['profit_loss_pct'] for r in results) / total_trades if total_trades > 0 else 0
+        
+        # 最大盈亏(基于已平仓交易)
+        profit_losses = [r['profit_loss'] for r in results]
+        max_profit = max(profit_losses) if profit_losses else 0
+        max_loss = min(profit_losses) if profit_losses else 0
+        
+        # 平均持有天数
+        avg_holding_days = sum(r['days_held'] for r in results) / total_trades if total_trades > 0 else 0
+        
+        # 盈亏比(基于已平仓交易)
+        total_profits = sum(r['profit_loss'] for r in results if r['profit_loss'] > 0)
+        total_losses = abs(sum(r['profit_loss'] for r in results if r['profit_loss'] < 0))
+        profit_factor = total_profits / total_losses if total_losses > 0 else float('inf') if total_profits > 0 else 0
+        
+        return {
+            'total_trades': total_trades,
+            'open_positions': open_positions,
+            'profitable_trades': profitable_trades,
+            'losing_trades': losing_trades,
+            'win_rate': win_rate,
+            'closed_profit_loss': closed_profit_loss,
+            'unrealized_profit_loss': unrealized_profit_loss,
+            'total_profit_loss': total_profit_loss,
+            'avg_profit_loss': avg_profit_loss,
+            'avg_profit_loss_pct': avg_profit_loss_pct,
+            'max_profit': max_profit,
+            'max_loss': max_loss,
+            'avg_holding_days': avg_holding_days,
+            'profit_factor': profit_factor
+        }
+    
+    def _print_strategy_stats(self, stats):
+        """打印策略统计信息"""
+        print(f"  已平仓交易: {stats['total_trades']}")
+        print(f"  未平仓头寸: {stats['open_positions']}")
+        print(f"  盈利交易: {stats['profitable_trades']}")
+        print(f"  亏损交易: {stats['losing_trades']}")
+        print(f"  胜率: {stats['win_rate']:.2%}")
+        print(f"  已实现盈亏: {stats['closed_profit_loss']:.2f}")
+        print(f"  未实现盈亏: {stats['unrealized_profit_loss']:.2f}")
+        print(f"  总盈亏: {stats['total_profit_loss']:.2f}")
+        print(f"  平均盈亏: {stats['avg_profit_loss']:.2f}")
+        print(f"  平均盈亏率: {stats['avg_profit_loss_pct']:.2%}")
+        print(f"  最大盈利: {stats['max_profit']:.2f}")
+        print(f"  最大亏损: {stats['max_loss']:.2f}")
+        print(f"  平均持有天数: {stats['avg_holding_days']:.1f}")
+        profit_factor_str = f"{stats['profit_factor']:.2f}" if stats['profit_factor'] != float('inf') else "∞"
+        print(f"  盈亏比: {profit_factor_str}")
+    
+    def _calculate_commodity_summary(self, commodity, by_commodity_strategy):
+        """计算品种级汇总统计"""
+        total_stats = {
+            'total_trades': 0,
+            'open_positions': 0,
+            'profitable_trades': 0,
+            'losing_trades': 0,
+            'closed_profit_loss': 0.0,
+            'unrealized_profit_loss': 0.0,
+            'total_profit_loss': 0.0,
+            'max_profit': 0.0,
+            'max_loss': 0.0,
+            'total_holding_days': 0.0,
+            'total_profits': 0.0,
+            'total_losses': 0.0,
+            'all_pct': []
+        }
+        
+        for strategy_name, commodity_stats in by_commodity_strategy.items():
+            if commodity in commodity_stats:
+                stats = commodity_stats[commodity]
+                total_stats['total_trades'] += stats['total_trades']
+                total_stats['open_positions'] += stats['open_positions']
+                total_stats['profitable_trades'] += stats['profitable_trades']
+                total_stats['losing_trades'] += stats['losing_trades']
+                total_stats['closed_profit_loss'] += stats['closed_profit_loss']
+                total_stats['unrealized_profit_loss'] += stats['unrealized_profit_loss']
+                total_stats['total_profit_loss'] += stats['total_profit_loss']
+                total_stats['max_profit'] = max(total_stats['max_profit'], stats['max_profit'])
+                total_stats['max_loss'] = min(total_stats['max_loss'], stats['max_loss'])
+                total_stats['total_holding_days'] += stats['avg_holding_days'] * stats['total_trades']
+                
+                if stats['profit_factor'] != float('inf'):
+                    profits = stats['closed_profit_loss'] if stats['closed_profit_loss'] > 0 else 0
+                    losses = abs(stats['closed_profit_loss']) if stats['closed_profit_loss'] < 0 else 0
+                    total_stats['total_profits'] += profits
+                    total_stats['total_losses'] += losses
+                
+                # 收集所有百分比数据
+                if stats['total_trades'] > 0:
+                    total_stats['all_pct'].extend([stats['avg_profit_loss_pct']] * stats['total_trades'])
+        
+        # 计算汇总指标
+        win_rate = total_stats['profitable_trades'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        avg_profit_loss = total_stats['closed_profit_loss'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        avg_profit_loss_pct = sum(total_stats['all_pct']) / len(total_stats['all_pct']) if total_stats['all_pct'] else 0
+        avg_holding_days = total_stats['total_holding_days'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        profit_factor = total_stats['total_profits'] / total_stats['total_losses'] if total_stats['total_losses'] > 0 else float('inf') if total_stats['total_profits'] > 0 else 0
+        
+        return {
+            'total_trades': total_stats['total_trades'],
+            'open_positions': total_stats['open_positions'],
+            'profitable_trades': total_stats['profitable_trades'],
+            'losing_trades': total_stats['losing_trades'],
+            'win_rate': win_rate,
+            'closed_profit_loss': total_stats['closed_profit_loss'],
+            'unrealized_profit_loss': total_stats['unrealized_profit_loss'],
+            'total_profit_loss': total_stats['total_profit_loss'],
+            'avg_profit_loss': avg_profit_loss,
+            'avg_profit_loss_pct': avg_profit_loss_pct,
+            'max_profit': total_stats['max_profit'],
+            'max_loss': total_stats['max_loss'],
+            'avg_holding_days': avg_holding_days,
+            'profit_factor': profit_factor
+        }
+    
+    def _calculate_strategy_summary(self, strategy_name, by_commodity_strategy):
+        """计算策略级汇总统计"""
+        if strategy_name not in by_commodity_strategy:
+            return self._get_empty_stats()
+        
+        strategy_stats = by_commodity_strategy[strategy_name]
+        
+        total_stats = {
+            'total_trades': 0,
+            'open_positions': 0,
+            'profitable_trades': 0,
+            'losing_trades': 0,
+            'closed_profit_loss': 0.0,
+            'unrealized_profit_loss': 0.0,
+            'total_profit_loss': 0.0,
+            'max_profit': 0.0,
+            'max_loss': 0.0,
+            'total_holding_days': 0.0,
+            'total_profits': 0.0,
+            'total_losses': 0.0,
+            'all_pct': []
+        }
+        
+        for commodity, stats in strategy_stats.items():
+            total_stats['total_trades'] += stats['total_trades']
+            total_stats['open_positions'] += stats['open_positions']
+            total_stats['profitable_trades'] += stats['profitable_trades']
+            total_stats['losing_trades'] += stats['losing_trades']
+            total_stats['closed_profit_loss'] += stats['closed_profit_loss']
+            total_stats['unrealized_profit_loss'] += stats['unrealized_profit_loss']
+            total_stats['total_profit_loss'] += stats['total_profit_loss']
+            total_stats['max_profit'] = max(total_stats['max_profit'], stats['max_profit'])
+            total_stats['max_loss'] = min(total_stats['max_loss'], stats['max_loss'])
+            total_stats['total_holding_days'] += stats['avg_holding_days'] * stats['total_trades']
+            
+            if stats['profit_factor'] != float('inf'):
+                profits = stats['closed_profit_loss'] if stats['closed_profit_loss'] > 0 else 0
+                losses = abs(stats['closed_profit_loss']) if stats['closed_profit_loss'] < 0 else 0
+                total_stats['total_profits'] += profits
+                total_stats['total_losses'] += losses
+            
+            # 收集所有百分比数据
+            if stats['total_trades'] > 0:
+                total_stats['all_pct'].extend([stats['avg_profit_loss_pct']] * stats['total_trades'])
+        
+        # 计算汇总指标
+        win_rate = total_stats['profitable_trades'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        avg_profit_loss = total_stats['closed_profit_loss'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        avg_profit_loss_pct = sum(total_stats['all_pct']) / len(total_stats['all_pct']) if total_stats['all_pct'] else 0
+        avg_holding_days = total_stats['total_holding_days'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        profit_factor = total_stats['total_profits'] / total_stats['total_losses'] if total_stats['total_losses'] > 0 else float('inf') if total_stats['total_profits'] > 0 else 0
+        
+        return {
+            'total_trades': total_stats['total_trades'],
+            'open_positions': total_stats['open_positions'],
+            'profitable_trades': total_stats['profitable_trades'],
+            'losing_trades': total_stats['losing_trades'],
+            'win_rate': win_rate,
+            'closed_profit_loss': total_stats['closed_profit_loss'],
+            'unrealized_profit_loss': total_stats['unrealized_profit_loss'],
+            'total_profit_loss': total_stats['total_profit_loss'],
+            'avg_profit_loss': avg_profit_loss,
+            'avg_profit_loss_pct': avg_profit_loss_pct,
+            'max_profit': total_stats['max_profit'],
+            'max_loss': total_stats['max_loss'],
+            'avg_holding_days': avg_holding_days,
+            'profit_factor': profit_factor
+        }
+    
+    def _calculate_overall_summary(self, by_strategy):
+        """计算总体汇总统计"""
+        total_stats = {
+            'total_trades': 0,
+            'open_positions': 0,
+            'profitable_trades': 0,
+            'losing_trades': 0,
+            'closed_profit_loss': 0.0,
+            'unrealized_profit_loss': 0.0,
+            'total_profit_loss': 0.0,
+            'max_profit': 0.0,
+            'max_loss': 0.0,
+            'total_holding_days': 0.0,
+            'total_profits': 0.0,
+            'total_losses': 0.0,
+            'all_pct': []
+        }
+        
+        for strategy_name, stats in by_strategy.items():
+            total_stats['total_trades'] += stats['total_trades']
+            total_stats['open_positions'] += stats['open_positions']
+            total_stats['profitable_trades'] += stats['profitable_trades']
+            total_stats['losing_trades'] += stats['losing_trades']
+            total_stats['closed_profit_loss'] += stats['closed_profit_loss']
+            total_stats['unrealized_profit_loss'] += stats['unrealized_profit_loss']
+            total_stats['total_profit_loss'] += stats['total_profit_loss']
+            total_stats['max_profit'] = max(total_stats['max_profit'], stats['max_profit'])
+            total_stats['max_loss'] = min(total_stats['max_loss'], stats['max_loss'])
+            total_stats['total_holding_days'] += stats['avg_holding_days'] * stats['total_trades']
+            
+            if stats['profit_factor'] != float('inf'):
+                profits = stats['closed_profit_loss'] if stats['closed_profit_loss'] > 0 else 0
+                losses = abs(stats['closed_profit_loss']) if stats['closed_profit_loss'] < 0 else 0
+                total_stats['total_profits'] += profits
+                total_stats['total_losses'] += losses
+            
+            # 收集所有百分比数据
+            if stats['total_trades'] > 0:
+                total_stats['all_pct'].extend([stats['avg_profit_loss_pct']] * stats['total_trades'])
+        
+        # 计算汇总指标
+        win_rate = total_stats['profitable_trades'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        avg_profit_loss = total_stats['closed_profit_loss'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        avg_profit_loss_pct = sum(total_stats['all_pct']) / len(total_stats['all_pct']) if total_stats['all_pct'] else 0
+        avg_holding_days = total_stats['total_holding_days'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        profit_factor = total_stats['total_profits'] / total_stats['total_losses'] if total_stats['total_losses'] > 0 else float('inf') if total_stats['total_profits'] > 0 else 0
+        
+        return {
+            'total_trades': total_stats['total_trades'],
+            'open_positions': total_stats['open_positions'],
+            'profitable_trades': total_stats['profitable_trades'],
+            'losing_trades': total_stats['losing_trades'],
+            'win_rate': win_rate,
+            'closed_profit_loss': total_stats['closed_profit_loss'],
+            'unrealized_profit_loss': total_stats['unrealized_profit_loss'],
+            'total_profit_loss': total_stats['total_profit_loss'],
+            'avg_profit_loss': avg_profit_loss,
+            'avg_profit_loss_pct': avg_profit_loss_pct,
+            'max_profit': total_stats['max_profit'],
+            'max_loss': total_stats['max_loss'],
+            'avg_holding_days': avg_holding_days,
+            'profit_factor': profit_factor
+        }
+    
+    def _get_empty_stats(self):
+        """返回空的统计数据"""
+        return {
+            'total_trades': 0,
+            'open_positions': 0,
+            'profitable_trades': 0,
+            'losing_trades': 0,
+            'win_rate': 0.0,
+            'closed_profit_loss': 0.0,
+            'unrealized_profit_loss': 0.0,
+            'total_profit_loss': 0.0,
+            'avg_profit_loss': 0.0,
+            'avg_profit_loss_pct': 0.0,
+            'max_profit': 0.0,
+            'max_loss': 0.0,
+            'avg_holding_days': 0.0,
+            'profit_factor': 0.0
+        }
+    
+    def _calculate_unrealized_pnl_for_commodity(self, strategy_name, commodity):
+        """计算特定品种未平仓头寸的未实现盈亏"""
+        if strategy_name == 'combined':
+            return (self._calculate_unrealized_pnl_for_commodity('grid_trading', commodity) + 
+                    self._calculate_unrealized_pnl_for_commodity('grid_hedge', commodity))
+        
+        unrealized_pnl = 0.0
+        strategy_positions = self.active_positions.get(strategy_name, {})
+        
+        if commodity in strategy_positions:
+            current_contract = self._get_current_contract(commodity, self.end_date.date())
+            if not current_contract:
+                return 0.0
+            
+            end_price = self._get_price_on_date(commodity, current_contract, self.end_date.date(), 'close')
+            if end_price is None:
+                return 0.0
+            
+            positions = strategy_positions[commodity]
+            for position_id, position in positions.items():
+                if position['status'] == 'open' and position['contract'] == current_contract:
+                    if strategy_name == 'grid_hedge':
+                        pnl = self._calculate_futures_pnl(
+                            position['entry_price'], end_price, position['quantity'], commodity, is_long=False
+                        )
+                    else:
+                        pnl = self._calculate_futures_pnl(
+                            position['entry_price'], end_price, position['quantity'], commodity, is_long=True
+                        )
+                    unrealized_pnl += pnl
+        
+        return unrealized_pnl
+    
+    def _count_open_positions_for_commodity(self, strategy_name, commodity):
+        """计算特定品种的未平仓头寸数量"""
+        if strategy_name == 'combined':
+            return (self._count_open_positions_for_commodity('grid_trading', commodity) + 
+                    self._count_open_positions_for_commodity('grid_hedge', commodity))
+        
+        count = 0
+        strategy_positions = self.active_positions.get(strategy_name, {})
+        
+        if commodity in strategy_positions:
+            positions = strategy_positions[commodity]
+            for position_id, position in positions.items():
+                if position['status'] == 'open':
+                    count += 1
+        
+        return count
+    
+    def _calculate_unrealized_pnl(self, strategy_name):
+        """
+        计算未平仓头寸的未实现盈亏
+        """
+        unrealized_pnl = 0.0
+        
+        if strategy_name == 'combined':
+            # 组合策略的未实现盈亏是网格交易和网格对冲的总和
+            return (self._calculate_unrealized_pnl('grid_trading') + 
+                    self._calculate_unrealized_pnl('grid_hedge'))
+        
+        # 获取策略对应的头寸字典
+        strategy_positions = self.active_positions.get(strategy_name, {})
+        
+        for commodity, positions in strategy_positions.items():
+            # 获取当前合约和最新价格
+            current_contract = self._get_current_contract(commodity, self.end_date.date())
+            if not current_contract:
+                continue
+            
+            # 获取结束日期的收盘价
+            end_price = self._get_price_on_date(commodity, current_contract, self.end_date.date(), 'close')
+            if end_price is None:
+                continue
+            
+            for position_id, position in positions.items():
+                if position['status'] == 'open' and position['contract'] == current_contract:
+                    # 使用正确的期货盈亏计算公式
+                    if strategy_name == 'grid_hedge':
+                        # 网格对冲是做空
+                        pnl = self._calculate_futures_pnl(
+                            position['entry_price'], end_price, position['quantity'], commodity, is_long=False
+                        )
+                    else:
+                        # 基础头寸和网格交易都是做多
+                        pnl = self._calculate_futures_pnl(
+                            position['entry_price'], end_price, position['quantity'], commodity, is_long=True
+                        )
+                    
+                    unrealized_pnl += pnl
+        
+        return unrealized_pnl
+    
+    def _count_open_positions(self, strategy_name):
+        """
+        计算未平仓头寸数量
+        """
+        if strategy_name == 'combined':
+            return (self._count_open_positions('grid_trading') + 
+                    self._count_open_positions('grid_hedge'))
+        
+        count = 0
+        strategy_positions = self.active_positions.get(strategy_name, {})
+        
+        for commodity, positions in strategy_positions.items():
+            for position_id, position in positions.items():
+                if position['status'] == 'open':
+                    count += 1
+        
+        return count
+    
+    def generate_comparison_report(self, performance_stats):
+        """
+        生成多级聚合的对比报告
+        包括品种-策略级、品种级、策略级和总体级报告
+        """
+        if self.verbose_logging:
+            print("\n=== 多级聚合对比分析报告 ===")
+        
+        strategies = ['base_position', 'grid_trading', 'grid_hedge', 'combined']
+        strategy_names = {
+            'base_position': '基础头寸交易',
+            'grid_trading': '网格交易',
+            'grid_hedge': '网格对冲策略',
+            'combined': '组合策略'
+        }
+        
+        all_comparison_data = {
+            'by_commodity_strategy': [],
+            'by_commodity': [],
+            'by_strategy': [],
+            'overall': []
+        }
+        
+        # 1. 品种-策略级对比报告
+        if self.verbose_logging:
+            print("\n--- 品种-策略级对比 ---")
+        
+        by_comm_strategy_data = []
+        for strategy in strategies:
+            for commodity in self.core_commodities.keys():
+                stats = performance_stats['by_commodity_strategy'].get(strategy, {}).get(commodity, {})
+                if stats.get('total_trades', 0) > 0 or stats.get('open_positions', 0) > 0:
+                    by_comm_strategy_data.append({
+                        '品种': commodity,
+                        '策略': strategy_names[strategy],
+                        '已平仓': stats.get('total_trades', 0),
+                        '未平仓': stats.get('open_positions', 0),
+                        '胜率': f"{stats.get('win_rate', 0):.2%}",
+                        '已实现': f"{stats.get('closed_profit_loss', 0):.2f}",
+                        '未实现': f"{stats.get('unrealized_profit_loss', 0):.2f}",
+                        '总盈亏': f"{stats.get('total_profit_loss', 0):.2f}",
+                        '平均盈亏': f"{stats.get('avg_profit_loss', 0):.2f}",
+                        '最大盈利': f"{stats.get('max_profit', 0):.2f}",
+                        '最大亏损': f"{stats.get('max_loss', 0):.2f}",
+                        '平均天数': f"{stats.get('avg_holding_days', 0):.1f}",
+                        '盈亏比': f"{stats.get('profit_factor', 0):.2f}" if stats.get('profit_factor', 0) != float('inf') else "∞"
+                    })
+        
+        if by_comm_strategy_data and self.verbose_logging:
+            df_comm_strategy = pd.DataFrame(by_comm_strategy_data)
+            print(df_comm_strategy.to_string(index=False))
+        all_comparison_data['by_commodity_strategy'] = by_comm_strategy_data
+        
+        # 2. 品种级汇总对比报告
+        if self.verbose_logging:
+            print("\n--- 品种级汇总对比 ---")
+        
+        by_commodity_data = []
+        for commodity in self.core_commodities.keys():
+            stats = performance_stats['by_commodity'].get(commodity, {})
+            if stats.get('total_trades', 0) > 0 or stats.get('open_positions', 0) > 0:
+                by_commodity_data.append({
+                    '品种': commodity,
+                    '已平仓': stats.get('total_trades', 0),
+                    '未平仓': stats.get('open_positions', 0),
+                    '胜率': f"{stats.get('win_rate', 0):.2%}",
+                    '已实现': f"{stats.get('closed_profit_loss', 0):.2f}",
+                    '未实现': f"{stats.get('unrealized_profit_loss', 0):.2f}",
+                    '总盈亏': f"{stats.get('total_profit_loss', 0):.2f}",
+                    '平均盈亏': f"{stats.get('avg_profit_loss', 0):.2f}",
+                    '最大盈利': f"{stats.get('max_profit', 0):.2f}",
+                    '最大亏损': f"{stats.get('max_loss', 0):.2f}",
+                    '平均天数': f"{stats.get('avg_holding_days', 0):.1f}",
+                    '盈亏比': f"{stats.get('profit_factor', 0):.2f}" if stats.get('profit_factor', 0) != float('inf') else "∞"
+                })
+        
+        if by_commodity_data and self.verbose_logging:
+            df_commodity = pd.DataFrame(by_commodity_data)
+            print(df_commodity.to_string(index=False))
+        all_comparison_data['by_commodity'] = by_commodity_data
+        
+        # 3. 策略级汇总对比报告
+        if self.verbose_logging:
+            print("\n--- 策略级汇总对比 ---")
+        
+        by_strategy_data = []
+        for strategy in strategies:
+            stats = performance_stats['by_strategy'].get(strategy, {})
+            by_strategy_data.append({
+                '策略': strategy_names[strategy],
+                '已平仓': stats.get('total_trades', 0),
+                '未平仓': stats.get('open_positions', 0),
+                '胜率': f"{stats.get('win_rate', 0):.2%}",
+                '已实现': f"{stats.get('closed_profit_loss', 0):.2f}",
+                '未实现': f"{stats.get('unrealized_profit_loss', 0):.2f}",
+                '总盈亏': f"{stats.get('total_profit_loss', 0):.2f}",
+                '平均盈亏': f"{stats.get('avg_profit_loss', 0):.2f}",
+                '最大盈利': f"{stats.get('max_profit', 0):.2f}",
+                '最大亏损': f"{stats.get('max_loss', 0):.2f}",
+                '平均天数': f"{stats.get('avg_holding_days', 0):.1f}",
+                '盈亏比': f"{stats.get('profit_factor', 0):.2f}" if stats.get('profit_factor', 0) != float('inf') else "∞"
+            })
+        
+        if by_strategy_data and self.verbose_logging:
+            df_strategy = pd.DataFrame(by_strategy_data)
+            print(df_strategy.to_string(index=False))
+        all_comparison_data['by_strategy'] = by_strategy_data
+        
+        # 4. 总体汇总报告
+        if self.verbose_logging:
+            print("\n--- 整体汇总 ---")
+        
+        overall_data = []
+        stats = performance_stats['overall']
+        overall_data.append({
+            '项目': '整体表现',
+            '已平仓': stats.get('total_trades', 0),
+            '未平仓': stats.get('open_positions', 0),
+            '胜率': f"{stats.get('win_rate', 0):.2%}",
+            '已实现': f"{stats.get('closed_profit_loss', 0):.2f}",
+            '未实现': f"{stats.get('unrealized_profit_loss', 0):.2f}",
+            '总盈亏': f"{stats.get('total_profit_loss', 0):.2f}",
+            '平均盈亏': f"{stats.get('avg_profit_loss', 0):.2f}",
+            '最大盈利': f"{stats.get('max_profit', 0):.2f}",
+            '最大亏损': f"{stats.get('max_loss', 0):.2f}",
+            '平均天数': f"{stats.get('avg_holding_days', 0):.1f}",
+            '盈亏比': f"{stats.get('profit_factor', 0):.2f}" if stats.get('profit_factor', 0) != float('inf') else "∞"
+        })
+        
+        if overall_data and self.verbose_logging:
+            df_overall = pd.DataFrame(overall_data)
+            print(df_overall.to_string(index=False))
+        all_comparison_data['overall'] = overall_data
+        
+        return all_comparison_data
+    
+    def generate_csv_output(self, performance_stats):
+        """
+        生成CSV输出文件
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤8: 生成CSV输出文件 ===")
+        
+        timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
+        
+        # 1. 生成交易记录CSV
+        all_trades = []
+        for strategy_name, trades in self.trading_results.items():
+            all_trades.extend(trades)
+        
+        if all_trades:
+            df_trades = pd.DataFrame(all_trades)
+            # 添加列顺序,确保合约切换相关字段在前面
+            column_order = ['commodity', 'contract', 'strategy', 'entry_date', 'exit_date', 
+                          'entry_price', 'exit_price', 'quantity', 'profit_loss', 'profit_loss_pct', 
+                          'days_held', 'exit_reason']
+            # 重新排列DataFrame的列顺序
+            existing_columns = [col for col in column_order if col in df_trades.columns]
+            other_columns = [col for col in df_trades.columns if col not in column_order]
+            df_trades = df_trades[existing_columns + other_columns]
+            
+            trades_filename = f'grid_trading_records_{timestamp}.csv'
+            df_trades.to_csv(trades_filename, index=False, encoding=self.output_encoding)
+            if self.verbose_logging:
+                print(f"交易记录已保存至: {trades_filename}")
+        
+        # 2. 生成多级性能统计CSV
+        csv_files = []
+        
+        # 2.1 品种-策略级统计CSV
+        by_comm_strategy_data = []
+        for strategy, commodity_data in performance_stats['by_commodity_strategy'].items():
+            for commodity, stats in commodity_data.items():
+                stats_record = {
+                    '品种': commodity,
+                    '策略': strategy,
+                    **stats
+                }
+                by_comm_strategy_data.append(stats_record)
+        
+        if by_comm_strategy_data:
+            df_comm_strategy = pd.DataFrame(by_comm_strategy_data)
+            comm_strategy_filename = f'grid_trading_by_commodity_strategy_{timestamp}.csv'
+            df_comm_strategy.to_csv(comm_strategy_filename, index=False, encoding=self.output_encoding)
+            csv_files.append(comm_strategy_filename)
+            if self.verbose_logging:
+                print(f"品种-策略级统计已保存至: {comm_strategy_filename}")
+        
+        # 2.2 品种级汇总统计CSV
+        by_commodity_data = []
+        for commodity, stats in performance_stats['by_commodity'].items():
+            stats_record = {
+                '品种': commodity,
+                **stats
+            }
+            by_commodity_data.append(stats_record)
+        
+        if by_commodity_data:
+            df_commodity = pd.DataFrame(by_commodity_data)
+            commodity_filename = f'grid_trading_by_commodity_{timestamp}.csv'
+            df_commodity.to_csv(commodity_filename, index=False, encoding=self.output_encoding)
+            csv_files.append(commodity_filename)
+            if self.verbose_logging:
+                print(f"品种级汇总统计已保存至: {commodity_filename}")
+        
+        # 2.3 策略级汇总统计CSV
+        by_strategy_data = []
+        for strategy, stats in performance_stats['by_strategy'].items():
+            stats_record = {
+                '策略': strategy,
+                **stats
+            }
+            by_strategy_data.append(stats_record)
+        
+        if by_strategy_data:
+            df_strategy = pd.DataFrame(by_strategy_data)
+            strategy_filename = f'grid_trading_by_strategy_{timestamp}.csv'
+            df_strategy.to_csv(strategy_filename, index=False, encoding=self.output_encoding)
+            csv_files.append(strategy_filename)
+            if self.verbose_logging:
+                print(f"策略级汇总统计已保存至: {strategy_filename}")
+        
+        # 2.4 整体汇总统计CSV
+        overall_data = [{
+            '项目': '整体汇总',
+            **performance_stats['overall']
+        }]
+        
+        if overall_data:
+            df_overall = pd.DataFrame(overall_data)
+            overall_filename = f'grid_trading_overall_{timestamp}.csv'
+            df_overall.to_csv(overall_filename, index=False, encoding=self.output_encoding)
+            csv_files.append(overall_filename)
+            if self.verbose_logging:
+                print(f"整体汇总统计已保存至: {overall_filename}")
+        
+        return trades_filename if all_trades else None, csv_files
+    
+    def run_complete_analysis(self):
+        """执行完整的网格交易分析流程(带主力合约切换)"""
+        if self.verbose_logging:
+            print("开始执行期货网格交易分析(带主力合约切换)")
+            print("=" * 60)
+        
+        try:
+            # 步骤1: 合约选择
+            self.select_contracts()
+            if not self.selected_contracts:
+                if self.verbose_logging:
+                    print("未选择到有效合约,分析终止")
+                return None
+            
+            # 步骤2: 构建主力合约历史变化
+            self.build_dominant_contract_history()
+            if not self.dominant_contract_history:
+                if self.verbose_logging:
+                    print("未获取到主力合约历史,分析终止")
+                return None
+            
+            # 步骤3: 收集价格数据
+            self.collect_price_data()
+            if not self.price_data:
+                if self.verbose_logging:
+                    print("未获取到有效价格数据,分析终止")
+                return None
+            
+            # 步骤4: 带合约切换的交易模拟
+            self.simulate_with_contract_switching()
+            
+            # 步骤5: 组合策略模拟
+            self.simulate_combined_strategy()
+            
+            # 步骤6: 性能统计分析
+            performance_stats = self.calculate_performance_statistics()
+            
+            # 步骤7: 生成对比报告
+            comparison_report = self.generate_comparison_report(performance_stats)
+            
+            # 步骤8: 生成CSV输出
+            # trades_file, stats_files = self.generate_csv_output(performance_stats)
+            
+            # 分析汇总
+            total_commodities = len(self.selected_contracts)
+            total_trades = sum(len(trades) for trades in self.trading_results.values())
+            contract_switches = sum(len(history) for history in self.dominant_contract_history.values())
+            
+            if self.verbose_logging:
+                print("\n" + "=" * 60)
+                print("分析完成汇总:")
+                print(f"分析商品数: {total_commodities}")
+                print(f"合约切换次数: {contract_switches}")
+                print(f"总交易笔数: {total_trades}")
+                # print(f"交易记录文件: {trades_file}")
+                # print(f"性能统计文件: {stats_file}")
+            
+            return {
+                'selected_contracts': self.selected_contracts,
+                'dominant_contract_history': self.dominant_contract_history,
+                'price_data': self.price_data,
+                'trading_results': self.trading_results,
+                'performance_stats': performance_stats,
+                'comparison_report': comparison_report,
+                # 'output_files': {
+                #     'trades_file': trades_file,
+                #     'stats_files': stats_files
+                # },
+                'summary': {
+                    'total_commodities': total_commodities,
+                    'contract_switches': contract_switches,
+                    'total_trades': total_trades
+                }
+            }
+            
+        except Exception as e:
+            if self.verbose_logging:
+                print(f"分析过程中出现错误: {str(e)}")
+                import traceback
+                traceback.print_exc()
+            return None
+
+
+# =====================================================================================
+# 主程序入口
+# =====================================================================================
+
+def run_grid_trading_analysis(config=None):
+    """运行期货网格交易分析"""
+    if config is None:
+        config = GridTradingConfig
+    
+    # 打印配置信息
+    config.print_config()
+    
+    # 创建分析器并运行
+    analyzer = FutureGridTradingAnalyzer(config)
+    results = analyzer.run_complete_analysis()
+    return results
+
+# 执行分析
+if __name__ == "__main__":
+    print("期货网格交易研究分析工具(带主力合约切换)")
+    print("研究期货网格交易策略在不同配置下的表现")
+    print("核心功能包括主力合约自动切换、强制平仓和重新建仓逻辑")
+    print("")
+    print("支持四种交易场景的对比分析:")
+    print("  1. 基础头寸交易 - 价格-数量网格配置")
+    print("  2. 网格交易策略 - 限价订单网格买入卖出")
+    print("  3. 网格对冲策略 - 带止损的增强网格交易")
+    print("  4. 组合策略 - 网格交易+网格对冲组合")
+    print("")
+    print("主要特点:")
+    print("  - 主力合约自动监控:每日检测主力合约变化")
+    print("  - 强制平仓机制:合约切换时立即平掉旧合约所有头寸")
+    print("  - 智能重新建仓:根据价格条件在新合约中重新建立头寸")
+    print("  - 完整交易记录:记录所有交易包括合约切换引起的强制平仓")
+    print("")
+    print("适用于聚宽在线研究平台")
+    
+    results = run_grid_trading_analysis()
+    
+    if results:
+        print("\n✅ 分析执行成功!")
+        summary = results['summary']
+        print(f"📊 结果摘要:")
+        print(f"   - 分析商品数: {summary['total_commodities']}")
+        print(f"   - 合约切换次数: {summary['contract_switches']}")
+        print(f"   - 总交易笔数: {summary['total_trades']}")
+        print(f"   - 整体总盈亏: {results['performance_stats']['overall']['total_profit_loss']:.2f}")
+        print(f"   - 整体胜率: {results['performance_stats']['overall']['win_rate']:.2%}")
+        print(f"   - 未平仓头寸: {results['performance_stats']['overall']['open_positions']}")
+        print(f"📂 输出文件:")
+        print(f"   - 交易记录文件: {results['output_files']['trades_file']}")
+        for i, stats_file in enumerate(results['output_files']['stats_files'], 1):
+            print(f"   - 统计文件{i}: {stats_file}")
+        
+        print(f"\n📈 多级汇总:")
+        
+        # 品种级汇总简要显示
+        print(f"   品种表现:")
+        for commodity in ['SA', 'M']:
+            comm_stats = results['performance_stats']['by_commodity'].get(commodity, {})
+            if comm_stats.get('total_trades', 0) > 0 or comm_stats.get('open_positions', 0) > 0:
+                print(f"     {commodity}: 交易{comm_stats.get('total_trades', 0)}笔, 盈亏{comm_stats.get('total_profit_loss', 0):.2f}, 胜率{comm_stats.get('win_rate', 0):.2%}")
+        
+        # 策略级汇总简要显示
+        print(f"   策略表现:")
+        strategy_names = {
+            'base_position': '基础头寸',
+            'grid_trading': '网格交易',
+            'grid_hedge': '网格对冲',
+            'combined': '组合策略'
+        }
+        for strategy, name in strategy_names.items():
+            strat_stats = results['performance_stats']['by_strategy'].get(strategy, {})
+            print(f"     {name}: 交易{strat_stats.get('total_trades', 0)}笔, 盈亏{strat_stats.get('total_profit_loss', 0):.2f}, 胜率{strat_stats.get('win_rate', 0):.2%}")
+        
+    else:
+        print("\n❌ 分析执行失败,请检查错误信息")

+ 3698 - 0
Lib/research/future_grid_trading_analysis2.py

@@ -0,0 +1,3698 @@
+"""
+期货网格交易研究分析工具(带主力合约切换)
+研究期货网格交易策略在不同配置下的表现,支持多种交易场景的对比分析
+核心功能包括主力合约自动切换、强制平仓和重新建仓逻辑
+
+本程序实现完整的网格交易分析流程:
+1. 主力合约监控与切换 - 自动检测主力合约变化并处理切换
+2. 合约选择逻辑 - 基于主力合约选择算法
+3. 基础头寸交易 - 价格-数量网格配置,支持合约切换时重新建仓
+4. 网格交易策略 - 限价订单网格买入卖出,合约切换时根据价格条件重新建仓
+5. 统计分析对比 - 多种交易场景性能分析
+
+主要特点:
+- 主力合约自动监控:每日检测主力合约变化
+- 强制平仓机制:合约切换时立即平掉旧合约所有头寸
+- 智能重新建仓:根据价格条件在新合约中重新建立头寸
+- 标准期货盈亏计算:使用正确的合约倍数和期货盈亏公式
+- 最终持仓结算:分析期结束时对所有未平仓头寸进行市值计价
+- 完整交易记录:记录所有交易包括合约切换引起的强制平仓
+
+期货盈亏计算公式:
+- 多头:(出场价格 - 入场价格) × 合约倍数 × 数量
+- 空头:(入场价格 - 出场价格) × 合约倍数 × 数量
+
+注:程序支持多个核心商品同时分析,生成详细的交易记录和统计报告
+
+作者: jukuan研究团队
+日期: 2025-09
+适用平台: 聚宽在线研究平台
+"""
+
+import pandas as pd
+import numpy as np
+from jqdata import *
+import datetime
+import warnings
+warnings.filterwarnings('ignore')
+
+# =====================================================================================
+# 分析配置参数 - 集中配置部分
+# =====================================================================================
+
+class GridTradingConfig:
+    """期货网格交易分析配置参数"""
+    
+    # ==================== 时间范围设置 ====================
+    START_DATE = datetime.datetime(2024, 10, 7)   # 分析开始日期
+    END_DATE = datetime.datetime(2025, 9, 19)    # 分析结束日期(修正为9月19日避免数据缺失)
+    
+    # ==================== 期货合约倍数配置 ====================
+    FUTURES_MULTIPLIER = {
+        # 贵金属
+        'AU': 1000,  # 黄金
+        'AG': 15,    # 白银
+        
+        # 有色金属
+        'CU': 5, 'AL': 5, 'ZN': 5, 'PB': 5, 'NI': 1, 'SN': 1, 'SS': 5,
+        
+        # 黑色系
+        'RB': 10, 'HC': 10, 'I': 100, 'JM': 100, 'J': 60,
+        
+        # 能源化工
+        'SP': 10, 'FU': 10, 'BU': 10, 'RU': 10, 'BR': 5, 'SC': 1000,
+        'NR': 10, 'LU': 10, 'LC': 1,
+        
+        # 化工
+        'FG': 20, 'TA': 5, 'MA': 10, 'SA': 20, 'L': 5, 'V': 5, 'EG': 10,
+        'PP': 5, 'EB': 5, 'PG': 20, 'UR': 20,
+        
+        # 农产品
+        'RM': 10, 'OI': 10, 'CF': 5, 'SR': 10, 'PF': 5, 'C': 10, 'CS': 10,
+        'CY': 5, 'A': 10, 'B': 10, 'M': 10, 'Y': 10, 'P': 10,
+        
+        # 股指期货
+        'IF': 300, 'IH': 300, 'IC': 200, 'IM': 200, 'TL': 10000,
+        
+        # 其他
+        'AP': 10, 'CJ': 5, 'PK': 5, 'JD': 10, 'LH': 16
+    }
+    
+    # ==================== 核心商品配置 ====================
+    CORE_COMMODITIES = {
+        # 'SA': ['SA2501.XZCE', 'SA2505.XZCE', 'SA2509.XZCE', 'SA2601.XZCE'],  # 纯碱
+        # 'M': ['M2501.XDCE', 'M2505.XDCE', 'M2509.XDCE', 'M2605.XDCE'],  # 豆粕
+        # 'UR': ['UR2501.XZCE', 'UR2505.XZCE', 'UR2509.XZCE', 'UR2601.XZCE'],  # 尿素
+        # 'LH': ['LH2501.XDCE', 'LH2505.XDCE', 'LH2509.XDCE', 'LH2601.XDCE'],  # 生猪
+        'TL': ['TL2503.CCFX', 'TL2506.CCFX', 'TL2509.CCFX', 'TL2512.CCFX']  # 30年期国债
+    }
+    
+    # ==================== 合约切换配置 ====================
+    REQUIRED_TRADING_DAYS = 30  # 合约切换前需要的最少有效交易日数
+    
+    # ==================== 基础头寸交易配置 ====================
+    BASE_POSITION_GRID = {
+        'SA': {1400: 4, 1300: 6, 1200: 8, 1100: 12, 1000: 14, 900: 16},
+        'M': {2800: 4, 2750: 6, 2700: 8, 2650: 12, 2600: 14, 2550: 16},
+        'UR': {1750: 4, 1700: 6, 1650: 8, 1600: 12, 1550: 14, 1500: 16},
+        'LH': {13000: 1, 12500: 1, 12000: 1, 11500: 1, 11000: 2},
+        'TL': {118: 1, 117: 1, 116: 1, 115: 1, 114: 2, 113: 2},
+    }
+    
+    # 统一退出价格(无止损)
+    BASE_POSITION_EXIT_PRICE = {
+        'SA': 1500,
+        'M': 3800,
+        'UR': 2400,
+        'LH': 20000,
+        'TL': 121,
+    }
+    
+    # ==================== 网格交易配置 ====================
+    GRID_TRADING_CONFIG = {
+        'SA': {
+            'start_price': 1250, # 开始价格
+            'grid_size': 50, # 网格大小
+            'quantity_per_grid': 5, # 每网格数量
+            'exit_grid_size': 50 # 退出网格大小
+        },
+        'M': {
+            'start_price': 2800,
+            'grid_size': 100,
+            'quantity_per_grid': 10,
+            'exit_grid_size': 100
+        },
+        'UR': {
+            'start_price': 1800,
+            'grid_size': 50,
+            'quantity_per_grid': 10,
+            'exit_grid_size': 50
+        },
+        'LH': {
+            'start_price': 13500,
+            'grid_size': 500,
+            'quantity_per_grid': 1,
+            'exit_grid_size': 500
+        },
+        'TL': {
+            'start_price': 118,
+            'grid_size': 1,
+            'quantity_per_grid': 1,
+            'exit_grid_size': 1
+        },
+    }
+    
+    # ==================== 对冲策略配置 ====================
+    HEDGE_ENABLED = True  # 是否启用对冲策略
+    HEDGE_USE_ATR_FILTER = False  # 是否使用ATR波动率过滤来控制对冲建仓
+    HEDGE_PROFIT_PULLBACK_PCT = 0.3  # 止盈回撤百分比 (m%)
+    HEDGE_STOPLOSS_PCT = 0.01  # 止损百分比 (n%)
+    HEDGE_ATR_PERIOD = 14  # ATR计算周期
+    HEDGE_ATR_LOOKBACK_MONTHS = 3  # ATR回看月数
+    HEDGE_ATR_THRESHOLD = 0.7  # ATR阈值系数
+    
+    # ==================== 对冲止盈策略配置 ====================
+    HEDGE_LEVEL1_RECOVERY_PCT = 0.30  # 一级止盈回升百分比(30%)
+    HEDGE_LEVEL2_RECOVERY_PCT = 0.60  # 二级止盈回升百分比(60%)
+    HEDGE_COST_AREA_PCT = 0.02        # 成本区域百分比(2%)
+    
+    # ==================== 输出设置 ====================
+    OUTPUT_ENCODING = 'utf-8-sig'  # 输出文件编码格式
+    VERBOSE_LOGGING = True          # 是否打印详细日志
+    
+    @classmethod
+    def print_config(cls):
+        """打印当前配置信息"""
+        print("=== 期货网格交易分析配置 ===")
+        print(f"分析时间范围: {cls.START_DATE.strftime('%Y-%m-%d')} 至 {cls.END_DATE.strftime('%Y-%m-%d')}")
+        print(f"核心商品数量: {len(cls.CORE_COMMODITIES)}")
+        print("核心商品列表:")
+        for commodity, contracts in cls.CORE_COMMODITIES.items():
+            print(f"  {commodity}: {contracts}")
+        print(f"\n网格交易配置:")
+        for commodity, config in cls.GRID_TRADING_CONFIG.items():
+            print(f"  {commodity}: 起始价{config['start_price']}, 网格大小{config['grid_size']}")
+        print(f"\n对冲策略配置:")
+        print(f"  对冲策略启用: {'是' if cls.HEDGE_ENABLED else '否'}")
+        if cls.HEDGE_ENABLED:
+            print(f"  ATR波动率过滤: {'启用' if cls.HEDGE_USE_ATR_FILTER else '禁用'}")
+            print(f"  止盈回撤百分比: {cls.HEDGE_PROFIT_PULLBACK_PCT:.1%}")
+            print(f"  止损百分比: {cls.HEDGE_STOPLOSS_PCT:.1%}")
+            print(f"  一级止盈回升百分比: {cls.HEDGE_LEVEL1_RECOVERY_PCT:.1%}")
+            print(f"  二级止盈回升百分比: {cls.HEDGE_LEVEL2_RECOVERY_PCT:.1%}")
+            print(f"  成本区域百分比: {cls.HEDGE_COST_AREA_PCT:.1%}")
+        print(f"\n详细日志: {'开启' if cls.VERBOSE_LOGGING else '关闭'}")
+        print("=" * 50)
+
+class FutureGridTradingAnalyzer:
+    """期货网格交易分析器"""
+    
+    def __init__(self, config=None):
+        """初始化分析器"""
+        if config is None:
+            config = GridTradingConfig
+        
+        self.config = config
+        self.start_date = config.START_DATE
+        self.end_date = config.END_DATE
+        self.core_commodities = config.CORE_COMMODITIES
+        self.base_position_grid = config.BASE_POSITION_GRID
+        self.base_position_exit_price = config.BASE_POSITION_EXIT_PRICE
+        self.grid_trading_config = config.GRID_TRADING_CONFIG
+        self.verbose_logging = config.VERBOSE_LOGGING
+        self.output_encoding = config.OUTPUT_ENCODING
+        
+        # 存储结果的字典
+        self.selected_contracts = {}  # 选中的合约
+        self.price_data = {}         # 价格数据
+        self.dominant_contract_history = {}  # 主力合约历史变化
+        self.active_positions = {    # 当前活跃头寸跟踪
+            'base_position': {},
+            'grid_trading': {},
+            'hedge_position': {}  # 对冲头寸追踪
+        }
+        self.trading_results = {     # 交易场景的结果
+            'base_position': [],
+            'grid_trading': [],
+            'hedge_position': []  # 对冲交易记录
+        }
+        self.hedge_state = {}  # 对冲策略状态追踪
+        self.ma_extended_data_cache = {}  # MA扩展数据缓存
+        
+        if self.verbose_logging:
+            print("初始化期货网格交易分析器")
+            print(f"核心商品: {list(self.core_commodities.keys())}")
+            print(f"分析期间: {self.start_date.strftime('%Y-%m-%d')} - {self.end_date.strftime('%Y-%m-%d')}")
+    
+    def select_contracts(self):
+        """
+        合约选择逻辑
+        1. 首先获取商品的主导合约
+        2. 如果主导合约在可用列表中,选择它
+        3. 如果主导合约不在列表中,选择未来到期日期最近且晚于主导合约的合约
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤1: 合约选择逻辑 ===")
+        
+        for commodity, available_contracts in self.core_commodities.items():
+            if self.verbose_logging:
+                print(f"\n处理商品: {commodity}")
+                print(f"可用合约: {available_contracts}")
+            
+            try:
+                # 获取商品的主导合约
+                dominant_contract = get_dominant_future(commodity, self.start_date.date())
+                
+                if self.verbose_logging:
+                    print(f"主导合约: {dominant_contract}")
+                
+                if dominant_contract in available_contracts:
+                    # 主导合约在可用列表中,直接选择
+                    selected_contract = dominant_contract
+                    if self.verbose_logging:
+                        print(f"选择主导合约: {selected_contract}")
+                else:
+                    # 主导合约不在列表中,选择最近的未来合约
+                    selected_contract = self._select_nearest_future_contract(
+                        commodity, dominant_contract, available_contracts
+                    )
+                    if self.verbose_logging:
+                        print(f"选择最近的未来合约: {selected_contract}")
+                
+                self.selected_contracts[commodity] = selected_contract
+                
+            except Exception as e:
+                if self.verbose_logging:
+                    print(f"获取{commodity}主导合约失败: {str(e)}")
+                # 默认选择第一个可用合约
+                self.selected_contracts[commodity] = available_contracts[0]
+                if self.verbose_logging:
+                    print(f"默认选择第一个合约: {available_contracts[0]}")
+        
+        if self.verbose_logging:
+            print(f"\n合约选择完成,共选择{len(self.selected_contracts)}个合约")
+            for commodity, contract in self.selected_contracts.items():
+                print(f"  {commodity}: {contract}")
+        
+        return self.selected_contracts
+    
+    def _select_nearest_future_contract(self, commodity, dominant_contract, available_contracts):
+        """选择最近的未来到期合约"""
+        if not dominant_contract:
+            return available_contracts[0]
+        
+        # 解析主导合约的到期月份
+        try:
+            # 提取合约代码中的月份信息 (例如 SA2507 -> 2507)
+            dominant_year_month = dominant_contract.split('.')[0][-4:]  # 取最后4位
+            dominant_year = int(dominant_year_month[:2]) + 2000  # 假设是21世纪
+            dominant_month = int(dominant_year_month[2:])
+        except:
+            return available_contracts[0]
+        
+        # 找到最近的未来合约
+        best_contract = available_contracts[0]
+        best_diff = float('inf')
+        
+        for contract in available_contracts:
+            try:
+                contract_year_month = contract.split('.')[0][-4:]
+                contract_year = int(contract_year_month[:2]) + 2000
+                contract_month = int(contract_year_month[2:])
+                
+                # 计算月份差异
+                contract_total_months = contract_year * 12 + contract_month
+                dominant_total_months = dominant_year * 12 + dominant_month
+                
+                # 只选择晚于主导合约的合约
+                if contract_total_months > dominant_total_months:
+                    diff = contract_total_months - dominant_total_months
+                    if diff < best_diff:
+                        best_diff = diff
+                        best_contract = contract
+            except:
+                continue
+        
+        return best_contract
+    
+    def _get_futures_multiplier(self, commodity):
+        """获取期货合约倍数"""
+        return self.config.FUTURES_MULTIPLIER.get(commodity, 10)  # 默认倍数为10
+    
+    def _calculate_futures_pnl(self, entry_price, exit_price, quantity, commodity, is_long=True):
+        """
+        计算期货盈亏
+        
+        参数:
+            entry_price: 入场价格
+            exit_price: 出场价格
+            quantity: 数量(手数)
+            commodity: 商品代码
+            is_long: 是否多头,True为多头,False为空头
+            
+        返回:
+            实际盈亏金额
+        """
+        multiplier = self._get_futures_multiplier(commodity)
+        
+        if is_long:
+            # 多头:(出场价格 - 入场价格) × 合约倍数 × 数量
+            pnl = (exit_price - entry_price) * multiplier * quantity
+        else:
+            # 空头:(入场价格 - 出场价格) × 合约倍数 × 数量
+            pnl = (entry_price - exit_price) * multiplier * quantity
+        
+        return pnl
+    
+    def build_dominant_contract_history(self):
+        """
+        构建主力合约历史变化记录
+        为每个商品在整个分析期间构建主力合约变化的时间序列
+        只有当合约真正发生变化时才记录为合约切换
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤2:构建主力合约历史变化记录 ===")
+        
+        for commodity in self.core_commodities.keys():
+            if self.verbose_logging:
+                print(f"构建 {commodity} 主力合约历史...")
+            
+            contract_history = []
+            current_date = self.start_date.date()
+            end_date = self.end_date.date()
+            current_selected_contract = None  # 跟踪选择的合约而不是主力合约
+            
+            while current_date <= end_date:
+                # 跳过非交易日
+                if current_date.weekday() >= 5:  # 周六周日
+                    current_date += datetime.timedelta(days=1)
+                    continue
+                
+                try:
+                    # 获取当日主力合约
+                    dominant_contract = get_dominant_future(commodity, current_date)
+                    # print(f"日期: {current_date}, 主力合约: {dominant_contract}")
+                    selected_contract = self._match_to_available_contract(commodity, dominant_contract)
+                    
+                    # 只有当选择的合约真正发生变化时才记录
+                    if selected_contract != current_selected_contract:
+                        contract_history.append({
+                            'date': current_date,
+                            'dominant_contract': dominant_contract,
+                            'selected_contract': selected_contract,
+                            'is_initial': current_selected_contract is None  # 标记是否为初始合约
+                        })
+                        
+                        if self.verbose_logging:
+                            if current_selected_contract is None:
+                                print(f"  {current_date}: 初始合约设置为 {selected_contract}")
+                            else:
+                                print(f"  {current_date}: 合约切换 {current_selected_contract} -> {selected_contract}")
+                        
+                        current_selected_contract = selected_contract
+                
+                except Exception as e:
+                    if self.verbose_logging:
+                        print(f"  获取 {current_date} 的主力合约时出错: {str(e)}")
+                
+                current_date += datetime.timedelta(days=1)
+            
+            self.dominant_contract_history[commodity] = contract_history
+        
+        if self.verbose_logging:
+            total_changes = sum(len(history) for history in self.dominant_contract_history.values())
+            actual_switches = sum(
+                sum(1 for change in history if not change.get('is_initial', False))
+                for history in self.dominant_contract_history.values()
+            )
+            initial_setups = total_changes - actual_switches
+            print(f"主力合约历史构建完成,共 {total_changes} 次记录({initial_setups} 次初始设置,{actual_switches} 次真实切换)")
+        
+        return self.dominant_contract_history
+    
+    def _match_to_available_contract(self, commodity, dominant_contract):
+        """将主力合约匹配到可用合约列表"""
+        available_contracts = self.core_commodities.get(commodity, [])
+        
+        if dominant_contract in available_contracts:
+            return dominant_contract
+        else:
+            return self._select_nearest_future_contract(commodity, dominant_contract, available_contracts)
+    
+    def collect_price_data(self):
+        """收集所有可能用到的合约价格数据(优化日期范围)"""
+        if self.verbose_logging:
+            print("\n=== 步骤3: 收集价格数据(优化日期范围) ===")
+        
+        # 清除之前的调整建议
+        if hasattr(self, 'adjustment_suggestions'):
+            self.adjustment_suggestions = []
+        
+        # 为每个商品创建数据存储结构
+        for commodity in self.core_commodities.keys():
+            print(f'收集{commodity}的价格数据:')
+            self.price_data[commodity] = {}
+            
+            # 根据主力合约历史确定每个合约的数据获取范围
+            contract_date_ranges = self._determine_contract_date_ranges(commodity)
+            
+            for contract, date_range in contract_date_ranges.items():
+                start_date, end_date = date_range
+                
+                if self.verbose_logging:
+                    print(f"获取 {contract} 价格数据...")
+                    print(f"  优化日期范围: {start_date} 至 {end_date}")
+                
+                try:
+                    # 获取价格数据(使用优化的日期范围)
+                    data = get_price(
+                        contract,
+                        start_date=start_date,
+                        end_date=end_date,
+                        frequency='daily',
+                        fields=['open', 'close', 'high', 'low', 'volume'],
+                        skip_paused=False,
+                        panel=False
+                    )
+                    
+                    if data is not None and len(data) > 0:
+                        # print(f"第一条有数据的日期是: {data.index[0].date()},数据是: {data.iloc[0]}")
+                        # print(f"最后一条有数据的日期是: {data.index[-1].date()}, 数据是: {data.iloc[-1]}")
+                        self.price_data[commodity][contract] = data
+
+                        # 检查这个数据里有多少条空值数据
+                        empty_data = data[data.isna().any(axis=1)]
+                        
+                        # 检查有效交易日数据并收集调整建议
+                        adjustment_info = self._check_thirty_day_trading_data(commodity, contract, data, start_date, end_date)
+                        if adjustment_info and adjustment_info.get('needs_adjustment'):
+                            # 暂存调整建议,稍后统一处理
+                            if not hasattr(self, 'adjustment_suggestions'):
+                                self.adjustment_suggestions = []
+                            self.adjustment_suggestions.append(adjustment_info)
+                        
+                        if self.verbose_logging:
+                            print(f"  ✅ 成功获取{len(data)}条数据记录")
+                            print(f"  空值数据: {len(empty_data)}条")
+                            print(f"  价格范围: {data['low'].min():.2f} - {data['high'].max():.2f}")
+                            print(f"  数据日期范围: {data.index[0].date()} 至 {data.index[-1].date()}")
+                    else:
+                        if self.verbose_logging:
+                            print(f"  ⚠️  未获取到{contract}的数据")
+                        
+                        # 如果优化日期范围没有数据,尝试使用更宽泛的日期范围
+                        if self.verbose_logging:
+                            print(f"  尝试使用更宽泛的日期范围获取数据...")
+                        
+                        try:
+                            fallback_data = get_price(
+                                contract,
+                                start_date=self.start_date,
+                                end_date=self.end_date,
+                                frequency='daily',
+                                fields=['open', 'close', 'high', 'low', 'volume'],
+                                skip_paused=False,
+                                panel=False
+                            )
+                            
+                            if fallback_data is not None and len(fallback_data) > 0:
+                                self.price_data[commodity][contract] = fallback_data
+                                
+                                # 检查有效交易日数据并收集调整建议(回退方案)
+                                adjustment_info = self._check_thirty_day_trading_data(commodity, contract, fallback_data, self.start_date, self.end_date)
+                                if adjustment_info and adjustment_info.get('needs_adjustment'):
+                                    if not hasattr(self, 'adjustment_suggestions'):
+                                        self.adjustment_suggestions = []
+                                    self.adjustment_suggestions.append(adjustment_info)
+                                
+                                if self.verbose_logging:
+                                    print(f"  ✅ 回退方案成功获取{len(fallback_data)}条数据记录")
+                                    print(f"  数据日期范围: {fallback_data.index[0].date()} 至 {fallback_data.index[-1].date()}")
+                            else:
+                                if self.verbose_logging:
+                                    print(f"  ❌ 回退方案也未获取到{contract}的数据")
+                        except Exception as fallback_e:
+                            if self.verbose_logging:
+                                print(f"  ❌ 回退方案出错: {str(fallback_e)}")
+                        
+                except Exception as e:
+                    if self.verbose_logging:
+                        print(f"  ❌ 获取{contract}数据时出错: {str(e)}")
+                    continue
+        
+        # 处理动态调整建议
+        if hasattr(self, 'adjustment_suggestions') and self.adjustment_suggestions:
+            self._apply_dynamic_adjustments()
+        
+        if self.verbose_logging:
+            total_contracts = sum(len(contracts) for contracts in self.price_data.values())
+            print(f"价格数据收集完成,共{total_contracts}个合约")
+        
+        return self.price_data
+    
+    def _determine_contract_date_ranges(self, commodity):
+        """
+        根据主力合约历史确定每个合约的最优数据获取日期范围
+        """
+        contract_ranges = {}
+        
+        if commodity not in self.dominant_contract_history:
+            # 如果没有主力合约历史,使用全范围
+            for contract in self.core_commodities[commodity]:
+                contract_ranges[contract] = (self.start_date, self.end_date)
+            return contract_ranges
+        
+        contract_history = self.dominant_contract_history[commodity]
+        
+        # 分析每个合约的活跃期间
+        for contract in self.core_commodities[commodity]:
+            contract_start = self.start_date
+            contract_end = self.end_date
+            
+            # 查找该合约在主力合约历史中的使用时间段
+            for i, history_record in enumerate(contract_history):
+                if history_record['selected_contract'] == contract:
+                    # 该合约开始使用的日期
+                    if history_record.get('is_initial', False):
+                        # 初始设置的合约,从分析开始日期或历史记录日期开始
+                        contract_start = max(self.start_date.date(), history_record['date'])
+                    else:
+                        # 切换到的合约,从切换日期开始
+                        contract_start = history_record['date']
+                    
+                    # 查找该合约结束使用的日期
+                    for j in range(i + 1, len(contract_history)):
+                        next_record = contract_history[j]
+                        if next_record['selected_contract'] != contract:
+                            # 找到下一次切换,该合约在此日期结束使用
+                            contract_end = next_record['date']
+                            break
+                    else:
+                        # 该合约一直使用到分析结束
+                        contract_end = self.end_date.date()
+                    
+                    break
+            
+            # 转换为datetime格式并添加缓冲区
+            if isinstance(contract_start, datetime.date):
+                contract_start = datetime.datetime.combine(contract_start, datetime.time.min)
+            if isinstance(contract_end, datetime.date):
+                contract_end = datetime.datetime.combine(contract_end, datetime.time.max)
+            
+            # 添加缓冲期以确保有足够的历史数据满足最低交易日要求
+            # 使用REQUIRED_TRADING_DAYS作为缓冲,保证数据充足性
+            contract_start_buffered = contract_start - datetime.timedelta(days=self.config.REQUIRED_TRADING_DAYS)
+            contract_end_buffered = contract_end # + datetime.timedelta(days=self.config.REQUIRED_TRADING_DAYS)
+            
+            # 确保不超出总体分析范围
+            contract_start_final = max(contract_start_buffered, self.start_date)
+            contract_end_final = min(contract_end_buffered, self.end_date)
+            
+            contract_ranges[contract] = (contract_start_final, contract_end_final)
+            
+            if self.verbose_logging:
+                print(f"  {contract}: {contract_start_final.date()} 至 {contract_end_final.date()}")
+        
+        return contract_ranges
+    
+    def _check_thirty_day_trading_data(self, commodity, contract, data, start_date, end_date):
+        """
+        检查合约是否有足够的有效交易日数据并进行动态调整
+        返回调整建议信息
+        """
+        if data is None or len(data) == 0:
+            print(f"    ⚠️  {contract}: 无价格数据")
+            return None
+        
+        required_days = self.config.REQUIRED_TRADING_DAYS
+        
+        # 检查空值数据
+        empty_data = data[data.isna().any(axis=1)]
+        empty_count = len(empty_data)
+        
+        # 过滤出非空的收盘价数据
+        valid_close_data = data['close'].dropna()
+        valid_count = len(valid_close_data)
+        
+        print(f"    📊 {contract}: 有效收盘价数据共{valid_count}天")
+        
+        adjustment_info = {
+            'contract': contract,
+            'commodity': commodity,
+            'empty_count': empty_count,
+            'valid_count': valid_count,
+            'required_days': required_days,
+            'needs_adjustment': False,
+            'suggested_switch_date': None
+        }
+        
+        # 检查是否有空值数据且需要调整
+        if empty_count > 0:
+            print(f"    ⚠️  {contract}: 检测到{empty_count}条空值数据")
+            
+            if valid_count >= required_days:
+                # 找到第N个有效收盘价的日期
+                nth_date = valid_close_data.index[required_days - 1]  # 索引从0开始
+                nth_price = valid_close_data.iloc[required_days - 1]
+                
+                print(f"    📍 {contract}: 第{required_days}个有效收盘价日期为{nth_date.date()},价格{nth_price:.2f}")
+                
+                # 检查当前切换日期是否需要调整
+                if commodity in self.dominant_contract_history:
+                    for history_record in self.dominant_contract_history[commodity]:
+                        if (history_record['selected_contract'] == contract and 
+                            not history_record.get('is_initial', False)):
+                            current_switch_date = history_record['date']
+                            
+                            # 转换日期格式进行比较
+                            if isinstance(current_switch_date, datetime.date):
+                                current_switch_datetime = datetime.datetime.combine(current_switch_date, datetime.time.min)
+                            else:
+                                current_switch_datetime = current_switch_date
+                            
+                            if nth_date > current_switch_datetime:
+                                print(f"    ❌ {contract}: 切换日期过早(当前:{current_switch_date}),建议调整至{nth_date.date()}")
+                                adjustment_info.update({
+                                    'needs_adjustment': True,
+                                    'suggested_switch_date': nth_date.date(),
+                                    'current_switch_date': current_switch_date
+                                })
+                            else:
+                                print(f"    ✅ {contract}: 切换日期{current_switch_date}合理,在第{required_days}个有效交易日之后")
+                            break
+            else:
+                print(f"    ❌ {contract}: 有效交易日不足{required_days}天(仅{valid_count}天),不符合切换要求")
+                adjustment_info['needs_adjustment'] = True
+        else:
+            # 没有空值数据,检查是否有足够的交易日
+            if valid_count >= required_days:
+                nth_date = valid_close_data.index[required_days - 1]
+                nth_price = valid_close_data.iloc[required_days - 1]
+                print(f"    ✅ {contract}: 第{required_days}个有效收盘价日期为{nth_date.date()},价格{nth_price:.2f}")
+            else:
+                print(f"    ❌ {contract}: 有效交易日不足{required_days}天(仅{valid_count}天)")
+                
+        return adjustment_info
+    
+    def _apply_dynamic_adjustments(self):
+        """应用动态调整建议,更新合约切换日期并重新获取数据"""
+        if self.verbose_logging:
+            print(f"\n=== 应用动态调整建议(共{len(self.adjustment_suggestions)}个) ===")
+        
+        adjustments_applied = []
+        
+        for suggestion in self.adjustment_suggestions:
+            if suggestion.get('suggested_switch_date'):
+                commodity = suggestion['commodity']
+                contract = suggestion['contract']
+                new_switch_date = suggestion['suggested_switch_date']
+                
+                print(f"📅 调整{commodity}的{contract}切换日期至{new_switch_date}")
+                
+                # 更新合约历史
+                if self._update_contract_switch_date(commodity, contract, new_switch_date):
+                    adjustments_applied.append(suggestion)
+        
+        # 如果有调整,重新获取相关的价格数据
+        if adjustments_applied:
+            print(f"✅ 完成{len(adjustments_applied)}个调整,重新获取相关价格数据")
+            self._refresh_price_data_for_adjustments(adjustments_applied)
+    
+    def _update_contract_switch_date(self, commodity, contract, new_switch_date):
+        """更新指定合约的切换日期"""
+        if commodity not in self.dominant_contract_history:
+            return False
+        
+        # 查找并更新对应的历史记录
+        for history_record in self.dominant_contract_history[commodity]:
+            if (history_record['selected_contract'] == contract and 
+                not history_record.get('is_initial', False)):
+                old_date = history_record['date']
+                history_record['date'] = new_switch_date
+                print(f"    📝 {contract}: 切换日期从{old_date}更新为{new_switch_date}")
+                return True
+        
+        return False
+    
+    def _refresh_price_data_for_adjustments(self, adjustments):
+        """为调整的合约重新获取价格数据"""
+        affected_commodities = set()
+        
+        for adjustment in adjustments:
+            commodity = adjustment['commodity']
+            affected_commodities.add(commodity)
+        
+        for commodity in affected_commodities:
+            print(f"🔄 重新获取{commodity}的价格数据...")
+            
+            # 重新计算日期范围
+            contract_date_ranges = self._determine_contract_date_ranges(commodity)
+            
+            # 重新获取每个合约的数据
+            for contract, date_range in contract_date_ranges.items():
+                start_date, end_date = date_range
+                
+                try:
+                    # 获取价格数据(使用新的日期范围)
+                    data = get_price(
+                        contract,
+                        start_date=start_date,
+                        end_date=end_date,
+                        frequency='daily',
+                        fields=['open', 'close', 'high', 'low', 'volume'],
+                        skip_paused=False,
+                        panel=False
+                    )
+                    
+                    if data is not None and len(data) > 0:
+                        self.price_data[commodity][contract] = data
+                        
+                        # 检查调整后的数据
+                        empty_data = data[data.isna().any(axis=1)]
+                        empty_count = len(empty_data)
+                        
+                        print(f"    ✅ {contract}: 重新获取{len(data)}条数据记录,空值{empty_count}条")
+                        
+                        if empty_count == 0:
+                            print(f"    🎉 {contract}: 空值数据已消除")
+                        
+                except Exception as e:
+                    print(f"    ❌ 重新获取{contract}数据时出错: {str(e)}")
+
+    def simulate_with_contract_switching(self):
+        """
+        模拟带有主力合约切换逻辑的交易
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤3: 带合约切换的交易模拟 ===")
+        
+        # 按日期顺序处理所有交易日
+        current_date = self.start_date.date()
+        end_date = self.end_date.date()
+        
+        while current_date <= end_date:
+            # 跳过非交易日
+            if current_date.weekday() >= 5:
+                current_date += datetime.timedelta(days=1)
+                continue
+            
+            # 检查每个商品的主力合约切换
+            for commodity in self.core_commodities.keys():
+                self._check_and_handle_contract_switch(commodity, current_date)
+            
+            # 处理正常的交易逻辑
+            self._process_daily_trading(current_date)
+            
+            current_date += datetime.timedelta(days=1)
+        
+        # 在交易循环结束后,计算所有未平仓头寸的最终盈亏
+        self._calculate_final_positions_pnl()
+        
+        if self.verbose_logging:
+            print("带合约切换的交易模拟完成")
+    
+    def _calculate_final_positions_pnl(self):
+        """
+        计算分析期结束时所有未平仓头寸的最终盈亏
+        将这些盈亏作为最终交易记录加入结果中
+        """
+        if self.verbose_logging:
+            print("\n=== 计算最终持仓盈亏 ===")
+        
+        final_date = self.end_date.date()
+        final_pnl_records = []
+        
+        # 添加诊断信息
+        if self.verbose_logging:
+            print(f"分析结束日期: {final_date}")
+            print(f"活跃头寸概览:")
+            for strategy_name in ['base_position', 'grid_trading', 'hedge_position']:
+                strategy_positions = self.active_positions.get(strategy_name, {})
+                total_positions = 0
+                open_positions = 0
+                for commodity, positions in strategy_positions.items():
+                    commodity_total = len(positions)
+                    commodity_open = sum(1 for p in positions.values() if p['status'] == 'open')
+                    total_positions += commodity_total
+                    open_positions += commodity_open
+                    if commodity_total > 0:
+                        print(f"  {strategy_name} - {commodity}: 总计 {commodity_total} 个头寸, 未平仓 {commodity_open} 个")
+                        
+                        # 详细列出所有头寸信息
+                        print(f"    详细头寸列表:")
+                        for pos_id, pos_info in positions.items():
+                            status = pos_info.get('status', 'Unknown')
+                            entry_price = pos_info.get('entry_price', 'N/A')
+                            contract = pos_info.get('contract', 'N/A')
+                            entry_date = pos_info.get('entry_date', 'N/A')
+                            quantity = pos_info.get('quantity', 'N/A')
+                            print(f"      {pos_id}: 状态={status}, 合约={contract}, 开仓价格={entry_price}, 日期={entry_date}, 数量={quantity}")
+                        
+                        print(f"  {strategy_name} 策略总计: {open_positions}/{total_positions} 个未平仓头寸")
+                        
+                        # 验证头寸计数的准确性
+                        actual_count = len(positions)
+                        open_count_verify = len([p for p in positions.values() if p.get('status') == 'open'])
+                        
+                        if actual_count != commodity_total or open_count_verify != commodity_open:
+                            print(f"    ⚠️  计数不匹配!实际头寸数: {actual_count}, 预期: {commodity_total}; 实际未平仓: {open_count_verify}, 预期: {commodity_open}")
+                        
+                        # 检查是否有重复的开仓价格(同一合约同一状态)
+                        open_positions_by_price = {}
+                        for pos_id, pos_info in positions.items():
+                            if pos_info.get('status') == 'open':
+                                price = pos_info.get('entry_price')
+                                contract = pos_info.get('contract')
+                                key = f"{contract}_{price}"
+                                if key not in open_positions_by_price:
+                                    open_positions_by_price[key] = []
+                                open_positions_by_price[key].append(pos_id)
+                        
+                        # for key, pos_ids in open_positions_by_price.items():
+                        #     if len(pos_ids) > 1:
+                        #         print(f"    ⚠️  发现重复的未平仓头寸: {key} -> {pos_ids}")
+                
+                print(f"  {strategy_name} 策略总计: {open_positions}/{total_positions} 个未平仓头寸")
+        
+        for strategy_name in ['base_position', 'grid_trading', 'hedge_position']:
+            strategy_positions = self.active_positions.get(strategy_name, {})
+            
+            for commodity, positions in strategy_positions.items():
+                # 获取当前合约和最终价格
+                current_contract = self._get_current_contract(commodity, final_date)
+                if not current_contract:
+                    if self.verbose_logging:
+                        print(f"  警告: 无法确定 {commodity} 在 {final_date} 的当前合约")
+                    continue
+                
+                final_price = self._get_price_on_date(commodity, current_contract, final_date, 'close')
+                if final_price is None:
+                    if self.verbose_logging:
+                        print(f"  警告: 无法获取 {commodity} {current_contract} 在 {final_date} 的价格")
+                    continue
+                
+                if self.verbose_logging and len(positions) > 0:
+                    print(f"  {commodity} {strategy_name}: 当前合约 {current_contract}, 结算价格 {final_price:.2f}")
+                
+                for position_id, position in positions.items():
+                    if self.verbose_logging:
+                        print(f"    检查头寸 {position_id}: 状态={position['status']}, 合约={position['contract']}, 开仓价格={position.get('entry_price', 'N/A')}")
+                    
+                    if position['status'] == 'open' and position['contract'] == current_contract:
+                        if self.verbose_logging:
+                            print(f"      匹配头寸进行结算: {position_id}")
+                            print(f"      头寸详情: 开仓日期={position.get('entry_date', 'N/A')}, 开仓价格={position['entry_price']}, 数量={position.get('quantity', 'N/A')}")
+                        
+                        # 判断是否为空头(对冲头寸)
+                        is_long = not position.get('is_short', False)
+                        
+                        # 计算最终盈亏
+                        profit_loss = self._calculate_futures_pnl(
+                            position['entry_price'], final_price, position['quantity'], commodity, is_long=is_long
+                        )
+                        
+                        if is_long:
+                            profit_loss_pct = (final_price - position['entry_price']) / position['entry_price']
+                        else:
+                            profit_loss_pct = (position['entry_price'] - final_price) / position['entry_price']
+                        
+                        # 计算持有天数
+                        entry_date = datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()
+                        days_held = (final_date - entry_date).days
+                        
+                        # 创建最终持仓盈亏记录
+                        final_record = {
+                            'commodity': commodity,
+                            'contract': current_contract,
+                            'strategy': strategy_name,
+                            'entry_date': position['entry_date'],
+                            'exit_date': final_date.strftime('%Y-%m-%d'),
+                            'entry_price': position['entry_price'],
+                            'exit_price': final_price,
+                            'quantity': position['quantity'],
+                            'profit_loss': profit_loss,
+                            'profit_loss_pct': profit_loss_pct,
+                            'days_held': days_held,
+                            'exit_reason': 'final_settlement'
+                        }
+                        
+                        # 如果是对冲头寸,添加is_short标记
+                        if strategy_name == 'hedge_position':
+                            final_record['is_short'] = True
+                        
+                        if self.verbose_logging:
+                            print(f"      创建最终结算记录: 头寸ID={position_id}, 开仓价格={position['entry_price']}, 结算价格={final_price:.2f}")
+                        
+                        final_pnl_records.append(final_record)
+                        
+                        # 将头寸标记为已平仓
+                        self.active_positions[strategy_name][commodity][position_id]['status'] = 'final_settled'
+                        
+                        if self.verbose_logging:
+                            print(f"  {commodity} {strategy_name} 最终结算: {position['entry_price']} -> {final_price:.2f}, 盈亏: {profit_loss:.2f}")
+        
+        # 将最终盈亏记录添加到交易结果中
+        for record in final_pnl_records:
+            strategy_name = record['strategy']
+            self.trading_results[strategy_name].append(record)
+        
+        
+        if self.verbose_logging:
+            total_final_records = len(final_pnl_records)
+            total_final_pnl = sum(record['profit_loss'] for record in final_pnl_records)
+            print(f"最终持仓结算完成,共 {total_final_records} 个头寸,总未实现盈亏: {total_final_pnl:.2f}")
+            
+            # 显示所有最终结算记录的详情
+            if final_pnl_records:
+                print(f"最终结算记录详情:")
+                for i, record in enumerate(final_pnl_records, 1):
+                    print(f"  {i}. {record['commodity']} {record['strategy']}: {record['entry_price']} -> {record['exit_price']:.2f}, 盈亏: {record['profit_loss']:.2f}, 合约: {record['contract']}")
+    
+    def _check_and_handle_contract_switch(self, commodity, current_date):
+        """
+        检查并处理主力合约切换
+        只有真正的合约切换才会触发平仓和重新建仓,初始设置不会
+        """
+        if commodity not in self.dominant_contract_history:
+            return
+        
+        # 检查当天是否有合约变化
+        contract_changes = self.dominant_contract_history[commodity]
+        for change in contract_changes:
+            if change['date'] == current_date:
+                # 检查是否为初始合约设置
+                if change.get('is_initial', False):
+                    # 初始合约设置,不需要平仓和重新建仓,只需要启动正常交易逻辑
+                    if self.verbose_logging:
+                        print(f"\n{current_date}: {commodity} 初始合约设置为 {change['selected_contract']}")
+                    return
+                
+                # 真正的合约切换
+                old_contract = self._get_current_contract(commodity, current_date - datetime.timedelta(days=1))
+                new_contract = change['selected_contract']
+                
+                if self.verbose_logging:
+                    print(f"\n{current_date}: {commodity} 合约切换 {old_contract} -> {new_contract}")
+                
+                # 平掉旧合约的所有头寸
+                self._close_all_positions_on_switch(commodity, old_contract, current_date)
+                
+                # 在新合约中重新建仓
+                self._reestablish_positions_in_new_contract(commodity, new_contract, current_date)
+                
+                break
+    
+    def _get_current_contract(self, commodity, date):
+        """获取指定日期的当前合约"""
+        if commodity not in self.dominant_contract_history:
+            return None
+        
+        contract_changes = self.dominant_contract_history[commodity]
+        current_contract = None
+        
+        for change in contract_changes:
+            if change['date'] <= date:
+                current_contract = change['selected_contract']
+            else:
+                break
+        
+        return current_contract
+    
+    def _close_all_positions_on_switch(self, commodity, old_contract, switch_date):
+        """
+        在合约切换时平掉旧合约的所有头寸
+        """
+        if self.verbose_logging:
+            print(f"  平掉 {old_contract} 的所有头寸")
+        
+        # 获取当日收盘价
+        close_price = self._get_price_on_date(commodity, old_contract, switch_date, 'close')
+        if close_price is None:
+            if self.verbose_logging:
+                print(f"    无法获取 {switch_date} 的价格数据,跳过平仓")
+            return
+        
+        # 平掉基础头寸交易的头寸
+        if commodity in self.active_positions['base_position']:
+            positions = self.active_positions['base_position'][commodity].copy()
+            for position_id, position in positions.items():
+                if position['contract'] == old_contract and position['status'] == 'open':
+                    # 使用正确的期货盈亏计算公式(基础头寸都是多头)
+                    profit_loss = self._calculate_futures_pnl(
+                        position['entry_price'], close_price, position['quantity'], commodity, is_long=True
+                    )
+                    profit_loss_pct = (close_price - position['entry_price']) / position['entry_price']
+                    
+                    trade_record = {
+                        'commodity': commodity,
+                        'contract': old_contract,
+                        'strategy': 'base_position',
+                        'entry_date': position['entry_date'],
+                        'exit_date': switch_date.strftime('%Y-%m-%d'),
+                        'entry_price': position['entry_price'],
+                        'exit_price': close_price,
+                        'quantity': position['quantity'],
+                        'profit_loss': profit_loss,
+                        'profit_loss_pct': profit_loss_pct,
+                        'days_held': (switch_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
+                        'exit_reason': 'contract_switch'
+                    }
+                    
+                    self.trading_results['base_position'].append(trade_record)
+                    self.active_positions['base_position'][commodity][position_id]['status'] = 'closed'
+                    self.active_positions['base_position'][commodity][position_id]['close_reason'] = 'contract_switch'
+                    
+                    if self.verbose_logging:
+                        print(f"    基础头寸平仓: {position['entry_price']} -> {close_price:.2f}, 盈亏: {profit_loss:.2f}")
+        
+        # 平掉网格交易的头寸
+        if commodity in self.active_positions['grid_trading']:
+            positions = self.active_positions['grid_trading'][commodity].copy()
+            for position_id, position in positions.items():
+                if position['contract'] == old_contract and position['status'] == 'open':
+                    # 使用正确的期货盈亏计算公式(网格交易都是多头)
+                    profit_loss = self._calculate_futures_pnl(
+                        position['entry_price'], close_price, position['quantity'], commodity, is_long=True
+                    )
+                    profit_loss_pct = (close_price - position['entry_price']) / position['entry_price']
+                    
+                    trade_record = {
+                        'commodity': commodity,
+                        'contract': old_contract,
+                        'strategy': 'grid_trading',
+                        'entry_date': position['entry_date'],
+                        'exit_date': switch_date.strftime('%Y-%m-%d'),
+                        'entry_price': position['entry_price'],
+                        'exit_price': close_price,
+                        'quantity': position['quantity'],
+                        'profit_loss': profit_loss,
+                        'profit_loss_pct': profit_loss_pct,
+                        'days_held': (switch_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
+                        'exit_reason': 'contract_switch'
+                    }
+                    
+                    self.trading_results['grid_trading'].append(trade_record)
+                    self.active_positions['grid_trading'][commodity][position_id]['status'] = 'closed'
+                    self.active_positions['grid_trading'][commodity][position_id]['close_reason'] = 'contract_switch'
+                    
+                    if self.verbose_logging:
+                        print(f"    网格头寸平仓: {position['entry_price']} -> {close_price:.2f}, 盈亏: {profit_loss:.2f}")
+        
+        # 平掉对冲头寸
+        if commodity in self.active_positions['hedge_position']:
+            positions = self.active_positions['hedge_position'][commodity].copy()
+            for position_id, position in positions.items():
+                if position['contract'] == old_contract and position['status'] == 'open':
+                    # 使用正确的期货盈亏计算公式(对冲头寸是空头)
+                    profit_loss = self._calculate_futures_pnl(
+                        position['entry_price'], close_price, position['quantity'], commodity, is_long=False
+                    )
+                    profit_loss_pct = (position['entry_price'] - close_price) / position['entry_price']
+                    
+                    trade_record = {
+                        'commodity': commodity,
+                        'contract': old_contract,
+                        'strategy': 'hedge_position',
+                        'entry_date': position['entry_date'],
+                        'exit_date': switch_date.strftime('%Y-%m-%d'),
+                        'entry_price': position['entry_price'],
+                        'exit_price': close_price,
+                        'quantity': position['quantity'],
+                        'profit_loss': profit_loss,
+                        'profit_loss_pct': profit_loss_pct,
+                        'days_held': (switch_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
+                        'exit_reason': 'contract_switch',
+                        'is_short': True
+                    }
+                    
+                    self.trading_results['hedge_position'].append(trade_record)
+                    self.active_positions['hedge_position'][commodity][position_id]['status'] = 'closed'
+                    self.active_positions['hedge_position'][commodity][position_id]['close_reason'] = 'contract_switch'
+                    
+                    if self.verbose_logging:
+                        print(f"    对冲头寸平仓: {position['entry_price']} -> {close_price:.2f}, 盈亏: {profit_loss:.2f}")
+    
+    def _reestablish_positions_in_new_contract(self, commodity, new_contract, switch_date):
+        """
+        在新合约中重新建仓
+        """
+        if self.verbose_logging:
+            print(f"  在 {new_contract} 中重新建仓")
+        
+        # 获取当日收盘价
+        close_price = self._get_price_on_date(commodity, new_contract, switch_date, 'close')
+        if close_price is None:
+            if self.verbose_logging:
+                print(f"    无法获取 {switch_date} 的价格数据,跳过重新建仓")
+            return
+        
+        # 基础头寸交易重新建仓
+        self._reestablish_base_positions(commodity, new_contract, close_price, switch_date)
+        
+        # 网格交易重新建仓
+        self._reestablish_grid_positions(commodity, new_contract, close_price, switch_date)
+        
+        # 对冲头寸重新建仓
+        self._reestablish_hedge_positions(commodity, new_contract, close_price, switch_date)
+    
+    def _reestablish_base_positions(self, commodity, new_contract, close_price, switch_date):
+        """重新建立基础头寸"""
+        if commodity not in self.base_position_grid:
+            return
+        
+        # 获取之前被平掉的基础头寸信息(按价格水平记录)
+        closed_positions = {}  # price_level -> quantity
+        if commodity in self.active_positions['base_position']:
+            for position in self.active_positions['base_position'][commodity].values():
+                if position['status'] == 'closed' and 'contract_switch' in position.get('close_reason', ''):
+                    # 只处理因合约切换而平掉的头寸
+                    original_price = position.get('original_price_level', position['entry_price'])
+                    if original_price not in closed_positions:
+                        closed_positions[original_price] = 0
+                    closed_positions[original_price] += position['quantity']
+                    
+                    if self.verbose_logging:
+                        print(f"    发现需重建的基础头寸: {original_price}水平 {position['quantity']}手 (原合约: {position['contract']})")
+        
+        # 根据当前价格和原始价格水平重建头寸
+        price_grid = self.base_position_grid[commodity]
+        reestablish_count = 0
+        
+        for target_price, configured_quantity in price_grid.items():
+            # 只有当目标价格大于等于当前价格时才重建头寸
+            # 这确保了只重建"应该持有"的价格水平头寸
+            if target_price >= close_price:
+                # 检查是否有该价格水平的平仓头寸需要重建
+                if target_price in closed_positions:
+                    quantity_to_reestablish = closed_positions[target_price]
+                    if self.verbose_logging:
+                        print(f"    重建条件检查: {target_price} >= {close_price:.2f} ✓ (重建原有平仓头寸)")
+                else:
+                    # 当前价格低于目标价格,应该建立该价格水平的头寸
+                    quantity_to_reestablish = configured_quantity
+                    if self.verbose_logging:
+                        print(f"    重建条件检查: {target_price} >= {close_price:.2f} ✓ (建立新头寸)")
+            else:
+                # 当前价格高于目标价格,不重建
+                if self.verbose_logging:
+                    print(f"    重建条件检查: {target_price} >= {close_price:.2f} ✗ (跳过重建)")
+                continue
+            
+            if quantity_to_reestablish > 0:
+                position_id = f"{commodity}_{new_contract}_{switch_date}_base_reestablish_{target_price}"
+                
+                if commodity not in self.active_positions['base_position']:
+                    self.active_positions['base_position'][commodity] = {}
+                
+                self.active_positions['base_position'][commodity][position_id] = {
+                    'contract': new_contract,
+                    'entry_date': switch_date.strftime('%Y-%m-%d'),
+                    'entry_price': close_price,  # 实际成交价格
+                    'original_price_level': target_price,  # 原始价格水平
+                    'quantity': quantity_to_reestablish,
+                    'status': 'open',
+                    'exit_target': self.base_position_exit_price.get(commodity),
+                    'hedge_checked': False  # 标记为未检查对冲,允许在换月当天进行对冲检查
+                }
+                
+                if self.verbose_logging:
+                    print(f"      创建重建头寸: {position_id}")
+                    print(f"      实际成交价格: {close_price}, 原始价格水平: {target_price}, 数量: {quantity_to_reestablish}")
+                
+                reestablish_count += quantity_to_reestablish
+                
+                if self.verbose_logging:
+                    print(f"    重建基础头寸 {target_price}水平: {quantity_to_reestablish} 手 @ {close_price:.2f}")
+        
+        if reestablish_count > 0 and self.verbose_logging:
+            print(f"    基础头寸重建完成,总计: {reestablish_count} 手")
+    
+    def _reestablish_grid_positions(self, commodity, new_contract, close_price, switch_date):
+        """重新建立网格交易头寸"""
+        if commodity not in self.grid_trading_config:
+            return
+        
+        config = self.grid_trading_config[commodity]
+        grid_size = config['grid_size']
+        quantity_per_grid = config['quantity_per_grid']
+        exit_grid_size = config['exit_grid_size']
+        
+        # 获取之前的网格头寸信息
+        previous_grid_levels = set()
+        if commodity in self.active_positions['grid_trading']:
+            for position in self.active_positions['grid_trading'][commodity].values():
+                if position['status'] == 'closed' and 'contract_switch' in position.get('close_reason', ''):
+                    # 只处理因合约切换而平掉的头寸
+                    previous_grid_levels.add(position['entry_price'])
+                    
+                    if self.verbose_logging:
+                        print(f"    发现需重建的网格头寸: {position['entry_price']}水平 {position['quantity']}手")
+        
+        # 仅在原始网格开仓价格大于等于当前价格时重新建仓
+        # 这确保了只重建"应该持有"的网格水平头寸
+        reestablish_count = 0
+        for grid_level in previous_grid_levels:
+            if grid_level >= close_price:
+                if self.verbose_logging:
+                    print(f"    网格重建条件检查: {grid_level} >= {close_price:.2f} ✓ (重建网格头寸)")
+                position_id = f"{commodity}_{new_contract}_{switch_date}_grid_{grid_level}"
+                
+                if commodity not in self.active_positions['grid_trading']:
+                    self.active_positions['grid_trading'][commodity] = {}
+                
+                self.active_positions['grid_trading'][commodity][position_id] = {
+                    'contract': new_contract,
+                    'entry_date': switch_date.strftime('%Y-%m-%d'),
+                    'entry_price': close_price,
+                    'original_grid_level': grid_level,
+                    'quantity': quantity_per_grid,
+                    'status': 'open',
+                    'exit_target': grid_level + exit_grid_size  # 保持原始退出价格
+                }
+                
+                reestablish_count += 1
+            else:
+                if self.verbose_logging:
+                    print(f"    网格重建条件检查: {grid_level} >= {close_price:.2f} ✗ (跳过重建)")
+                continue
+        
+        # 同时检查是否需要开立新的网格头寸(价格更低的情况)
+        start_price = config['start_price']
+        current_level = start_price
+        while current_level > close_price:
+            current_level -= grid_size
+            if current_level not in previous_grid_levels and current_level > 0:
+                # 这是一个新的网格水平
+                position_id = f"{commodity}_{new_contract}_{switch_date}_grid_new_{current_level}"
+                
+                if commodity not in self.active_positions['grid_trading']:
+                    self.active_positions['grid_trading'][commodity] = {}
+                
+                self.active_positions['grid_trading'][commodity][position_id] = {
+                    'contract': new_contract,
+                    'entry_date': switch_date.strftime('%Y-%m-%d'),
+                    'entry_price': close_price,
+                    'original_grid_level': current_level,
+                    'quantity': quantity_per_grid,
+                    'status': 'open',
+                    'exit_target': current_level + exit_grid_size
+                }
+                
+                reestablish_count += 1
+        
+        if self.verbose_logging and reestablish_count > 0:
+            print(f"    重建网格头寸: {reestablish_count} 个网格 @ {close_price:.2f}")
+    
+    def _reestablish_hedge_positions(self, commodity, new_contract, close_price, switch_date):
+        """重新建立对冲头寸"""
+        if not self.config.HEDGE_ENABLED:
+            return
+        
+        if commodity not in self.base_position_grid:
+            return
+        
+        # 计算等权重数量
+        hedge_quantity = self._calculate_equal_weight_hedge_quantity(commodity)
+        if hedge_quantity == 0:
+            return
+        
+        # 检查是否暂停建立新对冲
+        self._initialize_hedge_state(commodity)
+        if self.hedge_state[commodity].get('suspend_new_hedges', False):
+            if self.verbose_logging:
+                print(f"    对冲重建:波动率过滤暂停,跳过对冲头寸重建")
+            return
+        
+        # 获取之前被平掉的对冲头寸信息
+        closed_hedge_positions = {}  # price_level -> True
+        if commodity in self.active_positions['hedge_position']:
+            for position in self.active_positions['hedge_position'][commodity].values():
+                if position['status'] == 'closed' and 'contract_switch' in position.get('close_reason', ''):
+                    original_price = position.get('original_price_level', position['entry_price'])
+                    closed_hedge_positions[original_price] = True
+                    
+                    if self.verbose_logging:
+                        print(f"    发现需重建的对冲头寸: {original_price}水平 {position['quantity']}手")
+        
+        # 检查基础头寸,在有基础头寸的价格水平重建对冲头寸
+        reestablish_count = 0
+        if commodity in self.active_positions['base_position']:
+            for position in self.active_positions['base_position'][commodity].values():
+                if position['contract'] == new_contract and position['status'] == 'open':
+                    original_price_level = position.get('original_price_level', position['entry_price'])
+                    
+                    # 检查该价格水平是否已有对冲头寸
+                    has_hedge = False
+                    if commodity in self.active_positions['hedge_position']:
+                        for hedge_pos in self.active_positions['hedge_position'][commodity].values():
+                            if (hedge_pos['contract'] == new_contract and 
+                                hedge_pos['status'] == 'open' and
+                                hedge_pos.get('original_price_level', hedge_pos['entry_price']) == original_price_level):
+                                has_hedge = True
+                                break
+                    
+                    # 如果没有对冲头寸,则建立
+                    if not has_hedge:
+                        position_id = f"{commodity}_{new_contract}_{switch_date}_hedge_reestablish_{original_price_level}"
+                        
+                        if commodity not in self.active_positions['hedge_position']:
+                            self.active_positions['hedge_position'][commodity] = {}
+                        
+                        self.active_positions['hedge_position'][commodity][position_id] = {
+                            'contract': new_contract,
+                            'entry_date': switch_date.strftime('%Y-%m-%d'),
+                            'entry_price': close_price,
+                            'original_price_level': original_price_level,
+                            'quantity': hedge_quantity,
+                            'status': 'open',
+                            'is_short': True
+                        }
+                        
+                        reestablish_count += 1
+                        
+                        if self.verbose_logging:
+                            print(f"    重建对冲头寸: 网格{original_price_level}水平 @ {close_price:.2f}, 数量: {hedge_quantity}手(空头)")
+        
+        if self.verbose_logging and reestablish_count > 0:
+            print(f"    重建对冲头寸完成: {reestablish_count} 个头寸")
+        
+        # 更新最后建仓日期(如果有重建头寸)
+        if reestablish_count > 0 and commodity in self.hedge_state:
+            self.hedge_state[commodity]['last_hedge_entry_date'] = switch_date.strftime('%Y-%m-%d')
+        
+        # 重置对冲状态(因为是新合约)
+        if commodity in self.hedge_state:
+            self.hedge_state[commodity]['peak_profit'] = 0
+    
+    def _get_price_on_date(self, commodity, contract, date, price_type='close'):
+        """获取指定日期和合约的价格(增强NaN问题诊断)"""
+        if commodity not in self.price_data or contract not in self.price_data[commodity]:
+            if self.verbose_logging:
+                print(f"  ❌ 价格数据不存在: {commodity} -> {contract}")
+            return None
+        
+        price_data = self.price_data[commodity][contract]
+        
+        # 找到日期对应的价格
+        target_date = date if isinstance(date, datetime.date) else date.date()
+        
+        for idx, row in price_data.iterrows():
+            if idx.date() == target_date:
+                price_value = row[price_type]
+                
+                if self.verbose_logging:
+                    print(f'{price_type}的价格是: {price_value}')
+                
+                # 如果价格为NaN,进行详细诊断
+                if pd.isna(price_value):
+                    self._diagnose_nan_price_issue(commodity, contract, target_date, price_type, row)
+                    return None
+                else:
+                    return price_value
+        
+        # 如果没有找到精确日期,尝试查找最近的交易日
+        if self.verbose_logging:
+            print(f"  ⚠️  未找到 {contract} 在 {target_date} 的数据,尝试查找最近交易日...")
+        
+        return self._get_nearest_trading_day_price(commodity, contract, target_date, price_type)
+    
+    def _diagnose_nan_price_issue(self, commodity, contract, date, price_type, row):
+        """诊断NaN价格问题的根本原因"""
+        if self.verbose_logging:
+            print(f"  🔍 NaN价格问题诊断: {commodity} {contract} {date}")
+            print(f"    目标价格类型: {price_type}")
+            print(f"    该日所有价格数据: 开盘={row['open']}, 收盘={row['close']}, 最高={row['high']}, 最低={row['low']}, 成交量={row['volume']}")
+            
+            # 检查是否所有价格都是NaN
+            price_fields = ['open', 'close', 'high', 'low']
+            nan_fields = [field for field in price_fields if pd.isna(row[field])]
+            valid_fields = [field for field in price_fields if not pd.isna(row[field])]
+            
+            if len(nan_fields) == len(price_fields):
+                print(f"    ❌ 所有价格字段都为NaN - 可能该合约在此日期未开始交易")
+            else:
+                print(f"    ⚠️  部分价格字段为NaN: {nan_fields}")
+                print(f"    ✅ 有效价格字段: {valid_fields}")
+                
+                # 如果有有效价格,尝试使用替代方案
+                if valid_fields:
+                    fallback_price = row[valid_fields[0]]
+                    print(f"    💡 建议使用替代价格: {valid_fields[0]} = {fallback_price}")
+            
+            # 检查成交量是否为0或NaN
+            if pd.isna(row['volume']) or row['volume'] == 0:
+                print(f"    ⚠️  成交量异常: {row['volume']} - 可能该合约在此日期无交易活动")
+            
+            # 检查是否是合约刚上市的情况
+            price_data = self.price_data[commodity][contract]
+            first_valid_date = None
+            for idx, data_row in price_data.iterrows():
+                if not pd.isna(data_row['close']):
+                    first_valid_date = idx.date()
+                    break
+            
+            if first_valid_date and date < first_valid_date:
+                print(f"    🔍 合约首次有效交易日: {first_valid_date} (查询日期 {date} 早于首次交易日)")
+                print(f"    💡 建议: 合约 {contract} 在 {date} 可能尚未开始交易")
+            
+            # 提供解决建议
+            print(f"    📋 解决建议:")
+            print(f"      1. 检查合约 {contract} 的上市日期")
+            print(f"      2. 验证合约代码是否正确")
+            print(f"      3. 考虑调整合约切换日期")
+            if valid_fields:
+                print(f"      4. 临时使用替代价格: {valid_fields[0]} = {row[valid_fields[0]]}")
+    
+    def _get_nearest_trading_day_price(self, commodity, contract, target_date, price_type):
+        """获取最近交易日的价格"""
+        price_data = self.price_data[commodity][contract]
+        
+        # 查找最近的交易日(前后5天范围内)
+        search_range = 5
+        for offset in range(1, search_range + 1):
+            # 先查找之后的日期
+            future_date = target_date + datetime.timedelta(days=offset)
+            for idx, row in price_data.iterrows():
+                if idx.date() == future_date:
+                    price_value = row[price_type]
+                    if not pd.isna(price_value):
+                        if self.verbose_logging:
+                            print(f"  ✅ 使用后续交易日 {future_date} 的价格: {price_value}")
+                        return price_value
+                    break
+            
+            # 再查找之前的日期
+            past_date = target_date - datetime.timedelta(days=offset)
+            for idx, row in price_data.iterrows():
+                if idx.date() == past_date:
+                    price_value = row[price_type]
+                    if not pd.isna(price_value):
+                        if self.verbose_logging:
+                            print(f"  ✅ 使用前期交易日 {past_date} 的价格: {price_value}")
+                        return price_value
+                    break
+        
+        if self.verbose_logging:
+            print(f"  ❌ 在 {search_range} 天范围内未找到有效的 {price_type} 价格")
+        return None
+    
+    def _process_daily_trading(self, current_date):
+        """处理每日的正常交易逻辑"""
+        for commodity in self.core_commodities.keys():
+            current_contract = self._get_current_contract(commodity, current_date)
+            if not current_contract:
+                continue
+            
+            # 获取当日价格数据
+            daily_prices = self._get_daily_prices(commodity, current_contract, current_date)
+            if not daily_prices:
+                continue
+            
+            # 检查基础头寸入场和退出机会
+            self._check_base_position_trading(commodity, current_contract, current_date, daily_prices)
+            
+            # 检查网格交易入场和退出机会
+            self._check_grid_trading(commodity, current_contract, current_date, daily_prices)
+            
+            # ========== 对冲策略相关检查 ==========
+            if self.config.HEDGE_ENABLED:
+                # 检查波动率过滤
+                self._check_atr_volatility_filter(commodity, current_contract, current_date)
+                
+                # 检查基础头寸建仓完成状态
+                self._check_base_positions_complete(commodity, current_contract)
+                
+                # 检查对冲建仓机会
+                self._check_hedge_position_entry(commodity, current_contract, current_date, daily_prices)
+                
+                # 检查对冲止盈
+                self._check_hedge_take_profit(commodity, current_contract, current_date, daily_prices)
+                
+                # 检查对冲止损
+                self._check_hedge_stop_loss(commodity, current_contract, current_date, daily_prices)
+    
+    def _get_daily_prices(self, commodity, contract, date):
+        """获取指定日期的价格数据"""
+        if commodity not in self.price_data or contract not in self.price_data[commodity]:
+            return None
+        
+        price_data = self.price_data[commodity][contract]
+        
+        for idx, row in price_data.iterrows():
+            if idx.date() == date:
+                return {
+                    'open': row['open'],
+                    'close': row['close'],
+                    'high': row['high'],
+                    'low': row['low'],
+                    'volume': row['volume']
+                }
+        
+        return None
+    
+    def _check_base_position_trading(self, commodity, contract, current_date, daily_prices):
+        """检查基础头寸交易机会
+        
+        逻辑:每日主动检查所有网格水平
+        - 如果当前收盘价低于某个网格水平价格
+        - 且该网格水平没有未平仓头寸
+        - 则以当日收盘价在该网格水平开仓
+        """
+        if commodity not in self.base_position_grid:
+            return
+        
+        # 检查入场机会 - 遍历所有网格水平
+        price_grid = self.base_position_grid[commodity]
+        current_close_price = daily_prices['close']
+        daily_low = daily_prices['low']
+        daily_high = daily_prices['high']
+        
+        for entry_price, quantity in price_grid.items():
+            # 检查是否已经有这个价格水平的头寸
+            position_exists = False
+            if commodity in self.active_positions['base_position']:
+                for position in self.active_positions['base_position'][commodity].values():
+                    if (position['contract'] == contract and position['status'] == 'open'):
+                        # 检查原始价格水平
+                        position_price_level = position.get('original_price_level', position['entry_price'])
+                        if position_price_level == entry_price:
+                            position_exists = True
+                            break
+            
+            # 主动开仓逻辑:当前价格低于网格水平价格 且 该水平没有头寸
+            if not position_exists and current_close_price < entry_price:
+                price_touched_grid = daily_low <= entry_price <= daily_high
+                actual_entry_price = entry_price if price_touched_grid else current_close_price
+                # 以符合规则的价格建立头寸
+                position_id = f"{commodity}_{contract}_{current_date}_base_{entry_price}"
+                if commodity not in self.active_positions['base_position']:
+                    self.active_positions['base_position'][commodity] = {}
+                
+                self.active_positions['base_position'][commodity][position_id] = {
+                    'contract': contract,
+                    'entry_date': current_date.strftime('%Y-%m-%d'),
+                    'entry_price': actual_entry_price,
+                    'original_price_level': entry_price,  # 记录原始网格水平
+                    'quantity': quantity,
+                    'status': 'open',
+                    'exit_target': self.base_position_exit_price.get(commodity),
+                    'hedge_checked': False  # 初始化对冲检查标记
+                }
+                
+                if self.verbose_logging:
+                    print(f"  {current_date}: {commodity} 基础头寸入场 @ 网格{entry_price} (实际价格: {actual_entry_price:.2f}), 数量: {quantity},当天收盘价{current_close_price:.2f}低于网格价格{entry_price}")
+                    print(f" 当天最低价:{daily_low}, 当天最高价:{daily_high}")
+                    if price_touched_grid:
+                        print(f"  价格区间触及网格价{entry_price},按网格价成交")
+                    else:
+                        print(f"  价格区间未触及网格价{entry_price},按收盘价成交")
+        
+        # 检查退出机会
+        exit_target = self.base_position_exit_price.get(commodity)
+        if exit_target and commodity in self.active_positions['base_position']:
+            positions_to_close = []
+            for position_id, position in self.active_positions['base_position'][commodity].items():
+                if position['contract'] == contract and position['status'] == 'open' and daily_prices['high'] >= exit_target:
+                    positions_to_close.append(position_id)
+            
+            for position_id in positions_to_close:
+                position = self.active_positions['base_position'][commodity][position_id]
+                exit_price = min(exit_target, daily_prices['high'])
+                
+                # 使用正确的期货盈亏计算公式(基础头寸都是多头)
+                profit_loss = self._calculate_futures_pnl(
+                    position['entry_price'], exit_price, position['quantity'], commodity, is_long=True
+                )
+                profit_loss_pct = (exit_price - position['entry_price']) / position['entry_price']
+                
+                trade_record = {
+                    'commodity': commodity,
+                    'contract': contract,
+                    'strategy': 'base_position',
+                    'entry_date': position['entry_date'],
+                    'exit_date': current_date.strftime('%Y-%m-%d'),
+                    'entry_price': position['entry_price'],
+                    'exit_price': exit_price,
+                    'quantity': position['quantity'],
+                    'profit_loss': profit_loss,
+                    'profit_loss_pct': profit_loss_pct,
+                    'days_held': (current_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
+                    'exit_reason': 'target_reached'
+                }
+                
+                self.trading_results['base_position'].append(trade_record)
+                self.active_positions['base_position'][commodity][position_id]['status'] = 'closed'
+                
+                if self.verbose_logging:
+                    print(f"  {current_date}: {commodity} 基础头寸退出 {position['entry_price']} -> {exit_price:.2f}, 盈亏: {profit_loss:.2f}")
+    
+    def _check_grid_trading(self, commodity, contract, current_date, daily_prices):
+        """检查网格交易机会"""
+        if commodity not in self.grid_trading_config:
+            return
+        
+        config = self.grid_trading_config[commodity]
+        start_price = config['start_price']
+        grid_size = config['grid_size']
+        quantity_per_grid = config['quantity_per_grid']
+        exit_grid_size = config['exit_grid_size']
+        
+        # 检查入场机会
+        current_level = start_price
+        while current_level > daily_prices['low'] and current_level > 0:
+            position_exists = False
+            if commodity in self.active_positions['grid_trading']:
+                for position in self.active_positions['grid_trading'][commodity].values():
+                    if (position['contract'] == contract and 
+                        position.get('original_grid_level', position['entry_price']) == current_level and 
+                        position['status'] == 'open'):
+                        position_exists = True
+                        break
+            
+            if not position_exists and daily_prices['low'] <= current_level <= daily_prices['high']:
+                position_id = f"{commodity}_{contract}_{current_date}_grid_{current_level}"
+                if commodity not in self.active_positions['grid_trading']:
+                    self.active_positions['grid_trading'][commodity] = {}
+                
+                self.active_positions['grid_trading'][commodity][position_id] = {
+                    'contract': contract,
+                    'entry_date': current_date.strftime('%Y-%m-%d'),
+                    'entry_price': current_level,
+                    'original_grid_level': current_level,
+                    'quantity': quantity_per_grid,
+                    'status': 'open',
+                    'exit_target': current_level + exit_grid_size
+                }
+                
+                if self.verbose_logging:
+                    print(f"  {current_date}: {commodity} 网格入场 @ {current_level},数量:{quantity_per_grid},当天最低价为{daily_prices['low']},最高价为{daily_prices['high']}")
+            
+            current_level -= grid_size
+        
+        # 检查退出机会
+        if commodity in self.active_positions['grid_trading']:
+            positions_to_close = []
+            for position_id, position in self.active_positions['grid_trading'][commodity].items():
+                if position['contract'] == contract and position['status'] == 'open':
+                    if daily_prices['high'] >= position['exit_target']:
+                        positions_to_close.append(position_id)
+            
+            for position_id in positions_to_close:
+                position = self.active_positions['grid_trading'][commodity][position_id]
+                exit_price = position['exit_target']
+                
+                # 使用正确的期货盈亏计算公式(网格交易都是多头)
+                profit_loss = self._calculate_futures_pnl(
+                    position['entry_price'], exit_price, position['quantity'], commodity, is_long=True
+                )
+                profit_loss_pct = (exit_price - position['entry_price']) / position['entry_price']
+                
+                trade_record = {
+                    'commodity': commodity,
+                    'contract': contract,
+                    'strategy': 'grid_trading',
+                    'entry_date': position['entry_date'],
+                    'exit_date': current_date.strftime('%Y-%m-%d'),
+                    'entry_price': position['entry_price'],
+                    'exit_price': exit_price,
+                    'quantity': position['quantity'],
+                    'profit_loss': profit_loss,
+                    'profit_loss_pct': profit_loss_pct,
+                    'days_held': (current_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
+                    'exit_reason': 'target_reached'
+                }
+                
+                self.trading_results['grid_trading'].append(trade_record)
+                self.active_positions['grid_trading'][commodity][position_id]['status'] = 'closed'
+                
+                if self.verbose_logging:
+                    print(f"  {current_date}: {commodity} 网格退出 {position['entry_price']} -> {exit_price:.2f}, 盈亏: {profit_loss:.2f} ({profit_loss_pct:.2%})")
+    
+    # ==================== 对冲策略相关方法 ====================
+    
+    def _calculate_equal_weight_hedge_quantity(self, commodity):
+        """
+        计算等权重对冲数量
+        
+        从BASE_POSITION_GRID中汇总所有数量并平均分配到所有价格水平
+        
+        参数:
+            commodity: 商品代码
+            
+        返回:
+            等权重数量(整数)
+        """
+        if commodity not in self.base_position_grid:
+            return 0
+        
+        price_grid = self.base_position_grid[commodity]
+        
+        # 确保price_grid是字典类型
+        if not isinstance(price_grid, dict):
+            if self.verbose_logging:
+                print(f"  ⚠️  对冲数量计算错误: {commodity} 的price_grid不是字典类型,而是{type(price_grid)}")
+            return 0
+        
+        # 显式转换dict.values()为列表并求和,确保得到数值
+        quantities = list(price_grid.values())
+        total_quantity = sum(quantities)
+        num_levels = len(price_grid)
+        
+        if num_levels == 0:
+            return 0
+        
+        # 确保total_quantity是数值类型(包括numpy类型)
+        try:
+            # 尝试转换为Python int,这会处理numpy类型
+            total_quantity = int(total_quantity)
+        except (TypeError, ValueError):
+            if self.verbose_logging:
+                print(f"  ⚠️  对冲数量计算错误: total_quantity无法转换为数值类型,类型为{type(total_quantity)}: {total_quantity}")
+            return 0
+        
+        # 计算平均数量并向下取整
+        equal_weight_quantity = int(total_quantity / num_levels)
+        
+        return equal_weight_quantity
+    
+    def _initialize_hedge_state(self, commodity):
+        """初始化商品的对冲状态"""
+        if commodity not in self.hedge_state:
+            self.hedge_state[commodity] = {
+                'base_positions_complete': False,
+                'peak_profit': 0,
+                'atr_history': [],
+                'suspend_new_hedges': False,
+                # 新增:用于追踪多头仓位回撤和对冲分阶段止盈
+                'max_drawdown': 0,  # 多头仓位最大浮亏(绝对值)
+                'max_drawdown_price': 0,  # 出现最大浮亏时的价格(最大浮亏价格)
+                'first_tp_triggered': False,  # 一级止盈是否已触发
+                'hedge_quantity_closed': 0,  # 已平仓的对冲数量(用于计算剩余对冲比例)
+                'last_hedge_entry_date': None  # 最后一次对冲建仓日期
+            }
+    
+    def _check_base_positions_complete(self, commodity, contract):
+        """
+        检查基础头寸是否建仓完成
+        
+        当所有配置的网格价格水平都有持仓(状态为'open')时,认为建仓完成
+        
+        参数:
+            commodity: 商品代码
+            contract: 合约代码
+        """
+        if not self.config.HEDGE_ENABLED:
+            return
+        
+        if commodity not in self.base_position_grid:
+            return
+        
+        self._initialize_hedge_state(commodity)
+        
+        # 获取所有配置的价格水平
+        configured_levels = set(self.base_position_grid[commodity].keys())
+        
+        # 获取当前持仓的价格水平
+        open_levels = set()
+        if commodity in self.active_positions['base_position']:
+            for position in self.active_positions['base_position'][commodity].values():
+                if position['contract'] == contract and position['status'] == 'open':
+                    original_level = position.get('original_price_level', position['entry_price'])
+                    open_levels.add(original_level)
+        
+        # 检查是否所有配置水平都有持仓
+        was_complete = self.hedge_state[commodity]['base_positions_complete']
+        is_complete = configured_levels == open_levels
+        
+        self.hedge_state[commodity]['base_positions_complete'] = is_complete
+        
+        # 状态变化时记录日志
+        if is_complete and not was_complete:
+            if self.verbose_logging:
+                print(f"  ✅ {commodity} 基础头寸建仓完成:所有{len(configured_levels)}个价格水平都有持仓")
+    
+    def _calculate_atr(self, commodity, contract, current_date):
+        """
+        计算ATR并判断波动率过滤条件
+        
+        参数:
+            commodity: 商品代码
+            contract: 合约代码
+            current_date: 当前日期
+            
+        返回:
+            (current_atr, atr_3m_avg, pass_filter): 当前ATR、3个月ATR平均值、是否通过过滤
+        """
+        if commodity not in self.price_data or contract not in self.price_data[commodity]:
+            return None, None, False
+        
+        price_data = self.price_data[commodity][contract]
+        target_date = current_date if isinstance(current_date, datetime.date) else current_date.date()
+        
+        # 获取历史数据
+        historical_data = price_data[price_data.index.date <= target_date]
+        
+        if len(historical_data) < self.config.HEDGE_ATR_PERIOD:
+            # 数据不足,尝试扩展
+            extended_data = self._extend_price_data_for_ma(commodity, contract, current_date, 
+                                                           self.config.HEDGE_ATR_PERIOD * 6)
+            if extended_data is not None and len(extended_data) >= self.config.HEDGE_ATR_PERIOD:
+                historical_data = extended_data
+            else:
+                if self.verbose_logging:
+                    print(f"  ⚠️  ATR计算:{commodity} 数据不足(需要{self.config.HEDGE_ATR_PERIOD}天)")
+                return None, None, False
+        
+        # 计算True Range
+        historical_data = historical_data.copy()
+        historical_data['h-l'] = historical_data['high'] - historical_data['low']
+        historical_data['h-pc'] = abs(historical_data['high'] - historical_data['close'].shift(1))
+        historical_data['l-pc'] = abs(historical_data['low'] - historical_data['close'].shift(1))
+        historical_data['tr'] = historical_data[['h-l', 'h-pc', 'l-pc']].max(axis=1)
+        
+        # 计算14日ATR
+        atr_data = historical_data['tr'].rolling(window=self.config.HEDGE_ATR_PERIOD).mean()
+        current_atr = atr_data.iloc[-1]
+        
+        # 计算3个月ATR平均值
+        lookback_days = self.config.HEDGE_ATR_LOOKBACK_MONTHS * 30
+        if len(atr_data) >= lookback_days:
+            atr_3m_avg = atr_data.iloc[-lookback_days:].mean()
+        else:
+            # 使用所有可用数据
+            atr_3m_avg = atr_data.mean()
+        
+        # 判断是否通过过滤
+        threshold = self.config.HEDGE_ATR_THRESHOLD * atr_3m_avg
+        pass_filter = current_atr >= threshold
+        
+        return current_atr, atr_3m_avg, pass_filter
+    
+    def _check_atr_volatility_filter(self, commodity, contract, current_date):
+        """
+        检查ATR波动率过滤条件
+        
+        当HEDGE_USE_ATR_FILTER=True且当前14日ATR < 0.7×(3个月ATR平均值)时,暂停建立新的对冲头寸
+        当HEDGE_USE_ATR_FILTER=False时,不应用波动率过滤,始终允许建立对冲头寸
+        
+        参数:
+            commodity: 商品代码
+            contract: 合约代码
+            current_date: 当前日期
+        """
+        if not self.config.HEDGE_ENABLED:
+            return
+        
+        self._initialize_hedge_state(commodity)
+        
+        # 如果未启用ATR过滤,始终允许对冲
+        if not self.config.HEDGE_USE_ATR_FILTER:
+            self.hedge_state[commodity]['suspend_new_hedges'] = False
+            return
+        
+        current_atr, atr_3m_avg, pass_filter = self._calculate_atr(commodity, contract, current_date)
+        
+        if current_atr is None:
+            return
+        
+        was_suspended = self.hedge_state[commodity]['suspend_new_hedges']
+        should_suspend = not pass_filter
+        
+        self.hedge_state[commodity]['suspend_new_hedges'] = should_suspend
+        
+        # 状态变化时记录日志
+        if should_suspend != was_suspended:
+            if should_suspend:
+                if self.verbose_logging:
+                    print(f"  🚫 {commodity} 波动率过滤触发:暂停新对冲 (ATR={current_atr:.2f} < {self.config.HEDGE_ATR_THRESHOLD}×{atr_3m_avg:.2f}={self.config.HEDGE_ATR_THRESHOLD * atr_3m_avg:.2f})")
+            else:
+                if self.verbose_logging:
+                    print(f"  ✅ {commodity} 波动率恢复:允许新对冲 (ATR={current_atr:.2f} >= {self.config.HEDGE_ATR_THRESHOLD}×{atr_3m_avg:.2f}={self.config.HEDGE_ATR_THRESHOLD * atr_3m_avg:.2f})")
+    
+    def _check_hedge_position_entry(self, commodity, contract, current_date, daily_prices):
+        """
+        检查对冲头寸建仓机会
+        
+        只对当天新建仓的基础头寸进行对冲检查。
+        对冲操作只在基础头寸开仓当天有效,如果当天不满足对冲条件,
+        则标记该基础头寸为"无需对冲",后续不再检查。
+        
+        参数:
+            commodity: 商品代码
+            contract: 合约代码
+            current_date: 当前日期
+            daily_prices: 当日价格数据
+        """
+        if not self.config.HEDGE_ENABLED:
+            return
+        
+        if commodity not in self.base_position_grid:
+            return
+        
+        self._initialize_hedge_state(commodity)
+        
+        # 检查是否暂停建立新对冲
+        should_hedge = not self.hedge_state[commodity]['suspend_new_hedges']
+        
+        # 计算等权重数量
+        hedge_quantity = self._calculate_equal_weight_hedge_quantity(commodity)
+        if hedge_quantity == 0:
+            return
+        
+        current_close_price = daily_prices['close']
+        current_date_str = current_date.strftime('%Y-%m-%d')
+        
+        # 只检查当天新建仓的基础头寸
+        if commodity in self.active_positions['base_position']:
+            for position_id, position in self.active_positions['base_position'][commodity].items():
+                # 只处理当天建仓且状态为open的头寸
+                if (position['contract'] == contract and 
+                    position['status'] == 'open' and
+                    position['entry_date'] == current_date_str):
+                    
+                    # 检查该头寸是否已经处理过对冲检查
+                    if position.get('hedge_checked', False):
+                        continue
+                    
+                    # 标记该头寸已经进行过对冲检查
+                    self.active_positions['base_position'][commodity][position_id]['hedge_checked'] = True
+                    
+                    original_grid_level = position.get('original_price_level', position['entry_price'])
+                    
+                    # 关键修复:对冲头寸入场条件
+                    # 只有当日价格区间实际触及网格价格时,才在该网格水平建立对冲头寸
+                    # 检查条件:当日最低价 <= 网格价格 <= 当日最高价
+                    daily_low = daily_prices['low']
+                    daily_high = daily_prices['high']
+                    price_range_touches_grid = daily_low <= original_grid_level <= daily_high
+                    
+                    if not price_range_touches_grid:
+                        # 价格区间未触及网格水平,跳过对冲
+                        self.active_positions['base_position'][commodity][position_id]['hedged'] = False
+                        self.active_positions['base_position'][commodity][position_id]['hedge_skipped_reason'] = 'price_not_touch_grid'
+                        
+                        if self.verbose_logging:
+                            print(f"  ⏭️  {current_date}: {commodity} 基础头寸@网格{original_grid_level}未对冲 "
+                                  f"(原因: 价格区间[{daily_low:.2f}, {daily_high:.2f}]未触及网格{original_grid_level})")
+                        continue
+                    
+                    # 如果满足对冲条件,则建立对冲头寸
+                    if should_hedge:
+                        hedge_position_id = f"{commodity}_{contract}_{current_date}_hedge_{original_grid_level}"
+                        
+                        if commodity not in self.active_positions['hedge_position']:
+                            self.active_positions['hedge_position'][commodity] = {}
+                        
+                        actual_hedge_entry_price = original_grid_level
+                        self.active_positions['hedge_position'][commodity][hedge_position_id] = {
+                            'contract': contract,
+                            'entry_date': current_date_str,
+                            'entry_price': actual_hedge_entry_price,
+                            'fill_price': actual_hedge_entry_price,
+                            'original_price_level': original_grid_level,
+                            'quantity': hedge_quantity,
+                            'status': 'open',
+                            'is_short': True,
+                            'base_position_id': position_id  # 关联到对应的基础头寸
+                        }
+                        
+                        # 在基础头寸中记录已对冲状态
+                        self.active_positions['base_position'][commodity][position_id]['hedged'] = True
+                        self.active_positions['base_position'][commodity][position_id]['hedge_position_id'] = hedge_position_id
+                        
+                        # 更新最后建仓日期
+                        self.hedge_state[commodity]['last_hedge_entry_date'] = current_date_str
+                        
+                        if self.verbose_logging:
+                            print(f"  📉 {current_date}: {commodity} 对冲头寸建仓 @ 网格{original_grid_level} (记录开仓价: {original_grid_level:.2f}, 实际成交价: {actual_hedge_entry_price:.2f},当日收盘价: {current_close_price:.2f}), 数量: {hedge_quantity}手(空头)")
+                    else:
+                        # 不满足对冲条件,标记为无需对冲
+                        self.active_positions['base_position'][commodity][position_id]['hedged'] = False
+                        self.active_positions['base_position'][commodity][position_id]['hedge_skipped_reason'] = 'volatility_filter' if self.hedge_state[commodity]['suspend_new_hedges'] else 'other'
+                        
+                        if self.verbose_logging:
+                            reason = '波动率过滤' if self.hedge_state[commodity]['suspend_new_hedges'] else '其他条件'
+                            print(f"  ⏭️  {current_date}: {commodity} 基础头寸@网格{original_grid_level}未对冲 (原因: {reason})")
+    
+    def _check_hedge_take_profit(self, commodity, contract, current_date, daily_prices):
+        """
+        检查对冲头寸分阶段止盈条件
+        
+        策略说明:
+        对冲仓位是为了保护多头仓位免受回撤影响。随着多头仓位回升,分阶段平掉对冲仓位:
+        
+        一级止盈(50%对冲仓位):
+        - 触发条件:多头浮亏从最大回撤水平回升约30%时
+        - 操作:平掉50%的对冲仓位
+        
+        二级止盈(剩余100%对冲仓位):
+        - 触发条件A:浮亏继续收窄至从最大回撤回升约60%
+        - 触发条件B:市场反弹至多头成本区上方2%以内
+        - 操作:平掉剩余所有对冲仓位
+        
+        参数:
+            commodity: 商品代码
+            contract: 合约代码
+            current_date: 当前日期
+            daily_prices: 当日价格数据
+        """
+        if not self.config.HEDGE_ENABLED:
+            return
+        
+        if commodity not in self.hedge_state:
+            return
+        
+        # 收集所有活跃的对冲头寸
+        hedge_positions = []
+        if commodity in self.active_positions['hedge_position']:
+            for position_id, position in self.active_positions['hedge_position'][commodity].items():
+                if position['contract'] == contract and position['status'] == 'open':
+                    hedge_positions.append((position_id, position))
+        
+        if len(hedge_positions) == 0:
+            if self.verbose_logging:
+                print(f"  ⏭️ {current_date}: {commodity} 没有活跃的对冲头寸,跳过止盈检查")
+            return
+        
+        # 检查是否为对冲建仓当天,如果是则跳过止盈检查
+        current_date_str = current_date.strftime('%Y-%m-%d')
+        last_hedge_entry_date = self.hedge_state[commodity].get('last_hedge_entry_date')
+        
+        if last_hedge_entry_date == current_date_str:
+            if self.verbose_logging:
+                print(f"  ⏭️ {current_date}: {commodity} 对冲建仓当天,跳过止盈检查(活跃对冲头寸: {len(hedge_positions)})")
+            return
+        
+        if self.verbose_logging:
+            print(f"  📊 {current_date}: {commodity} 活跃的对冲头寸: {len(hedge_positions)}")
+        # 计算所有基础多头头寸的浮动盈亏
+        # 注意:浮亏峰值使用最低价计算,回升判断使用最高价计算
+        base_positions_pnl_at_low = 0  # 基于最低价的浮亏(用于峰值计算)
+        base_positions_pnl_at_high = 0  # 基于最高价的浮亏(用于回升判断)
+        base_positions_cost = 0  # 用于计算平均成本价
+        total_base_quantity = 0
+        
+        if commodity in self.active_positions['base_position']:
+            for position_id, position in self.active_positions['base_position'][commodity].items():
+                if position['contract'] == contract and position['status'] == 'open':
+                    # 计算基于最低价的浮动盈亏(多头浮亏峰值)
+                    floating_pnl_low = self._calculate_futures_pnl(
+                        position['entry_price'], daily_prices['low'], 
+                        position['quantity'], commodity, is_long=True
+                    )
+                    # 计算基于最高价的浮动盈亏(多头回升判断)
+                    floating_pnl_high = self._calculate_futures_pnl(
+                        position['entry_price'], daily_prices['high'], 
+                        position['quantity'], commodity, is_long=True
+                    )
+                    base_positions_pnl_at_low += floating_pnl_low
+                    base_positions_pnl_at_high += floating_pnl_high
+                    base_positions_cost += position['entry_price'] * position['quantity']
+                    total_base_quantity += position['quantity']
+        
+        # 如果没有活跃的基础头寸,则不处理
+        if total_base_quantity == 0:
+            if self.verbose_logging:
+                print(f"  ⏭️ {current_date}: {commodity} 无未平仓基础头寸,跳过止盈检查 (数量: {total_base_quantity})")
+            return
+        
+        # 计算平均成本价
+        avg_cost_price = base_positions_cost / total_base_quantity
+        daily_low = daily_prices['low']
+        daily_high = daily_prices['high']
+        daily_close = daily_prices['close']
+        
+        # 更新最大回撤(基于最低价的最大浮亏绝对值)
+        if self.verbose_logging:
+            print(f"  📊 {current_date}: {commodity} 基础头寸 - 平均成本价: {avg_cost_price:.2f}, 当日价格区间: [{daily_low:.2f}, {daily_high:.2f}], 收盘价: {daily_close:.2f}")
+            print(f"  📊 {current_date}: {commodity} 基础头寸浮盈亏 - 最低价: {base_positions_pnl_at_low:.2f}, 最高价: {base_positions_pnl_at_high:.2f}, 当前最大回撤: {self.hedge_state[commodity]['max_drawdown']:.2f}")
+        
+        # 使用最低价计算的浮亏更新峰值回撤
+        if base_positions_pnl_at_low < 0 and abs(base_positions_pnl_at_low) > self.hedge_state[commodity]['max_drawdown']:
+            self.hedge_state[commodity]['max_drawdown'] = abs(base_positions_pnl_at_low)
+            self.hedge_state[commodity]['max_drawdown_price'] = daily_low  # 保存出现最大浮亏时的价格
+            if self.verbose_logging:
+                print(f"  📊 {current_date}: {commodity} 更新最大回撤: {self.hedge_state[commodity]['max_drawdown']:.2f} (最大浮亏价格: {daily_low:.2f})")
+        
+        max_drawdown = self.hedge_state[commodity]['max_drawdown']
+        
+        # 获取期货合约倍数
+        contract_multiplier = self._get_futures_multiplier(commodity)
+        
+        # 计算一级止盈价格(使用配置参数)
+        # 回升公式: 最大浮亏价格 + (最大浮亏 * 回升百分比) / (数量 * 合约倍数)
+        level1_recovery_pct = self.config.HEDGE_LEVEL1_RECOVERY_PCT
+        level1_tp_loss_change = max_drawdown * level1_recovery_pct  # 一级回升后的剩余浮亏
+        level1_tp_loss = max_drawdown - level1_tp_loss_change
+        # 使用最大浮亏价格作为基准,如果从未有过浮亏则使用当日最低价作为备用
+        max_drawdown_price = self.hedge_state[commodity]['max_drawdown_price'] if self.hedge_state[commodity]['max_drawdown_price'] > 0 else daily_low
+        level1_tp_price = max_drawdown_price + level1_tp_loss_change / (total_base_quantity * contract_multiplier)
+        print(f"  📈 {current_date}: {commodity} 一级止盈价格: {level1_tp_price:.2f}, 最大浮亏价格: {max_drawdown_price:.2f}, 剩余浮亏: {level1_tp_loss:.2f}")
+        
+        # 计算二级止盈价格1(使用配置参数)
+        level2_recovery_pct = self.config.HEDGE_LEVEL2_RECOVERY_PCT
+        level2_tp1_loss_change = max_drawdown * level2_recovery_pct  # 二级回升后的剩余浮亏
+        level2_tp1_loss = max_drawdown - level2_tp1_loss_change
+        level2_tp1_price = max_drawdown_price + level2_tp1_loss_change / (total_base_quantity * contract_multiplier)
+        print(f"  📈 {current_date}: {commodity} 二级止盈价格1: {level2_tp1_price:.2f}, 最大浮亏价格: {max_drawdown_price:.2f}, 剩余浮亏: {level2_tp1_loss:.2f}")
+
+        # 计算二级止盈价格2(成本价附近 - 空头策略应该是上方)
+        # 对于空头对冲,价格需要上升到成本价以上才触发止盈
+        cost_area_pct = self.config.HEDGE_COST_AREA_PCT
+        level2_tp2_price = avg_cost_price * (1 + cost_area_pct)  # 成本价上方百分比
+        
+        # 取二级止盈价格中的较低者
+        level2_tp_price = min(level2_tp1_price, level2_tp2_price)
+        print(f"  📈 {current_date}: {commodity} 二级止盈价格: {level2_tp_price:.2f}, 一级: {level2_tp1_price:.2f}, 二级: {level2_tp2_price:.2f}")
+        level2_trigger_reason_detail = f'回升{level2_recovery_pct:.0%}' if level2_tp_price == level2_tp1_price else f'成本价上方{cost_area_pct:.0%}'
+        
+        if self.verbose_logging:
+            print(f"  📈 {current_date}: {commodity} 合约倍数: {contract_multiplier}, 总数量: {total_base_quantity}")
+            print(f"  📈 {current_date}: {commodity} 止盈参数 - 一级回升: {level1_recovery_pct:.0%}, 二级回升: {level2_recovery_pct:.0%}, 成本区域: {cost_area_pct:.0%}")
+            print(f"  📈 {current_date}: {commodity} 最大浮亏价格: {max_drawdown_price:.2f}, 平均成本价: {avg_cost_price:.2f}")
+            print(f"  📈 {current_date}: {commodity} 止盈价格水平 - 一级: {level1_tp_price:.2f}, 二级: {level2_tp1_price:.2f}(回升) vs {level2_tp2_price:.2f}(成本区) -> {level2_tp_price:.2f} ({level2_trigger_reason_detail})")
+            print(f"  📈 {current_date}: {commodity} 当日最高价: {daily_high:.2f}, 最大回撤: {max_drawdown:.2f}")
+            print(f"  📈 {current_date}: {commodity} 价格计算详情 - 一级剩余浮亏: {level1_tp_loss:.2f}, 二级剩余浮亏: {level2_tp1_loss:.2f}")
+            
+            # 显示止盈触发判断的详细信息
+            level1_reached = daily_high >= level1_tp_price
+            level2_reached = daily_high >= level2_tp_price
+            first_tp_status = '已触发' if self.hedge_state[commodity]['first_tp_triggered'] else '未触发'
+            
+            print(f"  🔍 {current_date}: {commodity} 止盈判断 - 一级触及: {level1_reached}, 二级触及: {level2_reached}, 一级状态: {first_tp_status}")
+        
+        # 一级止盈:当日最高价触及30%回升价格水平
+        if not self.hedge_state[commodity]['first_tp_triggered'] and daily_high >= level1_tp_price:
+            if self.verbose_logging:
+                print(f"  💰 {current_date}: {commodity} 一级止盈触发 - 最高价 {daily_high:.2f} >= 一级止盈价 {level1_tp_price:.2f} (回升{level1_recovery_pct:.0%})")
+                print(f"    多头浮盈亏区间: [{base_positions_pnl_at_low:.2f}, {base_positions_pnl_at_high:.2f}], 最大回撤: {max_drawdown:.2f}")
+            
+            # 标记一级止盈已触发
+            self.hedge_state[commodity]['first_tp_triggered'] = True
+            
+            # 计算需要平仓的数量(50%对冲仓位,使用四舍五入并确保至少平1手)
+            total_hedge_quantity = sum(pos[1]['quantity'] for pos in hedge_positions)
+            target_close_quantity = max(1, round(total_hedge_quantity * 0.5))
+            
+            if self.verbose_logging:
+                print(f"    一级止盈数量计算: 总对冲仓位 {total_hedge_quantity}手, 50%目标 {total_hedge_quantity * 0.5:.1f}手, 实际平仓 {target_close_quantity}手")
+            
+            self._close_hedge_positions(
+                commodity, contract, current_date, daily_prices, 
+                hedge_positions, target_close_quantity, 
+                exit_reason='take_profit_level1',
+                log_prefix='一级止盈(50%)',
+                exit_price=level1_tp_price
+            )
+            
+            # 重新收集剩余的对冲头寸(为了支持同日二级止盈)
+            hedge_positions = []
+            if commodity in self.active_positions['hedge_position']:
+                for position_id, position in self.active_positions['hedge_position'][commodity].items():
+                    if position['contract'] == contract and position['status'] == 'open':
+                        hedge_positions.append((position_id, position))
+        
+        # 二级止盈:当日最高价触及二级止盈价格水平(改为独立判断,支持同日连续止盈)
+        if self.hedge_state[commodity]['first_tp_triggered'] and daily_high >= level2_tp_price:
+            if self.verbose_logging:
+                print(f"  💰💰 {current_date}: {commodity} 二级止盈触发 - 最高价 {daily_high:.2f} >= 二级止盈价 {level2_tp_price:.2f} ({level2_trigger_reason_detail})")
+                print(f"    多头浮盈亏区间: [{base_positions_pnl_at_low:.2f}, {base_positions_pnl_at_high:.2f}], 最大回撤: {max_drawdown:.2f}")
+            
+            # 平掉所有剩余的对冲仓位
+            remaining_hedge_quantity = sum(pos[1]['quantity'] for pos in hedge_positions)
+            
+            if self.verbose_logging:
+                print(f"    二级止盈数量计算: 剩余对冲仓位 {remaining_hedge_quantity}手, 全部平仓")
+            
+            if remaining_hedge_quantity > 0:
+                self._close_hedge_positions(
+                    commodity, contract, current_date, daily_prices, 
+                    hedge_positions, remaining_hedge_quantity, 
+                    exit_reason='take_profit_level2',
+                    log_prefix='二级止盈(100%剩余)',
+                    exit_price=level2_tp_price
+                )
+            else:
+                if self.verbose_logging:
+                    print(f"    二级止盈: 无剩余对冲仓位需要平仓")
+            
+            # 重置对冲状态
+            if self.verbose_logging:
+                print(f"  🔄 {current_date}: {commodity} 重置对冲状态,清零最大回撤记录和最大浮亏价格")
+            self.hedge_state[commodity]['first_tp_triggered'] = False
+            self.hedge_state[commodity]['max_drawdown'] = 0
+            self.hedge_state[commodity]['max_drawdown_price'] = 0
+        # 检查最终的对冲仓位状态,显示等待信息
+        if self.verbose_logging:
+            # 重新检查剩余对冲仓位
+            final_hedge_positions = []
+            if commodity in self.active_positions['hedge_position']:
+                for position_id, position in self.active_positions['hedge_position'][commodity].items():
+                    if position['contract'] == contract and position['status'] == 'open':
+                        final_hedge_positions.append((position_id, position))
+            
+            remaining_quantity = sum(pos[1]['quantity'] for pos in final_hedge_positions)
+            
+            if remaining_quantity > 0:
+                if self.hedge_state[commodity]['first_tp_triggered']:
+                    gap = level2_tp_price - daily_high
+                    print(f"  ⏸️ {current_date}: {commodity} 剩余对冲仓位 {remaining_quantity}手,等待二级止盈(需要 {level2_tp_price:.2f},当前最高价 {daily_high:.2f},差距 {gap:.2f})")
+                else:
+                    gap = level1_tp_price - daily_high
+                    print(f"  ⏸️ {current_date}: {commodity} 剩余对冲仓位 {remaining_quantity}手,等待一级止盈(需要 {level1_tp_price:.2f},当前最高价 {daily_high:.2f},差距 {gap:.2f})")
+            else:
+                print(f"  ✅ {current_date}: {commodity} 所有对冲仓位已平仓完毕")
+    
+    def _close_hedge_positions(self, commodity, contract, current_date, daily_prices, 
+                               hedge_positions, target_close_quantity, exit_reason, log_prefix, exit_price=None):
+        """
+        平掉指定数量的对冲头寸
+        
+        参数:
+            commodity: 商品代码
+            contract: 合约代码
+            current_date: 当前日期
+            daily_prices: 当日价格数据
+            hedge_positions: 对冲头寸列表 [(position_id, position), ...]
+            target_close_quantity: 目标平仓数量
+            exit_reason: 退出原因
+            log_prefix: 日志前缀
+            exit_price: 平仓价格,如果为None则使用收盘价
+        """
+        if exit_price is None:
+            exit_price = daily_prices['close']
+        closed_quantity = 0
+        
+        for position_id, position in hedge_positions:
+            if closed_quantity >= target_close_quantity:
+                break
+            
+            # 计算本次平仓数量
+            quantity_to_close = min(position['quantity'], target_close_quantity - closed_quantity)
+            
+            # 如果部分平仓
+            if quantity_to_close < position['quantity']:
+                # 创建部分平仓的交易记录
+                profit_loss = self._calculate_futures_pnl(
+                    position['entry_price'], exit_price, quantity_to_close, commodity, is_long=False
+                )
+                profit_loss_pct = (position['entry_price'] - exit_price) / position['entry_price']
+                
+                trade_record = {
+                    'commodity': commodity,
+                    'contract': contract,
+                    'strategy': 'hedge_position',
+                    'entry_date': position['entry_date'],
+                    'exit_date': current_date.strftime('%Y-%m-%d'),
+                    'entry_price': position['entry_price'],
+                    'exit_price': exit_price,
+                    'quantity': quantity_to_close,
+                    'profit_loss': profit_loss,
+                    'profit_loss_pct': profit_loss_pct,
+                    'days_held': (current_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
+                    'exit_reason': exit_reason,
+                    'is_short': True
+                }
+                
+                self.trading_results['hedge_position'].append(trade_record)
+                
+                # 更新头寸数量
+                self.active_positions['hedge_position'][commodity][position_id]['quantity'] -= quantity_to_close
+                
+                if self.verbose_logging:
+                    remaining = position['quantity'] - quantity_to_close
+                    print(f"    {log_prefix} 部分平仓: {position['entry_price']:.2f} -> {exit_price:.2f}, "
+                          f"平仓{quantity_to_close}手, 剩余{remaining}手, 盈亏: {profit_loss:.2f}")
+                
+                closed_quantity += quantity_to_close
+            else:
+                # 全部平仓
+                profit_loss = self._calculate_futures_pnl(
+                    position['entry_price'], exit_price, position['quantity'], commodity, is_long=False
+                )
+                profit_loss_pct = (position['entry_price'] - exit_price) / position['entry_price']
+                
+                trade_record = {
+                    'commodity': commodity,
+                    'contract': contract,
+                    'strategy': 'hedge_position',
+                    'entry_date': position['entry_date'],
+                    'exit_date': current_date.strftime('%Y-%m-%d'),
+                    'entry_price': position['entry_price'],
+                    'exit_price': exit_price,
+                    'quantity': position['quantity'],
+                    'profit_loss': profit_loss,
+                    'profit_loss_pct': profit_loss_pct,
+                    'days_held': (current_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
+                    'exit_reason': exit_reason,
+                    'is_short': True
+                }
+                
+                self.trading_results['hedge_position'].append(trade_record)
+                self.active_positions['hedge_position'][commodity][position_id]['status'] = 'closed'
+                
+                if self.verbose_logging:
+                    print(f"    {log_prefix} 全部平仓: {position['entry_price']:.2f} -> {exit_price:.2f}, "
+                          f"数量{position['quantity']}手, 盈亏: {profit_loss:.2f}")
+                
+                closed_quantity += position['quantity']
+        
+        # 更新已平仓的对冲数量
+        self.hedge_state[commodity]['hedge_quantity_closed'] += closed_quantity
+    
+    def _check_hedge_stop_loss(self, commodity, contract, current_date, daily_prices):
+        """
+        检查对冲头寸硬性止损条件
+        
+        对于空头对冲头寸,当价格上涨导致浮动亏损超过阈值时立即平仓。
+        
+        止损逻辑:
+        - 空头头寸亏损 = 当前价格 > 入场价格
+        - 亏损百分比 = (当前价格 - 入场价格) / 入场价格
+        - 触发条件:亏损百分比 > HEDGE_STOPLOSS_PCT
+        
+        参数:
+            commodity: 商品代码
+            contract: 合约代码
+            current_date: 当前日期
+            daily_prices: 当日价格数据
+        """
+        if not self.config.HEDGE_ENABLED:
+            return
+        
+        if commodity not in self.active_positions['hedge_position']:
+            return
+        
+        positions_to_close = []
+        current_price = daily_prices['close']
+        
+        for position_id, position in self.active_positions['hedge_position'][commodity].items():
+            if position['contract'] == contract and position['status'] == 'open':
+                entry_price = position['entry_price']
+                grid_price_level = position.get('original_price_level', entry_price)
+                
+                # 对于空头头寸的亏损百分比计算
+                # 价格上涨 = 亏损,价格下跌 = 盈利
+                # 亏损百分比 = (当前价格 - 入场价格) / 入场价格,因为对冲是做空,所以价格越低是收益越大,所以应该乘以-1
+                price_change_pct = - (current_price - entry_price) / entry_price
+                if self.verbose_logging:
+                    fill_price = position.get('fill_price', entry_price)
+                    print(f"  📊 {current_date}: {commodity} 当前价格: {current_price:.2f}, 网格价: {grid_price_level:.2f}, 记录开仓价: {entry_price:.2f}, 实际成交价: {fill_price:.2f}, 对冲头寸浮盈亏比例: {price_change_pct:.4f}")
+                # 只有当价格上涨(亏损)且超过止损阈值时才触发止损
+                if price_change_pct > self.config.HEDGE_STOPLOSS_PCT:
+                    # 计算实际盈亏金额(用于日志显示)
+                    floating_pnl = self._calculate_futures_pnl(
+                        entry_price, current_price, 
+                        position['quantity'], commodity, is_long=False
+                    )
+                    positions_to_close.append((position_id, position, floating_pnl, price_change_pct))
+        
+        # 平掉触发止损的头寸
+        for position_id, position, floating_pnl, loss_pct in positions_to_close:
+            exit_price = daily_prices['close']
+            
+            profit_loss = self._calculate_futures_pnl(
+                position['entry_price'], exit_price, position['quantity'], commodity, is_long=False
+            )
+            profit_loss_pct = (position['entry_price'] - exit_price) / position['entry_price']
+            
+            trade_record = {
+                'commodity': commodity,
+                'contract': contract,
+                'strategy': 'hedge_position',
+                'entry_date': position['entry_date'],
+                'exit_date': current_date.strftime('%Y-%m-%d'),
+                'entry_price': position['entry_price'],
+                'exit_price': exit_price,
+                'quantity': position['quantity'],
+                'profit_loss': profit_loss,
+                'profit_loss_pct': profit_loss_pct,
+                'days_held': (current_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
+                'exit_reason': 'stop_loss_hard',  # 硬性止损
+                'is_short': True
+            }
+            
+            self.trading_results['hedge_position'].append(trade_record)
+            self.active_positions['hedge_position'][commodity][position_id]['status'] = 'closed'
+            
+            if self.verbose_logging:
+                grid_price_level = position.get('original_price_level', position['entry_price'])
+                fill_price = position.get('fill_price', position['entry_price'])
+                print(f"  🛑 {current_date}: {commodity} 对冲硬性止损触发 - "
+                      f"网格价@{grid_price_level:.2f}, 记录开仓@{position['entry_price']:.2f}, 实际成交@{fill_price:.2f}, 当前@{exit_price:.2f}, "
+                      f"价格上涨{loss_pct:.2%} > 阈值{self.config.HEDGE_STOPLOSS_PCT:.2%}, "
+                      f"平仓盈亏:{profit_loss:.2f}")
+    
+    def _extend_price_data_for_ma(self, commodity, contract, current_date, required_days=30):
+        """扩展价格数据以满足MA计算需求
+        
+        参数:
+            commodity: 商品代码
+            contract: 合约代码
+            current_date: 当前日期
+            required_days: 所需的最少数据天数
+        
+        返回:
+            扩展后的价格数据DataFrame,如果获取失败则返回None
+        """
+        cache_key = f"{commodity}_{contract}"
+        
+        # 检查缓存
+        if cache_key in self.ma_extended_data_cache:
+            cached_data = self.ma_extended_data_cache[cache_key]
+            target_date = current_date if isinstance(current_date, datetime.date) else current_date.date()
+            historical_data = cached_data[cached_data.index.date <= target_date]
+            
+            if len(historical_data) >= required_days:
+                if self.verbose_logging:
+                    print(f"  ℹ️  MA过滤器:使用缓存的扩展数据,共{len(historical_data)}天")
+                return historical_data
+        
+        # 获取现有数据
+        if commodity not in self.price_data or contract not in self.price_data[commodity]:
+            return None
+        
+        existing_data = self.price_data[commodity][contract]
+        target_date = current_date if isinstance(current_date, datetime.date) else current_date.date()
+        existing_historical = existing_data[existing_data.index.date <= target_date]
+        
+        if len(existing_historical) >= required_days:
+            return existing_historical
+        
+        # 数据不足,需要扩展获取
+        existing_count = len(existing_historical)
+        shortage = required_days - existing_count
+        
+        if self.verbose_logging:
+            print(f"  📊 MA过滤器:数据不足,开始扩展获取(当前{existing_count}天,需要{required_days}天,缺少{shortage}天)")
+        
+        # 计算扩展的开始日期:从现有数据最早日期往前推至少30个交易日
+        if len(existing_data) > 0:
+            earliest_date = existing_data.index.min().date()
+        else:
+            earliest_date = target_date
+        
+        # 往前推60个自然日(约等于40-45个交易日,提供充足缓冲)
+        extended_start_date = earliest_date - datetime.timedelta(days=60)
+        extended_end_date = target_date
+        
+        if self.verbose_logging:
+            print(f"  📊 扩展日期范围: {extended_start_date} 至 {extended_end_date}")
+        
+        try:
+            # 获取扩展的价格数据
+            extended_data = get_price(
+                contract,
+                start_date=extended_start_date,
+                end_date=extended_end_date,
+                frequency='daily',
+                fields=['open', 'close', 'high', 'low', 'volume'],
+                skip_paused=False,
+                panel=False
+            )
+            
+            if extended_data is None or len(extended_data) == 0:
+                if self.verbose_logging:
+                    print(f"  ⚠️  扩展数据获取失败:未获取到数据")
+                return existing_historical
+            
+            # 合并数据:将扩展数据与现有数据合并,保留所有日期的数据
+            # 使用concat合并,然后去重并按日期排序
+            combined_data = pd.concat([existing_data, extended_data])
+            combined_data = combined_data[~combined_data.index.duplicated(keep='first')]
+            combined_data = combined_data.sort_index()
+            
+            # 过滤到当前日期(仅用于返回给MA计算)
+            combined_historical = combined_data[combined_data.index.date <= target_date]
+            
+            if self.verbose_logging:
+                print(f"  ✅ 扩展数据获取成功:从{len(existing_historical)}天扩展到{len(combined_historical)}天")
+                print(f"     合并后完整数据范围:{combined_data.index.min().date()} 至 {combined_data.index.max().date()}(共{len(combined_data)}天)")
+            
+            # 缓存扩展后的完整数据
+            self.ma_extended_data_cache[cache_key] = combined_data
+            
+            # 更新主price_data,保留原有数据和新扩展的数据
+            self.price_data[commodity][contract] = combined_data
+            
+            return combined_historical
+            
+        except Exception as e:
+            if self.verbose_logging:
+                print(f"  ⚠️  扩展数据获取异常:{str(e)}")
+            return existing_historical
+    
+    def simulate_base_position_trading(self):
+        """
+        模拟基础头寸交易
+        为每种商品配置价格-数量网格,以指定价格水平和数量开立多头头寸
+        所有头寸使用统一的退出价格(无止损)
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤3: 基础头寸交易模拟 ===")
+        
+        base_position_results = []
+        
+        for commodity in self.price_data.keys():
+            if commodity not in self.base_position_grid:
+                continue
+                
+            price_grid = self.base_position_grid[commodity]
+            exit_price = self.base_position_exit_price[commodity]
+            price_data = self.price_data[commodity]['data']
+            contract = self.price_data[commodity]['contract']
+            
+            if self.verbose_logging:
+                print(f"\n分析 {commodity} ({contract}) 基础头寸交易")
+                print(f"价格网格: {price_grid}")
+                print(f"退出价格: {exit_price}")
+            
+            # 遍历每个价格水平
+            for entry_price, quantity in price_grid.items():
+                # 查找触发入场的日期
+                entry_dates = []
+                for date, row in price_data.iterrows():
+                    if row['low'] <= entry_price <= row['high']:
+                        entry_dates.append(date)
+                
+                if not entry_dates:
+                    continue
+                
+                # 使用第一个触发日期作为入场点
+                entry_date = entry_dates[0]
+                
+                # 查找退出点
+                exit_date = None
+                exit_price_actual = exit_price
+                
+                # 在入场后查找价格达到退出价格的日期
+                for date, row in price_data.iterrows():
+                    if date > entry_date and row['high'] >= exit_price:
+                        exit_date = date
+                        exit_price_actual = min(exit_price, row['high'])
+                        break
+                
+                # 如果没有达到退出价格,使用最后一日的收盘价退出
+                if exit_date is None:
+                    exit_date = price_data.index[-1]
+                    exit_price_actual = price_data.iloc[-1]['close']
+                
+                # 计算盈亏
+                profit_loss = (exit_price_actual - entry_price) * quantity
+                profit_loss_pct = (exit_price_actual - entry_price) / entry_price
+                
+                trade_record = {
+                    'commodity': commodity,
+                    'contract': contract,
+                    'strategy': 'base_position',
+                    'entry_date': entry_date.strftime('%Y-%m-%d'),
+                    'exit_date': exit_date.strftime('%Y-%m-%d'),
+                    'entry_price': entry_price,
+                    'exit_price': exit_price_actual,
+                    'quantity': quantity,
+                    'profit_loss': profit_loss,
+                    'profit_loss_pct': profit_loss_pct,
+                    'days_held': (exit_date - entry_date).days
+                }
+                
+                base_position_results.append(trade_record)
+                
+                if self.verbose_logging:
+                    print(f"  入场: {entry_date.strftime('%Y-%m-%d')} @ {entry_price}, 数量: {quantity}")
+                    print(f"  出场: {exit_date.strftime('%Y-%m-%d')} @ {exit_price_actual:.2f}")
+                    print(f"  盈亏: {profit_loss:.2f} ({profit_loss_pct:.2%})")
+        
+        self.trading_results['base_position'] = base_position_results
+        
+        if self.verbose_logging:
+            print(f"\n基础头寸交易模拟完成,共{len(base_position_results)}笔交易")
+        
+        return base_position_results
+    
+    def simulate_grid_trading(self):
+        """
+        模拟网格交易策略
+        从start_price开始,每次价格下降grid_size时买入quantity_per_grid
+        当价格从入场价格上涨exit_grid_size时退出
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤4: 网格交易策略模拟 ===")
+        
+        grid_trading_results = []
+        
+        for commodity in self.price_data.keys():
+            if commodity not in self.grid_trading_config:
+                continue
+                
+            config = self.grid_trading_config[commodity]
+            start_price = config['start_price']
+            grid_size = config['grid_size']
+            quantity_per_grid = config['quantity_per_grid']
+            exit_grid_size = config['exit_grid_size']
+            
+            price_data = self.price_data[commodity]['data']
+            contract = self.price_data[commodity]['contract']
+            
+            if self.verbose_logging:
+                print(f"\n分析 {commodity} ({contract}) 网格交易")
+                print(f"起始价格: {start_price}, 网格大小: {grid_size}")
+                print(f"每网格数量: {quantity_per_grid}, 退出网格大小: {exit_grid_size}")
+            
+            # 生成网格价格水平
+            grid_levels = []
+            current_level = start_price
+            min_price = price_data['low'].min()
+            
+            while current_level > min_price:
+                grid_levels.append(current_level)
+                current_level -= grid_size
+            
+            # 模拟每个网格水平的交易
+            for entry_price in grid_levels:
+                exit_price = entry_price + exit_grid_size
+                
+                # 查找入场机会
+                entry_date = None
+                for date, row in price_data.iterrows():
+                    if row['low'] <= entry_price <= row['high']:
+                        entry_date = date
+                        break
+                
+                if entry_date is None:
+                    continue
+                
+                # 查找退出机会
+                exit_date = None
+                exit_price_actual = exit_price
+                
+                for date, row in price_data.iterrows():
+                    if date > entry_date and row['high'] >= exit_price:
+                        exit_date = date
+                        exit_price_actual = exit_price
+                        break
+                
+                # 如果没有达到退出价格,使用最后一日收盘价
+                if exit_date is None:
+                    exit_date = price_data.index[-1]
+                    exit_price_actual = price_data.iloc[-1]['close']
+                
+                # 计算盈亏
+                profit_loss = (exit_price_actual - entry_price) * quantity_per_grid
+                profit_loss_pct = (exit_price_actual - entry_price) / entry_price
+                
+                trade_record = {
+                    'commodity': commodity,
+                    'contract': contract,
+                    'strategy': 'grid_trading',
+                    'entry_date': entry_date.strftime('%Y-%m-%d'),
+                    'exit_date': exit_date.strftime('%Y-%m-%d'),
+                    'entry_price': entry_price,
+                    'exit_price': exit_price_actual,
+                    'quantity': quantity_per_grid,
+                    'profit_loss': profit_loss,
+                    'profit_loss_pct': profit_loss_pct,
+                    'days_held': (exit_date - entry_date).days
+                }
+                
+                grid_trading_results.append(trade_record)
+                
+                if self.verbose_logging:
+                    print(f"  网格 {entry_price}: {entry_date.strftime('%Y-%m-%d')} -> {exit_date.strftime('%Y-%m-%d')}")
+                    print(f"    盈亏: {profit_loss:.2f} ({profit_loss_pct:.2%})")
+        
+        self.trading_results['grid_trading'] = grid_trading_results
+        
+        if self.verbose_logging:
+            print(f"\n网格交易模拟完成,共{len(grid_trading_results)}笔交易")
+        
+        return grid_trading_results
+    
+    def calculate_performance_statistics(self):
+        """
+        计算多品种多级聚合的性能统计
+        包括品种-策略级、品种级、策略级和总体级统计
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤6: 多级性能统计分析 ===")
+        
+        # 多级统计结构
+        performance_stats = {
+            'by_commodity_strategy': {},  # 品种-策略级统计
+            'by_commodity': {},           # 品种级汇总
+            'by_strategy': {},            # 策略级汇总  
+            'overall': {}                 # 总体汇总
+        }
+        
+        if self.verbose_logging:
+            print("\n--- 第一级:品种-策略级统计 ---")
+        
+        # 第一步:计算品种-策略级统计
+        for strategy_name, results in self.trading_results.items():
+            if strategy_name not in performance_stats['by_commodity_strategy']:
+                performance_stats['by_commodity_strategy'][strategy_name] = {}
+            
+            # 按品种分组交易结果
+            commodity_results = {}
+            for result in results:
+                commodity = result['commodity']
+                if commodity not in commodity_results:
+                    commodity_results[commodity] = []
+                commodity_results[commodity].append(result)
+            
+            # 为每个品种计算统计
+            for commodity in self.core_commodities.keys():
+                comm_results = commodity_results.get(commodity, [])
+                stats = self._calculate_single_strategy_stats(strategy_name, comm_results, commodity)
+                performance_stats['by_commodity_strategy'][strategy_name][commodity] = stats
+                
+                if self.verbose_logging:
+                    print(f"\n{commodity}-{strategy_name} 策略统计:")
+                    self._print_strategy_stats(stats)
+        
+        if self.verbose_logging:
+            print("\n--- 第二级:品种级汇总统计 ---")
+        
+        # 第二步:计算品种级汇总统计
+        for commodity in self.core_commodities.keys():
+            commodity_stats = self._calculate_commodity_summary(commodity, performance_stats['by_commodity_strategy'])
+            performance_stats['by_commodity'][commodity] = commodity_stats
+            
+            if self.verbose_logging:
+                print(f"\n{commodity} 品种汇总统计:")
+                self._print_strategy_stats(commodity_stats)
+        
+        if self.verbose_logging:
+            print("\n--- 第三级:策略级汇总统计 ---")
+        
+        # 第三步:计算策略级汇总统计
+        for strategy_name in self.trading_results.keys():
+            strategy_stats = self._calculate_strategy_summary(strategy_name, performance_stats['by_commodity_strategy'])
+            performance_stats['by_strategy'][strategy_name] = strategy_stats
+            
+            if self.verbose_logging:
+                print(f"\n{strategy_name} 策略汇总统计:")
+                self._print_strategy_stats(strategy_stats)
+        
+        if self.verbose_logging:
+            print("\n--- 第四级:整体汇总统计 ---")
+        
+        # 第四步:计算总体统计
+        overall_stats = self._calculate_overall_summary(performance_stats['by_strategy'])
+        performance_stats['overall'] = overall_stats
+        
+        if self.verbose_logging:
+            print(f"\n整体汇总统计:")
+            self._print_strategy_stats(overall_stats)
+        
+        # 添加对冲策略专项统计
+        if self.config.HEDGE_ENABLED:
+            hedge_special_stats = self._calculate_hedge_special_stats()
+            performance_stats['hedge_special_stats'] = hedge_special_stats
+            
+            if self.verbose_logging:
+                print(f"\n--- 对冲策略专项统计 ---")
+                self._print_hedge_special_stats(hedge_special_stats)
+        
+        return performance_stats
+    
+    def _calculate_single_strategy_stats(self, strategy_name, results, commodity):
+        """计算单个策略在特定品种下的统计数据"""
+        # 计算已平仓交易的盈亏
+        closed_profit_loss = sum(r['profit_loss'] for r in results) if results else 0.0
+        
+        # 计算未平仓头寸的未实现盈亏(特定品种)
+        unrealized_profit_loss = self._calculate_unrealized_pnl_for_commodity(strategy_name, commodity)
+        
+        # 总盈亏 = 已实现盈亏 + 未实现盈亏
+        total_profit_loss = closed_profit_loss + unrealized_profit_loss
+        
+        if not results and unrealized_profit_loss == 0:
+            return {
+                'total_trades': 0,
+                'open_positions': 0,
+                'profitable_trades': 0,
+                'losing_trades': 0,
+                'win_rate': 0.0,
+                'closed_profit_loss': 0.0,
+                'unrealized_profit_loss': 0.0,
+                'total_profit_loss': 0.0,
+                'avg_profit_loss': 0.0,
+                'avg_profit_loss_pct': 0.0,
+                'max_profit': 0.0,
+                'max_loss': 0.0,
+                'avg_holding_days': 0.0,
+                'profit_factor': 0.0
+            }
+        
+        # 基本统计
+        total_trades = len(results)
+        open_positions = self._count_open_positions_for_commodity(strategy_name, commodity)
+        profitable_trades = sum(1 for r in results if r['profit_loss'] > 0)
+        losing_trades = sum(1 for r in results if r['profit_loss'] < 0)
+        win_rate = profitable_trades / total_trades if total_trades > 0 else 0
+        
+        # 平均盈亏(基于已平仓交易)
+        avg_profit_loss = closed_profit_loss / total_trades if total_trades > 0 else 0
+        avg_profit_loss_pct = sum(r['profit_loss_pct'] for r in results) / total_trades if total_trades > 0 else 0
+        
+        # 最大盈亏(基于已平仓交易)
+        profit_losses = [r['profit_loss'] for r in results]
+        max_profit = max(profit_losses) if profit_losses else 0
+        max_loss = min(profit_losses) if profit_losses else 0
+        
+        # 平均持有天数
+        avg_holding_days = sum(r['days_held'] for r in results) / total_trades if total_trades > 0 else 0
+        
+        # 盈亏比(基于已平仓交易)
+        total_profits = sum(r['profit_loss'] for r in results if r['profit_loss'] > 0)
+        total_losses = abs(sum(r['profit_loss'] for r in results if r['profit_loss'] < 0))
+        profit_factor = total_profits / total_losses if total_losses > 0 else float('inf') if total_profits > 0 else 0
+        
+        return {
+            'total_trades': total_trades,
+            'open_positions': open_positions,
+            'profitable_trades': profitable_trades,
+            'losing_trades': losing_trades,
+            'win_rate': win_rate,
+            'closed_profit_loss': closed_profit_loss,
+            'unrealized_profit_loss': unrealized_profit_loss,
+            'total_profit_loss': total_profit_loss,
+            'avg_profit_loss': avg_profit_loss,
+            'avg_profit_loss_pct': avg_profit_loss_pct,
+            'max_profit': max_profit,
+            'max_loss': max_loss,
+            'avg_holding_days': avg_holding_days,
+            'profit_factor': profit_factor
+        }
+    
+    def _print_strategy_stats(self, stats):
+        """打印策略统计信息"""
+        print(f"  已平仓交易: {stats['total_trades']}")
+        print(f"  未平仓头寸: {stats['open_positions']}")
+        print(f"  盈利交易: {stats['profitable_trades']}")
+        print(f"  亏损交易: {stats['losing_trades']}")
+        print(f"  胜率: {stats['win_rate']:.2%}")
+        print(f"  已实现盈亏: {stats['closed_profit_loss']:.2f}")
+        print(f"  未实现盈亏: {stats['unrealized_profit_loss']:.2f}")
+        print(f"  总盈亏: {stats['total_profit_loss']:.2f}")
+        print(f"  平均盈亏: {stats['avg_profit_loss']:.2f}")
+        print(f"  平均盈亏率: {stats['avg_profit_loss_pct']:.2%}")
+        print(f"  最大盈利: {stats['max_profit']:.2f}")
+        print(f"  最大亏损: {stats['max_loss']:.2f}")
+        print(f"  平均持有天数: {stats['avg_holding_days']:.1f}")
+        profit_factor_str = f"{stats['profit_factor']:.2f}" if stats['profit_factor'] != float('inf') else "∞"
+        print(f"  盈亏比: {profit_factor_str}")
+    
+    def _calculate_commodity_summary(self, commodity, by_commodity_strategy):
+        """计算品种级汇总统计"""
+        total_stats = {
+            'total_trades': 0,
+            'open_positions': 0,
+            'profitable_trades': 0,
+            'losing_trades': 0,
+            'closed_profit_loss': 0.0,
+            'unrealized_profit_loss': 0.0,
+            'total_profit_loss': 0.0,
+            'max_profit': 0.0,
+            'max_loss': 0.0,
+            'total_holding_days': 0.0,
+            'total_profits': 0.0,
+            'total_losses': 0.0,
+            'all_pct': []
+        }
+        
+        for strategy_name, commodity_stats in by_commodity_strategy.items():
+            if commodity in commodity_stats:
+                stats = commodity_stats[commodity]
+                total_stats['total_trades'] += stats['total_trades']
+                total_stats['open_positions'] += stats['open_positions']
+                total_stats['profitable_trades'] += stats['profitable_trades']
+                total_stats['losing_trades'] += stats['losing_trades']
+                total_stats['closed_profit_loss'] += stats['closed_profit_loss']
+                total_stats['unrealized_profit_loss'] += stats['unrealized_profit_loss']
+                total_stats['total_profit_loss'] += stats['total_profit_loss']
+                total_stats['max_profit'] = max(total_stats['max_profit'], stats['max_profit'])
+                total_stats['max_loss'] = min(total_stats['max_loss'], stats['max_loss'])
+                total_stats['total_holding_days'] += stats['avg_holding_days'] * stats['total_trades']
+                
+                if stats['profit_factor'] != float('inf'):
+                    profits = stats['closed_profit_loss'] if stats['closed_profit_loss'] > 0 else 0
+                    losses = abs(stats['closed_profit_loss']) if stats['closed_profit_loss'] < 0 else 0
+                    total_stats['total_profits'] += profits
+                    total_stats['total_losses'] += losses
+                
+                # 收集所有百分比数据
+                if stats['total_trades'] > 0:
+                    total_stats['all_pct'].extend([stats['avg_profit_loss_pct']] * stats['total_trades'])
+        
+        # 计算汇总指标
+        win_rate = total_stats['profitable_trades'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        avg_profit_loss = total_stats['closed_profit_loss'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        avg_profit_loss_pct = sum(total_stats['all_pct']) / len(total_stats['all_pct']) if total_stats['all_pct'] else 0
+        avg_holding_days = total_stats['total_holding_days'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        profit_factor = total_stats['total_profits'] / total_stats['total_losses'] if total_stats['total_losses'] > 0 else float('inf') if total_stats['total_profits'] > 0 else 0
+        
+        return {
+            'total_trades': total_stats['total_trades'],
+            'open_positions': total_stats['open_positions'],
+            'profitable_trades': total_stats['profitable_trades'],
+            'losing_trades': total_stats['losing_trades'],
+            'win_rate': win_rate,
+            'closed_profit_loss': total_stats['closed_profit_loss'],
+            'unrealized_profit_loss': total_stats['unrealized_profit_loss'],
+            'total_profit_loss': total_stats['total_profit_loss'],
+            'avg_profit_loss': avg_profit_loss,
+            'avg_profit_loss_pct': avg_profit_loss_pct,
+            'max_profit': total_stats['max_profit'],
+            'max_loss': total_stats['max_loss'],
+            'avg_holding_days': avg_holding_days,
+            'profit_factor': profit_factor
+        }
+    
+    def _calculate_strategy_summary(self, strategy_name, by_commodity_strategy):
+        """计算策略级汇总统计"""
+        if strategy_name not in by_commodity_strategy:
+            return self._get_empty_stats()
+        
+        strategy_stats = by_commodity_strategy[strategy_name]
+        
+        total_stats = {
+            'total_trades': 0,
+            'open_positions': 0,
+            'profitable_trades': 0,
+            'losing_trades': 0,
+            'closed_profit_loss': 0.0,
+            'unrealized_profit_loss': 0.0,
+            'total_profit_loss': 0.0,
+            'max_profit': 0.0,
+            'max_loss': 0.0,
+            'total_holding_days': 0.0,
+            'total_profits': 0.0,
+            'total_losses': 0.0,
+            'all_pct': []
+        }
+        
+        for commodity, stats in strategy_stats.items():
+            total_stats['total_trades'] += stats['total_trades']
+            total_stats['open_positions'] += stats['open_positions']
+            total_stats['profitable_trades'] += stats['profitable_trades']
+            total_stats['losing_trades'] += stats['losing_trades']
+            total_stats['closed_profit_loss'] += stats['closed_profit_loss']
+            total_stats['unrealized_profit_loss'] += stats['unrealized_profit_loss']
+            total_stats['total_profit_loss'] += stats['total_profit_loss']
+            total_stats['max_profit'] = max(total_stats['max_profit'], stats['max_profit'])
+            total_stats['max_loss'] = min(total_stats['max_loss'], stats['max_loss'])
+            total_stats['total_holding_days'] += stats['avg_holding_days'] * stats['total_trades']
+            
+            if stats['profit_factor'] != float('inf'):
+                profits = stats['closed_profit_loss'] if stats['closed_profit_loss'] > 0 else 0
+                losses = abs(stats['closed_profit_loss']) if stats['closed_profit_loss'] < 0 else 0
+                total_stats['total_profits'] += profits
+                total_stats['total_losses'] += losses
+            
+            # 收集所有百分比数据
+            if stats['total_trades'] > 0:
+                total_stats['all_pct'].extend([stats['avg_profit_loss_pct']] * stats['total_trades'])
+        
+        # 计算汇总指标
+        win_rate = total_stats['profitable_trades'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        avg_profit_loss = total_stats['closed_profit_loss'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        avg_profit_loss_pct = sum(total_stats['all_pct']) / len(total_stats['all_pct']) if total_stats['all_pct'] else 0
+        avg_holding_days = total_stats['total_holding_days'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        profit_factor = total_stats['total_profits'] / total_stats['total_losses'] if total_stats['total_losses'] > 0 else float('inf') if total_stats['total_profits'] > 0 else 0
+        
+        return {
+            'total_trades': total_stats['total_trades'],
+            'open_positions': total_stats['open_positions'],
+            'profitable_trades': total_stats['profitable_trades'],
+            'losing_trades': total_stats['losing_trades'],
+            'win_rate': win_rate,
+            'closed_profit_loss': total_stats['closed_profit_loss'],
+            'unrealized_profit_loss': total_stats['unrealized_profit_loss'],
+            'total_profit_loss': total_stats['total_profit_loss'],
+            'avg_profit_loss': avg_profit_loss,
+            'avg_profit_loss_pct': avg_profit_loss_pct,
+            'max_profit': total_stats['max_profit'],
+            'max_loss': total_stats['max_loss'],
+            'avg_holding_days': avg_holding_days,
+            'profit_factor': profit_factor
+        }
+    
+    def _calculate_overall_summary(self, by_strategy):
+        """计算总体汇总统计"""
+        total_stats = {
+            'total_trades': 0,
+            'open_positions': 0,
+            'profitable_trades': 0,
+            'losing_trades': 0,
+            'closed_profit_loss': 0.0,
+            'unrealized_profit_loss': 0.0,
+            'total_profit_loss': 0.0,
+            'max_profit': 0.0,
+            'max_loss': 0.0,
+            'total_holding_days': 0.0,
+            'total_profits': 0.0,
+            'total_losses': 0.0,
+            'all_pct': []
+        }
+        
+        for strategy_name, stats in by_strategy.items():
+            total_stats['total_trades'] += stats['total_trades']
+            total_stats['open_positions'] += stats['open_positions']
+            total_stats['profitable_trades'] += stats['profitable_trades']
+            total_stats['losing_trades'] += stats['losing_trades']
+            total_stats['closed_profit_loss'] += stats['closed_profit_loss']
+            total_stats['unrealized_profit_loss'] += stats['unrealized_profit_loss']
+            total_stats['total_profit_loss'] += stats['total_profit_loss']
+            total_stats['max_profit'] = max(total_stats['max_profit'], stats['max_profit'])
+            total_stats['max_loss'] = min(total_stats['max_loss'], stats['max_loss'])
+            total_stats['total_holding_days'] += stats['avg_holding_days'] * stats['total_trades']
+            
+            if stats['profit_factor'] != float('inf'):
+                profits = stats['closed_profit_loss'] if stats['closed_profit_loss'] > 0 else 0
+                losses = abs(stats['closed_profit_loss']) if stats['closed_profit_loss'] < 0 else 0
+                total_stats['total_profits'] += profits
+                total_stats['total_losses'] += losses
+            
+            # 收集所有百分比数据
+            if stats['total_trades'] > 0:
+                total_stats['all_pct'].extend([stats['avg_profit_loss_pct']] * stats['total_trades'])
+        
+        # 计算汇总指标
+        win_rate = total_stats['profitable_trades'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        avg_profit_loss = total_stats['closed_profit_loss'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        avg_profit_loss_pct = sum(total_stats['all_pct']) / len(total_stats['all_pct']) if total_stats['all_pct'] else 0
+        avg_holding_days = total_stats['total_holding_days'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
+        profit_factor = total_stats['total_profits'] / total_stats['total_losses'] if total_stats['total_losses'] > 0 else float('inf') if total_stats['total_profits'] > 0 else 0
+        
+        return {
+            'total_trades': total_stats['total_trades'],
+            'open_positions': total_stats['open_positions'],
+            'profitable_trades': total_stats['profitable_trades'],
+            'losing_trades': total_stats['losing_trades'],
+            'win_rate': win_rate,
+            'closed_profit_loss': total_stats['closed_profit_loss'],
+            'unrealized_profit_loss': total_stats['unrealized_profit_loss'],
+            'total_profit_loss': total_stats['total_profit_loss'],
+            'avg_profit_loss': avg_profit_loss,
+            'avg_profit_loss_pct': avg_profit_loss_pct,
+            'max_profit': total_stats['max_profit'],
+            'max_loss': total_stats['max_loss'],
+            'avg_holding_days': avg_holding_days,
+            'profit_factor': profit_factor
+        }
+    
+    def _calculate_hedge_special_stats(self):
+        """
+        计算对冲策略专项统计
+        
+        返回对冲策略的详细统计信息,包括:
+        - 止盈/止损事件统计
+        - 波动率过滤统计
+        - 与基础头寸的相关性
+        """
+        hedge_results = self.trading_results.get('hedge_position', [])
+        
+        stats = {
+            'total_hedge_trades': len(hedge_results),
+            'total_hedge_pnl': sum(r['profit_loss'] for r in hedge_results),
+            'take_profit_count': 0,
+            'stop_loss_price_count': 0,
+            'contract_switch_count': 0,
+            'avg_holding_days': 0,
+            'volatility_filter_triggers': 0,
+            'correlation_with_base': 0.0
+        }
+        
+        if not hedge_results:
+            return stats
+        
+        # 统计不同类型的退出原因
+        for result in hedge_results:
+            exit_reason = result.get('exit_reason', '')
+            if exit_reason == 'take_profit':
+                stats['take_profit_count'] += 1
+            elif exit_reason == 'stop_loss_price_based':
+                stats['stop_loss_price_count'] += 1
+            elif exit_reason == 'contract_switch':
+                stats['contract_switch_count'] += 1
+        
+        # 平均持有期
+        stats['avg_holding_days'] = sum(r['days_held'] for r in hedge_results) / len(hedge_results)
+        
+        # 波动率过滤触发次数(从hedge_state中统计)
+        # 这里简化处理,实际可以在运行时记录
+        stats['volatility_filter_triggers'] = 0
+        
+        # 计算与基础头寸的相关性
+        stats['correlation_with_base'] = self._calculate_hedge_correlation()
+        
+        return stats
+    
+    def _calculate_hedge_correlation(self):
+        """
+        计算对冲头寸与基础头寸的盈亏相关性
+        
+        返回相关系数(-1到1之间)
+        """
+        hedge_results = self.trading_results.get('hedge_position', [])
+        base_results = self.trading_results.get('base_position', [])
+        
+        if not hedge_results or not base_results:
+            return 0.0
+        
+        # 按日期对齐盈亏
+        hedge_pnl_by_date = {}
+        for result in hedge_results:
+            exit_date = result['exit_date']
+            if exit_date not in hedge_pnl_by_date:
+                hedge_pnl_by_date[exit_date] = 0
+            hedge_pnl_by_date[exit_date] += result['profit_loss']
+        
+        base_pnl_by_date = {}
+        for result in base_results:
+            exit_date = result['exit_date']
+            if exit_date not in base_pnl_by_date:
+                base_pnl_by_date[exit_date] = 0
+            base_pnl_by_date[exit_date] += result['profit_loss']
+        
+        # 找到共同日期
+        common_dates = set(hedge_pnl_by_date.keys()) & set(base_pnl_by_date.keys())
+        
+        if len(common_dates) < 2:
+            return 0.0
+        
+        # 计算相关系数
+        hedge_values = [hedge_pnl_by_date[date] for date in common_dates]
+        base_values = [base_pnl_by_date[date] for date in common_dates]
+        
+        try:
+            correlation = np.corrcoef(hedge_values, base_values)[0, 1]
+            return correlation if not np.isnan(correlation) else 0.0
+        except:
+            return 0.0
+    
+    def _print_hedge_special_stats(self, stats):
+        """打印对冲策略专项统计"""
+        print(f"  对冲交易总数: {stats['total_hedge_trades']}")
+        print(f"  对冲总盈亏: {stats['total_hedge_pnl']:.2f}")
+        print(f"  止盈次数: {stats['take_profit_count']}")
+        print(f"  止损次数(价格触发): {stats['stop_loss_price_count']}")
+        print(f"  合约换月平仓: {stats['contract_switch_count']}")
+        print(f"  平均持有天数: {stats['avg_holding_days']:.1f}")
+        print(f"  与基础头寸相关系数: {stats['correlation_with_base']:.3f}")
+    
+    def _get_empty_stats(self):
+        """返回空的统计数据"""
+        return {
+            'total_trades': 0,
+            'open_positions': 0,
+            'profitable_trades': 0,
+            'losing_trades': 0,
+            'win_rate': 0.0,
+            'closed_profit_loss': 0.0,
+            'unrealized_profit_loss': 0.0,
+            'total_profit_loss': 0.0,
+            'avg_profit_loss': 0.0,
+            'avg_profit_loss_pct': 0.0,
+            'max_profit': 0.0,
+            'max_loss': 0.0,
+            'avg_holding_days': 0.0,
+            'profit_factor': 0.0
+        }
+    
+    def _calculate_unrealized_pnl_for_commodity(self, strategy_name, commodity):
+        """计算特定品种未平仓头寸的未实现盈亏"""
+        unrealized_pnl = 0.0
+        strategy_positions = self.active_positions.get(strategy_name, {})
+        
+        if commodity in strategy_positions:
+            current_contract = self._get_current_contract(commodity, self.end_date.date())
+            if not current_contract:
+                return 0.0
+            
+            end_price = self._get_price_on_date(commodity, current_contract, self.end_date.date(), 'close')
+            if end_price is None:
+                return 0.0
+            
+            positions = strategy_positions[commodity]
+            for position_id, position in positions.items():
+                if position['status'] == 'open' and position['contract'] == current_contract:
+                    # 判断是多头还是空头
+                    is_long = not position.get('is_short', False)
+                    pnl = self._calculate_futures_pnl(
+                        position['entry_price'], end_price, position['quantity'], commodity, is_long=is_long
+                    )
+                    unrealized_pnl += pnl
+        
+        return unrealized_pnl
+    
+    def _count_open_positions_for_commodity(self, strategy_name, commodity):
+        """计算特定品种的未平仓头寸数量"""
+        count = 0
+        strategy_positions = self.active_positions.get(strategy_name, {})
+        
+        if commodity in strategy_positions:
+            positions = strategy_positions[commodity]
+            for position_id, position in positions.items():
+                if position['status'] == 'open':
+                    count += 1
+        
+        return count
+    
+    def _calculate_unrealized_pnl(self, strategy_name):
+        """
+        计算未平仓头寸的未实现盈亏
+        """
+        unrealized_pnl = 0.0
+        
+        # 获取策略对应的头寸字典
+        strategy_positions = self.active_positions.get(strategy_name, {})
+        
+        for commodity, positions in strategy_positions.items():
+            # 获取当前合约和最新价格
+            current_contract = self._get_current_contract(commodity, self.end_date.date())
+            if not current_contract:
+                continue
+            
+            # 获取结束日期的收盘价
+            end_price = self._get_price_on_date(commodity, current_contract, self.end_date.date(), 'close')
+            if end_price is None:
+                continue
+            
+            for position_id, position in positions.items():
+                if position['status'] == 'open' and position['contract'] == current_contract:
+                    # 判断是多头还是空头
+                    is_long = not position.get('is_short', False)
+                    pnl = self._calculate_futures_pnl(
+                        position['entry_price'], end_price, position['quantity'], commodity, is_long=is_long
+                    )
+                    
+                    unrealized_pnl += pnl
+        
+        return unrealized_pnl
+    
+    def _count_open_positions(self, strategy_name):
+        """
+        计算未平仓头寸数量
+        """
+        count = 0
+        strategy_positions = self.active_positions.get(strategy_name, {})
+        
+        for commodity, positions in strategy_positions.items():
+            for position_id, position in positions.items():
+                if position['status'] == 'open':
+                    count += 1
+        
+        return count
+    
+    def generate_comparison_report(self, performance_stats):
+        """
+        生成多级聚合的对比报告
+        包括品种-策略级、品种级、策略级和总体级报告
+        """
+        if self.verbose_logging:
+            print("\n=== 多级聚合对比分析报告 ===")
+        
+        strategies = ['base_position', 'grid_trading']
+        strategy_names = {
+            'base_position': '基础头寸交易',
+            'grid_trading': '网格交易'
+        }
+        
+        all_comparison_data = {
+            'by_commodity_strategy': [],
+            'by_commodity': [],
+            'by_strategy': [],
+            'overall': []
+        }
+        
+        # 1. 品种-策略级对比报告
+        if self.verbose_logging:
+            print("\n--- 品种-策略级对比 ---")
+        
+        by_comm_strategy_data = []
+        for strategy in strategies:
+            for commodity in self.core_commodities.keys():
+                stats = performance_stats['by_commodity_strategy'].get(strategy, {}).get(commodity, {})
+                if stats.get('total_trades', 0) > 0 or stats.get('open_positions', 0) > 0:
+                    by_comm_strategy_data.append({
+                        '品种': commodity,
+                        '策略': strategy_names[strategy],
+                        '已平仓': stats.get('total_trades', 0),
+                        '未平仓': stats.get('open_positions', 0),
+                        '胜率': f"{stats.get('win_rate', 0):.2%}",
+                        '已实现': f"{stats.get('closed_profit_loss', 0):.2f}",
+                        '未实现': f"{stats.get('unrealized_profit_loss', 0):.2f}",
+                        '总盈亏': f"{stats.get('total_profit_loss', 0):.2f}",
+                        '平均盈亏': f"{stats.get('avg_profit_loss', 0):.2f}",
+                        '最大盈利': f"{stats.get('max_profit', 0):.2f}",
+                        '最大亏损': f"{stats.get('max_loss', 0):.2f}",
+                        '平均天数': f"{stats.get('avg_holding_days', 0):.1f}",
+                        '盈亏比': f"{stats.get('profit_factor', 0):.2f}" if stats.get('profit_factor', 0) != float('inf') else "∞"
+                    })
+        
+        if by_comm_strategy_data and self.verbose_logging:
+            df_comm_strategy = pd.DataFrame(by_comm_strategy_data)
+            print(df_comm_strategy.to_string(index=False))
+        all_comparison_data['by_commodity_strategy'] = by_comm_strategy_data
+        
+        # 2. 品种级汇总对比报告
+        if self.verbose_logging:
+            print("\n--- 品种级汇总对比 ---")
+        
+        by_commodity_data = []
+        for commodity in self.core_commodities.keys():
+            stats = performance_stats['by_commodity'].get(commodity, {})
+            if stats.get('total_trades', 0) > 0 or stats.get('open_positions', 0) > 0:
+                by_commodity_data.append({
+                    '品种': commodity,
+                    '已平仓': stats.get('total_trades', 0),
+                    '未平仓': stats.get('open_positions', 0),
+                    '胜率': f"{stats.get('win_rate', 0):.2%}",
+                    '已实现': f"{stats.get('closed_profit_loss', 0):.2f}",
+                    '未实现': f"{stats.get('unrealized_profit_loss', 0):.2f}",
+                    '总盈亏': f"{stats.get('total_profit_loss', 0):.2f}",
+                    '平均盈亏': f"{stats.get('avg_profit_loss', 0):.2f}",
+                    '最大盈利': f"{stats.get('max_profit', 0):.2f}",
+                    '最大亏损': f"{stats.get('max_loss', 0):.2f}",
+                    '平均天数': f"{stats.get('avg_holding_days', 0):.1f}",
+                    '盈亏比': f"{stats.get('profit_factor', 0):.2f}" if stats.get('profit_factor', 0) != float('inf') else "∞"
+                })
+        
+        if by_commodity_data and self.verbose_logging:
+            df_commodity = pd.DataFrame(by_commodity_data)
+            print(df_commodity.to_string(index=False))
+        all_comparison_data['by_commodity'] = by_commodity_data
+        
+        # 3. 策略级汇总对比报告
+        if self.verbose_logging:
+            print("\n--- 策略级汇总对比 ---")
+        
+        by_strategy_data = []
+        for strategy in strategies:
+            stats = performance_stats['by_strategy'].get(strategy, {})
+            by_strategy_data.append({
+                '策略': strategy_names[strategy],
+                '已平仓': stats.get('total_trades', 0),
+                '未平仓': stats.get('open_positions', 0),
+                '胜率': f"{stats.get('win_rate', 0):.2%}",
+                '已实现': f"{stats.get('closed_profit_loss', 0):.2f}",
+                '未实现': f"{stats.get('unrealized_profit_loss', 0):.2f}",
+                '总盈亏': f"{stats.get('total_profit_loss', 0):.2f}",
+                '平均盈亏': f"{stats.get('avg_profit_loss', 0):.2f}",
+                '最大盈利': f"{stats.get('max_profit', 0):.2f}",
+                '最大亏损': f"{stats.get('max_loss', 0):.2f}",
+                '平均天数': f"{stats.get('avg_holding_days', 0):.1f}",
+                '盈亏比': f"{stats.get('profit_factor', 0):.2f}" if stats.get('profit_factor', 0) != float('inf') else "∞"
+            })
+        
+        if by_strategy_data and self.verbose_logging:
+            df_strategy = pd.DataFrame(by_strategy_data)
+            print(df_strategy.to_string(index=False))
+        all_comparison_data['by_strategy'] = by_strategy_data
+        
+        # 4. 总体汇总报告
+        if self.verbose_logging:
+            print("\n--- 整体汇总 ---")
+        
+        overall_data = []
+        stats = performance_stats['overall']
+        overall_data.append({
+            '项目': '整体表现',
+            '已平仓': stats.get('total_trades', 0),
+            '未平仓': stats.get('open_positions', 0),
+            '胜率': f"{stats.get('win_rate', 0):.2%}",
+            '已实现': f"{stats.get('closed_profit_loss', 0):.2f}",
+            '未实现': f"{stats.get('unrealized_profit_loss', 0):.2f}",
+            '总盈亏': f"{stats.get('total_profit_loss', 0):.2f}",
+            '平均盈亏': f"{stats.get('avg_profit_loss', 0):.2f}",
+            '最大盈利': f"{stats.get('max_profit', 0):.2f}",
+            '最大亏损': f"{stats.get('max_loss', 0):.2f}",
+            '平均天数': f"{stats.get('avg_holding_days', 0):.1f}",
+            '盈亏比': f"{stats.get('profit_factor', 0):.2f}" if stats.get('profit_factor', 0) != float('inf') else "∞"
+        })
+        
+        if overall_data and self.verbose_logging:
+            df_overall = pd.DataFrame(overall_data)
+            print(df_overall.to_string(index=False))
+        all_comparison_data['overall'] = overall_data
+        
+        return all_comparison_data
+    
+    def generate_csv_output(self, performance_stats):
+        """
+        生成CSV输出文件
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤8: 生成CSV输出文件 ===")
+        
+        timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
+        
+        # 1. 生成交易记录CSV
+        all_trades = []
+        for strategy_name, trades in self.trading_results.items():
+            all_trades.extend(trades)
+        
+        if all_trades:
+            df_trades = pd.DataFrame(all_trades)
+            # 添加列顺序,确保合约切换相关字段在前面
+            column_order = ['commodity', 'contract', 'strategy', 'entry_date', 'exit_date', 
+                          'entry_price', 'exit_price', 'quantity', 'profit_loss', 'profit_loss_pct', 
+                          'days_held', 'exit_reason']
+            # 重新排列DataFrame的列顺序
+            existing_columns = [col for col in column_order if col in df_trades.columns]
+            other_columns = [col for col in df_trades.columns if col not in column_order]
+            df_trades = df_trades[existing_columns + other_columns]
+            
+            trades_filename = f'grid_trading_records_{timestamp}.csv'
+            df_trades.to_csv(trades_filename, index=False, encoding=self.output_encoding)
+            if self.verbose_logging:
+                print(f"交易记录已保存至: {trades_filename}")
+        
+        # 2. 生成多级性能统计CSV
+        csv_files = []
+        
+        # 2.1 品种-策略级统计CSV
+        by_comm_strategy_data = []
+        for strategy, commodity_data in performance_stats['by_commodity_strategy'].items():
+            for commodity, stats in commodity_data.items():
+                stats_record = {
+                    '品种': commodity,
+                    '策略': strategy,
+                    **stats
+                }
+                by_comm_strategy_data.append(stats_record)
+        
+        if by_comm_strategy_data:
+            df_comm_strategy = pd.DataFrame(by_comm_strategy_data)
+            comm_strategy_filename = f'grid_trading_by_commodity_strategy_{timestamp}.csv'
+            df_comm_strategy.to_csv(comm_strategy_filename, index=False, encoding=self.output_encoding)
+            csv_files.append(comm_strategy_filename)
+            if self.verbose_logging:
+                print(f"品种-策略级统计已保存至: {comm_strategy_filename}")
+        
+        # 2.2 品种级汇总统计CSV
+        by_commodity_data = []
+        for commodity, stats in performance_stats['by_commodity'].items():
+            stats_record = {
+                '品种': commodity,
+                **stats
+            }
+            by_commodity_data.append(stats_record)
+        
+        if by_commodity_data:
+            df_commodity = pd.DataFrame(by_commodity_data)
+            commodity_filename = f'grid_trading_by_commodity_{timestamp}.csv'
+            df_commodity.to_csv(commodity_filename, index=False, encoding=self.output_encoding)
+            csv_files.append(commodity_filename)
+            if self.verbose_logging:
+                print(f"品种级汇总统计已保存至: {commodity_filename}")
+        
+        # 2.3 策略级汇总统计CSV
+        by_strategy_data = []
+        for strategy, stats in performance_stats['by_strategy'].items():
+            stats_record = {
+                '策略': strategy,
+                **stats
+            }
+            by_strategy_data.append(stats_record)
+        
+        if by_strategy_data:
+            df_strategy = pd.DataFrame(by_strategy_data)
+            strategy_filename = f'grid_trading_by_strategy_{timestamp}.csv'
+            df_strategy.to_csv(strategy_filename, index=False, encoding=self.output_encoding)
+            csv_files.append(strategy_filename)
+            if self.verbose_logging:
+                print(f"策略级汇总统计已保存至: {strategy_filename}")
+        
+        # 2.4 整体汇总统计CSV
+        overall_data = [{
+            '项目': '整体汇总',
+            **performance_stats['overall']
+        }]
+        
+        if overall_data:
+            df_overall = pd.DataFrame(overall_data)
+            overall_filename = f'grid_trading_overall_{timestamp}.csv'
+            df_overall.to_csv(overall_filename, index=False, encoding=self.output_encoding)
+            csv_files.append(overall_filename)
+            if self.verbose_logging:
+                print(f"整体汇总统计已保存至: {overall_filename}")
+        
+        # 2.5 对冲策略专项统计CSV
+        if self.config.HEDGE_ENABLED and 'hedge_special_stats' in performance_stats:
+            hedge_stats = performance_stats['hedge_special_stats']
+            hedge_data = [{
+                '项目': '对冲策略专项统计',
+                '对冲交易总数': hedge_stats['total_hedge_trades'],
+                '对冲总盈亏': hedge_stats['total_hedge_pnl'],
+                '止盈次数': hedge_stats['take_profit_count'],
+                '止损次数_价格触发': hedge_stats['stop_loss_price_count'],
+                '合约换月平仓次数': hedge_stats['contract_switch_count'],
+                '平均持有天数': hedge_stats['avg_holding_days'],
+                '与基础头寸相关系数': hedge_stats['correlation_with_base']
+            }]
+            
+            df_hedge = pd.DataFrame(hedge_data)
+            hedge_filename = f'grid_trading_hedge_stats_{timestamp}.csv'
+            df_hedge.to_csv(hedge_filename, index=False, encoding=self.output_encoding)
+            csv_files.append(hedge_filename)
+            if self.verbose_logging:
+                print(f"对冲策略专项统计已保存至: {hedge_filename}")
+        
+        return trades_filename if all_trades else None, csv_files
+    
+    def run_complete_analysis(self):
+        """执行完整的网格交易分析流程(带主力合约切换)"""
+        if self.verbose_logging:
+            print("开始执行期货网格交易分析(带主力合约切换)")
+            print("=" * 60)
+        
+        try:
+            # 步骤1: 合约选择
+            self.select_contracts()
+            if not self.selected_contracts:
+                if self.verbose_logging:
+                    print("未选择到有效合约,分析终止")
+                return None
+            
+            # 步骤2: 构建主力合约历史变化
+            self.build_dominant_contract_history()
+            if not self.dominant_contract_history:
+                if self.verbose_logging:
+                    print("未获取到主力合约历史,分析终止")
+                return None
+            
+            # 步骤3: 收集价格数据
+            self.collect_price_data()
+            if not self.price_data:
+                if self.verbose_logging:
+                    print("未获取到有效价格数据,分析终止")
+                return None
+            
+            # 步骤4: 带合约切换的交易模拟
+            self.simulate_with_contract_switching()
+            
+            # 步骤5: 性能统计分析
+            performance_stats = self.calculate_performance_statistics()
+            
+            # 步骤6: 生成对比报告
+            comparison_report = self.generate_comparison_report(performance_stats)
+            
+            # 步骤8: 生成CSV输出
+            # trades_file, stats_files = self.generate_csv_output(performance_stats)
+            
+            # 分析汇总
+            total_commodities = len(self.selected_contracts)
+            total_trades = sum(len(trades) for trades in self.trading_results.values())
+            contract_switches = sum(len(history) for history in self.dominant_contract_history.values())
+            
+            if self.verbose_logging:
+                print("\n" + "=" * 60)
+                print("分析完成汇总:")
+                print(f"分析商品数: {total_commodities}")
+                print(f"合约切换次数: {contract_switches}")
+                print(f"总交易笔数: {total_trades}")
+                # print(f"交易记录文件: {trades_file}")
+                # print(f"性能统计文件: {stats_file}")
+            
+            return {
+                'selected_contracts': self.selected_contracts,
+                'dominant_contract_history': self.dominant_contract_history,
+                'price_data': self.price_data,
+                'trading_results': self.trading_results,
+                'performance_stats': performance_stats,
+                'comparison_report': comparison_report,
+                # 'output_files': {
+                #     'trades_file': trades_file,
+                #     'stats_files': stats_files
+                # },
+                'summary': {
+                    'total_commodities': total_commodities,
+                    'contract_switches': contract_switches,
+                    'total_trades': total_trades
+                }
+            }
+            
+        except Exception as e:
+            if self.verbose_logging:
+                print(f"分析过程中出现错误: {str(e)}")
+                import traceback
+                traceback.print_exc()
+            return None
+
+
+# =====================================================================================
+# 主程序入口
+# =====================================================================================
+
+def run_grid_trading_analysis(config=None):
+    """运行期货网格交易分析"""
+    if config is None:
+        config = GridTradingConfig
+    
+    # 打印配置信息
+    config.print_config()
+    
+    # 创建分析器并运行
+    analyzer = FutureGridTradingAnalyzer(config)
+    results = analyzer.run_complete_analysis()
+    return results
+
+# 执行分析
+if __name__ == "__main__":
+    print("期货网格交易研究分析工具(带主力合约切换)")
+    print("研究期货网格交易策略在不同配置下的表现")
+    print("核心功能包括主力合约自动切换、强制平仓和重新建仓逻辑")
+    print("")
+    print("支持两种独立交易策略的分析:")
+    print("  1. 基础头寸交易 - 价格-数量网格配置")
+    print("  2. 网格交易策略 - 限价订单网格买入卖出")
+    print("")
+    print("主要特点:")
+    print("  - 主力合约自动监控:每日检测主力合约变化")
+    print("  - 强制平仓机制:合约切换时立即平掉旧合约所有头寸")
+    print("  - 智能重新建仓:根据价格条件在新合约中重新建立头寸")
+    print("  - 完整交易记录:记录所有交易包括合约切换引起的强制平仓")
+    print("")
+    print("适用于聚宽在线研究平台")
+    
+    results = run_grid_trading_analysis()
+    
+    if results:
+        print("\n✅ 分析执行成功!")
+        summary = results['summary']
+        print(f"📊 结果摘要:")
+        print(f"   - 分析商品数: {summary['total_commodities']}")
+        print(f"   - 合约切换次数: {summary['contract_switches']}")
+        print(f"   - 总交易笔数: {summary['total_trades']}")
+        print(f"   - 整体总盈亏: {results['performance_stats']['overall']['total_profit_loss']:.2f}")
+        print(f"   - 整体胜率: {results['performance_stats']['overall']['win_rate']:.2%}")
+        print(f"   - 未平仓头寸: {results['performance_stats']['overall']['open_positions']}")
+        print(f"📂 输出文件:")
+        print(f"   - 交易记录文件: {results['output_files']['trades_file']}")
+        for i, stats_file in enumerate(results['output_files']['stats_files'], 1):
+            print(f"   - 统计文件{i}: {stats_file}")
+        
+        print(f"\n📈 多级汇总:")
+        
+        # 品种级汇总简要显示
+        print(f"   品种表现:")
+        for commodity in ['SA', 'M']:
+            comm_stats = results['performance_stats']['by_commodity'].get(commodity, {})
+            if comm_stats.get('total_trades', 0) > 0 or comm_stats.get('open_positions', 0) > 0:
+                print(f"     {commodity}: 交易{comm_stats.get('total_trades', 0)}笔, 盈亏{comm_stats.get('total_profit_loss', 0):.2f}, 胜率{comm_stats.get('win_rate', 0):.2%}")
+        
+        # 策略级汇总简要显示
+        print(f"   策略表现:")
+        strategy_names = {
+            'base_position': '基础头寸',
+            'grid_trading': '网格交易'
+        }
+        for strategy, name in strategy_names.items():
+            strat_stats = results['performance_stats']['by_strategy'].get(strategy, {})
+            print(f"     {name}: 交易{strat_stats.get('total_trades', 0)}笔, 盈亏{strat_stats.get('total_profit_loss', 0):.2f}, 胜率{strat_stats.get('win_rate', 0):.2%}")
+        
+    else:
+        print("\n❌ 分析执行失败,请检查错误信息")

+ 821 - 0
Lib/research/future_ma5_deviation_analysis.py

@@ -0,0 +1,821 @@
+"""
+期货合约5日移动平均线偏离分析
+研究期货合约在大幅偏离其5天移动平均线(K5)后是否出现重大反转走势
+
+本程序实现完整的分析流程:
+1. 数据结构设置 - 获取主力期货合约数据
+2. 数据收集 - 扩展时间范围获取OHLC数据
+3. 距离计算 - 计算价格相对K5的上下偏离距离
+4. 阈值确定 - 使用百分位数作为极端偏差阈值
+5. 信号检测与分析 - 识别极端偏差并分析后续走势
+6. 结果输出 - 生成Range_1偏差统计和信号分析结果CSV
+
+注:程序使用动态获取的主力合约进行分析,确保分析结果基于真实的交易活动
+
+作者: jukuan研究团队
+日期: 2025-09
+适用平台: 聚宽在线研究平台
+"""
+
+import pandas as pd
+import numpy as np
+from jqdata import *
+import datetime
+import warnings
+warnings.filterwarnings('ignore')
+
+# =====================================================================================
+# 分析配置参数 - 集中配置部分
+# =====================================================================================
+
+class AnalysisConfig:
+    """期货5日移动平均线偏离分析配置参数"""
+    
+    # ==================== 时间范围设置 ====================
+    ANALYSIS_START_DATE = datetime.datetime(2025, 8, 1)  # 分析开始日期
+    ANALYSIS_END_DATE = datetime.datetime(2025, 8, 20)   # 分析结束日期
+    
+    # ==================== 时间扩展参数 ====================
+    HISTORICAL_DAYS = 365      # 历史分析期:优势期前天数
+    FORWARD_DAYS = 20         # 正向分析期:优势期后天数
+    FORWARD_MONITOR_DAYS = 10  # 前向监控窗口:交易日数
+    
+    # ==================== 偏差阈值设置 ====================
+    DEVIATION_PERCENTILE = 90  # 偏差阈值百分位数(范围:0-100)
+    
+    # ==================== 分析规模控制 ====================
+    MAX_ANALYSIS_CONTRACTS = 10  # 最大分析合约数量限制
+    
+    
+    # ==================== 移动平均线参数 ====================
+    MA_PERIOD = 5  # K线移动平均线周期(天数)
+    
+    # ==================== 输出设置 ====================
+    OUTPUT_ENCODING = 'utf-8-sig'  # 输出文件编码格式
+    VERBOSE_LOGGING = True          # 是否打印详细日志
+    
+    @classmethod
+    def print_config(cls):
+        """打印当前配置信息"""
+        print("=== 期货5日移动平均线偏离分析配置 ===")
+        print(f"分析时间范围: {cls.ANALYSIS_START_DATE.strftime('%Y-%m-%d')} 至 {cls.ANALYSIS_END_DATE.strftime('%Y-%m-%d')}")
+        print(f"历史分析期: {cls.HISTORICAL_DAYS}天")
+        print(f"前向分析期: {cls.FORWARD_DAYS}天")
+        print(f"前向监控窗口: {cls.FORWARD_MONITOR_DAYS}个交易日")
+        print(f"偏差阈值百分位数: {cls.DEVIATION_PERCENTILE}%")
+        print(f"最大分析合约数: {cls.MAX_ANALYSIS_CONTRACTS}")
+        print(f"合约类型: 主力合约")
+        print(f"移动平均线周期: {cls.MA_PERIOD}日")
+        print(f"详细日志: {'开启' if cls.VERBOSE_LOGGING else '关闭'}")
+        print("=" * 50)
+
+class FutureMA5DeviationAnalyzer:
+    """期货5日移动平均线偏离分析器"""
+    
+    def __init__(self, config=None):
+        """初始化分析器"""
+        # 使用配置类参数
+        if config is None:
+            config = AnalysisConfig
+        
+        self.config = config
+        self.historical_days = config.HISTORICAL_DAYS
+        self.forward_days = config.FORWARD_DAYS  
+        self.deviation_percentile = config.DEVIATION_PERCENTILE
+        self.forward_monitor_days = config.FORWARD_MONITOR_DAYS
+        self.analysis_start_date = config.ANALYSIS_START_DATE
+        self.analysis_end_date = config.ANALYSIS_END_DATE
+        self.max_analysis_contracts = config.MAX_ANALYSIS_CONTRACTS
+        self.ma_period = config.MA_PERIOD
+        self.output_encoding = config.OUTPUT_ENCODING
+        self.verbose_logging = config.VERBOSE_LOGGING
+        
+        # 期货品种与交易所的映射关系
+        self.exchange_map = {
+            # 大连商品交易所 (XDCE)
+            'A': 'XDCE', 'B': 'XDCE', 'C': 'XDCE', 'CS': 'XDCE', 'FB': 'XDCE', 
+            'I': 'XDCE', 'J': 'XDCE', 'JD': 'XDCE', 'JM': 'XDCE', 'L': 'XDCE',
+            'M': 'XDCE', 'P': 'XDCE', 'PP': 'XDCE', 'PG': 'XDCE', 'RR': 'XDCE',
+            'V': 'XDCE', 'Y': 'XDCE', 'EB': 'XDCE', 'EG': 'XDCE', 'LH': 'XDCE',
+            
+            # 郑州商品交易所 (XZCE)
+            'AP': 'XZCE', 'CF': 'XZCE', 'CY': 'XZCE', 'FG': 'XZCE', 'JR': 'XZCE',
+            'LR': 'XZCE', 'MA': 'XZCE', 'OI': 'XZCE', 'PM': 'XZCE', 'RI': 'XZCE',
+            'RM': 'XZCE', 'RS': 'XZCE', 'SF': 'XZCE', 'SM': 'XZCE', 'SR': 'XZCE',
+            'TA': 'XZCE', 'WH': 'XZCE', 'ZC': 'XZCE', 'CJ': 'XZCE', 
+            'ME': 'XZCE', 'PF': 'XZCE', 'PK': 'XZCE', 'RO': 'XZCE', 'SA': 'XZCE',
+            'TC': 'XZCE', 'UR': 'XZCE', 'WS': 'XZCE', 'WT': 'XZCE',
+            
+            # 上海期货交易所 (XSGE)
+            'AG': 'XSGE', 'AL': 'XSGE', 'AU': 'XSGE', 'BU': 'XSGE', 'CU': 'XSGE',
+            'FU': 'XSGE', 'HC': 'XSGE', 'NI': 'XSGE', 'PB': 'XSGE', 'RB': 'XSGE',
+            'RU': 'XSGE', 'SN': 'XSGE', 'SP': 'XSGE', 'SS': 'XSGE', 'WR': 'XSGE',
+            'ZN': 'XSGE',
+            
+            # 上海国际能源交易中心 (XINE)
+            'BC': 'XINE', 'LU': 'XINE', 'NR': 'XINE', 'SC': 'XINE',
+            
+            # 中金所 (CCFX)
+            'IC': 'CCFX', 'IF': 'CCFX', 'IH': 'CCFX', 'T': 'CCFX', 'TF': 'CCFX', 'TS': 'CCFX',
+            
+            # 广期所 (GFEX)
+            'SI': 'GFEX', 'LC': 'GFEX', 'PS': 'GFEX'
+        }
+        
+        # 存储结果的字典
+        self.contract_data = {}  # 主合约数据结构
+        self.signal_results = {}  # 信号检测结果
+        
+        if self.verbose_logging:
+            print("初始化期货5日移动平均线偏离分析器")
+            print(f"配置参数 - 历史分析期: {self.historical_days}天, 前向分析期: {self.forward_days}天")
+            print(f"偏差阈值: {self.deviation_percentile}分位数, 监控窗口: {self.forward_monitor_days}个交易日")
+            print(f"最大分析合约数: {self.max_analysis_contracts}, 移动平均线周期: {self.ma_period}日")
+            print(f"合约类型: 主力合约")
+    
+    def _is_trading_day(self, check_date):
+        """
+        检查是否为交易日
+        
+        参数:
+            check_date (date): 要检查的日期
+        
+        返回:
+            bool: 是否为交易日
+        """
+        try:
+            # 使用聚宽API检查交易日
+            trade_days = get_trade_days(end_date=check_date, count=1)
+            if len(trade_days) > 0:
+                return trade_days[0].date() == check_date
+            return False
+        except:
+            # 如果API调用失败,简单判断是否为工作日
+            return check_date.weekday() < 5
+    
+    def _get_symbol_dominant_history(self, symbol, start_date, end_date):
+        """
+        获取单个标的的主力合约历史变化
+        
+        参数:
+            symbol (str): 标的编码
+            start_date (date): 开始日期
+            end_date (date): 结束日期
+        
+        返回:
+            list: 主力合约记录列表
+        """
+        results = []
+        current_date = start_date
+        current_dominant = None
+        period_start = None
+        
+        if self.verbose_logging:
+            print(f"  获取 {symbol} 主力合约历史...")
+        
+        # 按日遍历时间范围
+        while current_date <= end_date:
+            
+            # 跳过非交易日
+            if not self._is_trading_day(current_date):
+                current_date += datetime.timedelta(days=1)
+                continue
+            
+            try:
+                # 获取当日主力合约
+                dominant_contract = get_dominant_future(symbol, current_date)
+                
+                if dominant_contract is None:
+                    current_date += datetime.timedelta(days=1)
+                    continue
+                    
+                # 如果是新的主力合约
+                if dominant_contract != current_dominant:
+                    
+                    # 如果不是第一个合约,结束上一个合约的记录
+                    if current_dominant is not None and period_start is not None:
+                        results.append({
+                            'symbol': symbol,
+                            'dominant_contract': current_dominant,
+                            'start_date': period_start,
+                            'end_date': current_date - datetime.timedelta(days=1)
+                        })
+                    
+                    # 开始新合约的记录
+                    current_dominant = dominant_contract
+                    period_start = current_date
+                    
+                    if self.verbose_logging:
+                        print(f"    {current_date}: 主力合约变更为 {dominant_contract}")
+                
+            except Exception as e:
+                if self.verbose_logging:
+                    print(f"    获取 {current_date} 的主力合约时出错: {str(e)}")
+            
+            current_date += datetime.timedelta(days=1)
+        
+        # 处理最后一个合约
+        if current_dominant is not None and period_start is not None:
+            results.append({
+                'symbol': symbol,
+                'dominant_contract': current_dominant,
+                'start_date': period_start,
+                'end_date': end_date
+            })
+        
+        return results
+    
+    def get_dominant_contracts_history(self, symbol_list, start_date, end_date):
+        """
+        获取指定时间范围内多个标的的主力合约历史变化
+        
+        参数:
+            symbol_list (list): 标的编码列表
+            start_date (date): 开始日期
+            end_date (date): 结束日期
+        
+        返回:
+            pandas.DataFrame: 包含主力合约变化历史的数据框
+        """
+        if self.verbose_logging:
+            print(f"获取主力合约数据...")
+            print(f"标的列表: {symbol_list}")
+            print(f"时间范围: {start_date} 至 {end_date}")
+        
+        # 存储所有结果的列表
+        all_results = []
+        
+        # 逐个处理每个标的
+        for symbol in symbol_list:
+            if self.verbose_logging:
+                print(f"\n处理标的: {symbol}")
+            
+            try:
+                # 获取该标的在指定时间范围内的主力合约历史
+                symbol_results = self._get_symbol_dominant_history(symbol, start_date, end_date)
+                all_results.extend(symbol_results)
+                
+            except Exception as e:
+                if self.verbose_logging:
+                    print(f"处理标的 {symbol} 时发生错误: {str(e)}")
+                continue
+        
+        # 将结果转换为DataFrame
+        if all_results:
+            result_df = pd.DataFrame(all_results)
+            result_df = result_df.sort_values(['symbol', 'start_date']).reset_index(drop=True)
+            
+            if self.verbose_logging:
+                print(f"\n成功获取 {len(result_df)} 条主力合约记录")
+            return result_df
+        else:
+            if self.verbose_logging:
+                print("\n未获取到任何主力合约数据")
+            return pd.DataFrame(columns=['symbol', 'dominant_contract', 'start_date', 'end_date'])
+
+    def build_contract_structure(self):
+        """
+        构建期货主力合约数据结构
+        返回结构:Key=完整合约代码, Value=[start_date, end_date]
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤1: 构建期货主力合约数据结构 ===")
+        
+        # 分析时间范围
+        analysis_start = self.analysis_start_date
+        analysis_end = self.analysis_end_date
+        
+        if self.verbose_logging:
+            print(f"分析时间范围: {analysis_start.strftime('%Y-%m-%d')} 至 {analysis_end.strftime('%Y-%m-%d')}")
+            print(f"合约类型: 主力合约")
+        
+        # 构建主力合约数据结构
+        contract_dict = self._build_dominant_contract_structure(analysis_start, analysis_end)
+        
+        self.contract_data = contract_dict
+        if self.verbose_logging:
+            print(f"构建完成,共{len(contract_dict)}个期货合约")
+        return contract_dict
+    
+    def _build_dominant_contract_structure(self, analysis_start, analysis_end):
+        """构建基于主力合约的数据结构"""
+        if self.verbose_logging:
+            print("使用主力合约获取逻辑...")
+        
+        # 获取要分析的期货品种列表
+        symbol_list = list(self.exchange_map.keys())[:self.max_analysis_contracts]
+        
+        # 获取主力合约历史数据
+        dominant_df = self.get_dominant_contracts_history(
+            symbol_list, 
+            analysis_start.date(), 
+            analysis_end.date()
+        )
+        
+        contract_dict = {}
+        
+        if not dominant_df.empty:
+            # 将DataFrame转换为字典结构
+            for _, row in dominant_df.iterrows():
+                contract_code = row['dominant_contract']
+                start_date = row['start_date'] if isinstance(row['start_date'], datetime.date) else row['start_date'].date()
+                end_date = row['end_date'] if isinstance(row['end_date'], datetime.date) else row['end_date'].date()
+                
+                contract_dict[contract_code] = [start_date, end_date]
+                
+                if self.verbose_logging:
+                    print(f"  添加主力合约: {contract_code} ({start_date} 至 {end_date})")
+        
+        return contract_dict
+    
+    def collect_extended_data(self):
+        """
+        数据收集:为每个合约收集扩展时间范围的数据
+        扩展期限:从(start_date-365天)到(end_date+100天)
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤2: 收集扩展时间范围的数据 ===")
+        
+        extended_data = {}
+        
+        for contract_code, period in self.contract_data.items():
+            start_date_1, end_date_1 = period
+            
+            # 计算扩展期限
+            start_date_2 = start_date_1 - datetime.timedelta(days=self.historical_days)
+            end_date_2 = end_date_1 + datetime.timedelta(days=self.forward_days)
+            
+            if self.verbose_logging:
+                print(f"获取 {contract_code} 数据")
+                print(f"  原始期间: {start_date_1} 至 {end_date_1}")
+                print(f"  扩展期间: {start_date_2} 至 {end_date_2}")
+            
+            try:
+                # 获取日线数据
+                data = get_price(
+                    contract_code,
+                    start_date=start_date_2,
+                    end_date=end_date_2,
+                    frequency='daily',
+                    fields=['open', 'close', 'high', 'low', 'volume'],
+                    skip_paused=False,
+                    panel=False
+                )
+                
+                if data is not None and len(data) > 0:
+                    # 计算移动平均线
+                    data['K5'] = data['close'].rolling(window=self.ma_period).mean()
+                    
+                    extended_data[contract_code] = {
+                        'data': data,
+                        'original_start': start_date_1,
+                        'original_end': end_date_1,
+                        'extended_start': start_date_2,
+                        'extended_end': end_date_2
+                    }
+                    
+                    if self.verbose_logging:
+                        print(f"  成功获取{len(data)}条数据记录")
+                else:
+                    if self.verbose_logging:
+                        print(f"  未获取到数据")
+                    
+            except Exception as e:
+                if self.verbose_logging:
+                    print(f"  获取数据失败: {str(e)}")
+                continue
+        
+        if self.verbose_logging:
+            print(f"数据收集完成,共{len(extended_data)}个合约有效数据")
+        return extended_data
+    
+    def calculate_k5_distances(self, extended_data):
+        """
+        计算距离:每个交易日价格相对K5的上下偏离距离
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤3: 计算K5偏离距离 ===")
+        
+        distance_data = {}
+        
+        for contract_code, contract_info in extended_data.items():
+            data = contract_info['data']
+            if self.verbose_logging:
+                print(f"计算 {contract_code} 的K5距离")
+            
+            # 初始化距离列
+            data['upper_distance'] = np.nan
+            data['lower_distance'] = np.nan
+            
+            # 计算每日的上下偏离距离
+            for idx, row in data.iterrows():
+                if pd.isna(row['K5']):  # K5不可用则跳过
+                    continue
+                
+                K5 = row['K5']
+                high = row['high']
+                low = row['low']
+                
+                # 计算K5与高低价的差值
+                k5_minus_high = K5 - high
+                k5_minus_low = K5 - low
+                
+                if k5_minus_high > 0 and k5_minus_low > 0:
+                    # 高低都低于K5
+                    data.loc[idx, 'upper_distance'] = np.nan
+                    data.loc[idx, 'lower_distance'] = abs(K5 - low)
+                    
+                elif k5_minus_high < 0 and k5_minus_low < 0:
+                    # 高低都在K5以上
+                    data.loc[idx, 'upper_distance'] = abs(K5 - high)
+                    data.loc[idx, 'lower_distance'] = np.nan
+                    
+                elif k5_minus_high > 0 and k5_minus_low < 0:
+                    # 高低于K5,低高于K5(K5介于高与低之间)
+                    data.loc[idx, 'upper_distance'] = abs(K5 - high)
+                    data.loc[idx, 'lower_distance'] = abs(K5 - low)
+                
+                # 处理零情况
+                if abs(k5_minus_high) < 1e-6:  # high == K5
+                    data.loc[idx, 'upper_distance'] = 0.0
+                if abs(k5_minus_low) < 1e-6:   # low == K5
+                    data.loc[idx, 'lower_distance'] = 0.0
+            
+            distance_data[contract_code] = contract_info
+            
+            # 统计有效距离数据
+            valid_upper = data['upper_distance'].notna().sum()
+            valid_lower = data['lower_distance'].notna().sum()
+            if self.verbose_logging:
+                print(f"  上偏离距离有效数据: {valid_upper}条, 下偏离距离有效数据: {valid_lower}条")
+        
+        if self.verbose_logging:
+            print("K5距离计算完成")
+        return distance_data
+    
+    def determine_thresholds(self, distance_data):
+        """
+        阈值确定:使用Range_1数据计算百分位数阈值
+        Range_1: start_date_2 <= date < start_date_1
+        """
+        if self.verbose_logging:
+            print(f"\n=== 步骤4: 确定{self.deviation_percentile}分位数阈值 ===")
+        
+        threshold_data = {}
+        
+        for contract_code, contract_info in distance_data.items():
+            data = contract_info['data']
+            start_date_1 = contract_info['original_start']
+            start_date_2 = contract_info['extended_start']
+            
+            if self.verbose_logging:
+                print(f"计算 {contract_code} 的阈值")
+                print(f"  Range_1期间: {start_date_2} 至 {start_date_1}")
+            
+            # 筛选Range_1数据
+            range_1_mask = (data.index.date >= start_date_2) & (data.index.date < start_date_1)
+            range_1_data = data[range_1_mask]
+            
+            if len(range_1_data) == 0:
+                if self.verbose_logging:
+                    print(f"  Range_1无有效数据,跳过")
+                continue
+            
+            # 计算上下偏离距离的分布和阈值
+            upper_distances = range_1_data['upper_distance'].dropna()
+            lower_distances = range_1_data['lower_distance'].dropna()
+            
+            # 百分位数阈值
+            percentile_value = self.deviation_percentile / 100.0
+            upper_threshold = upper_distances.quantile(percentile_value) if len(upper_distances) > 0 else np.nan
+            lower_threshold = lower_distances.quantile(percentile_value) if len(lower_distances) > 0 else np.nan
+            
+            threshold_data[contract_code] = {
+                'contract_info': contract_info,
+                'upper_threshold': upper_threshold,
+                'lower_threshold': lower_threshold,
+                'range_1_stats': {
+                    'total_days': len(range_1_data),
+                    'upper_valid_days': len(upper_distances),
+                    'lower_valid_days': len(lower_distances),
+                    'upward_deviation_days': len(lower_distances[lower_distances >= lower_threshold]) if not pd.isna(lower_threshold) else 0,
+                    'downward_deviation_days': len(upper_distances[upper_distances >= upper_threshold]) if not pd.isna(upper_threshold) else 0
+                }
+            }
+            
+            if self.verbose_logging:
+                print(f"  上偏离{self.deviation_percentile}分位数阈值: {upper_threshold:.4f}" if not pd.isna(upper_threshold) else f"  上偏离阈值: 无效")
+                print(f"  下偏离{self.deviation_percentile}分位数阈值: {lower_threshold:.4f}" if not pd.isna(lower_threshold) else f"  下偏离阈值: 无效")
+            
+        if self.verbose_logging:
+            print("阈值确定完成")
+        return threshold_data
+    
+    def detect_signals_and_analyze(self, threshold_data):
+        """
+        信号检测与分析:在Range_2期间扫描极端偏差并分析后续走势
+        Range_2: start_date_1 <= date <= end_date_2
+        """
+        if self.verbose_logging:
+            print("\n=== 步骤5: 信号检测与前瞻性分析 ===")
+        
+        signal_dict = {}
+        
+        for contract_code, threshold_info in threshold_data.items():
+            contract_info = threshold_info['contract_info']
+            data = contract_info['data']
+            start_date_1 = contract_info['original_start']
+            end_date_2 = contract_info['extended_end']
+            
+            upper_threshold = threshold_info['upper_threshold']
+            lower_threshold = threshold_info['lower_threshold']
+            
+            if self.verbose_logging:
+                print(f"\n分析 {contract_code} 的信号")
+                print(f"  Range_2期间: {start_date_1} 至 {end_date_2}")
+            
+            # 筛选Range_2数据
+            range_2_mask = (data.index.date >= start_date_1) & (data.index.date <= end_date_2)
+            range_2_data = data[range_2_mask]
+            
+            contract_signals = {}
+            
+            # 扫描极端偏差天数
+            for idx, (signal_date, row) in enumerate(range_2_data.iterrows()):
+                upper_distance = row['upper_distance']
+                lower_distance = row['lower_distance']
+                
+                # 检查是否触发阈值
+                trigger_upper = not pd.isna(upper_distance) and not pd.isna(upper_threshold) and upper_distance >= upper_threshold
+                trigger_lower = not pd.isna(lower_distance) and not pd.isna(lower_threshold) and lower_distance >= lower_threshold
+                
+                if trigger_upper or trigger_lower:
+                    # 确定偏离方向和参考价格
+                    if trigger_lower:  # lower_distance触发表示向上偏离(价格在K5之上)
+                        deviation_direction = "up"
+                        deviation_distance = lower_distance
+                        reference_category = "low"
+                        reference_price = row['low']
+                    else:  # upper_distance触发表示向下偏离(价格在K5之下)
+                        deviation_direction = "down"
+                        deviation_distance = upper_distance
+                        reference_category = "high"
+                        reference_price = row['high']
+                    
+                    if self.verbose_logging:
+                        print(f"  发现信号: {signal_date.strftime('%Y-%m-%d')} {deviation_direction}偏离 距离{deviation_distance:.4f}")
+                    
+                    # 前瞻性分析
+                    forward_analysis = self._analyze_forward_performance(
+                        range_2_data, idx, deviation_direction, reference_price, self.forward_monitor_days
+                    )
+                    
+                    # 计算最大利润和风险
+                    max_profit_info = self._find_max_profit(forward_analysis)
+                    max_risk_info = self._find_max_risk(forward_analysis)
+                    
+                    contract_signals[signal_date] = {
+                        "deviation_direction": deviation_direction,
+                        "deviation_distance": float(deviation_distance),
+                        "reference_category": reference_category,
+                        "reference_price": float(reference_price),
+                        "forward_analysis": forward_analysis,
+                        "max_profit": max_profit_info,
+                        "max_risk": max_risk_info
+                    }
+            
+            if contract_signals:
+                signal_dict[contract_code] = contract_signals
+                if self.verbose_logging:
+                    print(f"  检测到{len(contract_signals)}个有效信号")
+            else:
+                if self.verbose_logging:
+                    print(f"  未检测到有效信号")
+        
+        if self.verbose_logging:
+            print(f"\n信号检测完成,共{sum(len(signals) for signals in signal_dict.values())}个信号")
+        self.signal_results = signal_dict
+        return signal_dict
+    
+    def _analyze_forward_performance(self, data, signal_idx, deviation_direction, reference_price, monitor_days):
+        """分析信号后续N个交易日的表现"""
+        forward_analysis = {}
+        
+        for i in range(1, monitor_days + 1):
+            future_idx = signal_idx + i
+            if future_idx < len(data):
+                future_row = data.iloc[future_idx]
+                future_date = data.index[future_idx]
+                
+                # 根据偏离方向选择对应价格
+                if deviation_direction == "up":
+                    corresponding_price = future_row['low']
+                    corresponding_return = (reference_price - corresponding_price) / reference_price
+                    reverse_return = (future_row['high'] - reference_price) / reference_price
+                else:  # down
+                    corresponding_price = future_row['high']
+                    corresponding_return = (corresponding_price - reference_price) / reference_price
+                    reverse_return = (reference_price - future_row['low']) / reference_price
+                
+                forward_analysis[future_date] = {
+                    "date": future_date.strftime('%Y-%m-%d'),
+                    "corresponding_price": float(corresponding_price),
+                    "corresponding_return": float(corresponding_return),
+                    "reverse_category": "high" if deviation_direction == "up" else "low",
+                    "reverse_return": float(reverse_return)
+                }
+        
+        return forward_analysis
+    
+    def _find_max_profit(self, forward_analysis):
+        """找到最大利润日期和收益率"""
+        if not forward_analysis:
+            return {"date": None, "return": 0.0}
+        
+        max_return = max(day_data["corresponding_return"] for day_data in forward_analysis.values())
+        max_date = None
+        
+        for date, day_data in forward_analysis.items():
+            if day_data["corresponding_return"] == max_return:
+                max_date = date.strftime('%Y-%m-%d')
+                break
+        
+        return {"date": max_date, "return": float(max_return)}
+    
+    def _find_max_risk(self, forward_analysis):
+        """找到最大风险日期和收益率"""
+        if not forward_analysis:
+            return {"date": None, "return": 0.0}
+        
+        max_risk = max(day_data["reverse_return"] for day_data in forward_analysis.values())
+        max_risk_date = None
+        
+        for date, day_data in forward_analysis.items():
+            if day_data["reverse_return"] == max_risk:
+                max_risk_date = date.strftime('%Y-%m-%d')
+                break
+        
+        return {"date": max_risk_date, "return": float(max_risk)}
+    
+    def generate_csv_outputs(self, threshold_data, signal_dict):
+        """生成两个CSV文件输出"""
+        if self.verbose_logging:
+            print("\n=== 步骤6: 生成CSV输出文件 ===")
+        
+        timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
+        
+        # CSV 1: Range_1偏差统计
+        self._generate_range1_stats_csv(threshold_data, timestamp)
+        
+        # CSV 2: 信号分析结果
+        self._generate_signal_analysis_csv(signal_dict, timestamp)
+        
+        if self.verbose_logging:
+            print("CSV文件生成完成")
+    
+    def _generate_range1_stats_csv(self, threshold_data, timestamp):
+        """生成Range_1偏差统计CSV"""
+        stats_data = []
+        
+        for contract_code, threshold_info in threshold_data.items():
+            stats = threshold_info['range_1_stats']
+            total_days = stats['total_days']
+            
+            upward_ratio = stats['upward_deviation_days'] / total_days if total_days > 0 else 0
+            downward_ratio = stats['downward_deviation_days'] / total_days if total_days > 0 else 0
+            
+            stats_data.append({
+                'contract_code': contract_code,
+                'upward_deviation_ratio': round(upward_ratio, 4),
+                'downward_deviation_ratio': round(downward_ratio, 4)
+            })
+        
+        if stats_data:
+            stats_df = pd.DataFrame(stats_data)
+            filename = f'range1_deviation_stats_{timestamp}.csv'
+            stats_df.to_csv(filename, index=False, encoding=self.output_encoding)
+            if self.verbose_logging:
+                print(f"Range_1偏差统计保存至: {filename}")
+    
+    def _generate_signal_analysis_csv(self, signal_dict, timestamp):
+        """生成信号分析结果CSV"""
+        signal_data = []
+        
+        for contract_code, contract_signals in signal_dict.items():
+            for signal_date, signal_info in contract_signals.items():
+                signal_data.append({
+                    'contract_code': contract_code,
+                    'signal_date': signal_date.strftime('%Y-%m-%d'),
+                    'deviation_direction': signal_info['deviation_direction'],
+                    'max_profit_date': signal_info['max_profit']['date'],
+                    'max_profit_return': round(signal_info['max_profit']['return'], 4),
+                    'max_risk_date': signal_info['max_risk']['date'],
+                    'max_risk_return': round(signal_info['max_risk']['return'], 4)
+                })
+        
+        if signal_data:
+            signal_df = pd.DataFrame(signal_data)
+            filename = f'signal_analysis_results_{timestamp}.csv'
+            signal_df.to_csv(filename, index=False, encoding=self.output_encoding)
+            if self.verbose_logging:
+                print(f"信号分析结果保存至: {filename}")
+    
+    def run_complete_analysis(self):
+        """执行完整的分析流程"""
+        if self.verbose_logging:
+            print("开始执行期货5日移动平均线偏离分析")
+            print("=" * 60)
+        
+        try:
+            # 步骤1: 构建合约数据结构
+            contract_structure = self.build_contract_structure()
+            
+            # 步骤2: 收集扩展数据
+            extended_data = self.collect_extended_data()
+            if not extended_data:
+                if self.verbose_logging:
+                    print("未获取到有效数据,分析终止")
+                return
+            
+            # 步骤3: 计算K5距离
+            distance_data = self.calculate_k5_distances(extended_data)
+            
+            # 步骤4: 确定阈值
+            threshold_data = self.determine_thresholds(distance_data)
+            if not threshold_data:
+                if self.verbose_logging:
+                    print("未能确定有效阈值,分析终止")
+                return
+            
+            # 步骤5: 信号检测与分析
+            signal_dict = self.detect_signals_and_analyze(threshold_data)
+            
+            # 步骤6: 生成输出文件
+            self.generate_csv_outputs(threshold_data, signal_dict)
+            
+            # 分析汇总
+            total_contracts = len(contract_structure)
+            valid_contracts = len(threshold_data)
+            total_signals = sum(len(signals) for signals in signal_dict.values())
+            
+            if self.verbose_logging:
+                print("\n" + "=" * 60)
+                print("分析完成汇总:")
+                print(f"总合约数: {total_contracts}")
+                print(f"有效合约数: {valid_contracts}")
+                print(f"检测信号数: {total_signals}")
+            
+            return {
+                'contract_structure': contract_structure,
+                'threshold_data': threshold_data,
+                'signal_results': signal_dict,
+                'summary': {
+                    'total_contracts': total_contracts,
+                    'valid_contracts': valid_contracts,
+                    'total_signals': total_signals
+                }
+            }
+            
+        except Exception as e:
+            if self.verbose_logging:
+                print(f"分析过程中出现错误: {str(e)}")
+                import traceback
+                traceback.print_exc()
+            return None
+
+
+# =====================================================================================
+# 主程序入口
+# =====================================================================================
+
+def run_ma5_deviation_analysis(config=None):
+    """运行期货5日移动平均线偏离分析"""
+    if config is None:
+        config = AnalysisConfig
+    
+    # 打印配置信息
+    config.print_config()
+    
+    # 创建分析器并运行
+    analyzer = FutureMA5DeviationAnalyzer(config)
+    results = analyzer.run_complete_analysis()
+    return results
+
+# 执行分析
+if __name__ == "__main__":
+    print("期货5日移动平均线偏离分析工具")
+    print("研究期货合约在大幅偏离K5后的反转走势规律")
+    print("使用动态获取的主力合约进行精准分析")
+    print("适用于聚宽在线研究平台")
+    
+    results = run_ma5_deviation_analysis()
+    
+    if results:
+        print("\n✅ 分析执行成功!")
+        summary = results['summary']
+        print(f"📊 结果摘要:")
+        print(f"   - 分析合约数: {summary['total_contracts']}")
+        print(f"   - 有效数据合约: {summary['valid_contracts']}")
+        print(f"   - 检测信号总数: {summary['total_signals']}")
+    else:
+        print("\n❌ 分析执行失败,请检查错误信息")

+ 1783 - 0
Lib/research/future_pattern_comprehensive.py

@@ -0,0 +1,1783 @@
+"""
+期货市场技术形态规律分析 - 综合版本
+研究期货品种在大幅下跌后出现特定K线形态的规律,并分析后续走势
+
+本文件包含:
+- 分析配置参数
+- 主要分析类FuturePatternAnalyzer
+- 基于合约切换日志的周线数据获取功能
+- 辅助工具函数和数据导出功能
+- 完整的分析流程
+
+主要功能:
+1. get_contract_weekly_data() - 基于合约切换日志获取周线数据的正式方法
+2. get_weekly_data_by_contract_log() - 核心数据获取逻辑,直接使用get_bars获取原生周线数据
+3. 简化的get_weekly_kline_data() - 直接使用8888加权合约,避免主力合约切换复杂性
+4. validate_manual_patterns() - 手动模式验证系统,支持指定日期的模式验证
+5. run_manual_pattern_validation() - 便捷的手动验证入口函数
+6. _display_full_candlestick_charts() - 模式识别失败时的完整K线图显示
+
+数据结构简化:
+- 移除了主力合约获取和切换逻辑
+- 使用单级字典结构:键为8888合约代码,值为日期范围
+- 所有数据检索均使用8888加权合约,简化了代码复杂性
+
+验证模式:
+- 自动搜索模式:使用identify_target_pattern()自动搜索符合条件的技术形态
+- 手动验证模式:使用validate_manual_patterns()验证指定日期是否符合模式条件
+- 两种模式使用相同的验证逻辑和标准,但工作流程独立
+
+作者: jukuan研究团队
+日期: 2025-09
+适用平台: 聚宽在线研究平台
+"""
+
+import pandas as pd
+import numpy as np
+from jqdata import *
+import datetime
+import matplotlib.pyplot as plt
+import matplotlib.patches as patches
+from matplotlib.dates import DateFormatter
+import warnings
+warnings.filterwarnings('ignore')
+
+# 中文字体设置
+plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']
+plt.rcParams['axes.unicode_minus'] = False
+
+# =====================================================================================
+# 第一部分:分析配置参数
+# 用户可根据需要调整以下参数来控制分析行为
+# =====================================================================================
+
+class AnalysisConfig:
+    """分析配置类 - 集中管理所有分析参数"""
+    
+    # ==================== 分析时间设置 ====================
+    START_DATE = '2022-01-01'  # 分析起始日期 (建议至少2年数据)
+    END_DATE = '2025-08-31'    # 分析结束日期
+    
+    # ==================== 筛选条件设置 ====================
+    MIN_DRAWDOWN_THRESHOLD = 0.30  # 最小回撤阈值 (0.3 = 30%)
+    
+    # K线形态识别条件
+    KLINE_CONDITIONS = {
+        'max_open_close_diff': 0.01,  # 开盘价与收盘价最大差异 (0.01 = 1%)
+        'min_high_open_gain': 0.03,   # 最高价相对开盘价的最小涨幅 (0.03 = 3%)
+    }
+    
+    # ==================== 分析参数设置 ====================
+    ANALYSIS_EXTEND_DAYS = 30    # 扩展分析天数 (在下跌期间前后各扩展的天数)
+    FOLLOW_UP_WEEKS = 10         # 后续跟踪周数
+    SUCCESS_RETURN_THRESHOLD = 0.05  # 成功率计算的收益率阈值 (0.05 = 5%)
+    
+    # ==================== 输出设置 ====================
+    GENERATE_CHARTS = True       # 是否生成K线图
+    MAX_CHART_COUNT = 10         # 最大图表生成数量 (避免生成过多图片)
+    CHART_OUTPUT_DIR = './'      # 图表保存目录
+    CSV_ENCODING = 'utf-8-sig'   # CSV文件编码
+    
+    # ==================== 期货品种过滤设置 ====================
+    # 需要排除的期货品种 (可根据需要调整)
+    EXCLUDED_FUTURES = [
+        'IF9999.CCFX',  # 
+        'LU8888.XINE',  # 
+        'NR8888.XINE',  # 
+        'LG8888.XDCE',  # 原木 - 新上市品种数据较少
+    ]
+    
+    # 重点关注的期货品种 (如果设置,将只分析这些品种)
+    FOCUS_FUTURES = []  # 空列表表示分析所有品种
+    
+    # ==================== 分析规模控制 ====================
+    MAX_ANALYSIS_FUTURES = 5    # 最大分析期货品种数 (-1表示分析所有品种)
+    VERBOSE_LOGGING = True       # 是否打印详细日志
+    SHOW_PROGRESS = True         # 是否在分析过程中显示进度
+    
+    # ==================== 数据质量控制 ====================
+    MIN_DATA_LENGTH = 100        # 最小数据长度要求 (日线数据点数)
+    MIN_WEEKLY_DATA = 20         # 最小周线数据要求
+    MAX_MISSING_DATA_RATIO = 0.1 # 最大缺失数据比例
+    
+    @classmethod
+    def get_config_dict(cls):
+        """获取分析配置字典"""
+        return {
+            'time_range': {
+                'start_date': cls.START_DATE,
+                'end_date': cls.END_DATE,
+            },
+            'filter_conditions': {
+                'min_drawdown': cls.MIN_DRAWDOWN_THRESHOLD,
+                'kline_conditions': cls.KLINE_CONDITIONS,
+            },
+            'analysis_params': {
+                'extend_days': cls.ANALYSIS_EXTEND_DAYS,
+                'follow_up_weeks': cls.FOLLOW_UP_WEEKS,
+                'success_threshold': cls.SUCCESS_RETURN_THRESHOLD,
+            },
+            'output_settings': {
+                'generate_charts': cls.GENERATE_CHARTS,
+                'max_chart_count': cls.MAX_CHART_COUNT,
+                'chart_dir': cls.CHART_OUTPUT_DIR,
+                'csv_encoding': cls.CSV_ENCODING,
+            },
+            'future_filter': {
+                'excluded': cls.EXCLUDED_FUTURES,
+                'focus_only': cls.FOCUS_FUTURES,
+            },
+            'analysis_control': {
+                'max_futures': cls.MAX_ANALYSIS_FUTURES,
+                'verbose': cls.VERBOSE_LOGGING,
+                'show_progress': cls.SHOW_PROGRESS,
+            },
+            'data_quality': {
+                'min_data_length': cls.MIN_DATA_LENGTH,
+                'min_weekly_data': cls.MIN_WEEKLY_DATA,
+                'max_missing_ratio': cls.MAX_MISSING_DATA_RATIO,
+            }
+        }
+    
+    @classmethod
+    def print_config(cls):
+        """打印当前配置信息"""
+        print("=== 期货技术形态分析配置 ===")
+        print(f"分析时间范围: {cls.START_DATE} 至 {cls.END_DATE}")
+        print(f"最小回撤阈值: {cls.MIN_DRAWDOWN_THRESHOLD*100:.0f}%")
+        print(f"开收差异阈值: {cls.KLINE_CONDITIONS['max_open_close_diff']*100:.0f}%")
+        print(f"最高涨幅阈值: {cls.KLINE_CONDITIONS['min_high_open_gain']*100:.0f}%")
+        print(f"后续跟踪周数: {cls.FOLLOW_UP_WEEKS}")
+        print(f"成功率阈值: {cls.SUCCESS_RETURN_THRESHOLD*100:.0f}%")
+        
+        if cls.EXCLUDED_FUTURES:
+            print(f"排除品种数: {len(cls.EXCLUDED_FUTURES)}")
+        if cls.FOCUS_FUTURES:
+            print(f"重点品种数: {len(cls.FOCUS_FUTURES)}")
+        
+        print(f"生成图表: {'是' if cls.GENERATE_CHARTS else '否'}")
+        print(f"最大分析品种数: {'所有' if cls.MAX_ANALYSIS_FUTURES == -1 else cls.MAX_ANALYSIS_FUTURES}")
+
+# =====================================================================================
+# 第二部分:期货技术形态分析器主类
+# 实现完整的技术形态识别和分析功能
+# =====================================================================================
+
+class FuturePatternAnalyzer:
+    """期货技术形态分析器 - 主要分析类"""
+    
+    def __init__(self, start_date=None, end_date=None, config=None):
+        """
+        初始化分析器
+        
+        参数:
+        - start_date: 分析开始日期,默认使用配置
+        - end_date: 分析结束日期,默认使用配置  
+        - config: 自定义配置,默认使用AnalysisConfig
+        """
+        # 使用提供的配置或默认配置
+        if config is None:
+            config = AnalysisConfig.get_config_dict()
+        
+        self.start_date = pd.to_datetime(start_date or config['time_range']['start_date'])
+        self.end_date = pd.to_datetime(end_date or config['time_range']['end_date'])
+        self.config = config
+        self.future_codes = {}  # 期货代码映射
+        
+        # 期货品种与交易所的映射关系
+        self.exchange_map = {
+            # 大连商品交易所 (XDCE)
+            'A': 'XDCE', 'B': 'XDCE', 'C': 'XDCE', 'CS': 'XDCE', 'FB': 'XDCE', 
+            'I': 'XDCE', 'J': 'XDCE', 'JD': 'XDCE', 'JM': 'XDCE', 'L': 'XDCE',
+            'M': 'XDCE', 'P': 'XDCE', 'PP': 'XDCE', 'PG': 'XDCE', 'RR': 'XDCE',
+            'V': 'XDCE', 'Y': 'XDCE', 'EB': 'XDCE', 'EG': 'XDCE', 'LH': 'XDCE',
+            
+            # 郑州商品交易所 (XZCE)
+            'AP': 'XZCE', 'CF': 'XZCE', 'CY': 'XZCE', 'FG': 'XZCE', 'JR': 'XZCE',
+            'LR': 'XZCE', 'MA': 'XZCE', 'OI': 'XZCE', 'PM': 'XZCE', 'RI': 'XZCE',
+            'RM': 'XZCE', 'RS': 'XZCE', 'SF': 'XZCE', 'SM': 'XZCE', 'SR': 'XZCE',
+            'TA': 'XZCE', 'WH': 'XZCE', 'ZC': 'XZCE', 'CJ': 'XZCE', 
+            'ME': 'XZCE', 'PF': 'XZCE', 'PK': 'XZCE', 'RO': 'XZCE', 'SA': 'XZCE',
+            'TC': 'XZCE', 'UR': 'XZCE', 'WS': 'XZCE', 'WT': 'XZCE',
+            
+            # 上海期货交易所 (XSGE)
+            'AG': 'XSGE', 'AL': 'XSGE', 'AU': 'XSGE', 'BU': 'XSGE', 'CU': 'XSGE',
+            'FU': 'XSGE', 'HC': 'XSGE', 'NI': 'XSGE', 'PB': 'XSGE', 'RB': 'XSGE',
+            'RU': 'XSGE', 'SN': 'XSGE', 'SP': 'XSGE', 'SS': 'XSGE', 'WR': 'XSGE',
+            'ZN': 'XSGE',
+            
+            # 上海国际能源交易中心 (XINE)
+            'BC': 'XINE', 'LU': 'XINE', 'NR': 'XINE', 'SC': 'XINE',
+            
+            # 中金所 (CCFX)
+            'IC': 'CCFX', 'IF': 'CCFX', 'IH': 'CCFX', 'T': 'CCFX', 'TF': 'CCFX', 'TS': 'CCFX',
+            
+            # 广期所 (GFEX)
+            'SI': 'GFEX', 'LC': 'GFEX', 'PS': 'GFEX'
+        }
+        
+        self._log(f"初始化分析器 - 分析期间: {self.start_date.strftime('%Y-%m-%d')} 至 {self.end_date.strftime('%Y-%m-%d')}")
+        
+    def _log(self, message):
+        """记录日志"""
+        if self.config['analysis_control']['verbose']:
+            print(message)
+    
+    def build_future_codes(self):
+        """构建期货品种基础数据"""
+        self._log("\n=== 步骤1: 构建期货品种基础数据 ===")
+        
+        # 构建8888主连合约代码
+        for code, exchange in self.exchange_map.items():
+            future_code = f"{code}8888.{exchange}"
+            
+            # 应用过滤器
+            if future_code in self.config['future_filter']['excluded']:
+                continue
+                
+            # 如果设置了重点关注品种,只添加重点品种
+            if (self.config['future_filter']['focus_only'] and 
+                future_code not in self.config['future_filter']['focus_only']):
+                continue
+                
+            self.future_codes[code] = future_code
+            
+        self._log(f"构建完成,共{len(self.future_codes)}个期货品种")
+        if self.config['analysis_control']['verbose']:
+            self._log("示例品种:")
+            for i, (code, full_code) in enumerate(list(self.future_codes.items())[:5]):
+                self._log(f"  {code} -> {full_code}")
+        
+        return self.future_codes
+    
+    def get_future_price_data(self, future_code, start_date, end_date):
+        """获取期货价格数据"""
+        try:
+            # print(f"获取{future_code}数据: {start_date} 至 {end_date}")
+            data = get_price(
+                future_code, 
+                start_date=start_date, 
+                end_date=end_date, 
+                frequency='daily',
+                fields=['open', 'close', 'high', 'low', 'volume'],
+                skip_paused=False,
+                panel=False
+            )
+            # print(f"获取的最后一条数据: {data.iloc[-1]}")
+            return data
+        except Exception as e:
+            if self.config['analysis_control']['verbose']:
+                print(f"  获取{future_code}数据失败: {str(e)}")
+            return None
+    
+    def calculate_drawdown(self, data):
+        """计算最大回撤"""
+        if data is None or len(data) == 0:
+            return None, None, None, None
+            
+        # 计算累计最高价
+        data['cum_max'] = data['high'].expanding().max()
+        
+        # 计算回撤
+        data['drawdown'] = (data['low'] - data['cum_max']) / data['cum_max']
+        
+        # 找到最大回撤
+        max_drawdown = data['drawdown'].min()  # 最大回撤是负值,所以用min
+        max_drawdown_idx = data['drawdown'].idxmin()
+        
+        # 找到最大回撤对应的高点
+        max_high_before_dd = data.loc[:max_drawdown_idx, 'cum_max'].iloc[-1]
+        high_point_idx = data[data['high'] == max_high_before_dd].index[0]
+        
+        return abs(max_drawdown), high_point_idx, max_drawdown_idx, data
+    
+    def find_major_decline_futures(self, min_drawdown=None):
+        """筛选大幅下跌的期货品种"""
+        if min_drawdown is None:
+            min_drawdown = self.config['filter_conditions']['min_drawdown']
+            
+        self._log(f"\n=== 步骤2: 筛选跌幅>{min_drawdown*100}%的期货品种 ===")
+        
+        decline_futures = []
+        total_count = len(self.future_codes)
+        max_futures = self.config['analysis_control']['max_futures']
+        analyzed_count = 0
+        
+        for code, future_code in self.future_codes.items():
+            analyzed_count += 1
+            
+            # 最大品种数限制
+            if max_futures > 0 and analyzed_count > max_futures:
+                break
+            
+            if self.config['analysis_control']['show_progress']:
+                display_total = min(total_count, max_futures) if max_futures > 0 else total_count
+                print(f"分析 {code}({future_code}) [{analyzed_count}/{display_total}]", end=" ")
+            
+            # 获取价格数据
+            price_data = self.get_future_price_data(future_code, self.start_date, self.end_date)
+            
+            if price_data is None or len(price_data) < self.config['data_quality']['min_data_length']:
+                if self.config['analysis_control']['show_progress']:
+                    print("- 数据不足")
+                continue
+            
+            # 计算最大回撤
+            max_drawdown, high_date, low_date, data_with_dd = self.calculate_drawdown(price_data)
+            
+            if max_drawdown is None or max_drawdown < min_drawdown:
+                if self.config['analysis_control']['show_progress']:
+                    print(f"- 最大回撤{max_drawdown:.1%}不符合条件")
+                continue
+                
+            # 记录符合条件的品种
+            decline_info = {
+                '品种代码': code,
+                '合约代码': future_code,
+                '最大回撤': max_drawdown,
+                '高点日期': high_date,
+                '低点日期': low_date,
+                '高点价格': data_with_dd.loc[high_date, 'high'],
+                '低点价格': data_with_dd.loc[low_date, 'low'],
+                '下跌天数': (low_date - high_date).days
+            }
+            decline_futures.append(decline_info)
+            
+            if self.config['analysis_control']['show_progress']:
+                print(f"✓ 最大回撤{max_drawdown:.1%}, 下跌期间{high_date.strftime('%Y-%m-%d')}至{low_date.strftime('%Y-%m-%d')}")
+        
+        self._log(f"\n找到{len(decline_futures)}个符合条件的期货品种")
+        return decline_futures
+    
+    def get_simplified_contract_mapping(self, decline_futures):
+        """获取8888加权合约映射 - 简化版本,直接使用加权合约"""
+        self._log("\n=== 步骤3: 创建8888加权合约映射 ===")
+        
+        contract_mapping = {}
+        
+        for future_info in decline_futures:
+            code = future_info['品种代码']
+            high_date = future_info['高点日期'] 
+            low_date = future_info['低点日期']
+            
+            # 扩展日期范围前后30天
+            extend_days = self.config['analysis_params']['extend_days']
+            start_date = high_date - pd.Timedelta(days=extend_days)
+            end_date = low_date + pd.Timedelta(days=extend_days)
+            
+            # 构建8888加权合约代码
+            contract_8888 = self.future_codes.get(code)
+            
+            if contract_8888:
+                # 直接使用8888合约,时间范围为分析期间
+                contract_mapping[contract_8888] = [start_date, end_date]
+                
+                self._log(f"  ✓ {code}: 使用8888加权合约 {contract_8888}")
+                self._log(f"    时间范围: {start_date.strftime('%Y-%m-%d')} 至 {end_date.strftime('%Y-%m-%d')}")
+            else:
+                self._log(f"  ⚠️ {code}: 未找到对应的8888合约代码")
+        
+        self._log(f"完成8888合约映射,共{len(contract_mapping)}个合约")
+        return contract_mapping
+    
+    
+    def get_weekly_kline_data(self, contract_mapping):
+        """获取周线K线数据 - 简化版本,直接使用8888加权合约"""
+        self._log("\n=== 步骤4: 获取周线K线数据 ===")
+        
+        weekly_data = {}
+        
+        for contract_code, time_range in contract_mapping.items():
+            start_date, end_date = time_range
+            
+            self._log(f"获取{contract_code}周线数据 ({start_date.strftime('%Y-%m-%d')} 至 {end_date.strftime('%Y-%m-%d')})")
+            
+            try:
+                # 计算需要获取的周数,确保充足的数据覆盖
+                time_diff = end_date - start_date
+                estimated_weeks = int(time_diff.days / 7) + 20  # 额外增加20周缓冲
+                estimated_weeks = max(estimated_weeks, 40)  # 最少40周
+                
+                self._log(f"  计算获取{estimated_weeks}周数据")
+                
+                # 直接使用get_bars API获取原生周线数据,避免日线转换的精度损失
+                weekly_bars = get_bars(
+                    security=contract_code,
+                    count=estimated_weeks,
+                    unit='1w',   # 直接获取周线数据
+                    fields=['date', 'open', 'high', 'low', 'close', 'volume'],
+                    include_now=False,
+                    end_dt=end_date,
+                    fq_ref_date=None,
+                    df=True
+                )
+                
+                if weekly_bars is not None and len(weekly_bars) > 0:
+                    # 确保日期类型一致,避免 'Cannot compare type Timestamp with type date' 错误
+                    # get_bars返回的date列可能是date类型,需统一转换为pandas Timestamp
+                    weekly_bars['date'] = pd.to_datetime(weekly_bars['date'])
+                    start_date = pd.to_datetime(start_date)
+                    end_date = pd.to_datetime(end_date)
+                    
+                    # 筛选在指定时间范围内的数据
+                    mask = (weekly_bars['date'] >= start_date) & (weekly_bars['date'] <= end_date)
+                    filtered_weekly_data = weekly_bars[mask].copy()
+                    
+                    if len(filtered_weekly_data) > 0:
+                        # 设置日期为索引
+                        filtered_weekly_data.set_index('date', inplace=True)
+                        
+                        # 从合约代码中提取品种代码(用于后续识别)
+                        symbol_code = contract_code.replace('8888.', '').split('.')[0]
+                        
+                        weekly_data[symbol_code] = {
+                            'data': filtered_weekly_data,
+                            'contract_code': contract_code,
+                            'time_range': time_range
+                        }
+                        self._log(f"  ✓ {symbol_code}({contract_code}): 获取{len(filtered_weekly_data)}根周K线")
+                    else:
+                        self._log(f"  ⚠️ {contract_code}: 筛选后周线数据不足")
+                else:
+                    self._log(f"  ⚠️ {contract_code}: 未获取到周线数据")
+                
+            except Exception as e:
+                self._log(f"  ❌ 获取{contract_code}周线数据失败: {str(e)}")
+                continue
+        
+        self._log(f"完成周线数据获取,共{len(weekly_data)}个品种")
+        return weekly_data
+    
+    def get_weekly_data_by_contract_log(self, contract_switch_log, extend_weeks=10):
+        """
+        基于合约切换日志获取每个合约的周线数据
+        
+        参数:
+        - contract_switch_log: 合约切换日志,格式为列表,每个元素包含:
+          {'contract': '合约代码', 'start_date': '开始日期', 'end_date': '结束日期'}
+        - extend_weeks: 延长周数,默认10周
+        
+        返回:
+        - dict: 每个合约的周线数据,键为合约代码,值为数据和时间范围信息
+        """
+        self._log(f"\n=== 基于合约切换日志获取周线数据 ===")
+        self._log(f"延长周数: {extend_weeks}周")
+        
+        weekly_data_by_contract = {}
+        
+        for contract_info in contract_switch_log:
+            contract_code = contract_info.get('contract')
+            start_date = contract_info.get('start_date')
+            end_date = contract_info.get('end_date')
+            
+            if not all([contract_code, start_date, end_date]):
+                self._log(f"⚠️ 合约信息不完整,跳过: {contract_info}")
+                continue
+            
+            # 转换日期格式
+            if isinstance(start_date, str):
+                start_date = pd.to_datetime(start_date)
+            if isinstance(end_date, str):
+                end_date = pd.to_datetime(end_date)
+            
+            # 计算延长后的时间范围
+            extended_start_date = start_date - pd.Timedelta(weeks=extend_weeks)
+            extended_end_date = end_date + pd.Timedelta(weeks=extend_weeks)
+            
+            self._log(f"处理合约: {contract_code}")
+            self._log(f"  原始时间范围: {start_date.strftime('%Y-%m-%d')} 至 {end_date.strftime('%Y-%m-%d')}")
+            self._log(f"  延长时间范围: {extended_start_date.strftime('%Y-%m-%d')} 至 {extended_end_date.strftime('%Y-%m-%d')}")
+            
+            try:
+                # 计算需要获取的周数,采用更稳健的方式
+                time_diff = extended_end_date - extended_start_date
+                estimated_weeks = int(time_diff.days / 7) + 20  # 额外增加20周作为充足缓冲
+                
+                # 确保最少获取60周数据(约1.2年)以提供充分的历史背景
+                estimated_weeks = max(estimated_weeks, 60)
+                
+                self._log(f"    计算获取{estimated_weeks}周数据 (时间跨度{time_diff.days}天)")
+                
+                # 直接使用get_bars API获取原生周线数据,避免日线转换的精度损失
+                weekly_bars = get_bars(
+                    security=contract_code,
+                    count=estimated_weeks,  # 扩展的周数以确保完整覆盖
+                    unit='1w',   # 直接获取周线数据,避免转换精度损失
+                    fields=['date', 'open', 'high', 'low', 'close', 'volume'],
+                    include_now=False,  # 不包含当前未完成的周期
+                    end_dt=extended_end_date,
+                    fq_ref_date=None,  # 不复权,保持原始价格
+                    df=True  # 返回DataFrame格式便于处理
+                )
+                
+                if weekly_bars is not None and len(weekly_bars) > 0:
+                    # 确保日期类型一致,避免 'Cannot compare type Timestamp with type date' 错误
+                    # get_bars返回的date列可能是date类型,需统一转换为pandas Timestamp
+                    weekly_bars['date'] = pd.to_datetime(weekly_bars['date'])
+                    extended_start_date = pd.to_datetime(extended_start_date)
+                    extended_end_date = pd.to_datetime(extended_end_date)
+                    
+                    # 筛选在延长时间范围内的数据
+                    mask = (weekly_bars['date'] >= extended_start_date) & (weekly_bars['date'] <= extended_end_date)
+                    filtered_data = weekly_bars[mask].copy()
+                    
+                    if len(filtered_data) > 0:
+                        # 设置日期为索引
+                        filtered_data.set_index('date', inplace=True)
+                        
+                        # 存储数据
+                        weekly_data_by_contract[contract_code] = {
+                            'data': filtered_data,
+                            'original_start': start_date,
+                            'original_end': end_date,
+                            'extended_start': extended_start_date,
+                            'extended_end': extended_end_date,
+                            'total_weeks': len(filtered_data)
+                        }
+                        
+                        self._log(f"  ✓ 获取到{len(filtered_data)}根周K线数据")
+                        self._log(f"    数据时间范围: {filtered_data.index[0].strftime('%Y-%m-%d')} 至 {filtered_data.index[-1].strftime('%Y-%m-%d')}")
+                        
+                        # 添加数据质量检查
+                        original_start_ts = pd.to_datetime(start_date)
+                        original_end_ts = pd.to_datetime(end_date)
+                        original_weeks = int((original_end_ts - original_start_ts).days / 7) + 1
+                        expected_total_weeks = original_weeks + 2 * extend_weeks
+                        if len(filtered_data) < expected_total_weeks * 0.7:  # 如果数据不足预期的70%
+                            self._log(f"    ⚠️ 数据可能不完整,预期约{expected_total_weeks}周,实际{len(filtered_data)}周")
+                    else:
+                        self._log(f"  ⚠️ 筛选后无有效数据")
+                        
+                else:
+                    self._log(f"  ⚠️ 未获取到周线数据")
+                    
+            except Exception as e:
+                self._log(f"  ❌ 获取{contract_code}周线数据失败: {str(e)}")
+                continue
+            
+        self._log(f"\n成功获取{len(weekly_data_by_contract)}个合约的周线数据")
+        return weekly_data_by_contract
+    
+    def identify_target_pattern(self, weekly_data, decline_futures):
+        """
+        识别目标K线形态
+        
+        当未识别到任何目标形态时,会自动显示完整的烛台图(K线图)作为数据可视化备选方案。
+        图表包含完整的OHLC数据,直接显示不保存到本地存储。
+        """
+        self._log("\n=== 步骤5: 识别目标K线形态 ===")
+        
+        pattern_results = []
+        kline_conditions = self.config['filter_conditions']['kline_conditions']
+        
+        # 创建品种代码到下跌信息的映射
+        decline_info_map = {future['品种代码']: future for future in decline_futures}
+        
+        for code, data_info in weekly_data.items():
+            data = data_info['data']
+            contract_code = data_info['contract_code']  # 8888加权合约代码
+            
+            # 从decline_futures中获取下跌时间信息
+            if code not in decline_info_map:
+                continue
+                
+            decline_info = decline_info_map[code]
+            decline_start = decline_info['高点日期']
+            decline_end = decline_info['低点日期']
+            extend_days = self.config['analysis_params']['extend_days']
+            
+            self._log(f"分析{code}的K线形态 (使用{contract_code})")
+            
+            for idx, (date, row) in enumerate(data.iterrows()):
+                open_price = row['open']
+                close_price = row['close']
+                high_price = row['high']
+                low_price = row['low']
+                volume = row['volume']
+                
+                # 检查是否在下跌期间或附近
+                date_in_range = (date >= decline_start - pd.Timedelta(days=extend_days) and 
+                               date <= decline_end + pd.Timedelta(days=extend_days))
+                
+                if not date_in_range:
+                    continue
+                
+                # 使用统一的核心验证逻辑
+                validation_result = self._core_pattern_validation(date, row, kline_conditions)
+                
+                if validation_result['validation_passed']:
+                    pattern_info = {
+                        '品种代码': code,
+                        '日期': date,
+                        '开盘价': validation_result['ohlc_data']['open'],
+                        '收盘价': validation_result['ohlc_data']['close'],
+                        '最高价': validation_result['ohlc_data']['high'],
+                        '最低价': validation_result['ohlc_data']['low'],
+                        '成交量': validation_result['ohlc_data']['volume'],
+                        '开收差异率': validation_result['indicators']['price_diff_pct']['actual_value'],
+                        '最高涨幅率': validation_result['indicators']['high_gain_pct']['actual_value'],
+                        '下跌开始日期': decline_start,
+                        '下跌结束日期': decline_end,
+                        '数据索引': idx,
+                        '使用合约': contract_code  # 8888加权合约
+                    }
+                    pattern_results.append(pattern_info)
+                    
+                    price_diff_pct = validation_result['indicators']['price_diff_pct']['actual_value'] / 100
+                    high_gain_pct = validation_result['indicators']['high_gain_pct']['actual_value'] / 100
+                    self._log(f"  ✓ 找到形态: {date.strftime('%Y-%m-%d')} 开收差{price_diff_pct:.1%} 最高涨{high_gain_pct:.1%} 合约{contract_code}")
+        
+        self._log(f"\n共识别出{len(pattern_results)}个目标K线形态")
+        
+        # 如果未识别到任何模式,显示完整的烛台图
+        if len(pattern_results) == 0:
+            self._log("\n⚠️ 未识别到任何目标形态,显示完整数据集的K线图")
+            self._display_full_candlestick_charts(weekly_data)
+        
+        return pattern_results
+    
+    def _display_full_candlestick_charts(self, weekly_data):
+        """显示完整的烛台图(K线图)"""
+        self._log("=== 显示完整数据集的K线图 ===")
+        
+        for code, data_info in weekly_data.items():
+            data = data_info['data']
+            contract_code = data_info['contract_code']
+            
+            if data is None or len(data) == 0:
+                self._log(f"⚠️ {code}: 无可用数据")
+                continue
+            
+            self._log(f"绘制{code}({contract_code})的完整K线图,共{len(data)}根K线")
+            
+            try:
+                # 创建图表
+                fig, ax = plt.subplots(figsize=(16, 10))
+                
+                # 准备数据用于绘制烛台图
+                dates = data.index
+                opens = data['open']
+                highs = data['high']
+                lows = data['low']
+                closes = data['close']
+                volumes = data['volume']
+                
+                # 绘制K线图
+                for i in range(len(data)):
+                    date = dates[i]
+                    open_price = opens.iloc[i]
+                    high_price = highs.iloc[i]
+                    low_price = lows.iloc[i]
+                    close_price = closes.iloc[i]
+                    
+                    # K线颜色:红涨绿跌
+                    color = 'red' if close_price > open_price else 'green'
+                    edge_color = 'darkred' if close_price > open_price else 'darkgreen'
+                    
+                    # 绘制影线(最高价到最低价的竖线)
+                    ax.plot([i, i], [low_price, high_price], color='black', linewidth=1)
+                    
+                    # 绘制实体(开盘价到收盘价的矩形)
+                    body_height = abs(close_price - open_price)
+                    bottom = min(open_price, close_price)
+                    
+                    # 使用矩形绘制K线实体
+                    rect = patches.Rectangle((i-0.4, bottom), 0.8, body_height,
+                                           linewidth=1, edgecolor=edge_color, 
+                                           facecolor=color, alpha=0.8)
+                    ax.add_patch(rect)
+                
+                # 设置图表标题和标签
+                ax.set_title(f'{code} ({contract_code}) 完整周线K线图\n'
+                           f'数据期间: {dates[0].strftime("%Y-%m-%d")} 至 {dates[-1].strftime("%Y-%m-%d")} '
+                           f'(共{len(data)}根K线)', 
+                           fontsize=14, fontweight='bold', pad=20)
+                
+                ax.set_xlabel('时间', fontsize=12)
+                ax.set_ylabel('价格', fontsize=12)
+                ax.grid(True, alpha=0.3)
+                
+                # 设置x轴标签
+                # 选择合适的标签间隔,避免过于密集
+                step = max(1, len(data) // 10)  # 显示约10个时间标签
+                tick_positions = range(0, len(data), step)
+                tick_labels = [dates[i].strftime('%Y-%m-%d') for i in tick_positions]
+                
+                ax.set_xticks(tick_positions)
+                ax.set_xticklabels(tick_labels, rotation=45, ha='right')
+                
+                # 添加统计信息
+                max_price = highs.max()
+                min_price = lows.min()
+                latest_close = closes.iloc[-1]
+                total_change = (latest_close - closes.iloc[0]) / closes.iloc[0] * 100
+                
+                # 在图表上添加统计文本
+                stats_text = (f'最高价: {max_price:.2f}\n'
+                            f'最低价: {min_price:.2f}\n'
+                            f'最新收盘: {latest_close:.2f}\n'
+                            f'总涨跌幅: {total_change:+.2f}%')
+                
+                ax.text(0.02, 0.98, stats_text, transform=ax.transAxes, 
+                       verticalalignment='top', bbox=dict(boxstyle='round', 
+                       facecolor='wheat', alpha=0.8), fontsize=10)
+                
+                # 调整布局并显示
+                plt.tight_layout()
+                plt.show()
+                
+                self._log(f"✓ 已显示{code}的完整K线图")
+                
+            except Exception as e:
+                self._log(f"❌ 绘制{code}K线图时出错: {str(e)}")
+                continue
+        
+        self._log("完成所有品种的K线图显示")
+    
+    def _core_pattern_validation(self, target_date, kline_data_row, kline_conditions):
+        """
+        统一的核心模式验证逻辑
+        
+        此函数包含两种模式共同的验证逻辑,消除代码重复。
+        无论是自动模式遍历所有K线,还是验证模式检查特定日期,
+        都使用相同的验证标准和计算方法。
+        
+        参数:
+        - target_date: 目标日期 (pandas Timestamp)
+        - kline_data_row: 单根K线数据 (pandas Series,包含open、high、low、close、volume)
+        - kline_conditions: 验证条件配置字典
+        
+        返回:
+        - dict: 包含验证结果和详细诊断信息的字典
+        """
+        
+        # 提取OHLC数据
+        open_price = kline_data_row['open']
+        close_price = kline_data_row['close']
+        high_price = kline_data_row['high']
+        low_price = kline_data_row['low']
+        volume = kline_data_row['volume']
+        
+        # 计算模式指标
+        price_diff_pct = abs(close_price - open_price) / open_price
+        high_gain_pct = (high_price - open_price) / open_price
+        
+        # 验证条件A: 开盘价与收盘价差异
+        condition_a = price_diff_pct <= kline_conditions['max_open_close_diff']
+        condition_a_threshold = kline_conditions['max_open_close_diff']
+        
+        # 验证条件B: 最高价相对开盘价涨幅
+        condition_b = high_gain_pct >= kline_conditions['min_high_open_gain']
+        condition_b_threshold = kline_conditions['min_high_open_gain']
+        
+        # 整体验证结果
+        validation_passed = condition_a and condition_b
+        
+        # 构建详细的验证结果
+        result = {
+            'date': target_date,
+            'validation_passed': validation_passed,
+            'ohlc_data': {
+                'open': round(open_price, 2),
+                'high': round(high_price, 2),
+                'low': round(low_price, 2),
+                'close': round(close_price, 2),
+                'volume': volume
+            },
+            'indicators': {
+                'price_diff_pct': {
+                    'actual_value': round(price_diff_pct * 100, 2),
+                    'threshold': round(condition_a_threshold * 100, 2),
+                    'unit': '%',
+                    'condition': '≤',
+                    'passed': condition_a,
+                    'description': '开盘价与收盘价差异率'
+                },
+                'high_gain_pct': {
+                    'actual_value': round(high_gain_pct * 100, 2),
+                    'threshold': round(condition_b_threshold * 100, 2),
+                    'unit': '%',
+                    'condition': '≥',
+                    'passed': condition_b,
+                    'description': '最高价相对开盘价涨幅'
+                }
+            }
+        }
+        
+        # 生成失败原因
+        if not validation_passed:
+            failed_conditions = []
+            if not condition_a:
+                failed_conditions.append(f"开收差异率{price_diff_pct*100:.2f}% > 阈值{condition_a_threshold*100:.1f}%")
+            if not condition_b:
+                failed_conditions.append(f"最高涨幅{high_gain_pct*100:.2f}% < 阈值{condition_b_threshold*100:.1f}%")
+            
+            result['failure_reason'] = "; ".join(failed_conditions)
+        
+        return result
+    
+    def validate_manual_patterns(self, manual_config, weekly_data):
+        """
+        手动验证模式功能
+        
+        参数:
+        - manual_config: 手动配置字典,格式:
+          {'B':['2022-07-22','2024-08-23'], 'A':['2023-01-06','2023-06-09']}
+        - weekly_data: 周线数据字典
+        
+        返回:
+        - dict: 验证结果,包含成功和失败的详细信息
+        """
+        self._log("\n=== 手动模式验证 ===")
+        self._log(f"配置的模式类型数: {len(manual_config)}")
+        
+        validation_results = {
+            'successful_validations': [],
+            'failed_validations': [],
+            'summary': {}
+        }
+        
+        kline_conditions = self.config['filter_conditions']['kline_conditions']
+        
+        for pattern_type, date_list in manual_config.items():
+            self._log(f"\n处理模式类型 '{pattern_type}': {len(date_list)} 个日期")
+            
+            type_results = {
+                'pattern_type': pattern_type,
+                'total_dates': len(date_list),
+                'successful_count': 0,
+                'failed_count': 0,
+                'validations': []
+            }
+            
+            for date_str in date_list:
+                try:
+                    # 转换日期格式
+                    target_date = pd.to_datetime(date_str)
+                    self._log(f"  验证日期: {target_date.strftime('%Y-%m-%d')}")
+                    
+                    # 验证这个日期
+                    validation_result = self._validate_single_date(
+                        target_date, pattern_type, weekly_data, kline_conditions
+                    )
+                    
+                    type_results['validations'].append(validation_result)
+                    
+                    if validation_result['validation_passed']:
+                        type_results['successful_count'] += 1
+                        validation_results['successful_validations'].append(validation_result)
+                        self._log(f"    ✓ 验证通过")
+                    else:
+                        type_results['failed_count'] += 1
+                        validation_results['failed_validations'].append(validation_result)
+                        self._log(f"    ❌ 验证失败: {validation_result['failure_reason']}")
+                        
+                except Exception as e:
+                    error_result = {
+                        'pattern_type': pattern_type,
+                        'target_date': date_str,
+                        'validation_passed': False,
+                        'failure_reason': f'日期处理错误: {str(e)}',
+                        'error': True
+                    }
+                    type_results['validations'].append(error_result)
+                    type_results['failed_count'] += 1
+                    validation_results['failed_validations'].append(error_result)
+                    self._log(f"    ❌ 日期处理错误: {str(e)}")
+            
+            validation_results['summary'][pattern_type] = type_results
+        
+        # 打印总体统计
+        total_validations = sum(result['total_dates'] for result in validation_results['summary'].values())
+        total_successful = len(validation_results['successful_validations'])
+        total_failed = len(validation_results['failed_validations'])
+        
+        self._log(f"\n=== 验证结果汇总 ===")
+        self._log(f"总验证数: {total_validations}")
+        self._log(f"成功验证: {total_successful}")
+        self._log(f"失败验证: {total_failed}")
+        self._log(f"成功率: {(total_successful/total_validations*100) if total_validations > 0 else 0:.1f}%")
+        
+        # 显示失败详情
+        if validation_results['failed_validations']:
+            self._display_validation_failures(validation_results['failed_validations'])
+        
+        return validation_results
+    
+    def _validate_single_date(self, target_date, pattern_type, weekly_data, kline_conditions):
+        """验证单个日期的模式 - 使用统一的核心验证逻辑"""
+        
+        validation_result = {
+            'pattern_type': pattern_type,
+            'target_date': target_date.strftime('%Y-%m-%d'),
+            'validation_passed': False,
+            'failure_reason': '',
+            'diagnostics': {}
+        }
+        
+        # 查找包含目标日期的数据
+        found_data = None
+        
+        for code, data_info in weekly_data.items():
+            data = data_info['data']
+            contract_code = data_info['contract_code']
+            
+            # 检查目标日期是否在数据范围内
+            if target_date >= data.index[0] and target_date <= data.index[-1]:
+                # 找到最接近的周线数据
+                closest_idx = data.index.get_indexer([target_date], method='nearest')[0]
+                if closest_idx >= 0 and closest_idx < len(data):
+                    found_data = data.iloc[closest_idx]
+                    validation_result['contract_code'] = contract_code
+                    validation_result['closest_date'] = data.index[closest_idx].strftime('%Y-%m-%d')
+                    break
+        
+        if found_data is None:
+            validation_result['failure_reason'] = '未找到匹配的数据'
+            return validation_result
+        
+        # 使用统一的核心验证逻辑
+        core_result = self._core_pattern_validation(target_date, found_data, kline_conditions)
+        
+        # 整合核心结果到验证结果
+        validation_result['validation_passed'] = core_result['validation_passed']
+        validation_result['diagnostics']['ohlc_data'] = core_result['ohlc_data']
+        validation_result['diagnostics']['indicators'] = core_result['indicators']
+        
+        if 'failure_reason' in core_result:
+            validation_result['failure_reason'] = core_result['failure_reason']
+        
+        return validation_result
+    
+    def _display_validation_failures(self, failed_validations):
+        """显示详细的验证失败信息"""
+        self._log("\n=== 详细失败诊断 ===")
+        
+        for i, failure in enumerate(failed_validations, 1):
+            if failure.get('error'):
+                continue  # 跳过错误情况
+                
+            self._log(f"\n失败 #{i}: 模式类型 '{failure['pattern_type']}' - 日期 {failure['target_date']}")
+            
+            if 'contract_code' in failure:
+                self._log(f"  合约: {failure['contract_code']}")
+                self._log(f"  最近日期: {failure['closest_date']}")
+            
+            # 显示OHLC数据
+            if 'ohlc_data' in failure['diagnostics']:
+                ohlc = failure['diagnostics']['ohlc_data']
+                self._log(f"  OHLC数据:")
+                self._log(f"    开盘: {ohlc['open']:>8}")
+                self._log(f"    最高: {ohlc['high']:>8}")
+                self._log(f"    最低: {ohlc['low']:>8}")
+                self._log(f"    收盘: {ohlc['close']:>8}")
+                self._log(f"    成交量: {ohlc['volume']}")
+            
+            # 显示指标验证详情
+            if 'indicators' in failure['diagnostics']:
+                self._log(f"  指标验证:")
+                for indicator_name, indicator_data in failure['diagnostics']['indicators'].items():
+                    status = "✓" if indicator_data['passed'] else "❌"
+                    self._log(f"    {status} {indicator_data['description']}:")
+                    self._log(f"        实际值: {indicator_data['actual_value']}{indicator_data['unit']}")
+                    self._log(f"        阈值: {indicator_data['condition']} {indicator_data['threshold']}{indicator_data['unit']}")
+                    self._log(f"        结果: {'通过' if indicator_data['passed'] else '失败'}")
+            
+            self._log(f"  失败原因: {failure['failure_reason']}")
+            
+    def analyze_future_performance(self, pattern_results, weekly_data):
+        """分析后续走势 - 逐周详细分析"""
+        self._log("\n=== 步骤6: 分析后续10周逐周表现 ===")
+        
+        performance_results = []
+        follow_up_weeks = self.config['analysis_params']['follow_up_weeks']
+        
+        for pattern in pattern_results:
+            code = pattern['品种代码']
+            pattern_date = pattern['日期']
+            pattern_idx = pattern['数据索引']
+            pattern_close_price = pattern['收盘价']
+            
+            if code not in weekly_data:
+                continue
+                
+            data = weekly_data[code]['data']
+            
+            # 基础信息
+            future_performance = {
+                '品种代码': code,
+                '形态日期': pattern_date,
+                '形态收盘价': pattern_close_price
+            }
+            
+            # 详细的逐周分析
+            print(f"\n{code} ({pattern_date.strftime('%Y-%m-%d')}) 后续10周详细分析:")
+            print("-" * 80)
+            print(f"{'周次':<4} {'日期':<12} {'开盘':<8} {'收盘':<8} {'最高':<8} {'最低':<8} "
+                  f"{'周涨跌幅':<10} {'最大跌幅':<10}")
+            print("-" * 80)
+            
+            # 逐周分析
+            for weeks in range(1, follow_up_weeks + 1):
+                future_idx = pattern_idx + weeks
+                
+                if future_idx < len(data):
+                    future_row = data.iloc[future_idx]
+                    week_date = data.index[future_idx]
+                    
+                    week_open = future_row['open']
+                    week_close = future_row['close']
+                    week_high = future_row['high']
+                    week_low = future_row['low']
+                    
+                    # a) 每周的涨跌幅:收盘价相对于开盘价的涨跌幅度
+                    weekly_change_pct = (week_close - week_open) / week_open * 100
+                    
+                    # b) 每周的最大跌幅:开盘价到当周最低价的最大跌幅
+                    max_decline_pct = (week_low - week_open) / week_open * 100
+                    
+                    # 存储详细数据
+                    future_performance[f'第{weeks}周_日期'] = week_date.strftime('%Y-%m-%d')
+                    future_performance[f'第{weeks}周_开盘价'] = round(week_open, 2)
+                    future_performance[f'第{weeks}周_收盘价'] = round(week_close, 2)
+                    future_performance[f'第{weeks}周_最高价'] = round(week_high, 2)
+                    future_performance[f'第{weeks}周_最低价'] = round(week_low, 2)
+                    future_performance[f'第{weeks}周_涨跌幅'] = round(weekly_change_pct, 2)
+                    future_performance[f'第{weeks}周_最大跌幅'] = round(max_decline_pct, 2)
+                    
+                    # 打印逐周详细信息
+                    print(f"{weeks:>2}   {week_date.strftime('%Y-%m-%d'):<12} "
+                          f"{week_open:<8.2f} {week_close:<8.2f} {week_high:<8.2f} {week_low:<8.2f} "
+                          f"{weekly_change_pct:>+8.2f}% {max_decline_pct:>+8.2f}%")
+                    
+                else:
+                    # 数据不足的情况
+                    for field in ['日期', '开盘价', '收盘价', '最高价', '最低价', '涨跌幅', '最大跌幅']:
+                        future_performance[f'第{weeks}周_{field}'] = None
+                    print(f"{weeks:>2}   {'无数据':<12} {'--':<8} {'--':<8} {'--':<8} {'--':<8} "
+                          f"{'--':<10} {'--':<10}")
+            
+            print("-" * 80)
+            
+            # 计算统计指标
+            weekly_changes = [future_performance[f'第{w}周_涨跌幅'] 
+                            for w in range(1, follow_up_weeks + 1) 
+                            if future_performance[f'第{w}周_涨跌幅'] is not None]
+            
+            max_declines = [future_performance[f'第{w}周_最大跌幅'] 
+                          for w in range(1, follow_up_weeks + 1) 
+                          if future_performance[f'第{w}周_最大跌幅'] is not None]
+            
+            if weekly_changes:
+                avg_weekly_change = sum(weekly_changes) / len(weekly_changes)
+                max_weekly_gain = max(weekly_changes)
+                max_weekly_loss = min(weekly_changes)
+                
+                future_performance['统计_平均周涨跌幅'] = round(avg_weekly_change, 2)
+                future_performance['统计_最大周涨幅'] = round(max_weekly_gain, 2)
+                future_performance['统计_最大周跌幅'] = round(max_weekly_loss, 2)
+                
+                print(f"统计汇总:")
+                print(f"  平均周涨跌幅: {avg_weekly_change:+.2f}%")
+                print(f"  最大周涨幅: {max_weekly_gain:+.2f}%")
+                print(f"  最大周跌幅: {max_weekly_loss:+.2f}%")
+            
+            if max_declines:
+                avg_max_decline = sum(max_declines) / len(max_declines)
+                worst_decline = min(max_declines)
+                
+                future_performance['统计_平均最大跌幅'] = round(avg_max_decline, 2)
+                future_performance['统计_最差跌幅'] = round(worst_decline, 2)
+                
+                print(f"  平均最大跌幅: {avg_max_decline:+.2f}%")
+                print(f"  最差单周跌幅: {worst_decline:+.2f}%")
+            
+            performance_results.append(future_performance)
+            print("=" * 80)
+        
+        return performance_results
+    
+    def create_visualizations(self, pattern_results, weekly_data):
+        """创建K线图可视化"""
+        if not self.config['output_settings']['generate_charts']:
+            return
+            
+        self._log("\n=== 步骤7: 生成K线图可视化 ===")
+        
+        max_charts = min(len(pattern_results), self.config['output_settings']['max_chart_count'])
+        
+        for i, pattern in enumerate(pattern_results[:max_charts]):
+            code = pattern['品种代码']
+            pattern_date = pattern['日期']
+            pattern_idx = pattern['数据索引']
+            
+            if code not in weekly_data:
+                continue
+                
+            data = weekly_data[code]['data']
+            
+            # 准备绘图数据(形态前后各5周)
+            start_idx = max(0, pattern_idx - 5)
+            end_idx = min(len(data), pattern_idx + 11)
+            plot_data = data.iloc[start_idx:end_idx]
+            
+            # 创建K线图
+            fig, ax = plt.subplots(figsize=(12, 8))
+            
+            # 绘制K线
+            for j, (date, row) in enumerate(plot_data.iterrows()):
+                open_price = row['open']
+                close_price = row['close']
+                high_price = row['high']
+                low_price = row['low']
+                
+                # K线颜色
+                color = 'red' if close_price > open_price else 'green'
+                
+                # 绘制影线
+                ax.plot([j, j], [low_price, high_price], color='black', linewidth=1)
+                
+                # 绘制实体
+                body_height = abs(close_price - open_price)
+                if close_price > open_price:
+                    rect = patches.Rectangle((j-0.3, open_price), 0.6, body_height, 
+                                           linewidth=1, edgecolor='red', facecolor='red', alpha=0.7)
+                else:
+                    rect = patches.Rectangle((j-0.3, close_price), 0.6, body_height, 
+                                           linewidth=1, edgecolor='green', facecolor='green', alpha=0.7)
+                ax.add_patch(rect)
+                
+                # 标记目标形态
+                if date == pattern_date:
+                    ax.scatter(j, high_price + (high_price - low_price) * 0.1, 
+                             color='yellow', s=100, marker='*', zorder=5)
+                    ax.text(j, high_price + (high_price - low_price) * 0.15, 
+                           '目标形态', ha='center', fontsize=10, color='red', fontweight='bold')
+            
+            # 设置图表
+            ax.set_title(f'{code} 周线K线图 - 目标形态分析\n形态日期: {pattern_date.strftime("%Y-%m-%d")}', 
+                        fontsize=14, fontweight='bold')
+            ax.set_xlabel('时间', fontsize=12)
+            ax.set_ylabel('价格', fontsize=12)
+            ax.grid(True, alpha=0.3)
+            
+            # 设置x轴标签
+            dates = [date.strftime('%m-%d') for date in plot_data.index]
+            ax.set_xticks(range(len(dates)))
+            ax.set_xticklabels(dates, rotation=45)
+            
+            # 显示图表
+            plt.tight_layout()
+            plt.show()  # 直接在界面中显示图表
+            self._log(f"显示图表: {code} {pattern_date.strftime('%Y-%m-%d')} K线形态分析")
+    
+    def generate_summary_report(self, decline_futures, pattern_results, performance_results):
+        """生成整体规律总结报告"""
+        self._log("\n=== 步骤8: 生成统计报告 ===")
+        
+        # 基础统计
+        total_decline_futures = len(decline_futures)
+        total_patterns = len(pattern_results)
+        total_analyzed = len(performance_results)
+        
+        print(f"\n=== 期货技术形态规律分析报告 ===")
+        print(f"分析期间: {self.start_date.strftime('%Y-%m-%d')} 至 {self.end_date.strftime('%Y-%m-%d')}")
+        print(f"总分析期货品种数: {len(self.future_codes)}")
+        print(f"大幅下跌品种数: {total_decline_futures}")
+        print(f"识别出的目标形态数: {total_patterns}")
+        print(f"完成后续走势分析数: {total_analyzed}")
+        
+        if total_analyzed == 0:
+            print("无有效数据进行统计分析")
+            return {}
+        
+        # 收益率统计
+        print(f"\n=== 后续走势统计分析 ===")
+        
+        # 提取所有统计指标
+        avg_weekly_changes = [r['统计_平均周涨跌幅'] for r in performance_results if '统计_平均周涨跌幅' in r]
+        max_weekly_gains = [r['统计_最大周涨幅'] for r in performance_results if '统计_最大周涨幅' in r]
+        max_weekly_losses = [r['统计_最大周跌幅'] for r in performance_results if '统计_最大周跌幅' in r]
+        avg_max_declines = [r['统计_平均最大跌幅'] for r in performance_results if '统计_平均最大跌幅' in r]
+        
+        if avg_weekly_changes:
+            print(f"平均周涨跌幅统计:")
+            print(f"  平均值: {np.mean(avg_weekly_changes):.2f}%")
+            print(f"  中位数: {np.median(avg_weekly_changes):.2f}%") 
+            print(f"  最大值: {max(avg_weekly_changes):.2f}%")
+            print(f"  最小值: {min(avg_weekly_changes):.2f}%")
+            
+            positive_weeks = sum(1 for r in avg_weekly_changes if r > 0)
+            print(f"  正收益形态比例: {positive_weeks}/{len(avg_weekly_changes)} ({positive_weeks/len(avg_weekly_changes)*100:.1f}%)")
+            
+        if max_weekly_gains:
+            print(f"\n最大周涨幅统计:")
+            print(f"  平均值: {np.mean(max_weekly_gains):.2f}%")
+            print(f"  中位数: {np.median(max_weekly_gains):.2f}%")
+            print(f"  最大值: {max(max_weekly_gains):.2f}%")
+            print(f"  最小值: {min(max_weekly_gains):.2f}%")
+            
+        if max_weekly_losses:
+            print(f"\n最大周跌幅统计:")
+            print(f"  平均值: {np.mean(max_weekly_losses):.2f}%")
+            print(f"  中位数: {np.median(max_weekly_losses):.2f}%")
+            print(f"  最大值: {max(max_weekly_losses):.2f}%") 
+            print(f"  最小值: {min(max_weekly_losses):.2f}%")
+            
+        if avg_max_declines:
+            print(f"\n平均最大跌幅统计:")
+            print(f"  平均值: {np.mean(avg_max_declines):.2f}%")
+            print(f"  中位数: {np.median(avg_max_declines):.2f}%")
+            print(f"  最大值: {max(avg_max_declines):.2f}%")
+            print(f"  最小值: {min(avg_max_declines):.2f}%")
+        
+        # 成功率统计 - 基于平均周涨跌幅是否为正
+        positive_performance_count = sum(1 for r in avg_weekly_changes if r > 0) if avg_weekly_changes else 0
+        success_rate = positive_performance_count / len(avg_weekly_changes) * 100 if avg_weekly_changes else 0
+        
+        # 基于最大周涨幅的成功率统计
+        success_threshold = self.config['analysis_params']['success_threshold'] * 100
+        high_gain_count = sum(1 for r in max_weekly_gains if r > success_threshold) if max_weekly_gains else 0
+        high_gain_rate = high_gain_count / len(max_weekly_gains) * 100 if max_weekly_gains else 0
+        
+        print(f"\n=== 交易成功率分析 ===")
+        print(f"正收益形态数量: {positive_performance_count}/{len(avg_weekly_changes) if avg_weekly_changes else 0}")
+        print(f"正收益成功率: {success_rate:.1f}%")
+        print(f"")
+        print(f"高收益标准: 最大周涨幅 > {success_threshold}%")
+        print(f"高收益形态数量: {high_gain_count}/{len(max_weekly_gains) if max_weekly_gains else 0}")
+        print(f"高收益成功率: {high_gain_rate:.1f}%")
+        
+        return {
+            'total_futures': len(self.future_codes),
+            'decline_futures': total_decline_futures,
+            'pattern_count': total_patterns,
+            'analyzed_count': total_analyzed,
+            'avg_weekly_change': np.mean(avg_weekly_changes) if avg_weekly_changes else 0,
+            'avg_max_gain': np.mean(max_weekly_gains) if max_weekly_gains else 0,
+            'avg_max_loss': np.mean(max_weekly_losses) if max_weekly_losses else 0,
+            'avg_max_decline': np.mean(avg_max_declines) if avg_max_declines else 0,
+            'success_rate': success_rate,
+            'high_gain_rate': high_gain_rate,
+            'positive_count': positive_performance_count,
+            'high_gain_count': high_gain_count
+        }
+    
+    def save_results(self, decline_futures, pattern_results, performance_results, summary_stats):
+        """保存结果到CSV文件"""
+        self._log("\n=== 保存分析结果 ===")
+        
+        timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
+        encoding = self.config['output_settings']['csv_encoding']
+        
+        # 保存大幅下跌期货品种
+        if decline_futures:
+            decline_df = pd.DataFrame(decline_futures)
+            decline_file = f'future_major_decline_{timestamp}.csv'
+            decline_df.to_csv(decline_file, index=False, encoding=encoding)
+            self._log(f"大幅下跌期货保存至: {decline_file}")
+        
+        # 保存识别的K线形态
+        if pattern_results:
+            pattern_df = pd.DataFrame(pattern_results)
+            pattern_file = f'future_kline_patterns_{timestamp}.csv'
+            pattern_df.to_csv(pattern_file, index=False, encoding=encoding)
+            self._log(f"K线形态数据保存至: {pattern_file}")
+        
+        # 保存后续走势分析
+        if performance_results:
+            performance_df = pd.DataFrame(performance_results)
+            performance_file = f'future_pattern_performance_{timestamp}.csv'
+            performance_df.to_csv(performance_file, index=False, encoding=encoding)
+            self._log(f"后续走势分析保存至: {performance_file}")
+        
+        # 保存汇总报告
+        if summary_stats:
+            summary_df = pd.DataFrame([summary_stats])
+            summary_file = f'future_pattern_summary_{timestamp}.csv'
+            summary_df.to_csv(summary_file, index=False, encoding=encoding)
+            self._log(f"汇总报告保存至: {summary_file}")
+    
+    def run_analysis(self, mode=1, manual_config=None):
+        """
+        统一的分析运行入口
+        
+        参数:
+        - mode: 执行模式
+          1 = 原始/自动模式:自动搜索符合条件的技术形态
+          2 = 验证模式:验证指定日期是否符合模式条件
+        - manual_config: 手动配置字典,仅在mode=2时使用
+        
+        返回:
+        - dict: 分析结果
+        """
+        
+        if mode == 1:
+            return self._run_original_mode()
+        elif mode == 2:
+            if manual_config is None:
+                raise ValueError("验证模式(mode=2)需要提供manual_config参数")
+            return self._run_validation_mode(manual_config)
+        else:
+            raise ValueError("mode参数必须为1(原始模式)或2(验证模式)")
+    
+    def _run_original_mode(self):
+        """原始/自动模式:自动搜索符合条件的技术形态"""
+        print("开始期货技术形态规律分析... [原始模式]")
+        
+        # 构建期货品种基础数据
+        self.build_future_codes()
+        
+        # 筛选大幅下跌的期货品种
+        decline_futures = self.find_major_decline_futures()
+        
+        if not decline_futures:
+            print("未找到符合条件的大幅下跌期货品种")
+            return {}
+        
+        # 创建8888加权合约映射
+        contract_mapping = self.get_simplified_contract_mapping(decline_futures)
+        
+        if not contract_mapping:
+            print("未能创建8888合约映射")
+            return {}
+        else:
+            print(f"创建8888合约映射成功,共{len(contract_mapping)}个合约")
+            for contract_code, time_range in contract_mapping.items():
+                start_date, end_date = time_range
+                print(f"  {contract_code}: {start_date.strftime('%Y-%m-%d')} 至 {end_date.strftime('%Y-%m-%d')}")
+        
+        # 获取周线K线数据
+        weekly_data = self.get_weekly_kline_data(contract_mapping)
+        
+        if not weekly_data:
+            print("未能获取周线数据")
+            return {}
+        
+        # 识别目标K线形态
+        pattern_results = self.identify_target_pattern(weekly_data, decline_futures)
+        
+        if not pattern_results:
+            print("未识别出目标K线形态")
+            return {}
+        
+        # 分析后续走势
+        performance_results = self.analyze_future_performance(pattern_results, weekly_data)
+        
+        # 创建可视化
+        self.create_visualizations(pattern_results, weekly_data)
+        
+        # 生成汇总报告
+        summary_stats = self.generate_summary_report(decline_futures, pattern_results, performance_results)
+        
+        print(f"\n=== 分析完成 ===")
+        print(f"共分析了{len(decline_futures)}个大幅下跌期货品种")
+        print(f"识别出{len(pattern_results)}个目标K线形态")
+        print(f"完成{len(performance_results)}个形态的后续走势分析")
+        
+        return {
+            'mode': 1,
+            'decline_futures': decline_futures,
+            'pattern_results': pattern_results,
+            'performance_results': performance_results,
+            'summary_stats': summary_stats
+        }
+    
+    def _run_validation_mode(self, manual_config):
+        """验证模式:验证指定日期是否符合模式条件"""
+        print("开始期货技术形态验证分析... [验证模式]")
+        
+        # 构建期货品种基础数据(仅构建需要的品种)
+        self.build_future_codes()
+        
+        # 从配置中提取需要验证的品种代码
+        config_codes = list(manual_config.keys())
+        print(f"配置的验证目标: {config_codes}")
+        
+        # 为每个配置的品种创建数据获取范围
+        sample_futures = []
+        
+        for code in config_codes:
+            if code in self.future_codes:
+                contract_8888 = self.future_codes[code]
+                date_list = manual_config[code]
+                
+                # 基于配置的日期计算数据获取范围
+                config_dates = [pd.to_datetime(date_str) for date_str in date_list]
+                earliest_date = min(config_dates)
+                latest_date = max(config_dates)
+                
+                # 为了确保有足够的数据进行验证,在日期范围前后各扩展一些时间
+                buffer_days = 90  # 3个月缓冲
+                start_date = earliest_date - pd.Timedelta(days=buffer_days)
+                end_date = latest_date + pd.Timedelta(days=buffer_days)
+                
+                sample_futures.append({
+                    '品种代码': code,
+                    '合约代码': contract_8888,
+                    '高点日期': start_date,
+                    '低点日期': end_date
+                })
+                
+                print(f"  ✓ {code} ({contract_8888}): {start_date.strftime('%Y-%m-%d')} 至 {end_date.strftime('%Y-%m-%d')}")
+            else:
+                print(f"  ⚠️ 未找到品种 '{code}' 对应的合约代码")
+        
+        print(f"实际处理的品种数: {len(sample_futures)}")
+        
+        if not sample_futures:
+            print("❌ 配置中的品种代码都无法找到对应的合约")
+            return {}
+        
+        # 创建简化的合约映射
+        contract_mapping = self.get_simplified_contract_mapping(sample_futures)
+        
+        if not contract_mapping:
+            print("❌ 无法创建合约映射")
+            return {}
+        
+        # 获取周线数据
+        weekly_data = self.get_weekly_kline_data(contract_mapping)
+        
+        if not weekly_data:
+            print("❌ 无法获取周线数据")
+            return {}
+        
+        print(f"✓ 成功获取 {len(weekly_data)} 个品种的数据")
+        
+        # 执行手动验证
+        validation_results = self.validate_manual_patterns(manual_config, weekly_data)
+        validation_results['mode'] = 2
+        
+        return validation_results
+
+# =====================================================================================
+# 第三部分:主程序运行函数
+# 提供统一的程序入口和运行选项
+# =====================================================================================
+
+def run_pattern_analysis(mode=1, manual_config=None):
+    """
+    统一的期货技术形态分析入口
+    
+    参数:
+    - mode: 执行模式
+      1 = 原始/自动模式:自动搜索符合条件的技术形态
+      2 = 验证模式:验证指定日期是否符合模式条件
+    - manual_config: 手动配置字典,仅在mode=2时使用
+    
+    返回:
+    - dict: 分析结果
+    """
+    print("期货技术形态规律分析工具")
+    print("="*60)
+    
+    # 打印配置信息
+    AnalysisConfig.print_config()
+    print("="*60)
+    
+    mode_name = "原始模式" if mode == 1 else "验证模式"
+    print(f"开始分析... [{mode_name}]")
+    
+    # 创建分析器并运行
+    analyzer = FuturePatternAnalyzer(
+        AnalysisConfig.START_DATE, 
+        AnalysisConfig.END_DATE
+    )
+    
+    results = analyzer.run_analysis(mode=mode, manual_config=manual_config)
+    return results
+    
+# =====================================================================================
+# 第四部分:辅助工具函数
+# 用于处理合约切换日志和数据导出
+# =====================================================================================
+
+def parse_contract_switch_log_from_text(log_text):
+    """
+    从文本格式的合约切换日志中解析合约信息
+    
+    参数:
+    - log_text: 日志文本,格式示例:
+      "A2203.XDCE:2022年1月26日至2022年2月22日
+       A2207.XDCE:2022年2月23日至2022年6月21日"
+    
+    返回:
+    - list: 标准化的合约切换日志
+    """
+    import re
+    
+    contracts = []
+    lines = log_text.strip().split('\n')
+    
+    for line in lines:
+        line = line.strip()
+        if not line:
+            continue
+            
+        # 正则匹配合约代码和日期范围
+        pattern = r'([A-Z0-9]+\.[A-Z]+):(\d{4})年(\d{1,2})月(\d{1,2})日至(\d{4})年(\d{1,2})月(\d{1,2})日'
+        match = re.search(pattern, line)
+        
+        if match:
+            contract_code = match.group(1)
+            start_year = int(match.group(2))
+            start_month = int(match.group(3))
+            start_day = int(match.group(4))
+            end_year = int(match.group(5))
+            end_month = int(match.group(6))
+            end_day = int(match.group(7))
+            
+            contracts.append({
+                'contract': contract_code,
+                'start_date': f'{start_year}-{start_month:02d}-{start_day:02d}',
+                'end_date': f'{end_year}-{end_month:02d}-{end_day:02d}'
+            })
+    
+    return contracts
+
+def export_contract_weekly_data(weekly_data_dict, filename_prefix='contract_weekly_data'):
+    """
+    导出合约周线数据到CSV文件
+    
+    参数:
+    - weekly_data_dict: get_weekly_data_by_contract_log返回的数据字典
+    - filename_prefix: 文件名前缀
+    
+    注意:导出的数据是直接从get_bars API获取的原生周线数据,
+          相比日线转换的数据具有更高的精度和准确性
+    """
+    import datetime
+    
+    timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
+    
+    for contract_code, contract_data in weekly_data_dict.items():
+        # 清理文件名中的特殊字符
+        safe_contract_name = contract_code.replace('.', '_')
+        filename = f'{filename_prefix}_{safe_contract_name}_{timestamp}.csv'
+        
+        # 准备数据
+        df = contract_data['data'].copy()
+        df['contract'] = contract_code
+        df['original_start'] = contract_data['original_start'].strftime('%Y-%m-%d')
+        df['original_end'] = contract_data['original_end'].strftime('%Y-%m-%d')
+        
+        # 保存到CSV
+        df.to_csv(filename, encoding='utf-8-sig')
+        print(f"合约 {contract_code} 的周线数据已保存至: {filename}")
+
+def run_manual_pattern_validation(manual_config, analyzer=None):
+    """
+    运行手动模式验证的便捷函数
+    
+    此函数只会处理manual_config中明确指定的品种和时间范围,
+    不会查询额外的品种数据。现在使用统一的分析框架。
+    
+    参数:
+    - manual_config: 手动配置字典,格式:
+      {'A':['2023-01-06','2023-06-09'], 'B':['2022-07-22','2024-08-23']}
+      键: 期货品种代码(如'A'、'B'、'RB'等)
+      值: 需要验证的日期列表('YYYY-MM-DD'格式)
+    - analyzer: 可选的分析器实例,如果为None则创建新实例
+    
+    返回:
+    - dict: 验证结果,包含成功和失败的详细信息
+    
+    注意:
+    - 系统只会获取配置中指定品种的数据
+    - 数据获取范围基于每个品种配置的日期范围自动计算
+    - 不会处理未在配置中明确指定的品种
+    - 现在使用统一的核心验证逻辑,确保与原始模式的一致性
+    """
+    print("\n=== 手动模式验证工具 ===")
+    print("此功能用于验证指定日期是否符合预定义的技术形态模式")
+    print("使用统一的验证逻辑,与原始模式保持完全一致")
+    
+    # 创建分析器实例(如果没有提供)
+    if analyzer is None:
+        analyzer = FuturePatternAnalyzer()
+    
+    # 使用统一的分析入口 - 验证模式
+    validation_results = analyzer.run_analysis(mode=2, manual_config=manual_config)
+    
+    return validation_results
+
+def demo_manual_validation():
+    """演示手动模式验证功能"""
+    print("\n=== 手动模式验证演示 ===")
+    
+    # 示例配置
+    demo_config = {
+        'A': ['2023-01-06', '2023-06-09', '2024-07-26'],
+        'B': ['2022-07-22', '2024-08-23', '2025-01-03']
+    }
+    
+    print("使用演示配置:")
+    for pattern_type, dates in demo_config.items():
+        print(f"  模式 '{pattern_type}': {len(dates)} 个日期")
+        for date in dates:
+            print(f"    - {date}")
+    
+    try:
+        # 运行验证
+        results = run_manual_pattern_validation(demo_config)
+        
+        if results:
+            print("\n✅ 验证完成")
+            return results
+        else:
+            print("\n⚠️ 验证未能完成")
+            return {}
+            
+    except Exception as e:
+        print(f"\n❌ 验证过程中出错: {str(e)}")
+        import traceback
+        traceback.print_exc()
+        return {}
+
+def get_contract_weekly_data(contract_switch_log, extend_weeks=10, export_data=False):
+    """
+    基于合约切换日志获取周线数据的正式方法
+    
+    参数:
+    - contract_switch_log: 合约切换日志,可以是文本格式或列表格式
+    - extend_weeks: 延长周数,默认10周
+    - export_data: 是否导出数据到CSV文件,默认False
+    
+    返回:
+    - dict: 每个合约的周线数据
+    """
+    print("\n=== 基于合约切换日志获取周线数据 ===")
+    
+    # 如果是文本格式,先解析
+    if isinstance(contract_switch_log, str):
+        print("解析合约切换日志:")
+        contracts = parse_contract_switch_log_from_text(contract_switch_log)
+        for contract in contracts:
+            print(f"   {contract['contract']}: {contract['start_date']} 至 {contract['end_date']}")
+    else:
+        contracts = contract_switch_log
+        print(f"处理{len(contracts)}个合约的切换日志")
+    
+    print(f"\n创建分析器并获取周线数据 (延长{extend_weeks}周):")
+    analyzer = FuturePatternAnalyzer()
+    
+    try:
+        # 获取周线数据
+        weekly_data = analyzer.get_weekly_data_by_contract_log(contracts, extend_weeks=extend_weeks)
+        
+        if weekly_data:
+            print("   ✓ 获取成功!")
+            for contract_code, data_info in weekly_data.items():
+                print(f"   {contract_code}: {data_info['total_weeks']}周数据")
+                print(f"     原始范围: {data_info['original_start'].strftime('%Y-%m-%d')} 至 {data_info['original_end'].strftime('%Y-%m-%d')}")
+                print(f"     延长范围: {data_info['extended_start'].strftime('%Y-%m-%d')} 至 {data_info['extended_end'].strftime('%Y-%m-%d')}")
+            
+            # 导出数据
+            if export_data:
+                export_contract_weekly_data(weekly_data)
+                
+            return weekly_data
+        else:
+            print("   ⚠️ 未获取到数据")
+            return {}
+            
+    except Exception as e:
+        print(f"   ❌ 获取失败: {str(e)}")
+        import traceback
+        traceback.print_exc()
+        return {}
+
+# =====================================================================================
+# 第五部分:程序主入口 - 自动运行分析
+# =====================================================================================
+
+print("期货市场技术形态规律分析工具")
+print("本工具专门研究期货品种在大幅下跌后出现特定K线形态的规律")  
+print("适用于聚宽在线研究平台")
+print("="*80)
+
+# =====================================================================================
+# 基于合约切换日志的周线数据获取功能使用说明
+# =====================================================================================
+# 
+# 使用示例1:处理文本格式的合约切换日志
+# sample_log = """
+# A2203.XDCE:2022年1月26日至2022年2月22日
+# A2207.XDCE:2022年2月23日至2022年6月21日
+# A2209.XDCE:2022年6月22日至2022年8月30日
+# """
+# weekly_data = get_contract_weekly_data(sample_log, extend_weeks=10, export_data=True)
+#
+# 使用示例2:处理列表格式的合约切换日志
+# contract_list = [
+#     {'contract': 'A2203.XDCE', 'start_date': '2022-01-26', 'end_date': '2022-02-22'},
+#     {'contract': 'A2207.XDCE', 'start_date': '2022-02-23', 'end_date': '2022-06-21'}
+# ]
+# weekly_data = get_contract_weekly_data(contract_list, extend_weeks=15, export_data=False)
+#
+# 新增功能1:模式识别失败时的完整K线图显示
+# 当identify_target_pattern函数未识别到任何目标形态时,系统会自动:
+# 1. 显示所有可用品种的完整烛台图
+# 2. 包含OHLC数据的专业K线图表现
+# 3. 添加统计信息(最高价、最低价、总涨跌幅等)
+# 4. 直接在界面中显示,不保存到本地文件
+#
+# 新增功能2:手动模式验证系统
+# 使用示例3:手动验证指定日期的模式匹配
+# 现在使用统一的入口函数,通过mode参数控制执行模式
+manual_config = {
+    'A': ['2023-01-06', '2023-06-09', '2024-07-26'],
+    'B': ['2022-07-22', '2024-08-23', '2025-01-03']
+}
+
+# 方法1: 使用统一入口函数
+validation_results = run_pattern_analysis(mode=2, manual_config=manual_config)
+
+# 方法2: 或者使用便捷函数(内部调用统一入口)
+# validation_results = run_manual_pattern_validation(manual_config)
+# 或运行演示: demo_manual_validation()
+#
+# 重构后的统一模式控制系统:
+# 1. mode=1(原始模式):自动搜索符合条件的技术形态,遍历所有期货品种
+# 2. mode=2(验证模式):验证指定日期是否符合模式条件,只处理配置品种
+# 3. 统一的核心验证逻辑:_core_pattern_validation()消除了代码重复
+# 4. 独立的执行路径:两种模式不再共享步骤1,完全独立执行
+# 5. 一致的验证标准:两种模式使用完全相同的模式识别条件和计算方法
+#
+# 数据获取特点:
+# 1. 直接使用get_bars API获取原生周线数据,避免日线转换的精度损失
+# 2. 自动延长时间范围(每个合约前后各延长指定周数)
+# 3. 确保获取充足的历史数据(最少60周作为背景)
+# 4. 支持数据导出到CSV文件便于后续分析
+# 5. 简化结构:直接使用8888加权合约,无需处理主力合约切换
+# 
+# 简化后的工作流程:
+# 1. 识别符合条件的大幅下跌期货品种
+# 2. 为每个品种创建8888合约映射(单级字典结构)
+# 3. 直接从8888合约获取周线数据,避免主力合约复杂性
+# 4. 进行技术形态识别和后续走势分析
+# 5. 当未识别到目标形态时,自动显示完整的烛台图(K线图)
+# =====================================================================================
+
+# 直接运行分析
+# print("\n开始运行完整分析...")
+# try:
+#     results = run_pattern_analysis()
+# except Exception as e:
+#     print(f"运行过程中出现错误: {str(e)}")
+#     import traceback
+#     traceback.print_exc()
+#     results = None
+
+# if results:
+#     print("\n✅ 分析完成!")
+#     if isinstance(results, dict) and 'summary_stats' in results:
+#         summary = results['summary_stats']
+#         print(f"\n📊 快速统计:")
+#         print(f"   - 识别到技术形态: {summary.get('pattern_count', 0)}个")
+#         print(f"   - 正收益成功率: {summary.get('success_rate', 0):.1f}%")
+#         print(f"   - 高收益成功率: {summary.get('high_gain_rate', 0):.1f}%")
+#         print(f"   - 平均周涨跌幅: {summary.get('avg_weekly_change', 0):+.2f}%")
+# else:
+#     print("\n❌ 分析未能完成,请检查错误信息。")

+ 226 - 0
Lib/research/get_dominant_future.py

@@ -0,0 +1,226 @@
+# 主力合约跟踪工具
+# 用于获取指定时间范围内某个或某些标的的主力合约变化信息
+
+from jqdata import *
+import pandas as pd
+from datetime import datetime, timedelta
+import warnings
+warnings.filterwarnings('ignore')
+
+def get_dominant_contracts_history(symbol_list, time_range):
+    """
+    获取指定时间范围内某个或某些标的的主力合约历史变化
+    
+    参数:
+        symbol_list (list): 标的编码列表,如 ['FU', 'BU']
+        time_range (list): 时间范围,格式为 ['20240101', '20250101']
+    
+    返回:
+        pandas.DataFrame: 包含以下列的数据框
+            - symbol: 原始标的编码
+            - dominant_contract: 主力合约完整编码
+            - start_date: 主力合约开始日期
+            - end_date: 主力合约结束日期
+    """
+    
+    # 验证输入参数
+    if not isinstance(symbol_list, list) or len(symbol_list) == 0:
+        raise ValueError("symbol_list必须是非空列表")
+    
+    if not isinstance(time_range, list) or len(time_range) != 2:
+        raise ValueError("time_range必须是包含两个日期的列表")
+    
+    # 转换日期格式
+    try:
+        start_date = datetime.strptime(time_range[0], '%Y%m%d').date()
+        end_date = datetime.strptime(time_range[1], '%Y%m%d').date()
+    except ValueError:
+        raise ValueError("日期格式错误,请使用YYYYMMDD格式")
+    
+    if start_date >= end_date:
+        raise ValueError("开始日期必须早于结束日期")
+    
+    print(f"开始获取主力合约数据...")
+    print(f"标的列表: {symbol_list}")
+    print(f"时间范围: {start_date} 至 {end_date}")
+    
+    # 存储所有结果的列表
+    all_results = []
+    
+    # 逐个处理每个标的
+    for symbol in symbol_list:
+        print(f"\n处理标的: {symbol}")
+        
+        try:
+            # 获取该标的在指定时间范围内的主力合约历史
+            symbol_results = _get_symbol_dominant_history(symbol, start_date, end_date)
+            all_results.extend(symbol_results)
+            
+        except Exception as e:
+            print(f"处理标的 {symbol} 时发生错误: {str(e)}")
+            continue
+    
+    # 将结果转换为DataFrame
+    if all_results:
+        result_df = pd.DataFrame(all_results)
+        result_df = result_df.sort_values(['symbol', 'start_date']).reset_index(drop=True)
+        
+        print(f"\n成功获取 {len(result_df)} 条主力合约记录")
+        return result_df
+    else:
+        print("\n未获取到任何主力合约数据")
+        return pd.DataFrame(columns=['symbol', 'dominant_contract', 'start_date', 'end_date'])
+
+
+def _get_symbol_dominant_history(symbol, start_date, end_date):
+    """
+    获取单个标的的主力合约历史变化
+    
+    参数:
+        symbol (str): 标的编码
+        start_date (date): 开始日期
+        end_date (date): 结束日期
+    
+    返回:
+        list: 主力合约记录列表
+    """
+    
+    results = []
+    current_date = start_date
+    current_dominant = None
+    period_start = None
+    
+    # 按日遍历时间范围
+    while current_date <= end_date:
+        
+        # 跳过非交易日
+        if not _is_trading_day(current_date):
+            current_date += timedelta(days=1)
+            continue
+        
+        try:
+            # 获取当日主力合约
+            dominant_contract = get_dominant_future(symbol, current_date)
+            
+            if dominant_contract is None:
+                current_date += timedelta(days=1)
+                continue
+                
+            # 如果是新的主力合约
+            if dominant_contract != current_dominant:
+                
+                # 如果不是第一个合约,结束上一个合约的记录
+                if current_dominant is not None and period_start is not None:
+                    results.append({
+                        'symbol': symbol,
+                        'dominant_contract': current_dominant,
+                        'start_date': period_start,
+                        'end_date': current_date - timedelta(days=1)
+                    })
+                
+                # 开始新合约的记录
+                current_dominant = dominant_contract
+                period_start = current_date
+                
+                print(f"  {current_date}: 主力合约变更为 {dominant_contract}")
+            
+        except Exception as e:
+            print(f"  获取 {current_date} 的主力合约时出错: {str(e)}")
+        
+        current_date += timedelta(days=1)
+    
+    # 处理最后一个合约
+    if current_dominant is not None and period_start is not None:
+        results.append({
+            'symbol': symbol,
+            'dominant_contract': current_dominant,
+            'start_date': period_start,
+            'end_date': end_date
+        })
+    
+    return results
+
+
+def _is_trading_day(check_date):
+    """
+    检查是否为交易日
+    
+    参数:
+        check_date (date): 要检查的日期
+    
+    返回:
+        bool: 是否为交易日
+    """
+    try:
+        # 使用聚宽API检查交易日
+        trade_days = get_trade_days(end_date=check_date, count=1)
+        if len(trade_days) > 0:
+            return trade_days[0].date() == check_date
+        return False
+    except:
+        # 如果API调用失败,简单判断是否为工作日
+        return check_date.weekday() < 5
+
+
+def get_dominant_contracts_summary(symbol_list, time_range):
+    """
+    获取主力合约变化的汇总信息
+    
+    参数:
+        symbol_list (list): 标的编码列表
+        time_range (list): 时间范围
+    
+    返回:
+        dict: 汇总信息
+    """
+    
+    df = get_dominant_contracts_history(symbol_list, time_range)
+    
+    if df.empty:
+        return {}
+    
+    summary = {}
+    
+    for symbol in symbol_list:
+        symbol_data = df[df['symbol'] == symbol]
+        
+        if not symbol_data.empty:
+            summary[symbol] = {
+                '主力合约数量': len(symbol_data),
+                '合约列表': symbol_data['dominant_contract'].tolist(),
+                '平均持续天数': (symbol_data['end_date'] - symbol_data['start_date']).dt.days.mean()
+            }
+        else:
+            summary[symbol] = {
+                '主力合约数量': 0,
+                '合约列表': [],
+                '平均持续天数': 0
+            }
+    
+    return summary
+
+
+def export_to_csv(symbol_list, time_range, filename=None):
+    """
+    导出主力合约数据到CSV文件
+    
+    参数:
+        symbol_list (list): 标的编码列表
+        time_range (list): 时间范围
+        filename (str): 导出文件名,如果为None则自动生成
+    
+    返回:
+        str: 导出的文件路径
+    """
+    
+    df = get_dominant_contracts_history(symbol_list, time_range)
+    
+    if filename is None:
+        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
+        symbols_str = '_'.join(symbol_list)
+        filename = f"dominant_contracts_{symbols_str}_{time_range[0]}_{time_range[1]}_{timestamp}.csv"
+    
+    df.to_csv(filename, index=False, encoding='utf-8-sig')
+    print(f"数据已导出到: {filename}")
+    
+    return filename

+ 2 - 0
pyproject.toml

@@ -5,6 +5,8 @@ description = "Add your description here"
 readme = "README.md"
 requires-python = ">=3.11"
 dependencies = [
+    "beautifulsoup4>=4.14.2",
     "matplotlib>=3.10.3",
     "pandas>=2.3.1",
+    "requests>=2.32.5",
 ]

+ 580 - 0
tools/margin_crawler.py

@@ -0,0 +1,580 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+期货保证金爬取和更新工具
+
+功能:
+1. 从指定网站爬取期货保证金数据
+2. 更新策略代码文件中的保证金配置
+3. 支持数据备份和文件备份
+"""
+
+import os
+import re
+import shutil
+from abc import ABC, abstractmethod
+from datetime import datetime
+from typing import Dict, List, Tuple
+
+import pandas as pd
+import requests
+from bs4 import BeautifulSoup
+
+
+# ============================================================================
+# 抽象基类
+# ============================================================================
+
+class WebCrawler(ABC):
+    """网页爬虫基类"""
+    
+    @abstractmethod
+    def crawl(self) -> pd.DataFrame:
+        """
+        爬取数据
+        
+        Returns:
+            pd.DataFrame: 包含['合约代码', '投机%']的DataFrame
+        """
+        pass
+
+
+class CodeUpdater(ABC):
+    """代码文件更新基类"""
+    
+    @abstractmethod
+    def read_config(self, file_path: str) -> Dict:
+        """
+        从代码文件读取现有配置
+        
+        Args:
+            file_path: 代码文件路径
+            
+        Returns:
+            dict: 配置字典
+        """
+        pass
+    
+    @abstractmethod
+    def update_config(self, file_path: str, margin_data: pd.DataFrame) -> List[str]:
+        """
+        更新保证金配置
+        
+        Args:
+            file_path: 代码文件路径
+            margin_data: 保证金数据DataFrame
+            
+        Returns:
+            list: 变更记录列表
+        """
+        pass
+    
+    @abstractmethod
+    def add_new_contracts(self, file_path: str, new_contracts: List[str], 
+                         margin_data: pd.DataFrame) -> List[str]:
+        """
+        新增合约配置(抽象方法,不同文件格式实现不同)
+        
+        Args:
+            file_path: 代码文件路径
+            new_contracts: 新合约代码列表
+            margin_data: 保证金数据DataFrame
+            
+        Returns:
+            list: 新增记录列表
+        """
+        pass
+    
+    def backup_file(self, file_path: str) -> str:
+        """
+        创建文件备份
+        
+        Args:
+            file_path: 要备份的文件路径
+            
+        Returns:
+            str: 备份文件路径
+        """
+        backup_path = f"{file_path}.bak"
+        shutil.copy2(file_path, backup_path)
+        print(f"[备份] 已创建文件备份: {backup_path}")
+        return backup_path
+
+
+# ============================================================================
+# 具体实现类 - 爬虫
+# ============================================================================
+
+class HuaAnFuturesCrawler(WebCrawler):
+    """华安期货网站爬虫"""
+    
+    def __init__(self, base_url: str):
+        """
+        初始化
+        
+        Args:
+            base_url: 华安期货保证金列表页URL
+        """
+        self.base_url = base_url
+        self.session = requests.Session()
+        self.session.headers.update({
+            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
+        })
+    
+    def crawl(self) -> pd.DataFrame:
+        """
+        爬取华安期货保证金数据
+        
+        Returns:
+            pd.DataFrame: 包含['合约代码', '投机%']的DataFrame
+        """
+        print(f"[爬取] 开始访问华安期货网站: {self.base_url}")
+        
+        # 1. 访问列表页
+        response = self.session.get(self.base_url, timeout=30)
+        response.encoding = 'utf-8'
+        soup = BeautifulSoup(response.text, 'html.parser')
+        
+        # 2. 找到"保证金标准"链接
+        margin_link = None
+        for link in soup.find_all('a'):
+            link_text = link.text.strip() if link.text else ''
+            if '保证金' in link_text:
+                margin_link = link.get('href')
+                if '标准' in link_text or '比例' in link_text:
+                    break
+        
+        if not margin_link:
+            raise ValueError("未找到保证金标准链接")
+        
+        # 处理相对路径
+        if not margin_link.startswith('http'):
+            from urllib.parse import urljoin
+            margin_link = urljoin(self.base_url, margin_link)
+        
+        print(f"[爬取] 找到保证金标准链接: {margin_link}")
+        
+        # 3. 访问保证金详情页
+        response = self.session.get(margin_link, timeout=30)
+        response.encoding = 'utf-8'
+        soup = BeautifulSoup(response.text, 'html.parser')
+        
+        # 4. 解析第一个tbody
+        tbody = soup.find('tbody')
+        if not tbody:
+            raise ValueError("未找到数据表格")
+        
+        print(f"[爬取] 开始解析保证金数据表格")
+        
+        # 5. 解析表格数据
+        data = []
+        rows = tbody.find_all('tr')
+        
+        for row in rows:
+            cols = row.find_all('td')
+            if len(cols) < 7:  # 至少需要7列(交易所、品种、合约代码、客户投机、客户套保、交易所投机、交易所套保)
+                continue
+            
+            # 提取合约代码(第3列,索引为2)
+            contract_code = cols[2].text.strip()
+            
+            # 只要纯字母的合约代码,且最多2个字母
+            if not contract_code.isalpha() or len(contract_code) > 2:
+                continue
+            
+            # 统一转换为大写(代码文件中都是大写)
+            contract_code = contract_code.upper()
+            
+            # 提取客户比例下的投机%(第4列,索引为3)
+            speculation_text = cols[3].text.strip()
+            
+            # 解析百分比数字
+            try:
+                speculation_rate = float(speculation_text.replace('%', ''))
+            except ValueError:
+                print(f"[警告] 无法解析合约 {contract_code} 的投机比例: {speculation_text}")
+                continue
+            
+            data.append({
+                '合约代码': contract_code,
+                '投机%': speculation_rate
+            })
+        
+        df = pd.DataFrame(data)
+        print(f"[爬取] 成功爬取 {len(df)} 个合约的保证金数据")
+        
+        return df
+
+
+# ============================================================================
+# 具体实现类 - 代码更新器
+# ============================================================================
+
+class FuturesConfigUpdater(CodeUpdater):
+    """期货配置更新器 - 针对g.futures_config字典"""
+    
+    def read_config(self, file_path: str) -> Dict:
+        """
+        从代码文件读取g.futures_config配置
+        
+        Args:
+            file_path: 代码文件路径
+            
+        Returns:
+            dict: {合约代码: {'long': 值, 'short': 值}}
+        """
+        with open(file_path, 'r', encoding='utf-8') as f:
+            content = f.read()
+        
+        # 匹配g.futures_config字典
+        pattern = r'g\.futures_config\s*=\s*\{(.*?)\n    \}'
+        match = re.search(pattern, content, re.DOTALL)
+        
+        if not match:
+            raise ValueError(f"未找到g.futures_config配置")
+        
+        config_block = match.group(1)
+        
+        # 解析每个合约的保证金配置
+        configs = {}
+        # 匹配每个合约配置行,例如:'AU': {'has_night_session': True, 'margin_rate': {'long': 0.14, 'short': 0.14}, ...}
+        contract_pattern = r"'([A-Z]+)':\s*\{[^}]*'margin_rate':\s*\{'long':\s*([\d.]+),\s*'short':\s*([\d.]+)\}"
+        
+        for match in re.finditer(contract_pattern, config_block):
+            contract_code = match.group(1)
+            long_rate = float(match.group(2))
+            short_rate = float(match.group(3))
+            configs[contract_code] = {'long': long_rate, 'short': short_rate}
+        
+        print(f"[读取] 从 {file_path} 读取到 {len(configs)} 个合约配置")
+        return configs
+    
+    def update_config(self, file_path: str, margin_data: pd.DataFrame) -> List[str]:
+        """
+        更新保证金配置
+        
+        Args:
+            file_path: 代码文件路径
+            margin_data: 保证金数据DataFrame
+            
+        Returns:
+            list: 变更记录列表
+        """
+        with open(file_path, 'r', encoding='utf-8') as f:
+            content = f.read()
+        
+        changes = []
+        
+        for _, row in margin_data.iterrows():
+            contract_code = row['合约代码']
+            new_rate = row['投机%'] / 100
+            
+            # 匹配该合约的整个配置行(包含合约代码,确保替换正确的那一行)
+            pattern = f"('{contract_code}':[^}}]*'margin_rate':\\s*\\{{'long':\\s*)([\\d.]+)(,\\s*'short':\\s*)([\\d.]+)(\\}})"
+            match = re.search(pattern, content)
+            
+            if not match:
+                continue
+            
+            old_long = float(match.group(2))
+            old_short = float(match.group(4))
+            
+            # 检查是否需要更新
+            if abs(old_long - new_rate) < 0.0001 and abs(old_short - new_rate) < 0.0001:
+                changes.append(f"  {contract_code}: {round(new_rate, 3)} (不变)")
+            else:
+                # 替换保证金值(使用整个匹配模式进行精确替换)
+                old_full_str = match.group(0)
+                # 保留3位小数
+                new_rate_str = f"{round(new_rate, 3):.3f}".rstrip('0').rstrip('.')
+                new_full_str = f"{match.group(1)}{new_rate_str}{match.group(3)}{new_rate_str}{match.group(5)}"
+                content = content.replace(old_full_str, new_full_str, 1)
+                changes.append(f"  {contract_code}: {round(old_long, 3)} -> {round(new_rate, 3)}")
+        
+        # 写回文件
+        with open(file_path, 'w', encoding='utf-8') as f:
+            f.write(content)
+        
+        return changes
+    
+    def add_new_contracts(self, file_path: str, new_contracts: List[str], 
+                         margin_data: pd.DataFrame) -> List[str]:
+        """
+        新增合约配置到g.futures_config
+        
+        Args:
+            file_path: 代码文件路径
+            new_contracts: 新合约代码列表
+            margin_data: 保证金数据DataFrame
+            
+        Returns:
+            list: 新增记录列表
+        """
+        with open(file_path, 'r', encoding='utf-8') as f:
+            content = f.read()
+        
+        additions = []
+        
+        # 找到g.futures_config字典的结束位置
+        pattern = r'(g\.futures_config\s*=\s*\{.*?)(\n    \})'
+        match = re.search(pattern, content, re.DOTALL)
+        
+        if not match:
+            raise ValueError("未找到g.futures_config配置块")
+        
+        config_block = match.group(1)
+        config_end = match.group(2)
+        
+        # 检查最后一行是否已经有逗号
+        config_lines = config_block.rstrip().split('\n')
+        last_line = config_lines[-1] if config_lines else ''
+        needs_comma = last_line.strip() and not last_line.rstrip().endswith(',')
+        
+        # 如果最后一行需要逗号,添加逗号
+        if needs_comma:
+            config_block = config_block.rstrip() + ','
+        
+        # 准备新增的配置行
+        new_lines = []
+        for contract_code in new_contracts:
+            # 从margin_data中获取保证金率
+            rate_row = margin_data[margin_data['合约代码'] == contract_code]
+            if rate_row.empty:
+                continue
+            
+            rate = rate_row.iloc[0]['投机%'] / 100
+            # 保留3位小数,去掉末尾的0
+            rate_str = f"{round(rate, 3):.3f}".rstrip('0').rstrip('.')
+            
+            # 生成新配置行(使用默认模板)
+            new_config = f"        '{contract_code}': {{'has_night_session': True, 'margin_rate': {{'long': {rate_str}, 'short': {rate_str}}}, 'multiplier': 1, 'trading_start_time': '21:00'}},"
+            new_lines.append(new_config)
+            additions.append(f"  新增 {contract_code}: 保证金率={round(rate, 3)}, multiplier=1 (需手动调整)")
+        
+        # 插入新配置(在字典结束前)
+        if new_lines:
+            new_content = config_block + '\n' + '\n'.join(new_lines) + config_end
+            content = content.replace(match.group(0), new_content)
+            
+            # 写回文件
+            with open(file_path, 'w', encoding='utf-8') as f:
+                f.write(content)
+        
+        return additions
+
+
+# ============================================================================
+# 管理类
+# ============================================================================
+
+class MarginCrawlerManager:
+    """保证金爬取管理器"""
+    
+    def __init__(self, workspace_root: str = '/Users/maxfeng/Documents/GitHub/jukuan'):
+        """
+        初始化管理器
+        
+        Args:
+            workspace_root: 工作区根目录
+        """
+        self.workspace_root = workspace_root
+        self.backup_dir = os.path.join(workspace_root, 'data/future_margin')
+        
+        # 确保备份目录存在
+        os.makedirs(self.backup_dir, exist_ok=True)
+        
+        # 爬虫配置
+        self.crawler_configs = {
+            'hua_future': {
+                'name': '华安期货',
+                'url': 'https://www.haqh.com/index.php?m=content&c=index&a=lists&catid=167',
+                'crawler_class': HuaAnFuturesCrawler
+            }
+        }
+        
+        # 更新器配置
+        self.updater_configs = {
+            'MAPatternStrategy_v001': {
+                'name': 'MA形态策略v001',
+                'file': os.path.join(workspace_root, 'Lib/future/MAPatternStrategy_v001.py'),
+                'updater_class': FuturesConfigUpdater
+            },
+            'MAPatternStrategy_v002': {
+                'name': 'MA形态策略v002',
+                'file': os.path.join(workspace_root, 'Lib/future/MAPatternStrategy_v002.py'),
+                'updater_class': FuturesConfigUpdater
+            }
+        }
+    
+    def run(self, crawler_key: str, updater_key: str):
+        """
+        执行完整的爬取和更新流程
+        
+        Args:
+            crawler_key: 爬虫配置的key
+            updater_key: 更新器配置的key
+        """
+        print("=" * 80)
+        print("期货保证金爬取和更新工具")
+        print("=" * 80)
+        
+        # 1. 获取配置
+        if crawler_key not in self.crawler_configs:
+            raise ValueError(f"未找到爬虫配置: {crawler_key}")
+        if updater_key not in self.updater_configs:
+            raise ValueError(f"未找到更新器配置: {updater_key}")
+        
+        crawler_config = self.crawler_configs[crawler_key]
+        updater_config = self.updater_configs[updater_key]
+        
+        print(f"\n[配置] 数据源: {crawler_config['name']}")
+        print(f"[配置] 目标文件: {updater_config['name']}")
+        
+        # 2. 爬取数据
+        print(f"\n{'=' * 80}")
+        print("步骤1: 爬取保证金数据")
+        print(f"{'=' * 80}")
+        crawler = crawler_config['crawler_class'](crawler_config['url'])
+        margin_data = crawler.crawl()
+        
+        # 验证测试数据
+        self._validate_test_data(margin_data)
+        
+        # 3. 读取现有配置
+        print(f"\n{'=' * 80}")
+        print("步骤2: 读取现有配置")
+        print(f"{'=' * 80}")
+        updater = updater_config['updater_class']()
+        file_path = updater_config['file']
+        existing_config = updater.read_config(file_path)
+        
+        # 4. 比对合约代码
+        print(f"\n{'=' * 80}")
+        print("步骤3: 比对合约代码")
+        print(f"{'=' * 80}")
+        existing_contracts = set(existing_config.keys())
+        crawled_contracts = set(margin_data['合约代码'].tolist())
+        
+        missing_in_crawl = existing_contracts - crawled_contracts
+        new_in_crawl = crawled_contracts - existing_contracts
+        
+        if missing_in_crawl:
+            print(f"[警告] 以下合约在代码文件中存在,但爬取结果中没有:")
+            for contract in sorted(missing_in_crawl):
+                print(f"  - {contract}")
+        
+        if new_in_crawl:
+            print(f"[发现] 以下合约在爬取结果中存在,但代码文件中没有:")
+            for contract in sorted(new_in_crawl):
+                rate = margin_data[margin_data['合约代码'] == contract].iloc[0]['投机%']
+                print(f"  - {contract} (保证金率: {rate}%)")
+        
+        if not missing_in_crawl and not new_in_crawl:
+            print("[信息] 合约代码完全一致")
+        
+        # 5. 备份数据
+        print(f"\n{'=' * 80}")
+        print("步骤4: 备份数据")
+        print(f"{'=' * 80}")
+        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
+        
+        # 备份原始配置数据
+        origin_data = []
+        for contract, rates in existing_config.items():
+            origin_data.append({
+                '合约代码': contract,
+                'long': rates['long'],
+                'short': rates['short']
+            })
+        origin_df = pd.DataFrame(origin_data)
+        origin_file = os.path.join(self.backup_dir, f"origin_{updater_key}_{timestamp}.csv")
+        origin_df.to_csv(origin_file, index=False, encoding='utf-8')
+        print(f"[备份] 原始配置已保存: {origin_file}")
+        
+        # 备份爬取数据
+        update_file = os.path.join(self.backup_dir, f"update_{crawler_key}_{timestamp}.csv")
+        margin_data.to_csv(update_file, index=False, encoding='utf-8')
+        print(f"[备份] 爬取数据已保存: {update_file}")
+        
+        # 6. 备份代码文件
+        print(f"\n{'=' * 80}")
+        print("步骤5: 备份代码文件")
+        print(f"{'=' * 80}")
+        updater.backup_file(file_path)
+        
+        # 7. 更新配置
+        print(f"\n{'=' * 80}")
+        print("步骤6: 更新保证金配置")
+        print(f"{'=' * 80}")
+        changes = updater.update_config(file_path, margin_data)
+        print("[更新] 保证金配置变更记录:")
+        for change in changes:
+            print(change)
+        
+        # 8. 新增合约
+        if new_in_crawl:
+            print(f"\n{'=' * 80}")
+            print("步骤7: 新增合约配置")
+            print(f"{'=' * 80}")
+            additions = updater.add_new_contracts(file_path, list(new_in_crawl), margin_data)
+            print("[新增] 合约配置新增记录:")
+            for addition in additions:
+                print(addition)
+        
+        print(f"\n{'=' * 80}")
+        print("完成!")
+        print(f"{'=' * 80}")
+    
+    def _validate_test_data(self, margin_data: pd.DataFrame):
+        """
+        验证测试数据
+        
+        Args:
+            margin_data: 爬取的保证金数据
+        """
+        print(f"\n[验证] 检查测试数据...")
+        
+        test_cases = [
+            ('A', 16),
+            ('CJ', 14)
+        ]
+        
+        all_passed = True
+        for contract_code, expected_rate in test_cases:
+            row = margin_data[margin_data['合约代码'] == contract_code]
+            if row.empty:
+                print(f"  ✗ {contract_code}: 未找到数据")
+                all_passed = False
+            else:
+                actual_rate = row.iloc[0]['投机%']
+                if abs(actual_rate - expected_rate) < 0.01:
+                    print(f"  ✓ {contract_code}: {actual_rate}% (预期: {expected_rate}%)")
+                else:
+                    print(f"  ✗ {contract_code}: {actual_rate}% (预期: {expected_rate}%)")
+                    all_passed = False
+        
+        if all_passed:
+            print("[验证] 测试数据验证通过!")
+        else:
+            print("[验证] 测试数据验证失败!")
+
+
+# ============================================================================
+# 主程序入口
+# ============================================================================
+
+def main():
+    """主函数"""
+    # 创建管理器
+    manager = MarginCrawlerManager()
+    
+    # 执行爬取和更新
+    manager.run(
+        crawler_key='hua_future',
+        updater_key='MAPatternStrategy_v001'
+    )
+
+
+if __name__ == '__main__':
+    main()
+

+ 150 - 0
uv.lock

@@ -6,6 +6,101 @@ resolution-markers = [
     "python_full_version < '3.12'",
 ]
 
+[[package]]
+name = "beautifulsoup4"
+version = "4.14.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "soupsieve" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" },
+]
+
+[[package]]
+name = "certifi"
+version = "2025.10.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" },
+    { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" },
+    { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" },
+    { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" },
+    { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" },
+    { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" },
+    { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" },
+    { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" },
+    { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" },
+    { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" },
+    { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" },
+    { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" },
+    { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" },
+    { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" },
+    { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" },
+    { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" },
+    { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" },
+    { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" },
+    { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" },
+    { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" },
+    { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" },
+    { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" },
+    { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" },
+    { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" },
+    { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" },
+    { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" },
+    { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" },
+    { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" },
+    { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" },
+    { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" },
+    { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" },
+    { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" },
+    { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
+    { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
+    { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
+    { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
+    { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
+    { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
+    { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
+    { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
+    { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
+    { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
+    { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
+    { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
+    { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
+    { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
+    { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
+    { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
+    { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
+    { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
+    { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
+    { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
+    { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
+    { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
+    { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
+    { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
+    { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
+    { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
+    { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
+    { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
+    { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
+    { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
+    { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
+    { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
+    { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
+]
+
 [[package]]
 name = "contourpy"
 version = "1.3.2"
@@ -102,19 +197,32 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/d7/d4/1d85a1996b6188cd2713230e002d79a6f3a289bb17cef600cba385848b72/fonttools-4.58.5-py3-none-any.whl", hash = "sha256:e48a487ed24d9b611c5c4b25db1e50e69e9854ca2670e39a3486ffcd98863ec4", size = 1115318, upload-time = "2025-07-03T14:04:45.378Z" },
 ]
 
+[[package]]
+name = "idna"
+version = "3.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
+]
+
 [[package]]
 name = "jukuan"
 version = "0.1.0"
 source = { virtual = "." }
 dependencies = [
+    { name = "beautifulsoup4" },
     { name = "matplotlib" },
     { name = "pandas" },
+    { name = "requests" },
 ]
 
 [package.metadata]
 requires-dist = [
+    { name = "beautifulsoup4", specifier = ">=4.14.2" },
     { name = "matplotlib", specifier = ">=3.10.3" },
     { name = "pandas", specifier = ">=2.3.1" },
+    { name = "requests", specifier = ">=2.32.5" },
 ]
 
 [[package]]
@@ -448,6 +556,21 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
 ]
 
+[[package]]
+name = "requests"
+version = "2.32.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "certifi" },
+    { name = "charset-normalizer" },
+    { name = "idna" },
+    { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
+]
+
 [[package]]
 name = "six"
 version = "1.17.0"
@@ -457,6 +580,24 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
 ]
 
+[[package]]
+name = "soupsieve"
+version = "2.8"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]
+
 [[package]]
 name = "tzdata"
 version = "2025.2"
@@ -465,3 +606,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be76
 wheels = [
     { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
 ]
+
+[[package]]
+name = "urllib3"
+version = "2.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
+]