在 GitHub 上查看

Larry Connors RSI-2 策略

该策略是经典 Larry Connors RSI-2 系统的 StockSharp 移植版本。它在小时线级别结合 2 周期 RSI、5 周期和 200 周期简单移动平均线,通过均值回归思路捕捉短期反弹,同时顺应长期趋势。可选的止损、止盈参数以点(pip)为单位,实现与 MetaTrader 原版一致的资金管理。

策略概览

  • 类型:带趋势过滤的均值回归。
  • 市场:面向外汇货币对,默认使用 H1 K 线。
  • 方向:支持做多与做空,但需符合慢速均线方向。
  • 指标组合:SMA(5) 负责离场,SMA(200) 负责趋势过滤,RSI(2) 负责触发信号。

交易规则

做多条件

  • RSI 数值低于 RSI Long Entry 阈值(默认 6)。
  • 蜡烛线收盘价位于 Slow SMA 上方。
  • 当前没有持仓。

做空条件

  • RSI 数值高于 RSI Short Entry 阈值(默认 95)。
  • 收盘价低于 Slow SMA
  • 当前为空仓。

平仓逻辑

  • 多头:当收盘价上穿 Fast SMA(默认 5)时平仓,可选的止损/止盈触发时同样平仓。
  • 空头:当收盘价下穿 Fast SMA 时平仓,止损/止盈同样适用。

风险控制

  • Use Stop Loss 控制是否启用按点数计算的固定止损。
  • Use Take Profit 控制是否启用对称的固定止盈。
  • 点数通过品种的 PriceStep 与小数位数自动换算成价格,兼容 4 位和 5 位报价模型。

默认参数

参数 默认值 说明
Trade Volume 1 每次进场的下单量。
Fast SMA Period 5 快速均线长度。
Slow SMA Period 200 趋势过滤均线长度。
RSI Period 2 RSI 观察周期。
RSI Long Entry 6 做多触发阈值。
RSI Short Entry 95 做空触发阈值。
Use Stop Loss true 是否启用止损。
Stop Loss (pips) 30 止损距离(点)。
Use Take Profit true 是否启用止盈。
Take Profit (pips) 60 止盈距离(点)。
Candle Type 1 小时 使用的 K 线类型。

上述关键参数全部支持 .SetCanOptimize(true),便于在 Designer 或 Tester 中批量优化。

实施细节

  • 信号在蜡烛线收盘后计算,确保与 MetaTrader 版本保持一致。
  • 策略内部跟踪入场价,并在达到止损或止盈时使用市价单全部平仓。
  • 每次启动都会重置点值和入场缓存,保证回测可重复。
  • 建议使用高质量的外汇历史数据,以评估策略在不同货币对上的表现。

使用建议

  1. 连接能够提供 1 小时 K 线的外汇数据源。
  2. 在 StockSharp Designer 中加载策略,或通过 API 直接运行。
  3. 根据经纪商合约细则调整止损/止盈点数。
  4. 如需迁移到其他品种,可优化 RSI 阈值与均线周期。

该实现完整复刻了 Larry Connors RSI-2 的核心思想,方便在 StockSharp 平台中与其他策略组件组合或比较。

using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Larry Connors RSI-2 strategy with a 200-period SMA filter and optional stop management.
/// </summary>
public class LarryConnersRsi2Strategy : Strategy
{
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<int> _fastSmaPeriod;
	private readonly StrategyParam<int> _slowSmaPeriod;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiLongEntry;
	private readonly StrategyParam<decimal> _rsiShortEntry;
	private readonly StrategyParam<bool> _useStopLoss;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<bool> _useTakeProfit;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _pipSize;
	private decimal? _longEntryPrice;
	private decimal? _shortEntryPrice;

