The Russian20 Momentum MA Strategy is a direct conversion of the MetaTrader 5 expert advisor Russian20-hp1.mq5. The original script was published by Gordago Software Corp. and relies on a two-hour chart, a 20-period simple moving average (SMA), and a 5-period Momentum indicator to identify short-term trend continuations. The StockSharp implementation keeps the same analytical core while adapting order handling and money management to the high-level strategy API.
Trading Logic
Data frequency: Works with the user-defined candle type (default is 2-hour candles, matching the MQL5 timeframe PERIOD_H2). The logic is executed only when a candle is closed.
Indicators:
Simple Moving Average with configurable period (default 20).
Momentum indicator with configurable period (default 5). The neutral Momentum level is 100, mirroring the MQL5 default output.
Long entry: Triggered when all of the following conditions are satisfied on the latest closed candle:
Close price is above the SMA.
Momentum value is greater than 100 (positive acceleration).
The close price is higher than the previous candle’s close, ensuring upward momentum in price action.
Short entry: Triggered when all of the following conditions are satisfied:
Close price is below the SMA.
Momentum value is less than 100 (negative acceleration).
The close price is lower than the previous candle’s close.
Long exit: The strategy liquidates long positions when Momentum drops below 100 or when a protective stop-loss or take-profit threshold is crossed.
Short exit: The strategy liquidates short positions when Momentum rises above 100 or when the configured protective thresholds are reached.
Risk Management
The original MQL5 expert places fixed stop loss and take profit orders in “pips” that are adjusted for 4- and 5-digit Forex pricing. The C# conversion reproduces this behaviour by:
Calculating an adjusted pip size from the security’s PriceStep. For symbols with three or five decimal places, the pip size equals PriceStep * 10, otherwise it equals PriceStep.
Translating the user inputs for stop loss and take profit into absolute price distances.
Monitoring price action on each closed candle and closing the position when the price crosses the calculated thresholds.
Parameters
Parameter
Default
Description
CandleType
2-hour candles
Data type used for signal generation.
MovingAverageLength
20
Lookback for the SMA filter.
MomentumPeriod
5
Lookback for the Momentum indicator.
StopLossBuyPips
50
Long stop-loss distance expressed in pips. Set to 0 to disable.
TakeProfitBuyPips
50
Long take-profit distance in pips. Set to 0 to disable.
StopLossSellPips
50
Short stop-loss distance in pips. Set to 0 to disable.
TakeProfitSellPips
50
Short take-profit distance in pips. Set to 0 to disable.
All numeric parameters are exposed through StrategyParam<T> and marked as optimizable when applicable, enabling backtesting and optimisation with StockSharp tools.
Implementation Notes
The strategy uses the high-level SubscribeCandles().Bind(...) API to stream candle data and simultaneously obtain SMA and Momentum values without manual indicator bookkeeping.
Momentum levels are evaluated exactly as in the MQL5 script (100 as the neutral level). Any breach beyond the stop-loss/take-profit offsets triggers a market exit, faithfully mimicking the original order placement logic.
The previous close is cached to verify price momentum without resorting to historical collection lookups, in line with the project’s performance guidelines.
Visualization hooks (DrawCandles, DrawIndicator, DrawOwnTrades) are wired for convenience when the host environment supports charting.
Usage Tips
The default timeframe and parameters correspond to the author’s original configuration. Adjust the candle type when working with instruments that do not produce 2-hour bars.
When trading assets quoted with unconventional tick sizes, review the calculated pip size to make sure the stop-loss and take-profit distances remain realistic.
The strategy is designed for a single open position at a time. External manual trades or simultaneous positions on the same security may interfere with the built-in exit logic.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Russian20 Momentum MA strategy. Combines SMA filter with momentum confirmation.
/// </summary>
public class Russian20MomentumMaStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _momPeriod;
private decimal? _prevMom;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
public int MomPeriod
{
get => _momPeriod.Value;
set => _momPeriod.Value = value;
}
public Russian20MomentumMaStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_maPeriod = Param(nameof(MaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "SMA period for trend filter", "Indicators");
_momPeriod = Param(nameof(MomPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Momentum Period", "Momentum lookback", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMom = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevMom = null;
var sma = new SimpleMovingAverage { Length = MaPeriod };
var mom = new Momentum { Length = MomPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, mom, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal maVal, decimal momVal)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevMom = momVal;
return;
}
if (_prevMom == null)
{
_prevMom = momVal;
return;
}
var close = candle.ClosePrice;
// Price above MA + momentum crosses above zero → buy
if (close > maVal && _prevMom.Value <= 100m && momVal > 100m && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Price below MA + momentum crosses below zero → sell
else if (close < maVal && _prevMom.Value >= 100m && momVal < 100m && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevMom = momVal;
}
}
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
from StockSharp.Algo.Indicators import SimpleMovingAverage, Momentum
from StockSharp.Algo.Strategies import Strategy
class russian20_momentum_ma_strategy(Strategy):
def __init__(self):
super(russian20_momentum_ma_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._ma_period = self.Param("MaPeriod", 20) \
.SetDisplay("MA Period", "SMA period for trend filter", "Indicators")
self._mom_period = self.Param("MomPeriod", 10) \
.SetDisplay("Momentum Period", "Momentum lookback", "Indicators")
self._prev_mom = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def MaPeriod(self):
return self._ma_period.Value
@property
def MomPeriod(self):
return self._mom_period.Value
def OnReseted(self):
super(russian20_momentum_ma_strategy, self).OnReseted()
self._prev_mom = None
def OnStarted2(self, time):
super(russian20_momentum_ma_strategy, self).OnStarted2(time)
self._prev_mom = None
sma = SimpleMovingAverage()
sma.Length = self.MaPeriod
mom = Momentum()
mom.Length = self.MomPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(sma, mom, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _on_process(self, candle, ma_value, mom_value):
if candle.State != CandleStates.Finished:
return
mv = float(ma_value)
momv = float(mom_value)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_mom = momv
return
if self._prev_mom is None:
self._prev_mom = momv
return
close = float(candle.ClosePrice)
# Price above MA + momentum crosses above 100
if close > mv and self._prev_mom <= 100.0 and momv > 100.0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Price below MA + momentum crosses below 100
elif close < mv and self._prev_mom >= 100.0 and momv < 100.0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_mom = momv
def CreateClone(self):
return russian20_momentum_ma_strategy()