Ver no GitHub

Estratégia do Consultor Especialista KDJ

Visão geral

Esta estratégia replica o MetaTrader 5 «KDJ Expert Advisor» de senlin ge. Ela opera em um único símbolo usando sinais do oscilador KDJ, uma evolução do oscilador estocástico onde a linha %K é suavizada duas vezes. A estratégia observa a diferença entre as linhas %K e %D (frequentemente chamada de linha J) para identificar reversões de momentum, abrindo apenas uma posição por vez. O gerenciamento de operações espelha o expert advisor original: cada operação recebe imediatamente um stop-loss fixo e take-profit expressos em pips e traduzidos em distância de preço usando as configurações do instrumento.

A implementação usa a API de alto nível do StockSharp com uma subscrição de velas e o indicador Stochastic integrado, configurado para corresponder aos parâmetros KDJ da versão MQL5. O código detecta automaticamente símbolos Forex de 3 ou 5 dígitos e ajusta o valor do pip de acordo.

Lógica do indicador

O indicador subjacente funciona em três etapas:

  1. Cálculo RSV – Para cada vela concluída, calcular o Valor Estocástico Bruto em KDJ Length velas: [ RSV = \frac{Close - LowestLow}{HighestHigh - LowestLow} \times 100 ]
  2. Suavização %K – Calcular a média dos últimos valores Smooth %K de RSV para obter a linha %K.
  3. Suavização %D – Calcular a média dos últimos valores Smooth %D de %K para obter a linha %D.

A estratégia então analisa K - D (referido como KDC na fonte original) e a inclinação de %K para detectar reversões.

Critérios de entrada

Uma posição de mercado é aberta apenas se não houver posição existente para o símbolo. Os sinais são avaliados em velas concluídas:

  • Compra quando qualquer uma das seguintes condições for verdadeira:
    • K - D cruza acima de zero (de negativo a positivo); ou
    • K - D está acima de zero e a linha %K está subindo (K_current > K_previous).
  • Venda quando qualquer uma das seguintes condições for verdadeira:
    • K - D cruza abaixo de zero (de positivo a negativo); ou
    • K - D está abaixo de zero e a linha %K está caindo (K_current < K_previous).

Isso corresponde à estrutura booleana do expert advisor MQL5 original, garantindo timing de operação idêntico.

Gestão de riscos

  • Cada ordem executada recebe um stop-loss protetor e take-profit, medidos em pips e convertidos em distância de preço através do tamanho do tick do instrumento. Um valor de zero desabilita o trecho de proteção correspondente.
  • A estratégia não faz pirâmide nem média de posições. Permanece zerada até que a posição atual seja fechada pelas ordens protetoras ou por intervenção manual.

Parâmetros

Parâmetro Descrição Padrão
Candle Type Tipo de dados/período das velas de entrada. Período de 15 minutos
KDJ Length Número de velas para o cálculo RSV. 30
Smooth %K Número de valores RSV usados para suavizar a linha %K. 3
Smooth %D Número de valores %K usados para suavizar a linha %D. 6
Stop Loss (pips) Distância do stop-loss protetor. Defina como 0 para desabilitar. 25
Take Profit (pips) Distância do take-profit protetor. Defina como 0 para desabilitar. 45
Order Volume Quantidade enviada com ordens a mercado. 1

Todos os parâmetros suportam intervalos de otimização idênticos às entradas do expert advisor original.

Notas de uso

  1. Configure o instrumento e o conector desejados no testador ou no ambiente ao vivo.
  2. Ajuste o tipo de vela para corresponder ao período do gráfico que deseja emular do MetaTrader.
  3. Opcionalmente, otimize os parâmetros KDJ, stop-loss, take-profit ou volume de ordem.
  4. Inicie a estratégia. As ordens são geradas apenas em velas completamente formadas.
  5. O gráfico exibe automaticamente velas, o indicador KDJ e as operações executadas para confirmação visual.

Diferenças do EA original

  • Usa o indicador Stochastic do StockSharp com períodos de suavização para replicar os buffers KDJ do MQL5; nenhum arquivo de indicador externo é necessário.
  • As ordens protetoras são gerenciadas através de StartProtection, que envia saídas a mercado quando acionadas.
  • O volume é um parâmetro fixo em vez do modelo de risco MoneyFixedMargin do MQL5, mantendo a implementação concisa e focada na lógica de sinal.
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>
/// Strategy that replicates the MetaTrader KDJ Expert Advisor logic.
/// Uses the KDJ oscillator to detect momentum reversals and opens a single position with fixed take-profit and stop-loss levels.
/// </summary>
public class KdjExpertAdvisorStrategy : Strategy
{
	private readonly StrategyParam<int> _kdjPeriod;
	private readonly StrategyParam<int> _smoothK;
	private readonly StrategyParam<int> _smoothD;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _previousK;
	private decimal? _previousKdc;
	private decimal _pipSize;

