Стратегия разворота от пивот‑уровней
Дневные пивот‑уровни и их уровни поддержки и сопротивления часто служат точками разворота для внутридневной динамики цен. Эта стратегия рассчитывает классические пивоты «флор‑трейдеров» по максимуму, минимуму и закрытию предыдущего дня, а затем ищет свечи, которые отскакивают от S1 или R1.
Тестирование показывает среднегодичную доходность около 127%. Стратегию лучше запускать на фондовом рынке.
Когда цена подходит к уровню поддержки S1 и формирует бычью свечу, открывается длинная позиция. Если цена тестирует сопротивление R1 и появляется медвежья свеча, открывается короткая. Сделки закрываются при достижении центрального пивота или если срабатывает защитный стоп.
Метод обновляет расчёты в начале каждого торгового дня, что делает его пригодным для сессий с чёткими внутридневными диапазонами.
Детали
- Условия входа: Бычья свеча у S1 или медвежья у R1.
- Лонг/Шорт: Оба.
- Условия выхода: Цена пересекает центральный пивот или стоп‑лосс.
- Стопы: Да, на процентной основе.
- Значения по умолчанию:
CandleType = 5 минут
StopLossPercent = 2
Пивоты каждого дня (по UTC) считаются по максимуму, минимуму и закрытию предыдущего дня. Для покупки нужна бычья свеча, коснувшаяся S1 и закрывшаяся выше него, для продажи — медвежья свеча, достигшая R1 и закрывшаяся ниже него; стоп задаётся в процентах от цены входа.
- Фильтры:
- Категория: Среднее возвращение
- Направление: Оба
- Индикаторы: Пивоты
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Сезонность: Да
- Нейронные сети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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.
/// Each day the classic floor pivots come from the previous day's high, low and close: P = (H + L + C) / 3, R1 = 2P - L, S1 = 2P - H.
/// While flat, a bullish candle that dips to S1 and closes above it buys, and a bearish candle that reaches R1 and closes below it sells.
/// The position closes when the close reaches the central pivot or at the percent stop.
/// </summary>
public class PivotPointReversalStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private DateTime? _day;
private decimal _dayHigh;
private decimal _dayLow;
private decimal _dayClose;
private (decimal Pivot, decimal R1, decimal S1)? _levels;
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public PivotPointReversalStrategy()
{
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_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();
_day = null;
_dayHigh = default;
_dayLow = default;
_dayClose = default;
_levels = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_day = null;
_levels = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var day = candle.OpenTime.Date;
if (_day != day)
{
// A new day takes its pivots from the day that has just ended.
if (_day != null)
{
var pivot = (_dayHigh + _dayLow + _dayClose) / 3m;
_levels = (pivot, 2 * pivot - _dayLow, 2 * pivot - _dayHigh);
}
_day = day;
_dayHigh = candle.HighPrice;
_dayLow = candle.LowPrice;
}
else
{
_dayHigh = Math.Max(_dayHigh, candle.HighPrice);
_dayLow = Math.Min(_dayLow, candle.LowPrice);
}
_dayClose = candle.ClosePrice;
if (_levels is not { } levels || !IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
if (Position > 0)
{
if (close >= levels.Pivot)
SellMarket(Position);
}
else if (Position < 0)
{
if (close <= levels.Pivot)
BuyMarket(-Position);
}
else if (close > candle.OpenPrice && candle.LowPrice <= levels.S1 && close > levels.S1)
{
BuyMarket(Volume);
}
else if (close < candle.OpenPrice && candle.HighPrice >= levels.R1 && close < levels.R1)
{
SellMarket(Volume);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class pivot_point_reversal_strategy(Strategy):
"""
Pivot Point Reversal strategy.
Each day the classic floor pivots come from the previous day's high, low and close: P = (H + L + C) / 3, R1 = 2P - L, S1 = 2P - H.
While flat, a bullish candle that dips to S1 and closes above it buys, and a bearish candle that reaches R1 and closes below it sells.
The position closes when the close reaches the central pivot or at the percent stop.
"""
def __init__(self):
super(pivot_point_reversal_strategy, self).__init__()
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._day = None
self._day_high = Decimal(0)
self._day_low = Decimal(0)
self._day_close = Decimal(0)
self._levels = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(pivot_point_reversal_strategy, self).OnReseted()
self._day = None
self._day_high = Decimal(0)
self._day_low = Decimal(0)
self._day_close = Decimal(0)
self._levels = None
def OnStarted2(self, time):
super(pivot_point_reversal_strategy, self).OnStarted2(time)
self._day = None
self._levels = None
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
day = candle.OpenTime.Date
if self._day is None or self._day != day:
# A new day takes its pivots from the day that has just ended.
if self._day is not None:
pivot = (self._day_high + self._day_low + self._day_close) / Decimal(3)
self._levels = (pivot, Decimal(2) * pivot - self._day_low, Decimal(2) * pivot - self._day_high)
self._day = day
self._day_high = candle.HighPrice
self._day_low = candle.LowPrice
else:
self._day_high = Math.Max(self._day_high, candle.HighPrice)
self._day_low = Math.Min(self._day_low, candle.LowPrice)
self._day_close = candle.ClosePrice
if self._levels is None or not self.IsFormedAndOnlineAndAllowTrading():
return
pivot, r1, s1 = self._levels
close = candle.ClosePrice
if self.Position > 0:
if close >= pivot:
self.SellMarket(self.Position)
elif self.Position < 0:
if close <= pivot:
self.BuyMarket(-self.Position)
elif close > candle.OpenPrice and candle.LowPrice <= s1 and close > s1:
self.BuyMarket(self.Volume)
elif close < candle.OpenPrice and candle.HighPrice >= r1 and close < r1:
self.SellMarket(self.Volume)
def CreateClone(self):
return pivot_point_reversal_strategy()