	/// <summary>
	/// Order volume for entries.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set
		{
			_tradeVolume.Value = value;
			Volume = value;
		}
	}

	/// <summary>
	/// Fast SMA period used for timing exits.
	/// </summary>
	public int FastSmaPeriod
	{
		get => _fastSmaPeriod.Value;
		set => _fastSmaPeriod.Value = value;
	}

	/// <summary>
	/// Slow SMA period used as a trend filter.
	/// </summary>
	public int SlowSmaPeriod
	{
		get => _slowSmaPeriod.Value;
		set => _slowSmaPeriod.Value = value;
	}

	/// <summary>
	/// RSI lookback length.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// RSI threshold for long entries.
	/// </summary>
	public decimal RsiLongEntry
	{
		get => _rsiLongEntry.Value;
		set => _rsiLongEntry.Value = value;
	}

	/// <summary>
	/// RSI threshold for short entries.
	/// </summary>
	public decimal RsiShortEntry
	{
		get => _rsiShortEntry.Value;
		set => _rsiShortEntry.Value = value;
	}

	/// <summary>
	/// Enables stop-loss handling in price pips.
	/// </summary>
	public bool UseStopLoss
	{
		get => _useStopLoss.Value;
		set => _useStopLoss.Value = value;
	}

	/// <summary>
	/// Stop-loss size expressed in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Enables take-profit handling in price pips.
	/// </summary>
	public bool UseTakeProfit
	{
		get => _useTakeProfit.Value;
		set => _useTakeProfit.Value = value;
	}

	/// <summary>
	/// Take-profit size expressed in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Timeframe used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes <see cref="LarryConnersRsi2Strategy"/>.
	/// </summary>
	public LarryConnersRsi2Strategy()
	{
		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Order volume", "Trading")
			;

		_fastSmaPeriod = Param(nameof(FastSmaPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Fast SMA Period", "Fast SMA length", "Indicators")
			;

		_slowSmaPeriod = Param(nameof(SlowSmaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Slow SMA Period", "Slow SMA length", "Indicators")
			;

		_rsiPeriod = Param(nameof(RsiPeriod), 2)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI lookback", "Indicators")
			;

		_rsiLongEntry = Param(nameof(RsiLongEntry), 6m)
			.SetDisplay("RSI Long Entry", "RSI threshold for longs", "Signals")
			;

		_rsiShortEntry = Param(nameof(RsiShortEntry), 95m)
			.SetDisplay("RSI Short Entry", "RSI threshold for shorts", "Signals")
			;

		_useStopLoss = Param(nameof(UseStopLoss), true)
			.SetDisplay("Use Stop Loss", "Enable stop-loss management", "Risk");

		_stopLossPips = Param(nameof(StopLossPips), 30m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk")
			;

		_useTakeProfit = Param(nameof(UseTakeProfit), true)
			.SetDisplay("Use Take Profit", "Enable take-profit management", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 60m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Risk")
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for candles", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		// Clear internal state between runs.
		_pipSize = 0m;
		_longEntryPrice = null;
		_shortEntryPrice = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		// Use configured trade volume for default order size.
		Volume = TradeVolume;

		// Pre-compute pip size multiplier for risk management calculations.
		var priceStep = Security?.PriceStep ?? 1m;
		var decimals = Security?.Decimals ?? 0;
		var pipMultiplier = decimals is 1 or 3 or 5 ? 10m : 1m;
		_pipSize = priceStep * pipMultiplier;
		if (_pipSize <= 0m)
			_pipSize = priceStep;

		// Prepare technical indicators.
		var fastSma = new SMA { Length = FastSmaPeriod };
		var slowSma = new SMA { Length = SlowSmaPeriod };
		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		// Subscribe to candles and bind indicators for combined processing.
		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastSma, slowSma, rsi, ProcessCandle)
			.Start();

		// Build optional chart visuals to monitor the strategy.
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fastSma);
			DrawIndicator(area, slowSma);
			DrawIndicator(area, rsi);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastSma, decimal slowSma, decimal rsi)
	{
		// Act only on fully formed candles to mimic MQL bar-close execution.
		if (candle.State != CandleStates.Finished)
			return;

		// Manage open long position exits before generating new signals.
		if (Position > 0)
		{
			if (UseStopLoss && _longEntryPrice.HasValue)
			{
				var stopPrice = _longEntryPrice.Value - StopLossPips * _pipSize;
				if (candle.LowPrice <= stopPrice)
				{
					SellMarket();
					ResetLongState();
					return;
				}
			}

			if (UseTakeProfit && _longEntryPrice.HasValue)
			{
				var targetPrice = _longEntryPrice.Value + TakeProfitPips * _pipSize;
				if (candle.HighPrice >= targetPrice)
				{
					SellMarket();
					ResetLongState();
					return;
				}
			}

			if (candle.ClosePrice > fastSma)
			{
				SellMarket();
				ResetLongState();
				return;
			}
		}
		else if (Position < 0)
		{
			if (UseStopLoss && _shortEntryPrice.HasValue)
			{
				var stopPrice = _shortEntryPrice.Value + StopLossPips * _pipSize;
				if (candle.HighPrice >= stopPrice)
				{
					BuyMarket();
					ResetShortState();
					return;
				}
			}

			if (UseTakeProfit && _shortEntryPrice.HasValue)
			{
				var targetPrice = _shortEntryPrice.Value - TakeProfitPips * _pipSize;
				if (candle.LowPrice <= targetPrice)
				{
					BuyMarket();
					ResetShortState();
					return;
				}
			}

			if (candle.ClosePrice < fastSma)
			{
				BuyMarket();
				ResetShortState();
				return;
			}
		}

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		// Generate new entries only when flat to match the MQL logic.
		if (Position == 0)
		{
			var canGoLong = rsi < RsiLongEntry && candle.ClosePrice > slowSma;
			if (canGoLong)
			{
				BuyMarket();
				_longEntryPrice = candle.ClosePrice;
				_shortEntryPrice = null;
				return;
			}

			var canGoShort = rsi > RsiShortEntry && candle.ClosePrice < slowSma;
			if (canGoShort)
			{
				SellMarket();
				_shortEntryPrice = candle.ClosePrice;
				_longEntryPrice = null;
			}
		}
	}

	private void ResetLongState()
	{
		// Drop long tracking data after an exit.
		_longEntryPrice = null;
	}

	private void ResetShortState()
	{
		// Drop short tracking data after an exit.
		_shortEntryPrice = null;
	}
}