Port des MetaTrader 5-Experten "Volume trader" (ID 21050) von Vladimir Karputov.
Auf der hochrangigen StockSharp-Strategie-API neu erstellt.
Handelt in Richtung der letzten Tick-Volumen-Änderung, während ein benutzerdefinierter Handelssitzungsfilter aktiv ist.
Handelslogik
Abonniert Kerzen, die durch CandleType definiert sind (Standard: 1-Stunden-Zeitrahmen), und liest deren Tick-Volumen (TotalVolume).
Bei jeder abgeschlossenen Kerze vergleicht die Strategie die Volumina der beiden vorherigen geschlossenen Kerzen und ahmt das MQL5-Skript nach, das bei der Geburt einer neuen Kerze läuft.
Wenn das neuere Volumen höher als das davor ist und keine Long-Position vorhanden ist, kauft die Strategie Volume Kontrakte und deckt zusätzlich eine bestehende Short-Position ab.
Wenn das neuere Volumen niedriger als das davor ist und keine Short-Position vorhanden ist, verkauft die Strategie Volume Kontrakte und schließt zusätzlich eine bestehende Long-Position.
Handelssignale werden ignoriert, wenn die Eröffnungszeit des nächsten Balkens außerhalb des [StartHour, EndHour]-Fensters liegt. Der Standardbereich 09:00–18:00 repliziert die ursprünglichen Eingaben.
Standardmäßig sind kein Stop-Loss oder Take-Profit definiert; die Strategie kehrt bei gegensätzlichem Signal einfach um.
Auftragsverwaltung
Einstiegsaufträge werden über BuyMarket oder SellMarket gesendet, um die Position sofort beim Beginn einer neuen Kerze zu drehen.
Wenn ein Umkehrsignal erscheint, handelt die Strategie automatisch die absolute Positionsgröße plus das konfigurierte Volume und stellt sicher, dass die vorherige Position geschlossen wird, bevor eine neue eröffnet wird.
Es gibt keine eingebaute Positionsgrößenlogik außer dem festen Volume-Parameter.
Parameter
Parameter
Standard
Beschreibung
CandleType
1-Stunden-Zeitrahmen
Für die Tick-Volumen-Berechnung verwendete Kerzenserie. Anpassen, um mit dem im ursprünglichen Experten verwendeten Zeitrahmen übereinzustimmen.
StartHour
9
Inklusive Stunde (0–23), die den Beginn der Handelssitzung markiert. Signale vor dieser Stunde werden ignoriert.
EndHour
18
Inklusive Stunde (0–23), die das Ende der Handelssitzung markiert. Signale nach dieser Stunde werden ignoriert.
Volume
0.1
Ordervolumen für neue Einstiege. Wird auch beim Umkehren einer bestehenden Position verwendet.
Verwendungshinweise
Sicherstellen, dass die Datenquelle Tick-Volumen in den Kerzen-Nachrichten bereitstellt. Wenn nur das tatsächlich gehandelte Volumen verfügbar ist, folgt das Verhalten diesen Daten.
Den CandleType-Parameter mit dem Chartzeitrahmen abgleichen, den Sie von MetaTrader reproduzieren möchten.
Erwägen, die Strategie mit externem Risikomanagement zu umhüllen (Stop-Loss, Take-Profit, tägliche Verlustlimits), falls dies die Handelsregeln erfordern.
Die Strategie ruft LogInfo auf, wenn eine Position eröffnet wird, was die Überprüfung von Signalentscheidungen im Protokoll erleichtert.
Unterschiede zur ursprünglichen MQL-Implementierung
Verwendet die Kerzen-Abonnement-Pipeline von StockSharp anstatt CopyTickVolume manuell aufzurufen.
Die Sitzungsfilterung basiert auf dem CloseTime der abgeschlossenen Kerze (der Startzeit des nächsten Balkens), um mit der MQL-Logik aligned zu bleiben, die bei Balkeneröffnung ausgeführt wird.
Die Auftragsausführung wird über High-Level-API-Helfer (BuyMarket, SellMarket) abgewickelt statt über direkte CTrade-Aufrufe.
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()