فهرست منبع

优化K线重建工具'kline_reconstruction.py',增强CSV文件读取的编码兼容性,支持多种日期格式解析,改进交易日计算逻辑,更新绘图功能以标注成交价和委托时间,同时新增目录打包功能以便于输出管理。

maxfeng 2 ماه پیش
والد
کامیت
4f7de0c45f
2فایلهای تغییر یافته به همراه363 افزوده شده و 110 حذف شده
  1. 1 1
      Lib/future/MAPatternStrategy_v002.py
  2. 362 109
      Lib/future/kline_reconstruction.py

+ 1 - 1
Lib/future/MAPatternStrategy_v002.py

@@ -188,7 +188,7 @@ def initialize(context):
         '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'},
+        # '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'},

+ 362 - 109
Lib/future/kline_reconstruction.py

@@ -8,7 +8,9 @@ import matplotlib.pyplot as plt
 import matplotlib.patches as patches
 from datetime import datetime, timedelta, date
 import re
+from tqdm import tqdm
 import os
+import zipfile
 import warnings
 warnings.filterwarnings('ignore')
 
@@ -31,13 +33,13 @@ def _get_current_directory():
         # 在 Jupyter notebook 环境中,__file__ 不存在,使用当前工作目录
         current_dir = os.getcwd()
         # 如果当前目录不是 future 目录,尝试查找
-        if not os.path.exists(os.path.join(current_dir, 'transaction.csv')):
+        if not os.path.exists(os.path.join(current_dir, 'transaction1.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')):
+                if os.path.exists(os.path.join(future_dir, 'transaction1.csv')):
                     current_dir = future_dir
     return current_dir
 
@@ -52,30 +54,45 @@ def read_and_filter_open_positions(csv_path):
     返回:
         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()
+    # 尝试多种编码格式
+    encodings = ['utf-8-sig', 'utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1']
+    
+    for encoding in encodings:
+        try:
+            df = pd.read_csv(csv_path, encoding=encoding)
+            
+            # 筛选交易类型第一个字符为"开"的行
+            open_positions = df[df['交易类型'].str[0] == '开'].copy()
+            
+            print(f"成功使用 {encoding} 编码读取CSV文件")
+            print(f"从CSV文件中读取到 {len(df)} 条记录")
+            print(f"筛选出 {len(open_positions)} 条开仓记录")
+            
+            return open_positions
+        except UnicodeDecodeError:
+            continue
+        except Exception as e:
+            # 如果是其他错误(比如列名不存在),也尝试下一种编码
+            if encoding == encodings[-1]:
+                # 最后一种编码也失败了,抛出错误
+                print(f"读取CSV文件时出错: {str(e)}")
+                raise
+            continue
+    
+    # 所有编码都失败了
+    print(f"无法使用任何编码格式读取CSV文件: {csv_path}")
+    return pd.DataFrame()
 
 
 def extract_contract_code_and_date(row):
     """
-    从标的列提取合约编号,从日期列提取日期
+    从标的列提取合约编号,从日期列提取日期,从委托时间计算实际交易日,从成交价列提取成交价,从交易类型提取开仓方向
     
     参数:
         row (pandas.Series): DataFrame的一行数据
     
     返回:
-        tuple: (contract_code, trade_date) 或 (None, None) 如果提取失败
+        tuple: (contract_code, actual_trade_date, trade_price, direction, order_time) 或 (None, None, None, None, None) 如果提取失败
     """
     try:
         # 提取合约编号:从"标的"列中提取括号内的内容
@@ -85,29 +102,122 @@ def extract_contract_code_and_date(row):
             contract_code = match.group(1)
         else:
             print(f"无法从标的 '{target_str}' 中提取合约编号")
