Auf GitHub ansehen

Expert NEWS-Strategie

Überblick

Die Expert NEWS-Strategie ist eine direkte Portierung des MQL5-Roboters "Expert_NEWS". Die Strategie platziert kontinuierlich symmetrische Stop-Orders ober- und unterhalb des aktuellen Marktpreises und verwaltet die resultierenden Positionen mit Break-Even-Schutz, Trailing Stops und geplanten Aktualisierungen ausstehender Orders. Die Implementierung basiert auf Level1-Quotes und hält das Standard-Handelsvolumen bei 0.1 Lots.

Handelslogik

  1. Quote-Abonnement – die Strategie lauscht auf beste Bid/Ask-Updates und berechnet Order-Preise aus den neuesten Werten.
  2. Initiale Stop-Orders – wenn keine Long-Position oder kein Buy Stop vorhanden ist, wird ein neuer Buy Stop bei ask + EntryOffsetTicks * PriceStep platziert. Wenn keine Short-Position oder kein Sell Stop vorhanden ist, wird ein Sell Stop bei bid - EntryOffsetTicks * PriceStep platziert.
  3. Order-Aktualisierung – alle OrderRefreshSeconds storniert die Strategie einen ausstehenden Stop und erstellt ihn neu, wenn der erforderliche Preis um mehr als TrailingStepTicks Ticks abweicht.
  4. Positionsschutz – nach einer Ausführung öffnet die Strategie schützende Stop- und Take-Profit-Orders, wenn die angeforderten Abstände die MinimumStopTicks-Bedingung erfüllen.
  5. Break-Even-Kontrolle – wenn UseBreakEven aktiviert ist, wird der Stop auf Einstieg ± BreakEvenProfitTicks gezogen, sobald sich der Markt weit genug bewegt und der neue Stop den Mindestabstand zum aktuellen Quote respektiert.
  6. Trailing Stop – sobald der Preis um TrailingStartTicks vorschreitet, folgt der Stop mit TrailingStopTicks als Abstand und TrailingStepTicks als minimaler Verbesserungsschritt.
  7. Bereinigung – das Schließen der Position storniert alle verbleibenden Schutz-Orders.

Parameter

Parameter Beschreibung
StopLossTicks Initialer Schutz-Stop-Abstand (Ticks). Auf null setzen, um die initiale Stop-Order zu deaktivieren.
TakeProfitTicks Initialer Take-Profit-Abstand (Ticks). Auf null setzen, um die Ziel-Order zu deaktivieren.
TrailingStopTicks Abstand des Trailing Stops (Ticks).
TrailingStartTicks Gewinn in Ticks, der erforderlich ist, bevor die Trailing-Logik aktiviert wird.
TrailingStepTicks Mindestverbesserung beim Aktualisieren des Trailing Stops oder der ausstehenden Einstiegs-Orders.
UseBreakEven Aktiviert die Break-Even-Verschiebung des Stops, sobald genügend Gewinn vorhanden ist.
BreakEvenProfitTicks Zusätzliches Gewinnpolster beim Verschieben des Stops auf Break-Even.
EntryOffsetTicks Abstand zwischen aktuellem Quote und jeder neuen Stop-Einstiegs-Order.
OrderRefreshSeconds Zeitintervall zwischen automatischen Aktualisierungsversuchen für ausstehende Stop-Orders.
MinimumStopTicks Manueller Fallback für die Stop-Level-Anforderung des Brokers. Stops, die näher als dieser Abstand sind, werden nicht übermittelt.

Positionsverwaltung

  • Schutz-Orders entsprechen immer dem Netto-Positionsvolumen. Teilausführungen skalieren die Stop- und Take-Profit-Orders automatisch.
  • Break-Even- und Trailing-Logik funktionieren auch wenn der initiale Stop deaktiviert ist; der Stop wird dynamisch erstellt, sobald die Regeln erfüllt sind.
  • Die Strategie speichert den letzten Stop-Preis im Speicher, sodass Trailing-Updates ein monotones Verhalten gewährleisten.

