Ver no GitHub

Equidistant Channel Strategy

Overview

The Equidistant Channel Strategy ports the original "Equidistant Channel" MQL4 expert advisor to the StockSharp high-level API. The strategy analyses MACD line crossovers and manages existing positions through Bollinger Band touches, breakeven logic, and money-based trailing targets.

When the MACD line crosses above its signal the strategy opens long positions, and when it crosses below the signal it opens short positions. While a trade is active the strategy watches for exits when price reaches Bollinger Bands, when floating profit hits configurable monetary or percentage targets, or when a trailing drawdown threshold is violated. A breakeven mode mirrors the MetaTrader implementation by moving the protective stop once profit exceeds a configurable number of price steps.

Indicators

  • MACD (12, 26, 9) — generates entry signals on crossovers between the MACD line and its signal line.
  • Bollinger Bands (20, 2) — provide exit levels whenever the candle close hits the upper or lower band.

Position Management

  • Optional stop loss, take profit, and trailing stop distances expressed in price points via StartProtection.
  • Money-based take profit and trailing logic that track floating profit using instrument price/step size metadata.
  • Percentage-based take profit calculated from the starting portfolio value.
  • Breakeven mode that pushes the stop to entry plus an offset once profit reaches a defined trigger.

Parameters

Group Name Default Description
Trading Volume 1 Order volume for new entries.
General Candle Type 5 minute Candle series used for calculations.
Indicators MACD Fast 12 Fast EMA length for MACD.
Indicators MACD Slow 26 Slow EMA length for MACD.
Indicators MACD Signal 9 Signal line length for MACD.
Indicators BB Period 20 Bollinger Bands lookback period.
Indicators BB Deviation 2 Bollinger Bands width in standard deviations.
Risk Stop Loss 20 Stop loss distance in price points.
Risk Take Profit 50 Take profit distance in price points.
Risk Trailing Stop 40 Trailing stop distance in price points.
Risk Use TP (Money) false Close when floating profit reaches an absolute money target.
Risk TP Money 10 Absolute take profit value in account currency.
Risk Use TP (%) false Close when floating profit reaches a percent of initial capital.
Risk TP Percent 10 Percent of initial capital for the percentage take profit.
Risk Enable Trailing true Enables trailing logic on floating profit.
Risk Trail Activate 40 Profit level (currency) that arms the trailing logic.
Risk Trail Step 10 Maximum allowed drawdown from the profit peak (currency).
Risk Use BB Stop true Enable exits when price touches Bollinger Bands.
Risk Use Breakeven true Enable the breakeven behaviour.
Risk Breakeven Trigger 10 Profit (price steps) required to arm the breakeven stop.
Risk Breakeven Offset 5 Offset (price steps) applied to the breakeven level.

Notes

  • The strategy works with a single instrument that provides valid PriceStep and StepPrice metadata so that monetary calculations are accurate.
  • The trailing profit module follows the MetaTrader behaviour: once floating profit exceeds the activation threshold the strategy records the running maximum and closes the trade when the drawdown exceeds the configured trailing step.
  • Breakeven logic mirrors the original EA by using price-step based triggers and offsets.
  • All comments inside the strategy code are written in English as required by the project guidelines.
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 EquidistantChannelStrategy : 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 EquidistantChannelStrategy()
	{
		_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;
	}
}