Открыть на GitHub

Стратегия Moving Average Shift

Обзор

Стратегия представляет собой порт на StockSharp классического советника Moving Average из MetaTrader 4. Система анализирует завершённые свечи и сравнивает их с сдвинутой простой скользящей средней (SMA), чтобы определить смену направления. Сделки совершаются рыночными заявками, в рынке одновременно может находиться только одна позиция.

Логика торговли

  1. Подписка на свечи выбранного таймфрейма (по умолчанию 5 минут) и расчёт SMA с заданным периодом.
  2. Сдвиг SMA на указанное количество завершённых свечей для имитации поведения функции iMA из MetaTrader.
  3. Анализ предыдущей завершённой свечи:
    • Бычий пробой (открытие ниже SMA и закрытие выше) открывает длинную позицию, если активных позиций нет.
    • Медвежий пробой (открытие выше и закрытие ниже SMA) открывает короткую позицию при отсутствии открытых позиций.
  4. Управление выходом на тех же условиях:
    • Длинная позиция закрывается, когда последняя свеча пересекает SMA сверху вниз.
    • Короткая позиция закрывается, когда свеча пересекает SMA снизу вверх.
  5. Одновременно поддерживается только одна позиция, что повторяет логику исходного советника, который последовательно чередовал покупки и продажи.

Параметры

Имя Описание Значение по умолчанию
CandleType Свечная серия для расчётов, выбирается любой таймфрейм DataType. 5-минутный таймфрейм
MovingPeriod Количество свечей в периоде SMA. 12
MovingShift Сдвиг значения SMA в завершённых свечах, аналог аргумента shift у iMA. 6
BaseVolume Базовый объём заявок для входа в позицию. Используется одинаково для длинных и коротких сделок. 1

Работа с индикатором

  • Индикатор SimpleMovingAverage создаётся в OnStarted и связывается со свечами через высокоуровневый метод Bind.
  • Выходные значения SMA попадают в небольшую очередь FIFO, что позволяет получить значение MovingShift свечей назад без ручного пересчёта индикатора.
  • Очередь хранит только MovingShift + 1 значений, поэтому объём памяти остаётся постоянным даже при большом сдвиге.

Управление ордерами и рисками

  • Заявки отправляются методами BuyMarket/SellMarket, размер задаётся параметром BaseVolume. При закрытии используется текущий модуль позиции, чтобы полностью выйти из сделки.
  • В оригинале MetaTrader лоты менялись в зависимости от свободной маржи и серии убытков. В версии для StockSharp логика упрощена: пользователь самостоятельно задаёт объём через параметр, что избавляет от зависимости от брокерских метрик и сохраняет главные правила входа/выхода.

Особенности конверсии

  • Сигналы оцениваются по предыдущей свече, повторяя проверку Volume[0] == 1 из MetaTrader, которая ждала появления нового бара.
  • Обрабатываются только завершённые свечи (CandleStates.Finished), чтобы не реагировать на незакрытые бары.
  • Стратегия использует вспомогательные методы StockSharp для отображения свечей, индикатора и сделок на графике при наличии области графика.

Использование

  1. Скомпилируйте стратегию в Designer, Shell или Runner.
  2. Выберите инструмент и портфель.
  3. Настройте параметры, если требуется другой таймфрейм, период или объём.
  4. Запустите стратегию — она подпишется на свечи, будет отслеживать пересечения SMA и торговать по описанным правилам.

Дополнительные идеи

  • При необходимости добавить защитные стопы и цели через StartProtection.
  • Заменить простую SMA на другой индикатор (EMA, LWMA и т.д.), сохранив текущую схему подписки.
  • Реализовать масштабирование позиции, изменяя метод GetEntryVolume.
using System;

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

namespace StockSharp.Samples.Strategies;

public class MovingAverageShiftStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevClose;
	private decimal _prevEma;
	private bool _hasPrev;
	private int _cooldownRemaining;

	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public MovingAverageShiftStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 50).SetDisplay("EMA Period", "EMA lookback", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 200).SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevClose = default;
		_prevEma = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevClose = 0;
		_prevEma = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal ema)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;
		if (!_hasPrev) { _prevClose = close; _prevEma = ema; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevClose = close;
			_prevEma = ema;
			return;
		}

		if (_prevClose <= _prevEma && close > ema && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevClose >= _prevEma && close < ema && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevClose = close;
		_prevEma = ema;
	}
}