在 GitHub 上查看

cm RSI 策略

概述

该策略是 MetaTrader 4 专家顾问 "cm_RSI" 的移植版本。它利用相对强弱指数 (RSI) 来捕捉动量反转。

算法使用K线开盘价计算 RSI。当 RSI 在低于买入阈值后向上突破该阈值时,开多头仓位;当 RSI 在高于卖出阈值后向下跌破该阈值时,开空头仓位。每笔交易均使用固定的止盈和止损点数进行保护。

策略逻辑

  1. 使用用户设定的周期,通过开盘价计算 RSI。
  2. 如果前一个 RSI 值低于买入水平且当前值从下向上突破该水平,则按市价买入。
  3. 如果前一个 RSI 值高于卖出水平且当前值从上向下跌破该水平,则按市价卖出。
  4. 每笔交易使用相同的成交量,并附带止损和止盈。

参数

名称 说明
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;
	}
}