Ver no GitHub

Estratégia do Volume Trader V2

Visão geral

Volume Trader V2 é uma conversão direta do MetaTrader consultor especialista Volume_trader_v2_www_forex-instruments_info.mq4. O sistema original observa como o volume total das últimas velas evolui e utiliza esse fluxo de curto prazo para decidir se uma simples exposição longa ou curta deve estar ativa. A porta StockSharp mantém o comportamento de uma posição por vez, o filtro de hora do dia e o requisito de agir apenas uma vez por vela concluída.

A estratégia assina uma série de velas configuráveis e armazena em cache o volume das duas últimas velas finalizadas. Quando uma nova barra fecha, os volumes das duas barras anteriores (Volume[1] e Volume[2] de MetaTrader) são comparados e uma direção de negociação atualizada é produzida:

  • Volume[1] < Volume[2] gera uma tendência longa.
  • Volume[1] > Volume[2] gera uma tendência curta.
  • Volumes iguais ou horários de negociação desativados removem qualquer exposição aberta.

Antes de enviar um novo pedido, a posição atual é nivelada se apontar na direção oposta, para que a implementação StockSharp corresponda ao ciclo de vida do pedido MetaTrader.

Parâmetros

Nome Padrão Descrição
CandleType Período de 5 minutos Tipo de dados solicitado de SubscribeCandles. Defina-o para corresponder ao período do gráfico usado em MetaTrader.
StartHour 8 Primeira hora de negociação (inclusive). Os sinais fora da janela são ignorados e qualquer posição é fechada.
EndHour 20 Última hora de negociação (inclusive). Quando a vela atual começa após esta hora, a estratégia permanece estável.
TradeVolume 0,1 Tamanho do lote replicado de EA. O valor também é atribuído a Strategy.Volume para que os métodos auxiliares usem a mesma quantidade.

Todos os parâmetros são instâncias StrategyParam<T> regulares para que possam ser otimizados ou expostos por meio da IU.

Lógica de negociação

  1. Lide apenas com velas finalizadas para garantir a paridade barra por barra com o EA.
  2. Armazene em cache Volume[1] e Volume[2] equivalentes em _previousVolume e _twoBarsAgoVolume antes de qualquer avaliação de sinal.
  3. Valide se o horário de início da vela está entre StartHour e EndHour (inclusive). Fora deste intervalo, qualquer posição ativa é fechada e nenhuma nova ordem é criada.
  4. Calcule a direção desejada:
    • Longo quando o volume mais recente é inferior à barra anterior.
    • Venda quando o volume mais recente é superior à barra anterior.
    • Caso contrário, neutro.
  5. Se a direção desejada for diferente da posição atual, feche primeiro a posição oposta (BuyMarket(-Position) ou SellMarket(Position)).
  6. Insira a nova posição usando o TradeVolume configurado somente quando a estratégia estiver plana ou posicionada na direção oposta.
  7. Atualize os volumes em cache para que o próximo ciclo ainda compare as duas últimas velas concluídas.

Este fluxo garante que nenhum pedido seja feito enquanto uma vela ainda está sendo construída e que a estratégia StockSharp reage exatamente uma vez por barra, assim como a implementação MetaTrader que dependia de LastBarChecked.

Notas adicionais

  • StartProtection() é chamado em OnStarted para reutilizar o auxiliar de proteção da estrutura que monitora a posição atual.
  • A propriedade Comment espelha as mensagens de diagnóstico EA ("Up trend", "Down trend", "No trend..." ou "Trading paused") para simplificar o monitoramento.
  • A estratégia não mantém cobranças extras e aproveita a assinatura de vela de alto nível API de acordo com as diretrizes do projeto.
  • Defina o tipo de vela, a segurança e o volume para corresponder ao instrumento e ao período usado originalmente em MetaTrader para obter resultados comparáveis.
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();
		}
	}
}