Estrategia Tendency EMA + RSI
Esta estrategia superpone un cruce de EMA rápida/media sobre una EMA de tendencia más lenta y un filtro RSI. Las operaciones largas requieren que la EMA rápida cruce por encima de la EMA media mientras ambas permanecen por encima de la línea de tendencia lenta y la vela cierra alcista. Las operaciones cortas reflejan estas reglas. Los extremos del RSI cierran posiciones, y una función opcional de "cerrar después de X barras" fija las ganancias si el precio se mueve en la dirección esperada rápidamente.
El enfoque busca participar solo en entradas de retroceso que se alineen con la tendencia prevalente, usando el RSI para salir cuando el momentum se estira en exceso. Funciona mejor en gráficos intradía donde los cruces de EMA ofrecen señales oportunas y se producen múltiples configuraciones en cada sesión.
Detalles
- Criterios de entrada:
- EMA rápida cruza por encima de EMA media, ambas por encima de EMA lenta, vela alcista.
- EMA rápida cruza por debajo de EMA media, ambas por debajo de EMA lenta, vela bajista.
- Largo/Corto: Largo habilitado, corto opcional.
- Criterios de salida:
- RSI > 70 cierra largo; RSI < 30 cierra corto.
- Opcional: cerrar después de X barras si la operación es rentable.
- Stops: Ninguno incorporado.
- Valores predeterminados:
- Longitud RSI = 14.
- Longitudes EMA A/B/C = 9/21/50.
- Cerrar después de X barras = desactivado, X = 5.
- Filtros:
- Categoría: Tendencia + Momentum
- Dirección: Ambos (largo por defecto)
- Indicadores: EMA, RSI
- Stops: No
- Complejidad: Moderado
- Marco temporal: Corto
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Tendency EMA + RSI Strategy.
/// Uses EMA crossover with RSI and trend filter.
/// Buys when fast EMA crosses above medium EMA while above slow EMA.
/// Exits when RSI becomes overbought/oversold.
/// </summary>
public class TendencyEmaRsiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _emaALength;
private readonly StrategyParam<int> _emaBLength;
private readonly StrategyParam<int> _emaCLength;
private readonly StrategyParam<int> _cooldownBars;
private RelativeStrengthIndex _rsi;
private ExponentialMovingAverage _emaA;
private ExponentialMovingAverage _emaB;
private ExponentialMovingAverage _emaC;
private decimal _prevEmaA;
private decimal _prevEmaB;
private int _cooldownRemaining;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int EmaALength
{
get => _emaALength.Value;
set => _emaALength.Value = value;
}
public int EmaBLength
{
get => _emaBLength.Value;
set => _emaBLength.Value = value;
}
public int EmaCLength
{
get => _emaCLength.Value;
set => _emaCLength.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public TendencyEmaRsiStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI calculation length", "RSI");
_emaALength = Param(nameof(EmaALength), 10)
.SetGreaterThanZero()
.SetDisplay("EMA A Length", "Fast EMA length", "Moving Averages");
_emaBLength = Param(nameof(EmaBLength), 20)
.SetGreaterThanZero()
.SetDisplay("EMA B Length", "Medium EMA length", "Moving Averages");
_emaCLength = Param(nameof(EmaCLength), 100)
.SetGreaterThanZero()
.SetDisplay("EMA C Length", "Slow/Trend EMA length", "Moving Averages");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsi = null;
_emaA = null;
_emaB = null;
_emaC = null;
_prevEmaA = 0;
_prevEmaB = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = RsiLength };
_emaA = new ExponentialMovingAverage { Length = EmaALength };
_emaB = new ExponentialMovingAverage { Length = EmaBLength };
_emaC = new ExponentialMovingAverage { Length = EmaCLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, _emaA, _emaB, _emaC, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _emaA);
DrawIndicator(area, _emaB);
DrawIndicator(area, _emaC);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal rsiVal, decimal emaA, decimal emaB, decimal emaC)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed || !_emaA.IsFormed || !_emaB.IsFormed || !_emaC.IsFormed)
{
_prevEmaA = emaA;
_prevEmaB = emaB;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevEmaA = emaA;
_prevEmaB = emaB;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevEmaA = emaA;
_prevEmaB = emaB;
return;
}
if (_prevEmaA == 0 || _prevEmaB == 0)
{
_prevEmaA = emaA;
_prevEmaB = emaB;
return;
}
// EMA crossovers
var emaCrossUp = emaA > emaB && _prevEmaA <= _prevEmaB;
var emaCrossDown = emaA < emaB && _prevEmaA >= _prevEmaB;
// Buy: EMA A crosses above EMA B + EMA A > EMA C (uptrend) + bullish candle
if (emaCrossUp && emaA > emaC && candle.ClosePrice > candle.OpenPrice && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Sell: EMA A crosses below EMA B + EMA A < EMA C (downtrend) + bearish candle
else if (emaCrossDown && emaA < emaC && candle.ClosePrice < candle.OpenPrice && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long: RSI overbought
else if (Position > 0 && rsiVal > 70)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Exit short: RSI oversold
else if (Position < 0 && rsiVal < 30)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
_prevEmaA = emaA;
_prevEmaB = emaB;
}
}
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 tendency_ema_rsi_strategy(Strategy):
"""Tendency EMA + RSI Strategy."""
def __init__(self):
super(tendency_ema_rsi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI calculation length", "RSI")
self._ema_a_length = self.Param("EmaALength", 10) \
.SetDisplay("EMA A Length", "Fast EMA length", "Moving Averages")
self._ema_b_length = self.Param("EmaBLength", 20) \
.SetDisplay("EMA B Length", "Medium EMA length", "Moving Averages")
self._ema_c_length = self.Param("EmaCLength", 100) \
.SetDisplay("EMA C Length", "Slow/Trend EMA length", "Moving Averages")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._rsi = None
self._ema_a = None
self._ema_b = None
self._ema_c = None
self._prev_ema_a = 0.0
self._prev_ema_b = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(tendency_ema_rsi_strategy, self).OnReseted()
self._rsi = None
self._ema_a = None
self._ema_b = None
self._ema_c = None
self._prev_ema_a = 0.0
self._prev_ema_b = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(tendency_ema_rsi_strategy, self).OnStarted2(time)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_length.Value)
self._ema_a = ExponentialMovingAverage()
self._ema_a.Length = int(self._ema_a_length.Value)
self._ema_b = ExponentialMovingAverage()
self._ema_b.Length = int(self._ema_b_length.Value)
self._ema_c = ExponentialMovingAverage()
self._ema_c.Length = int(self._ema_c_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._rsi, self._ema_a, self._ema_b, self._ema_c, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ema_a)
self.DrawIndicator(area, self._ema_b)
self.DrawIndicator(area, self._ema_c)
self.DrawOwnTrades(area)
def _on_process(self, candle, rsi_val, ema_a_val, ema_b_val, ema_c_val):
if candle.State != CandleStates.Finished:
return
if not self._rsi.IsFormed or not self._ema_a.IsFormed or not self._ema_b.IsFormed or not self._ema_c.IsFormed:
self._prev_ema_a = float(ema_a_val)
self._prev_ema_b = float(ema_b_val)
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_ema_a = float(ema_a_val)
self._prev_ema_b = float(ema_b_val)
return
ema_a = float(ema_a_val)
ema_b = float(ema_b_val)
ema_c = float(ema_c_val)
rsi = float(rsi_val)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_ema_a = ema_a
self._prev_ema_b = ema_b
return
if self._prev_ema_a == 0.0 or self._prev_ema_b == 0.0:
self._prev_ema_a = ema_a
self._prev_ema_b = ema_b
return
close = float(candle.ClosePrice)
open_price = float(candle.OpenPrice)
cooldown = int(self._cooldown_bars.Value)
ema_cross_up = ema_a > ema_b and self._prev_ema_a <= self._prev_ema_b
ema_cross_down = ema_a < ema_b and self._prev_ema_a >= self._prev_ema_b
if ema_cross_up and ema_a > ema_c and close > open_price and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif ema_cross_down and ema_a < ema_c and close < open_price and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
elif self.Position > 0 and rsi > 70:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and rsi < 30:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
self._prev_ema_a = ema_a
self._prev_ema_b = ema_b
def CreateClone(self):
return tendency_ema_rsi_strategy()