| Overall Statistics |
|
Total Trades 274 Average Win 0.47% Average Loss -0.65% Compounding Annual Return -16.412% Drawdown 21.900% Expectancy -0.184 Net Profit -16.576% Sharpe Ratio -1.026 Probabilistic Sharpe Ratio 0.597% Loss Rate 53% Win Rate 47% Profit-Loss Ratio 0.73 Alpha 0 Beta 0 Annual Standard Deviation 0.109 Annual Variance 0.012 Information Ratio -1.026 Tracking Error 0.109 Treynor Ratio 0 Total Fees $8661.06 |
# 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 clr import AddReference
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Logging")
AddReference("QuantConnect.Common")
from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Logging import Log
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Alphas import *
from datetime import timedelta
from enum import Enum
class RsiAlphaModel(AlphaModel):
'''Uses Wilder's RSI to create insights.
Using default settings, a cross over below 30 or above 70 will trigger a new insight.'''
def __init__(self,
period = 14,
resolution = Resolution.Daily):
'''Initializes a new instance of the RsiAlphaModel class
Args:
period: The RSI indicator period'''
self.period = period
self.resolution = resolution
self.insightPeriod = Time.Multiply(Extensions.ToTimeSpan(resolution), period)
self.symbolDataBySymbol ={}
self.lowcutoff = 30.0
self.highcutoff = 70.0
self.buffer = 5.0
resolutionString = Extensions.GetEnumString(resolution, Resolution)
self.Name = '{}({},{})'.format(self.__class__.__name__, period, resolutionString)
def Update(self, algorithm, data):
'''Updates this alpha model with the latest data from the algorithm.
This is called each time the algorithm receives data for subscribed securities
Args:
algorithm: The algorithm instance
data: The new data available
Returns:
The new insights generated'''
insights = []
for symbol, symbolData in self.symbolDataBySymbol.items():
rsi = symbolData.RSI
previous_state = symbolData.State
state = self.GetState(rsi, previous_state)
if state != previous_state and rsi.IsReady:
if state == State.TrippedLow:
insights.append(Insight.Price(symbol, self.insightPeriod, InsightDirection.Up, 0.01*(self.lowcutoff-rsi.Current.Value)/self.lowcutoff))
if state == State.TrippedHigh:
insights.append(Insight.Price(symbol, self.insightPeriod, InsightDirection.Down, 0.01*(rsi.Current.Value-self.highcutoff)/(100-self.highcutoff)))
symbolData.State = state
return insights
def OnSecuritiesChanged(self, algorithm, changes):
'''Cleans out old security data and initializes the RSI for any newly added securities.
Event fired each time the we add/remove securities from the data feed
Args:
algorithm: The algorithm instance that experienced the change in securities
changes: The security additions and removals from the algorithm'''
# clean up data for removed securities
symbols = [ x.Symbol for x in changes.RemovedSecurities ]
if len(symbols) > 0:
for subscription in algorithm.SubscriptionManager.Subscriptions:
if subscription.Symbol in symbols:
self.symbolDataBySymbol.pop(subscription.Symbol, None)
subscription.Consolidators.Clear()
# initialize data for added securities
addedSymbols = [ x.Symbol for x in changes.AddedSecurities if x.Symbol not in self.symbolDataBySymbol]
if len(addedSymbols) == 0: return
history = algorithm.History(addedSymbols, self.period, self.resolution)
for symbol in addedSymbols:
rsi = algorithm.RSI(symbol, self.period, MovingAverageType.Wilders, self.resolution)
if not history.empty:
ticker = SymbolCache.GetTicker(symbol)
if ticker not in history.index.levels[0]:
Log.Trace(f'RsiAlphaModel.OnSecuritiesChanged: {ticker} not found in history data frame.')
continue
for tuple in history.loc[ticker].itertuples():
rsi.Update(tuple.Index, tuple.close)
self.symbolDataBySymbol[symbol] = SymbolData(symbol, rsi)
def GetState(self, rsi, previous):
''' Determines the new state. This is basically cross-over detection logic that
includes considerations for bouncing using the configured bounce tolerance.'''
if rsi.Current.Value > self.highcutoff :
return State.TrippedHigh
if rsi.Current.Value < self.lowcutoff :
return State.TrippedLow
if previous == State.TrippedLow:
if rsi.Current.Value > (self.lowcutoff+self.buffer):
return State.Middle
if previous == State.TrippedHigh:
if rsi.Current.Value < (self.highcutoff-self.buffer):
return State.Middle
return previous
class SymbolData:
'''Contains data specific to a symbol required by this model'''
def __init__(self, symbol, rsi):
self.Symbol = symbol
self.RSI = rsi
self.State = State.Middle
class State(Enum):
'''Defines the state. This is used to prevent signal spamming and aid in bounce detection.'''
TrippedLow = 0
Middle = 1
TrippedHigh = 2class PanicToExcuberanceAlphaModel(AlphaModel):
def __init__(self, algorithm,
fastPeriod = 5,
slowPeriod = 252,
resolution = Resolution.Daily):
# Set resolution for EMA Indicator
self.resolution = resolution
self.algorithm = algorithm
self.slow = fastPeriod
self.fast = slowPeriod
# Add custom data from Quandl
self.yearlyHighs = algorithm.AddData(QuandlData, 'URC/NASDAQ_52W_HI',self.resolution).Symbol
self.yearlyLows = algorithm.AddData(QuandlData, 'URC/NASDAQ_52W_LOW', self.resolution).Symbol
# Add indicators using custom data
self.highema = algorithm.EMA(self.yearlyHighs, self.fast, self.resolution)
self.highmax = algorithm.MAX(self.yearlyHighs, self.slow, self.resolution)
self.highmin = algorithm.MIN(self.yearlyHighs, self.slow, self.resolution)
self.lowema = algorithm.EMA(self.yearlyLows, self.fast, self.resolution)
self.lowmax = algorithm.MAX(self.yearlyLows, self.slow, self.resolution)
self.lowmin = algorithm.MIN(self.yearlyLows, self.slow, self.resolution)
resolutionString = Extensions.GetEnumString(resolution, Resolution)
self.Name = '{}({},{},{})'.format(self.__class__.__name__, fastPeriod, slowPeriod, resolutionString)
# Set the prediction period
self.period = timedelta(days=30)
def Update(self, algorithm, data):
insights = []
# Return if no data
if not (data.ContainsKey(self.yearlyHighs) or data.ContainsKey(self.yearlyLows)): return insights
# Return if indicators are not ready
if not (self.highmin.IsReady or self.highmax.IsReady): return insights
if not (self.lowmin.IsReady or self.lowmax.IsReady): return insights
# Calculate the numerator of the custom indicator
highdelta = self.highmax.Current.Value - self.highmin.Current.Value
lowdelta = self.lowmax.Current.Value - self.lowmin.Current.Value
# Calculate the denominator of the custom indicator
highscaled = self.highema.Current.Value - self.highmin.Current.Value
lowscaled = self.lowema.Current.Value - self.lowmin.Current.Value
# Calculate the value of the custom indicator
highrank = highscaled / highdelta
lowrank = lowscaled / lowdelta
# Plot the custom indicator
# Emit insight on SPY if the indicator value exceeds 0.5
if highrank > 0.50:
insights.append(Insight.Price("SPY", self.period, InsightDirection.Up, 0.01))
# Emit insight on SPY if the indicator value exceeds 0.5
if lowrank < 0.50:
insights.append(Insight.Price("SPY", self.period, InsightDirection.Down, 0.01))
return insights
class QuandlData(PythonQuandl):
# Custom data Quandl link: https://www.quandl.com/data/URC/NASDAQ_52W_HI-NASDAQ-Number-of-Stocks-Making-52-Week-Highs
def __init__(self):
## Retrieve the data from the the Quandl object, specifying the data field used on Quandl
self.ValueColumnName = "NUMBERS OF STOCKS"
# More URC market data can be found at Quandl
# https://www.quandl.com/data/URC-Unicorn-Research-Corporationclass FederalInterestRateAlphaModel(AlphaModel):
def __init__(self, algorithm, insightDuration = 30):
## Add Quandl data for the Federal Interest Rate
self.fedRate = algorithm.AddData(QuandlFedRateColumns, 'FRED/FEDFUNDS', Resolution.Daily).Symbol
self.insightDuration = TimeSpan.FromDays(insightDuration)
def Update(self, algorithm, data):
insights = []
## Check for all Quandl Symbols in current data Slice
if not data.ContainsKey(self.fedRate):
return []
## The Federal Interest Rate (Federal Funds Rate) is one of the most important elements
## of the US economy and any change has profound effects across all markets, but especially
## in debt-based securities, currencies, consumer spending, and a negative effect on equities
## whose companies primarily do business in international markets. The data is monthly, so
## Insight generation will not occur with a high frequency.
## Additional Federal Reserve data can be found at Quandl
## https://www.quandl.com/data/FRED-Federal-Reserve-Economic-Data
## Generate Insights here!
return insights
class QuandlFedRateColumns(PythonQuandl):
def __init__(self):
## Rename the Quandl object column to the data we want, which is the 'Value' column
## of the CSV that our API call returns
self.ValueColumnName = "Value"from datetime import timedelta
from QuantConnect.Data.UniverseSelection import *
from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel
from MaximumDrawdownPercentPerSecurity import MaximumDrawdownPercentPerSecurity
from PanicToExcuberanceAlphaModel import PanicToExcuberanceAlphaModel
from MacdAlphaModel import MacdAlphaModel
from RsiAlphaModel import RsiAlphaModel
from TingoAlphaModel import TingoAlphaModel
class LiquidValueStocks(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 8, 17)
self.SetEndDate(2016, 8, 17)
# No need to set End Date as the final submission will be tested
# up until the review date
# Set $1m Strategy Cash to trade significant AUM
self.SetCash(1000000)
# Add a relevant benchmark, with the default being SPY
self.AddEquity('SPY')
self.SetBenchmark('SPY')
# On the Average Cross Chart we want 2 series, slow MA and fast MA
# avgCross = Chart("Rank")
# avgCross.AddSeries(Series("High Rank", SeriesType.Line, 0))
# self.AddChart(avgCross)
# Use the Alpha Streams Brokerage Model, developed in conjunction with
# funds to model their actual fees, costs, etc.
# Please do not add any additional reality modelling, such as Slippage, Fees, Buying Power, etc.
self.SetBrokerageModel(AlphaStreamsBrokerageModel())
self.UniverseSettings.Resolution = Resolution.Hour
self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
self.AddUniverseSelection(LiquidValueUniverseSelectionModel())
#1. Create and instance of the LongShortEYAlphaModel
# self.AddAlpha(EmaCrossAlphaModel(resolution=Resolution.Hour))
self.AddAlpha(PanicToExcuberanceAlphaModel(self, resolution = Resolution.Daily))
# self.AddAlpha(FederalInterestRateAlphaModel(self))
self.AddAlpha(MacdAlphaModel(12, 26, 9, MovingAverageType.Simple, Resolution.Daily))
self.AddAlpha(RsiAlphaModel(10, Resolution.Daily))
self.AddAlpha(TingoAlphaModel())
self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
self.SetRiskManagement(MaximumDrawdownPercentPerSecurity(0.01))
self.SetExecution(ImmediateExecutionModel())
self.SetWarmUp(300, Resolution.Hour)
class LiquidValueUniverseSelectionModel(FundamentalUniverseSelectionModel):
def __init__(self):
super().__init__(True, None, None)
def SelectCoarse(self, algorithm, coarse):
sortedByDollarVolume = sorted([x for x in coarse if x.HasFundamentalData and x.Volume > 0 and x.Price > 10],
key = lambda x: x.DollarVolume, reverse=True)[:300]
# If no security has met the QC500 criteria, the universe is unchanged.
# A new selection will be attempted on the next trading day as self.lastMonth is not updated
if len(sortedByDollarVolume) == 0:
return Universe.Unchanged
# return the symbol objects our sorted collection
return [f.Symbol for f in sortedByDollarVolume]
def SelectFine(self, algorithm, fine):
filtered_fine = [x for x in fine if x.OperationRatios.ROE.ThreeMonths
and x.OperationRatios.ROIC.ThreeMonths
and x.OperationRatios.OperationRevenueGrowth3MonthAvg.ThreeMonths
and x.ValuationRatios.EarningYield
and x.EarningReports.TotalDividendPerShare.ThreeMonths
and x.ValuationRatios.PriceChange1M
and x.ValuationRatios.BookValuePerShare
and x.ValuationRatios.FCFYield
and x.ValuationRatios.PERatio
and x.CompanyReference.CountryId == "USA"
and x.CompanyReference.PrimaryExchangeID in ["NYS","NAS"]
and (algorithm.Time - x.SecurityReference.IPODate).days > 180
and x.MarketCap > 5e8]
sortedByfactor1 = sorted(filtered_fine, key=lambda x: x.OperationRatios.ROIC.OneYear, reverse=True)
sortedByfactor2 = sorted(filtered_fine, key=lambda x: x.OperationRatios.ROE.OneYear, reverse=True)
sortedByfactor3 = sorted(filtered_fine, key=lambda x: x.OperationRatios.OperationRevenueGrowth3MonthAvg.OneYear, reverse=True)
sortedByfactor4 = sorted(filtered_fine, key=lambda x: x.ValuationRatios.EarningYield, reverse=True)
sortedByfactor5 = sorted(filtered_fine, key=lambda x: x.EarningReports.TotalDividendPerShare.Value, reverse=True)
sortedByfactor6 = sorted(filtered_fine, key=lambda x: x.ValuationRatios.PriceChange1M, reverse=False)
sortedByfactor7 = sorted(filtered_fine, key=lambda x: x.ValuationRatios.BookValuePerShare, reverse=True)
sortedByfactor8 = sorted(filtered_fine, key=lambda x: x.ValuationRatios.FCFYield, reverse=True)
sortedByfactor9 = sorted(filtered_fine, key=lambda x: x.ValuationRatios.PERatio, reverse=False)
num_stocks = len(filtered_fine)
stock_dict = {}
for i,ele in enumerate(sortedByfactor1):
rank1 = i
rank2 = sortedByfactor2.index(ele)
rank3 = sortedByfactor3.index(ele)
rank4 = sortedByfactor4.index(ele)
rank5 = sortedByfactor5.index(ele)
rank6 = sortedByfactor6.index(ele)
rank7 = sortedByfactor7.index(ele)
rank8 = sortedByfactor8.index(ele)
rank9 = sortedByfactor9.index(ele)
score = [rank1/num_stocks,
rank2/num_stocks,
rank9/num_stocks]
score = sum(score)
stock_dict[ele] = score
self.sorted_stock = sorted(stock_dict.items(), key=lambda d:d[1],reverse=False)
sorted_symbol = [self.sorted_stock[i][0] for i in range(len(self.sorted_stock))]
universe = sorted_symbol[:50]
# algorithm.Log(f"Fine update")
# for ff in universe:
# algorithm.Log(f"{ff.Symbol} {stock_dict[ff]} roe {ff.OperationRatios.ROE.OneYear} roic {ff.OperationRatios.ROIC.OneYear} PErat {ff.ValuationRatios.PERatio}")
if len(universe) == 0:
return Universe.Unchanged
return [f.Symbol for f in universe]# 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 clr import AddReference
AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Portfolio import PortfolioTarget
from QuantConnect.Algorithm.Framework.Risk import RiskManagementModel
class MaximumDrawdownPercentPerSecurity(RiskManagementModel):
'''Provides an implementation of IRiskManagementModel that limits the drawdown per holding to the specified percentage'''
def __init__(self, maximumDrawdownPercent = 0.05):
'''Initializes a new instance of the MaximumDrawdownPercentPerSecurity class
Args:
maximumDrawdownPercent: The maximum percentage drawdown allowed for any single security holding'''
self.maximumDrawdownPercent = -abs(maximumDrawdownPercent)
def ManageRisk(self, algorithm, targets):
'''Manages the algorithm's risk at each time step
Args:
algorithm: The algorithm instance
targets: The current portfolio targets to be assessed for risk'''
targets = []
for kvp in algorithm.Securities:
security = kvp.Value
if not security.Invested:
continue
vol = security.Volume
pnl = security.Holdings.UnrealizedProfitPercent
if pnl < self.maximumDrawdownPercent:
# liquidate
targets.append(PortfolioTarget(security.Symbol, 0))
return targets# 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 clr import AddReference
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Indicators")
from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Alphas import *
import numpy as np
class EmaCrossAlphaModel(AlphaModel):
'''Alpha model that uses an EMA cross to create insights'''
def __init__(self,
fastPeriod = 12,
slowPeriod = 26,
resolution = Resolution.Daily):
'''Initializes a new instance of the EmaCrossAlphaModel class
Args:
fastPeriod: The fast EMA period
slowPeriod: The slow EMA period'''
self.fastPeriod = fastPeriod
self.slowPeriod = slowPeriod
self.resolution = resolution
self.predictionInterval = Time.Multiply(Extensions.ToTimeSpan(resolution), fastPeriod)
self.symbolDataBySymbol = {}
resolutionString = Extensions.GetEnumString(resolution, Resolution)
self.Name = '{}({},{},{})'.format(self.__class__.__name__, fastPeriod, slowPeriod, resolutionString)
def Update(self, algorithm, data):
'''Updates this alpha model with the latest data from the algorithm.
This is called each time the algorithm receives data for subscribed securities
Args:
algorithm: The algorithm instance
data: The new data available
Returns:
The new insights generated'''
insights = []
pos = 0
neg = 0.0
for symbol, symbolData in self.symbolDataBySymbol.items():
if symbolData.Fast.IsReady and symbolData.Slow.IsReady:
mag = (symbolData.Fast.Current.Value-symbolData.Slow.Current.Value )/\
np.mean([symbolData.Fast.Current.Value,symbolData.Slow.Current.Value])
if symbolData.FastIsOverSlow:
if symbolData.Slow > symbolData.Fast:
neg += -mag
elif symbolData.SlowIsOverFast:
if symbolData.Fast > symbolData.Slow:
pos += mag
tot = pos + neg
for symbol, symbolData in self.symbolDataBySymbol.items():
if symbolData.Fast.IsReady and symbolData.Slow.IsReady:
mag = (symbolData.Fast.Current.Value-symbolData.Slow.Current.Value )/\
np.mean([symbolData.Fast.Current.Value,symbolData.Slow.Current.Value])
if symbolData.FastIsOverSlow:
if symbolData.Slow > symbolData.Fast:
insights.append(Insight.Price(symbolData.Symbol,
self.predictionInterval,
InsightDirection.Down,
mag,-mag,
"MyAlphaModel",-mag/tot))
elif symbolData.SlowIsOverFast:
if symbolData.Fast > symbolData.Slow:
insights.append(Insight.Price(symbolData.Symbol,
self.predictionInterval,
InsightDirection.Up,
mag,
mag,
"MyAlphaModel",mag/tot))
symbolData.FastIsOverSlow = symbolData.Fast > symbolData.Slow
return insights
def OnSecuritiesChanged(self, algorithm, changes):
'''Event fired each time the we add/remove securities from the data feed
Args:
algorithm: The algorithm instance that experienced the change in securities
changes: The security additions and removals from the algorithm'''
for added in changes.AddedSecurities:
symbolData = self.symbolDataBySymbol.get(added.Symbol)
if symbolData is None:
# create fast/slow EMAs
symbolData = SymbolData(added)
symbolData.Fast = algorithm.EMA(added.Symbol, self.fastPeriod, self.resolution)
symbolData.Slow = algorithm.EMA(added.Symbol, self.slowPeriod, self.resolution)
self.symbolDataBySymbol[added.Symbol] = symbolData
else:
# a security that was already initialized was re-added, reset the indicators
symbolData.Fast.Reset()
symbolData.Slow.Reset()
class SymbolData:
'''Contains data specific to a symbol required by this model'''
def __init__(self, security):
self.Security = security
self.Symbol = security.Symbol
self.Fast = None
self.Slow = None
# True if the fast is above the slow, otherwise false.
# This is used to prevent emitting the same signal repeatedly
self.FastIsOverSlow = False
@property
def SlowIsOverFast(self):
return not self.FastIsOverSlow# 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 clr import AddReference
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Logging")
AddReference("QuantConnect.Common")
from QuantConnect.Data.Custom.Tiingo import *
from datetime import datetime, timedelta
import numpy as np
class TingoAlphaModel:
def __init__(self):
# the sample pool of word sentiments
self.wordSentiment = {
"bad": -0.5, "good": 0.5, "negative": -0.5,
"great": 0.5, "growth": 0.5, "fail": -0.5,
"failed": -0.5, "success": 0.5, "nailed": 0.5,
"beat": 0.5, "missed": -0.5, "profitable": 0.5,
"beneficial": 0.5, "right": 0.5, "positive": 0.5,
"large":0.5, "attractive": 0.5, "sound": 0.5,
"excellent": 0.5, "wrong": -0.5, "unproductive": -0.5,
"lose": -0.5, "missing": -0.5, "mishandled": -0.5,
"un_lucrative": -0.5, "up": 0.5, "down": -0.5,
"unproductive": -0.5, "poor": -0.5, "wrong": -0.5,
"worthwhile": 0.5, "lucrative": 0.5, "solid": 0.5
}
self.day = -1
self.custom = {}
def Update(self, algorithm, data):
insights = []
# Run the model daily
if algorithm.Time.day == self.day:
return insights
self.day = algorithm.Time.day
weights = {}
# Fetch the wordSentiment data for the active securities and trade on any
for key, security in self.custom.items():
if not data.ContainsKey(security):
continue
news = data[security]
if news is None:
continue
descriptionWords = news.Description.lower().split(" ")
# Get the intersection words between sentiment sample pool and news description
intersection = set(self.wordSentiment.keys()).intersection(descriptionWords)
# Calculate the score sum of word sentiment
sentimentSum = sum([self.wordSentiment[i] for i in intersection])
if sentimentSum > 0:
weights[security.Underlying] = sentimentSum
# Sort securities by sentiment ranking,
count = min(10, len(weights))
if count == 0:
return insights
# Order the sentiment by value and select the top 10
sortedbyValue = sorted(weights.items(), key = lambda x:x[1], reverse=True)
selected = {kv[0]:kv[1] for kv in sortedbyValue[:count]}
# Populate the list of insights with the selected data where the sentiment sign is the direction and its value is the weight
closeTimeLocal = Expiry.EndOfDay(algorithm.Time)
for symbol, weight in selected.items():
insights.append(Insight.Price(symbol, closeTimeLocal, InsightDirection.Up, weight))
return insights
def OnSecuritiesChanged(self, algorithm, changes):
for security in changes.AddedSecurities:
# Tiingo's News is for US Equities
if security.Type == SecurityType.Equity:
self.custom[security.Symbol] = (algorithm.AddData(TiingoNews, security.Symbol).Symbol)
for security in changes.RemovedSecurities:
# Tiingo's News is for US Equities
if security.Type == SecurityType.Equity and security.Symbol in self.custom:
data = self.custom.pop(security.Symbol,None)# 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 clr import AddReference
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Indicators")
from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Alphas import *
class MacdAlphaModel(AlphaModel):
'''Defines a custom alpha model that uses MACD crossovers. The MACD signal line
is used to generate up/down insights if it's stronger than the bounce threshold.
If the MACD signal is within the bounce threshold then a flat price insight is returned.'''
def __init__(self,
fastPeriod = 12,
slowPeriod = 26,
signalPeriod = 9,
movingAverageType = MovingAverageType.Exponential,
resolution = Resolution.Daily):
''' Initializes a new instance of the MacdAlphaModel class
Args:
fastPeriod: The MACD fast period
slowPeriod: The MACD slow period</param>
signalPeriod: The smoothing period for the MACD signal
movingAverageType: The type of moving average to use in the MACD'''
self.fastPeriod = fastPeriod
self.slowPeriod = slowPeriod
self.signalPeriod = signalPeriod
self.movingAverageType = movingAverageType
self.resolution = resolution
self.insightPeriod = Time.Multiply(Extensions.ToTimeSpan(resolution), fastPeriod)
self.bounceThresholdPercent = 0.01
self.symbolData = {}
resolutionString = Extensions.GetEnumString(resolution, Resolution)
movingAverageTypeString = Extensions.GetEnumString(movingAverageType, MovingAverageType)
self.Name = '{}({},{},{},{},{})'.format(self.__class__.__name__, fastPeriod, slowPeriod, signalPeriod, movingAverageTypeString, resolutionString)
def Update(self, algorithm, data):
''' Determines an insight for each security based on it's current MACD signal
Args:
algorithm: The algorithm instance
data: The new data available
Returns:
The new insights generated'''
insights = []
for key, sd in self.symbolData.items():
if sd.Security.Price == 0:
continue
direction = InsightDirection.Flat
normalized_signal = sd.MACD.Signal.Current.Value / sd.Security.Price
if normalized_signal > self.bounceThresholdPercent:
direction = InsightDirection.Up
elif normalized_signal < -self.bounceThresholdPercent:
direction = InsightDirection.Down
# ignore signal for same direction as previous signal
if direction == sd.PreviousDirection:
continue
insight = Insight.Price(sd.Security.Symbol, self.insightPeriod, direction,normalized_signal)
sd.PreviousDirection = insight.Direction
insights.append(insight)
return insights
def OnSecuritiesChanged(self, algorithm, changes):
'''Event fired each time the we add/remove securities from the data feed.
This initializes the MACD for each added security and cleans up the indicator for each removed security.
Args:
algorithm: The algorithm instance that experienced the change in securities
changes: The security additions and removals from the algorithm'''
for added in changes.AddedSecurities:
self.symbolData[added.Symbol] = SymbolData(algorithm, added, self.fastPeriod, self.slowPeriod, self.signalPeriod, self.movingAverageType, self.resolution)
for removed in changes.RemovedSecurities:
data = self.symbolData.pop(removed.Symbol, None)
if data is not None:
# clean up our consolidator
algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, data.Consolidator)
class SymbolData:
def __init__(self, algorithm, security, fastPeriod, slowPeriod, signalPeriod, movingAverageType, resolution):
self.Security = security
self.MACD = MovingAverageConvergenceDivergence(fastPeriod, slowPeriod, signalPeriod, movingAverageType)
self.Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution)
algorithm.RegisterIndicator(security.Symbol, self.MACD, self.Consolidator)
self.PreviousDirection = None