高値安値ブレイクアウトATRトレーリングストップ戦略
この戦略はセッション最初の30分間のレンジのブレイクアウトを取引します。価格が初期の高値または安値を越えると、ATRベースのトレーリングストップを伴うポジションが開かれます。すべてのポジションは指定されたイントラデイ時刻に決済されます。
詳細
- エントリー条件:
- ロング: 終値が最初の30分の高値を上抜け
- ショート: 終値が最初の30分の安値を下抜け
- ロング/ショート: 設定可能(
Direction)。 - エグジット条件:
- ATRトレーリングストップまたは対称ターゲット
ExitHour:ExitMinuteにすべてのポジションを決済
- ストップ: あり、ATRベース。
- デフォルト値:
AtrPeriod= 14AtrMultiplier= 3.5mRiskPerTrade= 2mAccountSize= 10000mSessionStartHour= 9SessionStartMinute= 15ExitHour= 15ExitMinute= 15CandleType= TimeSpan.FromMinutes(30)
- フィルター:
- カテゴリ: ブレイクアウト
- 方向: 設定可能
- インジケーター: 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>
/// High-Low Breakout ATR Trailing Stop strategy.
/// The first candle opening at or after SessionStartHour:SessionStartMinute (UTC) sets the day's opening range. A close crossing
/// above its high buys and a close crossing below its low sells, within the allowed Direction. The stop trails AtrMultiplier ATRs
/// behind the close and a target sits the same distance from the entry. The position size risks RiskPerTrade percent of
/// AccountSize on the stop distance. Everything is closed at ExitHour:ExitMinute and no new trades open after it.
/// </summary>
public class HighLowBreakoutAtrTrailingStopStrategy : Strategy
{
/// <summary>
/// Allowed trade directions.
/// </summary>
public enum TradeDirections
{
/// <summary>
/// Long trades only.
/// </summary>
Long,
/// <summary>
/// Short trades only.
/// </summary>
Short,
/// <summary>
/// Both directions.
/// </summary>
Both,
}
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<decimal> _riskPerTrade;
private readonly StrategyParam<decimal> _accountSize;
private readonly StrategyParam<int> _sessionStartHour;
private readonly StrategyParam<int> _sessionStartMinute;
private readonly StrategyParam<int> _exitHour;
private readonly StrategyParam<int> _exitMinute;
private readonly StrategyParam<TradeDirections> _direction;
private readonly StrategyParam<DataType> _candleType;
private DateTime _rangeDate;
private decimal? _rangeHigh;
private decimal? _rangeLow;
private decimal? _prevClose;
private decimal _stopPrice;
private decimal _targetPrice;
/// <summary>
/// ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Trailing stop and target distance in ATRs.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Percent of the account risked per trade.
/// </summary>
public decimal RiskPerTrade
{
get => _riskPerTrade.Value;
set => _riskPerTrade.Value = value;
}
/// <summary>
/// Account size used for position sizing.
/// </summary>
public decimal AccountSize
{
get => _accountSize.Value;
set => _accountSize.Value = value;
}
/// <summary>
/// Session start hour (UTC).
/// </summary>
public int SessionStartHour
{
get => _sessionStartHour.Value;
set => _sessionStartHour.Value = value;
}
/// <summary>
/// Session start minute.
/// </summary>
public int SessionStartMinute
{
get => _sessionStartMinute.Value;
set => _sessionStartMinute.Value = value;
}
/// <summary>
/// Hour all positions are closed (UTC).
/// </summary>
public int ExitHour
{
get => _exitHour.Value;
set => _exitHour.Value = value;
}
/// <summary>
/// Minute all positions are closed.
/// </summary>
public int ExitMinute
{
get => _exitMinute.Value;
set => _exitMinute.Value = value;
}
/// <summary>
/// Allowed trade directions.
/// </summary>
public TradeDirections Direction
{
get => _direction.Value;
set => _direction.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public HighLowBreakoutAtrTrailingStopStrategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period", "Indicators");
_atrMultiplier = Param(nameof(AtrMultiplier), 3.5m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "Trailing stop and target distance in ATRs", "Risk");
_riskPerTrade = Param(nameof(RiskPerTrade), 2m)
.SetGreaterThanZero()
.SetDisplay("Risk Per Trade %", "Percent of the account risked per trade", "Risk");
_accountSize = Param(nameof(AccountSize), 10000m)
.SetGreaterThanZero()
.SetDisplay("Account Size", "Account size used for position sizing", "Risk");
_sessionStartHour = Param(nameof(SessionStartHour), 9)
.SetRange(0, 23)
.SetDisplay("Session Start Hour", "Session start hour (UTC)", "Session");
_sessionStartMinute = Param(nameof(SessionStartMinute), 15)
.SetRange(0, 59)
.SetDisplay("Session Start Minute", "Session start minute", "Session");
_exitHour = Param(nameof(ExitHour), 15)
.SetRange(0, 23)
.SetDisplay("Exit Hour", "Hour all positions are closed (UTC)", "Session");
_exitMinute = Param(nameof(ExitMinute), 15)
.SetRange(0, 59)
.SetDisplay("Exit Minute", "Minute all positions are closed", "Session");
_direction = Param(nameof(Direction), TradeDirections.Both)
.SetDisplay("Direction", "Allowed trade directions", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).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()
{
_rangeDate = default;
_rangeHigh = null;
_rangeLow = null;
_prevClose = null;
_stopPrice = 0m;
_targetPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private decimal GetEntryVolume(decimal stopDistance)
{
var volume = stopDistance > 0 ? AccountSize * RiskPerTrade / 100m / stopDistance : 0m;
var step = Security?.VolumeStep ?? 0m;
if (step > 0)
volume = Math.Floor(volume / step) * step;
var min = Security?.MinVolume ?? 0m;
if (volume <= 0 || volume < min)
return Volume;
var max = Security?.MaxVolume;
if (max is decimal maxVolume && maxVolume > 0 && volume > maxVolume)
volume = maxVolume;
return volume;
}
private void ProcessCandle(ICandleMessage candle, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
var openTime = candle.OpenTime;
var timeOfDay = openTime.TimeOfDay;
var sessionStart = new TimeSpan(SessionStartHour, SessionStartMinute, 0);
var exitTime = new TimeSpan(ExitHour, ExitMinute, 0);
var close = candle.ClosePrice;
var prevClose = _prevClose;
_prevClose = close;
// The first candle of the session sets the opening range.
if (_rangeDate != openTime.Date && timeOfDay >= sessionStart)
{
_rangeDate = openTime.Date;
_rangeHigh = candle.HighPrice;
_rangeLow = candle.LowPrice;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (timeOfDay >= exitTime || timeOfDay < sessionStart)
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
return;
}
var distance = AtrMultiplier * atr;
if (Position > 0)
{
if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _targetPrice)
{
SellMarket(Position);
return;
}
_stopPrice = Math.Max(_stopPrice, close - distance);
return;
}
if (Position < 0)
{
if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _targetPrice)
{
BuyMarket(-Position);
return;
}
_stopPrice = Math.Min(_stopPrice, close + distance);
return;
}
if (_rangeDate != openTime.Date || _rangeHigh is not decimal high || _rangeLow is not decimal low || prevClose is not decimal lastClose)
return;
if (Direction != TradeDirections.Short && lastClose <= high && close > high)
{
BuyMarket(GetEntryVolume(distance));
_stopPrice = close - distance;
_targetPrice = close + distance;
}
else if (Direction != TradeDirections.Long && lastClose >= low && close < low)
{
SellMarket(GetEntryVolume(distance));
_stopPrice = close + distance;
_targetPrice = close - distance;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
# Direction values, matching the C# TradeDirections enum.
DIRECTION_LONG = 0
DIRECTION_SHORT = 1
DIRECTION_BOTH = 2
class high_low_breakout_atr_trailing_stop_strategy(Strategy):
"""
High-Low Breakout ATR Trailing Stop strategy.
The first candle opening at or after SessionStartHour:SessionStartMinute (UTC) sets the day's opening range. A close crossing
above its high buys and a close crossing below its low sells, within the allowed Direction. The stop trails AtrMultiplier ATRs
behind the close and a target sits the same distance from the entry. The position size risks RiskPerTrade percent of
AccountSize on the stop distance. Everything is closed at ExitHour:ExitMinute and no new trades open after it.
"""
def __init__(self):
super(high_low_breakout_atr_trailing_stop_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "ATR period", "Indicators")
self._atr_multiplier = self.Param("AtrMultiplier", 3.5).SetGreaterThanZero().SetDisplay("ATR Multiplier", "Trailing stop and target distance in ATRs", "Risk")
self._risk_per_trade = self.Param("RiskPerTrade", 2.0).SetGreaterThanZero().SetDisplay("Risk Per Trade %", "Percent of the account risked per trade", "Risk")
self._account_size = self.Param("AccountSize", 10000.0).SetGreaterThanZero().SetDisplay("Account Size", "Account size used for position sizing", "Risk")
self._session_start_hour = self.Param("SessionStartHour", 9).SetRange(0, 23).SetDisplay("Session Start Hour", "Session start hour (UTC)", "Session")
self._session_start_minute = self.Param("SessionStartMinute", 15).SetRange(0, 59).SetDisplay("Session Start Minute", "Session start minute", "Session")
self._exit_hour = self.Param("ExitHour", 15).SetRange(0, 23).SetDisplay("Exit Hour", "Hour all positions are closed (UTC)", "Session")
self._exit_minute = self.Param("ExitMinute", 15).SetRange(0, 59).SetDisplay("Exit Minute", "Minute all positions are closed", "Session")
self._direction = self.Param("Direction", DIRECTION_BOTH).SetDisplay("Direction", "Allowed trade directions (0 Long, 1 Short, 2 Both)", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).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._range_date = None
self._range_high = None
self._range_low = None
self._prev_close = None
self._stop_price = 0.0
self._target_price = 0.0
def OnReseted(self):
super(high_low_breakout_atr_trailing_stop_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(high_low_breakout_atr_trailing_stop_strategy, self).OnStarted2(time)
self._reset_state()
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _get_entry_volume(self, stop_distance):
risk = float(self._account_size.Value) * float(self._risk_per_trade.Value) / 100.0
volume = risk / stop_distance if stop_distance > 0 else 0.0
sec = self.Security
step = float(sec.VolumeStep) if sec is not None and sec.VolumeStep is not None else 0.0
if step > 0:
volume = math.floor(volume / step) * step
min_volume = float(sec.MinVolume) if sec is not None and sec.MinVolume is not None else 0.0
if volume <= 0 or volume < min_volume:
return self.Volume
max_volume = float(sec.MaxVolume) if sec is not None and sec.MaxVolume is not None else 0.0
if max_volume > 0 and volume > max_volume:
volume = max_volume
return Decimal(round(volume, 8))
def _process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
open_time = candle.OpenTime
minutes = open_time.Hour * 60 + open_time.Minute
session_start = self._session_start_hour.Value * 60 + self._session_start_minute.Value
exit_time = self._exit_hour.Value * 60 + self._exit_minute.Value
close = float(candle.ClosePrice)
high_price = float(candle.HighPrice)
low_price = float(candle.LowPrice)
prev_close = self._prev_close
self._prev_close = close
# The first candle of the session sets the opening range.
if self._range_date != open_time.Date and minutes >= session_start:
self._range_date = open_time.Date
self._range_high = high_price
self._range_low = low_price
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if minutes >= exit_time or minutes < session_start:
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(-self.Position)
return
distance = float(self._atr_multiplier.Value) * float(atr_value)
if self.Position > 0:
if low_price <= self._stop_price or high_price >= self._target_price:
self.SellMarket(self.Position)
return
self._stop_price = max(self._stop_price, close - distance)
return
if self.Position < 0:
if high_price >= self._stop_price or low_price <= self._target_price:
self.BuyMarket(-self.Position)
return
self._stop_price = min(self._stop_price, close + distance)
return
if self._range_date != open_time.Date or self._range_high is None or self._range_low is None or prev_close is None:
return
direction = int(self._direction.Value)
if direction != DIRECTION_SHORT and prev_close <= self._range_high and close > self._range_high:
self.BuyMarket(self._get_entry_volume(distance))
self._stop_price = close - distance
self._target_price = close + distance
elif direction != DIRECTION_LONG and prev_close >= self._range_low and close < self._range_low:
self.SellMarket(self._get_entry_volume(distance))
self._stop_price = close + distance
self._target_price = close - distance
def CreateClone(self):
return high_low_breakout_atr_trailing_stop_strategy()