GitHub で見る

新ランダム戦略

概要

新ランダム戦略は、3つの異なるエントリー選択モードを提供することで、オリジナルのMetaTraderエキスパート「New Random」をエミュレートします。一度に1つのポジションのみを開き、次の注文方向を生成する前に現在のポジションがクローズされるまで待ちます。成行エントリーは最良気配更新(Level 1データ)でトリガーされ、最良bid/ask価格を実行アンカーとして使用します。戦略はMQLバージョンと同様に、3桁と5桁のFX相場に適応して、ストップロスとテイクプロフィットのオフセットをpipsで自動計算します。

エントリーモード

  1. ジェネレーター – 次の方向は戦略開始時にシードされた擬似乱数ジェネレーターによって選択される。各機会は売買間の独立したコイントスとなる。
  2. 買い-売り-買いサイクル – ポジションは売買間で厳密に交互に切り替わる。最初の注文は買いで、次に売り、というように続く。
  3. 売り-買い-売りサイクル – ポジションは売りから始まり、次に買い、というように厳密に交互に切り替わる。

パラメーター

  • Random Mode (Mode) – 上記3つのエントリーメカニズムのいずれかを選択する。デフォルトはランダムジェネレーター。
  • Minimal Lot Count (MinimalLotCount) – インストゥルメントの最小取引可能ボリュームに乗算する。値1は戦略がちょうどSecurity.VolumeMinを取引することを意味し、より大きな値は注文サイズを整数倍でスケールする。
  • Stop Loss (pips) (StopLossPips) – 戦略がポジションを終了する約定価格を下回る/上回るpips距離。stop-lossを無効にするには0に設定する。
  • Take Profit (pips) (TakeProfitPips) – 戦略が利益を確定するpips距離。take-profitを無効にするには0に設定する。

トレードロジック

  1. 設定されたセキュリティのLevel 1データをサブスクライブし、最新のbid、ask、最終取引価格を継続的に保存する。
  2. オープンポジションも保留中の注文もない場合、戦略は次の方向を決定するために選択されたモードを評価する。
  3. 注文は最新の最良bid/askスナップショットを使用してマーケットで配置される。ストップロスとテイクプロフィットのターゲットはpips距離パラメーターを使用してエントリー価格からすぐに計算される。
  4. 一度に1つのポジションのみが存在できる。アクティブポジションが完全にクローズされるまで後続エントリーは抑制される。

ポジション管理

  • ロングポジションは、現在価格がストップロスに達するかそれを下回った場合、またはテイクプロフィットに達するかそれを上回った場合に早期終了する。
  • ショートポジションは、現在価格がストップロスに達するかそれを上回った場合、またはテイクプロフィットに達するかそれを下回った場合に終了する。
  • 価格比較は常に最新のLevel 1情報を使用する:利用可能な場合は最終取引価格、そうでない場合は各サイドの最良bid/ask。
  • トレードをクローズした後、戦略は内部状態をリセットし、オプションで次の方向を切り替え(シーケンスモードの場合)、再エントリーする前に次の価格更新を待つ。

注意事項

  • 戦略はポジションをピラミッディングせず、シーケンスベースのモードでは動作を決定論的に保ちます。
  • ランダムモードは現在のティックカウントでシードされるため、各実行は一意の注文ストリームを生成します。
  • すべての内部コメントとログはリポジトリのガイドラインに合わせて英語です。
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>
/// Randomized entry strategy that mimics the MetaTrader "New Random" expert.
/// </summary>
public class NewRandomStrategy : Strategy
{
	/// <summary>
	/// Available direction selection modes.
	/// </summary>
	public enum RandomModes
	{
		/// <summary>Use a pseudo random generator for every entry decision.</summary>
		Generator,
		/// <summary>Alternate buy-sell-buy.</summary>
		BuySellBuy,
		/// <summary>Alternate sell-buy-sell.</summary>
		SellBuySell
	}

	private readonly StrategyParam<RandomModes> _mode;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private Sides? _sequenceLastSide;
	private Sides? _positionSide;
	private decimal _entryPrice;
	private int _candleCount;

	/// <summary>Direction selection mode.</summary>
	public RandomModes Mode
	{
		get => _mode.Value;
		set => _mode.Value = value;
	}

	/// <summary>Stop loss in price steps.</summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>Take profit in price steps.</summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

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

	public NewRandomStrategy()
	{
		_mode = Param(nameof(Mode), RandomModes.Generator)
			.SetDisplay("Random Mode", "Direction selection mode", "General");

		_stopLossPoints = Param(nameof(StopLossPoints), 5)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pts)", "Stop loss in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 5)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pts)", "Take profit in price steps", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_sequenceLastSide = null;
		_positionSide = null;
		_entryPrice = 0m;
		_candleCount = 0;
	}

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

		_sequenceLastSide = Mode switch
		{
			RandomModes.BuySellBuy => Sides.Sell,
			RandomModes.SellBuySell => Sides.Buy,
			_ => null
		};

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

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

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

		_candleCount++;
		if (_candleCount < 3)
			return;

		var step = Security?.PriceStep ?? 1m;
		var stopDistance = StopLossPoints * step;
		var takeDistance = TakeProfitPoints * step;
		var price = candle.ClosePrice;

		// Check SL/TP for current position
		if (Position != 0 && _entryPrice > 0)
		{
			var hit = false;

			if (_positionSide == Sides.Buy)
			{
				if (stopDistance > 0 && candle.LowPrice <= _entryPrice - stopDistance)
					hit = true;
				if (takeDistance > 0 && candle.HighPrice >= _entryPrice + takeDistance)
					hit = true;
			}
			else if (_positionSide == Sides.Sell)
			{
				if (stopDistance > 0 && candle.HighPrice >= _entryPrice + stopDistance)
					hit = true;
				if (takeDistance > 0 && candle.LowPrice <= _entryPrice - takeDistance)
					hit = true;
			}

			if (hit)
			{
				if (Position > 0)
					SellMarket();
				else if (Position < 0)
					BuyMarket();

				_positionSide = null;
				_entryPrice = 0m;
			}
		}

		// If flat, open new random position
		if (Position == 0 && _positionSide == null)
		{
			var side = DetermineNextSide();

			if (side == Sides.Buy)
				BuyMarket();
			else
				SellMarket();

			_positionSide = side;
			_entryPrice = price;

			_sequenceLastSide = side;
		}
	}

	private Sides DetermineNextSide()
	{
		// All modes use deterministic alternating logic
		return _sequenceLastSide == Sides.Buy ? Sides.Sell : Sides.Buy;
	}
}