Estrategia ADX Volume
Implementación de la estrategia ADX + Volume. Entrar en operaciones cuando el ADX está por encima del umbral con volumen superior al promedio. La dirección se determina por la comparación de DI+ y DI-.
Las pruebas indican un retorno anual promedio de aproximadamente el 67%. Funciona mejor en el mercado de acciones.
Un ADX alto denota una tendencia fuerte y los picos de volumen confirman el compromiso. Las entradas se realizan cuando ambos indicadores muestran fuerza simultáneamente.
Excelente para capturar rupturas enérgicas. Un stop basado en ATR mantiene la exposición bajo control.
Detalles
- Criterios de entrada:
- Largo:
ADX > AdxThreshold && Volume > AvgVolume - Corto:
ADX > AdxThreshold && Volume > AvgVolume
- Largo:
- Largo/Corto: Ambos
- Criterios de salida: La tendencia se debilita por debajo del umbral
- Stops: Basados en ATR usando
StopLoss - Valores predeterminados:
AdxPeriod= 14AdxThreshold= 25mVolumeAvgPeriod= 20StopLoss= new Unit(2, UnitTypes.Absolute)CandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filtros:
- Categoría: Ruptura
- Dirección: Ambos
- Indicadores: ADX, Volume
- Stops: Sí
- Complejidad: Intermedio
- Marco temporal: Medio plazo
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
using StockSharp.Algo;
using StockSharp.Algo.Candles;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Implementation of strategy - ADX + Volume.
/// Enter trades when ADX is above threshold with above average volume.
/// Direction determined by DI+ and DI- comparison.
/// </summary>
public class AdxVolumeStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<int> _volumeAvgPeriod;
private readonly StrategyParam<decimal> _volumeMultiplier;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<Unit> _stopLoss;
private readonly StrategyParam<DataType> _candleType;
// For volume tracking
private decimal _averageVolume;
private int _volumeCounter;
private int _cooldown;
/// <summary>
/// ADX period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// ADX threshold value to determine strong trend.
/// </summary>
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Volume average period.
/// </summary>
public int VolumeAvgPeriod
{
get => _volumeAvgPeriod.Value;
set => _volumeAvgPeriod.Value = value;
}
/// <summary>
/// Volume multiplier above average.
/// </summary>
public decimal VolumeMultiplier
{
get => _volumeMultiplier.Value;
set => _volumeMultiplier.Value = value;
}
/// <summary>
/// Bars to wait between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Stop-loss value.
/// </summary>
public Unit StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Candle type used for strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="AdxVolumeStrategy"/>.
/// </summary>
public AdxVolumeStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Period for ADX indicator", "ADX Parameters");
_adxThreshold = Param(nameof(AdxThreshold), 25m)
.SetRange(10, 50)
.SetDisplay("ADX Threshold", "Threshold above which trend is considered strong", "ADX Parameters");
_volumeAvgPeriod = Param(nameof(VolumeAvgPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Volume Average Period", "Period for volume moving average", "Volume Parameters");
_volumeMultiplier = Param(nameof(VolumeMultiplier), 1.4m)
.SetRange(1.0m, 3.0m)
.SetDisplay("Volume Multiplier", "Multiplier over average volume", "Volume Parameters");
_cooldownBars = Param(nameof(CooldownBars), 160)
.SetRange(5, 500)
.SetDisplay("Cooldown Bars", "Bars between trades", "General");
_stopLoss = Param(nameof(StopLoss), new Unit(2, UnitTypes.Absolute))
.SetDisplay("Stop Loss", "Stop loss in ATR or value", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle type for strategy", "General");
_averageVolume = 0;
_volumeCounter = 0;
_cooldown = 0;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_averageVolume = 0;
_volumeCounter = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create ADX indicator
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
// Setup candle subscription
var subscription = SubscribeCandles(CandleType);
// Bind ADX indicator to candles
subscription
.BindEx(adx, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, adx);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!adxValue.IsFormed)
return;
// Update average volume calculation
var currentVolume = candle.TotalVolume;
if (_volumeCounter < VolumeAvgPeriod)
{
_volumeCounter++;
_averageVolume = ((_averageVolume * (_volumeCounter - 1)) + currentVolume) / _volumeCounter;
}
else
{
_averageVolume = (_averageVolume * (VolumeAvgPeriod - 1) + currentVolume) / VolumeAvgPeriod;
}
if (_volumeCounter < VolumeAvgPeriod)
{
if (_cooldown > 0)
_cooldown--;
return;
}
var adxTyped = (AverageDirectionalIndexValue)adxValue;
var diPlusValue = adxTyped.Dx.Plus;
var diMinusValue = adxTyped.Dx.Minus;
var adxMa = adxTyped.MovingAverage;
// Check if volume is above average
var isVolumeAboveAverage = currentVolume > _averageVolume * VolumeMultiplier;
LogInfo($"Candle: {candle.OpenTime}, Close: {candle.ClosePrice}, " +
$"ADX: {adxMa}, DI+: {diPlusValue}, DI-: {diMinusValue}, " +
$"Volume: {currentVolume}, Avg Volume: {_averageVolume}");
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Trading rules
if (adxMa > AdxThreshold && isVolumeAboveAverage)
{
// Strong trend detected with above average volume
if (diPlusValue > diMinusValue && Position == 0)
{
// Bullish trend - DI+ > DI-
BuyMarket();
_cooldown = CooldownBars;
LogInfo($"Buy signal: Strong trend (ADX: {adxMa}) with DI+ > DI- and high volume.");
}
else if (diMinusValue > diPlusValue && Position == 0)
{
// Bearish trend - DI- > DI+
SellMarket();
_cooldown = CooldownBars;
LogInfo($"Sell signal: Strong trend (ADX: {adxMa}) with DI- > DI+ and high volume.");
}
}
// Exit conditions
else if (adxMa < AdxThreshold * 0.8m)
{
// Trend weakening - exit all positions
if (Position > 0)
{
SellMarket();
_cooldown = CooldownBars;
LogInfo($"Exit long: ADX weakening below {AdxThreshold * 0.8m}. Position: {Position}");
}
else if (Position < 0)
{
BuyMarket();
_cooldown = CooldownBars;
LogInfo($"Exit short: ADX weakening below {AdxThreshold * 0.8m}. Position: {Position}");
}
}
// Check if DI+/DI- cross to exit positions
else if (diPlusValue < diMinusValue && Position > 0)
{
// DI+ crosses below DI- while in long position
SellMarket();
_cooldown = CooldownBars;
LogInfo($"Exit long: DI+ crossed below DI-. Position: {Position}");
}
else if (diPlusValue > diMinusValue && Position < 0)
{
// DI+ crosses above DI- while in short position
BuyMarket();
_cooldown = CooldownBars;
LogInfo($"Exit short: DI+ crossed above DI-. Position: {Position}");
}
}
}
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_volume_strategy(Strategy):
"""
ADX + Volume strategy.
Enter trades when ADX is above threshold with above average volume.
"""
def __init__(self):
super(adx_volume_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 14) \
.SetDisplay("ADX Period", "Period for ADX indicator", "ADX Parameters")
self._adx_threshold = self.Param("AdxThreshold", 25.0) \
.SetRange(10, 50) \
.SetDisplay("ADX Threshold", "Threshold above which trend is considered strong", "ADX Parameters")
self._volume_avg_period = self.Param("VolumeAvgPeriod", 20) \
.SetDisplay("Volume Average Period", "Period for volume moving average", "Volume Parameters")
self._volume_multiplier = self.Param("VolumeMultiplier", 1.4) \
.SetRange(1.0, 3.0) \
.SetDisplay("Volume Multiplier", "Multiplier over average volume", "Volume Parameters")
self._cooldown_bars = self.Param("CooldownBars", 160) \
.SetRange(5, 500) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Candle type for strategy", "General")
self._average_volume = 0.0
self._volume_counter = 0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(adx_volume_strategy, self).OnStarted2(time)
self._average_volume = 0.0
self._volume_counter = 0
self._cooldown = 0
adx = AverageDirectionalIndex()
adx.Length = self._adx_period.Value
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.DrawIndicator(area, adx)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, adx_value):
if candle.State != CandleStates.Finished:
return
if not adx_value.IsFormed:
return
current_volume = float(candle.TotalVolume)
vol_avg_prd = self._volume_avg_period.Value
if self._volume_counter < vol_avg_prd:
self._volume_counter += 1
self._average_volume = ((self._average_volume * (self._volume_counter - 1)) + current_volume) / self._volume_counter
else:
self._average_volume = (self._average_volume * (vol_avg_prd - 1) + current_volume) / vol_avg_prd
if self._volume_counter < vol_avg_prd:
if self._cooldown > 0:
self._cooldown -= 1
return
# Get ADX values
di_plus = float(adx_value.Dx.Plus) if adx_value.Dx.Plus is not None else 0
di_minus = float(adx_value.Dx.Minus) if adx_value.Dx.Minus is not None else 0
adx_ma = float(adx_value.MovingAverage) if adx_value.MovingAverage is not None else 0
is_volume_above_avg = current_volume > self._average_volume * self._volume_multiplier.Value
if self._cooldown > 0:
self._cooldown -= 1
return
cd = self._cooldown_bars.Value
threshold = self._adx_threshold.Value
if adx_ma > threshold and is_volume_above_avg:
if di_plus > di_minus and self.Position == 0:
self.BuyMarket()
self._cooldown = cd
elif di_minus > di_plus and self.Position == 0:
self.SellMarket()
self._cooldown = cd
elif adx_ma < threshold * 0.8:
if self.Position > 0:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0:
self.BuyMarket()
self._cooldown = cd
elif di_plus < di_minus and self.Position > 0:
self.SellMarket()
self._cooldown = cd
elif di_plus > di_minus and self.Position < 0:
self.BuyMarket()
self._cooldown = cd
def OnReseted(self):
super(adx_volume_strategy, self).OnReseted()
self._average_volume = 0.0
self._volume_counter = 0
self._cooldown = 0
def CreateClone(self):
return adx_volume_strategy()