X2MA JFATL Crossover Strategy
This strategy is a StockSharp adaptation of the MetaTrader expert Exp_X2MA_JFatl. It combines a fast Simple Moving Average (SMA) with a slow Jurik Moving Average (JMA) and an additional Jurik filter to confirm trend direction. Trades are opened when the fast average crosses the slow one and the price is on the same side of the filter. Positions are closed when price moves against the filter or an opposite crossover occurs.
Details
- Entry Criteria:
- Long:
SMA_fastcrosses aboveJMA_slowandClose>JMA_filter. - Short:
SMA_fastcrosses belowJMA_slowandClose<JMA_filter.
- Long:
- Exit Criteria:
- Price moves to the opposite side of the filter.
- Opposite crossover of the averages.
- Long/Short: Both sides.
- Stops: Not used by default.
- Default Values:
Fast MA Length= 5.Slow MA Length= 12.Filter Length= 20.
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Multiple (SMA, JMA)
- Stops: No
- Complexity: Moderate
- Timeframe: Short-term
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// X2MA with JFATL filter strategy.
/// Opens long when the fast SMA crosses above the slow Jurik MA and price is above the filter.
/// Opens short when the fast SMA crosses below the slow Jurik MA and price is below the filter.
/// </summary>
public class X2MaJfatlStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _filterLength;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevDiff;
private bool _isInitialized;
private int _barsSinceTrade;
/// <summary>
/// Fast moving average length.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Slow Jurik moving average length.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// Jurik filter length.
/// </summary>
public int FilterLength
{
get => _filterLength.Value;
set => _filterLength.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public X2MaJfatlStrategy()
{
_fastLength = Param(nameof(FastLength), 5)
.SetGreaterThanZero()
.SetDisplay("Fast MA Length", "Length of the fast moving average", "Parameters")
.SetOptimize(5, 20, 1);
_slowLength = Param(nameof(SlowLength), 13)
.SetGreaterThanZero()
.SetDisplay("Slow MA Length", "Length of the slow Jurik MA", "Parameters")
.SetOptimize(10, 40, 2);
_filterLength = Param(nameof(FilterLength), 21)
.SetGreaterThanZero()
.SetDisplay("Filter Length", "Length of the Jurik filter", "Parameters")
.SetOptimize(10, 60, 5);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for calculation", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevDiff = 0m;
_isInitialized = false;
_barsSinceTrade = 10;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevDiff = 0m;
_isInitialized = false;
_barsSinceTrade = 10;
var fastMa = new SMA { Length = FastLength };
var slowMa = new JurikMovingAverage { Length = SlowLength };
var filterMa = new JurikMovingAverage { Length = FilterLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastMa, slowMa, filterMa, Process)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastMa);
DrawIndicator(area, slowMa);
DrawIndicator(area, filterMa);
DrawOwnTrades(area);
}
}
private void Process(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal filterValue)
{
if (candle.State != CandleStates.Finished)
return;
_barsSinceTrade++;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!_isInitialized)
{
_prevDiff = fastValue - slowValue;
_isInitialized = true;
return;
}
var diff = fastValue - slowValue;
// Exit if price moves against the filter
if (Position > 0 && candle.ClosePrice < filterValue)
{
SellMarket();
_barsSinceTrade = 0;
}
else if (Position < 0 && candle.ClosePrice > filterValue)
{
BuyMarket();
_barsSinceTrade = 0;
}
// Crossover entries
if (_barsSinceTrade >= 5 && _prevDiff <= 0m && diff > 0m && candle.ClosePrice > filterValue && Position <= 0)
{
BuyMarket();
_barsSinceTrade = 0;
}
else if (_barsSinceTrade >= 5 && _prevDiff >= 0m && diff < 0m && candle.ClosePrice < filterValue && Position >= 0)
{
SellMarket();
_barsSinceTrade = 0;
}
_prevDiff = diff;
}
}
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 SimpleMovingAverage, JurikMovingAverage
from StockSharp.Algo.Strategies import Strategy
class x2_ma_j_fatl_strategy(Strategy):
def __init__(self):
super(x2_ma_j_fatl_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 5) \
.SetDisplay("Fast MA Length", "Length of the fast moving average", "Parameters")
self._slow_length = self.Param("SlowLength", 13) \
.SetDisplay("Slow MA Length", "Length of the slow Jurik MA", "Parameters")
self._filter_length = self.Param("FilterLength", 21) \
.SetDisplay("Filter Length", "Length of the Jurik filter", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Type of candles for calculation", "General")
self._prev_diff = 0.0
self._is_initialized = False
self._bars_since_trade = 10
@property
def fast_length(self):
return self._fast_length.Value
@property
def slow_length(self):
return self._slow_length.Value
@property
def filter_length(self):
return self._filter_length.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(x2_ma_j_fatl_strategy, self).OnReseted()
self._prev_diff = 0.0
self._is_initialized = False
self._bars_since_trade = 10
def OnStarted2(self, time):
super(x2_ma_j_fatl_strategy, self).OnStarted2(time)
self._prev_diff = 0.0
self._is_initialized = False
self._bars_since_trade = 10
fast_ma = SimpleMovingAverage()
fast_ma.Length = int(self.fast_length)
slow_ma = JurikMovingAverage()
slow_ma.Length = int(self.slow_length)
filter_ma = JurikMovingAverage()
filter_ma.Length = int(self.filter_length)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast_ma, slow_ma, filter_ma, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ma)
self.DrawIndicator(area, slow_ma)
self.DrawIndicator(area, filter_ma)
self.DrawOwnTrades(area)
def process_candle(self, candle, fast_value, slow_value, filter_value):
if candle.State != CandleStates.Finished:
return
fast_value = float(fast_value)
slow_value = float(slow_value)
filter_value = float(filter_value)
self._bars_since_trade += 1
if not self._is_initialized:
self._prev_diff = fast_value - slow_value
self._is_initialized = True
return
diff = fast_value - slow_value
close = float(candle.ClosePrice)
if self.Position > 0 and close < filter_value:
self.SellMarket()
self._bars_since_trade = 0
elif self.Position < 0 and close > filter_value:
self.BuyMarket()
self._bars_since_trade = 0
if self._bars_since_trade >= 5 and self._prev_diff <= 0 and diff > 0 and close > filter_value and self.Position <= 0:
self.BuyMarket()
self._bars_since_trade = 0
elif self._bars_since_trade >= 5 and self._prev_diff >= 0 and diff < 0 and close < filter_value and self.Position >= 0:
self.SellMarket()
self._bars_since_trade = 0
self._prev_diff = diff
def CreateClone(self):
return x2_ma_j_fatl_strategy()