You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
buffer_platform/data/trading_calculate.py

535 lines
16 KiB

#!/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)
# 文件1trades_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)
# 文件2analysis.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()