ADX レンジブレイクアウト戦略
この戦略は、ADX が指定されたしきい値を下回って静かな市場を示している間に終値がルックバック期間の最高終値を上回ったときにロングポジションに参入します。取引は定義されたセッションと1日の最大取引数に制限されます。固定のストップロス(価格単位)がすべてのポジションを保護します。
詳細
- エントリー条件: セッション内で
Close >= previous highest closeかつADX < threshold - ロング/ショート: ロングのみ
- エグジット条件: ストップロスまたはセッション終了
- ストップ: あり
- デフォルト値:
AdxPeriod= 14HighestPeriod= 34AdxThreshold= 17.5StopLoss= 1000MaxTradesPerDay= 3CandleType= TimeSpan.FromMinutes(30)
- フィルター:
- カテゴリ: ブレイクアウト
- 方向: ロングのみ
- インジケーター: ADX
- ストップ: あり
- 複雑さ: 初心者
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// ADX Range Breakout strategy.
/// Buys when the close reaches the highest close of the previous HighestPeriod candles while ADX is below AdxThreshold, at most
/// MaxTradesPerDay times per trading day (UTC). A fixed StopLoss in price units protects the position and it is closed on the last
/// candle of the day.
/// </summary>
public class AdxRangeBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<int> _highestPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<int> _maxTradesPerDay;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _closes = [];
private DateTime _currentDay;
private int _tradesToday;
/// <summary>
/// ADX period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Previous candles whose highest close must be reached.
/// </summary>
public int HighestPeriod
{
get => _highestPeriod.Value;
set => _highestPeriod.Value = value;
}
/// <summary>
/// ADX level the market must stay below.
/// </summary>
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Stop loss distance in price units.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Maximum entries per trading day.
/// </summary>
public int MaxTradesPerDay
{
get => _maxTradesPerDay.Value;
set => _maxTradesPerDay.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public AdxRangeBreakoutStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "ADX period", "Indicators");
_highestPeriod = Param(nameof(HighestPeriod), 34)
.SetGreaterThanZero()
.SetDisplay("Highest Period", "Previous candles whose highest close must be reached", "Indicators");
_adxThreshold = Param(nameof(AdxThreshold), 17.5m)
.SetDisplay("ADX Threshold", "ADX level the market must stay below", "Indicators");
_stopLoss = Param(nameof(StopLoss), 1000m)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop loss distance in price units", "Risk");
_maxTradesPerDay = Param(nameof(MaxTradesPerDay), 3)
.SetGreaterThanZero()
.SetDisplay("Max Trades Per Day", "Maximum entries per trading day", "Risk");
_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();
_closes.Clear();
_currentDay = default;
_tradesToday = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_closes.Clear();
_currentDay = default;
_tradesToday = 0;
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(adx, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLoss, UnitTypes.Absolute), useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, adx);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
decimal? previousHighest = _closes.Count >= HighestPeriod ? _closes.Max() : null;
_closes.Add(close);
if (_closes.Count > HighestPeriod)
_closes.RemoveAt(0);
var day = candle.OpenTime.Date;
if (day != _currentDay)
{
_currentDay = day;
_tradesToday = 0;
}
if (!adxValue.IsFormed || ((IAverageDirectionalIndexValue)adxValue).MovingAverage is not decimal adx)
return;
if (previousHighest is not decimal highest || !IsFormedAndOnlineAndAllowTrading())
return;
var frame = CandleType.Arg is TimeSpan tf ? tf : TimeSpan.Zero;
var lastOfDay = (candle.OpenTime + frame).Date > day;
if (Position > 0)
{
if (lastOfDay)
SellMarket(Position);
return;
}
if (!lastOfDay && Position == 0 && _tradesToday < MaxTradesPerDay && close >= highest && adx < AdxThreshold)
{
BuyMarket(Volume);
_tradesToday++;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import AverageDirectionalIndex
from StockSharp.Algo.Strategies import Strategy
class adx_range_breakout_strategy(Strategy):
"""
ADX Range Breakout strategy.
Buys when the close reaches the highest close of the previous HighestPeriod candles while ADX is below AdxThreshold, at most
MaxTradesPerDay times per trading day (UTC). A fixed StopLoss in price units protects the position and it is closed on the last
candle of the day.
"""
def __init__(self):
super(adx_range_breakout_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 14).SetGreaterThanZero().SetDisplay("ADX Period", "ADX period", "Indicators")
self._highest_period = self.Param("HighestPeriod", 34).SetGreaterThanZero().SetDisplay("Highest Period", "Previous candles whose highest close must be reached", "Indicators")
self._adx_threshold = self.Param("AdxThreshold", 17.5).SetDisplay("ADX Threshold", "ADX level the market must stay below", "Indicators")
self._stop_loss = self.Param("StopLoss", 1000.0).SetNotNegative().SetDisplay("Stop Loss", "Stop loss distance in price units", "Risk")
self._max_trades_per_day = self.Param("MaxTradesPerDay", 3).SetGreaterThanZero().SetDisplay("Max Trades Per Day", "Maximum entries per trading day", "Risk")
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._closes = []
self._current_day = None
self._trades_today = 0
def OnReseted(self):
super(adx_range_breakout_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(adx_range_breakout_strategy, self).OnStarted2(time)
self._reset_state()
adx = AverageDirectionalIndex()
adx.Length = self._adx_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(adx, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss.Value), UnitTypes.Absolute), useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, adx)
def _process_candle(self, candle, adx_value):
if candle.State != CandleStates.Finished:
return
period = self._highest_period.Value
close = candle.ClosePrice
previous_highest = max(self._closes) if len(self._closes) >= period else None
self._closes.append(close)
while len(self._closes) > period:
self._closes.pop(0)
day = candle.OpenTime.Date
if self._current_day is None or day != self._current_day:
self._current_day = day
self._trades_today = 0
if not adx_value.IsFormed or adx_value.MovingAverage is None:
return
adx = adx_value.MovingAverage
if previous_highest is None or not self.IsFormedAndOnlineAndAllowTrading():
return
frame = self.candle_type.Arg if isinstance(self.candle_type.Arg, TimeSpan) else TimeSpan.Zero
last_of_day = candle.OpenTime.Add(frame).Date > day
if self.Position > 0:
if last_of_day:
self.SellMarket(self.Position)
return
if (not last_of_day and self.Position == 0 and self._trades_today < self._max_trades_per_day.Value
and close >= previous_highest and adx < Decimal(self._adx_threshold.Value)):
self.BuyMarket(self.Volume)
self._trades_today += 1
def CreateClone(self):
return adx_range_breakout_strategy()