Auf GitHub ansehen

Envelopes-EA-Strategie

Überblick

Die Strategie repliziert den MetaTrader-4 Expert Advisor "EnvelopesEA". Sie wendet eine exponentielle gleitende Durchschnittshülle auf den primären Kerzenstrom an und handelt Mean Reversion. Wenn der Markt weit außerhalb der Hülle liegt, wird eine konträre Marktorder gesendet. Positionen werden geschlossen, sobald der Preis in Gegenrichtung wieder in die Hülle eintritt. Der ursprüngliche Expert Advisor wurde 2019 auf EUR/USD getestet; die StockSharp-Portierung behält dieselbe Logik bei und stellt alle wichtigen Eingaben als optimierbare Parameter bereit.

Handelslogik

  1. Berechnen Sie einen exponentiellen gleitenden Durchschnitt (EMA) der Länge EnvelopePeriod auf den ausgewählten Kerzen.
  2. Erstellen Sie eine obere und eine untere Hülle, indem die EMA mit UpperDeviationPercent beziehungsweise LowerDeviationPercent erweitert wird.
  3. Wenden Sie einen zusätzlichen Einstiegspuffer an, definiert durch EntryOffsetPoints (multipliziert mit dem Preisschritt des Instruments), um verfrühte Trades zu vermeiden.
  4. Wenn keine Position offen ist:
    • Long einsteigen, wenn der Schlusskurs unter die untere Hülle abzüglich Einstiegspuffer fällt.
    • Short einsteigen, wenn der Schlusskurs über die obere Hülle zuzüglich Einstiegspuffer steigt.
  5. Wenn eine Position besteht:
    • Long-Positionen schließen, sobald der Schlusskurs wieder über die obere Hülle kreuzt.
    • Short-Positionen schließen, sobald der Schlusskurs wieder unter die untere Hülle kreuzt.

Die Strategie hält immer höchstens eine offene Position und verwendet Marktorders sowohl für Einstiege als auch für Ausstiege.

Geldmanagement

Das Ordervolumen wird direkt über den Parameter Volume (Lots) angegeben. Es gibt keine automatischen Martingale- oder Pyramiding-Regeln, wodurch das Verhalten identisch zur neuesten MQ4-Implementierung bleibt, in der Skalierungsfunktionen standardmäßig deaktiviert waren.

Parameter

Parameter Beschreibung Standard
Volume Ordervolumen in Lots. 0.2
EnvelopePeriod Länge der EMA, die die Basis der Hülle bildet. 50
UpperDeviationPercent Prozentuale Abweichung für das obere Band. 0.5
LowerDeviationPercent Prozentuale Abweichung für das untere Band. 0.5
EntryOffsetPoints Zusätzliche Distanz in Preisschritten, die der Preis vor dem Einstieg über das Band hinaus laufen muss. 100
CandleType Zeitrahmen für Kerzen und Indikatorberechnungen. 30-Minuten-Kerzen

Alle numerischen Parameter (außer CandleType) sind als optimierbar markiert, um die ursprünglichen Optimierungsabläufe nachbilden zu können.

Hinweise

  • Die Hülle verwendet eine EMA statt der SMA aus früheren Versionen, weil sich das MQ4-Skript in der neuesten Iteration zu einer exponentiellen Basis entwickelt hat. Dies reagiert schneller auf Preisschwünge und verbessert das Timing der Mean Reversion.
  • Der Einstiegspuffer wird mit dem Instrumenten-PriceStep multipliziert. Stellen Sie sicher, dass die Wertpapiermetadaten eine gültige Schrittgröße enthalten; andernfalls fällt die Strategie auf den konservativen Standard 0.0001 zurück.
  • Die Chart-Visualisierung enthält Preiskerzen, die EMA-Hülle und die Trades der Strategie, sodass das Signalverhalten leicht gegen den ursprünglichen Expert Advisor validiert werden kann.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class EnvelopesEaStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public EnvelopesEaStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}