Volume Trader V2 ist eine direkte Konvertierung des MetaTrader Expert Advisors Volume_trader_v2_www_forex-instruments_info.mq4. Das ursprüngliche System beobachtet, wie sich das Gesamtvolumen der letzten Kerzen entwickelt, und nutzt diesen kurzfristigen Fluss, um zu entscheiden, ob ein einfaches Long- oder Short-Engagement aktiv sein soll. Der StockSharp-Port behält das Verhalten bei jeweils einer Position, den Tageszeitfilter und die Anforderung bei, nur einmal pro abgeschlossener Kerze zu handeln.
Die Strategie abonniert eine konfigurierbare Kerzenserie und speichert das Volumen der letzten beiden fertigen Kerzen zwischen. Wenn ein neuer Balken schließt, werden die Volumina der beiden vorherigen Balken (MetaTraders Volume[1] und Volume[2]) verglichen und eine aktualisierte Handelsrichtung erstellt:
Volume[1] < Volume[2] erzeugt eine lange Tendenz.
Volume[1] > Volume[2] erzeugt eine kurze Tendenz.
Bei gleichen Volumina oder deaktivierten Handelszeiten werden alle offenen Positionen entfernt.
Vor dem Senden einer neuen Bestellung wird die aktuelle Position abgeflacht, wenn sie in die entgegengesetzte Richtung zeigt, sodass die StockSharp-Implementierung mit dem MetaTrader-Bestelllebenszyklus übereinstimmt.
Parameter
Name
Standard
Beschreibung
CandleType
Zeitrahmen von 5 Minuten
Von SubscribeCandles angeforderter Datentyp. Stellen Sie es so ein, dass es dem in MetaTrader verwendeten Diagrammzeitraum entspricht.
StartHour
8
Erste Handelsstunde (einschließlich). Signale außerhalb des Fensters werden ignoriert und jede Position wird geschlossen.
EndHour
20
Letzte Handelsstunde (einschließlich). Wenn die aktuelle Kerze nach dieser Stunde beginnt, bleibt die Strategie flach.
TradeVolume
0,1
Losgröße, repliziert aus EA. Der Wert wird auch Strategy.Volume zugewiesen, sodass Hilfsmethoden denselben Betrag verwenden.
Alle Parameter sind reguläre StrategyParam<T>-Instanzen, sodass sie über die Benutzeroberfläche optimiert oder verfügbar gemacht werden können.
Handelslogik
Behandeln Sie nur fertige Kerzen, um die Bar-für-Bar-Parität mit dem EA zu gewährleisten.
Zwischenspeichern Sie Volume[1]- und Volume[2]-Äquivalente in _previousVolume und _twoBarsAgoVolume, bevor Sie ein Signal auswerten.
Stellen Sie sicher, dass die Startzeit der Kerze zwischen StartHour und EndHour (einschließlich) liegt. Außerhalb dieses Bereichs wird jede aktive Position geschlossen und es werden keine neuen Aufträge erstellt.
Berechnen Sie die gewünschte Richtung:
Long, wenn das letzte Volumen niedriger ist als der vorherige Balken.
Short, wenn das letzte Volumen höher ist als der vorherige Balken.
Ansonsten neutral.
Wenn die gewünschte Richtung von der aktuellen Position abweicht, schließen Sie zuerst die entgegengesetzte Position (BuyMarket(-Position) oder SellMarket(Position)).
Geben Sie die neue Position nur dann mit dem konfigurierten TradeVolume ein, wenn die Strategie flach ist oder in die entgegengesetzte Richtung positioniert ist.
Aktualisieren Sie die zwischengespeicherten Volumina, damit im nächsten Zyklus immer noch die beiden letzten abgeschlossenen Kerzen verglichen werden.
Dieser Ablauf garantiert, dass keine Orders platziert werden, während sich eine Kerze noch aufbaut, und dass die StockSharp-Strategie genau einmal pro Balken reagiert, genau wie die MetaTrader-Implementierung, die auf LastBarChecked basierte.
Zusätzliche Hinweise
StartProtection() wird in OnStarted aufgerufen, um den Framework-Schutzhelfer wiederzuverwenden, der die aktuelle Position verfolgt.
Die Eigenschaft Comment spiegelt die EA-Diagnosemeldungen ("Up trend", "Down trend", "No trend..." oder "Trading paused") wider, um die Überwachung zu vereinfachen.
Die Strategie verwaltet keine zusätzlichen Sammlungen und nutzt das High-Level-Kerzenabonnement API gemäß den Projektrichtlinien.
Stellen Sie Kerzentyp, Sicherheit und Volumen so ein, dass sie mit dem ursprünglich in MetaTrader verwendeten Instrument und Zeitrahmen übereinstimmen, um vergleichbare Ergebnisse zu erzielen.
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()