Auf GitHub ansehen

NTOqF-Multifilter

Überblick

Die NTOqF Multi-Filter-Strategie portiert den MetaTrader 4 Expertenberater „NTOqF“ (Versionen V1–V3) auf den High-Level API von StockSharp. Der ursprüngliche Roboter kombiniert mehrere Oszillatoren und Trendfolgefilter, die jeweils unabhängig aktiviert oder deaktiviert werden können. Diese C#-Version behält die gleiche Konfigurierbarkeit bei, unterstützt separate Zeitrahmen für jeden Indikator und wendet Handelsmanagement durch feste Stopps, Take-Profit-Ziele und einen optionalen Trailing Stop, ausgedrückt in Pips, an.

Strategielogik

Indikatorfilter

  • RSI-Filter – erzeugt ein langes Signal, wenn der Wert RSI (an der konfigurierten Verschiebung) unter RSI Lower liegt, und ein kurzes Signal, wenn der Wert über RSI Upper liegt. Neutrale Messwerte streichen Einträge.
  • Stochastic-Filter – vergleicht %K und %D. Wenn Use Stochastic High/Low aktiviert ist, muss die Hauptlinie auch über Stoch High für Long-Positionen oder unter Stoch Low für Short-Positionen liegen; andernfalls werden einfache %K/%D-Kreuzungen verwendet.
  • ADX-Filter – verwendet +DI gegenüber –DI, um die Richtung zu bestimmen. Wenn die Option Use ADX Main aktiviert ist, muss die Hauptzeile ADX größer als ADX Main sein, bevor Einträge akzeptiert werden.
  • Parabolic SAR-Filter – interpretiert den SAR-Wert relativ zum Schlusskurs des ausgewählten Balkens. Werte über dem Preis begünstigen Long-Positionen (was das Verhalten im MQL-Code widerspiegelt), Werte unter dem Preis begünstigen Short-Positionen.
  • Filter für gleitenden Durchschnitt – vergleicht den ausgewählten gleitenden Durchschnitt (mit optionaler positiver Verschiebung) mit dem Schlusskurs bei der Basisverschiebung. Ein Preis über dem MA begünstigt Long-Positionen; Der Preis unten begünstigt Shorts.

Alle aktivierten Filter müssen sich auf die gleiche Richtung einigen. Wenn ein Filter einen neutralen Zustand zurückgibt (z. B. RSI bleibt zwischen seinen Schwellenwerten), wird keine Position geöffnet.

Einreisebestimmungen

  • Signale werden im primären Handelszeitraum (Candle Type) ausgewertet.
  • Es ist jeweils nur eine Position zulässig; Die Strategie wartet darauf, dass die vorherige Position geschlossen wird, bevor sie eine neue eingibt.
  • Das Bestellvolumen wird von Trade Volume (Lots) übernommen.

Ausgangsregeln

  • Fester Stop-Loss / Take-Profit – ausgedrückt in Pips und unter Verwendung der Schrittgröße des Instruments in Preis-Offsets umgewandelt. Setzen Sie einen Parameter auf 0, um die entsprechende Ebene zu deaktivieren.
  • Trailing Stop – wenn aktiviert, wird der Stop nachgezogen, sobald der nicht realisierte Gewinn die Trailing-Distanz überschreitet und der aktuelle Stop dem Preis um mehr als diese Distanz hinterherhinkt. Long-Positionen bewegen den Stop nach oben, Short-Positionen nach unten.

Verhalten in mehreren Zeitrahmen

Jeder Indikator kann seinen eigenen Zeitrahmen abonnieren. Ein Zeitrahmenwert von 0 verwendet den primären Handelszeitrahmen wieder, während positive Werte minutenbasierte TimeFrameCandle-Abonnements darstellen. Indikatorwerte werden nur für abgeschlossene Kerzen ausgewertet und respektieren den Parameter Shift, sodass die Strategie das „Rückblick“-Verhalten des ursprünglichen MetaTrader-Experten widerspiegeln kann.

