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;
}
}
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.Strategies import Strategy
class n_candles_sequence_strategy(Strategy):
"""Opens positions after detecting N identical-direction candles in a row."""
def __init__(self):
super(n_candles_sequence_strategy, self).__init__()
self._consecutive = self.Param("ConsecutiveCandles", 3) \
.SetGreaterThanZero() \
.SetDisplay("Consecutive Candles", "Number of identical candles in a row", "Entry")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles to analyze", "General")
self._direction = 0
self._count = 0
@property
def ConsecutiveCandles(self):
return self._consecutive.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(n_candles_sequence_strategy, self).OnStarted2(time)
self._direction = 0
self._count = 0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
d = 0
if float(candle.ClosePrice) > float(candle.OpenPrice):
d = 1
elif float(candle.ClosePrice) < float(candle.OpenPrice):
d = -1
if d == 0:
self._direction = 0
self._count = 0
return
if d == self._direction:
self._count += 1
else:
self._direction = d
self._count = 1
if self._count < self.ConsecutiveCandles:
return
if d > 0 and self.Position <= 0:
self.BuyMarket()
elif d < 0 and self.Position >= 0:
self.SellMarket()
def OnReseted(self):
super(n_candles_sequence_strategy, self).OnReseted()
self._direction = 0
self._count = 0
def CreateClone(self):
return n_candles_sequence_strategy()