Ver no GitHub

N Candles v3 Strategy

Overview

This strategy scans the latest finished candles and looks for a sequence where the last N bars share the same direction (all bullish or all bearish). When such a streak appears it enters in the direction of the sequence while respecting a cap on how many positions can be opened at once. The implementation migrates the original MetaTrader 5 expert advisor to the StockSharp high level API.

Trading Logic

  • The engine subscribes to the configured candle type and processes only completed bars.
  • For every finished candle the body direction is evaluated: bullish, bearish, or neutral (doji).
  • Doji candles reset the internal counter. Otherwise the counter increases when the current candle has the same direction as the previous ones. Once the counter reaches the Identical Candles parameter the strategy issues a new order.
  • Long signals close any existing short exposure first and then add a long unit while the total bought volume stays below Max Positions * Volume.
  • Short signals work symmetrically for bearish streaks.

Risk Management

  • After each filled trade the strategy places new protective stop-loss and take-profit orders based on the average entry price of the active position.
  • Distances are measured in security price steps: Take Profit Points multiplies the step to calculate the target above (long) or below (short) the entry; Stop Loss Points uses the same idea for the protective stop.
  • A stepped trailing stop can replace the initial stop once price moves by Trailing Stop Points in favor of the position. The stop is moved only when the price has advanced by at least Trailing Step Points beyond the previous trailing level.

Parameters

  • Candle Type – Time frame or candle source to analyse.
  • Identical Candles – Required number of consecutive candles with the same direction to trigger an entry.
  • Volume – Order size for each new entry in security units.
  • Max Positions – Maximum number of entry units that may be open in the same direction simultaneously.
  • Take Profit Points – Take-profit distance in multiples of the instrument price step.
  • Stop Loss Points – Stop-loss distance in multiples of the instrument price step.
  • Trailing Stop Points – Distance from the current price used to activate and maintain the trailing stop. Set to zero to disable trailing.
  • Trailing Step Points – Extra distance in price steps that must be covered before the trailing stop is moved again.

Additional Notes

  • The strategy operates in a netting manner: when a signal in the opposite direction appears, any existing exposure on the other side is closed before adding a new position.
  • All protective orders are re-created after every fill to keep their volume synchronized with the open position size.
  • Make sure the instrument provides a non-zero PriceStep; otherwise the default step value of 1 is used.
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 a sequence of candles with the same direction.
/// Uses StartProtection for take profit and stop loss management.
/// </summary>
public class NCandlesV3Strategy : Strategy
{
	private readonly StrategyParam<int> _identicalCandles;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<DataType> _candleType;

	private int _sequenceDirection;
	private int _sequenceCount;

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

	/// <summary>
	/// Number of consecutive candles required before entering a trade.
	/// </summary>
	public int IdenticalCandles
	{
		get => _identicalCandles.Value;
		set => _identicalCandles.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in price steps.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in price steps.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public NCandlesV3Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles analysed by the strategy", "General");

		_identicalCandles = Param(nameof(IdenticalCandles), 3)
			.SetRange(1, 10)
			.SetDisplay("Identical Candles", "Required number of equal candles", "Pattern");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 50m)
			.SetRange(0m, 500m)
			.SetDisplay("Take Profit Points", "Take profit distance in price steps", "Risk Management");

		_stopLossPoints = Param(nameof(StopLossPoints), 50m)
			.SetRange(0m, 500m)
			.SetDisplay("Stop Loss Points", "Stop loss distance in price steps", "Risk Management");
	}

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

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

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

		_sequenceDirection = 0;
		_sequenceCount = 0;

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

		var step = Security?.PriceStep ?? 1m;
		var takeProfit = TakeProfitPoints > 0 ? TakeProfitPoints * step : (decimal?)null;
		var stopLoss = StopLossPoints > 0 ? StopLossPoints * step : (decimal?)null;

		if (takeProfit != null || stopLoss != null)
			StartProtection(takeProfit ?? 0, stopLoss ?? 0);

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

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

		var direction = GetDirection(candle);
		UpdateSequence(direction);

		if (_sequenceCount < IdenticalCandles)
			return;

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

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

	private void UpdateSequence(int direction)
	{
		if (direction == 0)
		{
			_sequenceDirection = 0;
			_sequenceCount = 0;
			return;
		}

		if (_sequenceDirection == direction)
		{
			_sequenceCount++;
		}
		else
		{
			_sequenceDirection = direction;
			_sequenceCount = 1;
		}
	}
}