You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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),百万级交易数据也能在秒级完成处理。核心逻辑是:

  1. 先按时间排序拆分买卖单子集
  2. 分别完成多单找后续卖单、空单找后续买单的匹配
  3. 按开仓时间顺序分配未被占用的平仓单,避免重复配对
  4. 拼接开平仓字段得到最终可导出的结果

可直接运行的代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 18:06:27