Estratégia cm RSI
Visão geral
Esta estratégia é um port direto do especialista MetaTrader 4 "cm_RSI". Usa o indicador de Força Relativa (RSI) para capturar reversões de momentum.
O algoritmo monitora os valores do RSI calculados a partir dos preços de abertura das velas. Uma posição comprada é aberta quando o RSI sobe acima de um nível de compra configurável após estar abaixo. Uma posição vendida é aberta quando o RSI cai abaixo de um nível de venda configurável após estar acima. Cada negociação é protegida por valores fixos de take profit e stop loss expressos em pontos de preço.
Lógica da estratégia
- Calcular o RSI com um período definido pelo usuário usando os preços de abertura das velas.
- Se o valor anterior do RSI estava abaixo do nível de compra e o valor atual cruza acima, abrir uma posição comprada a mercado.
- Se o valor anterior do RSI estava acima do nível de venda e o valor atual cruza abaixo, abrir uma posição vendida a mercado.
- Cada negociação usa o mesmo volume configurável e é protegida com ordens de stop loss e take profit.
Parâmetros
| Nome | Descrição |
|---|---|
RsiPeriod |
Período de cálculo do RSI. |
BuyLevel |
Nível do RSI usado para acionar entradas compradas. |
SellLevel |
Nível do RSI usado para acionar entradas vendidas. |
TakeProfit |
Take profit em pontos de preço absolutos. |
StopLoss |
Stop loss em pontos de preço absolutos. |
OrderVolume |
Volume aplicado a cada negociação. |
CandleType |
Tipo de velas usadas para os cálculos. |
Notas
- A estratégia processa apenas velas concluídas.
- Mantém uma única posição aberta a qualquer momento.
StartProtectioné usado para gerenciar automaticamente as ordens de stop loss e 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()