Doppelboden-Muster (Double Bottom Pattern)
Diese musterbasierte Strategie sucht nach zwei aufeinanderfolgenden Tiefs auf annähernd dem gleichen Preisniveau, die durch einen festgelegten Abstand voneinander getrennt sind. Nach der Ausbildung des zweiten Bodens bestätigt eine bullische Kerze die Umkehr.
Tests zeigen eine durchschnittliche jährliche Rendite von etwa 55 %. Die Strategie eignet sich am besten für den Aktienmarkt.
Bei der Bestätigung kauft das System mit einem Stop unterhalb der Musttiefs. Das Setup zielt darauf ab, scharfe Erholungen nach erschöpftem Verkaufsdruck zu erfassen.
Ausstiege basieren auf einem vordefinierten Stop-Loss oder manuellen Gewinnzielen.
Details
- Einstiegskriterien: Zwei Böden bilden sich innerhalb von
SimilarityPercentnachDistanceKerzen. - Long/Short: Nur Long.
- Ausstiegskriterien: Kurs bricht ein oder Stop-Loss.
- Stops: Ja.
- Standardwerte:
Distance= 5SimilarityPercent= 2.0mCandleType= TimeSpan.FromMinutes(15)StopLossPercent= 1.0m
- Filter:
- Kategorie: Muster
- Richtung: Nur Long
- Indikatoren: Price Action
- Stops: Ja
- Komplexität: Mittel
- Zeitrahmen: Intraday
- 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>
/// Double Bottom reversal strategy.
/// Detects two similar bottoms and enters long on confirmation.
/// Uses SMA for exit signal.
/// </summary>
public class DoubleBottomStrategy : Strategy
{
private readonly StrategyParam<int> _distanceParam;
private readonly StrategyParam<decimal> _similarityPercent;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private decimal _recentLow;
private decimal _prevLow;
private int _barsSinceLow;
private int _cooldown;
/// <summary>
/// Distance between bottoms in bars.
/// </summary>
public int Distance
{
get => _distanceParam.Value;
set => _distanceParam.Value = value;
}
/// <summary>
/// Maximum percent difference between two bottoms.
/// </summary>
public decimal SimilarityPercent
{
get => _similarityPercent.Value;
set => _similarityPercent.Value = value;
}
/// <summary>
/// MA Period for exit.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Type of candles to use.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="DoubleBottomStrategy"/>.
/// </summary>
public DoubleBottomStrategy()
{
_distanceParam = Param(nameof(Distance), 20)
.SetRange(3, 100)
.SetDisplay("Distance", "Bars between bottoms", "Pattern");
_similarityPercent = Param(nameof(SimilarityPercent), 1.0m)
.SetRange(0.1m, 5.0m)
.SetDisplay("Similarity %", "Max % diff between bottoms", "Pattern");
_maPeriod = Param(nameof(MAPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period for exit SMA", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_recentLow = default;
_prevLow = default;
_barsSinceLow = default;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_recentLow = 0;
_prevLow = 0;
_barsSinceLow = 0;
_cooldown = 0;
var sma = new SimpleMovingAverage { Length = MAPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldown > 0)
{
_cooldown--;
TrackLows(candle);
return;
}
// Track new lows
if (_recentLow == 0 || candle.LowPrice < _recentLow)
{
if (_recentLow > 0)
_prevLow = _recentLow;
_recentLow = candle.LowPrice;
_barsSinceLow = 0;
}
else
{
_barsSinceLow++;
}
if (Position == 0 && _prevLow > 0 && _barsSinceLow >= Distance)
{
var priceDiff = Math.Abs((_recentLow - _prevLow) / _prevLow * 100);
if (priceDiff <= SimilarityPercent && candle.ClosePrice > smaValue)
{
BuyMarket();
_cooldown = CooldownBars;
_recentLow = 0;
_prevLow = 0;
}
else if (priceDiff <= SimilarityPercent && candle.ClosePrice < smaValue)
{
SellMarket();
_cooldown = CooldownBars;
_recentLow = 0;
_prevLow = 0;
}
}
else if (Position > 0 && candle.ClosePrice < smaValue)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && candle.ClosePrice > smaValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
private void TrackLows(ICandleMessage candle)
{
if (_recentLow == 0 || candle.LowPrice < _recentLow)
{
if (_recentLow > 0)
_prevLow = _recentLow;
_recentLow = candle.LowPrice;
_barsSinceLow = 0;
}
else
{
_barsSinceLow++;
}
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class double_bottom_strategy(Strategy):
"""
Double Bottom reversal strategy.
Detects two similar bottoms and enters long on confirmation.
Uses SMA for exit signal.
"""
def __init__(self):
super(double_bottom_strategy, self).__init__()
self._distance = self.Param("Distance", 20).SetDisplay("Distance", "Bars between bottoms", "Pattern")
self._similarity_pct = self.Param("SimilarityPercent", 1.0).SetDisplay("Similarity %", "Max % diff between bottoms", "Pattern")
self._ma_period = self.Param("MAPeriod", 20).SetDisplay("MA Period", "Period for exit SMA", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._recent_low = 0.0
self._prev_low = 0.0
self._bars_since_low = 0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(double_bottom_strategy, self).OnReseted()
self._recent_low = 0.0
self._prev_low = 0.0
self._bars_since_low = 0
self._cooldown = 0
def OnStarted2(self, time):
super(double_bottom_strategy, self).OnStarted2(time)
self._recent_low = 0.0
self._prev_low = 0.0
self._bars_since_low = 0
self._cooldown = 0
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _track_lows(self, candle):
low = float(candle.LowPrice)
if self._recent_low == 0 or low < self._recent_low:
if self._recent_low > 0:
self._prev_low = self._recent_low
self._recent_low = low
self._bars_since_low = 0
else:
self._bars_since_low += 1
def _process_candle(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
if self._cooldown > 0:
self._cooldown -= 1
self._track_lows(candle)
return
# Track new lows
low = float(candle.LowPrice)
if self._recent_low == 0 or low < self._recent_low:
if self._recent_low > 0:
self._prev_low = self._recent_low
self._recent_low = low
self._bars_since_low = 0
else:
self._bars_since_low += 1
close = float(candle.ClosePrice)
sv = float(sma_val)
cd = self._cooldown_bars.Value
dist = self._distance.Value
sim = float(self._similarity_pct.Value)
if self.Position == 0 and self._prev_low > 0 and self._bars_since_low >= dist:
price_diff = abs((self._recent_low - self._prev_low) / self._prev_low * 100.0)
if price_diff <= sim and close > sv:
self.BuyMarket()
self._cooldown = cd
self._recent_low = 0.0
self._prev_low = 0.0
elif price_diff <= sim and close < sv:
self.SellMarket()
self._cooldown = cd
self._recent_low = 0.0
self._prev_low = 0.0
elif self.Position > 0 and close < sv:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and close > sv:
self.BuyMarket()
self._cooldown = cd
def CreateClone(self):
return double_bottom_strategy()