IU Opening Range Breakout Strategy
The IU Opening Range Breakout strategy monitors the high and low of the first bar of each session and trades breakouts in either direction. Stops use the previous bar's extreme and targets are derived from a configurable risk-to-reward ratio. All positions are closed at a user-defined end time.
Details
- Entry Criteria:
- Go long when the close crosses above the first bar's high.
- Go short when the close crosses below the first bar's low.
- Long/Short: Both
- Exit Criteria:
- Stop at the previous bar's low/high.
- Target based on risk-to-reward ratio.
- Close all positions at
EndTime.
- Stops: Yes
- Default Values:
RiskReward= 2.0MaxTrades= 2EndTime= 15:00CandleType= 1 minute
- Filters:
- Category: Breakout
- Direction: Both
- Indicators: None
- Stops: Yes
- Complexity: Low
- Timeframe: Any
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// IU opening range breakout strategy.
/// The first candle of each UTC day sets the opening range. Before EndTime a close crossing above its high goes long and a close
/// crossing below its low goes short, reversing an opposite position, with at most MaxTrades entries a day. The stop sits at the
/// previous candle's low (long) or high (short), the target RiskReward times the stop distance away, and any open position is
/// closed at EndTime.
/// </summary>
public class IUOpeningRangeBreakoutStrategy : Strategy
{
private readonly StrategyParam<decimal> _riskReward;
private readonly StrategyParam<int> _maxTrades;
private readonly StrategyParam<TimeSpan> _endTime;
private readonly StrategyParam<DataType> _candleType;
private DateTime _currentDay;
private decimal? _rangeHigh;
private decimal? _rangeLow;
private decimal? _prevHigh;
private decimal? _prevLow;
private decimal? _prevClose;
private decimal? _stopPrice;
private decimal? _targetPrice;
private int _tradesToday;
/// <summary>
/// Target distance as a multiple of the stop distance.
/// </summary>
public decimal RiskReward
{
get => _riskReward.Value;
set => _riskReward.Value = value;
}
/// <summary>
/// Maximum entries per day.
/// </summary>
public int MaxTrades
{
get => _maxTrades.Value;
set => _maxTrades.Value = value;
}
/// <summary>
/// Time of day (UTC) when positions are closed and entries stop.
/// </summary>
public TimeSpan EndTime
{
get => _endTime.Value;
set => _endTime.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public IUOpeningRangeBreakoutStrategy()
{
_riskReward = Param(nameof(RiskReward), 2m)
.SetGreaterThanZero()
.SetDisplay("Risk/Reward", "Target distance as a multiple of the stop distance", "Risk")
.SetOptimize(1m, 3m, 0.5m);
_maxTrades = Param(nameof(MaxTrades), 2)
.SetGreaterThanZero()
.SetDisplay("Max Trades", "Maximum entries per day", "General");
_endTime = Param(nameof(EndTime), new TimeSpan(15, 0, 0))
.SetDisplay("End Time", "Time of day (UTC) when positions are closed", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).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();
ResetState();
}
private void ResetState()
{
_currentDay = default;
_rangeHigh = _rangeLow = null;
_prevHigh = _prevLow = _prevClose = null;
_stopPrice = _targetPrice = null;
_tradesToday = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var prevHigh = _prevHigh;
var prevLow = _prevLow;
var prevClose = _prevClose;
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_prevClose = candle.ClosePrice;
var day = candle.OpenTime.Date;
if (_currentDay != day)
{
// The first candle of the day is the opening range.
_currentDay = day;
_rangeHigh = candle.HighPrice;
_rangeLow = candle.LowPrice;
_tradesToday = 0;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (candle.OpenTime.TimeOfDay >= EndTime)
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
_stopPrice = _targetPrice = null;
return;
}
if (Position > 0 && _stopPrice is decimal longStop && _targetPrice is decimal longTarget)
{
if (candle.LowPrice <= longStop || candle.HighPrice >= longTarget)
{
SellMarket(Position);
_stopPrice = _targetPrice = null;
return;
}
}
else if (Position < 0 && _stopPrice is decimal shortStop && _targetPrice is decimal shortTarget)
{
if (candle.HighPrice >= shortStop || candle.LowPrice <= shortTarget)
{
BuyMarket(-Position);
_stopPrice = _targetPrice = null;
return;
}
}
if (_tradesToday >= MaxTrades || _rangeHigh is not decimal high || _rangeLow is not decimal low ||
prevClose is not decimal lastClose || prevHigh is not decimal lastHigh || prevLow is not decimal lastLow)
return;
var close = candle.ClosePrice;
if (lastClose <= high && close > high && Position <= 0 && lastLow < close)
{
BuyMarket(Volume + Math.Abs(Position));
_tradesToday++;
_stopPrice = lastLow;
_targetPrice = close + (close - lastLow) * RiskReward;
}
else if (lastClose >= low && close < low && Position >= 0 && lastHigh > close)
{
SellMarket(Volume + Math.Abs(Position));
_tradesToday++;
_stopPrice = lastHigh;
_targetPrice = close - (lastHigh - close) * RiskReward;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class iu_opening_range_breakout_strategy(Strategy):
"""
IU opening range breakout strategy.
The first candle of each UTC day sets the opening range. Before EndTime a close crossing above its high goes long and a close
crossing below its low goes short, reversing an opposite position, with at most MaxTrades entries a day. The stop sits at the
previous candle's low (long) or high (short), the target RiskReward times the stop distance away, and any open position is
closed at EndTime.
"""
def __init__(self):
super(iu_opening_range_breakout_strategy, self).__init__()
self._risk_reward = self.Param("RiskReward", 2.0).SetGreaterThanZero().SetDisplay("Risk/Reward", "Target distance as a multiple of the stop distance", "Risk").SetOptimize(1.0, 3.0, 0.5)
self._max_trades = self.Param("MaxTrades", 2).SetGreaterThanZero().SetDisplay("Max Trades", "Maximum entries per day", "General")
self._end_time = self.Param("EndTime", TimeSpan(15, 0, 0)).SetDisplay("End Time", "Time of day (UTC) when positions are closed", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._current_day = None
self._range_high = None
self._range_low = None
self._prev_high = None
self._prev_low = None
self._prev_close = None
self._stop_price = None
self._target_price = None
self._trades_today = 0
def OnReseted(self):
super(iu_opening_range_breakout_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(iu_opening_range_breakout_strategy, self).OnStarted2(time)
self._reset_state()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
prev_high = self._prev_high
prev_low = self._prev_low
prev_close = self._prev_close
self._prev_high = high
self._prev_low = low
self._prev_close = close
day = candle.OpenTime.Date
if self._current_day != day:
# The first candle of the day is the opening range.
self._current_day = day
self._range_high = high
self._range_low = low
self._trades_today = 0
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if candle.OpenTime.TimeOfDay >= self._end_time.Value:
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(-self.Position)
self._stop_price = None
self._target_price = None
return
if self.Position > 0 and self._stop_price is not None and self._target_price is not None:
if low <= self._stop_price or high >= self._target_price:
self.SellMarket(self.Position)
self._stop_price = None
self._target_price = None
return
elif self.Position < 0 and self._stop_price is not None and self._target_price is not None:
if high >= self._stop_price or low <= self._target_price:
self.BuyMarket(-self.Position)
self._stop_price = None
self._target_price = None
return
if self._trades_today >= self._max_trades.Value or self._range_high is None or self._range_low is None \
or prev_close is None or prev_high is None or prev_low is None:
return
rr = float(self._risk_reward.Value)
if prev_close <= self._range_high and close > self._range_high and self.Position <= 0 and prev_low < close:
self.BuyMarket(self.Volume + abs(self.Position))
self._trades_today += 1
self._stop_price = prev_low
self._target_price = close + (close - prev_low) * rr
elif prev_close >= self._range_low and close < self._range_low and self.Position >= 0 and prev_high > close:
self.SellMarket(self.Volume + abs(self.Position))
self._trades_today += 1
self._stop_price = prev_high
self._target_price = close - (prev_high - close) * rr
def CreateClone(self):
return iu_opening_range_breakout_strategy()