GitHub で見る

体積計算戦略

概要

出来高計算戦略 は、ストップロスとテイクプロフィットのレベルに基づいて推奨取引量を計算する、元の MetaTrader エキスパート アドバイザーのロジックを再現しています。戦略が開始されると、設定されたストップ価格を読み取り、選択した証券の現在の市場価格を評価し、利用可能なポートフォリオ資本を使用してリスク指標を導き出します。

この戦略では注文は出しません。その唯一の目的は、詳細な資金管理統計をログに提供し、読み取り専用プロパティを通じて計算値を公開することです。これは、取引を送信する前にポジションサイジングルールを検証したいマニュアルトレーダーにとって便利です。

パラメーター

  • ストップロス価格 – 計画されたポジションに使用される保護ストップの絶対価格レベル。
  • 利益確定価格 – 利益確定目標の絶対価格レベル。
  • 最大損失 % – 1 回の取引でリスクを負うことができるポートフォリオ価値の最大割合。この戦略では、このパーセンテージにポートフォリオの資本を乗算して、通貨換算で許容可能な最大損失を求めます。
  • ロングポジションです – 計画されたポジションがロング(true)かショート(false)かを決定します。方向は、現在の価格とストップ/ターゲットレベルの間の距離を計算するために必要です。

最大損失 % を除くすべてのパラメーターは、元のエキスパートの動作を反映して厳密に手動入力に保つために最適化から除外されます。

計算内容

  1. ポートフォリオの値 – 戦略は、利用可能な資本を見積もるために Portfolio.CurrentValue をフェッチします (Portfolio.BeginValue にフォールバックします)。値が指定されていない場合、計算は警告とともに停止します。
  2. 価格ステップの検証Security.PriceStepSecurity.StepPrice の値は、価格距離を契約ステップと現金金額に変換するため、定義する必要があります。メタデータが欠落していると計算ができなくなります。
  3. 現在の価格検出 – この戦略は最後の取引価格を検索します。利用できない場合は、最良の買値/売値を平均して価格を近似し、最終的には既知の最後の価格に戻ります。
  4. ステップ単位の距離 – ストップロスとテイクプロフィットの距離は両方とも価格ステップで測定されます。 MetaTrader スクリプトが MathCeil に依存するのと同じ方法で、距離は控えめに保つために切り上げられます (decimal.Ceiling)。
  5. リスクにさらされている資金 – 最大許容損失は PortfolioValue * MaxLoss% / 100 に相当します。
  6. 推奨量 – ステップあたりの損失は MaxLoss / StopSteps です。この値を StepPrice で割ると、損失を制御できるポジション量が生成されます。
  7. 期待利益 – テイクプロフィットステップに StepPrice と推奨ボリュームを乗算すると、目標が達成された場合の予想現金利益が得られます。
  8. リスク対報酬比 – テイクプロフィットステップ数とストップロスステップ数の比率。元のピップベースの計算に相当します。

計算された各値は戦略内に保存され、有益な英語メッセージとともにログに出力されます。リスクと報酬の比率が 3 以上の場合、戦略は「取引できる」という信号を出します。それ以外の場合は、取引が危険すぎるという警告が出力されます。

使用ワークフロー

  1. StockSharp 環境で目的のセキュリティとポートフォリオに戦略をアタッチします。
  2. 計画された手動取引に一致するストップロス価格とテイクプロフィット価格を設定します。
  3. 許容可能なリスクの割合と意図する方向を設定します。
  4. 戦略を開始します。すべてのメトリクスを含む出力がログにすぐに表示されます。
  5. 手動で取引を実行する前に、推奨されるボリュームとリスク対報酬の比率を確認してください。

注意事項

  • 必要なセキュリティ メタデータ フィールド (価格ステップまたはステップ価格) のいずれかが欠落している場合は、取引所からそれらを要求するか、セキュリティ設定を手動で調整します。
  • 計算は静的です。開始後に自動的に更新されません。市場の状況やリスクパラメーターが変化した場合は、戦略を再開します。
  • このストラテジーは注文を送信しないため、純粋に分析目的でバックテスト環境とライブ環境の両方で安全に実行できます。
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Volume Calculator strategy: EMA + volume confirmation.
/// Buys when price above EMA with increasing volume, sells below EMA with increasing volume.
/// </summary>
public class VolumeCalculatorStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaPeriod;

	private decimal _prevVolume;
	private bool _wasBullishSignal;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }

	public VolumeCalculatorStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_emaPeriod = Param(nameof(EmaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA trend filter period", "Indicators");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevVolume = 0;
		_wasBullishSignal = false;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevVolume = 0;
		_wasBullishSignal = false;
		_hasPrev = false;
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal emaValue)
	{
		if (candle.State != CandleStates.Finished) return;

		if (_hasPrev)
		{
			var volumeUp = candle.TotalVolume > _prevVolume;
			var bullishSignal = candle.ClosePrice > emaValue && volumeUp;
			var bearishSignal = candle.ClosePrice < emaValue && volumeUp;
			var crossedUp = bullishSignal && !_wasBullishSignal;
			var crossedDown = bearishSignal && _wasBullishSignal;

			if (crossedUp && Position <= 0)
				BuyMarket();
			else if (crossedDown && Position >= 0)
				SellMarket();

			if (bullishSignal || bearishSignal)
				_wasBullishSignal = bullishSignal;
		}

		_prevVolume = candle.TotalVolume;
		_hasPrev = true;
	}
}