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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/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()