Ma Adx Strategy
Strategy based on MA and ADX indicators. Enters position when price crosses MA with strong trend.
Testing indicates an average annual return of about 184%. It performs best in the crypto market.
The moving average dictates the trend, and ADX verifies whether it's strong enough to trade. Entries follow price crossings of the MA when ADX exceeds a threshold.
This classic trend approach appeals to systematic traders. Losses are managed with an ATR-based stop.
Details
- Entry Criteria:
- Long:
Close > MA && ADX > 25 - Short:
Close < MA && ADX > 25
- Long:
- Long/Short: Both
- Exit Criteria: Reverse MA cross or stop
- Stops:
StopLossPercentpercent with take profitTakeProfitAtrMultiplierATR - Default Values:
MaPeriod= 20AdxPeriod= 14CandleType= TimeSpan.FromMinutes(5).TimeFrame()StopLossPercent= 2mTakeProfitAtrMultiplier= 2m
- Filters:
- Category: Trend
- Direction: Both
- Indicators: Moving Average, ADX
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-term
- 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>
/// Strategy combining MA trend filter with manual ADX-like trend strength.
/// Enters when price crosses MA with strong directional movement.
/// </summary>
public class MaAdxStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<int> _cooldownBars;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private readonly List<decimal> _closes = new();
private int _cooldown;
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Moving Average period.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// ADX period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// ADX threshold for trend strength.
/// </summary>
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Strategy constructor.
/// </summary>
public MaAdxStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_maPeriod = Param(nameof(MaPeriod), 20)
.SetRange(10, 50)
.SetDisplay("MA Period", "Period of the Moving Average", "Indicators");
_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");
_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();
_highs.Clear();
_lows.Clear();
_closes.Clear();
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Use EMA as binding indicator to drive candle processing
var ma = new ExponentialMovingAverage { Length = MaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal maValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var high = candle.HighPrice;
var low = candle.LowPrice;
var close = candle.ClosePrice;
_highs.Add(high);
_lows.Add(low);
_closes.Add(close);
var adxPeriod = AdxPeriod;
// Need at least adxPeriod+1 bars
if (_closes.Count < adxPeriod + 2)
{
if (_cooldown > 0)
_cooldown--;
return;
}
// Manual ADX-like trend strength calculation
decimal sumTr = 0;
decimal sumDmPlus = 0;
decimal sumDmMinus = 0;
var count = _highs.Count;
var start = count - adxPeriod;
for (int i = start; i < count; i++)
{
var h = _highs[i];
var l = _lows[i];
var prevC = _closes[i - 1];
var prevH = _highs[i - 1];
var prevL = _lows[i - 1];
var tr = Math.Max(h - l, Math.Max(Math.Abs(h - prevC), Math.Abs(l - prevC)));
sumTr += tr;
var upMove = h - prevH;
var downMove = prevL - l;
if (upMove > downMove && upMove > 0)
sumDmPlus += upMove;
if (downMove > upMove && downMove > 0)
sumDmMinus += downMove;
}
decimal trendStrength = 0;
if (sumTr > 0)
{
var diPlus = 100m * sumDmPlus / sumTr;
var diMinus = 100m * sumDmMinus / sumTr;
var diSum = diPlus + diMinus;
trendStrength = diSum > 0 ? 100m * Math.Abs(diPlus - diMinus) / diSum : 0;
}
// Keep lists manageable
if (_highs.Count > adxPeriod * 3)
{
var trim = _highs.Count - adxPeriod * 2;
_highs.RemoveRange(0, trim);
_lows.RemoveRange(0, trim);
_closes.RemoveRange(0, trim);
}
var strongTrend = trendStrength > AdxThreshold;
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Long: price above MA + strong trend
if (close > maValue && strongTrend && Position == 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Short: price below MA + strong trend
else if (close < maValue && strongTrend && Position == 0)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit long: price crosses below MA
if (Position > 0 && close < maValue)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit short: price crosses above MA
else if (Position < 0 && close > maValue)
{
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class ma_adx_strategy(Strategy):
"""
Strategy combining MA trend filter with manual ADX-like trend strength.
Enters when price crosses MA with strong directional movement.
"""
def __init__(self):
super(ma_adx_strategy, self).__init__()
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._ma_period = self.Param("MaPeriod", 20) \
.SetRange(10, 50) \
.SetDisplay("MA Period", "Period of the Moving Average", "Indicators")
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._cooldown_bars = self.Param("CooldownBars", 100) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General") \
.SetRange(5, 500)
self._highs = []
self._lows = []
self._closes = []
self._cooldown = 0
@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 ma_period(self):
return self._ma_period.Value
@property
def adx_period(self):
return self._adx_period.Value
@property
def adx_threshold(self):
return self._adx_threshold.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
def OnStarted2(self, time):
super(ma_adx_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
self._closes = []
self._cooldown = 0
ma = ExponentialMovingAverage()
ma.Length = self.ma_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ma, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, ma_value):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
mv = float(ma_value)
self._highs.append(high)
self._lows.append(low)
self._closes.append(close)
adx_p = self.adx_period
# Need at least adxPeriod+2 bars
if len(self._closes) < adx_p + 2:
if self._cooldown > 0:
self._cooldown -= 1
return
# Manual ADX-like trend strength calculation
sum_tr = 0.0
sum_dm_plus = 0.0
sum_dm_minus = 0.0
count = len(self._highs)
start = count - adx_p
for i in range(start, count):
h = self._highs[i]
l = self._lows[i]
prev_c = self._closes[i - 1]
prev_h = self._highs[i - 1]
prev_l = self._lows[i - 1]
tr = max(h - l, max(abs(h - prev_c), abs(l - prev_c)))
sum_tr += tr
up_move = h - prev_h
down_move = prev_l - l
if up_move > down_move and up_move > 0:
sum_dm_plus += up_move
if down_move > up_move and down_move > 0:
sum_dm_minus += down_move
trend_strength = 0.0
if sum_tr > 0:
di_plus = 100.0 * sum_dm_plus / sum_tr
di_minus = 100.0 * sum_dm_minus / sum_tr
di_sum = di_plus + di_minus
trend_strength = 100.0 * abs(di_plus - di_minus) / di_sum if di_sum > 0 else 0.0
# Keep lists manageable
if len(self._highs) > adx_p * 3:
trim = len(self._highs) - adx_p * 2
self._highs = self._highs[trim:]
self._lows = self._lows[trim:]
self._closes = self._closes[trim:]
strong_trend = trend_strength > self.adx_threshold
if self._cooldown > 0:
self._cooldown -= 1
return
# Long: price above MA + strong trend
if close > mv and strong_trend and self.Position == 0:
self.BuyMarket()
self._cooldown = self.cooldown_bars
# Short: price below MA + strong trend
elif close < mv and strong_trend and self.Position == 0:
self.SellMarket()
self._cooldown = self.cooldown_bars
# Exit long: price crosses below MA
if self.Position > 0 and close < mv:
self.SellMarket()
self._cooldown = self.cooldown_bars
# Exit short: price crosses above MA
elif self.Position < 0 and close > mv:
self.BuyMarket()
self._cooldown = self.cooldown_bars
def OnReseted(self):
super(ma_adx_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._closes = []
self._cooldown = 0
def CreateClone(self):
return ma_adx_strategy()