Открыть на GitHub

Стратегия Volatility Pivot

Обзор

Volatility Pivot — это порт оригинального советника Exp_VolatilityPivot.mq5 на высокоуровневый API StockSharp. Стратегия воссоздаёт одноимённый индикатор: строит две адаптивные линии-пивоты, которые следуют за ценой на основе волатильности ATR или фиксированного ценового отклонения. При смене доминирующей стороны индикатор формирует одиночные стрелки-сигналы. В версии для StockSharp можно выбрать режим WithTrend (работа по направлению пробоя) или CounterTrend (контртрендовый отклик).

В отличие от реализации на MQL, вся логика использует только завершённые свечи выбранного CandleType. В режиме Atr значение ATR с периодом AtrPeriod дополнительно сглаживается EMA длиной SmoothingPeriod и умножается на AtrMultiplier. В режиме PriceDeviation используется фиксированное смещение DeltaPrice. Полученные уровни задают границы бычьего и медвежьего пивотов и определяют сигналы входа/выхода.

Рыночные данные и индикаторы

  • Базовые свечи (CandleType) – все расчёты ведутся на этом таймфрейме. По умолчанию используется 4-часовой интервал, как в исходном советнике.
  • ATR + EMA – в режиме Atr стратегия применяет индикатор AverageTrueRange и экспоненциальное сглаживание, чтобы получить адаптивную ширину пивота.
  • Режим фиксированного отклонения – при PriceDeviation расстояние между линиями всегда равно DeltaPrice, что удобно при заранее заданном стопе.
  • Отслеживание состояния пивота – стратегия хранит последнюю активную бычью/медвежью линию и генерирует сигнал только в момент переключения, полностью повторяя буферы MQL-индикатора.

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

  1. Расчёт пивота – для каждой завершённой свечи определяется уровень защитного стопа по правилам Volatility Pivot. Закрытие выше уровня активирует бычий пивот, ниже — медвежий.
  2. Фиксация сигналов – новый бычий (медвежий) сигнал появляется, когда соответствующий пивот активируется после паузы. Параметр SignalBar откладывает исполнение на заданное число завершённых свечей, как в оригинале.
  3. Фильтр направления (TradeDirection) – в режиме WithTrend стратегия открывает лонг по бычьему сигналу и шорт по медвежьему; в режиме CounterTrend трактовка инвертируется.
  4. Разрешения на вход – параметры EnableBuyEntries и EnableSellEntries ограничивают открытие новых лонгов и шортов.
  5. Разрешения на выходAllowLongExits и AllowShortExits управляют закрытием существующих позиций как по прямым сигналам, так и при продолжительной активности противоположной линии.
  6. Управление позицией – стратегия стремится к позиции +Volume для лонгов, -Volume для шортов и 0 при закрытии. При смене направления объём заявок автоматически перекрывает противоположный остаток.
  7. Защитные уровни – опциональные StopLoss и TakeProfit (в абсолютных ценовых значениях) контролируют каждую завершённую свечу и немедленно закрывают позицию при достижении порога.

Параметры

Параметр Описание Значение по умолчанию
CandleType Свечи для расчёта и исполнения. 4 часа
AtrPeriod Период ATR. 100
SmoothingPeriod Длина EMA для сглаживания ATR. 10
AtrMultiplier Множитель сглаженного ATR. 3.0
DeltaPrice Фиксированное отклонение в режиме PriceDeviation. 0.002
PivotMode Выбор режима расчёта пивота. Atr
TradeDirection Работа по тренду (WithTrend) или против него (CounterTrend). WithTrend
SignalBar Задержка исполнения в количестве завершённых свечей. 1
EnableBuyEntries Разрешить открытие лонгов. true
EnableSellEntries Разрешить открытие шортов. true
AllowLongExits Разрешить закрытие лонгов при медвежьих условиях. true
AllowShortExits Разрешить закрытие шортов при бычьих условиях. true
StopLoss Дистанция защитного стопа (в цене), 0 отключает. 0
TakeProfit Дистанция тейк-профита (в цене), 0 отключает. 0