-            return None, None
+            return None, None, None
+        
+        # 提取日期,支持多种日期格式
+        date_str = str(row['日期']).strip()
+        
+        # 尝试多种日期格式
+        date_formats = [
+            '%Y-%m-%d',      # 2025-01-02
+            '%d/%m/%Y',      # 14/10/2025
+            '%Y/%m/%d',      # 2025/01/02
+            '%d-%m-%Y',      # 14-10-2025
+            '%Y%m%d',        # 20250102
+        ]
+        
+        base_date = None
+        for date_format in date_formats:
+            try:
+                base_date = datetime.strptime(date_str, date_format).date()
+                break
+            except ValueError:
+                continue
         
-        # 提取日期
-        date_str = str(row['日期'])
+        if base_date is None:
+            print(f"日期格式错误: {date_str} (支持的格式: YYYY-MM-DD, DD/MM/YYYY, YYYY/MM/DD, DD-MM-YYYY, YYYYMMDD)")
+            return None, None, None
+        
+        # 提取委托时间,判断是否是晚上(>=21:00)
+        order_time_str = str(row['委托时间']).strip()
         try:
-            trade_date = datetime.strptime(date_str, '%Y-%m-%d').date()
-        except:
-            print(f"日期格式错误: {date_str}")
-            return None, None
+            # 解析时间格式 HH:MM:SS 或 HH:MM
+            time_parts = order_time_str.split(':')
+            hour = int(time_parts[0])
+            
+            # 如果委托时间 >= 21:00,需要找到下一个交易日
+            if hour >= 21:
+                # 使用get_trade_days获取从base_date开始的交易日
+                # count=2 表示获取包括base_date在内的2个交易日
+                # 如果base_date是交易日,则返回[base_date, next_trade_day]
+                # 如果base_date不是交易日,则返回下一个交易日和再下一个交易日
+                try:
+                    trade_days = get_trade_days(start_date=base_date, count=2)
+                    # print(f"trade_days: {trade_days}")
+                    if len(trade_days) >= 2:
+                        # 取第二个交易日(索引1)作为实际交易日
+                        next_trade_day = trade_days[1]
+                        if isinstance(next_trade_day, datetime):
+                            actual_trade_date = next_trade_day.date()
+                        elif isinstance(next_trade_day, date):
+                            actual_trade_date = next_trade_day
+                        else:
+                            # 如果类型不对,尝试转换
+                            actual_trade_date = base_date
+                            print(f"警告:获取的交易日类型异常: {type(next_trade_day)}")
+                    elif len(trade_days) == 1:
+                        # 如果只返回1个交易日,说明base_date就是交易日,但已经是最后一个交易日
+                        # 这种情况应该取下一个交易日,但可能超出了数据范围
+                        first_day = trade_days[0]
+                        if isinstance(first_day, datetime):
+                            actual_trade_date = first_day.date()
+                        elif isinstance(first_day, date):
+                            actual_trade_date = first_day
+                        else:
+                            actual_trade_date = base_date
+                        print(f"警告:只获取到1个交易日,可能已到数据边界")
+                    else:
+                        # 如果获取失败,使用base_date
+                        actual_trade_date = base_date
+                        print(f"警告:无法获取下一个交易日,使用原始日期")
+                except Exception as e:
+                    # 如果获取交易日失败,使用base_date
+                    actual_trade_date = base_date
+                    print(f"获取交易日时出错: {str(e)},使用原始日期")
+            else:
+                # 委托时间 < 21:00,使用原始日期
+                actual_trade_date = base_date
+        except Exception as e:
+            # 如果解析时间失败,使用原始日期
+            print(f"解析委托时间失败: {order_time_str}, 使用原始日期")
+            actual_trade_date = base_date
+        
+        # print(f"成交日期:{date_str},委托时间:{order_time_str},实际交易日:{actual_trade_date}")
         
