Heikin-Ashi Reversal Strategy
Heikin-Ashi candles smooth noise and highlight trend direction. A shift from a series of bearish HA candles to a bullish one, or vice versa, can indicate a change in momentum. This strategy trades those color flips and uses a percentage stop for protection.
Testing indicates an average annual return of about 145%. It performs best in the crypto market.
The logic calculates Heikin-Ashi values from regular candles. When the HA close crosses above the HA open after a bearish sequence, a long is taken. A cross below after a bullish run opens a short. The stop is placed a fixed percentage away from entry.
The method is simple yet effective during choppy swings when traditional candlesticks are noisy.
Details
- Entry Criteria: Heikin-Ashi candle changes color.
- Long/Short: Both.
- Exit Criteria: Stop-loss.
- Stops: Yes, percentage based.
- Default Values:
CandleType= 15 minuteStopLossPercent= 2
- Filters:
- Category: Reversal
- Direction: Both
- Indicators: Heikin-Ashi
- Stops: Yes
- Complexity: Basic
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
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>
/// Heikin Ashi Reversal strategy.
/// Computes Heikin-Ashi candles from regular candles. A bullish Heikin-Ashi candle after bearish ones turns the position long,
/// a bearish one after bullish ones turns it short; a percent stop limits the loss.
/// </summary>
public class HeikinAshiReversalStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private decimal? _haOpen;
private decimal _haClose;
private bool? _prevBullish;
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public HeikinAshiReversalStrategy()
{
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "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();
_haOpen = null;
_haClose = default;
_prevBullish = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_haOpen = null;
_haClose = default;
_prevBullish = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var haClose = (candle.OpenPrice + candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 4;
var haOpen = _haOpen is decimal prevOpen
? (prevOpen + _haClose) / 2
: (candle.OpenPrice + candle.ClosePrice) / 2;
_haOpen = haOpen;
_haClose = haClose;
// A Heikin-Ashi candle without a body keeps the previous color.
if (haClose == haOpen)
return;
var isBullish = haClose > haOpen;
var wasBullish = _prevBullish;
_prevBullish = isBullish;
if (wasBullish is not bool previous || previous == isBullish || !IsFormedAndOnlineAndAllowTrading())
return;
if (isBullish && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (!isBullish && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class heikin_ashi_reversal_strategy(Strategy):
"""
Heikin Ashi Reversal strategy.
Computes Heikin-Ashi candles from regular candles. A bullish Heikin-Ashi candle after bearish ones turns the position long,
a bearish one after bullish ones turns it short; a percent stop limits the loss.
"""
def __init__(self):
super(heikin_ashi_reversal_strategy, self).__init__()
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._ha_open = None
self._ha_close = Decimal(0)
self._prev_bullish = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(heikin_ashi_reversal_strategy, self).OnReseted()
self._ha_open = None
self._ha_close = Decimal(0)
self._prev_bullish = None
def OnStarted2(self, time):
super(heikin_ashi_reversal_strategy, self).OnStarted2(time)
self._ha_open = None
self._ha_close = Decimal(0)
self._prev_bullish = None
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
ha_close = (candle.OpenPrice + candle.HighPrice + candle.LowPrice + candle.ClosePrice) / Decimal(4)
if self._ha_open is None:
ha_open = (candle.OpenPrice + candle.ClosePrice) / Decimal(2)
else:
ha_open = (self._ha_open + self._ha_close) / Decimal(2)
self._ha_open = ha_open
self._ha_close = ha_close
# A Heikin-Ashi candle without a body keeps the previous color.
if ha_close == ha_open:
return
is_bullish = ha_close > ha_open
was_bullish = self._prev_bullish
self._prev_bullish = is_bullish
if was_bullish is None or was_bullish == is_bullish or not self.IsFormedAndOnlineAndAllowTrading():
return
if is_bullish and self.Position <= 0:
self.BuyMarket(self.Volume + Math.Abs(self.Position))
elif not is_bullish and self.Position >= 0:
self.SellMarket(self.Volume + Math.Abs(self.Position))
def CreateClone(self):
return heikin_ashi_reversal_strategy()