Holdfast Liquidity Levels — Installation & Setup Guide

Depth-driven liquidity levels for NinjaTrader 8.

Everything below takes about ten minutes. If you get stuck at any step, reply to your purchase email and include the step number.


What you received

File What it is
HoldfastLiquidityLevels.zip The indicator, ready to import into NinjaTrader
Your license key In your purchase email — looks like XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
This guide Keep it; the calibration section is worth revisiting

Do not unzip HoldfastLiquidityLevels.zip. NinjaTrader imports the zip file directly.


1. Before you install

Confirm these three things, because the indicator cannot work without them:

  1. NinjaTrader 8 — It does not run on NinjaTrader 7.

  2. A Rithmic data subscription with the full-depth MBO add-on. This is a hard requirement, not a recommendation. Holdfast reads order-by-order market-by-order data, which only the Rithmic MBO feed provides. The free Level 2 depth bundled with NinjaTrader is aggregated top-of-book and does not carry enough detail — Holdfast will show little or nothing on it. MBO is an inexpensive add-on to a Rithmic subscription. Rithmic sells depth per exchange – CME, CBOT, NYMEX and COMEX are billed separately – so make sure your subscription covers the exchange your instrument lists on.

    If you don’t have MBO yet, or you’re not sure whether what you have counts, start here: Getting Level 2 / MBO data. That page covers what MBO is and why nothing else substitutes for it, whether to buy one exchange or all four, and the two routes to it – through a prop firm like Apex that resells Rithmic data at a discount, or direct from Rithmic. It takes about ten minutes to read and will save you from paying for the wrong entitlement.

  3. Market depth enabled for your instrument. Open a SuperDOM on the instrument you plan to chart. If the ladder is filling with sizes, you have depth. If it’s empty, fix that before installing — the problem is your connection, not the indicator.


2. Install the indicator

  1. Close all charts (not required, but it avoids a restart later).
  2. In the NinjaTrader Control Center, go to Tools → Import → NinjaScript Add-On…
  3. Select HoldfastLiquidityLevels.zip.
  4. Click OK on the confirmation. NinjaTrader will import and compile.
  5. When prompted, restart NinjaTrader.

If you see a compile error on import, it almost always means an older version of Holdfast is already installed. Remove it (see Updating, below) and import again.


3. Activate your license

  1. Open any chart, right-click → Indicators…
  2. Find Holdfast Liquidity Levels in the list and click Add.
  3. In the properties on the right, find the License group at the top.
  4. Paste your license key into License key.
  5. Click OK.

Within a few seconds the indicator activates. To confirm, open New → NinjaScript Output — you’ll see a line confirming the license is active.

About your two activations. Each license runs on two machines. You don’t need to do anything special for the second one — just paste the same key. If you replace a computer, open the license page linked in your purchase email and deactivate the old machine to free the slot.

Offline behavior. Once activated, validation is cached locally. A dropped internet connection, a Rithmic outage, or a trip somewhere without WiFi will not lock you out mid-session. Holdfast re-checks periodically when it can reach the internet again.


4. Set it up for your instrument

Add the indicator to a chart of the instrument you trade. The settings that matter on day one:

Level Management

Setting What it does Start with
Primary level threshold Resting size at a price required to create a level See calibration below
Secondary level threshold Same, for the optional second contract ~75% of primary
Unproven timer (seconds) How long a new level must survive to become Holding 240 (4:00)
Holding timer (seconds) Continues from the unproven timer to Proven 180 (→ 7:00 total)
Pull ratio (% of threshold) How far size must drop before the level counts as pulled 50
Pull confirm (seconds) How long size must stay pulled before it registers 5
Clean break confirm bars Confirmed closes beyond a level before it’s removed 8
Recreate cooldown (seconds) Blocks a level from instantly re-forming at the same price 30
Max levels Cap on live levels: Unproven, Holding, Proven 50
Label detail Clean shows price, size, and the verdict; Detailed adds the live buy/sell split while a visit develops Clean
Max vacated levels How many verdict-carrying vacated levels stay on the chart; 0 removes them immediately 8
Vacated max age (seconds) A vacated level is dropped this long after price last touched it; each touch resets the clock. 0 disables 3600 (1:00:00)

When Max levels is reached, the level dropped is the least established one — unproven before holding, holding before proven, and within a tier the youngest goes first. A proven level that has held for an hour is the last thing to be given up, never the first. Vacated levels are not in this budget; Max vacated levels bounds those separately. If you see the cap binding at all, that is a signal the threshold is too low: raise the threshold rather than the cap.

A level is only drawn while it is actually holding its size. An unproven level must keep the full level threshold; once validated, a level is allowed to sag to the pull floor (Pull ratio, 50% of threshold by default) before it counts as pulled. Anything under that bar disappears from the chart immediately, without waiting for Pull confirm (seconds) to elapse or for the next depth update at that price. The level is still tracked underneath — if the size comes back, so does the line — but you will never see a level labelled (4) sitting under a threshold of 60. Vacated levels are exempt: they are records of liquidity that has already gone, and carry no size on the label at all.

Calibrating your threshold — do this once per instrument

This is the single most important setting, and the right number is different for every market. Resting size on crude looks nothing like resting size on the micro indices.

  1. Turn on Enable debug log (bottom of Level Management).

  2. Open New → NinjaScript Output.

  3. Let it run for 20–30 minutes of active session.

  4. Read the CREATE lines. Each one shows the actual resting size that triggered it:

    [HF 09:41:22.104] CREATE MNQ ask @ 28800 prim=137 sec=0 trigger=prim-feed
  5. If you’re getting far too many levels, raise the threshold above the sizes you’re seeing on ordinary prices. Too few, lower it.

  6. Turn the debug log back off when you’re done — it’s a diagnostic, not something to leave running.

A reasonable starting point is a threshold that only the top few sizes in your session would clear. You want the levels that stand out, not every price with size on it.

Calibrating everything else — what the log gives you

Turning on Enable debug log does more than print CREATE lines. Three things in it exist specifically so the rest of the settings can be measured rather than guessed.

A CONFIG block at load. Every setting that shapes the log, printed once at the top of the run, along with the instrument, tick size, and chart period:

[HF 09:38:02.001] === HOLDFAST LIQUIDITY LEVELS -- CONFIG ===
[HF 09:38:02.001] CONFIG instrument primary=MNQ secondary=NQ tickSize=0.25 period=Second15
[HF 09:38:02.002] CONFIG profile manual=False level=Micro_MNQ_Nasdaq speed=Scalp
[HF 09:38:02.002] CONFIG absorption revisitBars=20 holdBars=4 holdStartBar=1 rejectTicks=4 breakTicks=8 minRevisitVol=8

Calibration means comparing runs — across instruments, across chart periods, across settings changes — and a log that does not record the configuration that produced it cannot be compared to anything. It also catches a preset silently losing to a stale workspace value, which is otherwise invisible.

NEAR-MISS lines. A visit that reached no verdict, with all five flow measurements printed next to the gate each was tested against. -> means that gate passed, *> means it failed:

[HF 09:44:11.208] NEAR-MISS @ 20418.25 MNQ side=BID | mult=1.31->1.00 refill=44%->30%
  dom=51%*>60% size=88->40 | deplFrac=72%*><40% traded=100%->50%
  | passive=122 hit=160 B=77 S=160 departed=False

That one reads: the level had genuine refill and a healthy absorption multiple, and missed only on side dominance — 51% of the volume hit this level’s side against a 60% gate. A run full of those says Side dominance is the setting to move, and by roughly how much. Without these lines the log only ever shows values that passed their gate, which makes a threshold slightly too strict indistinguishable from one that is correct. This is what makes Absorption multiple, Refill fraction, Side dominance, Depletion fraction, and Depletion traded fraction calibratable at all.

They are throttled per price by Near-miss log interval (seconds) — an active visit re-enters the test on every tick. Lower it for a dense run, raise it for everyday use.

GATES counters. Four gates drop events without logging anything, and each can make a correctly configured indicator look dead:

[HF 09:45:02.114] GATES (last 60s) touchSettle=12 (TouchSettleSeconds=2) minDwell=41
  (FlowMinDwellSeconds=2) recreateCooldown=3 (RecreateCooldownSeconds=30) spreadPrints=207

A high minDwell means visits are being judged before they are allowed to say anything; a high touchSettle means trades are landing on levels too new to open a visit; spreadPrints counts trades that could not be attributed to a buyer or seller at all, which is a data-quality reading rather than a setting. Printed only when something was actually dropped, on the interval set by Gate counter interval (seconds).

Two more lines gained the numbers they were missing. EPISODE CLOSED now carries dwell and idleGap, so Visit idle close can be checked against how long visits actually run and what silence ended them. EXHAUSTION and DEPLETION CONFIRMED now carry the tick excursion past the level alongside the bar count, so Exhaustion reclaim ticks can be seen doing something rather than assumed.

Reading absorption verdicts. Every resolution logs its reason:

[HF 10:01:12.400] ABSORBED @ 20402.00 MNQ passive=118 B=41 S=372
  -- watching 20 bars for a revisit (min vol 8)
[HF 10:03:10.220] ABSORPTION REVISIT @ 20402.00 MNQ vol=96 (min 8) book=Swept
  -- hold window 4 bars
[HF 10:03:44.900] ABSORPTION REVISED @ 20402.00 MNQ -> FailedAbsorption
  (hold window expired 6t beyond the level (book=Swept))