-        return contract_code, trade_date
+        # 提取成交价
+        try:
+            trade_price = float(row['成交价'])
+        except (ValueError, KeyError):
+            print(f"无法提取成交价: {row.get('成交价', 'N/A')}")
+            return None, None, None, None, None
+        
+        # 提取开仓方向:从"交易类型"列提取,开多是'long',开空是'short'
+        try:
+            trade_type = str(row['交易类型']).strip()
+            if '开多' in trade_type or '多' in trade_type:
+                direction = 'long'
+            elif '开空' in trade_type or '空' in trade_type:
+                direction = 'short'
+            else:
+                print(f"无法识别交易方向: {trade_type}")
+                direction = 'unknown'
+        except (KeyError, ValueError):
+            print(f"无法提取交易类型")
+            direction = 'unknown'
+        
+        return contract_code, actual_trade_date, trade_price, direction, order_time_str
     except Exception as e:
         print(f"提取合约编号和日期时出错: {str(e)}")
-        return None, None
+        return None, None, None, None, None
 
 
-def calculate_trade_days_range(trade_date, days_before=60, days_after=10):
+def calculate_trade_days_range(trade_date, days_before=100, days_after=10):
     """
     计算交易日范围:往前days_before个交易日,往后days_after个交易日
     
     参数:
         trade_date (date): 开仓日期
-        days_before (int): 往前交易日数量,默认60
+        days_before (int): 往前交易日数量,默认100
         days_after (int): 往后交易日数量,默认10
     
     返回:
@@ -122,7 +232,14 @@ def calculate_trade_days_range(trade_date, days_before=60, days_after=10):
             print(f"无法获取足够的往前交易日,只获取到 {len(trade_days_before)} 个")
             return None, None
         
-        start_date = trade_days_before[0].date()
+        # 处理返回的日期对象:可能是date或datetime类型
+        first_day = trade_days_before[0]
+        if isinstance(first_day, datetime):
+            start_date = first_day.date()
+        elif isinstance(first_day, date):
+            start_date = first_day
+        else:
+            start_date = first_day
         
         # 往后找:从trade_date往后找days_after个交易日
         # get_trade_days(start_date=trade_date, count=n) 返回包括trade_date在内的n个交易日
@@ -132,7 +249,14 @@ def calculate_trade_days_range(trade_date, days_before=60, days_after=10):
             print(f"无法获取足够的往后交易日,只获取到 {len(trade_days_after)} 个")
             return None, None
         
-        end_date = trade_days_after[-1].date()
+        # 处理返回的日期对象:可能是date或datetime类型
+        last_day = trade_days_after[-1]
+        if isinstance(last_day, datetime):
+            end_date = last_day.date()
+        elif isinstance(last_day, date):
+            end_date = last_day
+        else:
+            end_date = last_day
         
         return start_date, end_date
     except Exception as e:
@@ -209,14 +333,17 @@ def filter_data_with_ma(data):
     return filtered_data
 
 
-def plot_kline_chart(data, contract_code, trade_date, save_path):
+def plot_kline_chart(data, contract_code, trade_date, trade_price, direction, order_time, save_path):
     """
-    绘制K线图(包含均线和开仓日期标注)
+    绘制K线图(包含均线和开仓日期、成交价、方向、委托时间标注)
     
     参数:
         data (pandas.DataFrame): 包含OHLC和均线数据的DataFrame
         contract_code (str): 合约编号
