Auf GitHub ansehen

Typische Breakout-Strategie für den Montag

Überblick

Die Monday-Typical-Breakout-Strategie ist eine C#-Portierung des MetaTrader-Expertenberaters yi1ywioff50qr6 (Repository-ID 8187). Der ursprüngliche Roboter überwacht stündliche Kerzen und eröffnet jeden Montag eine Long-Position, wenn die neue Sitzung über dem typischen Preis des vorherigen Balkens (high + low + close) / 3 beginnt. Diese Implementierung reproduziert die Einstiegslogik innerhalb des StockSharp-Strategierahmens auf hoher Ebene und fügt detaillierte Konfigurationsparameter für die Positionsgröße und Risikokontrolle hinzu.

Handelslogik

  1. Die Strategie abonniert die konfigurierte Kerzenserie (standardmäßig stündlich).
  2. Zu Beginn jeder fertigen Kerze wird geprüft, ob:
    • Die Kerze gehört zum Montag.
    • Die Öffnungszeit der Kerze entspricht dem konfigurierten Parameter Open Hour (Standard 09:00).
    • Es sind keine offenen Positionen oder aktiven Aufträge vorhanden.
    • Der Eröffnungspreis der Kerze ist höher als der typische Preis des vorherigen Balkens.
  3. Wenn alle Bedingungen erfüllt sind, sendet die Strategie eine Marktkauforder mit einem vom Money-Management-Block berechneten Volumen. Durch StartProtection werden schützende Stop-Loss- und Take-Profit-Distanzen angewendet.

Die Strategie eröffnet niemals Short-Positionen und platziert nur einen Trade pro qualifizierter Montagskerze.

Parameter

Parameter Beschreibung Standard
FixedVolume Losgröße für Einträge. Auf 0 setzen, um die Equity-Skalierungstabelle zu aktivieren. 0.1
OpenHour Handelssitzungsstunde (0-23), in der Montagssignale ausgewertet werden. 9
StopLossPoints Abstand in Preispunkten für den Schutzstopp. 0 deaktiviert den Stopp. 50
TakeProfitPoints Abstand in Preispunkten zum Gewinnziel. 0 deaktiviert das Ziel. 20
InitialEquity Eigenkapitalschwelle, die die eigenkapitalbasierte Losskalierung aktiviert. 600
EquityStep Zur Erhöhung der Handelsgröße ist eine Eigenkapitalerhöhung erforderlich. 300
InitialStepVolume Die Losgröße wird verwendet, wenn das Eigenkapital mindestens InitialEquity beträgt. 0.4
VolumeStep Zusätzliche Losgröße für jeden erreichten EquityStep hinzugefügt. 0.2
CandleType Kerzendatentyp, der die Strategie steuert (standardmäßig stündlich). 1 hour time-frame

Money-Management

  • Wenn FixedVolume größer als Null ist, verwendet die Strategie immer die feste Losgröße.
  • Wenn FixedVolume gleich Null ist, prüft die Strategie das Portfolio-Eigenkapital:
    • Wenn das Eigenkapital unter InitialEquity liegt, wird die Mindestmenge des Instruments verwendet.
    • Andernfalls beginnt das Volumen bei InitialStepVolume und erhöht sich um VolumeStep für jedes EquityStep zusätzliches Eigenkapital.
    • Die endgültige Lautstärke richtet sich nach den Mindest- und Stufenbeschränkungen des Instruments.

Risikomanagement

StartProtection wird während OnStarted aktiviert. Die Stop-Loss- und Take-Profit-Abstände werden mithilfe des Instruments PriceStep automatisch von Punkten in Preis-Offsets übersetzt. Setzen Sie einen der beiden Distanzen auf Null, um diese Komponente zu deaktivieren.

