CP Strat ORB
This strategy trades breakouts of the New York opening range (9:30-9:45) with a retest. It enters long after price breaks above the range high and closes back above it, and enters short after price breaks below the range low and closes back below it. Exits use fixed stop-loss and take-profit levels.
Details
- Entry Criteria: Breakout of the NY opening range followed by a retest and close beyond the range limit.
- Long/Short: Both.
- Exit Criteria: Fixed take-profit or stop-loss.
- Stops: Yes.
- Default Values:
MinRangePoints= 60mStopPoints= 20mTakePoints= 60mMaxTradesPerSession= 3CandleType= TimeSpan.FromMinutes(1)
- Filters:
- Category: Breakout
- Direction: Both
- Indicators: None
- Stops: Yes
- Complexity: Basic
- Timeframe: Intraday
- 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>
/// CP Strat ORB strategy.
/// The New York opening range spans 9:30-9:45 local exchange time and is traded only when it is at least MinRangePoints wide.
/// After a close above the range high, a later candle that dips back to the high and closes above it buys; after a close below
/// the range low, a candle that rallies back to the low and closes below it sells short. At most MaxTradesPerSession entries
/// are taken per day and every trade has a fixed StopPoints stop and TakePoints target.
/// </summary>
public class CpStratOrbStrategy : Strategy
{
private static readonly TimeSpan _rangeStart = new(9, 30, 0);
private static readonly TimeSpan _rangeEnd = new(9, 45, 0);
private static readonly TimeZoneInfo _newYork = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
private readonly StrategyParam<decimal> _minRangePoints;
private readonly StrategyParam<decimal> _stopPoints;
private readonly StrategyParam<decimal> _takePoints;
private readonly StrategyParam<int> _maxTradesPerSession;
private readonly StrategyParam<DataType> _candleType;
private DateTime? _sessionDate;
private decimal? _rangeHigh;
private decimal? _rangeLow;
private bool _brokeUp;
private bool _brokeDown;
private int _tradesToday;
/// <summary>
/// Minimum opening range width in points.
/// </summary>
public decimal MinRangePoints
{
get => _minRangePoints.Value;
set => _minRangePoints.Value = value;
}
/// <summary>
/// Stop loss in points.
/// </summary>
public decimal StopPoints
{
get => _stopPoints.Value;
set => _stopPoints.Value = value;
}
/// <summary>
/// Take profit in points.
/// </summary>
public decimal TakePoints
{
get => _takePoints.Value;
set => _takePoints.Value = value;
}
/// <summary>
/// Maximum entries per session.
/// </summary>
public int MaxTradesPerSession
{
get => _maxTradesPerSession.Value;
set => _maxTradesPerSession.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public CpStratOrbStrategy()
{
_minRangePoints = Param(nameof(MinRangePoints), 60m)
.SetNotNegative()
.SetDisplay("Min Range", "Minimum opening range width in points", "Range");
_stopPoints = Param(nameof(StopPoints), 20m)
.SetNotNegative()
.SetDisplay("Stop Points", "Stop loss in points", "Risk");
_takePoints = Param(nameof(TakePoints), 60m)
.SetNotNegative()
.SetDisplay("Take Points", "Take profit in points", "Risk");
_maxTradesPerSession = Param(nameof(MaxTradesPerSession), 3)
.SetGreaterThanZero()
.SetDisplay("Max Trades", "Maximum entries per session", "Risk");
_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();
_sessionDate = null;
ResetSession();
}
private void ResetSession()
{
_rangeHigh = null;
_rangeLow = null;
_brokeUp = false;
_brokeDown = false;
_tradesToday = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_sessionDate = null;
ResetSession();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var step = Security?.PriceStep ?? 1m;
StartProtection(
TakePoints > 0 ? new Unit(TakePoints * step, UnitTypes.Absolute) : new Unit(),
StopPoints > 0 ? new Unit(StopPoints * step, UnitTypes.Absolute) : new Unit(),
useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var local = TimeZoneInfo.ConvertTimeFromUtc(DateTime.SpecifyKind(candle.OpenTime, DateTimeKind.Utc), _newYork);
if (_sessionDate != local.Date)
{
_sessionDate = local.Date;
ResetSession();
}
var timeOfDay = local.TimeOfDay;
if (timeOfDay < _rangeStart)
return;
if (timeOfDay < _rangeEnd)
{
_rangeHigh = _rangeHigh is decimal high ? Math.Max(high, candle.HighPrice) : candle.HighPrice;
_rangeLow = _rangeLow is decimal low ? Math.Min(low, candle.LowPrice) : candle.LowPrice;
return;
}
if (_rangeHigh is not decimal rangeHigh || _rangeLow is not decimal rangeLow)
return;
var step = Security?.PriceStep ?? 1m;
if (rangeHigh - rangeLow < MinRangePoints * step)
return;
var close = candle.ClosePrice;
var longSignal = _brokeUp && candle.LowPrice <= rangeHigh && close > rangeHigh;
var shortSignal = _brokeDown && candle.HighPrice >= rangeLow && close < rangeLow;
if (close > rangeHigh)
_brokeUp = true;
if (close < rangeLow)
_brokeDown = true;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position != 0 || _tradesToday >= MaxTradesPerSession)
return;
if (longSignal)
{
BuyMarket(Volume);
_tradesToday++;
_brokeUp = false;
}
else if (shortSignal)
{
SellMarket(Volume);
_tradesToday++;
_brokeDown = false;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal, DateTime, DateTimeKind, TimeZoneInfo
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class cp_strat_orb_strategy(Strategy):
"""
CP Strat ORB strategy.
The New York opening range spans 9:30-9:45 local exchange time and is traded only when it is at least MinRangePoints wide.
After a close above the range high, a later candle that dips back to the high and closes above it buys; after a close below
the range low, a candle that rallies back to the low and closes below it sells short. At most MaxTradesPerSession entries
are taken per day and every trade has a fixed StopPoints stop and TakePoints target.
"""
_range_start = TimeSpan(9, 30, 0)
_range_end = TimeSpan(9, 45, 0)
_new_york = TimeZoneInfo.FindSystemTimeZoneById("America/New_York")
def __init__(self):
super(cp_strat_orb_strategy, self).__init__()
self._min_range_points = self.Param("MinRangePoints", 60.0).SetNotNegative().SetDisplay("Min Range", "Minimum opening range width in points", "Range")
self._stop_points = self.Param("StopPoints", 20.0).SetNotNegative().SetDisplay("Stop Points", "Stop loss in points", "Risk")
self._take_points = self.Param("TakePoints", 60.0).SetNotNegative().SetDisplay("Take Points", "Take profit in points", "Risk")
self._max_trades_per_session = self.Param("MaxTradesPerSession", 3).SetGreaterThanZero().SetDisplay("Max Trades", "Maximum entries per session", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._session_date = None
self._reset_session()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_session(self):
self._range_high = None
self._range_low = None
self._broke_up = False
self._broke_down = False
self._trades_today = 0
def OnReseted(self):
super(cp_strat_orb_strategy, self).OnReseted()
self._session_date = None
self._reset_session()
def _price_step(self):
step = self.Security.PriceStep if self.Security is not None else None
return step if step is not None else Decimal(1)
def OnStarted2(self, time):
super(cp_strat_orb_strategy, self).OnStarted2(time)
self._session_date = None
self._reset_session()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
step = self._price_step()
take = float(self._take_points.Value)
stop = float(self._stop_points.Value)
self.StartProtection(
Unit(Decimal(take) * step, UnitTypes.Absolute) if take > 0 else Unit(),
Unit(Decimal(stop) * step, UnitTypes.Absolute) if stop > 0 else Unit(),
useMarketOrders=True)
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
local = TimeZoneInfo.ConvertTimeFromUtc(DateTime.SpecifyKind(candle.OpenTime, DateTimeKind.Utc), self._new_york)
if self._session_date is None or self._session_date != local.Date:
self._session_date = local.Date
self._reset_session()
time_of_day = local.TimeOfDay
if time_of_day < self._range_start:
return
if time_of_day < self._range_end:
self._range_high = candle.HighPrice if self._range_high is None else max(self._range_high, candle.HighPrice)
self._range_low = candle.LowPrice if self._range_low is None else min(self._range_low, candle.LowPrice)
return
if self._range_high is None or self._range_low is None:
return
range_high = self._range_high
range_low = self._range_low
if range_high - range_low < Decimal(self._min_range_points.Value) * self._price_step():
return
close = candle.ClosePrice
long_signal = self._broke_up and candle.LowPrice <= range_high and close > range_high
short_signal = self._broke_down and candle.HighPrice >= range_low and close < range_low
if close > range_high:
self._broke_up = True
if close < range_low:
self._broke_down = True
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position != 0 or self._trades_today >= self._max_trades_per_session.Value:
return
if long_signal:
self.BuyMarket(self.Volume)
self._trades_today += 1
self._broke_up = False
elif short_signal:
self.SellMarket(self.Volume)
self._trades_today += 1
self._broke_down = False
def CreateClone(self):
return cp_strat_orb_strategy()