112 lines
5.0 KiB
Python
112 lines
5.0 KiB
Python
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||
#
|
||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
# you may not use this file except in compliance with the License.
|
||
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||
#
|
||
# Unless required by applicable law or agreed to in writing, software
|
||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
# See the License for the specific language governing permissions and
|
||
# limitations under the License.
|
||
|
||
from AlgorithmImports import *
|
||
|
||
#
|
||
# Equity indices exhibit mean reversion in daily returns. The Internal Bar Strength indicator (IBS),
|
||
# which relates the closing price of a security to its daily range can be used to identify overbought
|
||
# and oversold securities.
|
||
#
|
||
# This alpha ranks 33 global equity ETFs on its IBS value the previous day and predicts for the following day
|
||
# that the ETF with the highest IBS value will decrease in price, and the ETF with the lowest IBS value
|
||
# will increase in price.
|
||
#
|
||
# Source: Kakushadze, Zura, and Juan Andrés Serur. “4. Exchange-Traded Funds (ETFs).” 151 Trading Strategies, Palgrave Macmillan, 2018, pp. 90–91.
|
||
#
|
||
# This alpha is part of the Benchmark Alpha Series created by QuantConnect which are open sourced so the community and client funds can see an example of an alpha.
|
||
#
|
||
|
||
class GlobalEquityMeanReversionIBSAlpha(QCAlgorithm):
|
||
|
||
def initialize(self):
|
||
|
||
self.set_start_date(2018, 1, 1)
|
||
|
||
self.set_cash(100000)
|
||
|
||
# Set zero transaction fees
|
||
self.set_security_initializer(lambda security: security.set_fee_model(ConstantFeeModel(0)))
|
||
|
||
# Global Equity ETF tickers
|
||
tickers = ["ECH","EEM","EFA","EPHE","EPP","EWA","EWC","EWG",
|
||
"EWH","EWI","EWJ","EWL","EWM","EWM","EWO","EWP",
|
||
"EWQ","EWS","EWT","EWU","EWY","EWZ","EZA","FXI",
|
||
"GXG","IDX","ILF","EWM","QQQ","RSX","SPY","THD"]
|
||
|
||
symbols = [Symbol.create(ticker, SecurityType.EQUITY, Market.USA) for ticker in tickers]
|
||
|
||
# Manually curated universe
|
||
self.universe_settings.resolution = Resolution.DAILY
|
||
self.set_universe_selection(ManualUniverseSelectionModel(symbols))
|
||
|
||
# Use GlobalEquityMeanReversionAlphaModel to establish insights
|
||
self.set_alpha(MeanReversionIBSAlphaModel())
|
||
|
||
# Equally weigh securities in portfolio, based on insights
|
||
self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel())
|
||
|
||
# Set Immediate Execution Model
|
||
self.set_execution(ImmediateExecutionModel())
|
||
|
||
# Set Null Risk Management Model
|
||
self.set_risk_management(NullRiskManagementModel())
|
||
|
||
|
||
class MeanReversionIBSAlphaModel(AlphaModel):
|
||
'''Uses ranking of Internal Bar Strength (IBS) to create direction prediction for insights'''
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
lookback = kwargs['lookback'] if 'lookback' in kwargs else 1
|
||
resolution = kwargs['resolution'] if 'resolution' in kwargs else Resolution.DAILY
|
||
self.prediction_interval = Time.multiply(Extensions.to_time_span(resolution), lookback)
|
||
self.number_of_stocks = kwargs['number_of_stocks'] if 'number_of_stocks' in kwargs else 2
|
||
|
||
def update(self, algorithm, data):
|
||
|
||
insights = []
|
||
symbols_ibs = dict()
|
||
returns = dict()
|
||
|
||
for security in algorithm.active_securities.values:
|
||
if security.has_data:
|
||
high = security.high
|
||
low = security.low
|
||
hilo = high - low
|
||
|
||
# Do not consider symbol with zero open and avoid division by zero
|
||
if security.open * hilo != 0:
|
||
# Internal bar strength (IBS)
|
||
symbols_ibs[security.symbol] = (security.close - low)/hilo
|
||
returns[security.symbol] = security.close/security.open-1
|
||
|
||
# Number of stocks cannot be higher than half of symbols_ibs length
|
||
number_of_stocks = min(int(len(symbols_ibs)/2), self.number_of_stocks)
|
||
if number_of_stocks == 0:
|
||
return []
|
||
|
||
# Rank securities with the highest IBS value
|
||
ordered = sorted(symbols_ibs.items(), key=lambda kv: (round(kv[1], 6), kv[0]), reverse=True)
|
||
high_ibs = dict(ordered[0:number_of_stocks]) # Get highest IBS
|
||
low_ibs = dict(ordered[-number_of_stocks:]) # Get lowest IBS
|
||
|
||
# Emit "down" insight for the securities with the highest IBS value
|
||
for key,value in high_ibs.items():
|
||
insights.append(Insight.price(key, self.prediction_interval, InsightDirection.DOWN, abs(returns[key]), None))
|
||
|
||
# Emit "up" insight for the securities with the lowest IBS value
|
||
for key,value in low_ibs.items():
|
||
insights.append(Insight.price(key, self.prediction_interval, InsightDirection.UP, abs(returns[key]), None))
|
||
|
||
return insights
|