Ver en GitHub

MA by MA Crossover Strategy

This strategy implements a double smoothed moving average crossover. The price series is smoothed by a fast exponential moving average (EMA). The result of the fast EMA is then smoothed again by a slower EMA. The two series are compared to generate signals:

  • A long position is opened when the fast EMA crosses above the slow EMA.
  • A short position is opened when the fast EMA crosses below the slow EMA. Any existing opposite position is closed on crossover.

The strategy works on any timeframe of candlesticks.

Parameters

  • FastLength – period of the fast EMA.
  • SlowLength – period of the slow EMA applied to the fast EMA output.
  • EnableLong – allow opening long positions.
  • EnableShort – allow opening short positions.
  • CandleType – type of candles used for calculations.

Details

  • Entry Criteria:
    • Long: fast EMA crosses above slow EMA.
    • Short: fast EMA crosses below slow EMA.
  • Long/Short: Both directions supported.
  • Exit Criteria:
    • Opposite crossover closes an existing position.
  • Stops: No explicit stop-loss or take-profit is used.
  • Default Values:
    • FastLength = 7
    • SlowLength = 7
    • EnableLong = true
    • EnableShort = true
    • CandleType = 12-hour timeframe
  • Filters:
    • Category: Trend following
    • Direction: Both
    • Indicators: Moving averages
    • Stops: No
    • Complexity: Basic
    • Timeframe: Any
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>
/// Double smoothed moving average crossover strategy.
/// </summary>
public class MaByMaStrategy : Strategy
{
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _minSpreadPercent;
	private readonly StrategyParam<int> _cooldownBars;

	private bool _isInitialized;
	private bool _wasFastBelowSlow;
	private int _cooldownRemaining;

	public int FastLength { get => _fastLength.Value; set => _fastLength.Value = value; }
	public int SlowLength { get => _slowLength.Value; set => _slowLength.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public decimal MinSpreadPercent { get => _minSpreadPercent.Value; set => _minSpreadPercent.Value = value; }
	public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }

	public MaByMaStrategy()
	{
		_fastLength = Param(nameof(FastLength), 7)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA Length", "Period for fast EMA", "Indicator");

		_slowLength = Param(nameof(SlowLength), 21)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA Length", "Period for slow EMA", "Indicator");

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

		_minSpreadPercent = Param(nameof(MinSpreadPercent), 0.003m)
			.SetDisplay("Minimum Spread %", "Minimum normalized spread between fast and slow EMA values", "Filters");

		_cooldownBars = Param(nameof(CooldownBars), 6)
			.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_isInitialized = false;
		_wasFastBelowSlow = false;
		_cooldownRemaining = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var fastMa = new ExponentialMovingAverage { Length = FastLength };
		var slowMa = new ExponentialMovingAverage { Length = SlowLength };

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fastMa, slowMa, ProcessCandle).Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fastMa);
			DrawIndicator(area, slowMa);
			DrawOwnTrades(area);
		}
	}

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

		if (_cooldownRemaining > 0)
			_cooldownRemaining--;

		var spreadPercent = slowValue != 0m ? Math.Abs(fastValue - slowValue) / slowValue : 0m;
		if (!_isInitialized)
		{
			_wasFastBelowSlow = fastValue < slowValue;
			_isInitialized = true;
			return;
		}

		var isFastBelowSlow = fastValue < slowValue;
		if (_cooldownRemaining == 0 && spreadPercent >= MinSpreadPercent)
		{
			if (_wasFastBelowSlow && !isFastBelowSlow && Position <= 0)
			{
				if (Position < 0)
					BuyMarket();

				BuyMarket();
				_cooldownRemaining = CooldownBars;
			}
			else if (!_wasFastBelowSlow && isFastBelowSlow && Position >= 0)
			{
				if (Position > 0)
					SellMarket();

				SellMarket();
				_cooldownRemaining = CooldownBars;
			}
		}

		_wasFastBelowSlow = isFastBelowSlow;
	}
}