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.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// IU Opening Range Breakout Strategy.
/// Trades breakouts of the first session bar with risk to reward management and daily trade limit.
/// </summary>
public class IUOpeningRangeBreakoutStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _riskReward;
private readonly StrategyParam<int> _maxTrades;
private readonly StrategyParam<int> _cooldownDays;
private readonly StrategyParam<TimeSpan> _endTime;
private decimal _orHigh;
private decimal _orLow;
private bool _rangeSet;
private decimal _stopPrice;
private decimal _targetPrice;
private int _tradesToday;
private DateTime _currentDay;
private DateTime _nextTradeDate;
private decimal _prevHigh;
private decimal _prevLow;
private int _orBarCount;
/// <summary>
/// Candle type for processing.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Risk to reward ratio.
/// </summary>
public decimal RiskReward
{
get => _riskReward.Value;
set => _riskReward.Value = value;
}
/// <summary>
/// Maximum number of trades per day.
/// </summary>
public int MaxTrades
{
get => _maxTrades.Value;
set => _maxTrades.Value = value;
}
/// <summary>
/// Minimum days between entries.
/// </summary>
public int CooldownDays
{
get => _cooldownDays.Value;
set => _cooldownDays.Value = value;
}
/// <summary>
/// Time to close all positions.
/// </summary>
public TimeSpan EndTime
{
get => _endTime.Value;
set => _endTime.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public IUOpeningRangeBreakoutStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_riskReward = Param(nameof(RiskReward), 2m)
.SetGreaterThanZero()
.SetDisplay("Risk/Reward", "Risk to reward ratio", "General")
.SetOptimize(1m, 3m, 0.5m);
_maxTrades = Param(nameof(MaxTrades), 3)
.SetGreaterThanZero()
.SetDisplay("Max Trades", "Maximum trades per day", "General");
_cooldownDays = Param(nameof(CooldownDays), 3)
.SetDisplay("Cooldown Days", "Minimum days between entries", "General");
_endTime = Param(nameof(EndTime), new TimeSpan(15, 0, 0))
.SetDisplay("End Time", "Daily close time (UTC)", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_orHigh = 0m;
_orLow = 0m;
_rangeSet = false;
_stopPrice = 0m;
_targetPrice = 0m;
_tradesToday = 0;
_currentDay = default;
_nextTradeDate = DateTime.MinValue;
_prevHigh = 0m;
_prevLow = 0m;
_orBarCount = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_currentDay = time.Date;
_nextTradeDate = DateTime.MinValue;
var dummyEma1 = new ExponentialMovingAverage { Length = 10 };
var dummyEma2 = new ExponentialMovingAverage { Length = 20 };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(dummyEma1, dummyEma2, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal d1, decimal d2)
{
if (candle.State != CandleStates.Finished)
return;
var openTime = candle.OpenTime;
// Reset for new day
if (openTime.Date != _currentDay)
{
_currentDay = openTime.Date;
_rangeSet = false;
_tradesToday = 0;
_orBarCount = 0;
_orHigh = 0m;
_orLow = decimal.MaxValue;
}
_orBarCount++;
if (!_rangeSet)
{
_orHigh = Math.Max(_orHigh, candle.HighPrice);
_orLow = Math.Min(_orLow, candle.LowPrice);
if (_orBarCount >= 2)
_rangeSet = true;
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
return;
}
// Close positions at end of day
if (openTime.TimeOfDay >= EndTime && Position != 0)
{
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
}
if (Position == 0 && _tradesToday < MaxTrades && openTime.Date >= _nextTradeDate)
{
if (candle.HighPrice > _orHigh)
{
BuyMarket();
_tradesToday++;
_nextTradeDate = openTime.Date.AddDays(CooldownDays);
_stopPrice = _prevLow;
_targetPrice = candle.ClosePrice + (candle.ClosePrice - _stopPrice) * RiskReward;
}
else if (candle.LowPrice < _orLow)
{
SellMarket();
_tradesToday++;
_nextTradeDate = openTime.Date.AddDays(CooldownDays);
_stopPrice = _prevHigh;
_targetPrice = candle.ClosePrice - (_stopPrice - candle.ClosePrice) * RiskReward;
}
}
else if (Position > 0)
{
if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _targetPrice)
SellMarket();
}
else if (Position < 0)
{
if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _targetPrice)
BuyMarket();
}
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
}
}
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.Strategies import Strategy
class iu_opening_range_breakout_strategy(Strategy):
"""
IU Opening Range Breakout: trades breakouts of the first session bars
with risk/reward management and daily trade limit.
"""
def __init__(self):
super(iu_opening_range_breakout_strategy, self).__init__()
self._risk_reward = self.Param("RiskReward", 2.0) \
.SetDisplay("Risk/Reward", "Risk to reward ratio", "General")
self._max_trades = self.Param("MaxTrades", 3) \
.SetDisplay("Max Trades", "Maximum trades per day", "General")
self._cooldown_days = self.Param("CooldownDays", 3) \
.SetDisplay("Cooldown Days", "Minimum days between entries", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._or_high = 0.0
self._or_low = 0.0
self._range_set = False
self._stop_price = 0.0
self._target_price = 0.0
self._trades_today = 0
self._current_day = None
self._prev_high = 0.0
self._prev_low = 0.0
self._or_bar_count = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(iu_opening_range_breakout_strategy, self).OnReseted()
self._or_high = 0.0
self._or_low = 0.0
self._range_set = False
self._stop_price = 0.0
self._target_price = 0.0
self._trades_today = 0
self._current_day = None
self._prev_high = 0.0
self._prev_low = 0.0
self._or_bar_count = 0
def OnStarted2(self, time):
super(iu_opening_range_breakout_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
open_time = candle.OpenTime
day = open_time.Date
if self._current_day is None or self._current_day != day:
self._current_day = day
self._range_set = False
self._trades_today = 0
self._or_bar_count = 0
self._or_high = 0.0
self._or_low = float('inf')
self._or_bar_count += 1
if not self._range_set:
self._or_high = max(self._or_high, high)
self._or_low = min(self._or_low, low)
if self._or_bar_count >= 2:
self._range_set = True
self._prev_high = high
self._prev_low = low
return
if self.Position == 0 and self._trades_today < self._max_trades.Value:
if high > self._or_high:
self.BuyMarket()
self._trades_today += 1
self._stop_price = self._prev_low
self._target_price = close + (close - self._stop_price) * self._risk_reward.Value
elif low < self._or_low:
self.SellMarket()
self._trades_today += 1
self._stop_price = self._prev_high
self._target_price = close - (self._stop_price - close) * self._risk_reward.Value
elif self.Position > 0:
if low <= self._stop_price or high >= self._target_price:
self.SellMarket()
elif self.Position < 0:
if high >= self._stop_price or low <= self._target_price:
self.BuyMarket()
self._prev_high = high
self._prev_low = low
def CreateClone(self):
return iu_opening_range_breakout_strategy()