JFATL Digit System
Strategy built around the Jurik moving average (JFATL) slope. It opens long positions when the moving average turns upward and short positions when it turns downward. The idea imitates the color-coded digital system from the original MQL version.
Details
- Entry Criteria: Slope of Jurik moving average changes sign. Upward slope opens a long position, downward slope opens a short position.
- Long/Short: Both directions are traded.
- Exit Criteria: Position is reversed on opposite slope or closed by risk management.
- Stops: Percentage-based take profit and optional stop loss configured through
StartProtection. - Default Values: Length = 5, Phase = -100, Timeframe = 4 hours.
- Filters: None. The strategy relies solely on JMA slope.
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>
/// JFATL Digit System based on the slope of the Jurik moving average.
/// Opens long when the JMA turns upward and short when it turns downward.
/// </summary>
public class JfatlDigitSystemStrategy : Strategy
{
private readonly StrategyParam<int> _jmaLength;
private readonly StrategyParam<int> _jmaPhase;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevJma;
private decimal? _prevSlope;
public int JmaLength
{
get => _jmaLength.Value;
set => _jmaLength.Value = value;
}
public int JmaPhase
{
get => _jmaPhase.Value;
set => _jmaPhase.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public JfatlDigitSystemStrategy()
{
_jmaLength = Param(nameof(JmaLength), 5)
.SetGreaterThanZero()
.SetDisplay("JMA Length", "JMA period", "Parameters")
.SetOptimize(3, 20, 1);
_jmaPhase = Param(nameof(JmaPhase), -100)
.SetDisplay("JMA Phase", "JMA phase", "Parameters")
.SetOptimize(-100, 100, 20);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "Parameters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevJma = null;
_prevSlope = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevJma = null;
_prevSlope = null;
var jma = new JurikMovingAverage
{
Length = JmaLength,
Phase = JmaPhase
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(jma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, jma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal jmaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_prevJma is decimal prev)
{
var slope = jmaValue - prev;
if (_prevSlope is decimal prevSlope)
{
var turnedUp = prevSlope <= 0 && slope > 0;
var turnedDown = prevSlope >= 0 && slope < 0;
if (turnedUp && Position <= 0)
BuyMarket();
else if (turnedDown && Position >= 0)
SellMarket();
}
_prevSlope = slope;
}
_prevJma = jmaValue;
}
}
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 JurikMovingAverage
from StockSharp.Algo.Strategies import Strategy
class jfatl_digit_system_strategy(Strategy):
def __init__(self):
super(jfatl_digit_system_strategy, self).__init__()
self._jma_length = self.Param("JmaLength", 5) \
.SetDisplay("JMA Length", "JMA period", "Parameters")
self._jma_phase = self.Param("JmaPhase", -100) \
.SetDisplay("JMA Phase", "JMA phase", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "Parameters")
self._prev_jma = None
self._prev_slope = None
@property
def jma_length(self):
return self._jma_length.Value
@property
def jma_phase(self):
return self._jma_phase.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(jfatl_digit_system_strategy, self).OnReseted()
self._prev_jma = None
self._prev_slope = None
def OnStarted2(self, time):
super(jfatl_digit_system_strategy, self).OnStarted2(time)
self._prev_jma = None
self._prev_slope = None
jma = JurikMovingAverage()
jma.Length = self.jma_length
jma.Phase = self.jma_phase
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(jma, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, jma)
self.DrawOwnTrades(area)
def process_candle(self, candle, jma_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
jma_val = float(jma_value)
if self._prev_jma is not None:
slope = jma_val - self._prev_jma
if self._prev_slope is not None:
turned_up = self._prev_slope <= 0 and slope > 0
turned_down = self._prev_slope >= 0 and slope < 0
if turned_up and self.Position <= 0:
self.BuyMarket()
elif turned_down and self.Position >= 0:
self.SellMarket()
self._prev_slope = slope
self._prev_jma = jma_val
def CreateClone(self):
return jfatl_digit_system_strategy()