Стратегия Heiken Ashi Supertrend Adx
Стратегия объединяет свечи Heiken Ashi, направление Supertrend и опциональный фильтр ADX. Бычья свеча Heiken Ashi без нижней тени открывает длинную позицию в восходящем тренде. Медвежья свеча без верхней тени открывает короткую позицию в нисходящем тренде. Позиции закрываются по противоположному сигналу или ATR-трейлинг-стопу.
Тестирование показывает среднегодовую доходность около 128%. Лучше всего работает на рынке криптовалют.
Heiken Ashi сглаживает шум, а Supertrend и ADX подтверждают направление. ATR задаёт динамические стопы.
Детали
- Критерии входа:
- Long: бычья HA-свеча без нижней тени с опциональным подтверждением Supertrend и ADX
- Short: медвежья HA-свеча без верхней тени с опциональным подтверждением Supertrend и ADX
- Long/Short: Оба направления
- Критерии выхода: противоположная свеча или ATR-трейлинг-стоп
- Стопы: ATR-трейлинг-стоп
- Значения по умолчанию:
UseSupertrend= trueAtrPeriod= 10SupertrendMultiplier= 3mUseAdxFilter= falseAdxPeriod= 14AdxThreshold= 25mTrailAtrMultiplier= 2mCandleType= TimeSpan.FromMinutes(15).TimeFrame()
- Фильтры:
- Категория: Trend
- Направление: Оба
- Индикаторы: Heiken Ashi, Supertrend, ADX, ATR
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Среднесрочный
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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>
/// Heiken Ashi Supertrend ADX strategy.
/// A bullish Heiken Ashi candle without a lower wick buys and a bearish one without an upper wick sells. With UseSupertrend the
/// Supertrend (AtrPeriod, SupertrendMultiplier) must point the same way, and with UseAdxFilter the ADX must be above AdxThreshold.
/// An opposite Heiken Ashi candle closes the position, reversing it when the entry filters agree. Positions also exit on an ATR
/// trailing stop TrailAtrMultiplier ATRs behind the close.
/// </summary>
public class HeikenAshiSupertrendAdxStrategy : Strategy
{
private readonly StrategyParam<bool> _useSupertrend;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _supertrendMultiplier;
private readonly StrategyParam<bool> _useAdxFilter;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<decimal> _trailAtrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal? _haOpen;
private decimal? _haClose;
private decimal? _upperBand;
private decimal? _lowerBand;
private decimal? _prevClose;
private int _trend;
private decimal _trailStop;
/// <summary>
/// Require the Supertrend direction for entries.
/// </summary>
public bool UseSupertrend
{
get => _useSupertrend.Value;
set => _useSupertrend.Value = value;
}
/// <summary>
/// ATR period for Supertrend and the trailing stop.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Supertrend ATR multiplier.
/// </summary>
public decimal SupertrendMultiplier
{
get => _supertrendMultiplier.Value;
set => _supertrendMultiplier.Value = value;
}
/// <summary>
/// Require ADX above the threshold for entries.
/// </summary>
public bool UseAdxFilter
{
get => _useAdxFilter.Value;
set => _useAdxFilter.Value = value;
}
/// <summary>
/// ADX period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Minimum ADX value for entries.
/// </summary>
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Trailing stop distance in ATRs.
/// </summary>
public decimal TrailAtrMultiplier
{
get => _trailAtrMultiplier.Value;
set => _trailAtrMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public HeikenAshiSupertrendAdxStrategy()
{
_useSupertrend = Param(nameof(UseSupertrend), true)
.SetDisplay("Use Supertrend", "Require the Supertrend direction for entries", "Filters");
_atrPeriod = Param(nameof(AtrPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period for Supertrend and the trailing stop", "Indicators");
_supertrendMultiplier = Param(nameof(SupertrendMultiplier), 3m)
.SetGreaterThanZero()
.SetDisplay("Supertrend Multiplier", "Supertrend ATR multiplier", "Indicators");
_useAdxFilter = Param(nameof(UseAdxFilter), false)
.SetDisplay("Use ADX Filter", "Require ADX above the threshold for entries", "Filters");
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "ADX period", "Indicators");
_adxThreshold = Param(nameof(AdxThreshold), 25m)
.SetDisplay("ADX Threshold", "Minimum ADX value for entries", "Filters");
_trailAtrMultiplier = Param(nameof(TrailAtrMultiplier), 2m)
.SetNotNegative()
.SetDisplay("Trail ATR Multiplier", "Trailing stop distance in ATRs", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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();
ResetState();
}
private void ResetState()
{
_haOpen = null;
_haClose = null;
_upperBand = null;
_lowerBand = null;
_prevClose = null;
_trend = 1;
_trailStop = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var atr = new AverageTrueRange { Length = AtrPeriod };
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(atr, adx, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, adx);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue atrValue, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
// Heiken Ashi candle.
var haClose = (candle.OpenPrice + candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 4m;
var haOpen = _haOpen is decimal prevHaOpen && _haClose is decimal prevHaClose
? (prevHaOpen + prevHaClose) / 2m
: (candle.OpenPrice + candle.ClosePrice) / 2m;
var haHigh = Math.Max(candle.HighPrice, Math.Max(haOpen, haClose));
var haLow = Math.Min(candle.LowPrice, Math.Min(haOpen, haClose));
_haOpen = haOpen;
_haClose = haClose;
if (!atrValue.IsFormed)
{
_prevClose = candle.ClosePrice;
return;
}
var atr = atrValue.GetValue<decimal>();
var close = candle.ClosePrice;
// Supertrend on the regular candles.
var hl2 = (candle.HighPrice + candle.LowPrice) / 2m;
var lower = hl2 - SupertrendMultiplier * atr;
var upper = hl2 + SupertrendMultiplier * atr;
if (_lowerBand is decimal prevLower && _prevClose is decimal pc1 && pc1 > prevLower)
lower = Math.Max(lower, prevLower);
if (_upperBand is decimal prevUpper && _prevClose is decimal pc2 && pc2 < prevUpper)
upper = Math.Min(upper, prevUpper);
if (_trend < 0 && _upperBand is decimal lastUpper && close > lastUpper)
_trend = 1;
else if (_trend > 0 && _lowerBand is decimal lastLower && close < lastLower)
_trend = -1;
_lowerBand = lower;
_upperBand = upper;
_prevClose = close;
if (!adxValue.IsFormed || adxValue is not IAverageDirectionalIndexValue { MovingAverage: decimal adx })
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var bullishCandle = haClose > haOpen && haLow >= haOpen;
var bearishCandle = haClose < haOpen && haHigh <= haOpen;
var adxOk = !UseAdxFilter || adx > AdxThreshold;
var longSignal = bullishCandle && (!UseSupertrend || _trend > 0) && adxOk;
var shortSignal = bearishCandle && (!UseSupertrend || _trend < 0) && adxOk;
var trailDistance = TrailAtrMultiplier * atr;
if (Position > 0)
{
if (TrailAtrMultiplier > 0 && candle.LowPrice <= _trailStop)
{
SellMarket(Position);
return;
}
if (bearishCandle)
{
if (shortSignal)
{
SellMarket(Volume + Math.Abs(Position));
_trailStop = close + trailDistance;
}
else
{
SellMarket(Position);
}
return;
}
_trailStop = Math.Max(_trailStop, close - trailDistance);
}
else if (Position < 0)
{
if (TrailAtrMultiplier > 0 && candle.HighPrice >= _trailStop)
{
BuyMarket(-Position);
return;
}
if (bullishCandle)
{
if (longSignal)
{
BuyMarket(Volume + Math.Abs(Position));
_trailStop = close - trailDistance;
}
else
{
BuyMarket(-Position);
}
return;
}
_trailStop = Math.Min(_trailStop, close + trailDistance);
}
else if (longSignal)
{
BuyMarket(Volume);
_trailStop = close - trailDistance;
}
else if (shortSignal)
{
SellMarket(Volume);
_trailStop = close + trailDistance;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange, AverageDirectionalIndex
from StockSharp.Algo.Strategies import Strategy
class heiken_ashi_supertrend_adx_strategy(Strategy):
"""
Heiken Ashi Supertrend ADX strategy.
A bullish Heiken Ashi candle without a lower wick buys and a bearish one without an upper wick sells. With UseSupertrend the
Supertrend (AtrPeriod, SupertrendMultiplier) must point the same way, and with UseAdxFilter the ADX must be above AdxThreshold.
An opposite Heiken Ashi candle closes the position, reversing it when the entry filters agree. Positions also exit on an ATR
trailing stop TrailAtrMultiplier ATRs behind the close.
"""
def __init__(self):
super(heiken_ashi_supertrend_adx_strategy, self).__init__()
self._use_supertrend = self.Param("UseSupertrend", True).SetDisplay("Use Supertrend", "Require the Supertrend direction for entries", "Filters")
self._atr_period = self.Param("AtrPeriod", 10).SetGreaterThanZero().SetDisplay("ATR Period", "ATR period for Supertrend and the trailing stop", "Indicators")
self._supertrend_multiplier = self.Param("SupertrendMultiplier", 3.0).SetGreaterThanZero().SetDisplay("Supertrend Multiplier", "Supertrend ATR multiplier", "Indicators")
self._use_adx_filter = self.Param("UseAdxFilter", False).SetDisplay("Use ADX Filter", "Require ADX above the threshold for entries", "Filters")
self._adx_period = self.Param("AdxPeriod", 14).SetGreaterThanZero().SetDisplay("ADX Period", "ADX period", "Indicators")
self._adx_threshold = self.Param("AdxThreshold", 25.0).SetDisplay("ADX Threshold", "Minimum ADX value for entries", "Filters")
self._trail_atr_multiplier = self.Param("TrailAtrMultiplier", 2.0).SetNotNegative().SetDisplay("Trail ATR Multiplier", "Trailing stop distance in ATRs", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._ha_open = None
self._ha_close = None
self._upper_band = None
self._lower_band = None
self._prev_close = None
self._trend = 1
self._trail_stop = 0.0
def OnReseted(self):
super(heiken_ashi_supertrend_adx_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(heiken_ashi_supertrend_adx_strategy, self).OnStarted2(time)
self._reset_state()
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
adx = AverageDirectionalIndex()
adx.Length = self._adx_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(atr, adx, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, adx)
def _process_candle(self, candle, atr_value, adx_value):
if candle.State != CandleStates.Finished:
return
open_price = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
# Heiken Ashi candle.
ha_close = (open_price + high + low + close) / 4.0
if self._ha_open is not None and self._ha_close is not None:
ha_open = (self._ha_open + self._ha_close) / 2.0
else:
ha_open = (open_price + close) / 2.0
ha_high = max(high, ha_open, ha_close)
ha_low = min(low, ha_open, ha_close)
self._ha_open = ha_open
self._ha_close = ha_close
if not atr_value.IsFormed:
self._prev_close = close
return
atr = float(atr_value.GetValue[Decimal](None))
multiplier = float(self._supertrend_multiplier.Value)
# Supertrend on the regular candles.
hl2 = (high + low) / 2.0
lower = hl2 - multiplier * atr
upper = hl2 + multiplier * atr
if self._lower_band is not None and self._prev_close is not None and self._prev_close > self._lower_band:
lower = max(lower, self._lower_band)
if self._upper_band is not None and self._prev_close is not None and self._prev_close < self._upper_band:
upper = min(upper, self._upper_band)
if self._trend < 0 and self._upper_band is not None and close > self._upper_band:
self._trend = 1
elif self._trend > 0 and self._lower_band is not None and close < self._lower_band:
self._trend = -1
self._lower_band = lower
self._upper_band = upper
self._prev_close = close
if not adx_value.IsFormed or adx_value.MovingAverage is None:
return
adx = float(adx_value.MovingAverage)
if not self.IsFormedAndOnlineAndAllowTrading():
return
bullish_candle = ha_close > ha_open and ha_low >= ha_open
bearish_candle = ha_close < ha_open and ha_high <= ha_open
adx_ok = (not self._use_adx_filter.Value) or adx > float(self._adx_threshold.Value)
use_st = self._use_supertrend.Value
long_signal = bullish_candle and ((not use_st) or self._trend > 0) and adx_ok
short_signal = bearish_candle and ((not use_st) or self._trend < 0) and adx_ok
trail_mult = float(self._trail_atr_multiplier.Value)
trail_distance = trail_mult * atr
if self.Position > 0:
if trail_mult > 0 and low <= self._trail_stop:
self.SellMarket(self.Position)
return
if bearish_candle:
if short_signal:
self.SellMarket(self.Volume + abs(self.Position))
self._trail_stop = close + trail_distance
else:
self.SellMarket(self.Position)
return
self._trail_stop = max(self._trail_stop, close - trail_distance)
elif self.Position < 0:
if trail_mult > 0 and high >= self._trail_stop:
self.BuyMarket(-self.Position)
return
if bullish_candle:
if long_signal:
self.BuyMarket(self.Volume + abs(self.Position))
self._trail_stop = close - trail_distance
else:
self.BuyMarket(-self.Position)
return
self._trail_stop = min(self._trail_stop, close + trail_distance)
elif long_signal:
self.BuyMarket(self.Volume)
self._trail_stop = close - trail_distance
elif short_signal:
self.SellMarket(self.Volume)
self._trail_stop = close + trail_distance
def CreateClone(self):
return heiken_ashi_supertrend_adx_strategy()