StopWarden

StopWarden is not open yet. Nothing here can be downloaded or bought today; these pages describe how StopWarden is planned to work when it opens. Get one email when it opens.

Safety Scan (free)

The Safety Scan reads NinjaTrader's own log files on this PC (<Documents>\NinjaTrader 8\log\log.*.txt) for the last 30 days and reports what they show. It is read-only: it never places, changes or cancels an order, never writes into the NinjaTrader folder, and sends nothing anywhere. It needs no licence key and no settings file. When there is a settings file, the scan also counts orders named with your watchdog.extra_stop_name_prefixes as stops, as the watchdog does.

It reports what the log shows ("no stop order seen in NinjaTrader's log"), not what happened at the broker. Everything it cannot see is listed under Unknowns in every report.

Command


autopilot scan [--days 30] [--html <path>] [--open] [--json] [--text] [--anonymize]
               [--log-dir <folder>] [--transition-max S]
OptionMeaning
--days NHow far back to look (default 30). NinjaTrader deletes old log files, so the real window can be shorter; the report says so.
--html <path>Write the HTML report there. In the default output mode the report is always written (default %TEMP%\StopWarden-scan.html).
--openOpen the HTML report when done.
--jsonPrint the whole report as one JSON document (schema below) and nothing else.
--textPrint the plain-English report.
--anonymizeReplace account, strategy and connection names (for sharing a scan with support).
--log-dirScan another NinjaTrader log folder. Default: the folder in the settings file, else <Documents>\NinjaTrader 8\log (Documents is looked up the Windows way, so a redirected or synced Documents folder is found).
--transition-max SSeconds a stop order in flight still counts as covering (a built-in default applies when you leave it out).

Default output (for the StopWarden window)

One KEY=VALUE line each, then RESULT=OK:

KeyMeaning
SCAN_NAKEDPositions with no stop order seen in NinjaTrader's log for longer than the wait
SCAN_ORPHANWorking orders still resting for a while after their strategy was disabled, as the Scan counts it
SCAN_DISCONNECTConnection drops (a connection that was Connected and then was not; closing a connection yourself is not counted)
SCAN_RESTARTNinjaTrader restarts while a position was open
SCAN_STRATEGY_DISABLEDA strategy disabled and not re-enabled within 2 minutes
SCAN_NO_STOPPositions open longer than the wait with no working stop at any point
SCAN_STRATEGIESNumber of (account, strategy, instrument) rows in the stop-timing table
HTMLPath of the HTML report
RESULTOK

How gaps are measured

For every account and instrument, the scan rebuilds the position and the order book from the log, line by line.

  • A gap starts when the position is larger than the working stop contracts on the closing side (Sell stops for a long, Buy stops for a short, Stop Market or Stop Limit, same instrument and contract month) and ends when the stops cover it again or the position is flat. Only Working and Accepted stops count as working.
  • Stops in flight (ChangePending, ChangeSubmitted, Submitted, TriggerPending, Initialized) count as covering for at most --transition-max seconds (a built-in default applies when you leave it out). A gap that was covered in flight the whole time and ended within that limit is a normal amend or arrival: it is counted in in_flight_covered, not as a gap. A stop that stays in flight longer, or ends Rejected, is a gap for its whole length and is counted in stop_rejected. (Why: a stop that sits in Submitted for several seconds and is then rejected protected nothing; an unlimited "in flight counts" rule would hide it.)
  • The strategy of a gap is the strategy that sent the order whose fill opened the position, read from NinjaTrader's NinjaScript strategy '<name>/<id>' submitting order lines. Orders without such a line (manual, ATM or order-file orders) are grouped as (no strategy in log).
  • A session is a trading date on which the row had at least one position.

Does the strategy fit Protect's wait?

StopWarden waits before it acts on a position with no working stop. It also acts when a position has had no working stop for that long in total within one minute (a stop that keeps going missing for a moment adds up). For each strategy row the scan says whether its stops fit that wait, in the report and in the JSON. This is the same verdict the Learn report (autopilot learn) gives before Protect may act for a strategy.

A strategy fits when, in normal trading (leaving out gaps with a rejected stop and gaps cut short by a restart or the end of the log), both of these are well inside the wait:

  • its slowest stop: the longest gap before its stop was working (max_healthy_gap_s);
  • its most time without a working stop within one minute (max_uncovered_60s_s), counted the way StopWarden's own check counts it while it runs: a stop that is being changed, or that has just been sent, still counts as cover for a short time (transition_max_s, arriving_max_s), and a stop that is being cancelled does not. This is what catches a strategy that cancels and re-places its stop many times a minute.
ModeWhenFits the wait
BRACKETEDboth figures above are well inside the waityes
SLOWeither figure is not well inside the wait (and p99 of the gaps is within the wait)no: Protect stays off for it; Watch (alerts only)
STOPLESS20% or more of the positions were open well past the wait with no working stop at any pointno: a backup stop would change how the strategy trades; Watch (alerts only)
UNSTABLEp99 of the gaps is over the waitno: Watch (alerts only) until the long gaps are explained

A row is provisional until it has at least 20 round trips over at least 5 sessions (the Learn period).

