Auf GitHub ansehen

Volume Trader-Strategie

Übersicht

  • 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

  1. Abonniert Kerzen, die durch CandleType definiert sind (Standard: 1-Stunden-Zeitrahmen), und liest deren Tick-Volumen (TotalVolume).
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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;
	}
}