Ver en GitHub

JSatl Digit System Strategy

This example demonstrates a simplified port of the MQL5 "JSatl Digit System" expert advisor to StockSharp.

The strategy uses the Jurik Moving Average (JMA) to create a digital trend state:

  • When the close price is above the JMA, the state becomes up.
  • When the close price is below the JMA, the state becomes down.

If the state changes to up, short positions can be closed and/or a long position can be opened depending on the parameters. When the state changes to down, long positions can be closed and/or a short position can be opened.

Parameters

  • JmaLength – JMA period.
  • CandleType – candle series used for calculations.
  • StopLossPercent – protective stop loss in percent.
  • TakeProfitPercent – protective take profit in percent.
  • BuyPosOpen, SellPosOpen, BuyPosClose, SellPosClose – enable or disable actions for corresponding signals.
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>
/// JSatl Digit System strategy based on the Jurik Moving Average.
/// Generates digital trend states to open or close positions.
/// </summary>
public class JSatlDigitSystemStrategy : Strategy
{
	private readonly StrategyParam<int> _jmaLength;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _stopLossPercent;
	private readonly StrategyParam<decimal> _takeProfitPercent;
	private readonly StrategyParam<bool> _buyPosOpen;
	private readonly StrategyParam<bool> _sellPosOpen;
	private readonly StrategyParam<bool> _buyPosClose;
	private readonly StrategyParam<bool> _sellPosClose;

	private decimal? _lastState;

	/// <summary>
	/// Jurik moving average period.
	/// </summary>
	public int JmaLength
	{
		get => _jmaLength.Value;
		set => _jmaLength.Value = value;
	}

	/// <summary>
	/// Candle series type.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Stop loss in percent.
	/// </summary>
	public decimal StopLossPercent
	{
		get => _stopLossPercent.Value;
		set => _stopLossPercent.Value = value;
	}

	/// <summary>
	/// Take profit in percent.
	/// </summary>
	public decimal TakeProfitPercent
	{
		get => _takeProfitPercent.Value;
		set => _takeProfitPercent.Value = value;
	}

	/// <summary>
	/// Allow opening long positions.
	/// </summary>
	public bool BuyPosOpen
	{
		get => _buyPosOpen.Value;
		set => _buyPosOpen.Value = value;
	}

	/// <summary>
	/// Allow opening short positions.
	/// </summary>
	public bool SellPosOpen
	{
		get => _sellPosOpen.Value;
		set => _sellPosOpen.Value = value;
	}

	/// <summary>
	/// Allow closing long positions.
	/// </summary>
	public bool BuyPosClose
	{
		get => _buyPosClose.Value;
		set => _buyPosClose.Value = value;
	}

	/// <summary>
	/// Allow closing short positions.
	/// </summary>
	public bool SellPosClose
	{
		get => _sellPosClose.Value;
		set => _sellPosClose.Value = value;
	}

	public JSatlDigitSystemStrategy()
	{
		_jmaLength = Param(nameof(JmaLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("JMA Length", "Period of Jurik MA", "Parameters")
			
			.SetOptimize(2, 30, 1);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for candles", "Parameters");

		_stopLossPercent = Param(nameof(StopLossPercent), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss %", "Stop loss percent", "Risk")
			
			.SetOptimize(0.5m, 3m, 0.5m);

		_takeProfitPercent = Param(nameof(TakeProfitPercent), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit %", "Take profit percent", "Risk")
			
			.SetOptimize(1m, 5m, 1m);

		_buyPosOpen = Param(nameof(BuyPosOpen), true)
			.SetDisplay("Buy Open", "Enable long entries", "Trading");

		_sellPosOpen = Param(nameof(SellPosOpen), true)
			.SetDisplay("Sell Open", "Enable short entries", "Trading");

		_buyPosClose = Param(nameof(BuyPosClose), true)
			.SetDisplay("Buy Close", "Enable closing longs", "Trading");

		_sellPosClose = Param(nameof(SellPosClose), true)
			.SetDisplay("Sell Close", "Enable closing shorts", "Trading");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_lastState = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_lastState = null;

		var jma = new JurikMovingAverage { Length = JmaLength };

		SubscribeCandles(CandleType)
			.Bind(jma, ProcessCandle)
			.Start();

		StartProtection(
			takeProfit: new Unit(TakeProfitPercent * 100m, UnitTypes.Percent),
			stopLoss: new Unit(StopLossPercent * 100m, UnitTypes.Percent));
	}

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

		// Determine current digital state based on price relative to JMA
		var state = candle.ClosePrice > jmaValue ? 3m : 1m;

		// React only when the state changes
		if (_lastState is decimal last && last == state)
			return;

		if (state > 2m)
		{
			// Uptrend: close shorts and/or open long
			if (SellPosClose && Position < 0)
				BuyMarket();

			if (BuyPosOpen && Position <= 0)
				BuyMarket();
		}
		else
		{
			// Downtrend: close longs and/or open short
			if (BuyPosClose && Position > 0)
				SellMarket();

			if (SellPosOpen && Position >= 0)
				SellMarket();
		}

		_lastState = state;
	}
}