BBTrend SuperTrend Decision
Reglas de negociación
Se calculan dos bandas de Bollinger sobre cierres terminados: 20 y 50 barras, con dos desviaciones estándar poblacionales. Para bandas inferiores Ls/Ll, superiores Us/Ul y media corta Ms: BBTrend = 100 * (abs(Ls-Ll) - abs(Us-Ul)) / Ms.
SuperTrend se aplica al indicador. Los valores anterior y actual forman apertura/cierre sintéticos y su mínimo/máximo forma los extremos. El rango verdadero es el cambio absoluto; ATR comienza con diez rangos y sigue con suavizado Wilder. Las bandas parten del punto medio y 7 * ATR, conservando sus límites anteriores hasta la ruptura. Se espera la formación de ambos Bollinger y ATR.
SuperTrend alcista busca +Volume y bajista -Volume. La orden por la diferencia revierte toda la posición. TradeDirection = Buy/Sell limita nuevas entradas, pero la señal contraria todavía cierra la posición existente; null permite ambas direcciones. Una orden pendiente bloquea otra.
TpSlCondition selecciona None, TP, SL o Both (Python 0/1/2/3), desactivado por defecto. Las distancias habilitadas son porcentajes definidos por TakeProfitPerc y StopLossPerc.
La protección local nativa usa velas y actualizaciones de precio suscritas del instrumento. Las distancias parten de los precios ejecutados. Las salidas son a mercado y dependen del mercado disponible y del paso de precio.
Parámetros
| Parámetro | Predeterminado |
|---|---|
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()