chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
# 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 *
|
||||
|
||||
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.insightCollection = InsightCollection()
|
||||
self.symbolData = {}
|
||||
|
||||
self.Name = '{}({},{},{},{},{})'.format(self.__class__.__name__, fastPeriod, slowPeriod, signalPeriod, movingAverageType, resolution)
|
||||
|
||||
|
||||
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
|
||||
|
||||
sd.PreviousDirection = direction
|
||||
|
||||
if direction == InsightDirection.Flat:
|
||||
self.CancelInsights(algorithm, sd.Security.Symbol)
|
||||
continue
|
||||
|
||||
insight = Insight.Price(sd.Security.Symbol, self.insightPeriod, direction)
|
||||
insights.append(insight)
|
||||
self.insightCollection.Add(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:
|
||||
symbol = removed.Symbol
|
||||
|
||||
data = self.symbolData.pop(symbol, None)
|
||||
if data is not None:
|
||||
# clean up our consolidator
|
||||
algorithm.SubscriptionManager.RemoveConsolidator(symbol, data.Consolidator)
|
||||
|
||||
# remove from insight collection manager
|
||||
self.CancelInsights(algorithm, symbol)
|
||||
|
||||
def CancelInsights(self, algorithm, symbol):
|
||||
if not self.insightCollection.ContainsKey(symbol):
|
||||
return
|
||||
insights = self.insightCollection[symbol]
|
||||
algorithm.Insights.Cancel(insights)
|
||||
self.insightCollection.Clear([ symbol ]);
|
||||
|
||||
|
||||
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)
|
||||
algorithm.WarmUpIndicator(security.Symbol, self.MACD, resolution)
|
||||
|
||||
self.PreviousDirection = None
|
||||
Reference in New Issue
Block a user