Parameter

  • Kerzentyp – Handelszeitrahmen, der zur Steuerung der Ausführungen verwendet wird.
  • Volumen – Market-Order-Volumen (Lots).
  • Take Profit (Pips) – Gewinnziel; 0 deaktiviert.
  • Stop-Loss (Pips) – Schutzstopp; 0 deaktiviert.
  • Trailing verwenden / Trailing Stop (Pips) – Trailing Stop aktivieren und dimensionieren.
  • Verschiebung – Anzahl der abgeschlossenen Kerzen nach hinten beim Lesen der Indikatorwerte und des Preises.
  • RSI Parameter – Umschalten, Zeitraum, obere/untere Schwellenwerte und Zeitrahmen.
  • Stochastic-Parameter – Umschalten, %K/%D/Verlangsamungslängen, optionale hohe/niedrige Bestätigungsstufen und Zeitrahmen.
  • ADX-Parameter – Umschalten, Zeitraum, DI-Zeitrahmen, optionaler Hauptlinienschwellenwert und Hauptzeitrahmen.
  • Parabolic SAR Parameter – Umschalten, Beschleunigungsschritt, maximale Beschleunigung und Zeitrahmen.
  • Parameter für den gleitenden Durchschnitt – Umschalten, Zeitraum, zusätzliche auf den MA-Puffer angewendete Verschiebung, Mittelungsmethode (SMA/EMA/SMMA/LWMA), angewendeter Preis und Zeitrahmen.

Notizen

  • Indikatorwarteschlangen berücksichtigen den konfigurierten Shift und stellen sicher, dass Signale auf die gleiche Weise wie der MQL-Experte auf historischen Werten basieren.
  • Die Trailing-Logik wird erst aktiviert, wenn der Trade bereits mehr als die Trailing-Distanz im Gewinn ist und der Stop mehr als diese Distanz vom Preis entfernt ist, was dem ursprünglichen Verhalten von EA entspricht.
  • Für dieses Strategiepaket wird keine Python-Version bereitgestellt.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// NtoQf: RSI + EMA multi-filter strategy with ATR trailing.
/// Combines RSI overbought/oversold with EMA trend confirmation.
/// </summary>
public class NtoQfStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _rsiUpper;
	private readonly StrategyParam<decimal> _rsiLower;

	private decimal _prevRsi;

	public NtoQfStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI period.", "Indicators");

		_emaLength = Param(nameof(EmaLength), 50)
			.SetDisplay("EMA Length", "EMA trend filter.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period for trailing.", "Indicators");

		_rsiUpper = Param(nameof(RsiUpper), 70m)
			.SetDisplay("RSI Upper", "Overbought level.", "Signals");

		_rsiLower = Param(nameof(RsiLower), 30m)
			.SetDisplay("RSI Lower", "Oversold level.", "Signals");
	}

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

	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	public decimal RsiUpper
	{
		get => _rsiUpper.Value;
		set => _rsiUpper.Value = value;
	}

	public decimal RsiLower
	{
		get => _rsiLower.Value;
		set => _rsiLower.Value = value;
	}

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

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

		_prevRsi = 0;
	}

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

		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(rsi, ema, atr, ProcessCandle)
			.Start();

		StartProtection(
			takeProfit: new Unit(3, UnitTypes.Percent),
			stopLoss: new Unit(2, UnitTypes.Percent));

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

	private void ProcessCandle(ICandleMessage candle, decimal rsiVal, decimal emaVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_prevRsi == 0 || atrVal <= 0)
		{
			_prevRsi = rsiVal;
			return;
		}

		var close = candle.ClosePrice;

		// Entry: RSI exits extreme zone, confirmed by EMA trend
		if (Position == 0)
		{
			if (_prevRsi < RsiLower && rsiVal >= RsiLower && close > emaVal)
			{
				BuyMarket();
			}
			else if (_prevRsi > RsiUpper && rsiVal <= RsiUpper && close < emaVal)
			{
				SellMarket();
			}
		}

		_prevRsi = rsiVal;
	}
}