Ver no GitHub

3207 – MA Trend Strategy

Overview

The MA Trend Strategy replicates the MetaTrader expert MA Trend.mq5 using StockSharp's high-level API. The bot follows a single linear weighted moving average with a configurable forward shift. When the closing price rises above the shifted average the strategy goes long, while a drop below the average opens short positions. Optional stop-loss, take-profit, and trailing-stop rules mirror the risk controls from the original MQL implementation.

Trading Logic

  1. Subscribe to the configured candle type (defaults to 1-minute time frame) and calculate a moving average using the selected method and price source.
  2. Shift the moving average value forward by the requested number of completed candles before comparing it with the most recent close.
  3. Generate signals:
    • Long – close price above the shifted MA (reversed when ReverseSignals is enabled).
    • Short – close price below the shifted MA (reversed when ReverseSignals is enabled).
  4. Apply position management options:
    • Close the opposite exposure before opening a trade when CloseOpposite is true.
    • Block new entries if OnlyOnePosition is enabled and a position already exists.
  5. Manage exits with stop-loss, take-profit, and trailing-stop distances expressed in pips. The trailing logic requires price to move by TrailingStopPips + TrailingStepPips before tightening the stop, just like the MQL expert.

Parameters

Name Type Default Description
OrderVolume decimal 0.1 Order size in lots/contracts.
StopLossPips int 50 Stop-loss distance in pips. Zero disables the fixed stop.
TakeProfitPips int 140 Take-profit distance in pips. Zero disables the target.
TrailingStopPips int 15 Trailing stop distance. Set to zero to disable trailing.
TrailingStepPips int 5 Extra pips required before moving the trailing stop. Must stay positive when TrailingStopPips is greater than zero.
MaPeriod int 12 Moving average length.
MaShift int 3 Number of completed bars used to shift the moving average forward.
MaMethod MovingAverageKind Weighted Moving average calculation mode (Simple, Exponential, Smoothed, Weighted).
AppliedPrice AppliedPriceMode Weighted Candle price used as indicator input (Close, Open, High, Low, Median, Typical, Weighted).
OnlyOnePosition bool false Restrict the strategy to a single open position.
ReverseSignals bool false Swap long/short signal directions.
CloseOpposite bool false Close opposite exposure before entering a new position.
CandleType DataType 1 minute Candle type/time frame supplied to the indicator.

Notes

  • The pip size automatically adapts to instruments with 3/5 decimal pricing to match the original MetaTrader behaviour.
  • Trailing-stop validation reproduces the MQL check: if TrailingStopPips > 0 and TrailingStepPips <= 0, the strategy throws an exception during start.
  • All indicator updates and order decisions use finished candles only, ensuring deterministic backtests.
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 MaTrendStrategy : 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 MaTrendStrategy()
	{
		_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;
	}
}