在 GitHub 上查看

CDC PL RSI 策略

概述

CDC PL RSI Strategy 将 MetaTrader 专家顾问 Expert_ADC_PL_RSI 移植到 StockSharp 平台。策略仅分析已完成的 K 线,识别日本蜡烛图反转形态,并结合 RSI 指标进行确认。当 RSI 处于超卖区 (RSI < 40) 且出现 Piercing Line 形态时开多;当 RSI 处于超买区 (RSI > 60) 且出现 Dark Cloud Cover 形态时开空。仓位大小沿用 StockSharp 的 Volume 设置,保持原策略的固定手数思想。

指标与过滤器

  • 蜡烛图形态:完全复刻 MQL 逻辑。策略读取最近两根完成的 K 线,检查跳空位置、实体长度与自适应平均值的关系,以及趋势方向。
  • RSI 过滤:默认周期为 20,可在 10–40 之间优化。RSI 值用于确认动量,并在穿越 30 或 70 水平时触发平仓,与原始 EA 一致。
  • 实体平均与趋势线:两个简单移动平均分别对应 MQL 中的 AvgBodyCloseAvg 函数,一个对实体长度求平均,另一个跟踪收盘价趋势,避免在无序波动中触发信号。

交易规则

多头条件

  1. 最近两根完成 K 线满足 Piercing Line 形态。
  2. 上一根 K 线的 RSI 小于 40。
  3. 满足条件后按市价买入;若当前持有空头,则按“绝对仓位 + Volume”反手做多。

空头条件

  1. 最近两根完成 K 线满足 Dark Cloud Cover 形态。
  2. 上一根 K 线的 RSI 大于 60。
  3. 满足条件后按市价卖出;若当前持有多头,则按相同规则反手做空。

平仓条件

  • 当持有多头且 RSI 从 70 上方跌破或从 30 下方回升时,视为动量衰减,立即平仓。
  • 当持有空头且 RSI 从 30 下方上穿或从 70 上方回落时,立即平仓。

参数

名称 默认值 说明
RsiPeriod 20 RSI 计算周期,可在 10–40 之间、步长 5 进行优化。
BodyAveragePeriod 14 蜡烛实体平均及收盘价趋势的周期,可在 10–30 之间优化。
CandleType 1 小时时间框架 计算所用的蜡烛类型,可选择任意 StockSharp 支持的类型。
Volume(基类属性) 交易量需在启动前设置,决定单笔订单的基础数量。

使用步骤

  1. 在 StockSharp Designer、Shell 或 Runner 中绑定策略到目标证券与投资组合。
  2. 根据品种流动性配置合适的蜡烛类型与下单数量。
  3. 如有需要,可调整 RSI 与平均周期,或运行优化器寻找更优组合。
  4. 启动策略后,通过叠加的蜡烛图、RSI 曲线以及平均收盘价线监控信号与成交。

其他说明

  • 策略调用了 StartProtection(),可在平台侧启用止损、止盈、移动止损等保护措施。
  • 仅处理完成的 K 线,确保与原始 MQL 策略的节奏一致。
  • 不额外保存历史集合,所有滑动窗口计算交由指标完成,符合仓库的转换要求。
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// CDC PL RSI strategy: Dark Cloud Cover and Piercing Line candlestick patterns
/// confirmed by RSI levels.
/// </summary>
public class CdcPlRsiStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _oversoldLevel;
	private readonly StrategyParam<decimal> _overboughtLevel;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private readonly List<ICandleMessage> _candles = new();
	private decimal _prevRsi;
	private bool _hasPrevRsi;
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public decimal OversoldLevel { get => _oversoldLevel.Value; set => _oversoldLevel.Value = value; }
	public decimal OverboughtLevel { get => _overboughtLevel.Value; set => _overboughtLevel.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public CdcPlRsiStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period", "Indicators");
		_oversoldLevel = Param(nameof(OversoldLevel), 40m)
			.SetDisplay("Oversold Level", "RSI below this for long entry", "Signals");
		_overboughtLevel = Param(nameof(OverboughtLevel), 60m)
			.SetDisplay("Overbought Level", "RSI above this for short entry", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 6)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_candles.Clear();
		_prevRsi = 0m;
		_hasPrevRsi = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_candles.Clear();
		_hasPrevRsi = false;
		_candlesSinceTrade = SignalCooldownCandles;
		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(rsi, ProcessCandle).Start();

		StartProtection(
			takeProfit: new Unit(2, UnitTypes.Percent),
			stopLoss: new Unit(1, UnitTypes.Percent)
		);
	}

	private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
	{
		if (candle.State != CandleStates.Finished) return;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		_candles.Add(candle);
		if (_candles.Count > 5)
			_candles.RemoveAt(0);

		if (_candles.Count >= 2 && _hasPrevRsi)
		{
			var curr = _candles[^1];
			var prev = _candles[^2];

			// Piercing Line
			var isPiercing = prev.OpenPrice > prev.ClosePrice
				&& curr.ClosePrice > curr.OpenPrice
				&& curr.OpenPrice < prev.LowPrice
				&& curr.ClosePrice > (prev.OpenPrice + prev.ClosePrice) / 2m;

			// Dark Cloud Cover
			var isDarkCloud = prev.ClosePrice > prev.OpenPrice
				&& curr.OpenPrice > curr.ClosePrice
				&& curr.OpenPrice > prev.HighPrice
				&& curr.ClosePrice < (prev.OpenPrice + prev.ClosePrice) / 2m;

			if (isPiercing && rsiValue < OversoldLevel && Position == 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (isDarkCloud && rsiValue > OverboughtLevel && Position == 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevRsi = rsiValue;
		_hasPrevRsi = true;
	}
}