Ver en GitHub

Estrategia Color J2JMA StdDev

Esta estrategia calcula la pendiente de una Media Móvil Jurik (JMA) y la compara con la desviación estándar de las pendientes recientes. La idea es capturar movimientos direccionales fuertes cuando la pendiente supera un múltiplo de su volatilidad reciente.

Se abre una nueva posición larga cuando la pendiente del JMA sube por encima del umbral alto (K2 × desviación estándar). Se abre una nueva posición corta cuando la pendiente cae por debajo del umbral alto negativo. Las posiciones existentes se cierran cuando la pendiente cruza el umbral bajo opuesto (K1 × desviación estándar). Los niveles de stop loss y take profit se aplican en puntos desde el precio de entrada.

Parámetros:

  • JMA Length – período de la media móvil Jurik.
  • StdDev Period – número de pendientes recientes utilizadas para la desviación estándar.
  • K1 – multiplicador para el umbral bajo usado para cerrar posiciones.
  • K2 – multiplicador para el umbral alto usado para abrir posiciones.
  • Candle Type – marco temporal de velas para los cálculos.
  • Stop Loss – stop de protección en puntos.
  • Take Profit – objetivo de ganancias en puntos.
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 of Jurik moving average and its standard deviation.
/// Opens a long position when the JMA slope rises above the high threshold.
/// Opens a short position when the JMA slope falls below the negative high threshold.
/// Existing positions are closed when the slope crosses the opposite low threshold.
/// </summary>
public class ColorJ2JmaStdDevStrategy : Strategy
{
	private readonly StrategyParam<int> _jmaLength;
	private readonly StrategyParam<int> _stdDevPeriod;
	private readonly StrategyParam<decimal> _k1;
	private readonly StrategyParam<decimal> _k2;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _prevJma;
	private StandardDeviation _stdDev;

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

	public int StdDevPeriod
	{
		get => _stdDevPeriod.Value;
		set => _stdDevPeriod.Value = value;
	}

	public decimal K1
	{
		get => _k1.Value;
		set => _k1.Value = value;
	}

	public decimal K2
	{
		get => _k2.Value;
		set => _k2.Value = value;
	}

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

	public ColorJ2JmaStdDevStrategy()
	{
		_jmaLength = Param(nameof(JmaLength), 5)
			.SetDisplay("JMA Length", "Period of JMA", "Parameters")
			.SetOptimize(3, 20, 1);

		_stdDevPeriod = Param(nameof(StdDevPeriod), 9)
			.SetDisplay("StdDev Period", "Period of standard deviation", "Parameters")
			.SetOptimize(5, 20, 1);

		_k1 = Param(nameof(K1), 0.5m)
			.SetDisplay("K1", "First threshold multiplier (close)", "Parameters")
			.SetOptimize(0.3m, 2m, 0.3m);

		_k2 = Param(nameof(K2), 1.0m)
			.SetDisplay("K2", "Second threshold multiplier (entry)", "Parameters")
			.SetOptimize(0.5m, 3m, 0.5m);

		_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;
		_stdDev = null;
	}

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

		_prevJma = null;

		var jma = new JurikMovingAverage { Length = JmaLength };
		_stdDev = new StandardDeviation { Length = StdDevPeriod };
		Indicators.Add(_stdDev);

		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 (_prevJma is not decimal prev)
		{
			_prevJma = jmaValue;
			return;
		}

		var diff = jmaValue - prev;
		_prevJma = jmaValue;

		// Process diff through StdDev manually with IsFinal = true
		var stdResult = _stdDev.Process(new DecimalIndicatorValue(_stdDev, diff, candle.ServerTime) { IsFinal = true });

		if (!_stdDev.IsFormed)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var stDev = stdResult.GetValue<decimal>();

		if (stDev == 0)
			return;

		var lowThreshold = K1 * stDev;
		var highThreshold = K2 * stDev;

		// Close existing long when slope turns strongly down
		if (Position > 0 && diff < -lowThreshold)
		{
			SellMarket();
			return;
		}

		// Close existing short when slope turns strongly up
		if (Position < 0 && diff > lowThreshold)
		{
			BuyMarket();
			return;
		}

		// Open new long on strong positive slope
		if (Position <= 0 && diff > highThreshold)
		{
			BuyMarket();
		}
		// Open new short on strong negative slope
		else if (Position >= 0 && diff < -highThreshold)
		{
			SellMarket();
		}
	}
}