Открыть на GitHub

Стратегия Standard Deviation Channel

Обзор

Эта стратегия представляет собой порт экспертного советника MetaTrader Standard Deviation Channel на платформу StockSharp. Она строит волатильностный канал на основе линейно-взвешенной скользящей средней (LWMA) и торгует пробои, совпадающие с текущим трендом. Точки входа фильтруются силой импульса и подтверждением MACD, а выходы комбинируют фиксированные цели, перевод в безубыток и трейлинг-стоп.

Индикаторы и сигналы

  • Канал стандартного отклонения из LWMA и настраиваемого множителя отклонения. Для покупок верхняя граница должна направляться вверх, для продаж — нижняя граница должна идти вниз.
  • Трендовый фильтр: быстрые и медленные LWMA на тех же свечах. Покупки разрешены при LWMA_fast > LWMA_slow, продажи — при обратном соотношении.
  • Фильтр импульса: индикатор Momentum с периодом 14. Минимум одно из трёх последних значений должно отклоняться от нейтрального уровня 100 не меньше заданного порога.
  • Фильтр MACD: классическая конфигурация 12/26/9. Для покупок требуется MACD ≥ signal, для продаж — MACD ≤ signal.

Управление сделками

  • Размер позиции: задаётся параметром TradeVolume. При смене направления противоположная позиция закрывается рыночным ордером.
  • Тейк-профит и стоп-лосс: выражены в пунктах и пересчитываются через PriceStep инструмента. Стратегия закрывает позицию по рынку, когда диапазон свечи достигает целевой цены или стопа.
  • Переход в безубыток: при достижении прибыли BreakEvenTriggerPips стоп переносится на цену входа плюс BreakEvenOffsetPips (или минус для шортов).
  • Трейлинг-стоп: после прибыли TrailingStartPips стоп следует за ценой на расстоянии TrailingStepPips, фиксируя часть дохода.
  • Выход при возврате в канал: если цена закрывается внутри канала и наклон разворачивается против позиции, сделка закрывается досрочно.

Параметры

Название Описание
CandleType Основной таймфрейм для всех расчётов.
TradeVolume Базовый объём заявки.
TrendLength Длина LWMA, задающая базовую линию канала.
DeviationMultiplier Множитель стандартного отклонения для ширины канала.
FastMaLength / SlowMaLength Периоды LWMA для трендового фильтра.
MomentumPeriod Период индикатора Momentum.
MomentumThreshold Минимальное отклонение от 100, требуемое в одном из трёх последних значений.
TakeProfitPips / StopLossPips Дистанции фиксированных выходов (пересчитываются через PriceStep).
BreakEvenTriggerPips / BreakEvenOffsetPips Управляют переводом стопа в безубыток.
TrailingStartPips / TrailingStepPips Активация и шаг трейлинг-стопа.
MacdFastPeriod, MacdSlowPeriod, MacdSignalPeriod Параметры MACD.
MaxPositionUnits Максимальный абсолютный нетто-объём позиции.

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

  1. Запускайте стратегию на инструменте с корректным PriceStep, чтобы пересчёт пунктов работал верно.
  2. Настраивайте TrendLength и DeviationMultiplier под волатильность конкретного рынка.
  3. При необходимости увеличения количества сделок уменьшайте MomentumThreshold или периоды MACD/Momentum.
  4. Трейлинг срабатывает по закрытию свечи, поэтому внутрисессионные всплески, не подтвердившиеся закрытием, игнорируются.

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

  • В MetaTrader наклон канала считывается из графического объекта, а манименеджмент включает мартингейл и контроль эквити. В порте сохранена проверка наклона, но риск упрощён до фиксированного объёма и ограничения MaxPositionUnits.
  • Все выходы выполняются рыночными ордерами на закрытии свечи, потому что в StockSharp нет прямого аналога модификации ордеров MT4.
  • Почтовые и push-уведомления заменены сообщениями AddInfoLog.
  • Блок защиты по просадке счёта опущен; акцент сделан на защите каждой сделки.

Дисклеймер

Пример предназначен для учебных целей. Перед использованием на реальном счёте протестируйте стратегию на исторических и демо-данных.

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;

public class StandardDeviationChannelStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public StandardDeviationChannelStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}