Sentinel Suite · NinjaTrader 8 · living document·Docs home →

Sentinel Dataset Dictionary

The authoritative reference for the syntax and nomenclature of every Sentinel dataset — the training corpus the Council learns from. If you are about to read, write, ingest, or fit against a .jsonl under Sentinel\Excursions\, this is the map.

This doc is generated from live source (BarsTypes\SentinelFlux_v1_0_0.cs, AddOns\SentinelCore.SystemBuilder.cs VoterCatalog, Indicators\Council_v1_0_0.cs, and Sentinel\Lab\). When those change, regenerate. Related: ML spec · Flux bars spec · Process Atlas.


1 · The naming grammar

Everything keys off a scope. A scope is one chart's worth of decision context — exactly the coordinate a model is defined over.

GC  .  212201v12x48  @  STB24FCA
─┬─    ─────┬──────     ───┬────
inst      bartag          lane
└──────── scope ──────────┘        scope = inst . bartag [ @lane ]
TokenExampleMeaning
instGCMaster instrument (the NT MasterInstrument.Name).
bartag212201v12x48The bar construction = <BarsPeriodTypeId>v<Value>x<Value2>.
@lane@STB24FCAOptional human-named per-chart lane. Absent ⇒ bare scope. Sanitized to letters/digits.

Why bartag and not "5-minute": bar granularity determines label optimism. A Renko-labelled row and a minute-labelled row do not describe the same world, so the Lab never pools bartypes — the bartag is a hard partition key, not a cosmetic tag.

Two IDs stitch a single fire's records together across files:

KeyExampleGrammarRole
episodeIdGC-20260715-0053inst-yyyyMMdd-####Join key across corpus row ⇄ tick sidecar ⇄ Ledger.
fireId20260715T175912741_GC_L_29`ts(ms)_inst_<L\S>_<sessionSeq>`Unique per fire; also the tick-sidecar filename stem. L/S = long/short.

2 · Bar-type ID registry

The 212200–212299 block is reserved for the Sentinel bar-type family (declared in every Sentinel bars header). Those three are the ones that build a real corpus:

IdToolvNxM decodeNotes
212201SentinelTBarsv(SS/2)x(SS×2)v6x24 = Speed Setting 12The adaptive HA/Renko brick engine. Also publishes BrickState → the BRK voter.
212202SentinelTbarsCountsame speed pair (v6x24)Plain brick + ticks-to-next-brick HUD.
212203SentinelFluxv8 = imbalance threshold scaleOrder-flow imbalance bars (López de Prado × TBars). Publishes FluxState → the FLUX voter.
212204+(reserved)Future Sentinel bars.

Other IDs seen in older corpus files (present for completeness — these are not the Sentinel family and several are frozen/legacy):

IdToolSource
0Tick (native NT)0v150x1 = a 150-tick chart (Value=ticks, Value2=1).
2016ERP_Type_BarsBarsTypes\ERP_Type_Bars.cs
54321EdsRetraceBarsV2BarsTypes\EdsRetraceBarsV2.cs
2018ReversalRenkoBarsTypes\ReversalRenko.cs
2085RenkoATRBarsTypes\RenkoATRBarsType.cs
69696 / 69697TBarsElse / TbarsCount (legacy)pre-Sentinel TBars lineage
69700–69799TBarsNext familyreserved block
212121–212123TbarsSudo v1/2/3frozen checkpoints

3 · The dataset families

All live under Sentinel\Excursions\. One writer, one schema per folder — the Council recorder (SentinelExcursionRecorder_v2_0_0) owns the council\ tree; the Candidate recorder (SentinelCandidateRecorder_v1_0_0) owns the candidates\ tree; legacy per-sensor recorders default OFF.

PathFamilyFilename grammarWritten when
council\1.4\Labeled COUNCIL corpus (schema 1.4; frozen 1.3 beside it)<sessionStart>__<inst>__<bartag[@lane]>.jsonlOne row per Council fire, streamed on 60-min window completion (endReason="window").
council\ticks\Raw-tick path (ctick.2; ctick.1 frozen)<fireId>.jsonl = `<ts_ms>_<inst>_<L\S>_<seq>.jsonl`One file per fire: header line + tick tape, fire→resolution.
candidates\cand.1\CLOCK-EDGE candidate corpus (schema cand.1, signal CONT) — see §5b<sessionStart>__<inst>__<bartag[@lane]>.jsonlOne row per brick close (every-brick continuation candidate), streamed on 60-min window completion.
candidates\ticks\Candidate raw-tick path (ctick.2, kind candidate_tickpath)<fireId>.jsonlOne file per candidate: header + tick tape.
Excursions\ rootLegacy per-sensor baselines<sessionStart>__<inst>__<bartag>.jsonlCBRK (CompressionBase) first-touch baseline; default off elsewhere.

