Auf GitHub ansehen

Farhad-Krabbenstrategie

Überblick

Die Farhad Crab Strategy ist eine StockSharp High-Level-Portierung des MetaTrader Expertenberaters FarhadCrab1.mq4. Das Original EA ist ein schnelles Scalping-System, das für den M1-Zeitrahmen für GBP/JPY, GBP/USD und EUR/USD entwickelt wurde. Bei dieser Konvertierung wird die Handelslogik in C# neu erstellt, indem Filter für den gleitenden Intraday-Durchschnitt mit einem täglichen Trendsicherheitsnetz und automatisiertem Exit-Management kombiniert werden.

Die Strategie analysiert den aktuellen Zeitrahmen anhand eines 9-Perioden-EMA, der auf dem typischen Preis berechnet wird, und eines 9-Perioden-SMA, der auf der Öffnung der Kerze berechnet wird. Gleichzeitig verfolgt es einen geglätteten gleitenden Durchschnitt (SMMA) über 55 Perioden, der aus täglichen Kerzen gebildet wird. Immer wenn die kurzfristigen Filter genügend Aufwärtsdynamik zeigen und keine Position offen ist, wird ein Long-Trade ausgelöst. Wenn umgekehrt das Intraday-Hoch unter SMA der Eröffnungen bleibt, wird ein Short-Trade eröffnet. Der tägliche SMMA fungiert als Schutzschicht: Ein Überschreiten des Preises von unten zwingt alle Long-Trades zum Ausstieg, und ein Überschreiten von oben schließt Short-Positionen.

Das Exit-Management reproduziert das ursprüngliche Verhalten von EA mit konfigurierbaren Take-Profit-Levels in Pips und unabhängigen Trailing Stops für Long- und Short-Positionen. Die Trailing-Logik folgt der MetaTrader-Implementierung, indem sie den Stop erst verschiebt, nachdem der Markt um die konfigurierte Distanz vorgerückt ist. Die Strategie schließt Positionen über Market-Orders und nicht über ausstehende Stop-Orders, wodurch sie mit dem High-Level-Ereignisablauf API kompatibel ist.

Hauptmerkmale

  • Indikatorsatz identisch mit EA – 9-Perioden-EMA beim typischen Preis, 9-Perioden-SMA bei Eröffnungen und ein täglicher 55-Perioden-SMMA für die Trendrichtung.
  • Datenverarbeitung in mehreren Zeitrahmen – abonniert gleichzeitig den Handelszeitrahmen und die täglichen Kerzen, sodass StockSharp die erforderlichen Indikatoren ohne manuelle Pufferung berechnen kann.
  • Konfigurierbare Exits – symmetrische Take-Profit-Abstände (Long/Short) und Trailing-Stops, ausgedrückt in Pips, genau wie die ursprünglichen externen Eingaben.
  • Täglicher Sicherheitsschalter – repliziert die Regel von EA, die Long-Positionen schließt, wenn der tägliche SMMA über den Tagesschlusskurs steigt, und Short-Positionen, wenn er darunter fällt.
  • Integrierter Schutz – ruft StartProtection() einmal beim Start auf, um Positionen gemäß den Best Practices des Frameworks zu schützen.

Parameter

Parameter Beschreibung Standard
OrderVolume Auf neue Marktaufträge angewendetes Handelsvolumen. 0.1
LongTakeProfitPips Take-Profit-Distanz für Long-Positionen, gemessen in Pips. 10
ShortTakeProfitPips Take-Profit-Distanz für Short-Positionen, gemessen in Pips. 10
LongTrailingStopPips Trailing-Stop-Distanz für Long-Trades. Das Nachlaufen ist deaktiviert, wenn es auf Null gesetzt ist. 8
ShortTrailingStopPips Trailing-Stop-Distanz für Short-Trades. Das Nachlaufen ist deaktiviert, wenn es auf Null gesetzt ist. 8
DailyMaPeriod Länge des täglichen geglätteten gleitenden Durchschnitts, der für Schutzausstiege verwendet wird. 55
CandleType Primärer Zeitrahmen, der die Strategieberechnungen steuert. Standardmäßig werden 1-Minuten-Kerzen verwendet. 1m

