Auf GitHub ansehen

Strategie für die E-Freitag-Sitzung

Überblick

Die E-Friday Session-Strategie repliziert den klassischen MetaTrader-Expertenberater, der nur freitags handelt. Es beobachtet die vorherige Tageskerze und eröffnet zu einer konfigurierten Stunde zu Beginn der Freitagssitzung eine Position. Die Richtung ist konträr: Wenn der Vortag unter seinem Eröffnungskurs (bärische Kerze) schloss, kauft die Strategie; Wenn der Vortag über seinem Eröffnungskurs schloss (bullische Kerze), wird die Strategie verkauft. Positionen werden untertägig verwaltet und können automatisch nach einer konfigurierbaren Stunde oder durch Schutzstopps geschlossen werden.

Handelsregeln

  1. Sammeln Sie tägliche Kerzen (Standard: 1 Tag), um die Eröffnungs- und Schlusskurse des Vortages zu erhalten.
  2. Überwachen Sie freitags die Intraday-Kerzen (Standard: 1 Minute), um die konfigurierte Eintrittsstunde zu erkennen.
  3. Bei der ersten Kerze der Eintrittsstunde:
    • Gehen Sie long, wenn der Vortag bärisch war.
    • Gehen Sie short, wenn der Vortag bullisch war.
    • Überspringen Sie den Handel, wenn der Vortag ein Doji war (eröffnet gleich geschlossen).
  4. Optional können Sie die Position automatisch schließen, sobald die konfigurierte Ausstiegsstunde erreicht ist.
  5. Verwalten Sie Exits mithilfe von Stop-Loss, Take-Profit und optionaler Trailing-Stop-Logik, die den ursprünglichen Expert Advisor nachahmt, einschließlich der Gewinnaktivierung und der Trailing-Step-Schwellenwerte.

Implementierungshinweise

  • Verwendet StockSharp Kerzenabonnements auf hoher Ebene sowohl für den täglichen Kontext als auch für das Intraday-Timing.
  • Konvertiert punktbasierte Risikokontrollen aus der MQL-Version mithilfe der Preisstufe des Wertpapiers in absolute Preisversätze.
  • Behält Trailing-Stops im Code bei, aktualisiert sie bei jeder abgeschlossenen Kerze und schließt die Position, wenn Preisextreme durchbrochen werden.
  • Stellt nur einen Handel pro Freitag sicher, indem der tägliche Status verfolgt wird.
  • Unterstützt sowohl Long- als auch Short-Einträge unter Berücksichtigung des ursprünglichen Magic-Number-Gatings durch den Handel mit einem einzelnen Symbol pro Strategieinstanz.

Parameter

Name Beschreibung Standard
Volume Handelsgröße in Losen/Kontrakten. 0.1
StopLossPoints Stop-Loss-Distanz in Preisschritten (0 deaktiviert). 75
TakeProfitPoints Take-Profit-Distanz in Preisschritten (0 deaktiviert). 0
HourOpen Stunde des Tages (0-23), um die Position zu eröffnen. 7
UseClosePositions Aktivieren Sie das automatische Schließen nach der Ausgangsstunde. true
HourClose Stunde des Tages (0-23), um die Position zu schließen, falls aktiviert. 19
UseTrailing Aktivieren Sie Trailing-Stop-Anpassungen. true
ProfitTrailing Der Gewinn muss die Trailing-Distanz überschreiten, bevor das Trailing aktiviert wird. true
TrailingStopPoints Trailing-Stop-Distanz in Preisschritten. 60
TrailingStepPoints Vor dem Festziehen des Trailing Stop sind zusätzliche Punkte erforderlich. 5
IntradayCandleType Kerzentyp für Intraday-Timing (Standard-1-Minuten-Kerzen). TimeSpan.FromMinutes(1)
DailyCandleType Kerzentyp für die tägliche Stimmungserkennung (Standard-1-Tages-Kerzen). TimeSpan.FromDays(1)

Nutzungstipps

  • Richten Sie die Handelssitzung des Instruments so aus, dass die Einstiegsstunde am Freitag mit der gewünschten Marktöffnung übereinstimmt.
  • Wenn Sie Stop-Loss- und Trailing-Werte konfigurieren, drücken Sie diese in denselben „Punkten“ aus, die von der Preisstufe des Symbols verwendet werden, um das Verhalten von MetaTrader zu reproduzieren.
  • Die Strategie ist auf einen einzelnen Trade pro Freitag ausgelegt. Um mit mehreren Symbolen zu handeln, führen Sie für jedes Symbol separate Strategieinstanzen aus.

