Sentinel Suite · NinjaTrader 8 · living document

Field Manual

One document that takes you from "I have no idea what any of this is" to "I can run it, extend it, and trust it." The suite turns a pile of arguing indicators into one nervous system — sensors that watch, a Council that decides, hands that act, a gate that guards, and a ledger that remembers. Everything below is pulled from the live source, not remembered.

If you just want to run it

Read 0 → III, then the lab (VIII) and the worked example (IX), then operate the safety layer (IV). Skip the code parts.

If you'll write code for it

Read 0 → II for the model, then the seam reference (VII), the recipes (VII), the safety policy (IV), and trace it end-to-end in IX.

If you want it to learn

Read the lab (VIII) for how outcomes are measured, then X — what changes, what doesn't, how the offline fitting works outside NinjaTrader, and how you name your setups so the fleet stays legible.

If you're inheriting this cold

Read 0, then IV (safety) before touching anything live. The one rule: a model proposes, the Gate disposes.

SENSORS watch CORE carries COUNCIL decides BRIDGE/DECK act GATE guards LEDGER remembers LENS grades LAB learns
cyan — live · signal flow green — long · money up red — short · veto · block amber — caution · modulator
PART 0

Quickstart

see it breathe in five minutes

Five minutes, no risk

Watch many signals become one verdict

The fastest way to understand Sentinel is to watch it run. This is read-only — no orders, no risk. You'll put four sensors and the brain on a chart and see them fuse live.

  1. Open a GC chart.

    New → Chart → GC (front month), any bar type. GC (gold) is the reference instrument throughout this manual.

  2. Add four sensors.

    Right-click → Indicators → open the Sentinel folder and add Eye, SentinelTrend, ADXPro, and WoodiesCCIPro. All publish by default — you've put four voters on the bus without configuring anything.

  3. Add the brain.

    Add Council from the same folder. It immediately reads whatever is publishing for GC and draws a glass card, top-right.

  4. Read the card.

    A LONG / SHORT / FLAT pill (the fused bias), a big conviction % and size ×, a row of voter chips (green agrees, red disagrees, grey neutral), and a red VETO line when something blocks it.

  5. Watch it breathe.

    As price moves, sensors republish, chips flip colour, conviction climbs when they line up and falls toward the floor when they fight. Many signals → one honest verdict. That's the whole idea in one card.

What you just provedThe sensors don't know the Council exists — they just publish. The Council doesn't know your sensors — it just reads the bus and fuses. Add or remove a sensor and the voter count changes automatically. Nothing is wired by hand. That decoupling is the entire architecture, and you're now looking at it.

Live look

Your first chart, in pictures

Placeholders for the screenshots that make the quickstart concrete. Capture on SIM or replay, then drop each PNG at the path shown. (For the published artifact, embed as a data URI — its strict CSP blocks external images; the in-repo .html can use the relative img/… path directly.)

S1The Council card, live Bias pill, conviction %, size×, coloured voter chips, tally footer. → img/s1-council-card.png
S3The "Sentinel" picker folder The indicator picker expanded, suite tools clustered in their folder. → img/s3-picker-folder.png
PART I

The vision

from zero · why it exists

The problem

A pile of islands becomes one nervous system

A serious NinjaTrader chart ends up with a dozen indicators screaming at once — a trend line, a CCI, an ADX, a volatility band, a session clock — plus a strategy trying to trade, plus a prop firm's rules hanging over you like a guillotine. Nothing on that chart talks to anything else. The trend line doesn't know the CCI disagrees. The strategy doesn't know a news blackout starts in four minutes. Every tool is an island.

Sentinel is the plumbing that turns that pile of islands into one nervous system. It isn't one indicator — it's a design: a shared spine every tool plugs into, so they share a vocabulary, a safety layer, a memory, and a single fused opinion about what to do next.

The four jobs every trading system needs — every file in the suite belongs to exactly one of them.

