Данная стратегия переносит на StockSharp эксперта MetaTrader «Bullish and Bearish Engulfing». Алгоритм анализирует завершённые свечи выбранного таймфрейма, позволяет пропускать несколько последних баров и реагирует только тогда, когда сформировавшийся паттерн «бычьего/медвежьего поглощения» удовлетворяет дополнительному фильтру по дистанции. Решение предназначено для трейдеров, которые торгуют свечные модели и хотят автоматизировать их обработку, не теряя контроля над направлением сделок, объёмом и управлением текущих позиций.
Условия распознавания паттерна
После учёта параметра смещения стратегия проверяет две последовательные завершённые свечи:
Бычье поглощение
Текущая оцениваемая свеча закрывается выше открытия (бычье тело).
Предыдущая свеча закрывается ниже открытия (медвежье тело).
Максимум текущей свечи выше предыдущего максимума, а минимум ниже предыдущего минимума как минимум на величину фильтра.
Закрытие бычьей свечи выше открытия предыдущей, а её открытие ниже предыдущего закрытия — также с учётом фильтра.
Медвежье поглощение
Текущая свеча закрывается ниже открытия (медвежье тело).
Предыдущая свеча закрывается выше открытия (бычье тело).
Новая свеча делает более высокий максимум, но закрывается значительно ниже предыдущего открытия и открывается выше предыдущего закрытия — все сравнения проводятся с поправкой на фильтр.
Минимум текущей свечи ниже предыдущего минимума не менее чем на заданную дистанцию.
Эти критерии полностью повторяют оригинальный код для MetaTrader, где требовалось, чтобы свеча-поглотитель полностью перекрывала тело предыдущей и одновременно пробивала оба экстремума. Параметр дистанции задаётся в пунктах (pips) и автоматически переводится в цену с учётом PriceStep и количества знаков у инструмента (для 5- и 3-значных валютных котировок используется множитель 10).
Логика работы
Через высокоуровневый API выполняется подписка на выбранный тип свечей; обрабатываются только свечи с состоянием Finished.
Ведётся небольшой буфер с OHLC-значениями, достаточный для текущего смещения.
Как только в буфере есть минимум две подходящие свечи, проверяются условия бычьего и медвежьего поглощения.
При бычьем сигнале создаётся рыночная заявка на сторону, указанную в параметре BullishSide; при медвежьем сигнале используется значение BearishSide.
Если включён флаг CloseOppositePositions, наличие противоположной позиции приводит к увеличению объёма заявки на абсолютный размер позиции — таким образом противоположная позиция закрывается и сразу открывается новая в нужном направлении. Когда флаг выключен, сигналы игнорируются до тех пор, пока противоположная позиция не будет закрыта вручную.
Объём сделки задаётся параметром Volume (по умолчанию 1). Стратегия не добавляет стоп-лосс или тейк-профит автоматически — управление рисками должно быть настроено отдельно либо через защитные модули StockSharp (например, StartProtection).
Параметры
Параметр
Описание
Значение по умолчанию
Примечания
CandleType
Тип свечей (DataType), используемый для поиска сигналов.
Свечи 1 часа
Можно выбрать любой поддерживаемый таймфрейм.
Shift
Количество завершённых свечей, которые следует пропустить перед оценкой паттерна.
1
Значение 1 анализирует последнюю закрытую свечу; большие значения смещают анализ в прошлое.
DistanceInPips
Минимальная дистанция в пунктах между свечами поглощения.
0
Переводится в цену через шаг цены инструмента; помогает отсечь свечи с маленьким телом.
CloseOppositePositions
Нужно ли закрывать противоположные позиции перед входом.
true
При false сигнал пропускается, если открыта позиция противоположного направления.
BullishSide
Направление заявки при бычьем поглощении.
Buy
Можно переключить на Sell для контртрендовой торговли.
BearishSide
Направление заявки при медвежьем поглощении.
Sell
Можно переключить на Buy, чтобы торговать против тренда.
Volume
Базовый объём заявки.
1
При закрытии обратной позиции к объёму добавляется abs(Position).
Управление позицией и рисками
Сделки исполняются рыночными ордерами без встроенных защитных уровней. Рекомендуется подключать защитные модули или внешние ограничения риска.
В оригинальной MQL-версии объём рассчитывался через модуль Money Management. В портированной стратегии используется фиксированное значение Volume, что делает поведение предсказуемым в StockSharp; при необходимости добавьте собственный блок управления капиталом.
При активном параметре CloseOppositePositions разворот выполняется одной сделкой: объём равен базовому значению плюс абсолютная текущая позиция, что обеспечивает немедленный переход из старого направления в новое.
Структура файлов
CS/BullishBearishEngulfingStrategy.cs — C# реализация, использующая высокоуровневый API стратегий StockSharp.
Важно: Python-версия и соответствующая папка не создавались в рамках данной задачи — предоставлена только C# реализация.
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>
/// Engulfing pattern strategy that reacts to bullish and bearish engulfing candles.
/// </summary>
public class BullishBearishEngulfingStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _shift;
private readonly StrategyParam<decimal> _distanceInPips;
private readonly StrategyParam<bool> _closeOpposite;
private readonly StrategyParam<Sides> _bullishSide;
private readonly StrategyParam<Sides> _bearishSide;
private readonly List<CandleSnapshot> _candles = new();
/// <summary>
/// Initializes a new instance of <see cref="BullishBearishEngulfingStrategy"/>.
/// </summary>
public BullishBearishEngulfingStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Time frame for analysis", "General");
_shift = Param(nameof(Shift), 1)
.SetGreaterThanZero()
.SetDisplay("Shift", "Number of completed candles to skip", "Pattern")
.SetOptimize(1, 5, 1);
_distanceInPips = Param(nameof(DistanceInPips), 0m)
.SetNotNegative()
.SetDisplay("Distance (pips)", "Additional filter expressed in pips", "Pattern")
.SetOptimize(0m, 10m, 1m);
_closeOpposite = Param(nameof(CloseOppositePositions), true)
.SetDisplay("Close Opposite", "Close opposite position before entering", "Risk");
_bullishSide = Param(nameof(BullishSide), Sides.Buy)
.SetDisplay("Bullish Action", "Order side for bullish engulfing", "Pattern");
_bearishSide = Param(nameof(BearishSide), Sides.Sell)
.SetDisplay("Bearish Action", "Order side for bearish engulfing", "Pattern");
}
/// <summary>
/// Candle type used for pattern detection.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Number of fully completed candles to skip before pattern evaluation.
/// </summary>
public int Shift
{
get => _shift.Value;
set => _shift.Value = value;
}
/// <summary>
/// Additional price filter expressed in pips.
/// </summary>
public decimal DistanceInPips
{
get => _distanceInPips.Value;
set => _distanceInPips.Value = value;
}
/// <summary>
/// Indicates whether opposite positions should be closed before entering a new trade.
/// </summary>
public bool CloseOppositePositions
{
get => _closeOpposite.Value;
set => _closeOpposite.Value = value;
}
/// <summary>
/// Side used when a bullish engulfing pattern appears.
/// </summary>
public Sides BullishSide
{
get => _bullishSide.Value;
set => _bullishSide.Value = value;
}
/// <summary>
/// Side used when a bearish engulfing pattern appears.
/// </summary>
public Sides BearishSide
{
get => _bearishSide.Value;
set => _bearishSide.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_candles.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_candles.Clear();
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
// no protection needed
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var snapshot = new CandleSnapshot
{
Open = candle.OpenPrice,
High = candle.HighPrice,
Low = candle.LowPrice,
Close = candle.ClosePrice
};
_candles.Add(snapshot);
var maxCount = Math.Max(Shift + 2, 3);
while (_candles.Count > maxCount)
try { _candles.RemoveAt(0); } catch { break; }
if (_candles.Count < Shift + 1)
return;
var candles = _candles.ToArray();
var currentIndex = candles.Length - Shift;
if (currentIndex <= 0)
return;
var previousIndex = currentIndex - 1;
if (previousIndex < 0)
return;
var current = candles[currentIndex];
var previous = candles[previousIndex];
var distance = CalculateDistanceInPrice();
var isBullishEngulfing = current.Close > current.Open && previous.Open > previous.Close &&
current.High > previous.High + distance &&
current.Close > previous.Open + distance &&
current.Open < previous.Close - distance &&
current.Low < previous.Low - distance;
if (isBullishEngulfing)
{
HandleSignal(BullishSide);
return;
}
var isBearishEngulfing = current.Open > current.Close && previous.Open < previous.Close &&
current.High > previous.High + distance &&
current.Open > previous.Close + distance &&
current.Close < previous.Open - distance &&
current.Low < previous.Low - distance;
if (isBearishEngulfing)
HandleSignal(BearishSide);
}
private void HandleSignal(Sides side)
{
switch (side)
{
case Sides.Buy:
EnterLong();
break;
case Sides.Sell:
EnterShort();
break;
}
}
private void EnterLong()
{
if (Position > 0)
return;
if (Position < 0)
{
if (!CloseOppositePositions)
return;
// Close short first
BuyMarket();
}
BuyMarket();
}
private void EnterShort()
{
if (Position < 0)
return;
if (Position > 0)
{
if (!CloseOppositePositions)
return;
// Close long first
SellMarket();
}
SellMarket();
}
private decimal CalculateDistanceInPrice()
{
var priceStep = Security?.PriceStep;
if (priceStep == null)
return 0m;
var decimals = Security?.Decimals ?? 0;
var multiplier = decimals is 3 or 5 ? 10m : 1m;
return DistanceInPips * priceStep.Value * multiplier;
}
private struct CandleSnapshot
{
public decimal Open;
public decimal High;
public decimal Low;
public decimal Close;
}
}
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, Sides
from StockSharp.Algo.Strategies import Strategy
class bullish_bearish_engulfing_strategy(Strategy):
"""Engulfing pattern strategy that reacts to bullish and bearish engulfing candles."""
def __init__(self):
super(bullish_bearish_engulfing_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Time frame for analysis", "General")
self._shift = self.Param("Shift", 1) \
.SetGreaterThanZero() \
.SetDisplay("Shift", "Number of completed candles to skip", "Pattern")
self._distance_pips = self.Param("DistanceInPips", 0.0) \
.SetDisplay("Distance (pips)", "Additional filter in pips", "Pattern")
self._close_opposite = self.Param("CloseOpposite", True) \
.SetDisplay("Close Opposite", "Close opposite position before entering", "Risk")
self._candles = []
@property
def CandleType(self):
return self._candle_type.Value
@property
def Shift(self):
return self._shift.Value
@property
def DistanceInPips(self):
return self._distance_pips.Value
@property
def CloseOpposite(self):
return self._close_opposite.Value
def OnStarted2(self, time):
super(bullish_bearish_engulfing_strategy, self).OnStarted2(time)
self._candles = []
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
o = float(candle.OpenPrice)
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
c = float(candle.ClosePrice)
self._candles.append((o, h, lo, c))
max_count = max(self.Shift + 2, 3)
while len(self._candles) > max_count:
self._candles.pop(0)
if len(self._candles) < self.Shift + 1:
return
idx = len(self._candles) - self.Shift
if idx <= 0:
return
prev_idx = idx - 1
if prev_idx < 0:
return
cur = self._candles[idx]
prev = self._candles[prev_idx]
dist = self._calc_distance()
# Bullish engulfing
is_bullish = (cur[3] > cur[0] and prev[0] > prev[3] and
cur[1] > prev[1] + dist and
cur[3] > prev[0] + dist and
cur[0] < prev[3] - dist and
cur[2] < prev[2] - dist)
if is_bullish:
self._enter_long()
return
# Bearish engulfing
is_bearish = (cur[0] > cur[3] and prev[0] < prev[3] and
cur[1] > prev[1] + dist and
cur[0] > prev[3] + dist and
cur[3] < prev[0] - dist and
cur[2] < prev[2] - dist)
if is_bearish:
self._enter_short()
def _enter_long(self):
if self.Position > 0:
return
if self.Position < 0:
if not self.CloseOpposite:
return
self.BuyMarket()
self.BuyMarket()
def _enter_short(self):
if self.Position < 0:
return
if self.Position > 0:
if not self.CloseOpposite:
return
self.SellMarket()
self.SellMarket()
def _calc_distance(self):
sec = self.Security
if sec is None or sec.PriceStep is None:
return 0.0
step = float(sec.PriceStep)
decimals = sec.Decimals if sec.Decimals is not None else 0
mult = 10.0 if decimals == 3 or decimals == 5 else 1.0
return float(self.DistanceInPips) * step * mult
def OnReseted(self):
super(bullish_bearish_engulfing_strategy, self).OnReseted()
self._candles = []
def CreateClone(self):
return bullish_bearish_engulfing_strategy()