NNFX Auto Trade 策略
概述
NNFX Auto Trade Strategy 将原始 NNFX MetaTrader 4 面板移植到 StockSharp,通过参数而不是图形界面执行手动命令。交易者可以触发多空入场、立即平仓或单次执行保本与拖尾规则,完全遵循原策略的风险管理流程。
主要特点:
- 基于 ATR 的波动性头寸控制,同时支持自定义固定的止损与止盈距离。
- 入场仓位会拆分为两部分:第一部分在到达目标时落袋,第二部分留在市场中供交易者继续管理。
- 保本与拖尾命令按需执行,不会在每根 K 线上自动触发。
- 可在风险计算中加入外部资本,复现 MQL 版本的设置。
交易流程
- ATR 计算 – 订阅选定的 K 线类型并计算 Average True Range。当
UsePreviousDailyAtr打开时,新交易日的前 12 小时内沿用上一日的 ATR 值。 - 风险头寸 – 当
Buy或Sell命令被触发时,根据止损距离计算单手风险,并将设定的风险百分比转换为交易量。 - 仓位拆分 – 入场数量被平均分成两份,一份在达到目标价时平仓,另一份保持持仓。
- 止损管理 – 初始止损保存在内部字段,每根已完成的 K 线都会检查是否触发,同时可以通过命令移动到保本或按照 NNFX 公式更新拖尾。
- 退出控制 –
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 |
立即关闭所有仓位。 |
使用建议
- 需要证券对象提供有效的
Step、StepPrice与VolumeStep信息,才能正确换算风险和交易量。 - 手动参数在完成的 K 线上处理,因此在切换参数后需等待下一次蜡烛更新。
- 开启手动距离时,请同时填写
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class nnfx_auto_trade_strategy(Strategy):
def __init__(self):
super(nnfx_auto_trade_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(120)))
self._ema_period = self.Param("EmaPeriod", 100)
self._atr_period = self.Param("AtrPeriod", 14)
self._atr_multiplier = self.Param("AtrMultiplier", 2.5)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 12)
self._entry_price = 0.0
self._best_price = 0.0
self._was_bullish = False
self._has_prev_signal = False
self._candles_since_trade = 12
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
@property
def AtrPeriod(self):
return self._atr_period.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atr_period.Value = value
@property
def AtrMultiplier(self):
return self._atr_multiplier.Value
@AtrMultiplier.setter
def AtrMultiplier(self, value):
self._atr_multiplier.Value = value
@property
def SignalCooldownCandles(self):
return self._signal_cooldown_candles.Value
@SignalCooldownCandles.setter
def SignalCooldownCandles(self, value):
self._signal_cooldown_candles.Value = value
def OnReseted(self):
super(nnfx_auto_trade_strategy, self).OnReseted()
self._entry_price = 0.0
self._best_price = 0.0
self._was_bullish = False
self._has_prev_signal = False
self._candles_since_trade = self.SignalCooldownCandles
def OnStarted2(self, time):
super(nnfx_auto_trade_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._best_price = 0.0
self._has_prev_signal = False
self._candles_since_trade = self.SignalCooldownCandles
ema = ExponentialMovingAverage()
ema.Length = self.EmaPeriod
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, atr, self._process_candle).Start()
def _process_candle(self, candle, ema_value, atr_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
stop_dist = float(atr_value) * float(self.AtrMultiplier)
is_bullish = close > float(ema_value)
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
# Trailing stop check
if self.Position > 0:
if close > self._best_price:
self._best_price = close
if self._best_price - close > stop_dist:
self.SellMarket()
self._entry_price = 0.0
self._best_price = 0.0
self._candles_since_trade = 0
return
elif self.Position < 0:
if close < self._best_price:
self._best_price = close
if close - self._best_price > stop_dist:
self.BuyMarket()
self._entry_price = 0.0
self._best_price = 0.0
self._candles_since_trade = 0
return
# Entry signals
if self._has_prev_signal and is_bullish != self._was_bullish and self._candles_since_trade >= self.SignalCooldownCandles:
if is_bullish and self.Position <= 0:
self.BuyMarket()
self._entry_price = close
self._best_price = close
self._candles_since_trade = 0
elif not is_bullish and self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._best_price = close
self._candles_since_trade = 0
self._was_bullish = is_bullish
self._has_prev_signal = True
def CreateClone(self):
return nnfx_auto_trade_strategy()