View on GitHub

Milestone Trend Strategy

This strategy is a StockSharp port of the Milestone 22.5 expert advisor. It trades pullbacks within a trend by combining two smoothed moving averages with a volatility and spike filter. When a candle breaks the previous bar's extreme and the fast average supports the move, a position is opened in the direction of the dominant trend. ATR prevents trading in quiet markets and large candle bodies are treated as spikes.

Backtests of the original MQL version show good performance on major forex pairs. The C# translation focuses on clarity and uses only market orders for entries and exits.

Details

  • Entry Criteria:
    • Trend strength between MinTrend and MaxTrend.
    • Candle breaks the prior high or low and fast SMA confirms.
    • ATR above MinRange and candle body below CandleSpike.
  • Long/Short: Both directions.
  • Exit Criteria: Opposite signal closes the position.
  • Stops: Not implemented; opposite signal acts as stop.
  • Default Values:
    • SlowMaPeriod = 120
    • FastMaPeriod = 30
    • AtrPeriod = 14
    • MinTrend = 10
    • MaxTrend = 100
    • MinRange = 5
    • CandleSpike = 10
    • CandleType = TimeSpan.FromMinutes(1)
  • Filters:
    • Category: Trend following
    • Direction: Both
    • Indicators: SMA, ATR
    • Stops: No
    • Complexity: Intermediate
    • Timeframe: Intraday
    • Seasonality: No
    • Neural networks: No
    • Divergence: No
    • Risk level: Medium
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>
/// Milestone trend strategy using EMA crossover with trend confirmation.
/// </summary>
public class MilestoneTrendStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public MilestoneTrendStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast EMA period", "General");

		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow EMA period", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle type", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = 0;
		_prevSlow = 0;
		_hasPrev = false;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };

		SubscribeCandles(CandleType)
			.Bind(fast, slow, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal)
	{
		if (candle.State != CandleStates.Finished) return;

		if (!_hasPrev)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			_hasPrev = true;
			return;
		}

		var crossUp = _prevFast <= _prevSlow && fastVal > slowVal;
		var crossDown = _prevFast >= _prevSlow && fastVal < slowVal;

		if (crossUp && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
		}
		else if (crossDown && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}