#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 期货结算单数据处理脚本 读取期货结算单Excel,提取期货和期权交易数据,计算盈亏和持仓,合并输出 """ import os import glob import re from datetime import datetime from collections import defaultdict import xlrd import pandas as pd def find_header(sheet, keywords): """ 动态识别表头行 :param sheet: xlrd sheet对象 :param keywords: 关键词列表,如["合约", "品种合约"] :return: 表头行索引 """ for row_idx in range(min(20, sheet.nrows)): # 只扫描前20行 row_values = [str(sheet.cell_value(row_idx, col_idx)).strip() for col_idx in range(sheet.ncols)] row_text = ' '.join(row_values) # 检查是否包含关键词 has_keyword = any(kw in row_text for kw in keywords) # 检查是否包含辅助关键词 has_aux = any(aux in row_text for aux in ["买/卖", "成交价", "权利金"]) if has_keyword and has_aux: return row_idx return None def read_sheet(xls_file, sheet_name, trade_type): """ 读取单个Sheet的数据 :param xls_file: Excel文件路径 :param sheet_name: Sheet名称 :param trade_type: 交易类型(期货/期权) :return: DataFrame """ try: wb = xlrd.open_workbook(xls_file) if sheet_name not in wb.sheet_names(): print(f" 警告: {xls_file} 中未找到 {sheet_name}") return pd.DataFrame() sheet = wb.sheet_by_name(sheet_name) if sheet.nrows == 0: return pd.DataFrame() # 根据类型确定关键词 if trade_type == '期货': keywords = ["合约"] else: # 期权 keywords = ["品种合约"] # 查找表头行 header_row = find_header(sheet, keywords) if header_row is None: print(f" 警告: {sheet_name} 中未找到表头") return pd.DataFrame() # 读取表头 headers = [str(sheet.cell_value(header_row, col_idx)).strip() for col_idx in range(sheet.ncols)] # 读取数据行 data = [] for row_idx in range(header_row + 1, sheet.nrows): row_data = {} first_cell = str(sheet.cell_value(row_idx, 0)).strip() # 跳过合计行 if first_cell == '合计' or first_cell == '': continue for col_idx, header in enumerate(headers): if col_idx < sheet.ncols: value = sheet.cell_value(row_idx, col_idx) # 处理"--" if str(value).strip() == '--': value = None row_data[header] = value # 添加类型标签 row_data['类型'] = trade_type data.append(row_data) df = pd.DataFrame(data) print(f" 读取 {sheet_name}: {len(df)} 条记录") return df except Exception as e: print(f" 错误: 读取 {sheet_name} 失败 - {e}") return pd.DataFrame() def extract_variety(contract): """ 提取品种代码(所有前导字母) :param contract: 合约代码 :return: 品种代码 """ if pd.isna(contract) or not contract: return '' contract = str(contract).strip() match = re.match(r'^([A-Za-z]+)', contract) if match: return match.group(1).upper() return contract[:2].upper() def safe_float(value, default=0.0): """ 安全转换为float """ if pd.isna(value) or value is None or value == '': return default try: return float(value) except (ValueError, TypeError): return default def calc_pnl_futures(df): """ 计算期货盈亏 """ if df.empty: return {'平仓盈亏': 0, '手续费': 0, '净盈亏': 0} # 平仓盈亏 pnl_close = 0 if '平仓盈亏' in df.columns: for val in df['平仓盈亏']: if not pd.isna(val): pnl_close += safe_float(val) # 手续费 commission = 0 if '手续费' in df.columns: for val in df['手续费']: commission += safe_float(val) net_pnl = pnl_close - commission return { '平仓盈亏': pnl_close, '手续费': commission, '净盈亏': net_pnl } def calc_pnl_options(df): """ 计算期权盈亏 """ if df.empty: return {'权利金净收入': 0, '手续费': 0, '净盈亏': 0} # 权利金(带方向) premium_total = 0 if '权利金' in df.columns: for val in df['权利金']: premium_total += safe_float(val) # 手续费 commission = 0 if '手续费' in df.columns: for val in df['手续费']: commission += safe_float(val) net_pnl = premium_total - commission return { '权利金净收入': premium_total, '手续费': commission, '净盈亏': net_pnl } def calc_position_futures(df): """ 计算期货持仓 """ positions = defaultdict(float) if df.empty: return positions for _, row in df.iterrows(): contract = str(row.get('合约', '')).strip() if not contract or contract == '合计': continue bs_flag = str(row.get('买/卖', '')).strip() oc_flag = str(row.get('开/平', '')).strip() volume = safe_float(row.get('手数', 0)) # 买+开 → +手数;卖+开 → -手数;买+平 → -手数;卖+平 → +手数 if '买' in bs_flag and '开' in oc_flag: positions[contract] += volume elif '卖' in bs_flag and '开' in oc_flag: positions[contract] -= volume elif '买' in bs_flag and '平' in oc_flag: positions[contract] -= volume elif '卖' in bs_flag and '平' in oc_flag: positions[contract] += volume # 过滤0持仓 return {k: v for k, v in positions.items() if abs(v) > 0.001} def calc_position_options(df): """ 计算期权持仓 """ positions = defaultdict(float) if df.empty: return positions for _, row in df.iterrows(): contract = str(row.get('品种合约', '')).strip() if not contract or contract == '合计': continue bs_flag = str(row.get('买/卖', '')).strip() volume = safe_float(row.get('成交量', 0)) if '买' in bs_flag: positions[contract] += volume elif '卖' in bs_flag: positions[contract] -= volume # 过滤0持仓 return {k: v for k, v in positions.items() if abs(v) > 0.001} def merge_trades(df_futures, df_options): """ 合并期货和期权交易记录 """ # 统一字段 columns_map_futures = { '合约': '合约代码', '买/卖': '买卖', '成交价': '价格/权利金单价', '手数': '手数/成交量', '成交额': '金额/权利金', '实际成交日期': '实际成交日期', '成交时间': '成交时间' } columns_map_options = { '品种合约': '合约代码', '买/卖': '买卖', '权利金单价': '价格/权利金单价', '成交量': '手数/成交量', '权利金': '金额/权利金', '成交日期': '实际成交日期', '成交时间': '成交时间' } # 处理期货数据 if not df_futures.empty: df_f = df_futures.rename(columns=columns_map_futures).copy() df_f['品种'] = df_f['合约代码'].apply(extract_variety) df_f['方向'] = df_f['买卖'].apply(lambda x: '买' if '买' in str(x) else '卖') df_f['开平仓'] = df_f.get('开/平', '') # 确保必要字段存在 for col in ['平仓盈亏', '手续费']: if col not in df_f.columns: df_f[col] = 0 else: df_f = pd.DataFrame() # 处理期权数据 if not df_options.empty: df_o = df_options.rename(columns=columns_map_options).copy() df_o['品种'] = df_o['合约代码'].apply(extract_variety) df_o['方向'] = df_o['买卖'].apply(lambda x: '买' if '买' in str(x) else '卖') df_o['开平仓'] = '' df_o['平仓盈亏'] = 0 # 确保必要字段存在 for col in ['手续费']: if col not in df_o.columns: df_o[col] = 0 else: df_o = pd.DataFrame() # 合并 common_cols = ['品种', '合约代码', '类型', '买卖', '方向', '成交时间', '实际成交日期', '价格/权利金单价', '手数/成交量', '金额/权利金', '手续费', '平仓盈亏', '开平仓'] if not df_f.empty: df_f = df_f[[c for c in common_cols if c in df_f.columns]] if not df_o.empty: df_o = df_o[[c for c in common_cols if c in df_o.columns]] if not df_f.empty and not df_o.empty: return pd.concat([df_f, df_o], ignore_index=True) elif not df_f.empty: return df_f elif not df_o.empty: return df_o else: return pd.DataFrame() def save_output(trades_df, df_futures, df_options, output_dir): """ 保存输出文件 """ os.makedirs(output_dir, exist_ok=True) # 文件1:trades_merged.csv csv_path = os.path.join(output_dir, 'trades_merged.csv') if not trades_df.empty: trades_df.to_csv(csv_path, index=False, encoding='utf-8-sig') print(f"已保存交易明细: {csv_path}") else: print("警告: 无交易数据") # 计算分析数据 analysis_data = {} # Sheet1: 品种盈亏汇总 variety_summary = [] # 期货品种汇总 if not df_futures.empty: df_f = df_futures.copy() df_f['品种'] = df_f['合约'].apply(extract_variety) for variety, group in df_f.groupby('品种'): pnl = calc_pnl_futures(group) total_volume = sum(safe_float(v) for v in group.get('手数', [])) total_amount = sum(safe_float(v) for v in group.get('成交额', [])) buy_count = sum(1 for v in group.get('买/卖', []) if '买' in str(v)) sell_count = sum(1 for v in group.get('买/卖', []) if '卖' in str(v)) variety_summary.append({ '品种': variety, '类型': '期货', '总手数': total_volume, '总成交额': total_amount, '总手续费': pnl['手续费'], '平仓盈亏': pnl['平仓盈亏'], '净盈亏': pnl['净盈亏'], '买入次数': buy_count, '卖出次数': sell_count }) # 期权品种汇总 if not df_options.empty: df_o = df_options.copy() df_o['品种'] = df_o['品种合约'].apply(extract_variety) for variety, group in df_o.groupby('品种'): pnl = calc_pnl_options(group) total_volume = sum(safe_float(v) for v in group.get('成交量', [])) total_premium = sum(abs(safe_float(v)) for v in group.get('权利金', [])) buy_count = sum(1 for v in group.get('买/卖', []) if '买' in str(v)) sell_count = sum(1 for v in group.get('买/卖', []) if '卖' in str(v)) variety_summary.append({ '品种': variety, '类型': '期权', '总手数': total_volume, '总成交额/权利金': total_premium, '总手续费': pnl['手续费'], '平仓盈亏': 0, '净盈亏': pnl['净盈亏'], '买入次数': buy_count, '卖出次数': sell_count }) df_variety = pd.DataFrame(variety_summary) # Sheet2: 期货持仓 futures_positions = calc_position_futures(df_futures) futures_pos_data = [] for contract, pos in futures_positions.items(): futures_pos_data.append({ '合约': contract, '净持仓': pos, '方向': '多' if pos > 0 else '空' }) df_futures_pos = pd.DataFrame(futures_pos_data) # Sheet3: 期权持仓 options_positions = calc_position_options(df_options) options_pos_data = [] for contract, pos in options_positions.items(): options_pos_data.append({ '品种合约': contract, '净持仓': pos, '方向': '权利' if pos > 0 else '义务' }) df_options_pos = pd.DataFrame(options_pos_data) # 文件2:analysis.xlsx xlsx_path = os.path.join(output_dir, 'analysis.xlsx') with pd.ExcelWriter(xlsx_path, engine='openpyxl') as writer: if not df_variety.empty: df_variety.to_excel(writer, sheet_name='品种盈亏汇总', index=False) if not df_futures_pos.empty: df_futures_pos.to_excel(writer, sheet_name='期货持仓', index=False) if not df_options_pos.empty: df_options_pos.to_excel(writer, sheet_name='期权持仓', index=False) print(f"已保存分析结果: {xlsx_path}") return { 'variety_summary': variety_summary, 'futures_positions': futures_positions, 'options_positions': options_positions } def print_summary(df_futures, df_options, results): """ 打印摘要 """ print("\n" + "="*50) print("=== 交易摘要 ===") # 期货统计 if not df_futures.empty: pnl_f = calc_pnl_futures(df_futures) varieties_f = df_futures['合约'].apply(extract_variety).nunique() print(f"期货:{len(df_futures)}笔交易,{varieties_f}个品种") print(f" 总盈亏:{pnl_f['净盈亏']:.2f}元(含手续费{pnl_f['手续费']:.2f}元)") else: print("期货:无交易") # 期权统计 if not df_options.empty: pnl_o = calc_pnl_options(df_options) varieties_o = df_options['品种合约'].apply(extract_variety).nunique() print(f"期权:{len(df_options)}笔交易,{varieties_o}个品种") print(f" 总盈亏:{pnl_o['净盈亏']:.2f}元(含手续费{pnl_o['手续费']:.2f}元)") else: print("期权:无交易") # 持仓统计 print("\n=== 收盘持仓 ===") futures_pos = results['futures_positions'] if futures_pos: for contract, pos in sorted(futures_pos.items()): direction = '多' if pos > 0 else '空' print(f"期货:{contract} {direction} {abs(pos):.0f}手") else: print("期货:无持仓") options_pos = results['options_positions'] if options_pos: for contract, pos in sorted(options_pos.items()): direction = '权利' if pos > 0 else '义务' print(f"期权:{contract} {direction} {abs(pos):.0f}手") else: print("期权:无持仓") print("="*50 + "\n") def main(): """ 主函数 """ # 数据目录 data_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data') output_dir = os.path.dirname(os.path.abspath(__file__)) print(f"数据目录: {data_dir}") print(f"输出目录: {output_dir}\n") # 查找所有xls文件 xls_files = glob.glob(os.path.join(data_dir, '*.xls')) if not xls_files: print(f"错误: 在 {data_dir} 中未找到 .xls 文件") return print(f"找到 {len(xls_files)} 个结算单文件\n") # 读取所有文件 all_futures = [] all_options = [] for xls_file in sorted(xls_files): filename = os.path.basename(xls_file) print(f"处理: {filename}") # 读取期货成交明细 df_f = read_sheet(xls_file, '成交明细', '期货') if not df_f.empty: all_futures.append(df_f) # 读取期权成交明细 df_o = read_sheet(xls_file, '期权成交明细', '期权') if not df_o.empty: all_options.append(df_o) # 合并数据 df_futures = pd.concat(all_futures, ignore_index=True) if all_futures else pd.DataFrame() df_options = pd.concat(all_options, ignore_index=True) if all_options else pd.DataFrame() print(f"\n汇总: 期货 {len(df_futures)} 条, 期权 {len(df_options)} 条\n") # 合并交易记录 trades_merged = merge_trades(df_futures, df_options) # 保存输出 results = save_output(trades_merged, df_futures, df_options, output_dir) # 打印摘要 print_summary(df_futures, df_options, results) if __name__ == '__main__': main()