BBTrend SuperTrend Decision
Handelsregeln
Zwei Bollinger-Bänder werden aus abgeschlossenen Schlusskursen berechnet: 20 und 50 Kerzen, jeweils zwei Populationsstandardabweichungen. Mit unteren Bändern Ls/Ll, oberen Us/Ul und kurzem Mittelwert Ms gilt: BBTrend = 100 * (abs(Ls-Ll) - abs(Us-Ul)) / Ms.
SuperTrend arbeitet auf diesem Wert. Vorheriger/aktueller Wert bilden synthetische Eröffnung/Schluss und ihr Minimum/Maximum die Extreme. True Range ist die absolute Änderung; ATR beginnt mit zehn gemittelten Bereichen und wird danach nach Wilder geglättet. Bänder liegen am synthetischen Mittelpunkt mit Abstand 7 * ATR; bisherige Grenzen werden bis zum Durchbruch nachgezogen. Beide Bollinger und ATR müssen gebildet sein.
Aufwärtstrend setzt das Ziel +Volume, Abwärtstrend -Volume. Eine Order über die Differenz dreht die gesamte Position. TradeDirection = Buy/Sell beschränkt Einstiege; das Gegensignal schließt trotzdem die bestehende Position. null erlaubt beide Seiten. Offene Orders verhindern weitere Aufträge.
TpSlCondition wählt None, TP, SL, Both (Python 0/1/2/3), standardmäßig ohne Schutz. Aktivierte Abstände werden als Prozentwerte durch TakeProfitPerc und StopLossPerc festgelegt.
Der native lokale Schutz verwendet Kerzen und abonnierte Preisaktualisierungen des Instruments. Abstände beziehen sich auf tatsächliche Einstiegsausführungen. Ausstiege sind Marktorders; der Ausführungspreis hängt vom verfügbaren Markt und Preisraster ab.
Parameter
| Parameter | Standardwert |
|---|---|
ShortBbLength |
20 |
LongBbLength |
50 |
StdDev |
2 |
SupertrendLength |
10 |
SupertrendMultiplier |
7 |
TradeDirection |
null |
TpSlCondition |
None |
TakeProfitPerc |
30 |
StopLossPerc |
20 |
CandleType |
5m |
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>Two-window BBTrend with a formed Wilder SuperTrend and complete target-position reversals.</summary>
public class BbtrendSupertrendDecisionStrategy : Strategy
{
/// <summary>Optional native percentage protection components.</summary>
[Flags]
public enum TpSlModes
{
/// <summary>No percentage protection.</summary>
None = 0,
/// <summary>Take profit.</summary>
TP = 1 << 0,
/// <summary>Stop loss.</summary>
SL = 1 << 1,
/// <summary>Take profit and stop loss.</summary>
Both = TP | SL
}
private readonly StrategyParam<int> _shortBbLength;
private readonly StrategyParam<int> _longBbLength;
private readonly StrategyParam<decimal> _stdDev;
private readonly StrategyParam<int> _supertrendLength;
private readonly StrategyParam<decimal> _supertrendMultiplier;
private readonly StrategyParam<Sides?> _tradeDirection;
private readonly StrategyParam<TpSlModes> _tpSlCondition;
private readonly StrategyParam<decimal> _takeProfitPerc;
private readonly StrategyParam<decimal> _stopLossPerc;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _initialRanges = [];
private decimal? _previousBbTrend, _prevUp, _prevDn, _prevAtr, _prevSt;
private DateTime _lastTime;
private Order _pending;
/// <summary>Short Bollinger close window.</summary>
public int ShortBbLength
{
get => _shortBbLength.Value;
set => _shortBbLength.Value = value;
}
/// <summary>Long Bollinger close window.</summary>
public int LongBbLength
{
get => _longBbLength.Value;
set => _longBbLength.Value = value;
}
/// <summary>Population deviation multiplier.</summary>
public decimal StdDev
{
get => _stdDev.Value;
set => _stdDev.Value = value;
}
/// <summary>Wilder true range period on BBTrend.</summary>
public int SupertrendLength
{
get => _supertrendLength.Value;
set => _supertrendLength.Value = value;
}
/// <summary>SuperTrend band multiplier.</summary>
public decimal SupertrendMultiplier
{
get => _supertrendMultiplier.Value;
set => _supertrendMultiplier.Value = value;
}
/// <summary>Allowed entry side; null allows both.</summary>
public Sides? TradeDirection
{
get => _tradeDirection.Value;
set => _tradeDirection.Value = value;
}
/// <summary>Optional percentage protection mode.</summary>
public TpSlModes TpSlCondition
{
get => _tpSlCondition.Value;
set => _tpSlCondition.Value = value;
}
/// <summary>Optional profit distance in percent.</summary>
public decimal TakeProfitPerc
{
get => _takeProfitPerc.Value;
set => _takeProfitPerc.Value = value;
}
/// <summary>Optional loss distance in percent.</summary>
public decimal StopLossPerc
{
get => _stopLossPerc.Value;
set => _stopLossPerc.Value = value;
}
/// <summary>Finished signal candle interval.</summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>Initializes the published BBTrend, SuperTrend and optional protection parameters.</summary>
public BbtrendSupertrendDecisionStrategy()
{
_shortBbLength = Param(nameof(ShortBbLength), 20).SetGreaterThanZero().SetOptimize(10, 40, 5)
.SetDisplay("ShortBbLength", "Short Bollinger close window", "Parameters");
_longBbLength = Param(nameof(LongBbLength), 50).SetGreaterThanZero().SetOptimize(30, 100, 5)
.SetDisplay("LongBbLength", "Long Bollinger close window", "Parameters");
_stdDev = Param(nameof(StdDev), 2m).SetGreaterThanZero()
.SetDisplay("StdDev", "Population deviation multiplier", "Parameters");
_supertrendLength = Param(nameof(SupertrendLength), 10).SetGreaterThanZero().SetOptimize(5, 20, 1)
.SetDisplay("SupertrendLength", "Wilder true range period on BBTrend", "Parameters");
_supertrendMultiplier = Param(nameof(SupertrendMultiplier), 7m).SetGreaterThanZero().SetOptimize(1m, 10m, 1m)
.SetDisplay("SupertrendMultiplier", "SuperTrend band multiplier", "Parameters");
_tradeDirection = Param(nameof(TradeDirection), (Sides?)null)
.SetDisplay("TradeDirection", "Allowed entry side; null allows both", "Parameters");
_tpSlCondition = Param(nameof(TpSlCondition), TpSlModes.None)
.SetDisplay("TpSlCondition", "Optional percentage protection mode", "Parameters");
_takeProfitPerc = Param(nameof(TakeProfitPerc), 30m).SetGreaterThanZero()
.SetDisplay("TakeProfitPerc", "Optional profit distance in percent", "Parameters");
_stopLossPerc = Param(nameof(StopLossPerc), 20m).SetGreaterThanZero()
.SetDisplay("StopLossPerc", "Optional loss distance in percent", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("CandleType", "Finished signal candle interval", "Parameters");
OrderRegistering += order => _pending = order;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_initialRanges.Clear();
_previousBbTrend = _prevUp = _prevDn = _prevAtr = _prevSt = null;
_lastTime = default;
_pending = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
if (ShortBbLength >= LongBbLength || (TpSlCondition & ~TpSlModes.Both) != TpSlModes.None)
throw new InvalidOperationException("The short BB window must be smaller than the long window and the risk mode must be valid.");
base.OnStarted2(time);
StartProtection(TpSlCondition.HasFlag(TpSlModes.TP) ? TakeProfitPerc.Percents() : new Unit(),
TpSlCondition.HasFlag(TpSlModes.SL) ? StopLossPerc.Percents() : new Unit(), useMarketOrders: true, isLocalStop: true);
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quote = new Subscription(DataType.Level1, Security);
quote.MarketData.BuildField = field;
SubscribeLevel1(quote).Start();
}
var shortBb = new BollingerBands { Length = ShortBbLength, Width = StdDev };
var longBb = new BollingerBands { Length = LongBbLength, Width = StdDev };
var candles = SubscribeCandles(CandleType);
candles.BindEx([shortBb, longBb], ProcessCandle).Start();
var area = CreateChartArea();
if (area is not null)
{
DrawCandles(area, candles);
DrawIndicator(area, shortBb);
DrawIndicator(area, longBb);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
{
if (candle.State != CandleStates.Finished || candle.OpenTime <= _lastTime || !values.All(v => v.IsFormed)
|| values[0] is not BollingerBandsValue a || values[1] is not BollingerBandsValue b
|| a.LowBand is not decimal shortLow || a.UpBand is not decimal shortHigh || a.MovingAverage is not decimal middle
|| b.LowBand is not decimal longLow || b.UpBand is not decimal longHigh || middle <= 0m)
return;
_lastTime = candle.OpenTime;
var score = (Math.Abs(shortLow - longLow) - Math.Abs(shortHigh - longHigh)) / middle * 100m;
var previous = _previousBbTrend;
_previousBbTrend = score;
if (previous is null) return;
var range = Math.Abs(score - previous.Value);
if (_prevAtr is null)
{
_initialRanges.Enqueue(range);
if (_initialRanges.Count < SupertrendLength) return;
_prevAtr = _initialRanges.Average();
}
else _prevAtr = (_prevAtr.Value * (SupertrendLength - 1) + range) / SupertrendLength;
var upper = (score + previous.Value) / 2m + SupertrendMultiplier * _prevAtr.Value;
var lower = (score + previous.Value) / 2m - SupertrendMultiplier * _prevAtr.Value;
if (_prevUp is decimal u && upper >= u && previous <= u) upper = u;
if (_prevDn is decimal d && lower <= d && previous >= d) lower = d;
var down = _prevSt is null || (_prevSt == _prevUp ? score <= upper : score < lower);
_prevUp = upper;
_prevDn = lower;
_prevSt = down ? upper : lower;
if (!IsFormedAndOnlineAndAllowTrading() || _pending is not null && _pending.State is not (OrderStates.Done or OrderStates.Failed))
return;
var side = down ? Sides.Sell : Sides.Buy;
var target = TradeDirection is null || TradeDirection == side ? side == Sides.Buy ? Volume : -Volume : 0m;
var delta = target - Position;
if (delta > 0m) BuyMarket(delta);
else if (delta < 0m) SellMarket(-delta);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
import math
from collections import deque
from System import TimeSpan, DateTime, DateTimeKind, Decimal, Nullable
from StockSharp.Messages import DataType, CandleStates, Sides, OrderStates, Unit, UnitTypes
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
from enum import IntEnum
from System import Array
from StockSharp.Messages import Level1Fields
from StockSharp.Algo.Indicators import BollingerBands, IIndicator
class TpSlModes(IntEnum):
None_ = 0
TP = 1 << 0
SL = 1 << 1
Both = TP | SL
class bbtrend_supertrend_decision_strategy(Strategy):
def __init__(self):
super(bbtrend_supertrend_decision_strategy, self).__init__()
self._short_bb_length = self.Param("ShortBbLength", 20).SetGreaterThanZero().SetDisplay(
"ShortBbLength", "Short Bollinger close window", "Parameters")
self._long_bb_length = self.Param("LongBbLength", 50).SetGreaterThanZero().SetDisplay(
"LongBbLength", "Long Bollinger close window", "Parameters")
self._std_dev = self.Param("StdDev", 2.0).SetGreaterThanZero().SetDisplay(
"StdDev", "Population deviation multiplier", "Parameters")
self._supertrend_length = self.Param("SupertrendLength", 10).SetGreaterThanZero().SetDisplay(
"SupertrendLength", "Wilder true range period on BBTrend", "Parameters")
self._supertrend_multiplier = self.Param("SupertrendMultiplier", 7.0).SetGreaterThanZero().SetDisplay(
"SupertrendMultiplier", "SuperTrend band multiplier", "Parameters")
self._trade_direction = self.Param[Nullable[Sides]]("TradeDirection", Nullable[Sides]()).SetDisplay(
"TradeDirection", "Allowed entry side; null allows both", "Parameters")
self._tp_sl_condition = self.Param("TpSlCondition", int(TpSlModes.None_)).SetDisplay(
"TpSlCondition", "Optional percentage protection mode", "Parameters")
self._take_profit_perc = self.Param("TakeProfitPerc", 30.0).SetGreaterThanZero().SetDisplay(
"TakeProfitPerc", "Optional profit distance in percent", "Parameters")
self._stop_loss_perc = self.Param("StopLossPerc", 20.0).SetGreaterThanZero().SetDisplay(
"StopLossPerc", "Optional loss distance in percent", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay(
"CandleType", "Finished signal candle interval", "Parameters")
self._reset_state()
self.OrderRegistering += self._remember_order
@property
def ShortBbLength(self):
return self._short_bb_length.Value
@ShortBbLength.setter
def ShortBbLength(self, value):
self._short_bb_length.Value = value
@property
def LongBbLength(self):
return self._long_bb_length.Value
@LongBbLength.setter
def LongBbLength(self, value):
self._long_bb_length.Value = value
@property
def StdDev(self):
return self._std_dev.Value
@StdDev.setter
def StdDev(self, value):
self._std_dev.Value = value
@property
def SupertrendLength(self):
return self._supertrend_length.Value
@SupertrendLength.setter
def SupertrendLength(self, value):
self._supertrend_length.Value = value
@property
def SupertrendMultiplier(self):
return self._supertrend_multiplier.Value
@SupertrendMultiplier.setter
def SupertrendMultiplier(self, value):
self._supertrend_multiplier.Value = value
@property
def TradeDirection(self):
return self._trade_direction.Value
@TradeDirection.setter
def TradeDirection(self, value):
self._trade_direction.Value = value
@property
def TpSlCondition(self):
return self._tp_sl_condition.Value
@TpSlCondition.setter
def TpSlCondition(self, value):
self._tp_sl_condition.Value = value
@property
def TakeProfitPerc(self):
return self._take_profit_perc.Value
@TakeProfitPerc.setter
def TakeProfitPerc(self, value):
self._take_profit_perc.Value = value
@property
def StopLossPerc(self):
return self._stop_loss_perc.Value
@StopLossPerc.setter
def StopLossPerc(self, value):
self._stop_loss_perc.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _reset_state(self):
self._ranges = []
self._previous = self._up = self._down = self._atr = self._line = None
self._last_time = DateTime.MinValue
self._pending = None
def _remember_order(self, order):
self._pending = order
def OnReseted(self):
super(bbtrend_supertrend_decision_strategy, self).OnReseted()
self._reset_state()
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType), (self.Security, DataType.Level1)]
def OnStarted2(self, time):
mode = TpSlModes(int(self.TpSlCondition))
if self.ShortBbLength >= self.LongBbLength:
raise ValueError("The short BB window must be smaller than the long window.")
super(bbtrend_supertrend_decision_strategy, self).OnStarted2(time)
self.StartProtection(Unit(float(self.TakeProfitPerc), UnitTypes.Percent) if mode in (TpSlModes.TP, TpSlModes.Both) else Unit(),
Unit(float(self.StopLossPerc), UnitTypes.Percent) if mode in (TpSlModes.SL, TpSlModes.Both) else Unit(),
useMarketOrders=True, isLocalStop=True)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quote = Subscription(DataType.Level1, self.Security)
quote.MarketData.BuildField = field
self.SubscribeLevel1(quote).Start()
short_bb = BollingerBands()
short_bb.Length, short_bb.Width = self.ShortBbLength, self.StdDev
long_bb = BollingerBands()
long_bb.Length, long_bb.Width = self.LongBbLength, self.StdDev
candles = self.SubscribeCandles(self.CandleType)
candles.BindEx(Array[IIndicator]([short_bb, long_bb]), self._process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, candles)
self.DrawIndicator(area, short_bb)
self.DrawIndicator(area, long_bb)
self.DrawOwnTrades(area)
def _process(self, candle, values):
if candle.State != CandleStates.Finished or candle.OpenTime <= self._last_time or not all(v.IsFormed for v in values):
return
a, b = values[0], values[1]
if a.MovingAverage is None or a.LowBand is None or a.UpBand is None or b.LowBand is None or b.UpBand is None or a.MovingAverage <= 0:
return
self._last_time = candle.OpenTime
score = (abs(float(a.LowBand) - float(b.LowBand)) - abs(float(a.UpBand) - float(b.UpBand))) / float(a.MovingAverage) * 100.0
previous = self._previous
self._previous = score
if previous is None:
return
change = abs(score - previous)
if self._atr is None:
self._ranges.append(change)
if len(self._ranges) < self.SupertrendLength:
return
self._atr = sum(self._ranges) / self.SupertrendLength
else:
self._atr = (self._atr * (self.SupertrendLength - 1) + change) / self.SupertrendLength
upper = (score + previous) / 2.0 + float(self.SupertrendMultiplier) * self._atr
lower = (score + previous) / 2.0 - float(self.SupertrendMultiplier) * self._atr
if self._up is not None and upper >= self._up and previous <= self._up:
upper = self._up
if self._down is not None and lower <= self._down and previous >= self._down:
lower = self._down
down = self._line is None or (score <= upper if self._line == self._up else score < lower)
self._up, self._down, self._line = upper, lower, upper if down else lower
if not self.IsFormedAndOnlineAndAllowTrading() or (self._pending is not None and self._pending.State not in (OrderStates.Done, OrderStates.Failed)):
return
side = Sides.Sell if down else Sides.Buy
allowed = self.TradeDirection is None or self.TradeDirection == side
target = (self.Volume if side == Sides.Buy else -self.Volume) if allowed else Decimal(0)
delta = target - self.Position
if delta > 0:
self.BuyMarket(delta)
elif delta < 0:
self.SellMarket(-delta)
def CreateClone(self):
return bbtrend_supertrend_decision_strategy()