View on GitHub

Double Channel EA Strategy

Overview

The Double Channel EA replicates the trading logic of the MetaTrader 4 expert advisor "DoubleChannelEA_v1.2". The StockSharp p ort adapts the custom iDoubleChannel_v1.5 indicator and executes breakout trades when the indicator prints arrows. The strateg y is designed for discretionary testing with configurable risk management and schedule filters.

Key characteristics:

  • Custom DoubleChannelIndicator rebuilds the upper, lower, and middle channel buffers plus the buy/sell arrow signals.
  • High-level API usage with candle subscriptions, level-one spread validation, and native order helpers.
  • Optional money-management tools: stacking positions, break-even, trailing stop, take-profit, and stop-loss logic.
  • Time-of-day filter and spread filter block entries outside of user-defined operating conditions.

Trading Logic

  1. Subscribe to the selected CandleType and feed each finished candle into the DoubleChannelIndicator.
  2. The indicator stores a moving window of ChannelPeriod candles and calculates:
    • Middle line: arithmetic mean of closes.
    • Upper line: middle plus the difference of two price envelopes derived from highs and lows.
    • Lower line: middle plus the difference of complementary envelopes derived from opens and lows.
    • Arrow signals: the previous two channel positions must flip and the previous candle must close in the direction of the brea kout. The rules match the MT4 buffer conditions.
  3. Signals can be delayed by IndicatorShift bars to reproduce the indicator shift parameter.
  4. A buy signal opens a long position (stacking allowed when OpenEverySignal = true). A sell signal opens a short position. Op posite positions can be closed immediately when CloseInSignal = true.
  5. Protective exits manage the active position on every finished candle:
    • Static stop-loss / take-profit distances expressed in absolute price units.
    • Break-even activation once price advances by BreakEvenPoints + BreakEvenAfterPoints.
    • Trailing stop that requires an improvement of TrailingStepPoints before updating.
  6. Entries are rejected when:
    • The strategy is outside trading hours (UseTimeFilter).
    • The live spread exceeds MaxSpreadPoints.
    • MaxOrders stacked positions are already open for the current direction.

Money Management

The order volume is calculated as:

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

When reversing, the strategy automatically includes the absolute opposing position to flip to the new direction in a single mar ket order.

Parameters

Parameter Default Description
CandleType 15-minute time frame Primary candle subscription.
ChannelPeriod 14 Lookback for the custom channel.
IndicatorShift 0 Delay before acting on indicator values.
OpenEverySignal true Allows stacking positions on consecutive signals.
CloseInSignal false Closes current position when an opposite arrow appears.
UseTakeProfit false Enables TakeProfitPoints.
TakeProfitPoints 10 Absolute price distance for the target.
UseStopLoss false Enables StopLossPoints.
StopLossPoints 10 Absolute price distance for the protective stop.
UseTrailingStop false Enables trailing logic with TrailingStopPoints and TrailingStepPoints.
TrailingStopPoints 5 Distance from current price to the trailing stop.
TrailingStepPoints 1 Minimum improvement needed before updating the trailing stop.
UseBreakEven false Enables break-even adjustments.
BreakEvenPoints 4 Target stop level once break-even activates.
BreakEvenAfterPoints 2 Extra profit required before activating break-even.
AutoLotSize true Multiplies the manual lot by RiskFactor.
RiskFactor 1 Risk multiplier applied when auto sizing.
ManualLotSize 0.01 Base volume when auto sizing is disabled.
UseTimeFilter false Enables the schedule filter.
TimeStartTrade 0 Trading start hour (inclusive).
TimeEndTrade 0 Trading end hour (exclusive). Equal start and end means no restriction.
MaxOrders 0 Maximum stacked positions per direction (0 = unlimited).
MaxSpreadPoints 0 Maximum allowed bid-ask spread in price units.

Notes on Conversion

  • The original indicator rendered arrows by shifting values one bar ahead. The StockSharp version stores previous snapshots and checks the same cross-over criteria before emitting a signal on the current candle.
  • Spread filtering relies on level-one data. When quotes are unavailable the strategy blocks new orders, mimicking the MQL expe rt that refused to trade without spread information.
  • Money management in MT4 used account-based calculations. For portability the volume formula was simplified to a risk multipli er applied to the manual lot size.
  • Stop-loss, take-profit, trailing stop, and break-even distances are interpreted in absolute price units (the same convention a s other StockSharp conversions). Adjust them according to the instrument tick size.
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);
		}
	}
}