[HF 10:11:02.660] ABSORPTION WATCH CLOSED @ 20388.75 MNQ
  (no qualifying revisit within 20 bars (peak 23t clear) -- stays ABSORBED)

Tallying those reason strings over a session tells you which stage is deciding your absorptions:

Mostly seeing Means Setting to look at
ABSORPTION WATCH CLOSED ... stays ABSORBED Normal — most absorptions are never revisited
REVISIT IGNORED ... not a test on revisits you would have traded Real tests are being filtered out Minimum revisit volume is too high
hold window expired ... beyond the level on moves that came straight back Breaks are being called too early Absorption hold bars, or Absorption break ticks
hold window expired at the level very often Price keeps stalling inside the buffer Absorption rejection ticks may be too wide
No ABSORPTION REVISIT lines at all Nothing is ever revisited inside the window Absorption revisit bars may be too short

The log cannot tell you whether a verdict was right — it does not record what price did afterwards — so for that, line the timestamps up against the chart.

Turn all of this off when you are done. It is a diagnostic, not something to leave running.

Session

Set Session start time and Session end time to the window you actually trade. Levels are only created inside this window, which keeps overnight book noise off your chart. Times use your NinjaTrader time zone.

Watching a second contract

To see two books on one price scale (micros with their full-size counterpart, for example):

  1. Set Monitor secondary instrument to true (it already is by default).
  2. Set Secondary instrument to the exact contract name, including the expiry – e.g. NQ 12-26, ES 12-26, GC 12-26. A bare root such as GC will not load; NinjaTrader reports symbol is inaccessible. If you have selected an instrument profile, the paired root is filled in for you (MGC fills GC) and you only append the month. Your expiry is preserved across reloads – a profile only writes the root when the field does not already point at that contract. Update it at each roll.
  3. Set Secondary level threshold for that contract’s typical resting size.

Your chart instrument always takes precedence. A secondary level that would crowd a primary one is suppressed automatically, so the level you’re actually trading is never buried. Adjust Secondary suppression (ticks) if you want a wider or tighter exclusive zone.

Attribution and upgrades. A level is labelled with whichever book qualified for it. If both books qualify at the same price, your chart instrument wins. If only the secondary book qualified at the time, the level is labelled with the secondary contract – but if the primary book later reaches its own threshold at that same price, the level is promoted to primary and reports the primary size from then on. Promotion is one-way: a level never reverts to the secondary contract, so labels cannot alternate between the two contracts.

One caveat on running multiple charts. Each chart runs its own instance of the indicator, and levels build only from live depth going forward. Two charts opened at different times have observed different books, so their level sets and labels can legitimately differ. Treat one chart as your reference for levels rather than comparing across timeframes.


5. Reading the chart — quickstart

This is the 90-second version. The deeper mechanics are in Flow states further down; you do not need them to start.

Levels are lines with a life cycle. A line appears when real resting size shows up, and firms up as it survives:

Look State Meaning
Thin, dashed, faded, * prefix Unproven Size just appeared. Not yet trusted.
Solid, medium Holding Survived validation. Being tracked.
Thick, solid, bright Proven Rested long enough to build entries around.
Verdict styling + a bubble Vacated Price tested it and it reached a verdict. Kept as a record.

A label reads 28800.00 (137) MNQ [5:12/7:00] — price, resting size, instrument, and the level’s elapsed life against its next promotion. The timer is one continuous clock from 0:00.

When price trades at a level, it earns a verdict — and only levels that reach one stay on the chart as records. Everything price merely grazes stays a plain live level; anything whose size is pulled untested is removed. There are four outcomes, and each answers the same question: what ultimately happened here?

Absorption is rendered as soon as it is measured. While price is still at the level the label reads ABSORBING, present tense; the moment the visit closes it becomes ABSORBED — a verdict, not a question. A revisit can later revise it to DEFENDED or FAILED, and the bubble is rewritten in place. Depletion works the same way, staying provisional until its reclaim window passes.

Levels under watch pulse. While a level’s revisit window is open it is the thing being waited on, so its line flashes gently rather than fading to a record. Controlled by Flash levels under watch in the Visual group.

Each verdict draws a bubble at the bar it happened: the number inside is total contracts transacted, the color split shows buyers vs sellers, and hovering gives you the resting size, the ratio, and the verdict in words. Turn on the verdict ring to color the bubble by outcome so you can read it without hovering.

Prefer just the bubbles? Turn Show level lines off (Visual group). The horizontal lines and their price labels disappear and the flow bubbles remain, giving you a clean map of where flow actually resolved. Nothing else changes – levels are still tracked, still timed, and still produce verdicts and alerts; it is purely a display choice. Each bubble still carries its own price and verdict on hover, which is where that information goes when the labels are hidden.

Each outcome also names the side responsible — successful absorption and exhaustion name the aggressors, depletion and failed absorption name the passive orders that were eaten or gave way — but you can ignore that until you want it. That is enough to use it; read on only when you want to know why a level got the verdict it did.

Flow states

Once price has traded into a level, Holdfast classifies what is measurably happening there. A level that is only grazed stays neutral and looks exactly as it did before; only a genuine, one-sided test earns a verdict.

These are descriptions, not forecasts. No probability is printed, and that is deliberate: market depth is not stored historically, so there is no way to fit outcome odds against past order books. Any percentage would be invented. What can be measured honestly is what the flow is doing right now.

How a state is decided

Every state rests on one measurement the DOM cannot show you: refill. When 340 contracts trade into a level that was showing 92, and only 20 lots of resting size actually disappear, roughly 320 were replaced as fast as they filled. That is the difference between size being defended and size being consumed, and it is invisible in a snapshot because filled-and-replenished orders leave the displayed number unchanged.

Which volume counts. Not all volume at a level tests that level. Buys consume resting offers; sells consume resting bids. Volume on the other side traded at the same price but against the opposite book – which happens when price is on the far side of the level – and says nothing about whether the level’s own liquidity was tested. Side dominance (%) is the share of volume that must have hit the level’s own side before any state is declared. Take a level that rested 48 and then saw 12 buys and 125 sells transact at its price (visible on the bubble hover, or on the label in Detailed mode): at the default 60, it is nowhere near qualifying as an ask test, because only 12 of those 137 contracts lifted its offers. Set it to 50 to ignore the split.

The flow states

Absorption is always the initial verdict. When the refill mechanics fire, that is a completed measurement — someone defended here, observed at the book. It does not wait for permission from later price action, and a great many absorptions are never revisited at all: price simply leaves and keeps going, which is a perfectly good outcome needing no further qualification.

What a revisit adds is a second, separate measurement: given that price came back, did the level hold?

Verdict Meaning Reads as
ABSORBED Refill mechanics fired across a completed visit Reversal
REVISIT — DEFENDED Price came back on real volume and the level held Reversal, confirmed
REVISIT — FAILED Price came back and the level broke Continuation
DEPLETED Aggressors consumed the resting size and held the break Continuation
EXHAUSTED Aggressors consumed the resting size but failed to hold the break Bounce

The two revisit outcomes are revisions of the base verdict, not replacements for it — same event, same participants, more known about it. That is why they share absorption’s colour; only ring thickness and dash tell them apart.

ABSORBING, in the present tense, is the live reading while price is still trading at the level. The moment the visit closes it becomes ABSORBED. There is no longer an ABSORBED? state — a closed absorbing visit is an absorption, not a question about one.

Why absorption has three outcomes

The original absorption read fired on refill mechanics alone. That is a real measurement, but on its own it invited a specific misread: price bounces at a defended level, and most of the time it comes back and breaks through anyway. The base verdict was being traded as a reversal signal when the common sequence was defend → bounce → return → break.

Splitting the revisit out fixes that without weakening the base verdict:

level tested
  → absorption conditions met                  → ABSORBED  (verdict rendered now)
     revisit window opens
  → window closes with no qualifying revisit   → stays ABSORBED
  → price revisits (bar reaches the level AND
    minimum revisit volume transacts)
       hold window opens
       → closes back on the defended side      → REVISIT - DEFENDED
       → closes beyond it, or the break-tick
         override fires                        → REVISIT - FAILED
       → window expires with price still at
         the level, inside the buffer          → stays ABSORBED

A revisit has to clear two bars, because either one alone is gameable. Bar geometry has to reach the level — which a print-based test can miss entirely when price slices straight through — and real volume has to transact. Price drifting back on three contracts is not a test of anything, and without the volume gate it would decide a verdict.

Price decides held-or-broken; the book decides what it cost. A level that gets swept and reclaimed still held — that is the whole point of the DEFENDED case. So the book reading never overrides price; it appears as a qualifier instead:

Revisit book reading Label
Absorption fired again SELLERS ABSORBED, DEFENDED (REFILLED)
The visit depleted the size SELLERS ABSORBED, DEFENDED (SWEPT)
Neither SELLERS ABSORBED, DEFENDED

The same qualifier appears on the failed branch. FAILED (SWEPT) means aggressors ate through real resting size; bare FAILED means price walked through with nobody home — usually a sign the level was never worth much.

Expect the unqualified case most often. Absorption and depletion are deliberately narrow tests with a wide margin between them, and a revisit lands in that margin whenever the wall is partly consumed and partly replenished, or when price chops across the level and neither side reaches the dominance bar. A missing qualifier means the book declined to add a strong second opinion — not that nothing happened.

