Стратегия Long-Leg Doji Breakout
Стратегия определяет свечи длинноногого доджи и торгует пробой выше или ниже его диапазона. Дополнительный фильтр ATR гарантирует достаточную длину теней.
Подробности
- Условия входа:
- Long: ожидание пробоя && закрытие > максимум доджи && предыдущий close <= максимум доджи.
- Short: ожидание пробоя && закрытие < минимум доджи && предыдущий close >= минимум доджи.
- Направление: Оба направления.
- Условия выхода: Пересечение закрытия с SMA(20) против позиции.
- Стопы: Нет.
- Значения по умолчанию:
Порог тела доджи %= 0.1Минимальное отношение тени= 2Использовать фильтр ATR= trueПериод ATR= 14Множитель ATR= 0.5
- Фильтры:
- Категория: Пробой паттерна
- Направление: Оба
- Индикаторы: ATR, SMA
- Стопы: Нет
- Сложность: Низкая
- Таймфрейм: Любой
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Long-Leg Doji breakout strategy.
/// Detects long-legged doji candles and trades breakouts above or below the pattern.
/// Uses SMA trend filter for entries and exits.
/// </summary>
public class LongLegDojiBreakoutStrategy : Strategy
{
private readonly StrategyParam<decimal> _dojiBodyThreshold;
private readonly StrategyParam<decimal> _minWickRatio;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private AverageTrueRange _atr;
private SimpleMovingAverage _sma;
private decimal _dojiHigh;
private decimal _dojiLow;
private bool _waitingForBreakout;
private decimal _prevClose;
private decimal _prevSma;
private int _cooldown;
private decimal _entryPrice;
/// <summary>
/// Maximum body size as percentage of total range.
/// </summary>
public decimal DojiBodyThreshold
{
get => _dojiBodyThreshold.Value;
set => _dojiBodyThreshold.Value = value;
}
/// <summary>
/// Minimum wick to body ratio.
/// </summary>
public decimal MinWickRatio
{
get => _minWickRatio.Value;
set => _minWickRatio.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type for analysis.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public LongLegDojiBreakoutStrategy()
{
_dojiBodyThreshold = Param(nameof(DojiBodyThreshold), 15m)
.SetRange(0.01m, 100m)
.SetDisplay("Doji Body Threshold %", "Body size as % of range", "Pattern");
_minWickRatio = Param(nameof(MinWickRatio), 1.2m)
.SetRange(0.5m, 10m)
.SetDisplay("Minimum Wick Ratio", "Minimum wick to body ratio", "Pattern");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR calculation period", "Filter");
_cooldownBars = Param(nameof(CooldownBars), 70)
.SetGreaterThanZero()
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_dojiHigh = default;
_dojiLow = default;
_waitingForBreakout = false;
_prevClose = default;
_prevSma = default;
_cooldown = default;
_entryPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_atr = new AverageTrueRange { Length = AtrPeriod };
_sma = new SMA { Length = 10 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_sma, _atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldown > 0)
{
_cooldown--;
_prevClose = candle.ClosePrice;
_prevSma = smaValue;
return;
}
if (atrValue <= 0 || _prevSma == 0)
{
_prevClose = candle.ClosePrice;
_prevSma = smaValue;
return;
}
var bodySize = Math.Abs(candle.ClosePrice - candle.OpenPrice);
var candleRange = candle.HighPrice - candle.LowPrice;
// Detect doji
if (candleRange > 0)
{
var upperWick = candle.HighPrice - Math.Max(candle.OpenPrice, candle.ClosePrice);
var lowerWick = Math.Min(candle.OpenPrice, candle.ClosePrice) - candle.LowPrice;
var threshold = DojiBodyThreshold / 100m;
var isSmallBody = bodySize <= candleRange * threshold;
var hasLongWicks = upperWick >= bodySize * MinWickRatio && lowerWick >= bodySize * MinWickRatio;
if (isSmallBody && hasLongWicks)
{
_dojiHigh = candle.HighPrice;
_dojiLow = candle.LowPrice;
_waitingForBreakout = true;
}
}
// Entry: doji breakout with SMA confirmation, or SMA cross fallback
if (Position == 0 && _prevClose > 0)
{
var dojiLong = _waitingForBreakout && candle.ClosePrice > _dojiHigh && candle.ClosePrice > smaValue;
var dojiShort = _waitingForBreakout && candle.ClosePrice < _dojiLow && candle.ClosePrice < smaValue;
var smaCrossUp = _prevClose < _prevSma && candle.ClosePrice > smaValue;
var smaCrossDown = _prevClose > _prevSma && candle.ClosePrice < smaValue;
if (dojiLong || smaCrossUp)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
_waitingForBreakout = false;
_cooldown = CooldownBars;
}
else if (dojiShort || smaCrossDown)
{
SellMarket();
_entryPrice = candle.ClosePrice;
_waitingForBreakout = false;
_cooldown = CooldownBars;
}
}
// Exit: SMA cross or ATR-based stop
if (Position > 0)
{
var smaCross = _prevClose >= _prevSma && candle.ClosePrice < smaValue;
var atrStop = _entryPrice > 0 && candle.ClosePrice < _entryPrice - atrValue * 2;
if (smaCross || atrStop)
{
SellMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
}
else if (Position < 0)
{
var smaCross = _prevClose <= _prevSma && candle.ClosePrice > smaValue;
var atrStop = _entryPrice > 0 && candle.ClosePrice > _entryPrice + atrValue * 2;
if (smaCross || atrStop)
{
BuyMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
}
_prevClose = candle.ClosePrice;
_prevSma = smaValue;
}
}
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 SimpleMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class long_leg_doji_breakout_strategy(Strategy):
"""
Long-Leg Doji breakout strategy.
Detects doji patterns and trades breakouts with SMA confirmation.
"""
def __init__(self):
super(long_leg_doji_breakout_strategy, self).__init__()
self._doji_threshold = self.Param("DojiBodyThreshold", 15.0) \
.SetDisplay("Doji Body %", "Body size as % of range", "Pattern")
self._wick_ratio = self.Param("MinWickRatio", 1.2) \
.SetDisplay("Min Wick Ratio", "Min wick to body ratio", "Pattern")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR period", "Filter")
self._cooldown_bars = self.Param("CooldownBars", 70) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candles", "General")
self._doji_high = 0.0
self._doji_low = 0.0
self._waiting = False
self._prev_close = 0.0
self._prev_sma = 0.0
self._cooldown = 0
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(long_leg_doji_breakout_strategy, self).OnReseted()
self._doji_high = 0.0
self._doji_low = 0.0
self._waiting = False
self._prev_close = 0.0
self._prev_sma = 0.0
self._cooldown = 0
self._entry_price = 0.0
def OnStarted2(self, time):
super(long_leg_doji_breakout_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = 10
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sma_val, atr_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
sma = float(sma_val)
atr = float(atr_val)
if self._cooldown > 0:
self._cooldown -= 1
self._prev_close = close
self._prev_sma = sma
return
if atr <= 0 or self._prev_sma == 0:
self._prev_close = close
self._prev_sma = sma
return
body = abs(close - open_p)
rng = high - low
if rng > 0:
upper_wick = high - max(open_p, close)
lower_wick = min(open_p, close) - low
threshold = self._doji_threshold.Value / 100.0
small_body = body <= rng * threshold
long_wicks = upper_wick >= body * self._wick_ratio.Value and lower_wick >= body * self._wick_ratio.Value
if small_body and long_wicks:
self._doji_high = high
self._doji_low = low
self._waiting = True
if self.Position == 0 and self._prev_close > 0:
doji_long = self._waiting and close > self._doji_high and close > sma
doji_short = self._waiting and close < self._doji_low and close < sma
sma_cross_up = self._prev_close < self._prev_sma and close > sma
sma_cross_down = self._prev_close > self._prev_sma and close < sma
if doji_long or sma_cross_up:
self.BuyMarket()
self._entry_price = close
self._waiting = False
self._cooldown = self._cooldown_bars.Value
elif doji_short or sma_cross_down:
self.SellMarket()
self._entry_price = close
self._waiting = False
self._cooldown = self._cooldown_bars.Value
if self.Position > 0:
sma_cross = self._prev_close >= self._prev_sma and close < sma
atr_stop = self._entry_price > 0 and close < self._entry_price - atr * 2
if sma_cross or atr_stop:
self.SellMarket(abs(self.Position))
self._cooldown = self._cooldown_bars.Value
elif self.Position < 0:
sma_cross = self._prev_close <= self._prev_sma and close > sma
atr_stop = self._entry_price > 0 and close > self._entry_price + atr * 2
if sma_cross or atr_stop:
self.BuyMarket(abs(self.Position))
self._cooldown = self._cooldown_bars.Value
self._prev_close = close
self._prev_sma = sma
def CreateClone(self):
return long_leg_doji_breakout_strategy()