GitHub で見る

ワダ・アタール・ウィングリッド戦略

Waddah Attar Win Grid Strategy は、MQL/8210 スクリプトから MetaTrader 4 エキスパート アドバイザーを複製します。現在の買値/売値を中心に、買い指値注文と売り指値注文の対称的なはしごを継続的に維持します。価格が最新のグリッド レベルに向かってドリフトすると、この戦略は自動的に新しい未決注文を 1 ステップ遠くにスタックし、オプションで各追加注文の量を増やします。変動利益は注文帳が更新されるたびに監視され、設定された株式利益に達すると、すべてのポジションと約定注文が同時にクローズされます。

仕組み

  1. 初期化
    • 入札/売値の変更に即座に対応するために、注文帳の更新を購読します。
    • ベースライン株式参照として使用する現在のポートフォリオ値を記録します。
    • StockSharp の組み込みリスク保護サブシステムを開始します。
  2. ベースライン管理
    • アクティブな注文がなく、ネットポジションが横ばいの場合は、最新のポートフォリオ値が新しい参照残高になります。これは、各ティックの現在の口座残高を保存した元のエキスパートアドバイザーを反映しています。
  3. 初期グリッド配置
    • 取引が許可され、アクティブな注文がなくなると、ストラテジーはすぐに 2 つの保留中の注文を出します。
      • 現在の売値を下回る買い指値 Step Points
      • 現在の入札価格を上回る売り制限 Step Points
    • どちらの注文も First Volume 値を使用します。
  4. 新規注文の積み重ね
    • アスク価格が最新の買い指値から 5 価格ステップ以内に移動すると、この戦略は新しい買い指値を以前のレベルより完全に 1 ステップ下に設定します。
    • 入札価格が最新の売り指値から 5 価格ステップ以内に変動した場合、この戦略は新しい売り指値を以前のレベルより 1 ステップ上に設定します。
    • 新しい未決注文ごとに取引量が Increment Volume ずつ増加し、必要に応じてマーチンゲール スタイルのピラミッド法が有効になります。
  5. 利益の獲得
    • 変動利益は、現在のポートフォリオの資本と保存されている参照残高の差として計算されます。
    • この利益が Min Profit を超えると、すべてのアクティブな注文がキャンセルされ、すべてのオープン ポジションが 1 回の CloseAll コールでフラット化されます。
    • ベースライン エクイティが更新され、グリッドが白紙の状態で再スタートできるようになります。

戦略の特徴

  • 市場データ: 純粋にレベル 1 の注文帳スナップショット (最良の買い/売り) に基づいて動作します。
  • 注文タイプ: 指値注文のみを使用します。ストップやマーケットエントリーは自動的に生成されません。
  • エクスポージャー: ヘッジ対応ポートフォリオでロングポジションとショートポジションを同時に保持できます。
  • リスク管理: 厳密なストップロスがありません。変動利益目標と外部リスクルールに依存しています。
  • 再エントリー: 注文をフラット化または手動でキャンセルした後、次回市場データ ループが実行されるときに、初期グリッドが自動的に再作成されます。

パラメーター

パラメータ デフォルト 説明
Step Points 120 連続するグリッド レベル間の距離。価格ポイント (価格ステップ倍数) で表されます。
First Volume 0.1 未決注文の最初のペアに使用されるボリューム。
Increment Volume 0.0 新たに積み上げられた各注文に追加ボリュームが追加されます。すべての注文を同じサイズに保つには、ゼロに設定します。
Min Profit 450 すべてのオープンポジションと未決注文を決済するには変動利益(口座通貨)が必要です。

注意事項と制限事項

  • 機器の PriceStep が正しく設定されていることを確認してください。この戦略では、Step PointsPriceStep を乗算して実際の価格を導き出します。
  • このアルゴリズムは頻繁に注文をキャンセルしたり置き換えたりするため、ブローカーまたは取引所の未決注文数の制限を考慮する必要があります。
  • 組み込みのドローダウン保護機能はありません。外部のリスク管理またはポートフォリオレベルのストップと戦略を組み合わせることを検討してください。
  • 価格が急騰した場合、利益目標を達成せずにグリッドが無限に拡大する可能性があります。証拠金の使用量を制御するには、Increment Volume を慎重に選択してください。

