Hull Ma Rsi Strategy
Implementation of strategy - Hull Moving Average + RSI. Buy when HMA is rising and RSI is below 30 (oversold). Sell when HMA is falling and RSI is above 70 (overbought).
Testing indicates an average annual return of about 64%. It performs best in the forex market.
Hull MA provides a smoothed trend line and RSI highlights momentum divergences. Trades occur when RSI turns at extremes while price follows the Hull direction.
Suited to short-term swing traders who want early signals. ATR-based stops protect the trade.
Details
- Entry Criteria:
- Long:
HullMA turning up && RSI < RsiOversold - Short:
HullMA turning down && RSI > RsiOverbought
- Long:
- Long/Short: Both
- Exit Criteria:
- Hull MA change of direction
- Stops: ATR-based using
StopLoss - Default Values:
HmaPeriod= 9RsiPeriod= 14RsiOversold= 30mRsiOverbought= 70mStopLoss= new Unit(2, UnitTypes.Absolute)CandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Mean reversion
- Direction: Both
- Indicators: Hull MA, Moving Average, RSI
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-term
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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;
using StockSharp.Algo;
using StockSharp.Algo.Candles;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Implementation of strategy - Hull Moving Average + RSI.
/// Buy when HMA is rising and RSI is below 30 (oversold).
/// Sell when HMA is falling and RSI is above 70 (overbought).
/// </summary>
public class HullMaRsiStrategy : Strategy
{
private readonly StrategyParam<int> _hmaPeriod;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiOversold;
private readonly StrategyParam<decimal> _rsiOverbought;
private readonly StrategyParam<Unit> _stopLoss;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private decimal _hmaValue;
private decimal _prevHmaValue;
private int _cooldown;
/// <summary>
/// Hull Moving Average period.
/// </summary>
public int HmaPeriod
{
get => _hmaPeriod.Value;
set => _hmaPeriod.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// RSI oversold level.
/// </summary>
public decimal RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
/// <summary>
/// RSI overbought level.
/// </summary>
public decimal RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
/// <summary>
/// Stop-loss value.
/// </summary>
public Unit StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Bars to wait between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type used for strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="HullMaRsiStrategy"/>.
/// </summary>
public HullMaRsiStrategy()
{
_hmaPeriod = Param(nameof(HmaPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("HMA Period", "Period for Hull Moving Average", "HMA Parameters");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Period for Relative Strength Index", "RSI Parameters");
_rsiOversold = Param(nameof(RsiOversold), 30m)
.SetRange(1, 100)
.SetDisplay("RSI Oversold", "RSI level to consider market oversold", "RSI Parameters");
_rsiOverbought = Param(nameof(RsiOverbought), 70m)
.SetRange(1, 100)
.SetDisplay("RSI Overbought", "RSI level to consider market overbought", "RSI Parameters");
_stopLoss = Param(nameof(StopLoss), new Unit(2, UnitTypes.Absolute))
.SetDisplay("Stop Loss", "Stop loss in ATR or value", "Risk Management");
_cooldownBars = Param(nameof(CooldownBars), 130)
.SetRange(5, 500)
.SetDisplay("Cooldown Bars", "Bars between trades", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle type for strategy", "General");
_prevHmaValue = 0;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_hmaValue = 0;
_prevHmaValue = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
var hma = new HullMovingAverage { Length = HmaPeriod };
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
// Setup candle subscription
var subscription = SubscribeCandles(CandleType);
// Store HMA value in field, process logic on RSI callback.
subscription.Bind(hma, OnHma);
subscription
.Bind(rsi, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, hma);
// Create separate area for RSI
var rsiArea = CreateChartArea();
if (rsiArea != null)
{
DrawIndicator(rsiArea, rsi);
}
DrawOwnTrades(area);
}
}
private void OnHma(ICandleMessage candle, decimal hmaValue)
{
_hmaValue = hmaValue;
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_hmaValue == 0)
return;
if (_prevHmaValue == 0)
{
_prevHmaValue = _hmaValue;
return;
}
// Determine if HMA is rising or falling
var isHmaRising = _prevHmaValue != 0 && _hmaValue > _prevHmaValue;
var isHmaFalling = _prevHmaValue != 0 && _hmaValue < _prevHmaValue;
LogInfo($"Candle: {candle.OpenTime}, Close: {candle.ClosePrice}, " +
$"HMA: {_hmaValue}, Previous HMA: {_prevHmaValue}, " +
$"HMA Rising: {isHmaRising}, HMA Falling: {isHmaFalling}, " +
$"RSI: {rsiValue}");
if (_cooldown > 0)
{
_cooldown--;
_prevHmaValue = _hmaValue;
return;
}
// Trading rules
if (isHmaRising && rsiValue < RsiOversold && Position == 0)
{
BuyMarket();
_cooldown = CooldownBars;
LogInfo($"Buy signal: HMA rising and RSI oversold ({rsiValue} < {RsiOversold}).");
}
else if (isHmaFalling && rsiValue > RsiOverbought && Position == 0)
{
SellMarket();
_cooldown = CooldownBars;
LogInfo($"Sell signal: HMA falling and RSI overbought ({rsiValue} > {RsiOverbought}).");
}
// Exit conditions based on HMA direction change
else if (isHmaFalling && Position > 0)
{
SellMarket();
_cooldown = CooldownBars;
LogInfo($"Exit long: HMA started falling. Position: {Position}");
}
else if (isHmaRising && Position < 0)
{
BuyMarket();
_cooldown = CooldownBars;
LogInfo($"Exit short: HMA started rising. Position: {Position}");
}
// Update HMA value for next iteration
_prevHmaValue = _hmaValue;
}
}
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 HullMovingAverage, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class hull_ma_rsi_strategy(Strategy):
"""
Hull Moving Average + RSI strategy.
Buy when HMA is rising and RSI is oversold.
Sell when HMA is falling and RSI is overbought.
"""
def __init__(self):
super(hull_ma_rsi_strategy, self).__init__()
self._hma_period = self.Param("HmaPeriod", 9) \
.SetDisplay("HMA Period", "Period for Hull Moving Average", "HMA Parameters")
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "Period for Relative Strength Index", "RSI Parameters")
self._rsi_oversold = self.Param("RsiOversold", 30.0) \
.SetRange(1, 100) \
.SetDisplay("RSI Oversold", "RSI level to consider market oversold", "RSI Parameters")
self._rsi_overbought = self.Param("RsiOverbought", 70.0) \
.SetRange(1, 100) \
.SetDisplay("RSI Overbought", "RSI level to consider market overbought", "RSI Parameters")
self._cooldown_bars = self.Param("CooldownBars", 130) \
.SetRange(5, 500) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Candle type for strategy", "General")
self._hma_value = 0.0
self._prev_hma_value = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(hull_ma_rsi_strategy, self).OnStarted2(time)
self._hma_value = 0.0
self._prev_hma_value = 0.0
self._cooldown = 0
hma = HullMovingAverage()
hma.Length = self._hma_period.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(hma, self.OnHma)
subscription.Bind(rsi, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, hma)
rsi_area = self.CreateChartArea()
if rsi_area is not None:
self.DrawIndicator(rsi_area, rsi)
self.DrawOwnTrades(area)
def OnHma(self, candle, hma_value):
self._hma_value = float(hma_value)
def ProcessCandle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
if self._hma_value == 0:
return
if self._prev_hma_value == 0:
self._prev_hma_value = self._hma_value
return
is_hma_rising = self._hma_value > self._prev_hma_value
is_hma_falling = self._hma_value < self._prev_hma_value
rv = float(rsi_value)
if self._cooldown > 0:
self._cooldown -= 1
self._prev_hma_value = self._hma_value
return
cd = self._cooldown_bars.Value
os_level = self._rsi_oversold.Value
ob_level = self._rsi_overbought.Value
if is_hma_rising and rv < os_level and self.Position == 0:
self.BuyMarket()
self._cooldown = cd
elif is_hma_falling and rv > ob_level and self.Position == 0:
self.SellMarket()
self._cooldown = cd
elif is_hma_falling and self.Position > 0:
self.SellMarket()
self._cooldown = cd
elif is_hma_rising and self.Position < 0:
self.BuyMarket()
self._cooldown = cd
self._prev_hma_value = self._hma_value
def OnReseted(self):
super(hull_ma_rsi_strategy, self).OnReseted()
self._hma_value = 0.0
self._prev_hma_value = 0.0
self._cooldown = 0
def CreateClone(self):
return hull_ma_rsi_strategy()