MACD Alpha Signals with a Normalized Bounce Threshold
Summary
This alpha model turns MACD signal-line values into directional price insights. For each security with a nonzero price, it divides the MACD signal value by the current price and compares the result with a one-percent threshold. Values beyond the positive or negative boundary produce up or down insights; values inside the boundary are treated as flat. Repeated signals in the same direction are skipped, while a flat reading cancels outstanding insights.
The implementation maintains MACD indicators per security, warms them up at the chosen resolution, and removes consolidators and related insights when securities leave the universe. The default MACD periods are 12, 26, and 9, with daily resolution and an insight horizon based on the fast period. This is a framework example rather than an evaluated trading strategy: it provides no backtest results, and does not establish that the threshold or signal mapping is profitable.
Key ideas
- The model normalizes the MACD signal-line value by the security price before applying its threshold.
- Signals beyond the positive or negative threshold generate up or down insights, while values inside it map to flat.
- It suppresses a repeated direction and cancels existing insights when the state returns to flat.
- Per-security indicators are initialized and warmed up, then cleaned up when securities are removed.
- The example specifies default periods but presents no evidence of trading performance.
Tags
Full text
# MacdAlphaModel
# MacdAlphaModel
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.
## Source (Apache-2.0)
```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 *
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
```Shown in full with attribution under the source's licence. Licence: Apache-2.0
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.