Открыть на GitHub

Стратегия N Candles

Концепция

Стратегия N Candles отслеживает последовательности свечей, которые закрываются в одном направлении. Когда формируется заданное количество подряд идущих бычьих или медвежьих свечей, стратегия открывает позицию по направлению импульса. Реализация является адаптацией советника MetaTrader «N Candles v4» и сохраняет его параметры управления рисками, настройку в пунктах и алгоритм трейлинг-стопа, перенесённый на высокоуровневый API StockSharp.

Условия входа

  • Анализ выполняется только по завершённым свечам.
  • Свечи с закрытием выше открытия считаются бычьими, ниже открытия — медвежьими, а дожи обнуляют счётчик последовательности.
  • При появлении ConsecutiveCandles свечей подряд стратегия отправляет рыночную заявку в сторону движения.
  • В зависимости от параметра AccountingMode используются ограничения по суммарному объёму (неттинг) или по количеству позиций в каждом направлении (хеджинг).

Управление выходом

  • Параметры StopLossPips и TakeProfitPips задают фиксированные уровни выхода в пунктах относительно средней цены входа.
  • Если TrailingStopPips больше нуля, активируется трейлинг-стоп:
    • При отсутствии фиксированного стоп-лосса (например, когда StopLossPips равен нулю) стоп переводится в безубыток, когда цена проходит TrailingStopPips пунктов в прибыль.
    • После появления стоп-лосса он смещается вслед за ценой, как только разница между ценой и стопом превышает TrailingStopPips + TrailingStepPips.
  • Расчёт защитных уровней выполняется при каждом изменении позиции; на каждой свече проверяется, достигнут ли стоп или тейк-профит, и при срабатывании позиция немедленно закрывается.

Параметры

Параметр Описание Значение по умолчанию
ConsecutiveCandles Количество одинаковых свечей для сигнала. 3
TakeProfitPips Размер тейк-профита в пунктах (0 — отключить). 50
StopLossPips Размер стоп-лосса в пунктах (0 — отключить). 50
TrailingStopPips Дистанция трейлинг-стопа в пунктах (0 — отключить). 10
TrailingStepPips Дополнительное смещение для переноса трейлинг-стопа. 4
MaxPositionsPerDirection Максимум входов в одном направлении при хеджинге. 2
MaxNetVolume Максимальный суммарный объём позиции при неттинге. 2
AccountingMode Режим учёта: Netting или Hedging. Netting
CandleType Тип свечей для анализа. 1-минутные свечи

Параметры, заданные в пунктах, преобразуются в ценовые величины на основе шага цены инструмента. Для инструментов с 3 или 5 знаками после запятой размер пункта умножается на 10, как в оригинальном советнике.

Особенности реализации

  • Используется высокоуровневая подписка на свечи (SubscribeCandles), что избавляет от ручного хранения истории.
  • Для имитации поведения оригинального трейлинг-стопа стратегия отслеживает максимальную (для лонгов) или минимальную (для шортов) цену после входа.
  • Лимиты по позициям автоматически масштабируются вместе с базовым объёмом стратегии (Volume).
  • При закрытии позиции по стопу или тейк-профиту в лог выводится соответствующее сообщение, что упрощает анализ тестов.

Рекомендации по использованию

  • Выбирайте режим Hedging, если требуется моделировать площадку с независимыми позициями по каждому направлению; режим Netting соответствует единой неттинговой позиции.
  • Установите TrailingStepPips в ноль, чтобы трейлинг-стоп сдвигался при каждом продвижении цены на TrailingStopPips пунктов.
  • Если важно реагировать на внутрибарные колебания, используйте более мелкий таймфрейм свечей.
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;
	}
}