The first qualifying revisit decides. After that the verdict locks; a later return is a new event at a level that no longer has the same defenders.

A verdict describes that visit and is not a forecast. Price breaking the level an hour later does not retroactively unmake a defense that worked.

Naming

ABSORBED is named for the aggressor that got soaked up: SELLERS ABSORBED at a bid, BUYERS ABSORBED at an ask.

Both revisit outcomes name the aggressor too — SELLERS ABSORBED, DEFENDED and SELLERS ABSORBED, FAILED at a bid. Same participants, same event; only the qualifier changes.

What a verdict names follows the verb, and the verbs are not interchangeable:

Verb Happens to At a bid At an ask
ABSORBED the aggressors — aggressors get absorbed SELLERS BUYERS
EXHAUSTED the aggressors — aggressors get exhausted SELLERS BUYERS
DEPLETED the resting orders — the book side gets depleted BID ASK

Depletion is the only verdict that names the book rather than the participants — the resting side is a bid or an ask, so that is what the label says. An earlier build named the resting side on failed absorption, on the theory that the failure was the news, which asserted that an ask had been absorbed — the depletion concept wearing absorption’s word. The outcome of the defense belongs in the qualifier, not in switching which participant the sentence is about.

A trailing +SELLERS ABSORBED+ – marks an aggressor flip: within a short window, the side opposing the aggressor has begun out-trading them at the level. At a bid being hit by sellers, it means buyers are now lifting more than sellers are hitting. The defense has stopped merely holding and started pressing. Three things about it, because it is easy to assume it is broken when you have not seen one:

It is genuinely rare – a defense turning aggressive is a real escalation, not a routine state – so going a session or several without seeing one is normal.

Depletion — volume consumed the resting size (low refill) and price continued through, with no prior absorption. The liquidity was finite and it is gone. Named for the side of the book that was eaten: BID DEPLETED at a bid, ASK DEPLETED at an ask. A continuation signal, and a marker for a possible retest from the other side.

The depletion calculation is unchanged, including the cancellation guard: trades must account for at least Depletion traded fraction of the size that left the book, so a wall that mostly canceled is treated as a withdrawal rather than counted as consumed.

Exhaustion — same low-refill consumption as depletion, but price reclaimed the level instead of continuing. The aggressors spent themselves breaking it and had nothing left to carry price. Named for the aggressors that ran out: SELLERS EXHAUSTED (bid consumed, price back up), BUYERS EXHAUSTED (ask consumed, price back down). A reversal, but a fragile one – the liquidity that caused it is gone, so it often fails on a retest.

Depletion and exhaustion are the same event judged at different moments: a consumed level is provisionally DEPLETED, and a close back through it within the reclaim window upgrades it to EXHAUSTED. If the window passes without a reclaim, the depletion stands.

The window outlives the level. A depleted level can be retired and replaced when size returns at its price, aged out, dropped by the vacated cap, or removed on a clean break – none of which say anything about whether price took the level back. The pending answer is tracked independently, so the verdict is still decided on the closes it was owed. The debug log marks such a case [level already gone].

The reclaim window starts when the visit closes, not when the size was consumed. A visit stays open while price keeps trading at the level and only ends after Visit idle close (seconds) of silence, so a busy level can be measured as depleted minutes before its window opens. That is deliberate — a depletion is not final while price is still working the level — but it means an exhaustion can appear well down the chart from the bubble, which is drawn back at the bar price arrived. The debug log counts the window from the close: reclaimed within 1 of 4 bars means one completed bar after the visit ended, not one bar after the break.

Going deeper. These flow states, why refill separates them, and – importantly – why the same event can resolve one way on a 5-minute chart and another on a 15-second one, are covered at length in the Order flow reference. It includes a side-by-side of one session on 5-minute, 1-minute, and 15-second charts showing exactly which events survive onto which chart, and assumes no prior order-flow experience.

Reading a label

27000.00 (92) BID DEPLETED NQ

92 contracts were resting when price arrived. Sellers hit those bids, consumed them with little refill, and price continued down. In Detailed label mode the live buy/sell split is also shown while the visit develops ([B340/S512]); Clean mode shows only price, size, and the eventual verdict.

The passive count (92) is what was resting on arrival – never decremented, because what transacted is a separate measurement carried by the bubble. A level that has vacated without reaching a verdict shows no count at all: it is a spent line, and its detail, if any, lives on the bubble hover.

Bubbles and hover

A verdict bubble marks each visit that reached a state, drawn at the bar price arrived. It is a fixed size – set by Bubble radius – because the number inside carries the magnitude (total contracts transacted), so size does not need to. The fill is split into a buy sector and a sell sector by proportion, so which side pressed reads at a glance. With Show verdict ring on, a thin outline colors the outcome: absorbed, depleted, or exhaustion.

The bubble encodes three separate facts, one per channel. Nothing is said twice:

Channel Carries Set by
Fill (two-tone pie) Which side transacted more volume during the visit Buy / sell volume colors
Ring color Which family the verdict belongs to — absorption, depletion, exhaustion The verdict colors
Ring thickness Which state within that family The four ring-thickness settings
Ring dash FAILED, and nothing else Fixed, not a setting

Colour is the family, not the individual state: a base absorption and both of its revisit outcomes share Absorption color, because they are the same event with more known about it. What separates the three is the rim.

Verdict Ring color Ring Default weight
ABSORBING (live) Absorption Solid, always the finest 1 — fixed, ignores the settings
ABSORBED Absorption Solid 2Absorption ring thickness
ABSORBED, DEFENDED Absorption Solid 5Revisit ring thickness
ABSORBED, FAILED Absorption Dotted 5Revisit ring thickness
BID / ASK DEPLETED Depletion Solid 2Depletion ring thickness
SELLERS / BUYERS EXHAUSTED Exhaustion Solid 2Exhaustion ring thickness

The dash does exactly one job: separating FAILED from DEFENDED. Those two share a colour and a thickness by design, so the rim is the only channel left to tell them apart. It does not encode buyer versus seller — the words and the pie already carry the side — and there is no ring-style setting to configure.

Revisit weight is deliberately heavier than the base states. A level price came back to and actually tested is the rarer and more informative event, and it should read that way across a chart. The three base verdicts sit at the same weight as each other, because none of them outranks the others.

A live ABSORBING bubble always draws the finest solid ring, whatever the thickness settings say, so a defense still in progress never looks like a settled verdict.

The dotted pattern is spaced to divide the ring evenly, so it closes on a whole dot rather than a stub, at any bubble radius. The ring is drawn as its own circle standing just clear of the bubble rather than as an outline hugging its rim, so the dots never sit on top of the buy/sell split underneath.

The cheat sheet is underlined in the real thing: each state’s name is ruled in that state’s actual ring — same colour, same thickness, same dash — so a bubble on the chart can be matched to a row without being told the mapping.

Everything the face cannot show is on hover. Move the cursor over a bubble and a tooltip gives the price and instrument, the time the verdict was set, the verdict named as a sentence, how an absorption resolved (on its own bold line), the resting size the level held on arrival, and the buy/sell ratio. Total volume is not repeated – it is already the number on the face.

Every one of those lines has its own toggle under Bubble Tooltip, so you can cut the tooltip down to just what you use. Turn all six off and it stops drawing rather than showing an empty box. The ratio is the dominant side’s volume over the lesser side’s, printed as a single number because it is always against 1; which side is dominant is the pie on the face.

Bubble persistence decides how long a bubble outlives its level. Persist 24 hours (default) keeps it on the chart for a day as a session map, even after the line is gone. Remove with level clears it when the line goes.

Bubbles survive reloads and dropped connections

Because any settings change reloads the indicator and the tick-built flow state cannot be replayed, bubbles are cached to disk and restored on load, so nudging a setting mid-session no longer wipes your verdict bubbles. The same cache carries them through a data connection error and reconnect, which is exactly when you least want to lose the record of what just happened.

The cache is written as verdicts arrive, not only at teardown. That matters when several charts reload at once: NinjaTrader tears them down one at a time, and a chart whose save landed after its own reload had already looked for it would find nothing and start empty – while its real bubbles sat on disk unread. A file that is already current cannot lose that race. An empty chart also never overwrites a populated cache, so one missed restore cannot become a permanent one.

Each restored bubble is re-anchored by timestamp. A bubble is stored against a bar index, which only means anything inside the load that produced it – fetch a different span of history and every index shifts. On load, each bubble is put back on the bar its own timestamp lands in, so the marker sits on the candle it actually happened on. Anything older than the loaded range is dropped rather than clamped to the left edge, where it would read as a verdict that never happened there.

Bubble reload window is the remaining age limit on the cache file, defaulting to 5 minutes. It no longer has to be tight, because placement no longer depends on it. Raise it if you run many charts and the slowest ones still come back empty.

One caveat: the cache is keyed by instrument and chart period, so two charts on the same instrument at the same period share one file and will overwrite each other. Give them different periods, or expect the last writer to win.

Levels are not persisted this way, only bubbles. A restored level would show stale resting size – a claim about the book right now, made from data that is minutes old – while a finished bubble is a historical fact and stays true.

Clearing vacated levels without touching your settings

The flip side of that durability: bubbles from the last 24 hours stay on the chart by default, so a long session accumulates them. If the chart has become crowded and you want a clean slate, refresh the chart (right-click → Reload NinjaScript, or reload the data series). Levels build live only, so:

