Estrategia RSI de Interés Abierto en Opciones
La estrategia RSI Option Open Interest está construida en torno al interés abierto en opciones del RSI.
Las pruebas indican un retorno anual promedio de aproximadamente 130%. Funciona mejor en el mercado de acciones.
Las señales se activan cuando Option confirma cambios de tendencia en datos intradía (5m). Esto hace que el método sea adecuado para traders activos.
Los stops se basan en múltiplos de ATR y factores como RsiPeriod, CandleType. Ajuste estos valores predeterminados para equilibrar el riesgo y la recompensa.
Detalles
- Criterios de entrada: ver implementación para condiciones de indicadores.
- Largo/Corto: Ambos direcciones.
- Criterios de salida: señal opuesta o lógica de stop.
- Stops: Sí, usando cálculos basados en indicadores.
- Valores predeterminados:
RsiPeriod = 14CandleType = TimeSpan.FromMinutes(5).TimeFrame()OiPeriod = 20OiDeviationFactor = 2mStopLoss = 2m
- Filtros:
- Categoría: Seguimiento de tendencia
- Dirección: Ambos
- Indicadores: Option, Open, Interest
- Stops: Sí
- Complejidad: Intermedio
- Marco temporal: Intradía (5m)
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
using System;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// RSI strategy filtered by deterministic option open-interest spikes.
/// </summary>
public class RsiWithOptionOpenInterestStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _oiPeriod;
private readonly StrategyParam<decimal> _oiDeviationFactor;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<int> _cooldownBars;
private RelativeStrengthIndex _rsi = null!;
private SimpleMovingAverage _callOiSma = null!;
private SimpleMovingAverage _putOiSma = null!;
private StandardDeviation _callOiStdDev = null!;
private StandardDeviation _putOiStdDev = null!;
private decimal _currentCallOi;
private decimal _currentPutOi;
private decimal _avgCallOi;
private decimal _avgPutOi;
private decimal _stdDevCallOi;
private decimal _stdDevPutOi;
private decimal? _prevRsi;
private bool _prevCallOiSpike;
private bool _prevPutOiSpike;
private int _cooldownRemaining;
/// <summary>
/// RSI period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Open interest averaging period.
/// </summary>
public int OiPeriod
{
get => _oiPeriod.Value;
set => _oiPeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier for OI threshold.
/// </summary>
public decimal OiDeviationFactor
{
get => _oiDeviationFactor.Value;
set => _oiDeviationFactor.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Closed candles to wait before another position change.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initialize strategy.
/// </summary>
public RsiWithOptionOpenInterestStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetRange(5, 30)
.SetDisplay("RSI Period", "Period for RSI calculation", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_oiPeriod = Param(nameof(OiPeriod), 20)
.SetRange(10, 50)
.SetDisplay("OI Period", "Period for open interest averaging", "Options");
_oiDeviationFactor = Param(nameof(OiDeviationFactor), 2.5m)
.SetRange(1m, 4m)
.SetDisplay("OI StdDev Factor", "Standard deviation multiplier for OI threshold", "Options");
_stopLoss = Param(nameof(StopLoss), 2m)
.SetRange(1m, 5m)
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk Management");
_cooldownBars = Param(nameof(CooldownBars), 18)
.SetNotNegative()
.SetDisplay("Cooldown Bars", "Closed candles to wait before another position change", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsi?.Reset();
_callOiSma?.Reset();
_putOiSma?.Reset();
_callOiStdDev?.Reset();
_putOiStdDev?.Reset();
_rsi = null!;
_callOiSma = null!;
_putOiSma = null!;
_callOiStdDev = null!;
_putOiStdDev = null!;
_currentCallOi = 0m;
_currentPutOi = 0m;
_avgCallOi = 0m;
_avgPutOi = 0m;
_stdDevCallOi = 0m;
_stdDevPutOi = 0m;
_prevRsi = null;
_prevCallOiSpike = false;
_prevPutOiSpike = false;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex
{
Length = RsiPeriod
};
_callOiSma = new SimpleMovingAverage
{
Length = OiPeriod
};
_callOiStdDev = new StandardDeviation
{
Length = OiPeriod
};
_putOiSma = new SimpleMovingAverage
{
Length = OiPeriod
};
_putOiStdDev = new StandardDeviation
{
Length = OiPeriod
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _rsi);
DrawOwnTrades(area);
}
StartProtection(
new Unit(2, UnitTypes.Percent),
new Unit(StopLoss, UnitTypes.Percent)
);
}
private void ProcessCandle(ICandleMessage candle, decimal rsi)
{
if (candle.State != CandleStates.Finished)
return;
SimulateOptionOi(candle);
var callOiValueSma = _callOiSma.Process(new DecimalIndicatorValue(_callOiSma, _currentCallOi, candle.OpenTime) { IsFinal = true });
var putOiValueSma = _putOiSma.Process(new DecimalIndicatorValue(_putOiSma, _currentPutOi, candle.OpenTime) { IsFinal = true });
var callOiValueStdDev = _callOiStdDev.Process(new DecimalIndicatorValue(_callOiStdDev, _currentCallOi, candle.OpenTime) { IsFinal = true });
var putOiValueStdDev = _putOiStdDev.Process(new DecimalIndicatorValue(_putOiStdDev, _currentPutOi, candle.OpenTime) { IsFinal = true });
if (!_callOiSma.IsFormed || !_putOiSma.IsFormed || !_callOiStdDev.IsFormed || !_putOiStdDev.IsFormed ||
callOiValueSma.IsEmpty || putOiValueSma.IsEmpty || callOiValueStdDev.IsEmpty || putOiValueStdDev.IsEmpty)
{
_prevRsi = rsi;
return;
}
_avgCallOi = callOiValueSma.ToDecimal();
_avgPutOi = putOiValueSma.ToDecimal();
_stdDevCallOi = callOiValueStdDev.ToDecimal();
_stdDevPutOi = putOiValueStdDev.ToDecimal();
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevRsi = rsi;
return;
}
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var callOiThreshold = _avgCallOi + (OiDeviationFactor * _stdDevCallOi);
var putOiThreshold = _avgPutOi + (OiDeviationFactor * _stdDevPutOi);
var callOiSpike = _currentCallOi > callOiThreshold;
var putOiSpike = _currentPutOi > putOiThreshold;
var callOiSpikeTransition = !_prevCallOiSpike && callOiSpike;
var putOiSpikeTransition = !_prevPutOiSpike && putOiSpike;
var oversoldCross = _prevRsi is decimal previousRsi && previousRsi >= 35m && rsi < 35m;
var overboughtCross = _prevRsi is decimal previousRsi2 && previousRsi2 <= 65m && rsi > 65m;
if (_cooldownRemaining == 0 && oversoldCross && callOiSpikeTransition && Position <= 0)
{
BuyMarket(Volume + (Position < 0 ? Math.Abs(Position) : 0m));
_cooldownRemaining = CooldownBars;
}
else if (_cooldownRemaining == 0 && overboughtCross && putOiSpikeTransition && Position >= 0)
{
SellMarket(Volume + (Position > 0 ? Math.Abs(Position) : 0m));
_cooldownRemaining = CooldownBars;
}
else if (Position > 0 && rsi >= 52m)
{
SellMarket(Position);
_cooldownRemaining = CooldownBars;
}
else if (Position < 0 && rsi <= 48m)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
_prevRsi = rsi;
_prevCallOiSpike = callOiSpike;
_prevPutOiSpike = putOiSpike;
}
private void SimulateOptionOi(ICandleMessage candle)
{
var range = Math.Max(candle.HighPrice - candle.LowPrice, 1m);
var body = candle.ClosePrice - candle.OpenPrice;
var bodyRatio = Math.Abs(body) / range;
var rangeRatio = range / Math.Max(candle.OpenPrice, 1m);
var baseOi = Math.Max(candle.TotalVolume, 1m);
var spikeFactor = 1m + Math.Min(0.75m, (bodyRatio * 0.5m) + (rangeRatio * 20m));
if (body >= 0)
{
_currentCallOi = baseOi * spikeFactor;
_currentPutOi = baseOi * (0.75m + (1m - bodyRatio) * 0.25m);
}
else
{
_currentCallOi = baseOi * (0.75m + (1m - bodyRatio) * 0.25m);
_currentPutOi = baseOi * spikeFactor;
}
}
}
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, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, SimpleMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class rsi_with_option_open_interest_strategy(Strategy):
"""
RSI strategy filtered by deterministic option open-interest spikes.
"""
def __init__(self):
super(rsi_with_option_open_interest_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetRange(5, 30) \
.SetDisplay("RSI Period", "Period for RSI calculation", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._oi_period = self.Param("OiPeriod", 20) \
.SetRange(10, 50) \
.SetDisplay("OI Period", "Period for open interest averaging", "Options")
self._oi_deviation_factor = self.Param("OiDeviationFactor", 2.5) \
.SetRange(1.0, 4.0) \
.SetDisplay("OI StdDev Factor", "Standard deviation multiplier for OI threshold", "Options")
self._stop_loss = self.Param("StopLoss", 2.0) \
.SetRange(1.0, 5.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk Management")
self._cooldown_bars = self.Param("CooldownBars", 18) \
.SetNotNegative() \
.SetDisplay("Cooldown Bars", "Closed candles to wait before another position change", "General")
self._rsi = None
self._call_oi_sma = None
self._put_oi_sma = None
self._call_oi_std = None
self._put_oi_std = None
self._current_call_oi = 0.0
self._current_put_oi = 0.0
self._avg_call_oi = 0.0
self._avg_put_oi = 0.0
self._std_call_oi = 0.0
self._std_put_oi = 0.0
self._prev_rsi = None
self._prev_call_oi_spike = False
self._prev_put_oi_spike = False
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(rsi_with_option_open_interest_strategy, self).OnReseted()
self._rsi = None
self._call_oi_sma = None
self._put_oi_sma = None
self._call_oi_std = None
self._put_oi_std = None
self._current_call_oi = 0.0
self._current_put_oi = 0.0
self._avg_call_oi = 0.0
self._avg_put_oi = 0.0
self._std_call_oi = 0.0
self._std_put_oi = 0.0
self._prev_rsi = None
self._prev_call_oi_spike = False
self._prev_put_oi_spike = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(rsi_with_option_open_interest_strategy, self).OnStarted2(time)
oi_period = int(self._oi_period.Value)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_period.Value)
self._call_oi_sma = SimpleMovingAverage()
self._call_oi_sma.Length = oi_period
self._call_oi_std = StandardDeviation()
self._call_oi_std.Length = oi_period
self._put_oi_sma = SimpleMovingAverage()
self._put_oi_sma.Length = oi_period
self._put_oi_std = StandardDeviation()
self._put_oi_std.Length = oi_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._rsi, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._rsi)
self.DrawOwnTrades(area)
self.StartProtection(
Unit(2, UnitTypes.Percent),
Unit(float(self._stop_loss.Value), UnitTypes.Percent)
)
def ProcessCandle(self, candle, rsi):
if candle.State != CandleStates.Finished:
return
self.SimulateOptionOi(candle)
rsi_val = float(rsi)
call_sma_result = process_float(self._call_oi_sma, self._current_call_oi, candle.OpenTime, True)
put_sma_result = process_float(self._put_oi_sma, self._current_put_oi, candle.OpenTime, True)
call_std_result = process_float(self._call_oi_std, self._current_call_oi, candle.OpenTime, True)
put_std_result = process_float(self._put_oi_std, self._current_put_oi, candle.OpenTime, True)
if not self._call_oi_sma.IsFormed or not self._put_oi_sma.IsFormed or \
not self._call_oi_std.IsFormed or not self._put_oi_std.IsFormed or \
call_sma_result.IsEmpty or put_sma_result.IsEmpty or \
call_std_result.IsEmpty or put_std_result.IsEmpty:
self._prev_rsi = rsi_val
return
self._avg_call_oi = float(call_sma_result)
self._avg_put_oi = float(put_sma_result)
self._std_call_oi = float(call_std_result)
self._std_put_oi = float(put_std_result)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_rsi = rsi_val
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
oi_dev = float(self._oi_deviation_factor.Value)
cooldown = int(self._cooldown_bars.Value)
call_oi_threshold = self._avg_call_oi + oi_dev * self._std_call_oi
put_oi_threshold = self._avg_put_oi + oi_dev * self._std_put_oi
call_oi_spike = self._current_call_oi > call_oi_threshold
put_oi_spike = self._current_put_oi > put_oi_threshold
call_oi_spike_transition = (not self._prev_call_oi_spike) and call_oi_spike
put_oi_spike_transition = (not self._prev_put_oi_spike) and put_oi_spike
oversold_cross = self._prev_rsi is not None and self._prev_rsi >= 35.0 and rsi_val < 35.0
overbought_cross = self._prev_rsi is not None and self._prev_rsi <= 65.0 and rsi_val > 65.0
if self._cooldown_remaining == 0 and oversold_cross and call_oi_spike_transition and self.Position <= 0:
vol = self.Volume
if self.Position < 0:
vol = self.Volume + Math.Abs(self.Position)
self.BuyMarket(vol)
self._cooldown_remaining = cooldown
elif self._cooldown_remaining == 0 and overbought_cross and put_oi_spike_transition and self.Position >= 0:
vol = self.Volume
if self.Position > 0:
vol = self.Volume + Math.Abs(self.Position)
self.SellMarket(vol)
self._cooldown_remaining = cooldown
elif self.Position > 0 and rsi_val >= 52.0:
self.SellMarket(self.Position)
self._cooldown_remaining = cooldown
elif self.Position < 0 and rsi_val <= 48.0:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
self._prev_rsi = rsi_val
self._prev_call_oi_spike = call_oi_spike
self._prev_put_oi_spike = put_oi_spike
def SimulateOptionOi(self, candle):
range_val = max(float(candle.HighPrice - candle.LowPrice), 1.0)
body = float(candle.ClosePrice - candle.OpenPrice)
body_ratio = abs(body) / range_val
range_ratio = range_val / max(float(candle.OpenPrice), 1.0)
base_oi = max(float(candle.TotalVolume), 1.0)
spike_factor = 1.0 + min(0.75, (body_ratio * 0.5) + (range_ratio * 20.0))
if body >= 0:
self._current_call_oi = base_oi * spike_factor
self._current_put_oi = base_oi * (0.75 + (1.0 - body_ratio) * 0.25)
else:
self._current_call_oi = base_oi * (0.75 + (1.0 - body_ratio) * 0.25)
self._current_put_oi = base_oi * spike_factor
def CreateClone(self):
return rsi_with_option_open_interest_strategy()