Port do especialista MetaTrader 5 "Volume trader" (ID 21050) de Vladimir Karputov.
Recriado sobre a API de estratégia de alto nível do StockSharp.
Opera na direção da mudança mais recente de volume de tick enquanto um filtro de sessão de trading personalizado está ativo.
Lógica de Trading
Assina velas definidas por CandleType (padrão: período de 1 hora) e lê seu volume de tick (TotalVolume).
Em cada vela finalizada, a estratégia compara os volumes das duas velas fechadas anteriores, emulando o script MQL5 que é executado no nascimento de uma nova barra.
Se o volume mais recente é maior que o anterior e não há posição comprada, a estratégia compra contratos de Volume e adicionalmente cobre uma posição vendida existente.
Se o volume mais recente é menor que o anterior e não há posição vendida, a estratégia vende contratos de Volume e adicionalmente fecha uma posição comprada existente.
Sinais de trading são ignorados quando o horário de abertura da próxima barra cai fora da janela [StartHour, EndHour]. O intervalo padrão 09:00–18:00 replica as entradas originais.
Nenhum stop loss ou take profit é definido por padrão; a estratégia simplesmente reverte no sinal oposto.
Gestão de Ordens
Ordens de entrada são enviadas via BuyMarket ou SellMarket para inverter a posição imediatamente no início de uma nova vela.
Quando um sinal de reversão aparece, a estratégia automaticamente negocia o tamanho absoluto da posição mais o Volume configurado, garantindo que a posição anterior seja fechada antes que uma nova seja aberta.
Não há lógica de dimensionamento de posição incorporada além do parâmetro fixo Volume.
Parâmetros
Parâmetro
Padrão
Descrição
CandleType
Período de 1 hora
Série de velas usada para calcular o volume de tick. Ajustar para corresponder ao período usado no especialista original.
StartHour
9
Hora inclusiva (0–23) que marca o início da sessão de trading. Sinais antes desta hora são ignorados.
EndHour
18
Hora inclusiva (0–23) que marca o fim da sessão de trading. Sinais após esta hora são ignorados.
Volume
0.1
Volume de ordem para novas entradas. Também usado ao inverter uma posição existente.
Notas de Uso
Garantir que a fonte de dados forneça volume de tick nas mensagens de vela. Quando apenas o volume negociado real estiver disponível, o comportamento seguirá esses dados.
Alinhar o parâmetro CandleType com o período do gráfico que se deseja reproduzir do MetaTrader.
Considerar envolver a estratégia com gestão de risco externa (stop loss, take profit, limites de perda diária) se exigido pelas regras de trading.
A estratégia chama LogInfo quando uma posição é aberta, facilitando a auditoria de decisões de sinal no log.
Diferenças vs. Implementação MQL Original
Usa o pipeline de assinatura de velas do StockSharp em vez de chamar CopyTickVolume manualmente.
A filtragem de sessão baseia-se no CloseTime da vela finalizada (a hora de início da próxima barra) para permanecer alinhado com a lógica MQL que é executada na abertura da barra.
A execução de ordens é tratada por helpers de API de alto nível (BuyMarket, SellMarket) em vez de chamadas diretas 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class volume_trader_strategy(Strategy):
"""Volume-based reversal: rising volume suggests long, falling volume suggests short."""
def __init__(self):
super(volume_trader_strategy, self).__init__()
self._start_hour = self.Param("StartHour", 9).SetDisplay("Start Hour", "Trading session start", "Session")
self._end_hour = self.Param("EndHour", 18).SetDisplay("End Hour", "Trading session end", "Session")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(volume_trader_strategy, self).OnReseted()
self._prev_vol = None
self._prev_prev_vol = None
def OnStarted2(self, time):
super(volume_trader_strategy, self).OnStarted2(time)
self._prev_vol = None
self._prev_prev_vol = None
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
vol = float(candle.TotalVolume)
if self._prev_vol is not None and self._prev_prev_vol is not None:
hour = candle.CloseTime.Hour
start_h = self._start_hour.Value
end_h = self._end_hour.Value
in_session = hour >= start_h and hour <= end_h
if in_session:
if self._prev_vol > self._prev_prev_vol * 1.1 and self.Position <= 0:
vol_to_trade = self.Volume + (abs(self.Position) if self.Position < 0 else 0)
if vol_to_trade > 0:
self.BuyMarket(vol_to_trade)
elif self._prev_vol < self._prev_prev_vol * 0.9 and self.Position >= 0:
vol_to_trade = self.Volume + (abs(self.Position) if self.Position > 0 else 0)
if vol_to_trade > 0:
self.SellMarket(vol_to_trade)
self._prev_prev_vol = self._prev_vol
self._prev_vol = vol
def CreateClone(self):
return volume_trader_strategy()