Ausbrüche und Retests
Strategie, die bei Ausbrüchen aus jüngsten Hochs oder Tiefs und optionalen Retests mit Trailing-Stop-Management einsteigt.
Der Ansatz verfolgt Unterstützung und Widerstand, definiert durch die höchsten und niedrigsten Schlusskurse über ein Rückblickfenster. Ausbrüche öffnen Positionen in der Ausbruchsrichtung oder warten auf einen Retest des gebrochenen Niveaus. Ausstiege verwenden einen anfänglichen Stop-Loss, der sich in einen Trailing-Stop verwandelt, sobald der Gewinn einen Schwellenwert erreicht.
Details
- Einstiegskriterien: Ausbruch über Widerstand oder unter Unterstützung, optionaler Retest.
- Long/Short: Konfigurierbar.
- Ausstiegskriterien: Trailing-Stop oder entgegengesetzter Ausbruch.
- Stops: Anfänglicher Stop-Loss und Trailing-Stop.
- Standardwerte:
LookbackPeriod= 20RetestBarsSinceBreakout= 2RetestDetectionLimit= 2ProfitThresholdPercent= 5mTrailingStopGapPercent= 1mStopLossPercent= 2mCandleType= TimeSpan.FromMinutes(5)
- Filter:
- Kategorie: Ausbruch
- Richtung: Beide
- Indikatoren: Highest, Lowest
- Stops: Ja
- Komplexität: Mittel
- Zeitrahmen: Intraday (5m)
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Breakout and retest strategy with trailing stop.
/// </summary>
public class BreaksAndRetestsStrategy : Strategy
{
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _profitThresholdPercent;
private readonly StrategyParam<decimal> _trailingStopGapPercent;
private readonly StrategyParam<int> _maxHoldBars;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private decimal _prevHighest;
private decimal _prevLowest;
private decimal _entryPrice;
private bool _trailingStopActive;
private decimal _highestSinceTrailing;
private decimal _lowestSinceTrailing;
private int _barsInPosition;
private int _barsSinceExit;
public int LookbackPeriod { get => _lookbackPeriod.Value; set => _lookbackPeriod.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public decimal ProfitThresholdPercent { get => _profitThresholdPercent.Value; set => _profitThresholdPercent.Value = value; }
public decimal TrailingStopGapPercent { get => _trailingStopGapPercent.Value; set => _trailingStopGapPercent.Value = value; }
public int MaxHoldBars { get => _maxHoldBars.Value; set => _maxHoldBars.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public BreaksAndRetestsStrategy()
{
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Lookback Period", "Number of bars for support/resistance", "Levels");
_stopLossPercent = Param(nameof(StopLossPercent), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Initial stop loss", "Risk");
_profitThresholdPercent = Param(nameof(ProfitThresholdPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Profit Threshold %", "Activate trailing after profit", "Risk");
_trailingStopGapPercent = Param(nameof(TrailingStopGapPercent), 0.8m)
.SetGreaterThanZero()
.SetDisplay("Trailing Gap %", "Gap for trailing stop", "Risk");
_maxHoldBars = Param(nameof(MaxHoldBars), 25)
.SetGreaterThanZero()
.SetDisplay("Max Hold Bars", "Max bars to hold position", "Risk");
_cooldownBars = Param(nameof(CooldownBars), 3)
.SetGreaterThanZero()
.SetDisplay("Cooldown Bars", "Bars to wait after exit", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candles for calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highs.Clear();
_lows.Clear();
_prevHighest = 0m;
_prevLowest = 0m;
_entryPrice = 0m;
_trailingStopActive = false;
_highestSinceTrailing = 0m;
_lowestSinceTrailing = 0m;
_barsInPosition = 0;
_barsSinceExit = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ClosePosition()
{
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
_entryPrice = 0m;
_trailingStopActive = false;
_barsInPosition = 0;
_barsSinceExit = 0;
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
if (_highs.Count > LookbackPeriod + 1)
_highs.RemoveAt(0);
if (_lows.Count > LookbackPeriod + 1)
_lows.RemoveAt(0);
if (_highs.Count <= LookbackPeriod)
return;
// Compute highest/lowest from previous N candles (excluding current)
var highest = decimal.MinValue;
var lowest = decimal.MaxValue;
for (var i = 0; i < _highs.Count - 1; i++)
{
if (_highs[i] > highest) highest = _highs[i];
if (_lows[i] < lowest) lowest = _lows[i];
}
if (Position != 0)
{
_barsInPosition++;
// Handle stops
HandleStop(candle);
// Max hold exit
if (Position != 0 && _barsInPosition >= MaxHoldBars)
ClosePosition();
}
else
{
_barsSinceExit++;
// Breakout detection with cooldown
if (_barsSinceExit >= CooldownBars && _prevHighest > 0 && _prevLowest > 0)
{
if (candle.ClosePrice > _prevHighest)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
_trailingStopActive = false;
_barsInPosition = 0;
}
else if (candle.ClosePrice < _prevLowest)
{
SellMarket();
_entryPrice = candle.ClosePrice;
_trailingStopActive = false;
_barsInPosition = 0;
}
}
}
_prevHighest = highest;
_prevLowest = lowest;
}
private void HandleStop(ICandleMessage candle)
{
if (Position > 0 && _entryPrice > 0)
{
var profitPercent = (candle.ClosePrice - _entryPrice) / _entryPrice * 100m;
if (!_trailingStopActive && profitPercent >= ProfitThresholdPercent)
{
_trailingStopActive = true;
_highestSinceTrailing = candle.ClosePrice;
}
if (_trailingStopActive)
{
_highestSinceTrailing = Math.Max(_highestSinceTrailing, candle.ClosePrice);
var stop = _highestSinceTrailing * (1 - TrailingStopGapPercent / 100m);
if (candle.ClosePrice <= stop)
ClosePosition();
}
else
{
var stop = _entryPrice * (1 - StopLossPercent / 100m);
if (candle.ClosePrice <= stop)
ClosePosition();
}
}
else if (Position < 0 && _entryPrice > 0)
{
var profitPercent = (_entryPrice - candle.ClosePrice) / _entryPrice * 100m;
if (!_trailingStopActive && profitPercent >= ProfitThresholdPercent)
{
_trailingStopActive = true;
_lowestSinceTrailing = candle.ClosePrice;
}
if (_trailingStopActive)
{
_lowestSinceTrailing = Math.Min(_lowestSinceTrailing, candle.ClosePrice);
var stop = _lowestSinceTrailing * (1 + TrailingStopGapPercent / 100m);
if (candle.ClosePrice >= stop)
ClosePosition();
}
else
{
var stop = _entryPrice * (1 + StopLossPercent / 100m);
if (candle.ClosePrice >= stop)
ClosePosition();
}
}
}
}
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
from StockSharp.Algo.Strategies import Strategy
class breaks_and_retests_strategy(Strategy):
def __init__(self):
super(breaks_and_retests_strategy, self).__init__()
self._lookback_period = self.Param("LookbackPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Lookback Period", "Number of bars for support/resistance", "Levels")
self._stop_loss_percent = self.Param("StopLossPercent", 1.5) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss %", "Initial stop loss", "Risk")
self._profit_threshold_percent = self.Param("ProfitThresholdPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Profit Threshold %", "Activate trailing after profit", "Risk")
self._trailing_stop_gap_percent = self.Param("TrailingStopGapPercent", 0.8) \
.SetGreaterThanZero() \
.SetDisplay("Trailing Gap %", "Gap for trailing stop", "Risk")
self._max_hold_bars = self.Param("MaxHoldBars", 25) \
.SetGreaterThanZero() \
.SetDisplay("Max Hold Bars", "Max bars to hold position", "Risk")
self._cooldown_bars = self.Param("CooldownBars", 3) \
.SetGreaterThanZero() \
.SetDisplay("Cooldown Bars", "Bars to wait after exit", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Candles for calculations", "General")
self._highs = []
self._lows = []
self._prev_highest = 0.0
self._prev_lowest = 0.0
self._entry_price = 0.0
self._trailing_stop_active = False
self._highest_since_trailing = 0.0
self._lowest_since_trailing = 0.0
self._bars_in_position = 0
self._bars_since_exit = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(breaks_and_retests_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._prev_highest = 0.0
self._prev_lowest = 0.0
self._entry_price = 0.0
self._trailing_stop_active = False
self._highest_since_trailing = 0.0
self._lowest_since_trailing = 0.0
self._bars_in_position = 0
self._bars_since_exit = 0
def OnStarted2(self, time):
super(breaks_and_retests_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _close_position(self):
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._entry_price = 0.0
self._trailing_stop_active = False
self._bars_in_position = 0
self._bars_since_exit = 0
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
lookback = self._lookback_period.Value
self._highs.append(float(candle.HighPrice))
self._lows.append(float(candle.LowPrice))
if len(self._highs) > lookback + 1:
self._highs = self._highs[1:]
if len(self._lows) > lookback + 1:
self._lows = self._lows[1:]
if len(self._highs) <= lookback:
return
highest = max(self._highs[:-1])
lowest = min(self._lows[:-1])
close = float(candle.ClosePrice)
if self.Position != 0:
self._bars_in_position += 1
self._handle_stop(candle)
if self.Position != 0 and self._bars_in_position >= self._max_hold_bars.Value:
self._close_position()
else:
self._bars_since_exit += 1
if self._bars_since_exit >= self._cooldown_bars.Value and self._prev_highest > 0 and self._prev_lowest > 0:
if close > self._prev_highest:
self.BuyMarket()
self._entry_price = close
self._trailing_stop_active = False
self._bars_in_position = 0
elif close < self._prev_lowest:
self.SellMarket()
self._entry_price = close
self._trailing_stop_active = False
self._bars_in_position = 0
self._prev_highest = highest
self._prev_lowest = lowest
def _handle_stop(self, candle):
close = float(candle.ClosePrice)
sl_pct = float(self._stop_loss_percent.Value)
pt_pct = float(self._profit_threshold_percent.Value)
tg_pct = float(self._trailing_stop_gap_percent.Value)
if self.Position > 0 and self._entry_price > 0:
profit_pct = (close - self._entry_price) / self._entry_price * 100.0
if not self._trailing_stop_active and profit_pct >= pt_pct:
self._trailing_stop_active = True
self._highest_since_trailing = close
if self._trailing_stop_active:
if close > self._highest_since_trailing:
self._highest_since_trailing = close
stop = self._highest_since_trailing * (1.0 - tg_pct / 100.0)
if close <= stop:
self._close_position()
else:
stop = self._entry_price * (1.0 - sl_pct / 100.0)
if close <= stop:
self._close_position()
elif self.Position < 0 and self._entry_price > 0:
profit_pct = (self._entry_price - close) / self._entry_price * 100.0
if not self._trailing_stop_active and profit_pct >= pt_pct:
self._trailing_stop_active = True
self._lowest_since_trailing = close
if self._trailing_stop_active:
if close < self._lowest_since_trailing:
self._lowest_since_trailing = close
stop = self._lowest_since_trailing * (1.0 + tg_pct / 100.0)
if close >= stop:
self._close_position()
else:
stop = self._entry_price * (1.0 + sl_pct / 100.0)
if close >= stop:
self._close_position()
def CreateClone(self):
return breaks_and_retests_strategy()