⚠ Never pool council\ and candidates\. They answer different questions: the Council corpus is a fusion-gated sample ("the Council chose to fire"); the candidate corpus is the unfiltered clock-native population ("every brick close") — the only one that can measure whether the CLOCK itself carries a base rate. The signal field (COUNCIL vs CONT) and source (council vs candidate in the DB) keep them apart.

Filename anatomy (corpus): 20260715T090945__GC__212201v12x48@STB24FCA.jsonl


4 · Schema-1.3 corpus row

One row = one Council fire and everything that happened to it. Fields group into four blocks.

4a · Identity / keying

FieldExampleMeaning
schema"1.4"Row schema version. 1.4 = 1.3 + the provenance block (recVer/coreVer/barLabel). 1.4 rows live in council\1.4\; the frozen pre-provenance 1.3 corpus stays in council\1.3\. Never pool schemas across a fit.
recVer / coreVer / cnclVer"2.1.5" / "1.36.0" / "1.8.0"PROVENANCE (schema ≥ 1.4). Which recorder + SentinelCore + Council LOGIC wrote the row, so a logic change can't pool old/new rows invisibly. cnclVer is finer than coreVer (it catches a Council-only change that didn't bump Core). A null/absent coreVer = pre-provenance (old logic); a null cnclVer on a 1.4 row = pre-v2.1.5.
barLabel"SentinelFlux 8"Human bartag (schema ≥ 1.4) — the display name of bartype, denormalized for readers. Machine key stays bartype; see §2.
kind"excursion"Discriminator (ingesters skip non-excursion lines).
signal"COUNCIL"Which decision source emitted it. The Lab fits only COUNCIL rows.
inst / bartypeGC / 212201v12x48@STB24FCAPartition keys (bartype carries @lane). The machine key — never changed; render for humans via SentinelCore.FriendlyBartag/FriendlyScopebarLabel.
episodeIdGC-20260715-0053Join key to the Ledger. NB: per-episode, not per-fire (a fire↔sidecar 1:1 join uses inst+fireTime+firePx+dir).
fireTime / firePx2026-…T16:25:21Z / 4043Entry timestamp (UTC) + entry price. ⚠ Realtime-only rule (§9): a fireTime far in the past = a REPLAY leak — the corpus_probe reports these as stale_dated_rows.

4b · The decision — what the Council said at fire

FieldExampleMeaning
dir-1Side: +1 long, -1 short, 0 flat.
conviction0.5976Signed-agreement magnitude, 0–1. `=netScore/ denom`.
convBucket"MID"LOW / MID / HIGH.
sizeMult0.5079Context-damped size multiplier, 0–1. 0 = vetoed / below floor.
voters / agree / disagree10 / 4 / 2Roster tally.
netScore-2.45Σ(voteᵢ × wᵢ) — the raw fusion score.
activeW4.65The kind-aware denominator conviction was divided by.
votes{}{EYE:0, TRND:-1, …}Per-voter direction -1/0/+1. The Phase-2 feature vector.
voteW{}{EYE:1.4, CCI:1.2, …}Per-voter weight at fire (Roster.conf / F6 override of the catalog default).
reasonsEYE~ TRND▼ … denom 4.1/7.8Human audit string (see §6).

4c · Context modulators at fire

FieldMeaning
regime / adxTrend-vs-chop label + ADX value.
clockPhase0 closed · 1 opendrive · 2 midday · 3 close.
rvolTime-normalized relative volume.
mtfBiasHigher-timeframe ladder consensus -1/0/+1.
levelInPath / levelNameA structural level sits in the trade's path (headwind).
eyeHad / eyeScore / eyeDir / eyeAlignedEye-specific legacy fields (may be null).

4d · The OUTCOME / label — what happened after (all excursions in ticks)

FieldMeaning
maxMFE / maxMAEMax favorable / adverse excursion over the window.
barsToMFE / barsToMAE · msToMFE / msToMAEWhen those maxima occurred.
mfe1 mae1 … mfe60 mae60MFE/MAE at the 1 / 5 / 15 / 60-min milestones.
barrierTicksThe ATR-scaled ±1R barrier used for first-touch.
barsToTargetR / barsToStopRBars to touch +1R / -1R (-1 = never).
firstTouchTHE label: +1 target hit first · -1 stop first · 0 neither by window end.
ftAmbigBoth barriers touched the same bar (ambiguous — resolved pessimistically to loss).
endReason / endTime"window" = clean 60-min completion.

