Стратегия сравнивает показания индикатора момента с направлением цены, чтобы обнаружить ранние признаки разворота. Дивергенция появляется, когда цена делает новый экстремум, а индикатор момента не подтверждает его, что указывает на ослабление импульса.
Тестирование показывает среднегодичную доходность около 106%. Стратегию лучше запускать на фондовом рынке.
Бычий сигнал формируется, когда цена обновляет минимум, а осциллятор показывает более высокий минимум. Медвежий сигнал возникает, когда цена делает новый максимум, но индикатор момента не подтверждает. Позиции закрываются, когда момент пересекает ноль или дивергенция становится недействительной.
Такой подход подходит трейдерам, стремящимся предугадать разворот, а не присоединяться к тренду. Стопы используются для ограничения риска, если рынок продолжит движение против сигнала.
Подробности
Условия входа:
Long: цена делает более низкий минимум, а момент показывает более высокий минимум
Short: цена делает более высокий максимум, а момент показывает более низкий максимум
Long/Short: обе стороны.
Условия выхода:
Long: выход при пересечении момента ниже нуля
Short: выход при пересечении момента выше нуля
Стопы: да, фиксированный стоп‑лосс.
Параметры по умолчанию:
MomentumPeriod = 14
MaPeriod = 20
CandleType = TimeSpan.FromMinutes(5)
Фильтры:
Категория: Разворот
Направление: Обе стороны
Индикаторы: Момент
Стопы: Да
Сложность: Средняя
Таймфрейм: Внутридневной
Сезонность: Нет
Нейросети: Нет
Дивергенция: Да
Уровень риска: Средний
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()