Exceeded Candle-Strategie
Dieser musterbasierte Ansatz sucht nach bullischen Engulfing-Kerzen, die den vorherigen Balken übersteigen, während der Preis noch unterhalb der mittleren Bollinger-Bande liegt. Die Idee ist, dass eine starke Umkehr innerhalb eines Rückgangs den Preis zurück zur oberen Bande treiben kann. Die Strategie handelt nur Long und bricht Einstiege ab, wenn drei aufeinanderfolgende bearishe Kerzen erscheinen.
Sobald der Preis die obere Bollinger-Bande berührt, wird die Position geschlossen und der schnelle Rückprall erfasst. Die Methode eignet sich für kurze Zeitrahmen, bei denen Volatilitätsbänder Mean-Reversion-Schwingungen erfassen.
Details
- Einstiegskriterien:
- Long: vorherige Kerze rot, aktuelle Kerze grün und schließt über dem vorherigen Eröffnungskurs,
Close < MiddleBand, keine drei aufeinanderfolgenden roten Kerzen
- Long: vorherige Kerze rot, aktuelle Kerze grün und schließt über dem vorherigen Eröffnungskurs,
- Long/Short: Nur Long
- Ausstiegskriterien:
- Long:
Close > UpperBand
- Long:
- Stops: Keine
- Standardwerte:
BBLength= 20BBMultiplier= 2.0
- Filter:
- Kategorie: Mean Reversion
- Richtung: Nur Long
- Indikatoren: Bollinger Bands, price action
- Stops: Nein
- Komplexität: Niedrig
- Zeitrahmen: Kurzfristig
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Ja
- Risikolevel: Mittel
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Exceeded Candle Strategy - trades on candle engulfing patterns with BB filter.
/// Buys when a bullish candle surpasses the previous bearish candle and price is below BB middle.
/// Exits when price reaches the upper BB.
/// </summary>
public class ExceededCandleStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleTypeParam;
private readonly StrategyParam<int> _bbLength;
private readonly StrategyParam<decimal> _bbMultiplier;
private readonly StrategyParam<int> _cooldownBars;
private BollingerBands _bollinger;
private ICandleMessage _prevCandle;
private ICandleMessage _prevPrevCandle;
private ICandleMessage _prevPrevPrevCandle;
private int _cooldownRemaining;
public ExceededCandleStrategy()
{
_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General");
_bbLength = Param(nameof(BBLength), 20)
.SetGreaterThanZero()
.SetDisplay("BB Period", "Bollinger Bands period", "Bollinger Bands");
_bbMultiplier = Param(nameof(BBMultiplier), 1.5m)
.SetDisplay("BB StdDev", "Bollinger Bands standard deviation multiplier", "Bollinger Bands");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
public DataType CandleType
{
get => _candleTypeParam.Value;
set => _candleTypeParam.Value = value;
}
public int BBLength
{
get => _bbLength.Value;
set => _bbLength.Value = value;
}
public decimal BBMultiplier
{
get => _bbMultiplier.Value;
set => _bbMultiplier.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_bollinger = null;
_prevCandle = null;
_prevPrevCandle = null;
_prevPrevPrevCandle = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bollinger = new BollingerBands
{
Length = BBLength,
Width = BBMultiplier
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_bollinger, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _bollinger);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, IIndicatorValue bollingerValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_bollinger.IsFormed)
{
UpdateCandleHistory(candle);
return;
}
var bb = (BollingerBandsValue)bollingerValue;
if (bb.UpBand is not decimal upperBand ||
bb.LowBand is not decimal lowerBand ||
bb.MovingAverage is not decimal middleBand)
{
UpdateCandleHistory(candle);
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
UpdateCandleHistory(candle);
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
UpdateCandleHistory(candle);
return;
}
var close = candle.ClosePrice;
var open = candle.OpenPrice;
// Check for engulfing patterns
var greenExceeded = false;
var redExceeded = false;
if (_prevCandle != null)
{
// Bullish engulfing: previous was bearish, current is bullish and closes above previous open
greenExceeded = _prevCandle.ClosePrice < _prevCandle.OpenPrice &&
close > open &&
close > _prevCandle.OpenPrice;
// Bearish engulfing: previous was bullish, current is bearish and closes below previous open
redExceeded = _prevCandle.ClosePrice > _prevCandle.OpenPrice &&
close < open &&
close < _prevCandle.OpenPrice;
}
// Check for 3 consecutive bearish candles (avoid buying into strong downtrend)
var last3Red = false;
if (_prevCandle != null && _prevPrevCandle != null && _prevPrevPrevCandle != null)
{
last3Red = _prevCandle.ClosePrice < _prevCandle.OpenPrice &&
_prevPrevCandle.ClosePrice < _prevPrevCandle.OpenPrice &&
_prevPrevPrevCandle.ClosePrice < _prevPrevPrevCandle.OpenPrice;
}
// Buy: bullish engulfing below middle band, not in strong downtrend
if (greenExceeded && close < middleBand && !last3Red && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Sell: bearish engulfing above middle band
else if (redExceeded && close > middleBand && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long at upper band
else if (Position > 0 && close >= upperBand)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Exit short at lower band
else if (Position < 0 && close <= lowerBand)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
UpdateCandleHistory(candle);
}
private void UpdateCandleHistory(ICandleMessage candle)
{
_prevPrevPrevCandle = _prevPrevCandle;
_prevPrevCandle = _prevCandle;
_prevCandle = candle;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import BollingerBands
from StockSharp.Algo.Strategies import Strategy
class exceeded_candle_strategy(Strategy):
"""Exceeded Candle Strategy: trades on candle engulfing patterns with BB filter.
Buys when bullish engulfing below BB middle, sells on bearish engulfing above middle.
Exits at BB upper/lower bands."""
def __init__(self):
super(exceeded_candle_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General")
self._bb_length = self.Param("BBLength", 20) \
.SetDisplay("BB Period", "Bollinger Bands period", "Bollinger Bands")
self._bb_multiplier = self.Param("BBMultiplier", 1.5) \
.SetDisplay("BB StdDev", "Bollinger Bands standard deviation multiplier", "Bollinger Bands")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._bollinger = None
self._prev_candle = None
self._prev_prev_candle = None
self._prev_prev_prev_candle = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(exceeded_candle_strategy, self).OnReseted()
self._bollinger = None
self._prev_candle = None
self._prev_prev_candle = None
self._prev_prev_prev_candle = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(exceeded_candle_strategy, self).OnStarted2(time)
self._bollinger = BollingerBands()
self._bollinger.Length = int(self._bb_length.Value)
self._bollinger.Width = float(self._bb_multiplier.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(self._bollinger, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._bollinger)
self.DrawOwnTrades(area)
def _on_process(self, candle, bb_value):
if candle.State != CandleStates.Finished:
return
if not self._bollinger.IsFormed:
self._update_history(candle)
return
if bb_value.UpBand is None or bb_value.LowBand is None or bb_value.MovingAverage is None:
self._update_history(candle)
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._update_history(candle)
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._update_history(candle)
return
upper_band = float(bb_value.UpBand)
lower_band = float(bb_value.LowBand)
middle_band = float(bb_value.MovingAverage)
close = float(candle.ClosePrice)
opn = float(candle.OpenPrice)
cooldown = int(self._cooldown_bars.Value)
green_exceeded = False
red_exceeded = False
if self._prev_candle is not None:
prev_close = self._prev_candle[0]
prev_open = self._prev_candle[1]
green_exceeded = prev_close < prev_open and close > opn and close > prev_open
red_exceeded = prev_close > prev_open and close < opn and close < prev_open
last_3_red = False
if (self._prev_candle is not None and self._prev_prev_candle is not None
and self._prev_prev_prev_candle is not None):
last_3_red = (self._prev_candle[0] < self._prev_candle[1] and
self._prev_prev_candle[0] < self._prev_prev_candle[1] and
self._prev_prev_prev_candle[0] < self._prev_prev_prev_candle[1])
if green_exceeded and close < middle_band and not last_3_red and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif red_exceeded and close > middle_band and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
elif self.Position > 0 and close >= upper_band:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and close <= lower_band:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
self._update_history(candle)
def _update_history(self, candle):
self._prev_prev_prev_candle = self._prev_prev_candle
self._prev_prev_candle = self._prev_candle
self._prev_candle = (float(candle.ClosePrice), float(candle.OpenPrice))
def CreateClone(self):
return exceeded_candle_strategy()