python - 使用 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
由于我有大量数据,因此我希望代码尽可能避免出现任何循环。谢谢!
解决方案
我将您的示例数据扩展为具有 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_entry和long_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)
推荐阅读
- forms - Symfony:处理请求时出现错误?
- javascript - 反应原生
onDismiss 未调用 - php - 在 laravel 中编辑类别时如何显示特定类别的先前值?
- postgresql - PostgreSQL:错误:“NOT”处或附近的语法错误
- javascript - 开玩笑地测试猫鼬查询 - 只有第一个测试块执行查询
- python - 熊猫 - 组合列并一个接一个地放置?
- node.js - 是否可以写入 nodejs 标准输出以使接收的 nodejs 标准输入“on-data”返回单个段?
- php - 将 PHP mssql 更改为 sqlsrv (mssql_fetch_row)
- javascript - 令牌拦截器不再发送原始请求(Angular、Node.js)
- jenkins - 请求中没有包含有效的面包屑 - Jenkins 403