Стратегия MA ADX
Стратегия основана на индикаторах скользящей средней и ADX. Вход выполняется, когда цена пересекает скользящую среднюю при наличии сильного тренда.
Тестирование показывает среднегодичную доходность около 184%. Стратегию лучше запускать на крипторынке.
Скользящая средняя задаёт направление, а ADX подтверждает, достаточно ли он силён для торговли. Позиции открываются по пересечению MA, если ADX выше порога.
Этот классический трендовый подход нравится системным трейдерам. Потери ограничиваются стопом по ATR.
Подробности
- Условия входа:
- Лонг:
Close > MA && ADX > 25 - Шорт:
Close < MA && ADX > 25
- Лонг:
- Длинные/короткие: обе стороны
- Условия выхода: обратное пересечение MA или стоп
- Стопы:
StopLossPercentпроцентов и тейк-профитTakeProfitAtrMultiplierATR - Значения по умолчанию:
MaPeriod= 20AdxPeriod= 14CandleType= TimeSpan.FromMinutes(5).TimeFrame()StopLossPercent= 2mTakeProfitAtrMultiplier= 2m
- Фильтры:
- Категория: Тренд
- Направление: Оба
- Индикаторы: Скользящая средняя, ADX
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Среднесрочный
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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()