Auf GitHub ansehen

EMA (barabashkakvn Edition)-Strategie

Konvertiert aus dem MetaTrader-5-Expertenberater "EMA (barabashkakvn's edition)". Das System handelt den Kreuzungspunkt zweier exponentieller gleitender Durchschnitte, die auf dem Median-Preis berechnet werden, und verwendet virtuelle Take-Profit/Stop-Loss-Level in Pips. Positionen werden nur nach einer bestätigten Kreuzung und einem kleinen Rücksetzer zum vorigen Kerzenextrem eröffnet.

Kernidee

  1. Verfolgung von 5- und 10-Perioden-EMAs (Median-Preis) im gewählten Zeitrahmen.
  2. Wenn die schnelle EMA die langsame EMA kreuzt, ein ausstehiges Signal setzen statt sofort zu handeln.
  3. Warten, bis der Preis MoveBackPips vom vorigen Kerzenextremum zurückgesetzt hat, während der EMA-Spread 2 * pipSize übersteigt.
  4. In Richtung der Kreuzung einsteigen, sobald der Rücksetzer eintritt.
  5. Die offene Position mit virtuellen Zielen und Stops in Pips vom Einstandspreis verwalten.

Dieses Verhalten spiegelt die ursprüngliche MQL-Implementierung wider: Der Expertenberater wartete auf das Kreuzungsflag (check) und erforderte dann einen EMA-Spread plus einen Preisrücksetzer relativ zur vorherigen Kerze, um den Trade auszulösen. Die Ausstiegsregeln folgen ebenfalls dem „virtuellen" Ansatz, indem Positionen geschlossen werden, wenn Bid/Ask die angegebenen Abstände berührt hätte.

Indikatoren & Daten

  • 5-Perioden-EMA auf Median-Preis (high + low) / 2.
  • 10-Perioden-EMA auf Median-Preis.
  • Hoch/Tief der vorherigen fertigen Kerze für Rücksetzerprüfungen.
  • Alle Verarbeitung verwendet fertige Kerzen aus dem konfigurierten CandleType-Abonnement.

Parameter

Parameter Standard Beschreibung
OrderVolume 0.1 Handelsvolumen in Lots/Kontrakten für jeden Einstieg.
VirtualProfitPips 5 Abstand (in Pips) zwischen Einstandspreis und virtuellem Take-Profit.
MoveBackPips 3 Nach der Kreuzung erforderlicher Rücksetzer, gemessen vom vorigen Kerzenextremum.
StopLossPips 20 Abstand (in Pips) zwischen Einstandspreis und virtuellem Stop-Loss.
PipSize 0.0001 Pip-Größe in Preiseinheiten. Überschreiben bei Handelssymbolen mit anderer Pip-Definition.
FastLength 5 Länge der schnellen EMA.
SlowLength 10 Länge der langsamen EMA.
CandleType TimeFrame(1m) Für Berechnungen verwendete Kerzenquelle.

Alle pip-basierten Werte werden mit pipValue = PipSize in Preisabstände umgewandelt. Wenn der Parameter auf null oder eine negative Zahl gesetzt wird, fällt die Strategie auf Security.PriceStep zurück (wenn vom Board bereitgestellt).

Handelslogik

Einstiegsbedingungen

  • Signalanrüstung: ein ausstehiges Signal speichern, wann immer eine Kreuzung auftritt (FastEMA kreuzt über SlowEMA oder umgekehrt). Es wird noch kein Trade platziert.
  • Short-Einstieg: erfordert
    • Ausstehendes Signal vorhanden.
    • SlowEMA - FastEMA > 2 * pipSize.
    • Kerzenhoch aktuell ≥ Kerzentief vorherig + MoveBackPips * pipSize (Preis hat sich vom vorigen Tief nach oben zurückgesetzt).
  • Long-Einstieg: erfordert
    • Ausstehendes Signal vorhanden.
    • FastEMA - SlowEMA > 2 * pipSize.
    • Kerzentief aktuell ≤ Kerzenhoch vorherig - MoveBackPips * pipSize (Preis hat sich vom vorigen Hoch nach unten zurückgesetzt).

Nach dem Öffnen einer Position setzt sich das ausstehige Flag zurück, um Doppeleinstiege zu vermeiden.

Ausstiegsbedingungen

Virtuelle Ziele emulieren das MQL-Verhalten, indem Kerzenextreme mit den voreingestellten Abständen verglichen werden:

  • Long-Position:
    • Schließen wenn Kerzenhoch ≥ Einstandspreis + VirtualProfitPips * pipSize.
    • Schließen wenn Kerzentief ≤ Einstandspreis - StopLossPips * pipSize.
  • Short-Position:
    • Schließen wenn Kerzentief ≤ Einstandspreis - VirtualProfitPips * pipSize.
    • Schließen wenn Kerzenhoch ≥ Einstandspreis + StopLossPips * pipSize.

