Pivot Percentile Trend Strategy
This strategy computes percentile-based trend strength across multiple lookbacks and combines it with a SuperTrend filter. Long trades occur when trend strength is positive and price is above the SuperTrend line. Short trades are taken when trend strength is negative and price is below the SuperTrend.
Details
- Indicators: percentile analysis, SuperTrend
- Direction: Both
- Timeframe: Any
Calculation used by this sample
Seven windows of PercentileLength * 1..7 use nearest-rank high/low quartiles (75%/25%). A window votes +1 when the current high is strictly above its upper quartile and -1 when the current low is strictly below its lower quartile. The current candle belongs to each window.
The sum of all votes is the trend strength. A positive strength with the close above the price SuperTrend enters long; a negative strength with the close below it enters short. An opposing position is fully reversed to Volume. Neutral strength or a rejected filter keeps the current position. All windows must be formed; there is no cooldown or protective stop.
Defaults: PercentileLength=40, AtrPeriod=10, AtrMultiplier=3, CandleType=5m. SuperTrend uses the platform's Wilder ATR and price bands.
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()