Auf GitHub ansehen

AeroSpine-Strategie

Überblick

Die AeroSpine-Strategie ist eine Konvertierung des MetaTrader-Experten AEROSPINE.mq4. Sie handelt ein einzelnes Symbol und versucht, Ausbrüche vom täglichen Eröffnungspreis zu erfassen. Der ursprüngliche Roboter war für Tagescharts konzipiert, während er Ticks überwachte; der Port behält die Idee des täglichen Eröffnungsausbruchs bei, stützt sich jedoch auf fertige Kerzen von StockSharp.

Handelslogik

  • Zu Beginn jedes Handelstages speichert die Strategie den täglichen Eröffnungspreis aus der ersten Kerze des Tages.
  • Neue Positionen werden nur nach der konfigurierten Einstiegsstunde bewertet. Fertige Kerzen müssen einen minimalen Volumenfilter erfüllen und der aktuelle Spread muss unter dem konfigurierten Limit liegen.
  • Wenn keine Position offen ist und kein Recovery-Trade ausstehend ist:
    • Ein Long-Trade wird eröffnet, sobald das Kerzenhoch die tägliche Eröffnung um EntryOffsetPips übersteigt.
    • Ein Short-Trade wird eröffnet, sobald das Kerzentief die tägliche Eröffnung um EntryOffsetPips unterschreitet.
  • Nach einem Verlusttrade bereitet die Strategie einen Recovery-Einstieg in die entgegengesetzte Richtung vor. Recovery-Trades verwenden RecoveryOffsetPips und erhöhen das Volumen durch Addition des Basisvolumens zur Größe des Verlusttrades, was das Martingale-ähnliche Sizing des MQL-Experten repliziert.
  • Offene Positionen werden mit drei Mechanismen verwaltet:
    • Ein fester Take-Profit bei TakeProfitPips vom Einstiegspreis.
    • Ein optionaler Break-Even-Auslöser, der den Trade schließt, wenn der Preis zur Break-Even-Distanz zurückkehrt, nachdem er sich zugunsten der Position bewegt hat.
    • Ein Schutzausstieg, wenn der Preis zur täglichen Eröffnung zurückkehrt und diese um ExitOffsetPips gegen die Position überschreitet.

Parameter

Name Beschreibung
Candle Type Zeitrahmen der Arbeitskerzen für die Signalbewertung.
Volume Basis-Auftragsgröße für Ersteinstiege und zum Aufbau des Recovery-Volumens.
Entry Hour Mindeststunde (Börsenzeit), ab der neue Einstiege möglich sind.
Entry Offset Abstand in Pips von der täglichen Eröffnung, der überschritten werden muss, um den ersten Trade des Tages zu eröffnen.
Exit Offset Abstand in Pips jenseits der täglichen Eröffnung zum Schließen von Positionen, die zur Eröffnung zurückkehren.
Recovery Offset Abstand in Pips von der täglichen Eröffnung für einen Recovery-Trade nach einem Verlust.
Take Profit Fester Take-Profit-Abstand in Pips vom Einstiegspreis.
Break Even Abstand in Pips zum Aktivieren des Break-Even-Ausstiegs.
Use Break Even Aktiviert oder deaktiviert den Break-Even-Verwaltungsblock.
Volume Filter Minimales Kerzenvolumen für neue Einstiege, entsprechend der originalen Volume[0] > 10000-Prüfung.
Max Spread Lehnt neue Einstiege ab, wenn der aktuelle Spread breiter als der erlaubte Wert ist (aus Pips umgerechnet).
Enable Recovery Aktiviert die entgegengesetzte Recovery-Logik nach einem Verlusttrade.

Hinweise zur Konvertierung

  • Das originale EA platzierte Aufträge direkt bei Ticks während es einen Tageschart erzwang. Der Port emuliert dies mit Intraday-Kerzen: Die tägliche Eröffnung wird bei der ersten Kerze jedes Tages aktualisiert und die Ausbruchsprüfungen verwenden Kerzenhochs/-tiefs.
  • Alle MetaTrader-Schnittstellenelemente (Labels, Kapitalberechnungen über mehrere Symbole usw.) wurden entfernt. Nur die relevante Handelslogik für das aktuelle Symbol wurde beibehalten.
  • Break-Even und Stop-Modifikationen (OrderModify) werden über explizite ClosePosition()-Aufrufe simuliert, wenn die berechneten Schwellenwerte berührt werden.
  • Spread- und Volumenfilter entsprechen direkt den originalen MODE_SPREAD- und Volume[0]-Prüfungen.
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 AeroSpineStrategy : 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 AeroSpineStrategy()
	{
		_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;
	}
}