在 GitHub 上查看

NNFX Auto Trade 策略

概述

NNFX Auto Trade Strategy 将原始 NNFX MetaTrader 4 面板移植到 StockSharp,通过参数而不是图形界面执行手动命令。交易者可以触发多空入场、立即平仓或单次执行保本与拖尾规则,完全遵循原策略的风险管理流程。

主要特点:

  • 基于 ATR 的波动性头寸控制,同时支持自定义固定的止损与止盈距离。
  • 入场仓位会拆分为两部分:第一部分在到达目标时落袋,第二部分留在市场中供交易者继续管理。
  • 保本与拖尾命令按需执行,不会在每根 K 线上自动触发。
  • 可在风险计算中加入外部资本,复现 MQL 版本的设置。

交易流程

  1. ATR 计算 – 订阅选定的 K 线类型并计算 Average True Range。当 UsePreviousDailyAtr 打开时,新交易日的前 12 小时内沿用上一日的 ATR 值。
  2. 风险头寸 – 当 BuySell 命令被触发时,根据止损距离计算单手风险,并将设定的风险百分比转换为交易量。
  3. 仓位拆分 – 入场数量被平均分成两份,一份在达到目标价时平仓,另一份保持持仓。
  4. 止损管理 – 初始止损保存在内部字段,每根已完成的 K 线都会检查是否触发,同时可以通过命令移动到保本或按照 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 ATR 计算所用的 K 线类型。
BuyCommand false 触发做多入场的手动开关,执行后自动复位。
SellCommand false 触发做空入场的手动开关,执行后自动复位。
BreakevenCommand false 将止损移动到入场价。
TrailingCommand false 按 NNFX 规则执行一次拖尾。
CloseAllCommand false 立即关闭所有仓位。

使用建议

  • 需要证券对象提供有效的 StepStepPriceVolumeStep 信息,才能正确换算风险和交易量。
  • 手动参数在完成的 K 线上处理,因此在切换参数后需等待下一次蜡烛更新。
  • 开启手动距离时,请同时填写 AdvancedStopPipsAdvancedTakeProfitPips,否则策略仍会使用 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;
	}
}