GitHub で見る

ジグザグ エフゲトロフィ 1 戦略

概要

ZigZag EvgeTrofi 1 は、最新の ZigZag スイング ポイントに反応するオリジナルの MetaTrader エキスパート アドバイザーの動作を再現します。この戦略は、完了したすべてのローソク足を監視し、古典的な深さ、偏差、バックステップ構成を使用して最も新鮮なジグザグ ピボットを特定し、ピボットがまだ新しい場合は市場に参入します。スイングが高いとロングポジションがトリガーされ、スイングが低いとショートポジションがオープンし、元の EA シグナルマップと一致します。

取引ロジック

  • 設定されたローソク足タイプをサブスクライブし、長さが ZigZag 深さパラメーターと一致する最高値/最低値インジケーターをフィードします。このペアのインジケーターは、カスタム バッファーに依存せずにネイティブの ZigZag スイング検出をエミュレートします。
  • ローソク足が閉じるとき、その高値が追跡された最大値に接触しているか、またはその安値が追跡されている最小値に接触しているかを確認します。価格ステップの必要な偏差が満たされ、バックステップ距離 (反対側のピボット間の最小バー) が尊重される場合にのみ、新しいピボットに切り替えます。
  • ピボットが記録されたら、通過したバーの数を数え続けます。緊急度パラメーターは、ピボット後のバーがまだアクション可能であるとみなされるバーの数を定義します。この制限より古い信号は無視され、遅れて入力されるのを防ぎます。
  • 高いピボットの場合、戦略は買いの準備をし、低いピボットの場合、売りの準備をします。オープンポジションがすでに意図した方向と一致している場合、シグナルは処理済みとしてマークされ、追加の注文は送信されません。
  • アカウントが現在反対方向のエクスポージャーを保持している場合、戦略は新しい取引を開始する前にフラット化する成行注文を送信します。その後、構成された数量で成行注文を直ちに送信し、新しいポジションを確立します。
  • すべてのアクションには、完全に形成されたインジケーターの状態、完成したローソク足、およびプラスの取引高が必要です。この戦略は、市場とやり取りする前に、IsFormedAndOnlineAndAllowTrading() を使用して接続と権限をチェックし、健全な取引条件下でのみ注文が送信されるようにします。

パラメーター

名前 説明 デフォルト
Depth スイング検出ウィンドウを定義するジグザグの深さ。 17
Deviation 同じタイプのピボットを確認するために必要なポイント単位の最小価格変動。内部で商品の価格ステップに変換されます。 7
Backstep 反対側のピボットに切り替える前に通過する必要があるバーの最小数。 5
Urgency ピボット後のトレードが許可されるバーの最大数。 2
Candle Type 計算に使用されるローソク足のデータ タイプ (タイム フレームまたはカスタム集計)。 5分の時間枠
Volume すべてのエントリーで提出された成行注文の量。 0.1

実装メモ

  • 最高値/最低値インジケーターは高レベルの SubscribeCandles().Bind() API を介してバインドされているため、戦略は最終ローソク足のみで動作し、手動バッファリングを回避します。
  • 偏差パラメータは、商品価格ステップを使用して絶対価格差に変換されます。シンボルに価格ステップのメタデータがない場合は、値 1 がフォールバックとして使用され、取引所全体でロジックの一貫性が維持されます。
  • ブール ガードはピボットごとの重複取引を防止し、スイングごとに 1 回のみ動作する MetaTrader EA の動作と一致します。
  • 組み込みのチャート統合により、チャートが利用可能な場合はローソク足が描画され、取引が自動的に実行されるため、スイング ポイントとエントリーを視覚的に検証するのに役立ちます。
  • ポジション管理は対称的です。反対のエクスポージャは、新しい取引を確立する前に同量の成行注文でフラット化され、元のエキスパートアドバイザーと同様にポートフォリオを片面に保ちます。
namespace StockSharp.Samples.Strategies;

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;

/// <summary>
/// ZigZag swing strategy that reproduces the ZigZagEvgeTrofi 1 expert advisor.
/// Enters when a fresh ZigZag turning point appears within a limited number of bars.
/// </summary>
public class ZigZagEvgeTrofi1Strategy : Strategy
{
	private enum PivotTypes
	{
		None,
		High,
		Low
	}

	private readonly StrategyParam<int> _depth;
	private readonly StrategyParam<decimal> _deviation;
	private readonly StrategyParam<int> _backstep;
	private readonly StrategyParam<int> _urgency;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _volume;

	private Highest _highest;
	private Lowest _lowest;
	private PivotTypes _pivotType;
	private decimal _pivotPrice;
	private int _barsSincePivot;
	private bool _signalHandled;
	private decimal _priceStep;

	/// <summary>
	/// ZigZag depth parameter identical to the original expert advisor.
	/// </summary>
	public int Depth
	{
		get => _depth.Value;
		set => _depth.Value = value;
	}

	/// <summary>
	/// Minimum deviation expressed in points that confirms a new swing.
	/// </summary>
	public decimal Deviation
	{
		get => _deviation.Value;
		set => _deviation.Value = value;
	}

	/// <summary>
	/// Minimum bars required before switching to an opposite pivot.
	/// </summary>
	public int Backstep
	{
		get => _backstep.Value;
		set => _backstep.Value = value;
	}

	/// <summary>
	/// Number of bars after a pivot during which entries are allowed.
	/// </summary>
	public int Urgency
	{
		get => _urgency.Value;
		set => _urgency.Value = value;
	}

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

