Стратегия нисходящего разворота из трёх свечей
Это зеркальная версия бычьего паттерна, рассчитанная на быстрые медвежьи развороты. После двух сильных растущих свечей, обновляющих максимумы, решительная медвежья свеча закрывается ниже минимума предыдущей. Небольшой восходящий тренд перед этим помогает подтвердить усталость покупателей.
Тестирование показывает среднегодичную доходность около 88%. Стратегию лучше запускать на фондовом рынке.
Алгоритм отслеживает плавающее окно из трёх свечей. Когда появляется паттерн и выполнено условие восходящего тренда, открывается короткая позиция со стопом выше максимума паттерна. Правила просты, поэтому сигналы возникают сразу после закрытия свечи.
Сделка закрывается по защитному стопу или при формировании другого паттерна. Поскольку стратегия отыгрывает краткосрочные откаты в потенциальном нисходящем движении, она лучше всего работает на волатильных рынках.
Детали
- Условия входа: Две бычьи свечи с повышающимися максимумами, затем медвежья свеча, закрывающаяся ниже минимума средней свечи.
- Направление: Только шорт.
- Условия выхода: Стоп‑лосс или следующий паттерн.
- Стопы: Да, выше максимума паттерна.
- Значения по умолчанию:
CandleType= 15 минутStopLossPercent= 1RequireUptrend= trueUptrendLength= 5
- Фильтры:
- Категория: Паттерн
- Направление: Шорт
- Индикаторы: Свечной анализ
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейронные сети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Short-only three-bar reversal after an optional net uptrend.
/// A pattern-high stop and an opposite three-bar reversal close the full position.
/// </summary>
public class ThreeBarReversalDownStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<bool> _requireUptrend;
private readonly StrategyParam<int> _uptrendLength;
private readonly List<ICandleMessage> _recent = new();
private decimal? _patternStop;
private Order _entryOrder;
private Order _exitOrder;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public bool RequireUptrend { get => _requireUptrend.Value; set => _requireUptrend.Value = value; }
public int UptrendLength { get => _uptrendLength.Value; set => _uptrendLength.Value = value; }
public ThreeBarReversalDownStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Three-bar pattern timeframe", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 1m).SetRange(0m, 99m)
.SetDisplay("Stop above pattern high (%)", "Buffer above the highest of all three pattern highs; zero places the stop at that high.", "Protection");
_requireUptrend = Param(nameof(RequireUptrend), true)
.SetDisplay("Require Uptrend", "Require a net Close rise before the signal candle.", "Pattern");
_uptrendLength = Param(nameof(UptrendLength), 5).SetRange(2, 100)
.SetDisplay("Uptrend Length", "Number of preceding candles from first Close to last Close, including the two bullish pattern bars.", "Pattern");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
protected override void OnReseted()
{
base.OnReseted();
ClearState();
}
private void ClearState()
{
_recent.Clear();
_patternStop = null;
_entryOrder = _exitOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ClearState();
var asks = new Subscription(DataType.Level1, Security);
asks.MarketData.BuildField = Level1Fields.BestAskPrice;
SubscribeLevel1(asks).Bind(ProcessAsk).Start();
var candles = SubscribeCandles(CandleType);
candles.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, candles);
DrawOwnTrades(area);
}
}
private static bool IsPending(Order order)
=> order is not null && order.State is not (OrderStates.Done or OrderStates.Failed);
private void ProcessAsk(Level1ChangeMessage message)
{
if (message.TryGetDecimal(Level1Fields.BestAskPrice) is decimal ask && ask > 0m)
CheckStop(ask);
}
private bool CheckStop(decimal executableAsk)
{
if (Position >= 0m || _patternStop is not decimal stop || executableAsk < stop || IsPending(_exitOrder))
return false;
_exitOrder = BuyMarket(Math.Abs(Position));
_recent.Clear();
_patternStop = null;
return true;
}
private void Append(ICandleMessage candle)
{
_recent.Add(candle);
if (_recent.Count > UptrendLength)
_recent.RemoveAt(0);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (Position < 0m && CheckStop(candle.HighPrice))
return;
if (IsPending(_entryOrder) || IsPending(_exitOrder))
{
Append(candle);
return;
}
var previous = _recent.Count >= 1 ? _recent[^1] : null;
var older = _recent.Count >= 2 ? _recent[^2] : null;
var downPattern = older is not null && previous.ClosePrice > previous.OpenPrice &&
older.ClosePrice > older.OpenPrice && previous.HighPrice > older.HighPrice &&
candle.ClosePrice < candle.OpenPrice && candle.ClosePrice < previous.LowPrice;
var upPattern = older is not null && previous.ClosePrice < previous.OpenPrice &&
older.ClosePrice < older.OpenPrice && previous.LowPrice < older.LowPrice &&
candle.ClosePrice > candle.OpenPrice && candle.ClosePrice > previous.HighPrice;
if (Position < 0m && upPattern && IsFormedAndOnlineAndAllowTrading())
{
_exitOrder = BuyMarket(Math.Abs(Position));
_recent.Clear();
_patternStop = null;
}
else if (Position == 0m && downPattern &&
(!RequireUptrend || _recent.Count >= UptrendLength &&
_recent[^1].ClosePrice > _recent[^UptrendLength].ClosePrice) &&
IsFormedAndOnlineAndAllowTrading())
{
_patternStop = Math.Max(Math.Max(older.HighPrice, previous.HighPrice), candle.HighPrice) *
(1m + StopLossPercent / 100m);
_entryOrder = SellMarket(Volume);
}
Append(candle);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal, Math
from StockSharp.Messages import DataType, CandleStates, Level1Fields, OrderStates
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class three_bar_reversal_down_strategy(Strategy):
"""
Short-only three-bar reversal after an optional net uptrend.
A pattern-high stop and an opposite three-bar reversal close the full position.
"""
def __init__(self):
super(three_bar_reversal_down_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Three-bar pattern timeframe", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0).SetRange(0.0, 99.0).SetDisplay("Stop above pattern high (%)", "Buffer above the highest of all three pattern highs; zero places the stop at that high.", "Protection")
self._require_uptrend = self.Param("RequireUptrend", True).SetDisplay("Require Uptrend", "Require a net Close rise before the signal candle.", "Pattern")
self._uptrend_length = self.Param("UptrendLength", 5).SetRange(2, 100).SetDisplay("Uptrend Length", "Number of preceding candles from first Close to last Close, including the two bullish pattern bars.", "Pattern")
self._clear_state()
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def _clear_state(self):
self._recent = []
self._pattern_stop = None
self._entry_order = None
self._exit_order = None
def OnReseted(self):
super(three_bar_reversal_down_strategy, self).OnReseted()
self._clear_state()
def OnStarted2(self, time):
super(three_bar_reversal_down_strategy, self).OnStarted2(time)
self._clear_state()
asks = Subscription(DataType.Level1, self.Security)
asks.MarketData.BuildField = Level1Fields.BestAskPrice
self.SubscribeLevel1(asks).Bind(self._process_ask).Start()
candles = self.SubscribeCandles(self.candle_type)
candles.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, candles)
self.DrawOwnTrades(area)
def _is_pending(self, order):
return order is not None and order.State not in (OrderStates.Done, OrderStates.Failed)
def _process_ask(self, message):
if message.Changes.ContainsKey(Level1Fields.BestAskPrice):
ask = message.Changes[Level1Fields.BestAskPrice]
if ask is not None and ask > Decimal(0):
self._check_stop(ask)
def _check_stop(self, executable_ask):
if (self.Position >= 0 or self._pattern_stop is None or
executable_ask < self._pattern_stop or self._is_pending(self._exit_order)):
return False
self._exit_order = self.BuyMarket(Math.Abs(self.Position))
self._recent = []
self._pattern_stop = None
return True
def _append(self, candle):
self._recent.append(candle)
if len(self._recent) > self._uptrend_length.Value:
self._recent.pop(0)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self.Position < 0 and self._check_stop(candle.HighPrice):
return
if self._is_pending(self._entry_order) or self._is_pending(self._exit_order):
self._append(candle)
return
previous = self._recent[-1] if len(self._recent) >= 1 else None
older = self._recent[-2] if len(self._recent) >= 2 else None
down_pattern = (older is not None and previous.ClosePrice > previous.OpenPrice and
older.ClosePrice > older.OpenPrice and previous.HighPrice > older.HighPrice and
candle.ClosePrice < candle.OpenPrice and candle.ClosePrice < previous.LowPrice)
up_pattern = (older is not None and previous.ClosePrice < previous.OpenPrice and
older.ClosePrice < older.OpenPrice and previous.LowPrice < older.LowPrice and
candle.ClosePrice > candle.OpenPrice and candle.ClosePrice > previous.HighPrice)
if self.Position < 0 and up_pattern and self.IsFormedAndOnlineAndAllowTrading():
self._exit_order = self.BuyMarket(Math.Abs(self.Position))
self._recent = []
self._pattern_stop = None
elif (self.Position == 0 and down_pattern and
(not self._require_uptrend.Value or
len(self._recent) >= self._uptrend_length.Value and
self._recent[-1].ClosePrice > self._recent[-self._uptrend_length.Value].ClosePrice) and
self.IsFormedAndOnlineAndAllowTrading()):
pattern_high = Math.Max(Math.Max(older.HighPrice, previous.HighPrice), candle.HighPrice)
self._pattern_stop = pattern_high * (Decimal(1) + Decimal(self._stop_loss_percent.Value) / Decimal(100))
self._entry_order = self.SellMarket(self.Volume)
self._append(candle)
def CreateClone(self):
return three_bar_reversal_down_strategy()