开盘区间突破策略
该策略记录交易开始后前几分钟内的最高价和最低价。区间结束后,在区间外加上可配置缓冲的价格处布置突破订单。目标基于收益风险比,止损设置在区间的另一侧。
细节
- 入场条件:
- 区间结束后,收盘价高于区间高点加缓冲时做多。
- 收盘价低于区间低点减缓冲时做空。
- 多空方向:双向
- 出场条件:
- 根据区间和收益风险比设置止损和目标。
- 止损:是
- 默认值:
RangeMinutes= 15RewardRisk= 2.0EntryBuffer= 0.0001SessionStart= 08:00
- 过滤器:
- 类别:突破
- 方向:双向
- 指标:无
- 止损:是
- 复杂度:低
- 时间框架:任意
- 季节性:无
- 神经网络:无
- 背离:无
- 风险等级:中等
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()