Ver en GitHub

Estrategia de Vhf Sliding Windows

Descripción general

  • Convertida del asesor experto de MetaTrader 5 "VHF EA" por Vladimir Karputov.
  • Usa el indicador Vertical Horizontal Filter (VHF) para clasificar el régimen de mercado como tendencial o en rango.
  • Funciona en cualquier instrumento y marco temporal compatible con StockSharp; simplemente cambia el parámetro de tipo de vela para que coincida con el gráfico deseado.

Lógica de negociación

  1. Suscribirse a la serie de velas seleccionada y calcular el indicador VHF con período VhfPeriod en cada vela terminada.
  2. Mantener dos ventanas deslizantes de valores VHF recientes:
    • Ventana principal (MainWindowSize) – establece el rango VHF general y el punto medio.
    • Ventana de trabajo (WorkingWindowSize) – detecta rupturas a corto plazo por encima o por debajo de la mediana VHF local.
  3. Un régimen de tendencia alcista o bajista se confirma solo cuando el valor VHF actual es mayor que el punto medio de ambas ventanas.
  4. Mientras está en régimen de tendencia, comparar el último precio de cierre con el cierre hace MainWindowSize barras:
    • Cierre más alto que la referencia → el comportamiento predeterminado es abrir/mantener una posición larga.
    • Cierre más bajo que la referencia → el comportamiento predeterminado es abrir/mantener una posición corta.
    • Habilitar ReverseSignals para invertir estas direcciones.
  5. La estrategia cierra cualquier posición abierta cuando el valor VHF cae de nuevo dentro de la zona de rango (el VHF actual no está por encima de ambos puntos medios).
  6. Los cambios de posición se manejan comprando/vendiendo suficiente volumen para cerrar el lado opuesto y abrir el nuevo en una sola orden de mercado.

Parámetros

Parámetro Descripción Predeterminado Notas
MainWindowSize Número de valores VHF en la ventana deslizante principal. 11 Debe ser mayor que WorkingWindowSize.
WorkingWindowSize Número de valores VHF en la ventana secundaria. 7 Proporciona confirmación más rápida de rupturas.
VhfPeriod Período de retrovisión del Vertical Horizontal Filter. 9 Determina la sensibilidad del indicador.
Volume Volumen de orden (lotes) utilizado para nuevas entradas. 1 Se agrega al valor de posición actual absoluto al cambiar dirección.
ReverseSignals Invertir la lógica largo/corto derivada de la dirección del precio. true Coincide con el comportamiento predeterminado del EA original.
CandleType Marco temporal y tipo de vela para la suscripción de datos. Marco temporal de 15 minutos Cambiar para adaptar la estrategia a otros gráficos.

Gestión monetaria y salidas

  • La estrategia siempre opera con un volumen fijo definido por Volume.
  • La gestión de stop protector se delega al asistente integrado StartProtection() de StockSharp, que cierra de forma segura posiciones residuales inesperadas.
  • No se codifican objetivos de stop-loss o take-profit; las salidas dependen del cambio de régimen detectado por VHF.

Notas de implementación

  • Usa la API de suscripción de velas de alto nivel con vinculación de indicadores, siguiendo las directrices del proyecto.
  • Un indicador personalizado Vertical Horizontal Filter idéntico a la versión MQL está integrado en la estrategia.
  • Las declaraciones de registro describen cada cambio de posición y transición de régimen para facilitar la depuración.
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;

public class VhfSlidingWindowsStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public VhfSlidingWindowsStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}