Открыть на GitHub

Стратегия Two MA Four Level

Стратегия в точности повторяет логику советника MetaTrader "2MA_4Level", но реализована на высокоуровневом API StockSharp. Используются две сглаженные скользящие средние (SMMA) по медианной цене свечи. Сигналы формируются при пробое быстрой средней через медленную не только по базовой линии, но и через четыре дополнительные зоны, смещённые на заданное количество пунктов. Новая сделка открывается только при отсутствии позиции, а выход осуществляется стоп-лоссом и тейк-профитом в пунктах.

Логика работы

  • Рассчитываются две SMMA: быстрая и медленная (по умолчанию 50 и 130 периодов).
  • На каждой завершённой свече сравниваются значения скользящих на текущей и предыдущей свече.
  • Проводится пять проверок пересечения быстрой и медленной SMA:
    1. базовая линия без смещения;
    2. медленная SMA + MostTopLevel пунктов;
    3. медленная SMA + TopLevel пунктов;
    4. медленная SMA − LowermostLevel пунктов;
    5. медленная SMA − LowerLevel пунктов.
  • Если быстрая SMA пересекает одну из линий снизу вверх — открывается длинная позиция (только если позиция отсутствует). Пересечение сверху вниз запускает короткую сделку.
  • Функция StartProtection автоматически навешивает стоп и тейк, рассчитанные через шаг цены инструмента (Security.PriceStep).

Стратегия не наращивает позицию: новая сделка возможна только после полного закрытия предыдущей (по стопу или по цели).

Параметры

Параметр Значение по умолчанию Описание
FastPeriod 50 Период быстрой SMMA. Должен быть меньше SlowPeriod.
SlowPeriod 130 Период медленной SMMA.
MostTopLevel 500 Верхний уровень (в пунктах) для самого широкого подтверждения сигнала. Должен быть больше TopLevel.
TopLevel 250 Второй верхний уровень (в пунктах) для подтверждения сигнала.
LowerLevel 250 Второй нижний уровень (в пунктах). Должен быть меньше LowermostLevel.
LowermostLevel 500 Самый нижний уровень (в пунктах) для подтверждения сигнала.
TakeProfitPips 55 Расстояние до тейк-профита в пунктах.
StopLossPips 260 Расстояние до стоп-лосса в пунктах.
CandleType 15-минутные свечи Таймфрейм, на котором вычисляются индикаторы и принимаются решения.

Особенности реализации

  • Используется медианная цена ((High + Low) / 2), что соответствует настройке PRICE_MEDIAN в MT5.
  • Пересечения анализируются только на закрытых свечах, что исключает влияние незавершённых баров.
  • StartProtection вызывается один раз при запуске, поэтому каждая заявка автоматически получает заданные уровни риска.
  • В OnStarted предусмотрены проверки корректности параметров; при ошибке стратегия пишет сообщение в лог и останавливается.

Рекомендации по применению

  1. Убедитесь, что у инструмента задан шаг цены (PriceStep). Если данных нет, по умолчанию берётся значение 1, что может исказить расчёт пунктов.
  2. Для MT5 стратегия рассчитана на хеджинговые счета; в StockSharp соблюдается то же ограничение — в рынке может быть только одна позиция.
  3. Для FastPeriod и SlowPeriod включены флаги оптимизации (SetCanOptimize), что позволяет использовать стандартный оптимизатор StockSharp.
  4. Поскольку выход возможен только по стоп-лоссу или тейк-профиту, внимательно подбирайте размеры уровней с учётом волатильности инструмента.

Файлы

  • CS/TwoMaFourLevelStrategy.cs — реализация стратегии на C#.
  • README.md — документация на английском языке.
  • README_zh.md — документация на китайском языке.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Two smoothed moving average crossover strategy with level offsets.
