Equity indices exhibit mean reversion in daily returns, which means that if a significant price movement occurs during one day period, it is likely that the price will move in the opposite direction the following day. There are a couple of factors that might contribute to this phenomenon.

For example, a decline in stock price makes a stock more attractive to investors with everything else being equal because the stock becomes "cheaper." Consequently, increased demand for the stock might cause the stock price to mean-revert. Similarly, an upward movement of a stock price might cause investors to sell the stock as valuation metrics do not look as appealing as before.

Capturing the Mean-Reversion effect

There are a couple of methods that can be used to anticipate mean-reversion, one of which is using the indicator Internal-Bar Strength (IBS). The IBS indicator measures the relative position of the close price within the high-low price range.

IBS = (close-low)/(high-low)

It's possible to use IBS to determine which securities are likely to mean-revert in price the following day.

Constructing the Alpha Model

We apply this mean reversion logic described above to a set of global equity Exchange Traded Funds (ETFs). In the method Initialize() we select and construct our modules.

First, we define our security universe using the ManualUniverseSelectionModel():

## In Initialize()

# Global Equity ETF tickers
tickers = ["ECH","EEM","EFA","EPHE","EPP","EWA","EWC","EWG",

# Create symbol objects
symbols = [Symbol.Create(ticker, SecurityType.Equity, Market.USA) for ticker in tickers]

# Manually curated universe
self.UniverseSettings.Resolution = Resolution.Daily
Then, we create the Alpha model, MeanReversionIBSAlphaModel(), where we rank global equity ETFs on their IBS value and emit insights. The Alpha model contains two methods, __init__() and Update(): class MeanReversionIBSAlphaModel(AlphaModel):
'''Uses ranking of Internal Bar Strength (IBS) to create direction prediction for insights'''

def __init__():
# Set parameters

def Update():
# Rank ETFs on their IBS value and emit insights.
We define our parameters used in the algorithm in the method __init__(), such as the number of stocks to emit insights for, the lookback period and the prediction interval:def __init__(self):
# Set the lookback period
lookback = 1
# Set the prediction interval for insights
self.predictionInterval = Time.Multiply(Extensions.ToTimeSpan(Resolution.Daily), lookback)
# Set the number of stocks emitting insights
self.numberOfStocks = 2

In the method Update(), we implement the ranking mechanism and emit insights for the two ETFs exhibiting the highest and the lowest IBS value.

We calculate the IBS value for each ETF and store the indicator value in the symbolsIBS dictionary. Similarly, we calculate the intraday return for each ETF and store the value in the dictionary returns:

def Update(self, algorithm, data):
# Inititalize list to store insights
insights = []

# Inititalize dictionaries to store IBS and intraday return values
symbolsIBS = dict()
returns = dict()

for security in algorithm.ActiveSecurities.Values:
if security.HasData:
# Retrieve the High and Low price
high = security.High
low = security.Low

# Calculate the difference between High and Low price
hilo = high - low

# Do not consider symbol with zero open and avoid division by zero
if security.Open * hilo != 0:
# Store Internal Bar Strength (IBS) value in dictionary
symbolsIBS[security.Symbol] = (security.Close - low)/hilo
# Store intraday return value in dictionary
returns[security.Symbol] = security.Close/security.Open-1

We rank all ETFs on their IBS value and select two ETFs, the two ETFs that have the highest IBS values and the two that have the lowest IBS values:

## In Update()
# Rank securities with the highest IBS value
ordered = sorted(symbolsIBS.items(), key=lambda kv: (round(kv[1], 6), kv[0]), reverse=True)
highIBS = dict(ordered[0:number_of_stocks]) # Get highest IBS
lowIBS = dict(ordered[-number_of_stocks:]) # Get lowest IBS

Then, we emit insights for the four ETFs using intraday returns as a magnitude prediction:

## In Update()
# Emit "down" insight for the securities with the highest IBS value
for key,value in highIBS.items():
insights.append(Insight.Price(key, self.predictionInterval, InsightDirection.Down, abs(returns[key]), None))

# Emit "up" insight for the securities with the lowest IBS value
for key,value in lowIBS.items():
insights.append(Insight.Price(key, self.predictionInterval, InsightDirection.Up, abs(returns[key]), None))

return insights