Стратегия повторяет логику советника MetaTrader 5 Exp_RJTX_Matches_Smoothed_Duplex.mq5. Два независимых блока RJTX анализируют сглаженные ряды цен открытия и закрытия на выбранных таймфреймах. Каждый законченный бар классифицируется как бычий или медвежий в зависимости от того, превысило ли сглаженное закрытие сглаженное открытие Period баров назад. Бычьи «спички» активируют длинный модуль, а медвежьи управляют коротким модулем.
Формирование сигналов
Сглаживание – оба блока подают цены открытия и закрытия в выбранный метод сглаживания. Для открытия и закрытия создаются отдельные экземпляры индикаторов, чтобы избежать пересечения буферов.
Сравнение – после накопления достаточной истории текущее сглаженное закрытие сравнивается со сглаженным открытием, зафиксированным Period баров назад.
Определение спички – если закрытие выше, бар считается бычьим, иначе – медвежьим. Сигналы оцениваются после сдвига на SignalBar закрытых свечей, как и при чтении буферов в MT5.
Управление позицией
Длинный блок открывает лонг (при необходимости закрывая шорт) при появлении бычьей спички в окне оценки. Медвежья спичка закрывает длинную позицию, если включено разрешение на выход.
Короткий блок действует зеркально: медвежья спичка открывает шорт (с закрытием лонга при разрешении), а бычья закрывает его.
В StockSharp стратегия работает в неттинговом режиме. Поэтому при появлении противоположного сигнала текущая позиция закрывается перед открытием новой, вместо одновременного ведения двух хеджированных позиций, как в MT5. Чтобы запретить автоматическое закрытие противоположной позиции, отключите соответствующий параметр Allow ... Close.
Управление рисками
Стоп-лосс и тейк-профит задаются в шагах цены (PriceStep × points). Для каждого закрытого бара стратегия проверяет, достиг ли диапазон свечи уровней стопа или профита, и сразу закрывает позицию при срабатывании. Это имитирует защитные ордера MT5 без необходимости регистрировать их на стороне брокера.
Параметры
Раздел
Параметр
Значение по умолчанию
Описание
Long
LongCandleType
H4
Таймфрейм, используемый длинным блоком RJTX.
Long
LongVolume
0.1
Объём открытия длинной позиции.
Long
LongAllowOpen
true
Разрешить открытие лонгов.
Long
LongAllowClose
true
Разрешить закрытие лонгов по медвежьим спичкам.
Long
LongStopLossPoints
1000
Расстояние стоп-лосса в шагах цены (0 отключает проверку).
Long
LongTakeProfitPoints
2000
Расстояние тейк-профита в шагах цены (0 отключает проверку).
Long
LongSignalBar
1
Сдвиг при чтении буферов RJTX (0 = текущая закрытая свеча).
Long
LongPeriod
10
Количество баров между текущим сглаженным закрытием и историческим сглаженным открытием.
Long
LongMethod
Sma
Метод сглаживания (Sma, Ema, Smma, Lwma, Jjma, Jurx, Parma, T3, Vidya, Ama).
Long
LongLength
12
Длина фильтра сглаживания для рядов открытия/закрытия.
Long
LongPhase
15
Параметр фазы для фильтров Jurik (оставлен для совместимости).
Short
ShortCandleType
H4
Таймфрейм, используемый коротким блоком RJTX.
Short
ShortVolume
0.1
Объём открытия короткой позиции.
Short
ShortAllowOpen
true
Разрешить открытие шортов.
Short
ShortAllowClose
true
Разрешить закрытие шортов по бычьим спичкам.
Short
ShortStopLossPoints
1000
Расстояние стоп-лосса для шортов в шагах цены (0 отключает проверку).
Short
ShortTakeProfitPoints
2000
Расстояние тейк-профита для шортов в шагах цены (0 отключает проверку).
Short
ShortSignalBar
1
Сдвиг при чтении буферов RJTX в коротком блоке.
Short
ShortPeriod
10
Количество баров между текущим сглаженным закрытием и историческим сглаженным открытием.
Short
ShortMethod
Sma
Метод сглаживания в коротком блоке.
Short
ShortLength
12
Длина фильтра сглаживания для коротких сигналов.
Short
ShortPhase
15
Параметр фазы для фильтров Jurik в коротком блоке.
Примечания
Jjma соответствует Jurik Moving Average. Методы Jurx, Parma и Vidya аппроксимируются Zero-Lag EMA, Arnaud Legoux MA и EMA соответственно, поскольку в StockSharp нет точных аналогов из библиотеки SmoothAlgorithms.
Проверка стоп-лосса и тейк-профита выполняется по экстремумам свечи. Внутрибарачные движения, не попавшие в диапазон high/low, не приведут к срабатыванию.
Обработка сигналов происходит только на завершённых свечах, что соответствует проверке IsNewBar в оригинальном советнике.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Exp RJTX Matches Smoothed Duplex strategy using SmoothedMA crossover.
/// Buys when fast SmoothedMA crosses above slow SmoothedMA.
/// Sells on reverse crossover.
/// </summary>
public class ExpRjtxMatchesSmoothedDuplexStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private SmoothedMovingAverage _fast;
private SmoothedMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
/// <summary>
/// Fast smoothed MA period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow smoothed MA period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ExpRjtxMatchesSmoothedDuplexStrategy"/> class.
/// </summary>
public ExpRjtxMatchesSmoothedDuplexStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast smoothed MA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow smoothed MA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fast = null;
_slow = null;
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new SmoothedMovingAverage { Length = FastPeriod };
_slow = new SmoothedMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fast.IsFormed || !_slow.IsFormed)
{
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
// Check SL/TP
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
// SmoothedMA crossover
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 80;
}
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = close;
_cooldown = 80;
}
_prevFast = fastValue;
_prevSlow = slowValue;
}
}
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
from StockSharp.Algo.Indicators import SmoothedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class exp_rjtx_matches_smoothed_duplex_strategy(Strategy):
"""
SmoothedMA crossover strategy with SL/TP and cooldown.
Buys when fast SmoothedMA crosses above slow, sells on reverse.
"""
def __init__(self):
super(exp_rjtx_matches_smoothed_duplex_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 12) \
.SetDisplay("Fast Period", "Fast smoothed MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow smoothed MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(exp_rjtx_matches_smoothed_duplex_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(exp_rjtx_matches_smoothed_duplex_strategy, self).OnStarted2(time)
fast = SmoothedMovingAverage()
fast.Length = self._fast_period.Value
slow = SmoothedMovingAverage()
slow.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self._process_candle).Start()
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_fast = float(fast_val)
self._prev_slow = float(slow_val)
return
fast_val = float(fast_val)
slow_val = float(slow_val)
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step <= 0:
step = 1.0
sl_pts = self._stop_loss_points.Value
tp_pts = self._take_profit_points.Value
if self.Position > 0 and self._entry_price > 0:
if sl_pts > 0 and close <= self._entry_price - sl_pts * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if tp_pts > 0 and close >= self._entry_price + tp_pts * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if sl_pts > 0 and close >= self._entry_price + sl_pts * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if tp_pts > 0 and close <= self._entry_price - tp_pts * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 80
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return exp_rjtx_matches_smoothed_duplex_strategy()