Estrategia de Todas las Divergencias
La Estrategia de Todas las Divergencias busca divergencias alcistas y bajistas del RSI filtradas por la tendencia de una media móvil. Se abre una posición larga cuando el precio marca un mínimo más bajo mientras el RSI forma un mínimo más alto por encima de la media móvil. Se abre una posición corta cuando el precio marca un máximo más alto mientras el RSI forma un máximo más bajo por debajo de la media móvil. Una protección opcional de stop-loss y take-profit puede cerrar posiciones automáticamente, y un control de riesgo por media móvil sale tras varios cierres contra la tendencia.
Detalles
- Criterios de entrada:
- La posición del precio respecto a la media móvil define la tendencia.
- Largo: el precio hace un mínimo más bajo, el RSI un mínimo más alto, el precio por encima de la MA.
- Corto: el precio hace un máximo más alto, el RSI un máximo más bajo, el precio por debajo de la MA.
- Largo/Corto: Ambos lados.
- Criterios de salida:
- Señal opuesta o salida por riesgo de MA.
- Stops: Stop-loss y take-profit opcionales.
- Valores predeterminados:
MaLength= 50RsiLength= 14MaRiskCandles= 3UseProtection= False
- Filtros:
- Categoría: Divergencia
- Dirección: Ambos
- Indicadores: RSI, Moving Average
- Stops: Opcional
- Complejidad: Moderado
- Marco temporal: Cualquiera
- 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>
/// All Divergences strategy.
/// Confirms price swing lows and highs as pivots with PivotSide bars on each side and compares each new pivot with the previous
/// one. A lower price low with a higher RSI low while the close is above the moving average goes long; a higher price high
/// with a lower RSI high while the close is below the moving average goes short, reversing an opposite position. A position
/// is also closed after MaRiskCandles consecutive closes on the wrong side of the moving average, and optional percent
/// stop-loss and take-profit protect it.
/// </summary>
public class AllDivergencesStrategy : Strategy
{
private const int _pivotSide = 5;
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _maRiskCandles;
private readonly StrategyParam<bool> _useProtection;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly List<(decimal high, decimal low, decimal rsi)> _bars = [];
private decimal? _lastPivotLow;
private decimal _lastPivotLowRsi;
private decimal? _lastPivotHigh;
private decimal _lastPivotHighRsi;
private int _closesBelowMa;
private int _closesAboveMa;
/// <summary>
/// Moving average period.
/// </summary>
public int MaLength
{
get => _maLength.Value;
set => _maLength.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// Consecutive closes against the moving average that close a position.
/// </summary>
public int MaRiskCandles
{
get => _maRiskCandles.Value;
set => _maRiskCandles.Value = value;
}
/// <summary>
/// Enable percent stop-loss and take-profit.
/// </summary>
public bool UseProtection
{
get => _useProtection.Value;
set => _useProtection.Value = value;
}
/// <summary>
/// Stop-loss percentage used when protection is enabled.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Take-profit percentage used when protection is enabled.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public AllDivergencesStrategy()
{
_maLength = Param(nameof(MaLength), 50)
.SetGreaterThanZero()
.SetDisplay("MA Length", "Moving average period", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Indicators");
_maRiskCandles = Param(nameof(MaRiskCandles), 3)
.SetGreaterThanZero()
.SetDisplay("MA Risk Candles", "Consecutive closes against the MA that close a position", "Risk");
_useProtection = Param(nameof(UseProtection), false)
.SetDisplay("Use Protection", "Enable percent stop-loss and take-profit", "Risk");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop-loss percentage used when protection is enabled", "Risk");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 4m)
.SetNotNegative()
.SetDisplay("Take Profit %", "Take-profit percentage used when protection is enabled", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).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;
_lastPivotLowRsi = 0m;
_lastPivotHigh = null;
_lastPivotHighRsi = 0m;
_closesBelowMa = 0;
_closesAboveMa = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var ma = new SimpleMovingAverage { Length = MaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, ma, ProcessCandle)
.Start();
if (UseProtection)
StartProtection(new Unit(TakeProfitPercent, UnitTypes.Percent), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ma);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, rsi);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsi, decimal ma)
{
if (candle.State != CandleStates.Finished)
return;
_bars.Add((candle.HighPrice, candle.LowPrice, rsi));
var window = 2 * _pivotSide + 1;
if (_bars.Count > window)
_bars.RemoveAt(0);
var close = candle.ClosePrice;
_closesBelowMa = close < ma ? _closesBelowMa + 1 : 0;
_closesAboveMa = close > ma ? _closesAboveMa + 1 : 0;
if (_bars.Count < window)
return;
var bullishDivergence = false;
var bearishDivergence = false;
// The middle bar of the window is a pivot once PivotSide bars on each side have finished.
var (pivotHigh, pivotLow, pivotRsi) = _bars[_pivotSide];
var isPivotLow = true;
var isPivotHigh = true;
for (var i = 0; i < window; i++)
{
if (i == _pivotSide)
continue;
if (_bars[i].low <= pivotLow)
isPivotLow = false;
if (_bars[i].high >= pivotHigh)
isPivotHigh = false;
}
if (isPivotLow)
{
if (_lastPivotLow is decimal prevLow)
bullishDivergence = pivotLow < prevLow && pivotRsi > _lastPivotLowRsi;
_lastPivotLow = pivotLow;
_lastPivotLowRsi = pivotRsi;
}
if (isPivotHigh)
{
if (_lastPivotHigh is decimal prevHigh)
bearishDivergence = pivotHigh > prevHigh && pivotRsi < _lastPivotHighRsi;
_lastPivotHigh = pivotHigh;
_lastPivotHighRsi = pivotRsi;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (bullishDivergence && close > ma && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (bearishDivergence && close < ma && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && _closesBelowMa >= MaRiskCandles)
SellMarket(Position);
else if (Position < 0 && _closesAboveMa >= MaRiskCandles)
BuyMarket(-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, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
PIVOT_SIDE = 5
class all_divergences_strategy(Strategy):
"""
All Divergences strategy.
Confirms price swing lows and highs as pivots with PivotSide bars on each side and compares each new pivot with the previous
one. A lower price low with a higher RSI low while the close is above the moving average goes long; a higher price high
with a lower RSI high while the close is below the moving average goes short, reversing an opposite position. A position
is also closed after MaRiskCandles consecutive closes on the wrong side of the moving average, and optional percent
stop-loss and take-profit protect it.
"""
def __init__(self):
super(all_divergences_strategy, self).__init__()
self._ma_length = self.Param("MaLength", 50) \
.SetGreaterThanZero() \
.SetDisplay("MA Length", "Moving average period", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetGreaterThanZero() \
.SetDisplay("RSI Length", "RSI period", "Indicators")
self._ma_risk_candles = self.Param("MaRiskCandles", 3) \
.SetGreaterThanZero() \
.SetDisplay("MA Risk Candles", "Consecutive closes against the MA that close a position", "Risk")
self._use_protection = self.Param("UseProtection", False) \
.SetDisplay("Use Protection", "Enable percent stop-loss and take-profit", "Risk")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetNotNegative() \
.SetDisplay("Stop Loss %", "Stop-loss percentage used when protection is enabled", "Risk")
self._take_profit_percent = self.Param("TakeProfitPercent", 4.0) \
.SetNotNegative() \
.SetDisplay("Take Profit %", "Take-profit percentage used when protection is enabled", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def _reset_state(self):
self._bars = []
self._last_pivot_low = None
self._last_pivot_low_rsi = 0.0
self._last_pivot_high = None
self._last_pivot_high_rsi = 0.0
self._closes_below_ma = 0
self._closes_above_ma = 0
def OnReseted(self):
super(all_divergences_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(all_divergences_strategy, self).OnStarted2(time)
self._reset_state()
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
ma = SimpleMovingAverage()
ma.Length = self._ma_length.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, ma, self._process_candle).Start()
if self._use_protection.Value:
self.StartProtection(
Unit(Decimal(self._take_profit_percent.Value), UnitTypes.Percent),
Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent),
useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
def _process_candle(self, candle, rsi_value, ma_value):
if candle.State != CandleStates.Finished:
return
rsi = float(rsi_value)
ma = float(ma_value)
self._bars.append((float(candle.HighPrice), float(candle.LowPrice), rsi))
window = 2 * PIVOT_SIDE + 1
if len(self._bars) > window:
self._bars.pop(0)
close = float(candle.ClosePrice)
self._closes_below_ma = self._closes_below_ma + 1 if close < ma else 0
self._closes_above_ma = self._closes_above_ma + 1 if close > ma else 0
if len(self._bars) < window:
return
bullish_divergence = False
bearish_divergence = False
# The middle bar of the window is a pivot once PivotSide bars on each side have finished.
pivot_high, pivot_low, pivot_rsi = self._bars[PIVOT_SIDE]
is_pivot_low = True
is_pivot_high = True
for i in range(window):
if i == PIVOT_SIDE:
continue
if self._bars[i][1] <= pivot_low:
is_pivot_low = False
if self._bars[i][0] >= pivot_high:
is_pivot_high = False
if is_pivot_low:
if self._last_pivot_low is not None:
bullish_divergence = pivot_low < self._last_pivot_low and pivot_rsi > self._last_pivot_low_rsi
self._last_pivot_low = pivot_low
self._last_pivot_low_rsi = pivot_rsi
if is_pivot_high:
if self._last_pivot_high is not None:
bearish_divergence = pivot_high > self._last_pivot_high and pivot_rsi < self._last_pivot_high_rsi
self._last_pivot_high = pivot_high
self._last_pivot_high_rsi = pivot_rsi
if not self.IsFormedAndOnlineAndAllowTrading():
return
risk_candles = self._ma_risk_candles.Value
if bullish_divergence and close > ma and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif bearish_divergence and close < ma and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and self._closes_below_ma >= risk_candles:
self.SellMarket(self.Position)
elif self.Position < 0 and self._closes_above_ma >= risk_candles:
self.BuyMarket(-self.Position)
def CreateClone(self):
return all_divergences_strategy()