Unterschiede zum Original EA

  • Verwendet Kerzenschlussdaten für die Entscheidungsfindung, während die ursprünglich abgefragten Preise pro Tick verwendet werden.
  • Schutzausstiege werden über Marktaufträge ausgeführt, wenn Kerzen anzeigen, dass Stop- oder Zielniveaus innerhalb des Intervalls berührt wurden.
  • Strategieparameter werden über das StrategyParam-System von StockSharp bereitgestellt und unterstützen die Optimierung und UI-Bindung.

Ausführungsdetails: Der Freitagseinstieg wird bei der ersten abgeschlossenen Intraday-Kerze geprüft, deren Eröffnungsstunde HourOpen entspricht, nicht beim ersten Tick. Eine abgeschlossene Tageskerze vom Donnerstag ist erforderlich. Pro Freitag wird nur ein Einstiegsversuch unternommen, auch nach einem frühen Schutz-Ausstieg. Sitzungsschluss, Stop, Take-Profit und Trailing werden an den Extremwerten abgeschlossener Intraday-Kerzen geprüft und über Marktorders geschlossen. Der Trailing-Level wird nur nach dem konfigurierten Schritt nachgezogen; eine rückwirkende Ausführung innerhalb derselben Kerze wird nicht angenommen.

namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

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

/// <summary>
/// Takes at most one contrarian position on Friday at the configured hour,
/// using the previous completed daily candle; manages session and price exits.
/// </summary>
public class EFridaySessionStrategy : Strategy
{
	private readonly StrategyParam<int> _hourOpen;
	private readonly StrategyParam<int> _hourClose;
	private readonly StrategyParam<bool> _useClosePositions;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<bool> _useTrailing;
	private readonly StrategyParam<bool> _profitTrailing;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<decimal> _trailingStepPoints;
	private readonly StrategyParam<DataType> _intradayCandleType;
	private readonly StrategyParam<DataType> _dailyCandleType;

	private DateTime? _lastDailyDate;
	private decimal _dailyOpen;
	private decimal _dailyClose;
	private DateTime? _tradedDate;
	private decimal? _entryPrice;
	private decimal? _trailingLevel;
	private decimal _bestPrice;
	private Order _entryOrder;
	private Order _exitOrder;

	public int HourOpen { get => _hourOpen.Value; set => _hourOpen.Value = value; }
	public int HourClose { get => _hourClose.Value; set => _hourClose.Value = value; }
	public bool UseClosePositions { get => _useClosePositions.Value; set => _useClosePositions.Value = value; }
	public decimal StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public decimal TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
	public bool UseTrailing { get => _useTrailing.Value; set => _useTrailing.Value = value; }
	public bool ProfitTrailing { get => _profitTrailing.Value; set => _profitTrailing.Value = value; }
	public decimal TrailingStopPoints { get => _trailingStopPoints.Value; set => _trailingStopPoints.Value = value; }
	public decimal TrailingStepPoints { get => _trailingStepPoints.Value; set => _trailingStepPoints.Value = value; }
	public DataType IntradayCandleType { get => _intradayCandleType.Value; set => _intradayCandleType.Value = value; }
	public DataType DailyCandleType { get => _dailyCandleType.Value; set => _dailyCandleType.Value = value; }

