Стратегия является прямой конвертацией советника MetaTrader 4 «Channels» из библиотеки Gordago. Она сочетает сверхбыструю экспоненциальную скользящую среднюю (EMA) с тремя каналами на базе той же EMA, чтобы находить моменты выхода цены из сжатого диапазона. После открытия удерживается только одна позиция, а сопровождение сделки ведётся стоп-ордером и при необходимости трейлинг-стопом — полностью по логике исходного MQL-кода.
Логика торговли
По умолчанию используются часовые свечи, на основании которых рассчитываются:
Быстрая EMA с периодом 2 по ценам закрытия.
Вторая быстрая EMA с периодом 2 по ценам открытия (нужна для условий входа в шорт).
Медленная EMA с периодом 220 по закрытию, служащая базой для трёх каналов ±1.0%, ±0.7% и ±0.3%.
Покупка выполняется, когда быстрая EMA по закрытию удовлетворяет одному из шести проверяемых условий:
Пересечение вверх нижней границы канала 1%.
Пересечение вверх нижней границы канала 0.7%.
Две последовательные свечи ниже нижней границы канала 0.3% (сигнал перепроданности).
Пересечение вверх самой медленной EMA.
Пересечение вверх верхней границы канала 0.3%.
Пересечение вверх верхней границы канала 0.7%.
Продажа выполняется, когда быстрая EMA по цене открытия выполняет зеркальный набор проверок:
Пересечение вниз верхней границы канала 1%.
Пересечение вниз верхней границы канала 0.7%.
Пересечение вниз верхней границы канала 0.3%.
Пересечение вниз медленной EMA.
Пересечение вниз нижней границы канала 0.3%.
Пересечение вниз нижней границы канала 0.7%.
Одновременно может существовать только одна позиция: новые сигналы игнорируются, пока сделка активна, что полностью повторяет поведение исходного советника.
Управление рисками
Для длинных и коротких сделок задаются независимые расстояния до стоп-лосса и тейк-профита (в пунктах). Нулевое значение выключает соответствующий защитный ордер, как в оригинальной версии.
Настраиваемый трейлинг-стоп подтягивает стоп-ордер, когда цена проходит заданное количество пунктов в прибыльную сторону.
Все защитные ордера автоматически отменяются при закрытии позиции или остановке стратегии.
Параметры
Параметр
Описание
Candle Type
Таймфрейм, используемый для расчётов (по умолчанию 1 час).
Volume
Объём заявки при входе.
Fast EMA / Slow EMA
Периоды быстрой и медленной EMA.
Envelope 1%, Envelope 0.7%, Envelope 0.3%
Ширина трёх каналов в процентах.
Buy Stop-Loss, Sell Stop-Loss
Начальное расстояние до стоп-лосса для лонга и шорта.
Buy Take-Profit, Sell Take-Profit
Начальное расстояние до тейк-профита для лонга и шорта.
Buy Trailing, Sell Trailing
Дистанция трейлинг-стопа в пунктах.
Use Trading Hours
Включение фильтра по времени торговли.
From Hour, To Hour
Разрешённые часы для открытия позиций (границы включительно). Если начальный час больше конечного, окно охватывает переход через полночь.
Особенности использования
Значения в пунктах переводятся в цену через PriceStep, поэтому важно, чтобы минимальный шаг цены инструмента был корректно задан в системе.
Период быстрой EMA оставлен равным 2 для точного соответствия оригиналу. Изменение параметра существенно влияет на частоту сигналов.
Функции фильтрации по номеру счёта и звуковых уведомлений из MQL-версии не перенесены, поскольку они не относятся к торговой логике.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Channels strategy - EMA envelope breakout.
/// Buys when price breaks above fast EMA + offset, sells when it breaks below.
/// Uses slow EMA as trend filter.
/// </summary>
public class ChannelsStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevFast;
private decimal _prevSlow;
private bool _hasPrev;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ChannelsStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 10)
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 30)
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted() { base.OnReseted(); _prevFast = 0m; _prevSlow = 0m; _hasPrev = false; }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
if (!_hasPrev)
{
_prevFast = fast;
_prevSlow = slow;
_hasPrev = true;
return;
}
var close = candle.ClosePrice;
// Buy: price crosses above fast EMA, fast EMA above slow EMA (uptrend)
if (close > fast && fast > slow && _prevFast <= _prevSlow && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Sell: price crosses below fast EMA, fast EMA below slow EMA (downtrend)
else if (close < fast && fast < slow && _prevFast >= _prevSlow && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
// Exit long when fast crosses below slow
else if (Position > 0 && fast < slow && _prevFast >= _prevSlow)
{
SellMarket();
}
// Exit short when fast crosses above slow
else if (Position < 0 && fast > slow && _prevFast <= _prevSlow)
{
BuyMarket();
}
_prevFast = fast;
_prevSlow = slow;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class channels_strategy(Strategy):
def __init__(self):
super(channels_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 10) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 30) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(channels_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(channels_strategy, self).OnStarted2(time)
self._has_prev = False
fast = ExponentialMovingAverage()
fast.Length = self.fast_period
slow = ExponentialMovingAverage()
slow.Length = self.slow_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self.process_candle).Start()
def process_candle(self, candle, fast, slow):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast)
slow_val = float(slow)
if not self._has_prev:
self._prev_fast = fast_val
self._prev_slow = slow_val
self._has_prev = True
return
close = float(candle.ClosePrice)
if close > fast_val and fast_val > slow_val and self._prev_fast <= self._prev_slow and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif close < fast_val and fast_val < slow_val and self._prev_fast >= self._prev_slow and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
elif self.Position > 0 and fast_val < slow_val and self._prev_fast >= self._prev_slow:
self.SellMarket()
elif self.Position < 0 and fast_val > slow_val and self._prev_fast <= self._prev_slow:
self.BuyMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return channels_strategy()