ADX for BTC
Strategy uses Average Directional Index (ADX) with optional SMA trend filter to catch strong moves in Bitcoin.
Testing indicates an average annual return of about 80%. It performs best in the crypto market.
The system buys when ADX crosses above the entry level and the trend filter is bullish. The position closes when ADX falls below the exit level.
Details
- Entry Criteria: ADX crosses above
EntryLeveland (if enabled) fast SMA > slow SMA. - Long/Short: Long only.
- Exit Criteria: ADX crosses below
ExitLevel. - Stops: No.
- Default Values:
EntryLevel= 14mExitLevel= 45mSmaFilter= trueSmaLength= 200CandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Trend
- Direction: Long
- Indicators: ADX, SMA
- Stops: No
- Complexity: Basic
- Timeframe: Intraday (5m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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>
/// ADX for BTC strategy.
/// Long only: buys when ADX(14) crosses above EntryLevel while, with SmaFilter enabled, the close is above SMA(SmaLength), and closes
/// the long when ADX crosses below ExitLevel.
/// </summary>
public class AdxForBtcStrategy : Strategy
{
private const int _adxLength = 14;
private readonly StrategyParam<decimal> _entryLevel;
private readonly StrategyParam<decimal> _exitLevel;
private readonly StrategyParam<bool> _smaFilter;
private readonly StrategyParam<int> _smaLength;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevAdx;
/// <summary>
/// ADX level whose upward cross opens a long.
/// </summary>
public decimal EntryLevel
{
get => _entryLevel.Value;
set => _entryLevel.Value = value;
}
/// <summary>
/// ADX level whose downward cross closes the long.
/// </summary>
public decimal ExitLevel
{
get => _exitLevel.Value;
set => _exitLevel.Value = value;
}
/// <summary>
/// Require the close above the SMA.
/// </summary>
public bool SmaFilter
{
get => _smaFilter.Value;
set => _smaFilter.Value = value;
}
/// <summary>
/// SMA period of the trend filter.
/// </summary>
public int SmaLength
{
get => _smaLength.Value;
set => _smaLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public AdxForBtcStrategy()
{
_entryLevel = Param(nameof(EntryLevel), 14m)
.SetDisplay("Entry Level", "ADX level whose upward cross opens a long", "ADX");
_exitLevel = Param(nameof(ExitLevel), 45m)
.SetDisplay("Exit Level", "ADX level whose downward cross closes the long", "ADX");
_smaFilter = Param(nameof(SmaFilter), true)
.SetDisplay("SMA Filter", "Require the close above the SMA", "Filters");
_smaLength = Param(nameof(SmaLength), 200)
.SetGreaterThanZero()
.SetDisplay("SMA Length", "SMA period of the trend filter", "Filters");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
_prevAdx = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevAdx = null;
var adx = new AverageDirectionalIndex { Length = _adxLength };
var sma = new SimpleMovingAverage { Length = SmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(adx, sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, adx);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue, IIndicatorValue smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!adxValue.IsFormed || ((IAverageDirectionalIndexValue)adxValue).MovingAverage is not decimal adx)
return;
var prevAdx = _prevAdx;
_prevAdx = adx;
if (!smaValue.IsFormed || prevAdx is not decimal prev)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var trendOk = !SmaFilter || candle.ClosePrice > smaValue.ToDecimal();
if (Position == 0 && prev <= EntryLevel && adx > EntryLevel && trendOk)
BuyMarket(Volume);
else if (Position > 0 && prev >= ExitLevel && adx < ExitLevel)
SellMarket(Position);
}
}
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
from StockSharp.Algo.Indicators import AverageDirectionalIndex, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
ADX_LENGTH = 14
class adx_for_btc_strategy(Strategy):
"""
ADX for BTC strategy.
Long only: buys when ADX(14) crosses above EntryLevel while, with SmaFilter enabled, the close is above SMA(SmaLength), and closes
the long when ADX crosses below ExitLevel.
"""
def __init__(self):
super(adx_for_btc_strategy, self).__init__()
self._entry_level = self.Param("EntryLevel", 14.0).SetDisplay("Entry Level", "ADX level whose upward cross opens a long", "ADX")
self._exit_level = self.Param("ExitLevel", 45.0).SetDisplay("Exit Level", "ADX level whose downward cross closes the long", "ADX")
self._sma_filter = self.Param("SmaFilter", True).SetDisplay("SMA Filter", "Require the close above the SMA", "Filters")
self._sma_length = self.Param("SmaLength", 200).SetGreaterThanZero().SetDisplay("SMA Length", "SMA period of the trend filter", "Filters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_adx = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(adx_for_btc_strategy, self).OnReseted()
self._prev_adx = None
def OnStarted2(self, time):
super(adx_for_btc_strategy, self).OnStarted2(time)
self._prev_adx = None
adx = AverageDirectionalIndex()
adx.Length = ADX_LENGTH
sma = SimpleMovingAverage()
sma.Length = self._sma_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(adx, sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, adx)
def _process_candle(self, candle, adx_value, sma_value):
if candle.State != CandleStates.Finished:
return
if not adx_value.IsFormed or adx_value.MovingAverage is None:
return
adx = adx_value.MovingAverage
prev = self._prev_adx
self._prev_adx = adx
if not sma_value.IsFormed or prev is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
entry = Decimal(self._entry_level.Value)
exit_level = Decimal(self._exit_level.Value)
trend_ok = not self._sma_filter.Value or candle.ClosePrice > sma_value.GetValue[Decimal](None)
if self.Position == 0 and prev <= entry and adx > entry and trend_ok:
self.BuyMarket(self.Volume)
elif self.Position > 0 and prev >= exit_level and adx < exit_level:
self.SellMarket(self.Position)
def CreateClone(self):
return adx_for_btc_strategy()