Color Schaff JCCX Trend Cycle MMRec Duplex Strategy
Overview
Recreates the dual-direction Expert Advisor "ColorSchaffJCCXTrendCycle_MMRec_Duplex" from MetaTrader within StockSharp.
Uses two independent Schaff trend cycle stacks built on Jurik moving averages to detect bullish and bearish reversals.
Implements a simplified MMRec (money management recommender) module that reduces size after repeated losses.
Applies separate parameter sets for long and short trades, enabling asymmetric configurations across timeframes and price sources.
Indicator Pipeline
JCCX approximation – each price is processed by a Jurik moving average to obtain a detrended series. The detrended series and its absolute value are smoothed again with Jurik averages to approximate the original JCCX oscillator.
MACD layer – the difference between fast and slow JCCX outputs provides the momentum base.
Double stochastic transform – rolling min/max windows normalize the MACD momentum and produce the final Schaff trend cycle (STC) value in the range -100..+100.
Phase control – the Phase parameter modulates an internal smoothing factor (0.05–0.95) applied after every stochastic step, emulating Jurik "phase" behaviour.
The indicator stack is executed twice: once for the long block and once for the short block. Each block can use different candle types and price inputs.
Trading Logic
Long Block
Entry: when the long STC crosses above zero (current value > 0 and the previous delayed value ≤ 0). Existing short positions are closed first.
Exit: when the long STC falls below zero and long exits are enabled.
Stops: optional stop-loss and take-profit distances (expressed in price steps) are evaluated on every completed candle using candle highs/lows.
Short Block
Entry: when the short STC crosses below zero (current value < 0 and the delayed value ≥ 0). Any existing long position is flattened before opening a short.
Exit: when the short STC climbs above zero and short exits are enabled.
Stops: symmetrical stop-loss and take-profit checks for short trades.
The SignalBar parameter defines how many fully closed candles are skipped before signals are evaluated. A value of 1 reproduces the MetaTrader behaviour of using the previous completed candle.
Money Management (MMRec)
Two queues track the most recent trade results for longs and shorts.
TotalTrigger limits the queue length; only the latest N results are considered.
LossTrigger defines how many losses within that queue switch the trade size to SmallVolume.
When the loss threshold is not breached, the strategy uses NormalVolume.
Parameters
Group
Parameter
Description
Default
Long
LongCandleType
Candle type (timeframe) used for long calculations.
8 hour timeframe
Long
LongFastLength
Fast Jurik length inside the long JCCX approximation.
23
Long
LongSlowLength
Slow Jurik length for the long JCCX approximation.
50
Long
LongSmoothLength
Jurik smoothing length applied to the numerator/denominator.
8
Long
LongPhase
Phase parameter translated into a smoothing factor (0.05–0.95).
100
Long
LongCycle
Rolling window length for the stochastic transforms.
10
Long
LongSignalBar
Delay (in bars) before a signal is evaluated.
1
Long
LongAppliedPrice
Price source used for long calculations.
Close
Long
LongAllowOpen / LongAllowClose
Enable/disable long entries or exits.
true
Long
LongTotalTrigger
Number of recent long trades stored for the MMRec queue.
5
Long
LongLossTrigger
Losses required inside the queue to switch to small volume.
3
Long
LongSmallVolume / LongNormalVolume
Reduced and default long trade sizes.
0.01 / 0.1
Long
LongStopLoss / LongTakeProfit
Optional stop/take distances in price steps.
1000 / 2000
Short
Same as long (prefixed with Short).
Risk Notes
Price steps are retrieved from the current Security. Ensure the instrument has a valid PriceStep or adjust parameters accordingly.
Stop-loss and take-profit checks are evaluated on completed candles; intrabar execution quality depends on candle resolution.
The MMRec module relies on comparing entry and exit prices. In live trading slippage may alter the effective result.
Usage Tips
Start with identical long/short settings to emulate the original duplex EA, then experiment with asymmetric timeframes.
Lower the SignalBar to zero for faster responses; increase it to filter noise.
Optimise LongPhase/ShortPhase together with the smoothing lengths to fine-tune responsiveness vs. smoothness.
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()