Heikin Ashi V2 Strategy
This second version of the Heikin Ashi system adds an EMA filter. Trades occur only when the direction of the Heikin Ashi candle agrees with the trend defined by the EMA. The filter helps avoid counter-trend signals that the pure HA approach might generate.
Details
- Entry Criteria:
- Long:
HA_Close > HA_OpenandClose > EMA - Short:
HA_Close < HA_OpenandClose < EMA
- Long:
- Long/Short: Both sides
- Exit Criteria:
- Opposite signal
- Stops: None
- Default Values:
EmaLength= 20
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Heikin Ashi, EMA
- Stops: No
- Complexity: Low
- Timeframe: Short-term
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Heikin Ashi Strategy V2.
/// Uses fast and slow EMA crossover with Heikin-Ashi color confirmation.
/// Buys on bullish EMA cross when HA candle is green.
/// Sells on bearish EMA cross when HA candle is red.
/// </summary>
public class HeikinAshiV2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleTypeParam;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _cooldownBars;
private ExponentialMovingAverage _fastEma;
private ExponentialMovingAverage _slowEma;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _prevHaOpen;
private decimal _prevHaClose;
private int _cooldownRemaining;
public HeikinAshiV2Strategy()
{
_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General");
_fastPeriod = Param(nameof(FastPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Moving Averages");
_slowPeriod = Param(nameof(SlowPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Moving Averages");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
public DataType CandleType
{
get => _candleTypeParam.Value;
set => _candleTypeParam.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastEma = null;
_slowEma = null;
_prevFast = 0;
_prevSlow = 0;
_prevHaOpen = 0;
_prevHaClose = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastEma = new ExponentialMovingAverage { Length = FastPeriod };
_slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_fastEma, _slowEma, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastEma);
DrawIndicator(area, _slowEma);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
// Calculate Heikin-Ashi
decimal haOpen, haClose;
if (_prevHaOpen == 0)
{
haOpen = (candle.OpenPrice + candle.ClosePrice) / 2;
haClose = (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4;
}
else
{
haOpen = (_prevHaOpen + _prevHaClose) / 2;
haClose = (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4;
}
_prevHaOpen = haOpen;
_prevHaClose = haClose;
if (!_fastEma.IsFormed || !_slowEma.IsFormed)
{
_prevFast = fast;
_prevSlow = slow;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevFast = fast;
_prevSlow = slow;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevFast = fast;
_prevSlow = slow;
return;
}
if (_prevFast == 0)
{
_prevFast = fast;
_prevSlow = slow;
return;
}
var haGreen = haClose > haOpen;
var haRed = haClose < haOpen;
// Bullish: fast crosses above slow + HA is green
var bullishCross = fast > slow && _prevFast <= _prevSlow && haGreen;
// Bearish: fast crosses below slow + HA is red
var bearishCross = fast < slow && _prevFast >= _prevSlow && haRed;
if (bullishCross && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
else if (bearishCross && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
_prevFast = fast;
_prevSlow = slow;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class heikin_ashi_v2_strategy(Strategy):
"""Heikin Ashi V2 Strategy. Fast/slow EMA crossover with HA color confirmation."""
def __init__(self):
super(heikin_ashi_v2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General")
self._fast_period = self.Param("FastPeriod", 5) \
.SetDisplay("Fast EMA", "Fast EMA period", "Moving Averages")
self._slow_period = self.Param("SlowPeriod", 20) \
.SetDisplay("Slow EMA", "Slow EMA period", "Moving Averages")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._fast_ema = None
self._slow_ema = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_ha_open = 0.0
self._prev_ha_close = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(heikin_ashi_v2_strategy, self).OnReseted()
self._fast_ema = None
self._slow_ema = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_ha_open = 0.0
self._prev_ha_close = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(heikin_ashi_v2_strategy, self).OnStarted2(time)
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = int(self._fast_period.Value)
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = int(self._slow_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._fast_ema, self._slow_ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._fast_ema)
self.DrawIndicator(area, self._slow_ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast, slow):
if candle.State != CandleStates.Finished:
return
# Calculate Heikin-Ashi
if self._prev_ha_open == 0.0:
ha_open = (float(candle.OpenPrice) + float(candle.ClosePrice)) / 2.0
ha_close = (float(candle.OpenPrice) + float(candle.ClosePrice) + float(candle.HighPrice) + float(candle.LowPrice)) / 4.0
else:
ha_open = (self._prev_ha_open + self._prev_ha_close) / 2.0
ha_close = (float(candle.OpenPrice) + float(candle.ClosePrice) + float(candle.HighPrice) + float(candle.LowPrice)) / 4.0
self._prev_ha_open = ha_open
self._prev_ha_close = ha_close
if not self._fast_ema.IsFormed or not self._slow_ema.IsFormed:
self._prev_fast = float(fast)
self._prev_slow = float(slow)
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_fast = float(fast)
self._prev_slow = float(slow)
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_fast = float(fast)
self._prev_slow = float(slow)
return
f = float(fast)
s = float(slow)
cooldown = int(self._cooldown_bars.Value)
if self._prev_fast == 0.0:
self._prev_fast = f
self._prev_slow = s
return
ha_green = ha_close > ha_open
ha_red = ha_close < ha_open
bullish_cross = f > s and self._prev_fast <= self._prev_slow and ha_green
bearish_cross = f < s and self._prev_fast >= self._prev_slow and ha_red
if bullish_cross and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif bearish_cross and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
self._prev_fast = f
self._prev_slow = s
def CreateClone(self):
return heikin_ashi_v2_strategy()