Открыть на GitHub

Стратегия Color Schaff JJRSX MMRec Duplex

Обзор

Эта стратегия представляет собой порт MetaTrader-советника Exp_ColorSchaffJJRSXTrendCycle_MMRec_Duplex на платформу StockSharp. Исходный робот сочетает два осциллятора Schaff Trend Cycle на основе JJRSX и модуль MMRec (Money Management Recalculation), уменьшающий объём после серии убыточных сделок. В C# версии сохранена дуальная структура (лонг/шорт) и гибкие настройки управления риском, а недоступный индикатор JJRSX заменён надёжной аппроксимацией на базе встроенных инструментов.

Торговая логика

  • Рассчитываются два независимых осциллятора на выбранных таймфреймах: первый управляет длинными позициями, второй — короткими. Каждый осциллятор использует «быстрые» и «медленные» RSX-подобные линии, которые сглаживаются и нормализуются по методике Schaff Trend Cycle, формируя значения в диапазоне [-100; 100].
  • Лонг открывается, когда «длинный» осциллятор пересекает уровень 0 сверху вниз (previous > 0, current <= 0). В оригинале такие моменты трактуются как разворот в пользу покупателей. Закрытие длинной позиции происходит, если значение индикатора на один бар назад отрицательное.
  • Шорт открывается при пересечении нулевой линии снизу вверх (previous < 0, current >= 0). Закрытие шорта выполняется, когда значение индикатора на один бар назад становится положительным.
  • Параметр SignalBar воспроизводит MQL-поведение, позволяя оценивать сигналы по уже закрывшимся барам. Например, SignalBar = 1 анализирует предыдущую свечу и свечу перед ней. Для имитации вызовов CopyBuffer стратегия ведёт скользящую историю значений индикатора.

Управление капиталом (MMRec)

  • Для длинных и коротких сделок поддерживаются отдельные блоки MMRec. Базовый объём равен Strategy.Volume * MM, где MM — нормальный множитель (LongMm/ShortMm).
  • После закрытия каждой сделки стратегия фиксирует результат (по ценам закрытия свечей, аналогично тому, как EA анализирует историю через HistorySelect).
  • Если среди последних TotalTrigger сделок число убыточных достигает LossTrigger, следующий ордер для этого направления использует пониженный множитель (SmallMm). Когда условие перестаёт выполняться, возвращается основной множитель.
  • При развороте позиции (лонг→шорт или шорт→лонг) сперва рассчитывается итог предыдущей сделки и обновляются счётчики убытков, затем определяется объём нового ордера.

Аппроксимация индикатора

Оригинальный советник опирается на кастомный индикатор ColorSchaffJJRSXTrendCycle, построенный на JJRSX и алгоритмах Юрика. В StockSharp этих компонентов нет, поэтому реализован ColorSchaffJjrsxTrendCycleIndicator:

  • Лёгкий индикатор SimpleRsi вычисляет базовую RSX-динамику с экспоненциальным сглаживанием по заданному периоду.
  • Разность «быстрой» и «медленной» RSI-серий формирует MACD-подобный поток, который нормализуется в скользящем окне и дважды сглаживается с коэффициентом 0.5, имитируя логику Schaff Trend Cycle.
  • Индикатор поддерживает все типы цен (close, open, high, low, median, typical, weighted и т.д.), а также параметры цикла и периодов, что позволяет использовать оптимизацию аналогично оригиналу.

Параметры

Группа Параметр Описание
Long LongCandleType Тип свечей/таймфрейм для «длинного» индикатора.
Long LongTotalTrigger Количество последних длинных сделок, участвующих в анализе MMRec.
Long LongLossTrigger Число убытков в окне TotalTrigger, активирующее пониженный множитель.
Long LongSmallMm Пониженный множитель объёма после серии убытков.
Long LongMm Базовый множитель объёма для лонгов.
Long LongEnableOpen Разрешение на открытие длинных позиций.
Long LongEnableClose Разрешение на закрытие длинных позиций.
Long LongFastLength Аппроксимация «быстрого» периода JJRSX.
Long LongSlowLength Аппроксимация «медленного» периода JJRSX.
Long LongSmooth Длина экспоненциального сглаживания до нормализации.
Long LongCycleLength Размер окна для нормализации min/max.
Long LongSignalBar Сдвиг по истории при анализе сигналов.
Long LongAppliedPrice Тип цены для индикатора.
Short ShortCandleType Тип свечей/таймфрейм для «короткого» индикатора.
Short ShortTotalTrigger Количество последних коротких сделок в анализе MMRec.
Short ShortLossTrigger Число убытков, активирующее пониженный множитель для шортов.
Short ShortSmallMm Пониженный множитель объёма после серии убыточных шортов.
Short ShortMm Базовый множитель объёма для шортов.
Short ShortEnableOpen Разрешение на открытие коротких позиций.
Short ShortEnableClose Разрешение на закрытие коротких позиций.
Short ShortFastLength Аппроксимация «быстрого» периода JJRSX для шортов.
Short ShortSlowLength Аппроксимация «медленного» периода JJRSX для шортов.
Short ShortSmooth Сглаживание перед нормализацией для шортов.
Short ShortCycleLength Окно min/max для короткой стороны.
Short ShortSignalBar Сдвиг по истории при анализе коротких сигналов.
Short ShortAppliedPrice Тип цены для «короткого» индикатора.

Особенности реализации

  • Используются высокоуровневые подписки на свечи и индикаторы StockSharp без прямого доступа к буферам, что соответствует правилам конверсии.
  • Параметры StopLoss/TakeProfit из MQL не перенесены: они заданы в пунктах, поэтому в StockSharp их стоит реализовать через StartProtection либо внешние модули риска.
  • Результаты сделок оцениваются по ценам закрытия свечей, что делает поведение детерминированным и близким к MQL-реализации, которая анализирует историю сделок.
  • Кастомный индикатор публикует флаг IsFormed, поэтому торговые решения принимаются только после накопления достаточного количества данных.

Предупреждение

Несмотря на точное воспроизведение логики, результаты могут отличаться из-за других поставщиков данных, особенностей исполнения и используемой аппроксимации JJRSX. Перед реальной торговлей протестируйте стратегию на демо-счёте.

using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

public class ColorSchaffJjrsxMmrecDuplexStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private decimal? _prevFast, _prevSlow;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }

	public ColorSchaffJjrsxMmrecDuplexStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_fastPeriod = Param(nameof(FastPeriod), 10).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 30).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow period", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevSlow = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = null; _prevSlow = null;
		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, slow, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fast); DrawIndicator(area, slow); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!IsFormedAndOnlineAndAllowTrading()) { _prevFast = fast; _prevSlow = slow; return; }
		if (_prevFast == null || _prevSlow == null) { _prevFast = fast; _prevSlow = slow; return; }
		var prevAbove = _prevFast.Value > _prevSlow.Value;
		var currAbove = fast > slow;
		_prevFast = fast; _prevSlow = slow;
		if (!prevAbove && currAbove && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
		else if (prevAbove && !currAbove && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
	}
}