Открыть на GitHub

Стратегия Spasm

Краткое описание

  • Перенос советника MetaTrader 5 Spasm (barabashkakvn's edition) на высокоуровневый API StockSharp.
  • Отслеживает адаптивный ценовой канал, построенный на основе текущей волатильности, и переключается между бычьим и медвежьим режимами.
  • Работает на любых инструментах и таймфреймах, задаваемых параметром CandleType (по умолчанию — часовые свечи).

Подготовка данных

  1. Подписка на поток свечей, определяемых параметром CandleType, для выбранного инструмента.
  2. Построение оценки волатильности по последним VolatilityPeriod свечам:
    • При отключенном UseWeightedVolatility используется простое скользящее среднее диапазона свечей.
    • При включенном UseWeightedVolatility применяется линейно-взвешенное среднее, усиливающее влияние свежих наблюдений.
  3. Источник диапазона — High - Low. Если UseOpenCloseRange включён, вместо этого берётся абсолютная разница между ценами открытия и закрытия, как в оригинальном эксперте.
  4. Среднее значение диапазона переводится в количество минимальных шагов цены, умножается на VolatilityMultiplier, результат округляется вниз до целого числа шагов и снова умножается на шаг цены — так получается порог пробоя.
  5. В течение первых VolatilityPeriod * 3 завершённых свечей стратегия собирает последние экстремумы и их метки времени, чтобы определить, какая волна была сформирована позже, и зафиксировать начальное направление тренда.

Параметры

Имя Значение по умолчанию Описание
Volume 1 Объём заявки при каждом входе в позицию.
VolatilityMultiplier 5 Множитель усреднённой волатильности, задающий ширину буфера пробоя.
VolatilityPeriod 24 Количество свечей для расчёта волатильности и стартового анализа экстремумов.
UseWeightedVolatility false Переключает среднее волатильности с простого на линейно-взвешенное.
UseOpenCloseRange false Использует абсолютное изменение между открытием и закрытием вместо диапазона High-Low.
StopLossFraction 0.5 Доля волатильностного порога, применяемая для расчёта дистанции стоп-лосса (не менее трёх шагов цены).
CandleType часовой таймфрейм Тип и таймфрейм свечей, которые используются в расчётах.

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

  1. Отслеживание тренда
    • Переменные _highestPrice и _lowestPrice хранят актуальные экстремумы текущего движения.
    • Если цена поднимается выше _highestPrice + threshold, максимум обновляется до текущего значения High; аналогично при падении ниже _lowestPrice - threshold минимум обновляется до текущего Low.
    • Флаг _isTrendUp показывает активный режим: true — бычий, false — медвежий.
  2. Правила входа
    • Когда _isTrendUp == false и закрытие свечи превышает _lowestPrice + threshold, стратегия переходит в бычий режим и отправляет BuyMarket(Volume + Math.Abs(Position)), закрывая короткие позиции и открывая длинную на заданный объём.
    • Когда _isTrendUp == true и закрытие падает ниже _highestPrice - threshold, режим меняется на медвежий, вызывается SellMarket(Volume + Math.Abs(Position)), что переворачивает позицию в шорт.
  3. Управление стоп-лоссом
    • Для длинной позиции стоп рассчитывается как entry - max(threshold * StopLossFraction, 3 * priceStep).
    • Для короткой позиции стоп равен entry + max(threshold * StopLossFraction, 3 * priceStep).
    • Если минимум свечи достигает длинного стопа или максимум — короткого, позиция закрывается рыночной заявкой. При StopLossFraction = 0 защитный стоп не используется.
  4. Контроль рисков и инфраструктура
    • В методе OnStarted вызывается StartProtection(), чтобы активировать встроенную защиту позиции.
    • Обработка ведётся только по завершённым свечам, что исключает шум внутри бара и повторяет логику исходного советника.
    • Комментарии и имена параметров оставлены на английском языке согласно требованиям проекта.

Отличия от версии MQL

  • В оригинале перерасчёт порога выполнялся на каждом тике; в портированной версии расчёт производится на закрытых свечах, поскольку высокоуровневый API работает со свечными подписками.
  • Исполнение стоп-лоссов контролируется по данным свечей, поэтому внутрисвечные проколы оцениваются на границе бара.
  • В StockSharp нет прямых аналогов свойств символа MetaTrader (спред, минимальный стоп). При слишком малой дистанции стопа используется минимум в три шага цены, что повторяет защитный механизм MT5-реализации.

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

  • Убедитесь, что у инструмента задан корректный PriceStep; при отсутствии значения стратегия принимает шаг, равный 1.
  • Подходит для спот-рынка, фьючерсов и CFD при условии наличия свечей выбранного таймфрейма.
  • Целей по прибыли не предусмотрено — выход осуществляется только при смене режима или срабатывании стоп-лосса.
namespace StockSharp.Samples.Strategies;

using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Volatility breakout strategy converted from the MetaTrader Spasm expert advisor.
/// Tracks directional swings using adaptive thresholds derived from ATR.
/// Buys when price breaks above recent high + ATR*multiplier, sells when price breaks below recent low - ATR*multiplier.
/// </summary>
public class SpasmStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _volatilityMultiplier;

	private decimal _highestPrice;
	private decimal _lowestPrice;
	private decimal _prevRange;
	private bool _initialized;

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

	public decimal VolatilityMultiplier
	{
		get => _volatilityMultiplier.Value;
		set => _volatilityMultiplier.Value = value;
	}

	public SpasmStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for analysis", "General");

		_volatilityMultiplier = Param(nameof(VolatilityMultiplier), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Volatility Multiplier", "Multiplier applied to ATR for breakout bands", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_highestPrice = 0m;
		_lowestPrice = decimal.MaxValue;
		_prevRange = 0m;
		_initialized = false;
	}

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

		_highestPrice = 0m;
		_lowestPrice = decimal.MaxValue;
		_prevRange = 0m;
		_initialized = false;

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(OnProcess)
			.Start();

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (!_initialized)
		{
			_highestPrice = candle.HighPrice;
			_lowestPrice = candle.LowPrice;
			_prevRange = candle.HighPrice - candle.LowPrice;
			_initialized = true;
			return;
		}

		// Update extremes
		if (candle.HighPrice > _highestPrice)
			_highestPrice = candle.HighPrice;
		if (candle.LowPrice < _lowestPrice)
			_lowestPrice = candle.LowPrice;

		var threshold = _prevRange * VolatilityMultiplier;

		if (threshold <= 0)
			return;

		// Breakout above lowest + threshold => buy
		if (candle.ClosePrice > _lowestPrice + threshold && Position <= 0)
		{
			BuyMarket();
			// Reset extremes after entry
			_highestPrice = candle.HighPrice;
			_lowestPrice = candle.LowPrice;
		}
		// Breakout below highest - threshold => sell
		else if (candle.ClosePrice < _highestPrice - threshold && Position >= 0)
		{
			SellMarket();
			// Reset extremes after entry
			_highestPrice = candle.HighPrice;
			_lowestPrice = candle.LowPrice;
		}

		_prevRange = candle.HighPrice - candle.LowPrice;
	}
}