Auf GitHub ansehen

SurefireThing-Strategie

Überblick

Die SurefireThing-Strategie ist eine StockSharp High-Level-Portierung des MetaTrader 4 Expertenberaters Surefirething. Es arbeitet mit abgeschlossenen Kerzen, berechnet die Höhe der ausstehenden Aufträge aus dem Bereich der vorherigen Sitzung und setzt das Engagement am Ende jedes Handelstages zurück. Die Logik basiert auf der Bereitstellung eines symmetrischen Paars von Limit-Orders, die versuchen, eine Mean-Reversion um den vorherigen Schlusskurs herum zu erfassen.

Handelslogik

  • Am Ende jedes Handelstages versucht die Strategie, die Position zu reduzieren und alle aktiven ausstehenden Aufträge zu stornieren.
  • Anhand der letzten abgeschlossenen Kerze des Vortages wird die Preisspanne (High - Low) gemessen und mit RangeMultiplier multipliziert (standardmäßig 1,1 wie im Original EA).
  • Die Hälfte der angepassten Spanne wird zum vorherigen Schlusskurs addiert, um den Verkaufslimit-Einstiegspreis zu erhalten. Der gleiche Abstand wird vom Schlusskurs abgezogen, um die Kauf-Limit-Order zu platzieren.
  • Stop-Loss- und Take-Profit-Offsets werden in Preisschritten ausgedrückt. Wenn das Instrument einen gültigen Security.Step anzeigt, werden diese in absolute Distanzen umgewandelt und über StartProtection verwaltet, sodass besetzte Positionen automatisch Schutzbefehle erhalten.
  • Die Auftragserteilung erfolgt einmal pro Handelstag. Wenn Füllungen auftreten, verarbeitet der angehängte Schutz Exits; andernfalls bleiben Bestellungen bis zum nächsten täglichen Reset aktiv.

Parameter

Parameter Beschreibung Standard
OrderVolume Mit jeder ausstehenden Bestellung übermitteltes Volumen. 0.1
TakeProfitPoints Abstand (in Preisschritten) zum Gewinnziel. Wird in einen absoluten Offset umgewandelt, wenn der Schritt bekannt ist. 10
StopLossPoints Abstand (in Preisschritten) für den Schutzanschlag. Auf die gleiche Weise umgerechnet wie das Gewinnziel. 15
RangeMultiplier Faktor, der vor der Berechnung der Einstiegspreise auf den vorherigen Kerzenbereich angewendet wird. 1.1
CandleType Primärer Zeitrahmen, der von der Strategie verarbeitet wird. Standardmäßig werden 1-Minuten-Kerzen verwendet, können aber an das Originaldiagramm angepasst werden. TimeSpan.FromMinutes(1)

Implementierungshinweise

  • High-Level API: Kerzen werden durch SubscribeCandles(CandleType) verbraucht und im ProcessCandle-Handler verarbeitet, sobald sie fertig sind.
  • Tägliches Zurücksetzen: CloseForNewDay storniert ausstehende Aufträge und schließt Positionen, wenn anhand der Kerzenzeitstempel ein neuer Kalendertag erkannt wird.
  • Schutzlogik: ConfigureProtection übersetzt die punktbasierten Risikokontrollen in Unit Instanzen und aktiviert StartProtection, sodass Stop-Loss- und Take-Profit-Orders nach Ausführungen automatisch neu erstellt werden.
  • Auftragslebenszyklus: Verweise auf beide ausstehenden Aufträge werden gespeichert und über CancelPendingOrder sowie OnOrderChanged gelöscht, wenn die Aufträge abgeschlossen oder storniert werden.
  • Preisnormalisierung: Security.ShrinkPrice wird verwendet, um berechnete Preise auf die Tick-Größe des Instruments zu runden, bevor neue Aufträge übermittelt werden.

