Pivot Percentile Trend戦略
この戦略は複数のルックバック期間にわたるパーセンタイルベースのトレンド強度を計算し、SuperTrendフィルターと組み合わせます。トレンド強度が正でかつ価格がSuperTrend線の上にある場合にロング取引を行います。トレンド強度が負でかつ価格がSuperTrendの下にある場合にショート取引を行います。
詳細
- インジケーター: パーセンタイル分析、SuperTrend
- 方向: 両方
- 時間軸: 任意
このサンプルの計算方法
7つの窓 PercentileLength * 1..7 で High/Low の四分位数(75%/25%、nearest rank)を計算します。現在の High が上側四分位数を厳密に上回る場合は +1、Low が下側四分位数を厳密に下回る場合は -1 とします。現在の足も各窓に含めます。
合計がトレンド強度です。強度が正で Close が価格 SuperTrend より上ならロング、負で Close が下ならショートです。反対のポジションは完全に反転して Volume にします。中立またはフィルター不成立ではポジションを維持します。全窓の形成を待ち、シグナルの待機時間や保護ストップは設けません。
初期値:PercentileLength=40、AtrPeriod=10、AtrMultiplier=3、CandleType=5m。SuperTrend はプラットフォームの Wilder ATR と価格バンドを使います。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Trades the votes of seven high/low percentile windows, confirmed by the price Supertrend.
/// </summary>
public class PivotPercentileTrendStrategy : Strategy
{
private const int _windows = 7;
private readonly StrategyParam<int> _percentileLength;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<(decimal High, decimal Low)> _candles = new();
/// <summary>
/// Initializes the percentile and Supertrend settings.
/// </summary>
public PivotPercentileTrendStrategy()
{
_percentileLength = Param(nameof(PercentileLength), 40)
.SetGreaterThanZero()
.SetDisplay("Percentile Length", "Base window; the longest contains seven times as many candles", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Wilder ATR period of the price Supertrend", "Indicators");
_atrMultiplier = Param(nameof(AtrMultiplier), 3m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "ATR width of the price Supertrend", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles used for calculation", "General");
}
/// <summary>
/// Base lookback; the seven windows contain one through seven times this many candles.
/// </summary>
public int PercentileLength
{
get => _percentileLength.Value;
set => _percentileLength.Value = value;
}
/// <summary>
/// Period of the Wilder ATR used by Supertrend.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Positive multiplier of the Supertrend ATR.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candle type used for signals.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
private int MaximumHistory => checked(PercentileLength * _windows);
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_candles.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_candles.Clear();
var supertrend = new SuperTrend { Length = AtrPeriod, Multiplier = AtrMultiplier };
var subscription = SubscribeCandles(CandleType);
subscription
.BindWithEmpty(supertrend, (candle, line) =>
{
StoreCandle(candle);
if (line is decimal value)
ProcessCandle(candle, value);
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, supertrend);
DrawOwnTrades(area);
}
}
private void StoreCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_candles.Enqueue((candle.HighPrice, candle.LowPrice));
if (_candles.Count > MaximumHistory)
_candles.Dequeue();
}
private void ProcessCandle(ICandleMessage candle, decimal supertrend)
{
if (candle.State != CandleStates.Finished || _candles.Count < MaximumHistory)
return;
var history = _candles.ToArray();
var strength = 0;
for (var window = 1; window <= _windows; window++)
{
var period = PercentileLength * window;
var recent = history.AsSpan(history.Length - period, period);
var highs = new decimal[period];
var lows = new decimal[period];
for (var index = 0; index < period; index++)
{
highs[index] = recent[index].High;
lows[index] = recent[index].Low;
}
Array.Sort(highs);
Array.Sort(lows);
var upper = highs[(int)((3L * period + 3) / 4) - 1];
var lower = lows[(int)(((long)period + 3) / 4) - 1];
if (candle.HighPrice > upper)
strength++;
if (candle.LowPrice < lower)
strength--;
}
if (strength > 0 && candle.ClosePrice > supertrend && Position <= 0m)
BuyMarket(Volume + Math.Abs(Position));
else if (strength < 0 && candle.ClosePrice < supertrend && Position >= 0m)
SellMarket(Volume + Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import Array, Decimal, TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SuperTrend
from StockSharp.Algo.Strategies import Strategy
class pivot_percentile_trend_strategy(Strategy):
def __init__(self):
super(pivot_percentile_trend_strategy, self).__init__()
self._percentile_length = self.Param("PercentileLength", 40).SetGreaterThanZero() \
.SetDisplay("Percentile Length", "Base window; the longest contains seven times as many candles", "Indicators")
self._atr_period = self.Param("AtrPeriod", 10).SetGreaterThanZero() \
.SetDisplay("ATR Period", "Wilder ATR period of the price Supertrend", "Indicators")
self._atr_multiplier = self.Param("AtrMultiplier", Decimal.Parse("3")).SetGreaterThanZero() \
.SetDisplay("ATR Multiplier", "ATR width of the price Supertrend", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles used for calculation", "General")
self._history = []
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(pivot_percentile_trend_strategy, self).OnReseted()
self._history = []
def OnStarted2(self, time):
super(pivot_percentile_trend_strategy, self).OnStarted2(time)
self._history = []
self._supertrend = SuperTrend()
self._supertrend.Length = self._atr_period.Value
self._supertrend.Multiplier = self._atr_multiplier.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindWithEmpty(self._supertrend, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._supertrend)
self.DrawOwnTrades(area)
def OnProcess(self, candle, supertrend):
if candle.State != CandleStates.Finished:
return
self._history.append((candle.HighPrice, candle.LowPrice))
maximum_history = self._percentile_length.Value * 7
if len(self._history) > maximum_history:
self._history.pop(0)
if supertrend is None or len(self._history) < maximum_history:
return
strength = 0
for window in range(1, 8):
period = self._percentile_length.Value * window
recent = self._history[-period:]
highs = Array[Decimal]([bar[0] for bar in recent])
lows = Array[Decimal]([bar[1] for bar in recent])
Array.Sort[Decimal](highs)
Array.Sort[Decimal](lows)
upper = highs[(3 * period + 3) // 4 - 1]
lower = lows[(period + 3) // 4 - 1]
if candle.HighPrice > upper:
strength += 1
if candle.LowPrice < lower:
strength -= 1
if strength > 0 and candle.ClosePrice > supertrend and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif strength < 0 and candle.ClosePrice < supertrend and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return pivot_percentile_trend_strategy()