The Momentum Divergence strategy compares momentum readings with price direction to spot early signs of a reversal. Divergences occur when price makes a new extreme yet the momentum indicator fails to confirm, hinting at weakening strength.
Testing indicates an average annual return of about 106%. It performs best in the stocks market.
A bullish setup happens when price records a lower low while the momentum oscillator prints a higher low. A bearish setup forms when price pushes to a higher high but momentum fails to follow. Positions are closed when momentum crosses back through zero or the divergence is invalidated.
This approach appeals to traders looking to anticipate turning points rather than chase trends. Stops are used to control risk in case the market continues to move against the divergence signal.
Details
Entry Criteria:
Long: Price makes lower low && Momentum shows higher low
Short: Price makes higher high && Momentum shows lower high
Long/Short: Both sides.
Exit Criteria:
Long: Exit when momentum crosses below zero
Short: Exit when momentum crosses above zero
Stops: Yes, fixed stop-loss.
Default Values:
MomentumPeriod = 14
MaPeriod = 20
CandleType = TimeSpan.FromMinutes(5)
Filters:
Category: Reversal
Direction: Both
Indicators: Momentum
Stops: Yes
Complexity: Intermediate
Timeframe: Intraday
Seasonality: No
Neural networks: No
Divergence: Yes
Risk Level: Medium
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Momentum Divergence strategy.
/// Trades based on divergence between price and momentum.
/// </summary>
public class MomentumDivergenceStrategy : Strategy
{
private readonly StrategyParam<int> _momentumPeriodParam;
private readonly StrategyParam<int> _maPeriodParam;
private readonly StrategyParam<DataType> _candleTypeParam;
private Momentum _momentum;
private SimpleMovingAverage _sma;
private decimal _prevPrice;
private decimal _prevMomentum;
private decimal _currentPrice;
private decimal _currentMomentum;
/// <summary>
/// Momentum indicator period.
/// </summary>
public int MomentumPeriod
{
get => _momentumPeriodParam.Value;
set => _momentumPeriodParam.Value = value;
}
/// <summary>
/// Moving average period.
/// </summary>
public int MaPeriod
{
get => _maPeriodParam.Value;
set => _maPeriodParam.Value = value;
}
/// <summary>
/// Candle type for strategy.
/// </summary>
public DataType CandleType
{
get => _candleTypeParam.Value;
set => _candleTypeParam.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MomentumDivergenceStrategy()
{
_momentumPeriodParam = Param(nameof(MomentumPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Momentum Period", "Period for Momentum indicator", "Parameters")
.SetOptimize(10, 30, 5);
_maPeriodParam = Param(nameof(MaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period for Moving Average", "Parameters")
.SetOptimize(10, 50, 10);
_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle type for strategy", "Common");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevPrice = 0;
_prevMomentum = 0;
_currentPrice = 0;
_currentMomentum = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
_momentum = new Momentum { Length = MomentumPeriod };
_sma = new SMA { Length = MaPeriod };
// Create subscription and bind indicators
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_momentum, _sma, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _momentum);
DrawIndicator(area, _sma);
DrawOwnTrades(area);
}
// Enable position protection
StartProtection(
takeProfit: new Unit(0, UnitTypes.Absolute), // No take profit
stopLoss: new Unit(2, UnitTypes.Percent) // 2% stop loss
);
}
private void ProcessCandle(ICandleMessage candle, decimal momentumValue, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Store previous values before updating current ones
_prevPrice = _currentPrice;
_prevMomentum = _currentMomentum;
// Update current values
_currentPrice = candle.ClosePrice;
_currentMomentum = momentumValue;
// Skip first candle after indicators become formed
if (_prevPrice == 0 || _prevMomentum == 0)
return;
// Detect bullish divergence (price makes lower low but momentum makes higher low)
bool bullishDivergence = _currentPrice < _prevPrice && _currentMomentum > _prevMomentum;
// Detect bearish divergence (price makes higher high but momentum makes lower high)
bool bearishDivergence = _currentPrice > _prevPrice && _currentMomentum < _prevMomentum;
// Trading signals
if (bullishDivergence && Position <= 0)
{
// Bullish divergence - buy signal
BuyMarket(Volume + Math.Abs(Position));
}
else if (bearishDivergence && Position >= 0)
{
// Bearish divergence - sell signal
SellMarket(Volume + Math.Abs(Position));
}
// Exit when price crosses MA in the opposite direction
else if (Position > 0 && candle.ClosePrice < smaValue)
{
// Exit long position
SellMarket(Position);
}
else if (Position < 0 && candle.ClosePrice > smaValue)
{
// Exit short position
BuyMarket(Math.Abs(Position));
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import Momentum, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class momentum_divergence_strategy(Strategy):
"""
Momentum Divergence: trades based on divergence between price and momentum.
"""
def __init__(self):
super(momentum_divergence_strategy, self).__init__()
self._momentum_period = self.Param("MomentumPeriod", 14).SetDisplay("Momentum Period", "Period for Momentum", "Parameters")
self._ma_period = self.Param("MaPeriod", 20).SetDisplay("MA Period", "Period for SMA", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "Common")
self._prev_price = 0.0
self._prev_momentum = 0.0
self._current_price = 0.0
self._current_momentum = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(momentum_divergence_strategy, self).OnReseted()
self._prev_price = 0.0
self._prev_momentum = 0.0
self._current_price = 0.0
self._current_momentum = 0.0
def OnStarted2(self, time):
super(momentum_divergence_strategy, self).OnStarted2(time)
mom = Momentum()
mom.Length = self._momentum_period.Value
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(mom, sma, self._process_candle).Start()
self.StartProtection(None, Unit(2, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, mom)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, mom_val, sma_val):
if candle.State != CandleStates.Finished:
return
self._prev_price = self._current_price
self._prev_momentum = self._current_momentum
self._current_price = float(candle.ClosePrice)
self._current_momentum = float(mom_val)
if self._prev_price == 0 or self._prev_momentum == 0:
return
bullish_div = self._current_price < self._prev_price and self._current_momentum > self._prev_momentum
bearish_div = self._current_price > self._prev_price and self._current_momentum < self._prev_momentum
sma = float(sma_val)
if bullish_div and self.Position <= 0:
self.BuyMarket()
elif bearish_div and self.Position >= 0:
self.SellMarket()
elif self.Position > 0 and float(candle.ClosePrice) < sma:
self.SellMarket()
elif self.Position < 0 and float(candle.ClosePrice) > sma:
self.BuyMarket()
def CreateClone(self):
return momentum_divergence_strategy()