GitHub で見る

固定リスク資金管理戦略

概要

固定リスク資金管理戦略は、MetaTrader 5エキスパートアドバイザー Money Fixed Risk.mq5 の直接ポートです。元のスクリプトは定期的に、口座資本の固定割合以下にリスクを保つ最大ポジションサイズを計算し、対称的なストップロスとテイクプロフィット注文で保護された成行買いを出します。このStockSharpバージョンは、フレームワークが提供する高レベルのティックサブスクリプションAPIとリスク管理機能を使用して同じ動作を保持します。

戦略は選択した銘柄の各トレード(ティック)を監視します。設定可能なティック数の後、現在のポートフォリオ価値を評価し、pipsで設定されたストップ距離を価格単位に変換し、指定された資本パーセンテージ内にリスクを保つ最大ボリュームを計算します。計算されたボリュームが有効な場合、戦略はロング成行注文を出し、執行価格からストップ距離だけ離れたところにストップロスとテイクプロフィットレベルを設定します。ストップとターゲットはその後の各ティックで監視され、どちらかの境界に達するとポジションがクローズされます。

データ要件

  • エントリー条件が個々のティックをカウントするため、ティック(トレード)データが必要です。ローソク足データは使用されません。
  • ポジションサイジング式がブローカーの契約仕様と一致するように、銘柄に対して PriceStepStepPriceVolumeStepMinVolume、およびオプションの MaxVolume を正しく設定する必要があります。

戦略の仕組み

  1. SubscribeTrades() を通じてティック更新を待ちます。
  2. 最後に取引された価格を追跡し、内部カウンターを増加させます。
  3. ティックカウンターが Ticks Interval に達するたびに、カウンターをリセットして次を実行します:
    • PriceStepDecimals からpipサイズを決定します(5桁および3桁の相場は自動的に10倍にスケールされます)。
    • 設定されたストップロス距離をpipsから価格単位に変換します。
    • 現在の口座資本を決定します(Portfolio.CurrentValue を試し、CurrentBalance、次に BeginValue にフォールバック)。
    • ストップ距離と StepPrice を使用してコントラクトあたりの金銭的リスクを計算します。
    • 金銭的リスクを資本の Risk % 以下に保つ最大ボリュームを導出し、取引所のボリュームステップと制限に正規化します。
  4. 計算されたボリュームが正の場合、既存のショートエクスポージャーを解消し新しいロングポジションを開くサイズの買い成行注文を送信します。
  5. エントリー価格周辺のストップロスとテイクプロフィット価格を記録します。その後の各ティックでトレード価格を監視し、どちらかのレベルが違反されたらポジションをクローズします。

パラメーター

  • Stop Loss (pips) – pipsで表したストップロス距離。テイクプロフィットは反対方向の同じ距離に配置されます。
  • Risk % – 各トレードでリスクを取るポートフォリオ資本のパーセンテージ。
  • Ticks Interval – 再評価して新しいポジションを開く可能性があるまでに待つティック数。

すべてのパラメーターは最適化と検証(ゼロより大きくなければならない)をサポートします。

マネーマネジメントの詳細

  • リスク金額 = Equity * (Risk % / 100)
  • 価格単位でのストップ距離 = Stop Loss (pips) * pip size(3桁および5桁の銘柄のpipサイズは PriceStep * 10、それ以外は PriceStep)。
  • コントラクトあたりの金銭的リスク = (stop distance / PriceStep) * StepPrice
  • ポジションサイズ = Risk amount / monetary risk per contract(最も近い VolumeStep に切り捨て、MinVolume/MaxVolume で制限)。正規化されたサイズが最小ボリュームを下回る場合、注文はスキップされます。

元のエキスパートアドバイザーとの違い

  • MetaTraderライブラリを呼び出さずにStockSharp内で完全に実行されます。
  • プラットフォームレベルの保護がアクティブなままになるよう StartProtection() を使用します。
  • ターミナル残高オブジェクトを照会する代わりに、現在の資本情報に戦略ポートフォリオを使用します。
  • この教育例では明示的なストップ注文の必要性を排除するため、ポジションをクローズするために継続的なティック監視を使用します。

