BBTrend SuperTrend Decision
Regras de negociação
Duas bandas de Bollinger são calculadas sobre fechamentos concluídos: 20 e 50 barras, com duas vezes o desvio padrão populacional. Para bandas inferiores Ls/Ll, superiores Us/Ul e média curta Ms: BBTrend = 100 * (abs(Ls-Ll) - abs(Us-Ul)) / Ms.
SuperTrend opera sobre esse indicador. Os valores anterior/atual formam abertura/fechamento sintéticos e seu mínimo/máximo define os extremos. O true range é a variação absoluta; ATR começa com a média de dez amplitudes e depois usa Wilder. As bandas partem do ponto médio com distância 7 * ATR, mantendo limites anteriores até o rompimento. Ambas as bandas e ATR precisam estar formados.
Tendência de alta visa +Volume; baixa visa -Volume. A ordem pela diferença reverte a posição inteira. TradeDirection = Buy/Sell limita entradas, mas o sinal contrário continua fechando a posição existente; null permite ambos. Uma ordem pendente impede nova submissão.
TpSlCondition seleciona None, TP, SL ou Both (Python 0/1/2/3), desativado por padrão. Distâncias ativadas são percentuais definidos por TakeProfitPerc e StopLossPerc.
A proteção local nativa recebe velas e atualizações de preço assinadas do instrumento. As distâncias partem das execuções reais de entrada. As saídas são a mercado; o preço depende do mercado disponível e do passo de preço.
Parâmetros
| Parâmetro | Padrão |
|---|---|
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()