GitHub で見る

トゥユル無修正戦略

概要

Tuyul 無修正は、元の MetaTrader 5 エキスパート アドバイザーを StockSharp の高レベルの API で再構築したスイングフォロー戦略です。システムはジグザグ スイングを観察し、移動平均トレンド フィルターでエントリーを調整し、最新レッグの 57% Fibonacci リトレースメントで指値注文を出します。価格がそのレベルに再び到達すると、この戦略は同じレッグから得られるストップロスとテイクプロフィットのレベルで取引を保護しながら、支配的なスイングに参加しようとします。

取引ロジック

  1. データの準備
    • 選択した Candle Type パラメータによって定義された 1 つのキャンドル サブスクリプション。
    • ジグザグ インジケーター (深さ/偏差/バックステップ) は、確認された最新のスイング ハイとスイングを追跡するために使用されます。
    • 高速 EMA と低速 EMA (デフォルトは 9/21) は指向性フィルターを提供します。
  2. 信号検出
    • ジグザグが新しいピボット (新しい高値または新しい安値のいずれか) を確認すると、戦略は最新のスイング ペアを更新します。
    • アクティブな注文がなく、オープンポジションがない場合は、以前の EMA の値がトレンドを決定します。
      • 低速 EMA を上回る高速 EMA → 強気のコンテキスト。
      • 高速 EMA が低速 EMA を下回っている → 弱気のコンテキスト。
  3. 注文の発注
    • 強気の状況では、戦略は最後のスイング安値とスイング高の間の 57% リトレースメントで 買い指値注文を配置します。
    • 弱気の状況では、スイング高からスイング低までの対称的な 57% リトレースメントで 売り指値注文を配置します。
    • ストップロスはジグザグの反対側の極端な位置に固定され、テイクプロフィットはストップ距離に Take Profit Multiplier を乗じた値に等しくなります (デフォルトは 1.2)。
    • Wait Bars After Signal キャンドルの注文は引き続き有効です。その後、新しい信号を待つためにキャンセルされます。
  4. ポジション管理
    • 注文が約定すると、戦略は後続のローソク足を監視します。価格が事前に定義されたストップロスまたはテイクプロフィットのいずれかに達すると、ロングポジションがクローズされます。同じミラーリングされたロジックがショートポジションにも適用されます。
    • 取引は特定の平日に限定できます。許可された日以外では、すべての保留中の注文は削除されますが、元のアドバイザーの動作に従い、既存のポジションはそのまま残されます。

パラメーター

名前 説明
Volume Per Trade エントリーごとに送信される注文量。
TP Multiplier テイクプロフィットオフセットを計算するために停止距離に適用される乗数。
ZigZag Depth スイングを確認するときに検査されるローソク足の数。
ZigZag Deviation ZigZag が新しいピボットを検証する前に必要な最小偏差 (ポイント単位)。
ZigZag Backstep 反対側のジグザグ ピボット間のローソク足の最小数。
Wait Bars After Signal キャンセルされる前に未決注文を維持するための最大キャンドル数。
Fast EMA トレンドフィルターとして使用される高速指数移動平均の期間。
Slow EMA トレンドフィルターとして使用される低速指数移動平均の期間。
Allow Monday … Allow Friday 個々の平日の取引を有効または無効にするトグル。
Candle Type すべてのインジケーターの計算と取引の決定に使用されるローソク足シリーズ。

注意事項

  • Fibonacci のリトレースメント率は、ソース EA と同様に 57% に固定されています。
  • ストップロスとテイクプロフィットのレベルはローソク足の終値で監視されます。閾値を超えるバー内スパイクは、次の評価で市場からの撤退を引き起こします。
  • この戦略は、元の実装を反映して、一度に 1 つの未決注文を保持します。
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;