5 · Tick sidecar (ctick.2)

A two-part file, one per fire. Line 1 = header (join keys + tick-resolution outcome), lines 2…N = the path. (ctick.2 = ctick.1 + the recVer/coreVer/barLabel provenance block, matching the row schema-1.4 bump; ctick.1 sidecars stay valid and load unchanged.)

Header fields of note: recVer/coreVer/barLabel (provenance, ctick.2) · fireId · episodeId · scope · firePx · conviction/sizeMult · barrierTicks · maxFavTicks/maxAdvTicks · msToMaxFav/msToMaxAdv · msToTargetR/msToStopR · firstTouchTick / ftAmbigTick · ticks (count) · trunc (buffer overflowed).

Path rows: {"ms": <ms since fireTime>, "px": <last-trade price>}.

The row's firstTouch (bar resolution) and the sidecar's firstTouchTick (tick resolution) are the same label at two fidelities. The Lab prefers firstTouch; the tick path is for grading fill quality and msToFirstTouch.


5b · The CLOCK-EDGE candidate corpus (cand.1)

The second oven (2026-07-17; hypothesis in SENTINEL_CLOCK_EDGE_HYPOTHESIS.md). Tests whether the sampling clock, not the fused voters, carries the edge. SentinelCandidateRecorder_v1_0_0 fires one candidate per brick close — direction = the brick's own direction (the simplest CONTINUATION primitive), no gate, no fusion. Same fidelity spine as the Council recorder (realtime-gated, provenance-stamped, tick sidecar, window-streamed). Derive rule × regime × exit labels late; de-overlap in the Lab (adjacent candidates autocorrelate — use effective-N, not raw count).

Row = schema cand.1, kind:"candidate", signal:"CONT". Shares the Council row's IDENTITY block (recVer/coreVer/barLabel/inst/bartype/fireTime/firePx) and the entire OUTCOME/label block (§4d: maxMFE/maxMAE, milestones, barrierTicks, barsToTargetR/StopR, firstTouch, endReason). It has no vote vector / conviction / episodeId (there is no fusion). It ADDS:

FieldMeaning
runLengthConsecutive same-direction bricks incl. this one → exhaustion is a retroactive slice (high runLength).
regime / adxComputed locally by the recorder.
rvol / volZ / climax / dryUpParticipation seam context at fire (null if Participation not on the chart).
clockPhase / minsToCloseClock seam (instrument-keyed).
mtfBiasMTF ladder consensus -1/0/+1.
fluxDir / fluxPressure / fluxDivergFlux seam (present on Flux charts — the Flux bars type publishes it; null/0 on TBars).

All context is consulted, never fused — each field is a Lab-side retroactive filter, never a hardcoded gate.

Sidecar = candidates\ticks\<fireId>.jsonl, kind:"candidate_tickpath" (schema ctick.2) — same shape as §5 but carries runLength and no conviction/episodeId. Row↔sidecar join on inst+bartype+fireTime (exactly one brick closes per bar time). See §9b for the DB columns; the Lab reads source='candidate'.


6 · Decoding the reasons audit string

Built in Council_v1_0_0.BuildReasons(). Example:

EYE~ TRND▼ CCI▼ ADX▼ ENV▼ BRK▲ CMP~ IMKT▲ WAE~ GREV~ STF▼* · clk:Midday · vol×1.3 · roster 10/10 ?STF · denom 4.1/7.8
TokenMeaning
TAG▲ / TAG▼ / TAG~Voter direction: bullish (Dir>0) / bearish (Dir<0) / neutral (Dir=0).
TAG*Heard but NOT countedw=0 explorer, or undeclared (drift).
· clk:<phase>Clock phase (Closed/OpenDrive/Midday/Close).
· vol×NRVOL size multiplier.
· vs-MTFHigher-timeframe ladder disagrees with the side.
· into <level>A structural level is in the path.
· chop NSTF chop reading triggered the chop veto.
· in-value · · hi-volIn value-area · high-volatility regime.
· +flowDiv / -flowDiv · · flux:absorb / · flux▲/▼Flow-divergence / Flux absorption or flow direction.
· roster P/DPresent of Declared voters.
⚠TAG,…Declared but MISSING (crashed/stale/not loaded).
?TAG,…Present but UNEXPECTED (loaded, not in Roster.conf → excluded from fusion).
· denom X/YEffective kind-aware denominator X vs static declared weight Y. `conviction =netScore/X`.

