GitHub で見る

ダブル ZigZag アライメント戦略

この戦略は、MQL5 エキスパート「Double ZigZag」の StockSharp 移植版です。異なるルックバック期間を持つ2つのスイング検出器を組み合わせて、 デュアル ZigZag の確認ロジックを再現します。両方の検出器が3つの連続したピボットで一致し、最新のスイングが以前のものと比較して 十分な強さを示した場合にのみトレードが発動します。

コンセプト

  • 高速スイング検出器は、移動最高値/最低値ウィンドウを使用して元の ZigZag(13, 5, 3) 設定を近似します。
  • 低速スイング検出器はより長いウィンドウ(デフォルト x8)を使用して主要な転換点を確認します。
  • 両方の検出器が同じローソク足で方向を反転したとき、「アライメント」ピボットが前回のアライメント以降に発生した 高速スイングの数とともに記録されます。これらのカウンターはソース EA の up および dw カウンターの直接的な類似物です。

ロング セットアップ

  1. 最新のアライメントはスイング高値、前のアライメントはスイング安値、その前もスイング高値です。
  2. 最新のアライメント以降に蓄積された高速スイングの数が、前のセグメントカウントに StrengthMultiplier(デフォルト 2.1)を 掛けた値より大きいこと。これは元の条件 up > dw * k をエミュレートします。
  3. 最新のスイング高値が中間のスイング安値を古い高値よりも積極的に上回ること、つまり (previousHigh - swingLow) * BreakoutMultiplier < (newestHigh - swingLow)(同じデフォルト乗数 2.1)。
  4. すべての条件が満たされると、戦略は設定された Volume に未決済のショートポジション分を加えたボリュームを買い、 ネットポジションがロングになるようにします。

ショート セットアップ

  1. 最新のアライメントはスイング安値、前のアライメントはスイング高値、その前も別のスイング安値です。
  2. 最新のセグメントカウントが前のカウントを StrengthMultiplier で割った値より小さいこと(変換された確認 up * k < dw)。
  3. 現在のスイング安値が BreakoutMultiplier を使用して、古い安値よりも積極的に中間のスイング高値を下回ること。
  4. 戦略は既存のロングポジションを閉じてネットショートポジションを確立するのに十分なボリュームを売ります。

ポジション管理

  • シグナルは相互排他的です。新しいロングは自動的にすべてのショートを閉じ、逆も同様です。
  • ストップロスやテイクプロフィット注文はありません。ポジションは反対のアライメントシグナルが現れるまで保持されます。
  • 戦略は CandleType で指定されたローソク足タイプ(デフォルト: 1分足時間軸)で動作します。

デフォルト値

  • FastLength = 13
  • SlowLength = 104
  • StrengthMultiplier = 2.1
  • BreakoutMultiplier = 2.1
  • CandleType = TimeSpan.FromMinutes(1) 時間軸

タグ

  • カテゴリ: トレンドフォロー / パターン認識
  • 方向: ロング/ショート
  • インジケーター: ZigZag(近似)、Highest/Lowest
  • ストップ: なし
  • 時間軸: デフォルトはイントラデイ
  • 複雑さ: 中級(同期したスイング追跡が必要)
  • 季節性: いいえ
  • ニューラルネットワーク: いいえ
  • ダイバージェンス: いいえ
  • リスクレベル: ストップなしの常時エクスポージャーによる中
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Double ZigZag alignment strategy. Uses fast and slow swing detectors
/// based on highest/lowest price lookbacks to find aligned pivot points and trade breakouts.
/// </summary>
public class DoubleZigZagStrategy : Strategy
{
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();

	private int _fastDirection;
	private int _slowDirection;
	private decimal _lastFastPivotHigh;
	private decimal _lastFastPivotLow;

	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

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

	public DoubleZigZagStrategy()
	{
		_fastLength = Param(nameof(FastLength), 8)
			.SetDisplay("Fast Length", "Lookback for the fast swing detector", "Indicators");
		_slowLength = Param(nameof(SlowLength), 30)
			.SetDisplay("Slow Length", "Lookback for the slow confirmation swing", "Indicators");
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to analyze", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_highs.Clear();
		_lows.Clear();
		_fastDirection = 0;
		_slowDirection = 0;
		_lastFastPivotHigh = 0;
		_lastFastPivotLow = 0;
	}

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

		_highs.Clear();
		_lows.Clear();
		_fastDirection = 0;
		_slowDirection = 0;
		_lastFastPivotHigh = 0;
		_lastFastPivotLow = 0;

		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;

		_highs.Add(candle.HighPrice);
		_lows.Add(candle.LowPrice);

		var maxBuf = Math.Max(FastLength, SlowLength) + 1;
		if (_highs.Count > maxBuf)
		{
			_highs.RemoveAt(0);
			_lows.RemoveAt(0);
		}

		if (_highs.Count < SlowLength)
			return;

		// Calculate fast and slow highest/lowest
		var fastHighest = GetMax(_highs, FastLength);
		var fastLowest = GetMin(_lows, FastLength);
		var slowHighest = GetMax(_highs, SlowLength);
		var slowLowest = GetMin(_lows, SlowLength);

		var prevFastDir = _fastDirection;
		var prevSlowDir = _slowDirection;

		// Detect fast swing pivot
		if (_fastDirection <= 0 && candle.HighPrice >= fastHighest)
		{
			_fastDirection = 1;
			_lastFastPivotHigh = candle.HighPrice;
		}
		else if (_fastDirection >= 0 && candle.LowPrice <= fastLowest)
		{
			_fastDirection = -1;
			_lastFastPivotLow = candle.LowPrice;
		}

		// Detect slow swing pivot
		if (_slowDirection <= 0 && candle.HighPrice >= slowHighest)
			_slowDirection = 1;
		else if (_slowDirection >= 0 && candle.LowPrice <= slowLowest)
			_slowDirection = -1;

		// When both fast and slow pivot in the same direction, generate signal
		var fastFlippedUp = prevFastDir <= 0 && _fastDirection > 0;
		var fastFlippedDown = prevFastDir >= 0 && _fastDirection < 0;
		var slowFlippedUp = prevSlowDir <= 0 && _slowDirection > 0;
		var slowFlippedDown = prevSlowDir >= 0 && _slowDirection < 0;

		if (fastFlippedUp && (slowFlippedUp || _slowDirection > 0))
		{
			if (Position <= 0)
				BuyMarket();
		}
		else if (fastFlippedDown && (slowFlippedDown || _slowDirection < 0))
		{
			if (Position >= 0)
				SellMarket();
		}
	}

	private static decimal GetMax(List<decimal> data, int length)
	{
		var max = decimal.MinValue;
		var start = Math.Max(0, data.Count - length);
		for (var i = start; i < data.Count; i++)
		{
			if (data[i] > max)
				max = data[i];
		}
		return max;
	}

	private static decimal GetMin(List<decimal> data, int length)
	{
		var min = decimal.MaxValue;
		var start = Math.Max(0, data.Count - length);
		for (var i = start; i < data.Count; i++)
		{
			if (data[i] < min)
				min = data[i];
		}
		return min;
	}
}