I am calculating a dataframe of profit/loss amounts for every row in a dataframe of prices.
The requirements for this piece of logic are as follows:
- We buy/sell the asset at the current time period.
- We hold the asset for
holding_period. - If, during the holding period, the price exceeds
take_profit, exit at that price for a profit. - If, during the holding period, the price exceeds
stop_loss, exit at that price for a loss. - The first
take_profitorstop_losslevel seen determines whether we exit at a profit or loss. - If neither the take profit or stop loss are reached, exit at the last price in the holding period.
The way I have implemented this is to use pandas.rolling_apply, which applies a provided function onto a rolling window of each series in the dataframe.
Given rolling_apply calls a function for every row and column combination in the dataframe, it is a serious bottleneck.
I am wondering if there are better ways to achieve this using other pandas/numpy functionality?
This is the current implementation:
def potential_pnl(prices, side, periods, take_profit=np.nan, stop_loss=np.nan):
# set sign depending on direction of price movement required by BUY/SELL
if side == Side.SELL:
take_profit *= -1
else:
stop_loss *= -1
def period_potential_pnl(window):
# enter at the first price, rest of the window are possible exit prices
entry_price = window[0]
exit_prices = window[1:]
take_profit_price = entry_price + take_profit
stop_loss_price = entry_price + stop_loss
# calculate array of bools showing where take_profit/stop_loss is reached
if side == Side.BUY:
filtered = exit_prices[ (exit_prices >= take_profit_price) |
(exit_prices <= stop_loss_price) ]
else:
filtered = exit_prices[ (exit_prices <= take_profit_price) |
(exit_prices >= stop_loss_price) ]
# if neither take_profit/stop_loss is reached, exit at the last price
# otherwise exit at the first price which exceeds take_profit/stop_loss
if len(filtered) == 0:
exit_price = exit_prices[-1]
else:
exit_price = filtered[0]
exit_pnl = exit_price - entry_price
if side == Side.SELL:
exit_pnl *= -1
return exit_pnl
# apply `period_potential_pnl` onto the dataframe
pnl = pd.rolling_apply(prices, periods + 1, period_potential_pnl)
# shift back by periods so the exit pnl is lined up with the entry price
pnl = pnl.shift(-periods)[:-periods]
return pnl
Things I have tried:
I initially used pandas.rolling_max and pandas.rolling_min to determine whether the take_profit or stop_loss was reached.
The problem I had with this approach was two-fold:
- You can't use the max as the exit price for
take_profit, because thetake_profitcould very well have been reached at a lower price; it is impossible to know in realtime what the max of the holding period will be. - You can't determine which of the
take_profitorstop_lossis reached first.
Question:
Is there a more efficient way to calculate the pnl at each period?
Aucun commentaire:
Enregistrer un commentaire