RoleThe job, in plain EnglishWho does it
Signal sources"Is there an edge, and which way?"Trend · CCI · ADX · VolEnvelope · Compression · WAE · Eye + the orthogonal axes
Decision"Given all the signals, what's the verdict?"The Council (the brain)
Execution"Put the trade on and manage its life."Deck (manual) · GTrader21 (auto) · Bridge (auto)
Observation & safety"Keep us alive, remember everything, prove it later."SentinelCore (Gate/Ledger/Alerts) · Risk · Dashboard · Copier · Log · Lens · Arc
The one big idea · the decoupled seam busTools never call each other directly. They publish small typed facts to a shared bus (SentinelCore) and read facts back off it. A sensor publishes TrendState; it doesn't know the Council exists. The Council publishes CouncilState; it doesn't know the Bridge exists. This buys fault isolation (a missing publisher just means its fact is absent — every reader treats absent = abstain), independent evolution (rewrite a sensor's internals freely, as long as it still publishes its seam), and one decision, many consumers (the Council fuses once; everyone reads the same verdict).
The honest caveat you must keep repeatingToday most voters are price-derived — ADX, CCI, Trend, VolEnvelope and Brick all echo the same OHLC. When they "agree," that isn't five witnesses confirming a fact; it's one witness saying the same thing five ways. Conviction measures agreement, not confirmation. The suite gets genuinely smarter only as orthogonal axes (Clock, Participation, Location, MTF, Intermarket) add information the price sensors can't see.

The cast — eleven tools, one nervous system. Nothing calls another directly; each depends only on the shared spine, so any piece can be missing and the rest still runs.

The spineSentinelCore

The one shared dependency. Carries the kill-switch, the order gate, the ledger, and every sensor's published state.

exposes every …State seam
The brainCouncil

Fuses every sensor's vote into one explainable verdict — direction, conviction, size — and republishes it.

publishes CouncilState
The auto handsBridge

The autopilot. Consults the Council verdict, gates it, sizes it, fires — recording each shot to the ledger.

reads CouncilState → orders
The manual handsDeck

The human's order pad. Manual entry, plus SIGNAL ARM: read any indicator's plot and arm or auto-fire on it.

reads any plot → orders
The command surfaceCockpit

The one place to answer "is my brain alive, and why isn't it trading?" — a floatable rail that re-reads every seam so no card stays buried. Surfaces the decision, the gate, and the honest stale/floor reasons.

reads every …State seam
The scoutEye

Runs GodTrades across ~24 bar-types, scores them, qualifies the best direction — a meta-signal.

publishes EyeVerdict
The mirrorCopier

Mirrors fills from a primary account to followers, honouring prop rules, gating on Eye.

reads EyeVerdict + fills
The judgeLens

Analytics over the ledger + logs — win-rate, expectancy, MAE/MFE, and whether each weight earns its seat.

reads Ledger + Log
The watchdogRisk

Watches feed health. On lag or stall it engages the kill-switch, halting new entries until clean.

engages the kill
The supervisorArc

Fleet orchestration across instruments — which slots are live, leader supervision, per-instrument gating.

the SlotLive gate
The sensorsTrend · ADX · CCI …

The eyes on the tape. Each watches one thing and publishes its read as a typed seam.

publish one …State each
PART II

The mental model

how it all fits

Anatomy of a seam

How one tool tells another what it sees

The whole suite is glued by one tiny idea: a sensor publishes a small typed record — a …State — into SentinelCore, keyed by scope (one chart: instrument × bar type) and stamped with the time. A consumer consults it, checking freshness. Neither knows the other exists.

TRENDSTATE
InstrumentGC
Direction+1
TrendPrice4082.4
DistanceTicks+6
SourceSentinelTrend
UpdatedUtc18:20:20Z
1
Keyed by instrument

Stored per master-instrument ("GC"), never per class — so a tool on any chart of that instrument reads it.

2
A signed reading

The payload is plain int/double/bool. The bus never couples to a private enum — publish (int), the reader interprets.

3
Stamped every publish

UpdatedUtc is the freshness clock. Consumers pass a maxAgeSec and get null if it's gone cold.

4
Helper verbs

Each state ships an Aligned(dir) / Near(atr) helper so consumers ask a question, not re-implement logic.

5
Fail-open

Absent or stale ⇒ the consumer abstains. A quiet sensor never blocks the system — it just stops voting.

The core loop

Signal in, decision out, graded, fed back

Follow one signal from birth to graded outcome. Sensors publish into SentinelCore; the Council fuses; the Bridge acts through the Order Gate; every fill lands in the Ledger; and Lens grades the outcome to tune the weights. Publish → fuse → consume → record is the whole suite — everything else is detail.

SENSORS
voters + axes
SENTINELCORE
seam registry
COUNCIL
bias·conv·size
BRIDGE
gate + size×mult
ORDER GATE
kill·feed·risk
LEDGER
durable record
Three ideas to lock in1 · The seam is advisory; the Gate is authoritative. Reading CouncilState tells you whether there's an edge — it does not put the trade on safely. You still call GateEntry at submit. 2 · Every fire is recorded so it can be graded. Writing the verdict to the Ledger on each fire is what lets Lens later answer "does the Council's confidence actually make money?" 3 · The weights are a hypothesis until the data says otherwise.
PART III

The flows

the signal path, end to end

01 Council fusion

Ten voices, one verdict

Each sensor with a fresh reading casts a signed, weighted vote; a stale or absent one abstains. Modulators from above scale conviction by context — session, volume, higher-timeframe, location. Hard vetoes from below can zero it outright. Out comes one explainable verdict: direction, conviction, and a size multiplier.

ClockParticipationMTFLocation

Voters · weighted

EYE 1.4TRND 1.0 CCI 0.8ADX 0.6 ENV 0.6BRK 0.5 CMP 0.7IMKT 0.6 WAE 0.7GREV 0.9
FUSE
netScore → deadband
VERDICT
Bias−1 / 0 / +1
Convictionagreement 0 → 1
SizeMultconv × context
killrollovernews lockoutliquidity wall

Hard vetoes

The roster is DECLARED, not emergentThe Council used to fuse whatever seams happened to be fresh. So when the Eye crashed on load it simply never voted — across 332 verdicts, carrying the heaviest weight (1.4) — and nothing anywhere said so. Under fail-open abstention a crashed sensor is indistinguishable from a quiet one.

Now the expected voter set is declared, and resolved against reality every update: declared + fresh → votes · declared + absent → reported missing, RosterComplete=false, named in the audit string · present but undeclared → flagged unexpected and not folded into the fusion. The Cockpit shows Roster 8/10 — EYE, BRK missing.

The declaration comes from Sentinel\Models\<INST>\<bartag>\Roster.conf, else defaults to every known voter whose weight > 0. It must be config-derived, never observation-derived — building the "expected" set by looking at which seams are live would bake today's outage into it, and the roster could never report the very thing it exists to catch. This also makes the model attributable: you can finally tell the model apart from what happened to be loaded on the chart.
Weight 0 · the exploration primitiveA voter declared with w = 0 still votes and is still recorded in the mask — but contributes nothing to netScore, activeW, the agree/disagree tally, or the breadth damping. You accumulate a candidate sensor's full history, and can measure exactly what it would have contributed, before it ever influences a single trade. Adding and retiring sensors becomes a config change with zero code and zero risk.

The voter roster — the weights are the edge. Note the last column: most voters are price-derived (they echo the same candles), so their agreement is confluence, not confirmation. Only orthogonal axes carry genuinely new information.

VoterWhat it sensesWeightAxis
EyeBest-scoring GodTrades direction across ~24 bar-types (a meta-signal)1.4meta
God ReversalCandle-grammar reversal at a band edge (shaved · engulf · equal-high · VI)0.9timing · mean-rev
TrendTrailing-line regime direction (ATR + CCI hysteresis)1.0price
CCIWoodies CCI trend bias (×1.5 when strong)0.8price
CompressionCoil-base breakout, held a few bars0.7price
WAEConfirmed momentum-explosion breakout0.7price
ADXRegime strength + DI bias, when trend is on0.6price
EnvelopeVolatility regime (honest-Bollinger trend/squeeze)0.6price
IntermarketNet lean from correlated instruments (ZN/ZB for gold)0.6macro
BrickAdaptive-brick micro-trend direction0.5price
How the vote fuses (Council v1.2.1)netScore = Σ sign(dir)×weight; declaredW = Σ base weight of the DECLARED voters. Bias = ±1 when |netScore| > deadband·declaredW (deadband 0.15), else FLAT. Conviction = min(1, |netScore| / declaredW)agreement, and nothing else. contextMult = ×breadth ×squeeze ×clock ×rvol ×mtf ×location (all fail-open, all ≤ 1). SizeMult = 0 if vetoed / Bias 0 / conviction < floor, else conviction × contextMult.

Two rules that were learned the hard way, both by measuring:
The denominator is the DECLARED weight, not the weight that showed up. It used to be activeW (present, directional voters), so a missing voter did not dilute conviction — it vanished from the denominator. One awake sensor of weight 0.6 gave 0.6/0.6 = 1.0: perfect unanimity. The fewer sensors awake, the more certain the Council sounded. Seen live as size=0.57 (1/0, 2v) · roster 2/10 — a tradeable verdict from one of ten voters.
Conviction and context are separate numbers. Every modulator used to multiply into conviction before the floor test, so the floor silently asked "do my sensors agree and is the context good?" Once absence began diluting the base, both penalties compounded and nothing could clear the floor — four voters aligned, zero dissent, still size=0.00. Now the floor gates on agreement; a poor context makes the trade smaller, never silently absent.

The weights are the edge — tune them, ship them ON, and let Lens grade whether agreement actually paid.
⚠ Open: state voters vs trigger votersdeclaredW still conflates two kinds of voter. STATE voters (TRND ADX ENV IMKT) always carry a direction. TRIGGER voters (EYE BRK CMP WAE GREV) are ±1 only on the rare bar they fire, and read ~ otherwise — yet they carry 4.2 of the 7.80 total weight. Over half the model is therefore parked at zero on a typical bar, pinning conviction near 0.16 (measured: mean 0.160, max 0.36 across 97 verdicts).

The Council is scoring a silent trigger identically to ENV~ genuinely reading a flat regime. Those are different statements: a trigger that has not fired is an absence of evidence, not evidence against. Same one-number-two-jobs bug as ②, one level down. The fix — a voter kind in Roster.conf (CMP w=0.7 trigger), whose weight joins declaredW only on the bar it fires — is not built. Interim: ConvictionFloor lowered 0.35 → 0.20, which sits on a cliff (0.25→11% of verdicts, 0.20→41%, 0.15→60%). Do not nudge it by feel; fit it.

02 The order gate

One pre-submit choke point

No order reaches the broker without passing GateEntry — the single place kill-switch, feed-health, a rate guard, and risk-based sizing are all enforced. Pass and it submits; fail and it's blocked with an alert. The Risk watchdog can engage the kill on feed lag — exactly what fired on GC recently (a 2.6 s lag → halt → auto-release when clean).

INTENT
dir + size

GateEntry · choke point

kill-switch global/scoped feed-health lag/stall rate guard throttle size-for-risk ticks→qty
SUBMIT
→ fill
LEDGER
+ slippage
BLOCKED
+ alert
RISK WATCHDOG
feed lag · stall
↑ feeds the gate

03 The seam hub

Publish once, consult anywhere

Every tool talks to one small shared core, never to each other. An indicator publishes its …State and forgets it; a consumer consults it fresh — or abstains if it's stale or absent. That fail-open seam is why the suite degrades gracefully instead of breaking when one sensor goes quiet.

Publishers · indicators

TrendADXCCIEnvelope LiquidityBrickClockParticipation MTFLocationIntermarketWAE GodReversalEye
SENTINELCORE
…State registry

Consumers

CouncilBridgeCopier GTrader21DeckLens

stale · absent ⇒ consumer abstains — fail-open, with a heartbeat re-stamp so a still market doesn't read as "gone"

04 God Reversal detection

A reversal at a predictable place

The newest sensor, encoding the candle grammar the structural signals miss. It only looks when price is at a band edge, reads the reversal candle, scores the confluence, clears the no-trade guards, then fires — marking the chart and publishing GodReversalState as the Council's GREV voter. Fires on the candle's close: confirmed, non-repainting.

At a Bollinger band edge?
the "predictable place"
no →idle · mid-band
Read the reversal candle
shaved · engulf · equal-high · doji-exhaust · VI · attack
Confluence ≥ MinQuality?
stacked-confirmation score
no →drop
No-trade guards pass?
endless-doji chop · sideways grind
fail →drop
FIRE ▲▼
mark chart + single-candle stop → publish GodReversalState → Council GREV

05 Eye → Copy loop

Qualify first, then mirror only the qualified

Eye is a scout, not a signal of its own. It runs the GodTrades logic across ~24 bar-type rows, simulates each, ranks them on rolling performance, and qualifies a direction only when the best row clears its bar. The Copier mirrors a primary fill to followers only when Eye endorses it — and everything is logged so Lens adjudicates whether Eye actually earns its gate.

Eye · scan

~24 bar-type rows simulate GodTrades score & rank
EYEVERDICT
dir + score
Eye-gate
endorsed & fresh?
no / stale →block mirror
MIRROR
primary → followers

06 Deck · SIGNAL ARM

Point the cockpit at any signal

The Deck is the human's manual trader — and its SIGNAL ARM turns any loaded indicator's plot into a trigger, discovered live from the chart (no hardcoding). A rule reads the plot on the just-closed bar — re-checked every tick so the bar-boundary race self-heals — then either arms (highlights BUY/SELL for a human to confirm) or auto-fires through a deliberately fail-closed gate: one shot per bar, flat-only, forced MARKET. Because the Council exposes Bias/Conviction plots, the Deck can arm directly off the Council.

ANY PLOT
from ChartControl
RULE TEST
sign · rising · A×B · thresh
ARM
human confirms
FIRE
→ gate
AUTO-FIRE
fail-CLOSED gate
MARKET
tag Deck:signal

reads the just-closed bar and re-checks each tick → fires on the bar after the signal: confirmed, never repainting

The interdiction layer · Helm

Grab the wheel without stopping the car

Three ways to trade the suite: the Deck — you drive · the Bridge — it drives · Helm — you grab the wheel of a running Bridge without stopping it. Helm owns nothing: it publishes an intent addressed to one running actor's instanceKey, and that actor executes it with its own order handles and stays the sole owner — because a panel that touches a managed strategy's orders desyncs the position and can terminate the strategy. It is the publish/consult idiom pointed the other way: human input as a seam. Risk-reducing verbs (Flatten · Pause · Skip · tighten-stop) are fail-open; risk-adding verbs (Resume · widen-stop · Scale-up) pass the same GateEntry — the gate does not care whose finger it was. Every intent is written to the Ledger and marks its episode interdicted, so the Lab grades the policy, not the human.

COCKPIT ⑤
a human clicks
INTENT
verb + expiry · one-shot
BRIDGE
its own handles
ACTS
exit · move stop · pause
add risk?
Resume · widen · scale-up
GATE
kill · governor · session

the Bridge publishes HelmState back → the Cockpit renders reality (position · live stop/target · paused/override), never a guess. Verbs: Pause · Resume · SkipNext · FlattenNow · MoveStop · MoveTarget · BreakevenNow · Scale · TakeOver · HandBack.

Live look

The suite on a live chart

What the flows above look like rendered on price — the glass cards, the veto, and the on-chart order tools.

S2A Council VETO The red VETO: … line firing — e.g. news lockout or a liquidity wall. → img/s2-council-veto.png
S4Stacked glass cards Two+ Sentinel cards in one corner, auto-stacked by CardLayout, never overlapping. → img/s4-stacked-cards.png
S10Deck on-chart order visuals Entry (cyan) / stop (red) / target (green) lines with R / $ / tick chips. → img/s10-deck-visuals.png
S12The Bridge card + ARM button The autopilot's glass card with the clickable ARM BRIDGE control. → img/s12-bridge-card.png
PART IV

Orchestration & safety

control the fleet · guard every order

07 Fleet orchestration · Arc

One board decides which instruments trade, and when

A NinjaScript AddOn can't start or stop a chart strategy — so Arc doesn't try. It publishes a plan (per instrument: enabled, contracts, session window) and watches the leader account; Sentinel-aware strategies consult SlotLive at entry and trade only when their slot is live. Load a strategy once per chart, then control the whole fleet from one place.

Arc.conf
leader + slots
ARC
publish + supervise
SENTINELCORE
enable·qty·session
SlotLive?
at entry time
off / closed →skip entry
TRADE
this instrument
GC = LIVE 25f NQ = IDLE 0f ES = IDLE 0f health: OFF · CLOSED · IDLE · LIVE · DARK (leader down)

fail-open: no plan ⇒ the strategy trades normally — Arc adds control without becoming a single point of failure

08 The hardening substrate

Three layers that keep a tired human safe at 9:32am

The safety system isn't eighteen features — it's three substrates every order path shares. One guards the submit, one records and can restore, one watches and shouts. The question the whole thing exists to answer: could a tired human on a funded account blow it up? → No.

Layer 1Gate
kill-switchfeed-healthrate guardsize-for-risk
pre-submit choke
→ Clear / Advisory / Hard
Layer 2Record & restore
Ledger Order·Action·FillJournal · SlippageState atomic blobrestore on restart
append-only stream
+ survives a restart
Layer 3Watch & notify
readinessreconnect reconcileauto-flattenAlerts 2-tierConditions debounce · re-state
detect & alert
→ sound + push

Layer 3, in detail · the rule that governs every detector

A latch that never re-arms is indistinguishable from a detector that never fires

Three different things get written as if (set.Add(key)) Warn();, and only one of them is correctly a latch. Confusing them produced four separate bugs in this suite — including 160 false CRITICAL "naked position" alerts, because a stop order transiting ChangePending during a routine trail-step read as no stop at all. The alert you most need to trust had been crying wolf for five days.

KindMeansCorrect behaviourExample
Action latch"do this once"latch; clear on the day roll_hardFlattened — flatten an account once ✔
Transition log"say when it changed"fire on change_govPrevStatus — governor status ✔
Condition alert"something is wrong now"debounce transients → report → keep re-stating on a cooldown while true → auto-clear on resolvenaked position · orphan orders · scope contention · ambiguous scope
// SentinelCore.Conditions — route every condition alert through this: if (SentinelCore.Conditions.ShouldReport(acct + "|naked|" + instr, isNaked, debounceSec: 10, cooldownSec: 300)) Alerts.Critical(…, account: acct); // silent <10s; then re-stated every 5 min while true

Never leave a CRITICAL condition on cooldownSec: 0 — silence would then mean "still broken." For event-shaped detections that have no "false" observation to feed back (scope contention is only ever observed at write time), report on detection and Clear(key) on teardown.

How the latch bug was caught — and why it mattersNothing in the system reported it. Two Councils on one NQ chart collided for twenty minutes in total silence, because the contention warning was a HashSet that fired once per process. The tell was that their roster deviations logged as duplicate same-millisecond lines — impossible for a single instance, since deviations are change-logged. The roster caught the bug in the thing built to catch bugs. That is the general lesson: cross-checking two independent sources finds what neither reports alone, and it is what a future Doctor view should automate.

The one policy that governs it all — know which side of it any actor is on:

ActorOn an ambiguous / absent signalWhy
Manual (Deck actions)fail OPEN — allowa human is in the loop
Automated (GTrader21 · Copier · Bridge · Deck auto)fail CLOSED — blockno human to catch a mistake
Exitsnever gateyou must always be able to get out
A thrown exceptionfail OPENa bug must not freeze exits
What CanEnter actually checksAn automated entry fires only when all are true: not global kill · not scoped instrument kill · feed healthy · governor allows today (daily cap / loss-stop) · trailing-drawdown cushion intact · inside the account session · not in rollover blackout · not in a news lockout. Then, at submit, GateEntry risk-sizes and returns Clear / Advisory / Hard. Reconcile only ever detects + alerts — it never auto-cancels a real stop.

09 A strategy's lifecycle · GTrader21

How one strategy threads the whole machine

GTrader21 is the worked example — an unmanaged GodTrades strategy that uses every layer. On restart it restores or reconciles its position (never duplicating a stop). A signal only becomes an order after clearing three gates in series — Arc, Council, then the Order Gate — and every fill is captured for grading.

ON RESTART · restore + reconcile
resume · leave-unmanaged + alert · detect + alert — never a dup stop
SIGNAL
GodTrades
Arc
SlotLive
Council
edge+aligned
Gate
Clear only
SUBMIT
unmanaged
TRAIL · BE
persist state each change
LEDGER
fill + slippage

three gates, all fail-CLOSED for an automated strategy — a signal that clears the panel arm still stops if Arc, the Council, or the risk gate says no

The prop-firm governor

Compliance that falls out by construction

On a funded account, two rules can end it that have nothing to do with your strategy: the trailing drawdown (Risk owns it — the real-time floor vs the firm's −$4,500-type limit) and the consistency rule (the Governor owns it — no single day may exceed R × your total profit). Rather than track a ratio intraday, the Governor caps each day at DailyCap = R × ProfitTarget — if no day ever exceeds that, compliance is automatic.

FirmR (ratio)TargetDailyCapPreset
Lucid0.20$9,000$1,800/daylucid
Bulenox0.40$9,000$3,600/daybulenox
TPT0.50$9,000$4,500/daytpt
Apex0.30apex
Set the reset hour · this saves accountsTwo daily triggers reset at session rollover: profit cap (today's realized ≥ DailyCap → DayComplete, bank it) and loss stop (≤ −DailyLossStop → DayHalted). Set resetHour to your firm's rollover (TPT 5 PM ET, etc.) — a wrong reset silently breaks the daily rule. Prop rules drift constantly; always verify against the firm's live docs (Docs/PropFirmRules.md). The most common way a funded account dies isn't strategy — it's the trailing floor ratcheting on an unrealized peak.

Operate it

Prove the safety net before you trust it

A safety system you haven't watched fire is a story you're telling yourself. Most of the Dashboard Test tab works with the market closed — do these first.

  1. Alert channel.

    Test tab → Test Critical → hear a sound + a Risk alert row + a ledger line. Set a pushCommand (ntfy/Pushover/Slack), re-fire, confirm the phone push — no NT restart needed.

  2. Dry-run gate probe.

    Pick account + instrument + qty/stop/risk → Evaluate → expect GATE = CLEAR + sized qty. Engage the top-bar KILL → Evaluate → GATE = HARD. No order is ever sent.

  3. Self-checks.

    Run checks → expect 3/3 PASS (scoped-kill isolation · sizer unaffordable→0 / generous→≥1 · TickValue > 0).

  4. Ledger is flowing.

    Fire a SIM Deck order, toggle the kill → a JSON line per order + per toggle in Ledger\ledger-YYYY-MM-DD.jsonl, mirrored in the Journal tab (▶ Live streams within ~2s).

The funded-account gate · market-open proofs still pendingDon't risk a real prop dollar until each of these has shown ☑ live: kill-switch blocks entries everywhere · the fail-open/closed split observed for real (Deck lets a human click while GTrader21/Copier refuse) · hard daily-loss auto-flatten + lockout · GTrader21 restore with no duplicate stop · stop-fill slippage captured · reconnect naked-position alert. Until then, the safety layer is built but unproven.

Live look

The safety surfaces

The dashboard tabs where you prove the net actually catches something.

S8Dashboard — Test tab The dry-run gate probe showing GATE = CLEAR, then HARD after the kill. → img/s8-test-tab.png
S9Journal + Slippage A fill row with intended / fill / slip — red (worse) vs green (better). → img/s9-journal-slippage.png
PART V

Component catalog

every load-bearing file

The two files everything depends on

A shippable tool is just three files

Every subscribed Sentinel indicator hard-references exactly two AddOn files — that's the entire compile dependency. SentinelCore is the bus + the safety substrates (seam registry, kill-switch, feed-health, Gate, Ledger, State, Alerts, and the account registries). SentinelSkin is the look (the glass-card Painter, the palette, and CardLayout's anti-overlap docking). With the services absent, every Get…State returns no-data and the tool degrades to neutral, throwing nothing — that standalone-safe property is a hard rule.

The sensing layer · the voters — each is an Indicators.Sentinel indicator, publishes a seam (default ON), and is wired into the Council.

SensorPublishesWeightThe read it contributes
EyeEyeVerdict1.4Adaptive GodTrades scanner's directional qualification
SentinelTrendTrendState1.0ATR trailing-line direction (supersedes TrendMagic)
God ReversalGodReversalState0.9Candle-grammar reversal at a band edge
WoodiesCCIProCciState0.8Woodies CCI trend state −2..+2 (×1.5 if strong)
CompressionBaseCompressionState0.7Coil-base breakout direction ±1
Sentinel WAEWaeState0.7Waddah Attar confirmed momentum-explosion breakout
ADXProAdxState0.6ADX trend on/off + DI bias (×1.25 if strong)
VolEnvelopeEnvelopeState0.6"Honest Bollinger" regime (squeeze/trend/expansion)
IntermarketIntermarketState0.6Correlated-instrument lean — the one truly orthogonal voter
Brick (bar type)BrickState0.5Adaptive HA/Renko brick direction

The orthogonal axes — the independence engine. Four modulate conviction; Intermarket (above) is the fifth and it votes. Plus the one veto sensor.

AxisPublishesCouncil role
ClockClockState (phase / mins-to-close / kill window)midday & off-session damp + kill-window veto
ParticipationParticipationState (RVOL + climax/dry-up)thin-tape damp (never inflates)
LocationLevelState (VWAP/PDH-PDL/OR/IB + nearest)into-a-level damp (don't trade into the wall)
MTFMtfState (1/5/15/60/240 ladder)counter-higher-TF damp
LiquidityWallsLiquidityState (absorption z-score + walls)hard veto — a wall on the intended side zeroes conviction

Execution & the service layer — the hands, and everything that keeps them safe and remembered.

ToolRole
GTrader21 strategyAutomated GodTrades (BG/FC/OBR), unmanaged, panel + risk card; auto-reads lab configs; UseCouncilGate decouple
Bridge strategyThe autopilot — consumes CouncilState, sizes ×SizeMult → GateEntry, records every fire. Base Strategies namespace
Deck indicatorManual order deck + full trade management + on-chart order visuals + SIGNAL ARM
Dashboard12-tab control center: Copy · Log · Risk · Journal · Slippage · Lens · Eye · Arc · Assist · Excursion · Accounts · Test
RiskFeed lag/stall watchdog → engages the kill; rollover; hosts news-lockout + the governor
Alert · State · Copier · Log · Lens · ArcSound/push · state.json snapshot · fill-mirror · per-trade MAE/MFE · weight-grading · fleet orchestration
PART VI

Seam reference

the …State contracts

The shape every seam shares

One publisher, one consult, one snapshot

// every …State seam follows this identical trio on SentinelCore: void Set<X>State(string scope, string bartype, string instrument, …); // publisher <X>State Get<X>State(string scopeOrInstrument, double maxAgeSec); // consult; null if absent OR stale List<X> All<X>States(); // snapshot of every scope

Keyed by SCOPE"<instrument>.<barTag>", e.g. GC.69697v6x24, from SentinelCore.ScopeOf(Instrument, BarsPeriod). A scope is one chart's worth of context, and exactly the coordinate a model is defined over. Case-insensitive, lock-guarded. Get… returns null if nothing was published or the entry is older than maxAgeSec (pass 0 to disable expiry) — absent/stale = the consumer abstains, the fail-open backbone. The read always travels as int / double / bool; the bus never couples to a private enum.

All seams share one SeamStore<T>, whose Get() resolves in three rungs. (1) exact key — a migrated publisher consulted by scope; the normal path. (2) a scope asked of an instrument-keyed entry — a publisher that has not migrated yet. That rung is what lets the migration land one F5 at a time rather than all at once, and it disappears when the last publisher moves. (3) a bare instrument asked of scope-keyed entries — resolved only if exactly one scope carries it, else null plus a throttled log. That rung is fail-CLOSED on purpose: "I don't know which chart you mean" must never be answered with "here's whichever wrote last."

Why scope, and not just the instrument?Two GC charts on different bar types are two different worlds — different sensors loaded, different bar clocks, different volatility. Keyed by "GC" they overwrote each other's readings every bar, and a Council could fuse the other chart's ADX and report it as confluence. Scope separates GC from NQ, and GC-TBars from GC-150tick. It cannot separate two charts sharing instrument and bar type — nor two copies of one indicator on a single chart. That case is detected and logged as SCOPE CONTENTION, never silently permitted.
SeamKey payloadConvenience
EyeVerdictDirection(±1/0), Score
TrendStateDirection, TrendPrice, DistanceTicks, BarsInTrend, FlippedIsUp/IsDown, Aligned
CciStateTrendState(−2..+2), MainCci, TurboCci, Signal, WeakeningBias, Strong, Aligned
AdxStateAdx, DiPlus, DiMinus, Bias, Slope5, StrongTrendOn, Building, Aligned
EnvelopeStateRegime(0–4), Stretch, BandwidthPctile, MultUp/DownIsSqueeze, IsTrend
BrickStateDirection, Atr, SameDirCount, TicksToUpper/LowerAtrTicks(), Aligned
CompressionStateSignal(pulse), BreakDir(held), Coil, Compressed, ArmedJustBroke, Aligned
IntermarketStateLean, Score(−1..1), RefCount, RefsAligned
WaeStateSignal(±1 confirmed), Power, Explosion, DeadZone, IsExplodingAligned
GodReversalStateSignal(pulse), Dir(held), Quality, Setup, AtBand, ExhaustedJustReversed, Aligned
ClockStatePhase(0–3), MinsToClose, InSession, InKillWindowIsMidday, IsClose
ParticipationStateRvol, VolZ, Climax, DryUpBacked
LevelStateVwap±bands, Pdh/Pdl, Orh/Orl, NearestName, NearestDistTicks/AtrNear(), InPath()
MtfStateBias, AlignmentScore(−1..1), AlignedCount, AllAgree, DirsAligned
CouncilStateBias, Conviction, SizeMult, Agree/Disagree/Voters, Vetoed, ReasonsHasEdge, Aligned
LiquidityStateZscore, AbsorbSide, WallAbove/Below, DistAbove/BelowTicksBlocksEntry(dir,ticks)

Aligned(int dir): +1=long, −1=short, 0=flat. Also on Core, keyed by account: the kill-switch, scoped kill, governor, trailing drawdown, account profiles, news lockout, rollover, and fleet slots — bundled into the combined entry gate CanEnter.

PART VII

Extending Sentinel

add a voter · get logging
The Council Protocol · memorize thisAny new signal / regime / bias / context indicator MUST: (a) publish a …State seam carrying its read as int/double/bool (never an enum); (b) gate publishing behind a PublishState property that defaults ON — don't ship it dark; (c) be wired into the Council as a voter, modulator, or veto, appearing in the Reasons audit. A hidden plot alone is not enough — the Council reads seams, not plots.

Step 1 · publish your seam

Default-ON, keyed by scope, never throws

// SetDefaults: PublishState = true; // ON out of the box — never ship dark // resolved lazily, then cached. A null scope no-ops the publish — the right // fail-silent for an indicator that is not configured yet. private string _scope; private string Scope() { if (_scope == null) { try { _scope = SentinelCore.ScopeOf(Instrument, BarsPeriod); } catch { } } return _scope; } // end of the compute path in OnBarUpdate: if (PublishState && Instrument?.MasterInstrument != null) { try { SentinelCore.SetMyState( Scope(), // THIS CHART — never the bare instrument SentinelCore.BarTag(BarsPeriod), Instrument.MasterInstrument.Name, (int)myDir, // publish int, never an enum "MySensor"); } catch { } // publishing must NEVER throw into OnBarUpdate }

Publish every processed bar so the seam stays fresh — consumers staleness-gate, so a seam you stop refreshing goes stale and your voter silently abstains.

Two things that will bite youA crashed sensor is indistinguishable from a quiet one. Fail-open abstention means an indicator that throws in OnStateChange simply never votes, forever, and nothing says so — the Eye did exactly this across 332 verdicts while carrying the heaviest weight in the Council. Guard anything that can throw at construction, and declare your voter in the roster (below) so its silence is reported instead of assumed.

A seam has no history and no as-of semantics. SetXState stamps UpdatedUtc = DateTime.UtcNow even while the publisher replays historical bars, so a consumer's freshness gate cannot tell replay from live. Anything that records must gate on State == State.Realtime.

Step 2 · wire it into the Council

One line of logic; the plumbing is generic

// add a weight (SetDefaults + a [NinjaScriptProperty] in the Weights group), then // inside the gather try{} in OnBarUpdate, alongside the other voters: var mine = SentinelCore.GetMyState(inst, StaleSec); if (mine != null) AddVote("MINE", mine.Direction, WeightMyNew, ref netScore, ref activeW, ref voters);

AddVote collapses the value to its sign, adds sign×weight to netScore and weight to activeW, counts the fresh voter, and appends to the tally / card chip / Reasons token — all generic. Pass 0 when present-but-neutral (still counts toward breadth). Fold strength into the weight (× (mine.Strong ? 1.5 : 1.0)). If your sensor is context, make it a modulator (conviction *= myDamp); if it's a hard gate, add a branch to the veto chain.

Step 3 · consume & record

Read one seam, then still gate at submit

var v = SentinelCore.GetCouncilState(Scope(), maxAgeSec); // SCOPE, not the bare instrument if (v != null && v.HasEdge && v.Aligned(myDir)) { // HasEdge = !Vetoed && Bias≠0 && SizeMult>0 // SIZE → CLAMP → VALIDATE. Never skip the middle step. int want = Math.Max(1, (int)Math.Round(baseQty * v.SizeMult)); want = SentinelCore.SizedQuantity(Account, want); // × SizeScale × governor, clamp ContractLimit var g = SentinelCore.GateEntry(Account, Instrument, want, stopTicks, 0, instr); if (g.IsClear) { /* submit */ SentinelCore.NoteOrderSubmitted(Account); } } // on fill: SentinelCore.Ledger.Fill(...) + record the Council verdict for Lens to grade
Three ways this line goes wrongHasEdge must gate on SizeMult, not Conviction. Since conviction became pure agreement, a below-floor verdict still has Conviction > 0. The old test reported an edge with SizeMult = 0 — and because a consumer computes Math.Max(1, baseQty × SizeMult), it would fire a one-lot on a stand-down. Size is the only number that can say no.
SizedQuantity() is not optional. The Bridge skipped it for months, so the account profile's size=, the governor's RecommendedSize(), and ContractLimit were all silently ignored — a governor telling a strategy to size down was not obeyed. It is, in Core's own words, "the one place sizing math lives."
③ Pass riskDollars = 0 to GateEntry when you have already sized. Otherwise the Gate re-sizes from risk and can reject your quantity as "risk too small". That flip silently blocked the Bridge's first live trade.

Resolution note: with baseQty = 1, SizeMult cannot scale a position down1 × 0.19 rounds to 0 and the Max(1,…) floor restores a 1-lot. SizeMult only has resolution at baseQty ≥ 2. ConvictionFloor (SizeMult = 0) is what expresses "do not trade" — SizeMult is not a substitute for it.
The gotchas that biteLog CHANGES only, never per-tick (SentinelCore.Log("MyTool", …) → sentinel.log). Never build a second journal — the Ledger is the one event stream. Strategies stay in the base Strategies namespace (NT hides sub-namespaced strategies); indicators use Indicators.Sentinel. After forking a versioned file, strip all NT generated #regions or a running NT re-appends them (CS0111/CS0102). Position a card from ChartPanel.X/Y/W/H, never ActualHeight.
PART VIII

The MAE/MFE lab

turn a hunch into a graded rule

Measure the path, don't trade it

Record every excursion, simulate any TP/SL after

MAE = max adverse excursion (how far a signal went against you). MFE = max favorable. If you know the distribution of both for a signal, you can pick a take-profit and stop that match how it actually behaves — instead of guessing. The trick: record the full, untruncated price path after every signal, so you can simulate any TP/SL against real excursions.

RECORDER
full MFE/MAE path
ANALYTICS
expectancy grid
.conf
TP/SL rule
STRATEGY
GTrader21 · Bridge

The workflow, click by click — from a blank chart to a live TP/SL rule the strategy obeys.

  1. Record.

    Drop SentinelExcursionRecorder on each chart / instrument / bar-type you want to characterize (e.g. GC 100T, NQ 1000T). It places no orders and never truncates the path — leave the strategy off, or run a measurement template with every TP / SL / reverse / BE / trailing / cutoff OFF so nothing clips the excursion. It flushes each signal to \Sentinel\Excursions\*.jsonl at end-of-day, and also records the Council verdict as its own signal, tagged by conviction bucket.

  2. Load.

    Dashboard → Excursion tab → Load / Refresh excursions. The status line shows unique records / files / groups (with duplicate + legacy-schema records skipped). Tick "Confident only (n≥30)" to hide small-sample noise.

  3. Scan the edge chart.

    One diverging bar per signal group (trend regime): green MFE vs red MAE at 15 min, ranked by edge, a when HasEdge (median MFE > median MAE). This is your shortlist.

  4. Drill into a group.

    Pick one in "Detail signal" and read the four panels + two referees below — the growth line, the outcome scatter, the 12-config grid, and whether the Eye / Conviction actually paid.

  5. Find the ◆ and apply.

    = best raw EV (usually a wide-stop mirage — big EV bought with catastrophic risk). = best responsible config, Stop ≤ TP (R:R ≥ 1) — apply the ◆. "Apply ◆ to GTrader21 config" writes one .conf; "Sync all ◆ configs" writes one for every confident group with positive Exp.

  6. The strategy auto-reads it.

    On the target chart's strategy, group "14. Sentinel Integration": set UseSentinelConfig = true and SentinelConfigName = "GC_FC_Short". On DataLoaded it overrides its own TP / SL / trend-filter / Eye-gate with the lab values before any trade, and republishes what it loaded so it appears in the dashboard's Active lab configs list.

Reading the tab — the four analysis panels and the two referees that grade whether a filter earns its seat.

Growth lineMedian MFE vs MAE at 5 / 15 / 60 min — does the favorable side pull away from the adverse side over time?
Outcome scatterEvery fire as a dot: MAE15 (x) vs MFE15 (y), coloured by regime, a hollow ring = Eye-endorsed, dashed lines = the chosen TP / SL.
Expectancy grid12 configs — TP at the 33 / 50 / 67th pct of MFE15, Stop = TP × {0.5, 0.75, 1.0, 1.5}. Each shows HitRate + Exp (est. ticks/trade); ★ and ◆ are marked.
Eye refereeDoes Eye-endorsed out-earn the rest at 15m by ≥ 3 ticks? → +1 / −1 / 0. If conclusive, the written .conf flips useEyeGate on.
Conviction refereeFor the COUNCIL group: does the HIGH-conviction bucket out-earn MID / LOW? The same test, applied to the Council's own confidence.

What Apply writes — a plain key = value file; the strategy applies five keys, the rest is informational.

# \Sentinel\GTraderConfigs\GC_FC_Short.conf instrument = GC signal = FC direction = Short useTrendFilter = true → UseTrendFilter trendAdxThreshold = 25 → TrendAdxThreshold useEyeGate = false → UseEyeGate (only when the Eye referee is conclusive) takeProfitTicks = 40 → ProfitTargetTicks (+ UseProfitTarget) stopLossTicks = 30 → StopLossTicks (+ UseStopLoss)
Fill-resolution caveat · hard-wonThe grid is a screen, not a verdict. Validate the chosen TP/SL at High/Tick fill resolution, not bar level — bar-level excursion is optimistic. One strategy's apparent 81% win rate collapsed to 37.5% at tick fills. First real lab finding: signal quality is FC > BG > OBR, and FC-in-trend (ADX ≥ 25) is the first cross-instrument edge; OBR ≈ noise.

Live look

The Excursion tab

The three read-decide panels and the one-click handoff that turn recorded paths into a live rule.

S5Edge chart Diverging MFE / MAE bars per signal group, ranked, ✓ on the real edges. → img/s5-edge-chart.png
S6TP/SL grid The 12 configs with the ★ (raw EV) and ◆ (responsible) marked. → img/s6-tpsl-grid.png
S7Outcome scatter MAE15 vs MFE15 dots, regime-coloured, Eye rings, dashed TP/SL lines. → img/s7-outcome-scatter.png
S11The .conf handoff GTrader21 group 14 UseSentinelConfig + the Active-lab-configs list. → img/s11-conf-handoff.png
PART IX

Worked example

GC FC-Short, raw signal → graded edge

One trade, birth to grade

The whole suite in a single story

You trade GC and suspect the GodTrades FC (continuation) signal is your edge, but only in a trend. Here's how the suite turns that hunch into a graded, live, risk-gated strategy. (Numbers illustrative; the process is exact.)

Act 1 — Measure. Put the Excursion recorder on a GC chart for a couple of weeks. It never trades — it records the full price path after every FC/BG/OBR fire (MFE/MAE at 1/5/15/60m), tagged with regime and Eye verdict. You end with a few hundred FC-Short fires and their real excursions.

Act 2 — Find the responsible edge. Excursion tab → Load. GC · FC · Short (trend) shows green MFE ≫ red MAE at 15m. The ◆ lands at TP 40t / Stop 30t, ~+6t/trade at 58% hit rate, firing 1.8×/day, n = 140. The Eye referee says endorsed fires out-earn the rest. Click Apply ◆ → writes GC_FC_Short.conf.

Act 3 — Hand it to the strategy. On the live GC chart, set the strategy's UseSentinelConfig = true. On load it overrides its own dialog — TP 40 / Stop 30 / trend filter on — before it can place a single trade. The lab result is now the live rule. (The Bridge does exactly this off the Council; the executor is pluggable.)

Act 4 — A signal fires. Watch the gauntlet:

Trend filter — ADX 27 ≥ 25trend confirmed
Council — Bias −1, HasEdge, Conviction 0.71size × 0.71
CanEnter — kill / feed / governor / drawdown / session / rollover / newsall clear
GateEntry(GC, qty 1, stop 30t) → Level = Clear→ SUBMIT

Act 5 — Fill, record, manage. The sell fills at 2041.2 (intended 2041.4 → 0.2t slip, visible in the Slippage tab). The strategy records the Council verdict on this fire into the Ledger — the seed Lens will grade — sets its 40t target / 30t stop, and persists position-state so a mid-trade restart re-adopts the stop instead of duplicating it. Target hits: +40t minus slip. One clean, fully-audited round trip.

Act 6 — Grade it. Days later, 30+ of these. Dashboard → Lens answers the only question that matters: when the Council was confident (0.7+) on FC-Short, did those trades pay more than the low-conviction ones? If yes → conviction is real edge; lean in. If no → "agreement" was just correlated price-lenses nodding along; the fix isn't more of the same voters, it's an orthogonal axis carrying information the others don't have.

That is the full Sentinel loopmeasure → find the responsible edge → hand it to the strategy → run the safety gauntlet → record every verdict → grade whether the confidence paid → tune the weights → repeat. Every part of this manual is one station on that loop.
PART X

The learning loop

stop guessing the weights

The one-sentence version

The Council is already a model. Nobody ever fitted it.

The Council computes netScore = Σ(voteᵢ × wᵢ), calls the sign of it the bias, and calls |netScore| ÷ Σ(active weight) the conviction. That is a normalized linear model over ten signed features. The coefficients — WeightEye = 1.4, WeightTrend = 1.0, WeightCci = 0.8 … — the 0.35 conviction floor, and the 0.15 deadband are all numbers a human picked.

So "adding machine learning" to Sentinel does not mean bolting a neural network onto price bars. It means fitting the parameters the Council already has, using the outcomes the Recorder already writes. The architecture doesn't change. Three guesses become three measurements.

The shape of itSensors still publish. The Council still fuses. The Bridge still fires. The Gate still guards. The model can only make the Council quieter or louder — it can never open the Gate. Every safety property in Part IV survives untouched, because the learned artifact only ever supplies weights, and weights feed conviction, and conviction feeds a request that the Gate is still free to refuse.

The whole machine, as one map

Signal → verdict → order → grade → weights → signal

The entire decision-and-learning cycle on one surface. The top row is the live decision path — sensor to order, left to right. The bottom row is the offline learning path — every fire graded against what price did next, the weights refit per bar type, fed back in. The loop closes at COUNCIL, the only box both paths touch — and nothing the bottom row produces can open the Gate on the top.

SENSORS
22 voters · 5 axes
SENTINELCORE
scope-keyed seams
COUNCIL
Σ vote×w → bias·conv·size
CONSUMERS
Bridge·GTrader21·Deck
ORDER GATE
fail-closed
LEDGER
durable record
▲ modulators × dampen — Clock · Participation · MTF · Location ▼ hard vetoes zero it — kill · rollover · news · wall
RECORDER
verdict + firstTouch
CORPUS
council\1.3 · per bar type
LAB · train.py
purged CV · fit
MODEL.CONF
weights · floor
Read the loop in one breathDown the top row, a signal becomes a guarded order and a durable record. Along the bottom row, that record becomes a first-touch label, the labels become a per-bar-type fit, the fit becomes Model.conf. Up the return arrow, Model.conf becomes the Council's weights — or, if it is missing, stale, or for the wrong instrument, the hand-set weights stand. The machine can get smarter, or fall back to exactly what it is today; it can never get more dangerous, because the Gate lives on the top row and the model on the bottom.

What stays the same, what changes — the honest inventory. Nothing in the left column is touched.

LayerStays exactly the sameWhat the learning loop changes
SensorsPublish a …State seam, default on. Never know who reads them.Nothing. A sensor is a voter whether its weight is 1.4 or 0.
SentinelCoreOne static bus. Publish / consult. Freshness via UpdatedUtc.Additive fields only: the decision vector on CouncilState, an EpisodeId, a scope-aware key.
CouncilFuses votes → bias · conviction · size×. Hard vetoes. Reasons audit. Publishes one verdict.Reads its weights from Model.conf when present. Falls back to the hand-set weights if absent, stale, or wrong-instrument.
Bridge / Deck / GTrader21Consume CouncilState. Size, bracket, fire. ARM is a deliberate click.Claim a unique instanceKey before arming, and stamp instance · model · policy · episode on every Ledger row.
The GateFail-closed. Kill · governor · session · rate. The one pre-submit choke point.Nothing. Ever. A model is never allowed to widen a limit.
LedgerAppend-only JSONL. order · action · fill.Additive context: strat · instance · model · policy · episode — the five identities that today all read "SentinelBridge".
ArcPublishes a fleet plan; strategies consult SlotLive(), fail-open.Slots keyed by instanceKey, not by instrument — so it can idle one GC chart and run another.
RecorderRecords MFE/MAE for every signal. Places no orders.Also records the inputs — the voter vector — plus a firstTouch barrier latch. Schema 1.2 → 1.3.
YouArm the chart. Read the Cockpit. Trust the Gate.Run train.py when the market is closed. Decide whether to promote a challenger.
Prerequisite · the correctness bug (Council + 4 sensor seams fixed; 11 sensors still open)Every seam was keyed by instrument name alone_council["GC"], _adx["GC"], _trend["GC"]. Two GC charts on different bar types therefore overwrote each other's readings, and a Bridge on one chart could read — and trade — the other chart's brain. Separately, SetCouncilState stamps UpdatedUtc = DateTime.UtcNow even while replaying historical bars, so the freshness gate could not tell a live verdict from a replayed one.

Council seam fixed in Core v1.15.0 (scope keys + the Recorder gates on State.Realtime). Batch 1 of the sensors fixed in v1.18.0Adx · Trend · Cci · Envelope, the four price-derived voters, now share a SeamStore<T> keyed by scope. Eleven seams remain instrument-keyed (Eye, Liquidity, Brick, Clock, Participation, Level, Mtf, Compression, Intermarket, Wae, GodReversal), as does Arc's _fleet — that is the rest of Phase 1.4. The store's scope→instrument shim means a half-migrated tree still resolves correctly, so batches land one F5 at a time.

Note scope cannot separate two charts sharing instrument and bar type, nor two copies of one indicator on a single chart. That is detected and logged as SCOPE CONTENTION — proved live: two Councils on one NQ chart. See Docs/SENTINEL_ML_SPEC.md §10–11.

Across the file boundary

How the processing works outside NinjaTrader

NinjaTrader never runs Python. Python never touches bin\Custom. They meet at exactly two places, and both are flat files on disk. That is the whole integration — no DLLs to reference, no ONNX runtime, no CS0101 collisions, no F5, and no way for a broken experiment to stop the platform from trading.

inside NT · .NET 4.8 · data thread

SENSORS publish
COUNCIL fuses
BRIDGE fires
RECORDER observes

outside NT · python · no platform access

dataset.py load · fold
labels.py barrier · weight
cv.py purged CV
train.py fit · emit
MODEL.CONF
w.EYE1.22
w.TRND0.61
calib.floor0.42
expiresUtc+30d
MODEL.CONF
flat key = value
COUNCIL
weights, not decisions
1
The Recorder writes the training set

One row per Council episode — a maximal run of constant bias. The row holds the inputs (which voter said what, at what weight, plus the orthogonal-axis context) and the outcome (the full MFE/MAE path, and which barrier was touched first). It places no orders.

2
The files cross the boundary

Sentinel\Excursions\*.jsonl and Sentinel\Ledger\*.jsonl. Plain text, append-only, one row per line. Python opens them with json.loads. Nothing is exported, nothing is compiled.

3
The Lab fits, and mostly refuses to

Most of the code is defensive, not predictive — censoring, uniqueness weighting, purged cross-validation. Its job is to stop you believing an edge you don't have.

4
One flat artifact returns

Model.conf is key = value — so the C# side needs no JSON parser. It lands in the Sentinel\ config-git repo, committed next to the P&L it produced.

5
The Council reads weights, never decisions

It still computes its own verdict, on this chart, this tick. The file supplies numbers. If the file is missing, expired, or for the wrong instrument, the hand-set weights are used and it logs once. Fail-open.

What each offline stage actually does — five ideas, four of which exist only to keep you honest.

Direction foldingMultiply every vote by the verdict's own direction. A long with EYE = +1 and a short with EYE = −1 are the same evidence — "the Eye agreed." Halves the feature space, doubles effective N.
Barrier labelNot "did the trade win" — trades only exist above the floor. Instead: from the fire, was +R touched before −R? That label exists for every verdict, including the ones you stood down on. The training set contains the counterfactual. That is why the floor can be learned, not merely checked.
CensoringA verdict that drifted sideways into the close is dropped, not scored as a loss. It never cost anything; teaching the model to fear it is a lie.
Uniqueness weightingTwo verdicts 90 seconds apart share most of their forward window. They are not independent observations. Without this, effective N is wildly overstated and every significance test lies.
Purged walk-forward CVTrain only on rows whose outcome closed before the test fold opened, with an embargo. Never random k-fold — overlapping labels leak the test set into training. This is the single most common way traders convince themselves they have an edge they don't.
Partial poolingFor the GC / NQ / ES / CL fleet: fit one joint model — a shared global weight vector plus small per-instrument deviations, penalized hard. Thin instruments shrink toward the consensus. You learn which contracts genuinely differ instead of assuming all of them do.

Before the Council trusts a model file — every guard must pass, or the hand-set weights are used and nothing stops.

schema is recognisedelse → hand weights
instrument matches this charta GC model never runs NQ
bartype matches this chartbar granularity is the model
expiresUtc is in the futurea stale model self-disables
every w.* voter tag presentno silent partial weights
roster is completeelse publish, but flag
The ruleA model file is never allowed to stop the platform. Any guard that fails costs you the learned weights, not the session. This is the same fail-open posture every sensor already has: a stale voter abstains, it doesn't halt the Council.

Who does what

Each tool's job in the learning loop

Nothing new is invented. Every part below already exists and already does most of this — the loop mostly consists of teaching them to write down what they knew at the time.

observesExcursion Recorder

Writes one row per Council episode: the decision vector in, the MFE/MAE path and first-touch barrier out. It is the only component that sees inputs and outcomes together. Places no orders, ever.

writes Excursions\*.jsonl · schema 1.3
is the modelCouncil

At runtime the Council is the fitted linear model. Today its coefficients are hand-set; after the loop closes it reads them from disk. Its arithmetic, vetoes and Reasons audit are unchanged either way.

reads Model.conf · publishes CouncilState
carriesSentinelCore

The bus. Gains the decision vector, the EpisodeId, and scope-aware seam keys. Additive only — it is one static class in one assembly and can never be forked.

Set/GetCouncilState · Ledger
actsBridge · Deck · GTrader21

Unchanged as traders. They gain one obligation: stamp episodeId and modelId on every order, action and fill, so a trade can be traced back to the exact decision and the exact model that produced it.

writes Ledger w/ strat·model·episode
guardsThe Gate

Completely untouched. Fail-closed. Kill, governor, session and rate limits are policy, not prediction. The learned model proposes; the Gate disposes.

GateEntry — unchanged
joinsLedger

Today its only identity is a free-text tag, so a fill cannot be joined to its verdict. With episode as a real key it becomes a database rather than a diary — the spine the whole loop hangs from.

Ledger\*.jsonl · order · action · fill
gradesLens

Joins fills → episode → verdict → outcome and reports which model paid, per instrument. This is the job it was always described as doing and could not actually do, because the join key didn't exist.

reads LedgerExcursions
fitsThe Lab

Python, outside bin\Custom. Loads the JSONL, folds by direction, labels by barrier, weights by uniqueness, validates by purged walk-forward, and emits one flat artifact. Cannot break the platform because the platform cannot see it.

Sentinel\Lab\ → Model.conf
shows you · names thingsCockpit

The operator's answer to "why isn't it trading?" — and now also "which model is loaded, is the roster complete, how old is the verdict." The stale ▸ floor why-line gains a roster rung: Roster 8/10 — EYE, BRK missing. It is also the naming surface (§ setups): the whole fleet in one list, aliases editable in place.

reads every …State seam · writes Aliases.conf
supervisesArc

Publishes the fleet plan each chart consults before it fires. Today its slots are keyed by instrument, so it sees one "GC" and cannot idle one GC chart while running another — the same collision the seams have. Slots must key by instanceKey.

FleetSlot · SlotLive() — fail-open

GC · NQ · ES · CL

One chart, one scope, one model

The mechanics of running the fleet reduce to a single rule: a chart is a scope, and a scope is a model. Everything else — file paths, model identity, which verdict the Bridge reads — falls out of that one coordinate.

Lane AIn the NT tree
scope the seamsas-of guardEpisodeIdLedger contextrostermodelId
behavior-neutral
one F5 · patch in place
Lane BOutside the tree
Sentinel\Lab\pythonnever compiled by NTbranch freely
zero platform risk
iterate for weeks
Lane CConsumption
UseLearnedWeightsdefault OFFfail-openchallengers
fork by flag, not by file
promote via config edit

The coordinate propagates everywhere. Add a fourth chart and nothing collides, because nothing was ever global.

# one chart = one scope = one model scope = GC|TBC6-24-69697 ← instrument | bartype tag seam key = _council[scope] ← was _council["GC"] — the collision config = Sentinel\Models\GC\TBC6-24-69697\{Roster.conf, model.conf, challengers\} data = Sentinel\Excursions\<stamp>__GC__TBC6-24-69697.jsonl modelId = sha1(scope + roster + weights + thresholds)[0..7] workspace = "Sentinel Fleet.wsz" ← NT's own fleet manifest
Champion & challenger · how to explore while runningTwo Councils cannot coexist on one instrument — they'd stomp the same seam. But you never needed two. Scoring K weight vectors against one voter vector is K dot products — nanoseconds. So the Council evaluates the champion and every challenger on the identical vote, publishes only the champion, and records them all. Every live bar grades every candidate model at zero capital risk. And because champion and challenger see the same episode, the comparison is paired — it cancels the market-regime variance term outright. Promotion is a config edit. Demotion is instant.
Weight-0 voters · the exploration primitiveThe sensing layer is deliberately provisional; the voters will churn. Add a candidate sensor to Roster.conf with w = 0. It votes. It is recorded. It contributes nothing to netScore or activeW. You accumulate its full history — and can measure exactly what it would have contributed — before it ever influences a single trade. Adding and retiring sensors becomes a config change with zero risk and zero code.

The interface

Naming your setups — how a trader actually drives this

Every identifier above is content-addressed: gc-tbars-a3f91c2, pol-5f21, GC-20260709-0042. Machine-perfect, human-hostile. No trader will ever type one, and none should ever have to see one. A design this structured is worthless if the interface isn't straightforward — so the naming rules come first, and the hashes hide behind them.

Rule ① · the system must be usable with zero namingNaming is an optional refinement, never a setup step. Every alias auto-derives into something readable, so a trader who never opens a settings dialog still gets a legible card and legible Ledger rows. A hash is never shown by default.
ThingAuto-derived default (type nothing)You may override to
Setup the instanceGC · TBars 6-24 · Sim101"GC Morning"
Brain the modelHand weights — or Model.conf's own alias="Balanced v3"
Play the policyTP40/SL20 — or the lab .conf filename"Tight"
GC MORNING · ● LIVE
scopeGC · TBars 6-24 · Sim101
brainBalanced v3
playTight 20/40
verdictLONG · 0.62 · 1×
roster8 / 10 ⚠
 [ ARM BRIDGE ]
NAME TAKEN · ✕ BLOCKED
aliasGC Morning
conflictarmed on Sim101
wherechart 2
verdict— not read —
 arm disabled
2
Alias ≠ identity

The keys are derived and immutable; the alias is a mutable display string bound to one. Rename freely — nothing re-keys, no history orphans. Aliases live in Sentinel\Aliases.conf, in the config-git repo. The Ledger writes both: the id as the join key, the alias denormalized beside it so a raw JSONL line reads without a lookup.

3
One human name, not three

You configure a chart. Call it a Setup, and let its name be the only thing you ever type. modelId and policyId become automatically-versioned eras inside it. Widen the stop on "GC Morning" and the policy fingerprint changes while the name does not — so Lens reports "GC Morning — policy changed 2026-08-14", which is how you already think about it.

4
Name where uniqueness is visible

A per-chart property grid is the worst place to notice you've named two things alike — you can only see one at a time. The Cockpit is the naming surface: the whole fleet at once, alias editable in place.

Humans name the thing they can see; the machine versions everything underneath. Three surfaces, one precedence order.

CockpitThe real workflow. One row per discovered setup — alias · scope · brain · play · armed · roster · day P&L. Edit the alias in place. This is where the fleet becomes legible and where a duplicate name is obvious.
F6 property gridA Setup name string on the strategy. A seed, not the store — a [Display]-only property (never [NinjaScriptProperty]) so it serializes to the workspace without touching the generated region.
The on-chart cardDisplays, never edits. Setup alias large, derived scope small beneath it, brain and play on their own rows, then the verdict block and the ARM button.
PrecedenceAliases.conf > F6 Setup name > auto-derived default.
Rule ⑤ · the name is an interlock, not a labelTwo setups may share a nickname; they may never share an identity. So the two collisions are handled oppositely — and the distinction is a safety property, not a nicety.

Duplicate instanceKey → BLOCK the arm. That means two Bridges on the same scope and the same account. NT's account position is shared across strategies, and a managed strategy whose account position moves underneath it desyncs and refuses all new entries until you disable and re-enable it. The configuration that is ambiguous in the Ledger is the same configuration that is dangerous in the account — so naming and safety turn out to be one mechanism, and you get the second one free.

Duplicate alias → WARN only. Confusing, never dangerous.

The corollary for A/B testing: two armed Bridges on one instrument need separate accounts, or one runs shadow-record — evaluating and writing its hypothetical fire without submitting an order.
A fourth naming axisThe Federated Naming Law governs file · class · display · namespace — all build-time names, fixed at compile. Setup name is a runtime instance alias: a different kind of thing, chosen by the trader, changeable at will, and bound to a derived key that never moves. Every actor in the suite — Bridge · Deck · GTrader21 · Copier — should expose it identically.
Where this honestly stands · 2026-07-09, end of dayPhase 0 and Phase 1.1–1.3 are BUILT and F5-verified. Python is installed and the Lab runs (it immediately caught three of its own bugs, including overwriting the live Model.conf with a model it had just rejected). SentinelCore is at v1.15.0: CouncilState is keyed by scope (GC.69697v6x24) and carries BarTimeUtc + IsHistorical, and the Recorder now records Council fires only in State.Realtime — the as-of guard.

Fixed since: Eye_v1_1_0 defaulted its TBars and NinzaRenko bar-type IDs to numbers that are not registered bar types, so AddDataSeries threw out of State.Configure on every load. That is why EYE — weight 1.4, the heaviest voter — never voted once in 332 verdicts. It loads now, and every row add is guarded so a bad bar type degrades to "row skipped and logged" instead of taking the sensor offline. ⚠ It is present but still directionally neutral until it qualifies: present in the tally, contributing breadth, but its 1.4 has yet to enter netScore. The declared roster now reports any such absence instead of hiding it, and batch 1 of the sensor seams (Adx/Trend/Cci/Envelope) is scope-keyed.

Fixed since (2026-07-10): the Eye's EYE row appears again; batch 1 + 2 of the sensor seams are scope-keyed (Adx · Trend · Cci · Envelope · Brick · Compression · Wae · GodReversal — 8 of 15); the sensors gained an OnMarketData heartbeat, so a healthy OnBarClose voter no longer ages out of the roster in a quiet market (measured: a fully-loaded chart used to report roster 3/10; after the heartbeat only the two un-heartbeated seams ever drop). Conviction was rebuilt twice — absence now dilutes, and context damping moved out of conviction into SizeMult. HasEdge gates on size. SentinelBridge finally routes sizing through SizedQuantity().

Still true, still uncomfortable: the Council's bias chatters at a 0.4-minute median cadence against a 15-minute label window, so effective N was 60, not 301. Conviction ranks outcomes slightly backwards on that (contaminated) sample. The corpus gets archived, not repaired, at step 1.5. Seven sensor seams — and Arc's _fleet — are still keyed by instrument alone (the rest of Phase 1.4; EyeVerdict among them, which is why EYE still drops out of the roster). And scope alone cannot separate two charts sharing instrument and bar type, nor two copies of one indicator on a single chart; that is now detected and logged as SCOPE CONTENTION — proved live.

And the newest one: declaredW conflates state voters with trigger voters, so half the model's weight is parked at zero on a typical bar and conviction is pinned near 0.16 (1 of 97 verdicts cleared the 0.35 floor). The floor was lowered to 0.20 as an interim; the real fix — a voter kind in Roster.conf — is not built.

None of this says the Council is wrong. It says nobody ever checked — which is precisely the gap this part closes. Everything above was found by measuring, in a single day, after the roster made the model's own silence visible.
PART XI

Under the hood

the ideas that hold it together

Non-repaint timing

Why a signal you can trust always arrives one bar late

A candle's shape isn't final until it closes. So every Sentinel signal is computed on a closed bar and consumed on the next one — read at barsAgo=1, re-checked each tick so the processing-order race between two indicators self-heals. The cost is a one-bar delay; the payoff is a signal that never repaints out from under you.

BAR N
forming…
COMPUTE
grammar on N
PULSE ±1 · HELD dir
into the seam
CONSUME & ACT
read barsAgo=1

a one-bar pulse is fleeting, so sensors also publish a held direction for a few bars — that's what the Council actually votes on

Principles

Six decisions that shape everything

Fail-open sensing, fail-closed firing

A missing sensor abstains — the system keeps deciding with what it has. A missing safety check blocks: the order gate and auto-fire fail closed. Read loosely, act strictly.

One choke point

Every order — Bridge, Deck, GTrader21, Copier — passes the same GateEntry. Kill-switch, feed-health, sizing, and rate-limits live in one place, so safety can't be forgotten on one path.

Confluence isn't confirmation

Most voters read the same candles, so their agreement can be an echo. The suite treats conviction as alignment, and only gets truly smarter as orthogonal axes come online.

The weights are learnable

The Council's vote weights are the edge, and they're not frozen. Every fire records the verdict; Lens grades which voices actually paid — so the weights become measured, not guessed.

Advisory, then enforced

The Council advises — it shapes bias and size but never sends an order. The consuming strategy still calls its own gate at submit. Decision and enforcement are separate layers.

Frozen checkpoints

Every tool is versioned per-file with an in-file changelog; old versions are frozen, never edited. A safe fallback always exists, and a chart never silently loses an indicator to a rename.

PART XII

Reference

glossary · file index

Glossary

The words that recur

Seam / busA small typed fact published to SentinelCore (…State) that any tool can read.
Voter / modulator / vetoA Council input that adds a directional vote / scales conviction / hard-blocks.
Bias / Conviction / SizeMultThe fused direction (−1/0/+1) / alignment strength (0..1) / size multiplier (0..1, 0 when vetoed).
MFE / MAEMax favorable / adverse excursion — the raw material of TP/SL selection.
★ vs ◆Best raw-EV config vs best responsible config (R:R ≥ 1). Apply the ◆.
Fail-open / closedAllow vs block when a signal is ambiguous or absent.
Root symbolInstrument.MasterInstrument.Name (e.g. "GC") — the seam registry key.
GodTradesThe BG/FC/OBR signal family the Eye scans and GTrader21 trades.

File index

The load-bearing files

FileRole
AddOns/SentinelCore_v1_0_0.csThe bus + Gate/Ledger/State/Alerts + all registries
AddOns/SentinelSkin.csGlass-card Painter + palette + CardLayout
Indicators/Council_v1_0_0.csThe fusion brain
Indicators/{Eye,SentinelTrend,WoodiesCCIPro,ADXPro,VolEnvelope,CompressionBase,Intermarket,SentinelWAE,SentinelGodReversal}_*.csThe voters
Indicators/{Clock,Participation,Location,Mtf}_v1_0_0.csThe orthogonal modulator axes
Strategies/GTrader21v_0_1_7.cs · SentinelBridge_v0_2_0.csThe executors
Indicators/Deck_v0_2_2.csThe manual order deck + SIGNAL ARM
AddOns/SentinelDashboard + Sentinel{Risk,Alert,State,Copier,Log,Lens,Arc}Service*.csThe control center + service layer
Docs/SENTINEL_DESIGN_SYSTEM.md · SENTINEL_GOD_REVERSAL_DOCTRINE.md · ROADMAP.mdThe specs this manual condenses
Docs/SENTINEL_ML_SPEC.mdThe learning loop (Part X) in full — schema 1.3, scope keys, identity, the fleet
Docs/SENTINEL_DATASET_DICTIONARY.mdThe corpus reference — scope grammar, bar-type IDs, schema-1.3 fields, the full 22-voter catalog, how the Lab reads it
Sentinel/Lab/The offline trainer. Python, outside bin\Custom, never compiled by NT
Where this is todayThe trading loop is closed and running — sensors → Council → Bridge (auto) fire and record; GTrader21 gained the Council decouple. Built and compiling: 22 voters, 5 orthogonal axes, the full hardening substrate. Still pending: live market-open safety proofs (kill / auto-flatten / restore / reconnect).

The learning loop (Part X) is now running, not just spec. Its correctness prerequisites are done — seams are keyed by scope (13 scope-keyed + 2 instrument-by-design), historical bars are excluded via the as-of guard, and episodeId exists. The Council reads a shared voter catalog (Models\catalog.conf, emitted from the one C# source), and the offline Lab fits per bar type from the clean schema-1.3 corpus with a data-driven voter set. The honest next step is to bake enough clean rows, then fit the ConvictionFloor + weights — the first look (Lab\voter_edge.py) shows the edge lives largely in bar construction, and the hand-set weights are a hypothesis, not yet a measurement. This document is living; it is updated as the system evolves.