Стратегия представляет собой порт MetaTrader-советника «Tops bottoms trend and rsi ea» на платформу StockSharp. Алгоритм анализирует закрытые свечи выбранного таймфрейма, ищет формирование локальных максимумов и минимумов в заданном окне истории и подтверждает сигналы фильтром Relative Strength Index (RSI). При выполнении условий стратегия открывает единственную рыночную позицию и сразу рассчитывает защитные стоп-лосс и тейк-профит на основе пипсовых расстояний.
Логика торговли
Источник данных. Подписка оформляется на указанный тип свечей, в расчёт берутся только завершённые бары, чтобы исключить неполные данные.
Поиск дна (лонг). Закрытие текущей свечи должно находиться минимум на BuyTrendPips пипсов ниже максимума свечи сдвига BuyTrendCandles. Все промежуточные минимумы обязаны оставаться выше текущего закрытия, а фильтр качества (BuyTrendQuality) не допускает сильного расхождения недавних максимумов от опорного уровня. Дополнительно требуется, чтобы значение RSI на предыдущей свече было ниже BuyRsiThreshold. При выполнении условий открывается длинная позиция объёмом BuyVolume.
Поиск вершины (шорт). Закрытие текущей свечи должно быть минимум на SellTrendPips пипсов выше минимума свечи сдвига SellTrendCandles. Промежуточные максимумы не могут превышать текущее закрытие, а фильтр качества (SellTrendQuality) контролирует близость недавних минимумов к опорному уровню. Если при этом значение RSI на предыдущей свече превышает SellRsiThreshold, открывается короткая позиция объёмом SellVolume.
Управление рисками. После входа стратегия запоминает цену исполнения и вычисляет защитные уровни в пипсах. Стоп-лосс задаётся параметрами BuyStopLossPips или SellStopLossPips. Тейк-профит рассчитывается как процент от стопа (BuyTakeProfitPercentOfStop, SellTakeProfitPercentOfStop). Для длинных позиций при нулевом проценте используется фиксированное значение BuyTakeProfitPips. При достижении ценой соответствующих уровней позиция закрывается рыночным ордером.
Контроль позиции. В работе допускается только одна открытая позиция, новые сигналы игнорируются до её закрытия. RSI всегда оценивается по предыдущей свече (сдвиг 1), что повторяет логику оригинального эксперта.
Параметры
Параметр
Описание
Значение по умолчанию
BuyVolume
Объём заявки при открытии длинных позиций.
0.01
BuyStopLossPips
Размер стоп-лосса для лонгов в пипсах.
20
BuyTakeProfitPips
Фиксированный тейк-профит в пипсах для лонгов (используется при отключённом проценте).
5
BuyTakeProfitPercentOfStop
Тейк-профит как процент от расстояния стоп-лосса для лонгов.
100
SellVolume
Объём заявки при открытии коротких позиций.
0.01
SellStopLossPips
Размер стоп-лосса для шортов в пипсах.
20
SellTakeProfitPercentOfStop
Тейк-профит как процент от расстояния стоп-лосса для шортов.
100
SellTrendCandles
Количество свечей в окне поиска вершины.
10
SellTrendPips
Минимальный подъём над опорным минимумом для открытия шорта (в пипсах).
20
SellTrendQuality
Фильтр качества тренда для шортов (ограничение 1–9).
5
BuyTrendCandles
Количество свечей в окне поиска дна.
10
BuyTrendPips
Минимальное падение ниже опорного максимума для открытия лонга (в пипсах).
20
BuyTrendQuality
Фильтр качества тренда для лонгов (ограничение 1–9).
5
BuyRsiPeriod
Период RSI для подтверждения длинных сделок.
14
BuyRsiThreshold
Порог RSI, определяющий перепроданность для лонгов.
40
SellRsiPeriod
Период RSI для подтверждения коротких сделок.
14
SellRsiThreshold
Порог RSI, определяющий перекупленность для шортов.
60
CandleType
Таймфрейм анализируемых свечей.
30 минут
Примечания
Перевод пипсов в цену выполняется через PriceStep инструмента. Для пятизначных котировок и дробных пипсов применяется нормализация к классическому размеру пипса, как в оригинальном советнике.
Из-за использования значения RSI предыдущей свечи стратегия пропускает первые несколько баров до формирования индикатора и затем поддерживает сдвиг в одну свечу.
При полном закрытии позиции защитные уровни сбрасываются, чтобы следующий вход стартовал с чистыми настройками риска.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Tops Bottoms Trend RSI strategy: RSI trend direction with EMA filter.
/// Buys when RSI above 50 and close above EMA, sells when RSI below 50 and close below EMA.
/// </summary>
public class TopsBottomsTrendRsiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _emaPeriod;
private decimal _prevRsi;
private bool _hasPrev;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public TopsBottomsTrendRsiStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period", "Indicators");
_emaPeriod = Param(nameof(EmaPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA trend filter period", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRsi = 0;
_hasPrev = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevRsi = 0;
_hasPrev = false;
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(rsi, ema, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished) return;
if (_hasPrev)
{
if (_prevRsi <= 45 && rsiValue > 45 && candle.ClosePrice > emaValue && Position <= 0)
BuyMarket();
else if (_prevRsi >= 55 && rsiValue < 55 && candle.ClosePrice < emaValue && Position >= 0)
SellMarket();
}
_prevRsi = rsiValue;
_hasPrev = true;
}
}
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, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class tops_bottoms_trend_rsi_strategy(Strategy):
def __init__(self):
super(tops_bottoms_trend_rsi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._rsi_period = self.Param("RsiPeriod", 21)
self._ema_period = self.Param("EmaPeriod", 50)
self._prev_rsi = 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 RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
def OnReseted(self):
super(tops_bottoms_trend_rsi_strategy, self).OnReseted()
self._prev_rsi = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(tops_bottoms_trend_rsi_strategy, self).OnStarted2(time)
self._prev_rsi = 0.0
self._has_prev = False
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
ema = ExponentialMovingAverage()
ema.Length = self.EmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, ema, self._process_candle).Start()
def _process_candle(self, candle, rsi_value, ema_value):
if candle.State != CandleStates.Finished:
return
rsi_val = float(rsi_value)
ema_val = float(ema_value)
close = float(candle.ClosePrice)
if self._has_prev:
if self._prev_rsi <= 45 and rsi_val > 45 and close > ema_val and self.Position <= 0:
self.BuyMarket()
elif self._prev_rsi >= 55 and rsi_val < 55 and close < ema_val and self.Position >= 0:
self.SellMarket()
self._prev_rsi = rsi_val
self._has_prev = True
def CreateClone(self):
return tops_bottoms_trend_rsi_strategy()