View on GitHub

N Candles Sequence Strategy

Concept

The N Candles strategy scans the market for consecutive candles that all close in the same direction. Once a configurable number of bullish or bearish candles has appeared, the strategy enters in the direction of the sequence. The implementation is a direct conversion of the MetaTrader "N Candles v4" expert advisor and preserves its risk controls, pip-based configuration, and optional trailing stop behaviour within the StockSharp high-level API.

Entry Conditions

  • Every finished candle is evaluated once.
  • Candles that close up are counted as bullish, candles that close down are counted as bearish, and doji candles reset the sequence.
  • When ConsecutiveCandles bullish (or bearish) candles appear in a row, the strategy submits a market order in the direction of the move.
  • Hedging-style stacking or netting-style exposure caps are applied depending on the selected AccountingMode.

Exit Management

  • StopLossPips and TakeProfitPips define static exit levels measured in pips from the average entry price of the active position.
  • If TrailingStopPips is greater than zero, the stop level trails the most favourable price:
    • When no fixed stop exists (for example when StopLossPips is zero) the strategy waits until price moves by TrailingStopPips in favour of the trade before placing a break-even stop.
    • Once a stop has been set, it moves towards the market when the distance between price and the stop exceeds TrailingStopPips + TrailingStepPips.
  • Protective levels are recalculated whenever position size changes and are checked against every finished candle, guaranteeing that any stop-loss or take-profit event closes the trade immediately.

Parameters

Name Description Default
ConsecutiveCandles Number of identical candles required to trigger an entry. 3
TakeProfitPips Take-profit distance in pips. Use zero to disable the target. 50
StopLossPips Stop-loss distance in pips. Use zero to disable the stop. 50
TrailingStopPips Trailing stop distance in pips. Zero disables trailing. 10
TrailingStepPips Additional movement required before the trailing stop advances. 4
MaxPositionsPerDirection Maximum number of stacked entries per direction when hedging. 2
MaxNetVolume Maximum absolute net position size when operating in netting mode. 2
AccountingMode Switch between Netting (volume cap) and Hedging (entry count cap). Netting
CandleType Candle aggregation used for pattern detection. 1-minute candles

All pip-based parameters are converted to price offsets using the instrument tick size. If the security has 3 or 5 decimal places the pip size is scaled by a factor of ten to mirror MetaTrader's definition.

Implementation Notes

  • The strategy relies on the StockSharp high-level candle subscription (SubscribeCandles) and avoids manual history buffers.
  • Protective logic keeps track of the highest (for longs) or lowest (for shorts) price seen after entry to emulate the original trailing behaviour.
  • Position limits adapt automatically to the base strategy Volume. Increasing Volume expands both stop and take-profit order sizes proportionally.
  • Logging messages are emitted whenever a protective exit (stop or take profit) closes a position, providing clarity during backtests.

Usage Tips

  • Choose Hedging mode when simulating platforms that allow multiple tickets per direction, or stay with Netting to mirror single-position accounts.
  • Set TrailingStepPips to zero for a classical trailing stop that moves whenever the market advances by TrailingStopPips.
  • Because exits are evaluated on completed candles, consider a shorter candle interval if intrabar precision is critical.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that opens positions after detecting N identical candles in a row.
/// Enters in the direction of the candle streak.
/// </summary>
public class NCandlesSequenceStrategy : Strategy
{
	private readonly StrategyParam<int> _consecutiveCandles;
	private readonly StrategyParam<DataType> _candleType;

	private int _consecutiveDirection;
	private int _consecutiveCount;

	/// <summary>
	/// Number of identical candles required before entering a trade.
	/// </summary>
	public int ConsecutiveCandles
	{
		get => _consecutiveCandles.Value;
		set => _consecutiveCandles.Value = value;
	}

	/// <summary>
	/// Candle type used for pattern detection.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public NCandlesSequenceStrategy()
	{
		_consecutiveCandles = Param(nameof(ConsecutiveCandles), 3)
			.SetGreaterThanZero()
			.SetDisplay("Consecutive Candles", "Number of identical candles in a row", "Entry")
			.SetOptimize(2, 6, 1);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to analyze", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_consecutiveDirection = 0;
		_consecutiveCount = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_consecutiveDirection = 0;
		_consecutiveCount = 0;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var direction = GetCandleDirection(candle);

		if (direction == 0)
		{
			_consecutiveDirection = 0;
			_consecutiveCount = 0;
			return;
		}

		if (direction == _consecutiveDirection)
		{
			_consecutiveCount++;
		}
		else
		{
			_consecutiveDirection = direction;
			_consecutiveCount = 1;
		}

		if (_consecutiveCount < ConsecutiveCandles)
			return;

		if (direction > 0 && Position <= 0)
		{
			BuyMarket();
		}
		else if (direction < 0 && Position >= 0)
		{
			SellMarket();
		}
	}

	private static int GetCandleDirection(ICandleMessage candle)
	{
		if (candle.ClosePrice > candle.OpenPrice)
			return 1;
		if (candle.ClosePrice < candle.OpenPrice)
			return -1;
		return 0;
	}
}