7 · The voter catalog

The source of truth is SentinelCore.SystemBuilder.VoterCatalog, which the Council emits on load to the shared file Sentinel\Models\catalog.conf (tag|role|kind|defWeight|display|seam, one line each). The Python Lab reads that file, so both sides build the fit's feature columns from the same map — no more hardcoded voter list drifting behind the Council. Weights below are catalog DEFAULTS — a scope's Roster.conf (and the Council's F6) override them (e.g. live GC runs CCI at 1.2, not 0.8), and the Lab's fit baseline uses the recorded voteW{} per scope, not these defaults. KIND sets how a voter counts toward the conviction denominator: STATE always dilutes; TRIGGER dilutes only when it fired or is absent (a quiet trigger is absence of evidence, not evidence against).

7a · Weighted voters (the Roster.conf surface)

TagDisplayKindDef wSeamNote
EYEEyeTrigger1.4EyeVerdictGodTrades qualifier — strongest single voice.
TRNDSentinelTrendState1.0TrendStateStructural trailing-line trend.
CCIWoodies CCIState0.8CciStateWoodies CCI bias (×1.5 strong).
ADXADX ProState0.6AdxStateRegime/strength confirmer.
ENVVol EnvelopeState0.6EnvelopeStateTrend regime + drives the squeeze modulator.
BRKBrickState0.5BrickStateAdaptive brick micro-trend (from the SentinelTBars bar type).
CMPCompressionTrigger0.7CompressionStateHeld breakout off a compression base.
IMKTIntermarketState0.6IntermarketStateCorrelated-instrument lean (instrument-keyed).
WAEWAETrigger0.7WaeStateWaddah-Attar momentum-explosion breakout.
GREVGod ReversalTrigger0.9GodReversalStateCandle-grammar reversal (mean-reversion voice).
STFStoch FilterState0.0StfStateGaussian midline slope. w=0 = exploration; drives the chop veto.
FLOWFlowState0.9FlowStateTick-rule CVD regime — not price-derived.
STRCStructureState0.7StructureStateSwing HH/HL·LH/LL structure.
EXHExhaustionTrigger0.5ExhaustionStateLeledc reversal (mean-reversion).
AVMAADXVMAState0.6AdxvmaStateADX-vol adaptive-MA trinary trend.
SPRTSuperTrendState0.7SuperTrendStateATR-band trailing flip (always ±1).
PSARParabolic SARState0.5SarStateWilder SAR trend/stop (always ±1).
ZSCZ-ScoreTrigger0.4ZScoreState(Close−SMA)/σ fade voice.
ARCHTrend ArchitectState0.7TrendArchitectStateComposite PRISM trend + regime gate.
VDYAVIDYAState0.5VidyaStateChande-CMO adaptive-MA trend.
HARMHarmonicTrigger0.4HarmonicStateXABCD pattern completions (reversal).
FLUXFluxState0.7FluxStateNet order-flow direction of the imbalance bar (from the SentinelFlux bar type).

7b · Context axes (consulted, NOT in Roster.conf — toggled via the Council's Consult* settings)

TagDisplayRoleSeamNote
CLOCKClockModulatorClockStateSession phase / mins-to-close (instrument-keyed).
PARTICParticipationModulatorParticipationStateRVOL + climax/dry-up (scope-keyed — load on every Council chart).
MTFMTFModulatorMtfStateHTF consensus ladder (counter-HTF penalty).
LOCLocationVetoLevelStateVWAP/PDH-PDL/OR/IB levels in the path.
LIQLiquidityVetoLiquidityStateOrder-flow absorption walls.

8 · How train.py consumes it

Sentinel\Lab\train.py — the offline fitter. Nothing here touches bin\Custom; it reads the corpus and emits Sentinel\Model.candidate.conf (gated to Model.conf only with --promote).

python train.py --inst GC --bartype 212201v6x24 --barrier 20 --cost 1.5

Pipeline (dataset.pylabels.pytrain.py):

  1. Loadload_jsonl(council\<schema>\) reads every row where kind=="excursion", then filters by inst + bartype. Never pools bartypes (§1). council_rows keeps signal=="COUNCIL".
  2. Labelmake_labels: prefers firstTouch (+1→WIN, -1→LOSS, 0→CENSORED); for old 1.2 rows falls back to the pessimistic mfeN/maeN milestone. Censored rows are dropped from the fit.
  3. Uniqueness weightuniqueness_weights (AFML ch. 4): two fires 90 s apart share most of their forward window and are not independent. effective_n = Σ(avg uniqueness) is the N you may quote.
  4. Fold by direction — the model feature is x_i = voteᵢ × dir: "did the voter agree with the taken side." Halves the feature space and makes a fitted coefficient directly comparable to WeightEye — it drops straight into Model.conf.
  5. Phase 1 — calibration (needs only conviction, so schema 1.2 is enough): Platt-scale conviction → P(win) under purged walk-forward CV; expectancy_floor = the lowest conviction whose calibrated win-prob clears breakeven after cost → the learned ConvictionFloor (replaces the hand-set 0.20). nan = "no conviction level pays" — a real answer.
  6. Phase 2 — weights (needs ≥150 rows carrying the votes{} vector): the voter set is data-driven per fitactive_voter_tags admits the catalog voters this bartype actually records above a support threshold (max(30, 5% of vector rows)) with ≥1 non-neutral vote, in catalog order. Rosters range 2→18 voters across bartypes, so a fixed list would drop real voters (e.g. STF) or fit all-zero columns; under-supported and undeclared tags are reported, not silently dropped. Then ridge-logistic on the folded features + orthogonal axes (clockPhase, log_rvol, mtfBias×dir, levelInPath, activeW, voters); grid over C; OOF AUC vs the recorded-voteW baseline. Fitted w.TAG values rescaled to the current weight total (conviction is scale-invariant) → the learned weights.
  7. Emit + gate — writes status=ok|reject (reject if no profitable floor, or the fit didn't beat the hand weights). --promote refuses to overwrite the live Model.conf on reject.

Which fields feed which stage

Field(s)Consumed by
inst, bartypePartition (never pooled).
firstTouch / mfeN,maeN,barrierTicksLabel (y).
fireTime + resolution timeUniqueness weight / purged CV windows.
conviction (or recomputed netScore/activeW)Phase 1 calibration + the floor.
votes{}, dirPhase 2 folded feature vector.
clockPhase, rvol, mtfBias, levelInPath, activeW, votersPhase 2 orthogonal-axis features.
votes{} keysData-driven voter set (which w.TAG columns the fit builds).
voteW{}Per-scope baseline weight (median) + compared against the fitted w.TAG.

9 · Quick rules

9a · Proving the corpus is trustworthy — corpus_probe.py

Lab\health\corpus_probe.py (read-only, mirrors probe.py) is the "prove the recording" surface — it never opens the corpus at record time, so nothing else validates it. Run python corpus_probe.py (or --loop 300). It writes corpus_integrity / corpus_folder / corpus_events to sentinel.db (Grafana-readable) with: reconciliation (Ledger fires ↔ excursion rows ↔ tick sidecars, joined on inst+fireTime+firePx+dir; a recon_gap, not pass/fail) · schema hygiene (mixed-schema / unexpected-for-folder, rows_13 vs rows_14) · provenance coverage (prov_coverage_pct + distinct coreVers — a silent logic change shows as two versions in the pool) · silent-loss (trunc sidecars, malformed lines) · stale_dated_rows (the realtime-leak detector). First live run (2026-07-16) flagged ~7,671 stale-dated rows (~83% of the 1.3 corpus, oldest fireTime 2025-12-09) — a replay/backfill leak that had been invisible.

9b · The DB warehouse (sentinel.db) columns

ingest.py reads council\1.3\ + council\1.4\, candidates\cand.1\, and the ticks/council\ticks\/ candidates\ticks\ sidecars. The trades table carries a source discriminator (council | candidate | manual), provenance (rec_ver, core_ver, bar_label, scope) + the context that used to be dropped (regime, adx, conv_bucket, agree, disagree, voters, end_reason, milestone curves + barsTo* in one milestones_json blob). Candidate columns (source='candidate'; NULL for council/manual): run_length, rvol, vol_z, climax, dry_up, clock_phase, mins_to_close, mtf_bias, flux_dir, flux_pressure, flux_diverg. Candidate row↔sidecar reconcile on inst+bartype+entry_utc (mirrors the council row folder; no vote vector). Indexed on (inst,bartype), episode_id, src, schema, bartype, entry_utc. The ingester prints a drop tally by reason (silent loss made visible). train.py reads the JSONL directly, not the DB — the DB is the derived warehouse (see SENTINEL_DB_MIGRATION_SPEC).