Limits RSI Momentum Bot Strategy
Summary
This strategy places limit orders based on Relative Strength Index (RSI) and Momentum indicators. It aims to buy at discounts and sell at premiums by using pending orders instead of market executions.
Trading Rules
- Operates only during the specified time window.
- On each finished candle, RSI and Momentum values are calculated.
- Buy limit is placed below the candle open when RSI and Momentum are both below their buy thresholds.
- Sell limit is placed above the candle open when RSI and Momentum are both above their sell thresholds.
- When a position is opened, the opposite pending order is cancelled.
- Stop-loss and take-profit are managed automatically via
StartProtection.
Parameters
Volume– order volume.LimitOrderDistance– distance in price steps from the candle open to place pending orders.TakeProfit– profit target in price steps.StopLoss– loss limit in price steps.RsiPeriod– period for RSI calculation.RsiBuyRestrict/RsiSellRestrict– RSI thresholds that allow long or short entries.MomentumPeriod– period for Momentum calculation.MomentumBuyRestrict/MomentumSellRestrict– Momentum thresholds for long or short entries.StartTime/EndTime– trading session boundaries.CandleType– candle interval used for indicator calculations.
Notes
The strategy is converted from the MQL4 script "The Limits Bot with RSI & Momentum" and uses the high-level API of StockSharp.
using System;
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>
/// Strategy that places limit orders based on RSI and Momentum values.
/// A buy limit is set below the candle open when both indicators signal oversold.
/// A sell limit is set above the open when indicators show overbought conditions.
/// Opposite pending order is cancelled once a position is opened.
/// Stop-loss and take-profit are managed via StartProtection.
/// </summary>
public class LimitsRsiMomentumBotStrategy : Strategy
{
private readonly StrategyParam<int> _limitOrderDistance;
private readonly StrategyParam<int> _takeProfit;
private readonly StrategyParam<int> _stopLoss;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiBuyRestrict;
private readonly StrategyParam<decimal> _rsiSellRestrict;
private readonly StrategyParam<int> _momentumPeriod;
private readonly StrategyParam<decimal> _momentumBuyRestrict;
private readonly StrategyParam<decimal> _momentumSellRestrict;
private readonly StrategyParam<TimeSpan> _startTime;
private readonly StrategyParam<TimeSpan> _endTime;
private readonly StrategyParam<DataType> _candleType;
private Order _buyOrder;
private Order _sellOrder;
/// <summary>
/// Distance from candle open to place limit orders in price steps.
/// </summary>
public int LimitOrderDistance
{
get => _limitOrderDistance.Value;
set => _limitOrderDistance.Value = value;
}
/// <summary>
/// Take profit in price steps.
/// </summary>
public int TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Stop loss in price steps.
/// </summary>
public int StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// RSI threshold for long entries.
/// </summary>
public decimal RsiBuyRestrict
{
get => _rsiBuyRestrict.Value;
set => _rsiBuyRestrict.Value = value;
}
/// <summary>
/// RSI threshold for short entries.
/// </summary>
public decimal RsiSellRestrict
{
get => _rsiSellRestrict.Value;
set => _rsiSellRestrict.Value = value;
}
/// <summary>
/// Momentum period.
/// </summary>
public int MomentumPeriod
{
get => _momentumPeriod.Value;
set => _momentumPeriod.Value = value;
}
/// <summary>
/// Momentum threshold for long entries.
/// </summary>
public decimal MomentumBuyRestrict
{
get => _momentumBuyRestrict.Value;
set => _momentumBuyRestrict.Value = value;
}
/// <summary>
/// Momentum threshold for short entries.
/// </summary>
public decimal MomentumSellRestrict
{
get => _momentumSellRestrict.Value;
set => _momentumSellRestrict.Value = value;
}
/// <summary>
/// Trading start time.
/// </summary>
public TimeSpan StartTime
{
get => _startTime.Value;
set => _startTime.Value = value;
}
/// <summary>
/// Trading end time.
/// </summary>
public TimeSpan EndTime
{
get => _endTime.Value;
set => _endTime.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="LimitsRsiMomentumBotStrategy"/>.
/// </summary>
public LimitsRsiMomentumBotStrategy()
{
_limitOrderDistance = Param(nameof(LimitOrderDistance), 5)
.SetGreaterThanZero()
.SetDisplay("Limit Order Distance", "Distance from candle open in price steps", "Trading")
.SetOptimize(3, 10, 1);
_takeProfit = Param(nameof(TakeProfit), 35)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Profit target in price steps", "Protection")
.SetOptimize(20, 60, 5);
_stopLoss = Param(nameof(StopLoss), 8)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Loss limit in price steps", "Protection")
.SetOptimize(5, 20, 1);
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Period for RSI calculation", "Indicators");
_rsiBuyRestrict = Param(nameof(RsiBuyRestrict), 30m)
.SetDisplay("RSI Buy Threshold", "Max RSI value to allow buys", "Indicators");
_rsiSellRestrict = Param(nameof(RsiSellRestrict), 70m)
.SetDisplay("RSI Sell Threshold", "Min RSI value to allow sells", "Indicators");
_momentumPeriod = Param(nameof(MomentumPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Momentum Period", "Period for Momentum calculation", "Indicators");
_momentumBuyRestrict = Param(nameof(MomentumBuyRestrict), 1m)
.SetDisplay("Momentum Buy Threshold", "Max Momentum value to allow buys", "Indicators");
_momentumSellRestrict = Param(nameof(MomentumSellRestrict), 1m)
.SetDisplay("Momentum Sell Threshold", "Min Momentum value to allow sells", "Indicators");
_startTime = Param(nameof(StartTime), TimeSpan.Zero)
.SetDisplay("Start Time", "Trading start time", "Trading");
_endTime = Param(nameof(EndTime), new TimeSpan(23, 59, 0))
.SetDisplay("End Time", "Trading end time", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for strategy", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var momentum = new Momentum { Length = MomentumPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, momentum, ProcessCandle)
.Start();
var step = Security.PriceStep ?? 1m;
StartProtection(
takeProfit: new Unit(TakeProfit * step, UnitTypes.Absolute),
stopLoss: new Unit(StopLoss * step, UnitTypes.Absolute));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, rsi);
DrawIndicator(area, momentum);
DrawOwnTrades(area);
}
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_buyOrder = null;
_sellOrder = null;
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal momentumValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!IsTradingTime(candle.OpenTime))
return;
var step = Security.PriceStep ?? 1m;
var buySignal = rsiValue < RsiBuyRestrict && momentumValue < MomentumBuyRestrict && Position <= 0;
var sellSignal = rsiValue > RsiSellRestrict && momentumValue > MomentumSellRestrict && Position >= 0;
if (buySignal)
{
if (_sellOrder != null && _sellOrder.State == OrderStates.Active)
{
CancelOrder(_sellOrder);
_sellOrder = null;
}
if (_buyOrder != null && _buyOrder.State == OrderStates.Active)
return;
var price = candle.OpenPrice - LimitOrderDistance * step;
_buyOrder = BuyLimit(price);
}
else if (_buyOrder != null && _buyOrder.State == OrderStates.Active)
{
CancelOrder(_buyOrder);
_buyOrder = null;
}
if (sellSignal)
{
if (_buyOrder != null && _buyOrder.State == OrderStates.Active)
{
CancelOrder(_buyOrder);
_buyOrder = null;
}
if (_sellOrder != null && _sellOrder.State == OrderStates.Active)
return;
var price = candle.OpenPrice + LimitOrderDistance * step;
_sellOrder = SellLimit(price);
}
else if (_sellOrder != null && _sellOrder.State == OrderStates.Active)
{
CancelOrder(_sellOrder);
_sellOrder = null;
}
}
private bool IsTradingTime(DateTimeOffset time)
{
var t = time.TimeOfDay;
return t >= StartTime && t <= EndTime;
}
/// <inheritdoc />
protected override void OnPositionReceived(Position position)
{
base.OnPositionReceived(position);
if (Position > 0)
{
if (_sellOrder != null && _sellOrder.State == OrderStates.Active)
{
CancelOrder(_sellOrder);
_sellOrder = null;
}
}
else if (Position < 0)
{
if (_buyOrder != null && _buyOrder.State == OrderStates.Active)
{
CancelOrder(_buyOrder);
_buyOrder = null;
}
}
else
{
_buyOrder = null;
_sellOrder = null;
}
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import RelativeStrengthIndex, Momentum
from StockSharp.Algo.Strategies import Strategy
class limits_rsi_momentum_bot_strategy(Strategy):
"""
RSI + Momentum confirmation with StartProtection for SL/TP.
"""
def __init__(self):
super(limits_rsi_momentum_bot_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14).SetDisplay("RSI Period", "RSI period", "Indicators")
self._rsi_buy = self.Param("RsiBuyRestrict", 30.0).SetDisplay("RSI Buy", "Max RSI for buy", "Indicators")
self._rsi_sell = self.Param("RsiSellRestrict", 70.0).SetDisplay("RSI Sell", "Min RSI for sell", "Indicators")
self._mom_period = self.Param("MomentumPeriod", 14).SetDisplay("Mom Period", "Momentum period", "Indicators")
self._mom_buy = self.Param("MomentumBuyRestrict", 1.0).SetDisplay("Mom Buy", "Max momentum for buy", "Indicators")
self._mom_sell = self.Param("MomentumSellRestrict", 1.0).SetDisplay("Mom Sell", "Min momentum for sell", "Indicators")
self._tp_points = self.Param("TakeProfit", 35).SetDisplay("TP", "Take profit in steps", "Risk")
self._sl_points = self.Param("StopLoss", 8).SetDisplay("SL", "Stop loss in steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Timeframe", "General")
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(limits_rsi_momentum_bot_strategy, self).OnReseted()
self._cooldown = 0
def OnStarted2(self, time):
super(limits_rsi_momentum_bot_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
mom = Momentum()
mom.Length = self._mom_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, mom, self._process_candle).Start()
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
tp = Unit(self._tp_points.Value * step, UnitTypes.Absolute)
sl = Unit(self._sl_points.Value * step, UnitTypes.Absolute)
self.StartProtection(tp, sl)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle, rsi_val, mom_val):
if candle.State != CandleStates.Finished:
return
if self._cooldown > 0:
self._cooldown -= 1
return
rsi = float(rsi_val)
mom = float(mom_val)
buy_signal = rsi < self._rsi_buy.Value and mom < self._mom_buy.Value and self.Position <= 0
sell_signal = rsi > self._rsi_sell.Value and mom > self._mom_sell.Value and self.Position >= 0
if buy_signal:
self.BuyMarket()
self._cooldown = 10
elif sell_signal:
self.SellMarket()
self._cooldown = 10
def CreateClone(self):
return limits_rsi_momentum_bot_strategy()