Esta configuración busca una reversión alcista pronunciada cuando una vela envuelve completamente la barra bajista anterior. Dicha formación suele poner fin a una caída a corto plazo e insinúa un renovado impulso ascendente. El filtro de tendencia bajista opcional cuenta velas rojas consecutivas para confirmar el agotamiento de los vendedores.
Las pruebas indican un rendimiento anual promedio de aproximadamente el 76%. Funciona mejor en el mercado forex.
Durante la operación en vivo, el algoritmo observa cada vela entrante y mantiene un registro de la barra anterior. Si la nueva vela cierra más alto de lo que abre y su cuerpo envuelve la barra anterior, se activa una entrada larga. El stop se coloca justo por debajo del mínimo del patrón para limitar el riesgo.
Las operaciones permanecen abiertas hasta que se activa el stop o alguna otra señal sugiere una salida discrecional. Dado que la confirmación de barras bajistas previas fortalece la configuración, la estrategia evita perseguir reversiones débiles.
Detalles
Criterios de entrada: La vela alcista envuelve la barra bajista anterior, con tendencia bajista opcional presente.
Largo/Corto: Solo largos.
Criterios de salida: Stop-loss o discrecional.
Stops: Sí, por debajo del mínimo del patrón.
Valores predeterminados:
CandleType = 15 minute
StopLossPercent = 1
RequireDowntrend = true
DowntrendBars = 3
Filtros:
Categoría: Patrón
Dirección: Largo
Indicadores: Candlestick
Stops: Sí
Complejidad: Intermedio
Marco temporal: Intradía
Estacionalidad: No
Redes neuronales: No
Divergencia: No
Nivel de riesgo: Medio
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()