Python Pandas匹配交易买卖对并追加反向行到对应行末尾
交易逐笔P&L配对高效实现方案
我有一个存储交易列表的DataFrame,目标是实现逐笔交易的P&L(盈亏)统计,同时将数据处理为可直接导入其他软件的格式。
具体处理逻辑如下:
- 做多场景:针对每笔*Buy(买入)交易,查找时间上距离最近的下一笔Sell(卖出)*交易,将该卖出交易行的全部内容追加到对应买入行的末尾
- 做空场景:针对每笔*Sell(卖出)交易,查找时间上距离最近的下一笔Buy(买入)*交易,将该买入交易行追加到对应卖出行的末尾
输入DataFrame示例
| |Side|Qty|Price |Time |Commission| ID | 5min_time | |-------|----|---|-------|------------------------------------|--------------------------------------| |228 |Buy |1 |4087.00|2022-06-10 00:16:00+08:00|0.4 | 727127819| 2022-06-10 00:15:00+08:00| |229 |Buy |1 |4087.00|2022-06-10 00:16:00+08:00|0.4 | 727127819| 2022-06-10 00:15:00+08:00| |230 |Buy |1 |4098.75|2022-06-10 00:57:00+08:00|0.4 | 727141021| 2022-06-10 00:55:00+08:00| |231 |Sell|1 |4094.25|2022-06-10 00:56:00+08:00|0.4 | 727140488| 2022-06-10 00:55:00+08:00| |232 |Sell|1 |4094.25|2022-06-10 00:56:00+08:00|0.4 | 727140488| 2022-06-10 00:55:00+08:00| |233 |Buy |1 |4104.25|2022-06-10 00:59:00+08:00|0.4 | 727141284| 2022-06-10 00:55:00+08:00| |234 |Sell|1 |4085.75|2022-06-10 01:43:00+08:00|0.4 | 727156519| 2022-06-10 01:40:00+08:00| |235 |Sell|1 |4085.75|2022-06-10 01:43:00+08:00|0.4 | 727156519| 2022-06-10 01:40:00+08:00| |236 |Sell|1 |4085.75|2022-06-10 01:43:00+08:00|0.4 | 727156519| 2022-06-10 01:40:00+08:00| |237 |Sell|1 |4085.75|2022-06-10 01:43:00+08:00|0.4 | 727156519| 2022-06-10 01:40:00+08:00| |238 |Buy |1 |4059.75|2022-06-10 03:04:00+08:00|0.4 | 727156591| 2022-06-10 03:00:00+08:00| |239 |Buy |1 |4059.75|2022-06-10 03:04:00+08:00|0.4 | 727156591| 2022-06-10 03:00:00+08:00|
期望输出结果
| |Side|Qty|Price |Time |Commission| ID | 5min_time | |-------|----|---|-------|------------------------------------|--------------------------------------| |228 |Buy |1 |4087.00|2022-06-10 00:16:00+08:00|0.4 | 727127819| 2022-06-10 00:15:00+08:00|231 |Sell|1 |4094.25|2022-06-10 00:56:00+08:00|0.4 | 727140488| 2022-06-10 00:55:00+08:00| |229 |Buy |1 |4087.00|2022-06-10 00:16:00+08:00|0.4 | 727127819| 2022-06-10 00:15:00+08:00|232 |Sell|1 |4094.25|2022-06-10 00:56:00+08:00|0.4 | 727140488| 2022-06-10 00:55:00+08:00| |230 |Buy |1 |4098.75|2022-06-10 00:57:00+08:00|0.4 | 727141021| 2022-06-10 00:55:00+08:00|234 |Sell|1 |4085.75|2022-06-10 01:43:00+08:00|0.4 | 727156519| 2022-06-10 01:40:00+08:00| |233 |Buy |1 |4104.25|2022-06-10 00:59:00+08:00|0.4 | 727141284| 2022-06-10 00:55:00+08:00|235 |Sell|1 |4085.75|2022-06-10 01:43:00+08:00|0.4 | 727156519| 2022-06-10 01:40:00+08:00| |236 |Sell|1 |4085.75|2022-06-10 01:43:00+08:00|0.4 | 727156519| 2022-06-10 01:40:00+08:00|238 |Buy |1 |4059.75|2022-06-10 03:04:00+08:00|0.4 | 727156591| 2022-06-10 03:00:00+08:00| |237 |Sell|1 |4085.75|2022-06-10 01:43:00+08:00|0.4 | 727156519| 2022-06-10 01:40:00+08:00|239 |Buy |1 |4059.75|2022-06-10 03:04:00+08:00|0.4 | 727156591| 2022-06-10 03:00:00+08:00|
逐行循环遍历、逐个追加的实现方式执行效率极低,以下是符合Pythonic风格的高效实现方案:
实现思路
用pandas内置的merge_asof做时间维度的向前最近邻匹配,避免O(n²)复杂度的逐行循环,整体时间复杂度为O(n log n),百万级交易数据也能在秒级完成处理。核心逻辑是:
- 先按时间排序拆分买卖单子集
- 分别完成多单找后续卖单、空单找后续买单的匹配
- 按开仓时间顺序分配未被占用的平仓单,避免重复配对
- 拼接开平仓字段得到最终可导出的结果
可直接运行的代码
import pandas as pd # 数据预处理:确保时间格式正确、按时间升序排列 df = df.copy() df['Time'] = pd.to_datetime(df['Time']) df = df.sort_values('Time').reset_index(drop=False) # 保留原行索引做唯一标识 # 拆分多空开仓、平仓数据集 buy_open = df[df['Side'] == 'Buy'].add_suffix('_open') sell_close = df[df['Side'] == 'Sell'].add_suffix('_close') sell_open = df[df['Side'] == 'Sell'].add_suffix('_open') buy_close = df[df['Side'] == 'Buy'].add_suffix('_close') # 做多配对:买入单匹配时间最近的后续卖出单 long_pair = pd.merge_asof( buy_open, sell_close, left_on='Time_open', right_on='Time_close', direction='forward', allow_exact_matches=False ).dropna(subset=['index_close']) # 做空配对:卖出单匹配时间最近的后续买入单 short_pair = pd.merge_asof( sell_open, buy_close, left_on='Time_open', right_on='Time_close', direction='forward', allow_exact_matches=False ).dropna(subset=['index_close']) # 按开仓时间顺序分配平仓单,每个平仓单仅配对一次 used_close = set() valid_pairs = [] all_pairs = pd.concat([long_pair, short_pair]).sort_values('Time_open').reset_index(drop=True) for _, row in all_pairs.iterrows(): close_id = row['index_close'] if close_id not in used_close: valid_pairs.append(row) used_close.add(close_id) # 整理列顺序,输出和示例格式完全对齐的结果 res = pd.DataFrame(valid_pairs) open_cols = [c for c in res.columns if c.endswith('_open')] close_cols = [c for c in res.columns if c.endswith('_close')] res = res[open_cols + close_cols] res.columns = [c.replace('_open', '') for c in open_cols] + [f'close_{c.replace("_close", "")}' for c in close_cols]
说明:如果需要按交易品种、合约ID等维度分组配对,只需要在
merge_asof中添加by参数传入分组字段即可;如果允许同一时间戳的交易互相配对,把allow_exact_matches改为True即可。
内容的提问来源于stack exchange,提问作者Dmitry
相关产品推荐
相关产品推荐

