使用手册
功能在哪 · 是什么 · 怎么调出来一、第 1 章 NinjaScript 是什么
NinjaScript 是 NinjaTrader 8 的内置开发语言,本质是 C# 的一个子集,加了大量交易专用语法糖。
它能写三类东西:
- 策略(Strategy):自动交易,本教程主角
- 指标(Indicator):画在图表上的计算线
- 附加组件(AddOn):扩展平台功能
和 MQL4 比:
- 真 C#,可以写复杂逻辑、用 .NET 类库
- 期货市场基因(CME、CBOT 等),tick 级回测支持好
- 回测引擎和实盘共用同一份代码,不用改
白话注解:NinjaScript 就是"期货版的 C#"。你会 C#,上手很快;不会也没关系,它比 MQL4 稍难,但功能更强、坑更少。
二、第 10 章 SetTrailStop 移动止损与三 Set 方法互斥
// 两个重载
SetTrailStop(CalculationMode mode, double value)
SetTrailStop(string fromEntrySignal, CalculationMode mode, double value, bool isSimulatedStop)
protected override void OnStateChange()
{
if (State == State.Configure)
{
SetTrailStop(CalculationMode.Ticks, 12); // 距入场价12跳的移动止损
}
}
1、互斥规则(官方原文)
- SetTrailStop 不能与 SetStopLoss / SetParabolicStop 用于同一持仓(同一 fromEntrySignal)。若混用,SetStopLoss 永远优先,移动止损失效
- 三者可以在同一策略共存,前提是绑定不同的入场信号名
- CalculationMode.Price 和 Currency 对 SetTrailStop 无效:用了会被记日志、止损单直接被忽略。要这两种模式请用 SetStopLoss
- 移动止损单以 stop-market(止损市价)形式提交,内部信号名
"Trail stop" - 与止盈单自动 OCO
白话注解:SetTrailStop = 吊在价格后面的"跟屁虫止损",价格往有利方向走它就跟着挪,往不利方向走它就不动。一个仓位只能有一个"保镖",SetStopLoss 和 SetTrailStop 抢位时,StopLoss 赢。
三、第 23 章 导入导出与安装别人代码的防骗提醒
1、导出/导入
- 导出:NinjaScript Editor 里右键文件 → Export,生成 .zip 导入包
- 导入:Control Center → Tools → Import → NinjaScript Add-On,选 zip 文件
- 导入会新增/覆盖 Custom 目录代码,导入前备份自己的文件
2、防骗提醒(本站固定立场)
- 网上下载的 .zip 策略包,导入前先读源码。重点搜:外部网络请求、写文件、账户资金操作——真 C# 什么都干得出来
- "稳赚 EA""月化 20% 的 NinjaScript 机器人"——业绩不代表未来收益,卖策略本身就是一门"生意",卖家的利润来自你的钱包不是来自策略
- 任何让你远程控制你电脑"帮忙装 EA"的人,都值得警惕
- 遇到可疑代码直接删,别编译
操作教程
一步一步 · 跟着做完一件事四、第 2 章 开发环境与文件位置
- 打开 NinjaTrader 8 → 菜单 New → NinjaScript Editor
- 左侧 Solution Explorer:项目树(Strategies / Indicators 文件夹)
- 右键 Strategies 文件夹 → New Strategy,可用向导生成骨架
- 顶部工具栏 F5 编译(代码里有错会在下方 Error 面板列出)
文件位置:
Documents\NinjaTrader 8\bin\Custom\Strategies\ ← 策略 .cs 文件
Documents\NinjaTrader 8\bin\Custom\Indicators\ ← 指标 .cs 文件
注意:Custom 目录下的文件升级时可能被覆盖调整,重要代码自己备份。编译失败时先看 Error 面板第一行,后面的错误往往是第一个引起的连锁反应。
五、第 4 章 参数声明:[NinjaScriptProperty]
NT8 的可优化参数用 C# 属性 + 特性标签声明,这是官方唯一推荐方式:
#region Properties
[Range(1, int.MaxValue), NinjaScriptProperty] // Range 限制取值范围
[Display(Name = "Fast Period", Description = "快线周期", Order = 1, GroupName = "Parameters")]
public int FastPeriod { get; set; }
[Range(2, int.MaxValue), NinjaScriptProperty]
[Display(Name = "Slow Period", Description = "慢线周期", Order = 2, GroupName = "Parameters")]
public int SlowPeriod { get; set; }
#endregion
声明之后:
- 策略配置界面自动出现这两个输入框
- 带
[NinjaScriptProperty]的参数才能被优化器扫描;不带也能当输入用,只是不能优化 - 代码里直接用
FastPeriod、SlowPeriod
警告:AddParameter / GetParameter 在 NT8 不可用。旧版教程写的AddParameter(typeof(int), "Fast period", 10)+(int)GetParameter(0)是 NT7 时代的机制,NT8 的官方开发文档里参数一律用[NinjaScriptProperty]。官方论坛也已确认 NT7 的Parameters集合在 NT8 中不再存在(仅优化器内部有一个未公开文档的OptimizationParameters,别依赖它)。
白话注解:参数 = 策略的"旋钮"。加了 [NinjaScriptProperty],回测优化时才能让机器自动试"快线 5-20、慢线 30-60"各种组合。
六、第 6 章 OnBarUpdate 与 CurrentBar / BarsInProgress
protected override void OnBarUpdate()
{
if (CurrentBar < 20) return; // 数据不足,先跳过
if (BarsInProgress != 0) return; // 只处理主数据系列的K线
// 你的交易逻辑
}
CurrentBar:当前是第几根 K 线(从 0 数)。访问High[1]、Low[2]之前,必须确认 CurrentBar 够大,否则越界报错BarsInProgress:本次回调属于哪个数据系列(0 = 主系列,1 = 第一个 AddDataSeries 加的,以此类推)CurrentBars[0]、CurrentBars[1]:多系列时分别取各系列的当前 K 线序号
白话注解:OnBarUpdate 是策略的心脏,每根 K 线跳一次。CurrentBar 是"现在是第几分钟",BarsInProgress 是"这声心跳来自哪颗心脏"。
七、第 7 章 入场下单:EnterLong / EnterShort 全重载
官方 EnterLong 共 5 个重载(EnterShort 完全对称):
EnterLong() // 数量取策略对话框设置
EnterLong(string signalName)
EnterLong(int quantity)
EnterLong(int quantity, string signalName)
EnterLong(int barsInProgressIndex, int quantity, string signalName) // 高级:指定提交到哪个系列
要点:
signalName:自定义信号名,最长 50 字符。它是后面平仓、追踪、统计的"身份证",务必规范命名(如"EMACross")quantity:不传时取策略配置对话框里的数量;传 0 会被自动改成 1(股票是 100),别指望 0 表示"不交易"- 开多后持空仓时再 EnterLong,NT8 默认按反转处理(平空+开多),不是拒绝
// 正确示例
EnterLong(1, "EMACross");
EnterLong(quantity: 1, signalName: "EMACross"); // C#命名参数写法,参数名是signalName不是name
白话注解:EnterLong = "我要买"。给它起个名字(signalName),后面平仓时喊这个名字,它就知道平哪笔单。
八、第 8 章 平仓下单:Exit 系列与 fromEntrySignal 陷阱
官方 ExitLong 共 6 个重载(ExitShort 对称):
ExitLong()
ExitLong(int quantity)
ExitLong(string fromEntrySignal) // ←注意!这个string是入场信号名
ExitLong(string signalName, string fromEntrySignal)
ExitLong(int quantity, string signalName, string fromEntrySignal)
ExitLong(int barsInProgressIndex, int quantity, string signalName, string fromEntrySignal)
1、最大的坑:单个 string 参数是 fromEntrySignal
EnterLong(1, "Long"); // 入场信号名叫 "Long"
ExitLong("ExitLong"); // 不是平仓信号名!"ExitLong"被当成fromEntrySignal去找入场叫"ExitLong"的仓
ExitLong("Long"); // 平掉入场信号名为"Long"的持仓
ExitLong("", "Long"); // 完整写法:平仓信号名"",对应入场"Long"
ExitLong(); // 无多仓时忽略;有则全平(不指定fromEntrySignal=平掉全部)
2、平仓家族全表
| 方法 | 作用 |
|---|---|
ExitLong() / ExitShort() | 市价平多 / 平空 |
ExitLongLimit(double limitPrice) | 限价平多 |
ExitLongStopMarket(double stopPrice) | 止损市价平多 |
ExitShortLimit(double limitPrice) | 限价平空 |
ExitShortStopMarket(double stopPrice) | 止损市价平空 |
规则:Exit 方法只会平掉对应方向的仓位——没有多仓时调 ExitLong 会被忽略;不传 quantity 时全平;fromEntrySignal 传空字符串 = 关联所有入场。
白话注解:fromEntrySignal 就是"平谁"。你开了三笔不同信号的单,Exit 喊错名字就平错单。要么名字对准,要么留空全平。
九、第 9 章 SetStopLoss / SetProfitTarget 止损止盈
这两个方法让平台自动给每笔入场挂止损/止盈单,是新手最该用的方案。
1、真实重载(官方文档原样)
// SetStopLoss 两个重载
SetStopLoss(CalculationMode mode, double value)
SetStopLoss(string fromEntrySignal, CalculationMode mode, double value, bool isSimulatedStop)
// SetProfitTarget 四个重载
SetProfitTarget(CalculationMode mode, double value)
SetProfitTarget(CalculationMode mode, double value, bool isMIT)
SetProfitTarget(string fromEntrySignal, CalculationMode mode, double value)
SetProfitTarget(string fromEntrySignal, CalculationMode mode, double value, bool isMIT)
警告:旧版教程的 SetStopLoss(parameterName: "StopLossTicks", ticks: 50) 是虚构签名,不存在名为 ticks 的参数。写法必须按上面的官方重载来。
2、CalculationMode 五种模式(全表)
| 模式 | value 含义 |
|---|---|
CalculationMode.Ticks | 距平均入场价的 tick 数 |
CalculationMode.Pips | 距平均入场价的外汇点(pips)数 |
CalculationMode.Price | 绝对价格(value 就是价位本身) |
CalculationMode.Percent | 百分比偏移。注意 1 = 100%,0.1 = 10%,0.01 = 1% |
CalculationMode.Currency | 盈亏金额(美元)。用此模式时 StopTargetHandling 自动变为 ByStrategyPosition |
3、标准用法
protected override void OnStateChange()
{
if (State == State.Configure) // 注意:不能写在 SetDefaults 阶段!
{
SetStopLoss("EMACross", CalculationMode.Ticks, 50, false); // 距入场价50跳的止损
SetProfitTarget("EMACross", CalculationMode.Ticks, 100); // 距入场价100跳的止盈
}
}
官方规则(逐条来自帮助文档):
- Set 类方法禁止在 State.SetDefaults 阶段调用(官方原文警告),写在 Configure 或之后
- 止损单与止盈单之间自动形成 OCO 关系:一边成交,另一边自动撤销
- 止盈单内部信号名固定为
"Profit target"、止损为"Stop loss",做信号统计时会用到 - 持仓被策略的其他退出单平掉时,Set 生成的挂单自动取消
- Set 方法应在提交对应入场之前调用好,保证初始价位已设定
白话注解:Set 方法像"自动保镖"——你一入场,平台自动帮你挂好止损止盈两张单,一方成交另一方自动撤。你只需要在入场前把规则说清楚。
十、第 11 章 持仓判断与仓位管理属性
protected override void OnBarUpdate()
{
// 当前持仓方向
if (Position.MarketPosition == MarketPosition.Flat) { /* 空仓 */ }
if (Position.MarketPosition == MarketPosition.Long) { /* 持多 */ }
if (Position.MarketPosition == MarketPosition.Short) { /* 持空 */ }
int qty = Position.Quantity; // 当前仓位数量
double avg = Position.AveragePrice; // 平均入场价
double pnl = SystemPerformance.AllTrades.TradesPerformance.Currency.CumProfit; // 累计盈亏
}
1、仓位行为控制属性(在 SetDefaults 设置)
| 属性 | 含义 |
|---|---|
EntriesPerDirection = 1 | 每个方向最多同时几笔入场 |
EntryHandling.AllEntries | 所有入场算作一个计数单位(另一值 EntryUniqueEntries 按信号名独立计数) |
BarsRequiredToTrade = 20 | 前 20 根 K 线只加载不交易 |
IsExitOnSessionCloseStrategy = true | 交易日结束前自动平掉所有仓位 |
StartBehavior = StartBehavior.WaitUntilFlat | 启动时等空仓再开始(另有 ImmediatelySubmit、AdoptAccountPosition、WaitUntilFlatSynchronizeAccount) |
白话注解:判断"我现在有没有仓位、有几手、成本多少",全靠 Position 对象。策略收市自动清仓这个开关(IsExitOnSessionCloseStrategy),做日内强烈建议开。
十一、第 14 章 多品种多周期:AddDataSeries 全重载
官方共 9 个重载:
// 主品种
AddDataSeries(BarsPeriod barsPeriod)
AddDataSeries(BarsPeriodType periodType, int period)
// 其他品种
AddDataSeries(string instrumentName, BarsPeriodType periodType, int period)
AddDataSeries(string instrumentName, BarsPeriodType periodType, int period, MarketDataType marketDataType)
AddDataSeries(string instrumentName, BarsPeriod barsPeriod)
AddDataSeries(string instrumentName, BarsPeriod barsPeriod, string tradingHoursName)
AddDataSeries(string instrumentName, BarsPeriod barsPeriod, string tradingHoursName, bool? isResetOnNewTradingDay)
AddDataSeries(string instrumentName, BarsPeriod barsPeriod, int barsToLoad, string tradingHoursName, bool? isResetOnNewTradingDay)
AddDataSeries(string instrumentName) // 仅 R15+ 版本
官方警告(逐条):
- 只能在 State.Configure 阶段调用,写在别处不保证生效
- 参数必须硬编码:不能依赖运行期变量(如用户参数、Instrument)。动态传参会报"Unable to load bars series"
- 同一品种加多个序列时,barsToLoad 只对第一个序列生效
- 多序列共用交易时段模板时,实盘只有当所有序列的时段内都有 tick,OnBarUpdate 才处理,未处理的 tick 排队——别指望任意时刻都有回调
- instrumentName 传 null = 用主品种
protected override void OnStateChange()
{
if (State == State.Configure)
{
// 主品种加一个5分钟系列 → BarsInProgress=1
AddDataSeries(BarsPeriodType.Minute, 5);
// 加另一个品种100tick系列 → BarsInProgress=2
AddDataSeries("ES 09-26", BarsPeriodType.Tick, 100);
}
}
protected override void OnBarUpdate()
{
if (BarsInProgress == 0) { /* 主系列逻辑 */ }
else if (BarsInProgress == 1) { /* 5分钟系列逻辑 */ }
else if (BarsInProgress == 2) { /* ES tick 系列逻辑 */ }
}
白话注解:AddDataSeries = "同时订阅几路行情"。BarsInProgress 告诉你此刻哪路行情在敲门。参数写死不是 bug 是规定—— Configure 阶段品种信息还没就绪,动态传参必然出错。
十二、第 15 章 Tick 级事件:Calculate 与 OnMarketData
1、Calculate 三档
| 取值 | 触发频率 | 适用 |
|---|---|---|
Calculate.OnBarClose | 每根 K 线收盘一次(默认) | 绝大多数趋势/均线策略 |
Calculate.OnPriceChange | 价格变动时 | 盘中盯价但不需要每 tick |
Calculate.OnEachTick | 每个 tick | tick 级逻辑(CPU 代价大) |
2、OnMarketData:逐笔 Level 1 事件
protected override void OnMarketData(MarketDataEventArgs marketDataUpdate)
{
if (marketDataUpdate.MarketDataType == MarketDataType.Last)
Print(string.Format("Last = {0} {1}", marketDataUpdate.Price, marketDataUpdate.Volume));
else if (marketDataUpdate.MarketDataType == MarketDataType.Ask)
Print("Ask = " + marketDataUpdate.Price);
else if (marketDataUpdate.MarketDataType == MarketDataType.Bid)
Print("Bid = " + marketDataUpdate.Price);
}
官方规则:
- 默认只在实时运行,回测不触发;要历史回放需启用 TickReplay
- TickReplay 只回放 Last 事件(只存成交时刻的最优买卖价),历史 bid/ask 事件不回放——需要历史买卖价要走 OnBarUpdate 的 bid/ask 序列
- 不用就删掉:留一个空的 OnMarketData 重写也会白白挂一路数据流吃 CPU(官方原文警告)
- 要在事件间做比较,得自己用局部变量记录上一个值
白话注解:OnBarUpdate 是"每根 K 线报一次到",OnMarketData 是"每笔成交/挂单变动都报到"。报得越勤,CPU 越累,没用到就删干净。
十三、第 16 章 时间条件:ToTime / ToDay
ToTime() 把时间转成 Hmmss 格式整数:7:30 AM → 73000;下午 2:15:12 → 141512。
// 只在 7:45 到 13:45 之间交易(官方示例)
if (ToTime(Time[0]) >= 74500 && ToTime(Time[0]) <= 134500)
{
// 策略逻辑
}
// 预定义时间阈值,更清晰
int startTime = ToTime(9, 30, 0); // 93000
if (ToTime(Time[0]) >= startTime) { /* 9:30后交易 */ }
// ToDay():日期转整数,格式 yyyymmdd,可用来限定日期区间
if (ToDay(Time[0]) == 20250403) { /* 只在指定日交易 */ }
两个重载:ToTime(DateTime time) 和 ToTime(int hour, int minute, int second)。熟悉 C# 的话也可以直接用 .NET 的 DateTime 属性比较。
白话注解:时间过滤是策略的"上班打卡"。注意时区——NT8 的 Time 用的是你图表/时段模板设定的时区,做美盘策略时确认清楚再写数字。
十四、第 17 章 动态调整止损与空仓重置陷阱
Set 方法的值可以在 OnBarUpdate() 里重新调用实现动态调整(比如按 ATR 收紧止损):
protected override void OnBarUpdate()
{
if (Position.MarketPosition == MarketPosition.Long)
{
// 按ATR动态收紧止损(示例逻辑,仅演示写法)
double atrValue = atr[0];
SetStopLoss("EMACross", CalculationMode.Ticks, 2 * atrValue / TickSize, false);
}
else if (Position.MarketPosition == MarketPosition.Flat)
{
// 关键:空仓时重置,否则下次开仓沿用上次的值
SetStopLoss("EMACross", CalculationMode.Ticks, 50, false);
SetProfitTarget("EMACross", CalculationMode.Ticks, 100);
}
}
官方明确提示的三个坑:
- 空仓必须重置:动态修改后不平仓重置,上次设的值会用于下一次持仓的止盈/止损单生成
- 动态改值要小心拒单:把止损改到越过当前市价的位置,改单可能被经纪商拒绝(SetTrailStop 在 OnBarClose 模式下,若下一根开盘跳空过大也会拒单)
- 别和 Exit 方法混用同一仓位:持仓被 Exit 方法平掉时,Set 挂单自动取消;但同一笔仓位一边靠 Set 保镖一边手动 Exit,管理逻辑容易打架。要么全程 Set,要么全程手写 Exit
TickSize 换算:把价格距离换算成 tick 数用 距离 / TickSize,别自己拍脑袋除以 0.25 之类的常数——不同品种 tick 大小不同。
十五、第 18 章 防重复下单与信号管理
1、三道防线
// 防线1:持仓状态判断
if (Position.MarketPosition != MarketPosition.Flat) return;
// 防线2:EntriesPerDirection=1 + EntryHandling.AllEntries(SetDefaults里配)
// 每方向只允许1笔,NT自动拦截多余入场
// 防线3:BarsSinceEntryExecution 判断距上次入场过了几根K线(官方示例用法)
if ((BarsSinceEntryExecution() > 10 || BarsSinceEntryExecution() == -1)
&& CrossAbove(SMA(10), SMA(20), 1))
{
EnterLong(5, "SMACross"); // -1 表示从未入场过
}
2、信号命名规范
- 入场信号名全局唯一(
"EMACross"、"Breakout30m"),平仓时用fromEntrySignal精确对应 - 不要动态拼名字——统计和调试都靠名字对账
- Set 方法生成的退出单有固定内部名:
"Profit target"/"Stop loss"/"Trail stop",写 BarsSinceExitExecution 之类统计时注意区分
白话注解:重复下单 = 同一个信号反复触发,仓位瞬间翻倍。三道防线叠着用,比单一防线稳。信号名就是"工牌",全厂不能有重名的。
十六、第 19 章 订单追踪与调试:TraceOrders / Print / Log
// SetDefaults 里打开订单跟踪
TraceOrders = true;
// OnBarUpdate 里打印状态
protected override void OnBarUpdate()
{
Print(string.Format("{0} Close={1} Pos={2}",
Time[0], Close[0], Position.MarketPosition));
}
TraceOrders = true:NT 把每笔订单的提交/修改/拒绝及原因写进 Output 窗口,排单问题的第一工具Print():输出到 New → NinjaScript Output 窗口Log(string message, LogLevel level):写进 NinjaTrader 日志(Control Center → Log),级别含 Alert/Error 等- 回测时 Print 同样有效,Historical 阶段前缀会标注
白话注解:策略"不按预期下单"时,九成问题 TraceOrders 一开就现形——是单被拒了、被规则拦了,还是根本没触发。先开日志再猜,别瞎改代码。
十七、第 20 章 实盘部署:Sim101 / StartBehavior / VPS
1、上线流程
- 图表上右键 → Strategies(或 Control Center → Strategies)
- 选策略、配参数,Sim101 模拟账户先跑至少数天
- 确认行为符合预期后,账户切实盘 → Start / Enabled
- NinjaTrader 8 必须保持运行,关机 = 策略停转
2、必须认识的三个安全属性(SetDefaults 配置)
| 属性 | 作用 |
|---|---|
RealtimeErrorHandling = RealtimeErrorHandling.StopCancelClose | 实盘下单出错时默认"停策略+撤单+平仓",这是官方默认保护,别随手改成 IgnoreAllErrors |
StartBehavior = StartBehavior.WaitUntilFlat | 启动时若账户有仓位,先等空仓再开始,避免策略"接管"不明仓位 |
IsExitOnSessionCloseStrategy = true | 收盘前自动清仓,防隔夜裸奔 |
3、VPS 部署
- 选 Windows VPS,装 NinjaTrader 8
- 连接行情 feed 与经纪商接口
- 策略加载并 Start,确认 TraceOrders 无异常
- VPS 24 小时不关机,定期远程检查
白话注解:Sim101 是"靶场",实盘前先在那儿打够弹药。实盘出错时平台默认自保(停撤平),这是好事,别图省事关掉。
💬 用户评价 (0)
立即登录