GitHub で見る

ColorJFatl Digit Strategy

This strategy uses the slope direction of a Jurik Moving Average (JMA) to generate trades. The JMA approximates the "ColorJFatl_Digit" indicator from the original MQL5 expert. A long position is opened when the JMA turns upward, while a short position is opened when the JMA turns downward. Opposite positions are closed when the slope reverses.

The system trades both directions and does not employ hard stops by default. It suits instruments where trend changes can be captured by a smooth adaptive moving average.

Details

  • Entry Criteria:
    • Long: JMA slope changes from negative to positive.
    • Short: JMA slope changes from positive to negative.
  • Long/Short: Both sides.
  • Exit Criteria:
    • Long: JMA slope turns negative.
    • Short: JMA slope turns positive.
  • Stops: None by default.
  • Default Values:
    • JMA Length = 5
    • Timeframe = 4 hours
  • Filters:
    • Category: Trend following
    • Direction: Both
    • Indicators: Single
    • Stops: No
    • Complexity: Simple
    • Timeframe: Medium-term
    • Seasonality: No
    • Neural networks: No
    • Divergence: No
    • Risk level: Medium
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>
/// Strategy based on the slope changes of Jurik Moving Average (JMA).
/// Opens long when JMA turns up, opens short when JMA turns down.
/// </summary>
public class ColorJFatlDigitStrategy : Strategy
{
	private readonly StrategyParam<int> _jmaLength;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _prevJma;
	private decimal? _prevSlope;

	public int JmaLength { get => _jmaLength.Value; set => _jmaLength.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public ColorJFatlDigitStrategy()
	{
		_jmaLength = Param(nameof(JmaLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("JMA Length", "Period for Jurik Moving Average", "Parameters");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe of indicator", "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 };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(jma, Process)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, jma);
			DrawOwnTrades(area);
		}
	}

	private void Process(ICandleMessage candle, decimal jmaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var slope = _prevJma is decimal prev ? jmaValue - prev : (decimal?)null;

		if (slope is decimal s && _prevSlope is decimal ps)
		{
			// JMA slope turns positive -> buy
			if (ps <= 0m && s > 0m && Position <= 0)
				BuyMarket();
			// JMA slope turns negative -> sell
			else if (ps >= 0m && s < 0m && Position >= 0)
				SellMarket();
		}

		_prevSlope = slope;
		_prevJma = jmaValue;
	}
}