Auf GitHub ansehen

ATR Step-Trader-Strategie

Überblick

Die ATR Step Trader-Strategie ist eine direkte Portierung des MetaTrader5-Expertenberaters atrTrader.mq5. Es kombiniert einen Filter für den schnellen/langsamen gleitenden Durchschnitt mit Breakout- und Pyramiding-Regeln, die auf dem Average True Range (ATR) basieren. Der Port behält den balkengesteuerten Workflow des ursprünglichen EA bei: Es werden nur abgeschlossene Kerzen verarbeitet, der schnelle SMA muss für eine feste Anzahl von Balken über oder unter dem langsamen SMA liegen und jede Entscheidung ist an ATR-Vielfache verankert, um die Abstände zwischen den Märkten zu normalisieren.

Indikatoren und Daten

  • Einfache gleitende Durchschnitte (SMA). Zwei gleitende Durchschnitte (FastPeriod und SlowPeriod) definieren den primären Trendfilter. Beides wird auf die Abo-Kerzenserie angewendet.
  • Average True Range (ATR). Ein AverageTrueRange-Indikator (AtrPeriod) wandelt die Volatilität in Preisabstände um. Jede Breakout-, Add-on- und Stop-Berechnung verwendet ATR Vielfache.
  • Höchst-/Tiefstpreiskanäle. Die Indikatoren Highest und Lowest verfolgen das extreme Hoch und Tief der letzten MomentumPeriod-Kerzen. Sie ersetzen die iHighest/iLowest-Aufrufe aus dem MQL-Code.
  • Zeitrahmen. Der Standardkerzentyp ist eine Stunde (TimeSpan.FromHours(1)) und spiegelt das PERIOD_CURRENT-Verhalten des ursprünglichen Skripts wider. Sie können zu einem anderen Zeitrahmen wechseln, indem Sie den Parameter CandleType bearbeiten.

Eingabelogik

  1. Warten Sie, bis die Kerze fertig ist. Unvollendete Kerzen werden ignoriert, um mit dem MT5 OnTick + iTime Guard synchron zu bleiben.
  2. Aktualisieren Sie die Streak-Zähler für den gleitenden Durchschnitt. Ein Aufwärtstrend nimmt zu, wenn der schnelle SMA über dem langsamen SMA liegt; Ein Abwärtstrend nimmt zu, wenn er unten angezeigt wird. Gemischte Messwerte setzen den gegenteiligen Trend zurück.
  3. Sobald der Aufwärtstrend MomentumPeriod erreicht, prüfen Sie, ob der Schlusskurs immer noch um mindestens StepMultiplier * ATR unter dem jüngsten Hoch liegt. Wenn ja, kaufen Sie auf dem Markt.
  4. Sobald der Abwärtstrend MomentumPeriod erreicht, prüfen Sie, ob der Schlusskurs immer noch mindestens StepMultiplier * ATR über dem jüngsten Tief liegt. Wenn ja, verkaufen Sie es auf dem Markt.
  5. Jede neue Position initialisiert den Richtungszustand: Die Strategie merkt sich die höchsten und niedrigsten gefüllten Preise pro Seite, sodass spätere Pyramiden Referenzanker haben. Mit der ersten Order ist auch ein Stop in Volatilitätsgröße (StepMultiplier * StopMultiplier * ATR) verbunden.

Positionsmanagement

  • Pyramidenbildung: Während die Anzahl der aktiven Einträge unter PyramidLimit liegt, fügt die Strategie immer dann eine weitere Einheit hinzu, wenn sich der Preis um entweder +/- StepsMultiplier * ATR von der aktuellen Extremreferenz entfernt. Dies spiegelt das „Schritte“-Skalierungsraster von EA wider und funktioniert sowohl in günstige als auch in ungünstige Richtungen.
  • Schutzstopps: Der anfängliche Stopp für eine neue Order liegt StepMultiplier * StopMultiplier * ATR vom Ausführungspreis entfernt. Wenn die Pyramide voll ist, werden die Stopps auf StepMultiplier * ATR hinter (für Long-Positionen) oder vor (für Short-Positionen) des letzten Schlusskurses verschärft, wodurch die nachlaufende Aktualisierung von EA nachgeahmt wird, wenn drei Positionen offen sind.
  • Ungünstige Ausstiege: Wenn der Preis um StepsMultiplier * ATR über das verfolgte Extrem hinaus zurückgeht, verlässt die Strategie sofort alle Positionen auf dieser Seite mit einer Marktorder. Dies erfasst die EA-Logik, die den gesamten Stack verwirft, wenn der Preis die letzte Leiterkante durchbricht.
  • Zustandsrücksetzung: Nach einem vollständigen Ausstieg werden die Streak-Zähler und die Stoppreferenzen ATR zurückgesetzt, sodass sich vor dem Wiedereintritt eine neue Trendsequenz entwickeln muss.

