Ver no GitHub

Estratégia de Caçador de Ressonância

Visão geral

A estratégia Resonance Hunter é a porta StockSharp do MetaTrader consultor especialista Exp_ResonanceHunter. Ele monitora três pares de moedas correlacionados por slot e procura impulso síncrono em seus osciladores Stochastic. Quando os osciladores ressoam na mesma direção, a estratégia abre uma posição no símbolo primário, enquanto os símbolos secundário e de confirmação atuam como filtros. A negociação é fechada assim que o instrumento líder perde impulso ou quando o stop loss configurado é atingido.

Três slots estão pré-configurados:

  1. EURUSD foi negociado com EURJPY e USDJPY como confirmações.
  2. GBPUSD foi negociado com GBPJPY e USDJPY.
  3. AUDUSD foi negociado com AUDJPY e USDJPY.

Cada slot pode ser ativado ou desativado de forma independente e pode usar seu próprio período de tempo e parâmetros de indicador.

Parâmetros

Todos os parâmetros são agrupados por slot (Slot 1–3). Cada grupo compartilha as seguintes configurações:

Parâmetro Descrição
{Slot} Enabled Permite a negociação do slot.
{Slot} Primary Instrumento negociado pela estratégia e utilizado para sinais de saída.
{Slot} Secondary Segundo instrumento que participa da verificação de ressonância.
{Slot} Confirmation Terceiro instrumento utilizado na verificação de ressonância.
{Slot} Candle Type Prazo aplicado a todos os três instrumentos (padrão = 1 hora).
{Slot} K Period Stochastic %K retrospectiva.
{Slot} D Period Período de suavização para %D.
{Slot} Slowing Suavização adicional para %K.
{Slot} Volume Volume do pedido em lotes. A exposição oposta existente é compensada.
{Slot} Stop Loss Distância de stop-loss estilo MetaTrader em pontos. Defina como 0 para desabilitar a parada de proteção.

Lógica de negociação

  1. Para cada instrumento configurado, um StochasticOscillator com os parâmetros selecionados é calculado nas velas concluídas.
  2. Uma vez que as últimas velas dos três instrumentos compartilham o mesmo tempo de abertura, as diferenças %K - %D são avaliadas:
    • A diferença positiva marca um impulso ascendente (Up), a diferença negativa marca um impulso descendente (Down).
    • Regras de consistência adicionais do indicador original ajustam os impulsos comparando a magnitude de cada par.
  3. Uma entrada longa é gerada quando todos os três impulsos apontam para cima. Uma entrada curta aparece quando todos os três impulsos apontam para baixo.
  4. Antes de enviar novas ordens, a estratégia fecha as posições existentes se o símbolo primário indicar um impulso oposto (espelha os buffers UpStop/DnStop do indicador).
  5. Depois de inserir uma posição, um preço de stop de proteção é calculado usando o último fechamento e a distância {Slot} Stop Loss. A cada nova vela primária, o stop é verificado e, se violado, a posição é fechada imediatamente.

Os pedidos são encaminhados por meio de BuyMarket/SellMarket. A exposição existente no símbolo primário é compensada para que a estratégia possa reverter diretamente quando necessário.

Notas

  • A estratégia requer dados de velas sincronizados para os três instrumentos dentro de cada slot. Se um símbolo ficar para trás, o sinal será adiado até que os carimbos de data e hora da barra se alinhem.
  • Os níveis de stop são emulados dentro da estratégia (nenhuma ordem de stop real é enviada) para corresponder ao comportamento MetaTrader.
  • Os valores de parâmetro padrão reproduzem o consultor especialista original, mas podem ser otimizados por meio da interface Param.
