Auf GitHub ansehen

SimpleTrade-Strategie

Überblick

Diese Strategie ist eine C#-Konvertierung des MetaTrader 5 Expert Advisors "SimpleTrade (barabashkakvns Edition)". Sie vergleicht den Eröffnungspreis der aktuellen Bar mit dem Eröffnungspreis drei Bars zuvor. Wenn das aktuelle Open höher ist, geht die Strategie long; andernfalls geht sie short. Jede Position wird nur eine abgeschlossene Kerze lang gehalten und mit einer festen Stop-Loss-Distanz in Pips abgesichert.

Die StockSharp-Implementierung abonniert die ausgewählte Kerzenserie über die High-Level-API und reagiert nur auf abgeschlossene Bars, um sicherzustellen, dass Entscheidungen auf abgeschlossenen Preisdaten basieren. Positionen werden beim nächsten Bar-Übergang oder früher geschlossen, wenn das Stop-Level innerhalb der Bar-Range berührt wird.

Handelslogik

  • Einstieg
    • Bei jeder abgeschlossenen Bar den Eröffnungspreis speichern und eine rollende Geschichte der letzten vier Opens pflegen.
    • Wenn keine offene Position vorhanden ist und mindestens vier Eröffnungspreise verfügbar sind, das neueste Open mit dem vor drei Bars aufgezeichneten vergleichen.
    • Eine Long-Position eingehen, wenn das aktuelle Open über dem Open drei Bars zuvor liegt; andernfalls eine Short-Position eingehen.
  • Ausstieg
    • Jeder Trade ist durch ein Stop-Level geschützt, das als StopLossPips × Pip-Größe vom Einstiegs-Eröffnungspreis berechnet wird.
    • Auf der folgenden Bar wird die Position unabhängig vom Ergebnis geschlossen, um den ursprünglichen Expert Advisor zu replizieren, der einen Trade nie länger als eine Kerze hält.
    • Wenn das Bar-High (für Shorts) oder Low (für Longs) das Stop-Level durchdringt, versucht die Strategie, die Position sofort zum Marktpreis zu schließen.

Parameter

Parameter Standard Beschreibung
StopLossPips 120 Abstand vom Einstiegs-Eröffnungspreis zum Schutz-Stop, gemessen in Pips. Der Code reproduziert das MetaTrader-Verhalten, indem er den Preisschritt für Symbole mit 3 oder 5 Dezimalstellen mit 10 multipliziert.
TradeVolume 1 Order-Volumen für Market-Einstiege. Anpassen, um mit der Kontraktgröße des gehandelten Instruments übereinzustimmen.
CandleType 1-Stunden-Zeitrahmen Gibt an, welche Kerzenserie die Strategie abonniert. Den Zeitrahmen wählen, der dem in MetaTrader verwendeten Chart entspricht.

Alle Parameter sind als StrategyParam<T>-Objekte exponiert, damit sie über die grafische Benutzeroberfläche optimiert oder geändert werden können.

Implementierungshinweise

  • Die rollende Geschichte von vier Eröffnungspreisen wird ohne Sammlungen gepflegt, um den Repository-Richtlinien zu entsprechen.
  • Stops werden nicht als separate Orders eingereicht; stattdessen prüft die Logik die Kerzen-Ranges und gibt einen Market-Ausstieg aus, wenn das Stop-Level ausgelöst worden wäre.
  • Da StockSharp Positionen asynchron verarbeitet, verlässt die Strategie einen bestehenden Trade, bevor ein neues Einstiegssignal ausgewertet wird. Im Live-Trading entspricht dies der ursprünglichen "Schließen dann Wiederöffnen"-Sequenz und vermeidet gleichzeitig überlappende Orders.
  • Die Pip-Größe wird von Security.PriceStep abgeleitet. Bei 5- oder 3-stelligen Symbolen wird der Schritt mit zehn multipliziert, damit ein Pip der MetaTrader-Definition entspricht.