-        trade_date (date): 开仓日期
+        trade_date (date): 实际交易日
+        trade_price (float): 成交价
+        direction (str): 开仓方向,'long'或'short'
+        order_time (str): 委托时间
         save_path (str): 保存路径
     """
     try:
@@ -230,18 +357,91 @@ def plot_kline_chart(data, contract_code, trade_date, save_path):
         lows = data['low']
         closes = data['close']
         
+        # 调试:打印数据结构信息(仅第一次调用时打印)
+        if not hasattr(plot_kline_chart, '_debug_printed'):
+            print(f"\n=== K线数据索引类型调试信息 ===")
+            print(f"索引类型: {type(dates)}")
+            print(f"索引数据类型: {type(dates[0]) if len(dates) > 0 else 'N/A'}")
+            print(f"前3个索引值: {[dates[i] for i in range(min(3, len(dates)))]}")
+            print(f"索引是否为DatetimeIndex: {isinstance(dates, pd.DatetimeIndex)}")
+            print(f"================================\n")
+            plot_kline_chart._debug_printed = True
+        
+        # 统一转换为date类型进行比较
+        trade_date_normalized = trade_date
+        if isinstance(trade_date, datetime):
+            trade_date_normalized = trade_date.date()
+        elif isinstance(trade_date, pd.Timestamp):
+            trade_date_normalized = trade_date.date()
+        elif not isinstance(trade_date, date):
+            try:
+                trade_date_normalized = pd.to_datetime(trade_date).date()
+            except:
+                pass
+        
         # 找到开仓日期在数据中的位置
         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:
+        
+        # 如果索引是DatetimeIndex,直接使用date比较
+        if isinstance(dates, pd.DatetimeIndex):
+            # 将DatetimeIndex转换为date进行比较
+            try:
+                # 使用normalize()将时间部分去掉,然后比较date
+                trade_date_normalized_dt = pd.Timestamp(trade_date_normalized)
+                # 查找匹配的日期
+                mask = dates.normalize() == trade_date_normalized_dt
+                if mask.any():
+                    trade_date_idx = mask.argmax()
+            except Exception as e:
+                print(f"使用DatetimeIndex匹配时出错: {e}")
+        
+        # 如果还没找到,使用循环方式查找
+        if trade_date_idx is None:
+            for i, date_idx in enumerate(dates):
+                date_to_compare = None
+                # 处理pandas Timestamp类型
+                if isinstance(date_idx, pd.Timestamp):
+                    date_to_compare = date_idx.date()
+                elif isinstance(date_idx, datetime):
+                    date_to_compare = date_idx.date()
+                elif isinstance(date_idx, date):
+                    date_to_compare = date_idx
+                elif hasattr(date_idx, 'date'):
+                    try:
+                        date_to_compare = date_idx.date()
+                    except:
+                        pass
+                
+                # 比较日期
+                if date_to_compare is not None and date_to_compare == trade_date_normalized:
                     trade_date_idx = i
                     break
         
+        # 如果还是没找到,尝试查找最接近的日期(前后各找1天)
+        if trade_date_idx is None:
+            print(f"警告:未找到精确匹配的交易日 {trade_date_normalized}")
+            print(f"  尝试查找前后1天的日期...")
+            for offset in [-1, 1]:
+                try:
+                    target_date = trade_date_normalized + timedelta(days=offset)
+                    for i, date_idx in enumerate(dates):
+                        date_to_compare = None
+                        if isinstance(date_idx, pd.Timestamp):
+                            date_to_compare = date_idx.date()
+                        elif isinstance(date_idx, datetime):
+                            date_to_compare = date_idx.date()
+                        elif isinstance(date_idx, date):
+                            date_to_compare = date_idx
+                        
+                        if date_to_compare == target_date:
+                            trade_date_idx = i
+                            print(f"  找到最接近的日期 {target_date} (偏移{offset}天) 在索引 {i}")
+                            break
+                    if trade_date_idx is not None:
+                        break
+                except:
+                    pass
+        
         # 绘制K线
         for i in range(len(data)):
             date_idx = dates[i]
@@ -275,15 +475,40 @@ def plot_kline_chart(data, contract_code, trade_date, save_path):
         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,
+            # 绘制标记点(使用成交价)
+            ax.plot(trade_date_idx, trade_price, marker='*', markersize=20, 
+                   color='yellow', markeredgecolor='black', markeredgewidth=2,
                    label='Open Position', zorder=10)
             # 添加垂直线
             ax.axvline(x=trade_date_idx, color='yellow', linestyle='--', 
                       linewidth=2, alpha=0.7, zorder=5)
+            
+            # 标注日期、成交价、方向和委托时间文本
+            date_label = trade_date.strftime('%Y-%m-%d')
+            price_label = f'Price: {trade_price:.2f}'
+            direction_label = f'Direction: {direction}'
+            time_label = f'Time: {order_time}'
+            
+            # 计算文本位置(在标记点上方,确保可见)
+            price_range = highs.max() - lows.min()
+            y_offset = max(price_range * 0.08, (highs.max() - trade_price) * 0.3)  # 至少8%的价格范围,或30%的上方空间
+            text_y = trade_price + y_offset
+            
+            # 如果文本位置超出图表范围,放在标记点下方
+            if text_y > highs.max():
+                text_y = trade_price - price_range * 0.08
+            
+            # 添加文本标注(包含所有信息)
+            annotation_text = f'{date_label}\n{price_label}\n{direction_label}\n{time_label}'
+            ax.text(trade_date_idx, text_y, annotation_text, 
+                   fontsize=10, ha='center', va='bottom',
+                   bbox=dict(boxstyle='round,pad=0.6', facecolor='yellow', alpha=0.9, edgecolor='black', linewidth=1.5),
+                   zorder=11, weight='bold')
+        else:
+            # 即使没找到精确日期,也尝试标注(使用最接近的日期)
+            print(f"警告:交易日 {trade_date} 不在K线数据范围内,无法标注")
         
         # 设置图表标题和标签(使用英文)
         contract_simple = contract_code.split('.')[0]  # 提取合约编号的简约部分
@@ -295,7 +520,7 @@ def plot_kline_chart(data, contract_code, trade_date, save_path):
         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)
+        ax.legend(loc='lower left', fontsize=10)
         
         # 设置x轴标签
         step = max(1, len(data) // 10)  # 显示约10个时间标签
@@ -332,63 +557,89 @@ def plot_kline_chart(data, contract_code, trade_date, save_path):
         # 调整布局并保存
         plt.tight_layout()
         plt.savefig(save_path, dpi=150, bbox_inches='tight')
-        plt.close()
-        
-        print(f"K线图已保存到: {save_path}")
+        # print(f"K线图已保存到: {save_path}")
+        plt.close(fig)
         
     except Exception as e:
         print(f"绘制K线图时出错: {str(e)}")
+        # 确保即使出错也关闭图形
+        try:
+            plt.close('all')
+        except:
+            pass
         raise
 
 
-def reconstruct_kline_from_transactions(csv_path=None, output_dir=None):
+def create_zip_archive(directory_path, zip_filename=None):
+    """
+    将指定目录打包成zip文件
+    
+    参数:
+        directory_path (str): 要打包的目录路径
+        zip_filename (str): zip文件名,如果为None则自动生成
+    
+    返回:
+        str: zip文件路径
+    """
+    if not os.path.exists(directory_path):
+        print(f"目录不存在: {directory_path}")
+        return None
+    
+    if zip_filename is None:
+        # 自动生成zip文件名
+        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
+        dir_name = os.path.basename(os.path.normpath(directory_path))
+        zip_filename = f"{dir_name}_{timestamp}.zip"
+        # 保存在目录的父目录中
+        zip_path = os.path.join(os.path.dirname(directory_path), zip_filename)
+    else:
+        zip_path = zip_filename
+    
+    try:
+        print(f"\n=== 开始打包目录: {directory_path} ===")
+        file_count = 0
+        with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
+            # 遍历目录中的所有文件
+            for root, dirs, files in os.walk(directory_path):
+                for file in files:
+                    file_path = os.path.join(root, file)
+                    # 计算相对路径:相对于要打包的目录
+                    arcname = os.path.relpath(file_path, directory_path)
+                    zipf.write(file_path, arcname)
+                    file_count += 1
+        
+        # 获取zip文件大小
+        zip_size = os.path.getsize(zip_path) / (1024 * 1024)  # MB
+        print(f"✓ 打包完成: {zip_path}")
+        print(f"  包含文件数: {file_count} 个")
+        print(f"  文件大小: {zip_size:.2f} MB")
+        
+        return zip_path
+    except Exception as e:
+        print(f"✗ 打包时出错: {str(e)}")
+        return None
+
+
+def reconstruct_kline_from_transactions(csv_filename=None, output_dir=None):
     """
     主函数:从交易记录中复原K线图
     
     参数:
