Открыть на GitHub

Стратегия NNFX Auto Trade

Обзор

NNFX Auto Trade Strategy переносит панель ручной торговли NNFX из MetaTrader 4 в StockSharp. Вместо графического интерфейса используются параметры стратегии: трейдер включает входы, переводит позицию в безубыток и запускает трейлинг одним кликом.

Особенности:

  • Расчёт объёма по ATR с возможностью задать фиксированные значения стопа и тейк-профита.
  • Вход делится на две части: первая закрывается по цели, вторая остаётся в рынке для дальнейшего сопровождения.
  • Команды безубытка и трейлинга выполняются по требованию, повторяя логику исходного советника.
  • В расчёт риска можно включить внешний капитал, как это делалось в MQL-версии.

Логика торговли

  1. ATR – стратегия подписывается на выбранные свечи и считает Average True Range. При активном параметре UsePreviousDailyAtr в первые 12 часов нового дня используется значение ATR предыдущей сессии.
  2. Риск-менеджмент – при ручной команде Buy или Sell рассчитывается денежный риск, после чего определяется объём сделки.
  3. Деление позиции – половина объёма закрывается по целевому уровню, оставшаяся часть остаётся в позиции.
  4. Стопы – начальные уровни хранятся во внутреннем состоянии, команды позволяют перенести стоп в ноль или подтянуть его по формуле NNFX.
  5. ВыходыCloseAll мгновенно закрывает позицию, стопы и цели срабатывают при достижении цены.

Параметры

Параметр Значение Описание
RiskPercent 2.0 Доля капитала (с учётом AdditionalCapital), которую допускается потерять в одной сделке.
AdditionalCapital 0 Дополнительный капитал, участвующий в расчёте риска.
UseAdvancedTargets false Использовать фиксированные пипсовые расстояния вместо коэффициентов ATR.
AdvancedStopPips 0 Расстояние до стопа (в пипсах) при включённом ручном режиме.
AdvancedTakeProfitPips 0 Расстояние до тейк-профита (в пипсах) при включённом ручном режиме.
UsePreviousDailyAtr true В первые 12 часов нового дня использовать ATR предыдущей дневной свечи.
AtrPeriod 14 Период ATR.
AtrStopMultiplier 1.5 Множитель ATR для расчёта стопа.
AtrTakeProfitMultiplier 1.0 Множитель ATR для расчёта тейк-профита.
CandleType 1 Minute Тип свечей для расчётов.
BuyCommand false Ручная команда на открытие длинной позиции. После обработки сбрасывается.
SellCommand false Ручная команда на открытие короткой позиции. После обработки сбрасывается.
BreakevenCommand false Команда перевода стопа в безубыток.
TrailingCommand false Команда разового подтягивания стопа по правилам NNFX.
CloseAllCommand false Команда немедленного закрытия всех позиций.

Рекомендации по использованию

  • Для корректного расчёта объёмов требуются актуальные Step, StepPrice и VolumeStep инструмента.
  • Команды обрабатываются на закрытии свечи, поэтому после переключения параметра необходимо дождаться следующего обновления данных.
  • При ручном режиме стопа и цели заполните оба параметра (AdvancedStopPips и AdvancedTakeProfitPips), иначе стратегия продолжит использовать ATR.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// NNFX Auto Trade strategy: ATR-based trend following with EMA filter.
/// Enters on EMA direction with ATR-based trailing stop management.
/// </summary>
public class NnfxAutoTradeStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrMultiplier;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private decimal _entryPrice;
	private decimal _bestPrice;
	private bool _wasBullish;
	private bool _hasPrevSignal;
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public NnfxAutoTradeStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(120).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_emaPeriod = Param(nameof(EmaPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA trend filter period", "Indicators");
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR period", "Indicators");
		_atrMultiplier = Param(nameof(AtrMultiplier), 2.5m)
			.SetDisplay("ATR Multiplier", "ATR multiplier for stop", "Risk");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 12)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = 0m;
		_bestPrice = 0m;
		_wasBullish = false;
		_hasPrevSignal = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_entryPrice = 0;
		_bestPrice = 0;
		_hasPrevSignal = false;
		_candlesSinceTrade = SignalCooldownCandles;
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, atr, ProcessCandle).Start();
	}

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

		var close = candle.ClosePrice;
		var stopDist = atrValue * AtrMultiplier;
		var isBullish = close > emaValue;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		// Trailing stop check
		if (Position > 0)
		{
			if (close > _bestPrice) _bestPrice = close;
			if (_bestPrice - close > stopDist)
			{
				SellMarket();
				_entryPrice = 0;
				_bestPrice = 0;
				_candlesSinceTrade = 0;
				return;
			}
		}
		else if (Position < 0)
		{
			if (close < _bestPrice) _bestPrice = close;
			if (close - _bestPrice > stopDist)
			{
				BuyMarket();
				_entryPrice = 0;
				_bestPrice = 0;
				_candlesSinceTrade = 0;
				return;
			}
		}

		// Entry signals
		if (_hasPrevSignal && isBullish != _wasBullish && _candlesSinceTrade >= SignalCooldownCandles)
		{
			if (isBullish && Position <= 0)
			{
				BuyMarket();
				_entryPrice = close;
				_bestPrice = close;
				_candlesSinceTrade = 0;
			}
			else if (!isBullish && Position >= 0)
			{
				SellMarket();
				_entryPrice = close;
				_bestPrice = close;
				_candlesSinceTrade = 0;
			}
		}

		_wasBullish = isBullish;
		_hasPrevSignal = true;
	}
}