Pivot Point Reversal Strategy
Daily pivot points and their support and resistance levels often act as turning points for intraday price action. This strategy calculates the classic floor-trader pivots from the prior day's high, low and close, then looks for candles bouncing off S1 or R1.
Testing indicates an average annual return of about 127%. It performs best in the stocks market.
When price approaches support level S1 and forms a bullish candle, a long entry is taken. If price tests resistance level R1 and prints a bearish candle, a short is opened. Trades exit upon reaching the central pivot or if the protective stop is hit.
The method resets at the start of each trading day with new pivot calculations, making it well suited for sessions with clear intraday ranges.
Details
- Entry Criteria: Bullish candle near S1 or bearish candle near R1.
- Long/Short: Both.
- Exit Criteria: Price crossing the central pivot or stop-loss.
- Stops: Yes, percentage based.
- Default Values:
CandleType= 5 minuteStopLossPercent= 2
- Filters:
- Category: Mean Reversion
- Direction: Both
- Indicators: Pivot Points
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: Yes
- Neural networks: No
- Divergence: No
- Risk level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Pivot Point Reversal strategy.
/// Calculates pivot points from a rolling window of highs, lows, closes.
/// P = (H + L + C) / 3, S1 = 2*P - H, R1 = 2*P - L
/// Buys on bounce off S1, sells on bounce off R1, exits at pivot.
/// </summary>
public class PivotPointReversalStrategy : Strategy
{
private readonly StrategyParam<int> _lookback;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private readonly List<decimal> _closes = new();
private int _cooldown;
/// <summary>
/// Lookback period for pivot calculation.
/// </summary>
public int Lookback
{
get => _lookback.Value;
set => _lookback.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public PivotPointReversalStrategy()
{
_lookback = Param(nameof(Lookback), 60)
.SetGreaterThanZero()
.SetDisplay("Lookback", "Lookback for pivot calc", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highs.Clear();
_lows.Clear();
_closes.Clear();
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highs.Clear();
_lows.Clear();
_closes.Clear();
_cooldown = 0;
var sma = new SimpleMovingAverage { Length = 20 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
_closes.Add(candle.ClosePrice);
if (_highs.Count > Lookback)
{
_highs.RemoveAt(0);
_lows.RemoveAt(0);
_closes.RemoveAt(0);
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_highs.Count < Lookback)
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Calculate pivot points from lookback window
decimal high = decimal.MinValue, low = decimal.MaxValue, close = 0;
for (int i = 0; i < _highs.Count; i++)
{
if (_highs[i] > high) high = _highs[i];
if (_lows[i] < low) low = _lows[i];
}
close = _closes[_closes.Count - 1];
var pivot = (high + low + close) / 3;
var r1 = 2 * pivot - low;
var s1 = 2 * pivot - high;
var buffer = (r1 - s1) * 0.02m;
if (buffer <= 0)
return;
var isBullish = candle.ClosePrice > candle.OpenPrice;
var isBearish = candle.ClosePrice < candle.OpenPrice;
// Bounce off S1 (buy)
if (Position == 0 && candle.LowPrice <= s1 + buffer && isBullish)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Bounce off R1 (sell)
else if (Position == 0 && candle.HighPrice >= r1 - buffer && isBearish)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit at pivot
else if (Position > 0 && candle.ClosePrice > pivot)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && candle.ClosePrice < pivot)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class pivot_point_reversal_strategy(Strategy):
"""
Pivot Point Reversal strategy.
Calculates pivot points from a rolling window of highs, lows, closes.
P = (H + L + C) / 3, S1 = 2*P - H, R1 = 2*P - L
Buys on bounce off S1, sells on bounce off R1, exits at pivot.
"""
def __init__(self):
super(pivot_point_reversal_strategy, self).__init__()
self._lookback = self.Param("Lookback", 60).SetDisplay("Lookback", "Lookback for pivot calc", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._highs = []
self._lows = []
self._closes = []
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(pivot_point_reversal_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._closes = []
self._cooldown = 0
def OnStarted2(self, time):
super(pivot_point_reversal_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
self._closes = []
self._cooldown = 0
sma = SimpleMovingAverage()
sma.Length = 20
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
self._highs.append(float(candle.HighPrice))
self._lows.append(float(candle.LowPrice))
self._closes.append(float(candle.ClosePrice))
lb = self._lookback.Value
if len(self._highs) > lb:
self._highs.pop(0)
self._lows.pop(0)
self._closes.pop(0)
if len(self._highs) < lb:
return
if self._cooldown > 0:
self._cooldown -= 1
return
# Calculate pivot points from lookback window
high = max(self._highs)
low = min(self._lows)
close = self._closes[-1]
pivot = (high + low + close) / 3.0
r1 = 2.0 * pivot - low
s1 = 2.0 * pivot - high
buffer = (r1 - s1) * 0.02
if buffer <= 0:
return
is_bullish = candle.ClosePrice > candle.OpenPrice
is_bearish = candle.ClosePrice < candle.OpenPrice
cd = self._cooldown_bars.Value
# Bounce off S1 (buy)
if self.Position == 0 and float(candle.LowPrice) <= s1 + buffer and is_bullish:
self.BuyMarket()
self._cooldown = cd
# Bounce off R1 (sell)
elif self.Position == 0 and float(candle.HighPrice) >= r1 - buffer and is_bearish:
self.SellMarket()
self._cooldown = cd
# Exit at pivot
elif self.Position > 0 and float(candle.ClosePrice) > pivot:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and float(candle.ClosePrice) < pivot:
self.BuyMarket()
self._cooldown = cd
def CreateClone(self):
return pivot_point_reversal_strategy()