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 based long-only strategy for BTC.
/// Enters when ADX crosses above entry level, exits when ADX crosses below exit level.
/// </summary>
public class AdxForBtcStrategy : Strategy
{
private readonly StrategyParam<decimal> _entryLevel;
private readonly StrategyParam<decimal> _exitLevel;
private readonly StrategyParam<int> _smaLength;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private decimal _prevAdx;
private int _cooldownRemaining;
public decimal EntryLevel { get => _entryLevel.Value; set => _entryLevel.Value = value; }
public decimal ExitLevel { get => _exitLevel.Value; set => _exitLevel.Value = value; }
public int SmaLength { get => _smaLength.Value; set => _smaLength.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public AdxForBtcStrategy()
{
_entryLevel = Param(nameof(EntryLevel), 14m)
.SetGreaterThanZero()
.SetDisplay("Entry Level", "ADX threshold for entry", "Strategy");
_exitLevel = Param(nameof(ExitLevel), 40m)
.SetGreaterThanZero()
.SetDisplay("Exit Level", "ADX threshold for exit", "Strategy");
_smaLength = Param(nameof(SmaLength), 50)
.SetGreaterThanZero()
.SetDisplay("SMA Length", "Length for trend SMA", "Strategy");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_cooldownBars = Param(nameof(CooldownBars), 10)
.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();
_prevAdx = 0m;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var adx = new AverageDirectionalIndex { Length = 14 };
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);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue, IIndicatorValue smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var adxTyped = (IAverageDirectionalIndexValue)adxValue;
if (adxTyped.MovingAverage is not decimal adxMa ||
adxTyped.Dx.Plus is not decimal diPlus ||
adxTyped.Dx.Minus is not decimal diMinus)
return;
var smaVal = smaValue.ToDecimal();
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevAdx = adxMa;
return;
}
// Enter long when ADX crosses above entry level with +DI > -DI and price above SMA
if (_prevAdx > 0 && _prevAdx <= EntryLevel && adxMa > EntryLevel && diPlus > diMinus && candle.ClosePrice > smaVal && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Enter short when ADX crosses above entry level with -DI > +DI and price below SMA
else if (_prevAdx > 0 && _prevAdx <= EntryLevel && adxMa > EntryLevel && diMinus > diPlus && candle.ClosePrice < smaVal && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit when ADX drops below exit level
else if (Position > 0 && adxMa < ExitLevel && _prevAdx >= ExitLevel)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
else if (Position < 0 && adxMa < ExitLevel && _prevAdx >= ExitLevel)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
_prevAdx = adxMa;
}
}
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, SimpleMovingAverage, IndicatorHelper
from StockSharp.Algo.Strategies import Strategy
class adx_for_btc_strategy(Strategy):
def __init__(self):
super(adx_for_btc_strategy, self).__init__()
self._entry_level = self.Param("EntryLevel", 14.0) \
.SetGreaterThanZero() \
.SetDisplay("Entry Level", "ADX threshold for entry", "Strategy")
self._exit_level = self.Param("ExitLevel", 40.0) \
.SetGreaterThanZero() \
.SetDisplay("Exit Level", "ADX threshold for exit", "Strategy")
self._sma_length = self.Param("SmaLength", 50) \
.SetGreaterThanZero() \
.SetDisplay("SMA Length", "Length for trend SMA", "Strategy")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._prev_adx = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(adx_for_btc_strategy, self).OnReseted()
self._prev_adx = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(adx_for_btc_strategy, self).OnStarted2(time)
adx = AverageDirectionalIndex()
adx.Length = 14
sma = SimpleMovingAverage()
sma.Length = int(self._sma_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(adx, sma, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _on_process(self, candle, adx_value, sma_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
adx_ma = adx_value.MovingAverage
if adx_ma is None:
return
dx = adx_value.Dx
di_plus_val = dx.Plus
di_minus_val = dx.Minus
if di_plus_val is None or di_minus_val is None:
return
adx_v = float(adx_ma)
di_plus = float(di_plus_val)
di_minus = float(di_minus_val)
sma_v = float(IndicatorHelper.ToDecimal(sma_value))
close = float(candle.ClosePrice)
entry = float(self._entry_level.Value)
exit_lv = float(self._exit_level.Value)
cooldown = int(self._cooldown_bars.Value)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_adx = adx_v
return
if self._prev_adx > 0 and self._prev_adx <= entry and adx_v > entry and di_plus > di_minus and close > sma_v and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif self._prev_adx > 0 and self._prev_adx <= entry and adx_v > entry and di_minus > di_plus and close < sma_v and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
elif self.Position > 0 and adx_v < exit_lv and self._prev_adx >= exit_lv:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and adx_v < exit_lv and self._prev_adx >= exit_lv:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
self._prev_adx = adx_v
def CreateClone(self):
return adx_for_btc_strategy()