マルチバンド比較
マルチバンド比較は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()