Стратегия Color Schaff JCCX Trend Cycle MMRec Duplex
Обзор
Переносит двунаправленного эксперта "ColorSchaffJCCXTrendCycle_MMRec_Duplex" из MetaTrader в инфраструктуру StockSharp.
Использует две независимые цепочки индикатора Schaff Trend Cycle на основе скользящих средних Джурика для поиска бычьих и медвежьих разворотов.
Реализует упрощённый блок MMRec, уменьшающий объём после серии убыточных сделок.
Позволяет настраивать раздельные параметры для длинной и короткой частей, включая таймфреймы и источник цены.
Схема индикаторов
Аппроксимация JCCX – цена фильтруется скользящей средней Джурика, после чего разность с исходной ценой и её модуль сглаживаются ещё раз, формируя аналог исходного осциллятора JCCX.
Слой MACD – разница между быстрым и медленным JCCX задаёт базовый импульс.
Двойное стохастическое преобразование – скользящие окна минимума/максимума нормализуют импульс и формируют окончательное значение Schaff Trend Cycle в диапазоне -100..+100.
Фаза – параметр Phase управляет внутренним коэффициентом сглаживания (0.05–0.95), что имитирует поведение фазового параметра в оригинальном индикаторе.
Полный конвейер рассчитывается отдельно для длинного и короткого блоков, поэтому оба направления могут работать на разных типах свечей и ценах.
Торговая логика
Длинный блок
Вход: индикатор пересекает уровень 0 снизу вверх (текущее значение > 0, предыдущее задержанное ≤ 0). Перед открытием длинной позиции закрываются короткие сделки.
Выход: индикатор падает ниже 0 при включённой опции закрытия.
Стопы: по закрытию каждой свечи проверяются уровни стоп-лосса и тейк-профита (значения указываются в шагах цены).
Короткий блок
Вход: индикатор опускается ниже 0 (текущее значение < 0, задержанное ≥ 0). Перед открытием короткой позиции закрываются длинные.
Выход: индикатор поднимается выше 0 при разрешённом закрытии.
Стопы: симметричная проверка стоп-лосса и тейк-профита для коротких сделок.
Параметр SignalBar задаёт количество уже закрытых свечей, которые пропускаются перед оценкой сигнала. Значение 1 повторяет поведение оригинального эксперта, использующего предыдущую завершённую свечу.
Управление объёмом (MMRec)
Для длинной и короткой частей ведутся независимые очереди результатов последних сделок.
TotalTrigger ограничивает длину очереди – учитывается только N последних сделок.
LossTrigger определяет число убытков в очереди, после которого используется объём SmallVolume.
При отсутствии необходимого количества убытков применяется стандартный объём NormalVolume.
Параметры
Группа
Параметр
Описание
По умолчанию
Long
LongCandleType
Тип свечей для расчёта длинного блока.
8 часов
Long
LongFastLength
Быстрый период JCCX.
23
Long
LongSlowLength
Медленный период JCCX.
50
Long
LongSmoothLength
Период сглаживания числителя и знаменателя.
8
Long
LongPhase
Фазовый параметр, переводимый в коэффициент сглаживания.
100
Long
LongCycle
Длина окна для стохастических преобразований.
10
Long
LongSignalBar
Задержка (в барах) перед оценкой сигнала.
1
Long
LongAppliedPrice
Источник цены для длинного блока.
Close
Long
LongAllowOpen / LongAllowClose
Разрешение на открытие/закрытие длинных сделок.
true
Long
LongTotalTrigger
Количество последних длинных сделок в очереди MMRec.
5
Long
LongLossTrigger
Число убытков, переключающее объём на SmallVolume.
3
Long
LongSmallVolume / LongNormalVolume
Уменьшенный и стандартный объёмы для длинных сделок.
0.01 / 0.1
Long
LongStopLoss / LongTakeProfit
Стоп-лосс и тейк-профит в шагах цены.
1000 / 2000
Short
Аналогичные параметры (с префиксом Short).
Риски и рекомендации
Используется значение PriceStep из выбранного инструмента – убедитесь, что оно задано корректно.
Стопы проверяются на закрытии свечи; точность исполнения зависит от выбранного таймфрейма.
Модуль MMRec оценивает результат по цене закрытия свечи, поэтому в реальной торговле проскальзывание может изменить исход сделки.
Практические советы
Начинайте с симметричных параметров длинной и короткой частей, чтобы повторить оригинальный алгоритм, затем экспериментируйте с асимметрией.
Для более быстрой реакции уменьшите SignalBar до 0; увеличение значения помогает отфильтровать шум.
Оптимизируйте Phase вместе со сглаживанием, чтобы подобрать баланс между скоростью и гладкостью сигналов.
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 ColorSchaffJccxTrendCycleMmrecDuplexStrategy : 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 ColorSchaffJccxTrendCycleMmrecDuplexStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 12).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 26).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(); }
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class color_schaff_jccx_trend_cycle_mmrec_duplex_strategy(Strategy):
def __init__(self):
super(color_schaff_jccx_trend_cycle_mmrec_duplex_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 12) \
.SetDisplay("Fast EMA", "Fast period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 26) \
.SetDisplay("Slow EMA", "Slow period", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
def OnReseted(self):
super(color_schaff_jccx_trend_cycle_mmrec_duplex_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(color_schaff_jccx_trend_cycle_mmrec_duplex_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_slow = None
fast = ExponentialMovingAverage()
fast.Length = self.FastPeriod
slow = ExponentialMovingAverage()
slow.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast, slow, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if self._prev_fast is None or self._prev_slow is None:
self._prev_fast = fv
self._prev_slow = sv
return
prev_above = self._prev_fast > self._prev_slow
curr_above = fv > sv
self._prev_fast = fv
self._prev_slow = sv
if not prev_above and curr_above and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif prev_above and not curr_above and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return color_schaff_jccx_trend_cycle_mmrec_duplex_strategy()