/// </summary>
public class TwoMaFourLevelStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _mostTopLevel;
	private readonly StrategyParam<int> _topLevel;
	private readonly StrategyParam<int> _lowerLevel;
	private readonly StrategyParam<int> _lowermostLevel;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<DataType> _candleType;

	private SmoothedMovingAverage _fastMa = null!;
	private SmoothedMovingAverage _slowMa = null!;
	private decimal? _prevFast;
	private decimal? _prevSlow;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int MostTopLevel { get => _mostTopLevel.Value; set => _mostTopLevel.Value = value; }
	public int TopLevel { get => _topLevel.Value; set => _topLevel.Value = value; }
	public int LowerLevel { get => _lowerLevel.Value; set => _lowerLevel.Value = value; }
	public int LowermostLevel { get => _lowermostLevel.Value; set => _lowermostLevel.Value = value; }
	public int TakeProfitPips { get => _takeProfitPips.Value; set => _takeProfitPips.Value = value; }
	public int StopLossPips { get => _stopLossPips.Value; set => _stopLossPips.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public TwoMaFourLevelStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Period of the fast smoothed MA", "Moving Averages")
			.SetOptimize(20, 150, 5);

		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Period of the slow smoothed MA", "Moving Averages")
			.SetOptimize(60, 300, 5);

		_mostTopLevel = Param(nameof(MostTopLevel), 2)
			.SetGreaterThanZero()
			.SetDisplay("Extreme Upper Level", "Highest positive offset in points", "Levels");

		_topLevel = Param(nameof(TopLevel), 1)
			.SetGreaterThanZero()
			.SetDisplay("Upper Level", "Second positive offset in points", "Levels");

		_lowerLevel = Param(nameof(LowerLevel), 1)
			.SetGreaterThanZero()
			.SetDisplay("Lower Level", "Second negative offset in points", "Levels");

		_lowermostLevel = Param(nameof(LowermostLevel), 2)
			.SetGreaterThanZero()
			.SetDisplay("Extreme Lower Level", "Largest negative offset in points", "Levels");

		_takeProfitPips = Param(nameof(TakeProfitPips), 500)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pips)", "Distance to take profit", "Risk");

		_stopLossPips = Param(nameof(StopLossPips), 1000)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pips)", "Distance to stop loss", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for analysis", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		if (Security != null)
			yield return (Security, CandleType);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevSlow = null;
	}

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

		if (FastPeriod >= SlowPeriod)
		{
			this.LogError("FastPeriod must be less than SlowPeriod.");
			Stop();
			return;
		}

		if (MostTopLevel <= TopLevel)
		{
			this.LogError("MostTopLevel must be greater than TopLevel.");
			Stop();
			return;
		}

		if (LowerLevel >= LowermostLevel)
		{
			this.LogError("LowerLevel must be less than LowermostLevel.");
			Stop();
			return;
		}

		_fastMa = new SmoothedMovingAverage { Length = FastPeriod };
		_slowMa = new SmoothedMovingAverage { Length = SlowPeriod };

		var pip = Security?.PriceStep ?? 1m;

		StartProtection(
			takeProfit: new Unit(TakeProfitPips * pip, UnitTypes.Absolute),
			stopLoss: new Unit(StopLossPips * pip, UnitTypes.Absolute));

		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 fast, decimal slow)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_prevFast is null || _prevSlow is null)
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		var pip = Security?.PriceStep ?? 0.05m;
		var signal = GetSignal(fast, slow, _prevFast.Value, _prevSlow.Value, pip);

		if (signal > 0)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		else if (signal < 0)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}

		_prevFast = fast;
		_prevSlow = slow;
	}

	private int GetSignal(decimal fast, decimal slow, decimal prevFast, decimal prevSlow, decimal pip)
	{
		if (IsCrossUp(prevFast, fast, prevSlow, slow, 0m) ||
			IsCrossUp(prevFast, fast, prevSlow, slow, MostTopLevel * pip) ||
			IsCrossUp(prevFast, fast, prevSlow, slow, TopLevel * pip) ||
			IsCrossUp(prevFast, fast, prevSlow, slow, -LowermostLevel * pip) ||
			IsCrossUp(prevFast, fast, prevSlow, slow, -LowerLevel * pip))
		{
			return 1;
		}

		if (IsCrossDown(prevFast, fast, prevSlow, slow, 0m) ||
			IsCrossDown(prevFast, fast, prevSlow, slow, MostTopLevel * pip) ||
			IsCrossDown(prevFast, fast, prevSlow, slow, TopLevel * pip) ||
			IsCrossDown(prevFast, fast, prevSlow, slow, -LowermostLevel * pip) ||
			IsCrossDown(prevFast, fast, prevSlow, slow, -LowerLevel * pip))
		{
			return -1;
		}

		return 0;
	}

	private static bool IsCrossUp(decimal prevFast, decimal fast, decimal prevSlow, decimal slow, decimal offset)
	{
		var prevSlowShifted = prevSlow + offset;
		var slowShifted = slow + offset;
		return prevFast <= prevSlowShifted && fast > slowShifted;
	}

	private static bool IsCrossDown(decimal prevFast, decimal fast, decimal prevSlow, decimal slow, decimal offset)
	{
		var prevSlowShifted = prevSlow + offset;
		var slowShifted = slow + offset;
		return prevFast >= prevSlowShifted && fast < slowShifted;
	}
}