BBTrend SuperTrend Decision
交易规则
在已完成的收盘价上计算两组布林带:20 与 50 根,宽度为两倍总体标准差。短/长下轨为 Ls/Ll、上轨为 Us/Ul、短期均值为 Ms 时,BBTrend = 100 * (abs(Ls-Ll) - abs(Us-Ul)) / Ms。
对 BBTrend 应用 SuperTrend。前值与当前值分别作为合成开盘和收盘,两者的最小/最大值作为低/高。真实波幅为分数的绝对变化;ATR 先取十个波幅的平均,再用 Wilder 平滑。轨道为合成中点加减 7 * ATR,未突破时沿用跟踪边界。两组布林带与 ATR 全部形成后才能交易。
上升趋势目标为 +Volume,下降趋势为 -Volume,按目标与当前仓位的差额下单以完整反转。TradeDirection = Buy/Sell 限定开仓方向,反向信号仍平掉已有仓位;null 允许双向。活动订单期间不重复提交。
TpSlCondition 可选 None、TP、SL、Both(Python 为 0/1/2/3),默认关闭。启用的止盈和止损分别使用 TakeProfitPerc、StopLossPerc 百分比,而非绝对价差。
本地原生保护使用该品种的蜡烛和已订阅价格更新,距离以实际入场成交为基准。退出为市价订单,成交价受可用市场及价格步长影响。
参数
| 参数 | 默认值 |
|---|---|
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()