CDC PL RSI Strategy переносит эксперта MetaTrader Expert_ADC_PL_RSI на платформу StockSharp. Алгоритм анализирует завершённые свечи, выявляет свечные разворотные модели и подтверждает сигналы индикатором RSI. Длинные позиции открываются после модели «Пробой облака тьмы» (Piercing Line) в сочетании с перепроданностью RSI, короткие — после «Покрывающей тёмное облако» (Dark Cloud Cover) в зоне перекупленности. Управление капиталом оставлено базовому механизму StockSharp: объём задаётся свойством Volume стратегии.
Логика индикаторов и фильтров
Свечные модели. Поведение полностью повторяет исходный MQL‑код: используются две последние завершённые свечи, проверяются разрывы цен, длина тел относительно скользящего среднего и направление тренда.
RSI. Осциллятор с периодом 20 (значение можно оптимизировать) служит для подтверждения импульса. Для лонга требуется RSI < 40, для шорта — RSI > 60. История значений дополнительно используется для фиксации позиции при обратных пересечениях уровней 30 и 70.
Среднее тело и тренд. Два индикатора SMA дублируют функции AvgBody и CloseAvg из MQL: первый усредняет длину тел, второй оценивает тренд закрытий. Это позволяет фильтровать шумовые свечи и гарантировать появление моделей после направленного движения.
Торговые правила
Вход в лонг
Найти модель Piercing Line на двух последних свечах.
Убедиться, что RSI предыдущей свечи ниже 40.
Открыть покупку по рынку. При наличии короткой позиции стратегия переворачивается, покупая сумму текущего объёма и абсолютной величины позиции.
Вход в шорт
Найти модель Dark Cloud Cover на двух последних свечах.
Проверить, что RSI предыдущей свечи выше 60.
Открыть продажу по рынку. При наличии длинной позиции выполняется разворот с тем же объёмным правилом.
Выход
Закрывать лонг, когда RSI опускается ниже 70 или поднимается выше 30, сигнализируя об окончании импульса.
Закрывать шорт, когда RSI поднимается выше 30 или опускается ниже 70, что соответствует логике оригинального эксперта.
Параметры
Имя
Значение по умолчанию
Описание
RsiPeriod
20
Период RSI. Допускается оптимизация в диапазоне 10–40 с шагом 5.
BodyAveragePeriod
14
Период сглаживания длины тел и трендового фильтра по закрытиям. Оптимизируется в диапазоне 10–30 с шагом 5.
CandleType
Таймфрейм 1 час
Тип свечей для расчёта. Можно выбрать любой поддерживаемый формат StockSharp.
Volume (устанавливается пользователем)
—
Базовый объём заявки, задаётся перед запуском стратегии.
Использование
Привяжите стратегию к нужным портфелю и инструменту в Designer, Shell или Runner.
Настройте таймфрейм свечей и объём сделок под специфику инструмента.
При необходимости измените периоды RSI и скользящей средней или запустите оптимизацию.
Запустите стратегию и контролируйте сигналы через наложенные на график свечи, линию RSI и линию среднего закрытия.
Дополнительно
Метод StartProtection() активирован, поэтому можно задействовать стандартные защитные механизмы (стоп-лосс, тейк-профит, трейлинг и т.п.).
Обрабатываются только полностью сформированные свечи — логика совпадает с реализацией в MetaTrader.
Хранение больших коллекций не требуется: все расчёты выполняются индикаторами, что соответствует требованиям конвертации.
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()