NY Eröffnungsbereich-Ausbruch - MA-Stop-Strategie
Handelt Ausbrüche aus dem New Yorker Eröffnungsbereich 9:30-9:45 mit optionalen gleitenden Durchschnitts-Ausstiegen. Einstiege erfolgen auf der Kerze nach dem Ausbruch, wenn diese innerhalb der Cutoff-Zeit liegt und der Preis den MA-Filter erfüllt.
Details
- Einstiegskriterien:
- Vorherige Kerze schließt jenseits des Eröffnungsbereich-Hochs (Long) oder -Tiefs (Short) vor der Cutoff-Zeit.
- Aktuelle Kerze ist die erste nach dem Ausbruch und erfüllt den MA-Filter wenn aktiviert.
- Long/Short: Konfigurierbar über
TradeDirection. - Ausstiegskriterien:
- Stop auf der gegenüberliegenden Seite des Eröffnungsbereichs.
- Take-Profit gemäß
TakeProfitType: festes Risiko-Ertrags-Verhältnis, gleitender Durchschnitt-Kreuzung oder beides.
- Stops: Ja, an Bereichsgrenzen.
- Standardwerte:
CutoffHour= 12CutoffMinute= 0TradeDirection= LongOnlyTakeProfitType= FixedRiskRewardTpRatio= 2.5MaType= SMAMaLength= 100CandleType= TimeSpan.FromMinutes(1)
- Filter:
- Kategorie: Ausbruch
- Richtung: Konfigurierbar
- Indikatoren: Moving Average
- Stops: Ja
- Komplexität: Mittel
- Zeitrahmen: Intraday
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
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>
/// NY opening range breakout with MA stop strategy.
/// The 9:30-9:45 (UTC candle open times) range is recorded each day. When the previous candle is the first to close beyond the range
/// high (low) before CutoffHour:CutoffMinute, the current candle enters long (short) if its close is on the same side of the moving
/// average and the side is allowed by TradeDirection. The stop sits at the opposite side of the range; the exit target follows
/// TakeProfitType: TpRatio times the risk, a close back across the moving average, or whichever comes first.
/// </summary>
public class NyOpeningRangeBreakoutMaStopStrategy : Strategy
{
/// <summary>
/// Allowed trade directions.
/// </summary>
public enum TradeDirections
{
/// <summary>
/// Long entries only.
/// </summary>
LongOnly,
/// <summary>
/// Short entries only.
/// </summary>
ShortOnly,
/// <summary>
/// Long and short entries.
/// </summary>
Both,
}
/// <summary>
/// Take profit modes.
/// </summary>
public enum TakeProfitTypes
{
/// <summary>
/// Fixed reward-to-risk target.
/// </summary>
FixedRiskReward,
/// <summary>
/// Exit when the close crosses the moving average.
/// </summary>
MaCross,
/// <summary>
/// Whichever of the two exits comes first.
/// </summary>
Both,
}
/// <summary>
/// Moving average types.
/// </summary>
public enum MaTypes
{
/// <summary>
/// Simple moving average.
/// </summary>
SMA,
/// <summary>
/// Exponential moving average.
/// </summary>
EMA,
/// <summary>
/// Smoothed moving average.
/// </summary>
SMMA,
/// <summary>
/// Weighted moving average.
/// </summary>
WMA,
}
private static readonly TimeSpan _rangeStart = new(9, 30, 0);
private static readonly TimeSpan _rangeEnd = new(9, 45, 0);
private readonly StrategyParam<int> _cutoffHour;
private readonly StrategyParam<int> _cutoffMinute;
private readonly StrategyParam<TradeDirections> _tradeDirection;
private readonly StrategyParam<TakeProfitTypes> _takeProfitType;
private readonly StrategyParam<decimal> _tpRatio;
private readonly StrategyParam<MaTypes> _maType;
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<DataType> _candleType;
private DateTime _currentDay;
private decimal? _rangeHigh;
private decimal? _rangeLow;
private decimal? _prevClose;
private decimal? _prevPrevClose;
private bool _prevBeforeCutoff;
private decimal? _stopPrice;
private decimal? _takePrice;
/// <summary>
/// Hour after which no new breakouts are taken.
/// </summary>
public int CutoffHour
{
get => _cutoffHour.Value;
set => _cutoffHour.Value = value;
}
/// <summary>
/// Minute of the cutoff time.
/// </summary>
public int CutoffMinute
{
get => _cutoffMinute.Value;
set => _cutoffMinute.Value = value;
}
/// <summary>
/// Allowed trade direction.
/// </summary>
public TradeDirections TradeDirection
{
get => _tradeDirection.Value;
set => _tradeDirection.Value = value;
}
/// <summary>
/// Take profit mode.
/// </summary>
public TakeProfitTypes TakeProfitType
{
get => _takeProfitType.Value;
set => _takeProfitType.Value = value;
}
/// <summary>
/// Reward-to-risk ratio of the fixed target.
/// </summary>
public decimal TpRatio
{
get => _tpRatio.Value;
set => _tpRatio.Value = value;
}
/// <summary>
/// Moving average type.
/// </summary>
public MaTypes MaType
{
get => _maType.Value;
set => _maType.Value = value;
}
/// <summary>
/// Moving average period.
/// </summary>
public int MaLength
{
get => _maLength.Value;
set => _maLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public NyOpeningRangeBreakoutMaStopStrategy()
{
_cutoffHour = Param(nameof(CutoffHour), 12)
.SetRange(0, 23)
.SetDisplay("Cutoff Hour", "Hour after which no new breakouts are taken", "Session");
_cutoffMinute = Param(nameof(CutoffMinute), 0)
.SetRange(0, 59)
.SetDisplay("Cutoff Minute", "Minute of the cutoff time", "Session");
_tradeDirection = Param(nameof(TradeDirection), TradeDirections.LongOnly)
.SetDisplay("Trade Direction", "Allowed trade direction", "Trading");
_takeProfitType = Param(nameof(TakeProfitType), TakeProfitTypes.FixedRiskReward)
.SetDisplay("Take Profit Type", "Fixed reward-to-risk, MA cross or both", "Risk");
_tpRatio = Param(nameof(TpRatio), 2.5m)
.SetGreaterThanZero()
.SetDisplay("TP Ratio", "Reward-to-risk ratio of the fixed target", "Risk");
_maType = Param(nameof(MaType), MaTypes.SMA)
.SetDisplay("MA Type", "Moving average type", "Moving Average");
_maLength = Param(nameof(MaLength), 100)
.SetGreaterThanZero()
.SetDisplay("MA Length", "Moving average period", "Moving Average");
_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();
ResetState();
}
private void ResetState()
{
_currentDay = default;
_rangeHigh = null;
_rangeLow = null;
_prevClose = null;
_prevPrevClose = null;
_prevBeforeCutoff = false;
_stopPrice = null;
_takePrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
DecimalLengthIndicator ma = MaType switch
{
MaTypes.EMA => new ExponentialMovingAverage(),
MaTypes.SMMA => new SmoothedMovingAverage(),
MaTypes.WMA => new WeightedMovingAverage(),
_ => new SimpleMovingAverage(),
};
ma.Length = MaLength;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal maValue)
{
if (candle.State != CandleStates.Finished)
return;
var day = candle.OpenTime.Date;
var tod = candle.OpenTime.TimeOfDay;
if (day != _currentDay)
{
_currentDay = day;
_rangeHigh = null;
_rangeLow = null;
_prevClose = null;
_prevPrevClose = null;
_prevBeforeCutoff = false;
}
var close = candle.ClosePrice;
if (tod >= _rangeStart && tod < _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;
return;
}
var prevClose = _prevClose;
var prevPrevClose = _prevPrevClose;
var prevBeforeCutoff = _prevBeforeCutoff;
var cutoff = new TimeSpan(CutoffHour, CutoffMinute, 0);
if (tod >= _rangeEnd)
{
_prevPrevClose = _prevClose;
_prevClose = close;
_prevBeforeCutoff = tod < cutoff;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
var useFixed = TakeProfitType != TakeProfitTypes.MaCross;
var useMa = TakeProfitType != TakeProfitTypes.FixedRiskReward;
if (Position > 0)
{
if ((_stopPrice is decimal stop && candle.LowPrice <= stop)
|| (useFixed && _takePrice is decimal take && candle.HighPrice >= take)
|| (useMa && close < maValue))
{
SellMarket(Position);
_stopPrice = null;
_takePrice = null;
return;
}
}
else if (Position < 0)
{
if ((_stopPrice is decimal stop && candle.HighPrice >= stop)
|| (useFixed && _takePrice is decimal take && candle.LowPrice <= take)
|| (useMa && close > maValue))
{
BuyMarket(-Position);
_stopPrice = null;
_takePrice = null;
return;
}
}
if (_rangeHigh is not decimal high || _rangeLow is not decimal low || prevClose is not decimal pc || !prevBeforeCutoff)
return;
// The previous candle must be the first close beyond the range.
var brokeUp = pc > high && (prevPrevClose is not decimal ppc || ppc <= high);
var brokeDown = pc < low && (prevPrevClose is not decimal ppc2 || ppc2 >= low);
var allowLong = TradeDirection != TradeDirections.ShortOnly;
var allowShort = TradeDirection != TradeDirections.LongOnly;
if (brokeUp && allowLong && close > maValue && close > low && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = low;
_takePrice = close + (close - low) * TpRatio;
}
else if (brokeDown && allowShort && close < maValue && close < high && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = high;
_takePrice = close - (high - close) * TpRatio;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import (SimpleMovingAverage, ExponentialMovingAverage, SmoothedMovingAverage,
WeightedMovingAverage)
from StockSharp.Algo.Strategies import Strategy
# Values matching the C# TradeDirections enum.
DIRECTION_LONG_ONLY = 0
DIRECTION_SHORT_ONLY = 1
DIRECTION_BOTH = 2
# Values matching the C# TakeProfitTypes enum.
TP_FIXED_RISK_REWARD = 0
TP_MA_CROSS = 1
TP_BOTH = 2
# Values matching the C# MaTypes enum.
MA_SMA = 0
MA_EMA = 1
MA_SMMA = 2
MA_WMA = 3
RANGE_START = TimeSpan(9, 30, 0)
RANGE_END = TimeSpan(9, 45, 0)
class ny_opening_range_breakout_ma_stop_strategy(Strategy):
"""
NY opening range breakout with MA stop strategy.
The 9:30-9:45 (UTC candle open times) range is recorded each day. When the previous candle is the first to close beyond the range
high (low) before CutoffHour:CutoffMinute, the current candle enters long (short) if its close is on the same side of the moving
average and the side is allowed by TradeDirection. The stop sits at the opposite side of the range; the exit target follows
TakeProfitType: TpRatio times the risk, a close back across the moving average, or whichever comes first.
"""
def __init__(self):
super(ny_opening_range_breakout_ma_stop_strategy, self).__init__()
self._cutoff_hour = self.Param("CutoffHour", 12).SetRange(0, 23).SetDisplay("Cutoff Hour", "Hour after which no new breakouts are taken", "Session")
self._cutoff_minute = self.Param("CutoffMinute", 0).SetRange(0, 59).SetDisplay("Cutoff Minute", "Minute of the cutoff time", "Session")
self._trade_direction = self.Param("TradeDirection", DIRECTION_LONG_ONLY).SetDisplay("Trade Direction", "Allowed trade direction (0 LongOnly, 1 ShortOnly, 2 Both)", "Trading")
self._take_profit_type = self.Param("TakeProfitType", TP_FIXED_RISK_REWARD).SetDisplay("Take Profit Type", "0 FixedRiskReward, 1 MaCross, 2 Both", "Risk")
self._tp_ratio = self.Param("TpRatio", 2.5).SetGreaterThanZero().SetDisplay("TP Ratio", "Reward-to-risk ratio of the fixed target", "Risk")
self._ma_type = self.Param("MaType", MA_SMA).SetDisplay("MA Type", "Moving average type (0 SMA, 1 EMA, 2 SMMA, 3 WMA)", "Moving Average")
self._ma_length = self.Param("MaLength", 100).SetGreaterThanZero().SetDisplay("MA Length", "Moving average period", "Moving Average")
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._current_day = None
self._range_high = None
self._range_low = None
self._prev_close = None
self._prev_prev_close = None
self._prev_before_cutoff = False
self._stop_price = None
self._take_price = None
def OnReseted(self):
super(ny_opening_range_breakout_ma_stop_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(ny_opening_range_breakout_ma_stop_strategy, self).OnStarted2(time)
self._reset_state()
ma_type = self._ma_type.Value
if ma_type == MA_EMA:
ma = ExponentialMovingAverage()
elif ma_type == MA_SMMA:
ma = SmoothedMovingAverage()
elif ma_type == MA_WMA:
ma = WeightedMovingAverage()
else:
ma = SimpleMovingAverage()
ma.Length = self._ma_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ma_value):
if candle.State != CandleStates.Finished:
return
day = candle.OpenTime.Date
tod = candle.OpenTime.TimeOfDay
if self._current_day is None or day != self._current_day:
self._current_day = day
self._range_high = None
self._range_low = None
self._prev_close = None
self._prev_prev_close = None
self._prev_before_cutoff = False
close = candle.ClosePrice
if tod >= RANGE_START and tod < 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
prev_close = self._prev_close
prev_prev_close = self._prev_prev_close
prev_before_cutoff = self._prev_before_cutoff
cutoff = TimeSpan(self._cutoff_hour.Value, self._cutoff_minute.Value, 0)
if tod >= RANGE_END:
self._prev_prev_close = self._prev_close
self._prev_close = close
self._prev_before_cutoff = tod < cutoff
if not self.IsFormedAndOnlineAndAllowTrading():
return
tp_type = self._take_profit_type.Value
use_fixed = tp_type != TP_MA_CROSS
use_ma = tp_type != TP_FIXED_RISK_REWARD
if self.Position > 0:
if ((self._stop_price is not None and candle.LowPrice <= self._stop_price)
or (use_fixed and self._take_price is not None and candle.HighPrice >= self._take_price)
or (use_ma and close < ma_value)):
self.SellMarket(self.Position)
self._stop_price = None
self._take_price = None
return
elif self.Position < 0:
if ((self._stop_price is not None and candle.HighPrice >= self._stop_price)
or (use_fixed and self._take_price is not None and candle.LowPrice <= self._take_price)
or (use_ma and close > ma_value)):
self.BuyMarket(-self.Position)
self._stop_price = None
self._take_price = None
return
if self._range_high is None or self._range_low is None or prev_close is None or not prev_before_cutoff:
return
high = self._range_high
low = self._range_low
# The previous candle must be the first close beyond the range.
broke_up = prev_close > high and (prev_prev_close is None or prev_prev_close <= high)
broke_down = prev_close < low and (prev_prev_close is None or prev_prev_close >= low)
direction = self._trade_direction.Value
allow_long = direction != DIRECTION_SHORT_ONLY
allow_short = direction != DIRECTION_LONG_ONLY
ratio = Decimal(self._tp_ratio.Value)
if broke_up and allow_long and close > ma_value and close > low and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = low
self._take_price = close + (close - low) * ratio
elif broke_down and allow_short and close < ma_value and close < high and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = high
self._take_price = close - (high - close) * ratio
def CreateClone(self):
return ny_opening_range_breakout_ma_stop_strategy()