	public EFridaySessionStrategy()
	{
		Volume = 0.1m;
		_hourOpen = Param(nameof(HourOpen), 7).SetRange(0, 23);
		_hourClose = Param(nameof(HourClose), 19).SetRange(0, 23);
		_useClosePositions = Param(nameof(UseClosePositions), true);
		_stopLossPoints = Param(nameof(StopLossPoints), 75m).SetNotNegative();
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 0m).SetNotNegative();
		_useTrailing = Param(nameof(UseTrailing), true);
		_profitTrailing = Param(nameof(ProfitTrailing), true);
		_trailingStopPoints = Param(nameof(TrailingStopPoints), 60m).SetNotNegative();
		_trailingStepPoints = Param(nameof(TrailingStepPoints), 5m).SetNotNegative();
		_intradayCandleType = Param(nameof(IntradayCandleType), TimeSpan.FromMinutes(1).TimeFrame());
		_dailyCandleType = Param(nameof(DailyCandleType), TimeSpan.FromDays(1).TimeFrame());
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, IntradayCandleType), (Security, DailyCandleType)];

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

	private void ClearState()
	{
		_lastDailyDate = null;
		_dailyOpen = 0m;
		_dailyClose = 0m;
		_tradedDate = null;
		_entryPrice = null;
		_trailingLevel = null;
		_bestPrice = 0m;
		_entryOrder = null;
		_exitOrder = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		ClearState();
		if ((Security?.PriceStep ?? 0m) <= 0m)
			throw new InvalidOperationException("E-Friday requires a positive PriceStep.");
		SubscribeCandles(DailyCandleType).Bind(ProcessDailyCandle).Start();
		SubscribeCandles(IntradayCandleType).Bind(ProcessIntradayCandle).Start();
	}

	private void ProcessDailyCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;
		_lastDailyDate = candle.OpenTime.Date;
		_dailyOpen = candle.OpenPrice;
		_dailyClose = candle.ClosePrice;
	}

	private static bool IsPending(Order order) => order is not null &&
		order.State is not (OrderStates.Done or OrderStates.Failed);

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

		if (Position != 0m && !IsPending(_exitOrder))
		{
			if (UseClosePositions && candle.OpenTime.Hour >= HourClose)
			{
				ExitPosition("EFriday session close");
				return;
			}
			if (HandleRiskExits(candle))
				return;
		}

		var day = candle.OpenTime.Date;
		if (candle.OpenTime.DayOfWeek != DayOfWeek.Friday ||
			candle.OpenTime.Hour != HourOpen || _tradedDate == day ||
			_lastDailyDate != day.AddDays(-1) ||
			Position != 0m || IsPending(_entryOrder) || IsPending(_exitOrder))
			return;

		if (_dailyClose == _dailyOpen)
			return;

		var side = _dailyClose < _dailyOpen ? Sides.Buy : Sides.Sell;
		var volume = Volume;
		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(volume, min);
		if (Security?.MaxVolume is decimal max && max > 0m)
			volume = Math.Min(volume, max);
		if (volume <= 0m)
			return;

		_entryOrder = Market(side, volume, "EFriday entry");
		_tradedDate = day;
	}

	private bool HandleRiskExits(ICandleMessage candle)
	{
		if (_entryPrice is not decimal entry)
			return false;
		var point = Security?.PriceStep ?? 0m;
		var longPosition = Position > 0m;

		var stop = StopLossPoints * point;
		var take = TakeProfitPoints * point;
		if (stop > 0m && (longPosition ? candle.LowPrice <= entry - stop :
			candle.HighPrice >= entry + stop))
		{
			ExitPosition("EFriday stop loss");
			return true;
		}
		if (take > 0m && (longPosition ? candle.HighPrice >= entry + take :
			candle.LowPrice <= entry - take))
		{
			ExitPosition("EFriday take profit");
			return true;
		}

		// Check the previously established trailing level before using the
		// latest bar's extremes: intrabar high/low order is not available.
		if (_trailingLevel is decimal oldStop &&
			(longPosition ? candle.LowPrice <= oldStop : candle.HighPrice >= oldStop))
		{
			ExitPosition("EFriday trailing stop");
			return true;
		}

		if (!UseTrailing || TrailingStopPoints <= 0m)
			return false;

		var distance = TrailingStopPoints * point;
		var improvement = TrailingStepPoints * point;
		_bestPrice = longPosition ? Math.Max(_bestPrice, candle.HighPrice) :
			Math.Min(_bestPrice, candle.LowPrice);
		var profit = longPosition ? _bestPrice - entry : entry - _bestPrice;
		if (ProfitTrailing && profit < distance)
			return false;

		var next = longPosition ? _bestPrice - distance : _bestPrice + distance;
		if (_trailingLevel is not decimal current ||
			(longPosition ? next >= current + improvement && next > current :
				next <= current - improvement && next < current))
			_trailingLevel = next;

		return false;
	}

	private Order Market(Sides side, decimal volume, string comment)
	{
		var order = new Order
		{
			Security = Security,
			Portfolio = Portfolio,
			Type = OrderTypes.Market,
			Side = side,
			Volume = volume,
			Comment = comment,
		};
		if (comment == "EFriday entry")
			_entryOrder = order;
		else
			_exitOrder = order;
		RegisterOrder(order);
		return order;
	}

	private void ExitPosition(string comment)
	{
		if (Position == 0m || IsPending(_exitOrder))
			return;
		_exitOrder = Market(Position > 0m ? Sides.Sell : Sides.Buy, Math.Abs(Position), comment);
	}

	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;
			_exitOrder = null;
		}
	}
}