Ver en GitHub

Estrategia Volume Trader V2

Descripción general

Volume Trader V2 es una conversión directa del MetaTrader asesor experto Volume_trader_v2_www_forex-instruments_info.mq4. El sistema original observa cómo evoluciona el volumen total de las últimas velas y utiliza este flujo a corto plazo para decidir si debe estar activa una simple exposición larga o corta. El puerto StockSharp mantiene el comportamiento de una posición a la vez, el filtro de hora del día y el requisito de actuar solo una vez por vela completa.

La estrategia se suscribe a una serie de velas configurables y almacena en caché el volumen de las dos últimas velas terminadas. Cuando se cierra una nueva barra, los volúmenes de las dos barras anteriores (MetaTrader's Volume[1] y Volume[2]) se comparan y se produce una dirección comercial actualizada:

  • Volume[1] < Volume[2] genera un sesgo largo.
  • Volume[1] > Volume[2] genera un sesgo corto.
  • Volúmenes iguales u horarios de negociación desactivados eliminan cualquier exposición abierta.

Antes de enviar una nueva orden, la posición actual se aplana si apunta en la dirección opuesta para que la implementación de StockSharp coincida con el ciclo de vida de la orden MetaTrader.

Parámetros

Nombre Predeterminado Descripción
CandleType marco de tiempo de 5 minutos Tipo de datos solicitado de SubscribeCandles. Configúrelo para que coincida con el período del gráfico utilizado en MetaTrader.
StartHour 8 Primera hora de negociación (inclusive). Las señales fuera de la ventana se ignoran y se cierra cualquier posición.
EndHour 20 Última hora de negociación (inclusive). Cuando la vela actual comienza después de esta hora, la estrategia se mantiene plana.
TradeVolume 0.1 Tamaño de lote replicado de EA. El valor también se asigna a Strategy.Volume por lo que los métodos auxiliares utilizan la misma cantidad.

Todos los parámetros son instancias StrategyParam<T> regulares, por lo que pueden optimizarse o exponerse a través de la interfaz de usuario.

Lógica de trading

  1. Manipule únicamente velas terminadas para garantizar la paridad barra por barra con el EA.
  2. Almacene en caché los equivalentes de Volume[1] y Volume[2] en _previousVolume y _twoBarsAgoVolume antes de cualquier evaluación de señal.
  3. Valide que la hora de inicio de la vela sea entre StartHour y EndHour (inclusive). Fuera de este rango, cualquier posición activa se cierra y no se crean nuevas órdenes.
  4. Calcule la dirección deseada:
    • Largo cuando el volumen más reciente es inferior al de la barra anterior.
    • Corto cuando el volumen más reciente es superior a la barra anterior.
    • Neutral en caso contrario.
  5. Si la dirección deseada difiere de la posición actual, cierre primero la posición opuesta (BuyMarket(-Position) o SellMarket(Position)).
  6. Ingrese la nueva posición usando el TradeVolume configurado solo cuando la estrategia sea plana o esté posicionada en la dirección opuesta.
  7. Actualice los volúmenes almacenados en caché para que el siguiente ciclo aún compare las dos últimas velas completadas.

Este flujo garantiza que no se realicen órdenes mientras una vela aún se esté construyendo y que la estrategia StockSharp reaccione exactamente una vez por barra, al igual que la implementación MetaTrader que se basó en LastBarChecked.

Notas adicionales

  • Se llama a StartProtection() en OnStarted para reutilizar el asistente de protección del marco que realiza un seguimiento de la posición actual.
  • La propiedad Comment refleja los mensajes de diagnóstico EA ("Up trend", "Down trend", "No trend..." o "Trading paused") para simplificar la supervisión.
  • La estrategia no mantiene colecciones adicionales y aprovecha la suscripción de vela de alto nivel API de acuerdo con las pautas del proyecto.
  • Establezca el tipo de vela, la seguridad y el volumen para que coincidan con el instrumento y el período de tiempo utilizados originalmente en MetaTrader para obtener resultados comparables.
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>
/// Volume Trader V2 strategy converted from the MetaTrader expert Volume_trader_v2_www_forex-instruments_info.mq4.
/// Follows the original logic by comparing the volume of the last two finished candles and trading only during configured hours.
/// </summary>
public class VolumeTraderV2Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _startHour;
	private readonly StrategyParam<int> _endHour;
	private readonly StrategyParam<decimal> _tradeVolume;

	private decimal? _previousVolume;
	private decimal? _twoBarsAgoVolume;

	/// <summary>
	/// Initializes a new instance of the <see cref="VolumeTraderV2Strategy"/> class.
	/// </summary>
	public VolumeTraderV2Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromDays(1).TimeFrame())
		.SetDisplay("Candle Type", "Time frame used to request candles", "Data");

		_startHour = Param(nameof(StartHour), 0)
		.SetDisplay("Start Hour", "First hour (inclusive) when trading is allowed", "Trading")
		.SetRange(0, 23);

		_endHour = Param(nameof(EndHour), 23)
		.SetDisplay("End Hour", "Last hour (inclusive) when trading is allowed", "Trading")
		.SetRange(0, 23);

		_tradeVolume = Param(nameof(TradeVolume), 0.1m)
		.SetDisplay("Trade Volume", "Order volume replicated from the original EA", "Trading")
		.SetGreaterThanZero();

		Volume = TradeVolume;
	}

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

	/// <summary>
	/// First trading hour (inclusive).
	/// </summary>
	public int StartHour
	{
		get => _startHour.Value;
		set => _startHour.Value = value;
	}

	/// <summary>
	/// Last trading hour (inclusive).
	/// </summary>
	public int EndHour
	{
		get => _endHour.Value;
		set => _endHour.Value = value;
	}

	/// <summary>
	/// Default order volume for market operations.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set
		{
			_tradeVolume.Value = value;
			Volume = value;
		}
	}

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

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

		// Drop cached volume values so the warm-up sequence matches the EA behavior after a reset.
		_previousVolume = null;
		_twoBarsAgoVolume = null;
	}

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

		// Subscribe to candles and process them with the same granularity as the original indicator buffers.
		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(ProcessCandle)
		.Start();

		StartProtection(null, null);
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		// Only act on finished candles to replicate the bar-by-bar logic.
		if (candle.State != CandleStates.Finished)
		return;

		var currentVolume = candle.TotalVolume;

		// Collect the first two candles before generating signals.
		if (_previousVolume is null)
		{
			_previousVolume = currentVolume;
			return;
		}

		if (_twoBarsAgoVolume is null)
		{
			_twoBarsAgoVolume = _previousVolume;
			_previousVolume = currentVolume;
			return;
		}

		var volume1 = _previousVolume.Value;
		var volume2 = _twoBarsAgoVolume.Value;

		var hour = candle.OpenTime.Hour;
		var hourValid = hour >= StartHour && hour <= EndHour;

		var shouldGoLong = hourValid && volume1 < volume2;
		var shouldGoShort = hourValid && volume1 > volume2;

		var comment = !hourValid
			? "Trading paused"
			: shouldGoLong
			? "Up trend"
			: shouldGoShort
			? "Down trend"
			: "No trend...";

		if (!shouldGoLong && !shouldGoShort)
		{
			// Exit the market when no direction is active (equal volume or outside trading hours).
			ClosePosition();
		}
		else if (shouldGoLong)
		{
			// Flatten any short position before opening a new long trade.
			if (Position < 0)
			BuyMarket();

			if (Position <= 0)
			BuyMarket();
		}
		else if (shouldGoShort)
		{
			// Flatten any long position before opening a new short trade.
			if (Position > 0)
			SellMarket();

			if (Position >= 0)
			SellMarket();
		}

		// Shift the cached volumes to emulate Volume[1] and Volume[2] from MetaTrader.
		_twoBarsAgoVolume = _previousVolume;
		_previousVolume = currentVolume;
	}

	private void ClosePosition()
	{
		// Mirror the EA behavior by leaving the market whenever the signal is neutral.
		if (Position > 0)
		{
			SellMarket();
		}
		else if (Position < 0)
		{
			BuyMarket();
		}
	}
}