Стратегия Multi-Band Comparison
Multi-Band Comparison использует SMA, стандартное отклонение и квантильные уровни цены. Стратегия открывает лонг, когда цена закрывается выше верхнего квантиля минус стандартное отклонение заданное число баров, и закрывает позицию при обратном условии.
Детали
- Данные: Свечи цены.
- Условия входа:
- Лонг: Закрытие выше (верхний квантиль - стандартное отклонение) в течение
EntryConfirmBarsбаров.
- Лонг: Закрытие выше (верхний квантиль - стандартное отклонение) в течение
- Условия выхода: Закрытие ниже этой линии в течение
ExitConfirmBarsбаров. - Стопы: Нет.
- Значения по умолчанию:
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()