/// <summary>
/// Strategy converted from the "Tuyul Uncensored" MetaTrader expert advisor.
/// It watches ZigZag swings, aligns with an EMA trend filter, and places Fibonacci retracement limit orders.
/// </summary>
public class TuyulUncensoredStrategy : Strategy
{
	private readonly StrategyParam<decimal> _volume;
	private readonly StrategyParam<decimal> _takeProfitMultiplier;
	private readonly StrategyParam<int> _zigZagDepth;
	private readonly StrategyParam<decimal> _zigZagDeviation;
	private readonly StrategyParam<int> _zigZagBackstep;
	private readonly StrategyParam<int> _waitBars;
	private readonly StrategyParam<int> _fastEmaPeriod;
	private readonly StrategyParam<int> _slowEmaPeriod;
	private readonly StrategyParam<bool> _allowMonday;
	private readonly StrategyParam<bool> _allowTuesday;
	private readonly StrategyParam<bool> _allowWednesday;
	private readonly StrategyParam<bool> _allowThursday;
	private readonly StrategyParam<bool> _allowFriday;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _fibLevel;

	private List<(DateTimeOffset Time, decimal Price)> _pivots;

	private ZigZag _zigZag;
	private ExponentialMovingAverage _fastEma;
	private ExponentialMovingAverage _slowEma;

	private decimal _lastZigZagHigh;
	private decimal _lastZigZagLow;

	private decimal? _previousFast;
	private decimal? _previousSlow;


	private decimal? _activeStop;
	private decimal? _activeTake;
	private int _activeDirection;

	/// <summary>
	/// Initializes a new instance of the <see cref="TuyulUncensoredStrategy"/> class.
	/// </summary>
	public TuyulUncensoredStrategy()
	{
		_volume = Param(nameof(VolumePerTrade), 0.03m)
		.SetDisplay("Volume", "Order volume per trade", "General")
		.SetGreaterThanZero();

		_takeProfitMultiplier = Param(nameof(TakeProfitMultiplier), 1.2m)
		.SetDisplay("TP Multiplier", "Take profit distance relative to stop loss", "Risk")
		.SetGreaterThanZero();

		_zigZagDepth = Param(nameof(ZigZagDepth), 12)
		.SetDisplay("ZigZag Depth", "Number of bars to evaluate for swings", "ZigZag")
		.SetGreaterThanZero();

		_zigZagDeviation = Param(nameof(ZigZagDeviation), 0.05m)
		.SetDisplay("ZigZag Deviation", "Minimum deviation in points to confirm a swing", "ZigZag")
		.SetNotNegative();

		_zigZagBackstep = Param(nameof(ZigZagBackstep), 3)
		.SetDisplay("ZigZag Backstep", "Bars required between opposite pivots", "ZigZag")
		.SetNotNegative();

		_waitBars = Param(nameof(WaitBarsAfterSignal), 12)
		.SetDisplay("Wait Bars", "Candles to keep the pending order before cancelling", "Trading")
		.SetNotNegative();

		_fastEmaPeriod = Param(nameof(FastEmaPeriod), 9)
		.SetDisplay("Fast EMA", "Period of the fast EMA filter", "Trend")
		.SetGreaterThanZero();

		_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 21)
		.SetDisplay("Slow EMA", "Period of the slow EMA filter", "Trend")
		.SetGreaterThanZero();

		_allowMonday = Param(nameof(AllowMonday), true)
		.SetDisplay("Allow Monday", "Enable trading on Monday", "Schedule");

		_allowTuesday = Param(nameof(AllowTuesday), true)
		.SetDisplay("Allow Tuesday", "Enable trading on Tuesday", "Schedule");

		_allowWednesday = Param(nameof(AllowWednesday), true)
		.SetDisplay("Allow Wednesday", "Enable trading on Wednesday", "Schedule");

		_allowThursday = Param(nameof(AllowThursday), true)
		.SetDisplay("Allow Thursday", "Enable trading on Thursday", "Schedule");

		_allowFriday = Param(nameof(AllowFriday), true)
		.SetDisplay("Allow Friday", "Enable trading on Friday", "Schedule");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Type of candles used for analysis", "General");

