Estrategia de Ruptura del Rango de Apertura
La estrategia de Ruptura del Rango de Apertura rastrea los precios más altos y más bajos durante los primeros minutos de una sesión de trading. Después de que el rango termina, se colocan órdenes de ruptura más allá del rango con un buffer configurable. Los objetivos se derivan de una proporción recompensa-riesgo mientras que los stops se establecen en el lado opuesto del rango.
Detalles
- Criterios de entrada:
- Después del rango de apertura, ir largo cuando el precio cierra por encima del máximo más el buffer.
- Ir corto cuando el precio cierra por debajo del mínimo menos el buffer.
- Largo/Corto: Ambos
- Criterios de salida:
- Stop y objetivo basados en el rango y la proporción recompensa-riesgo.
- Stops: Sí
- Valores predeterminados:
RangeMinutes= 15RewardRisk= 2.0EntryBuffer= 0.0001SessionStart= 08:00
- Filtros:
- Categoría: Ruptura
- Dirección: Ambos
- Indicadores: Ninguno
- Stops: Sí
- Complejidad: Bajo
- Marco temporal: Cualquiera
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Opening Range Breakout Strategy.
/// Candles opening during the first RangeMinutes after SessionStart (UTC) form the opening range. Afterwards, a close above the
/// range high plus EntryBuffer buys and a close below the range low minus EntryBuffer sells, once per session. The stop sits
/// on the opposite side of the range and the target is RewardRisk times the risk away from the entry.
/// </summary>
public class OpeningRangeBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _rangeMinutes;
private readonly StrategyParam<decimal> _rewardRisk;
private readonly StrategyParam<decimal> _entryBuffer;
private readonly StrategyParam<TimeSpan> _sessionStart;
private readonly StrategyParam<DataType> _candleType;
private DateTime? _sessionDate;
private decimal? _rangeHigh;
private decimal? _rangeLow;
private bool _tradedToday;
private decimal? _stopPrice;
private decimal? _targetPrice;
/// <summary>
/// Length of the opening range in minutes.
/// </summary>
public int RangeMinutes
{
get => _rangeMinutes.Value;
set => _rangeMinutes.Value = value;
}
/// <summary>
/// Target distance as a multiple of the risk.
/// </summary>
public decimal RewardRisk
{
get => _rewardRisk.Value;
set => _rewardRisk.Value = value;
}
/// <summary>
/// Price buffer beyond the range required for a breakout.
/// </summary>
public decimal EntryBuffer
{
get => _entryBuffer.Value;
set => _entryBuffer.Value = value;
}
/// <summary>
/// Session start time (UTC).
/// </summary>
public TimeSpan SessionStart
{
get => _sessionStart.Value;
set => _sessionStart.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public OpeningRangeBreakoutStrategy()
{
_rangeMinutes = Param(nameof(RangeMinutes), 15)
.SetGreaterThanZero()
.SetDisplay("Range Minutes", "Length of the opening range in minutes", "Session");
_rewardRisk = Param(nameof(RewardRisk), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Reward/Risk", "Target distance as a multiple of the risk", "Risk");
_entryBuffer = Param(nameof(EntryBuffer), 0.0001m)
.SetNotNegative()
.SetDisplay("Entry Buffer", "Price buffer beyond the range required for a breakout", "Trading");
_sessionStart = Param(nameof(SessionStart), new TimeSpan(8, 0, 0))
.SetDisplay("Session Start", "Session start time (UTC)", "Session");
_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()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_sessionDate = null;
_rangeHigh = null;
_rangeLow = null;
_tradedToday = false;
_stopPrice = null;
_targetPrice = null;
}
/// <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 openTime = candle.OpenTime;
var date = openTime.Date;
if (_sessionDate != date)
{
_sessionDate = date;
_rangeHigh = null;
_rangeLow = null;
_tradedToday = false;
}
var rangeStart = date + SessionStart;
var rangeEnd = rangeStart + TimeSpan.FromMinutes(RangeMinutes);
if (openTime >= rangeStart && openTime < rangeEnd)
{
_rangeHigh = _rangeHigh is decimal h ? Math.Max(h, candle.HighPrice) : candle.HighPrice;
_rangeLow = _rangeLow is decimal l ? Math.Min(l, candle.LowPrice) : candle.LowPrice;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position > 0)
{
if ((_stopPrice is decimal stop && candle.LowPrice <= stop) || (_targetPrice is decimal target && candle.HighPrice >= target))
{
SellMarket(Position);
_stopPrice = null;
_targetPrice = null;
}
return;
}
if (Position < 0)
{
if ((_stopPrice is decimal stop && candle.HighPrice >= stop) || (_targetPrice is decimal target && candle.LowPrice <= target))
{
BuyMarket(-Position);
_stopPrice = null;
_targetPrice = null;
}
return;
}
if (_tradedToday || openTime < rangeEnd || _rangeHigh is not decimal high || _rangeLow is not decimal low)
return;
var close = candle.ClosePrice;
if (close > high + EntryBuffer)
{
BuyMarket(Volume);
_stopPrice = low;
_targetPrice = close + RewardRisk * (close - low);
_tradedToday = true;
}
else if (close < low - EntryBuffer)
{
SellMarket(Volume);
_stopPrice = high;
_targetPrice = close - RewardRisk * (high - close);
_tradedToday = true;
}
}
}
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 opening_range_breakout_strategy(Strategy):
"""
Opening Range Breakout Strategy.
Candles opening during the first RangeMinutes after SessionStart (UTC) form the opening range. Afterwards, a close above the
range high plus EntryBuffer buys and a close below the range low minus EntryBuffer sells, once per session. The stop sits
on the opposite side of the range and the target is RewardRisk times the risk away from the entry.
"""
def __init__(self):
super(opening_range_breakout_strategy, self).__init__()
self._range_minutes = self.Param("RangeMinutes", 15).SetGreaterThanZero().SetDisplay("Range Minutes", "Length of the opening range in minutes", "Session")
self._reward_risk = self.Param("RewardRisk", 2.0).SetGreaterThanZero().SetDisplay("Reward/Risk", "Target distance as a multiple of the risk", "Risk")
self._entry_buffer = self.Param("EntryBuffer", 0.0001).SetNotNegative().SetDisplay("Entry Buffer", "Price buffer beyond the range required for a breakout", "Trading")
self._session_start = self.Param("SessionStart", TimeSpan(8, 0, 0)).SetDisplay("Session Start", "Session start time (UTC)", "Session")
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._session_date = None
self._range_high = None
self._range_low = None
self._traded_today = False
self._stop_price = None
self._target_price = None
def OnReseted(self):
super(opening_range_breakout_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(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
open_time = candle.OpenTime
date = open_time.Date
if self._session_date is None or self._session_date != date:
self._session_date = date
self._range_high = None
self._range_low = None
self._traded_today = False
range_start = date.Add(self._session_start.Value)
range_end = range_start.Add(TimeSpan.FromMinutes(self._range_minutes.Value))
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if open_time >= range_start and open_time < range_end:
self._range_high = high if self._range_high is None else max(self._range_high, high)
self._range_low = low if self._range_low is None else min(self._range_low, low)
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position > 0:
if (self._stop_price is not None and low <= self._stop_price) or (self._target_price is not None and high >= self._target_price):
self.SellMarket(self.Position)
self._stop_price = None
self._target_price = None
return
if self.Position < 0:
if (self._stop_price is not None and high >= self._stop_price) or (self._target_price is not None and low <= self._target_price):
self.BuyMarket(-self.Position)
self._stop_price = None
self._target_price = None
return
if self._traded_today or open_time < range_end or self._range_high is None or self._range_low is None:
return
close = float(candle.ClosePrice)
buffer = float(self._entry_buffer.Value)
reward_risk = float(self._reward_risk.Value)
range_high = self._range_high
range_low = self._range_low
if close > range_high + buffer:
self.BuyMarket(self.Volume)
self._stop_price = range_low
self._target_price = close + reward_risk * (close - range_low)
self._traded_today = True
elif close < range_low - buffer:
self.SellMarket(self.Volume)
self._stop_price = range_high
self._target_price = close - reward_risk * (range_high - close)
self._traded_today = True
def CreateClone(self):
return opening_range_breakout_strategy()