Nutzungshinweise

  • Das Original EA ist für Stundenkerzen konzipiert. Bei kürzeren Zeitrahmen können mehrere Montagskerzen zur gleichen Stunde entstehen. Der Port behält das Verhalten eines einzelnen Eintrags pro Kerze bei und ignoriert weiterhin zusätzliche Signale, während eine Position offen ist.
  • Stellen Sie sicher, dass die Portfolioinformationen (Portfolio.CurrentValue) verfügbar sind, wenn der dynamische Größenblock aktiviert ist.
  • Die Strategie erfordert Level-1-Daten zur Ausführung von Marktaufträgen und das entsprechende Kerzenabonnement für den konfigurierten CandleType.

Konvertierungshinweise

  • Die Filterung nach magischen Zahlen von MQL wird durch die Positions- und Reihenfolgeprüfungen von StockSharp (Position und ActiveOrders) ersetzt.
  • Zeitvergleiche nutzen DateTimeOffset aus der Kerzeneröffnungszeit mit .ToLocalTime(), um mit der Chartzeit in Einklang zu bleiben.
  • Schutzbestellungen werden vom High-Level-Helper StartProtection statt einer manuellen Auftragserteilung bearbeitet.
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;
using StockSharp.Algo.Candles;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Port of the MetaTrader strategy yi1ywioff50qr6 (ID 8187).
/// Buys on Monday when the hourly open breaks above the prior bar's typical price.
/// Applies equity-based position sizing when the fixed lot is disabled.
/// </summary>
public class MondayTypicalBreakoutStrategy : Strategy
{
	private readonly StrategyParam<decimal> _fixedVolume;
	private readonly StrategyParam<int> _openHour;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<decimal> _initialEquity;
	private readonly StrategyParam<decimal> _equityStep;
	private readonly StrategyParam<decimal> _initialStepVolume;
	private readonly StrategyParam<decimal> _volumeStep;
	private readonly StrategyParam<DataType> _candleType;

	private ICandleMessage _previousCandle;
	private DateTimeOffset? _lastSignalTime;
	private decimal _priceStep;

	/// <summary>
	/// Initializes parameters to mirror the MQL expert defaults.
	/// </summary>
	public MondayTypicalBreakoutStrategy()
	{
		_fixedVolume = Param(nameof(FixedVolume), 0.1m)
		.SetNotNegative()
		.SetDisplay("Fixed Volume", "Lot size used for entries (set to 0 to enable equity scaling)", "Risk");

		_openHour = Param(nameof(OpenHour), 9)
		.SetRange(0, 23)
		.SetDisplay("Open Hour", "Hour of the session to evaluate Monday breakout entries", "Session");

		_stopLossPoints = Param(nameof(StopLossPoints), 50)
		.SetNotNegative()
		.SetDisplay("Stop Loss (points)", "Protective stop distance expressed in price points", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 20)
		.SetNotNegative()
		.SetDisplay("Take Profit (points)", "Profit target distance expressed in price points", "Risk");

		_initialEquity = Param(nameof(InitialEquity), 600m)
		.SetGreaterThanZero()
		.SetDisplay("Initial Equity", "Account equity threshold that triggers the first scaling tier", "Money Management");

		_equityStep = Param(nameof(EquityStep), 300m)
		.SetGreaterThanZero()
		.SetDisplay("Equity Step", "Incremental equity required to raise the position size", "Money Management");

		_initialStepVolume = Param(nameof(InitialStepVolume), 0.4m)
		.SetGreaterThanZero()
		.SetDisplay("Initial Step Volume", "Lot size used once the equity threshold is met", "Money Management");

		_volumeStep = Param(nameof(VolumeStep), 0.2m)
		.SetNotNegative()
		.SetDisplay("Volume Step", "Additional lot size added for each equity step", "Money Management");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe used for detecting the Monday breakout", "General");
	}

	/// <summary>
	/// Fixed lot size used for entries (set to zero to enable scaling).
	/// </summary>
	public decimal FixedVolume
	{
		get => _fixedVolume.Value;
		set => _fixedVolume.Value = value;
	}

