Открыть на GitHub

Стратегия возврата Above Below MA

Обзор

Above Below MA — это порт на StockSharp советника MetaTrader 4 «AboveBelowMA». Оригинал работает на 15-минутном графике GBP/USD, вычисляет экспоненциальную скользящую среднюю (EMA) длиной 1 по типичной цене (High + Low + Close) / 3 и отслеживает моменты, когда цена отклоняется от EMA против её текущего наклона. Стратегия стремится закрыть противоположные позиции и открыть сделку в сторону наклона EMA. Реализация использует высокоуровневые API StockSharp (SubscribeCandles и Bind).

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

  • Подписка на выбранный тип свечей (по умолчанию 15 минут) и расчёт EMA по типичной цене.
  • Хранение последнего и предыдущего значений EMA, чтобы определить направление наклона: рост EMA даёт приоритет покупкам, снижение — продажам.
  • Сигнал на покупку: свеча открывается минимум на один шаг цены ниже EMA, закрывается ниже EMA, а предыдущее значение EMA меньше текущего. Стратегия закрывает короткие позиции и, если позиция обнулена, открывает покупку по рынку.
  • Сигнал на продажу: свеча открывается минимум на один шаг цены выше EMA, закрывается выше EMA, а предыдущее значение EMA больше текущего. Вначале закрываются длинные позиции, после чего выполняется рыночная продажа.
  • Сигналы обрабатываются только по завершённым свечам, чтобы исключить ложные входы на незакрытых барах.

Управление объёмом

  • В MetaTrader размер сделки равен AccountFreeMargin / 10000, но не более 5 лотов. В версии для StockSharp аналогичная логика активируется параметром UseDynamicVolume: текущая стоимость портфеля делится на BalanceToVolumeDivider (10000 по умолчанию).
  • Итоговый объём ограничивается параметром MaxVolume, повторяя максимум в 5 лотов. При отключённой динамике используется фиксированный объём InitialVolume.
  • Значение объёма дополнительно выравнивается по шагу объёма инструмента и его минимальным/максимальным ограничениям.

Параметры

Параметр Описание
EmaLength Период EMA (по умолчанию 1, как в исходном советнике).
CandleType Тип свечей, используемый для расчёта сигнала (по умолчанию 15 минут).
InitialVolume Фиксированный объём при отключённой динамике.
UseDynamicVolume Включение динамического расчёта объёма (Баланс / BalanceToVolumeDivider).
BalanceToVolumeDivider Делитель для расчёта динамического объёма, аналог AccountFreeMargin / 10000.
MaxVolume Максимально допустимый объём позиции.

Примечания

  • Перед открытием позиции в противоположную сторону вызывается ClosePosition(), что повторяет обработку CheckOrders в MetaTrader.
  • Перерасчёт сигналов только на закрытых свечах может давать небольшое запаздывание относительно тикового варианта, но повышает устойчивость в тестах и реальной торговле.
  • Для корректной работы динамического модуля убедитесь, что инструмент предоставляет валидные PriceStep, VolumeStep и данные о стоимости портфеля.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Above/Below MA Rejoin strategy.
/// Buys when price is below a rising EMA (pullback in uptrend).
/// Sells when price is above a falling EMA (pullback in downtrend).
/// </summary>
public class AboveBelowMaRejoinStrategy : Strategy
{
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevEma;
	private decimal _prevClose;
	private bool _hasPrev;

	public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public AboveBelowMaRejoinStrategy()
	{
		_emaLength = Param(nameof(EmaLength), 20)
			.SetDisplay("EMA Period", "EMA lookback period", "Indicators");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevEma = 0m;
		_prevClose = 0m;
		_hasPrev = false;
	}

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

		_hasPrev = false;

		var ema = new ExponentialMovingAverage { Length = EmaLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ema, ProcessCandle)
			.Start();
	}

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

		var close = candle.ClosePrice;

		if (!_hasPrev)
		{
			_prevEma = emaValue;
			_prevClose = close;
			_hasPrev = true;
			return;
		}

		var emaRising = emaValue > _prevEma;
		var emaFalling = emaValue < _prevEma;

		// Price rejoins from below in uptrend - buy
		if (emaRising && _prevClose < _prevEma && close >= emaValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Price rejoins from above in downtrend - sell
		else if (emaFalling && _prevClose > _prevEma && close <= emaValue && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevEma = emaValue;
		_prevClose = close;
	}
}