Стратегия Hpcs Inter6 RSI
Обзор
Hpcs Inter6 RSI переносит эксперта MetaTrader _HPCS_Inter6_MT4_EA_V01_WE на высокоуровневый API StockSharp. Алгоритм анализирует индекс относительной силы (RSI) на настраиваемых свечах и реагирует на резкие развороты около классических уровней 70/30. Пересечение RSI выше 70 инициирует короткую позицию, пересечение ниже 30 — длинную. Каждая сделка сразу получает симметричные уровни тейк-профита и стоп-лосса, заданные в пунктах.
Данные и индикаторы
- Источники данных — свечи выбранного таймфрейма (по умолчанию 1 час).
- Индикатор — RSI с настраиваемым периодом (по умолчанию 14), который пересчитывается через механизм
Bind.
Логика входа
- Стратегия ожидает закрытия свечи, чтобы работать только с завершёнными данными.
- После каждой закрытой свечи сравниваются текущие и предыдущие значения RSI.
- Продажа: если RSI пересекает уровень
UpperLevel (70) снизу вверх, отправляется рыночная заявка на продажу. Наличие длинной позиции приводит к её закрытию перед открытием шорта, поэтому итоговый объём шорта равен параметру TradeVolume.
- Покупка: если RSI пересекает уровень
LowerLevel (30) сверху вниз, отправляется рыночная заявка на покупку. Активные короткие позиции закрываются перед открытием лонга, итоговый объём равен TradeVolume.
- В течение одной свечи допускается только один сигнал — дополнительная защита от повторных входов на той же свече полностью повторяет логику MQL.
Логика выхода
- После входа вычисляются уровни тейк-профита и стоп-лосса на одинаковом расстоянии от цены входа.
- Для длинной позиции выход выполняется, если максимум свечи достигает тейк-профита или минимум опускается до стоп-лосса.
- Для короткой позиции выход выполняется, если минимум свечи достигает тейк-профита или максимум поднимается к стоп-лоссу.
- При отсутствии позиции защитные уровни сбрасываются.
Расстояние в пунктах переводится в цену через минимальный шаг котировки инструмента. Для инструментов с тремя или пятью знаками после запятой расстояние дополнительно умножается на десять, что соответствует определению пункта в MetaTrader.
Параметры
| Параметр |
Значение по умолчанию |
Описание |
CandleType |
Таймфрейм 1 час |
Источник свечей для расчёта RSI. |
RsiLength |
14 |
Период RSI. |
UpperLevel |
70 |
Уровень RSI для открытия продаж при пересечении снизу вверх. |
LowerLevel |
30 |
Уровень RSI для открытия покупок при пересечении сверху вниз. |
TradeVolume |
1 |
Объём рыночных заявок; перед разворотом противоположная позиция закрывается. |
OffsetInPips |
10 |
Расстояние до тейк-профита и стоп-лосса в пунктах. |
Все параметры оформлены через StrategyParam, поэтому их можно оптимизировать в StockSharp.
Дополнительно
- Выход по тейк-профиту и стоп-лоссу моделируется по экстремумам свечей, что соответствует фиксированным ценовым уровням в оригинальном советнике.
- Стратегия не выставляет отложенные ордера — все операции выполняются рыночными заявками.
- При наличии области на графике автоматически добавляются свечи и кривая RSI для визуального контроля.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Port of the MetaTrader strategy _HPCS_Inter6_MT4_EA_V01_WE.
/// Trades RSI reversals at the 70/30 levels with symmetric fixed targets and stops.
/// </summary>
public class HpcsInter6RsiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<decimal> _upperLevel;
private readonly StrategyParam<decimal> _lowerLevel;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<decimal> _offsetInPips;
private readonly StrategyParam<int> _signalCooldownCandles;
private RelativeStrengthIndex _rsi;
private decimal? _previousRsi;
private DateTimeOffset? _lastSignalTime;
private decimal? _targetPrice;
private decimal? _stopPrice;
private bool _isLongPosition;
private int _candlesSinceTrade;
/// <summary>
/// Candle type used for the RSI evaluation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// RSI lookback length.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// Upper RSI level that triggers short entries when crossed from below.
/// </summary>
public decimal UpperLevel
{
get => _upperLevel.Value;
set => _upperLevel.Value = value;
}
/// <summary>
/// Lower RSI level that triggers long entries when crossed from above.
/// </summary>
public decimal LowerLevel
{
get => _lowerLevel.Value;
set => _lowerLevel.Value = value;
}
/// <summary>
/// Market order volume used for entries.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Target and stop distance expressed in pips.
/// </summary>
public decimal OffsetInPips
{
get => _offsetInPips.Value;
set => _offsetInPips.Value = value;
}
public int SignalCooldownCandles
{
get => _signalCooldownCandles.Value;
set => _signalCooldownCandles.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="HpcsInter6RsiStrategy"/> class.
/// </summary>
public HpcsInter6RsiStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Time frame for RSI evaluation", "General");
_rsiLength = Param(nameof(RsiLength), 7)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "Lookback period for RSI", "Parameters")
.SetOptimize(5, 40, 1);
_upperLevel = Param(nameof(UpperLevel), 65m)
.SetDisplay("Upper RSI", "Upper RSI level for shorts", "Parameters")
.SetOptimize(60m, 90m, 5m);
_lowerLevel = Param(nameof(LowerLevel), 35m)
.SetDisplay("Lower RSI", "Lower RSI level for longs", "Parameters")
.SetOptimize(10m, 40m, 5m);
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Order volume for entries", "Trading")
.SetOptimize(0.1m, 5m, 0.1m);
_offsetInPips = Param(nameof(OffsetInPips), 30m)
.SetGreaterThanZero()
.SetDisplay("Offset (pips)", "Target and stop distance in pips", "Risk")
.SetOptimize(5m, 30m, 5m);
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between entries", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsi = null;
_previousRsi = null;
_lastSignalTime = null;
_targetPrice = null;
_stopPrice = null;
_isLongPosition = false;
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_previousRsi = null;
_lastSignalTime = null;
_targetPrice = null;
_stopPrice = null;
_isLongPosition = false;
_candlesSinceTrade = SignalCooldownCandles;
_rsi = new RelativeStrengthIndex
{
Length = RsiLength
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _rsi);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed)
return;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
UpdateActivePositionTargets(candle);
var previousRsi = _previousRsi;
_previousRsi = rsiValue;
if (previousRsi is null)
return;
var candleTime = candle.OpenTime;
if (_lastSignalTime.HasValue && _lastSignalTime.Value == candleTime)
return;
if (_candlesSinceTrade >= SignalCooldownCandles && TryEnterShort(candle, rsiValue, previousRsi.Value))
{
_lastSignalTime = candleTime;
_candlesSinceTrade = 0;
return;
}
if (_candlesSinceTrade >= SignalCooldownCandles && TryEnterLong(candle, rsiValue, previousRsi.Value))
{
_lastSignalTime = candleTime;
_candlesSinceTrade = 0;
}
}
private void UpdateActivePositionTargets(ICandleMessage candle)
{
if (Position > 0)
{
if (!_isLongPosition)
{
_targetPrice = null;
_stopPrice = null;
return;
}
var shouldExit = (_targetPrice.HasValue && candle.HighPrice >= _targetPrice.Value)
|| (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value);
if (shouldExit)
{
SellMarket(Math.Abs(Position));
_targetPrice = null;
_stopPrice = null;
}
}
else if (Position < 0)
{
if (_isLongPosition)
{
_targetPrice = null;
_stopPrice = null;
return;
}
var shouldExit = (_targetPrice.HasValue && candle.LowPrice <= _targetPrice.Value)
|| (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value);
if (shouldExit)
{
BuyMarket(Math.Abs(Position));
_targetPrice = null;
_stopPrice = null;
}
}
else
{
_targetPrice = null;
_stopPrice = null;
_isLongPosition = false;
}
}
private bool TryEnterShort(ICandleMessage candle, decimal currentRsi, decimal previousRsi)
{
if (!(currentRsi > UpperLevel && previousRsi <= UpperLevel))
return false;
var volume = TradeVolume;
if (volume <= 0m)
return false;
if (Position > 0)
{
volume += Math.Abs(Position);
}
SellMarket(volume);
var offset = CalculateOffset();
if (offset > 0m)
{
var entryPrice = candle.ClosePrice;
_targetPrice = entryPrice - offset;
_stopPrice = entryPrice + offset;
_isLongPosition = false;
}
else
{
_targetPrice = null;
_stopPrice = null;
_isLongPosition = false;
}
return true;
}
private bool TryEnterLong(ICandleMessage candle, decimal currentRsi, decimal previousRsi)
{
if (!(currentRsi < LowerLevel && previousRsi >= LowerLevel))
return false;
var volume = TradeVolume;
if (volume <= 0m)
return false;
if (Position < 0)
{
volume += Math.Abs(Position);
}
BuyMarket(volume);
var offset = CalculateOffset();
if (offset > 0m)
{
var entryPrice = candle.ClosePrice;
_targetPrice = entryPrice + offset;
_stopPrice = entryPrice - offset;
_isLongPosition = true;
}
else
{
_targetPrice = null;
_stopPrice = null;
_isLongPosition = true;
}
return true;
}
private decimal CalculateOffset()
{
var priceStep = Security?.PriceStep ?? 0.01m;
if (priceStep <= 0m)
priceStep = 0.01m;
var decimals = Security?.Decimals ?? 0;
var factor = decimals is 3 or 5 ? 10m : 1m;
return OffsetInPips * priceStep * factor;
}
}
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 hpcs_inter6_rsi_strategy(Strategy):
"""
HPCS Inter6 RSI: trades RSI reversals at configurable levels.
Sells when RSI crosses above upper level, buys when crossing below lower level.
Manages SL/TP based on pip offset with signal cooldown.
"""
def __init__(self):
super(hpcs_inter6_rsi_strategy, self).__init__()
self._rsi_length = self.Param("RsiLength", 7) \
.SetDisplay("RSI Length", "Lookback period for RSI", "Parameters")
self._upper_level = self.Param("UpperLevel", 65.0) \
.SetDisplay("Upper RSI", "Upper RSI level for shorts", "Parameters")
self._lower_level = self.Param("LowerLevel", 35.0) \
.SetDisplay("Lower RSI", "Lower RSI level for longs", "Parameters")
self._offset_in_pips = self.Param("OffsetInPips", 30.0) \
.SetDisplay("Offset (pips)", "Target and stop distance in pips", "Risk")
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 4) \
.SetDisplay("Signal Cooldown", "Bars to wait between entries", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60))) \
.SetDisplay("Candle Type", "Time frame for RSI evaluation", "General")
self._prev_rsi = None
self._target_price = None
self._stop_price = None
self._is_long_position = False
self._candles_since_trade = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(hpcs_inter6_rsi_strategy, self).OnReseted()
self._prev_rsi = None
self._target_price = None
self._stop_price = None
self._is_long_position = False
self._candles_since_trade = self._signal_cooldown_candles.Value
def OnStarted2(self, time):
super(hpcs_inter6_rsi_strategy, self).OnStarted2(time)
self._candles_since_trade = self._signal_cooldown_candles.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.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)
def _process_candle(self, candle, rsi_val):
if candle.State != CandleStates.Finished:
return
rsi = float(rsi_val)
if self._candles_since_trade < self._signal_cooldown_candles.Value:
self._candles_since_trade += 1
self._update_position_targets(candle)
prev_rsi = self._prev_rsi
self._prev_rsi = rsi
if prev_rsi is None:
return
cooldown = self._signal_cooldown_candles.Value
if self._candles_since_trade >= cooldown:
if rsi > self._upper_level.Value and prev_rsi <= self._upper_level.Value:
self._enter_short(candle)
self._candles_since_trade = 0
return
if self._candles_since_trade >= cooldown:
if rsi < self._lower_level.Value and prev_rsi >= self._lower_level.Value:
self._enter_long(candle)
self._candles_since_trade = 0
def _update_position_targets(self, candle):
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self.Position > 0:
if not self._is_long_position:
self._target_price = None
self._stop_price = None
return
should_exit = False
if self._target_price is not None and high >= self._target_price:
should_exit = True
if self._stop_price is not None and low <= self._stop_price:
should_exit = True
if should_exit:
self.SellMarket()
self._target_price = None
self._stop_price = None
elif self.Position < 0:
if self._is_long_position:
self._target_price = None
self._stop_price = None
return
should_exit = False
if self._target_price is not None and low <= self._target_price:
should_exit = True
if self._stop_price is not None and high >= self._stop_price:
should_exit = True
if should_exit:
self.BuyMarket()
self._target_price = None
self._stop_price = None
else:
self._target_price = None
self._stop_price = None
self._is_long_position = False
def _enter_short(self, candle):
if self.Position > 0:
self.SellMarket()
self.SellMarket()
offset = self._calculate_offset()
if offset > 0:
entry = float(candle.ClosePrice)
self._target_price = entry - offset
self._stop_price = entry + offset
self._is_long_position = False
else:
self._target_price = None
self._stop_price = None
self._is_long_position = False
def _enter_long(self, candle):
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
offset = self._calculate_offset()
if offset > 0:
entry = float(candle.ClosePrice)
self._target_price = entry + offset
self._stop_price = entry - offset
self._is_long_position = True
else:
self._target_price = None
self._stop_price = None
self._is_long_position = True
def _calculate_offset(self):
step = 0.01
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step <= 0:
step = 0.01
decimals = 0
if self.Security is not None and self.Security.Decimals is not None:
decimals = int(self.Security.Decimals)
factor = 10.0 if decimals in (3, 5) else 1.0
return self._offset_in_pips.Value * step * factor
def CreateClone(self):
return hpcs_inter6_rsi_strategy()