Reverse Strategy — это стратегия возврата к среднему, которая использует индикаторы Bollinger Bands и Relative Strength Index (RSI) для поиска истощённых движений. Когда цена уходит за границы полос Боллинджера и затем возвращается внутрь вместе с разворотом RSI из зоны перепроданности или перекупленности, стратегия предполагает разворот и открывает позицию против предыдущего движения. Управление риском реализовано через фиксированные стоп-лосс и тейк-профит, основанные на ширине полос.
Логика торговли
Подписка на заданный тип свечей (по умолчанию 5-минутные).
Расчёт полос Боллинджера на основе простого скользящего среднего с указанным периодом и множителем стандартного отклонения.
Расчёт RSI с указанным периодом.
Отслеживание предыдущей завершённой свечи для определения пробоев:
Сигнал на покупку: предыдущий закрытие ниже предыдущей нижней полосы и RSI ниже уровня перепроданности. Текущее закрытие должно подняться выше нижней полосы, а RSI — вернуться выше порога перепроданности.
Сигнал на продажу: предыдущий закрытие выше предыдущей верхней полосы и RSI выше уровня перекупленности. Текущее закрытие должно опуститься ниже верхней полосы, а RSI — упасть ниже порога перекупленности.
При покупке стратегия отправляет рыночную заявку, ставит защитный стоп на одну сигму ниже цены входа и цель на две сигмы выше.
При продаже стратегия отправляет рыночную заявку, ставит защитный стоп на одну сигму выше цены входа и цель на две сигмы ниже.
Сопровождение позиции:
Длинные позиции закрываются при касании верхней полосы, достижении стопа или цели.
Короткие позиции закрываются при касании нижней полосы, достижении стопа или цели.
Параметры
Имя
Описание
Значение по умолчанию
CandleType
Таймфрейм свечей для подписки.
5 минут
BollingerPeriod
Период расчёта среднего и стандартного отклонения полос Боллинджера.
20
BollingerWidth
Множитель стандартного отклонения для полос.
2.0
RsiPeriod
Период расчёта RSI.
14
RsiOverbought
Уровень RSI для определения перекупленности и сигналов на продажу.
70
RsiOversold
Уровень RSI для определения перепроданности и сигналов на покупку.
30
Все параметры доступны для оптимизации в StockSharp Designer или Runner. Изменяя уровни RSI, можно регулировать частоту сигналов, а изменение ширины полос определяет степень отклонения цены перед входом.
Практические рекомендации
Стратегия использует высокоуровневый API StockSharp с автоматической подпиской на свечи и привязкой индикаторов.
Сделки совершаются рыночными ордерами (BuyMarket/SellMarket), а контроль стопов и целей выполняется в коде без установки заявок в стакане.
Базовые настройки ориентированы на внутридневную торговлю; для других таймфреймов измените параметр CandleType.
При работе на реальном счёте рекомендуется дополнить стратегию фильтрами тренда, волатильности или торговых сессий.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simplified from "Reverse" MetaTrader expert.
/// Uses Bollinger Band touches with RSI confirmation for mean-reversion entries.
/// Enters long when price crosses above lower band with RSI oversold,
/// enters short when price crosses below upper band with RSI overbought.
/// </summary>
public class ReverseStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerWidth;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiOverbought;
private readonly StrategyParam<decimal> _rsiOversold;
private ExponentialMovingAverage _ema;
private RelativeStrengthIndex _rsi;
private decimal _prevClose;
private decimal _prevRsi;
private decimal _prevLower;
private decimal _prevUpper;
private bool _hasPrev;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
public decimal BollingerWidth
{
get => _bollingerWidth.Value;
set => _bollingerWidth.Value = value;
}
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
public decimal RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
public decimal RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
public ReverseStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for signals", "General");
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Period", "MA length for Bollinger Bands", "Indicators");
_bollingerWidth = Param(nameof(BollingerWidth), 1m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Width", "Standard deviation multiplier", "Indicators");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period", "Indicators");
_rsiOverbought = Param(nameof(RsiOverbought), 70m)
.SetDisplay("RSI Overbought", "Upper threshold for short signals", "Signals");
_rsiOversold = Param(nameof(RsiOversold), 30m)
.SetDisplay("RSI Oversold", "Lower threshold for long signals", "Signals");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ema = new ExponentialMovingAverage { Length = BollingerPeriod };
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_hasPrev = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ema, _rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawIndicator(area, _rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_ema.IsFormed || !_rsi.IsFormed)
return;
var close = candle.ClosePrice;
var bandOffset = emaValue * (BollingerWidth / 100m);
var upperBand = emaValue + bandOffset;
var lowerBand = emaValue - bandOffset;
if (!_hasPrev)
{
_prevClose = close;
_prevRsi = rsiValue;
_prevLower = lowerBand;
_prevUpper = upperBand;
_hasPrev = true;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
// Long: price crosses up from below lower band + RSI was oversold
var longSignal = _prevClose < _prevLower && close >= lowerBand && _prevRsi < RsiOversold;
// Short: price crosses down from above upper band + RSI was overbought
var shortSignal = _prevClose > _prevUpper && close <= upperBand && _prevRsi > RsiOverbought;
if (longSignal)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
else if (shortSignal)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
// Exit long at upper band
if (Position > 0 && close >= upperBand)
SellMarket(Position);
// Exit short at lower band
if (Position < 0 && close <= lowerBand)
BuyMarket(Math.Abs(Position));
_prevClose = close;
_prevRsi = rsiValue;
_prevLower = lowerBand;
_prevUpper = upperBand;
}
/// <inheritdoc />
protected override void OnReseted()
{
_ema = null;
_rsi = null;
_prevClose = 0;
_prevRsi = 0;
_prevLower = 0;
_prevUpper = 0;
_hasPrev = false;
base.OnReseted();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class reverse_strategy(Strategy):
def __init__(self):
super(reverse_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._bollinger_period = self.Param("BollingerPeriod", 20)
self._bollinger_width = self.Param("BollingerWidth", 1.0)
self._rsi_period = self.Param("RsiPeriod", 14)
self._rsi_overbought = self.Param("RsiOverbought", 70.0)
self._rsi_oversold = self.Param("RsiOversold", 30.0)
self._prev_close = 0.0
self._prev_rsi = 0.0
self._prev_lower = 0.0
self._prev_upper = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def BollingerPeriod(self):
return self._bollinger_period.Value
@BollingerPeriod.setter
def BollingerPeriod(self, value):
self._bollinger_period.Value = value
@property
def BollingerWidth(self):
return self._bollinger_width.Value
@BollingerWidth.setter
def BollingerWidth(self, value):
self._bollinger_width.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def RsiOverbought(self):
return self._rsi_overbought.Value
@RsiOverbought.setter
def RsiOverbought(self, value):
self._rsi_overbought.Value = value
@property
def RsiOversold(self):
return self._rsi_oversold.Value
@RsiOversold.setter
def RsiOversold(self, value):
self._rsi_oversold.Value = value
def OnReseted(self):
super(reverse_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_rsi = 0.0
self._prev_lower = 0.0
self._prev_upper = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(reverse_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._prev_rsi = 0.0
self._prev_lower = 0.0
self._prev_upper = 0.0
self._has_prev = False
ema = ExponentialMovingAverage()
ema.Length = self.BollingerPeriod
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, rsi, self._process_candle).Start()
def _process_candle(self, candle, ema_value, rsi_value):
if candle.State != CandleStates.Finished:
return
ema_val = float(ema_value)
rsi_val = float(rsi_value)
close = float(candle.ClosePrice)
band_offset = ema_val * (float(self.BollingerWidth) / 100.0)
upper_band = ema_val + band_offset
lower_band = ema_val - band_offset
if not self._has_prev:
self._prev_close = close
self._prev_rsi = rsi_val
self._prev_lower = lower_band
self._prev_upper = upper_band
self._has_prev = True
return
# Long: price crosses up from below lower band + RSI was oversold
long_signal = (self._prev_close < self._prev_lower and close >= lower_band and
self._prev_rsi < float(self.RsiOversold))
# Short: price crosses down from above upper band + RSI was overbought
short_signal = (self._prev_close > self._prev_upper and close <= upper_band and
self._prev_rsi > float(self.RsiOverbought))
if long_signal:
if self.Position <= 0:
self.BuyMarket()
elif short_signal:
if self.Position >= 0:
self.SellMarket()
# Exit long at upper band
if self.Position > 0 and close >= upper_band:
self.SellMarket()
# Exit short at lower band
if self.Position < 0 and close <= lower_band:
self.BuyMarket()
self._prev_close = close
self._prev_rsi = rsi_val
self._prev_lower = lower_band
self._prev_upper = upper_band
def CreateClone(self):
return reverse_strategy()