Multi-Band Comparison 策略
Multi-Band Comparison 使用 SMA、标准差和价格分位数带。当价格连续 EntryConfirmBars 根 K 线收在上分位数减去标准差之上时做多;当价格连续 ExitConfirmBars 根 K 线收在该水平之下时平仓。
详情
- 数据:价格 K 线。
- 入场条件:
- 多头:收盘价高于(上分位数 - 标准差)并持续
EntryConfirmBars根 K 线。
- 多头:收盘价高于(上分位数 - 标准差)并持续
- 出场条件:收盘价低于该水平并持续
ExitConfirmBars根 K 线。 - 止损:无。
- 默认值:
Length= 20BollingerMultiplier= 1UpperQuantile= 0.95EntryConfirmBars= 1ExitConfirmBars= 1
- 过滤器:
- 类别:统计
- 方向:多头
- 指标:SMA, Standard Deviation
- 复杂度:中
- 风险等级:中
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Multi-band comparison strategy.
/// The trigger line is the UpperQuantile quantile of the last Length closes minus BollingerMultiplier standard deviations.
/// A long opens after EntryConfirmBars consecutive closes above the line and closes after ExitConfirmBars consecutive closes below it.
/// The SMA middle band is drawn for comparison. Long only, no stops.
/// </summary>
public class MultiBandComparisonStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<decimal> _bollingerMultiplier;
private readonly StrategyParam<decimal> _upperQuantile;
private readonly StrategyParam<int> _entryConfirmBars;
private readonly StrategyParam<int> _exitConfirmBars;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _closes = new();
private int _aboveCount;
private int _belowCount;
/// <summary>
/// Period of the SMA, standard deviation and quantile window.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.Value = value;
}
/// <summary>
/// Standard deviation multiplier.
/// </summary>
public decimal BollingerMultiplier
{
get => _bollingerMultiplier.Value;
set => _bollingerMultiplier.Value = value;
}
/// <summary>
/// Quantile of the closes that forms the upper band.
/// </summary>
public decimal UpperQuantile
{
get => _upperQuantile.Value;
set => _upperQuantile.Value = value;
}
/// <summary>
/// Consecutive closes above the line required to enter.
/// </summary>
public int EntryConfirmBars
{
get => _entryConfirmBars.Value;
set => _entryConfirmBars.Value = value;
}
/// <summary>
/// Consecutive closes below the line required to exit.
/// </summary>
public int ExitConfirmBars
{
get => _exitConfirmBars.Value;
set => _exitConfirmBars.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MultiBandComparisonStrategy()
{
_length = Param(nameof(Length), 20)
.SetGreaterThanZero()
.SetDisplay("Length", "Period of the SMA, standard deviation and quantile window", "Bands");
_bollingerMultiplier = Param(nameof(BollingerMultiplier), 1m)
.SetNotNegative()
.SetDisplay("BB Mult", "Standard deviation multiplier", "Bands");
_upperQuantile = Param(nameof(UpperQuantile), 0.95m)
.SetRange(0m, 1m)
.SetDisplay("Upper Quantile", "Quantile of the closes that forms the upper band", "Bands");
_entryConfirmBars = Param(nameof(EntryConfirmBars), 1)
.SetGreaterThanZero()
.SetDisplay("Entry Confirm Bars", "Consecutive closes above the line required to enter", "Trading");
_exitConfirmBars = Param(nameof(ExitConfirmBars), 1)
.SetGreaterThanZero()
.SetDisplay("Exit Confirm Bars", "Consecutive closes below the line required to exit", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_closes.Clear();
_aboveCount = 0;
_belowCount = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var sma = new SimpleMovingAverage { Length = Length };
var std = new StandardDeviation { Length = Length };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(sma, std, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private decimal GetQuantile()
{
var sorted = _closes.OrderBy(c => c).ToArray();
var position = UpperQuantile * (sorted.Length - 1);
var lower = (int)Math.Floor(position);
var upper = Math.Min(lower + 1, sorted.Length - 1);
var fraction = position - lower;
return sorted[lower] + (sorted[upper] - sorted[lower]) * fraction;
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue smaValue, IIndicatorValue stdValue)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
_closes.Enqueue(close);
if (_closes.Count > Length)
_closes.Dequeue();
if (!smaValue.IsFormed || !stdValue.IsFormed || _closes.Count < Length)
return;
var line = GetQuantile() - stdValue.ToDecimal() * BollingerMultiplier;
if (close > line)
{
_aboveCount++;
_belowCount = 0;
}
else if (close < line)
{
_belowCount++;
_aboveCount = 0;
}
else
{
_aboveCount = 0;
_belowCount = 0;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position <= 0 && _aboveCount >= EntryConfirmBars)
BuyMarket(Volume + Math.Abs(Position));
else if (Position > 0 && _belowCount >= ExitConfirmBars)
SellMarket(Position);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
class multi_band_comparison_strategy(Strategy):
"""
Multi-band comparison strategy.
The trigger line is the UpperQuantile quantile of the last Length closes minus BollingerMultiplier standard deviations.
A long opens after EntryConfirmBars consecutive closes above the line and closes after ExitConfirmBars consecutive closes below it.
The SMA middle band is drawn for comparison. Long only, no stops.
"""
def __init__(self):
super(multi_band_comparison_strategy, self).__init__()
self._length = self.Param("Length", 20).SetGreaterThanZero().SetDisplay("Length", "Period of the SMA, standard deviation and quantile window", "Bands")
self._bollinger_multiplier = self.Param("BollingerMultiplier", 1.0).SetNotNegative().SetDisplay("BB Mult", "Standard deviation multiplier", "Bands")
self._upper_quantile = self.Param("UpperQuantile", 0.95).SetDisplay("Upper Quantile", "Quantile of the closes that forms the upper band", "Bands")
self._entry_confirm_bars = self.Param("EntryConfirmBars", 1).SetGreaterThanZero().SetDisplay("Entry Confirm Bars", "Consecutive closes above the line required to enter", "Trading")
self._exit_confirm_bars = self.Param("ExitConfirmBars", 1).SetGreaterThanZero().SetDisplay("Exit Confirm Bars", "Consecutive closes below the line required to exit", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._closes = []
self._above_count = 0
self._below_count = 0
def OnReseted(self):
super(multi_band_comparison_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(multi_band_comparison_strategy, self).OnStarted2(time)
self._reset_state()
sma = SimpleMovingAverage()
sma.Length = self._length.Value
std = StandardDeviation()
std.Length = self._length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(sma, std, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _get_quantile(self):
ordered = sorted(self._closes)
position = float(self._upper_quantile.Value) * (len(ordered) - 1)
lower = int(math.floor(position))
upper = min(lower + 1, len(ordered) - 1)
fraction = position - lower
return ordered[lower] + (ordered[upper] - ordered[lower]) * fraction
def _process_candle(self, candle, sma_value, std_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
length = self._length.Value
self._closes.append(close)
if len(self._closes) > length:
self._closes.pop(0)
if not sma_value.IsFormed or not std_value.IsFormed or len(self._closes) < length:
return
std = float(std_value.GetValue[Decimal](None))
line = self._get_quantile() - std * float(self._bollinger_multiplier.Value)
if close > line:
self._above_count += 1
self._below_count = 0
elif close < line:
self._below_count += 1
self._above_count = 0
else:
self._above_count = 0
self._below_count = 0
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position <= 0 and self._above_count >= self._entry_confirm_bars.Value:
self.BuyMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and self._below_count >= self._exit_confirm_bars.Value:
self.SellMarket(self.Position)
def CreateClone(self):
return multi_band_comparison_strategy()