This strategy replicates the MetaTrader 5 expert advisor MA on Momentum Min Profit.mq5 by trading the crossover between a Momentum indicator and a moving average that is calculated on top of the momentum series. A bullish signal appears when momentum crosses above its average while the previous bar kept momentum below the neutral 100 level. A bearish signal is generated when momentum crosses below the average with the previous bar above 100. The implementation keeps the original money based equity stop and the fixed take-profit distance measured in points.
Trading logic
Request candles defined by CandleType and feed them into the Momentum indicator.
Smooth the momentum stream with a moving average defined by MomentumMovingAverageType and MomentumMovingAveragePeriod.
Detect crossovers using the previous bar values to avoid double signals.
Optional features from the MQL version:
Reverse the direction of the generated signals.
Close the opposite exposure before entering a new trade or skip the entry entirely.
Enforce a single net position at any time.
Allow triggering on the current (forming) candle instead of the fully closed bar.
Apply risk management:
Equity stop in money: PnL + Position * (close - PositionPrice) must remain above StopLossMoney.
Take-profit distance in points converted through Security.PriceStep.
Parameters
Parameter
Type
Default
Description
CandleType
DataType
TimeSpan.FromMinutes(5).TimeFrame()
Candles used to compute momentum.
MomentumPeriod
int
14
Lookback period of the Momentum indicator.
MomentumMovingAveragePeriod
int
6
Length of the moving average applied to momentum.
MomentumMovingAverageType
MomentumMovingAverageType
Smoothed
Moving average algorithm (Simple, Exponential, Smoothed, Weighted).
ReverseSignals
bool
false
Mirror MetaTrader buy/sell signals.
CloseOpposite
bool
true
Close the opposite exposure before opening a new position.
OnlyOnePosition
bool
true
Keep a single net position.
UseCurrentCandle
bool
false
Evaluate signals on the current forming candle instead of the closed bar.
StopLossMoney
decimal
15
Equity drawdown allowed before closing all trades.
TakeProfitPoints
decimal
460
Profit target in instrument points (multiplied by PriceStep).
MomentumReference
decimal
100
Neutral momentum level copied from the MQL strategy.
Implementation notes
The moving average is implemented with LengthIndicator<decimal> instances to reuse StockSharp built-in SMA/EMA/SMMA/WMA classes.
The original order queue and magic-number filters map to StockSharp net positions, therefore the strategy sends a single market order sized to both flatten the opposite side and open the new exposure when CloseOpposite is enabled.
Equity protection closes all positions via CloseAll() once the floating loss breaches the threshold, exactly matching the MetaTrader behaviour of monitoring the combined commission, swap and profit.
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;
/// <summary>
/// Momentum crossing its own moving average strategy.
/// Converted from MetaTrader 5 (MA on Momentum Min Profit.mq5).
/// </summary>
public class MaOnMomentumMinProfitStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _momentumPeriod;
private readonly StrategyParam<int> _maPeriod;
private Momentum _momentum;
private readonly Queue<decimal> _momentumHistory = new();
private decimal? _prevMomentum;
private decimal? _prevSignal;
/// <summary>
/// Candle type used for signal calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Momentum period.
/// </summary>
public int MomentumPeriod
{
get => _momentumPeriod.Value;
set => _momentumPeriod.Value = value;
}
/// <summary>
/// Moving average period applied to momentum values.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Initialize <see cref="MaOnMomentumMinProfitStrategy"/>.
/// </summary>
public MaOnMomentumMinProfitStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Type of candles used for the momentum calculation", "General");
_momentumPeriod = Param(nameof(MomentumPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Momentum Period", "Lookback for the momentum indicator", "Momentum");
_maPeriod = Param(nameof(MaPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period of the moving average applied to momentum", "Momentum");
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevMomentum = null;
_prevSignal = null;
_momentumHistory.Clear();
_momentum = new Momentum { Length = MomentumPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_momentum, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal momentumValue)
{
if (candle.State != CandleStates.Finished)
return;
_momentumHistory.Enqueue(momentumValue);
while (_momentumHistory.Count > MaPeriod)
_momentumHistory.Dequeue();
if (!_momentum.IsFormed)
return;
if (_momentumHistory.Count < MaPeriod)
{
_prevMomentum = momentumValue;
return;
}
// Calculate SMA of momentum
var sum = 0m;
var history = _momentumHistory.ToArray();
foreach (var v in history)
sum += v;
var signalValue = sum / history.Length;
if (_prevMomentum is null || _prevSignal is null)
{
_prevMomentum = momentumValue;
_prevSignal = signalValue;
return;
}
var crossUp = _prevMomentum < _prevSignal && momentumValue > signalValue;
var crossDown = _prevMomentum > _prevSignal && momentumValue < signalValue;
var volume = Volume;
if (volume <= 0)
volume = 1;
var minSpread = 0.5m;
if (crossUp && Math.Abs(momentumValue - signalValue) >= minSpread)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
else if (crossDown && Math.Abs(momentumValue - signalValue) >= minSpread)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevMomentum = momentumValue;
_prevSignal = signalValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_prevMomentum = null;
_prevSignal = null;
_momentum = null;
_momentumHistory.Clear();
base.OnReseted();
}
}