Ver en GitHub

Estrategia Volume Trader

Descripción General

  • Port del asesor experto de MetaTrader 5 "Volume trader" (ID 21050) de Vladimir Karputov.
  • Recreado sobre la API de estrategia de alto nivel de StockSharp.
  • Opera en la dirección del cambio más reciente de volumen de tick mientras un filtro de sesión de trading personalizado está activo.

Lógica de Trading

  1. Se suscribe a las velas definidas por CandleType (predeterminado: marco temporal de 1 hora) y lee su volumen de tick (TotalVolume).
  2. En cada vela finalizada, la estrategia compara los volúmenes de las dos velas cerradas previas, emulando el script MQL5 que se ejecuta al nacer una nueva barra.
  3. Si el volumen más reciente es mayor que el anterior y no hay posición larga, la estrategia compra contratos de Volume y adicionalmente cubre una posición corta existente.
  4. Si el volumen más reciente es menor que el anterior y no hay posición corta, la estrategia vende contratos de Volume y adicionalmente cierra una posición larga existente.
  5. Las señales de trading se ignoran cuando la hora de apertura de la siguiente barra cae fuera de la ventana [StartHour, EndHour]. El rango predeterminado 09:00–18:00 replica las entradas originales.
  6. No se define stop loss ni take profit por defecto; la estrategia simplemente se revierte ante la señal opuesta.

Gestión de Órdenes

  • Las órdenes de entrada se envían mediante BuyMarket o SellMarket para voltear la posición inmediatamente al inicio de una nueva vela.
  • Cuando aparece una señal de reversión, la estrategia automáticamente negocia el tamaño absoluto de la posición más el Volume configurado, asegurando que la posición previa se cierre antes de que se abra una nueva.
  • No hay lógica de sizing de posición incorporada más allá del parámetro fijo Volume.

Parámetros

Parámetro Predeterminado Descripción
CandleType Marco temporal de 1 hora Serie de velas usada para calcular el volumen de tick. Ajustar para que coincida con el marco temporal usado en el experto original.
StartHour 9 Hora inclusiva (0–23) que marca el inicio de la sesión de trading. Las señales antes de esta hora se ignoran.
EndHour 18 Hora inclusiva (0–23) que marca el fin de la sesión de trading. Las señales después de esta hora se ignoran.
Volume 0.1 Volumen de orden para nuevas entradas. También se usa al voltear una posición existente.

Notas de Uso

  • Asegurarse de que la fuente de datos proporciona volumen de tick en los mensajes de vela. Cuando solo está disponible el volumen real negociado, el comportamiento seguirá esos datos.
  • Alinear el parámetro CandleType con el marco temporal del gráfico que se desea reproducir de MetaTrader.
  • Considerar envolver la estrategia con gestión de riesgo externa (stop loss, take profit, límites de pérdida diaria) si lo requieren las reglas de trading.
  • La estrategia llama a LogInfo cuando se abre una posición, facilitando la auditoría de decisiones de señal en el registro.

Diferencias vs. Implementación MQL Original

  • Usa el pipeline de suscripción de velas de StockSharp en lugar de llamar manualmente a CopyTickVolume.
  • El filtrado de sesión se basa en el CloseTime de la vela finalizada (la hora de inicio de la siguiente barra) para mantenerse alineado con la lógica MQL que se ejecuta en la apertura de barra.
  • La ejecución de órdenes se maneja a través de ayudantes API de alto nivel (BuyMarket, SellMarket) en lugar de llamadas directas a CTrade.
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 based reversal strategy that reacts to increasing or decreasing tick volume.
/// </summary>
public class VolumeTraderStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _startHour;
	private readonly StrategyParam<int> _endHour;

	private decimal? _previousVolume;
	private decimal? _previousPreviousVolume;

	/// <summary>
	/// Candle type used to calculate the signals.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Inclusive start hour of the trading session.
	/// </summary>
	public int StartHour
	{
		get => _startHour.Value;
		set => _startHour.Value = value;
	}

	/// <summary>
	/// Inclusive end hour of the trading session.
	/// </summary>
	public int EndHour
	{
		get => _endHour.Value;
		set => _endHour.Value = value;
	}


	/// <summary>
	/// Initializes a new instance of <see cref="VolumeTraderStrategy"/>.
	/// </summary>
	public VolumeTraderStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for signal calculation", "General");

		_startHour = Param(nameof(StartHour), 9)
			.SetDisplay("Start Hour", "Inclusive start hour for trading", "Session")
			.SetRange(0, 23);

		_endHour = Param(nameof(EndHour), 18)
			.SetDisplay("End Hour", "Inclusive end hour for trading", "Session")
			.SetRange(0, 23);

	}

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

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

		_previousVolume = null;
		_previousPreviousVolume = null;
	}

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

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

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

	private void ProcessCandle(ICandleMessage candle)
	{
		// Wait until the candle is finished to avoid partial data.
		if (candle.State != CandleStates.Finished)
			return;

		var currentVolume = candle.TotalVolume;

		if (_previousVolume.HasValue && _previousPreviousVolume.HasValue)
		{
			// MQL version trades at the open of the next bar, so use the next bar time for the filter.
			var nextBarTime = candle.CloseTime;
			var hour = nextBarTime.Hour;
			var inSession = hour >= StartHour && hour <= EndHour;

			if (inSession && IsFormedAndOnlineAndAllowTrading())
			{
				var prevVolume = _previousVolume.Value;
				var prevPrevVolume = _previousPreviousVolume.Value;

				// Rising volume suggests upward pressure -> go long.
				if (prevVolume > prevPrevVolume * 1.1m && Position <= 0)
				{
					var volumeToTrade = Volume + (Position < 0 ? Math.Abs(Position) : 0m);

					if (volumeToTrade > 0)
					{
						BuyMarket(volumeToTrade);
						LogInfo($"Volume increased from {prevPrevVolume} to {prevVolume}. Opening long position.");
					}
				}
				// Falling volume suggests weakening demand -> go short.
				else if (prevVolume < prevPrevVolume * 0.9m && Position >= 0)
				{
					var volumeToTrade = Volume + (Position > 0 ? Math.Abs(Position) : 0m);

					if (volumeToTrade > 0)
					{
						SellMarket(volumeToTrade);
						LogInfo($"Volume decreased from {prevPrevVolume} to {prevVolume}. Opening short position.");
					}
				}
			}
		}

		// Shift stored volumes so the latest closed candle becomes the previous reference.
		_previousPreviousVolume = _previousVolume;
		_previousVolume = currentVolume;
	}
}