This strategy recreates the behaviour of the original Exp_JFatlCandle_MMRec.mq5 Expert Advisor inside the StockSharp framework.
It analyses the colour changes produced by the JFatl candle filter and combines them with an adaptive money management block
that reduces the trading size after a configurable number of recent losses.
Trading idea
Build synthetic candles by filtering the classic OHLC values with the Fast Adaptive Trend Line (FATL) kernel.
The implementation uses the original 39-tap coefficient table followed by an exponential smoothing stage in order to
approximate the Jurik moving average used in MetaTrader.
Detect colour transitions of the synthetic candle body:
colour 2 (bullish) means the filtered close is above the filtered open;
colour 0 (bearish) means the filtered close is below the filtered open;
colour 1 marks a neutral body.
A bullish colour on the bar that is SignalBar + 1 periods old forces the strategy to flat any shorts and to prepare
for a new long entry when the bar SignalBar periods old is no longer bullish.
A bearish colour observed in the same way closes longs and enables a short entry when the more recent bar is not bearish anymore.
Long and short positions are sized through the MMRecounter logic. When the last TotalTrigger trades of the
corresponding direction include at least LossTrigger negative results, the strategy switches to the reduced position size.
Parameters
Parameter
Description
CandleType
Time-frame of the candles that are fed into the FATL filter (default: 12 hours).
SignalBar
Number of completed bars to look back when reading the colour buffer. 0 means to use the current finished bar, 1 reproduces the MT5 defaults.
SmoothingLength
Exponential smoothing length applied after the FATL kernel to emulate Jurik smoothing.
NormalVolume
Default position size used when the recent track record is healthy.
ReducedVolume
Position size applied after the MMRecounter detects too many losses.
BuyTotalTrigger / SellTotalTrigger
Amount of historical trades (per direction) inspected by the MMRecounter.
BuyLossTrigger / SellLossTrigger
Minimal number of losses inside the inspected window that forces the reduced position size.
EnableBuyEntries / EnableSellEntries
Allow opening long/short positions.
EnableBuyExits / EnableSellExits
Allow closing long/short positions when the opposite signal appears.
StopLossPoints
Optional protective stop for both directions expressed in security price steps. Set to 0 to disable.
TakeProfitPoints
Optional profit target in price steps. Set to 0 to disable.
Trading rules
Build the filtered OHLC values and determine the candle colour at each finished bar.
Let C1 be the colour of the bar SignalBar + 1 periods ago and C0 the colour of the bar SignalBar periods ago
(for SignalBar = 0 the current bar is used as C0 and the previous bar as C1).
If C1 == 2 (bullish)
close any short position when EnableSellExits is true;
open a long position with the calculated position size when EnableBuyEntries is trueandC0 != 2.
If C1 == 0 (bearish)
close any long position when EnableBuyExits is true;
open a short position when EnableSellEntries is trueandC0 != 0.
Positions can also be closed by stop-loss or take-profit boundaries when the candle range touches the configured level.
Money management
The strategy stores the profit of every completed long and short trade separately. When a new entry is considered, it scans
up to TotalTrigger previous trades of that direction. If at least LossTrigger trades within that window ended with a negative
result, the reduced volume is used; otherwise, the normal volume is traded.
Notes
Price-step based stop-loss and take-profit logic relies on the Security.PriceStep value. If the instrument does not provide it,
a step of 1 is assumed.
The FATL filter needs at least 39 historical candles before it becomes operational. No trades are generated until
enough data is accumulated.
The strategy keeps a compact trade history for the MMRecounter block; once the history exceeds 100 items the oldest records
are discarded automatically.
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 JfatlCandleMmRecStrategy : 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 JfatlCandleMmRecStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 7).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 21).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA 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 jfatl_candle_mm_rec_strategy(Strategy):
def __init__(self):
super(jfatl_candle_mm_rec_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", 7) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 21) \
.SetDisplay("Slow EMA", "Slow EMA 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(jfatl_candle_mm_rec_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(jfatl_candle_mm_rec_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 jfatl_candle_mm_rec_strategy()