在 GitHub 上查看
RSI MA on RSI Dual 策略
概述
RSI MA on RSI Dual 策略在 StockSharp 中重现了 MetaTrader 的 "RSI_MAonRSI_Dual" 专家顾问。策略同时计算快慢两组相对强弱指数 (RSI),并对每组 RSI 结果使用相同周期的简单移动平均进行平滑。当两条平滑 RSI 线在同一侧穿越中性水平线时触发交易信号。
本实现保留了原始 EA 的核心逻辑,同时提供时间过滤、方向限制以及反转信号等选项。
指标
- 快 RSI:可配置周期的相对强弱指数。
- 慢 RSI:使用独立周期的相对强弱指数。
- RSI 上的移动平均:对快慢两组 RSI 结果应用同一长度的简单移动平均。
所有指标共用同一个价格类型(默认收盘价),平滑后的两条 RSI 曲线会绘制在单独的图表面板上。
入场规则
- 等待当前完成 K 线上的两条平滑 RSI 都形成。
- 做多条件:
- 快线在当前 K 线上穿慢线(当前值大于慢线,上一根 K 线小于慢线)。
- 两条平滑 RSI 均低于中性水平(默认 50)。
- 做空条件:
- 快线在当前 K 线下穿慢线(当前值小于慢线,上一根 K 线上于慢线)。
- 两条平滑 RSI 均高于中性水平。
- 可通过
ReverseSignals 参数反转买卖方向。
- 每根 K 线最多触发一次信号,避免重复下单。
仓位管理
AllowLong / AllowShort 控制是否允许开多或开空。
CloseOpposite 会在反向开仓前平掉现有头寸。
OnlyOnePosition 限制同一时间最多持有一个仓位。
- 策略使用
Volume 指定的固定数量发送市价单。
时间过滤
通过 UseTimeFilter 控制是否启用交易时段过滤。当启用时,仅在 SessionStart 与 SessionEnd 之间允许交易,支持跨越午夜的时段。时间依据收到的 K 线所提供的交易所时区进行判断。
参数
| 参数 |
说明 |
CandleType |
策略分析的 K 线类型。 |
FastRsiPeriod |
快 RSI 的周期。 |
SlowRsiPeriod |
慢 RSI 的周期。 |
MaPeriod |
平滑两条 RSI 的移动平均长度。 |
AppliedPrice |
参与 RSI 计算的价格类型。 |
NeutralLevel |
划分多空区域的 RSI 中性水平。 |
AllowLong / AllowShort |
控制多空方向是否允许交易。 |
ReverseSignals |
反转买卖信号方向。 |
CloseOpposite |
开新仓前是否平掉相反仓位。 |
OnlyOnePosition |
是否限制为单一持仓。 |
UseTimeFilter |
是否启用交易时段过滤。 |
SessionStart / SessionEnd |
交易窗口的开始与结束时间。 |
与原始 EA 的差异
- 未复刻原始 MQL5 代码中的资金管理、止损或移动止损模块。StockSharp 版本仅使用固定手数市价单。
- 移除了所有平台专用的日志与提示,若需要可使用 StockSharp 的日志系统。
- 交易状态跟踪改由 StockSharp 的订单事件处理。
尽管如此,核心的信号生成与方向过滤逻辑与原始专家顾问保持一致。
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>
/// Dual moving averages calculated on top of RSI values.
/// Fast RSI MA crossing slow RSI MA generates entry signals.
/// </summary>
public class RsiMaOnRsiDualStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastRsiPeriod;
private readonly StrategyParam<int> _slowRsiPeriod;
private readonly StrategyParam<int> _maPeriod;
private RelativeStrengthIndex _fastRsi;
private RelativeStrengthIndex _slowRsi;
private readonly Queue<decimal> _fastRsiHistory = new();
private readonly Queue<decimal> _slowRsiHistory = new();
private decimal? _previousFastMa;
private decimal? _previousSlowMa;
public RsiMaOnRsiDualStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle type", "Candles processed by the strategy.", "General");
_fastRsiPeriod = Param(nameof(FastRsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Fast RSI period", "Length of the fast RSI smoothing window.", "Indicators");
_slowRsiPeriod = Param(nameof(SlowRsiPeriod), 28)
.SetGreaterThanZero()
.SetDisplay("Slow RSI period", "Length of the slow RSI smoothing window.", "Indicators");
_maPeriod = Param(nameof(MaPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("MA period", "Number of RSI values averaged by the smoothing moving average.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastRsiPeriod
{
get => _fastRsiPeriod.Value;
set => _fastRsiPeriod.Value = value;
}
public int SlowRsiPeriod
{
get => _slowRsiPeriod.Value;
set => _slowRsiPeriod.Value = value;
}
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousFastMa = null;
_previousSlowMa = null;
_fastRsiHistory.Clear();
_slowRsiHistory.Clear();
_fastRsi = null!;
_slowRsi = null!;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_previousFastMa = null;
_previousSlowMa = null;
_fastRsiHistory.Clear();
_slowRsiHistory.Clear();
_fastRsi = new RelativeStrengthIndex { Length = FastRsiPeriod };
_slowRsi = new RelativeStrengthIndex { Length = SlowRsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_fastRsi, _slowRsi, ProcessCandle)
.Start();
var priceArea = CreateChartArea();
if (priceArea != null)
{
DrawCandles(priceArea, subscription);
DrawOwnTrades(priceArea);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastRsiValue, decimal slowRsiValue)
{
if (candle.State != CandleStates.Finished)
return;
_fastRsiHistory.Enqueue(fastRsiValue);
_slowRsiHistory.Enqueue(slowRsiValue);
while (_fastRsiHistory.Count > MaPeriod)
_fastRsiHistory.Dequeue();
while (_slowRsiHistory.Count > MaPeriod)
_slowRsiHistory.Dequeue();
if (!_fastRsi.IsFormed || !_slowRsi.IsFormed)
return;
if (_fastRsiHistory.Count < MaPeriod || _slowRsiHistory.Count < MaPeriod)
return;
// Calculate SMA of each RSI
var fastSum = 0m;
var fastHistory = _fastRsiHistory.ToArray();
foreach (var v in fastHistory)
fastSum += v;
var fastMa = fastSum / MaPeriod;
var slowSum = 0m;
var slowHistory = _slowRsiHistory.ToArray();
foreach (var v in slowHistory)
slowSum += v;
var slowMa = slowSum / MaPeriod;
if (_previousFastMa is null || _previousSlowMa is null)
{
_previousFastMa = fastMa;
_previousSlowMa = slowMa;
return;
}
var crossUp = _previousFastMa < _previousSlowMa && fastMa > slowMa;
var crossDown = _previousFastMa > _previousSlowMa && fastMa < slowMa;
var volume = Volume;
if (volume <= 0)
volume = 1;
if (crossUp)
{
if (Position <= 0)
BuyMarket(volume);
}
else if (crossDown)
{
if (Position >= 0)
SellMarket(volume);
}
_previousFastMa = fastMa;
_previousSlowMa = slowMa;
}
}
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 rsi_ma_on_rsi_dual_strategy(Strategy):
def __init__(self):
super(rsi_ma_on_rsi_dual_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._fast_rsi_period = self.Param("FastRsiPeriod", 14)
self._slow_rsi_period = self.Param("SlowRsiPeriod", 28)
self._ma_period = self.Param("MaPeriod", 12)
self._fast_rsi_history = []
self._slow_rsi_history = []
self._prev_fast_ma = 0.0
self._prev_slow_ma = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def FastRsiPeriod(self):
return self._fast_rsi_period.Value
@FastRsiPeriod.setter
def FastRsiPeriod(self, value):
self._fast_rsi_period.Value = value
@property
def SlowRsiPeriod(self):
return self._slow_rsi_period.Value
@SlowRsiPeriod.setter
def SlowRsiPeriod(self, value):
self._slow_rsi_period.Value = value
@property
def MaPeriod(self):
return self._ma_period.Value
@MaPeriod.setter
def MaPeriod(self, value):
self._ma_period.Value = value
def OnReseted(self):
super(rsi_ma_on_rsi_dual_strategy, self).OnReseted()
self._fast_rsi_history = []
self._slow_rsi_history = []
self._prev_fast_ma = 0.0
self._prev_slow_ma = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(rsi_ma_on_rsi_dual_strategy, self).OnStarted2(time)
self._fast_rsi_history = []
self._slow_rsi_history = []
self._prev_fast_ma = 0.0
self._prev_slow_ma = 0.0
self._has_prev = False
fast_rsi = RelativeStrengthIndex()
fast_rsi.Length = self.FastRsiPeriod
slow_rsi = RelativeStrengthIndex()
slow_rsi.Length = self.SlowRsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_rsi, slow_rsi, self._process_candle).Start()
def _process_candle(self, candle, fast_rsi_value, slow_rsi_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_rsi_value)
slow_val = float(slow_rsi_value)
ma_len = self.MaPeriod
self._fast_rsi_history.append(fast_val)
self._slow_rsi_history.append(slow_val)
while len(self._fast_rsi_history) > ma_len:
self._fast_rsi_history.pop(0)
while len(self._slow_rsi_history) > ma_len:
self._slow_rsi_history.pop(0)
if len(self._fast_rsi_history) < ma_len or len(self._slow_rsi_history) < ma_len:
return
fast_ma = sum(self._fast_rsi_history) / ma_len
slow_ma = sum(self._slow_rsi_history) / ma_len
if self._has_prev:
cross_up = self._prev_fast_ma < self._prev_slow_ma and fast_ma > slow_ma
cross_down = self._prev_fast_ma > self._prev_slow_ma and fast_ma < slow_ma
if cross_up and self.Position <= 0:
self.BuyMarket()
elif cross_down and self.Position >= 0:
self.SellMarket()
self._prev_fast_ma = fast_ma
self._prev_slow_ma = slow_ma
self._has_prev = True
def CreateClone(self):
return rsi_ma_on_rsi_dual_strategy()