Прорыв по ADX
Стратегия ADX Breakout отслеживает резкие расширения индикатора ADX. Когда значения выходят за пределы типичного диапазона, цена часто начинает новое движение.
Тестирование показывает среднегодичную доходность около 97%. Стратегию лучше запускать на крипторынке.
Позиция открывается, как только индикатор пробивает диапазон, определённый по последним данным и множителю отклонения. Возможны сделки в обе стороны со стопом.
Система подходит трейдерам, стремящимся войти в ранние прорывы. Сделки закрываются, когда ADX возвращается к среднему. По умолчанию используется ADXPeriod = 14.
Подробности
- Условия входа: индикатор превышает среднее на величину множителя отклонения.
- Длинные/короткие: оба направления.
- Условия выхода: индикатор возвращается к среднему.
- Стопы: да.
- Значения по умолчанию:
ADXPeriod= 14AvgPeriod= 20Multiplier= 0.1mCandleType= TimeSpan.FromMinutes(5)StopLoss= 2.0m
- Фильтры:
- Категория: Breakout
- Направление: оба
- Индикаторы: ADX
- Стопы: да
- Сложность: средняя
- Таймфрейм: краткосрочный
- Сезонность: нет
- Нейросети: нет
- Дивергенция: нет
- Уровень риска: средний
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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that trades on ADX breakouts.
/// When ADX breaks out above its average, it enters position in the direction determined by price.
/// </summary>
public class ADXBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<int> _avgPeriod;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLoss;
private AverageDirectionalIndex _adx;
private SimpleMovingAverage _adxAverage;
private decimal _prevAdxValue;
private decimal _prevAdxAvgValue;
/// <summary>
/// ADX period.
/// </summary>
public int ADXPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Period for ADX average calculation.
/// </summary>
public int AvgPeriod
{
get => _avgPeriod.Value;
set => _avgPeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier for breakout detection.
/// </summary>
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <summary>
/// Candle type for strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Initialize <see cref="ADXBreakoutStrategy"/>.
/// </summary>
public ADXBreakoutStrategy()
{
_adxPeriod = Param(nameof(ADXPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Period for ADX indicator", "Indicators")
.SetOptimize(10, 30, 2);
_avgPeriod = Param(nameof(AvgPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Average Period", "Period for ADX average calculation", "Indicators")
.SetOptimize(10, 50, 5);
_multiplier = Param(nameof(Multiplier), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Multiplier", "Standard deviation multiplier for breakout detection", "Indicators")
.SetOptimize(0.0m, 1.0m, 0.1m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_stopLoss = Param(nameof(StopLoss), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop Loss percentage", "Risk Management")
.SetOptimize(1.0m, 5.0m, 0.5m);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevAdxValue = 0;
_prevAdxAvgValue = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
_adx = new AverageDirectionalIndex { Length = ADXPeriod };
_adxAverage = new SMA { Length = AvgPeriod };
// Create subscription and bind indicators
var subscription = SubscribeCandles(CandleType);
// First bind ADX to the candle subscription
subscription
.BindEx(_adx, ProcessAdx)
.Start();
// Enable stop loss protection
StartProtection(
takeProfit: new Unit(0, UnitTypes.Absolute),
stopLoss: new Unit(StopLoss, UnitTypes.Percent)
);
// Create chart area for visualization
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _adx);
DrawOwnTrades(area);
}
}
private void ProcessAdx(ICandleMessage candle, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!adxValue.IsFinal)
return;
// Get current ADX value
if (((AverageDirectionalIndexValue)adxValue).MovingAverage is not decimal currentAdx)
{
return;
}
// Process ADX through average indicator
var adxAvgValue = _adxAverage.Process(new DecimalIndicatorValue(_adxAverage, currentAdx, candle.ServerTime));
var currentAdxAvg = adxAvgValue.ToDecimal();
// For first values, just save and skip
if (_prevAdxValue == 0)
{
_prevAdxValue = currentAdx;
_prevAdxAvgValue = currentAdxAvg;
return;
}
// Calculate standard deviation of ADX (simplified approach)
var stdDev = Math.Abs(currentAdx - currentAdxAvg) * 2; // Simplified approximation
// Check if trading is allowed
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevAdxValue = currentAdx;
_prevAdxAvgValue = currentAdxAvg;
return;
}
// ADX breakout detection (ADX increases significantly above its average)
if (currentAdx > currentAdxAvg + Multiplier * stdDev)
{
// Determine direction based on price movement
var priceDirection = candle.ClosePrice > candle.OpenPrice;
// Cancel active orders before placing new ones
CancelActiveOrders();
// Trade in the direction of price movement
if (priceDirection && Position <= 0)
{
// Bullish breakout - Buy
BuyMarket(Volume + Math.Abs(Position));
}
else if (!priceDirection && Position >= 0)
{
// Bearish breakout - Sell
SellMarket(Volume + Math.Abs(Position));
}
}
// Check for exit condition - ADX returns to average
else if ((Position > 0 && currentAdx < currentAdxAvg) ||
(Position < 0 && currentAdx < currentAdxAvg))
{
// Exit position
ClosePosition();
}
// Update previous values
_prevAdxValue = currentAdx;
_prevAdxAvgValue = currentAdxAvg;
}
}
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, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import AverageDirectionalIndex, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class adx_breakout_strategy(Strategy):
"""
Strategy that trades on ADX breakouts.
When ADX breaks out above its average, it enters position in the direction determined by price.
"""
def __init__(self):
super(adx_breakout_strategy, self).__init__()
# Initialize strategy parameters
self._adx_period = self.Param("ADXPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ADX Period", "Period for ADX indicator", "Indicators") \
.SetCanOptimize(True) \
.SetOptimize(10, 30, 2)
self._avg_period = self.Param("AvgPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Average Period", "Period for ADX average calculation", "Indicators") \
.SetCanOptimize(True) \
.SetOptimize(10, 50, 5)
self._multiplier = self.Param("Multiplier", 0.1) \
.SetGreaterThanZero() \
.SetDisplay("Multiplier", "Standard deviation multiplier for breakout detection", "Indicators") \
.SetCanOptimize(True) \
.SetOptimize(0.0, 1.0, 0.1)
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._stop_loss = self.Param("StopLoss", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss %", "Stop Loss percentage", "Risk Management") \
.SetCanOptimize(True) \
.SetOptimize(1.0, 5.0, 0.5)
# Internal indicators
self._adx = None
self._adx_average = None
self._prev_adx_value = 0
self._prev_adx_avg_value = 0
@property
def ADXPeriod(self):
"""ADX period."""
return self._adx_period.Value
@ADXPeriod.setter
def ADXPeriod(self, value):
self._adx_period.Value = value
@property
def AvgPeriod(self):
"""Period for ADX average calculation."""
return self._avg_period.Value
@AvgPeriod.setter
def AvgPeriod(self, value):
self._avg_period.Value = value
@property
def Multiplier(self):
"""Standard deviation multiplier for breakout detection."""
return self._multiplier.Value
@Multiplier.setter
def Multiplier(self, value):
self._multiplier.Value = value
@property
def CandleType(self):
"""Candle type for strategy."""
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def StopLoss(self):
"""Stop-loss percentage."""
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
def OnReseted(self):
"""
Resets internal state when strategy is reset.
"""
super(adx_breakout_strategy, self).OnReseted()
self._prev_adx_value = 0
self._prev_adx_avg_value = 0
def OnStarted2(self, time):
"""Called when the strategy starts."""
super(adx_breakout_strategy, self).OnStarted2(time)
# Create indicators
self._adx = AverageDirectionalIndex()
self._adx.Length = self.ADXPeriod
self._adx_average = SimpleMovingAverage()
self._adx_average.Length = self.AvgPeriod
# Create subscription and bind indicators
subscription = self.SubscribeCandles(self.CandleType)
# First bind ADX to the candle subscription
subscription.BindEx(self._adx, self.ProcessAdx).Start()
# Enable stop loss protection
self.StartProtection(
takeProfit=Unit(0, UnitTypes.Absolute),
stopLoss=Unit(self.StopLoss, UnitTypes.Percent)
)
# Create chart area for visualization
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._adx)
self.DrawOwnTrades(area)
def ProcessAdx(self, candle, adxValue):
if candle.State != CandleStates.Finished:
return
if not adxValue.IsFinal:
return
# Get current ADX value
if adxValue.MovingAverage is None:
return
currentAdx = float(adxValue.MovingAverage)
# Process ADX through average indicator (no IsFinal, matching CS)
currentAdxAvg = float(process_float(self._adx_average, currentAdx, candle.ServerTime, True))
# For first values, just save and skip
if self._prev_adx_value == 0:
self._prev_adx_value = currentAdx
self._prev_adx_avg_value = currentAdxAvg
return
# Calculate standard deviation of ADX (simplified approach)
stdDev = Math.Abs(currentAdx - currentAdxAvg) * 2 # Simplified approximation
# Check if trading is allowed
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_adx_value = currentAdx
self._prev_adx_avg_value = currentAdxAvg
return
# ADX breakout detection (ADX increases significantly above its average)
if currentAdx > currentAdxAvg + self.Multiplier * stdDev:
# Determine direction based on price movement
priceDirection = candle.ClosePrice > candle.OpenPrice
# Cancel active orders before placing new ones
self.CancelActiveOrders()
# Trade in the direction of price movement
if priceDirection and self.Position <= 0:
# Bullish breakout - Buy
self.BuyMarket(self.Volume + Math.Abs(self.Position))
elif not priceDirection and self.Position >= 0:
# Bearish breakout - Sell
self.SellMarket(self.Volume + Math.Abs(self.Position))
# Check for exit condition - ADX returns to average
elif (self.Position > 0 and currentAdx < currentAdxAvg) or (
self.Position < 0 and currentAdx < currentAdxAvg):
# Exit position
self.ClosePosition()
# Update previous values
self._prev_adx_value = currentAdx
self._prev_adx_avg_value = currentAdxAvg
def CreateClone(self):
"""!! REQUIRED!! Creates a new instance of the strategy."""
return adx_breakout_strategy()