cm RSI 策略
概述
该策略是 MetaTrader 4 专家顾问 "cm_RSI" 的移植版本。它利用相对强弱指数 (RSI) 来捕捉动量反转。
算法使用K线开盘价计算 RSI。当 RSI 在低于买入阈值后向上突破该阈值时,开多头仓位;当 RSI 在高于卖出阈值后向下跌破该阈值时,开空头仓位。每笔交易均使用固定的止盈和止损点数进行保护。
策略逻辑
- 使用用户设定的周期,通过开盘价计算 RSI。
- 如果前一个 RSI 值低于买入水平且当前值从下向上突破该水平,则按市价买入。
- 如果前一个 RSI 值高于卖出水平且当前值从上向下跌破该水平,则按市价卖出。
- 每笔交易使用相同的成交量,并附带止损和止盈。
参数
| 名称 | 说明 |
|---|---|
RsiPeriod |
RSI 计算周期。 |
BuyLevel |
触发多头信号的 RSI 水平。 |
SellLevel |
触发空头信号的 RSI 水平。 |
TakeProfit |
绝对价格点数的止盈。 |
StopLoss |
绝对价格点数的止损。 |
OrderVolume |
每笔交易的成交量。 |
CandleType |
用于计算的K线类型。 |
备注
- 仅处理已完成的K线。
- 任意时刻只持有一个仓位。
- 通过
StartProtection自动管理止损和止盈。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Wilder RSI on finished candle OPEN prices; market entries on strict threshold crossings.
/// The selected order volume and PriceStep-based protection are applied to every entry.
/// </summary>
public class CmRsiStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _buyLevel;
private readonly StrategyParam<decimal> _sellLevel;
private readonly StrategyParam<int> _takeProfit;
private readonly StrategyParam<int> _stopLoss;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<DataType> _candleType;
private decimal? _previousOpen;
private decimal? _previousRsi;
private decimal _sumGain;
private decimal _sumLoss;
private decimal _averageGain;
private decimal _averageLoss;
private int _changes;
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public decimal BuyLevel { get => _buyLevel.Value; set => _buyLevel.Value = value; }
public decimal SellLevel { get => _sellLevel.Value; set => _sellLevel.Value = value; }
public int TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public int StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal OrderVolume { get => _orderVolume.Value; set => _orderVolume.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public CmRsiStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14).SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI calculation period", "Indicators")
.SetOptimize(7, 21, 1);
_buyLevel = Param(nameof(BuyLevel), 30m)
.SetDisplay("Buy Level", "RSI level to enter long", "Indicators")
.SetOptimize(10m, 40m, 5m);
_sellLevel = Param(nameof(SellLevel), 70m)
.SetDisplay("Sell Level", "RSI level to enter short", "Indicators")
.SetOptimize(60m, 90m, 5m);
_takeProfit = Param(nameof(TakeProfit), 200).SetNotNegative()
.SetDisplay("Take Profit", "Take profit distance in price steps", "Risk Management")
.SetOptimize(100, 400, 50);
_stopLoss = Param(nameof(StopLoss), 100).SetNotNegative()
.SetDisplay("Stop Loss", "Stop loss distance in price steps", "Risk Management")
.SetOptimize(50, 200, 50);
_orderVolume = Param(nameof(OrderVolume), 0.1m).SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume of each trade", "General")
.SetOptimize(0.1m, 1m, 0.1m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetRsi();
}
private void ResetRsi()
{
_previousOpen = null;
_previousRsi = null;
_sumGain = _sumLoss = _averageGain = _averageLoss = 0m;
_changes = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetRsi();
var step = Security.PriceStep ?? 0m;
if (step <= 0m)
throw new InvalidOperationException("Cm RSI requires a positive Security.PriceStep for point-based stops.");
SubscribeCandles(CandleType).Bind(ProcessCandle).Start();
StartProtection(
takeProfit: TakeProfit > 0 ? new Unit(TakeProfit * step, UnitTypes.Absolute) : new Unit(),
stopLoss: StopLoss > 0 ? new Unit(StopLoss * step, UnitTypes.Absolute) : new Unit());
}
private static decimal ToRsi(decimal averageGain, decimal averageLoss)
{
if (averageLoss == 0m)
return averageGain == 0m ? 50m : 100m;
if (averageGain == 0m)
return 0m;
return 100m * averageGain / (averageGain + averageLoss);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var open = candle.OpenPrice;
if (_previousOpen is not decimal lastOpen)
{
_previousOpen = open;
return;
}
_previousOpen = open;
var change = open - lastOpen;
var gain = Math.Max(change, 0m);
var loss = Math.Max(-change, 0m);
if (_changes < RsiPeriod)
{
_sumGain += gain;
_sumLoss += loss;
_changes++;
if (_changes < RsiPeriod)
return;
_averageGain = _sumGain / RsiPeriod;
_averageLoss = _sumLoss / RsiPeriod;
_previousRsi = ToRsi(_averageGain, _averageLoss);
return; // First completed RSI is the signal baseline, never an entry.
}
_averageGain = (_averageGain * (RsiPeriod - 1) + gain) / RsiPeriod;
_averageLoss = (_averageLoss * (RsiPeriod - 1) + loss) / RsiPeriod;
var rsi = ToRsi(_averageGain, _averageLoss);
if (_previousRsi is decimal previous && IsFormedAndOnlineAndAllowTrading())
{
var position = Position;
if (previous < BuyLevel && rsi > BuyLevel && position <= 0m)
{
if (position < 0m)
BuyMarket(-position);
BuyMarket(OrderVolume);
}
else if (previous > SellLevel && rsi < SellLevel && position >= 0m)
{
if (position > 0m)
SellMarket(position);
SellMarket(OrderVolume);
}
}
_previousRsi = rsi;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal, Convert
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class cm_rsi_strategy(Strategy):
"""Wilder RSI on completed candle opens, with volume and price-step protection."""
def __init__(self):
super(cm_rsi_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14).SetDisplay("RSI Period", "RSI calculation period", "Indicators")
self._buy_level = self.Param("BuyLevel", 30.0).SetDisplay("Buy Level", "RSI level to enter long", "Indicators")
self._sell_level = self.Param("SellLevel", 70.0).SetDisplay("Sell Level", "RSI level to enter short", "Indicators")
self._take_profit = self.Param("TakeProfit", 200).SetDisplay("Take Profit", "Take profit in price steps", "Risk Management")
self._stop_loss = self.Param("StopLoss", 100).SetDisplay("Stop Loss", "Stop loss in price steps", "Risk Management")
self._order_volume = self.Param("OrderVolume", 0.1).SetDisplay("Order Volume", "Volume of each trade", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Type of candles", "General")
self._reset_rsi()
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def BuyLevel(self):
return self._buy_level.Value
@BuyLevel.setter
def BuyLevel(self, value):
self._buy_level.Value = value
@property
def SellLevel(self):
return self._sell_level.Value
@SellLevel.setter
def SellLevel(self, value):
self._sell_level.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def OrderVolume(self):
return self._order_volume.Value
@OrderVolume.setter
def OrderVolume(self, value):
self._order_volume.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _reset_rsi(self):
zero = Decimal(0)
self._previous_open = None
self._previous_rsi = None
self._sum_gain = self._sum_loss = zero
self._average_gain = self._average_loss = zero
self._changes = 0
def OnReseted(self):
super(cm_rsi_strategy, self).OnReseted()
self._reset_rsi()
def OnStarted2(self, time):
super(cm_rsi_strategy, self).OnStarted2(time)
self._reset_rsi()
step = self.Security.PriceStep
if step <= 0:
raise ValueError("Cm RSI requires a positive Security.PriceStep for point-based stops")
self.SubscribeCandles(self.CandleType).Bind(self.process_candle).Start()
self.StartProtection(
takeProfit=Unit(Decimal(self.TakeProfit) * step, UnitTypes.Absolute) if self.TakeProfit > 0 else Unit(),
stopLoss=Unit(Decimal(self.StopLoss) * step, UnitTypes.Absolute) if self.StopLoss > 0 else Unit(),
)
@staticmethod
def _to_rsi(avg_gain, avg_loss):
zero = Decimal(0)
if avg_loss == zero:
return Decimal(50) if avg_gain == zero else Decimal(100)
if avg_gain == zero:
return zero
return Decimal(100) * avg_gain / (avg_gain + avg_loss)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
price = candle.OpenPrice
if self._previous_open is None:
self._previous_open = price
return
change = price - self._previous_open
self._previous_open = price
gain = max(change, Decimal(0))
loss = max(-change, Decimal(0))
period = int(self.RsiPeriod)
if period <= 0:
raise ValueError('RsiPeriod must be positive')
if self._changes < period:
self._sum_gain += gain
self._sum_loss += loss
self._changes += 1
if self._changes < period:
return
self._average_gain = self._sum_gain / Decimal(period)
self._average_loss = self._sum_loss / Decimal(period)
self._previous_rsi = self._to_rsi(self._average_gain, self._average_loss)
return
self._average_gain = (self._average_gain * Decimal(period - 1) + gain) / Decimal(period)
self._average_loss = (self._average_loss * Decimal(period - 1) + loss) / Decimal(period)
rsi = self._to_rsi(self._average_gain, self._average_loss)
if self._previous_rsi is not None and self.IsFormedAndOnlineAndAllowTrading():
position = self.Position
if self._previous_rsi < Convert.ToDecimal(self.BuyLevel) and rsi > Convert.ToDecimal(self.BuyLevel) and position <= 0:
if position < 0:
self.BuyMarket(-position)
self.BuyMarket(Convert.ToDecimal(self.OrderVolume))
elif self._previous_rsi > Convert.ToDecimal(self.SellLevel) and rsi < Convert.ToDecimal(self.SellLevel) and position >= 0:
if position > 0:
self.SellMarket(position)
self.SellMarket(Convert.ToDecimal(self.OrderVolume))
self._previous_rsi = rsi
def CreateClone(self):
return cm_rsi_strategy()