	/// <summary>
	/// Volume applied to each market order.
	/// </summary>
	public decimal VolumePerTrade
	{
		get => _volume.Value;
		set => _volume.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="ZigZagEvgeTrofi1Strategy"/> class.
	/// </summary>
	public ZigZagEvgeTrofi1Strategy()
	{
		_depth = Param(nameof(Depth), 17)
			.SetGreaterThanZero()
			.SetDisplay("Depth", "ZigZag depth parameter", "ZigZag")
			
			.SetOptimize(5, 40, 1);

		_deviation = Param(nameof(Deviation), 7m)
			.SetGreaterThanZero()
			.SetDisplay("Deviation", "Minimum price movement in points", "ZigZag")
			
			.SetOptimize(1m, 20m, 1m);

		_backstep = Param(nameof(Backstep), 5)
			.SetNotNegative()
			.SetDisplay("Backstep", "Bars to wait before switching pivots", "ZigZag")
			
			.SetOptimize(0, 10, 1);

		_urgency = Param(nameof(Urgency), 2)
			.SetNotNegative()
			.SetDisplay("Urgency", "Maximum bars to trade the latest pivot", "Trading")
			
			.SetOptimize(0, 5, 1);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for analysis", "General");

		_volume = Param(nameof(VolumePerTrade), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Volume", "Order volume per trade", "Trading");
	}

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

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

		_highest = null;
		_lowest = null;
		_pivotType = PivotTypes.None;
		_pivotPrice = 0m;
		_barsSincePivot = int.MaxValue;
		_signalHandled = true;
		_priceStep = 0m;
	}

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

		_priceStep = GetEffectivePriceStep();
		_highest = new Highest { Length = Depth };
		_lowest = new Lowest { Length = Depth };

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

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

	private void ProcessCandle(ICandleMessage candle, decimal highestValue, decimal lowestValue)
	{
		// React only on completed candles to mirror the original bar-by-bar logic.
		if (candle.State != CandleStates.Finished)
			return;

		// Ensure both indicators collected enough data before generating signals.
		if (_highest == null || _lowest == null || !_highest.IsFormed || !_lowest.IsFormed)
			return;

		// Track how many bars have passed since the latest identified pivot.
		if (_pivotType != PivotTypes.None && _barsSincePivot < int.MaxValue)
			_barsSincePivot++;

		var deviationPrice = GetDeviationPrice();
		var canSwitch = _pivotType == PivotTypes.None || _barsSincePivot >= Backstep;

		// Detect a new swing high whenever price matches the tracked maximum.
		if (candle.HighPrice >= highestValue && highestValue > 0m)
		{
			var difference = candle.HighPrice - _pivotPrice;
			if ((_pivotType != PivotTypes.High && canSwitch) || (_pivotType == PivotTypes.High && difference >= deviationPrice))
				SetPivot(PivotTypes.High, candle.HighPrice);
		}
		// Detect a new swing low whenever price touches the tracked minimum.
		else if (candle.LowPrice <= lowestValue && lowestValue > 0m)
		{
			var difference = _pivotPrice - candle.LowPrice;
			if ((_pivotType != PivotTypes.Low && canSwitch) || (_pivotType == PivotTypes.Low && difference >= deviationPrice))
				SetPivot(PivotTypes.Low, candle.LowPrice);
		}

		// Stop if no pivot is available after the checks above.
		if (_pivotType == PivotTypes.None)
			return;

		// Skip if the pivot is already considered stale by the urgency filter.
		if (_barsSincePivot > Urgency)
			return;

		// Avoid firing multiple orders for the same pivot.
		if (_signalHandled)
			return;

		// Confirm that trading permissions and connections are valid.
		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var volume = VolumePerTrade;
		if (volume <= 0m)
		{
			_signalHandled = true;
			return;
		}

		var isBuySignal = _pivotType == PivotTypes.High;

		// Do nothing if a position in the same direction is already open.
		if (isBuySignal)
		{
			if (Position > 0m)
			{
				_signalHandled = true;
				return;
			}
		}
		else
		{
			if (Position < 0m)
			{
				_signalHandled = true;
				return;
			}
		}

		// Close existing exposure in the opposite direction before reversing.
		if (isBuySignal)
		{
			if (Position < 0m)
			{
				var closeVolume = Math.Abs(Position);
				if (closeVolume > 0m)
					BuyMarket(closeVolume);
			}
			BuyMarket(volume);
		}
		else
		{
			if (Position > 0m)
			{
				var closeVolume = Math.Abs(Position);
				if (closeVolume > 0m)
					SellMarket(closeVolume);
			}
			SellMarket(volume);
		}

		_signalHandled = true;
	}

	// Update the internal pivot state when a new turning point is registered.
	private void SetPivot(PivotTypes type, decimal price)
	{
		_pivotType = type;
		_pivotPrice = price;
		_barsSincePivot = 0;
		_signalHandled = false;
	}

	// Convert the deviation parameter expressed in points to an absolute price move.
	private decimal GetDeviationPrice()
	{
		var step = _priceStep > 0m ? _priceStep : 1m;
		var deviation = Deviation;
		if (deviation <= 0m)
			return step;

		var value = deviation * step;
		return value >= step ? value : step;
	}

	// Determine the effective price step for transforming point-based inputs.
	private decimal GetEffectivePriceStep()
	{
		if (Security != null)
		{
			if (Security.PriceStep.HasValue && Security.PriceStep.Value > 0m)
				return Security.PriceStep.Value;
		}

		return 1m;
	}
}