Verwendungstipps

  • Die Strategie auf Instrumenten mit konsistenten Tick-Größen ausführen, wo pip-basierte Stops sinnvoll sind (z.B. wichtige Forex-Paare).
  • Den StopLossPips-Wert pro Instrument optimieren; große Werte erweitern den Schutzpuffer, während kleinere Werte die Strategie empfindlicher für Intrabar-Rauschen machen.
  • Sicherstellen, dass die Broker-Verbindung Kerzen-Updates mit endgültigen Zuständen sendet, damit die Strategie die korrekten Eröffnungspreise erhält.

Risiken und Einschränkungen

  • Trades nur eine Bar lang zu halten bedeutet, dass die Strategie stark vom gewählten Zeitrahmen abhängt. Backtesting verschiedener Kerzen-Dauern ist unerlässlich.
  • Die Verwendung von Kerzen-Extremen zur Emulation von Stop-Ausführungen führt in volatilen Märkten im Vergleich zu nativen Stop-Orders zu Slippage.
  • Die Strategie bleibt nach den ersten vier Datenbars immer im Markt (entweder long oder short), was in Seitwärtsmärkten häufige Trades erzeugen kann.
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simple trade strategy converted from MetaTrader that compares the current open with the open three bars ago.
/// The logic holds positions for a single bar and protects the trade with a fixed stop distance.
/// </summary>
public class SimpleTradeStrategy : Strategy
{
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _openCurrent;
	private decimal _openMinus1;
	private decimal _openMinus2;
	private decimal _openMinus3;
	private int _historyCount;
	private decimal? _stopPrice;

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

	/// <summary>
	/// Trade volume used for market orders.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Candle type processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initialize <see cref="SimpleTradeStrategy"/> parameters.
	/// </summary>
	public SimpleTradeStrategy()
	{
		_stopLossPips = Param(nameof(StopLossPips), 120)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pips)", "Fixed protective distance in pips", "Risk Management")
			;

		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Volume", "Order volume in lots", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle source for decisions", "General");
	}

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

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

		_openCurrent = 0m;
		_openMinus1 = 0m;
		_openMinus2 = 0m;
		_openMinus3 = 0m;
		_historyCount = 0;
		_stopPrice = null;
	}

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

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

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

		// Exit existing trades before evaluating new entries to mimic the original MQL behaviour.
		if (TryCloseExistingPosition(candle))
			return;

		UpdateHistory(candle.OpenPrice);

		// Need at least four opens to compare with the value three bars ago.
		if (_historyCount < 4)
			return;

		ExecuteEntry(candle);
	}

	private bool TryCloseExistingPosition(ICandleMessage candle)
	{
		if (Position > 0)
		{
			var volume = Position;

			// Close long trades at the protective stop or at the bar change.
			if (_stopPrice is decimal stop && candle.LowPrice <= stop)
			{
				SellMarket();
			}
			else
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
			}

			_stopPrice = null;
			return true;
		}

		if (Position < 0)
		{
			var volume = Math.Abs(Position);

			// Close short trades at the protective stop or at the bar change.
			if (_stopPrice is decimal stop && candle.HighPrice >= stop)
			{
				BuyMarket();
			}
			else
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
			}

			_stopPrice = null;
			return true;
		}

		return false;
	}

	private void UpdateHistory(decimal currentOpen)
	{
		// Shift the stored opens so that _openMinus3 keeps the value three candles back.
		_openMinus3 = _openMinus2;
		_openMinus2 = _openMinus1;
		_openMinus1 = _openCurrent;
		_openCurrent = currentOpen;

		if (_historyCount < 4)
			_historyCount++;
	}

	private void ExecuteEntry(ICandleMessage candle)
	{
		var pipSize = CalculatePipSize();
		var stopOffset = pipSize * StopLossPips;

		// Enter long when the current open is above the open three bars ago, otherwise enter short.
		if (_openCurrent > _openMinus3)
		{
			BuyMarket();
			_stopPrice = candle.OpenPrice - stopOffset;
		}
		else
		{
			SellMarket();
			_stopPrice = candle.OpenPrice + stopOffset;
		}
	}

	private decimal CalculatePipSize()
	{
		// Reproduce the MetaTrader adjustment: multiply by ten for symbols with 3 or 5 decimals.
		var step = Security?.PriceStep ?? 1m;
		var decimals = Security?.Decimals;

		if (decimals == 3 || decimals == 5)
			step *= 10m;

		return step;
	}
}