Auf GitHub ansehen

Alligator-Trendstrategie

Die Strategie reproduziert das klassische Bill Williams Alligator-System aus dem originalen MetaTrader-Skript (Alligator.mq5). Sie verwendet drei geglättete gleitende Durchschnitte, die auf dem Median-Preis berechnet und vorwärts verschoben werden, um die Marktphase zu visualisieren. Eine Long-Position wird eröffnet, wenn die schnelle Lips-Linie über Teeth liegt und Teeth über Jaw. Eine Short-Position wird eröffnet, wenn die Ausrichtung umgekehrt ist. Nur eine Position kann gleichzeitig aktiv sein.

Sobald ein Trade aktiv ist, schützt die Strategie die Position mit einem Stop-Loss und Take-Profit in Pips. Wenn sich der Markt um eine konfigurierbare Null-Level-Distanz zugunsten des Trades bewegt, wird der Stop auf Break-even verschoben. Ein Trailing Stop folgt dem höchsten Hoch (bei Longs) oder tiefsten Tief (bei Shorts) mit einem Mindestschritt, um häufige Stop-Aktualisierungen zu vermeiden. Positionen werden geschlossen, wenn die Stop-Loss-, Trailing-Stop- oder Take-Profit-Level erreicht werden.

Die Standardkonfiguration zielt auf 30-Minuten-Kerzen und Forex-ähnliche Pip-Werte ab, aber die Parameter können für andere Märkte optimiert werden. Da die ursprüngliche MQL-Version die brokerspezifische Pip-Verarbeitung verwendet, basiert die Konvertierung auf dem PriceStep des Instruments, um Pip-Abstände in absolute Preise umzurechnen.

Handelsregeln

Einstieg

  • Long: Keine offene Position und Lips > Teeth > Jaw auf der letzten abgeschlossenen Kerze.
  • Short: Keine offene Position und Lips < Teeth < Jaw auf der letzten abgeschlossenen Kerze.

Ausstieg und Risikomanagement

  • Anfangs-Stop: Platziert StopLossPips unterhalb (Long) oder oberhalb (Short) des Ausführungspreises.
  • Take Profit: Platziert TakeProfitPips vom Ausführungspreis entfernt.
  • Null-Level: Wenn der Preis um ZeroLevelPips vorrückt, wird der Stop auf den Einstiegspreis verschoben.
  • Trailing Stop: Nach der Null-Level-Aktivierung folgt der Stop dem Extremwert mit TrailingStopPips und wird nur aktualisiert, wenn die Verbesserung TrailingStepPips überschreitet.
  • Positionen werden sofort aufgelöst, wenn ein Stop oder das Take-Profit-Level auf Kerzendaten berührt wird.

Parameter

Parameter Standard Beschreibung
CandleType 30-Minuten-Zeitrahmen Kerzenserie für Indikatorberechnungen und Signalauswertung.
JawLength 13 Geglättete Moving-Average-Periode für die blaue Jaw-Linie.
TeethLength 8 Geglättete Moving-Average-Periode für die rote Teeth-Linie.
LipsLength 5 Geglättete Moving-Average-Periode für die grüne Lips-Linie.
JawShift 8 Vorwärtsverschiebung der Jaw-Linie, in Bars angegeben.
TeethShift 5 Vorwärtsverschiebung der Teeth-Linie, in Bars angegeben.
LipsShift 3 Vorwärtsverschiebung der Lips-Linie, in Bars angegeben.
EnableLong true Erlaubt oder sperrt Long-Einstiege.
EnableShort true Erlaubt oder sperrt Short-Einstiege.
StopLossPips 45 Stop-Loss-Abstand in Pips vom Ausführungspreis.
TakeProfitPips 145 Take-Profit-Abstand in Pips vom Ausführungspreis.
ZeroLevelPips 30 Abstand in Pips, der erforderlich ist, um den Stop auf Break-even zu verschieben.
TrailingStopPips 50 Abstand zwischen dem aktuellen Extremwert und dem Trailing Stop.
TrailingStepPips 10 Minimale Pip-Verbesserung, die vor der Aktualisierung des Trailing Stops erforderlich ist.

