Открыть на GitHub

NTOqF Мультифильтр

Обзор

NTOqF Мультифильтр — это перенос советника MetaTrader 4 «NTOqF» (версии V1–V3) на высокоуровневый API StockSharp. Оригинальный эксперт использует набор независимых фильтров, которые можно включать и выключать по отдельности. В C# версии сохранена гибкость настройки: каждому индикатору можно назначить собственный таймфрейм, а управление позицией выполняется через фиксированные стопы/тейки и настраиваемый трейлинг-стоп в пунктах.

Логика стратегии

Индикаторные фильтры

  • RSI — формирует сигнал на покупку, когда RSI (с учётом параметра Shift) опускается ниже RSI Lower, и сигнал на продажу, когда RSI поднимается выше RSI Upper. Значения между уровнями блокируют входы.
  • Стохастик — сравнивает линии %K и %D. При активной опции Use Stochastic High/Low %K дополнительно должна быть выше Stoch High для лонга или ниже Stoch Low для шорта, иначе используется только взаимное положение %K и %D.
  • ADX — направление определяется сравнением +DI и –DI. Если включён фильтр Use ADX Main, то значение основной линии ADX должно превышать порог ADX Main.
  • Parabolic SAR — анализирует положение SAR относительно закрытия выбранной свечи. Значение выше цены трактуется как бычий сигнал, ниже — как медвежий (полностью соответствует оригинальному коду MQL).
  • Скользящая средняя — сравнивает выбранную MA (с дополнительным сдвигом MaShift, если он больше нуля) с ценой закрытия на базовом смещении. Цена выше MA — сигнал в лонг, ниже — в шорт.

Для входа требуется совпадение направлений всех включённых фильтров. Если какой-либо фильтр вернул нейтральное состояние, сделка не открывается.

Правила входа

  • Сигналы оцениваются на основном таймфрейме Candle Type.
  • Разрешена только одна позиция одновременно; новая сделка открывается после закрытия предыдущей.
  • Объём заявки задаётся параметром Trade Volume (в лотах).

Правила выхода

  • Фиксированные стоп и тейк — указываются в пунктах и автоматически преобразуются в ценовой интервал. Значение 0 отключает соответствующий уровень.
  • Трейлинг-стоп — при включении стоп подтягивается, когда прибыль превышает расстояние трейлинга и текущий стоп находится дальше этого расстояния от цены. Для покупок стоп поднимается, для продаж опускается.

Работа с несколькими таймфреймами

Для каждого индикатора можно указать собственный минутный таймфрейм; значение 0 означает использование торгового таймфрейма. Все индикаторы обновляются только по завершённым свечам и учитывают параметр Shift, поэтому логика полностью совпадает с поведением оригинального советника.

Параметры

  • Candle Type — таймфрейм, на котором принимаются торговые решения.
  • Volume — объём рыночной заявки в лотах.
  • Take Profit (pips) — расстояние до тейк-профита; 0 отключает уровень.
  • Stop Loss (pips) — расстояние до стоп-лосса; 0 отключает уровень.
  • Use Trailing / Trailing Stop (pips) — активация и значение трейлинг-стопа.
  • Shift — количество завершённых свечей, отстоящих от текущей, при чтении цен и индикаторов.
  • Блок RSI — флаг использования, период, уровни перекупленности/перепроданности, таймфрейм.
  • Блок стохастика — флаг использования, длины %K/%D/Slowing, уровни high/low (при необходимости), таймфрейм.
  • Блок ADX — флаг использования, период, таймфрейм DI, порог основной линии и её таймфрейм.
  • Parabolic SAR — флаг, шаг ускорения, максимальное ускорение, таймфрейм.
  • Скользящая средняя — флаг, период, дополнительный сдвиг, тип (SMA/EMA/SMMA/LWMA), источник цены, таймфрейм.

Примечания

  • Очереди значений строго соблюдают параметр Shift, поэтому расчёты выполняются по тем же историческим значениям, что и в MQL-версии.
  • Трейлинг-стоп срабатывает только после того, как прибыль превысила заданное расстояние и текущий стоп находится дальше этого расстояния от цены.
  • В пакет входит только реализация на C#; Python-версии нет.
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;
	}
}