Estrategia cm RSI
Descripción general
Esta estrategia es un port directo del experto de MetaTrader 4 "cm_RSI". Utiliza el indicador de Fuerza Relativa (RSI) para detectar reversiones de momentum.
El algoritmo monitorea los valores del RSI calculados a partir de los precios de apertura de las velas. Una posición larga se abre cuando el RSI sube por encima de un nivel de compra configurable después de estar por debajo. Una posición corta se abre cuando el RSI cae por debajo de un nivel de venta configurable después de estar por encima. Cada operación está protegida por valores fijos de take profit y stop loss expresados en puntos de precio.
Lógica de la estrategia
- Calcular el RSI con un período definido por el usuario usando los precios de apertura de las velas.
- Si el valor anterior del RSI estaba por debajo del nivel de compra y el valor actual cruza por encima, abrir una posición larga a mercado.
- Si el valor anterior del RSI estaba por encima del nivel de venta y el valor actual cruza por debajo, abrir una posición corta a mercado.
- Cada operación usa el mismo volumen configurable y está protegida con órdenes de stop loss y take profit.
Parámetros
| Nombre | Descripción |
|---|---|
RsiPeriod |
Período de cálculo del RSI. |
BuyLevel |
Nivel de RSI utilizado para disparar entradas largas. |
SellLevel |
Nivel de RSI utilizado para disparar entradas cortas. |
TakeProfit |
Take profit en puntos de precio absolutos. |
StopLoss |
Stop loss en puntos de precio absolutos. |
OrderVolume |
Volumen aplicado a cada operación. |
CandleType |
Tipo de velas utilizadas para los cálculos. |
Notas
- La estrategia procesa solo velas terminadas.
- Mantiene una única posición abierta en cualquier momento.
StartProtectionse utiliza para gestionar automáticamente las órdenes de stop loss y take profit.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Wilder RSI on finished candle OPEN prices; market entries on strict threshold crossings.
/// The selected order volume and PriceStep-based protection are applied to every entry.
/// </summary>
public class CmRsiStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _buyLevel;
private readonly StrategyParam<decimal> _sellLevel;
private readonly StrategyParam<int> _takeProfit;
private readonly StrategyParam<int> _stopLoss;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<DataType> _candleType;
private decimal? _previousOpen;
private decimal? _previousRsi;
private decimal _sumGain;
private decimal _sumLoss;
private decimal _averageGain;
private decimal _averageLoss;
private int _changes;
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public decimal BuyLevel { get => _buyLevel.Value; set => _buyLevel.Value = value; }
public decimal SellLevel { get => _sellLevel.Value; set => _sellLevel.Value = value; }
public int TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public int StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal OrderVolume { get => _orderVolume.Value; set => _orderVolume.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public CmRsiStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14).SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI calculation period", "Indicators")
.SetOptimize(7, 21, 1);
_buyLevel = Param(nameof(BuyLevel), 30m)
.SetDisplay("Buy Level", "RSI level to enter long", "Indicators")
.SetOptimize(10m, 40m, 5m);
_sellLevel = Param(nameof(SellLevel), 70m)
.SetDisplay("Sell Level", "RSI level to enter short", "Indicators")
.SetOptimize(60m, 90m, 5m);
_takeProfit = Param(nameof(TakeProfit), 200).SetNotNegative()
.SetDisplay("Take Profit", "Take profit distance in price steps", "Risk Management")
.SetOptimize(100, 400, 50);
_stopLoss = Param(nameof(StopLoss), 100).SetNotNegative()
.SetDisplay("Stop Loss", "Stop loss distance in price steps", "Risk Management")
.SetOptimize(50, 200, 50);
_orderVolume = Param(nameof(OrderVolume), 0.1m).SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume of each trade", "General")
.SetOptimize(0.1m, 1m, 0.1m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetRsi();
}
private void ResetRsi()
{
_previousOpen = null;
_previousRsi = null;
_sumGain = _sumLoss = _averageGain = _averageLoss = 0m;
_changes = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetRsi();
var step = Security.PriceStep ?? 0m;
if (step <= 0m)
throw new InvalidOperationException("Cm RSI requires a positive Security.PriceStep for point-based stops.");
SubscribeCandles(CandleType).Bind(ProcessCandle).Start();
StartProtection(
takeProfit: TakeProfit > 0 ? new Unit(TakeProfit * step, UnitTypes.Absolute) : new Unit(),
stopLoss: StopLoss > 0 ? new Unit(StopLoss * step, UnitTypes.Absolute) : new Unit());
}
private static decimal ToRsi(decimal averageGain, decimal averageLoss)
{
if (averageLoss == 0m)
return averageGain == 0m ? 50m : 100m;
if (averageGain == 0m)
return 0m;
return 100m * averageGain / (averageGain + averageLoss);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var open = candle.OpenPrice;
if (_previousOpen is not decimal lastOpen)
{
_previousOpen = open;
return;
}
_previousOpen = open;
var change = open - lastOpen;
var gain = Math.Max(change, 0m);
var loss = Math.Max(-change, 0m);
if (_changes < RsiPeriod)
{
_sumGain += gain;
_sumLoss += loss;
_changes++;
if (_changes < RsiPeriod)
return;
_averageGain = _sumGain / RsiPeriod;
_averageLoss = _sumLoss / RsiPeriod;
_previousRsi = ToRsi(_averageGain, _averageLoss);
return; // First completed RSI is the signal baseline, never an entry.
}
_averageGain = (_averageGain * (RsiPeriod - 1) + gain) / RsiPeriod;
_averageLoss = (_averageLoss * (RsiPeriod - 1) + loss) / RsiPeriod;
var rsi = ToRsi(_averageGain, _averageLoss);
if (_previousRsi is decimal previous && IsFormedAndOnlineAndAllowTrading())
{
var position = Position;
if (previous < BuyLevel && rsi > BuyLevel && position <= 0m)
{
if (position < 0m)
BuyMarket(-position);
BuyMarket(OrderVolume);
}
else if (previous > SellLevel && rsi < SellLevel && position >= 0m)
{
if (position > 0m)
SellMarket(position);
SellMarket(OrderVolume);
}
}
_previousRsi = rsi;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal, Convert
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class cm_rsi_strategy(Strategy):
"""Wilder RSI on completed candle opens, with volume and price-step protection."""
def __init__(self):
super(cm_rsi_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14).SetDisplay("RSI Period", "RSI calculation period", "Indicators")
self._buy_level = self.Param("BuyLevel", 30.0).SetDisplay("Buy Level", "RSI level to enter long", "Indicators")
self._sell_level = self.Param("SellLevel", 70.0).SetDisplay("Sell Level", "RSI level to enter short", "Indicators")
self._take_profit = self.Param("TakeProfit", 200).SetDisplay("Take Profit", "Take profit in price steps", "Risk Management")
self._stop_loss = self.Param("StopLoss", 100).SetDisplay("Stop Loss", "Stop loss in price steps", "Risk Management")
self._order_volume = self.Param("OrderVolume", 0.1).SetDisplay("Order Volume", "Volume of each trade", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Type of candles", "General")
self._reset_rsi()
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def BuyLevel(self):
return self._buy_level.Value
@BuyLevel.setter
def BuyLevel(self, value):
self._buy_level.Value = value
@property
def SellLevel(self):
return self._sell_level.Value
@SellLevel.setter
def SellLevel(self, value):
self._sell_level.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def OrderVolume(self):
return self._order_volume.Value
@OrderVolume.setter
def OrderVolume(self, value):
self._order_volume.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _reset_rsi(self):
zero = Decimal(0)
self._previous_open = None
self._previous_rsi = None
self._sum_gain = self._sum_loss = zero
self._average_gain = self._average_loss = zero
self._changes = 0
def OnReseted(self):
super(cm_rsi_strategy, self).OnReseted()
self._reset_rsi()
def OnStarted2(self, time):
super(cm_rsi_strategy, self).OnStarted2(time)
self._reset_rsi()
step = self.Security.PriceStep
if step <= 0:
raise ValueError("Cm RSI requires a positive Security.PriceStep for point-based stops")
self.SubscribeCandles(self.CandleType).Bind(self.process_candle).Start()
self.StartProtection(
takeProfit=Unit(Decimal(self.TakeProfit) * step, UnitTypes.Absolute) if self.TakeProfit > 0 else Unit(),
stopLoss=Unit(Decimal(self.StopLoss) * step, UnitTypes.Absolute) if self.StopLoss > 0 else Unit(),
)
@staticmethod
def _to_rsi(avg_gain, avg_loss):
zero = Decimal(0)
if avg_loss == zero:
return Decimal(50) if avg_gain == zero else Decimal(100)
if avg_gain == zero:
return zero
return Decimal(100) * avg_gain / (avg_gain + avg_loss)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
price = candle.OpenPrice
if self._previous_open is None:
self._previous_open = price
return
change = price - self._previous_open
self._previous_open = price
gain = max(change, Decimal(0))
loss = max(-change, Decimal(0))
period = int(self.RsiPeriod)
if period <= 0:
raise ValueError('RsiPeriod must be positive')
if self._changes < period:
self._sum_gain += gain
self._sum_loss += loss
self._changes += 1
if self._changes < period:
return
self._average_gain = self._sum_gain / Decimal(period)
self._average_loss = self._sum_loss / Decimal(period)
self._previous_rsi = self._to_rsi(self._average_gain, self._average_loss)
return
self._average_gain = (self._average_gain * Decimal(period - 1) + gain) / Decimal(period)
self._average_loss = (self._average_loss * Decimal(period - 1) + loss) / Decimal(period)
rsi = self._to_rsi(self._average_gain, self._average_loss)
if self._previous_rsi is not None and self.IsFormedAndOnlineAndAllowTrading():
position = self.Position
if self._previous_rsi < Convert.ToDecimal(self.BuyLevel) and rsi > Convert.ToDecimal(self.BuyLevel) and position <= 0:
if position < 0:
self.BuyMarket(-position)
self.BuyMarket(Convert.ToDecimal(self.OrderVolume))
elif self._previous_rsi > Convert.ToDecimal(self.SellLevel) and rsi < Convert.ToDecimal(self.SellLevel) and position >= 0:
if position > 0:
self.SellMarket(position)
self.SellMarket(Convert.ToDecimal(self.OrderVolume))
self._previous_rsi = rsi
def CreateClone(self):
return cm_rsi_strategy()