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
Lide apenas com velas finalizadas para garantir a paridade barra por barra com o EA.
Armazene em cache Volume[1] e Volume[2] equivalentes em _previousVolume e _twoBarsAgoVolume antes de qualquer avaliação de sinal.
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.
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.
Se a direção desejada for diferente da posição atual, feche primeiro a posição oposta (BuyMarket(-Position) ou SellMarket(Position)).
Insira a nova posição usando o TradeVolume configurado somente quando a estratégia estiver plana ou posicionada na direção oposta.
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();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class volume_trader_v2_strategy(Strategy):
"""Volume Trader V2: compares volume of the last two finished candles and trades
only during configured hours. Goes long when previous volume < two-bars-ago volume,
short when previous volume > two-bars-ago volume."""
def __init__(self):
super(volume_trader_v2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromDays(1))) \
.SetDisplay("Candle Type", "Time frame used to request candles", "Data")
self._start_hour = self.Param("StartHour", 0) \
.SetDisplay("Start Hour", "First hour (inclusive) when trading is allowed", "Trading")
self._end_hour = self.Param("EndHour", 23) \
.SetDisplay("End Hour", "Last hour (inclusive) when trading is allowed", "Trading")
self._trade_volume = self.Param("TradeVolume", 0.1) \
.SetGreaterThanZero() \
.SetDisplay("Trade Volume", "Order volume replicated from the original EA", "Trading")
self._previous_volume = None
self._two_bars_ago_volume = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def StartHour(self):
return self._start_hour.Value
@property
def EndHour(self):
return self._end_hour.Value
@property
def TradeVolume(self):
return self._trade_volume.Value
def OnReseted(self):
super(volume_trader_v2_strategy, self).OnReseted()
self._previous_volume = None
self._two_bars_ago_volume = None
def OnStarted2(self, time):
super(volume_trader_v2_strategy, self).OnStarted2(time)
self.Volume = float(self.TradeVolume)
self.StartProtection(None, None)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
current_volume = candle.TotalVolume
if self._previous_volume is None:
self._previous_volume = current_volume
return
if self._two_bars_ago_volume is None:
self._two_bars_ago_volume = self._previous_volume
self._previous_volume = current_volume
return
volume1 = self._previous_volume
volume2 = self._two_bars_ago_volume
hour = candle.OpenTime.Hour
hour_valid = hour >= self.StartHour and hour <= self.EndHour
should_go_long = hour_valid and volume1 < volume2
should_go_short = hour_valid and volume1 > volume2
if not should_go_long and not should_go_short:
# Exit position when no direction is active
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
elif should_go_long:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
elif should_go_short:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
self._two_bars_ago_volume = self._previous_volume
self._previous_volume = current_volume
def CreateClone(self):
return volume_trader_v2_strategy()