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-индикатора.
Логика торговли
Расчёт пивота – для каждой завершённой свечи определяется уровень защитного стопа по правилам Volatility Pivot. Закрытие выше уровня активирует бычий пивот, ниже — медвежий.
Фиксация сигналов – новый бычий (медвежий) сигнал появляется, когда соответствующий пивот активируется после паузы. Параметр SignalBar откладывает исполнение на заданное число завершённых свечей, как в оригинале.
Фильтр направления (TradeDirection) – в режиме WithTrend стратегия открывает лонг по бычьему сигналу и шорт по медвежьему; в режиме CounterTrend трактовка инвертируется.
Разрешения на вход – параметры EnableBuyEntries и EnableSellEntries ограничивают открытие новых лонгов и шортов.
Разрешения на выход – AllowLongExits и AllowShortExits управляют закрытием существующих позиций как по прямым сигналам, так и при продолжительной активности противоположной линии.
Управление позицией – стратегия стремится к позиции +Volume для лонгов, -Volume для шортов и 0 при закрытии. При смене направления объём заявок автоматически перекрывает противоположный остаток.
Защитные уровни – опциональные 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. Перед запуском стратегии настройте его в соответствии с выбранным инструментом.
Рекомендации по применению
Назначьте стратегии нужные Security и Portfolio, установите Volume в желаемый объём.
Убедитесь, что источник данных предоставляет непрерывные завершённые свечи выбранного таймфрейма, иначе ATR и задержка сигналов не сформируются.
Выбирайте PivotMode исходя из поведения инструмента: ATR-режим реагирует на волатильность, фиксированное отклонение даёт постоянную дистанцию.
Настройте SignalBar, чтобы воспроизвести временной лаг оригинального советника (по умолчанию 1 завершённая свеча; значение 0 означает немедленное исполнение после закрытия бара).
Если используются StopLoss/TakeProfit, подбирайте величины с учётом реальной волатильности инструмента.
Отслеживайте сообщения лога: стратегия выводит причины входов, выходов и срабатывания защитных уровней.
Отличия от оригинального советника
Исключены параметры управления капиталом, зависящие от баланса/свободной маржи; размер позиции определяется только 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class volatility_pivot_strategy(Strategy):
"""ATR-based trailing pivot stop: flip long/short when price crosses pivot line."""
def __init__(self):
super(volatility_pivot_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "ATR period", "Indicator")
self._atr_mult = self.Param("AtrMultiplier", 5).SetGreaterThanZero().SetDisplay("ATR Multiplier", "Multiplier for pivot", "Indicator")
self._ema_period = self.Param("EmaPeriod", 100).SetGreaterThanZero().SetDisplay("EMA Period", "EMA trend filter", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(volatility_pivot_strategy, self).OnReseted()
self._pivot_stop = 0
self._is_long = True
self._initialized = False
self._cooldown = 0
def OnStarted2(self, time):
super(volatility_pivot_strategy, self).OnStarted2(time)
self._pivot_stop = 0
self._is_long = True
self._initialized = False
self._cooldown = 0
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(atr, ema, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def OnProcess(self, candle, atr_val, ema_val):
if candle.State != CandleStates.Finished:
return
av = float(atr_val)
ev = float(ema_val)
close = float(candle.ClosePrice)
mult = self._atr_mult.Value
if av <= 0:
return
if self._cooldown > 0:
self._cooldown -= 1
return
delta = av * mult
if not self._initialized:
self._pivot_stop = close - delta if close > ev else close + delta
self._is_long = close > ev
self._initialized = True
return
if self._is_long:
new_stop = close - delta
if new_stop > self._pivot_stop:
self._pivot_stop = new_stop
if close < self._pivot_stop:
self._is_long = False
self._pivot_stop = close + delta
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown = 60
else:
new_stop = close + delta
if new_stop < self._pivot_stop:
self._pivot_stop = new_stop
if close > self._pivot_stop:
self._is_long = True
self._pivot_stop = close - delta
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown = 60
def CreateClone(self):
return volatility_pivot_strategy()