Открыть на GitHub

Стратегия Mean Reversion Momentum

Обзор

Стратегия Mean Reversion — порт эксперта MetaTrader Mean reversion.mq4. В версии для StockSharp сохранена исходная идея: покупки выполняются после серии понижающихся закрытий, продажи — после последовательного роста. Сигналы подтверждаются сочетанием двух линейно-взвешенных средних, импульсом на старшем таймфрейме и месячным MACD.

После входа стратегия повторяет правила управления позицией из оригинала: стоп-лосс и тейк-профит в пунктах, опциональный перевод в безубыток и трейлинг-стоп, фиксирующий прибыль по мере движения цены.

Торговая логика

  1. Рабочий таймфрейм — выбираемый ряд свечей (по умолчанию 15 минут).
  2. Поиск истощения — анализируется BarsToCount последних закрытий. Для покупки требуется, чтобы текущее закрытие было ниже каждого из предыдущих; для продажи — выше.
  3. Фильтр тренда — быстрая LWMA (FastMaLength) должна быть выше медленной (SlowMaLength) для лонга и ниже — для шорта.
  4. Импульсный фильтр — индикатор Momentum (MomentumLength) считается на «метатрейдеровском» старшем таймфрейме (M15 → H1, H1 → D1 и т.д.). Минимум одно из трёх последних значений должно отклоняться от 100 больше, чем MomentumThreshold.
  5. Подтверждение MACD — месячный MACD (12/26/9) обязан иметь основную линию выше сигнальной для покупок и ниже — для продаж.

При выполнении условий открывается позиция объёмом OrderVolume. Обратный сигнал закрывает текущую позицию перед разворотом.

Управление позицией

  • Стоп-лосс и тейк-профит — задаются в пунктах параметрами StopLossPips и TakeProfitPips.
  • Безубыток — при активации стоп переносится к цене входа плюс BreakEvenOffsetPips, как только прибыль превысит BreakEvenTriggerPips.
  • Трейлинг-стоп — если EnableTrailing = true и прибыль превышает TrailingStopPips, стоп сопровождает цену с шагом TrailingStepPips.

Расчёт пунктов выполняется исходя из шага цены инструмента, как в MetaTrader.

Параметры

Название Описание Значение по умолчанию
OrderVolume Объём рыночных заявок. 1
CandleType Основной ряд свечей. M15
BarsToCount Количество закрытий для проверки истощения. 10
FastMaLength Период быстрой LWMA. 6
SlowMaLength Период медленной LWMA. 85
MomentumLength Период Momentum на старшем таймфрейме. 14
MomentumThreshold Минимальное отклонение Momentum от 100. 0.3
StopLossPips Размер стоп-лосса в пунктах. 20
TakeProfitPips Размер тейк-профита в пунктах. 50
UseBreakEven Включение переноса стопа в безубыток. false
BreakEvenTriggerPips Прибыль для активации безубытка. 30
BreakEvenOffsetPips Дополнительный запас при переносе стопа. 30
EnableTrailing Включение трейлинг-стопа. true
TrailingStopPips Прибыль для запуска трейлинга. 40
TrailingStepPips Дистанция трейлинг-стопа. 40

Примечания

  • Старший таймфрейм для Momentum копирует шаги MetaTrader: M1→M15, M5→M30, M15→H1, M30→H4, H1→D1, H4→W1, D1→MN1, W1→MN1.
  • MACD всегда рассчитывается на месячном таймфрейме (MN1).
  • Стратегия требует свечных таймфреймов; тиковые и диапазонные бары не поддерживаются.
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>
/// Simplified from "Mean Reversion" MetaTrader expert.
/// Buys after multi-bar sell-off when RSI is oversold, sells after multi-bar rally when RSI is overbought.
/// Uses consecutive bar count for exhaustion detection with RSI confirmation.
/// </summary>
public class MeanReversionMomentumStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _barsToCount;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiOverbought;
	private readonly StrategyParam<decimal> _rsiOversold;

	private RelativeStrengthIndex _rsi;
	private readonly List<decimal> _closeHistory = new();

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

	public int BarsToCount
	{
		get => _barsToCount.Value;
		set => _barsToCount.Value = value;
	}

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	public decimal RsiOverbought
	{
		get => _rsiOverbought.Value;
		set => _rsiOverbought.Value = value;
	}

	public decimal RsiOversold
	{
		get => _rsiOversold.Value;
		set => _rsiOversold.Value = value;
	}

	public MeanReversionMomentumStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe", "General");

		_barsToCount = Param(nameof(BarsToCount), 5)
			.SetGreaterThanZero()
			.SetDisplay("Bars To Count", "Number of consecutive bars for exhaustion detection", "Signal");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period for confirmation", "Indicators");

		_rsiOverbought = Param(nameof(RsiOverbought), 70m)
			.SetDisplay("RSI Overbought", "RSI level for sell signal", "Signals");

		_rsiOversold = Param(nameof(RsiOversold), 30m)
			.SetDisplay("RSI Oversold", "RSI level for buy signal", "Signals");
	}

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

		_closeHistory.Clear();
		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };

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

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

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

		_closeHistory.Add(candle.ClosePrice);
		if (_closeHistory.Count > BarsToCount + 1)
			_closeHistory.RemoveAt(0);

		if (!_rsi.IsFormed || _closeHistory.Count < BarsToCount + 1)
			return;

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// Count consecutive down bars
		var downCount = 0;
		for (int i = _closeHistory.Count - 1; i >= 1; i--)
		{
			if (_closeHistory[i] < _closeHistory[i - 1])
				downCount++;
			else
				break;
		}

		// Count consecutive up bars
		var upCount = 0;
		for (int i = _closeHistory.Count - 1; i >= 1; i--)
		{
			if (_closeHistory[i] > _closeHistory[i - 1])
				upCount++;
			else
				break;
		}

		// Multi-bar sell-off + RSI oversold -> mean reversion buy
		if (downCount >= BarsToCount && rsiValue < RsiOversold)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));

			if (Position <= 0)
				BuyMarket(volume);
		}
		// Multi-bar rally + RSI overbought -> mean reversion sell
		else if (upCount >= BarsToCount && rsiValue > RsiOverbought)
		{
			if (Position > 0)
				SellMarket(Position);

			if (Position >= 0)
				SellMarket(volume);
		}
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_closeHistory.Clear();
		_rsi = null;

		base.OnReseted();
	}
}