Parameter

Gruppe Name Beschreibung Standard
Trendfilter FastPeriod Schnelle SMA-Länge, die die kurzfristige Richtung misst. 70
Trendfilter SlowPeriod Langsame SMA-Länge, die die langfristige Richtung misst. 180
Trendfilter MomentumPeriod Anzahl aufeinanderfolgender fertiger Kerzen, die den Trend bestätigen müssen. 50
Volatilität AtrPeriod ATR Fenster, das für alle Entfernungsberechnungen verwendet wird. 100
Eingabelogik StepMultiplier ATR Vielfaches, das erste Ausbrüche verhindert. 4
Eingabelogik StepsMultiplier ATR Vielfaches, das Pyramidenschichten trennt. 2
Risikomanagement StopMultiplier Zusätzlicher Multiplikator, der auf den anfänglichen Stopp jenseits der Basisschrittentfernung angewendet wird. 3
Positionsgrößenbestimmung PyramidLimit Maximale Anzahl Einträge pro Richtung. 3
Handel TradeVolume Mit jeder Marktorder übermitteltes Strategievolumen. 1
Allgemein CandleType Kerzentyp (Zeitrahmen), der für das Abonnement verwendet wird. TimeFrame(1h)

Praktische Hinweise

  • Die StockSharp-Version verwendet die Strategieeigenschaft Volume für die Größenanpassung. Passen Sie TradeVolume an die Vertragsgröße Ihres Instruments an, bevor Sie es in Betrieb nehmen.
  • Es wird davon ausgegangen, dass Marktaufträge sofort ausgeführt werden, genau wie bei der Verwendung von CTrade.Buy/Sell durch MT5. In dünnen Märkten möchten Sie möglicherweise die Marktaufträge durch Limit- oder Stop-Aufträge ersetzen.
  • Die Hoch-/Tief-Referenzen replizieren die h_price- und l_price-Variablen von EA und werden jedes Mal aktualisiert, wenn eine neue Ebene hinzugefügt oder entfernt wird. Sie sind wichtig, um zu bestimmen, wann die Leiter hinzugefügt oder gespült werden muss.
  • Da die EA Stop-Verluste pro Position speichern, während StockSharp sie auf Strategieebene verwaltet, wendet der Port die strengste Stop-Logik auf den gesamten Stapel an. Dies führt zu demselben Verhalten (alle Positionen werden gemeinsam geschlossen), wobei weniger Aufträge auf Brokerseite verwaltet werden müssen.
  • Testen Sie die Strategie immer in der Simulation. ATR-Distanzen passen sich der Volatilität an, aber in Märkten mit Lücken oder hohem Slippage kann das realisierte Risiko immer noch die prognostizierte Stop-Distanz überschreiten.
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>
/// Multi-step ATR trend strategy converted from the "atrTrader" MQL5 expert advisor.
/// Filters trends with a dual moving-average stack, opens breakouts, and pyramids positions using ATR distances.
/// </summary>
public class AtrStepTraderStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<int> _pyramidLimit;
	private readonly StrategyParam<decimal> _stepMultiplier;
	private readonly StrategyParam<decimal> _stepsMultiplier;
	private readonly StrategyParam<decimal> _stopMultiplier;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;

	private int _bullishStreak;
	private int _bearishStreak;
	private decimal? _previousSlow;
	private decimal? _longEntryHigh;
	private decimal? _longEntryLow;
	private decimal? _shortEntryHigh;
	private decimal? _shortEntryLow;
	private decimal? _longStopPrice;
	private decimal? _shortStopPrice;

	/// <summary>
	/// Fast moving average length.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow moving average length.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// ATR calculation period.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// Number of consecutive bars that must confirm the trend direction.
	/// </summary>
	public int MomentumPeriod
	{
		get => _momentumPeriod.Value;
		set => _momentumPeriod.Value = value;
	}

	/// <summary>
	/// Maximum number of stacked entries per direction.
	/// </summary>
	public int PyramidLimit
	{
		get => _pyramidLimit.Value;
		set => _pyramidLimit.Value = value;
	}

	/// <summary>
	/// ATR multiple used for breakout gating.
	/// </summary>
	public decimal StepMultiplier
	{
		get => _stepMultiplier.Value;
		set => _stepMultiplier.Value = value;
	}

	/// <summary>
	/// ATR multiple used for pyramiding distance checks.
	/// </summary>
	public decimal StepsMultiplier
	{
		get => _stepsMultiplier.Value;
		set => _stepsMultiplier.Value = value;
	}

	/// <summary>
	/// Additional multiplier that widens the protective stop distance.
	/// </summary>
	public decimal StopMultiplier
	{
		get => _stopMultiplier.Value;
		set => _stopMultiplier.Value = value;
	}

	/// <summary>
	/// Base order volume for market entries.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

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

	/// <summary>
	/// Initialize <see cref="AtrStepTraderStrategy"/>.
	/// </summary>
	public AtrStepTraderStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 70)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA Period", "Length of the fast moving average", "Trend Filter")
			
			.SetOptimize(50, 100, 10);

		_slowPeriod = Param(nameof(SlowPeriod), 180)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA Period", "Length of the slow moving average", "Trend Filter")
			
			.SetOptimize(120, 240, 20);

		_atrPeriod = Param(nameof(AtrPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR window used for distance calculations", "Volatility")
			
			.SetOptimize(50, 150, 10);

		_momentumPeriod = Param(nameof(MomentumPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Bars", "Number of consecutive bars required for trend confirmation", "Trend Filter")
			
			.SetOptimize(30, 80, 5);

		_pyramidLimit = Param(nameof(PyramidLimit), 3)
			.SetGreaterThanZero()
			.SetDisplay("Pyramid Limit", "Maximum number of entries per direction", "Position Sizing")
			
			.SetOptimize(2, 4, 1);

		_stepMultiplier = Param(nameof(StepMultiplier), 4m)
			.SetGreaterThanZero()
			.SetDisplay("Step Multiplier", "ATR multiple for breakout validation", "Entry Logic")
			
			.SetOptimize(2m, 6m, 1m);

		_stepsMultiplier = Param(nameof(StepsMultiplier), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Steps Multiplier", "ATR multiple for add-on spacing", "Entry Logic")
			
			.SetOptimize(1m, 3m, 0.5m);

		_stopMultiplier = Param(nameof(StopMultiplier), 3m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Multiplier", "Extra multiplier applied on top of the step distance", "Risk Management")
			
			.SetOptimize(2m, 4m, 0.5m);

		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Base order size for market entries", "Trading")
			
			.SetOptimize(0.5m, 2m, 0.5m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Time frame used for processing", "General");
	}

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

		_bullishStreak = 0;
		_bearishStreak = 0;
		_previousSlow = null;
		_longEntryHigh = null;
		_longEntryLow = null;
		_shortEntryHigh = null;
		_shortEntryLow = null;
		_longStopPrice = null;
		_shortStopPrice = null;
	}

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

		Volume = TradeVolume;

		var fastMa = new SimpleMovingAverage { Length = FastPeriod };
		var slowMa = new SimpleMovingAverage { Length = SlowPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };
		var highest = new Highest { Length = MomentumPeriod };
		var lowest = new Lowest { Length = MomentumPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastMa, slowMa, atr, highest, lowest, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fastMa);
			DrawIndicator(area, slowMa);
			DrawIndicator(area, atr);
			DrawIndicator(area, highest);
			DrawIndicator(area, lowest);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal atrValue, decimal highest, decimal lowest)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (atrValue <= 0m)
			return;

		UpdateMomentumCounters(fastValue, slowValue);

		var price = candle.ClosePrice;
		var previousSlow = _previousSlow;
		_previousSlow = slowValue;

		var volume = Volume;
		if (volume <= 0m)
			volume = 1m;

		var netPosition = Position;
		var longCount = netPosition > 0m ? (int)Math.Round(netPosition / volume, MidpointRounding.AwayFromZero) : 0;
		var shortCount = netPosition < 0m ? (int)Math.Round(-netPosition / volume, MidpointRounding.AwayFromZero) : 0;

		if (longCount == 0 && shortCount == 0)
		{
			if (previousSlow.HasValue && slowValue > 0m)
			{
				var bullishReady = _bullishStreak >= MomentumPeriod && price > previousSlow.Value;
				if (bullishReady)
				{
					BuyMarket(Volume);
					longCount = 1;
					_longEntryHigh = price;
					_longEntryLow = price;
					_longStopPrice = price - StepMultiplier * StopMultiplier * atrValue;
				}
			}

			if (longCount == 0 && previousSlow.HasValue && slowValue > 0m)
			{
				var bearishReady = _bearishStreak >= MomentumPeriod && price < previousSlow.Value;
				if (bearishReady)
				{
					SellMarket(Volume);
					shortCount = 1;
					_shortEntryHigh = price;
					_shortEntryLow = price;
					_shortStopPrice = price + StepMultiplier * StopMultiplier * atrValue;
				}
			}
		}
		else if (longCount > 0 && shortCount == 0)
		{
			ManageLongPosition(ref longCount, price, atrValue);
		}
		else if (shortCount > 0 && longCount == 0)
		{
			ManageShortPosition(ref shortCount, price, atrValue);
		}
	}

	private void UpdateMomentumCounters(decimal fastValue, decimal slowValue)
	{
		if (fastValue > slowValue)
		{
			_bullishStreak++;
			_bearishStreak = 0;
		}
		else if (fastValue < slowValue)
		{
			_bearishStreak++;
			_bullishStreak = 0;
		}
		else
		{
			_bullishStreak++;
			_bearishStreak++;
		}
	}

	private void ManageLongPosition(ref int longCount, decimal price, decimal atrValue)
	{
		if (_longEntryHigh is not decimal high || _longEntryLow is not decimal low)
			return;

		var stepsDistance = StepsMultiplier * atrValue;
		var stepDistance = StepMultiplier * atrValue;

		if (_longStopPrice.HasValue && price <= _longStopPrice.Value)
		{
			SellMarket(Position);
			longCount = 0;
			ResetLongState();
			return;
		}

		if (longCount < PyramidLimit)
		{
			if (price >= high + stepsDistance || price <= low - stepsDistance)
			{
				BuyMarket(Volume);
				longCount++;
				_longEntryHigh = Math.Max(high, price);
				_longEntryLow = Math.Min(low, price);
				UpdateLongStopAfterEntry(price, atrValue);
				return;
			}
		}

		if (price <= low - stepsDistance)
		{
			SellMarket(Position);
			longCount = 0;
			ResetLongState();
			return;
		}

		if (longCount >= PyramidLimit)
		{
			var tightened = price - stepDistance;
			if (!_longStopPrice.HasValue || tightened > _longStopPrice.Value)
				_longStopPrice = tightened;
		}
	}

	private void ManageShortPosition(ref int shortCount, decimal price, decimal atrValue)
	{
		if (_shortEntryHigh is not decimal high || _shortEntryLow is not decimal low)
			return;

		var stepsDistance = StepsMultiplier * atrValue;
		var stepDistance = StepMultiplier * atrValue;

		if (_shortStopPrice.HasValue && price >= _shortStopPrice.Value)
		{
			BuyMarket(Math.Abs(Position));
			shortCount = 0;
			ResetShortState();
			return;
		}

		if (shortCount < PyramidLimit)
		{
			if (price <= low - stepsDistance || price >= high + stepsDistance)
			{
				SellMarket(Volume);
				shortCount++;
				_shortEntryHigh = Math.Max(high, price);
				_shortEntryLow = Math.Min(low, price);
				UpdateShortStopAfterEntry(price, atrValue);
				return;
			}
		}

		if (price >= high + stepsDistance)
		{
			BuyMarket(Math.Abs(Position));
			shortCount = 0;
			ResetShortState();
			return;
		}

		if (shortCount >= PyramidLimit)
		{
			var tightened = price + stepDistance;
			if (!_shortStopPrice.HasValue || tightened < _shortStopPrice.Value)
				_shortStopPrice = tightened;
		}
	}

	private void UpdateLongStopAfterEntry(decimal entryPrice, decimal atrValue)
	{
		var stop = entryPrice - StepMultiplier * StopMultiplier * atrValue;
		if (!_longStopPrice.HasValue || stop > _longStopPrice.Value)
			_longStopPrice = stop;
	}

	private void UpdateShortStopAfterEntry(decimal entryPrice, decimal atrValue)
	{
		var stop = entryPrice + StepMultiplier * StopMultiplier * atrValue;
		if (!_shortStopPrice.HasValue || stop < _shortStopPrice.Value)
			_shortStopPrice = stop;
	}

	private void ResetLongState()
	{
		_longEntryHigh = null;
		_longEntryLow = null;
		_longStopPrice = null;
		_bullishStreak = 0;
	}

	private void ResetShortState()
	{
		_shortEntryHigh = null;
		_shortEntryLow = null;
		_shortStopPrice = null;
		_bearishStreak = 0;
	}
}