	/// <summary>
	/// Main lookback period used to calculate RSV for the KDJ oscillator.
	/// </summary>
	public int KdjPeriod
	{
		get => _kdjPeriod.Value;
		set => _kdjPeriod.Value = value;
	}

	/// <summary>
	/// Smoothing period applied to the %K line.
	/// </summary>
	public int SmoothK
	{
		get => _smoothK.Value;
		set => _smoothK.Value = value;
	}

	/// <summary>
	/// Smoothing period applied to the %D line.
	/// </summary>
	public int SmoothD
	{
		get => _smoothD.Value;
		set => _smoothD.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Volume applied to every market order.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Candle type used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="KdjExpertAdvisorStrategy"/> class.
	/// </summary>
	public KdjExpertAdvisorStrategy()
	{
		_kdjPeriod = Param(nameof(KdjPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("KDJ Length", "Lookback period for KDJ RSV calculation", "KDJ")
			
			.SetOptimize(10, 60, 5);

		_smoothK = Param(nameof(SmoothK), 3)
			.SetGreaterThanZero()
			.SetDisplay("Smooth %K", "Smoothing length for %K", "KDJ")
			
			.SetOptimize(1, 10, 1);

		_smoothD = Param(nameof(SmoothD), 6)
			.SetGreaterThanZero()
			.SetDisplay("Smooth %D", "Smoothing length for %D", "KDJ")
			
			.SetOptimize(1, 15, 1);

		_stopLossPips = Param(nameof(StopLossPips), 250)
			.SetNotNegative()
			.SetDisplay("Stop Loss (pips)", "Protective stop distance in pips", "Risk")

			.SetOptimize(0, 1000, 50);

		_takeProfitPips = Param(nameof(TakeProfitPips), 450)
			.SetNotNegative()
			.SetDisplay("Take Profit (pips)", "Profit target distance in pips", "Risk")

			.SetOptimize(0, 1500, 50);

		_orderVolume = Param(nameof(OrderVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Quantity used for entries", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for KDJ calculation", "Data");
	}

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

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

		_previousK = null;
		_previousKdc = null;
		_pipSize = 0m;
	}

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

		_pipSize = CalculatePipSize();

		var stopLossUnit = StopLossPips > 0 ? new Unit(StopLossPips * _pipSize, UnitTypes.Absolute) : null;
		var takeProfitUnit = TakeProfitPips > 0 ? new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute) : null;

		StartProtection(
			takeProfit: takeProfitUnit,
			stopLoss: stopLossUnit,
			useMarketOrders: true);

		var kdj = new StochasticOscillator
		{
			K = { Length = KdjPeriod },
			D = { Length = SmoothD }
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(kdj, ProcessCandle)
			.Start();

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

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

		var stochastic = (StochasticOscillatorValue)kdjValue;
		if (stochastic.K is not decimal k || stochastic.D is not decimal d)
			return;

		var kdc = k - d;

		var buySignal = false;
		var sellSignal = false;

		if (_previousKdc.HasValue)
		{
			buySignal |= _previousKdc.Value < 0m && kdc > 0m;
			sellSignal |= _previousKdc.Value > 0m && kdc < 0m;
		}

		if (_previousK.HasValue)
		{
			buySignal |= kdc > 0m && _previousK.Value < k;
			sellSignal |= kdc < 0m && _previousK.Value > k;
		}

		if (buySignal || sellSignal)
		{
			if (Position == 0)
			{
				if (buySignal)
				{
					LogInfo($"Buy signal at {candle.ClosePrice}: K={k:F2}, D={d:F2}, K-D={kdc:F2}");
					BuyMarket();
				}
				else if (sellSignal)
				{
					LogInfo($"Sell signal at {candle.ClosePrice}: K={k:F2}, D={d:F2}, K-D={kdc:F2}");
					SellMarket();
				}
			}
		}

		_previousK = k;
		_previousKdc = kdc;
	}

	private decimal CalculatePipSize()
	{
		var security = Security;
		if (security == null)
			return 1m;

		var step = security.PriceStep ?? 1m;
		var decimals = security.Decimals;
		var multiplier = (decimals == 3 || decimals == 5) ? 10m : 1m;

		return step * multiplier;
	}
}