Alle Parameter werden über StrategyParam<T> verfügbar gemacht und dort, wo es sinnvoll ist, als optimierbar markiert, sodass sie über den StockSharp-Optimierer optimiert werden können.

Handelsregeln

  1. Long-Einträge: Wenn das aktuelle Kerzentief über dem 9-Perioden-EMA des typischen Preises bleibt und keine Position aktiv ist, eröffnen Sie einen Long-Trade.
  2. Short-Einträge: Wenn das aktuelle Kerzenhoch unter dem 9-Perioden-SMA des Eröffnungspreises bleibt und keine Position aktiv ist, eröffnen Sie einen Short-Trade.
  3. Täglicher schützender Ausstieg (Long): Schließen Sie jede Long-Position, wenn der tägliche SMMA über den Tagesschlusskurs steigt, während er zuvor unter dem vorherigen Schlusskurs lag.
  4. Täglicher schützender Ausstieg (Short): Schließen Sie jede Short-Position, wenn der tägliche SMMA unter den Tagesschluss fällt, während er zuvor über dem vorherigen Schlusskurs lag.
  5. Take-Profit: Schließen Sie die Position, sobald das konfigurierte Pip-Ziel erreicht ist.
  6. Trailing Stop: Nachdem eine Position die Trailing-Distanz erreicht hat, können Sie Gewinne sichern, indem Sie die Retracement-Distanz überwachen und aussteigen, wenn der Preis um diesen Betrag zurückgeht.

Implementierungshinweise

  • Der Code basiert ausschließlich auf SubscribeCandles().Bind(...)-Aufrufen auf hoher Ebene, wodurch manuelle Indikatorpuffer entfallen und die Richtlinien des Projekts eingehalten werden.
  • Pips werden aus dem PriceStep des Instruments mit der üblichen Anpassung im MetaTrader-Stil für 3- und 5-stellige Notierungen berechnet. Dadurch bleibt das Verhalten mit den punktbasierten Parametern von EA konsistent.
  • Das Stop-Loss- und Take-Profit-Management wird intern durchgeführt, indem Positionen geschlossen werden, wenn die Bedingungen erfüllt sind, und nicht durch die Registrierung von Limit-/Stop-Orders. Dieser Ansatz entspricht den im Originalskript gefundenen sofortigen Exits und bleibt gleichzeitig mit der asynchronen Auftragsausführung in StockSharp kompatibel.
  • Die Strategie setzt ihren Status innerhalb von OnReseted zurück und stellt so sicher, dass Optimierungsläufe und wiederholte Starts von vorne beginnen.

Nutzungstipps

  • Der ursprüngliche EA war auf hochvolatile GBP- und EUR-Paare im M1-Zeitrahmen zugeschnitten. Ähnliche Ergebnisse sind zu erwarten, wenn der gleiche Zeitrahmen und die gleichen Instrumente angewendet werden, die Parameter jedoch so ausgelegt sind, dass sie unterschiedlichen Volatilitätsprofilen Rechnung tragen.
  • Da das System jeweils nur eine Position behält, eignet es sich für einfaches Backtesting und Live-Ausführung ohne komplexe Positionspyramide.
  • Trailing-Stops werden bei Instrumenten mit glatten Trends effektiver. Erwägen Sie bei schwankenden Märkten eine Reduzierung der Nachlaufdistanz oder die ausschließliche Nutzung von Take-Profit-Exits.
  • Als primäre Risikokontrolle dient der tägliche SMMA-Exit. Für Swing-orientierte Setups können Sie DailyMaPeriod erhöhen, um den Langzeitfilter weniger reaktiv zu machen.

Prüfbare Formeln: Für jede abgeschlossene M1-Kerze gilt typical=(high+low+close)/3. Der EMA(9) der typischen Preise wird mit dem Mittelwert der ersten neun Werte initialisiert, anschließend EMA=(8*EMA+2*typical)/10. Der SMA(9) basiert auf Open. Ohne Position oder ausstehende Order: Kauf bei low > EMA, sonst Verkauf bei high < SMA(Open). Der Tages-SMMA(55) startet nach 55 abgeschlossenen Tageskerzen und folgt (54*SMMA+close)/55. Ein ungünstiges Kreuzen des Tagesschlusses löst einen Ausstieg aus. Take-Profit und Trailing nutzen den tatsächlichen Ausführungskurs und abgeschlossene M1-Kerzen; neue Trailing-Level gelten erst für spätere Kerzen. Ein verkürzter DailyMaPeriod testet nur die Mechanik und nicht den Default 55. Ein Backtest garantiert keine Rendite.

namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;
using System.Linq;

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

/// <summary>
/// Farhad Crab uses EMA(9) on typical M1 prices, SMA(9) of M1 opens,
/// and daily SMMA protection with direction-specific take/trailing exits.
/// </summary>
public class FarhadCrabStrategy : Strategy
{
	private readonly StrategyParam<int> _dailyMaPeriod;
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<decimal> _longTakeProfitPips;
	private readonly StrategyParam<decimal> _shortTakeProfitPips;
	private readonly StrategyParam<decimal> _longTrailingStopPips;
	private readonly StrategyParam<decimal> _shortTrailingStopPips;
	private readonly StrategyParam<DataType> _candleType;

	private readonly Queue<decimal> _typicalSeed = new();
	private readonly Queue<decimal> _opens = new();
	private readonly Queue<decimal> _dailySeed = new();
	private decimal? _emaTypical;
	private decimal? _dailySmma;
	private decimal? _priorSmma;
	private decimal? _priorDailyClose;
	private decimal? _entryPrice;
	private decimal? _trailingLevel;
	private decimal _bestPrice;
	private Order _pendingOrder;
	private Order _entryOrder;

	public int DailyMaPeriod { get => _dailyMaPeriod.Value; set => _dailyMaPeriod.Value = value; }
	public decimal OrderVolume { get => _orderVolume.Value; set => _orderVolume.Value = value; }
	public decimal LongTakeProfitPips { get => _longTakeProfitPips.Value; set => _longTakeProfitPips.Value = value; }
	public decimal ShortTakeProfitPips { get => _shortTakeProfitPips.Value; set => _shortTakeProfitPips.Value = value; }
	public decimal LongTrailingStopPips { get => _longTrailingStopPips.Value; set => _longTrailingStopPips.Value = value; }
	public decimal ShortTrailingStopPips { get => _shortTrailingStopPips.Value; set => _shortTrailingStopPips.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public FarhadCrabStrategy()
	{
		_dailyMaPeriod = Param(nameof(DailyMaPeriod), 55).SetGreaterThanZero();
		_orderVolume = Param(nameof(OrderVolume), 0.1m).SetGreaterThanZero();
		_longTakeProfitPips = Param(nameof(LongTakeProfitPips), 10m).SetNotNegative();
		_shortTakeProfitPips = Param(nameof(ShortTakeProfitPips), 10m).SetNotNegative();
		_longTrailingStopPips = Param(nameof(LongTrailingStopPips), 8m).SetNotNegative();
		_shortTrailingStopPips = Param(nameof(ShortTrailingStopPips), 8m).SetNotNegative();
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame());
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType), (Security, TimeSpan.FromDays(1).TimeFrame())];

	protected override void OnReseted()
	{
		base.OnReseted();
		ClearState();
	}

	private void ClearState()
	{
		_typicalSeed.Clear();
		_opens.Clear();
		_dailySeed.Clear();
		_emaTypical = null;
		_dailySmma = null;
		_priorSmma = null;
		_priorDailyClose = null;
		_entryPrice = null;
		_trailingLevel = null;
		_bestPrice = 0m;
		_pendingOrder = null;
		_entryOrder = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		ClearState();
		if ((Security?.PriceStep ?? 0m) <= 0m)
			throw new InvalidOperationException("Farhad Crab requires a positive instrument PriceStep.");
		SubscribeCandles(TimeSpan.FromDays(1).TimeFrame()).Bind(ProcessDailyCandle).Start();
		SubscribeCandles(CandleType).Bind(ProcessCandle).Start();
	}

	private decimal PipSize()
	{
		var step = Security?.PriceStep ?? 0m;
		return Security?.Decimals is 3 or 5 ? step * 10m : step;
	}

	private bool Pending => _pendingOrder is not null &&
		_pendingOrder.State is not (OrderStates.Done or OrderStates.Failed);

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

		var priorSmma = _dailySmma;
		var priorClose = _priorDailyClose;
		if (_dailySmma is null)
		{
			_dailySeed.Enqueue(candle.ClosePrice);
			if (_dailySeed.Count == DailyMaPeriod)
				_dailySmma = _dailySeed.Average();
		}
		else
			_dailySmma = (_dailySmma.Value * (DailyMaPeriod - 1) + candle.ClosePrice) / DailyMaPeriod;

		_priorSmma = priorSmma;
		_priorDailyClose = candle.ClosePrice;

		if (Pending || Position == 0m || priorSmma is null || priorClose is null || _dailySmma is null)
			return;

		if (Position > 0m && priorSmma < priorClose && _dailySmma > candle.ClosePrice ||
			Position < 0m && priorSmma > priorClose && _dailySmma < candle.ClosePrice)
			Exit("Farhad Crab daily safety exit");
	}

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

		var typical = (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m;
		if (_emaTypical is null)
		{
			_typicalSeed.Enqueue(typical);
			if (_typicalSeed.Count == 9)
				_emaTypical = _typicalSeed.Average();
		}
		else
			_emaTypical = (_emaTypical.Value * 8m + typical * 2m) / 10m;

		_opens.Enqueue(candle.OpenPrice);
		while (_opens.Count > 9)
			_opens.Dequeue();

		if (Pending)
			return;
		if (Position != 0m)
		{
			HandleRisk(candle);
			return;
		}
		if (_emaTypical is null || _opens.Count < 9)
			return;

		if (candle.LowPrice > _emaTypical.Value)
			Enter(Sides.Buy);
		else if (candle.HighPrice < _opens.Average())
			Enter(Sides.Sell);
	}

	private void Enter(Sides side)
	{
		var volume = OrderVolume;
		if (Security?.VolumeStep is decimal step && step > 0m)
			volume = decimal.Floor(volume / step) * step;
		if (Security?.MinVolume is decimal min && min > 0m)
			volume = Math.Max(min, volume);
		if (Security?.MaxVolume is decimal max && max > 0m)
			volume = Math.Min(max, volume);
		if (volume > 0m)
			Submit(side, volume, "Farhad Crab entry");
	}

	private void HandleRisk(ICandleMessage candle)
	{
		if (_entryPrice is not decimal entry)
			return;
		var positive = Position > 0m;
		var pip = PipSize();
		var target = (positive ? LongTakeProfitPips : ShortTakeProfitPips) * pip;
		if (target > 0m && (positive ? candle.HighPrice >= entry + target :
			candle.LowPrice <= entry - target))
		{
			Exit("Farhad Crab take profit");
			return;
		}

		if (_trailingLevel is decimal level && (positive ? candle.LowPrice <= level :
			candle.HighPrice >= level))
		{
			Exit("Farhad Crab trailing stop");
			return;
		}

		var distance = (positive ? LongTrailingStopPips : ShortTrailingStopPips) * pip;
		if (distance <= 0m)
			return;

		_bestPrice = positive ? Math.Max(_bestPrice, candle.HighPrice) :
			Math.Min(_bestPrice, candle.LowPrice);
		var move = positive ? _bestPrice - entry : entry - _bestPrice;
		if (move < distance)
			return;
		var next = positive ? _bestPrice - distance : _bestPrice + distance;
		if (_trailingLevel is null || (positive ? next > _trailingLevel.Value : next < _trailingLevel.Value))
			_trailingLevel = next;
	}

	private void Exit(string reason)
	{
		if (!Pending && Position != 0m)
			Submit(Position > 0m ? Sides.Sell : Sides.Buy, Math.Abs(Position), reason);
	}

	private void Submit(Sides side, decimal volume, string comment)
	{
		_pendingOrder = new Order
		{
			Security = Security,
			Portfolio = Portfolio,
			Type = OrderTypes.Market,
			Side = side,
			Volume = volume,
			Comment = comment,
		};
		if (comment == "Farhad Crab entry")
			_entryOrder = _pendingOrder;
		RegisterOrder(_pendingOrder);
	}

	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);
		if (trade?.Order == _entryOrder && trade.Trade is not null)
		{
			_entryPrice = trade.Trade.Price;
			_bestPrice = _entryPrice.Value;
			_trailingLevel = null;
		}
		else if (Position == 0m)
		{
			_entryPrice = null;
			_trailingLevel = null;
			_bestPrice = 0m;
		}
	}
}