Nach jedem Ausstieg setzen sich die virtuellen Level zurück und die Strategie wartet auf die nächste Kreuzung.

Implementierungshinweise

  • Verwendet das High-Level-Kerzenabonnement (SubscribeCandles) und zeichnet EMAs plus Trades im optionalen Chartbereich.
  • Median-Preis wird direkt aus dem Kerzenhoch/-tief berechnet, um PRICE_MEDIAN von MetaTrader abzugleichen.
  • Das Kreuzungsflag (_hasCrossSignal) reproduziert die ursprüngliche check-Variable und stellt sicher, dass Trades nur nach Kreuzungs- und Rücksetzerprüfungen stattfinden.
  • StartProtection() wird in OnStarted aufgerufen, um eingebaute Risikoüberwachung zu aktivieren, obwohl die Strategie Ausstiege manuell behandelt.
  • Der Code behält alle Kommentare auf Englisch, wie angefordert, und stützt sich ausschließlich auf fertige Kerzen ohne direkten Zugriff auf Indikatorpuffer.

Verwendungstipps

  • PipSize anpassen bei Instrumenten mit nicht-standardmäßigen Pip-Definitionen (z.B. JPY-Paare, Indizes, Krypto-Kurse).
  • Da Ausstiege auf Kerzenextremen basieren, hält die Verwendung kürzerer Zeitrahmen (1–5 Minuten) das Verhalten näher am ursprünglichen tick-basierten Expertenberater.
  • Optimierung kann EMA-Längen, Pip-Abstände und Rücksetzer-Werte mit den bereitgestellten Parameter-Metadaten erkunden.
  • Die Strategie handelt jeweils eine Position; externe Positionen auf demselben Instrument können das virtuelle Ausstiegs-Tracking beeinträchtigen.

