Эта стратегия представляет собой портирование эксперта MetaTrader 5 e_RP_250 на платформу StockSharp. Исходная система
работает от разворотных точек, которые вычисляет пользовательский индикатор rPoint. Поскольку оригинальный индикатор недоступен
в StockSharp, при конверсии используется связка индикаторов Highest и Lowest, полностью воспроизводящая поведение rPoint.
Как только формируется новый локальный максимум или минимум, стратегия переворачивает позицию и навешивает те же stop-loss,
take-profit и опциональный трейлинг-стоп, что и в версии для MQL.
В исходном коде не приводятся проверенные результаты тестирования, поэтому перед реальной торговлей обязательно выполните
собственную оценку эффективности.
Логика торговли
Подписка на свечи, заданные параметром CandleType (по умолчанию 5-минутные бары).
Отслеживание максимума и минимума за последние ReversePoint свечей (по умолчанию 250 баров).
Если текущая свеча обновляет максимум, закрывается лонг (если он был) и открывается шорт.
Если текущая свеча обновляет минимум, закрывается шорт (если он был) и открывается лонг.
Уровни stop-loss и take-profit задаются в пунктах и реализуются через StartProtection.
Дополнительный трейлинг-стоп фиксирует прибыль после прохождения ценой заданного количества пунктов.
В каждый момент времени открыта только одна позиция. Стратегия также блокирует повторные сигналы на одной и той же свече,
запоминая время последнего срабатывания — тем самым повторяется защита TimeN из исходного скрипта.
Параметры
Параметр
Описание
TakeProfitPoints
Дистанция take-profit в ценовых пунктах (по умолчанию 15). Ноль отключает автоматическое фиксирование прибыли.
StopLossPoints
Дистанция stop-loss в ценовых пунктах (по умолчанию 999). Ноль означает торговлю без фиксированного стопа.
TrailingStopPoints
Опциональный трейлинг-стоп в пунктах (по умолчанию 0 — трейлинг выключен).
ReversePoint
Количество свечей для определения разворотных точек. Чем больше значение, тем «плавнее» реакции на шум.
CandleType
Тип анализируемых свечей. По умолчанию 5-минутный таймфрейм, но можно выбрать любой DataType.
Управление позицией
StartProtection выставляет те же stop-loss и take-profit, что и оригинальный эксперт.
Трейлинг-стоп отслеживает наилучшую цену после входа и закрывает сделку при откате на заданное расстояние.
Сигнал обратного направления закрывает текущую позицию и затем инициирует новый вход.
Рекомендации по использованию
Убедитесь, что поставщик данных поддерживает выбранный тип свечей, иначе сигналы появляться не будут.
Проверьте корректность PriceStep у инструмента — от него зависит пересчёт пунктов в цену.
Экспериментируйте со значением ReversePoint, чтобы подобрать чувствительность под волатильность конкретного актива.
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 e_RP_250 MQL script.
/// </summary>
public class ERp250Strategy : Strategy
{
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 _latestHighSignal;
private decimal _latestLowSignal;
private decimal _lastExecutedHigh;
private decimal _lastExecutedLow;
private DateTimeOffset? _lastSignalTime;
private decimal? _bestLongPrice;
private decimal? _bestShortPrice;
private decimal _trailingDistance;
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
public int ReversePoint
{
get => _reversePoint.Value;
set => _reversePoint.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public ERp250Strategy()
{
_takeProfitPoints = Param(nameof(TakeProfitPoints), 15m)
.SetDisplay("Take Profit Points", "Take profit distance in price points", "Risk")
;
_stopLossPoints = Param(nameof(StopLossPoints), 999m)
.SetDisplay("Stop Loss Points", "Stop loss distance in price points", "Risk")
;
_trailingStopPoints = Param(nameof(TrailingStopPoints), 0m)
.SetDisplay("Trailing Stop Points", "Trailing stop distance in price points", "Risk")
;
_reversePoint = Param(nameof(ReversePoint), 400)
.SetDisplay("Reverse Point Length", "Candles used to confirm reversal points", "Signals")
.SetGreaterThanZero()
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to analyse", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highest = null;
_lowest = null;
_latestHighSignal = 0m;
_latestLowSignal = 0m;
_lastExecutedHigh = 0m;
_lastExecutedLow = 0m;
_lastSignalTime = null;
_bestLongPrice = null;
_bestShortPrice = null;
_trailingDistance = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highest = new Highest { Length = ReversePoint };
_lowest = new Lowest { Length = ReversePoint };
var step = Security?.PriceStep ?? 1m;
if (step <= 0m)
step = 1m;
var takeDistance = TakeProfitPoints > 0m ? step * TakeProfitPoints : 0m;
var stopDistance = StopLossPoints > 0m ? step * StopLossPoints : 0m;
_trailingDistance = TrailingStopPoints > 0m ? step * TrailingStopPoints : 0m;
// Enable protective orders that match the original stop and take-profit distances.
StartProtection(
takeDistance > 0m ? new Unit(takeDistance, UnitTypes.Absolute) : default,
stopDistance > 0m ? new Unit(stopDistance, UnitTypes.Absolute) : default
);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var highValue = _highest.Process(new DecimalIndicatorValue(_highest, candle.HighPrice, candle.OpenTime) { IsFinal = true }).ToNullableDecimal();
var lowValue = _lowest.Process(new DecimalIndicatorValue(_lowest, candle.LowPrice, candle.OpenTime) { IsFinal = true }).ToNullableDecimal();
if (highValue is null || lowValue is null)
return;
// Update the latest reversal levels detected by the rolling highest/lowest indicators.
if (highValue.Value == candle.HighPrice)
_latestHighSignal = candle.HighPrice;
if (lowValue.Value == candle.LowPrice)
_latestLowSignal = candle.LowPrice;
// Manage an existing long position by trailing profits and reacting to opposite signals.
if (Position > 0)
{
_bestLongPrice = (_bestLongPrice is null || candle.HighPrice > _bestLongPrice) ? candle.HighPrice : _bestLongPrice;
if (_trailingDistance > 0m && _bestLongPrice is decimal bestLong && bestLong - candle.ClosePrice >= _trailingDistance)
{
SellMarket();
_bestLongPrice = null;
return;
}
if (_latestHighSignal != 0m && _latestHighSignal != _lastExecutedHigh)
{
SellMarket();
_bestLongPrice = null;
return;
}
}
else if (Position < 0)
{
_bestShortPrice = (_bestShortPrice is null || candle.LowPrice < _bestShortPrice) ? candle.LowPrice : _bestShortPrice;
if (_trailingDistance > 0m && _bestShortPrice is decimal bestShort && candle.ClosePrice - bestShort >= _trailingDistance)
{
BuyMarket();
_bestShortPrice = null;
return;
}
if (_latestLowSignal != 0m && _latestLowSignal != _lastExecutedLow)
{
BuyMarket();
_bestShortPrice = null;
return;
}
}
else
{
_bestLongPrice = null;
_bestShortPrice = null;
}
if (Position != 0)
return;
// Avoid placing more than one order within the same candle.
if (_lastSignalTime == candle.OpenTime)
return;
// Execute a new short position when a fresh reversal high is detected.
if (_latestHighSignal != 0m && _latestHighSignal != _lastExecutedHigh)
{
SellMarket();
_lastExecutedHigh = _latestHighSignal;
_lastSignalTime = candle.OpenTime;
_bestShortPrice = candle.ClosePrice;
_bestLongPrice = null;
return;
}
// Execute a new long position when a fresh reversal low is detected.
if (_latestLowSignal != 0m && _latestLowSignal != _lastExecutedLow)
{
BuyMarket();
_lastExecutedLow = _latestLowSignal;
_lastSignalTime = candle.OpenTime;
_bestLongPrice = candle.ClosePrice;
_bestShortPrice = null;
}
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class e_rp250_strategy(Strategy):
def __init__(self):
super(e_rp250_strategy, self).__init__()
self._take_profit_points = self.Param("TakeProfitPoints", 15.0)
self._stop_loss_points = self.Param("StopLossPoints", 999.0)
self._trailing_stop_points = self.Param("TrailingStopPoints", 0.0)
self._reverse_point = self.Param("ReversePoint", 400)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._latest_high_signal = 0.0
self._latest_low_signal = 0.0
self._last_executed_high = 0.0
self._last_executed_low = 0.0
self._last_signal_time = None
self._best_long_price = None
self._best_short_price = None
self._trailing_distance = 0.0
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def TrailingStopPoints(self):
return self._trailing_stop_points.Value
@TrailingStopPoints.setter
def TrailingStopPoints(self, value):
self._trailing_stop_points.Value = value
@property
def ReversePoint(self):
return self._reverse_point.Value
@ReversePoint.setter
def ReversePoint(self, value):
self._reverse_point.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(e_rp250_strategy, self).OnStarted2(time)
self._highest = Highest()
self._highest.Length = self.ReversePoint
self._lowest = Lowest()
self._lowest.Length = self.ReversePoint
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if step <= 0.0:
step = 1.0
take_distance = step * float(self.TakeProfitPoints) if float(self.TakeProfitPoints) > 0.0 else 0.0
stop_distance = step * float(self.StopLossPoints) if float(self.StopLossPoints) > 0.0 else 0.0
self._trailing_distance = step * float(self.TrailingStopPoints) if float(self.TrailingStopPoints) > 0.0 else 0.0
tp_unit = Unit(take_distance, UnitTypes.Absolute) if take_distance > 0.0 else Unit()
sl_unit = Unit(stop_distance, UnitTypes.Absolute) if stop_distance > 0.0 else Unit()
self.StartProtection(tp_unit, sl_unit)
self._latest_high_signal = 0.0
self._latest_low_signal = 0.0
self._last_executed_high = 0.0
self._last_executed_low = 0.0
self._last_signal_time = None
self._best_long_price = None
self._best_short_price = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
high_result = process_float(self._highest, candle.HighPrice, candle.OpenTime, True)
low_result = process_float(self._lowest, candle.LowPrice, candle.OpenTime, True)
if high_result.IsEmpty or low_result.IsEmpty:
return
high_value = float(high_result)
low_value = float(low_result)
if abs(high_value - high) < 1e-10:
self._latest_high_signal = high
if abs(low_value - low) < 1e-10:
self._latest_low_signal = low
# Manage existing long position
if self.Position > 0:
if self._best_long_price is None or high > self._best_long_price:
self._best_long_price = high
if self._trailing_distance > 0.0 and self._best_long_price is not None and self._best_long_price - close >= self._trailing_distance:
self.SellMarket()
self._best_long_price = None
return
if self._latest_high_signal != 0.0 and self._latest_high_signal != self._last_executed_high:
self.SellMarket()
self._best_long_price = None
return
elif self.Position < 0:
if self._best_short_price is None or low < self._best_short_price:
self._best_short_price = low
if self._trailing_distance > 0.0 and self._best_short_price is not None and close - self._best_short_price >= self._trailing_distance:
self.BuyMarket()
self._best_short_price = None
return
if self._latest_low_signal != 0.0 and self._latest_low_signal != self._last_executed_low:
self.BuyMarket()
self._best_short_price = None
return
else:
self._best_long_price = None
self._best_short_price = None
if self.Position != 0:
return
# Avoid placing more than one order within the same candle
if self._last_signal_time is not None and self._last_signal_time == candle.OpenTime:
return
# Short on fresh reversal high
if self._latest_high_signal != 0.0 and self._latest_high_signal != self._last_executed_high:
self.SellMarket()
self._last_executed_high = self._latest_high_signal
self._last_signal_time = candle.OpenTime
self._best_short_price = close
self._best_long_price = None
return
# Long on fresh reversal low
if self._latest_low_signal != 0.0 and self._latest_low_signal != self._last_executed_low:
self.BuyMarket()
self._last_executed_low = self._latest_low_signal
self._last_signal_time = candle.OpenTime
self._best_long_price = close
self._best_short_price = None
def OnReseted(self):
super(e_rp250_strategy, self).OnReseted()
self._latest_high_signal = 0.0
self._latest_low_signal = 0.0
self._last_executed_high = 0.0
self._last_executed_low = 0.0
self._last_signal_time = None
self._best_long_price = None
self._best_short_price = None
self._trailing_distance = 0.0
def CreateClone(self):
return e_rp250_strategy()