GitHub で見る

ボリュームトレーダー V2 戦略

概要

Volume Trader V2 は、MetaTrader エキスパート アドバイザー Volume_trader_v2_www_forex-instruments_info.mq4 を直接変換したものです。オリジナルのシステムは、最新のローソク足の総体積がどのように変化するかを観察し、この短期フローを使用して、単純な長時間露出と短時間露出のどちらをアクティブにするかを決定します。 StockSharp ポートは、一度に 1 つのポジションの動作、時刻フィルター、および完了したキャンドルごとに 1 回だけ動作するという要件を維持します。

この戦略は、構成可能なローソク足シリーズをサブスクライブし、最後の 2 つの終了したローソク足のボリュームをキャッシュします。新しい足が閉じると、前の 2 つの足 (MetaTrader の Volume[1]Volume[2]) の出来高が比較され、更新された取引方向が生成されます。

  • Volume[1] < Volume[2]長い バイアスを生成します。
  • Volume[1] > Volume[2]短い バイアスを生成します。
  • 取引量が等しいか、取引時間が無効になっている場合は、未処理のエクスポージャが削除されます。

新しい注文を送信する前に、StockSharp の実装が MetaTrader の注文ライフサイクルと一致するように、現在のポジションが逆方向を向いている場合はフラット化されます。

パラメーター

名前 デフォルト 説明
CandleType 5分の時間枠 SubscribeCandles からリクエストされたデータ型。 MetaTrader で使用されるチャート期間と一致するように設定します。
StartHour 8 最初の取引時間 (含む)。窓の外のシグナルは無視され、どのポジションもクローズされます。
EndHour 20 最終取引時間 (含む)。現在のローソク足がこの時間を過ぎて始まる場合、戦略は横ばいのままです。
TradeVolume 0.1 ロットサイズは EA から複製されました。この値は Strategy.Volume にも割り当てられるため、ヘルパー メソッドは同じ量を使用します。

すべてのパラメータは通常の StrategyParam<T> インスタンスであるため、UI を通じて最適化または公開できます。

取引ロジック

  1. EA とのバーごとの同等性を保証するために、完成したキャンドルのみを処理してください。
  2. 信号を評価する前に、Volume[1]Volume[2] に相当するものを _previousVolume_twoBarsAgoVolume にキャッシュします。
  3. ローソク足の開始時刻が StartHourEndHour (両端の値を含む) の間にあることを検証します。この範囲外では、アクティブなポジションはクローズされ、新しい注文は作成されません。
  4. 目的の方向を計算します。
    • 最新のボリュームが前のバーよりも低い場合はロングです。
    • 最新の出来高が前のバーよりも高い場合はショートします。
    • それ以外の場合は中立。
  5. 希望の方向が現在位置と異なる場合は、最初に反対側の位置を閉じます (BuyMarket(-Position) または SellMarket(Position))。
  6. 戦略がフラットまたは反対方向に配置されている場合にのみ、構成された TradeVolume を使用して新しいポジションを入力します。
  7. キャッシュされたボリュームを更新して、次のサイクルでも最後に完了した 2 つのキャンドルを比較するようにします。

このフローは、ローソク足がまだ構築されている間に注文が出されないこと、および LastBarChecked に依存した MetaTrader 実装と同様に、StockSharp ストラテジーがバーごとに 1 回だけ反応することを保証します。

追加メモ

  • StartProtection() は、現在の位置を追跡するフレームワーク保護ヘルパーを再利用するために、OnStarted で呼び出されます。
  • Comment プロパティは、監視を簡素化するために、EA 診断メッセージ ("Up trend""Down trend""No trend..."、または "Trading paused") をミラーリングします。
  • この戦略では追加のコレクションは維持されず、プロジェクト ガイドラインに従って高レベルのキャンドル サブスクリプション API が活用されます。
  • 比較可能な結果を得るには、MetaTrader で最初に使用された商品と時間枠に一致するようにローソクのタイプ、セキュリティ、ボリュームを設定します。
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();
		}
	}
}