使用上の注意

  • このサンプルは元のファイルと同様にロングポジションのみを開きます。ショートトレードが必要な場合は ProcessTrade を拡張してください。
  • バックテスト時は、ティックデータに設定されたティックインターバルに達するのに十分な深さが含まれていることを確認してください。そうでないとトレードはトリガーされません。
  • ポジションサイジングはブローカーのメタデータに依存するため、ライブ運用前に PriceStepStepPrice、ボリューム制約の正確さを確認してください。
  • 実装はインジケーターコレクションの使用を避けて変換ガイドラインを尊重し、プライベートフィールドを通じてロジックをステートフルに保ちます。
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>
/// Recreates the Money Fixed Risk expert advisor using StockSharp's high level API.
/// Uses ATR to determine position sizing via risk percentage and opens long positions
/// with symmetric stop-loss and take-profit levels based on ATR.
/// </summary>
public class MoneyFixedRiskStrategy : Strategy
{
	private readonly StrategyParam<decimal> _atrMultiplier;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<int> _candleInterval;
	private readonly StrategyParam<DataType> _candleType;

	private int _candleCounter;
	private decimal _stopPrice;
	private decimal _takeProfitPrice;
	private decimal _entryPrice;

	/// <summary>
	/// ATR multiplier for stop distance.
	/// </summary>
	public decimal AtrMultiplier
	{
		get => _atrMultiplier.Value;
		set => _atrMultiplier.Value = value;
	}

	/// <summary>
	/// ATR calculation period.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// Number of candles between position evaluations.
	/// </summary>
	public int CandleInterval
	{
		get => _candleInterval.Value;
		set => _candleInterval.Value = value;
	}

	/// <summary>
	/// Candle type used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="MoneyFixedRiskStrategy"/>.
	/// </summary>
	public MoneyFixedRiskStrategy()
	{
		_atrMultiplier = Param(nameof(AtrMultiplier), 1.5m)
			.SetGreaterThanZero()
			.SetDisplay("ATR Multiplier", "Multiplier for ATR-based stop distance", "Risk")
			.SetOptimize(0.5m, 3m, 0.5m);

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
			.SetOptimize(7, 28, 7);

		_candleInterval = Param(nameof(CandleInterval), 10)
			.SetGreaterThanZero()
			.SetDisplay("Candle Interval", "Candles between position evaluations", "General")
			.SetOptimize(5, 30, 5);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles for processing", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_candleCounter = 0;
		_stopPrice = 0m;
		_takeProfitPrice = 0m;
		_entryPrice = 0m;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var atr = new AverageTrueRange { Length = AtrPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(atr, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, atr);
			DrawOwnTrades(area);
		}
	}

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

		var price = candle.ClosePrice;

		// Manage existing long position
		if (Position > 0 && _stopPrice > 0m)
		{
			if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takeProfitPrice)
			{
				SellMarket(Position);
				_stopPrice = 0m;
				_takeProfitPrice = 0m;
				_entryPrice = 0m;
			}
		}

		// Manage existing short position
		if (Position < 0 && _stopPrice > 0m)
		{
			if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takeProfitPrice)
			{
				BuyMarket(Math.Abs(Position));
				_stopPrice = 0m;
				_takeProfitPrice = 0m;
				_entryPrice = 0m;
			}
		}

		_candleCounter++;

		if (_candleCounter < CandleInterval)
			return;

		_candleCounter = 0;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (Position != 0)
			return;

		if (atrValue <= 0m)
			return;

		var stopDistance = atrValue * AtrMultiplier;

		// Alternate between long and short based on price relative to previous entry
		var goLong = _entryPrice == 0m || price > _entryPrice;

		if (goLong)
		{
			BuyMarket(Volume);
			_entryPrice = price;
			_stopPrice = price - stopDistance;
			_takeProfitPrice = price + stopDistance;
		}
		else
		{
			SellMarket(Volume);
			_entryPrice = price;
			_stopPrice = price + stopDistance;
			_takeProfitPrice = price - stopDistance;
		}
	}
}