Стратегия дивергенций
Стратегия ищет дивергенции цены и RSI на простых пивотах. Бычья дивергенция ведёт к покупке, медвежья — к продаже.
Подробности
- Критерии входа: дивергенции цены и RSI.
- Длинные/короткие: оба направления (настраивается).
- Критерии выхода: обратный сигнал RSI или защитные ордера.
- Стопы: да (стоп и тейк-профит).
- Параметры по умолчанию:
TradeDirection= BothRsiPeriod= 14StopLossPercent= 2мRiskReward= 2мCandleType= TimeSpan.FromMinutes(5)
- Фильтры:
- Категория: Тренд
- Направление: Оба
- Индикаторы: RSI
- Стопы: Да
- Сложность: Базовая
- Таймфрейм: Внутридневной (5м)
- Сезонность: Нет
- Нейронные сети: Нет
- Дивергенция: Да
- Уровень риска: Средний
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()