Hinweise

  • Der Alligator-Indikator wird auf dem Median-Preis (High + Low) / 2 berechnet, um der MetaTrader-Implementierung zu entsprechen.
  • Verschobene Linienwerte werden mit internen Puffern emuliert, so dass Vergleiche dieselben verschobenen Daten wie das Originalskript verwenden.
  • Die Strategie geht davon aus, dass ein Trade ausgeführt wird, bevor ein neues Signal auf derselben Bar verarbeitet wird, entsprechend der Bar-für-Bar-Ausführung des Quell-EAs.
  • Optimieren Sie die Pip-Abstände entsprechend der Tick-Größe und Volatilität des gehandelten Instruments.
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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Classic Bill Williams Alligator strategy with stop management rules.
/// </summary>
/// <remarks>
/// The strategy opens a long position when the Lips, Teeth, and Jaw lines are aligned upward
/// and opens a short position when they are aligned downward. Once in a trade the algorithm
/// applies the zero level rule to move the stop to break-even, updates a trailing stop with a
/// configurable step, and closes the position at the stop or take-profit levels.
/// </remarks>
public class AlligatorTrendStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _jawLength;
	private readonly StrategyParam<int> _teethLength;
	private readonly StrategyParam<int> _lipsLength;
	private readonly StrategyParam<int> _jawShift;
	private readonly StrategyParam<int> _teethShift;
	private readonly StrategyParam<int> _lipsShift;
	private readonly StrategyParam<bool> _enableLong;
	private readonly StrategyParam<bool> _enableShort;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _zeroLevelPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _trailingStepPips;

	private readonly List<decimal> _jawBuffer = new();
	private readonly List<decimal> _teethBuffer = new();
	private readonly List<decimal> _lipsBuffer = new();

	private decimal _entryPrice;

	private decimal? _longStop;
	private decimal? _longTake;
	private bool _longBreakevenActivated;
	private decimal _longBestPrice;

	private decimal? _shortStop;
	private decimal? _shortTake;
	private bool _shortBreakevenActivated;
	private decimal _shortBestPrice;

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

	/// <summary>
	/// Jaw length.
	/// </summary>
	public int JawLength
	{
		get => _jawLength.Value;
		set => _jawLength.Value = value;
	}

	/// <summary>
	/// Teeth length.
	/// </summary>
	public int TeethLength
	{
		get => _teethLength.Value;
		set => _teethLength.Value = value;
	}

	/// <summary>
	/// Lips length.
	/// </summary>
	public int LipsLength
	{
		get => _lipsLength.Value;
		set => _lipsLength.Value = value;
	}

	/// <summary>
	/// Forward shift applied to the jaw line.
	/// </summary>
	public int JawShift
	{
		get => _jawShift.Value;
		set => _jawShift.Value = value;
	}

	/// <summary>
	/// Forward shift applied to the teeth line.
	/// </summary>
	public int TeethShift
	{
		get => _teethShift.Value;
		set => _teethShift.Value = value;
	}

	/// <summary>
	/// Forward shift applied to the lips line.
	/// </summary>
	public int LipsShift
	{
		get => _lipsShift.Value;
		set => _lipsShift.Value = value;
	}

	/// <summary>
	/// Enable long trades.
	/// </summary>
	public bool EnableLong
	{
		get => _enableLong.Value;
		set => _enableLong.Value = value;
	}

	/// <summary>
	/// Enable short trades.
	/// </summary>
	public bool EnableShort
	{
		get => _enableShort.Value;
		set => _enableShort.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Distance to move the stop to break-even.
	/// </summary>
	public decimal ZeroLevelPips
	{
		get => _zeroLevelPips.Value;
		set => _zeroLevelPips.Value = value;
	}

	/// <summary>
	/// Distance between price extreme and trailing stop.
	/// </summary>
	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Minimum increment for trailing stop updates.
	/// </summary>
	public decimal TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="AlligatorTrendStrategy"/> class.
	/// </summary>
	public AlligatorTrendStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles used for calculations", "General");

		_jawLength = Param(nameof(JawLength), 13)
			.SetGreaterThanZero()
			.SetDisplay("Jaw Length", "Smoothed moving average period for the jaw", "Alligator")
			;

		_teethLength = Param(nameof(TeethLength), 8)
			.SetGreaterThanZero()
			.SetDisplay("Teeth Length", "Smoothed moving average period for the teeth", "Alligator")
			;

		_lipsLength = Param(nameof(LipsLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Lips Length", "Smoothed moving average period for the lips", "Alligator")
			;

		_jawShift = Param(nameof(JawShift), 8)
			.SetDisplay("Jaw Shift", "Forward shift applied to the jaw line", "Alligator")
			;

		_teethShift = Param(nameof(TeethShift), 5)
			.SetDisplay("Teeth Shift", "Forward shift applied to the teeth line", "Alligator")
			;

		_lipsShift = Param(nameof(LipsShift), 3)
			.SetDisplay("Lips Shift", "Forward shift applied to the lips line", "Alligator")
			;

		_enableLong = Param(nameof(EnableLong), true)
			.SetDisplay("Enable Long", "Allow long entries", "Trading");

		_enableShort = Param(nameof(EnableShort), true)
			.SetDisplay("Enable Short", "Allow short entries", "Trading");

		_stopLossPips = Param(nameof(StopLossPips), 500m)
			.SetDisplay("Stop Loss", "Stop-loss distance in pips", "Risk")
			;

		_takeProfitPips = Param(nameof(TakeProfitPips), 2000m)
			.SetDisplay("Take Profit", "Take-profit distance in pips", "Risk")
			;

		_zeroLevelPips = Param(nameof(ZeroLevelPips), 300m)
			.SetDisplay("Zero Level", "Distance to move stop to break-even", "Risk")
			;

		_trailingStopPips = Param(nameof(TrailingStopPips), 500m)
			.SetDisplay("Trailing Stop", "Trailing stop distance in pips", "Risk")
			;

		_trailingStepPips = Param(nameof(TrailingStepPips), 100m)
			.SetDisplay("Trailing Step", "Minimum trailing stop increment in pips", "Risk")
			;
	}

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

		_jawBuffer.Clear();
		_teethBuffer.Clear();
		_lipsBuffer.Clear();
		_entryPrice = 0m;
		ResetLong();
		ResetShort();
	}

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

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

		var jaw = new SmoothedMovingAverage { Length = JawLength };
		var teeth = new SmoothedMovingAverage { Length = TeethLength };
		var lips = new SmoothedMovingAverage { Length = LipsLength };

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

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

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

			var medianPrice = (candle.HighPrice + candle.LowPrice) / 2m;

			var inputValue = new DecimalIndicatorValue(jaw, medianPrice, candle.CloseTime) { IsFinal = true };
			var jawValue = jaw.Process(inputValue);
			var teethValue = teeth.Process(new DecimalIndicatorValue(teeth, medianPrice, candle.CloseTime) { IsFinal = true });
			var lipsValue = lips.Process(new DecimalIndicatorValue(lips, medianPrice, candle.CloseTime) { IsFinal = true });

			if (!jawValue.IsFormed || !teethValue.IsFormed || !lipsValue.IsFormed)
				return;

			var jawShifted = GetShiftedValue(_jawBuffer, jawValue.GetValue<decimal>(), JawShift);
			var teethShifted = GetShiftedValue(_teethBuffer, teethValue.GetValue<decimal>(), TeethShift);
			var lipsShifted = GetShiftedValue(_lipsBuffer, lipsValue.GetValue<decimal>(), LipsShift);

			if (!jawShifted.HasValue || !teethShifted.HasValue || !lipsShifted.HasValue)
				return;

			if (!jaw.IsFormed)
				return;

			if (ManagePosition(candle))
				return;

			var bullishAlignment = lipsShifted.Value > teethShifted.Value && teethShifted.Value > jawShifted.Value;
			var bearishAlignment = lipsShifted.Value < teethShifted.Value && teethShifted.Value < jawShifted.Value;

			if (Position == 0)
			{
				if (bullishAlignment && EnableLong)
				{
					BuyMarket();
				}
				else if (bearishAlignment && EnableShort)
				{
					SellMarket();
				}
			}
		}
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);

		var tradeMsg = trade.Trade;
		if (tradeMsg is null)
			return;
		var price = tradeMsg.Price;
		var direction = trade.Order.Side;

		var distanceToStop = StopLossPips > 0m ? GetPriceByPips(StopLossPips) : (decimal?)null;
		var distanceToTake = TakeProfitPips > 0m ? GetPriceByPips(TakeProfitPips) : (decimal?)null;

		if (direction == Sides.Buy)
		{
			if (Position > 0)
			{
				_entryPrice = price;
				_longStop = distanceToStop.HasValue ? price - distanceToStop.Value : null;
				_longTake = distanceToTake.HasValue ? price + distanceToTake.Value : null;
				_longBreakevenActivated = false;
				_longBestPrice = price;
			}
			else if (Position == 0)
			{
				ResetShort();
			}
		}
		else if (direction == Sides.Sell)
		{
			if (Position < 0)
			{
				_entryPrice = price;
				_shortStop = distanceToStop.HasValue ? price + distanceToStop.Value : null;
				_shortTake = distanceToTake.HasValue ? price - distanceToTake.Value : null;
				_shortBreakevenActivated = false;
				_shortBestPrice = price;
			}
			else if (Position == 0)
			{
				ResetLong();
			}
		}

		if (Position == 0)
		{
			ResetLong();
			ResetShort();
		}
	}

	private bool ManagePosition(ICandleMessage candle)
	{
		if (Position > 0)
		{
			var entryPrice = _entryPrice;
			if (entryPrice == 0m)
				return false;

			_longBestPrice = Math.Max(_longBestPrice, candle.HighPrice);

			if (_longTake.HasValue && candle.HighPrice >= _longTake.Value)
			{
				SellMarket();
				ResetLong();
				return true;
			}

			if (_longStop.HasValue && candle.LowPrice <= _longStop.Value)
			{
				SellMarket();
				ResetLong();
				return true;
			}

			if (ZeroLevelPips > 0m && !_longBreakevenActivated && _longStop.HasValue && entryPrice > _longStop.Value)
			{
				var zeroDistance = GetPriceByPips(ZeroLevelPips);
				if (_longBestPrice - entryPrice >= zeroDistance)
				{
					_longStop = entryPrice;
					_longBreakevenActivated = true;
				}
			}

			if (TrailingStopPips > 0m)
			{
				var trailingDistance = GetPriceByPips(TrailingStopPips);
				var step = GetPriceByPips(TrailingStepPips);
				var candidate = _longBestPrice - trailingDistance;

				if (!_longStop.HasValue || candidate - _longStop.Value >= step)
					_longStop = candidate;
			}
		}
		else if (Position < 0)
		{
			var entryPrice = _entryPrice;
			if (entryPrice == 0m)
				return false;

			_shortBestPrice = _shortBestPrice == 0m ? candle.LowPrice : Math.Min(_shortBestPrice, candle.LowPrice);

			if (_shortTake.HasValue && candle.LowPrice <= _shortTake.Value)
			{
				BuyMarket();
				ResetShort();
				return true;
			}

			if (_shortStop.HasValue && candle.HighPrice >= _shortStop.Value)
			{
				BuyMarket();
				ResetShort();
				return true;
			}

			if (ZeroLevelPips > 0m && !_shortBreakevenActivated && _shortStop.HasValue && entryPrice < _shortStop.Value)
			{
				var zeroDistance = GetPriceByPips(ZeroLevelPips);
				if (entryPrice - candle.LowPrice >= zeroDistance)
				{
					_shortStop = entryPrice;
					_shortBreakevenActivated = true;
				}
			}

			if (TrailingStopPips > 0m)
			{
				var trailingDistance = GetPriceByPips(TrailingStopPips);
				var step = GetPriceByPips(TrailingStepPips);
				var candidate = _shortBestPrice + trailingDistance;

				if (!_shortStop.HasValue || _shortStop.Value - candidate >= step)
					_shortStop = candidate;
			}
		}
		else
		{
			ResetLong();
			ResetShort();
		}

		return false;
	}

	private static decimal? GetShiftedValue(List<decimal> buffer, decimal value, int shift)
	{
		if (shift <= 0)
			return value;

		buffer.Add(value);

		if (buffer.Count <= shift)
			return null;

		var result = buffer[0];
		try { buffer.RemoveAt(0); } catch { }
		return result;
	}

	private decimal GetPriceByPips(decimal pips)
	{
		if (pips <= 0m)
			return 0m;

		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
			step = 1m;

		return pips * step;
	}

	private void ResetLong()
	{
		_longStop = null;
		_longTake = null;
		_longBreakevenActivated = false;
		_longBestPrice = 0m;
	}

	private void ResetShort()
	{
		_shortStop = null;
		_shortTake = null;
		_shortBreakevenActivated = false;
		_shortBestPrice = 0m;
	}
}