Estrategia Improve MA & RSI con Cobertura
Esta estrategia porta el experto original de MetaTrader "Improve" a StockSharp usando la API de alto nivel. Opera simultáneamente dos instrumentos: el símbolo principal seleccionado para la estrategia y un símbolo de cobertura. La dirección de la operación se define por la relación entre dos medias móviles suavizadas en el instrumento principal y el índice de fuerza relativa (RSI). La pata de cobertura refleja la dirección de la pata principal, creando una exposición pareada que busca beneficiarse de movimientos de momentum sincronizados mientras limita el riesgo de un solo instrumento.
Lógica de la estrategia
- Calcular dos Medias Móviles Suavizadas (SMMA) en el símbolo primario con períodos rápido y lento configurables.
- Calcular RSI en las mismas velas y monitorear los umbrales de sobreventa/sobrecompra.
- Entrar largo en ambos instrumentos cuando la SMMA lenta está por encima de la SMMA rápida y el RSI está en o por debajo del umbral de sobreventa.
- Entrar corto en ambos instrumentos cuando la SMMA lenta está por debajo de la SMMA rápida y el RSI está en o por encima del umbral de sobrecompra.
- Las posiciones permanecen abiertas hasta que el beneficio abierto combinado de ambas patas supera el objetivo monetario configurado, en cuyo punto la estrategia liquida ambos lados.
El algoritmo lleva un registro de los precios de cierre más recientes de cada instrumento. El beneficio combinado se estima a partir de la diferencia entre el cierre actual y el precio de entrada almacenado de cada pata. Dado que no se aplica stop-loss, las posiciones pueden permanecer abiertas por períodos prolongados cuando el precio no alcanza el objetivo de beneficio.
Parámetros
| Parámetro |
Descripción |
| Volume |
Cantidad de orden para ambos instrumentos, el primario y el de cobertura. |
| Profit Target |
Objetivo monetario compartido por ambas patas; cuando se alcanza, la estrategia cierra cada posición abierta. |
| Hedge Security |
Instrumento secundario que se opera junto con el instrumento principal. |
| Fast MA |
Período de la Media Móvil Suavizada rápida (predeterminado 8). |
| Slow MA |
Período de la Media Móvil Suavizada lenta (predeterminado 21). Debe ser mayor que el período de la MA rápida. |
| RSI Period |
Longitud usada para calcular el RSI (predeterminado 21). |
| Oversold |
Nivel RSI que activa entradas largas junto con la condición de MA (predeterminado 30). |
| Overbought |
Nivel RSI que activa entradas cortas junto con la condición de MA (predeterminado 70). |
| Candle Type |
Marco temporal para cálculos; por defecto velas de 1 hora pero puede ajustarse. |
Indicadores
- Media Móvil Suavizada (SMMA) – usada dos veces para definir los componentes de tendencia rápida y lenta.
- Índice de Fuerza Relativa (RSI) – determina condiciones de sobreventa/sobrecompra para confirmación.
Reglas de entrada y salida
- Entrada larga
- SMMA lenta > SMMA rápida en el símbolo primario.
- RSI ≤ Sobreventa.
- Ambas patas se abren con órdenes a mercado en la misma dirección (compra/compra).
- Entrada corta
- SMMA lenta < SMMA rápida en el símbolo primario.
- RSI ≥ Sobrecompra.
- Ambas patas se abren con órdenes a mercado en la misma dirección (venta/venta).
- Salida
- Cuando
(beneficio primario + beneficio de cobertura) ≥ Profit Target, la estrategia cierra ambas posiciones usando órdenes a mercado.
- No se aplica lógica adicional de stop-loss o trailing; la gestión de riesgos debe agregarse externamente si se requiere.
Notas de uso
- Asegurarse de que tanto el instrumento principal como el de cobertura estén asignados antes de iniciar la estrategia; de lo contrario lanzará una excepción.
- La estimación de beneficio combinado depende de los precios de cierre de velas. El deslizamiento y las diferencias de ejecución entre las dos patas pueden afectar el beneficio real realizado.
- Dado que la estrategia abre ambas patas simultáneamente, es adecuada para instrumentos correlacionados (por ejemplo, pares de divisas o futuros relacionados) donde se espera que se muevan en tándem.
- Considerar añadir controles de riesgo a nivel de portafolio cuando se opere en vivo, ya que el algoritmo original usa solo el objetivo de beneficio virtual para las salidas.
using System;
using System.Linq;
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;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Dual smoothed moving average and RSI hedge strategy converted from Improve.mq5.
/// </summary>
public class ImproveMaRsiHedgeStrategy : Strategy
{
private readonly StrategyParam<decimal> _profitTarget;
private readonly StrategyParam<Security> _hedgeSecurity;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _oversoldLevel;
private readonly StrategyParam<decimal> _overboughtLevel;
private readonly StrategyParam<DataType> _candleType;
private SmoothedMovingAverage _fastMa = null!;
private SmoothedMovingAverage _slowMa = null!;
private RelativeStrengthIndex _rsi = null!;
private decimal _baseLastClose;
private decimal _hedgeLastClose;
private decimal _baseEntryPrice;
private decimal _hedgeEntryPrice;
private bool _hasBaseClose;
private bool _hasHedgeClose;
private int _pairDirection;
/// <summary>
/// Profit target across both legs expressed in money.
/// </summary>
public decimal ProfitTarget
{
get => _profitTarget.Value;
set => _profitTarget.Value = value;
}
/// <summary>
/// Second instrument traded alongside the primary security.
/// </summary>
public Security HedgeSecurity
{
get => _hedgeSecurity.Value;
set => _hedgeSecurity.Value = value;
}
/// <summary>
/// Smoothed moving average period for the fast line.
/// </summary>
public int FastMaPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Smoothed moving average period for the slow line.
/// </summary>
public int SlowMaPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// RSI calculation length.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// RSI oversold threshold.
/// </summary>
public decimal OversoldLevel
{
get => _oversoldLevel.Value;
set => _oversoldLevel.Value = value;
}
/// <summary>
/// RSI overbought threshold.
/// </summary>
public decimal OverboughtLevel
{
get => _overboughtLevel.Value;
set => _overboughtLevel.Value = value;
}
/// <summary>
/// Type of candles used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ImproveMaRsiHedgeStrategy"/> class.
/// </summary>
public ImproveMaRsiHedgeStrategy()
{
_profitTarget = Param(nameof(ProfitTarget), 50m)
.SetGreaterThanZero()
.SetDisplay("Profit Target", "Combined profit target across both legs", "Risk")
;
_hedgeSecurity = Param<Security>(nameof(HedgeSecurity))
.SetDisplay("Hedge Security", "Secondary instrument to trade", "General");
_fastPeriod = Param(nameof(FastMaPeriod), 8)
.SetGreaterThanZero()
.SetDisplay("Fast MA", "Fast smoothed MA period", "Indicators")
;
_slowPeriod = Param(nameof(SlowMaPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("Slow MA", "Slow smoothed MA period", "Indicators")
;
_rsiPeriod = Param(nameof(RsiPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Length of the RSI", "Indicators")
;
_oversoldLevel = Param(nameof(OversoldLevel), 30m)
.SetDisplay("Oversold", "RSI oversold threshold", "Indicators")
;
_overboughtLevel = Param(nameof(OverboughtLevel), 70m)
.SetDisplay("Overbought", "RSI overbought threshold", "Indicators")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Time frame for calculations", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security != null)
yield return (Security, CandleType);
if (HedgeSecurity != null)
yield return (HedgeSecurity, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastMa = null!;
_slowMa = null!;
_rsi = null!;
_baseLastClose = 0m;
_hedgeLastClose = 0m;
_baseEntryPrice = 0m;
_hedgeEntryPrice = 0m;
_hasBaseClose = false;
_hasHedgeClose = false;
_pairDirection = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (Security == null)
throw new InvalidOperationException("Primary security must be specified.");
if (HedgeSecurity == null)
throw new InvalidOperationException("Hedge security must be specified.");
if (FastMaPeriod >= SlowMaPeriod)
throw new InvalidOperationException("Fast MA period must be less than slow MA period.");
_fastMa = new SmoothedMovingAverage { Length = FastMaPeriod };
_slowMa = new SmoothedMovingAverage { Length = SlowMaPeriod };
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var baseSubscription = SubscribeCandles(CandleType);
baseSubscription
.Bind(_fastMa, _slowMa, _rsi, ProcessBaseCandle)
.Start();
var hedgeSubscription = SubscribeCandles(CandleType, false, HedgeSecurity);
hedgeSubscription
.Bind(ProcessHedgeCandle)
.Start();
}
private void ProcessBaseCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
_baseLastClose = candle.ClosePrice;
_hasBaseClose = true;
CheckProfitTarget();
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!_fastMa.IsFormed || !_slowMa.IsFormed || !_rsi.IsFormed)
return;
if (_pairDirection != 0)
return;
if (!_hasHedgeClose)
return;
if (slowValue > fastValue && rsiValue <= OversoldLevel)
{
OpenPair(1);
}
else if (slowValue < fastValue && rsiValue >= OverboughtLevel)
{
OpenPair(-1);
}
}
private void ProcessHedgeCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_hedgeLastClose = candle.ClosePrice;
_hasHedgeClose = true;
CheckProfitTarget();
}
private void OpenPair(int direction)
{
if (direction == 0)
return;
var basePos = GetPositionValue(Security, Portfolio) ?? 0m;
var hedgePos = GetPositionValue(HedgeSecurity, Portfolio) ?? 0m;
if (basePos != 0m || hedgePos != 0m)
return;
var volume = Volume;
if (direction > 0)
{
BuyMarket(volume, Security);
BuyMarket(volume, HedgeSecurity);
}
else
{
SellMarket(volume, Security);
SellMarket(volume, HedgeSecurity);
}
_pairDirection = direction;
_baseEntryPrice = _baseLastClose;
_hedgeEntryPrice = _hedgeLastClose;
}
private void CheckProfitTarget()
{
if (_pairDirection == 0 || !_hasBaseClose || !_hasHedgeClose)
return;
var baseProfit = _pairDirection > 0
? (_baseLastClose - _baseEntryPrice) * Volume
: (_baseEntryPrice - _baseLastClose) * Volume;
var hedgeProfit = _pairDirection > 0
? (_hedgeLastClose - _hedgeEntryPrice) * Volume
: (_hedgeEntryPrice - _hedgeLastClose) * Volume;
var totalProfit = baseProfit + hedgeProfit;
if (totalProfit >= ProfitTarget)
{
ClosePair();
}
}
private void ClosePair()
{
var basePos = GetPositionValue(Security, Portfolio) ?? 0m;
if (basePos > 0)
{
SellMarket(basePos, Security);
}
else if (basePos < 0)
{
BuyMarket(-basePos, Security);
}
var hedgePos = GetPositionValue(HedgeSecurity, Portfolio) ?? 0m;
if (hedgePos > 0)
{
SellMarket(hedgePos, HedgeSecurity);
}
else if (hedgePos < 0)
{
BuyMarket(-hedgePos, HedgeSecurity);
}
_pairDirection = 0;
_baseEntryPrice = 0m;
_hedgeEntryPrice = 0m;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Indicators import SmoothedMovingAverage, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType
from System import TimeSpan
class improve_ma_rsi_hedge_strategy(Strategy):
def __init__(self):
super(improve_ma_rsi_hedge_strategy, self).__init__()
self._profit_target = self.Param("ProfitTarget", 50.0)
self._fast_period = self.Param("FastMaPeriod", 8)
self._slow_period = self.Param("SlowMaPeriod", 21)
self._rsi_period = self.Param("RsiPeriod", 21)
self._oversold_level = self.Param("OversoldLevel", 30.0)
self._overbought_level = self.Param("OverboughtLevel", 70.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._fast_ma = None
self._slow_ma = None
self._rsi = None
self._base_last_close = 0.0
self._base_entry_price = 0.0
self._pair_direction = 0
@property
def ProfitTarget(self):
return self._profit_target.Value
@property
def FastMaPeriod(self):
return self._fast_period.Value
@property
def SlowMaPeriod(self):
return self._slow_period.Value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@property
def OversoldLevel(self):
return self._oversold_level.Value
@property
def OverboughtLevel(self):
return self._overbought_level.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(improve_ma_rsi_hedge_strategy, self).OnStarted2(time)
self._fast_ma = SmoothedMovingAverage()
self._fast_ma.Length = self.FastMaPeriod
self._slow_ma = SmoothedMovingAverage()
self._slow_ma.Length = self.SlowMaPeriod
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiPeriod
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._fast_ma, self._slow_ma, self._rsi, self._process_candle).Start()
def _process_candle(self, candle, fast_val, slow_val, rsi_val):
fast_value = float(fast_val)
slow_value = float(slow_val)
rsi_value = float(rsi_val)
self._base_last_close = float(candle.ClosePrice)
if not self._fast_ma.IsFormed or not self._slow_ma.IsFormed or not self._rsi.IsFormed:
return
if self._pair_direction != 0:
if self._pair_direction > 0:
pnl = (self._base_last_close - self._base_entry_price) * float(self.Volume)
else:
pnl = (self._base_entry_price - self._base_last_close) * float(self.Volume)
if pnl >= self.ProfitTarget:
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(abs(self.Position))
self._pair_direction = 0
self._base_entry_price = 0.0
return
if slow_value > fast_value and rsi_value <= self.OversoldLevel:
self.BuyMarket()
self._pair_direction = 1
self._base_entry_price = self._base_last_close
elif slow_value < fast_value and rsi_value >= self.OverboughtLevel:
self.SellMarket()
self._pair_direction = -1
self._base_entry_price = self._base_last_close
def OnReseted(self):
super(improve_ma_rsi_hedge_strategy, self).OnReseted()
self._fast_ma = None
self._slow_ma = None
self._rsi = None
self._base_last_close = 0.0
self._base_entry_price = 0.0
self._pair_direction = 0
def CreateClone(self):
return improve_ma_rsi_hedge_strategy()