	/// <summary>
	/// Hour (0-23) when Monday entries are evaluated.
	/// </summary>
	public int OpenHour
	{
		get => _openHour.Value;
		set => _openHour.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in price points.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in price points.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Minimum equity required before the scaling table becomes active.
	/// </summary>
	public decimal InitialEquity
	{
		get => _initialEquity.Value;
		set => _initialEquity.Value = value;
	}

	/// <summary>
	/// Equity increment that increases the trade size by <see cref="VolumeStep"/>.
	/// </summary>
	public decimal EquityStep
	{
		get => _equityStep.Value;
		set => _equityStep.Value = value;
	}

	/// <summary>
	/// Volume applied when the first equity threshold is met.
	/// </summary>
	public decimal InitialStepVolume
	{
		get => _initialStepVolume.Value;
		set => _initialStepVolume.Value = value;
	}

	/// <summary>
	/// Additional volume added for each equity tier.
	/// </summary>
	public decimal VolumeStep
	{
		get => _volumeStep.Value;
		set => _volumeStep.Value = value;
	}

	/// <summary>
	/// Candle type used for detecting breakout conditions.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

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

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

		_previousCandle = null;
		_lastSignalTime = null;
		_priceStep = 0m;
	}

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

		_priceStep = Security?.PriceStep ?? 0m;
		if (_priceStep <= 0m)
		_priceStep = 0.0001m;

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

		if (TakeProfitPoints > 0 || StopLossPoints > 0)
		{
			var takeDistance = TakeProfitPoints > 0
			? new Unit(TakeProfitPoints * _priceStep, UnitTypes.Absolute)
			: new Unit(0m);
			var stopDistance = StopLossPoints > 0
			? new Unit(StopLossPoints * _priceStep, UnitTypes.Absolute)
			: new Unit(0m);

			StartProtection(takeProfit: takeDistance, stopLoss: stopDistance);
		}
	}

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

		var previous = _previousCandle;
		_previousCandle = candle;

		if (previous is null)
		return;

		if (!IsFormedAndOnlineAndAllowTrading())
		return;

		if (Position != 0m)
		return;

		var candleTime = candle.OpenTime.ToLocalTime();
		if (candleTime.DayOfWeek != DayOfWeek.Monday)
		return;

		if (candleTime.Hour != OpenHour)
		return;

		if (_lastSignalTime is DateTimeOffset last && last == candle.OpenTime)
		return;

		var typicalPrice = (previous.HighPrice + previous.LowPrice + previous.ClosePrice) / 3m;
		if (candle.OpenPrice <= typicalPrice)
		return;

		var volume = CalculateOrderVolume();
		volume = AlignVolume(volume);

		if (volume <= 0m)
		return;

		BuyMarket(volume);

		_lastSignalTime = candle.OpenTime;
	}

	private decimal CalculateOrderVolume()
	{
		var fixedVolume = FixedVolume;
		if (fixedVolume > 0m)
		return fixedVolume;

		var security = Security;
		var portfolio = Portfolio;

		var minVolume = security?.MinVolume ?? 0m;
		if (minVolume <= 0m)
		minVolume = 0.01m;

		var equity = portfolio?.CurrentValue ?? portfolio?.BeginValue ?? 0m;
		if (equity <= 0m)
		return minVolume;

		if (equity < InitialEquity)
		return minVolume;

		if (EquityStep <= 0m)
		return InitialStepVolume;

		var stepsDecimal = (equity - InitialEquity) / EquityStep;
		if (stepsDecimal < 0m)
		stepsDecimal = 0m;

		var steps = (int)Math.Floor(stepsDecimal);
		var dynamicVolume = InitialStepVolume + VolumeStep * steps;

		if (dynamicVolume < minVolume)
		dynamicVolume = minVolume;

		return dynamicVolume;
	}

	private decimal AlignVolume(decimal volume)
	{
		var security = Security;
		if (security == null)
		return volume;

		var minVolume = security.MinVolume ?? 0m;
		var maxVolume = security.MaxVolume ?? 0m;
		var step = security.VolumeStep ?? 0m;

		if (minVolume > 0m && volume < minVolume)
		volume = minVolume;

		if (maxVolume > 0m && volume > maxVolume)
		volume = maxVolume;

		if (step > 0m)
		{
			var steps = Math.Round(volume / step, MidpointRounding.AwayFromZero);
			volume = steps * step;
		}

		return volume;
	}
}