A refresh is the intended way to do this. Reaching for Bubble persistence or Max vacated levels to thin the chart changes how the tool behaves from then on, whereas a refresh only resets what is currently drawn. Bear in mind the trade-off: you also give up the earlier part of the session’s map, and levels that were mid-clock start their validation again from zero, so it is worth doing between setups rather than while you are waiting on one.


Tuning

Thresholds are exposed rather than hardcoded, because they vary by instrument the same way size thresholds do.

Setting Default Effect
Absorption multiple 1.0 How much must trade relative to arrival size
Refill fraction (%) 30 Share of volume replaced rather than drawn down
Depletion fraction (%) 40 How far resting size must fall
Side dominance (%) 60 Share of volume that must have hit the level’s own side
Aggressor window (seconds) 12 Rolling window for the flip test
Minimum dwell (seconds) 2 Time before any state is declared
Visit idle close (seconds) 15 Silence that ends a visit and freezes its verdict
Exhaustion reclaim bars 4 Bar closes a depleted level watches for a reversal, counted from the visit close
Exhaustion reclaim ticks 4 How far beyond the level a reclaiming close must reach
Exhaustion counts from bar 1 Which bar close the reclaim test starts at
Absorption revisit bars 20 How long an absorbed level is watched for price coming back
Absorption hold bars 4 Once it does, how long it has to close back on the defended side
Minimum revisit volume 10 Volume a revisit must transact to count as a test at all
Absorption rejection ticks 4 How far beyond a level a close must be for price to count as having left it
Minimum bubble volume 10 Hide bubbles whose total transacted volume is below this

Minimum bubble volume exists because every bubble is the same size. A visit that traded a single contract gets the same marker, the same ring, and the same hover target as one that traded three hundred, so at a glance the trivial one reads as just as significant. Set it to a figure well under your level threshold but above incidental prints — 10 is a reasonable start on the micros, higher on liquid index futures. 0 shows everything. The same floor gates alerts: a verdict too small to draw a bubble for will not interrupt you either. Nothing is discarded — the flow logic still measures these visits, so lowering the setting brings them straight back.

Absorption revisit bars is how long an absorbed level is watched. Too short and genuine revisits fall outside it, so almost everything stays at the base verdict and the distinction stops earning its keep. Too long and levels stay under watch for most of the session. Scale it to your chart period — the aim is that a revisit you would actually trade lands inside the window. Note that the base verdict is never held up by this: it is rendered immediately either way.

Minimum revisit volume is what stops price drifting back on a handful of contracts from deciding a verdict. Set it well below your level threshold but above incidental prints; the profiles start it at a tenth of the primary threshold. If you see REVISIT IGNORED on returns you would have traded, it is too high.

Absorption rejection ticks does double duty: it is how far price must close from the level to count as having rejected it, and how far back through it price must close to count as breaking it. Too small and ordinary noise around the level reads as both leaving and breaking.

Exhaustion counts from bar deserves a note, because 0 produces a specific false reading. At 0 the candle the visit closed on is itself eligible for the reclaim test — so a single bar that wicks through a level and closes back on the other side is enough to call exhaustion, on the strength of one candle’s shape. Price never accepted beyond the level at all, and the next bar frequently closes straight back through, leaving a bubble that describes nothing.

The default 1 skips that candle and requires the reclaim on a later close, which makes exhaustion a reversal of something that actually happened. Raise it further on fast charts if wicks are still slipping through.

The clamp cuts both ways, and this is worth understanding. The value is held below Exhaustion reclaim bars, so setting it too high cannot switch exhaustion off — it just narrows the window. But the same clamp means a reclaim window of 1 forces this to 0, which is precisely the false reading described above. A one-bar window has no room for a skip. That is why no band ships a reclaim window below 2, and why the CONFIG exhaustion line in the debug log reports the value actually in force and marks it CLAMPED when the two disagree:

CONFIG exhaustion reclaimBars=1 reclaimTicks=4 startBar=0 (CLAMPED from 1 -- reclaimBars is too small for the skip)

If you set the reclaim window to 1 by hand, expect single-candle exhaustions and read that line to confirm why.

A level can re-enter a state on a later revisit, with volume accumulating into the same running totals. A level that absorbs, goes quiet, then absorbs again when price returns is showing a participant who is still there – stronger evidence than either visit alone, and it is labelled DEFENDED (REFILLED) for exactly that reason.

Data status notice

Show data status (Visual group, on by default) puts a one-line notice in a chart corner whenever depth is not arriving, so an empty chart can be told apart from a broken one. It distinguishes a closed market from a feed that carries trades but not depth. It disappears on its own the moment depth starts flowing, so a working session shows nothing.

Data status corner places it (bottom-right by default, opposite the flow cheat sheet, which now defaults to top-left and is shown by default). Data status idle (seconds) sets how long depth must be absent before it appears – 20 by default. Raise it if a genuinely quiet book makes the notice flicker; lower it if you want a dropped feed flagged faster.

Ray mode

Draw as rays from creation (Visual group) switches every level from a full-width line to a ray beginning at the bar where the liquidity first appeared — a heat-map style read of when levels were built.

Verdict rays and retest markers

A verdict bubble marks where flow resolved, but the level it resolved on vacates and eventually fades. Verdict rays keep that price on the chart: switch one on and the most recent verdicts of that type draw a horizontal line in their own colour, from the bubble across to the right edge. The ray stands in for the vacated level — where a vacated level and a ray share a price, the ray replaces it, line and label both, so you are not reading the same price twice. A vacated level with no ray over it is untouched, and a live level always keeps its own line.

Each verdict type carries the switch under its own section — Absorption, Absorption Revisit - Defended, Absorption Revisit - Failed, Depletion, Exhaustion — and each has three controls beside it:

Retest markers are a separate switch on the two revisit sections. Turn one on and every defended or failed retest drops a dot, in the absorption colour, at the level price on the bar where the revisit landed — the candle that actually came back and tested the level. That is not the same bar as the verdict: the hold window has to run for several closes before a retest can be called defended or failed, so the confirmation sits well to the right of the test itself. The tooltip still reports the confirmation time. A defended and a failed retest at the same price are told apart by where their dots sit in time.

Rays and markers are both part of the flow-bubble layer: with Show flow bubbles off, neither draws. Turning off a verdict type, or setting its bubble below Minimum bubble volume, drops its rays and markers with it.


Profiles – the fast way

There is now a Preset group at the top of the settings, and for most people it replaces the two tuning sections that follow. It has one toggle and two dropdowns:

Setting What it does
Manual (ignore profile) On by default. While it is on, the two dropdowns below are ignored and your own settings stand – the shipped MNQ 15-second calibration, or anything you have hand-tuned. Turn it off to let a profile set the instrument- and timeframe-specific values for you.
Profile: instrument The market you are charting, grouped by type. Sets the two size thresholds, the suppression zone, and the reclaim ticks to a sensible starting point for that book.
Profile: timeframe Your chart’s speed band – Scalp, Pulse, Swing, or Anchor. Sets the handful of bar-counted and granularity settings to match.

A profile is applied when the indicator loads. After you change either dropdown, reload the indicator for it to take effect – OK out of the settings and back in, or remove and re-add it. This is deliberate: applying once at load is more predictable than having settings shift under you mid-session, and instrument and timeframe are things you pick once per chart.

While a profile is active, it owns its fields. If you select a profile and then hand-edit one of the values it controls, your edit is overwritten the next time the chart loads. To hand-tune, switch Manual back on – then nothing is overwritten and every value is yours.

The dropdowns stay visible in Manual mode. NinjaTrader cannot gray a setting out based on another; in Manual mode the two dropdowns simply do nothing. Ignore them.

The thresholds a profile sets are seeds, not measurements – a starting point in the right order of magnitude. The one exception is MNQ, which is measured. Calibrate any instrument you trade seriously using the debug-log procedure in section 4; the two tuning sections below explain the reasoning behind every value a profile writes, for when you want to set them by hand.


Tuning for your timeframe

The defaults ship calibrated for MNQ on a 15-second chart – the configuration this indicator was built and traded on. On any other period or instrument, a few settings want adjusting. You can let the Preset dropdowns above do it, or set the values yourself from the tables here.

Two axes, and they do not overlap. Almost everything here is wall-clock driven – depth is depth, and a four-minute validation window is four minutes whether you are looking at 15-second or 5-minute bars. Every setting in the dialog is scaled by one of three things, and knowing which saves you from changing the wrong one:

Scaled by Settings Change it when
The book Primary level threshold, Secondary level threshold, Secondary instrument, Secondary suppression (ticks), Exhaustion reclaim ticks, Absorption rejection ticks, Absorption break ticks, Minimum revisit volume, Minimum dwell, Visit idle close, Touch settle, Aggressor window, Vacated max age You change instrument. See Instrument profiles below.
Chart period Clean break confirm bars, Exhaustion reclaim bars, Max vacated levels, Absorption revisit bars, Absorption hold bars, Absorption hold counts from bar You change timeframe. See the table below.
Neither Unproven and holding timers, Pull ratio, Pull confirm, Recreate cooldown, Max levels, every flow fraction, everything visual Rarely. These travel unchanged between charts.

