GitHub で見る

Rnd Trade戦略

概要

  • MetaTrader 5のエキスパートアドバイザー RndTrade.mq5 をStockSharpのハイレベル戦略APIに変換したものです。
  • 固定の時間間隔で既存のポジションを閉じ、ランダムに選択された方向で新しい成行ポジションを即座に開きます。
  • 元のタイマーコールバックの決定論的な置き換えとして時間ベースのローソク足サブスクリプションを使用します。

パラメーター

名前 タイプ デフォルト 説明
IntervalMinutes int 60 現在のポジションのクローズと新しいランダムポジションのオープンの間の分数。ゼロより大きくなければなりません。
Volume decimal 1 成行エントリーに使用するポジションサイズ。基底クラス Strategy から派生します。

データサブスクリプション

  • IntervalMinutes と一致する長さの時間軸ローソク足をサブスクライブします (例:60 → 60分ローソク足)。
  • ローソク足クローズイベント (CandleStates.Finished) を使用して、間隔ごとに正確に1回ロジックを起動します。

トレーディングロジック

  1. 各インターバルローソク足の完了を待ちます。
  2. 戦略が形成され、オンラインで、取引が許可されるまで処理をスキップします。
  3. 前のインターバル中に作成されたオープンポジションを閉じます。
  4. ロングまたはショートエントリーを決定するためにランダム値を生成します。
  5. 選択した方向に設定されたボリュームで成行注文 (BuyMarket または SellMarket) を送信します。

実装メモ

  • インジケーター値やコレクションの手動ポーリングを避けるために SubscribeCandles().Bind(ProcessCandle) に依存します。
  • 明示的なストップロスやテイクプロフィットが設定されていない場合でも、組み込みのリスクモジュールがアクティブになるよう、起動時に StartProtection() を呼び出します。
  • 元のMQL戦略で見つかった MathRand() 動作を模倣するために標準ライブラリの Random を使用します。
  • コードには、各変換ステップがStockSharpの機能にどのようにマップされるかを説明する英語のコメントが含まれています。

元のMQL戦略との違い

  • タイマーイベント (OnTimer) は、MetaTraderのタイマーAPIの代わりにローソク足サブスクリプションによってエミュレートされます。
  • ポジションのクローズは、ポジションリストを反復してチケットごとに PositionClose を呼び出す代わりに ClosePosition() で処理されます。
  • StockSharpバージョンは、シンボルの最小ロット照会の代わりに組み込みの Volume プロパティをポジションサイジングに使用します。
  • 注文フィルルールとスリッページ設定は接続されたブローカーまたはシミュレーターによって管理されるため、戦略では明示的に設定されません。

使い方

  1. StockSharp環境内で戦略をポートフォリオとインストゥルメントにアタッチします。
  2. 希望するトレーディング頻度とサイズに応じて IntervalMinutesVolume を設定します。
  3. 戦略を開始します。追加の入力なしに、各インターバルでポジションを自動的にフラット化して再オープンします。
  4. 現時点ではPython実装は提供されていません。C#バージョンのみ利用可能です。
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>
/// Random direction trading strategy converted from the MetaTrader RndTrade EA.
/// The strategy closes any open position on each interval and immediately opens a new random position.
/// </summary>
public class RndTradeStrategy : Strategy
{
	private readonly StrategyParam<int> _intervalMinutes;

	/// <summary>
	/// Interval in minutes between closing the current position and opening a new random one.
	/// </summary>
	public int IntervalMinutes
	{
		get => _intervalMinutes.Value;
		set => _intervalMinutes.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="RndTradeStrategy"/> class.
	/// </summary>
	public RndTradeStrategy()
	{
		_intervalMinutes = Param(nameof(IntervalMinutes), 360)
			.SetGreaterThanZero()
			.SetDisplay("Interval Minutes", "Minutes between closing and opening positions", "General");

		Volume = 1;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, TimeSpan.FromMinutes(IntervalMinutes).TimeFrame())];
	}

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

		// Use time-based candles as a deterministic timer replacement.
		var timeFrame = TimeSpan.FromMinutes(IntervalMinutes).TimeFrame();

		var subscription = SubscribeCandles(timeFrame);
		subscription
			.Bind(ProcessCandle)
			.Start();

	}

	private void ProcessCandle(ICandleMessage candle)
	{
		// Process only final candles to execute logic exactly once per interval.
		if (candle.State != CandleStates.Finished)
			return;

		// Always close the existing position before selecting a new random direction.
		if (Position > 0)
			SellMarket(Position);
		else if (Position < 0)
			BuyMarket(Math.Abs(Position));

		// Derive a deterministic pseudo-random direction from the candle data.
		if (ShouldBuy(candle))
		{
			// Enter long after flattening the previous position.
			if (Position <= 0)
				BuyMarket(Volume);
		}
		else
		{
			// Enter short after flattening the previous position.
			if (Position >= 0)
				SellMarket(Volume);
		}
	}

	private static bool ShouldBuy(ICandleMessage candle)
	{
		var hash = HashCode.Combine(candle.OpenTime.Ticks, candle.ClosePrice, candle.TotalVolume);
		return (hash & 1) == 0;
	}
}