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 Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that buys breakouts when ADX is below a threshold.
/// Enters long if price closes above the previous highest close.
/// </summary>
public class AdxRangeBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _highestPeriod;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private decimal _prevHighest;
private int _cooldownRemaining;
public int HighestPeriod { get => _highestPeriod.Value; set => _highestPeriod.Value = value; }
public int AdxPeriod { get => _adxPeriod.Value; set => _adxPeriod.Value = value; }
public decimal AdxThreshold { get => _adxThreshold.Value; set => _adxThreshold.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public AdxRangeBreakoutStrategy()
{
_highestPeriod = Param(nameof(HighestPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Highest Lookback", "Bars for highest close", "Indicators");
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Period for ADX", "Indicators");
_adxThreshold = Param(nameof(AdxThreshold), 25m)
.SetDisplay("ADX Threshold", "Upper ADX limit for range", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 15)
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHighest = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
var highest = new Highest { Length = HighestPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(adx, highest, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue, IIndicatorValue highestValue)
{
if (candle.State != CandleStates.Finished)
return;
var curHighest = highestValue.ToDecimal();
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevHighest = curHighest;
return;
}
if (_prevHighest == 0)
{
_prevHighest = curHighest;
return;
}
var adxTyped = (IAverageDirectionalIndexValue)adxValue;
if (adxTyped.MovingAverage is not decimal adxMa)
{
_prevHighest = curHighest;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevHighest = curHighest;
return;
}
// Buy breakout when ADX is low (range-bound market breaking out)
if (Position == 0 && adxMa < AdxThreshold && candle.ClosePrice > _prevHighest)
{
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long when ADX rises (trend established, take profit)
else if (Position > 0 && (adxMa >= AdxThreshold * 1.5m || candle.ClosePrice < _prevHighest * 0.98m))
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
_prevHighest = curHighest;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageDirectionalIndex, Highest, IndicatorHelper
from StockSharp.Algo.Strategies import Strategy
class adx_range_breakout_strategy(Strategy):
def __init__(self):
super(adx_range_breakout_strategy, self).__init__()
self._highest_period = self.Param("HighestPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Highest Lookback", "Bars for highest close", "Indicators")
self._adx_period = self.Param("AdxPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ADX Period", "Period for ADX", "Indicators")
self._adx_threshold = self.Param("AdxThreshold", 25.0) \
.SetDisplay("ADX Threshold", "Upper ADX limit for range", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 15) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._prev_highest = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(adx_range_breakout_strategy, self).OnReseted()
self._prev_highest = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(adx_range_breakout_strategy, self).OnStarted2(time)
adx = AverageDirectionalIndex()
adx.Length = int(self._adx_period.Value)
highest = Highest()
highest.Length = int(self._highest_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(adx, highest, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle, adx_value, highest_value):
if candle.State != CandleStates.Finished:
return
cur_highest = float(IndicatorHelper.ToDecimal(highest_value))
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_highest = cur_highest
return
if self._prev_highest == 0:
self._prev_highest = cur_highest
return
adx_ma = adx_value.MovingAverage
if adx_ma is None:
self._prev_highest = cur_highest
return
adx_v = float(adx_ma)
close = float(candle.ClosePrice)
threshold = float(self._adx_threshold.Value)
cooldown = int(self._cooldown_bars.Value)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_highest = cur_highest
return
# Buy breakout when ADX is low
if self.Position == 0 and adx_v < threshold and close > self._prev_highest:
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
# Exit long when ADX rises
elif self.Position > 0 and (adx_v >= threshold * 1.5 or close < self._prev_highest * 0.98):
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
self._prev_highest = cur_highest
def CreateClone(self):
return adx_range_breakout_strategy()