Heiken Ashi Smoothed Trend Strategy
This strategy uses EMA-smoothed Heiken-Ashi candles to detect trend reversals. A bullish candle turning from red to green opens a long position and closes any short. Conversely, a bearish candle turning from green to red opens a short and closes any long.
- Indicators: Heikin-Ashi (with EMA smoothing)
- Entry Rules:
- Enter long when the smoothed Heikin-Ashi candle becomes bullish.
- Enter short when the smoothed candle becomes bearish.
- Exit Rules:
- Reverse position on opposite signal.
- Parameters:
EmaLength– smoothing period for the EMA.CandleType– timeframe of candles.
The algorithm recomputes the smoothed open and close for each finished candle and flips the position accordingly.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Heiken-Ashi strategy using EMA-smoothed candles.
/// Opens long positions when candle color turns bullish and short positions when it turns bearish.
/// </summary>
public class HeikenAshiSmoothedTrendStrategy : Strategy
{
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _openEma;
private ExponentialMovingAverage _closeEma;
private ExponentialMovingAverage _highEma;
private ExponentialMovingAverage _lowEma;
private decimal? _prevHaOpen;
private decimal? _prevHaClose;
private bool? _prevIsGreen;
/// <summary>
/// Length for EMA smoothing.
/// </summary>
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <summary>
/// Type of candles to process.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="HeikenAshiSmoothedTrendStrategy"/>.
/// </summary>
public HeikenAshiSmoothedTrendStrategy()
{
_emaLength = Param(nameof(EmaLength), 30)
.SetDisplay("EMA Length", "Length for smoothing", "General")
.SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_openEma = null;
_closeEma = null;
_highEma = null;
_lowEma = null;
_prevHaOpen = null;
_prevHaClose = null;
_prevIsGreen = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_openEma = new ExponentialMovingAverage { Length = EmaLength };
_closeEma = new ExponentialMovingAverage { Length = EmaLength };
_highEma = new ExponentialMovingAverage { Length = EmaLength };
_lowEma = new ExponentialMovingAverage { Length = EmaLength };
Indicators.Add(_openEma);
Indicators.Add(_closeEma);
Indicators.Add(_highEma);
Indicators.Add(_lowEma);
// Use a dummy EMA for warmup/binding, do manual EMA processing inside
var warmup = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(warmup, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal _warmupVal)
{
if (candle.State != CandleStates.Finished)
return;
var t = candle.OpenTime;
var oResult = _openEma.Process(new DecimalIndicatorValue(_openEma, candle.OpenPrice, t) { IsFinal = true });
var cResult = _closeEma.Process(new DecimalIndicatorValue(_closeEma, candle.ClosePrice, t) { IsFinal = true });
var hResult = _highEma.Process(new DecimalIndicatorValue(_highEma, candle.HighPrice, t) { IsFinal = true });
var lResult = _lowEma.Process(new DecimalIndicatorValue(_lowEma, candle.LowPrice, t) { IsFinal = true });
if (!oResult.IsFormed || !cResult.IsFormed || !hResult.IsFormed || !lResult.IsFormed)
return;
var openEma = oResult.GetValue<decimal>();
var closeEma = cResult.GetValue<decimal>();
var highEma = hResult.GetValue<decimal>();
var lowEma = lResult.GetValue<decimal>();
var haClose = (openEma + highEma + lowEma + closeEma) / 4m;
var haOpen = _prevHaOpen is null ? (openEma + closeEma) / 2m : (_prevHaOpen.Value + _prevHaClose!.Value) / 2m;
var isGreen = haClose >= haOpen;
var buySignal = isGreen && _prevIsGreen == false;
var sellSignal = !isGreen && _prevIsGreen == true;
if (IsFormedAndOnlineAndAllowTrading())
{
if (buySignal && Position <= 0)
BuyMarket();
else if (sellSignal && Position >= 0)
SellMarket();
}
_prevHaOpen = haOpen;
_prevHaClose = haClose;
_prevIsGreen = isGreen;
}
}
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 indicator_extensions import *
class heiken_ashi_smoothed_trend_strategy(Strategy):
def __init__(self):
super(heiken_ashi_smoothed_trend_strategy, self).__init__()
self._ema_length = self.Param("EmaLength", 30) \
.SetDisplay("EMA Length", "Length for smoothing", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._open_ema = None
self._close_ema = None
self._high_ema = None
self._low_ema = None
self._prev_ha_open = None
self._prev_ha_close = None
self._prev_is_green = None
@property
def ema_length(self):
return self._ema_length.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(heiken_ashi_smoothed_trend_strategy, self).OnReseted()
self._open_ema = None
self._close_ema = None
self._high_ema = None
self._low_ema = None
self._prev_ha_open = None
self._prev_ha_close = None
self._prev_is_green = None
def OnStarted2(self, time):
super(heiken_ashi_smoothed_trend_strategy, self).OnStarted2(time)
self._open_ema = ExponentialMovingAverage()
self._open_ema.Length = self.ema_length
self._close_ema = ExponentialMovingAverage()
self._close_ema.Length = self.ema_length
self._high_ema = ExponentialMovingAverage()
self._high_ema.Length = self.ema_length
self._low_ema = ExponentialMovingAverage()
self._low_ema.Length = self.ema_length
self.Indicators.Add(self._open_ema)
self.Indicators.Add(self._close_ema)
self.Indicators.Add(self._high_ema)
self.Indicators.Add(self._low_ema)
warmup = ExponentialMovingAverage()
warmup.Length = self.ema_length
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(warmup, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle, _warmup_val):
if candle.State != CandleStates.Finished:
return
t = candle.OpenTime
o_result = process_float(self._open_ema, float(candle.OpenPrice), t, True)
c_result = process_float(self._close_ema, float(candle.ClosePrice), t, True)
h_result = process_float(self._high_ema, float(candle.HighPrice), t, True)
l_result = process_float(self._low_ema, float(candle.LowPrice), t, True)
if not o_result.IsFormed or not c_result.IsFormed or not h_result.IsFormed or not l_result.IsFormed:
return
open_ema = float(o_result)
close_ema = float(c_result)
high_ema = float(h_result)
low_ema = float(l_result)
ha_close = (open_ema + high_ema + low_ema + close_ema) / 4.0
if self._prev_ha_open is None:
ha_open = (open_ema + close_ema) / 2.0
else:
ha_open = (self._prev_ha_open + self._prev_ha_close) / 2.0
is_green = ha_close >= ha_open
buy_signal = is_green and self._prev_is_green == False
sell_signal = not is_green and self._prev_is_green == True
if buy_signal and self.Position <= 0:
self.BuyMarket()
elif sell_signal and self.Position >= 0:
self.SellMarket()
self._prev_ha_open = ha_open
self._prev_ha_close = ha_close
self._prev_is_green = is_green
def CreateClone(self):
return heiken_ashi_smoothed_trend_strategy()