Verwendungshinweise

  • Stellen Sie sicher, dass Security.PriceStep konfiguriert ist; jeder Tick-Abstandsparameter wird mit diesem Wert multipliziert.
  • Das Standard-Volumen beträgt 0.1, um den ursprünglichen Roboter zu spiegeln. Passen Sie die Volume-Eigenschaft an, wenn eine andere Größe erforderlich ist.
  • MinimumStopTicks sollte auf die Stop-Level-Anforderung des Handelsplatzes gesetzt werden, wenn dieser eine vorschreibt. Lassen Sie es auf null, um die engstmöglichen Stops zu erlauben.
  • Der Algorithmus ist nicht auf historische Bars angewiesen und kann nur mit Streaming-Quotes betrieben werden.
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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Breakout strategy converted from the MQL Expert NEWS robot.
/// Detects breakouts above/below a reference price range and enters positions.
/// Uses stop loss and take profit for position management.
/// </summary>
public class ExpertNewsStrategy : Strategy
{
	private readonly StrategyParam<decimal> _entryOffset;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<int> _lookbackPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();
	private decimal _entryPrice;
	private int _lastSignal;

	/// <summary>
	/// Entry offset from the high/low range.
	/// </summary>
	public decimal EntryOffset
	{
		get => _entryOffset.Value;
		set => _entryOffset.Value = value;
	}

	/// <summary>
	/// Stop loss distance in price units.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Take profit distance in price units.
	/// </summary>
	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Number of bars to determine high/low range.
	/// </summary>
	public int LookbackPeriod
	{
		get => _lookbackPeriod.Value;
		set => _lookbackPeriod.Value = value;
	}

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

	/// <summary>
	/// Constructor.
	/// </summary>
	public ExpertNewsStrategy()
	{
		_entryOffset = Param(nameof(EntryOffset), 200m)
			.SetGreaterThanZero()
			.SetDisplay("Entry Offset", "Offset from range high/low for entry", "Parameters");

		_stopLoss = Param(nameof(StopLoss), 1000m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Stop loss in price units", "Risk");

		_takeProfit = Param(nameof(TakeProfit), 2000m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit", "Take profit in price units", "Risk");

		_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Lookback Period", "Bars for range calculation", "Parameters");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_highs.Clear();
		_lows.Clear();
		_entryPrice = 0m;
		_lastSignal = 0;
	}

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

		SubscribeCandles(CandleType)
			.Bind(ProcessCandle)
			.Start();

		StartProtection(
			new Unit(TakeProfit, UnitTypes.Absolute),
			new Unit(StopLoss, UnitTypes.Absolute));
	}

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

		_highs.Add(candle.HighPrice);
		_lows.Add(candle.LowPrice);

		if (_highs.Count > LookbackPeriod + 1)
			_highs.RemoveAt(0);
		if (_lows.Count > LookbackPeriod + 1)
			_lows.RemoveAt(0);

		if (_highs.Count <= LookbackPeriod)
			return;

		// Compute range from prior bars (excluding current)
		var rangeHigh = decimal.MinValue;
		var rangeLow = decimal.MaxValue;
		for (int i = 0; i < _highs.Count - 1; i++)
		{
			if (_highs[i] > rangeHigh) rangeHigh = _highs[i];
			if (_lows[i] < rangeLow) rangeLow = _lows[i];
		}

		var close = candle.ClosePrice;
		var breakoutUp = close > rangeHigh + EntryOffset;
		var breakoutDown = close < rangeLow - EntryOffset;

		if (breakoutUp && _lastSignal != 1 && Position <= 0)
		{
			BuyMarket();
			_entryPrice = close;
			_lastSignal = 1;
		}
		else if (breakoutDown && _lastSignal != -1 && Position >= 0)
		{
			SellMarket();
			_entryPrice = close;
			_lastSignal = -1;
		}
		else if (!breakoutUp && !breakoutDown)
			_lastSignal = 0;
	}
}