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()
- 过滤器:
- 类别: 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()