Стратегия по бычьему поглощению
Данная настройка ищет резкий разворот вверх, когда свеча полностью поглощает предыдущую медвежью. Такая формация часто завершает краткосрочное снижение и указывает на возобновление восходящего импульса. Необязательный фильтр нисходящего тренда учитывает подряд идущие красные свечи, подтверждая истощение продавцов.
Тестирование показывает среднегодичную доходность около 76%. Стратегию лучше запускать на рынке Форекс.
В режиме реального времени алгоритм отслеживает каждую новую свечу и запоминает предыдущую. Если новая свеча закрывается выше открытия и её тело перекрывает предыдущую, открывается длинная позиция. Стоп размещается сразу под минимумом паттерна, ограничивая риск.
Сделка остаётся открытой, пока не сработает стоп или не появится сигнал на ручной выход. Поскольку подтверждение ранее сформированных красных свеч усиливает сигнал, стратегия избегает слабых разворотов.
Детали
- Условия входа: Бычья свеча поглощает предыдущую медвежью, при необходимости присутствует нисходящий тренд.
- Направление: Только лонг.
- Условия выхода: Стоп‑лосс или по усмотрению трейдера.
- Стопы: Да, под минимумом паттерна.
- Значения по умолчанию:
CandleType= 15 минутStopLossPercent= 1RequireDowntrend= trueDowntrendBars= 3
- Фильтры:
- Категория: Паттерн
- Направление: Лонг
- Индикаторы: Свечной анализ
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейронные сети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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>
/// Long-only bullish body engulfing after optional consecutive bearish candles.
/// Protection sits below the lower of the two pattern lows.
/// </summary>
public class EngulfingBullishStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<bool> _requireDowntrend;
private readonly StrategyParam<int> _downtrendBars;
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 RequireDowntrend { get => _requireDowntrend.Value; set => _requireDowntrend.Value = value; }
public int DowntrendBars { get => _downtrendBars.Value; set => _downtrendBars.Value = value; }
public EngulfingBullishStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Engulfing timeframe", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 1m).SetRange(0m, 99m)
.SetDisplay("Stop below pattern low (%)", "Buffer below the lower of the two pattern lows; zero places the stop at the low.", "Protection");
_requireDowntrend = Param(nameof(RequireDowntrend), true)
.SetDisplay("Require Downtrend", "Require consecutive bearish candles immediately before the engulfing candle.", "Pattern");
_downtrendBars = Param(nameof(DowntrendBars), 3).SetRange(1, 100)
.SetDisplay("Downtrend Bars", "Number of prior consecutive bearish candles, including the engulfed candle.", "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 bids = new Subscription(DataType.Level1, Security);
bids.MarketData.BuildField = Level1Fields.BestBidPrice;
SubscribeLevel1(bids).Bind(ProcessBid).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 ProcessBid(Level1ChangeMessage message)
{
if (message.TryGetDecimal(Level1Fields.BestBidPrice) is decimal bid && bid > 0m)
CheckStop(bid);
}
private void CheckStop(decimal executableBid)
{
if (Position > 0m && _patternStop is decimal stop && executableBid <= stop && !IsPending(_exitOrder))
{
_exitOrder = SellMarket(Position);
_recent.Clear();
_patternStop = null;
}
}
private void Append(ICandleMessage candle)
{
_recent.Add(candle);
if (_recent.Count > DowntrendBars)
_recent.RemoveAt(0);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (Position > 0m)
{
// Bar-low fallback covers gaps or missing executable quote updates.
CheckStop(candle.LowPrice);
if (Position > 0m && !IsPending(_exitOrder))
Append(candle);
return;
}
if (IsPending(_entryOrder) || IsPending(_exitOrder))
{
Append(candle);
return;
}
var previous = _recent.LastOrDefault();
var downtrend = !RequireDowntrend ||
_recent.Count >= DowntrendBars &&
_recent.TakeLast(DowntrendBars).All(bar => bar.ClosePrice < bar.OpenPrice);
var engulfing = previous is not null && previous.ClosePrice < previous.OpenPrice &&
candle.ClosePrice > candle.OpenPrice &&
candle.OpenPrice <= previous.ClosePrice &&
candle.ClosePrice >= previous.OpenPrice;
if (engulfing && downtrend && IsFormedAndOnlineAndAllowTrading())
{
_patternStop = Math.Min(previous.LowPrice, candle.LowPrice) * (1m - StopLossPercent / 100m);
_entryOrder = BuyMarket(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 engulfing_bullish_strategy(Strategy):
"""
Long-only bullish body engulfing after optional consecutive bearish candles.
Protection sits below the lower of the two pattern lows.
"""
def __init__(self):
super(engulfing_bullish_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Engulfing timeframe", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0).SetRange(0.0, 99.0).SetDisplay("Stop below pattern low (%)", "Buffer below the lower of the two pattern lows; zero places the stop at the low.", "Protection")
self._require_downtrend = self.Param("RequireDowntrend", True).SetDisplay("Require Downtrend", "Require consecutive bearish candles immediately before the engulfing candle.", "Pattern")
self._downtrend_bars = self.Param("DowntrendBars", 3).SetRange(1, 100).SetDisplay("Downtrend Bars", "Number of prior consecutive bearish candles, including the engulfed candle.", "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(engulfing_bullish_strategy, self).OnReseted()
self._clear_state()
def OnStarted2(self, time):
super(engulfing_bullish_strategy, self).OnStarted2(time)
self._clear_state()
bids = Subscription(DataType.Level1, self.Security)
bids.MarketData.BuildField = Level1Fields.BestBidPrice
self.SubscribeLevel1(bids).Bind(self._process_bid).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_bid(self, message):
if message.Changes.ContainsKey(Level1Fields.BestBidPrice):
bid = message.Changes[Level1Fields.BestBidPrice]
if bid is not None and bid > Decimal(0):
self._check_stop(bid)
def _check_stop(self, executable_bid):
if (self.Position > 0 and self._pattern_stop is not None and
executable_bid <= self._pattern_stop and not self._is_pending(self._exit_order)):
self._exit_order = self.SellMarket(self.Position)
self._recent = []
self._pattern_stop = None
def _append(self, candle):
self._recent.append(candle)
if len(self._recent) > self._downtrend_bars.Value:
self._recent.pop(0)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self.Position > 0:
# Bar-low fallback covers gaps or missing executable quote updates.
self._check_stop(candle.LowPrice)
if self.Position > 0 and not self._is_pending(self._exit_order):
self._append(candle)
return
if self._is_pending(self._entry_order) or self._is_pending(self._exit_order):
self._append(candle)
return
previous = self._recent[-1] if self._recent else None
downtrend = (not self._require_downtrend.Value or
len(self._recent) >= self._downtrend_bars.Value and
all(bar.ClosePrice < bar.OpenPrice for bar in self._recent[-self._downtrend_bars.Value:]))
engulfing = (previous is not None and previous.ClosePrice < previous.OpenPrice and
candle.ClosePrice > candle.OpenPrice and
candle.OpenPrice <= previous.ClosePrice and
candle.ClosePrice >= previous.OpenPrice)
if engulfing and downtrend and self.IsFormedAndOnlineAndAllowTrading():
pattern_low = Math.Min(previous.LowPrice, candle.LowPrice)
self._pattern_stop = pattern_low * (Decimal(1) - Decimal(self._stop_loss_percent.Value) / Decimal(100))
self._entry_order = self.BuyMarket(self.Volume)
self._append(candle)
def CreateClone(self):
return engulfing_bullish_strategy()