using System;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "Resonance Hunter" MetaTrader expert.
/// Uses multiple Stochastic oscillators on different periods to find
/// resonance (all pointing same direction) for entry signals.
/// </summary>
public class ResonanceHunterStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastKPeriod;
	private readonly StrategyParam<int> _slowKPeriod;
	private readonly StrategyParam<int> _dPeriod;

	// Manual stochastic: track highest high and lowest low over K periods
	private readonly decimal[] _highs1 = new decimal[100];
	private readonly decimal[] _lows1 = new decimal[100];
	private readonly decimal[] _highs2 = new decimal[100];
	private readonly decimal[] _lows2 = new decimal[100];
	private int _barCount;

	private decimal? _prevFastK;
	private decimal? _prevSlowK;
	private decimal? _prevFastD;
	private decimal? _prevSlowD;

	// Simple smoothing queues for %D
	private readonly decimal[] _fastKHistory = new decimal[3];
	private readonly decimal[] _slowKHistory = new decimal[3];
	private int _fastKCount;
	private int _slowKCount;

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

	public int FastKPeriod
	{
		get => _fastKPeriod.Value;
		set => _fastKPeriod.Value = value;
	}

	public int SlowKPeriod
	{
		get => _slowKPeriod.Value;
		set => _slowKPeriod.Value = value;
	}

	public int DPeriod
	{
		get => _dPeriod.Value;
		set => _dPeriod.Value = value;
	}

	public ResonanceHunterStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_fastKPeriod = Param(nameof(FastKPeriod), 8)
			.SetGreaterThanZero()
			.SetDisplay("Fast K Period", "Fast stochastic K period", "Indicators");

		_slowKPeriod = Param(nameof(SlowKPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("Slow K Period", "Slow stochastic K period", "Indicators");

		_dPeriod = Param(nameof(DPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("D Period", "Smoothing period for %D line", "Indicators");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_barCount = 0;
		_prevFastK = null;
		_prevSlowK = null;
		_prevFastD = null;
		_prevSlowD = null;
		_fastKCount = 0;
		_slowKCount = 0;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

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

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

		var idx = _barCount % 100;
		_highs1[idx] = candle.HighPrice;
		_lows1[idx] = candle.LowPrice;
		_highs2[idx] = candle.HighPrice;
		_lows2[idx] = candle.LowPrice;
		_barCount++;

		if (_barCount < SlowKPeriod)
			return;

		// Calculate fast stochastic %K
		var fastK = CalculateStochasticK(_highs1, _lows1, candle.ClosePrice, FastKPeriod);
		// Calculate slow stochastic %K
		var slowK = CalculateStochasticK(_highs2, _lows2, candle.ClosePrice, SlowKPeriod);

		if (fastK == null || slowK == null)
			return;

		// Calculate %D as SMA of %K
		var fastD = AddToSmoothing(_fastKHistory, ref _fastKCount, fastK.Value, DPeriod);
		var slowD = AddToSmoothing(_slowKHistory, ref _slowKCount, slowK.Value, DPeriod);

		if (fastD == null || slowD == null || _prevFastK == null || _prevSlowK == null || _prevFastD == null || _prevSlowD == null)
		{
			_prevFastK = fastK;
			_prevSlowK = slowK;
			_prevFastD = fastD;
			_prevSlowD = slowD;
			return;
		}

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// Resonance buy: both stochastics cross above their D lines
		var fastBullCross = _prevFastK.Value < _prevFastD.Value && fastK.Value > fastD.Value;
		var slowBullCross = _prevSlowK.Value < _prevSlowD.Value && slowK.Value > slowD.Value;
		var bothOversold = fastK.Value < 30 && slowK.Value < 30;

		// Resonance sell: both stochastics cross below their D lines
		var fastBearCross = _prevFastK.Value > _prevFastD.Value && fastK.Value < fastD.Value;
		var slowBearCross = _prevSlowK.Value > _prevSlowD.Value && slowK.Value < slowD.Value;
		var bothOverbought = fastK.Value > 70 && slowK.Value > 70;

		// Buy when both signals confirm or fast crosses with slow already bullish
		var buySignal = (fastBullCross && (slowBullCross || slowK.Value > slowD.Value)) && bothOversold;
		// Sell when both signals confirm or fast crosses with slow already bearish
		var sellSignal = (fastBearCross && (slowBearCross || slowK.Value < slowD.Value)) && bothOverbought;

		if (buySignal)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else if (sellSignal)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevFastK = fastK;
		_prevSlowK = slowK;
		_prevFastD = fastD;
		_prevSlowD = slowD;
	}

	private decimal? CalculateStochasticK(decimal[] highs, decimal[] lows, decimal close, int period)
	{
		if (_barCount < period)
			return null;

		var hh = decimal.MinValue;
		var ll = decimal.MaxValue;

		for (var i = 0; i < period; i++)
		{
			var idx = (_barCount - 1 - i) % 100;
			if (idx < 0) idx += 100;
			if (highs[idx] > hh) hh = highs[idx];
			if (lows[idx] < ll) ll = lows[idx];
		}

		var range = hh - ll;
		if (range <= 0)
			return 50m;

		return (close - ll) / range * 100m;
	}

	private static decimal? AddToSmoothing(decimal[] history, ref int count, decimal value, int period)
	{
		var idx = count % history.Length;
		history[idx] = value;
		count++;

		if (count < period)
			return null;

		var sum = 0m;
		var n = Math.Min(period, history.Length);
		for (var i = 0; i < n; i++)
		{
			var j = (count - 1 - i) % history.Length;
			if (j < 0) j += history.Length;
			sum += history[j];
		}

		return sum / n;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		Array.Clear(_highs1);
		Array.Clear(_lows1);
		Array.Clear(_highs2);
		Array.Clear(_lows2);
		Array.Clear(_fastKHistory);
		Array.Clear(_slowKHistory);
		_barCount = 0;
		_prevFastK = null;
		_prevSlowK = null;
		_prevFastD = null;
		_prevSlowD = null;
		_fastKCount = 0;
		_slowKCount = 0;

		base.OnReseted();
	}
}