How it works
Three mechanisms carry the whole product: a configuration you seal for the day, a record you can verify without our code, and a rule that stops trading whenever the guard does not know something. The limits of all three are on this page, not in a footnote.
1. The commitment seal
You set one number: a personal daily loss limit, strictly smaller than your firm's. When you press Arm, the configuration is canonicalised and hashed (SHA-256), and the seal is written to disk with the exact config it covers, the moment it was armed, the trading day it governs, and the point in the ledger it was tied to. The seal expires at the next session reset — 17:00 CT by default.
Until then, every change is rejected — including one that would make the limit stricter.
There is nothing to negotiate at 14:30 because there is nothing you can change. A config edited under the
seal is detected on the next evaluation (CONFIG_TAMPERED) and a hand-edited seal is detected on
the next start (SEAL_MISMATCH); both put the guard into a lockout rather than trusting the new
file.
Moving the clock does not help — while it is running. The seal's expiry is measured on a monotonic counter you cannot set. Push your system clock past 17:00 and you get a blocked account and a logged anomaly, not a released seal.
2. The lockout, in an order that survives a crash
When your day's P&L reaches the limit, the sequence is deliberately ordered so that the worst moment to be interrupted is still safe:
- write
LOCKEDto disk — before the first order reaches your broker; - cancel every working order;
- flatten every position;
- verify — and if the positions are not actually flat, say
LOCKOUT_INCOMPLETEand keep trying, rather than report a success it did not observe.
Kill NinjaTrader mid-flatten and the restart comes back locked, not "armed and fine". That is a test, not
a promise: the soak suite kills the process mid-lockout on a simulated account and checks that the restart
resumes LOCKED.
A single flatten is not a lockout — and nothing is cancelled while locked. This is the sentence to read twice. Entering the lockout sweeps the resting orders once. After that, a new order — from the DOM, a chart, a running strategy — reaches your broker and can fill: the guard does not cancel it. Since 2026-09-03 the code to write it down is deployed — a line naming the order, its instrument and its direction, saying it was seen while locked and that nothing was done to it. No lockout has occurred under that build, so the ledger does not yet hold a single one of those lines: a capability installed, not a behaviour observed. That is a record, not a brake: the order still goes through. Until that date it was not even recorded, and this page said so.
What the guard does instead is keep trying to flatten, and verify rather than assume:
every cycle it asks the platform to flatten, then reads the positions back, and if they are not flat it
writes LOCKOUT_INCOMPLETE and tries again. Here is what that has actually done, in
full — and in the two units that must not be mixed, because we mixed them ourselves and the defect came
out looking worse than it is.
| counted in | what the ledger holds |
|---|---|
| lockout episodes (a limit actually reached) | 2, both on a simulated account: 2026-08-26 and 2026-08-31 |
| …of those, under the code that ships today | 1 — and it verified, on the third attempt |
| flatten cycles that ended incomplete | 169 — but 167 of them are the single 2026-08-26 episode, and 2 are the first two attempts of the one that then succeeded |
| verified flattens | 1 |
So the honest sentence is not "one success in 170 tries". It is: this product has reached a lockout twice, once under the code you would install, and that once it closed the position and proved it. The 2026-08-26 episode failed for a cause we found and removed the next day — the guard was cancelling its own flatten orders — and it is still the only large sample we have of what failure looks like. It did not give up, because it has no limit to give up at: from the third attempt onwards it marks the failure and tells you it needs you, and then it keeps trying — that day, 164 more times. Two episodes is not evidence that it works. It is the number we have, and publishing it as 1-against-169 would have been a defect exaggerated, which is exactly as false as a capability promised. So "the position gets closed" is not something this page can promise you: it is something the guard attempts, reports honestly when it fails, and never stops attempting.
The same is true one step earlier, of the cancel, and here the record says something sharper than we
first published. Entering the lockout asks the platform to cancel every working order, and the ledger holds
168 such requests — 167 of them from the same 2026-08-26 episode and 1 from the 2026-08-31
one, so again: two episodes, not 168 chances — and all 168 report having cancelled
zero orders. Every one of them carries count: 0. In the product's entire recorded life
the sweep has never cancelled an order. Whether that is because nothing was resting or because it did not see
what was there is not determinable from the record, with one exception: in the 2026-08-26 episode
the trader's order arrives 64 ms after the sweep, so there it was simply not yet there.
There is also no separate verification event for the cancel, so the only re-read that ever covered working
orders is folded inside the single verified flatten. The cancel is not claimed here as a thing that
happened; it is a thing that was asked for, 168 times, with nothing to show for it in the record.
The failure is also, today, undiagnosable from the record: the event carries a counter and no reason, so a broker that did not confirm and a position that reopened look identical in the log. That is an open defect, and it is named here rather than left for you to discover.
Which means this does not replace watching your platform, and it is not built to.
If the guard says it needs you — that is the panel turning red and writing
LOCKOUT_INCOMPLETE — go and check the flatten yourself in NinjaTrader.
When the last brake does not verify, a person is what closes the gap, and that is the one instruction
this page can honestly give you today.
Until 2026-08-27 the guard did cancel on sight, and a live test the day before showed what that costs:
it cancelled its own flatten orders and four orders of the trader's,
rejected twelve times between them in the six and a half seconds from 23:44:27 to 23:44:33. The record
names each order's direction — two Sell, one SellShort, one
BuyToCover — and it does not say whether any of them was reducing the
position or opening one, because the event carries no quantity. Cancelling wrongly can trap someone in a
sinking position, which is unbounded and caused by us; not cancelling costs one order's worth of
exposure. The trade was taken
deliberately and it is written down, with its restoration condition, in AMENDMENTS.md, A11.
ORDER_REJECTED_LOCKED is not written any more.
Nothing here can stop an order from reaching the venue, and that is a platform fact rather than a design choice: 2,912 types were scanned inside the running NinjaTrader process and no pre-submit hook exists — there is no event that can veto an order before submission. This page used to quote a 315.9 ms cycle, of which 14.4 ms were ours. That measurement was real, and it measured the cancel-on-sight mechanism that was removed on 2026-08-27, so it is not a claim about the current build and it has been taken down rather than restated.
3. Fail-closed: when it does not know, it stops
Any of these means the guard does not know the truth, so it declares itself blind and stops enforcing your limit — it takes no broker action at all while it does not know, and it says why:
- P&L not computable — for example, no market data for an open position;
- its own P&L and NinjaTrader's disagreeing beyond the tolerance you set;
- a guarded account missing from the platform's account list, disconnected, or its currency changed;
- a clock anomaly;
- the ledger not writable, or its chain failing verification;
- the state or the seal unreadable, or of a schema version it does not recognise.
FAIL_CLOSED is not a lockout: it clears by itself the moment the unknown resolves — but it
clears through a re-computation, never by assumption. It is logged on entry and on exit, with the
reason, so "the guard was blind for 40 minutes" is something you can find out afterwards.
Two of the defects found during development were exactly here, and both are in the record rather than quietly fixed: a clock anomaly that cleared itself because the P&L happened to be computable — which was never what was in doubt — and a truncated state file that threw an exception instead of resolving to "unknown". Malformed input now always resolves to unparseable, which is an unknown, which fails closed.
4. The ledger you can verify without our code
Every arm, every breach, every cancel, every rejected attempt to loosen the limit is appended to a JSONL file, one event per line, each line carrying the hash of the previous one. It is append-only and SHA-256 chained, so an edit anywhere breaks the chain from that point on, and the break has a location.
You do not need our software to check it. Anything that can hash JSON will do:
import json, hashlib
prev = "genesis"
for line in open(r"...\deadman-guardian\ledger.jsonl", encoding="utf-8"):
e = json.loads(line); h = e.pop("hash")
canon = json.dumps(e, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
assert e["prev"] == prev and hashlib.sha256(canon.encode()).hexdigest() == h
prev = h
print("chain OK")
The uninstaller deliberately leaves your ledger behind.
5. What it never does
It never opens a position. The only orders it ever sends are cancels and flattens. It opens no socket:
no telemetry, no cloud, no licence check, no auto-update. It reads your fills and your account's P&L,
locally, and writes to three files on your disk. One of the 26 named guarantees asserts this by reflection —
that the core library references no NinjaTrader assembly and no network stack — and another asserts that no
double appears anywhere on its public surface, because money is decimal or it is a
rounding bug waiting for a bad day.
What this does not protect against
This is section 17 of the specification, which was written before the first line of C#. It is repeated here in full, on the page that sells the thing, for the reason that a guarantee is only worth what its stated exceptions are worth.
Deleting the add-on before the session
A trader who closes NinjaTrader, removes the add-on, and reopens the platform is not protected, and nothing in this design can prevent that. What remains is that the absence is visible: a day with a stop event and no start, or a trading day with no open event, is a hole in a hash-chained record, with a shape. Premeditation wins; it just does not get to look like an accident. That is the whole claim — this tool converts an impulse into a decision that has to be taken cold, in advance, and that leaves a mark.
Manipulating the system clock
Partially defended, and the boundary is exact.
- In session (the add-on has been running since arming): defended. Expiry is measured on a monotonic counter you cannot set, divergence between wall and monotonic time is detected, and the seal is maintained in every ambiguous case. Moving the clock forward blocks trading rather than unlocking it.
- Across a restart: not defended. Monotonic continuity dies with the process. Someone who closes NinjaTrader, sets the clock past the expiry, and reopens it gets a released seal, because the code has no evidence that time did not pass.
What remains, again, is the trace: every backward observation is logged, and correcting the clock afterwards leaves a non-monotonic timestamp sequence inside a hash-chained file that cannot be quietly repaired. Closing this gap properly needs a time source outside the machine, and v1 opens no sockets. It is listed as a v2 question, not as a solved problem.
Slippage and gaps
The guard removes discretion and exposure; it does not bound the number. A market that gaps through the limit between the breach and the fill produces a loss larger than the limit, and no add-on running inside a trading platform can prevent that. Three prop firms say the same thing about their own daily loss limits.
Everything outside its sight
Trading the same firm account from another platform, from a phone app, or from a second NinjaTrader installation. Firm rules other than the daily loss limit. A user with disk access who deletes the state directory between sessions — detected on the next start as a missing state, which fails closed, but not prevented.
Firm minimum-hold rules
Two firms require every individual trade to stay open at least ten seconds. If your limit is reached within ten seconds of a position opening, the flatten is itself the violation — a defect of the combination, not of either rule alone. v1 flattens anyway and says so, and the ledger records each position's age at the flatten so you can see whether a lockout could have tripped the rule. The full reasoning, the quotes and an honest sizing of how often this can bite are on the compatibility page under the ten-second problem.
The part that costs a sale
Where a venue-side self-set daily loss limit exists, it is stronger than this add-on. Tradovate exposes one, and firms that use it document that once set it cannot be overridden even by their own staff. A venue-side limit does not depend on your machine being on, on NinjaTrader running, or on this code being installed. Use it if you have it.
This add-on is for when you do not — or when you want a stricter personal limit on top of it, with a local auditable record of every attempt to loosen it.
NinjaTrader's own risk plumbing is not a substitute either, and not for the reason people assume: its risk templates are per-instrument margins and size caps with no daily-loss concept at all, and the daily-loss-shaped fields are values the venue reports and NinjaTrader mirrors. The members that would let an add-on act on them are public in the assembly and absent from the published API reference. This add-on ignores all of them: a public setter is not an enforcement contract, and a guardian built on an undocumented member stops protecting silently when that member changes.
What was measured, rather than assumed
Every claim in the specification that depended on NinjaTrader behaving a certain way was checked inside the running process:
- No pre-submit hook exists — 2,912 types scanned at runtime, zero events that could veto an order before submission.
- What follows from having no hook — an order sent while locked reaches the venue and can fill. What the guard does after that is attempt a flatten and verify it; how often that has actually verified is stated above, in full.
America/Chicagothrows inside NinjaTrader — the runtime resolves Windows time zone ids only, so an embedded translation table is what makes the session boundary work at all.- Sleep does not stop the monotonic clock — two real machine suspends moved wall against monotonic time by 41 and 53 ms, where a stopped counter would have moved 5,000. A suspend raises no false alarm.
Requirements, and where it is today
NinjaTrader 8 on Windows, running on your own machine. Installation is a script, one F5 in
the NinjaScript Editor, and a restart. Uninstalling is one command and restores NinjaTrader's project file
from a backup the installer made before touching it; your ledger is deliberately left behind.
25 of 26 named guarantees implemented, all passing, 0 skipped — that is the
conformance statement, rather than "it works". The one that is not is G8,
"orders after lockout are cancelled and logged", unimplemented since 2026-08-27 for the reason
given further up this page. Its text is deliberately not rewritten to match the code, because the gap is
real and rewriting it would delete the gap from view. That number is computed from the specification, not
typed: it said "26 of 26" for the six days from 2026-08-27 to 2026-09-02, after G8 stopped
being implemented, because nothing obliged a hand-written number to go down. A test
count used to sit here and was removed rather than updated:
the number climbs on its own, says nothing about coverage, and is wrong again the following week. The
soak run quoted below is from 2026-08-21 and predates three findings
that followed it, the last of which showed the guard stopped enforcing after its first verified
flatten. It has not been re-run, and today it cannot be: one of its assertions describes behaviour
that was removed on 2026-08-27. The last soak run (2026-08-21, 12:28 UTC) drove the add-on
through six attack scenarios on a simulated account and passed 6 of 6. It has not been run for a
full session on a funded account.