-        csv_path (str): CSV文件路径,默认为 'Lib/future/transaction.csv'
-        output_dir (str): 输出目录,默认为 'Lib/future/K'
+        csv_filename (str): CSV文件名,如果为None则需要在代码中设置文件名
+        output_dir (str): 输出目录,如果为None则自动设置为当前目录的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')
+    # ========== 路径配置:只需在这里设置CSV文件名 ==========
+    if csv_filename is None:
+        # 设置CSV文件名(只需修改文件名,不需要完整路径)
+        csv_filename = 'transaction4.csv'
+    # ====================================================
+    
+    # 获取当前目录并拼接CSV文件路径
+    current_dir = _get_current_directory()
+    csv_path = os.path.join(current_dir, csv_filename)
     
+    # 自动设置输出目录
     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')
     
     # 确保输出目录存在
@@ -408,28 +659,24 @@ def reconstruct_kline_from_transactions(csv_path=None, output_dir=None):
     success_count = 0
     fail_count = 0
     
-    for idx, row in open_positions.iterrows():
-        print(f"\n--- 处理第 {idx + 1}/{len(open_positions)} 条记录 ---")
+    for idx, row in tqdm(open_positions.iterrows(), total=len(open_positions), desc="处理开仓记录"):
+        # 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"跳过:无法提取合约编号或日期")
+            # 提取合约编号、实际交易日、成交价、开仓方向和委托时间
+            contract_code, actual_trade_date, trade_price, direction, order_time = extract_contract_code_and_date(row)
+            if contract_code is None or actual_trade_date is None or trade_price is None or direction is None or order_time 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)
+            start_date, end_date = calculate_trade_days_range(actual_trade_date, days_before=100, 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:
@@ -437,8 +684,6 @@ def reconstruct_kline_from_transactions(csv_path=None, output_dir=None):
                 fail_count += 1
                 continue
             
-            print(f"获取到 {len(kline_data)} 条K线数据")
-            
             # 计算均线
             kline_data = calculate_moving_averages(kline_data)
             
@@ -449,18 +694,15 @@ def reconstruct_kline_from_transactions(csv_path=None, output_dir=None):
                 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"
+            filename = f"{contract_simple}_{actual_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)
+            # 绘制K线图(传入实际交易日和成交价)
+            plot_kline_chart(filtered_data, contract_code, actual_trade_date, trade_price, direction, order_time, save_path)
             
             success_count += 1
-            print(f"✓ 成功处理")
             
         except Exception as e:
             print(f"✗ 处理时出错: {str(e)}")
@@ -471,7 +713,18 @@ def reconstruct_kline_from_transactions(csv_path=None, output_dir=None):
     print(f"\n=== 处理完成 ===")
     print(f"成功: {success_count} 条")
     print(f"失败: {fail_count} 条")
-    print(f"总计: {len(open_positions)} 条")
+    print(f"总计: {success_count + fail_count} 条")
+    
+    # 3. 打包图片目录
+    if success_count > 0:
+        print(f"\n=== 步骤3: 打包图片目录 ===")
+        zip_path = create_zip_archive(output_dir)
+        if zip_path:
+            print(f"✓ 打包文件已保存: {zip_path}")
+        else:
+            print(f"✗ 打包失败")
+    else:
+        print(f"\n未生成任何图片,跳过打包步骤")
 
 
 # 使用示例