Nutzungsempfehlungen

  • Richten Sie CandleType an dem Zeitrahmen aus, der vom ursprünglichen EA verwendet wurde (normalerweise das Diagramm, an dem es angehängt wurde), um die gleichen Referenzkerzen beizubehalten.
  • Passen Sie RangeMultiplier an, wenn Instrumente unterschiedliche Volatilitätseigenschaften aufweisen, damit die ausstehenden Aufträge in realistischen Abständen bleiben.
  • Wenn der Broker Mindesthalteabstände vorschreibt, stellen Sie sicher, dass TakeProfitPoints und StopLossPoints diese Einschränkungen nach der Umrechnung in absolute Preise einhalten.
  • Die Strategie geht von kontinuierlichen Intraday-Daten aus. Wenn große Lücken auftreten (Wochenenden, Feiertage), löst die nächste verfügbare Kerze immer noch einen Reset und eine neue Auftragserteilung basierend auf dem zuletzt beobachteten Balken aus.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Daily range breakout strategy.
/// Calculates buy/sell levels from previous day's range.
/// Buys when price drops below the lower level, sells when above the upper level.
/// Closes position at end of day.
/// </summary>
public class SurefireThingStrategy : Strategy
{
	private readonly StrategyParam<decimal> _rangeMultiplier;
	private readonly StrategyParam<DataType> _candleType;

	private DateTime? _currentDay;
	private decimal _buyLevel;
	private decimal _sellLevel;
	private bool _levelsReady;
	private decimal _prevDayClose;
	private decimal _prevDayHigh;
	private decimal _prevDayLow;
	private decimal _dayHigh;
	private decimal _dayLow;
	private decimal _dayClose;
	private bool _hasPrevDay;
	private bool _tradedToday;

	public decimal RangeMultiplier
	{
		get => _rangeMultiplier.Value;
		set => _rangeMultiplier.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public SurefireThingStrategy()
	{
		_rangeMultiplier = Param(nameof(RangeMultiplier), 0.5m)
			.SetDisplay("Range Mult", "Multiplier for range-based levels", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Candle series", "General");
	}

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

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

		_currentDay = null;
		_buyLevel = 0m;
		_sellLevel = 0m;
		_levelsReady = false;
		_prevDayClose = 0m;
		_prevDayHigh = 0m;
		_prevDayLow = 0m;
		_dayHigh = 0m;
		_dayLow = 0m;
		_dayClose = 0m;
		_hasPrevDay = false;
		_tradedToday = false;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_currentDay = null;
		_levelsReady = false;
		_hasPrevDay = false;
		_tradedToday = false;

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

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

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

		var day = candle.OpenTime.Date;

		// New day detected
		if (_currentDay == null || day > _currentDay.Value)
		{
			// Close position at end of previous day
			if (Position > 0)
				SellMarket();
			else if (Position < 0)
				BuyMarket();

			// Save previous day stats
			if (_currentDay != null)
			{
				_prevDayClose = _dayClose;
				_prevDayHigh = _dayHigh;
				_prevDayLow = _dayLow;
				_hasPrevDay = true;
			}

			// Calculate new levels
			if (_hasPrevDay)
			{
				var range = _prevDayHigh - _prevDayLow;
				if (range > 0)
				{
					var halfRange = range * RangeMultiplier;
					_buyLevel = _prevDayClose - halfRange;
					_sellLevel = _prevDayClose + halfRange;
					_levelsReady = true;
				}
			}

			_currentDay = day;
			_dayHigh = candle.HighPrice;
			_dayLow = candle.LowPrice;
			_dayClose = candle.ClosePrice;
			_tradedToday = false;
		}
		else
		{
			if (candle.HighPrice > _dayHigh) _dayHigh = candle.HighPrice;
			if (candle.LowPrice < _dayLow) _dayLow = candle.LowPrice;
			_dayClose = candle.ClosePrice;
		}

		if (!_levelsReady)
			return;

		var price = candle.ClosePrice;

		// Only one trade per day per direction
		if (!_tradedToday && Position == 0)
		{
			if (price <= _buyLevel)
			{
				BuyMarket();
				_tradedToday = true;
			}
			else if (price >= _sellLevel)
			{
				SellMarket();
				_tradedToday = true;
			}
		}
	}
}