Heiken Ashi Supertrend ADX戦略
Heiken Ashiローソク足、Supertrendの方向、およびオプションのADXフィルターを組み合わせた戦略です。下ヒゲのない強気のHeiken Ashiローソク足は、上昇トレンドでロングを開きます。上ヒゲのない弱気のローソク足は、下降トレンドでショートを開きます。ポジションは逆シグナルまたはATRベースのトレーリングストップで決済されます。
テストでは年平均リターンが約128%であることが示されています。暗号資産市場で最もよく機能します。
Heiken Ashiはノイズを平滑化し、SupertrendとADXが方向を確認します。ATRが動的なストップを決定します。
詳細
- エントリー条件:
- ロング: 下ヒゲのない強気のHAローソク足、オプションのSupertrend上昇とADX確認あり
- ショート: 上ヒゲのない弱気のHAローソク足、オプションのSupertrend下降とADX確認あり
- ロング/ショート: 両方
- エグジット条件: 逆方向のローソク足またはATRトレーリングストップ
- ストップ: ATRトレーリングストップ
- デフォルト値:
UseSupertrend= trueAtrPeriod= 10SupertrendMultiplier= 3mUseAdxFilter= falseAdxPeriod= 14AdxThreshold= 25mTrailAtrMultiplier= 2mCandleType= TimeSpan.FromMinutes(15).TimeFrame()
- フィルター:
- カテゴリ: トレンド
- 方向: 両方
- インジケーター: 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()