Estrategia Adx Stochastic
Estrategia que combina el ADX (Índice Direccional Promedio) para la fuerza de la tendencia y el Oscilador Stochastic para el momento de entrada con condiciones de sobreventa/sobrecompra.
Las pruebas indican un retorno anual promedio de aproximadamente 172%. Funciona mejor en el mercado de divisas.
El ADX resalta la fuerza de la tendencia mientras el Stochastic identifica retrocesos. Las señales largas o cortas aparecen cuando el momentum gira mientras el ADX permanece alto.
Es adecuado para traders que combinan el seguimiento de tendencia con el timing del oscilador. Los stops protectores basados en ATR ayudan a controlar los drawdowns.
Detalles
- Criterios de entrada:
- Largo:
ADX > AdxThreshold && StochK < StochOversold && Bullish - Corto:
ADX > AdxThreshold && StochK > StochOverbought && Bearish
- Largo:
- Largo/Corto: Ambos
- Criterios de salida:
- Salir cuando
ADX < AdxThreshold
- Salir cuando
- Stops: Basado en porcentaje en
StopLossPercent - Valores predeterminados:
AdxPeriod= 14AdxThreshold= 25mStochPeriod= 14StochK= 3StochD= 3StochOversold= 20mStochOverbought= 80mStopLossPercent= 2.0mCandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filtros:
- Categoría: Reversión a la media
- Dirección: Ambos
- Indicadores: ADX, Stochastic Oscillator
- Stops: Sí
- Complejidad: Intermedio
- Marco temporal: Medio plazo
- 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 combining ADX for trend strength and manual Stochastic %K for entry timing.
/// Enters when ADX shows strong trend and Stochastic is oversold/overbought.
/// </summary>
public class AdxStochasticStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<decimal> _stochOversold;
private readonly StrategyParam<decimal> _stochOverbought;
private readonly StrategyParam<int> _cooldownBars;
private decimal _adxValue;
private int _cooldown;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private const int StochPeriod = 14;
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// ADX period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// ADX threshold for strong trend.
/// </summary>
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Stochastic oversold level.
/// </summary>
public decimal StochOversold
{
get => _stochOversold.Value;
set => _stochOversold.Value = value;
}
/// <summary>
/// Stochastic overbought level.
/// </summary>
public decimal StochOverbought
{
get => _stochOverbought.Value;
set => _stochOverbought.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Strategy constructor.
/// </summary>
public AdxStochasticStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetRange(7, 21)
.SetDisplay("ADX Period", "Period of the ADX indicator", "Indicators");
_adxThreshold = Param(nameof(AdxThreshold), 25m)
.SetDisplay("ADX Threshold", "ADX level for strong trend", "Indicators");
_stochOversold = Param(nameof(StochOversold), 20m)
.SetDisplay("Stochastic Oversold", "Level considered oversold", "Indicators");
_stochOverbought = Param(nameof(StochOverbought), 80m)
.SetDisplay("Stochastic Overbought", "Level considered overbought", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 100)
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
.SetRange(5, 500);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_adxValue = 0;
_cooldown = 0;
_highs.Clear();
_lows.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(adx, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var adxArea = CreateChartArea();
if (adxArea != null)
DrawIndicator(adxArea, adx);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Extract ADX value
if (adxValue is AverageDirectionalIndexValue typedAdx && typedAdx.MovingAverage is decimal adx)
_adxValue = adx;
if (_adxValue == 0)
return;
// Track highs/lows for manual stochastic
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
var maxBuf = StochPeriod * 2;
if (_highs.Count > maxBuf)
{
_highs.RemoveRange(0, _highs.Count - maxBuf);
_lows.RemoveRange(0, _lows.Count - maxBuf);
}
if (_highs.Count < StochPeriod)
return;
// Manual Stochastic %K
var start = _highs.Count - StochPeriod;
var highestHigh = decimal.MinValue;
var lowestLow = decimal.MaxValue;
for (var i = start; i < _highs.Count; i++)
{
if (_highs[i] > highestHigh) highestHigh = _highs[i];
if (_lows[i] < lowestLow) lowestLow = _lows[i];
}
var diff = highestHigh - lowestLow;
if (diff == 0) return;
var stochK = 100m * (candle.ClosePrice - lowestLow) / diff;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var strongTrend = _adxValue > AdxThreshold;
// Long: strong trend + stochastic oversold
if (strongTrend && stochK < StochOversold && Position == 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Short: strong trend + stochastic overbought
else if (strongTrend && stochK > StochOverbought && Position == 0)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit when trend weakens
if (!strongTrend && Position > 0)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (!strongTrend && Position < 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageDirectionalIndex
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class adx_stochastic_strategy(Strategy):
"""
Strategy combining ADX for trend strength and manual Stochastic %K for entry timing.
Enters when ADX shows strong trend and Stochastic is oversold/overbought.
"""
def __init__(self):
super(adx_stochastic_strategy, self).__init__()
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._adx_period = self.Param("AdxPeriod", 14) \
.SetRange(7, 21) \
.SetDisplay("ADX Period", "Period of the ADX indicator", "Indicators")
self._adx_threshold = self.Param("AdxThreshold", 25.0) \
.SetDisplay("ADX Threshold", "ADX level for strong trend", "Indicators")
self._stoch_oversold = self.Param("StochOversold", 20.0) \
.SetDisplay("Stochastic Oversold", "Level considered oversold", "Indicators")
self._stoch_overbought = self.Param("StochOverbought", 80.0) \
.SetDisplay("Stochastic Overbought", "Level considered overbought", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 100) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General") \
.SetRange(5, 500)
self._adx_value = 0.0
self._cooldown = 0
self._highs = []
self._lows = []
self._STOCH_PERIOD = 14
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
@property
def adx_period(self):
return self._adx_period.Value
@property
def adx_threshold(self):
return self._adx_threshold.Value
@property
def stoch_oversold(self):
return self._stoch_oversold.Value
@property
def stoch_overbought(self):
return self._stoch_overbought.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
def OnStarted2(self, time):
super(adx_stochastic_strategy, self).OnStarted2(time)
self._adx_value = 0.0
self._cooldown = 0
self._highs = []
self._lows = []
adx = AverageDirectionalIndex()
adx.Length = self.adx_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(adx, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
adx_area = self.CreateChartArea()
if adx_area is not None:
self.DrawIndicator(adx_area, adx)
def ProcessCandle(self, candle, adx_value):
if candle.State != CandleStates.Finished:
return
# Extract ADX value
if hasattr(adx_value, 'MovingAverage') and adx_value.MovingAverage is not None:
self._adx_value = float(adx_value.MovingAverage)
if self._adx_value == 0:
return
# Track highs/lows for manual stochastic
self._highs.append(float(candle.HighPrice))
self._lows.append(float(candle.LowPrice))
max_buf = self._STOCH_PERIOD * 2
if len(self._highs) > max_buf:
self._highs = self._highs[-max_buf:]
self._lows = self._lows[-max_buf:]
if len(self._highs) < self._STOCH_PERIOD:
return
# Manual Stochastic %K
start = len(self._highs) - self._STOCH_PERIOD
highest_high = max(self._highs[start:])
lowest_low = min(self._lows[start:])
diff = highest_high - lowest_low
if diff == 0:
return
close = float(candle.ClosePrice)
stoch_k = 100.0 * (close - lowest_low) / diff
if self._cooldown > 0:
self._cooldown -= 1
return
strong_trend = self._adx_value > self.adx_threshold
# Long: strong trend + stochastic oversold
if strong_trend and stoch_k < self.stoch_oversold and self.Position == 0:
self.BuyMarket()
self._cooldown = self.cooldown_bars
# Short: strong trend + stochastic overbought
elif strong_trend and stoch_k > self.stoch_overbought and self.Position == 0:
self.SellMarket()
self._cooldown = self.cooldown_bars
# Exit when trend weakens
if not strong_trend and self.Position > 0:
self.SellMarket()
self._cooldown = self.cooldown_bars
elif not strong_trend and self.Position < 0:
self.BuyMarket()
self._cooldown = self.cooldown_bars
def OnReseted(self):
super(adx_stochastic_strategy, self).OnReseted()
self._adx_value = 0.0
self._cooldown = 0
self._highs = []
self._lows = []
def CreateClone(self):
return adx_stochastic_strategy()