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, что гарантирует обработку только завершённых свечей.
Защитные уровни ведутся отдельно для длинных и коротких позиций и сбрасываются при закрытии позиций либо при отсутствии ценовых данных.
Логи информируют о найденных пробоях, срабатываниях стоп-лосса и тейк-профита, что облегчает отладку в тестах.
Рекомендации по использованию
Проверяйте актуальность порога доджи для каждого инструмента: на волатильных рынках имеет смысл увеличить DojiBodyThresholdSteps.
Оптимизируйте BreakoutBufferSteps, если требуется фильтровать мелкие ложные пробои и шум.
Добавляйте внешние фильтры риска (портфельные стопы, торговые сессии), если стратегия используется на нескольких инструментах одновременно.
Подбирайте 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;
}
}
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.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class doji_arrows_breakout_strategy(Strategy):
def __init__(self):
super(doji_arrows_breakout_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR period for doji detection", "Indicators")
self._doji_threshold = self.Param("DojiThreshold", 0.3) \
.SetDisplay("Doji Threshold", "Max body/ATR ratio for doji", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_was_doji = False
self._has_prev = False
@property
def atr_period(self):
return self._atr_period.Value
@property
def doji_threshold(self):
return self._doji_threshold.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(doji_arrows_breakout_strategy, self).OnReseted()
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_was_doji = False
self._has_prev = False
def OnStarted2(self, time):
super(doji_arrows_breakout_strategy, self).OnStarted2(time)
self._has_prev = False
self._prev_was_doji = False
atr = AverageTrueRange()
atr.Length = self.atr_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(atr, self.process_candle).Start()
def process_candle(self, candle, atr):
if candle.State != CandleStates.Finished:
return
atr_val = float(atr)
if atr_val <= 0:
return
body = abs(float(candle.ClosePrice) - float(candle.OpenPrice))
is_doji = body / atr_val < self.doji_threshold
if self._has_prev and self._prev_was_doji:
if float(candle.ClosePrice) > self._prev_high and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif float(candle.ClosePrice) < self._prev_low and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
self._prev_was_doji = is_doji
self._has_prev = True
def CreateClone(self):
return doji_arrows_breakout_strategy()