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:
EURUSD foi negociado com EURJPY e USDJPY como confirmações.
GBPUSD foi negociado com GBPJPY e USDJPY.
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
Para cada instrumento configurado, um StochasticOscillator com os parâmetros selecionados é calculado nas velas concluídas.
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.
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.
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).
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();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class resonance_hunter_strategy(Strategy):
def __init__(self):
super(resonance_hunter_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._fast_k_period = self.Param("FastKPeriod", 8)
self._slow_k_period = self.Param("SlowKPeriod", 21)
self._d_period = self.Param("DPeriod", 3)
self._highs = []
self._lows = []
self._bar_count = 0
self._fast_k_history = []
self._slow_k_history = []
self._prev_fast_k = None
self._prev_slow_k = None
self._prev_fast_d = None
self._prev_slow_d = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def FastKPeriod(self):
return self._fast_k_period.Value
@FastKPeriod.setter
def FastKPeriod(self, value):
self._fast_k_period.Value = value
@property
def SlowKPeriod(self):
return self._slow_k_period.Value
@SlowKPeriod.setter
def SlowKPeriod(self, value):
self._slow_k_period.Value = value
@property
def DPeriod(self):
return self._d_period.Value
@DPeriod.setter
def DPeriod(self, value):
self._d_period.Value = value
def OnReseted(self):
super(resonance_hunter_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._bar_count = 0
self._fast_k_history = []
self._slow_k_history = []
self._prev_fast_k = None
self._prev_slow_k = None
self._prev_fast_d = None
self._prev_slow_d = None
def OnStarted2(self, time):
super(resonance_hunter_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
self._bar_count = 0
self._fast_k_history = []
self._slow_k_history = []
self._prev_fast_k = None
self._prev_slow_k = None
self._prev_fast_d = None
self._prev_slow_d = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _calc_stochastic_k(self, close, period):
if self._bar_count < period:
return None
highs_slice = self._highs[-period:]
lows_slice = self._lows[-period:]
hh = max(highs_slice)
ll = min(lows_slice)
r = hh - ll
if r <= 0:
return 50.0
return (close - ll) / r * 100.0
def _add_to_smoothing(self, history, value, period):
history.append(value)
while len(history) > period:
history.pop(0)
if len(history) < period:
return None
return sum(history) / len(history)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
self._highs.append(high)
self._lows.append(low)
self._bar_count += 1
# Keep buffer manageable
max_buf = max(self.SlowKPeriod, self.FastKPeriod) + 10
while len(self._highs) > max_buf:
self._highs.pop(0)
self._lows.pop(0)
if self._bar_count < self.SlowKPeriod:
return
fast_k = self._calc_stochastic_k(close, self.FastKPeriod)
slow_k = self._calc_stochastic_k(close, self.SlowKPeriod)
if fast_k is None or slow_k is None:
return
d_period = self.DPeriod
fast_d = self._add_to_smoothing(self._fast_k_history, fast_k, d_period)
slow_d = self._add_to_smoothing(self._slow_k_history, slow_k, d_period)
if (fast_d is None or slow_d is None or
self._prev_fast_k is None or self._prev_slow_k is None or
self._prev_fast_d is None or self._prev_slow_d is None):
self._prev_fast_k = fast_k
self._prev_slow_k = slow_k
self._prev_fast_d = fast_d
self._prev_slow_d = slow_d
return
# Resonance buy: both stochastics cross above their D lines
fast_bull_cross = self._prev_fast_k < self._prev_fast_d and fast_k > fast_d
slow_bull_cross = self._prev_slow_k < self._prev_slow_d and slow_k > slow_d
both_oversold = fast_k < 30 and slow_k < 30
# Resonance sell: both stochastics cross below their D lines
fast_bear_cross = self._prev_fast_k > self._prev_fast_d and fast_k < fast_d
slow_bear_cross = self._prev_slow_k > self._prev_slow_d and slow_k < slow_d
both_overbought = fast_k > 70 and slow_k > 70
buy_signal = (fast_bull_cross and (slow_bull_cross or slow_k > slow_d)) and both_oversold
sell_signal = (fast_bear_cross and (slow_bear_cross or slow_k < slow_d)) and both_overbought
if buy_signal:
if self.Position <= 0:
self.BuyMarket()
elif sell_signal:
if self.Position >= 0:
self.SellMarket()
self._prev_fast_k = fast_k
self._prev_slow_k = slow_k
self._prev_fast_d = fast_d
self._prev_slow_d = slow_d
def CreateClone(self):
return resonance_hunter_strategy()