Estrategia de Divergencia
Estrategia basada en la divergencia entre precio y RSI con detección simple de pivotes.
La Estrategia de Divergencia utiliza máximos y mínimos de pivote en el precio y el RSI para detectar divergencias alcistas y bajistas. Cuando el precio hace un nuevo máximo pero el RSI no lo confirma, la estrategia vende. Por el contrario, cuando el precio hace un nuevo mínimo mientras el RSI sube, compra.
Detalles
- Criterios de entrada: Divergencias entre precio y RSI.
- Largo/Corto: Ambas direcciones (configurable).
- Criterios de salida: Señal opuesta del RSI u órdenes de protección.
- Stops: Sí (stop loss y take profit).
- Valores predeterminados:
TradeDirection= BothRsiPeriod= 14StopLossPercent= 2mRiskReward= 2mCandleType= TimeSpan.FromMinutes(5)
- Filtros:
- Categoría: Tendencia
- Dirección: Ambos
- Indicadores: RSI
- Stops: Sí
- Complejidad: Básico
- Marco temporal: Intradía (5m)
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: Sí
- Nivel de riesgo: Medio
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Price and RSI divergence strategy.
/// A pivot is a candle whose low (high) is below (above) the lows (highs) of its neighbours on both sides.
/// A pivot low below the previous pivot low with a higher RSI is a bullish divergence and buys;
/// a pivot high above the previous pivot high with a lower RSI is a bearish divergence and sells.
/// TradeDirection ("Long", "Short" or "Both") limits the entries; an opposite divergence reverses or closes the position.
/// A percent stop loss and a take profit of RiskReward times the stop protect every position.
/// </summary>
public class DivergenceStrategy : Strategy
{
private readonly StrategyParam<string> _tradeDirection;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _riskReward;
private readonly StrategyParam<DataType> _candleType;
private readonly List<(decimal high, decimal low, decimal rsi)> _bars = [];
private (decimal price, decimal rsi)? _lastPivotLow;
private (decimal price, decimal rsi)? _lastPivotHigh;
/// <summary>
/// Allowed trade direction: Long, Short or Both.
/// </summary>
public string TradeDirection { get => _tradeDirection.Value; set => _tradeDirection.Value = value; }
/// <summary>
/// RSI period.
/// </summary>
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
/// <summary>
/// Take profit as a multiple of the stop loss.
/// </summary>
public decimal RiskReward { get => _riskReward.Value; set => _riskReward.Value = value; }
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Constructor.
/// </summary>
public DivergenceStrategy()
{
_tradeDirection = Param(nameof(TradeDirection), "Both")
.SetDisplay("Trade Direction", "Allowed trade direction: Long, Short or Both", "Trading");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_riskReward = Param(nameof(RiskReward), 2m)
.SetNotNegative()
.SetDisplay("Risk Reward", "Take profit as a multiple of the stop loss", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_bars.Clear();
_lastPivotLow = null;
_lastPivotHigh = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(rsi, ProcessCandle)
.Start();
var takePercent = StopLossPercent * RiskReward;
StartProtection(
takePercent > 0m ? new Unit(takePercent, UnitTypes.Percent) : new Unit(),
StopLossPercent > 0m ? new Unit(StopLossPercent, UnitTypes.Percent) : new Unit(),
useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, rsi);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!rsiValue.IsFormed)
return;
_bars.Add((candle.HighPrice, candle.LowPrice, rsiValue.GetValue<decimal>()));
if (_bars.Count > 3)
_bars.RemoveAt(0);
if (_bars.Count < 3)
return;
// The middle bar is confirmed as a pivot once the bar after it has closed.
var left = _bars[0];
var mid = _bars[1];
var right = _bars[2];
var bullish = false;
var bearish = false;
if (mid.low < left.low && mid.low < right.low)
{
if (_lastPivotLow is { } prevLow && mid.low < prevLow.price && mid.rsi > prevLow.rsi)
bullish = true;
_lastPivotLow = (mid.low, mid.rsi);
}
if (mid.high > left.high && mid.high > right.high)
{
if (_lastPivotHigh is { } prevHigh && mid.high > prevHigh.price && mid.rsi < prevHigh.rsi)
bearish = true;
_lastPivotHigh = (mid.high, mid.rsi);
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
var allowLong = !TradeDirection.EqualsIgnoreCase("Short");
var allowShort = !TradeDirection.EqualsIgnoreCase("Long");
if (bullish && !bearish)
{
if (allowLong && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (!allowLong && Position < 0)
BuyMarket(-Position);
}
else if (bearish && !bullish)
{
if (allowShort && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (!allowShort && Position > 0)
SellMarket(Position);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class divergence_strategy(Strategy):
"""
Price and RSI divergence strategy.
A pivot is a candle whose low (high) is below (above) the lows (highs) of its neighbours on both sides.
A pivot low below the previous pivot low with a higher RSI is a bullish divergence and buys;
a pivot high above the previous pivot high with a lower RSI is a bearish divergence and sells.
TradeDirection ("Long", "Short" or "Both") limits the entries; an opposite divergence reverses or closes the position.
A percent stop loss and a take profit of RiskReward times the stop protect every position.
"""
def __init__(self):
super(divergence_strategy, self).__init__()
self._trade_direction = self.Param("TradeDirection", "Both").SetDisplay("Trade Direction", "Allowed trade direction: Long, Short or Both", "Trading")
self._rsi_period = self.Param("RsiPeriod", 14).SetGreaterThanZero().SetDisplay("RSI Period", "RSI period", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._risk_reward = self.Param("RiskReward", 2.0).SetNotNegative().SetDisplay("Risk Reward", "Take profit as a multiple of the stop loss", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def _reset_state(self):
self._bars = []
self._last_pivot_low = None
self._last_pivot_high = None
def OnReseted(self):
super(divergence_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(divergence_strategy, self).OnStarted2(time)
self._reset_state()
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(rsi, self._process_candle).Start()
stop = float(self._stop_loss_percent.Value)
take = stop * float(self._risk_reward.Value)
self.StartProtection(
Unit(Decimal(take), UnitTypes.Percent) if take > 0 else Unit(),
Unit(Decimal(stop), UnitTypes.Percent) if stop > 0 else Unit(),
useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
if not rsi_value.IsFormed:
return
self._bars.append((float(candle.HighPrice), float(candle.LowPrice), float(rsi_value.GetValue[Decimal](None))))
if len(self._bars) > 3:
self._bars.pop(0)
if len(self._bars) < 3:
return
# The middle bar is confirmed as a pivot once the bar after it has closed.
left, mid, right = self._bars
bullish = False
bearish = False
if mid[1] < left[1] and mid[1] < right[1]:
prev_low = self._last_pivot_low
if prev_low is not None and mid[1] < prev_low[0] and mid[2] > prev_low[1]:
bullish = True
self._last_pivot_low = (mid[1], mid[2])
if mid[0] > left[0] and mid[0] > right[0]:
prev_high = self._last_pivot_high
if prev_high is not None and mid[0] > prev_high[0] and mid[2] < prev_high[1]:
bearish = True
self._last_pivot_high = (mid[0], mid[2])
if not self.IsFormedAndOnlineAndAllowTrading():
return
direction = str(self._trade_direction.Value).lower()
allow_long = direction != "short"
allow_short = direction != "long"
if bullish and not bearish:
if allow_long and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif not allow_long and self.Position < 0:
self.BuyMarket(-self.Position)
elif bearish and not bullish:
if allow_short and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif not allow_short and self.Position > 0:
self.SellMarket(self.Position)
def CreateClone(self):
return divergence_strategy()