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 中的
AvgBody与CloseAvg函数,一个对实体长度求平均,另一个跟踪收盘价趋势,避免在无序波动中触发信号。
交易规则
多头条件
- 最近两根完成 K 线满足 Piercing Line 形态。
- 上一根 K 线的 RSI 小于 40。
- 满足条件后按市价买入;若当前持有空头,则按“绝对仓位 + Volume”反手做多。
空头条件
- 最近两根完成 K 线满足 Dark Cloud Cover 形态。
- 上一根 K 线的 RSI 大于 60。
- 满足条件后按市价卖出;若当前持有多头,则按相同规则反手做空。
平仓条件
- 当持有多头且 RSI 从 70 上方跌破或从 30 下方回升时,视为动量衰减,立即平仓。
- 当持有空头且 RSI 从 30 下方上穿或从 70 上方回落时,立即平仓。
参数
| 名称 | 默认值 | 说明 |
|---|---|---|
RsiPeriod |
20 | RSI 计算周期,可在 10–40 之间、步长 5 进行优化。 |
BodyAveragePeriod |
14 | 蜡烛实体平均及收盘价趋势的周期,可在 10–30 之间优化。 |
CandleType |
1 小时时间框架 | 计算所用的蜡烛类型,可选择任意 StockSharp 支持的类型。 |
Volume(基类属性) |
— | 交易量需在启动前设置,决定单笔订单的基础数量。 |
使用步骤
- 在 StockSharp Designer、Shell 或 Runner 中绑定策略到目标证券与投资组合。
- 根据品种流动性配置合适的蜡烛类型与下单数量。
- 如有需要,可调整 RSI 与平均周期,或运行优化器寻找更优组合。
- 启动策略后,通过叠加的蜡烛图、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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class cdc_pl_rsi_strategy(Strategy):
def __init__(self):
super(cdc_pl_rsi_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "RSI period", "Indicators")
self._oversold_level = self.Param("OversoldLevel", 40.0) \
.SetDisplay("Oversold Level", "RSI below this for long entry", "Signals")
self._overbought_level = self.Param("OverboughtLevel", 60.0) \
.SetDisplay("Overbought Level", "RSI above this for short entry", "Signals")
self._signal_cooldown = self.Param("SignalCooldownCandles", 6) \
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading")
self._rsi = None
self._candles = []
self._has_prev_rsi = False
self._candles_since_trade = 0
@property
def rsi_period(self):
return self._rsi_period.Value
@property
def oversold_level(self):
return self._oversold_level.Value
@property
def overbought_level(self):
return self._overbought_level.Value
@property
def signal_cooldown(self):
return self._signal_cooldown.Value
def OnReseted(self):
super(cdc_pl_rsi_strategy, self).OnReseted()
self._rsi = None
self._candles = []
self._has_prev_rsi = False
self._candles_since_trade = self.signal_cooldown
def OnStarted2(self, time):
super(cdc_pl_rsi_strategy, self).OnStarted2(time)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.rsi_period
self._candles = []
self._has_prev_rsi = False
self._candles_since_trade = self.signal_cooldown
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._rsi, self._process_candle)
subscription.Start()
self.StartProtection(takeProfit=Unit(2, UnitTypes.Percent), stopLoss=Unit(1, UnitTypes.Percent), useMarketOrders=True)
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
if not self._rsi.IsFormed:
return
rsi_val = float(rsi_value)
if self._candles_since_trade < self.signal_cooldown:
self._candles_since_trade += 1
self._candles.append(candle)
if len(self._candles) > 5:
self._candles.pop(0)
if len(self._candles) >= 2 and self._has_prev_rsi:
curr = self._candles[-1]
prev = self._candles[-2]
is_piercing = (float(prev.OpenPrice) > float(prev.ClosePrice)
and float(curr.ClosePrice) > float(curr.OpenPrice)
and float(curr.OpenPrice) < float(prev.LowPrice)
and float(curr.ClosePrice) > (float(prev.OpenPrice) + float(prev.ClosePrice)) / 2.0)
is_dark_cloud = (float(prev.ClosePrice) > float(prev.OpenPrice)
and float(curr.OpenPrice) > float(curr.ClosePrice)
and float(curr.OpenPrice) > float(prev.HighPrice)
and float(curr.ClosePrice) < (float(prev.OpenPrice) + float(prev.ClosePrice)) / 2.0)
if is_piercing and rsi_val < self.oversold_level and self.Position == 0 and self._candles_since_trade >= self.signal_cooldown:
self.BuyMarket()
self._candles_since_trade = 0
elif is_dark_cloud and rsi_val > self.overbought_level and self.Position == 0 and self._candles_since_trade >= self.signal_cooldown:
self.SellMarket()
self._candles_since_trade = 0
self._has_prev_rsi = True
def CreateClone(self):
return cdc_pl_rsi_strategy()