You do not normally pick the band. Derive band from chart period is on by default, and the indicator reads the band off the chart itself — a 1-minute chart is Pulse, a 5-minute chart is Swing, and changing the period changes the band with it. The dropdown is rewritten to match, so it always names the band actually in force rather than sitting on a value nothing is using — with derivation on it is a readout, and editing it has no effect until you turn derivation off. It stays editable as the fallback for charts that have no period to read (see below), and for anyone who wants to override.

This exists because the dropdown drifts. A chart’s period gets changed and the band does not, and nothing reported the mismatch — a 5-minute chart could sit on Scalp for weeks watching levels for a hundred minutes instead of sixty. The debug log now always names the band in force and where it came from:

CONFIG profile manual=False level=Micro_MNQ_Nasdaq speed=Swing (from Minute5)

Chart periods fall into four bands. These are the values that get written:

Setting Scalp (5-15 sec) Pulse (30 sec-1 min) Swing (2-5 min) Anchor (10 min+) Why it varies
Clean break confirm bars 8 4 2 2 The main bar-counted setting. Aim for roughly two to ten minutes of closes beyond the level. Capped at 20.
Exhaustion reclaim bars 4 2 2 2 A depleted level watches this many closes for a reclaim. Scale it so the window covers about a minute. 2 is the floor — see below.
Absorption revisit bars 20 16 12 8 How long an absorbed level is watched for price coming back. A bar count, so the same number is five minutes on 15s and over an hour on 10m — which is why it is scaled here rather than left fixed.
Absorption hold bars 4 3 2 2 Once price revisits, how many closes it has to close back on the defended side. Mirrors the exhaustion reclaim window and does the same job.
Absorption hold counts from bar 1 1 1 1 Skips the revisit bar itself, so a wick into the level that closes back on the same candle is not a verdict.
Max vacated levels 8 12 20 24 More history is visible on a slower chart.

Tick, volume and range bars have no fixed period, so there is nothing to read and the dropdown is used instead — the log says so explicitly, speed=Pulse (dropdown -- chart period is not time-based). Set it yourself: time a bar during your session, note how long a typical one takes to complete when the market is moving, and use the band it lands in. If a typical bar closes in under 20 seconds, you are in Scalp.

Size thresholds and validation timers are deliberately not on this list. Keeping them identical across charts is what allows a 15-second and a 5-minute chart to agree about which levels exist and what state they are in. Varying them would make the two charts disagree by design.

Nor is anything measured in seconds. Minimum dwell, Visit idle close, Touch settle and Aggressor window used to be on this table and are not any more — they moved to the instrument profile. They measure the order book, which does not slow down because the candles got wider, and having the chart period own them meant the same book event was measured at a different age depending on which chart you had open. A wall that was gone in three seconds got judged at two seconds on one chart and five on another, so the same absorption was recorded on one and missed on the other. That is a bug, not a tuning choice.

These are reasoned starting points, not measured values. They are a sane place to begin, not a calibration.

Tuning for your instrument

Settings that genuinely change with the market: the two size thresholds, the suppression zone that keeps the second book’s labels off your primary ones, three tick distances — exhaustion reclaim, absorption rejection, and absorption held — and the four flow clocks, which describe how fast that contract’s book moves. Everything else is cosmetic or bar-counted and travels between instruments unchanged. The Profile: instrument dropdown sets them all; the tables below are what it writes.

The four flow clocks default to minimum dwell 2s, visit idle close 20s, touch settle 2s, aggressor window 15s on every instrument, and a profile only overrides them where that contract’s book demonstrably behaves differently. These are a starting point seeded from the values that were producing verdicts on fast charts, not a calibration — the near-miss log is what turns them into one.

The three tick distances all answer the same question — how far beyond this level is meaningfully beyond it? — so unless a calibration run says otherwise, the profiles derive the two absorption distances from the reclaim distance each instrument already carries: rejection = reclaim, held = reclaim × 2. Any of the three can be pinned per instrument once measured.

The thresholds cannot be copied blind. Resting size on crude looks nothing like resting size on the micro indices, and a threshold lifted from someone else’s market will either bury your chart in levels or show you none. The profiles below give a seed for every contract: a starting number in the right order of magnitude, not a measurement of your feed on your session.

Read them honestly. One number in those tables was measured – MNQ at 80, from the chart this indicator was built and traded on. Every other seed is ranked against it by how deep that book typically rests relative to the micro indices. That ranking is sound; the exact figure is not, and it will drift with volatility regimes, contract rolls and time of day. So:

  1. Select the instrument profile, or set the seed by hand.
  2. Run the calibration in section 4 – debug log on, 20 to 30 minutes of active session, read the actual CREATE sizes.
  3. Set Primary level threshold from what you measured and keep that number. (You are now hand-tuning, so switch Manual on to stop the profile overwriting it on the next load.)

There is no shortcut around this, and it is worth the twenty minutes: a threshold set from your own feed is the difference between a chart of levels that mean something and a chart of noise.

How to know the threshold is right. A normal session should leave you with a handful of levels that stand out, not a wall of them. If Max levels (50) is binding, the threshold is too low – raise the threshold rather than the cap. If a full session produces almost nothing and the corner notice is silent, it is too high.

Secondary threshold. Start at about 75% of the primary for the same instrument. If you are pairing a micro with its full-size twin, use the twin’s own seed instead – the two books rest in completely different contract counts, and 75% of a micro threshold is meaningless on the e-mini. The profiles below give the paired value directly.

Instrument profiles

What is actually available

Apex routes through Rithmic into NinjaTrader, and that path reaches CME Group’s four exchanges: CME, CBOT, NYMEX and COMEX. Rithmic sells depth per exchange, so the subscription has to match the product – ES and the FX majors are CME, ZN and the grains are CBOT, CL and NG are NYMEX, GC and SI are COMEX. Order-by-order MBO is carried on the outright futures books; options and calendar spreads are not, and Holdfast will not build levels on them.

Two things change without notice: which contracts Apex permits at any given time, and which books your own subscription covers. Verify both before trusting a profile. The quickest test is the indicator itself – load a chart during an active session and watch the corner notice. No depth data means that book is not reaching you, whatever the profile says.

Seeds below are for the front month during that market’s active hours. Roll months and holiday sessions rest thinner. Each group also lists the suppression and reclaim-tick values the profile sets.

Profile 1 – Micro equity index

The calibrated home. Suppression 10 ticks, reclaim 4 ticks. Most active 09:30-16:00 ET.

Symbol Contract Exchange Primary threshold seed Pair as secondary
MNQ Micro E-mini Nasdaq-100 CME 80 – measured NQ at 60
MES Micro E-mini S&P 500 CME 200 ES at 400
MYM Micro E-mini Dow CBOT 100 YM at 60
M2K Micro E-mini Russell 2000 CME 60 RTY at 30

Profile 2 – E-mini equity index

Thinner in contract count than the micros – a size that looks small here is large in notional. Suppression 10 ticks, reclaim 4 ticks. Most active 09:30-16:00 ET.

Symbol Contract Exchange Primary threshold seed Pair as secondary
ES E-mini S&P 500 CME 400 MES at 200
NQ E-mini Nasdaq-100 CME 60 – measured MNQ at 80
YM E-mini Dow CBOT 60 MYM at 100
RTY E-mini Russell 2000 CME 30 M2K at 60

Pairing is more useful micro-first than e-mini-first: the micro book is where the retail flow rests, and seeing the e-mini behind it is the added information. Running it the other way adds less.

Profile 3 – Metals

Suppression 10 ticks, reclaim 4 ticks – 6 on silver and platinum, whose ticks are finer relative to their range. Most active 08:20-13:30 ET.

Symbol Contract Exchange Primary threshold seed
GC Gold COMEX 40
MGC Micro Gold COMEX 150
SI Silver COMEX 20
SIL Micro Silver COMEX 15
HG Copper COMEX 40
MHG Micro Copper COMEX 25
PL Platinum NYMEX 12

Pair MGC with GC at 40. Note that PL lists on NYMEX, not COMEX – a COMEX-only depth subscription will not carry it.

MGC’s seed is measured rather than ranked. The routine near-touch ladder on micro gold carries 102-127 lots, so the earlier seed of 60 admitted essentially every visible price – levels created and withdrawn within 5-15 seconds, dozens per minute, with the max-levels cap binding constantly. The walls worth drawing sit at 250-330, with a clear gap between roughly 140 and 250. 150 clears the ambient ladder and keeps everything above the gap. That measurement was taken in high volume, when depth is at its fattest; a quieter session carries less, and 150 stays above its noise floor rather than needing to move with it. The other seeds in this table are still ranked estimates – calibrate them the same way.

Profile 4 – Energy

Wide daily ranges against small ticks, so the suppression zone wants to be wider than the index default. Suppression 12 ticks, reclaim 6 ticks. Most active 09:00-14:30 ET; crude inventories at 10:30 ET Wednesday will distort any calibration run that covers it.

Symbol Contract Exchange Primary threshold seed
CL Crude Oil NYMEX 40
MCL Micro Crude Oil NYMEX 60
NG Natural Gas NYMEX 30
MNG Micro Natural Gas NYMEX 25
RB RBOB Gasoline NYMEX 20
HO Heating Oil (ULSD) NYMEX 20
BZ Brent Crude NYMEX 15

Pair MCL with CL at 40 if you trade the micro.

Profile 5 – Crypto futures

Check the Apex permitted list first – coverage of these has changed more than once. Suppression 10 ticks, reclaim 4 ticks. Depth is genuinely 24-hour, so the session filter matters more here than anywhere else on this list.

