Mean reversion strategy that dynamically adjusts for changing volatility

Now, let's apply the previously introduced concepts of using a volatility measure to adjust the number of days used in Fast and Slow EMA and using a volatility-adjusted APO entry signal. We will use the standard deviation (STDEV) indicator we explored in Chapter 2, Deciphering the Markets with Technical Analysis, as a measure of volatility. Let's observe the output of that indicator quickly to recap the Google dataset:

From the output, it seems like volatility measure ranges from somewhere between $8 over 20 days to $40 over 20 days, with $15 over 20 days being the average. So we will use a volatility factor that ranges from 0 to 1, by designing it to be , where values closer to 0 indicate very low volatility, values around 1 indicate normal volatility, and values above 1 indicate above-normal volatility. The way in which we incorporate STDEV into our strategy is through the following changes:

  • Instead of having static K_FAST and K_SLOW smoothing factors for the fast and slow EMA, we will instead make them additionally a function of volatility and use K_FAST * stdev_factor and K_SLOW * stdev_factor, to make them more reactive to newest observations during periods of higher than normal volatility, which makes intuitive sense.
  • Instead of using static APO_VALUE_FOR_BUY_ENTRY and APO_VALUE_FOR_SELL_ENTRY thresholds for entering positions based on the primary trading signal APO, we will also incorporate volatility to have dynamic thresholds APO_VALUE_FOR_BUY_ENTRY * stdev_factor and APO_VALUE_FOR_SELL_ENTRY * stdev_factor. This makes us less aggressive in entering positions during periods of higher volatility, by increasing the threshold for entry by a factor of volatility, which also makes intuitive sense based on what we discussed in the previous section.
  • Finally, we will incorporate volatility in one last threshold and that is by having a dynamic expected profit threshold to lock in profit in a position. In this case, instead of using the static MIN_PROFIT_TO_CLOSE threshold, we will use a dynamic MIN_PROFIT_TO_CLOSE / stdev_factor. Here, the idea is to be more aggressive in exciting positions during periods of increased volatility, because as we discussed before, during periods of higher than normal volatility, it is riskier to hold on to positions for longer periods of time.

Let's look at the modifications needed to the basic mean reversion strategy to achieve this. First, we need some code to track and update the volatility measure (STDEV):

import statistics as stats
import math as math

# Constants/variables that are used to compute standard deviation as a volatility measure
SMA_NUM_PERIODS = 20 # look back period
price_history = [] # history of prices

Then the main strategy loop simply becomes this, while the position and PnL management section of the strategy remains the same:

close=data['Close']
for close_price in close:
price_history.append(close_price)
if len(price_history) > SMA_NUM_PERIODS: # we track at most 'time_period' number of prices
del (price_history[0])

sma = stats.mean(price_history)
variance = 0 # variance is square of standard deviation
for hist_price in price_history:
variance = variance + ((hist_price - sma) ** 2)

stdev = math.sqrt(variance / len(price_history))
stdev_factor = stdev/15
if stdev_factor == 0:
stdev_factor = 1

# This section updates fast and slow EMA and computes APO trading signal
if (ema_fast == 0): # first observation
ema_fast = close_price
ema_slow = close_price
else:
ema_fast = (close_price - ema_fast) * K_FAST*stdev_factor + ema_fast
ema_slow = (close_price - ema_slow) * K_SLOW*stdev_factor + ema_slow

ema_fast_values.append(ema_fast)
ema_slow_values.append(ema_slow)

apo = ema_fast - ema_slow
apo_values.append(apo)

And as we said, the use of the trading signal to manage positions has the same trading logic as before. First, let's look at the sell trade logic:

  # We will perform a sell trade at close_price if the following conditions are met:
# 1. The APO trading signal value is above Sell-Entry threshold and the difference between last trade-price and current-price is different enough.
# 2. We are long( positive position ) and either APO trading signal value is at or above 0 or current position is profitable enough to lock profit.
if ((apo > APO_VALUE_FOR_SELL_ENTRY*stdev_factor and abs(close_price - last_sell_price) > MIN_PRICE_MOVE_FROM_LAST_TRADE*stdev_factor) # APO above sell entry threshold, we should sell
or
(position > 0 and (apo >= 0 or open_pnl > MIN_PROFIT_TO_CLOSE/stdev_factor))): # long from negative APO and APO has gone positive or position is profitable, sell to close position
orders.append(-1) # mark the sell trade
last_sell_price = close_price
position -= NUM_SHARES_PER_TRADE # reduce position by the size of this trade
sell_sum_price_qty += (close_price*NUM_SHARES_PER_TRADE) # update vwap sell-price
sell_sum_qty += NUM_SHARES_PER_TRADE
print( "Sell ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position )

Now, let's look at similar logic for buy trades:

  # We will perform a buy trade at close_price if the following conditions are met:
# 1. The APO trading signal value is below Buy-Entry threshold and the difference between last trade-price and current-price is different enough.
# 2. We are short( negative position ) and either APO trading signal value is at or below 0 or current position is profitable enough to lock profit.
elif ((apo < APO_VALUE_FOR_BUY_ENTRY*stdev_factor and abs(close_price - last_buy_price) > MIN_PRICE_MOVE_FROM_LAST_TRADE*stdev_factor) # APO below buy entry threshold, we should buy
or
(position < 0 and (apo <= 0 or open_pnl > MIN_PROFIT_TO_CLOSE/stdev_factor))): # short from positive APO and APO has gone negative or position is profitable, buy to close position
orders.append(+1) # mark the buy trade
last_buy_price = close_price
position += NUM_SHARES_PER_TRADE # increase position by the size of this trade
buy_sum_price_qty += (close_price*NUM_SHARES_PER_TRADE) # update the vwap buy-price
buy_sum_qty += NUM_SHARES_PER_TRADE
print( "Buy ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position )
else:
# No trade since none of the conditions were met to buy or sell
orders.append(0)

Let's compare PnLs from a static constant thresholds mean reversion strategy and a volatility-adjusted mean reversion strategy to see whether we improved performance or not:

In this case, adjusting the trading strategy for volatility increases the strategy performance by 200%!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.252.23