RSI Trader Strategy
Overview
This strategy replicates the MetaTrader expert advisor "RSI trader v0.15" in the StockSharp high-level API. It aligns trend direction between price action and a smoothed Relative Strength Index (RSI). Trading is performed on a single instrument using one-hour candles by default, but the timeframe is configurable through the CandleType parameter.
Trading Logic
- Calculate a standard RSI with a configurable period.
- Smooth the RSI with two simple moving averages (SMA): a fast signal average and a slower confirmation average.
- Track two moving averages of closing price: a short simple moving average and a long weighted moving average to approximate the original MQL SMA/LWMA pair.
- Generate trend states on each finished candle:
- Bullish alignment: short price MA above long price MA and fast RSI SMA above slow RSI SMA.
- Bearish alignment: short price MA below long price MA and fast RSI SMA below slow RSI SMA.
- Sideways / disagreement: moving averages point in opposite directions, signalling no clear trend.
- Act on the detected state:
- Open a long position when bullish alignment appears and no position is currently open.
- Open a short position when bearish alignment appears and no position is currently open.
- Immediately close any open position when the sideways state is detected, mirroring the protective exit in the MQL version.
- Optional reversal mode flips all entry directions, allowing the user to trade counter-trend against the detected signals.
The strategy respects StockSharp's built-in protection handling and requires completed candles before taking any action.
Parameters
| Parameter |
Description |
Default |
RsiPeriod |
Lookback period used for RSI calculation. |
14 |
ShortRsiMaPeriod |
Length of the fast SMA applied to RSI values. |
9 |
LongRsiMaPeriod |
Length of the slow SMA applied to RSI values. |
45 |
ShortPriceMaPeriod |
Length of the short SMA applied to closing prices. |
9 |
LongPriceMaPeriod |
Length of the long weighted moving average applied to prices. |
45 |
Reverse |
When true, buy and sell orders are swapped (mirrors the original "Reverse" input). |
false |
CandleType |
Data type for price candles. Defaults to one-hour time frame. |
1h |
All integer parameters expose optimization ranges mirroring the flexibility of the MetaTrader expert input settings.
Risk Management
- Positions are closed as soon as price and RSI trends disagree (sideways state), reproducing the EA's immediate exit behaviour.
StartProtection() is enabled on start to cooperate with StockSharp's protective infrastructure.
Notes
- The strategy relies on the base
Volume property of Strategy to define trade size.
- Only completed candles are processed; partial updates are ignored to avoid premature signals.
- Weighted moving average is used to match the original long LWMA applied to price closes.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo.Candles;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// RSI trader strategy aligning price and RSI moving-average trends.
/// </summary>
public class RsiTraderStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _shortRsiMaPeriod;
private readonly StrategyParam<int> _longRsiMaPeriod;
private readonly StrategyParam<int> _shortPriceMaPeriod;
private readonly StrategyParam<int> _longPriceMaPeriod;
private readonly StrategyParam<bool> _reverse;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _closes = new();
private readonly List<decimal> _rsiValues = new();
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public int ShortRsiMaPeriod { get => _shortRsiMaPeriod.Value; set => _shortRsiMaPeriod.Value = value; }
public int LongRsiMaPeriod { get => _longRsiMaPeriod.Value; set => _longRsiMaPeriod.Value = value; }
public int ShortPriceMaPeriod { get => _shortPriceMaPeriod.Value; set => _shortPriceMaPeriod.Value = value; }
public int LongPriceMaPeriod { get => _longPriceMaPeriod.Value; set => _longPriceMaPeriod.Value = value; }
public bool Reverse { get => _reverse.Value; set => _reverse.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public RsiTraderStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14).SetDisplay("RSI Period", "RSI calculation length", "RSI").SetGreaterThanZero();
_shortRsiMaPeriod = Param(nameof(ShortRsiMaPeriod), 12).SetDisplay("Short RSI MA", "Short moving average on RSI", "RSI").SetGreaterThanZero();
_longRsiMaPeriod = Param(nameof(LongRsiMaPeriod), 60).SetDisplay("Long RSI MA", "Long moving average on RSI", "RSI").SetGreaterThanZero();
_shortPriceMaPeriod = Param(nameof(ShortPriceMaPeriod), 12).SetDisplay("Short Price MA", "Short simple moving average", "Price").SetGreaterThanZero();
_longPriceMaPeriod = Param(nameof(LongPriceMaPeriod), 60).SetDisplay("Long Price MA", "Long weighted moving average", "Price").SetGreaterThanZero();
_reverse = Param(nameof(Reverse), false).SetDisplay("Reverse", "Flip buy/sell signals", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Primary candle type", "Data");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_closes.Clear();
_rsiValues.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_closes.Add(candle.ClosePrice);
var maxCache = Math.Max(LongPriceMaPeriod, Math.Max(LongRsiMaPeriod + RsiPeriod, 300));
if (_closes.Count > maxCache)
_closes.RemoveAt(0);
var rsi = CalculateRsi();
if (rsi is null)
return;
_rsiValues.Add(rsi.Value);
if (_rsiValues.Count > maxCache)
_rsiValues.RemoveAt(0);
if (_rsiValues.Count < LongRsiMaPeriod || _closes.Count < LongPriceMaPeriod)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var shortRsi = AverageLast(_rsiValues, ShortRsiMaPeriod);
var longRsi = AverageLast(_rsiValues, LongRsiMaPeriod);
var shortPrice = AverageLast(_closes, ShortPriceMaPeriod);
var longPrice = WeightedAverageLast(_closes, LongPriceMaPeriod);
var goLong = shortPrice > longPrice && shortRsi > longRsi;
var goShort = shortPrice < longPrice && shortRsi < longRsi;
var sideways = !goLong && !goShort;
if (sideways && Position != 0)
{
if (Position > 0)
SellMarket(Position);
else
BuyMarket(Math.Abs(Position));
return;
}
if (Position != 0)
return;
if (goLong)
{
if (Reverse)
SellMarket();
else
BuyMarket();
}
else if (goShort)
{
if (Reverse)
BuyMarket();
else
SellMarket();
}
}
private decimal? CalculateRsi()
{
if (_closes.Count <= RsiPeriod)
return null;
decimal gainSum = 0m;
decimal lossSum = 0m;
var start = _closes.Count - RsiPeriod;
for (var i = start; i < _closes.Count; i++)
{
var change = _closes[i] - _closes[i - 1];
if (change > 0m)
gainSum += change;
else
lossSum -= change;
}
var averageGain = gainSum / RsiPeriod;
var averageLoss = lossSum / RsiPeriod;
if (averageLoss == 0m)
return 100m;
var rs = averageGain / averageLoss;
return 100m - 100m / (1m + rs);
}
private static decimal AverageLast(IReadOnlyList<decimal> values, int length)
{
decimal sum = 0m;
var start = values.Count - length;
for (var i = start; i < values.Count; i++)
sum += values[i];
return sum / length;
}
private static decimal WeightedAverageLast(IReadOnlyList<decimal> values, int length)
{
decimal weightedSum = 0m;
decimal weightSum = 0m;
var start = values.Count - length;
var weight = 1m;
for (var i = start; i < values.Count; i++)
{
weightedSum += values[i] * weight;
weightSum += weight;
weight += 1m;
}
return weightSum > 0m ? weightedSum / weightSum : values[^1];
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class rsi_trader_strategy(Strategy):
def __init__(self):
super(rsi_trader_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14)
self._short_rsi_ma_period = self.Param("ShortRsiMaPeriod", 12)
self._long_rsi_ma_period = self.Param("LongRsiMaPeriod", 60)
self._short_price_ma_period = self.Param("ShortPriceMaPeriod", 12)
self._long_price_ma_period = self.Param("LongPriceMaPeriod", 60)
self._reverse = self.Param("Reverse", False)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._closes = []
self._rsi_values = []
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def ShortRsiMaPeriod(self):
return self._short_rsi_ma_period.Value
@ShortRsiMaPeriod.setter
def ShortRsiMaPeriod(self, value):
self._short_rsi_ma_period.Value = value
@property
def LongRsiMaPeriod(self):
return self._long_rsi_ma_period.Value
@LongRsiMaPeriod.setter
def LongRsiMaPeriod(self, value):
self._long_rsi_ma_period.Value = value
@property
def ShortPriceMaPeriod(self):
return self._short_price_ma_period.Value
@ShortPriceMaPeriod.setter
def ShortPriceMaPeriod(self, value):
self._short_price_ma_period.Value = value
@property
def LongPriceMaPeriod(self):
return self._long_price_ma_period.Value
@LongPriceMaPeriod.setter
def LongPriceMaPeriod(self, value):
self._long_price_ma_period.Value = value
@property
def Reverse(self):
return self._reverse.Value
@Reverse.setter
def Reverse(self, value):
self._reverse.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(rsi_trader_strategy, self).OnStarted2(time)
self._closes = []
self._rsi_values = []
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
self._closes.append(close)
max_cache = max(int(self.LongPriceMaPeriod), max(int(self.LongRsiMaPeriod) + int(self.RsiPeriod), 300))
while len(self._closes) > max_cache:
self._closes.pop(0)
rsi = self._calculate_rsi()
if rsi is None:
return
self._rsi_values.append(rsi)
while len(self._rsi_values) > max_cache:
self._rsi_values.pop(0)
if len(self._rsi_values) < int(self.LongRsiMaPeriod) or len(self._closes) < int(self.LongPriceMaPeriod):
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
short_rsi = self._average_last(self._rsi_values, int(self.ShortRsiMaPeriod))
long_rsi = self._average_last(self._rsi_values, int(self.LongRsiMaPeriod))
short_price = self._average_last(self._closes, int(self.ShortPriceMaPeriod))
long_price = self._weighted_average_last(self._closes, int(self.LongPriceMaPeriod))
go_long = short_price > long_price and short_rsi > long_rsi
go_short = short_price < long_price and short_rsi < long_rsi
sideways = not go_long and not go_short
if sideways and self.Position != 0:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
return
if self.Position != 0:
return
if go_long:
if self.Reverse:
self.SellMarket()
else:
self.BuyMarket()
elif go_short:
if self.Reverse:
self.BuyMarket()
else:
self.SellMarket()
def _calculate_rsi(self):
rsi_period = int(self.RsiPeriod)
if len(self._closes) <= rsi_period:
return None
gain_sum = 0.0
loss_sum = 0.0
start = len(self._closes) - rsi_period
for i in range(start, len(self._closes)):
change = self._closes[i] - self._closes[i - 1]
if change > 0.0:
gain_sum += change
else:
loss_sum -= change
average_gain = gain_sum / rsi_period
average_loss = loss_sum / rsi_period
if average_loss == 0.0:
return 100.0
rs = average_gain / average_loss
return 100.0 - 100.0 / (1.0 + rs)
def _average_last(self, values, length):
total = 0.0
start = len(values) - length
for i in range(start, len(values)):
total += values[i]
return total / length
def _weighted_average_last(self, values, length):
weighted_sum = 0.0
weight_sum = 0.0
start = len(values) - length
weight = 1.0
for i in range(start, len(values)):
weighted_sum += values[i] * weight
weight_sum += weight
weight += 1.0
if weight_sum > 0.0:
return weighted_sum / weight_sum
return values[-1]
def OnReseted(self):
super(rsi_trader_strategy, self).OnReseted()
self._closes = []
self._rsi_values = []
def CreateClone(self):
return rsi_trader_strategy()