Symbol Contract Exchange Primary threshold seed
MBT Micro Bitcoin CME 15
MET Micro Ether CME 30
BTC Bitcoin CME 5
ETH Ether CME 10

Profile 6 – Treasuries and short-term rates

The deepest books on this list by an order of magnitude, and the finest ticks. Thresholds in the thousands are normal here and do not mean something is wrong. Suppression 5 ticks, reclaim 3 ticks. Most active 08:00-15:00 ET, with the sharpest depth around the 08:30 ET data releases.

Symbol Contract Exchange Primary threshold seed
ZQ 30-Day Fed Funds CBOT 3000
SR3 3-Month SOFR CME 3000
ZT 2-Year T-Note CBOT 2000
ZF 5-Year T-Note CBOT 1500
ZN 10-Year T-Note CBOT 1000
TN Ultra 10-Year T-Note CBOT 300
ZB 30-Year T-Bond CBOT 400
UB Ultra T-Bond CBOT 200

These are the profiles most worth calibrating rather than seeding. The spread between ZQ and UB is wider than the spread between any two other groups here.

Profile 7 – FX

Deep books against very fine ticks, which is why the suppression zone tightens. Suppression 6 ticks, reclaim 3 ticks. Most active 03:00-11:00 ET, across the London and New York overlap – calibrating one of these on a US afternoon will give you a threshold that is far too low for the morning.

Symbol Contract Exchange Primary threshold seed
6E Euro FX CME 300
6J Japanese Yen CME 200
6A Australian Dollar CME 150
6B British Pound CME 120
6C Canadian Dollar CME 100
6S Swiss Franc CME 60
6N New Zealand Dollar CME 60
M6E Micro Euro FX CME 40
M6A Micro AUD CME 40
M6B Micro GBP CME 40

The FX micros are thin enough to sit near the edge of what Holdfast can read. Calibrate before concluding the indicator is quiet.

Profile 8 – Grains and oilseeds

Suppression 4 ticks, reclaim 3 ticks. Depth concentrates almost entirely in the day session, 09:30-13:20 CT – set the session filter to it. The overnight electronic session on these contracts is thin enough to generate misleading levels.

Symbol Contract Exchange Primary threshold seed
ZC Corn CBOT 80
ZS Soybeans CBOT 40
ZW Chicago Wheat CBOT 40
ZL Soybean Oil CBOT 40
ZM Soybean Meal CBOT 25
KE KC Hard Red Wheat CBOT 15

Profile 9 – Livestock and thinner markets

Suppression 4 ticks, reclaim 3 ticks. Day session only, 09:30-14:05 ET. These books rest in single and low double digits, so a level is a rarer event – expect a much emptier chart, and treat the ones that do form with more weight.

Symbol Contract Exchange Primary threshold seed
LE Live Cattle CME 15
HE Lean Hogs CME 15
PA Palladium NYMEX 5

Too thin for level work

These are tradeable and will load, but their books rarely rest enough size for a level to mean anything. Holdfast will show you very little on them, and that is the correct answer rather than a fault:

ZO (Oats), ZR (Rough Rice), GF (Feeder Cattle), XC / XW / XK (mini grains), EMD (S&P MidCap 400), NKD (Nikkei), E7 / J7 (half-size FX), the micro yield futures (2YY, 5YY, 10Y, 30Y), and the minor FX crosses.

If you trade one of these, run the calibration anyway before deciding – the point of the debug log is that it answers this question in twenty minutes with your own data.

6. Troubleshooting

No levels appear at all. Check the corner notice first — it usually answers this. Holdfast watches the depth feed and tells you when nothing is arriving:

Notice Meaning What to do
Not connected – check your data connection The price feed is down. Reconnect your data feed; nothing will appear until it is back.
Market closed – levels will appear at the open No depth and no trades. Nothing. Depth only flows live.
No depth data – this indicator requires Level 2 / MBO Trades are arriving but depth is not. Your feed lacks the MBO add-on, or it doesn’t cover this instrument’s exchange. See requirements in section 1, and Getting Level 2 / MBO data for how to buy the right one.
Depth feed idle 2:14 Depth was flowing and stopped. A session close, or a dropped connection worth checking.
(no notice) Depth is arriving normally. The feed is fine — work through the list below.

If depth is arriving and levels still do not appear: 1. Does a SuperDOM on this instrument populate? If not, the problem is your data connection. 2. Is the current time inside your session window? 3. Is your threshold too high for this instrument? Turn on the debug log and see what sizes are actually arriving. 4. Check Control Center → Log for errors mentioning Holdfast.

Levels appear but disappear after four minutes. That’s the validation filter working — the size isn’t surviving your unproven timer. Either the book at that price is genuinely thin and transient, or your pull ratio is too aggressive. Try lowering the threshold slightly or raising the pull ratio.

A setting shows the wrong default on a fresh add (e.g. clean break shows 2, not 8). NinjaTrader remembers the last values you used as a per-indicator default, and a plain re-add restores those, not the shipped defaults. After any update, use Remove NinjaScript Assembly (section 7) and re-add, rather than only recompiling – that clears the remembered values. Selecting an instrument or timeframe profile also fixes this, because a profile re-writes its fields on every load.

Nothing on a historical chart. Expected. Market depth is not stored historically by any platform. Levels build live from the book as the session runs.

Labels look crowded. Increase Secondary suppression (ticks), or turn off Show instruments / Show timers to shorten the label text.

License won’t activate. 1. Confirm the key was pasted with no leading or trailing spaces. 2. Confirm you have internet access (activation itself requires it; ongoing use does not). 3. If you’ve already used both activations, deactivate an old machine from your license page. 4. Still stuck — reply to your purchase email with the message shown in the NinjaScript Output window.


7. Updating

When a new version ships you’ll get an email with the new zip.

  1. Remove the old version first: Control Center → Tools → Remove NinjaScript Assembly… → select Holdfast Liquidity Levels → Remove.
  2. Restart NinjaTrader.
  3. Import the new zip as in step 2.
  4. Restart again.

Your settings and license key are preserved. Skipping the removal step is the usual cause of import errors.


8. Support

Reply to your purchase email. Include: - What you’re seeing versus what you expected - Your instrument and data feed - A screenshot of the chart if it’s visual - Any relevant lines from the NinjaScript Output window


Appendix — every setting, in full

Each setting’s tooltip in NinjaTrader is a one-line reminder of what it is. This is the reasoning behind every one that has more to say — why the gate exists, what it costs to set wrong, and how to calibrate it.

License

License key

Paste the key from your Holdfast receipt email. One key activates two machines; free a slot yourself from the customer portal.

Preset

Manual (ignore profile)

On: the profile dropdowns below are ignored and your own settings stand. Off: the Instrument and Timeframe dropdowns set the instrument- and timeframe-specific values for you, immediately. While a profile is active it owns those fields – to hand-tune one, switch Manual on first, or your edit is overwritten next time a profile applies.

Profile: instrument

Instrument preset. Sets the two size thresholds, suppression zone, and reclaim ticks to a starting point for that market. Seeds are ranked estimates – calibrate with the debug log (see the guide). Ignored when Manual is on.

Derive band from chart period

Read the band off the chart’s own period instead of the dropdown above. On by default, because the dropdown is the commonest thing to get wrong — a chart’s period gets changed and the band does not, and nothing reported the mismatch. Boundaries are the ones the dropdown documents: 15 seconds and under is Scalp, up to a minute is Pulse, up to five minutes is Swing, beyond that Anchor. Tick, volume and range bars have no period to read, so those fall back to the dropdown and the log says so. The dropdown above is rewritten to whatever the chart resolves to, so the dialog and the settings underneath it can never name different bands. Turn it off to pick the band by hand on every chart. Ignored when Manual is on, along with everything else the profiles write.

Profile: timeframe

Chart-period band. Sets the bar-counted settings to suit your chart’s speed. Scalp = 15 sec and under, Pulse = up to 1 min, Swing = up to 5 min, Anchor = over 5 min. With Derive band from chart period on this is a readout: it is rewritten to match the chart and editing it does nothing. Turn derivation off to pick the band by hand — for tick/volume/range bars, time a typical bar and use the band it lands in. Ignored when Manual is on.

Level Management

Monitor secondary instrument

Also form levels from a second, correlated contract’s depth, drawn in their own colours alongside the chart’s own. The contract is named in Secondary instrument below. Where both books qualify at the same price the primary wins, and secondary levels inside Secondary suppression (ticks) of a primary are neither created nor drawn. Turn it off to track the charted instrument only; leave it on and a chart showing no secondary levels at all almost always means the contract below is missing its expiry month, which is skipped at load with a note in the debug log.

Secondary instrument

Optional correlated contract whose depth also forms levels – typically the full-size version of what you trade (NQ alongside MNQ, ES alongside MES). Enter the full contract including expiry, e.g. NQ 12-26, and update it at each roll. Leave blank to use the charted instrument only.

Primary level threshold

Resting size at a price required to create a level, and the bar it must keep holding. This is the most consequential setting in the indicator, and the right number is different in every market – resting size on crude looks nothing like resting size on the micro indices. An unproven level must hold the full threshold for its entire validation window or it is dropped as spoof; once proven it may sag to the pull floor (Pull ratio, half the threshold by default) before it counts as pulled. That same floor gates absorption, so a threshold set too low reports defence at prices nothing was really defending. Instrument-scoped, so the profile writes it unless Manual is on; calibrate your own from the CREATE lines in the debug log, aiming for a number only the largest few sizes in your session clear.

