#!/usr/bin/env python # -*- coding: utf-8 -*- """ 期货开仓记录分析工具 分析 records.csv 中的期货交易数据,提供多维度统计分析 """ import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from datetime import datetime, timedelta import re import os import sys # 设置中文字体 plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans'] plt.rcParams['axes.unicode_minus'] = False # 期货配置字典(从MAPatternStrategy_v002.py复制) 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.07, 'short': 0.07}, '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'}, 'HO': {'has_night_session': False, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 100, 'trading_start_time': '09:30'}, 'LG': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 90, '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'}, 'OP': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 40, 'trading_start_time': '09:00'}, 'BC': {'has_night_session': True, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '21:00'}, 'SH': {'has_night_session': True, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 30, 'trading_start_time': '21:00'}, 'TS': {'has_night_session': False, 'margin_rate': {'long': 0.015, 'short': 0.015}, 'multiplier': 2000000, 'trading_start_time': '09:30'}, 'AD': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, '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'}, 'SI': {'has_night_session': False, 'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '09: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'}, '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'}, 'BZ': {'has_night_session': False, 'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 30, 'trading_start_time': '09:00'}, } def extract_symbol_code(target_str): """ 从标的字段提取品种代码 例如: '原油2002(SC2002.XINE)' -> 'SC' """ match = re.search(r'\(([A-Z]+)\d+\.', target_str) if match: return match.group(1) return None def calculate_time_segment(order_time_str, trading_start_time_str): """ 计算开仓时间相对于交易开始时间的时间段 返回: '<30分钟', '30-60分钟', '>1小时' """ try: # 解析时间字符串 order_time = datetime.strptime(order_time_str, '%H:%M:%S') start_time = datetime.strptime(trading_start_time_str, '%H:%M') # 如果是夜盘品种(21:00开盘),需要特殊处理跨日情况 if trading_start_time_str == '21:00': # 如果委托时间在21:00之前,说明是第二天的交易时间 if order_time.hour < 21 and order_time.hour >= 0: # 加24小时处理跨日 order_time = order_time + timedelta(days=1) # 计算时间差(分钟) time_diff = (order_time - start_time).total_seconds() / 60 # 处理负数情况(可能是跨日) if time_diff < 0: time_diff += 24 * 60 # 分类 if time_diff < 30: return '<30分钟' elif time_diff < 60: return '30-60分钟' else: return '>1小时' except Exception as e: print(f"时间计算错误: {order_time_str}, {trading_start_time_str}, {e}") return '未知' def calculate_session_type(order_time_str, has_night_session): """ 计算交易时段类型:夜盘、上午、下午 """ try: order_time = datetime.strptime(order_time_str, '%H:%M:%S') hour = order_time.hour if has_night_session and (hour >= 21 or hour < 3): return '夜盘' elif 9 <= hour < 12: return '上午' elif 12 <= hour < 16: return '下午' else: return '其他' except: return '未知' def load_and_preprocess_data(csv_path): """ 加载并预处理数据 """ print("正在加载数据...") df = pd.read_csv(csv_path) print(f"原始数据行数: {len(df)}") print(f"数据列: {df.columns.tolist()}") # 提取品种代码 df['品种代码'] = df['标的'].apply(extract_symbol_code) # 获取品种配置信息 df['trading_start_time'] = df['品种代码'].apply( lambda x: FUTURES_CONFIG.get(x, {}).get('trading_start_time', None) ) df['has_night_session'] = df['品种代码'].apply( lambda x: FUTURES_CONFIG.get(x, {}).get('has_night_session', False) ) # 计算开盘后时间段 df['开盘后时间段'] = df.apply( lambda row: calculate_time_segment(row['委托时间'], row['trading_start_time']) if pd.notna(row['trading_start_time']) else '未知', axis=1 ) # 计算交易时段 df['交易时段'] = df.apply( lambda row: calculate_session_type(row['委托时间'], row['has_night_session']), axis=1 ) # 计算保证金收益率 df['保证金收益率'] = (df['交易盈亏'] / df['保证金']) * 100 # 计算穿越均线数量 df['穿越均线数量'] = df['crossed_ma_lines'].apply( lambda x: len(x.split(';')) if pd.notna(x) else 0 ) # 判断是否盈利 df['是否盈利'] = df['交易盈亏'] > 0 # 成交额分组 df['成交额分组'] = pd.cut(df['成交额'], bins=[0, 100000, 200000, 500000, float('inf')], labels=['<10万', '10-20万', '20-50万', '>50万']) print(f"预处理后数据行数: {len(df)}") print(f"品种代码提取成功率: {df['品种代码'].notna().sum() / len(df) * 100:.2f}%") return df def calculate_statistics(group_df): """ 计算统计指标 """ total_count = len(group_df) win_count = (group_df['交易盈亏'] > 0).sum() win_rate = win_count / total_count if total_count > 0 else 0 avg_profit_loss = group_df['交易盈亏'].mean() # 计算盈亏比 profit_trades = group_df[group_df['交易盈亏'] > 0]['交易盈亏'] loss_trades = group_df[group_df['交易盈亏'] <= 0]['交易盈亏'] avg_profit = profit_trades.mean() if len(profit_trades) > 0 else 0 avg_loss = abs(loss_trades.mean()) if len(loss_trades) > 0 else 0 profit_loss_ratio = avg_profit / avg_loss if avg_loss > 0 else np.inf avg_margin_return = group_df['保证金收益率'].mean() return pd.Series({ '出现次数': total_count, '胜率': win_rate, '平均盈亏': avg_profit_loss, '盈亏比': profit_loss_ratio, '平均保证金收益率': avg_margin_return }) def analyze_ma_lines(df): """ 分析crossed_ma_lines维度 """ print("\n" + "="*80) print("均线组合分析") print("="*80) ma_stats = df.groupby('crossed_ma_lines').apply(calculate_statistics).round(4) ma_stats = ma_stats.sort_values('出现次数', ascending=False) print(ma_stats.to_string()) return ma_stats def analyze_time_segment(df): """ 分析开盘后时间段维度 """ print("\n" + "="*80) print("开盘后时间段分析") print("="*80) time_stats = df.groupby('开盘后时间段').apply(calculate_statistics).round(4) # 按指定顺序排列 order = ['<30分钟', '30-60分钟', '>1小时', '未知'] time_stats = time_stats.reindex([o for o in order if o in time_stats.index]) print(time_stats.to_string()) return time_stats def analyze_cross_dimension(df): """ 交叉分析:均线组合 × 开盘后时间段 """ print("\n" + "="*80) print("交叉分析:均线组合 × 开盘后时间段") print("="*80) # 样本量分布 cross_count = pd.crosstab(df['crossed_ma_lines'], df['开盘后时间段']) print("\n样本量分布:") print(cross_count.to_string()) # 胜率对比 cross_winrate = pd.crosstab( df['crossed_ma_lines'], df['开盘后时间段'], values=df['是否盈利'], aggfunc='mean' ).round(4) print("\n胜率对比:") print(cross_winrate.to_string()) # 平均盈亏 cross_profit = pd.crosstab( df['crossed_ma_lines'], df['开盘后时间段'], values=df['交易盈亏'], aggfunc='mean' ).round(2) print("\n平均盈亏:") print(cross_profit.to_string()) # 平均保证金收益率 cross_return = pd.crosstab( df['crossed_ma_lines'], df['开盘后时间段'], values=df['保证金收益率'], aggfunc='mean' ).round(4) print("\n平均保证金收益率(%):") print(cross_return.to_string()) return cross_count, cross_winrate, cross_profit, cross_return def analyze_trade_type_and_variety(df): """ 分析交易类型和品种维度 """ print("\n" + "="*80) print("交易类型分析") print("="*80) trade_type_stats = df.groupby('交易类型').apply(calculate_statistics).round(4) print(trade_type_stats.to_string()) print("\n" + "="*80) print("品种类型分析") print("="*80) variety_stats = df.groupby('品种').apply(calculate_statistics).round(4) print(variety_stats.to_string()) print("\n" + "="*80) print("具体品种代码分析(前20名)") print("="*80) symbol_stats = df.groupby('品种代码').apply(calculate_statistics).round(4) symbol_stats = symbol_stats.sort_values('出现次数', ascending=False).head(20) print(symbol_stats.to_string()) return trade_type_stats, variety_stats, symbol_stats def analyze_additional_dimensions(df): """ 其他维度分析 """ print("\n" + "="*80) print("成交额分组分析") print("="*80) amount_stats = df.groupby('成交额分组').apply(calculate_statistics).round(4) print(amount_stats.to_string()) print("\n" + "="*80) print("交易时段分析") print("="*80) session_stats = df.groupby('交易时段').apply(calculate_statistics).round(4) print(session_stats.to_string()) print("\n" + "="*80) print("穿越均线数量分析") print("="*80) ma_count_stats = df.groupby('穿越均线数量').apply(calculate_statistics).round(4) print(ma_count_stats.to_string()) print("\n" + "="*80) print("多空对比(按均线组合)- 前10个组合") print("="*80) # 获取出现次数最多的前10个均线组合 top_ma_lines = df['crossed_ma_lines'].value_counts().head(10).index df_top = df[df['crossed_ma_lines'].isin(top_ma_lines)] long_short_stats = df_top.groupby(['crossed_ma_lines', '交易类型']).apply( calculate_statistics ).round(4) print(long_short_stats.to_string()) print("\n" + "="*80) print("品种特性分析(有夜盘 vs 无夜盘)") print("="*80) night_session_stats = df.groupby('has_night_session').apply(calculate_statistics).round(4) night_session_stats.index = ['无夜盘', '有夜盘'] print(night_session_stats.to_string()) print("\n" + "="*80) print("组合策略分析:最佳组合(样本量>=10)") print("="*80) # 三维组合分析 combo_stats = df.groupby(['crossed_ma_lines', '开盘后时间段', '交易类型']).apply( calculate_statistics ).round(4) # 筛选样本量>=10的组合 combo_stats = combo_stats[combo_stats['出现次数'] >= 10] # 按保证金收益率排序,显示前10 combo_stats_sorted = combo_stats.sort_values('平均保证金收益率', ascending=False).head(10) print("\n保证金收益率最高的10个组合:") print(combo_stats_sorted.to_string()) # 按胜率排序,显示前10 combo_stats_sorted_winrate = combo_stats.sort_values('胜率', ascending=False).head(10) print("\n胜率最高的10个组合:") print(combo_stats_sorted_winrate.to_string()) return { 'amount_stats': amount_stats, 'session_stats': session_stats, 'ma_count_stats': ma_count_stats, 'long_short_stats': long_short_stats, 'night_session_stats': night_session_stats, 'combo_stats': combo_stats } def create_visualizations(df, ma_stats, time_stats, cross_winrate, cross_profit, cross_return, output_dir): """ 创建数据可视化图表 """ print("\n" + "="*80) print("生成可视化图表...") print("="*80) # 创建输出目录 os.makedirs(output_dir, exist_ok=True) def annotate_barh(ax, bars, formatter=lambda v: f"{v:.0f}", offset_ratio=0.01): """ 为水平柱状图添加数值标注 """ if bars is None or len(bars) == 0: return max_width = max((bar.get_width() for bar in bars), default=0) offset = max(max_width * offset_ratio, 0.5) for bar in bars: width = bar.get_width() if np.isnan(width): continue ha = 'left' x = width + offset if width < 0: ha = 'right' x = width - offset y = bar.get_y() + bar.get_height() / 2 ax.text(x, y, formatter(width), va='center', ha=ha, fontsize=9) def annotate_bar(ax, bars, formatter=lambda v: f"{v:.0f}", offset_ratio=0.01): """ 为垂直柱状图添加数值标注 """ if bars is None or len(bars) == 0: return max_height = max((bar.get_height() for bar in bars), default=0) offset = max(max_height * offset_ratio, 0.5) for bar in bars: height = bar.get_height() if np.isnan(height): continue va = 'bottom' y = height + offset if height < 0: va = 'top' y = height - offset x = bar.get_x() + bar.get_width() / 2 ax.text(x, y, formatter(height), va=va, ha='center', fontsize=9) # 1. 均线组合表现对比(前15个) fig, axes = plt.subplots(2, 2, figsize=(16, 12)) top_ma = ma_stats.head(15) # 出现次数 bars = axes[0, 0].barh(range(len(top_ma)), top_ma['出现次数']) axes[0, 0].set_yticks(range(len(top_ma))) axes[0, 0].set_yticklabels(top_ma.index) axes[0, 0].set_xlabel('出现次数') axes[0, 0].set_title('均线组合出现次数(Top 15)') axes[0, 0].invert_yaxis() annotate_barh(axes[0, 0], bars) # 胜率 colors = ['green' if x > 0.5 else 'red' for x in top_ma['胜率']] bars = axes[0, 1].barh(range(len(top_ma)), top_ma['胜率'], color=colors) axes[0, 1].set_yticks(range(len(top_ma))) axes[0, 1].set_yticklabels(top_ma.index) axes[0, 1].set_xlabel('胜率') axes[0, 1].set_title('均线组合胜率(Top 15)') axes[0, 1].axvline(x=0.5, color='black', linestyle='--', alpha=0.5) axes[0, 1].invert_yaxis() annotate_barh(axes[0, 1], bars, formatter=lambda v: f"{v:.1%}", offset_ratio=0.02) # 平均盈亏 colors = ['green' if x > 0 else 'red' for x in top_ma['平均盈亏']] bars = axes[1, 0].barh(range(len(top_ma)), top_ma['平均盈亏'], color=colors) axes[1, 0].set_yticks(range(len(top_ma))) axes[1, 0].set_yticklabels(top_ma.index) axes[1, 0].set_xlabel('平均盈亏(元)') axes[1, 0].set_title('均线组合平均盈亏(Top 15)') axes[1, 0].axvline(x=0, color='black', linestyle='--', alpha=0.5) axes[1, 0].invert_yaxis() annotate_barh(axes[1, 0], bars, formatter=lambda v: f"{v:,.0f}", offset_ratio=0.015) # 保证金收益率 colors = ['green' if x > 0 else 'red' for x in top_ma['平均保证金收益率']] bars = axes[1, 1].barh(range(len(top_ma)), top_ma['平均保证金收益率'], color=colors) axes[1, 1].set_yticks(range(len(top_ma))) axes[1, 1].set_yticklabels(top_ma.index) axes[1, 1].set_xlabel('平均保证金收益率(%)') axes[1, 1].set_title('均线组合平均保证金收益率(Top 15)') axes[1, 1].axvline(x=0, color='black', linestyle='--', alpha=0.5) axes[1, 1].invert_yaxis() annotate_barh(axes[1, 1], bars, formatter=lambda v: f"{v:.2f}%", offset_ratio=0.02) plt.tight_layout() plt.savefig(os.path.join(output_dir, 'ma_lines_analysis.png'), dpi=150, bbox_inches='tight') print(f"已保存: {os.path.join(output_dir, 'ma_lines_analysis.png')}") plt.close() # 2. 开盘后时间段表现 fig, axes = plt.subplots(1, 2, figsize=(14, 6)) order = ['<30分钟', '30-60分钟', '>1小时'] time_stats_filtered = time_stats[time_stats.index.isin(order)] time_stats_filtered = time_stats_filtered.loc[[idx for idx in order if idx in time_stats_filtered.index]] profit_colors = ['green' if val >= 0 else 'red' for val in time_stats_filtered['平均盈亏']] bars = axes[0].bar(range(len(time_stats_filtered)), time_stats_filtered['平均盈亏'], color=profit_colors) axes[0].set_xticks(range(len(time_stats_filtered))) axes[0].set_xticklabels(time_stats_filtered.index) axes[0].set_ylabel('平均盈亏(元)') axes[0].set_title('不同时间段平均盈亏') axes[0].axhline(y=0, color='black', linestyle='--', alpha=0.5) annotate_bar(axes[0], bars, formatter=lambda v: f"{v:,.0f}") margin_colors = ['green' if val >= 0 else 'red' for val in time_stats_filtered['平均保证金收益率']] bars = axes[1].bar(range(len(time_stats_filtered)), time_stats_filtered['平均保证金收益率'], color=margin_colors) axes[1].set_xticks(range(len(time_stats_filtered))) axes[1].set_xticklabels(time_stats_filtered.index) axes[1].set_ylabel('平均保证金收益率(%)') axes[1].set_title('不同时间段平均保证金收益率') axes[1].axhline(y=0, color='black', linestyle='--', alpha=0.5) annotate_bar(axes[1], bars, formatter=lambda v: f"{v:.2f}%") plt.tight_layout() plt.savefig(os.path.join(output_dir, 'time_segment_analysis.png'), dpi=150, bbox_inches='tight') print(f"已保存: {os.path.join(output_dir, 'time_segment_analysis.png')}") plt.close() # 3. 交叉分析热力图 fig, axes = plt.subplots(1, 3, figsize=(22, 10)) # 选择前15个均线组合 top_ma_lines = ma_stats.head(15).index heatmap_cols = ['<30分钟', '30-60分钟', '>1小时'] def prepare_heatmap(table): filtered = table.reindex(index=[idx for idx in top_ma_lines if idx in table.index]) if filtered.empty: return filtered cols = [col for col in heatmap_cols if col in filtered.columns] if cols: filtered = filtered[cols] return filtered cross_winrate_filtered = prepare_heatmap(cross_winrate) cross_profit_filtered = prepare_heatmap(cross_profit) cross_return_filtered = prepare_heatmap(cross_return) # 胜率热力图 sns.heatmap(cross_winrate_filtered, annot=True, fmt='.2f', cmap='RdYlGn', center=0.5, vmin=0, vmax=1, ax=axes[0], cbar_kws={'label': '胜率'}) axes[0].set_title('均线组合 × 时间段 胜率热力图(Top 15)') axes[0].set_xlabel('开盘后时间段') axes[0].set_ylabel('均线组合') # 平均盈亏热力图 sns.heatmap(cross_profit_filtered, annot=True, fmt='.0f', cmap='RdYlGn', center=0, ax=axes[1], cbar_kws={'label': '平均盈亏(元)'}) axes[1].set_title('均线组合 × 时间段 平均盈亏热力图(Top 15)') axes[1].set_xlabel('开盘后时间段') axes[1].set_ylabel('均线组合') # 平均保证金收益率热力图 sns.heatmap(cross_return_filtered, annot=True, fmt='.2f', cmap='RdYlGn', center=0, ax=axes[2], cbar_kws={'label': '平均保证金收益率(%)'}) axes[2].set_title('均线组合 × 时间段 平均保证金收益率热力图(Top 15)') axes[2].set_xlabel('开盘后时间段') axes[2].set_ylabel('均线组合') plt.tight_layout() plt.savefig(os.path.join(output_dir, 'cross_analysis_heatmap.png'), dpi=150, bbox_inches='tight') print(f"已保存: {os.path.join(output_dir, 'cross_analysis_heatmap.png')}") plt.close() # 4. 品种表现分析 fig, axes = plt.subplots(2, 2, figsize=(16, 12)) # 交易类型对比 trade_type_stats = df.groupby('交易类型').apply(calculate_statistics) axes[0, 0].bar(trade_type_stats.index, trade_type_stats['胜率'], color=['green', 'red']) axes[0, 0].set_ylabel('胜率') axes[0, 0].set_title('交易类型胜率对比') axes[0, 0].axhline(y=0.5, color='black', linestyle='--', alpha=0.5) axes[0, 1].bar(trade_type_stats.index, trade_type_stats['平均保证金收益率'], color=['green', 'red']) axes[0, 1].set_ylabel('平均保证金收益率(%)') axes[0, 1].set_title('交易类型保证金收益率对比') axes[0, 1].axhline(y=0, color='black', linestyle='--', alpha=0.5) # 品种类型对比 variety_stats = df.groupby('品种').apply(calculate_statistics) axes[1, 0].bar(variety_stats.index, variety_stats['胜率']) axes[1, 0].set_ylabel('胜率') axes[1, 0].set_title('品种类型胜率对比') axes[1, 0].axhline(y=0.5, color='black', linestyle='--', alpha=0.5) axes[1, 1].bar(variety_stats.index, variety_stats['平均保证金收益率']) axes[1, 1].set_ylabel('平均保证金收益率(%)') axes[1, 1].set_title('品种类型保证金收益率对比') axes[1, 1].axhline(y=0, color='black', linestyle='--', alpha=0.5) plt.tight_layout() plt.savefig(os.path.join(output_dir, 'variety_analysis.png'), dpi=150, bbox_inches='tight') print(f"已保存: {os.path.join(output_dir, 'variety_analysis.png')}") plt.close() print("\n所有图表已生成!") def save_results_to_csv(df, ma_stats, time_stats, output_dir): """ 保存分析结果到CSV """ print("\n" + "="*80) print("保存分析结果到CSV...") print("="*80) # 保存增强后的原始数据 output_file = os.path.join(output_dir, 'records_enhanced.csv') df.to_csv(output_file, index=False, encoding='utf-8-sig') print(f"已保存增强数据: {output_file}") # 保存均线组合统计 output_file = os.path.join(output_dir, 'ma_lines_stats.csv') ma_stats.to_csv(output_file, encoding='utf-8-sig') print(f"已保存均线组合统计: {output_file}") # 保存时间段统计 output_file = os.path.join(output_dir, 'time_segment_stats.csv') time_stats.to_csv(output_file, encoding='utf-8-sig') print(f"已保存时间段统计: {output_file}") # 保存品种统计 symbol_stats = df.groupby('品种代码').apply(calculate_statistics) output_file = os.path.join(output_dir, 'symbol_stats.csv') symbol_stats.to_csv(output_file, encoding='utf-8-sig') print(f"已保存品种统计: {output_file}") # 保存组合策略统计 combo_stats = df.groupby(['crossed_ma_lines', '开盘后时间段', '交易类型']).apply( calculate_statistics ) combo_stats = combo_stats[combo_stats['出现次数'] >= 5] combo_stats = combo_stats.sort_values('平均保证金收益率', ascending=False) output_file = os.path.join(output_dir, 'combo_strategy_stats.csv') combo_stats.to_csv(output_file, encoding='utf-8-sig') print(f"已保存组合策略统计: {output_file}") def main(): """ 主函数 """ # 设置路径 script_dir = os.path.dirname(os.path.abspath(__file__)) csv_path = os.path.join(script_dir, 'records.csv') output_dir = os.path.join('data', 'future', 'analysis_results') # 检查文件是否存在 if not os.path.exists(csv_path): print(f"错误: 找不到文件 {csv_path}") return print("="*80) print("期货开仓记录分析工具") print("="*80) # 加载和预处理数据 df = load_and_preprocess_data(csv_path) # 进行各维度分析 ma_stats = analyze_ma_lines(df) time_stats = analyze_time_segment(df) cross_count, cross_winrate, cross_profit, cross_return = analyze_cross_dimension(df) trade_type_stats, variety_stats, symbol_stats = analyze_trade_type_and_variety(df) additional_stats = analyze_additional_dimensions(df) # 生成可视化图表 create_visualizations(df, ma_stats, time_stats, cross_winrate, cross_profit, cross_return, output_dir) # 保存结果到CSV save_results_to_csv(df, ma_stats, time_stats, output_dir) print("\n" + "="*80) print("分析完成!") print(f"结果保存在: {output_dir}") print("="*80) if __name__ == '__main__': main()