Стратегия Genie RSI
Эта стратегия торгует развороты по индикатору RSI. При значении RSI выше 80 открывается короткая позиция, при значении ниже 20 открывается длинная позиция. После входа можно использовать параметры тейк-профита и трейлинг-стопа.
Стратегия подходит для рынков с ярко выраженными колебаниями. Таймфрейм задаётся параметром CandleType.
Детали
- Условия входа
- Лонг: RSI опускается ниже 20 на закрытой свече при отсутствии позиции.
- Шорт: RSI поднимается выше 80 на закрытой свече при отсутствии позиции.
- Условия выхода
- Лонг: RSI поднимается выше 80, цена достигает тейк-профита или касается уровня трейлинг-стопа.
- Шорт: RSI опускается ниже 20, цена достигает тейк-профита или касается уровня трейлинг-стопа.
- Индикаторы: RSI.
- Параметры:
RSI Period– период расчёта RSI.Take Profit– расстояние до цели в ценовых единицах.Trailing Stop– расстояние трейлинг-стопа в ценовых единицах.Candle Type– тип обрабатываемых свечей.
- Управление позицией: Входы и выходы выполняются рыночными ордерами. Трейлинг-стоп пересчитывается на каждой завершённой свече.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// RSI-based reversal strategy using fixed thresholds.
/// Sells when RSI rises above overbought level and buys when RSI falls below oversold level.
/// Includes optional take profit and trailing stop management.
/// </summary>
public class GenieRsiStrategy : Strategy
{
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _trailingStop;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private decimal _entryPrice;
private decimal _trailingLevel;
private bool _isLong;
private decimal? _prevRsi;
private int _cooldownRemaining;
/// <summary>
/// Take profit distance in price units.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Trailing stop distance in price units.
/// </summary>
public decimal TrailingStop
{
get => _trailingStop.Value;
set => _trailingStop.Value = value;
}
/// <summary>
/// RSI calculation period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Number of completed candles to wait after a position change.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public GenieRsiStrategy()
{
_takeProfit = Param(nameof(TakeProfit), 500m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Take profit distance in price units", "Risk Management")
;
_trailingStop = Param(nameof(TrailingStop), 200m)
.SetNotNegative()
.SetDisplay("Trailing Stop", "Trailing stop distance in price units", "Risk Management");
_rsiPeriod = Param(nameof(RsiPeriod), 15)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Period for RSI indicator", "Indicators")
.SetOptimize(5, 30, 5);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_cooldownBars = Param(nameof(CooldownBars), 4)
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Risk Management");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_trailingLevel = 0m;
_isLong = false;
_prevRsi = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, rsi);
DrawOwnTrades(area);
}
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle, decimal rsi)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var price = candle.ClosePrice;
var crossedDown = _prevRsi is decimal prevRsi1 && prevRsi1 >= 20m && rsi < 20m;
var crossedUp = _prevRsi is decimal prevRsi2 && prevRsi2 <= 80m && rsi > 80m;
_prevRsi = rsi;
// Entry logic when flat
if (Position == 0 && _cooldownRemaining == 0)
{
if (crossedUp)
{
SellMarket();
_entryPrice = price;
_trailingLevel = price + TrailingStop;
_isLong = false;
_cooldownRemaining = CooldownBars;
}
else if (crossedDown)
{
BuyMarket();
_entryPrice = price;
_trailingLevel = price - TrailingStop;
_isLong = true;
_cooldownRemaining = CooldownBars;
}
return;
}
// Manage open position
if (_isLong)
{
// Update trailing stop for long position
if (TrailingStop > 0)
{
var newLevel = price - TrailingStop;
if (newLevel > _trailingLevel)
_trailingLevel = newLevel;
if (price <= _trailingLevel)
{
SellMarket();
_entryPrice = 0m;
_cooldownRemaining = CooldownBars;
return;
}
}
// Take profit for long position
if (TakeProfit > 0 && price - _entryPrice >= TakeProfit)
{
SellMarket();
_entryPrice = 0m;
_cooldownRemaining = CooldownBars;
return;
}
// Exit if RSI indicates overbought
if (crossedUp)
{
SellMarket();
_entryPrice = 0m;
_cooldownRemaining = CooldownBars;
}
}
else
{
// Update trailing stop for short position
if (TrailingStop > 0)
{
var newLevel = price + TrailingStop;
if (_trailingLevel == 0m || newLevel < _trailingLevel)
_trailingLevel = newLevel;
if (price >= _trailingLevel)
{
BuyMarket();
_entryPrice = 0m;
_cooldownRemaining = CooldownBars;
return;
}
}
// Take profit for short position
if (TakeProfit > 0 && _entryPrice - price >= TakeProfit)
{
BuyMarket();
_entryPrice = 0m;
_cooldownRemaining = CooldownBars;
return;
}
// Exit if RSI indicates oversold
if (crossedDown)
{
BuyMarket();
_entryPrice = 0m;
_cooldownRemaining = CooldownBars;
}
}
}
}
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 RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class genie_rsi_strategy(Strategy):
"""
RSI-based reversal strategy.
Sells when RSI crosses above 80 (overbought), buys when RSI crosses below 20 (oversold).
Includes trailing stop, take profit, and RSI exit signals.
"""
def __init__(self):
super(genie_rsi_strategy, self).__init__()
self._take_profit = self.Param("TakeProfit", 500.0) \
.SetDisplay("Take Profit", "Take profit distance in price units", "Risk Management")
self._trailing_stop = self.Param("TrailingStop", 200.0) \
.SetDisplay("Trailing Stop", "Trailing stop distance in price units", "Risk Management")
self._rsi_period = self.Param("RsiPeriod", 15) \
.SetDisplay("RSI Period", "Period for RSI indicator", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 4) \
.SetDisplay("Cooldown Bars", "Completed candles to wait after position change", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._entry_price = 0.0
self._trailing_level = 0.0
self._is_long = False
self._prev_rsi = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(genie_rsi_strategy, self).OnReseted()
self._entry_price = 0.0
self._trailing_level = 0.0
self._is_long = False
self._prev_rsi = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(genie_rsi_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, rsi)
self.DrawOwnTrades(area)
def _process_candle(self, candle, rsi_val):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
price = float(candle.ClosePrice)
rsi = float(rsi_val)
crossed_down = self._prev_rsi is not None and self._prev_rsi >= 20.0 and rsi < 20.0
crossed_up = self._prev_rsi is not None and self._prev_rsi <= 80.0 and rsi > 80.0
self._prev_rsi = rsi
if self.Position == 0 and self._cooldown_remaining == 0:
if crossed_up:
self.SellMarket()
self._entry_price = price
self._trailing_level = price + self._trailing_stop.Value
self._is_long = False
self._cooldown_remaining = self._cooldown_bars.Value
elif crossed_down:
self.BuyMarket()
self._entry_price = price
self._trailing_level = price - self._trailing_stop.Value
self._is_long = True
self._cooldown_remaining = self._cooldown_bars.Value
return
if self._is_long:
trailing = self._trailing_stop.Value
if trailing > 0:
new_level = price - trailing
if new_level > self._trailing_level:
self._trailing_level = new_level
if price <= self._trailing_level:
self.SellMarket()
self._entry_price = 0.0
self._cooldown_remaining = self._cooldown_bars.Value
return
tp = self._take_profit.Value
if tp > 0 and price - self._entry_price >= tp:
self.SellMarket()
self._entry_price = 0.0
self._cooldown_remaining = self._cooldown_bars.Value
return
if crossed_up:
self.SellMarket()
self._entry_price = 0.0
self._cooldown_remaining = self._cooldown_bars.Value
else:
trailing = self._trailing_stop.Value
if trailing > 0:
new_level = price + trailing
if self._trailing_level == 0.0 or new_level < self._trailing_level:
self._trailing_level = new_level
if price >= self._trailing_level:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown_remaining = self._cooldown_bars.Value
return
tp = self._take_profit.Value
if tp > 0 and self._entry_price - price >= tp:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown_remaining = self._cooldown_bars.Value
return
if crossed_down:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown_remaining = self._cooldown_bars.Value
def CreateClone(self):
return genie_rsi_strategy()