Открыть на GitHub

Стратегия Doji Arrows

Обзор

Doji Arrows Strategy — портирование советника MetaTrader Doji_arrows_expert1.mq4 на инфраструктуру StockSharp. Идея простая: найти нейтральную свечу-доджи и отработать пробой на следующей свече. Если рынок формирует свечу с минимальным телом (open ≈ close), а затем закрывается выше максимума/ниже минимума доджи, алгоритм трактует движение как пробой и открывает позицию в его направлении.

Торговая логика

  • Окно анализа сигналов. Алгоритм хранит две последние завершённые свечи. Древняя свеча должна быть доджи, а следующая подтверждает пробой.
  • Критерий доджи. Свеча считается доджи, если модуль разницы между ценами открытия и закрытия не превышает DojiBodyThresholdSteps * PriceStep. При значении по умолчанию (1 шаг цены) допускается погрешность в один тик.
  • Подтверждение пробоя.
    • Для покупки следующая свеча закрывается выше максимума доджи и дополнительного фильтра BreakoutBufferSteps.
    • Для продажи следующая свеча закрывается ниже минимума доджи и того же фильтра.
  • Один сигнал на серию. Стратегия запоминает, был ли пробой на предыдущем баре, и реагирует только на новые события, что повторяет поведение оригинального советника с одиночными стрелками.
  • Исполнение сделок.
    • При возникновении пробоя против текущей позиции сначала закрывается встречная позиция, после чего открывается сделка в новом направлении объёмом Volume + |Position|, чтобы перевернуться и открыть новую позицию одним действием.
    • В нейтральном состоянии выставляется рыночный ордер в сторону пробоя.

Риск-менеджмент

  • Начальный стоп-лосс. После входа выставляется внутренний уровень защиты на расстоянии InitialStopSteps * PriceStep от цены входа.
  • Фиксированный тейк-профит. Закрывает позицию, когда цена достигает TakeProfitSteps * PriceStep от точки входа.
  • Трейлинг-стоп. Когда прибыль превышает TrailingStopSteps * PriceStep, стоп подтягивается по каждой свече, фиксируя прибыль и давая тренду развиваться.
  • Все расчёты ведутся в шагах цены, что позволяет применять стратегию на разных инструментах без перенастройки формул.

Параметры

Имя Описание Значение по умолчанию
CandleType Тип/таймфрейм свечей для анализа. 5-минутные свечи
DojiBodyThresholdSteps Максимальный размер тела доджи в шагах цены. 1
BreakoutBufferSteps Дополнительный фильтр над/под экстремумом доджи. 0
InitialStopSteps Размер начального стоп-лосса в шагах. 20
TakeProfitSteps Дистанция тейк-профита в шагах. 25
TrailingStopSteps Дистанция трейлинг-стопа в шагах. 10

Все параметры созданы через StrategyParam<T>, отображаются в интерфейсе и готовы к оптимизации.

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

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

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

  1. Проверяйте актуальность порога доджи для каждого инструмента: на волатильных рынках имеет смысл увеличить DojiBodyThresholdSteps.
  2. Оптимизируйте BreakoutBufferSteps, если требуется фильтровать мелкие ложные пробои и шум.
  3. Добавляйте внешние фильтры риска (портфельные стопы, торговые сессии), если стратегия используется на нескольких инструментах одновременно.
  4. Подбирайте CandleType под свой горизонт: минутные свечи подходят для скальпинга, 15-минутные — для свинг-сделок.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Doji Arrows breakout strategy.
/// Detects doji candles (small body) and trades breakout of the doji range on the next candle.
/// Uses ATR to define what constitutes a small body.
/// </summary>
public class DojiArrowsBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _dojiThreshold;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevHigh;
	private decimal _prevLow;
	private bool _prevWasDoji;
	private bool _hasPrev;

	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public decimal DojiThreshold { get => _dojiThreshold.Value; set => _dojiThreshold.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public DojiArrowsBreakoutStrategy()
	{
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetDisplay("ATR Period", "ATR period for doji detection", "Indicators");

		_dojiThreshold = Param(nameof(DojiThreshold), 0.3m)
			.SetDisplay("Doji Threshold", "Max body/ATR ratio for doji", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
	protected override void OnReseted() { base.OnReseted(); _prevHigh = 0m; _prevLow = 0m; _prevWasDoji = false; _hasPrev = false; }

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_hasPrev = false;
		_prevWasDoji = false;

		var atr = new AverageTrueRange { Length = AtrPeriod };

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

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

		if (atr <= 0)
			return;

		var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
		var isDoji = body / atr < DojiThreshold;

		if (_hasPrev && _prevWasDoji)
		{
			// Breakout above doji high
			if (candle.ClosePrice > _prevHigh && Position <= 0)
			{
				if (Position < 0)
					BuyMarket();
				BuyMarket();
			}
			// Breakout below doji low
			else if (candle.ClosePrice < _prevLow && Position >= 0)
			{
				if (Position > 0)
					SellMarket();
				SellMarket();
			}
		}

		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
		_prevWasDoji = isDoji;
		_hasPrev = true;
	}
}