Конвертация советника MetaTrader 4 Spazm (8683) на высокий уровень API StockSharp.
Торгует адаптивные пробои: сравнивает цену закрытия со смещёнными на волатильность уровнями вокруг последнего максимума и минимума.
При желании наносит на график линии между последовательными экстремумами, повторяя визуализацию оригинального MQL-скрипта.
Подготовка данных
Стратегия подписывается на свечи типа CandleType по выбранному инструменту.
Каждая завершённая свеча формирует выборку диапазона для оценки волатильности:
По умолчанию используется диапазон High - Low.
Если UseOpenCloseRange = true, берётся модуль тела |Open - Close|.
Полученный диапазон переводится в количество ценовых шагов через PriceStep, что делает расчёты независимыми от тиковой стоимости инструмента.
В зависимости от UseWeightedVolatility выбирается индикатор усреднения диапазона:
false — простое скользящее среднее длиной VolatilityPeriod.
true — линейно взвешенное скользящее среднее, усиливающее влияние последних свечей.
Сглаженный диапазон (в шагах) умножается на VolatilityMultiplier и возвращается в ценовое выражение. Это и есть адаптивный порог, который используется при проверке пробоев.
Параллельно в течение первых VolatilityPeriod * 3 свечей стратегия запоминает последние экстремумы и их время. Как только накоплено достаточное количество данных, по более свежему экстремуму определяется исходное направление тренда.
Параметры
Параметр
Значение по умолчанию
Описание
Volume
1
Объём заявки при открытии или реверсе позиции.
VolatilityMultiplier
5
Множитель усреднённой волатильности для расчёта ширины пробойного коридора.
VolatilityPeriod
24
Длина выборки для индикатора волатильности и для поиска стартовых экстремумов.
UseWeightedVolatility
false
Переключение между простым и линейно взвешенным средним.
UseOpenCloseRange
false
Использовать диапазон тела свечи вместо High - Low.
StopLossMultiplier
0
Множитель порога для расчёта защитного стопа. Минимальный отступ — три шага цены. 0 отключает стопы.
DrawSwingLines
true
Рисовать на графике линию между последним максимумом и минимумом.
CandleType
4 часа
Таймфрейм свечей, на которых выполняются расчёты.
Логика торговли
Инициализация
Пока обрабатываются первые VolatilityPeriod * 3 свечей, обновляются _highestPrice, _lowestPrice и их время.
После накопления достаточного количества баров направление определяется по тому, какой экстремум сформирован позже: последний минимум → бычий режим, последний максимум → медвежий.
Эти значения сохраняются как стартовые опорные точки для отрисовки линий.
Оценка волатильности
Каждая завершённая свеча добавляет свой диапазон в выбранное скользящее среднее.
Порог всегда не меньше одного шага цены, чтобы избежать нулевых значений.
Сопровождение экстремумов
На каждом баре стратегия обновляет локальные максимум и минимум, если появляются новые абсолютные значения.
При смене тренда соответствующий экстремум фиксируется в качестве пивота и, при активной визуализации, соединяется линией с противоположным экстремумом.
Правила входа
В бычьем режиме (_isTrendUp = true) закрытие ниже _highestPrice - threshold вызывает реверс в шорт. Объём равен Volume + |Position|, что одновременно закрывает старую позицию и открывает новую.
В медвежьем режиме (_isTrendUp = false) закрытие выше _lowestPrice + threshold зеркально переворачивает позицию в лонг.
Управление стопом
Если StopLossMultiplier > 0, цена входа корректируется на threshold * StopLossMultiplier (но не менее трёх шагов). Полученный уровень хранится как виртуальный стоп.
При касании стопа минимумом (для лонга) или максимумом (для шорта) позиция немедленно закрывается рыночным ордером.
Служебные действия
StartProtection() активирует встроенные механизмы защиты сразу после запуска.
Все расчёты выполняются только по завершённым свечам, что соответствует циклу пересчёта советника в MetaTrader.
Отличия от версии MQL
Оригинальный советник работает по тикам, тогда как порт задействует завершённые свечи — это стандартный подход при использовании свечных подписок StockSharp.
Биржевые ограничения брокера (MODE_STOPLEVEL и т.п.) недоступны, поэтому минимальный стоп задаётся консервативно — три шага цены.
Для реверса позиций используется один вызов BuyMarket/SellMarket с объёмом Volume + |Position|, что заменяет цикл закрытия отдельных ордеров из MQL.
Визуализация построена на методе DrawLine, но последовательность соединения минимумов и максимумов совпадает с оригинальными трендовыми линиями.
Рекомендации по применению
Убедитесь, что у инструмента задан PriceStep. Если брокер его не предоставляет, стратегия использует значение 1, что может потребовать ручной корректировки.
Выбор слишком малого таймфрейма ухудшает качество оценки волатильности. Рекомендуется использовать интервалы, близкие к исходному H4.
Параметр StopLossMultiplier можно оставить равным нулю для повторения поведения исходного советника без ограничений риска.
Стратегия не содержит целей по прибыли и выходит из рынка только при смене режима или срабатывании защитного стопа.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Volatility breakout strategy that tracks swing extremes and reverses
/// when price breaks beyond an ATR-based volatility band.
/// </summary>
public class SpazmVolatilityBreakoutStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _multiplier;
private decimal _swingHigh;
private decimal _swingLow;
private bool _trendUp;
private bool _initialized;
public SpazmVolatilityBreakoutStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis.", "General");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "Period for ATR volatility.", "Indicators");
_multiplier = Param(nameof(Multiplier), 2.0m)
.SetDisplay("Multiplier", "ATR multiplier for breakout threshold.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_swingHigh = 0;
_swingLow = decimal.MaxValue;
_trendUp = true;
_initialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (atrValue <= 0)
return;
var close = candle.ClosePrice;
var high = candle.HighPrice;
var low = candle.LowPrice;
var threshold = atrValue * Multiplier;
if (!_initialized)
{
_swingHigh = high;
_swingLow = low;
_initialized = true;
return;
}
if (_trendUp)
{
// Track swing high
if (high > _swingHigh)
_swingHigh = high;
// Reversal: price breaks below swing high minus threshold
if (close < _swingHigh - threshold)
{
_trendUp = false;
_swingLow = low;
// Enter short on trend reversal
if (Position > 0)
SellMarket();
if (Position == 0)
SellMarket();
}
}
else
{
// Track swing low
if (low < _swingLow)
_swingLow = low;
// Reversal: price breaks above swing low plus threshold
if (close > _swingLow + threshold)
{
_trendUp = true;
_swingHigh = high;
// Enter long on trend reversal
if (Position < 0)
BuyMarket();
if (Position == 0)
BuyMarket();
}
}
}
}
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
from StockSharp.Algo.Strategies import Strategy
class spazm_volatility_breakout_strategy(Strategy):
def __init__(self):
super(spazm_volatility_breakout_strategy, self).__init__()
self._atr_length = self.Param("AtrLength", 14).SetDisplay("ATR Length", "Period for ATR volatility", "Indicators")
self._multiplier = self.Param("Multiplier", 2.0).SetDisplay("Multiplier", "ATR multiplier for breakout threshold", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).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(spazm_volatility_breakout_strategy, self).OnReseted()
self._swing_high = 0
self._swing_low = 999999999
self._trend_up = True
self._initialized = False
def OnStarted2(self, time):
super(spazm_volatility_breakout_strategy, self).OnStarted2(time)
self._swing_high = 0
self._swing_low = 999999999
self._trend_up = True
self._initialized = False
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(atr, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, atr)
self.DrawOwnTrades(area)
def OnProcess(self, candle, atr_val):
if candle.State != CandleStates.Finished:
return
if atr_val <= 0:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
threshold = atr_val * self._multiplier.Value
if not self._initialized:
self._swing_high = high
self._swing_low = low
self._initialized = True
return
if self._trend_up:
if high > self._swing_high:
self._swing_high = high
if close < self._swing_high - threshold:
self._trend_up = False
self._swing_low = low
if self.Position > 0:
self.SellMarket()
if self.Position == 0:
self.SellMarket()
else:
if low < self._swing_low:
self._swing_low = low
if close > self._swing_low + threshold:
self._trend_up = True
self._swing_high = high
if self.Position < 0:
self.BuyMarket()
if self.Position == 0:
self.BuyMarket()
def CreateClone(self):
return spazm_volatility_breakout_strategy()