Открыть на GitHub

Стратегия Inverse Reaction

Обзор

Inverse Reaction — это стратегия возврата к среднему, перенесённая с MetaTrader-советника "IREA". Она реагирует на аномально крупные колебания отдельной свечи и ожидает обратное движение на следующей свече. Стратегия рассчитывает динамический порог уверенности по недавним изменениям цены и открывает сделки только тогда, когда импульс превышает порог, но остаётся в пределах заданных границ. Одновременно удерживается не более одной позиции.

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

  1. Индикатор обратной реакции — для каждой закрытой свечи вычисляется разница между ценой открытия и закрытия. Абсолютное значение разницы подаётся на простое скользящее среднее длиной MaPeriod. Полученный средний диапазон умножается на коэффициент Coefficient, формируя динамический порог (аналог Dynamic Confidence Level оригинального индикатора).
  2. Фильтр сигнала — абсолютная разница открытия/закрытия текущей свечи должна быть больше динамического порога, больше MinCriteriaPoints * PriceStep и меньше MaxCriteriaPoints * PriceStep. Если предыдущая свеча уже удовлетворяла тем же условиям, сигнал игнорируется — таким образом повторные срабатывания подавляются так же, как в оригинальном советнике.
  3. Направление сделки — отрицательное изменение (медвежья свеча) трактуется как шанс на отскок вверх, поэтому открывается покупка. Положительное изменение предполагает разворот вниз и приводит к продаже. Новая сделка возможна только при отсутствии открытых позиций.
  4. Управление риском — после входа стратегия следит за последующими свечами. При достижении расчётных уровней стоп-лосса или тейк-профита (переведённых из пунктов через PriceStep) позиция закрывается рыночным ордером. Дополнительно вызывается StartProtection() для подключения встроенной защиты StockSharp.

Параметры

Параметр Описание
StopLossPoints Размер стоп-лосса в пунктах (умножается на PriceStep).
TakeProfitPoints Размер тейк-профита в пунктах.
TradeVolume Объём каждой сделки.
SlippagePoints Информационный параметр, сохранённый для совместимости с версией MQL; непосредственно не применяется.
MinCriteriaPoints Минимально допустимый размер свечи (в пунктах) для сигнала.
MaxCriteriaPoints Максимально допустимый размер свечи (в пунктах).
Coefficient Множитель динамического порога уверенности.
MaPeriod Период скользящего среднего внутри индикатора (не менее 3).
CandleType Тип/таймфрейм свечей (по умолчанию 1 час).

Рекомендации по использованию

  • Убедитесь, что у инструмента задан корректный PriceStep. Если шаг цены отсутствует, стратегия использует значение 1.0, что может исказить пороги.
  • Подбирайте MinCriteriaPoints и MaxCriteriaPoints под волатильность выбранного таймфрейма. Слишком узкий диапазон отсечёт большинство сигналов, слишком широкий пропустит экстремальные движения.
  • Значение Coefficient по умолчанию (1.618) повторяет золотое сечение, использованное в исходном индикаторе. При увеличении коэффициента стратегия будет реагировать только на более редкие аномальные свечи.
  • Закрытие по стопу/профиту выполняется рыночными ордерами на следующей свече, достигшей уровня. Фактическая цена исполнения может отличаться от целевого уровня — учитывайте это в тестах и при работе в реальном времени.
  • Стратегия никогда не усредняет и не накапливает позиции. Сигнал рассматривается только после закрытия текущей сделки.

Примечания

  • Перед реальной торговлей обязательно протестируйте стратегию на исторических данных. Оригинальная версия проектировалась для валютного рынка, поэтому для других активов потребуется подстройка параметров.
  • Параметр SlippagePoints сохранён для полноты, но не влияет на регистрацию заявок, поскольку механизм проскальзывания в StockSharp отличается от MetaTrader.
  • Значение MaPeriod должно оставаться ≥ 3. В оригинальном коде меньшие значения запрещались, чтобы предотвратить нестабильное поведение индикатора.
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>
/// Strategy that reacts to large single-bar moves expecting a mean reversion.
/// </summary>
public class InverseReactionStrategy : Strategy
{
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _coefficient;
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _absChanges = new();

	public decimal StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public decimal TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
	public decimal Coefficient { get => _coefficient.Value; set => _coefficient.Value = value; }
	public int MaPeriod { get => _maPeriod.Value; set => _maPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public InverseReactionStrategy()
	{
		_stopLossPoints = Param(nameof(StopLossPoints), 1000m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Stop-loss distance in points", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 250m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit", "Take-profit distance in points", "Risk");

		_coefficient = Param(nameof(Coefficient), 1.618m)
			.SetGreaterThanZero()
			.SetDisplay("Coefficient", "Confidence coefficient", "Signal");

		_maPeriod = Param(nameof(MaPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Moving average length", "Signal");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe", "General");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_absChanges.Clear();
	}

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

		var step = Security?.PriceStep ?? 1m;
		if (step <= 0m) step = 1m;

		StartProtection(
			takeProfit: new Unit(TakeProfitPoints * step, UnitTypes.Absolute),
			stopLoss: new Unit(StopLossPoints * step, UnitTypes.Absolute),
			useMarketOrders: true);

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

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

		var change = candle.ClosePrice - candle.OpenPrice;
		var absChange = Math.Abs(change);

		_absChanges.Add(absChange);
		if (_absChanges.Count > MaPeriod)
			_absChanges.RemoveAt(0);

		if (_absChanges.Count < MaPeriod)
			return;

		var avg = 0m;
		for (int i = 0; i < _absChanges.Count; i++)
			avg += _absChanges[i];
		avg /= _absChanges.Count;

		var threshold = avg * Coefficient;

		if (Position != 0)
			return;

		if (absChange > threshold && absChange > 0m)
		{
			if (change < 0m)
			{
				// Large bearish bar => expect reversion upward
				BuyMarket();
			}
			else
			{
				// Large bullish bar => expect reversion downward
				SellMarket();
			}
		}
	}
}