Risiken

  • Kerzenbasierte Simulation kann Intrabar-Berührungen der virtuellen Level verpassen; bei Präzisionsbedarf höher aufgelöste Daten in Betracht ziehen.
  • Virtuelle Ausstiege platzieren keine echten Schutzorders, daher können Verbindungsunterbrechungen oder Slippage zu größeren Verlusten als erwartet im Live-Handel führen.
  • Wie bei jedem Kreuzungssystem verschlechtert sich die Performance in Seitwärtsmärkten; bei Bedarf Filter kombinieren.
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>
/// EMA crossover strategy with virtual take profit and stop loss distances.
/// Converted from the MQL5 expert "EMA (barabashkakvn's edition)".
/// </summary>
public class EmaBarabashkakvnEditionStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<int> _virtualProfitPips;
	private readonly StrategyParam<int> _moveBackPips;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<decimal> _pipSize;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _fastEma;
	private ExponentialMovingAverage _slowEma;
	private bool _hasCrossSignal;
	private decimal? _prevFast;
	private decimal? _prevSlow;
	private decimal? _prevHigh;
	private decimal? _prevLow;
	private decimal? _entryPrice;
	private decimal? _virtualTarget;
	private decimal? _virtualStop;

	/// <summary>
	/// Order volume in lots.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Virtual take profit distance in pips.
	/// </summary>
	public int VirtualProfitPips
	{
		get => _virtualProfitPips.Value;
		set => _virtualProfitPips.Value = value;
	}

	/// <summary>
	/// Retracement distance after a crossover in pips.
	/// </summary>
	public int MoveBackPips
	{
		get => _moveBackPips.Value;
		set => _moveBackPips.Value = value;
	}

	/// <summary>
	/// Virtual stop loss distance in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Pip size in price units.
	/// </summary>
	public decimal PipSize
	{
		get => _pipSize.Value;
		set => _pipSize.Value = value;
	}

	/// <summary>
	/// Fast EMA length applied to median price.
	/// </summary>
	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	/// <summary>
	/// Slow EMA length applied to median price.
	/// </summary>
	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of <see cref="EmaBarabashkakvnEditionStrategy"/>.
	/// </summary>
	public EmaBarabashkakvnEditionStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Volume", "Order volume in lots", "Trading")
			
			.SetOptimize(0.05m, 1m, 0.05m);

		_virtualProfitPips = Param(nameof(VirtualProfitPips), 5)
			.SetGreaterThanZero()
			.SetDisplay("Virtual Profit", "Take profit distance in pips", "Risk")
			
			.SetOptimize(2, 20, 1);

		_moveBackPips = Param(nameof(MoveBackPips), 3)
			.SetGreaterThanZero()
			.SetDisplay("Move Back", "Retracement after crossover in pips", "Entries")
			
			.SetOptimize(1, 10, 1);

		_stopLossPips = Param(nameof(StopLossPips), 20)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Virtual stop loss distance in pips", "Risk")
			
			.SetOptimize(10, 60, 2);

		_pipSize = Param(nameof(PipSize), 0.0001m)
			.SetGreaterThanZero()
			.SetDisplay("Pip Size", "Instrument pip size in price units", "General");

		_fastLength = Param(nameof(FastLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA length on median price", "Indicators")
			
			.SetOptimize(3, 15, 1);

		_slowLength = Param(nameof(SlowLength), 10)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA length on median price", "Indicators")
			
			.SetOptimize(8, 40, 1);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Source candles", "General");
	}

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

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

		_fastEma = default;
		_slowEma = default;
		_hasCrossSignal = false;
		_prevFast = default;
		_prevSlow = default;
		_prevHigh = default;
		_prevLow = default;
		_entryPrice = default;
		_virtualTarget = default;
		_virtualStop = default;
	}

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

		_fastEma = new EMA { Length = FastLength };
		_slowEma = new EMA { Length = SlowLength };

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

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

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

		// Calculate median price as in the original expert (PRICE_MEDIAN).
		var medianPrice = (candle.HighPrice + candle.LowPrice) / 2m;

		// Update EMA values using the median price.
		var fastValue = _fastEma.Process(new DecimalIndicatorValue(_fastEma, medianPrice, candle.OpenTime) { IsFinal = true });
		var slowValue = _slowEma.Process(new DecimalIndicatorValue(_slowEma, medianPrice, candle.OpenTime) { IsFinal = true });

		if (!_fastEma.IsFormed || !_slowEma.IsFormed)
		{
			_prevHigh = candle.HighPrice;
			_prevLow = candle.LowPrice;
			_prevFast = fastValue.ToDecimal();
			_prevSlow = slowValue.ToDecimal();
			return;
		}

		var fast = fastValue.ToDecimal();
		var slow = slowValue.ToDecimal();

		if (_prevFast is decimal prevFast && _prevSlow is decimal prevSlow)
		{
			var bullishCross = prevFast <= prevSlow && fast > slow;
			var bearishCross = prevFast >= prevSlow && fast < slow;

			if (bullishCross || bearishCross)
				_hasCrossSignal = true;
		}

		_prevFast = fast;
		_prevSlow = slow;

		var pipValue = PipSize;
		if (pipValue <= 0m)
			pipValue = Security?.PriceStep ?? 0.0001m;

		var moveBackPrice = MoveBackPips * pipValue;
		var profitDistance = VirtualProfitPips * pipValue;
		var stopDistance = StopLossPips * pipValue;

		if (Position == 0 && _hasCrossSignal && _prevHigh is decimal prevHigh && _prevLow is decimal prevLow)
		{
			var bearishSpread = slow - fast;
			var bullishSpread = fast - slow;

			var bearishReady = bearishSpread > 2m * pipValue && candle.HighPrice >= prevLow + moveBackPrice;
			var bullishReady = bullishSpread > 2m * pipValue && candle.LowPrice <= prevHigh - moveBackPrice;

			if (bearishReady)
			{
				// Enter short after bearish cross and retracement above the previous low.
				_entryPrice = candle.ClosePrice;
				_virtualTarget = _entryPrice - profitDistance;
				_virtualStop = _entryPrice + stopDistance;
				SellMarket();
				_hasCrossSignal = false;
			}
			else if (bullishReady)
			{
				// Enter long after bullish cross and retracement below the previous high.
				_entryPrice = candle.ClosePrice;
				_virtualTarget = _entryPrice + profitDistance;
				_virtualStop = _entryPrice - stopDistance;
				BuyMarket();
				_hasCrossSignal = false;
			}
		}
		else if (Position != 0 && _entryPrice is decimal && _virtualTarget is decimal target && _virtualStop is decimal stop)
		{
			if (Position > 0)
			{
				// Long position: use high for profit target and low for stop.
				var hitTarget = candle.HighPrice >= target;
				var hitStop = candle.LowPrice <= stop;

				if (hitTarget || hitStop)
				{
					SellMarket();
					_hasCrossSignal = false;
					_entryPrice = null;
					_virtualTarget = null;
					_virtualStop = null;
				}
			}
			else if (Position < 0)
			{
				// Short position: use low for profit target and high for stop.
				var hitTarget = candle.LowPrice <= target;
				var hitStop = candle.HighPrice >= stop;

				if (hitTarget || hitStop)
				{
					BuyMarket();
					_hasCrossSignal = false;
					_entryPrice = null;
					_virtualTarget = null;
					_virtualStop = null;
				}
			}
		}

		if (Position == 0)
		{
			_entryPrice = null;
			_virtualTarget = null;
			_virtualStop = null;
		}

		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
	}
}