Стратегия RPoint 250 Reversal
RPoint 250 Reversal — портирование эксперта MetaTrader 4 e_RPoint_250 на платформу StockSharp. В оригинале советник
использует пользовательский индикатор RPoint, который отмечает последние экстремумы цены. В StockSharp такого индикатора нет,
поэтому стратегия имитирует его работу связкой стандартных индикаторов Highest и Lowest. Как только появляется новый максимум
или минимум, система моментально переворачивает позицию и заново выставляет стоп-лосс, тейк-профит и трейлинг, как в версии MQL.
Логика работы
- Подписываемся на свечи с типом
CandleType (по умолчанию пятиминутные).
- Рассчитываем скользящий максимум и минимум за последние
ReversePoint баров — это аналог уровней RPoint.
- Если формируется новый максимум, закрываем все покупки и открываем продажу объёмом
OrderVolume.
- Если формируется новый минимум, закрываем все продажи и открываем покупку объёмом
OrderVolume.
- Защита позиции выполняется через
StartProtection: расстояния StopLossPoints и TakeProfitPoints задаются в пунктах цены.
- При ненулевом
TrailingStopPoints прибыль фиксируется трейлингом — позиция закрывается, когда цена откатывает на указанное
число пунктов от лучшего значения после входа.
- Запоминаем время свечи с последним входом и не открываем новые сделки в ту же свечу. Это полностью повторяет защиту
TimeN
в исходном коде.
Стратегия поддерживает только одну позицию: перед входом в противоположную сторону текущая сделка всегда закрывается.
Параметры
| Параметр |
Тип |
Значение по умолчанию |
Описание |
OrderVolume |
decimal |
0.1 |
Объём рыночных заявок, аналог параметра Lots в MetaTrader. |
TakeProfitPoints |
decimal |
15 |
Размер тейк-профита в пунктах. Ноль отключает фиксацию прибыли. |
StopLossPoints |
decimal |
999 |
Размер стоп-лосса в пунктах. Ноль позволяет торговать без фиксированного стопа. |
TrailingStopPoints |
decimal |
0 |
Ширина трейлинга в пунктах. При нуле трейлинг не используется. |
ReversePoint |
int |
250 |
Количество свечей для поиска последнего экстремума. Большие значения уменьшают шум. |
CandleType |
DataType |
TimeSpan.FromMinutes(5).TimeFrame() |
Тип свечей для анализа. Подберите под таймфрейм в MetaTrader. |
Особенности реализации
Highest и Lowest подключены через высокоуровневый метод Bind, поэтому дополнительные буферы не нужны.
StartProtection переводит параметры стопа и тейка в абсолютные цены, и StockSharp сам сопровождает позицию.
- Трейлинг реализован на закрытых свечах: если цена откатывает на заданное количество пунктов от максимальной прибыли, позиция
закрывается маркет-ордером.
- Переменные
_executedHighLevel и _executedLowLevel хранят уровень последнего срабатывания, чтобы не дублировать сделки.
Это прямой аналог Reverse_High и Reverse_Low из MQL.
- Поле
_lastSignalTime повторяет переменную TimeN и блокирует повторный вход в пределах одной свечи.
Рекомендации по использованию
- Запустите стратегию в портфеле, где доступен нужный инструмент и тип свечей.
- Настройте
OrderVolume под торговые ограничения брокера и собственное управление капиталом.
- Подберите
ReversePoint под волатильность инструмента: большой горизонт сглаживает шум, но реагирует медленнее.
- Убедитесь, что значения
StopLossPoints, TakeProfitPoints и TrailingStopPoints совместимы с PriceStep инструмента.
- Перед реальной торговлей выполните тест в StockSharp Designer или Backtester и убедитесь, что поведение соответствует ожиданиям.
- Контролируйте журналы стратегии — они помогут отследить перевороты и сравнить их с оригинальным экспертом.
Поскольку индикатор RPoint заменён стандартными компонентами, возможны незначительные расхождения с MetaTrader при разных
источниках данных или правилах округления. Всегда проверяйте стратегию на собственных котировках до запуска в продакшне.
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>
/// Reverse-point breakout strategy converted from the MetaTrader 4 expert e_RPoint_250.
/// </summary>
public class RPoint250Strategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<int> _reversePoint;
private readonly StrategyParam<DataType> _candleType;
private Highest _highest;
private Lowest _lowest;
private decimal _lastHighLevel;
private decimal _lastLowLevel;
private decimal _executedHighLevel;
private decimal _executedLowLevel;
private DateTimeOffset? _lastSignalTime;
private decimal _priceStep;
private decimal _trailingDistance;
private decimal? _bestLongPrice;
private decimal? _bestShortPrice;
public RPoint250Strategy()
{
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetDisplay("Order Volume", "Base volume for market entries.", "Trading")
;
_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
.SetDisplay("Take Profit Points", "Take profit distance expressed in price points.", "Risk")
;
_stopLossPoints = Param(nameof(StopLossPoints), 999m)
.SetDisplay("Stop Loss Points", "Stop loss distance expressed in price points.", "Risk")
;
_trailingStopPoints = Param(nameof(TrailingStopPoints), 0m)
.SetDisplay("Trailing Stop Points", "Optional trailing distance in price points.", "Risk")
;
_reversePoint = Param(nameof(ReversePoint), 250)
.SetDisplay("Reverse Point Length", "Number of candles scanned for the latest reversal levels.", "Signals")
.SetGreaterThanZero()
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle aggregation used for calculations.", "General");
}
/// <summary>
/// Market order volume used for both entries and reversals.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Take-profit distance in price points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Stop-loss distance in price points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Trailing-stop distance in price points.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Number of candles used to approximate the rPoint indicator.
/// </summary>
public int ReversePoint
{
get => _reversePoint.Value;
set => _reversePoint.Value = value;
}
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highest = null;
_lowest = null;
_lastHighLevel = 0m;
_lastLowLevel = 0m;
_executedHighLevel = 0m;
_executedLowLevel = 0m;
_lastSignalTime = null;
_priceStep = 0m;
_trailingDistance = 0m;
_bestLongPrice = null;
_bestShortPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highest = new Highest { Length = Math.Max(1, ReversePoint) };
_lowest = new Lowest { Length = Math.Max(1, ReversePoint) };
_priceStep = Security?.PriceStep ?? 0m;
if (_priceStep <= 0m)
_priceStep = 1m;
var takeDistance = TakeProfitPoints > 0m ? _priceStep * TakeProfitPoints : 0m;
var stopDistance = StopLossPoints > 0m ? _priceStep * StopLossPoints : 0m;
_trailingDistance = TrailingStopPoints > 0m ? _priceStep * TrailingStopPoints : 0m;
// Apply the same static protection as in the original MQL script.
var tp = takeDistance > 0m ? new Unit(takeDistance, UnitTypes.Absolute) : (Unit)null;
var sl = stopDistance > 0m ? new Unit(stopDistance, UnitTypes.Absolute) : (Unit)null;
if (tp != null || sl != null)
StartProtection(tp, sl);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_highest, _lowest, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _highest);
DrawIndicator(area, _lowest);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal highestValue, decimal lowestValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_highest.IsFormed || !_lowest.IsFormed)
return;
// Capture the latest swing levels as soon as they appear.
if (highestValue == candle.HighPrice && highestValue != _lastHighLevel)
_lastHighLevel = highestValue;
if (lowestValue == candle.LowPrice && lowestValue != _lastLowLevel)
_lastLowLevel = lowestValue;
if (Position > 0)
{
_bestLongPrice = _bestLongPrice is null || candle.HighPrice > _bestLongPrice
? candle.HighPrice
: _bestLongPrice;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Close the long position when price retraces by the trailing distance.
if (_trailingDistance > 0m && _bestLongPrice is decimal bestLong && bestLong - candle.LowPrice >= _trailingDistance)
{
SellMarket(Position);
_bestLongPrice = null;
return;
}
// Reverse the position when a new high reversal point appears.
if (_lastHighLevel != 0m && _lastHighLevel != _executedHighLevel)
{
SellMarket(Position);
_bestLongPrice = null;
return;
}
}
else if (Position < 0)
{
_bestShortPrice = _bestShortPrice is null || candle.LowPrice < _bestShortPrice
? candle.LowPrice
: _bestShortPrice;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Close the short position when price rallies by the trailing distance.
if (_trailingDistance > 0m && _bestShortPrice is decimal bestShort && candle.HighPrice - bestShort >= _trailingDistance)
{
BuyMarket(-Position);
_bestShortPrice = null;
return;
}
// Reverse the position when a new low reversal point appears.
if (_lastLowLevel != 0m && _lastLowLevel != _executedLowLevel)
{
BuyMarket(-Position);
_bestShortPrice = null;
return;
}
}
else
{
_bestLongPrice = null;
_bestShortPrice = null;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (OrderVolume <= 0m)
return;
if (_lastSignalTime == candle.OpenTime)
return;
// Enter short when the reversal high changes.
if (_lastHighLevel != 0m && _lastHighLevel != _executedHighLevel)
{
SellMarket(OrderVolume);
_executedHighLevel = _lastHighLevel;
_lastSignalTime = candle.OpenTime;
_bestShortPrice = candle.ClosePrice;
return;
}
// Enter long when the reversal low changes.
if (_lastLowLevel != 0m && _lastLowLevel != _executedLowLevel)
{
BuyMarket(OrderVolume);
_executedLowLevel = _lastLowLevel;
_lastSignalTime = candle.OpenTime;
_bestLongPrice = candle.ClosePrice;
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import Highest, Lowest
class r_point250_strategy(Strategy):
def __init__(self):
super(r_point250_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 1.0) \
.SetDisplay("Order Volume", "Base volume for market entries", "Trading")
self._take_profit_points = self.Param("TakeProfitPoints", 500.0) \
.SetDisplay("Take Profit Points", "Take profit distance in price points", "Risk")
self._stop_loss_points = self.Param("StopLossPoints", 999.0) \
.SetDisplay("Stop Loss Points", "Stop loss distance in price points", "Risk")
self._trailing_stop_points = self.Param("TrailingStopPoints", 0.0) \
.SetDisplay("Trailing Stop Points", "Optional trailing distance in price points", "Risk")
self._reverse_point = self.Param("ReversePoint", 250) \
.SetDisplay("Reverse Point Length", "Number of candles scanned for reversal levels", "Signals")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle aggregation used for calculations", "General")
self._last_high_level = 0.0
self._last_low_level = 0.0
self._executed_high_level = 0.0
self._executed_low_level = 0.0
self._last_signal_time = None
self._price_step = 1.0
self._trailing_distance = 0.0
self._best_long_price = None
self._best_short_price = None
@property
def OrderVolume(self):
return self._order_volume.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TrailingStopPoints(self):
return self._trailing_stop_points.Value
@property
def ReversePoint(self):
return self._reverse_point.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(r_point250_strategy, self).OnStarted2(time)
highest = Highest()
highest.Length = max(1, self.ReversePoint)
lowest = Lowest()
lowest.Length = max(1, self.ReversePoint)
ps = self.Security.PriceStep if self.Security is not None else None
self._price_step = float(ps) if ps is not None else 1.0
if self._price_step <= 0:
self._price_step = 1.0
tp_pts = float(self.TakeProfitPoints)
sl_pts = float(self.StopLossPoints)
trail_pts = float(self.TrailingStopPoints)
take_dist = self._price_step * tp_pts if tp_pts > 0 else 0.0
stop_dist = self._price_step * sl_pts if sl_pts > 0 else 0.0
self._trailing_distance = self._price_step * trail_pts if trail_pts > 0 else 0.0
tp = Unit(take_dist, UnitTypes.Absolute) if take_dist > 0 else None
sl = Unit(stop_dist, UnitTypes.Absolute) if stop_dist > 0 else None
if tp is not None or sl is not None:
self.StartProtection(tp, sl)
self._highest = highest
self._lowest = lowest
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(highest, lowest, self.ProcessCandle).Start()
def ProcessCandle(self, candle, highest_value, lowest_value):
if candle.State != CandleStates.Finished:
return
if not self._highest.IsFormed or not self._lowest.IsFormed:
return
highest_value = float(highest_value)
lowest_value = float(lowest_value)
high_price = float(candle.HighPrice)
low_price = float(candle.LowPrice)
close_price = float(candle.ClosePrice)
if highest_value == high_price and highest_value != self._last_high_level:
self._last_high_level = highest_value
if lowest_value == low_price and lowest_value != self._last_low_level:
self._last_low_level = lowest_value
if self.Position > 0:
if self._best_long_price is None or high_price > self._best_long_price:
self._best_long_price = high_price
if (self._trailing_distance > 0 and self._best_long_price is not None
and self._best_long_price - low_price >= self._trailing_distance):
self.SellMarket(self.Position)
self._best_long_price = None
return
if self._last_high_level != 0 and self._last_high_level != self._executed_high_level:
self.SellMarket(self.Position)
self._best_long_price = None
return
elif self.Position < 0:
if self._best_short_price is None or low_price < self._best_short_price:
self._best_short_price = low_price
if (self._trailing_distance > 0 and self._best_short_price is not None
and high_price - self._best_short_price >= self._trailing_distance):
self.BuyMarket(-self.Position)
self._best_short_price = None
return
if self._last_low_level != 0 and self._last_low_level != self._executed_low_level:
self.BuyMarket(-self.Position)
self._best_short_price = None
return
else:
self._best_long_price = None
self._best_short_price = None
ov = float(self.OrderVolume)
if ov <= 0:
return
if self._last_signal_time == candle.OpenTime:
return
if self._last_high_level != 0 and self._last_high_level != self._executed_high_level:
self.SellMarket(ov)
self._executed_high_level = self._last_high_level
self._last_signal_time = candle.OpenTime
self._best_short_price = close_price
return
if self._last_low_level != 0 and self._last_low_level != self._executed_low_level:
self.BuyMarket(ov)
self._executed_low_level = self._last_low_level
self._last_signal_time = candle.OpenTime
self._best_long_price = close_price
def OnReseted(self):
super(r_point250_strategy, self).OnReseted()
self._last_high_level = 0.0
self._last_low_level = 0.0
self._executed_high_level = 0.0
self._executed_low_level = 0.0
self._last_signal_time = None
self._best_long_price = None
self._best_short_price = None
def CreateClone(self):
return r_point250_strategy()