ファイル

  • CS/WaddahAttarWinGridStrategy.cs — 取引ロジックの C# 実装。
  • README.md — このドキュメント (英語)。
  • README_ru.md — 内容が同一のロシア語翻訳。
  • README_zh.md — 内容が同一の中国語翻訳。
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Grid strategy converted from the "Waddah Attar Win" MetaTrader 4 expert advisor.
/// Places paired orders around the market, pyramids positions with an optional volume increment,
/// and closes the entire exposure once the floating profit target is achieved.
/// </summary>
public class WaddahAttarWinGridStrategy : Strategy
{
	private readonly StrategyParam<int> _stepPoints;
	private readonly StrategyParam<decimal> _firstVolume;
	private readonly StrategyParam<decimal> _incrementVolume;
	private readonly StrategyParam<decimal> _minProfit;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _lastBuyGridPrice;
	private decimal _lastSellGridPrice;
	private decimal _currentBuyVolume;
	private decimal _currentSellVolume;
	private decimal _referenceBalance;
	private bool _gridActive;

	/// <summary>
	/// Distance in price points between consecutive grid levels.
	/// </summary>
	public int StepPoints
	{
		get => _stepPoints.Value;
		set => _stepPoints.Value = value;
	}

	/// <summary>
	/// Volume for the very first pair of orders.
	/// </summary>
	public decimal FirstVolume
	{
		get => _firstVolume.Value;
		set => _firstVolume.Value = value;
	}

	/// <summary>
	/// Volume increment applied to each newly stacked order.
	/// </summary>
	public decimal IncrementVolume
	{
		get => _incrementVolume.Value;
		set => _incrementVolume.Value = value;
	}

	/// <summary>
	/// Floating profit target in account currency that closes all positions.
	/// </summary>
	public decimal MinProfit
	{
		get => _minProfit.Value;
		set => _minProfit.Value = value;
	}

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

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public WaddahAttarWinGridStrategy()
	{
		_stepPoints = Param(nameof(StepPoints), 1500)
			.SetGreaterThanZero()
			.SetDisplay("Step (Points)", "Distance between grid levels in points", "Grid")
			.SetOptimize(20, 400, 10);

		_firstVolume = Param(nameof(FirstVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("First Volume", "Volume for the initial orders", "Trading");

		_incrementVolume = Param(nameof(IncrementVolume), 0m)
			.SetDisplay("Increment Volume", "Additional volume added when stacking new orders", "Trading");

		_minProfit = Param(nameof(MinProfit), 450m)
			.SetNotNegative()
			.SetDisplay("Min Profit", "Floating profit target in account currency", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle type for price data", "General");
	}

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

		_lastBuyGridPrice = 0m;
		_lastSellGridPrice = 0m;
		_currentBuyVolume = 0m;
		_currentSellVolume = 0m;
		_referenceBalance = 0m;
		_gridActive = false;
	}

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

		_referenceBalance = Portfolio?.CurrentValue ?? 0m;

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var priceStep = Security?.PriceStep ?? 0.01m;
		if (priceStep <= 0m)
			priceStep = 0.01m;

		var stepOffset = StepPoints * priceStep;
		if (stepOffset <= 0m)
			return;

		var price = candle.ClosePrice;

		// Check profit target
		var floatingProfit = (Portfolio?.CurrentValue ?? 0m) - _referenceBalance;
		if (MinProfit > 0m && floatingProfit >= MinProfit && _gridActive)
		{
			if (Position > 0)
				SellMarket(Position);
			else if (Position < 0)
				BuyMarket(Math.Abs(Position));

			_referenceBalance = Portfolio?.CurrentValue ?? _referenceBalance;
			_gridActive = false;
			_lastBuyGridPrice = 0m;
			_lastSellGridPrice = 0m;
			_currentBuyVolume = 0m;
			_currentSellVolume = 0m;
			return;
		}

		// Initialize grid on first candle
		if (!_gridActive)
		{
			_lastBuyGridPrice = price;
			_lastSellGridPrice = price;
			_currentBuyVolume = FirstVolume;
			_currentSellVolume = FirstVolume;
			_gridActive = true;
			_referenceBalance = Portfolio?.CurrentValue ?? _referenceBalance;
			return;
		}

		// Check if price dropped enough to trigger a buy grid level
		if (price <= _lastBuyGridPrice - stepOffset)
		{
			BuyMarket(_currentBuyVolume);
			_lastBuyGridPrice = price;
			_currentBuyVolume += IncrementVolume;
		}

		// Check if price rose enough to trigger a sell grid level
		if (price >= _lastSellGridPrice + stepOffset)
		{
			SellMarket(_currentSellVolume);
			_lastSellGridPrice = price;
			_currentSellVolume += IncrementVolume;
		}
	}
}