GitHub で見る

ロック戦略

ロック戦略はMetaTraderからのクラシックな「ロック」エキスパートアドバイザーを再現します:常にロングとショートのポジションのヘッジペアを維持し、利益ロック条件が満たされるまでリサイクルし続けます。このアルゴリズムは固定pip基準のテイクプロフィットを適用できる小さなティックサイズを持つ商品のために設計されています。

トレードワークフロー

  1. 初期ヘッジ – 市場データが利用可能になり次第、戦略は同じボリュームでロングとショートのポジションを開きます。両方の注文が約定した場合、次のヘッジに使用されるボリュームはLotExponential係数で掛けられます。
  2. テイクプロフィット管理 – 各レッグはエントリー価格を保存します。ローソク足の終値がエントリーからTakeProfitPips(インストゥルメントティックに変換)移動すると、そのレッグは成行注文で閉じられます。反対側は開いたままで、MQLバージョンのヘッジ的な動作を維持します。
  3. 再ヘッジ – アクティブなレッグの合計数が1つまたはゼロになると、戦略はすぐに新しいペアを開きます。開いているレッグがない場合、新しいペアを作成する前にベースボリュームがLotSizeにリセットされます。
  4. ボリューム制御 – ヘルパーメソッドAdjustVolumeが取引所の制限を適用します:ボリュームをセキュリティのVolumeStepに丸め、MinVolumeMaxVolumeで制限し、調整された値がゼロになった場合にスケールアップをキャンセルします。

利益ロック条件

オリジナルのMQLロジックはアカウント残高と資本を監視します:残高が資本をExcessBalanceOverEquity超え、資本が最後にロックされた残高より少なくともMinProfit上にある場合、すべてのレッグが閉じられます。C#の実装は、戦略がフラットなときに観察された資本を追跡し、それを実行中の残高として扱うことでこの動作を反映します。条件がトリガーされると、すべてのレッグが清算され、LotSizeでサイクルが再開する前にベース残高が更新されます。

パラメーター

  • LotSize – 最初のヘッジサイクルのベースボリューム(デフォルト:0.1m)。
  • TakeProfitPips – 各レッグを閉じるためのpips距離(デフォルト:100)。0の値で自動退出が無効になります。
  • LotExponential – 両方のレッグが正常に開いた後に現在のボリュームに適用される乗数(デフォルト:2m)。
  • ExcessBalanceOverEquity – 利益を確保する前の残高と資本の間に許容されるギャップ(デフォルト:3000m)。
  • MinProfit – すべてのレッグを閉じる前に達成しなければならない追加の資本成長(デフォルト:500m)。
  • CandleType – 戦略ロジックを駆動するタイムフレーム(デフォルト:1分タイムフレーム)。

実装上の注意

  • pipサイズはSecurity.PriceStepSecurity.Decimalsから再計算されるので、戦略は3/5桁のFXシンボルや標準的な先物や株式に適応します。
  • 注文は成行実行を使用して配置され、ブローカー側のテイクプロフィットで成行注文を送信するMQLエキスパートの動作を反映します。
  • 戦略はヘッジされたレッグの完全な履歴を保持し、ソーススクリプトが許可していたのと同様に、各側に複数の積み上げポジションを可能にします。
namespace StockSharp.Samples.Strategies;

using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Lock strategy (simplified).
/// Uses fast and slow EMA crossover with momentum confirmation.
/// </summary>
public class LockStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;

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

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public LockStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Source candles", "General");

		_fastPeriod = Param(nameof(FastPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
	}

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

		var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
		var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };

		decimal prevFast = 0, prevSlow = 0;
		bool hasPrev = false;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, (ICandleMessage candle, decimal fastValue, decimal slowValue) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!hasPrev)
				{
					prevFast = fastValue;
					prevSlow = slowValue;
					hasPrev = true;
					return;
				}

				if (!IsFormedAndOnlineAndAllowTrading())
				{
					prevFast = fastValue;
					prevSlow = slowValue;
					return;
				}

				// Buy on golden cross
				if (prevFast <= prevSlow && fastValue > slowValue && Position <= 0)
				{
					BuyMarket();
				}
				// Sell on death cross
				else if (prevFast >= prevSlow && fastValue < slowValue && Position >= 0)
				{
					SellMarket();
				}

				prevFast = fastValue;
				prevSlow = slowValue;
			})
			.Start();

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