Estrategia de Ruptura de Rango ADX
Esta estrategia entra en posiciones largas cuando el cierre supera el cierre más alto de un período de retrospectiva mientras el ADX permanece por debajo de un umbral especificado, lo que indica un mercado en calma. Las operaciones se limitan a una sesión definida y a un número máximo de operaciones por día. Un stop-loss fijo en unidades de precio protege cada posición.
Detalles
- Criterios de entrada:
Close >= previous highest closeyADX < thresholddentro de la sesión - Largo/Corto: Solo largos
- Criterios de salida: Stop-loss o fin de sesión
- Stops: Sí
- Valores predeterminados:
AdxPeriod= 14HighestPeriod= 34AdxThreshold= 17.5StopLoss= 1000MaxTradesPerDay= 3CandleType= TimeSpan.FromMinutes(30)
- Filtros:
- Categoría: Ruptura
- Dirección: Solo largos
- Indicadores: ADX
- Stops: Sí
- Complejidad: Principiante
- Marco temporal: Intradía
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
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()