The Stochastic Momentum Filter Strategy is a StockSharp port of the MetaTrader expert advisor Stochastic.mq4 (folder MQL/23473). The original robot combines two stochastic oscillators, linear weighted moving averages (LWMA), a momentum deviation filter, and a higher timeframe MACD trend check. This C# version recreates the same building blocks on top of the StockSharp high-level API and keeps the multi-layered confirmation workflow:
Trend filter – a fast LWMA must be above (or below) a slow LWMA before long (or short) trades are allowed.
Oscillator confirmation – both a fast stochastic (5/2/2) and a slow stochastic (21/4/10) must agree on oversold/overbought zones.
Momentum deviation – at least one of the three most recent momentum readings must deviate from the 100 baseline by more than a configurable threshold, matching the expert's use of the MT4 iMomentum function.
Higher timeframe MACD – the MACD main line on a configurable higher timeframe must stay above the signal line for longs (and below for shorts). The default 30-day timeframe approximates the original monthly filter.
Risk logic – stop loss, take profit, and optional trailing are handled through StartProtection, mirroring the EA's protective parameters. Position flips close opposing exposure automatically before establishing the new net position.
The strategy subscribes to two candle streams: the trading timeframe and the higher timeframe that feeds the MACD filter. All calculations are performed with StockSharp indicators and processed through the high-level Bind helpers.
Parameters
Name
Default
Description
StochasticBuyLevel
30
Oversold level that both stochastic oscillators must breach for long setups.
StochasticSellLevel
80
Overbought level that both stochastic oscillators must reach for short setups.
FastMaPeriod
6
Length of the fast LWMA trend filter.
SlowMaPeriod
85
Length of the slow LWMA trend filter.
FastStochasticPeriod
5
%K period of the fast stochastic oscillator.
FastStochasticSignal
2
%D smoothing period of the fast stochastic.
FastStochasticSmoothing
2
Extra smoothing applied to the fast stochastic (matches MT4 “slowing”).
SlowStochasticPeriod
21
%K period of the slow stochastic oscillator.
SlowStochasticSignal
4
%D smoothing period of the slow stochastic.
SlowStochasticSmoothing
10
Extra smoothing applied to the slow stochastic.
MomentumPeriod
14
Look-back of the momentum oscillator (same as MT4 iMomentum).
MomentumThreshold
0.3
Minimum absolute deviation from the 100 baseline required within the last three momentum values.
MacdFastPeriod
12
Fast EMA period for the higher timeframe MACD.
MacdSlowPeriod
26
Slow EMA period for the higher timeframe MACD.
MacdSignalPeriod
9
Signal EMA period for the higher timeframe MACD.
TakeProfitPoints
50
Take-profit distance (in price points). Set to 0 to disable.
StopLossPoints
20
Stop-loss distance (in price points). Set to 0 to disable.
EnableTrailing
true
Enables StockSharp trailing of the protective stop.
TradeVolume
1
Net position size targeted on each signal.
MaxNetPositions
1
Caps the stacked net exposure (multiplies TradeVolume).
CandleType
15m time frame
Main trading timeframe.
HigherTimeframe
30d time frame
Timeframe used for MACD confirmation.
Trading Logic
Indicator preparation – the strategy binds both LWMAs, both stochastic oscillators, the momentum indicator, and the MACD to their respective candle streams.
Momentum history – the absolute distance of the momentum oscillator from 100 is stored for the last three finished bars. This replicates the EA's MomLevelB/MomLevelS arrays.
Entry rules
Long: fast LWMA above slow LWMA, both stochastic %K and %D values below StochasticBuyLevel, momentum deviation above MomentumThreshold, and MACD main line above the signal line.
Short: fast LWMA below slow LWMA, both stochastic %K and %D values above StochasticSellLevel, momentum deviation above the threshold, and MACD main line below the signal line.
Position handling – orders are sent with BuyMarket/SellMarket. When a reversal signal appears the strategy automatically closes any opposite net exposure before establishing the new direction.
Protection – StartProtection applies the configured take-profit and stop-loss distances (in points). When EnableTrailing is true, StockSharp manages stop trailing similarly to the EA's trailing routine.
Differences Compared to the MQL Version
Volume scaling: the EA scales lot sizes using LotExponent and allows multiple simultaneous tickets. The StockSharp port focuses on net exposure and targets a single TradeVolume per direction (bounded by MaxNetPositions).
Margin management: margin checks, equity stops, and notification functions from the original script are not reproduced because they rely on MT4 account APIs.
Freeze levels: low-level broker-specific freeze-level checks are omitted; StockSharp order routing handles exchange constraints.
Break-even toggle: the MT4 "move to breakeven" helper is replaced by StockSharp's trailing protection.
Usage Notes
Assign a security and connector, then start the strategy. It will automatically subscribe to both the trading timeframe and the higher timeframe required by the MACD filter.
If your data source does not support a 30-day candle type, adjust HigherTimeframe to a supported interval (e.g., weekly or daily). The trend confirmation logic still expects the MACD main line to stay on the same side of its signal line.
Set TradeVolume to match your portfolio units. The strategy sets Volume during OnStarted, so Designer/Runner will use this size when submitting orders.
Set TakeProfitPoints/StopLossPoints to zero if protective orders should be disabled.
All comments inside the code are written in English, and indentation uses tabs, following repository guidelines.
Files
CS/StochasticMomentumFilterStrategy.cs – StockSharp implementation of the strategy logic.
README.md – English documentation (this file).
README_ru.md – Russian documentation.
README_zh.md – Chinese documentation.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class StochasticMomentumFilterStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public StochasticMomentumFilterStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class stochastic_momentum_filter_strategy(Strategy):
def __init__(self):
super(stochastic_momentum_filter_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(stochastic_momentum_filter_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(stochastic_momentum_filter_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return stochastic_momentum_filter_strategy()