GitHub で見る

SimpleTrade戦略

概要

この戦略はMetaTrader 5のエキスパートアドバイザー「SimpleTrade(barabashkakvn版)」のC#変換です。現在のバーの始値を3バー前の始値と比較します。現在の始値が高い場合、戦略はロングになります。そうでない場合はショートになります。各ポジションは1つの完成したローソク足の間だけ保持され、pipsで表現された固定のストップロス距離で保護されます。

StockSharpの実装は高レベルAPIを通じて選択されたローソク足シリーズを購読し、完成したバーにのみ反応することで、決定が完成した価格データに基づいていることを保証します。ポジションは次のバー遷移時、またはバーの範囲内でストップレベルが触れられた場合はそれ以前にクローズされます。

トレーディングロジック

  • エントリー
    • 各完成したバーで、始値を保存し、最後の4つの始値のローリング履歴を維持します。
    • オープンポジションがなく、少なくとも4つの始値が利用可能な場合、最新の始値と3バー前に記録されたものを比較します。
    • 現在の始値が3バー前の始値を上回っている場合はロングポジションを取ります。そうでない場合はショートポジションを取ります。
  • 決済
    • 各取引はエントリーの始値からStopLossPips × pipサイズとして計算されたストップレベルで保護されます。
    • 次のバーでは結果に関わらずポジションがクローズされ、取引を1本のローソク足以上保持しないオリジナルのエキスパートアドバイザーを再現します。
    • バーの高値(ショートの場合)または安値(ロングの場合)がストップレベルを超えた場合、戦略は即座に成行でポジションをクローズしようとします。

パラメーター

パラメーター デフォルト値 説明
StopLossPips 120 エントリーの始値から保護ストップまでの距離(pips単位)。コードはMetaTraderの動作を再現し、3または5桁の小数点を持つシンボルの場合は価格ステップを10倍します。
TradeVolume 1 成行エントリーに使用する注文ボリューム。取引する銘柄の契約サイズに合わせて調整してください。
CandleType 1時間時間軸 戦略が購読するローソク足シリーズを指定します。MetaTraderで使用していたチャートに対応する時間軸を選択してください。

すべてのパラメーターはグラフィカルインターフェースを通じて最適化または変更できるようにStrategyParam<T>オブジェクトとして公開されています。

実装上の注意事項

  • 4つの始値のローリング履歴は、リポジトリのガイドラインに従うためにコレクションなしで維持されます。
  • ストップは別の注文として送信されません。代わりに、ロジックはローソク足の範囲を確認し、ストップレベルがトリガーされたはずの場合に成行決済を発行します。
  • StockSharpはポジションを非同期で処理するため、戦略は新しいエントリーシグナルを評価する前に既存の取引から退出します。ライブ取引では、これはオリジナルの「クローズしてから再オープン」のシーケンスを反映しながら、重複注文を避けます。
  • pipサイズはSecurity.PriceStepから導出されます。5桁または3桁のシンボルの場合、ステップは10倍されてpipがMetaTraderの定義と一致するようにします。

使用上のヒント

  • pipベースのストップが意味を持つ一貫したティックサイズを持つ銘柄(例:主要FXペア)で戦略を実行してください。
  • 銘柄ごとにStopLossPips値を最適化してください。大きな値はプロテクティブバッファを広げ、小さな値は戦略をバー内ノイズにより敏感にします。
  • ブローカー接続が最終状態のローソク足更新を送信していることを確認して、戦略が正しい始値を受け取れるようにしてください。

リスクと制限事項

  • 取引を1本のバーのみ保持することは、戦略が選択した時間軸に大きく依存することを意味します。異なるローソク足の期間でバックテストすることが不可欠です。
  • ストップ執行をエミュレートするためにローソク足の極値を使用することは、ネイティブストップ注文と比較して不安定な市場でスリッページを引き起こします。
  • 戦略は最初の4バーのデータの後、常に市場(ロングまたはショート)に留まり、横ばい市場では頻繁な取引を生成する可能性があります。
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simple trade strategy converted from MetaTrader that compares the current open with the open three bars ago.
/// The logic holds positions for a single bar and protects the trade with a fixed stop distance.
/// </summary>
public class SimpleTradeStrategy : Strategy
{
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _openCurrent;
	private decimal _openMinus1;
	private decimal _openMinus2;
	private decimal _openMinus3;
	private int _historyCount;
	private decimal? _stopPrice;

	/// <summary>
	/// Stop loss distance expressed in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Trade volume used for market orders.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

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

	/// <summary>
	/// Initialize <see cref="SimpleTradeStrategy"/> parameters.
	/// </summary>
	public SimpleTradeStrategy()
	{
		_stopLossPips = Param(nameof(StopLossPips), 120)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pips)", "Fixed protective distance in pips", "Risk Management")
			;

		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Volume", "Order volume in lots", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle source for decisions", "General");
	}

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

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

		_openCurrent = 0m;
		_openMinus1 = 0m;
		_openMinus2 = 0m;
		_openMinus3 = 0m;
		_historyCount = 0;
		_stopPrice = null;
	}

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

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

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

		// Exit existing trades before evaluating new entries to mimic the original MQL behaviour.
		if (TryCloseExistingPosition(candle))
			return;

		UpdateHistory(candle.OpenPrice);

		// Need at least four opens to compare with the value three bars ago.
		if (_historyCount < 4)
			return;

		ExecuteEntry(candle);
	}

	private bool TryCloseExistingPosition(ICandleMessage candle)
	{
		if (Position > 0)
		{
			var volume = Position;

			// Close long trades at the protective stop or at the bar change.
			if (_stopPrice is decimal stop && candle.LowPrice <= stop)
			{
				SellMarket();
			}
			else
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
			}

			_stopPrice = null;
			return true;
		}

		if (Position < 0)
		{
			var volume = Math.Abs(Position);

			// Close short trades at the protective stop or at the bar change.
			if (_stopPrice is decimal stop && candle.HighPrice >= stop)
			{
				BuyMarket();
			}
			else
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
			}

			_stopPrice = null;
			return true;
		}

		return false;
	}

	private void UpdateHistory(decimal currentOpen)
	{
		// Shift the stored opens so that _openMinus3 keeps the value three candles back.
		_openMinus3 = _openMinus2;
		_openMinus2 = _openMinus1;
		_openMinus1 = _openCurrent;
		_openCurrent = currentOpen;

		if (_historyCount < 4)
			_historyCount++;
	}

	private void ExecuteEntry(ICandleMessage candle)
	{
		var pipSize = CalculatePipSize();
		var stopOffset = pipSize * StopLossPips;

		// Enter long when the current open is above the open three bars ago, otherwise enter short.
		if (_openCurrent > _openMinus3)
		{
			BuyMarket();
			_stopPrice = candle.OpenPrice - stopOffset;
		}
		else
		{
			SellMarket();
			_stopPrice = candle.OpenPrice + stopOffset;
		}
	}

	private decimal CalculatePipSize()
	{
		// Reproduce the MetaTrader adjustment: multiply by ten for symbols with 3 or 5 decimals.
		var step = Security?.PriceStep ?? 1m;
		var decimals = Security?.Decimals;

		if (decimals == 3 || decimals == 5)
			step *= 10m;

		return step;
	}
}