Secondary level threshold

Resting size required to create a level from the paired contract’s book, set from that contract’s own liquidity rather than as a fraction of the primary – the full-size contract usually rests far larger size than the micro. It is judged against the contract a level is finally attributed to rather than the feed that raised it: the primary wins wherever the primary book also qualifies at that price, and a level that ends up secondary is re-checked against this number before it is admitted, so nothing is ever labelled against a threshold it did not clear. Ignored while Monitor secondary instrument is off or Secondary instrument is blank; instrument-scoped otherwise, so the profile writes it unless Manual is on.

Unproven timer (seconds)

Spoof-validation window. Unproven level becomes Holding when this expires with the level still valid.

Pull ratio (% of threshold)

Size must stay at or above this percentage of the threshold to keep a level alive. Applies to every state: unproven levels that fall below are deleted as withdrawn, established levels become Vacated. Set to 100 to require the full threshold at all times.

Pull confirm (seconds)

Size must stay below the pull ratio for this long before a level is treated as pulled. Prevents state flicker from normal book churn.

Recreate cooldown (seconds)

After a level is removed at a price, no new level may form there until this expires. Prevents rapid remove/recreate churn and label flicker.

Max levels

Cap on live levels (Unproven, Holding, Proven). Vacated levels are held separately under ‘Max vacated levels’.

Touch settle (seconds)

A level must exist this long before price reaching it counts as a touch. Stops a level born at the current price being marked touched immediately. Timeframe independent.

Max vacated levels

How many vacated levels stay on the chart. Levels price has touched are kept in preference to untouched ones. Set to 0 to remove vacated levels immediately.

Vacated max age (seconds)

A vacated level is removed once this many seconds pass since price last touched it (each touch resets the clock). Its bubble remains. Set to 0 to disable the age timer and rely on the count cap alone.

This is wall-clock and does not follow your chart period. It used to: a level that had gone stopped being drawn after 15 minutes on a 15-second chart and an hour on a 5-minute one, for the same price on the same book — so a level could still be there to be retested on one chart and already forgotten on another. How long departed liquidity stays relevant is a property of the market, not of how wide the candles were drawn. One hour on every chart, adjustable per instrument.

Enable debug log

Prints every level create/remove/state-change with a reason to the NinjaScript Output window. Also prints a CONFIG block at load naming the instrument, period, and every setting that shapes the log, so a captured run is self-describing.

Log flow near-misses

Log every visit that reached NO verdict, printing all five flow measurements next to the gate each was tested against, with a marker on the ones that failed. This is what makes the absorption multiple, refill fraction, side dominance, and the two depletion fractions calibratable: without it the log only ever shows values that PASSED their gate, and a threshold slightly too strict looks identical to one that is correct. Requires the debug log to be on.

Near-miss log interval (seconds)

Minimum spacing between near-miss lines at the same price. An active visit re-enters the near-miss test on every tick, so without throttling one quiet level buries the log. Lower it for a dense calibration run, raise it for everyday use.

Gate counter interval (seconds)

How often to print the counters for gates that drop events silently: the touch-settle delay, the minimum-dwell check, the recreate cooldown, and prints inside the spread that cannot be attributed to a side. Each of these can make a correctly configured indicator look dead, and none of them logged anything before. Prints only when something was actually dropped. 0 disables.

Visual

Show level lines

Draw the horizontal line and price label for each level. Turn OFF for a bubbles-only chart: levels are still tracked, timed, and still produce flow verdicts and alerts – only the horizontals and their labels are hidden. Each bubble still shows its price and verdict on hover.

Flash levels under watch

Pulse the line of any level that has absorbed and is inside its revisit window. That level is the one being waited on – it has already produced a verdict and price coming back is the event that would revise it – so it is marked as live rather than faded to a record. All watched levels pulse in phase with each other.

Flash period (seconds)

How long one full pulse takes. Longer is calmer.

Show data status

Shows a corner notice when no depth data is arriving, so an empty chart can be told apart from a broken one. It distinguishes a closed market from a data feed that does not carry Level 2. Hidden whenever depth is flowing normally.

Data status idle (seconds)

How long depth must be absent before the status notice appears. Too low and a quiet book flickers the notice; too high and a dropped feed goes unnoticed.

Secondary suppression (ticks)

While a primary level exists, secondary levels within this many ticks are neither created nor drawn. Primary levels always take precedence.

Absorption

Show verdict ring

Outline each flow bubble in its verdict color so the bubble alone shows the outcome without reading the label. Colour is the family – absorption, depletion, exhaustion. Thickness says which state within that family. Dash separates the two revisit outcomes, which share a colour and a thickness by design.

Show absorption

Show absorption bubbles. Absorption is the base verdict: the refill mechanics fired across a completed visit, which is a finished measurement. Most absorptions are never revisited and stay exactly as rendered. Also governs the live ABSORBING bubble while price is still at the level. Off also suppresses the absorption alert.

Absorption color

Colour for absorption and BOTH revisit outcomes. Defended and failed are offshoots of an absorption rather than separate findings, so they deliberately share its colour – thickness and dash tell them apart. Colouring them separately would break the association the eye needs to make.

Absorption ring thickness

Ring weight for a base absorption. Sits at the same weight as depletion and exhaustion by default, since all three are base states.

Revisit ring thickness

Ring weight shared by BOTH revisit outcomes, defended and failed. Heavier than the base states by default: a level that price came back to and tested is the rarer and more informative event, and it should read that way across a chart. Defended draws solid, failed draws dotted – that is fixed, not a setting, because with a shared colour and a shared weight the dash is the only channel left to separate them.

Absorption alert sound

Wav file in the NinjaTrader sounds folder.

Absorption retest bars

How many bars an absorbed level is watched for price to return and test it. The ABSORBED verdict is already final and rendered the moment the visit closes — this window never holds it up. What a qualifying revisit inside the window does is revise that verdict: to REVISIT DEFENDED if the level holds, or REVISIT FAILED if it breaks. If nothing qualifying returns before the window expires, the verdict simply stays ABSORBED — the common case, and not a failure. Scale it to your chart period, so a revisit you would actually trade lands inside it. Timeframe-scoped.

Absorption rejection ticks

How far beyond the level, in ticks, a bar must close for price to count as having left it. Used twice: to confirm the initial rejection after absorption, and to detect the level breaking on the retest. Too small and ordinary noise around the level reads as both leaving and breaking. Instrument-scoped – written by the instrument profile, since a tick distance means something different on every contract.

Absorption hold bars

Once price REVISITS an absorbed level, how many bar closes it has to close back on the defended side. Much shorter than the revisit window – this mirrors the depletion-to-exhaustion reclaim window and does the same job. If the window expires with price genuinely beyond the level the verdict becomes REVISIT FAILED; if it expires with price still at the level, inside the rejection-tick buffer, nothing was decided and the verdict stays ABSORBED. Timeframe-scoped.

Absorption hold counts from bar

Which bar close the hold test starts at. At 0 the revisit bar itself is judged, which misreads the common case of price wicking into a level and closing back on the same candle. 1 skips that candle. Clamped below the hold window so a bad value cannot disable the test. Timeframe-scoped.

Absorption break ticks

Override on the hold window: once price closes this far beyond the level in the breaking direction, the level is called broken immediately rather than waiting the window out. Fires even inside the skipped opening bars. Set to 0 to disable the override and always run the full hold window. Instrument-scoped – twelve ticks of crude is not twelve ticks of MNQ.

Minimum revisit volume

How much has to transact during a revisit before it counts as a test of the level at all. Price drifting back on a handful of contracts, wicking through, and leaving is not a test, and without this it would decide a verdict. Below this the level simply stays ABSORBED and the revisit window keeps running. The rejection-tick buffer does this job on the price axis; this is its equivalent on the volume axis. Instrument-scoped.

Absorption ray

Draw a horizontal line in the absorption colour from each of the most recent absorption bubbles across to the right edge, standing in for the vacated level at that price. Off by default. See Verdict rays and retest markers.

Absorption ray count

How many of the most recent absorptions to ray. Deliberately small — recent structure, not a full history. 5 by default.

Absorption ray opacity

Opacity of the absorption ray, 0100. 50 by default.

Absorption ray style

Dash style of the absorption ray — solid, dashed, or dotted. Dashed by default.

Absorption Revisit - Defended

Absorption ray thickness

Line weight of the absorption ray — fine, thin, medium, or thick. Fine by default: a ray is a reference line, and fine keeps it from competing with the level lines. Heavier makes the ray read as a level in its own right, which is what you want if you trade off them directly.

Show revisit defended

Show absorptions that price revisited and that held. The rarer of the two revisit outcomes and usually the more valuable. Off also suppresses its alert.

Revisit defended alert sound

Wav file in the NinjaTrader sounds folder.

Defended ray

Draw a horizontal line in the absorption colour from each of the most recent defended-retest bubbles across to the right edge, replacing the vacated level at that price. Off by default. See Verdict rays and retest markers.

Defended ray count

How many of the most recent defended retests to ray. 5 by default.

Defended ray opacity

Opacity of the defended-retest ray, 0100. 50 by default.

Defended ray style

