MaRsi Trigger Strategy
This strategy combines fast and slow exponential moving averages (EMA) with RSI to detect trend reversals. When the fast EMA and fast RSI are both above their slow counterparts, it treats the market as bullish and opens a long position. When both are below, it opens a short position. Parameters allow enabling or disabling long and short entries or exits.
Details
- Entry Criteria:
- Long: fast EMA > slow EMA AND fast RSI > slow RSI with previous trend bearish.
- Short: fast EMA < slow EMA AND fast RSI < slow RSI with previous trend bullish.
- Exit Criteria:
- Long: trend turns bearish and long exits are allowed.
- Short: trend turns bullish and short exits are allowed.
- Indicators: EMA, RSI.
- Stops: Not included.
- Timeframe: 4-hour candles by default.
- Parameters:
FastRsiPeriod= 3SlowRsiPeriod= 13FastMaPeriod= 5SlowMaPeriod= 10AllowBuyEntry= trueAllowSellEntry= trueAllowLongExit= trueAllowShortExit= true
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>
/// Strategy based on moving averages and RelativeStrengthIndex crossover.
/// </summary>
public class MaRsiTriggerStrategy : Strategy
{
private readonly StrategyParam<int> _fastRsiPeriod;
private readonly StrategyParam<int> _slowRsiPeriod;
private readonly StrategyParam<int> _fastMaPeriod;
private readonly StrategyParam<int> _slowMaPeriod;
private readonly StrategyParam<bool> _allowBuyEntry;
private readonly StrategyParam<bool> _allowSellEntry;
private readonly StrategyParam<bool> _allowLongExit;
private readonly StrategyParam<bool> _allowShortExit;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _minRsiSpread;
private readonly StrategyParam<decimal> _minMaSpreadPercent;
private readonly StrategyParam<int> _cooldownBars;
private int _previousTrend;
private int _cooldownRemaining;
/// <summary>
/// Fast RelativeStrengthIndex period.
/// </summary>
public int FastRsiPeriod { get => _fastRsiPeriod.Value; set => _fastRsiPeriod.Value = value; }
/// <summary>
/// Slow RelativeStrengthIndex period.
/// </summary>
public int SlowRsiPeriod { get => _slowRsiPeriod.Value; set => _slowRsiPeriod.Value = value; }
/// <summary>
/// Fast EMA period.
/// </summary>
public int FastMaPeriod { get => _fastMaPeriod.Value; set => _fastMaPeriod.Value = value; }
/// <summary>
/// Slow EMA period.
/// </summary>
public int SlowMaPeriod { get => _slowMaPeriod.Value; set => _slowMaPeriod.Value = value; }
/// <summary>
/// Allow opening long positions.
/// </summary>
public bool AllowBuyEntry { get => _allowBuyEntry.Value; set => _allowBuyEntry.Value = value; }
/// <summary>
/// Allow opening short positions.
/// </summary>
public bool AllowSellEntry { get => _allowSellEntry.Value; set => _allowSellEntry.Value = value; }
/// <summary>
/// Allow closing long positions when trend becomes bearish.
/// </summary>
public bool AllowLongExit { get => _allowLongExit.Value; set => _allowLongExit.Value = value; }
/// <summary>
/// Allow closing short positions when trend becomes bullish.
/// </summary>
public bool AllowShortExit { get => _allowShortExit.Value; set => _allowShortExit.Value = value; }
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Minimum spread between fast and slow RelativeStrengthIndex values.
/// </summary>
public decimal MinRsiSpread { get => _minRsiSpread.Value; set => _minRsiSpread.Value = value; }
/// <summary>
/// Minimum normalized EMA spread.
/// </summary>
public decimal MinMaSpreadPercent { get => _minMaSpreadPercent.Value; set => _minMaSpreadPercent.Value = value; }
/// <summary>
/// Number of completed candles to wait after a position change.
/// </summary>
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
/// <summary>
/// Initializes a new instance of the <see cref="MaRsiTriggerStrategy"/> class.
/// </summary>
public MaRsiTriggerStrategy()
{
_fastRsiPeriod = Param(nameof(FastRsiPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("Fast RelativeStrengthIndex Period", "Period of the fast RelativeStrengthIndex", "RelativeStrengthIndex")
.SetOptimize(2, 10, 1);
_slowRsiPeriod = Param(nameof(SlowRsiPeriod), 13)
.SetGreaterThanZero()
.SetDisplay("Slow RelativeStrengthIndex Period", "Period of the slow RelativeStrengthIndex", "RelativeStrengthIndex")
.SetOptimize(10, 30, 1);
_fastMaPeriod = Param(nameof(FastMaPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Fast EMA Period", "Period of the fast EMA", "MA")
.SetOptimize(3, 15, 1);
_slowMaPeriod = Param(nameof(SlowMaPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Slow EMA Period", "Period of the slow EMA", "MA")
.SetOptimize(5, 30, 1);
_allowBuyEntry = Param(nameof(AllowBuyEntry), true)
.SetDisplay("Allow Buy Entry", "Enable entering long positions", "General");
_allowSellEntry = Param(nameof(AllowSellEntry), true)
.SetDisplay("Allow Sell Entry", "Enable entering short positions", "General");
_allowLongExit = Param(nameof(AllowLongExit), true)
.SetDisplay("Allow Long Exit", "Enable exiting long positions", "General");
_allowShortExit = Param(nameof(AllowShortExit), true)
.SetDisplay("Allow Short Exit", "Enable exiting short positions", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_minRsiSpread = Param(nameof(MinRsiSpread), 6m)
.SetDisplay("Minimum RelativeStrengthIndex Spread", "Minimum spread between fast and slow RelativeStrengthIndex values", "Filters");
_minMaSpreadPercent = Param(nameof(MinMaSpreadPercent), 0.0025m)
.SetDisplay("Minimum EMA Spread %", "Minimum normalized spread between fast and slow EMA values", "Filters");
_cooldownBars = Param(nameof(CooldownBars), 6)
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousTrend = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastRsi = new RelativeStrengthIndex { Length = FastRsiPeriod };
var slowRsi = new RelativeStrengthIndex { Length = SlowRsiPeriod };
var fastMa = new ExponentialMovingAverage { Length = FastMaPeriod };
var slowMa = new ExponentialMovingAverage { Length = SlowMaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastRsi, slowRsi, fastMa, slowMa, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastMa);
DrawIndicator(area, slowMa);
DrawIndicator(area, fastRsi);
DrawIndicator(area, slowRsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastRsiValue, decimal slowRsiValue, decimal fastMaValue, decimal slowMaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var normalizedMaSpread = slowMaValue != 0m ? Math.Abs(fastMaValue - slowMaValue) / slowMaValue : 0m;
var rsiSpread = Math.Abs(fastRsiValue - slowRsiValue);
var trend = 0;
if (fastMaValue > slowMaValue && normalizedMaSpread >= MinMaSpreadPercent)
trend++;
else if (fastMaValue < slowMaValue && normalizedMaSpread >= MinMaSpreadPercent)
trend--;
if (fastRsiValue > slowRsiValue && rsiSpread >= MinRsiSpread)
trend++;
else if (fastRsiValue < slowRsiValue && rsiSpread >= MinRsiSpread)
trend--;
if (_cooldownRemaining == 0)
{
if (_previousTrend < 0 && trend > 0)
{
if (AllowShortExit && Position < 0)
BuyMarket();
if (AllowBuyEntry && Position <= 0)
{
BuyMarket();
_cooldownRemaining = CooldownBars;
}
}
else if (_previousTrend > 0 && trend < 0)
{
if (AllowLongExit && Position > 0)
SellMarket();
if (AllowSellEntry && Position >= 0)
{
SellMarket();
_cooldownRemaining = CooldownBars;
}
}
}
if (trend != 0)
_previousTrend = trend;
}
}
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 RelativeStrengthIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_rsi_trigger_strategy(Strategy):
def __init__(self):
super(ma_rsi_trigger_strategy, self).__init__()
self._fast_rsi_period = self.Param("FastRsiPeriod", 3) \
.SetDisplay("Fast RSI Period", "Period of the fast RSI", "RSI")
self._slow_rsi_period = self.Param("SlowRsiPeriod", 13) \
.SetDisplay("Slow RSI Period", "Period of the slow RSI", "RSI")
self._fast_ma_period = self.Param("FastMaPeriod", 5) \
.SetDisplay("Fast EMA Period", "Period of the fast EMA", "MA")
self._slow_ma_period = self.Param("SlowMaPeriod", 10) \
.SetDisplay("Slow EMA Period", "Period of the slow EMA", "MA")
self._allow_buy_entry = self.Param("AllowBuyEntry", True) \
.SetDisplay("Allow Buy Entry", "Enable entering long positions", "General")
self._allow_sell_entry = self.Param("AllowSellEntry", True) \
.SetDisplay("Allow Sell Entry", "Enable entering short positions", "General")
self._allow_long_exit = self.Param("AllowLongExit", True) \
.SetDisplay("Allow Long Exit", "Enable exiting long positions", "General")
self._allow_short_exit = self.Param("AllowShortExit", True) \
.SetDisplay("Allow Short Exit", "Enable exiting short positions", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._min_rsi_spread = self.Param("MinRsiSpread", 6.0) \
.SetDisplay("Minimum RSI Spread", "Minimum spread between fast and slow RSI values", "Filters")
self._min_ma_spread_percent = self.Param("MinMaSpreadPercent", 0.0025) \
.SetDisplay("Minimum EMA Spread %", "Minimum normalized spread between fast and slow EMA values", "Filters")
self._cooldown_bars = self.Param("CooldownBars", 6) \
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading")
self._previous_trend = 0
self._cooldown_remaining = 0
@property
def fast_rsi_period(self):
return self._fast_rsi_period.Value
@property
def slow_rsi_period(self):
return self._slow_rsi_period.Value
@property
def fast_ma_period(self):
return self._fast_ma_period.Value
@property
def slow_ma_period(self):
return self._slow_ma_period.Value
@property
def allow_buy_entry(self):
return self._allow_buy_entry.Value
@property
def allow_sell_entry(self):
return self._allow_sell_entry.Value
@property
def allow_long_exit(self):
return self._allow_long_exit.Value
@property
def allow_short_exit(self):
return self._allow_short_exit.Value
@property
def candle_type(self):
return self._candle_type.Value
@property
def min_rsi_spread(self):
return self._min_rsi_spread.Value
@property
def min_ma_spread_percent(self):
return self._min_ma_spread_percent.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
def OnReseted(self):
super(ma_rsi_trigger_strategy, self).OnReseted()
self._previous_trend = 0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(ma_rsi_trigger_strategy, self).OnStarted2(time)
fast_rsi = RelativeStrengthIndex()
fast_rsi.Length = self.fast_rsi_period
slow_rsi = RelativeStrengthIndex()
slow_rsi.Length = self.slow_rsi_period
fast_ma = ExponentialMovingAverage()
fast_ma.Length = self.fast_ma_period
slow_ma = ExponentialMovingAverage()
slow_ma.Length = self.slow_ma_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast_rsi, slow_rsi, fast_ma, slow_ma, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ma)
self.DrawIndicator(area, slow_ma)
self.DrawOwnTrades(area)
def process_candle(self, candle, fast_rsi_val, slow_rsi_val, fast_ma_val, slow_ma_val):
if candle.State != CandleStates.Finished:
return
fast_rsi_val = float(fast_rsi_val)
slow_rsi_val = float(slow_rsi_val)
fast_ma_val = float(fast_ma_val)
slow_ma_val = float(slow_ma_val)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
normalized_ma_spread = abs(fast_ma_val - slow_ma_val) / slow_ma_val if slow_ma_val != 0 else 0.0
rsi_spread = abs(fast_rsi_val - slow_rsi_val)
trend = 0
min_ma_sp = float(self.min_ma_spread_percent)
min_rsi_sp = float(self.min_rsi_spread)
if fast_ma_val > slow_ma_val and normalized_ma_spread >= min_ma_sp:
trend += 1
elif fast_ma_val < slow_ma_val and normalized_ma_spread >= min_ma_sp:
trend -= 1
if fast_rsi_val > slow_rsi_val and rsi_spread >= min_rsi_sp:
trend += 1
elif fast_rsi_val < slow_rsi_val and rsi_spread >= min_rsi_sp:
trend -= 1
if self._cooldown_remaining == 0:
if self._previous_trend < 0 and trend > 0:
if self.allow_short_exit and self.Position < 0:
self.BuyMarket()
if self.allow_buy_entry and self.Position <= 0:
self.BuyMarket()
self._cooldown_remaining = self.cooldown_bars
elif self._previous_trend > 0 and trend < 0:
if self.allow_long_exit and self.Position > 0:
self.SellMarket()
if self.allow_sell_entry and self.Position >= 0:
self.SellMarket()
self._cooldown_remaining = self.cooldown_bars
if trend != 0:
self._previous_trend = trend
def CreateClone(self):
return ma_rsi_trigger_strategy()