The JSON also keeps proposed_grace_s (BRACKETED rows only). It is a measurement of how much of the wait the strategy's own stops use. It is not a second wait, and the report does not print it.

JSON schema (stopwarden.scan/2)

autopilot scan --json prints one object. Fields are only ever added within schema stopwarden.scan/2; a removed or renamed field changes the schema string. Schema /2 (2026-10-02): the count of long gaps is now ge_naked_limit (renamed from the /1 field), and the threshold is the wait. Added within /2 on the same day: max_healthy_gap_s, max_uncovered_60s_s, mode SLOW, and the params fit_factor, budget_window_s, arriving_max_s, exit_settle_s.

FieldTypeMeaning
schemastring"stopwarden.scan/2"
generatedstringLocal time the scan ran (ISO 8601)
versionstringStopWarden version
windowobjectSee below
paramsobjectSee below
countsobjectSee below
findingsarraySee below, oldest first
calibrationarrayOne row per (account, strategy, instrument), see below
no_stop_seenarrayPositions open longer than the wait with no working stop at any point, oldest first
unknownsarray of stringWhat this scan cannot see, in plain English
anonymizedbooltrue when --anonymize was used

window: days (int, asked for), from / to (ISO 8601, the asked window), log_files (int, log files in the folder), log_files_read (int), first_event / last_event (ISO 8601 or null, the log lines actually inside the window), sessions (int, NinjaTrader starts inside the window).

params: naked_min_seconds (the wait), orphan_min_seconds, transition_max_s, in_flight_states (array of the five state names), grace_min_s, grace_max_s, grace_margin_s, learn_min_round_trips (20), learn_min_sessions (5), stopless_share (0.2), fit_factor, budget_window_s (60, the one-minute window), arriving_max_s and exit_settle_s (StopWarden's cover rules used for max_uncovered_60s_s).

counts: NAKED, ORPHAN, DISCONNECT, RESTART, STRATEGY_DISABLED (int, findings of each kind), naked_seconds (float, sum over NAKED findings), worst_naked_seconds (float or null), no_stop_seen (int), strategies (int, calibration rows).

findings[]: kind (NAKED | ORPHAN | DISCONNECT | RESTART | STRATEGY_DISABLED), when (ISO 8601, start of the episode), account (string, empty for DISCONNECT), strategy (string, may be empty), instrument (string, full name such as NQ DEC26, may be empty), detail (plain English, in the log's words), seconds (float, length of the episode; 0 for RESTART and STRATEGY_DISABLED).

calibration[]:

FieldTypeMeaning
account, strategystring
instrumentstringInstrument root, such as NQ (contract months are combined)
round_tripsintPositions opened in the window
sessionsintTrading dates with a position
episodesintGaps counted (not covered in flight)
in_flight_coveredintGaps covered by a stop in flight for their whole length, within transition_max_s
p50_s, p90_s, p95_s, p99_s, max_sfloat or nullGap length percentiles (linear interpolation), seconds
ge_naked_limitintGaps of naked_min_seconds or more
stop_rejectedintGaps during which a closing-side stop was rejected
local_stop_episodesintGaps that involved a TriggerPending or Initialized stop (may be held on this PC, not at the broker)
positions_without_stopintPositions open longer than the wait with no working stop at any point
modestringBRACKETED \SLOW \STOPLESS \UNSTABLE
max_healthy_gap_sfloatSlowest stop in normal trading: the longest counted gap without a rejected stop and not cut by a restart or the end of the log (0 when none)
max_uncovered_60s_sfloatMost seconds without a working stop within any one minute of normal trading, counted with StopWarden's own cover rules
proposed_grace_sfloat or nullHow much of the wait the strategy's own stops use (null unless BRACKETED); not printed in the report
gaps_longer_than_graceint or nullGaps in the window longer than proposed_grace_s
learn_completeboolAt least 20 round trips over at least 5 sessions
longestarrayUp to 5 of the longest gaps, longest first: start, seconds, opened_by, closed_by (the log events, without order names), stop_rejected
recommendationstringThe plain-English recommendation shown in the report

no_stop_seen[]: account, strategy, instrument (root), start, end (ISO 8601), seconds (float), max_qty (int, largest position size), note (plain English: no stop order seen, or a stop was sent but rejected / never seen working, and whether the position was still open when NinjaTrader restarted or the log ended).

What the scan cannot see

Every report lists these under Unknowns, plus anything specific to that log (for example when NinjaTrader's log starts later than the window asked for):

  • Orders held at the broker that NinjaTrader never wrote to its log.
  • Broker timing: times are the PC clock as NinjaTrader logged them.
  • Whether a TriggerPending or Initialized stop is held at the broker or by NinjaTrader on this PC. When the scan saw one in a gap, it adds that StopWarden, while it runs, counts such a stop as working only if the connection name in its settings matches the one in NinjaTrader's log and that connection is up.
  • A position being closed by a market or limit exit is not recognised as an exit in progress; those seconds count as gaps.
  • The most time without a working stop within one minute uses the times NinjaTrader logged. StopWarden counts the same seconds while it runs, and looks at the log about once a second.