Dash style of the defended-retest ray. Solid by default, matching the defended ring, so the ray reads the same way its bubble’s rim does.

Defended ray thickness

Line weight of the defended-retest ray — fine, thin, medium, or thick. Fine by default: a ray is a reference line, and fine keeps it from competing with the level lines. Heavier makes the ray read as a level in its own right, which is what you want if you trade off them directly.

Defended retest marker

Drop a dot in the absorption colour at the level price, on the bar the qualifying revisit landed on, marking where the retest happened. The tooltip still reports when the verdict was confirmed, which is several closes later. Independent of the ray count: every visible defended retest gets one. Off by default.

Absorption Revisit - Failed

Show revisit failed

Show absorptions that price revisited and that broke. A continuation read, not a reversal one – the participant who held the first attack was not there for the second. Off also suppresses its alert.

Revisit failed alert sound

Wav file in the NinjaTrader sounds folder.

Failed ray

Draw a horizontal line in the absorption colour from each of the most recent failed-retest bubbles across to the right edge, replacing the vacated level at that price. Off by default. See Verdict rays and retest markers.

Failed ray count

How many of the most recent failed retests to ray. 5 by default.

Failed ray opacity

Opacity of the failed-retest ray, 0100. 50 by default.

Failed ray style

Dash style of the failed-retest ray. Dotted by default, matching the failed ring, so the ray reads the same way its bubble’s rim does.

Failed ray thickness

Line weight of the failed-retest ray — fine, thin, medium, or thick. Fine by default: a ray is a reference line, and fine keeps it from competing with the level lines. Heavier makes the ray read as a level in its own right, which is what you want if you trade off them directly.

Failed retest marker

Drop a dot in the absorption colour at the level price, on the bar the qualifying revisit landed on, marking where the retest happened. The tooltip still reports when the verdict was confirmed, which is several closes later. Independent of the ray count: every visible failed retest gets one. Off by default.

Depletion

Show depletion

Show depletion bubbles. Off also suppresses the depletion alert.

Alert on depletion

Play a sound and raise an alert when a depletion bubble forms. Suppressed if depletion is hidden.

Depletion ray

Draw a horizontal line in the depletion colour from each of the most recent depletion bubbles across to the right edge, standing in for the vacated level at that price. Off by default. See Verdict rays and retest markers.

Depletion ray count

How many of the most recent depletions to ray. 5 by default.

Depletion ray opacity

Opacity of the depletion ray, 0100. 50 by default.

Depletion ray style

Dash style of the depletion ray — solid, dashed, or dotted. Dashed by default.

Exhaustion

Depletion ray thickness

Line weight of the depletion ray — fine, thin, medium, or thick. Fine by default: a ray is a reference line, and fine keeps it from competing with the level lines. Heavier makes the ray read as a level in its own right, which is what you want if you trade off them directly.

Show exhaustion

Show exhaustion bubbles. Off also suppresses the exhaustion alert.

Exhaustion reclaim bars

How many bar closes a depleted level watches for a reclaim. A reclaim within the window upgrades DEPLETED to exhaustion; otherwise depletion is final. The window opens when the VISIT closes – after Visit idle close (seconds) of silence at the level – not at the moment the size was consumed, so a level price keeps working stays provisional for as long as it is busy and the window runs from the end of that. Timeframe dependent – scale it so the window covers roughly a minute of wall-clock time on your chart period.

Exhaustion counts from bar

Which bar close the reclaim test starts at. 0 is the candle the visit closed on, which means a single candle that wicks through a level and closes back the other side reads as exhaustion on its own – often reversed by the very next bar. 1 skips that candle and requires the reclaim on a later close. Values at or above the reclaim bars window are clamped so exhaustion is never switched off by accident.

Alert on exhaustion

Play a sound and raise an alert when an exhaustion bubble forms (including a depletion that upgrades to exhaustion). Suppressed if exhaustion is hidden.

Exhaustion ray

Draw a horizontal line in the exhaustion colour from each of the most recent exhaustion bubbles across to the right edge, standing in for the vacated level at that price. Off by default. See Verdict rays and retest markers.

Exhaustion ray count

How many of the most recent exhaustions to ray. Counted across both sides — sellers and buyers exhausted share the tally, as they share a colour. 5 by default.

Exhaustion ray opacity

Opacity of the exhaustion ray, 0100. 50 by default.

Exhaustion ray style

Dash style of the exhaustion ray — solid, dashed, or dotted. Dashed by default.

Cheat Sheet

Exhaustion ray thickness

Line weight of the exhaustion ray — fine, thin, medium, or thick. Fine by default: a ray is a reference line, and fine keeps it from competing with the level lines. Heavier makes the ray read as a level in its own right, which is what you want if you trade off them directly.

Show cheat sheet

Show an on-chart legend of the flow states and what each implies. Each verdict name is colored and underlined to match its bubble ring.

Flow Analysis

Minimum bubble volume

Hide any verdict bubble whose total transacted volume is below this. A visit that traded a handful of contracts is not evidence of anything, but its bubble is the same size as one that traded three hundred, so a low-volume marker reads as more significant than it is. Scale it to your instrument – a figure well under your level threshold, but above incidental prints. 0 shows every bubble.

Bubble radius

Fixed radius for every bubble, in pixels. Bubbles are uniform in size – the number inside carries total volume, so size does not need to encode it.

Bubble persistence

Persist 24 hours keeps a verdict bubble on the chart for a day after its level is gone, as a session map. Remove with level clears it when the line goes.

Bubble reload window (seconds)

After the indicator reloads – which every settings change forces – verdict bubbles are restored from cache only if the cache file is younger than this. Default 300. It no longer needs to be tight: restored bubbles are re-anchored to the bar their own timestamp falls in, so a cache written before the current bar range still lands correctly, and anything outside that range is dropped. Raise it if several charts reload at once and the slowest ones lose their bubbles.

Visit idle close (seconds)

Silence at a level for this long ends the visit. Depletion and absorption are both provisional at that point – depletion still watches for a reclaim, absorption still waits for its retest – so the visit is recorded and its final verdict settled on later bar closes. A later return opens a new visit, which for a level that absorbed is the retest that decides it.

Label detail

Clean shows price, size, and the verdict only. Detailed adds the live buy/sell split while a visit develops, for study. Both show the same bubbles.

Enable flow states

Classify what happens at a level price has traded into, and resolve each visit to a verdict: ABSORBED — which a later revisit can revise to REVISIT DEFENDED or REVISIT FAILED — DEPLETED, or EXHAUSTED. None is a probability; each is a description of observed flow and what price did afterwards.

Refill fraction (%)

Share of traded volume that must have been replaced rather than drawn out of resting size. Above this reads as absorbing, below it as depleting.

Side dominance (%)

Share of the volume at the price that must have hit THIS level’s resting side before a state is declared. Buys consume resting offers, sells consume resting bids; volume on the other side traded at the same price but against the opposite book and says nothing about whether this level was tested. Set to 50 to ignore the split.

Show volume delta

Detailed label mode only: append net aggression at the price as a signed figure (buys minus sells), colored by sign. Has no effect in Clean mode.

Depletion traded share (%)

Of the resting size that left the book, trades against this side must account for at least this share for the level to count as DEPLETED. Below it, the size was mostly canceled – a pull, not depletion – and the level is treated as a withdrawal. Guards against a wall that vanishes on cancellations reading as a depleted bid or ask.

Aggressor window (seconds)

Rolling window used to detect an aggressor flip. Cumulative totals cannot show one because they are dominated by whatever traded first.

Minimum dwell (seconds)

How long price must have been trading at a level before any state is declared. Stops a couple of prints being read as a trend.

Bubble Tooltip

Tooltip: price

Show the level’s own price and instrument. Worth keeping: after a reload the live level may be gone, and this is the only thing that still identifies where the bubble sat.

Tooltip: time

Show the wall-clock time the displayed verdict was set. On a verdict that was later upgraded this is the time of the UPGRADE, not of the original visit.

Tooltip: resolution

Show how an absorption resolved – AWAITING RETEST, DEFENSE HELD, or DEFENSE FAILED – on its own bold line beneath the verdict. Only absorption states have a resolution; the line is absent on depletion and exhaustion rather than blank.

Tooltip: passive size

Show the firm resting size the level was holding when price arrived. This is the denominator every flow measurement is taken against.

Tooltip: ratio

Show the dominant side’s transacted volume over the lesser side’s, for the visit. Always expressed against 1, so only the one number is printed. Which side is dominant is the bubble’s own two-tone pie.

Tooltip: revisit book

Show what the BOOK did during a revisit – refilled, meaning the size came back and defended again, or swept, meaning it was eaten – and on what volume. Price decides whether the level held; this says what holding it cost, which is the difference between a defender who is still there and one whose liquidity is now gone. Absent unless the revisit produced a clear reading, which most do not: absorption and depletion are narrow tests and a typical revisit lands in the margin between them.

Tooltip: excursion

Show how far, in ticks, price closed clear of the level in the defended direction. This is the strong-versus-weak read on an absorption that was never revisited: one that drove price a long way and one that barely moved it carry the same verdict, and this is what separates them without inventing a state for it.


Holdfast Liquidity Levels is chart-analysis software. It does not provide trading advice, signals, or recommendations. Futures trading involves substantial risk of loss and is not suitable for every investor. NinjaTrader and Rithmic are trademarks of their respective owners.