RPoint 250 反转策略
RPoint 250 反转策略 是 MetaTrader 4 专家顾问 e_RPoint_250 在 StockSharp 平台上的移植版本。原始脚本依赖自定义
指标 RPoint 来标记最近的摆动高点与低点。由于该指标在 StockSharp 中不可用,本实现改用内置的 Highest 与 Lowest
指标重建同样的逻辑。当新的极值取代旧的极值时,策略会立即反手,并重新应用与 MQL 版本一致的止损、止盈和拖尾机制。
交易流程
- 订阅
CandleType指定的 K 线序列(默认 5 分钟)。 - 计算最近
ReversePoint根 K 线的最高价与最低价,这两个数值即为模拟的 RPoint 水平。 - 若出现新的最高价,关闭所有多头仓位并按
OrderVolume开立空单。 - 若出现新的最低价,关闭所有空头仓位并按
OrderVolume开立多单。 - 通过
StartProtection设置保护性订单,StopLossPoints与TakeProfitPoints以价格点数表示。 - 当
TrailingStopPoints大于零时启用拖尾:价格自入场以来移动的最大幅度回撤超过该阈值时,仓位将被平仓。 - 记录最近一次进场所在 K 线的开盘时间,避免在同一根 K 线上重复下单,对应 MQL 中的
TimeN逻辑。
策略始终只持有一侧仓位,在反向进场前会先行平仓。
参数说明
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
OrderVolume |
decimal |
0.1 |
每次市价单的手数,对应原版的 Lots 输入。 |
TakeProfitPoints |
decimal |
15 |
止盈距离(点数)。设为 0 可禁用固定止盈。 |
StopLossPoints |
decimal |
999 |
止损距离(点数)。设为 0 可关闭固定止损。 |
TrailingStopPoints |
decimal |
0 |
拖尾幅度(点数)。为零时不启用拖尾。 |
ReversePoint |
int |
250 |
搜索最近极值时回溯的 K 线数量。数值越大越平滑但反应更慢。 |
CandleType |
DataType |
TimeSpan.FromMinutes(5).TimeFrame() |
用于分析的 K 线周期,可根据 MetaTrader 图表调整。 |
实现细节
- 通过高阶 API
Bind将Highest与Lowest绑定到行情订阅,无需手动维护缓存队列。 StartProtection使用绝对价格单位还原原策略的止损与止盈距离,StockSharp 会在持仓变化后自动挂单。- 拖尾采用收盘 K 线评估:当价格从入场后的最佳水平回撤超过阈值时,以市价单离场。
_executedHighLevel与_executedLowLevel保存最近一次触发的极值,防止重复下单,相当于 MQL 代码中的Reverse_High/Reverse_Low变量。_lastSignalTime复制了TimeN的节流机制,保证每根 K 线最多只会触发一次进场。
使用建议
- 将策略加载到支持目标品种与所选 K 线周期的组合上。
- 根据账户规模与风控规则调整
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()