纽约首根K线突破回测策略
本策略在纽约交易时段记录首根K线的高低点,当价格突破并回测该水平后入场。使用ATR设定止损与盈亏比目标,可选EMA趋势过滤和追踪止损。
细节
- 入场条件:价格突破首根K线的高点或低点并在
RetestThresholdATR内回测。 - 多空方向:均可。
- 出场条件:ATR止损与
RewardRiskRatio目标,可启用追踪止损。 - 止损:
AtrMultiplier× ATR。 - 默认参数:
NyStartHour= 9NyStartMinute= 30SessionLength= 4AtrPeriod= 14AtrMultiplier= 1.2RewardRiskRatio= 1.5MinBreakSize= 0.15RetestThreshold= 0.25UseEmaFilter= trueEmaLength= 13
- 筛选:
- 分类: Breakout
- 方向: 双向
- 指标: ATR, EMA
- 止损: ATR
- 复杂度: 中等
- 时间框架: 日内
- 季节性: 是
- 神经网络: 否
- 背离: 否
- 风险: 中
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 first candle break and retest strategy.
/// The candle opening at NyStartHour:NyStartMinute (UTC) defines the session range. During the next SessionLength hours a close
/// beyond its high or low by at least MinBreakSize ATR marks a breakout; a later candle that pulls back to within RetestThreshold
/// ATR of the broken level and closes beyond it enters in the breakout direction, optionally only on the matching side of the EMA.
/// The stop sits AtrMultiplier ATR from entry and the target RewardRiskRatio times that distance.
/// </summary>
public class NyFirstCandleBreakAndRetestStrategy : Strategy
{
private readonly StrategyParam<int> _nyStartHour;
private readonly StrategyParam<int> _nyStartMinute;
private readonly StrategyParam<int> _sessionLength;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<decimal> _rewardRiskRatio;
private readonly StrategyParam<decimal> _minBreakSize;
private readonly StrategyParam<decimal> _retestThreshold;
private readonly StrategyParam<bool> _useEmaFilter;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<DataType> _candleType;
private DateTime _currentDay;
private decimal? _rangeHigh;
private decimal? _rangeLow;
private bool _brokeUp;
private bool _brokeDown;
private decimal? _stopPrice;
private decimal? _takePrice;
/// <summary>
/// Session start hour (UTC).
/// </summary>
public int NyStartHour
{
get => _nyStartHour.Value;
set => _nyStartHour.Value = value;
}
/// <summary>
/// Session start minute.
/// </summary>
public int NyStartMinute
{
get => _nyStartMinute.Value;
set => _nyStartMinute.Value = value;
}
/// <summary>
/// Session length in hours.
/// </summary>
public int SessionLength
{
get => _sessionLength.Value;
set => _sessionLength.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Stop distance in ATR.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Target distance as a multiple of the stop distance.
/// </summary>
public decimal RewardRiskRatio
{
get => _rewardRiskRatio.Value;
set => _rewardRiskRatio.Value = value;
}
/// <summary>
/// Minimum breakout beyond the first candle in ATR.
/// </summary>
public decimal MinBreakSize
{
get => _minBreakSize.Value;
set => _minBreakSize.Value = value;
}
/// <summary>
/// Maximum retest distance from the broken level in ATR.
/// </summary>
public decimal RetestThreshold
{
get => _retestThreshold.Value;
set => _retestThreshold.Value = value;
}
/// <summary>
/// Require the close on the trade side of the EMA.
/// </summary>
public bool UseEmaFilter
{
get => _useEmaFilter.Value;
set => _useEmaFilter.Value = value;
}
/// <summary>
/// EMA period.
/// </summary>
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public NyFirstCandleBreakAndRetestStrategy()
{
_nyStartHour = Param(nameof(NyStartHour), 9)
.SetRange(0, 23)
.SetDisplay("Start Hour", "Session start hour (UTC)", "Session");
_nyStartMinute = Param(nameof(NyStartMinute), 30)
.SetRange(0, 59)
.SetDisplay("Start Minute", "Session start minute", "Session");
_sessionLength = Param(nameof(SessionLength), 4)
.SetGreaterThanZero()
.SetDisplay("Session Length", "Session length in hours", "Session");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period", "Risk");
_atrMultiplier = Param(nameof(AtrMultiplier), 1.2m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "Stop distance in ATR", "Risk");
_rewardRiskRatio = Param(nameof(RewardRiskRatio), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Reward/Risk", "Target distance as a multiple of the stop distance", "Risk");
_minBreakSize = Param(nameof(MinBreakSize), 0.15m)
.SetNotNegative()
.SetDisplay("Min Break Size", "Minimum breakout beyond the first candle in ATR", "Entry");
_retestThreshold = Param(nameof(RetestThreshold), 0.25m)
.SetNotNegative()
.SetDisplay("Retest Threshold", "Maximum retest distance from the broken level in ATR", "Entry");
_useEmaFilter = Param(nameof(UseEmaFilter), true)
.SetDisplay("Use EMA Filter", "Require the close on the trade side of the EMA", "Entry");
_emaLength = Param(nameof(EmaLength), 13)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA period", "Entry");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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;
_brokeUp = false;
_brokeDown = false;
_stopPrice = null;
_takePrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var atr = new AverageTrueRange { Length = AtrPeriod };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atr, decimal ema)
{
if (candle.State != CandleStates.Finished)
return;
var openTime = candle.OpenTime;
var day = openTime.Date;
if (day != _currentDay)
{
_currentDay = day;
_rangeHigh = null;
_rangeLow = null;
_brokeUp = false;
_brokeDown = false;
}
var sessionStart = day + new TimeSpan(NyStartHour, NyStartMinute, 0);
var sessionEnd = sessionStart + TimeSpan.FromHours(SessionLength);
if (openTime == sessionStart)
{
_rangeHigh = candle.HighPrice;
_rangeLow = candle.LowPrice;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
if (Position > 0 && _stopPrice is decimal longStop && _takePrice is decimal longTake)
{
if (candle.LowPrice <= longStop || candle.HighPrice >= longTake)
{
SellMarket(Position);
_stopPrice = null;
_takePrice = null;
return;
}
}
else if (Position < 0 && _stopPrice is decimal shortStop && _takePrice is decimal shortTake)
{
if (candle.HighPrice >= shortStop || candle.LowPrice <= shortTake)
{
BuyMarket(-Position);
_stopPrice = null;
_takePrice = null;
return;
}
}
if (openTime <= sessionStart || openTime >= sessionEnd || _rangeHigh is not decimal high || _rangeLow is not decimal low)
return;
var stopDistance = atr * AtrMultiplier;
if (_brokeUp && Position <= 0 && candle.LowPrice <= high + RetestThreshold * atr && close > high && (!UseEmaFilter || close > ema))
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - stopDistance;
_takePrice = close + stopDistance * RewardRiskRatio;
_brokeUp = false;
return;
}
if (_brokeDown && Position >= 0 && candle.HighPrice >= low - RetestThreshold * atr && close < low && (!UseEmaFilter || close < ema))
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + stopDistance;
_takePrice = close - stopDistance * RewardRiskRatio;
_brokeDown = false;
return;
}
// A breakout candle only arms the setup; the retest has to come on a later candle.
if (close > high + MinBreakSize * atr)
_brokeUp = true;
else if (close < low - MinBreakSize * atr)
_brokeDown = true;
}
}
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 AverageTrueRange, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ny_first_candle_break_and_retest_strategy(Strategy):
"""
NY first candle break and retest strategy.
The candle opening at NyStartHour:NyStartMinute (UTC) defines the session range. During the next SessionLength hours a close
beyond its high or low by at least MinBreakSize ATR marks a breakout; a later candle that pulls back to within RetestThreshold
ATR of the broken level and closes beyond it enters in the breakout direction, optionally only on the matching side of the EMA.
The stop sits AtrMultiplier ATR from entry and the target RewardRiskRatio times that distance.
"""
def __init__(self):
super(ny_first_candle_break_and_retest_strategy, self).__init__()
self._ny_start_hour = self.Param("NyStartHour", 9).SetRange(0, 23).SetDisplay("Start Hour", "Session start hour (UTC)", "Session")
self._ny_start_minute = self.Param("NyStartMinute", 30).SetRange(0, 59).SetDisplay("Start Minute", "Session start minute", "Session")
self._session_length = self.Param("SessionLength", 4).SetGreaterThanZero().SetDisplay("Session Length", "Session length in hours", "Session")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "ATR period", "Risk")
self._atr_multiplier = self.Param("AtrMultiplier", 1.2).SetGreaterThanZero().SetDisplay("ATR Multiplier", "Stop distance in ATR", "Risk")
self._reward_risk_ratio = self.Param("RewardRiskRatio", 1.5).SetGreaterThanZero().SetDisplay("Reward/Risk", "Target distance as a multiple of the stop distance", "Risk")
self._min_break_size = self.Param("MinBreakSize", 0.15).SetNotNegative().SetDisplay("Min Break Size", "Minimum breakout beyond the first candle in ATR", "Entry")
self._retest_threshold = self.Param("RetestThreshold", 0.25).SetNotNegative().SetDisplay("Retest Threshold", "Maximum retest distance from the broken level in ATR", "Entry")
self._use_ema_filter = self.Param("UseEmaFilter", True).SetDisplay("Use EMA Filter", "Require the close on the trade side of the EMA", "Entry")
self._ema_length = self.Param("EmaLength", 13).SetGreaterThanZero().SetDisplay("EMA Length", "EMA period", "Entry")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).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._broke_up = False
self._broke_down = False
self._stop_price = None
self._take_price = None
def OnReseted(self):
super(ny_first_candle_break_and_retest_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(ny_first_candle_break_and_retest_strategy, self).OnStarted2(time)
self._reset_state()
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
ema = ExponentialMovingAverage()
ema.Length = self._ema_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(atr, ema, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, atr, ema):
if candle.State != CandleStates.Finished:
return
open_time = candle.OpenTime
day = open_time.Date
if self._current_day is None or day != self._current_day:
self._current_day = day
self._range_high = None
self._range_low = None
self._broke_up = False
self._broke_down = False
session_start = day.Add(TimeSpan(self._ny_start_hour.Value, self._ny_start_minute.Value, 0))
session_end = session_start.Add(TimeSpan.FromHours(self._session_length.Value))
if open_time == session_start:
self._range_high = candle.HighPrice
self._range_low = candle.LowPrice
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
if self.Position > 0 and self._stop_price is not None and self._take_price is not None:
if candle.LowPrice <= self._stop_price or candle.HighPrice >= self._take_price:
self.SellMarket(self.Position)
self._stop_price = None
self._take_price = None
return
elif self.Position < 0 and self._stop_price is not None and self._take_price is not None:
if candle.HighPrice >= self._stop_price or candle.LowPrice <= self._take_price:
self.BuyMarket(-self.Position)
self._stop_price = None
self._take_price = None
return
if open_time <= session_start or open_time >= session_end or self._range_high is None or self._range_low is None:
return
high = self._range_high
low = self._range_low
stop_distance = atr * Decimal(self._atr_multiplier.Value)
reward_risk = Decimal(self._reward_risk_ratio.Value)
retest = Decimal(self._retest_threshold.Value) * atr
use_ema = self._use_ema_filter.Value
if self._broke_up and self.Position <= 0 and candle.LowPrice <= high + retest and close > high and (not use_ema or close > ema):
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - stop_distance
self._take_price = close + stop_distance * reward_risk
self._broke_up = False
return
if self._broke_down and self.Position >= 0 and candle.HighPrice >= low - retest and close < low and (not use_ema or close < ema):
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + stop_distance
self._take_price = close - stop_distance * reward_risk
self._broke_down = False
return
# A breakout candle only arms the setup; the retest has to come on a later candle.
min_break = Decimal(self._min_break_size.Value) * atr
if close > high + min_break:
self._broke_up = True
elif close < low - min_break:
self._broke_down = True
def CreateClone(self):
return ny_first_candle_break_and_retest_strategy()