		_fibLevel = Param(nameof(FibLevel), 0.57m)
		.SetDisplay("Fibonacci Level", "Retracement level used to position pending orders", "Trading")
		.SetRange(0m, 1m);
	}

	/// <summary>
	/// Volume traded on each entry signal.
	/// </summary>
	public decimal VolumePerTrade
	{
		get => _volume.Value;
		set => _volume.Value = value;
	}

	/// <summary>
	/// Take profit multiplier relative to the stop distance.
	/// </summary>
	public decimal TakeProfitMultiplier
	{
		get => _takeProfitMultiplier.Value;
		set => _takeProfitMultiplier.Value = value;
	}

	/// <summary>
	/// ZigZag depth parameter.
	/// </summary>
	public int ZigZagDepth
	{
		get => _zigZagDepth.Value;
		set => _zigZagDepth.Value = value;
	}

	/// <summary>
	/// ZigZag deviation parameter expressed in points.
	/// </summary>
	public decimal ZigZagDeviation
	{
		get => _zigZagDeviation.Value;
		set => _zigZagDeviation.Value = value;
	}

	/// <summary>
	/// ZigZag backstep parameter.
	/// </summary>
	public int ZigZagBackstep
	{
		get => _zigZagBackstep.Value;
		set => _zigZagBackstep.Value = value;
	}

	/// <summary>
	/// Number of candles to keep a pending order active.
	/// </summary>
	public int WaitBarsAfterSignal
	{
		get => _waitBars.Value;
		set => _waitBars.Value = value;
	}

	/// <summary>
	/// Period of the fast EMA filter.
	/// </summary>
	public int FastEmaPeriod
	{
		get => _fastEmaPeriod.Value;
		set => _fastEmaPeriod.Value = value;
	}

	/// <summary>
	/// Period of the slow EMA filter.
	/// </summary>
	public int SlowEmaPeriod
	{
		get => _slowEmaPeriod.Value;
		set => _slowEmaPeriod.Value = value;
	}

	/// <summary>
	/// Determines whether Monday is tradable.
	/// </summary>
	public bool AllowMonday
	{
		get => _allowMonday.Value;
		set => _allowMonday.Value = value;
	}

	/// <summary>
	/// Determines whether Tuesday is tradable.
	/// </summary>
	public bool AllowTuesday
	{
		get => _allowTuesday.Value;
		set => _allowTuesday.Value = value;
	}

	/// <summary>
	/// Determines whether Wednesday is tradable.
	/// </summary>
	public bool AllowWednesday
	{
		get => _allowWednesday.Value;
		set => _allowWednesday.Value = value;
	}

	/// <summary>
	/// Determines whether Thursday is tradable.
	/// </summary>
	public bool AllowThursday
	{
		get => _allowThursday.Value;
		set => _allowThursday.Value = value;
	}

	/// <summary>
	/// Determines whether Friday is tradable.
	/// </summary>
	public bool AllowFriday
	{
		get => _allowFriday.Value;
		set => _allowFriday.Value = value;
	}

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

	/// <summary>
	/// Fibonacci retracement level used to place pending orders.
	/// </summary>
	public decimal FibLevel
	{
		get => _fibLevel.Value;
		set => _fibLevel.Value = value;
	}

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

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

		_pivots = null;
		_zigZag = null;
		_fastEma = null;
		_slowEma = null;
		_lastZigZagHigh = 0m;
		_lastZigZagLow = 0m;

		_previousFast = null;
		_previousSlow = null;

		_activeStop = null;
		_activeTake = null;
		_activeDirection = 0;
	}

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

		_pivots = new List<(DateTimeOffset Time, decimal Price)>();

		_zigZag = new ZigZag
		{
			Deviation = ZigZagDeviation
		};

		_fastEma = new ExponentialMovingAverage { Length = FastEmaPeriod };
		_slowEma = new ExponentialMovingAverage { Length = SlowEmaPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(_zigZag, _fastEma, _slowEma, ProcessCandle)
		.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal zigZagValue, decimal fastEmaValue, decimal slowEmaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		UpdateActiveProtection(candle);

		var (newHigh, newLow) = UpdateZigZagState(candle, zigZagValue);

		if (Position == 0m && (newHigh || newLow) &&
		_previousFast.HasValue && _previousSlow.HasValue)
		{
			TryEnterMarket(_previousFast.Value, _previousSlow.Value);
		}

		_previousFast = fastEmaValue;
		_previousSlow = slowEmaValue;

		if (Position == 0m && _activeDirection != 0)
			ClearActiveProtection();
	}

	private void UpdateActiveProtection(ICandleMessage candle)
	{
		if (_activeDirection == 1 && Position > 0m && _activeStop.HasValue && _activeTake.HasValue)
		{
			if (candle.LowPrice <= _activeStop.Value || candle.HighPrice >= _activeTake.Value)
			{
				SellMarket(Position);
				ClearActiveProtection();
			}
		}
		else if (_activeDirection == -1 && Position < 0m && _activeStop.HasValue && _activeTake.HasValue)
		{
			if (candle.HighPrice >= _activeStop.Value || candle.LowPrice <= _activeTake.Value)
			{
				BuyMarket(-Position);
				ClearActiveProtection();
			}
		}
	}

	private void TryEnterMarket(decimal previousFast, decimal previousSlow)
	{
		var high = _lastZigZagHigh;
		var low = _lastZigZagLow;

		if (high <= 0m || low <= 0m || high <= low)
			return;

		var volume = VolumePerTrade;
		if (volume <= 0m)
			volume = Volume;
		if (volume <= 0m)
			return;

		if (previousFast > previousSlow)
		{
			var stopPrice = low;
			var fibPrice = low + (high - low) * FibLevel;
			var slDistance = fibPrice - stopPrice;
			if (slDistance <= 0m)
				return;

			var takePrice = fibPrice + slDistance * TakeProfitMultiplier;
			BuyMarket(volume);
			_activeStop = stopPrice;
			_activeTake = takePrice;
			_activeDirection = 1;
		}
		else if (previousFast < previousSlow)
		{
			var stopPrice = high;
			var fibPrice = high - (high - low) * FibLevel;
			var slDistance = stopPrice - fibPrice;
			if (slDistance <= 0m)
				return;

			var takePrice = fibPrice - slDistance * TakeProfitMultiplier;
			SellMarket(volume);
			_activeStop = stopPrice;
			_activeTake = takePrice;
			_activeDirection = -1;
		}
	}

	private (bool newHigh, bool newLow) UpdateZigZagState(ICandleMessage candle, decimal zigZagValue)
	{
		var newHigh = false;
		var newLow = false;

		if (zigZagValue == 0m)
			return (false, false);

		var index = _pivots.FindIndex(p => p.Time == candle.OpenTime);
		if (index >= 0)
		{
			if (_pivots[index].Price == zigZagValue)
				return (false, false);

			_pivots[index] = (candle.OpenTime, zigZagValue);
		}
		else
		{
			_pivots.Add((candle.OpenTime, zigZagValue));
			if (_pivots.Count > 300)
				_pivots.RemoveAt(0);
		}

		if (_pivots.Count < 2)
			return (false, false);

		var previous = _pivots[^2];
		var last = _pivots[^1];
		var isHigh = last.Price > previous.Price;

		if (isHigh)
		{
			if (_lastZigZagHigh != last.Price)
			{
				_lastZigZagHigh = last.Price;
				newHigh = true;
			}

			if (_lastZigZagLow != previous.Price)
			{
				_lastZigZagLow = previous.Price;
				newLow = true;
			}
		}
		else
		{
			if (_lastZigZagLow != last.Price)
			{
				_lastZigZagLow = last.Price;
				newLow = true;
			}

			if (_lastZigZagHigh != previous.Price)
			{
				_lastZigZagHigh = previous.Price;
				newHigh = true;
			}
		}

		return (newHigh, newLow);
	}

	private bool IsTradingDayAllowed(DayOfWeek day)
	{
		return day switch
		{
			DayOfWeek.Monday => AllowMonday,
			DayOfWeek.Tuesday => AllowTuesday,
			DayOfWeek.Wednesday => AllowWednesday,
			DayOfWeek.Thursday => AllowThursday,
			DayOfWeek.Friday => AllowFriday,
			_ => false,
		};
	}


	private void ClearActiveProtection()
	{
		_activeStop = null;
		_activeTake = null;
		_activeDirection = 0;
	}
}