Negroni オープニングレンジ戦略
設定可能な時間ウィンドウで定義されるプレマーケットまたはオープニングレンジに基づいてブレイクアウトを取引します。注文は指定した取引セッション内のみ許可され、セッション終了時にすべての建玉がクローズされます。
パラメーター
CandleType= TimeSpan.FromMinutes(1).TimeFrame()MaxTradesPerDay= 3Direction= TradeDirection.LongShortSessionStart= new TimeSpan(9, 30, 0)SessionEnd= new TimeSpan(14, 0, 0)CloseTime= new TimeSpan(16, 0, 0)UsePreMarketRange= truePreMarketStart= new TimeSpan(8, 0, 0)PreMarketEnd= new TimeSpan(9, 0, 0)OpenRangeStart= new TimeSpan(9, 5, 0)OpenRangeEnd= new TimeSpan(9, 30, 0)
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Negroni opening range strategy.
/// Each day the high and low of the pre-market window (or of the opening range window when UsePreMarketRange is off) form the range.
/// Inside the trading session a close above the range high goes long and a close below the range low goes short, within the allowed
/// Direction and at most MaxTradesPerDay entries a day. Any open position is closed at CloseTime. Times are UTC candle open times.
/// </summary>
public class NegroniOpeningRangeStrategy : Strategy
{
/// <summary>
/// Allowed trade directions.
/// </summary>
public enum TradeDirection
{
/// <summary>
/// Long entries only.
/// </summary>
LongOnly,
/// <summary>
/// Short entries only.
/// </summary>
ShortOnly,
/// <summary>
/// Long and short entries.
/// </summary>
LongShort,
}
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maxTradesPerDay;
private readonly StrategyParam<TradeDirection> _direction;
private readonly StrategyParam<TimeSpan> _sessionStart;
private readonly StrategyParam<TimeSpan> _sessionEnd;
private readonly StrategyParam<TimeSpan> _closeTime;
private readonly StrategyParam<bool> _usePreMarketRange;
private readonly StrategyParam<TimeSpan> _preMarketStart;
private readonly StrategyParam<TimeSpan> _preMarketEnd;
private readonly StrategyParam<TimeSpan> _openRangeStart;
private readonly StrategyParam<TimeSpan> _openRangeEnd;
private DateTime _currentDate;
private decimal? _rangeHigh;
private decimal? _rangeLow;
private int _tradesToday;
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Maximum entries per day.
/// </summary>
public int MaxTradesPerDay
{
get => _maxTradesPerDay.Value;
set => _maxTradesPerDay.Value = value;
}
/// <summary>
/// Allowed trade direction.
/// </summary>
public TradeDirection Direction
{
get => _direction.Value;
set => _direction.Value = value;
}
/// <summary>
/// Start of the trading session.
/// </summary>
public TimeSpan SessionStart
{
get => _sessionStart.Value;
set => _sessionStart.Value = value;
}
/// <summary>
/// End of the trading session.
/// </summary>
public TimeSpan SessionEnd
{
get => _sessionEnd.Value;
set => _sessionEnd.Value = value;
}
/// <summary>
/// Time when any open position is closed.
/// </summary>
public TimeSpan CloseTime
{
get => _closeTime.Value;
set => _closeTime.Value = value;
}
/// <summary>
/// Use the pre-market range instead of the opening range.
/// </summary>
public bool UsePreMarketRange
{
get => _usePreMarketRange.Value;
set => _usePreMarketRange.Value = value;
}
/// <summary>
/// Start of the pre-market window.
/// </summary>
public TimeSpan PreMarketStart
{
get => _preMarketStart.Value;
set => _preMarketStart.Value = value;
}
/// <summary>
/// End of the pre-market window.
/// </summary>
public TimeSpan PreMarketEnd
{
get => _preMarketEnd.Value;
set => _preMarketEnd.Value = value;
}
/// <summary>
/// Start of the opening range window.
/// </summary>
public TimeSpan OpenRangeStart
{
get => _openRangeStart.Value;
set => _openRangeStart.Value = value;
}
/// <summary>
/// End of the opening range window.
/// </summary>
public TimeSpan OpenRangeEnd
{
get => _openRangeEnd.Value;
set => _openRangeEnd.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public NegroniOpeningRangeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_maxTradesPerDay = Param(nameof(MaxTradesPerDay), 3)
.SetGreaterThanZero()
.SetDisplay("Max Trades Per Day", "Maximum entries per day", "Trading");
_direction = Param(nameof(Direction), TradeDirection.LongShort)
.SetDisplay("Direction", "Allowed trade direction", "Trading");
_sessionStart = Param(nameof(SessionStart), new TimeSpan(9, 30, 0))
.SetDisplay("Session Start", "Start of the trading session", "Session");
_sessionEnd = Param(nameof(SessionEnd), new TimeSpan(14, 0, 0))
.SetDisplay("Session End", "End of the trading session", "Session");
_closeTime = Param(nameof(CloseTime), new TimeSpan(16, 0, 0))
.SetDisplay("Close Time", "Time when any open position is closed", "Session");
_usePreMarketRange = Param(nameof(UsePreMarketRange), true)
.SetDisplay("Use Pre-Market Range", "Use the pre-market range instead of the opening range", "Range");
_preMarketStart = Param(nameof(PreMarketStart), new TimeSpan(8, 0, 0))
.SetDisplay("Pre-Market Start", "Start of the pre-market window", "Range");
_preMarketEnd = Param(nameof(PreMarketEnd), new TimeSpan(9, 0, 0))
.SetDisplay("Pre-Market End", "End of the pre-market window", "Range");
_openRangeStart = Param(nameof(OpenRangeStart), new TimeSpan(9, 5, 0))
.SetDisplay("Open Range Start", "Start of the opening range window", "Range");
_openRangeEnd = Param(nameof(OpenRangeEnd), new TimeSpan(9, 30, 0))
.SetDisplay("Open Range End", "End of the opening range window", "Range");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_currentDate = default;
_rangeHigh = null;
_rangeLow = 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 date = candle.OpenTime.Date;
if (date != _currentDate)
{
_currentDate = date;
_rangeHigh = null;
_rangeLow = null;
_tradesToday = 0;
}
var tod = candle.OpenTime.TimeOfDay;
var rangeStart = UsePreMarketRange ? PreMarketStart : OpenRangeStart;
var rangeEnd = UsePreMarketRange ? PreMarketEnd : OpenRangeEnd;
if (tod >= rangeStart && tod < 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 (!IsFormedAndOnlineAndAllowTrading())
return;
if (tod >= CloseTime)
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
return;
}
if (tod < rangeEnd || tod < SessionStart || tod >= SessionEnd)
return;
if (_rangeHigh is not decimal rangeHigh || _rangeLow is not decimal rangeLow || _tradesToday >= MaxTradesPerDay)
return;
var close = candle.ClosePrice;
var allowLong = Direction != TradeDirection.ShortOnly;
var allowShort = Direction != TradeDirection.LongOnly;
if (allowLong && close > rangeHigh && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_tradesToday++;
}
else if (allowShort && close < rangeLow && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_tradesToday++;
}
}
}
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
# Direction values, matching the C# TradeDirection enum.
DIRECTION_LONG_ONLY = 0
DIRECTION_SHORT_ONLY = 1
DIRECTION_LONG_SHORT = 2
class negroni_opening_range_strategy(Strategy):
"""
Negroni opening range strategy.
Each day the high and low of the pre-market window (or of the opening range window when UsePreMarketRange is off) form the range.
Inside the trading session a close above the range high goes long and a close below the range low goes short, within the allowed
Direction and at most MaxTradesPerDay entries a day. Any open position is closed at CloseTime. Times are UTC candle open times.
"""
def __init__(self):
super(negroni_opening_range_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._max_trades_per_day = self.Param("MaxTradesPerDay", 3).SetGreaterThanZero().SetDisplay("Max Trades Per Day", "Maximum entries per day", "Trading")
self._direction = self.Param("Direction", DIRECTION_LONG_SHORT).SetDisplay("Direction", "Allowed trade direction (0 LongOnly, 1 ShortOnly, 2 LongShort)", "Trading")
self._session_start = self.Param("SessionStart", TimeSpan(9, 30, 0)).SetDisplay("Session Start", "Start of the trading session", "Session")
self._session_end = self.Param("SessionEnd", TimeSpan(14, 0, 0)).SetDisplay("Session End", "End of the trading session", "Session")
self._close_time = self.Param("CloseTime", TimeSpan(16, 0, 0)).SetDisplay("Close Time", "Time when any open position is closed", "Session")
self._use_pre_market_range = self.Param("UsePreMarketRange", True).SetDisplay("Use Pre-Market Range", "Use the pre-market range instead of the opening range", "Range")
self._pre_market_start = self.Param("PreMarketStart", TimeSpan(8, 0, 0)).SetDisplay("Pre-Market Start", "Start of the pre-market window", "Range")
self._pre_market_end = self.Param("PreMarketEnd", TimeSpan(9, 0, 0)).SetDisplay("Pre-Market End", "End of the pre-market window", "Range")
self._open_range_start = self.Param("OpenRangeStart", TimeSpan(9, 5, 0)).SetDisplay("Open Range Start", "Start of the opening range window", "Range")
self._open_range_end = self.Param("OpenRangeEnd", TimeSpan(9, 30, 0)).SetDisplay("Open Range End", "End of the opening range window", "Range")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._current_date = None
self._range_high = None
self._range_low = None
self._trades_today = 0
def OnReseted(self):
super(negroni_opening_range_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(negroni_opening_range_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
date = candle.OpenTime.Date
if self._current_date is None or date != self._current_date:
self._current_date = date
self._range_high = None
self._range_low = None
self._trades_today = 0
tod = candle.OpenTime.TimeOfDay.TotalMinutes
if self._use_pre_market_range.Value:
range_start = self._pre_market_start.Value.TotalMinutes
range_end = self._pre_market_end.Value.TotalMinutes
else:
range_start = self._open_range_start.Value.TotalMinutes
range_end = self._open_range_end.Value.TotalMinutes
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if range_start <= tod < 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)
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if tod >= self._close_time.Value.TotalMinutes:
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(-self.Position)
return
if tod < range_end or tod < self._session_start.Value.TotalMinutes or tod >= self._session_end.Value.TotalMinutes:
return
if self._range_high is None or self._range_low is None or self._trades_today >= self._max_trades_per_day.Value:
return
close = float(candle.ClosePrice)
direction = self._direction.Value
allow_long = direction != DIRECTION_SHORT_ONLY
allow_short = direction != DIRECTION_LONG_ONLY
if allow_long and close > self._range_high and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._trades_today += 1
elif allow_short and close < self._range_low and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._trades_today += 1
def CreateClone(self):
return negroni_opening_range_strategy()