首页 > 解决方案 > 使用 Pandas Dataframe 计算每笔交易的损益 (PnL)

问题描述

我有这个熊猫数据框,其中 long_entry 或 short_entry 中的 1 表示当时进入交易,对应的多头/空头头寸。long_exit 或 short_exit 中的 1 表示退出交易。我可以知道如何计算要显示在新列 df['pnl_per_trade'] 中的每笔交易的 PnL?

此回测在任何时间点最多只能有 1 笔交易/头寸。

下面是我的数据框。如我们所见,多头交易于 2019 年 2 月 26 日进入并于 2019 年 1 月 3 日结束,盈亏为 64.45 美元,而空头交易于 2019 年 4 月 3 日进入并于 2019 年 5 月 3 日结束盈亏为 -$119.11(亏损)。

        date    price       long_entry  long_exit   short_entry short_exit
0   24/2/2019   4124.25           0          0           0              0
1   25/2/2019   4130.67           0          0           0              0
2   26/2/2019   4145.67           1          0           0              0
3   27/2/2019   4180.10           0          0           0              0
4   28/2/2019   4200.05           0          0           0              0
5   1/3/2019    4210.12           0          1           0              0
6   2/3/2019    4198.10           0          0           0              0
7   3/3/2019    4210.34           0          0           0              0
8   4/3/2019    4100.12           0          0           1              0
9   5/3/2019    4219.23           0          0           0              1

我希望有这样的输出:

        date    price       long_entry  long_exit   short_entry short_exit  pnl
0   24/2/2019   4124.25           0          0           0             0    NaN
1   25/2/2019   4130.67           0          0           0             0    NaN
2   26/2/2019   4145.67           1          0           0             0  64.45
3   27/2/2019   4180.10           0          0           0             0    NaN
4   28/2/2019   4200.05           0          0           0             0    NaN
5   1/3/2019    4210.12           0          1           0             0    NaN
6   2/3/2019    4198.10           0          0           0             0    NaN
7   3/3/2019    4210.34           0          0           0             0    NaN
8   4/3/2019    4100.12           0          0           1             0 -119.11
9   5/3/2019    4219.23           0          0           0             1    NaN

由于我有大量数据,因此我希望代码尽可能避免出现任何循环。谢谢!

标签: pythonpandasback-testing

解决方案


我将您的示例数据扩展为具有 2 个长 PnL 值并将日期列更改为DateTime

df = pd.DataFrame(data=[
    [ '24/2/2019', 4124.25, 0, 0, 0, 0 ],
    [ '25/2/2019', 4130.67, 0, 0, 0, 0 ],
    [ '26/2/2019', 4145.67, 1, 0, 0, 0 ],
    [ '27/2/2019', 4180.10, 0, 0, 0, 0 ],
    [ '28/2/2019', 4200.05, 0, 0, 0, 0 ],
    [ '1/3/2019',  4210.12, 0, 1, 0, 0 ],
    [ '2/3/2019',  4198.10, 0, 0, 0, 0 ],
    [ '3/3/2019',  4210.34, 0, 0, 0, 0 ],
    [ '4/3/2019',  4100.12, 0, 0, 1, 0 ],
    [ '5/3/2019',  4219.23, 0, 0, 0, 1 ],
    [ '6/3/2019',  4210.00, 1, 0, 0, 0 ],
    [ '7/3/2019',  4212.00, 0, 0, 0, 0 ],
    [ '8/3/2019',  4214.00, 0, 1, 0, 0 ]],
    columns=['date','price', 'long_entry', 'long_exit',
        'short_entry', 'short_exit'])
df.date = pd.to_datetime(df.date)

下一步是生成df2仅包含长条目开始和结束的行(实际上只需要日期价格 列,但出于说明目的,我还包括long_entrylong_exit

df2 = df.query('long_entry > 0 or long_exit > 0').iloc[:,0:4]; df2

结果(对于我的数据)是:

         date    price  long_entry  long_exit
2  2019-02-26  4145.67           1          0
5  2019-01-03  4210.12           0          1
10 2019-06-03  4210.00           1          0
12 2019-08-03  4214.00           0          1

然后我们必须定义一个即将应用的函数:

def fn(src):
    return pd.Series([src.iloc[0, 0], src.iloc[1, 1] - src.iloc[0, 1]])

下一步是将上述函数应用于连续的行对(进入和退出),设置列名并将日期 列更改为索引:

lProf = df2.groupby(np.arange( len(df2.index)) // 2).apply(fn)
lProf.columns = ['date', 'pnl']
lProf.set_index('date', inplace=True)

结果是:

             pnl
date             
2019-02-26  64.45
2019-06-03   4.00

到目前为止,我们已经从条目中插入了数据。现在是时候为条目生成类似的 DataFrame,应用与以前相同的函数:

df2 = df.query('short_entry > 0 or short_exit > 0').iloc[:,[0, 1, 4, 5]]
sProf = df2.groupby(np.arange( len(df2.index)) // 2).apply(fn)
sProf.columns = ['date', 'pnl']
sProf.set_index('date', inplace=True)

但是这次我们必须改变接收值的符号:

sProf = -sProf

结果是:

               pnl
date              
2019-04-03 -119.11

在我们将结果添加到主 DataFrame 之前,我们必须将 日期列设置为索引:

df.set_index('date', inplace=True)

现在我们添加条目的结果:

df['pnl'] = lProf

创建了一个新列,所以现在,要添加条目的结果,我们必须更新

df.update(sProf)

如果要将日期作为常规列返回,请运行:

df.reset_index(inplace=True)

推荐阅读