Длинная стратегия прорыва диапазона открытия (ORB) с pivot-уровнями
Стратегия покупает при пробое максимума диапазона открытия, если уровень R1 из дневных pivot-поинтов находится выше этого максимума. Стоп-приказ сопровождается по уровням pivot.
Детали
- Условия входа:
- После диапазона открытия вход на пробой максимума, если R1 выше.
- Длинные/короткие: Только длинные
- Условия выхода:
- Трейлинг-стоп по pivot-уровням и закрытие в начале нового дня.
- Стопы: Да
- Значения по умолчанию:
RangeMinutes= 15SessionStart= 09:30MaxTradesPerDay= 1StopLossPercent= 3InitialSlType= Percentage
- Фильтры:
- Категория: Прорыв
- Направление: Лонг
- Индикаторы: Pivot Points
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Любой
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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>
/// Opening range breakout with pivot point trailing stop.
/// Uses a rolling high/low channel for breakout entry and pivot-based trailing stop for exits.
/// </summary>
public class LongOnlyOpeningRangeBreakoutWithPivotPointsStrategy : Strategy
{
public enum SlTypes
{
Percentage,
PreviousLow
}
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rangeBars;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<SlTypes> _initialSlType;
private readonly StrategyParam<int> _pivotLength;
private Highest _highest;
private Lowest _lowest;
private decimal _entryPrice;
private decimal _sl0;
private decimal _trailStop;
private int _cooldown;
private bool _prevReady;
private decimal _prevHighest;
private decimal _prevLowest;
// Pivot levels
private decimal _r1;
private decimal _r2;
private decimal _s1;
private decimal _s2;
// For pivot calc
private decimal _pivotHigh;
private decimal _pivotLow;
private decimal _pivotClose;
private int _pivotBarCount;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int RangeBars { get => _rangeBars.Value; set => _rangeBars.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public SlTypes InitialSlType { get => _initialSlType.Value; set => _initialSlType.Value = value; }
public int PivotLength { get => _pivotLength.Value; set => _pivotLength.Value = value; }
public LongOnlyOpeningRangeBreakoutWithPivotPointsStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Working candle type", "General");
_rangeBars = Param(nameof(RangeBars), 20)
.SetGreaterThanZero()
.SetDisplay("Range Bars", "Lookback bars for channel", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 5m)
.SetDisplay("Stop Loss %", "Initial stop loss percent", "Risk");
_initialSlType = Param(nameof(InitialSlType), SlTypes.Percentage)
.SetDisplay("Initial SL Type", "Initial stop loss type", "Risk");
_pivotLength = Param(nameof(PivotLength), 20)
.SetGreaterThanZero()
.SetDisplay("Pivot Length", "Bars for pivot calculation", "Indicators");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = default;
_sl0 = default;
_trailStop = default;
_cooldown = default;
_prevReady = false;
_prevHighest = default;
_prevLowest = default;
_r1 = default;
_r2 = default;
_s1 = default;
_s2 = default;
_pivotHigh = default;
_pivotLow = default;
_pivotClose = default;
_pivotBarCount = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highest = new Highest { Length = RangeBars };
_lowest = new Lowest { Length = RangeBars };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_highest, _lowest, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _highest);
DrawIndicator(area, _lowest);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal highestValue, decimal lowestValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_highest.IsFormed || !_lowest.IsFormed)
return;
// Update pivot levels every PivotLength bars
_pivotBarCount++;
_pivotHigh = _pivotHigh == 0 ? candle.HighPrice : Math.Max(_pivotHigh, candle.HighPrice);
_pivotLow = _pivotLow == 0 ? candle.LowPrice : Math.Min(_pivotLow, candle.LowPrice);
_pivotClose = candle.ClosePrice;
if (_pivotBarCount >= PivotLength)
{
var pivot = (_pivotHigh + _pivotLow + _pivotClose) / 3m;
_r1 = pivot + pivot - _pivotLow;
_r2 = pivot + (_pivotHigh - _pivotLow);
_s1 = pivot + pivot - _pivotHigh;
_s2 = pivot - (_pivotHigh - _pivotLow);
_pivotHigh = 0;
_pivotLow = 0;
_pivotBarCount = 0;
}
if (_cooldown > 0)
{
_cooldown--;
_prevHighest = highestValue;
_prevLowest = lowestValue;
_prevReady = true;
return;
}
// Long breakout entry
if (_prevReady && Position <= 0 && candle.ClosePrice > _prevHighest && _r1 > 0)
{
if (Position < 0)
BuyMarket();
_entryPrice = candle.ClosePrice;
_sl0 = _entryPrice * (1m - StopLossPercent / 100m);
_trailStop = 0m;
BuyMarket();
_cooldown = 40;
}
// Short breakdown entry
else if (_prevReady && Position >= 0 && candle.ClosePrice < _prevLowest && _s1 > 0)
{
if (Position > 0)
SellMarket();
_entryPrice = candle.ClosePrice;
_sl0 = _entryPrice * (1m + StopLossPercent / 100m);
_trailStop = 0m;
SellMarket();
_cooldown = 40;
}
// Trailing stop for long position
if (Position > 0 && _r1 > 0)
{
if (candle.HighPrice > _r2)
_trailStop = Math.Max(_trailStop, _r1);
else if (candle.HighPrice > _r1)
_trailStop = Math.Max(_trailStop, highestValue);
var sl = Math.Max(_sl0, _trailStop);
if (candle.LowPrice <= sl)
{
SellMarket();
_cooldown = 40;
}
}
// Trailing stop for short position
if (Position < 0 && _s1 > 0)
{
if (candle.LowPrice < _s2)
_trailStop = _trailStop == 0 ? _s1 : Math.Min(_trailStop, _s1);
else if (candle.LowPrice < _s1)
_trailStop = _trailStop == 0 ? lowestValue : Math.Min(_trailStop, lowestValue);
var sl = _trailStop > 0 ? Math.Min(_sl0, _trailStop) : _sl0;
if (candle.HighPrice >= sl)
{
BuyMarket();
_cooldown = 40;
}
}
_prevHighest = highestValue;
_prevLowest = lowestValue;
_prevReady = true;
}
}
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 Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class long_only_opening_range_breakout_with_pivot_points_strategy(Strategy):
def __init__(self):
super(long_only_opening_range_breakout_with_pivot_points_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Working candle type", "General")
self._range_bars = self.Param("RangeBars", 20) \
.SetGreaterThanZero() \
.SetDisplay("Range Bars", "Lookback bars for channel", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 5.0) \
.SetDisplay("Stop Loss %", "Initial stop loss percent", "Risk")
self._pivot_length = self.Param("PivotLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("Pivot Length", "Bars for pivot calculation", "Indicators")
self._entry_price = 0.0
self._sl0 = 0.0
self._trail_stop = 0.0
self._cooldown = 0
self._prev_ready = False
self._prev_highest = 0.0
self._prev_lowest = 0.0
self._r1 = 0.0
self._r2 = 0.0
self._s1 = 0.0
self._s2 = 0.0
self._pivot_high = 0.0
self._pivot_low = 0.0
self._pivot_close = 0.0
self._pivot_bar_count = 0
@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(long_only_opening_range_breakout_with_pivot_points_strategy, self).OnReseted()
self._entry_price = 0.0
self._sl0 = 0.0
self._trail_stop = 0.0
self._cooldown = 0
self._prev_ready = False
self._prev_highest = 0.0
self._prev_lowest = 0.0
self._r1 = 0.0
self._r2 = 0.0
self._s1 = 0.0
self._s2 = 0.0
self._pivot_high = 0.0
self._pivot_low = 0.0
self._pivot_close = 0.0
self._pivot_bar_count = 0
def OnStarted2(self, time):
super(long_only_opening_range_breakout_with_pivot_points_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._sl0 = 0.0
self._trail_stop = 0.0
self._cooldown = 0
self._prev_ready = False
self._prev_highest = 0.0
self._prev_lowest = 0.0
self._r1 = 0.0
self._r2 = 0.0
self._s1 = 0.0
self._s2 = 0.0
self._pivot_high = 0.0
self._pivot_low = 0.0
self._pivot_close = 0.0
self._pivot_bar_count = 0
self._highest = Highest()
self._highest.Length = self._range_bars.Value
self._lowest = Lowest()
self._lowest.Length = self._range_bars.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._highest, self._lowest, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._highest)
self.DrawIndicator(area, self._lowest)
self.DrawOwnTrades(area)
def OnProcess(self, candle, highest_val, lowest_val):
if candle.State != CandleStates.Finished:
return
if not self._highest.IsFormed or not self._lowest.IsFormed:
return
hv = float(highest_val)
lv = float(lowest_val)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
self._pivot_bar_count += 1
if self._pivot_high == 0.0:
self._pivot_high = high
else:
self._pivot_high = max(self._pivot_high, high)
if self._pivot_low == 0.0:
self._pivot_low = low
else:
self._pivot_low = min(self._pivot_low, low)
self._pivot_close = close
if self._pivot_bar_count >= self._pivot_length.Value:
pivot = (self._pivot_high + self._pivot_low + self._pivot_close) / 3.0
self._r1 = pivot + pivot - self._pivot_low
self._r2 = pivot + (self._pivot_high - self._pivot_low)
self._s1 = pivot + pivot - self._pivot_high
self._s2 = pivot - (self._pivot_high - self._pivot_low)
self._pivot_high = 0.0
self._pivot_low = 0.0
self._pivot_bar_count = 0
if self._cooldown > 0:
self._cooldown -= 1
self._prev_highest = hv
self._prev_lowest = lv
self._prev_ready = True
return
sl_pct = float(self._stop_loss_percent.Value) / 100.0
if self._prev_ready and self.Position <= 0 and close > self._prev_highest and self._r1 > 0.0:
if self.Position < 0:
self.BuyMarket()
self._entry_price = close
self._sl0 = self._entry_price * (1.0 - sl_pct)
self._trail_stop = 0.0
self.BuyMarket()
self._cooldown = 40
elif self._prev_ready and self.Position >= 0 and close < self._prev_lowest and self._s1 > 0.0:
if self.Position > 0:
self.SellMarket()
self._entry_price = close
self._sl0 = self._entry_price * (1.0 + sl_pct)
self._trail_stop = 0.0
self.SellMarket()
self._cooldown = 40
if self.Position > 0 and self._r1 > 0.0:
if high > self._r2:
self._trail_stop = max(self._trail_stop, self._r1)
elif high > self._r1:
self._trail_stop = max(self._trail_stop, hv)
sl = max(self._sl0, self._trail_stop)
if low <= sl:
self.SellMarket()
self._cooldown = 40
if self.Position < 0 and self._s1 > 0.0:
if low < self._s2:
if self._trail_stop == 0.0:
self._trail_stop = self._s1
else:
self._trail_stop = min(self._trail_stop, self._s1)
elif low < self._s1:
if self._trail_stop == 0.0:
self._trail_stop = lv
else:
self._trail_stop = min(self._trail_stop, lv)
sl = min(self._sl0, self._trail_stop) if self._trail_stop > 0.0 else self._sl0
if high >= sl:
self.BuyMarket()
self._cooldown = 40
self._prev_highest = hv
self._prev_lowest = lv
self._prev_ready = True
def CreateClone(self):
return long_only_opening_range_breakout_with_pivot_points_strategy()