GitHub で見る

Volume Trader戦略

概要

  • Vladimir KarputovによるMetaTrader 5エキスパートアドバイザー **"Volume trader"(ID 21050)**のポート。
  • StockSharpの高レベル戦略APIの上に再構築。
  • カスタムトレーディングセッションフィルターがアクティブな間、最新のティックボリューム変化の方向に取引します。

トレードロジック

  1. CandleTypeで定義されたキャンドル(デフォルト:1時間時間軸)にサブスクライブし、そのティックボリューム(TotalVolume)を読み取ります。
  2. 完成した各キャンドルで、戦略は前の2つの閉じたキャンドルのボリュームを比較し、新しいバーの誕生時に実行されるMQL5スクリプトを模倣します。
  3. より新しいボリュームが前のものより高く、ロングポジションがない場合、戦略はVolumeコントラクトを買い、さらに既存のショートポジションをカバーします。
  4. より新しいボリュームが前のものより低く、ショートポジションがない場合、戦略はVolumeコントラクトを売り、さらに既存のロングポジションを閉じます。
  5. 次のバーの開始時刻が[StartHour, EndHour]ウィンドウの外にある場合、トレードシグナルは無視されます。デフォルト範囲09:00–18:00は元の入力値を複製します。
  6. デフォルトではストップロスもテイクプロフィットも定義されていません。戦略は反対のシグナルで単純に反転します。

注文管理

  • エントリー注文はBuyMarketまたはSellMarketを通じて送信され、新しいキャンドルの開始時にすぐにポジションを反転します。
  • 反転シグナルが現れると、戦略は自動的に絶対ポジションサイズと設定されたVolumeを取引し、新しいポジションが開かれる前に前のポジションが閉じられることを確保します。
  • 固定のVolumeパラメーター以外のポジションサイズロジックは組み込まれていません。

パラメーター

パラメーター デフォルト 説明
CandleType 1時間時間軸 ティックボリューム計算に使用されるキャンドルシリーズ。元のエキスパートで使用される時間軸に合わせて調整。
StartHour 9 トレーディングセッションの開始を示す包括的な時間(0–23)。この時間より前のシグナルは無視されます。
EndHour 18 トレーディングセッションの終了を示す包括的な時間(0–23)。この時間より後のシグナルは無視されます。
Volume 0.1 新規エントリーの注文量。既存ポジションを反転するときにも使用されます。

使用上の注記

  • データソースがキャンドルメッセージでティックボリュームを提供していることを確認してください。実際の取引ボリュームのみが利用可能な場合、動作はそのデータに従います。
  • MetaTraderから再現しようとしているチャートの時間軸に合わせてCandleTypeパラメーターを合わせてください。
  • トレーディングルールで要求される場合は、外部リスク管理(ストップロス、テイクプロフィット、日次損失制限)で戦略をラップすることを検討してください。
  • 戦略はポジションが開かれたときにLogInfoを呼び出し、ログ内のシグナル決定の監査を容易にします。

オリジナルMQL実装との違い

  • CopyTickVolumeを手動で呼び出す代わりにStockSharpのキャンドルサブスクリプションパイプラインを使用します。
  • セッションフィルタリングは完成したキャンドルのCloseTime(次のバーの開始時刻)に基づいており、バーオープン時に実行されるMQLロジックと整合するように維持されます。
  • 注文実行はCTradeへの直接呼び出しではなく、高レベルAPIヘルパー(BuyMarketSellMarket)を通じて処理されます。
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;
	}
}