vendredi 17 juin 2016

Pandas: rolling_apply bottleneck

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_profit or stop_loss level 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 the take_profit could 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_profit or stop_loss is reached first.

Question:

Is there a more efficient way to calculate the pnl at each period?

Aucun commentaire:

Enregistrer un commentaire