JMaster RSX 策略
概述
JMaster RSX 策略是 MetaTrader 4 智能交易系统 jMasterRSXv1 的直接移植版本。系统在快速(M5)与慢速(M30)两个时间框架上计算 Jurik RSX 振荡指标,并在高周期方向与低周期超买/超卖条件一致时开仓。所有判断都在新 K 线开盘时使用上一根已完成的蜡烛数据,完全复制原始 EA 使用 shift = 1 的做法。
指标与数据
- 快速周期 Jurik RSX(长度 =
RsxLength)——在FastCandleType指定的蜡烛序列(默认 5 分钟)上运行,自带的 RSX 指标严格按照原始rsx.mq4递归滤波结构实现。 - 慢速周期 Jurik RSX——在
SlowCandleType指定的蜡烛序列(默认 30 分钟)上计算。最新完成的慢速数值会延迟一根柱子后再用于判定,以模拟 MT4 中的shift行为。
入场逻辑
- 等待新的快速周期蜡烛开盘(即 StockSharp 中一根蜡烛完成)。
- 读取上一根快速 RSX 数值以及上一根慢速 RSX 数值(慢速序列再滞后一根)。
- 做多条件: 慢速 RSX 高于
MidlineLevel(默认 50),且快速 RSX 低于OversoldLevel(默认 25)。 - 做空条件: 慢速 RSX 低于
MidlineLevel,且快速 RSX 高于OverboughtLevel(默认 75)。 - 当当前无持仓时,以
Volume指定的手数市价开仓。
出场逻辑
- 当做空条件成立时,立即平掉已有的多单。
- 当做多条件成立时,立即平掉已有的空单。
- 策略不会叠加仓位;每次都会先平仓再等待下一次信号。
仓位管理
- 下单手数由
Volume参数控制(默认0.1)。 - 没有实现递进加仓或资金管理逻辑,与原始 EA 在
DecreaseFactor = 0时的行为一致。
参数
| 名称 | 说明 | 默认值 |
|---|---|---|
FastCandleType |
快速 RSX 所使用的蜡烛类型 | M5 |
SlowCandleType |
慢速 RSX 所使用的蜡烛类型 | M30 |
RsxLength |
两个 RSX 指标共同的长度 | 14 |
OverboughtLevel |
做空所需的快速 RSX 阈值 | 75 |
OversoldLevel |
做多所需的快速 RSX 阈值 | 25 |
MidlineLevel |
慢速 RSX 牛熊分界线 | 50 |
Volume |
市价单下单手数 | 0.1 |
使用提示
- 请确保历史数据同时提供两个时间框架的完整蜡烛,策略只会在蜡烛收盘后做出反应。
- 慢速 RSX 数值会有一根柱子的延迟,因此高周期的反转会在下一根快速 K 线上才反映出来,此设计与原版 EA 完全一致,可避免偷看未来。
- 内置 RSX 指标输出 0–100 范围数值,方便与其他振荡指标进行组合或对比。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// JMaster RSX: RSI-based momentum with EMA trend filter.
/// Buys when RSI exits oversold and price above EMA.
/// Sells when RSI exits overbought and price below EMA.
/// </summary>
public class JmasterRsxStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _overbought;
private readonly StrategyParam<decimal> _oversold;
private decimal _prevRsi;
private decimal _entryPrice;
public JmasterRsxStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_emaLength = Param(nameof(EmaLength), 30)
.SetDisplay("EMA Length", "EMA trend filter.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period for stops.", "Indicators");
_overbought = Param(nameof(Overbought), 75m)
.SetDisplay("Overbought", "RSI overbought level.", "Signals");
_oversold = Param(nameof(Oversold), 25m)
.SetDisplay("Oversold", "RSI oversold level.", "Signals");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal Overbought
{
get => _overbought.Value;
set => _overbought.Value = value;
}
public decimal Oversold
{
get => _oversold.Value;
set => _oversold.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRsi = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevRsi = 0;
_entryPrice = 0;
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, ema, atr, ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiVal, decimal emaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevRsi == 0 || atrVal <= 0)
{
_prevRsi = rsiVal;
return;
}
var close = candle.ClosePrice;
// Entry
if (Position == 0)
{
if (_prevRsi < Oversold && rsiVal >= Oversold && close > emaVal)
{
_entryPrice = close;
BuyMarket();
}
else if (_prevRsi > Overbought && rsiVal <= Overbought && close < emaVal)
{
_entryPrice = close;
SellMarket();
}
}
_prevRsi = rsiVal;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import RelativeStrengthIndex, ExponentialMovingAverage, AverageTrueRange
class jmaster_rsx_strategy(Strategy):
def __init__(self):
super(jmaster_rsx_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._ema_length = self.Param("EmaLength", 30) \
.SetDisplay("EMA Length", "EMA trend filter.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period for stops.", "Indicators")
self._overbought = self.Param("Overbought", 75.0) \
.SetDisplay("Overbought", "RSI overbought level.", "Signals")
self._oversold = self.Param("Oversold", 25.0) \
.SetDisplay("Oversold", "RSI oversold level.", "Signals")
self._prev_rsi = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
@property
def Overbought(self):
return self._overbought.Value
@property
def Oversold(self):
return self._oversold.Value
def OnStarted2(self, time):
super(jmaster_rsx_strategy, self).OnStarted2(time)
self._prev_rsi = 0.0
self._entry_price = 0.0
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._rsi, self._ema, self._atr, self.ProcessCandle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent)
)
def ProcessCandle(self, candle, rsi_val, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
rv = float(rsi_val)
ev = float(ema_val)
av = float(atr_val)
if self._prev_rsi == 0 or av <= 0:
self._prev_rsi = rv
return
close = float(candle.ClosePrice)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_rsi = rv
return
# Entry
if self.Position == 0:
if self._prev_rsi < float(self.Oversold) and rv >= float(self.Oversold) and close > ev:
self._entry_price = close
self.BuyMarket()
elif self._prev_rsi > float(self.Overbought) and rv <= float(self.Overbought) and close < ev:
self._entry_price = close
self.SellMarket()
self._prev_rsi = rv
def OnReseted(self):
super(jmaster_rsx_strategy, self).OnReseted()
self._prev_rsi = 0.0
self._entry_price = 0.0
def CreateClone(self):
return jmaster_rsx_strategy()