Открыть на GitHub

Double Channel EA (Стратегия)

Общее описание

Стратегия Double Channel EA переносит логику советника MetaTrader 4 "DoubleChannelEA_v1.2" на платформу StockSharp. Реализова н собственный индикатор DoubleChannelIndicator, который повторяет буферы верхнего и нижнего канала, среднюю линию и стрелочные сигналы оригинального индикатора iDoubleChannel_v1.5. Стратегия подходит для визуального тестирования и демонстрирует работа ю с высокоуровневым API.

Особенности портирования:

  • Подписка на свечи и использование BindEx для передачи данных индикатора без ручной работы с коллекциями.
  • Контроль спрэда через Level1-подписку; при отсутствии котировок новые сделки блокируются.
  • Гибкий риск-менеджмент: тейк-профит, стоп-лосс, трейлинг, перевод в безубыток, накопление позиций.
  • Возможность ограничить торговлю по времени и по количеству одновременно открытых ордеров.

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

  1. Для каждого завершённого бара выбирается значение индикатора DoubleChannelIndicator.
  2. Индикатор хранит скользящее окно из ChannelPeriod баров и рассчитывает:
    • среднюю линию как простое среднее закрытий,
    • верхний и нижний каналы через комбинацию высоких/низких/открытий/закрытий, совпадая с формулами MT4,
    • стрелки на основе разворота канала и закрытия предыдущей свечи в сторону пробоя.
  3. Параметр IndicatorShift сдвигает сигналы на заданное число закрытых свечей.
  4. Сигнал на покупку открывает длинную позицию (если OpenEverySignal = true, то допускается наращивание). Сигнал на продажу отк рывает короткую позицию. При CloseInSignal = true встречный сигнал закрывает текущую позицию.
  5. На каждом баре выполняется защита позиции: проверка тейк-профита, стоп-лосса, перевода в безубыток и трейлинга.
  6. Новые сделки не совершаются, если нарушены условия по времени (UseTimeFilter), по спрэд-ограничению (MaxSpreadPoints) или п о количеству одновременно открытых позиций (MaxOrders).

Управление объёмом

volume = ManualLotSize * (AutoLotSize ? max(RiskFactor, 0.1) : 1)

При развороте стратегия автоматически добавляет объём встречной позиции, чтобы перейти в новое направление одним рыночным ордеро м.

Параметры

Параметр Значение по умолчанию Описание
CandleType Таймфрейм 15 минут Тип свечей для расчётов.
ChannelPeriod 14 Длина окна индикатора.
IndicatorShift 0 Сдвиг сигналов в барах.
OpenEverySignal true Разрешить накопление позиций.
CloseInSignal false Закрывать позицию по встречному сигналу.
UseTakeProfit false Включить тейк-профит.
TakeProfitPoints 10 Дистанция тейк-профита в абсолютных ценовых единицах.
UseStopLoss false Включить стоп-лосс.
StopLossPoints 10 Дистанция стоп-лосса в абсолютных ценовых единицах.
UseTrailingStop false Включить трейлинг-стоп.
TrailingStopPoints 5 Базовая дистанция трейлинга.
TrailingStepPoints 1 Минимальный шаг обновления трейлинга.
UseBreakEven false Включить перевод в безубыток.
BreakEvenPoints 4 Уровень установки стопа после безубытка.
BreakEvenAfterPoints 2 Дополнительная прибыль до активации безубытка.
AutoLotSize true Умножать ручной лот на RiskFactor.
RiskFactor 1 Множитель риска при авторасчёте.
ManualLotSize 0.01 Базовый объём при ручном управлении.
UseTimeFilter false Включить фильтр по времени.
TimeStartTrade 0 Час начала торговли (включительно).
TimeEndTrade 0 Час окончания торговли (исключительно). Совпадение начала и конца снимает ограничение.
MaxOrders 0 Максимум одновременно открытых ордеров на направление (0 = без лимита).
MaxSpreadPoints 0 Максимально допустимый спрэд в ценовых единицах.

Примечания по конверсии

  • Логика стрелок повторяет условия MT4: индикатор хранит два предыдущих снимка канала и формирует сигнал на текущей свече, когда к анал меняет расположение.
  • В MT4 вычисление объёма опиралось на параметры депозита. Здесь используется простая формула-множитель для совместимости.
  • Все дистанции (тейк-профит, стоп-лосс, трейлинг, безубыток) интерпретируются как абсолютные значения цены. Пользователь должен у читывать шаг цены инструмента.
  • При отсутствии котировок Level1 стратегия ведёт себя консервативно и не открывает новые сделки, как и оригинальный советник.
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Double Channel EA strategy: BB + EMA trend.
/// Buys when close touches lower BB. Sells when close touches upper BB.
/// </summary>
public class DoubleChannelEaStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _bbPeriod;
	private readonly StrategyParam<int> _emaPeriod;

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

	public int BbPeriod
	{
		get => _bbPeriod.Value;
		set => _bbPeriod.Value = value;
	}

	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	public DoubleChannelEaStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");

		_bbPeriod = Param(nameof(BbPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("BB Period", "Bollinger Bands period", "Indicators");

		_emaPeriod = Param(nameof(EmaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA period for trend", "Indicators");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var bb = new BollingerBands { Length = BbPeriod };
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };

		decimal? prevClose = null;
		decimal? prevEma = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(bb, ema, (candle, bbVal, emaVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				var bbv = (BollingerBandsValue)bbVal;
				if (bbv.UpBand is not decimal upper || bbv.LowBand is not decimal lower)
					return;

				if (emaVal.IsEmpty)
					return;

				var emaDecimal = emaVal.GetValue<decimal>();
				var close = candle.ClosePrice;

				if (prevClose.HasValue && prevEma.HasValue)
				{
					var crossAboveEma = prevClose.Value <= prevEma.Value && close > emaDecimal;
					var crossBelowEma = prevClose.Value >= prevEma.Value && close < emaDecimal;

					if (crossAboveEma && Position <= 0)
						BuyMarket();
					else if (crossBelowEma && Position >= 0)
						SellMarket();
				}

				prevClose = close;
				prevEma = emaDecimal;
			})
			.Start();

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