Dieses Setup sucht nach einer starken bullischen Umkehr, wenn eine Kerze den vorherigen bärischen Balken vollständig umschließt. Eine solche Formation beendet oft einen kurzfristigen Rückgang und deutet auf neuen Aufwärtsschwung hin. Der optionale Abwärtstrend-Filter zählt aufeinanderfolgende rote Kerzen, um die Erschöpfung der Verkäufer zu bestätigen.
Tests zeigen eine durchschnittliche jährliche Rendite von etwa 76%. Sie funktioniert am besten auf dem Forex-Markt.
Im Live-Betrieb beobachtet der Algorithmus jede eingehende Kerze und verfolgt den vorherigen Balken. Wenn die neue Kerze höher schließt als sie öffnet und ihr Körper den vorherigen Balken umschließt, wird ein Long-Einstieg ausgelöst. Der Stop wird knapp unterhalb des Mustertiefs platziert, um das Risiko zu begrenzen.
Trades bleiben offen, bis der Stop ausgelöst wird oder ein anderes Signal auf einen manuellen Ausstieg hindeutet. Da die Bestätigung durch frühere Abwärtsbalken das Setup stärkt, vermeidet die Strategie das Nachjagen schwacher Umkehrungen.
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()