Важно: размер позиции задаётся свойством Strategy.Volume. Перед запуском стратегии настройте его в соответствии с выбранным инструментом.

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

  1. Назначьте стратегии нужные Security и Portfolio, установите Volume в желаемый объём.
  2. Убедитесь, что источник данных предоставляет непрерывные завершённые свечи выбранного таймфрейма, иначе ATR и задержка сигналов не сформируются.
  3. Выбирайте PivotMode исходя из поведения инструмента: ATR-режим реагирует на волатильность, фиксированное отклонение даёт постоянную дистанцию.
  4. Настройте SignalBar, чтобы воспроизвести временной лаг оригинального советника (по умолчанию 1 завершённая свеча; значение 0 означает немедленное исполнение после закрытия бара).
  5. Если используются StopLoss/TakeProfit, подбирайте величины с учётом реальной волатильности инструмента.
  6. Отслеживайте сообщения лога: стратегия выводит причины входов, выходов и срабатывания защитных уровней.

Отличия от оригинального советника

  • Исключены параметры управления капиталом, зависящие от баланса/свободной маржи; размер позиции определяется только Strategy.Volume.
  • Не используются функции вспомогательной библиотеки MQL (максимальное отклонение цены, глобальные переменные, ручная загрузка истории).
  • Нет уведомлений и прочих сервисных возможностей оригинала.
  • Защитные ордера реализованы через проверку завершённых свечей и не выставляются внутри бара.

Рекомендуемые улучшения

  • Добавить фильтры торговых сессий или волатильности, чтобы избегать низколиквидных периодов.
  • Визуализировать пивот-линии на графике либо реализовать на их основе динамический трейлинг-стоп.
  • При работе с несколькими инструментами дополнить стратегию портфельным риск-менеджментом.
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>
/// Volatility pivot strategy using ATR-based trailing stop.
/// Follows trend by flipping long/short when price crosses the ATR-based pivot line.
/// </summary>
public class VolatilityPivotStrategy : Strategy
{
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrMultiplier;
	private readonly StrategyParam<int> _emaPeriod;

	private AverageTrueRange _atr;
	private ExponentialMovingAverage _ema;

	private decimal _pivotStop;
	private decimal _prevClose;
	private bool _isLong;
	private bool _initialized;
	private int _cooldown;

	/// <summary>
	/// ATR calculation period.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// Multiplier applied to ATR for pivot distance.
	/// </summary>
	public decimal AtrMultiplier
	{
		get => _atrMultiplier.Value;
		set => _atrMultiplier.Value = value;
	}

	/// <summary>
	/// EMA period for trend confirmation.
	/// </summary>
	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public VolatilityPivotStrategy()
	{
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR calculation period", "Indicator");

		_atrMultiplier = Param(nameof(AtrMultiplier), 5m)
			.SetGreaterThanZero()
			.SetDisplay("ATR Multiplier", "Multiplier for pivot distance", "Indicator");

		_emaPeriod = Param(nameof(EmaPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA trend filter period", "Indicator");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_atr = null;
		_ema = null;
		_pivotStop = 0;
		_prevClose = 0;
		_isLong = true;
		_initialized = false;
		_cooldown = 0;
	}

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

		_atr = new AverageTrueRange { Length = AtrPeriod };
		_ema = new ExponentialMovingAverage { Length = EmaPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_atr, _ema, ProcessCandle);
		subscription.Start();
	}

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

		if (!_atr.IsFormed || !_ema.IsFormed)
		{
			_prevClose = candle.ClosePrice;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevClose = candle.ClosePrice;
			return;
		}

		var close = candle.ClosePrice;
		var delta = atrValue * AtrMultiplier;

		if (!_initialized)
		{
			_pivotStop = close > emaValue ? close - delta : close + delta;
			_isLong = close > emaValue;
			_initialized = true;
			_prevClose = close;
			return;
		}

		// Update pivot stop
		if (_isLong)
		{
			var newStop = close - delta;
			if (newStop > _pivotStop)
				_pivotStop = newStop;

			// Check for reversal
			if (close < _pivotStop)
			{
				_isLong = false;
				_pivotStop = close + delta;

				if (Position > 0)
					SellMarket();

				SellMarket();
				_cooldown = 60;
			}
		}
		else
		{
			var newStop = close + delta;
			if (newStop < _pivotStop)
				_pivotStop = newStop;

			// Check for reversal
			if (close > _pivotStop)
			{
				_isLong = true;
				_pivotStop = close - delta;

				if (Position < 0)
					BuyMarket();

				BuyMarket();
				_cooldown = 60;
			}
		}

		_prevClose = close;
	}
}