La estrategia Resonance Hunter es el StockSharp puerto del MetaTrader asesor experto Exp_ResonanceHunter. Supervisa tres pares de divisas correlacionados por ranura y busca impulso sincrónico en sus osciladores Stochastic. Cuando los osciladores resuenan en la misma dirección, la estrategia abre una posición en el símbolo principal, mientras que los símbolos secundario y de confirmación actúan como filtros. La operación se cierra tan pronto como el instrumento principal pierde impulso o cuando se alcanza el stop loss configurado.
Hay tres ranuras preconfiguradas:
El EURUSD negoció con EURJPY y USDJPY como confirmaciones.
GBPUSD negoció con GBPJPY y USDJPY.
AUDUSD negoció con AUDJPY y USDJPY.
Cada ranura se puede habilitar o deshabilitar de forma independiente y puede usar su propio período de tiempo y parámetros de indicador.
Parámetros
Todos los parámetros están agrupados por ranura (Ranura 1–3). Cada grupo comparte las siguientes configuraciones:
Parámetro
Descripción
{Slot} Enabled
Permite el comercio de la ranura.
{Slot} Primary
Instrumento negociado por la estrategia y utilizado para señales de salida.
{Slot} Secondary
Segundo instrumento que participa en la verificación de resonancia.
{Slot} Confirmation
Tercer instrumento utilizado en la verificación de resonancia.
{Slot} Candle Type
Plazo aplicado a los tres instrumentos (predeterminado = 1 hora).
{Slot} K Period
Stochastic %K retrospectiva.
{Slot} D Period
Período de suavizado para %D.
{Slot} Slowing
Suavizado adicional para %K.
{Slot} Volume
Volumen de pedidos en lotes. La exposición opuesta existente se compensa.
{Slot} Stop Loss
Distancia de stop-loss estilo MetaTrader en puntos. Establezca en 0 para desactivar la parada de protección.
Lógica de trading
Para cada instrumento configurado, se calcula un StochasticOscillator con los parámetros seleccionados en las velas completadas.
Una vez que las últimas velas de los tres instrumentos comparten el mismo tiempo de apertura, se evalúan las diferencias %K - %D:
La diferencia positiva marca un impulso ascendente (Up), la diferencia negativa marca un impulso a la baja (Down).
Reglas de consistencia adicionales del indicador original ajustan los impulsos comparando la magnitud de cada par.
Una entrada larga se genera cuando los tres impulsos apuntan hacia arriba. Aparece una entrada corta cuando los tres impulsos apuntan hacia abajo.
Antes de enviar nuevas órdenes, la estrategia cierra las posiciones existentes si el símbolo principal indica un impulso opuesto (refleja los buffers UpStop/DnStop del indicador).
Después de ingresar una posición, se calcula un precio de parada de protección utilizando el último cierre y la distancia {Slot} Stop Loss. En cada nueva vela primaria se comprueba el tope y, si se supera, la posición se cierra inmediatamente.
Los pedidos se enrutan a través de BuyMarket/SellMarket. La exposición existente en el símbolo principal se compensa para que la estrategia pueda revertirse directamente cuando sea necesario.
Notas
La estrategia requiere datos de velas sincronizados para los tres instrumentos dentro de cada ranura. Si un símbolo va por detrás, la señal se pospone hasta que las marcas de tiempo de las barras se alineen.
Los niveles de stop se emulan dentro de la estrategia (no se envían órdenes de stop reales) para que coincidan con el comportamiento MetaTrader.
Los valores de parámetros predeterminados reproducen el asesor experto original, pero se pueden optimizar a través de la interfaz 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()