RSI 趋势交易策略
概览
该策略将 MetaTrader 中的 "RSI trader v0.15" 智能交易系统迁移到 StockSharp 高级 API。通过比较价格均线和经过平滑处理的 RSI 趋势,判断市场方向。默认使用一小时 K 线,但可以通过 CandleType 参数修改。
交易逻辑
- 以设定周期计算标准 RSI。
- 使用两条简单移动平均线对 RSI 进行平滑处理:一条快速信号均线和一条慢速确认均线。
- 对收盘价应用两条移动平均线:短周期简单移动平均和长周期加权移动平均,对应原 MQL 程序中的 SMA 与 LWMA 组合。
- 在每根完成的 K 线上评估趋势状态:
- 多头共振:短周期价格均线高于长周期价格均线,且快速 RSI 均线高于慢速 RSI 均线。
- 空头共振:短周期价格均线低于长周期价格均线,且快速 RSI 均线低于慢速 RSI 均线。
- 盘整/分歧:价格与 RSI 均线方向相反,表示没有清晰趋势。
- 根据状态执行操作:
- 出现多头共振且当前没有持仓时开多。
- 出现空头共振且当前没有持仓时开空。
- 一旦检测到盘整状态,立即平掉所有持仓,这与原始 EA 的保护逻辑一致。
- 启用
Reverse时会反转买卖方向,可用于构建反趋势策略。
策略仅在 K 线收盘后行动,并启用 StockSharp 的保护机制以处理异常情况。
参数
| 参数 | 说明 | 默认值 |
|---|---|---|
RsiPeriod |
RSI 计算周期。 | 14 |
ShortRsiMaPeriod |
作用于 RSI 的快速 SMA 周期。 | 9 |
LongRsiMaPeriod |
作用于 RSI 的慢速 SMA 周期。 | 45 |
ShortPriceMaPeriod |
价格短周期 SMA 周期。 | 9 |
LongPriceMaPeriod |
价格长周期加权移动平均周期。 | 45 |
Reverse |
为 true 时反转买卖方向。 |
false |
CandleType |
使用的 K 线数据类型,默认 1 小时。 | 1h |
所有整数参数都预设了优化范围,便于在 StockSharp 优化器中快速迭代。
风险管理
- 当价格与 RSI 趋势出现分歧时立即平仓,以复制原 EA 的“横盘退出”保护。
- 在启动时调用
StartProtection(),结合 StockSharp 的防护框架。
其他说明
- 策略使用基类
Volume属性作为下单手数。 - 仅处理已完成的 K 线,忽略未收盘数据,防止提前触发信号。
- 长周期价格均线使用加权移动平均,以匹配 MQL 中的 LWMA 计算方式。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo.Candles;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// RSI trader strategy aligning price and RSI moving-average trends.
/// </summary>
public class RsiTraderStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _shortRsiMaPeriod;
private readonly StrategyParam<int> _longRsiMaPeriod;
private readonly StrategyParam<int> _shortPriceMaPeriod;
private readonly StrategyParam<int> _longPriceMaPeriod;
private readonly StrategyParam<bool> _reverse;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _closes = new();
private readonly List<decimal> _rsiValues = new();
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public int ShortRsiMaPeriod { get => _shortRsiMaPeriod.Value; set => _shortRsiMaPeriod.Value = value; }
public int LongRsiMaPeriod { get => _longRsiMaPeriod.Value; set => _longRsiMaPeriod.Value = value; }
public int ShortPriceMaPeriod { get => _shortPriceMaPeriod.Value; set => _shortPriceMaPeriod.Value = value; }
public int LongPriceMaPeriod { get => _longPriceMaPeriod.Value; set => _longPriceMaPeriod.Value = value; }
public bool Reverse { get => _reverse.Value; set => _reverse.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public RsiTraderStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14).SetDisplay("RSI Period", "RSI calculation length", "RSI").SetGreaterThanZero();
_shortRsiMaPeriod = Param(nameof(ShortRsiMaPeriod), 12).SetDisplay("Short RSI MA", "Short moving average on RSI", "RSI").SetGreaterThanZero();
_longRsiMaPeriod = Param(nameof(LongRsiMaPeriod), 60).SetDisplay("Long RSI MA", "Long moving average on RSI", "RSI").SetGreaterThanZero();
_shortPriceMaPeriod = Param(nameof(ShortPriceMaPeriod), 12).SetDisplay("Short Price MA", "Short simple moving average", "Price").SetGreaterThanZero();
_longPriceMaPeriod = Param(nameof(LongPriceMaPeriod), 60).SetDisplay("Long Price MA", "Long weighted moving average", "Price").SetGreaterThanZero();
_reverse = Param(nameof(Reverse), false).SetDisplay("Reverse", "Flip buy/sell signals", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Primary candle type", "Data");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_closes.Clear();
_rsiValues.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_closes.Add(candle.ClosePrice);
var maxCache = Math.Max(LongPriceMaPeriod, Math.Max(LongRsiMaPeriod + RsiPeriod, 300));
if (_closes.Count > maxCache)
_closes.RemoveAt(0);
var rsi = CalculateRsi();
if (rsi is null)
return;
_rsiValues.Add(rsi.Value);
if (_rsiValues.Count > maxCache)
_rsiValues.RemoveAt(0);
if (_rsiValues.Count < LongRsiMaPeriod || _closes.Count < LongPriceMaPeriod)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var shortRsi = AverageLast(_rsiValues, ShortRsiMaPeriod);
var longRsi = AverageLast(_rsiValues, LongRsiMaPeriod);
var shortPrice = AverageLast(_closes, ShortPriceMaPeriod);
var longPrice = WeightedAverageLast(_closes, LongPriceMaPeriod);
var goLong = shortPrice > longPrice && shortRsi > longRsi;
var goShort = shortPrice < longPrice && shortRsi < longRsi;
var sideways = !goLong && !goShort;
if (sideways && Position != 0)
{
if (Position > 0)
SellMarket(Position);
else
BuyMarket(Math.Abs(Position));
return;
}
if (Position != 0)
return;
if (goLong)
{
if (Reverse)
SellMarket();
else
BuyMarket();
}
else if (goShort)
{
if (Reverse)
BuyMarket();
else
SellMarket();
}
}
private decimal? CalculateRsi()
{
if (_closes.Count <= RsiPeriod)
return null;
decimal gainSum = 0m;
decimal lossSum = 0m;
var start = _closes.Count - RsiPeriod;
for (var i = start; i < _closes.Count; i++)
{
var change = _closes[i] - _closes[i - 1];
if (change > 0m)
gainSum += change;
else
lossSum -= change;
}
var averageGain = gainSum / RsiPeriod;
var averageLoss = lossSum / RsiPeriod;
if (averageLoss == 0m)
return 100m;
var rs = averageGain / averageLoss;
return 100m - 100m / (1m + rs);
}
private static decimal AverageLast(IReadOnlyList<decimal> values, int length)
{
decimal sum = 0m;
var start = values.Count - length;
for (var i = start; i < values.Count; i++)
sum += values[i];
return sum / length;
}
private static decimal WeightedAverageLast(IReadOnlyList<decimal> values, int length)
{
decimal weightedSum = 0m;
decimal weightSum = 0m;
var start = values.Count - length;
var weight = 1m;
for (var i = start; i < values.Count; i++)
{
weightedSum += values[i] * weight;
weightSum += weight;
weight += 1m;
}
return weightSum > 0m ? weightedSum / weightSum : values[^1];
}
}
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.Strategies import Strategy
class rsi_trader_strategy(Strategy):
def __init__(self):
super(rsi_trader_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14)
self._short_rsi_ma_period = self.Param("ShortRsiMaPeriod", 12)
self._long_rsi_ma_period = self.Param("LongRsiMaPeriod", 60)
self._short_price_ma_period = self.Param("ShortPriceMaPeriod", 12)
self._long_price_ma_period = self.Param("LongPriceMaPeriod", 60)
self._reverse = self.Param("Reverse", False)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._closes = []
self._rsi_values = []
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def ShortRsiMaPeriod(self):
return self._short_rsi_ma_period.Value
@ShortRsiMaPeriod.setter
def ShortRsiMaPeriod(self, value):
self._short_rsi_ma_period.Value = value
@property
def LongRsiMaPeriod(self):
return self._long_rsi_ma_period.Value
@LongRsiMaPeriod.setter
def LongRsiMaPeriod(self, value):
self._long_rsi_ma_period.Value = value
@property
def ShortPriceMaPeriod(self):
return self._short_price_ma_period.Value
@ShortPriceMaPeriod.setter
def ShortPriceMaPeriod(self, value):
self._short_price_ma_period.Value = value
@property
def LongPriceMaPeriod(self):
return self._long_price_ma_period.Value
@LongPriceMaPeriod.setter
def LongPriceMaPeriod(self, value):
self._long_price_ma_period.Value = value
@property
def Reverse(self):
return self._reverse.Value
@Reverse.setter
def Reverse(self, value):
self._reverse.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(rsi_trader_strategy, self).OnStarted2(time)
self._closes = []
self._rsi_values = []
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
self._closes.append(close)
max_cache = max(int(self.LongPriceMaPeriod), max(int(self.LongRsiMaPeriod) + int(self.RsiPeriod), 300))
while len(self._closes) > max_cache:
self._closes.pop(0)
rsi = self._calculate_rsi()
if rsi is None:
return
self._rsi_values.append(rsi)
while len(self._rsi_values) > max_cache:
self._rsi_values.pop(0)
if len(self._rsi_values) < int(self.LongRsiMaPeriod) or len(self._closes) < int(self.LongPriceMaPeriod):
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
short_rsi = self._average_last(self._rsi_values, int(self.ShortRsiMaPeriod))
long_rsi = self._average_last(self._rsi_values, int(self.LongRsiMaPeriod))
short_price = self._average_last(self._closes, int(self.ShortPriceMaPeriod))
long_price = self._weighted_average_last(self._closes, int(self.LongPriceMaPeriod))
go_long = short_price > long_price and short_rsi > long_rsi
go_short = short_price < long_price and short_rsi < long_rsi
sideways = not go_long and not go_short
if sideways and self.Position != 0:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
return
if self.Position != 0:
return
if go_long:
if self.Reverse:
self.SellMarket()
else:
self.BuyMarket()
elif go_short:
if self.Reverse:
self.BuyMarket()
else:
self.SellMarket()
def _calculate_rsi(self):
rsi_period = int(self.RsiPeriod)
if len(self._closes) <= rsi_period:
return None
gain_sum = 0.0
loss_sum = 0.0
start = len(self._closes) - rsi_period
for i in range(start, len(self._closes)):
change = self._closes[i] - self._closes[i - 1]
if change > 0.0:
gain_sum += change
else:
loss_sum -= change
average_gain = gain_sum / rsi_period
average_loss = loss_sum / rsi_period
if average_loss == 0.0:
return 100.0
rs = average_gain / average_loss
return 100.0 - 100.0 / (1.0 + rs)
def _average_last(self, values, length):
total = 0.0
start = len(values) - length
for i in range(start, len(values)):
total += values[i]
return total / length
def _weighted_average_last(self, values, length):
weighted_sum = 0.0
weight_sum = 0.0
start = len(values) - length
weight = 1.0
for i in range(start, len(values)):
weighted_sum += values[i] * weight
weight_sum += weight
weight += 1.0
if weight_sum > 0.0:
return weighted_sum / weight_sum
return values[-1]
def OnReseted(self):
super(rsi_trader_strategy, self).OnReseted()
self._closes = []
self._rsi_values = []
def CreateClone(self):
return rsi_trader_strategy()