Sentinel Suite · NinjaTrader 8 · living documentDocs home →
Status: ✅ BUILT & LIVE-VALIDATED (2026-07-14) · Author: Sentinel Suite · Date: 2026-07-14 Last re-certified against the source: 2026-08-10 — §3, §5 and §8 corrected where they described knobs and an ATR clamp the shipped bars type does not have. §8 is now split into the one F6 param and the internal constants. Artifact: BarsTypes\SentinelFlux_v1_0_0.cs · class SentinelFlux_v1_0_0 · display "SentinelFlux v1.0.0" BarsPeriodType id: 212203 (reserved Sentinel bars block 212200–212299) Seam: SentinelCore.FluxState (v1.31.0) → the FLUX voter in the Council (v1.6.3, now 22 voters) + a FluxAbsorbDamp absorption modulator.
Built from scratch as an order-flow imbalance bar type (NOT a fork of TBars) — it borrows only the TBars discipline (quote-rule signing, ATR/price/time/tick backstops, HA render, deterministic latch). Live-validated on a full 10/10 Council roster on
GC.212203v8; two threshold fixes shipped same-day (canonicalθ* = fluxScale × E[|θ|]+ winsorization). Grading vs TBars is pre-registered as EXP-0004.
One line: a bar closes when accumulated signed order-flow imbalance crosses an adaptive threshold — López de Prado's information-driven bars — stabilized by the TBars discipline (self-consistent threshold + ATR-price/time/tick backstops + HA bodies + deterministic per-session latch) so it can never hit the runaway failure mode that makes naïve imbalance bars unusable.
This is not a novelty bar. It is aimed directly at two documented, load-bearing weaknesses of the Sentinel learning loop.
1a. The orthogonality gap. The Council's own caveat, repeated across the notes: "today's voters are nearly all PRICE-DERIVED (ADX/CCI/Trend/Envelope/Brick echo the same OHLC), so conviction = agreement, NOT confirmation." Every renko family in the tree (Renko, TBars, BetterRenko, WickedRenko, RenkoATR) reduces the market to price geometry — where price went. None encode how much it cost to get there. A bar whose clock is signed order flow does two things nothing else in the suite does:
FluxState) → a genuinely orthogonal Council voter sourced from the aggregated tape, complementary to LiquidityState (sourced from the book/absorption).1b. The label-fidelity gap. The Council trains on first-touch triple-barrier labels measured in bars ([[sentinel-ml-lab]], schema 1.3). Time/tick/range bars carry wildly unequal information per bar, so a horizon of "N bars" is a fuzzy blend of dead-market bars and event bars — the label's clock drifts. Information-driven bars are constructed so each bar carries ≈ constant information; "N bars ahead" ≈ "constant information ahead." This sharpens the exact label the ConvictionFloor and per-bar-type weight fit consume. This is López de Prado's founding argument for these bars (AFML ch. 2), and it lands 1:1 on the Sentinel rig.
1c. It costs nothing to test. A bar type is just a scope tag (GC.212203v…). The recorder, ingester, Lab\compare_bartypes.py, train.py, and council_paths.py already separate and grade any scope. SentinelFlux slots into the measurement loop with zero new plumbing (see §9).
López de Prado, Advances in Financial Machine Learning, ch. 2 — information-driven bars. Sign each trade with the tick rule b_t = b_{t-1} if Δp=0 else sign(Δp) ∈ {−1,+1}, accumulate a signed imbalance, and close a bar when the imbalance exceeds its expectation. Three flavours by what is accumulated:
| Flavour | Accumulator θ_T | Samples |
|---|---|---|
| Tick Imbalance Bars (TIB) | Σ b_t | net trade-direction pressure |
| Volume Imbalance Bars (VIB) | Σ b_t·v_t | net contract pressure (default here) |
| Dollar Imbalance Bars (DIB) | Σ b_t·v_t·p_t | net notional pressure (cross-contract stable) |
Bar closes when |θ_T| ≥ E₀[T]·|E[b·v]| (VIB form), where E₀[T] is the expected bar length and E[b·v] the expected signed volume per tick — both estimated online by EWMA.
The known failure mode (why a naïve port is a bad bar type). Both EWMAs are fed back from realized bars. In a strong trend, P[buy]→1 so |E[b·v]| inflates and bars lengthen, so the threshold explodes → one gigantic bar swallows the move. In a balanced chop, E[b·v]→0 so the threshold collapses → bars fire every tick. LdP flags this instability explicitly; it is the reason imbalance bars are famous in theory and rare in production.
The Sentinel move: we already own the cure. TBars solved the analogous renko instability with an ATR floor, a density controller with Min/Max rails, and forced time-bricks. We transplant that exact stabilization layer onto the imbalance clock (§5). That transplant — a theoretically-superior sampling clock hardened by a proven adaptive-brick discipline — is the hybrid.
SentinelFlux is a hybrid on three independent axes:
θ* tracks the tape's own realized E[|θ|], and the TBars-style force-close rails (price / time / tick) guarantee termination.⚠ Axis 2 read "ATR-clamped" until 2026-08-10 and that has been false since the 2026-07-14 hotfix, which removed the ATR term from
θ*(§5). ATR now drives only the price backstop. The wording survived because the hotfix was written into §5 where the formula lives, and the one-line summary up here was never re-read against it — a correction applied at the point of detail leaves every summary of that detail stale.
Plus a microstructure upgrade the tick rule can't do (§4).
OnDataPoint(... bid, ask ...) gives us the prevailing quotes per trade, so we sign better than the tick rule:
Lee–Ready / quote rule (primary):
if close >= ask → b = +1 (buyer-initiated / lifted the offer)
if close <= bid → b = −1 (seller-initiated / hit the bid)
else → b = tick-rule fallback (sign of Δp; carry prior sign on Δp==0)
Fallback to the pure tick rule whenever bid<=0 || ask<=0 (historical tick data without quotes) — keeps it deterministic and reproducible on reload (critical for the corpus; the as-of / lookahead lesson [[seam-scope-migration]]).
Signed contribution per trade: Δθ = b · w, where w = 1 (TIB), w = volume (VIB, default), w = volume·price (DIB).
State per forming bar: θ (signed imbalance), buyVol, sellVol, nTicks, barOpen, running H/L, birthTime.
The threshold (as SHIPPED, updated once per CLOSED bar — never per tick, for determinism):
imbEwma = EWMA(IntensityLen) of realized |θ| at each close // E[|θ|] — the expected imbalance per bar
θ* = max(1, fluxScale · imbEwma) // fluxScale = FluxSize / 8 (the one knob)
This is the canonical López de Prado imbalance-bar rule: a bar closes when accumulated imbalance reaches its expectation. It is self-consistent — bars close at θ\, so imbEwma EWMAs toward θ\ and the target is stable; bars that close early on a backstop carry |θ| < θ* and pull it down, which makes imbalance the primary close reason (self-correcting). There is no ATR term in θ\* — ATR drives only the price backstop (below).
⚠ Hotfix (2026-07-14, first live GC load). The initial build used
θ* = fluxScale · atrTicks · (|θ| / net-displacement). ATR is a true-range measure (includes wicks) while the intensity divided by net displacement (close−open); in chop both factors inflate together, so θ\ ballooned to ~2.5× realized |θ| and every realtime bar closed on the 90 s TIME backstop (θ ≈ 38 vs θ\ ≈ 90 — the imbalance clock was dormant). Replaced with the self-consistentE[|θ|]form above. Found by reading the per-bar close-reason log, not by reasoning — the pattern to repeat.
Close condition (per tick):
CLOSE the bar when ANY of:
(a) |θ| ≥ θ* // imbalance target hit (the information event)
(b) price displacement ≥ PriceBackstopMult · atr // hard price backstop (prevents the "one giant bar")
(c) (now − birthTime) ≥ ForceStagnationSecs // time backstop (prevents "never closes in a dead tape")
(d) nTicks ≥ MaxTicksPerBar // tick backstop (belt-and-suspenders)
On close: brick flow-direction = sign(θ); price-direction = sign(close − barOpen). These can DIVERGE — that divergence is absorption and is surfaced natively (§7). Then update imbEwma (winsorized) and atr, roll HA bodies, seed the next bar.
⚠ Corrected 2026-08-10: this line named
expT/expImb, neither of which exists in the shipped bars type — they are TBars-lineage names that were carried over when this section was drafted from the TBars spec. The only per-close estimators SentinelFlux maintains areimbEwma(theE[|θ|]EWMA, winsorized atWinsorMult) and the brickatr.
⛔ Micro-split and quiet hours are NOT implemented. This section previously read "inherited from TBars unchanged (optional)"; there is no such code and no such switch in
SentinelFlux_v1_0_0.cs— see the §8 note. Treat them as unbuilt, not as defaulted-on behaviour.
Why this is stable: the EWMA gives the information-adaptive behaviour (bars sync to surprise); θ* is bounded below by the max(1, …) floor and pulled toward realized |θ| from above, so it can neither explode nor collapse; the three backstops guarantee termination. ⚠ ATR is not part of that bound — it scales only the price backstop (b). The bar always closes for a legible reason, logged in BrickLog (reason ∈ {imb, price, time, tick}) so the corpus can audit why each bar formed — itself a feature.
Same discipline as SentinelTBars ([[sentinel-tbars-tool]]):
BuiltFrom = Tick (need true tick timestamps + quotes).Core.Globals.Now − time > 5 min → skip) so a historical rebuild never stamps a stale FluxState as fresh (the as-of guard — [[state-vs-trigger-voters]], [[seam-scope-migration]]).SentinelCore.ScopeOf(bars.Instrument, bars.BarsPeriod) → two GC charts on different Flux params never clobber each other (the scope lesson).SentinelCore.FluxState (the orthogonal voter)Publish per closed bar (+ live countdown per tick, like BrickState). Proposed FluxState fields:
| Field | Meaning |
|---|---|
FlowDir (int −1/0/+1) | sign(θ) of the just-closed bar (net order-flow direction) |
PriceDir (int) | sign(close−open) — for divergence detection |
Pressure (double 0..1) | buyVol / (buyVol+sellVol) — the buy/sell balance |
Theta, Threshold (double) | live accumulator vs current θ* (→ PercentToClose) |
Cvd (double) | running cumulative volume delta (session) — a bonus tape read |
Divergence (int) | 1 when FlowDir opposes PriceDir with meaningful θ (absorption) |
+ scope / instr / atrTicks / UpdatedUtc | standard seam envelope |
Council wiring ([[council]], [[state-vs-trigger-voters]]):
FlowDir. Its whole value is that it does not echo the OHLC bloc.Divergence==1 against the intended side (flow absorbing the move you'd take), damp SizeMult (a soft veto), mirroring the LiquidityWalls wall veto but from aggregated tape rather than the book. Add to the Council Reasons audit (flux:absorb).FLUX; w=0 remains the exploration primitive.This satisfies the standing protocol (design-system §9): every new signal indicator/bar must publish a …State seam, default publish ON, and be wired into the Council with a Reasons line.
🔴 CORRECTED 2026-08-10 — this section listed ELEVEN F6 params and the bars type exposes exactly ONE. The table was written as a design intent and read ever after as a description of the shipped grid. Four of the eleven (
EnableMicroSplit,EnableQuietHours,PublishFluxState,ShowIndicatorLabel) do not exist in the source in any form; the rest exist only as private fields with no F6 surface. Verified againstSentinelFlux_v1_0_0.csby name. ⭐ Why this mattered more than a doc nit: an operator reading it would look for knobs that are not there, and a sweep designed against it would vary parameters that cannot be varied — producing a corpus whose scopes differ only by accident.
| Param | Default | Role |
|---|---|---|
Flux Size (= BaseBarsPeriodValue, renamed in SetDefaults) | 8 | the one knob — fluxScale = FluxSize / FluxRefSize(8) sets the INFORMATION per bar (raise to coarsen) |
Value and Value2 are removed from the grid (SafeRemoveProperty). Value is still written behind the scenes — set to the size, with Value2 = 0 — for one reason only: so the scope tag separates a sweep (GC.212203v8 vs v12) automatically. ⚠ That Value2 = 0 is also what keeps Flux out of the TBars Speed signature (Value2 == 4 × Value) in SentinelCore.FriendlyBartag and its Python mirror, so a Flux tag renders as SentinelFlux 8 — a SIZE, never a "Speed" and never a threshold.
There is intentionally one primary "size" feel, exposed the way TBars exposes Speed Settings: Flux Size sets how much information a bar represents (θ\* self-tunes to the tape via E[|θ|]). The size-sweep axis for EXP-0004 (§9) is Flux Size.
These are private fields in the bars type. The values below are read from the shipped source and are correct; what was wrong is the claim that a human can set them. Changing one is a code edit and a version bump, not an F6 change.
⛔ Writing convention in this table, and it is load-bearing:
absθbelow means|θ|. A pipe cannot appear inside a cell on this page —renderers.confassigns this doc to md2atlas, and md2atlas splits table rows on|before honouring the\|escape, so an escaped pipe still explodes the cell into extra columns. Measured 2026-08-10: the first draft of this very table renderedIntensityLenas seven columns. This is the concrete form of the known md2atlas defect already recorded againstSENTINEL_DESIGN_SYSTEM. Check the rendered HTML, not the markdown.
| Field | Value | Role |
|---|---|---|
_mode | ImbMode.Volume (VIB) | Tick / Volume / Dollar accumulator — the enum exists, the selector does not |
_useQuoteRule | true | Lee–Ready quote rule, tick-rule fallback |
IntensityLen | 50 | EWMA length for E[absθ] (the self-consistent close threshold θ* = max(1, fluxScale × E[absθ])) |
WinsorMult | 4.0 | block-trade guard: cap a bar's E[absθ] contribution at × the running estimate |
DivergenceFrac | 0.5 | absθ ≥ this × θ* AND flow≠price ⇒ divergence (absorption). Was missing from this section entirely |
AtrLength | 14 | per-brick ATR EMA — feeds the price backstop ONLY, not θ* |
PriceBackstopMult | 2.5 | force-close at ×ATR price displacement |
ForceStagnationSecs | 90 | time backstop. ⚠ documented here as ForceStagnationSeconds; the real identifier is …Secs |
MaxTicksPerBar | 5000 | tick backstop |
RealtimePublishMinutes | 5.0 (const) | the real seam gate — FluxState publishes only for near-realtime bars. There is no PublishFluxState switch |
FluxLogThrottleSeconds | 10.0 (const) | human-log throttle |
⛔ EnableMicroSplit · EnableQuietHours · PublishFluxState · ShowIndicatorLabel DO NOT EXIST. The first two are unbuilt (§5); the third is superseded by RealtimePublishMinutes; the fourth is the Sentinel indicator label-remover standard and does not apply to a bars type, whose on-chart name comes from ChartLabel().
Pre-registered as EXP-0004 (EXP-0002/0003 established TBars 6/24 as the edge-bearing bar via the same rig [[continue-here-2026-07-12b]]). Run SentinelFlux side-by-side with SentinelTBars on GC; scope tags separate them automatically; bake clean schema-1.3 Council rows on a multi-voter chart; then grade.
H1 — sharper labels. Conviction becomes more predictive of first-touch on Flux bars than on TBars. → AUC(conviction → firstTouch) on GC.212203v… > on GC.212201v6x24, and materially above the ~0.48 baseline. Tool: train.py --inst GC --bartype 212203vXxY --barrier 20 --cost 1.5 --spend-holdout.
H2 — orthogonality is real. The FLUX voter does not echo the price bloc. → From the recorded schema-1.3 vote vector (§2.1), |corr(FLUX, {TRND,CCI,ADX,ENV})| < 0.5. Tool: council_paths.py --scope GC.212203v… (vote-vector join already implemented).
H3 — fill fidelity holds. Flux bars are finer during fast moves (imbalance fires quickly when one side dominates), so stop/target first-touch fill fidelity ≥ TBars' proven ~99.5% ([[corpus-hygiene-and-fill-fidelity]]). Tool: the tick-true recorder sidecars (council\ticks\) already measure first-touch at tick resolution.
H4 — path quality. Per-fire MFE/MAE ratio and favorable-dwell (from council_paths.py) are ≥ TBars at matched conviction.
KILL LINE (honest, pre-registered): if after ≥ 8 baked multi-voter sessions H1 fails (AUC ≤ TBars) AND H2 fails (corr ≥ 0.5), the orthogonality thesis is falsified for this instrument → archive SentinelFlux to _archive\, keep the finding. No nudging the params to rescue it — fit it, don't nudge it is the standing rule.
Confounds to control: (1) match the barrier in ticks not bars across the two types; (2) same sessions / same clock window; (3) require the same voter roster on both charts (else H2's correlation is measured over different voters); (4) beware the STF-only single-voter chart — conviction pins at 1.00 there (the current corpus limitation), so EXP-0004 needs the full stack.
FlowDir may differ. Gate: log the signing mode per session; compare replay==live ([[council-historical-replay]]).imbEwma is seeded are pre-convergence. As SHIPPED this is handled by an explicit warmup flag, not by seeding from ATR: while warmup is true θ* returns double.MaxValue, so the imbalance clock cannot fire and early bars close on a physical backstop instead. warmup clears once imbEwma is seeded. ⚠ Corrected 2026-08-10. This read "First ExpEwmaBars bars … seed expT/expImb from the ATR band on bar 0 (already the clamp floor)" — three phantoms in one sentence (ExpEwmaBars, expT, expImb) plus an ATR clamp floor that the 07-14 hotfix removed. The mitigation described here was never built; a different and better one was. ⏭ Still owed: mark warmup bars warmup=1 in BrickLog so the corpus can drop them — the flag exists in the engine but is not stamped on the row, so a consumer cannot currently tell a pre-convergence bar from a converged one.SignMode/Mode switch can add a runs variant later if EXP-0004 motivates it.BarsTypes\SentinelFlux_v1_0_0.cs (id 212203), borrowing only the TBars discipline (ATR/price/time/tick backstops, HA render, deterministic per-session latch, publish seam) — not the fixed-brick geometry. Class/Name/BarsPeriod in sync (naming law [[naming-federation]]).SentinelCore.FluxState seam (Core version bump) + Council FLUX voter + divergence modulator + Roster FLUX entry.nt8bridge compile --type Indicator (strip generated region first) → F5 authoritative ([[nt8-bridge-compile-loop]]).FLUX in the Roster; bake ≥ 8 sessions; run the EXP-0004 grade..html sibling of this spec ([[docs-need-html-copies]]).Cross-refs: [[sentinel-tbars-tool]] (the discipline we transplant) · [[council]] + [[state-vs-trigger-voters]] (fusion) · [[sentinel-ml-lab]] (why the label sharpens) · [[declared-roster]] · [[corpus-hygiene-and-fill-fidelity]] · [[council-historical-replay]] (the grading rig).