Documentation
Guides and reference for TheStrat Suite: bar types, signals, Full Timeframe Continuity, targets and stops, settings, and the engineering internals of the indicator. New here: start with the Quick Start in the Settings Reference, then Bar Types and Signals; the rest is reference.
Bar Types & Notation
Every candle in TheStrat is classified by one question: what did it do to the previous candle's range? This doc defines the three structures, the notation the Suite uses for them, and the rules that resolve the ambiguities. Where the broader Strat community never standardized something, I picked a rule and wrote down why.
Applies to v2.2.7-split and later. (v2.2.6 and earlier used lowercase f2u/f2d for a live Failing 2 — see the notation rule below for why that was retired.)
The three structures
| Type | Name | Definition (vs prior candle) |
|---|---|---|
| 1 | Inside | Broke neither side: high ≤ prior high AND low ≥ prior low |
| 2 | Directional | Broke exactly one side: 2u took out the prior high only, 2d the prior low only |
| 3 | Outside | Broke both sides |
Three edge rules the definitions imply:
- Equal is not a break. A high that exactly matches the prior high has not crossed it. A bar with an equal high and a held low is still an inside bar.
- Gaps don't get their own type. A gap-up open above the prior high already makes the bar at least a
2u(its high is above the prior high by definition); if it also trades below the prior low, it's a3. Classification is always the range test, never the open. - A
2ucan be red. Theuin2unames which side broke, not the candle's color. A bar that pokes above the prior high and fades to close red is still a2u— that failure mode has its own name below.
The second channel: close vs open
Independent of structure, every candle either closed above its open or didn't. This is a separate piece of information — it drives Full Timeframe Continuity, the data-table colors (yellow = inside closing above open, orange = inside closing below), and the direction suffix on types where "which side broke" can't provide one:
| Token | The digit says | The u/d says |
|---|---|---|
2u / 2d |
one side broke | which side broke |
1u / 1d |
nothing broke | close above / below open |
3u / 3d |
both sides broke | close above / below open |
Read that table twice — the u/d suffix means two different things depending on the digit. It's the single most common misreading of Strat notation. The Suite keeps the community convention on-chart, but treats the two meanings as separate channels internally, and so should you: structure (1/2/3 + break side) and sign (close vs open) are orthogonal facts about the same candle.
One tie-break: a close exactly at the open counts as not above — it buckets down (1d, 3d).
Failing 2s: F2u / F2d
A Failing 2 is a directional bar whose break got rejected: price took out one side of the prior range, then came back. F2u = a failed upside break (bearish implication). F2d = a failed downside break (bullish implication). Note the direction letter names the break that failed, not the trade it sets up — an F2u is a short setup.
Also known in the community as a Range Reclaim, Potential 3, or 1-bar Rev Strat. The Suite's settings use "Failing 2s (Range Reclaims)."
The Suite offers four detection methods (Advanced → Failing 2 Detection):
- Reclaim (default): the bar closes back inside the prior candle's range.
- Open: the bar closes against its breakout direction (a
2uclosing below its open). - Reclaim + Open: both required.
- Reclaim OR Open: either suffices.
The notation rule (decided 2026-07-21, v2.2.7)
Notation is tense-free: always uppercase F2u/F2d, live or closed. Earlier versions wrote a forming Failing 2 in lowercase (f2u) to signal "this is still in progress." That case distinction was retired because it was a liveness marker applied to exactly one bar type — a forming 2u that might still become a 3u was never written differently — and because case-significant notation fails everywhere it matters: it can't be spoken aloud, it's illegible at small label sizes, and it's a footgun in any API or query context.
Liveness is carried where it's carried for every other bar type:
- Position — the last token of a combo is always the forming candle.
*prefix — a potential state that hasn't confirmed (a pre-F2 open level shows*F2d).- Words — Universal mode says
FAILINGwhile live; past tense is failed. Humans get tense from language, not casing.
Hammers & Shooters (HAM / SHO)
Not bar types — proportion patterns layered on top of any structure, defined by where the body sits in the candle's range. A hammer rejected the low (conviction for upside); a shooter rejected the high. The Suite offers three definitions (Filters → Hammer/Shooter Detection):
- Broad (default): open and close both in the upper half (hammer) or lower half (shooter). A body centered exactly at the midpoint is neither.
- Classic: small body (≤30% of range), rejection wick ≥3× the body, body confined to the top/bottom third, close near the rejected extreme.
- Pin Bar (strict): open and close both inside the extreme quarter of the range.
An optional color filter requires hammers to be green and shooters red. In Strat notation these appear as HAM/ SHO suffixes; Universal mode marks them with ◆.
Reading combo strings
2d-1-2u reads oldest → newest: the candle two back (C2) was a 2d, the prior candle (C1) was inside, the current candle (CC) broke up. Conventions:
*prefix = potential — the current candle hasn't broken the trigger yet (*2d-1-2uis a setup, not a signal).Finside a combo marks a slot that was itself a Failing 2:F2d-2umeans the prior bar failed its downside break and the current bar is following through up.- Dashes are a display setting;
2d12uand2d-1-2uare the same string.
The canonical model (builders & API consumers)
Every classification above is a rendering of one four-field tuple — this is the model to use in any programmatic context, and it's how the Suite and priceactionapi (an external API serving the same bar classifications — traders can skip its column) stay mutually translatable:
| Field | Values | Suite rendering | priceactionapi |
|---|---|---|---|
structure |
1, 2u, 2d, 3 |
the digit + break side | same, uppercase family |
above_open |
true / false | u/d on 1s and 3s; table colors; FTFC |
> / < (ao flag) |
failed |
true / false | F prefix |
F prefix (is_failed) |
live |
forming / closed | last combo slot; * when unconfirmed |
provisional |
Rule of thumb: the tuple is the truth; notations are projections. When two displays seem to disagree, resolve them back to the tuple before assuming a bug.
Engine internals: calcBarType, detectBarTypeAndFailed, detectFailed2 — see ../engineering/ docs.
Signals
A signal in TheStrat Suite is a context + action pair: what the prior candle (C1) set up, and what the current candle (CC) does to its range. The Suite draws the actionable levels, tells you when one is live, and stays out of the way otherwise.
Bar-type notation is defined in bar-types.md — this doc assumes it.
How a signal works
- Levels. When an enabled setup exists, the prior candle's high and/or low are drawn as trigger lines. An inside-bar setup draws both sides (yellow above, orange below) — the break direction isn't known yet.
- Potential. Until price crosses a trigger, labels carry the
*prefix (*2d-1-2u): a setup, not a trade. - In force. Price beyond the trigger with the setup's conditions met: the line takes the signal color (green/red), the
*drops, the alert fires, and the timeframe highlights in the data table. A signal stays in force only while price holds beyond the trigger. One exception: a Failing 2 is in force from detection — the failure itself is the event, not a trigger cross. - Targets. Magnitude and exhaustion levels project from there — covered in
targets-and-stops.md.
The signal types
| Family | Type | Example | Default | One line |
|---|---|---|---|---|
| Reversals | Inside Reversal | 2d-1-2u |
On | Consolidation resolves against the prior move |
| Reversals | 2-2 Reversal | 2d-2u |
On | Direct reversal, no pause |
| Continuations | Inside Continuation | 2u-1-2u |
On | Pause, then the move resumes |
| Continuations | 2-2 Continuation | 2u-2u |
Off | Sustained push — noisy unfiltered |
| Reclaims | Failing 2 (Range Reclaim) | F2u / F2d |
Off | The breakout itself fails — one-bar reversal |
| Expansions | 3-2 Expansion | 3-2u |
Off | Outside bar's indecision resolves |
| Expansions | Outside Bar (3 Exp) | 3u / 3d |
Off | CC engulfs the prior range |
Inside Reversals — the bread-and-butter
C1 is inside, CC breaks opposite C2's direction. Price coiled, then rejected the prior move. A C2 with no direction (doji) counts as the reversal side, so the setup still qualifies. Filters: HAM/SHO (require C1 to be a hammer/shooter — the coil already rejected one side), FTFC.
2-2 Reversals — faster, less coiled
CC immediately breaks against a directional C1. Filters: HAM/SHO, FTFC, and the distinctive one — F2: require C1 to have been a Failing 2 itself (F2d-2u). The prior bar already trapped traders on a failed break; their exits fuel your move.
Inside Continuations
C1 inside, CC resumes C2's direction — a trend pullback in miniature. Filters: HAM/SHO, FTFC.
2-2 Continuations — off for a reason
Any two trending bars qualify, which is why this ships disabled. Turn it on with HAM/SHO or FTFC attached, or expect noise. Continuation setups where C2 was an outside bar are excluded (the "trend" was one bar old).
Failing 2s (Range Reclaims)
CC breaks a side, then reclaims it — the one-bar reversal the community also calls a Potential 3 or 1-bar Rev Strat. Direction letter names the failed break: F2u is bearish, F2d bullish. In force from detection; the take-action zone is the reclaimed C1 range itself.
Note the two-layer design: detection (Advanced → Failing 2 Detection, on by default) classifies F2s everywhere — table, labels, line styling — while the signal toggle (Signals → Reclaims, off by default) decides whether they trade: levels, windows, alerts. Detection on + signal off means you see F2s but aren't prompted to act on them.
3-2 Expansions
C1 was an outside bar — both sides ran, indecision. CC committing to one side resolves it, and the move often runs hard if the 3 shook out weak hands. Filters: HAM/SHO, FTFC.
Outside Bars (3 Exp)
CC itself takes both sides of C1. Direction from close vs open (3u/3d). This is volatility expansion, not a directional setup in the classic sense — magnitude/exhaustion targets for it are separately toggleable under Advanced.
Cross-cutting filters
- HAM/SHO — per-family toggle requiring
C1conviction (seebar-types.mdfor the three definitions). - FTFC — per-family toggle requiring all monitored timeframes to agree on direction (close vs open). Ships off everywhere. It filters hard, so I made it opt-in.
- Lead Signal (Filters → Lead Signal, off) — the highest timeframe with a signal in force becomes the Lead; lower timeframes only show setups aligned with it. Counter-trend noise on the 15m stops fighting your daily.
- Domino (always computed) — consecutive inside bars stacked across timeframes, flagged in the table and alertable. A market coiled on multiple timeframes at once.
Universal mode names
With Label Style = Universal, the same signals read in plain language: ↑REVERSAL, ↑CONTINUATION, ↑INSIDE, ↑EXP, OUTSIDE, and FAILING for a live F2 — with ◆ marking HAM/SHO conviction and * still meaning potential. Same engine, same triggers; only the vocabulary changes.
Why a given line draws or doesn't: ../engineering/drawing-decisions.md. Targets and stops: targets-and-stops.md.
Full Timeframe Continuity (FTFC)
One question, asked on every timeframe at once: is the current candle trading above or below its open? When every timeframe you monitor gives the same answer, you have Full Timeframe Continuity — the 30-minute candle, the daily, and the monthly are all being pushed the same way at the same moment. FTFC doesn't generate trades; it tells you which trades are swimming with the current and which are fighting it.
Bar-type notation is defined in bar-types.md — this doc assumes it. Applies to v2.2.7-split.
The idea
Every candle carries two independent facts (bar-types.md calls them channels): its structure — what it did to the prior range — and its sign — close above or below its own open. FTFC is built entirely from the sign channel. Structure tells you what a setup is; continuity tells you whether the broader stack is leaning with it.
Three readings:
| Reading | Meaning |
|---|---|
| FTFC Up | Every monitored timeframe's current candle is above its open |
| FTFC Down | Every monitored timeframe's current candle is at or below its open |
| Conflict | Anything else — the timeframes disagree |
Conflict is the market's resting state. Full continuity is the exception, and that's exactly what makes it information: when every enabled timeframe agrees, participants at every scale are positioned the same way, and a trade against them needs all of them to be wrong.
How the Suite computes it
calculateFTFC re-evaluates on every chart update. Each enabled timeframe slot contributes its forming candle — not the last completed one — and the test is close-so-far versus open. All above → Up. All at-or-below → Down. Mixed → Conflict.
The details that matter:
- It's live by design. The forming candle's close moves tick by tick, so FTFC can flip intrabar — especially when a big timeframe is hovering at its open. That's the point: continuity is a statement about now.
- Flat buckets down. A close exactly at the open counts as not above — it blocks FTFC Up, not FTFC Down. Same tie-break as everywhere else in the Suite (
bar-types.md). - Only enabled slots vote. The Timeframe Preset (or your Custom rows) decides which of the six slots are live; a disabled slot contributes nothing — no levels, no table row, no FTFC vote. Slots with no data yet (young symbols) are skipped rather than counted against continuity.
- Preview Mode doesn't distort it. FTFC always reads the real forming candles, never the preview-shifted synthetic ones.
- Keep the chart at or below Timeframe 1. Slots below the chart's timeframe are refused for signals and greyed in the table (see
../engineering/htf-correctness.md) — don't leave them enabled expecting a meaningful vote.
Where you see it
The data table row. Both table modes render a merged full-width FTFC cell: green FTFC Up, red FTFC Down, gray Conflict. With Label Style = Universal the same cell reads Trend Up / Trend Down / No Trend. The table is FTFC's only on-chart readout — hide the table and you lose the display (alerts still work).
Don't reconstruct it from the CC column. A 2u can close red — on 2s the suffix names the break side, not the sign (bar-types.md, the u/d overload). Only 1u/1d and 3u/3d suffixes carry close-vs-open. The FTFC cell is the authoritative read.
Alerts. Two surfaces:
- Include FTFC (Alerts - Detailed → Include FTFC, off) appends the state to consolidated alert text:
1H 2d-1-2u HAM @ 542.10 … | FTFC Up. - Four dedicated alert conditions — FTFC Shifted, FTFC Up, FTFC Down, FTFC Conflict — fire on the transition into that state, not continuously while it holds.
The settings that touch it
| Setting | Where | Default | What it does |
|---|---|---|---|
| FTFC checkbox, per signal family | Each Signals group — all seven families have one | Off | Suppresses that family's setups when continuity stands against them |
| Exhaustion Excludes from FTFC | Advanced → Exhaustion Behavior | Off | Table cell only: drops structurally finished timeframes from the reading |
| Include FTFC | Alerts - Detailed | Off | Appends the FTFC state to consolidated alert messages |
| Preset / Timeframes 1–6 | Timeframe Preset, Timeframes | TheStrat Classic | Decides which slots vote |
How the filter actually gates
The per-family FTFC checkbox is a veto, not a prerequisite. With it on, a bullish setup is suppressed while FTFC reads full Down — and only then. Conflict, the usual state, does not suppress:
| FTFC reading | Bullish setup, filter on | Bearish setup, filter on |
|---|---|---|
| Up | shows | suppressed |
| Conflict | shows | shows |
| Down | suppressed | shows |
So the filter removes the lowest-probability case — a setup fighting a fully aligned stack — without demanding the stack already agree with you before a setup can appear. (The gate lives in shouldDrawC1Level; each family's checkbox is wired to the family a setup resolves to — a hammer reversal obeys the 2-2 Reversals toggle, not Inside Reversals' — FIX BTC-1.)
Two places are deliberately stricter — they demand agreement with the trade, not just absence of opposition:
- Failing 2 in-force. With the Reclaims FTFC filter on, a bullish
F2dgoes in force (and alerts) only under full FTFC Up; its levels still draw under Conflict. A reclaim fades a just-failed breakout, so the filter insists the whole stack back the fade. Expect far fewer F2 signals with this on. - Outside-bar targets. With the Outside Bars FTFC filter on, a
3u's magnitude and exhaustion targets require full FTFC Up (mirrored for3d).
Exhaustion Excludes from FTFC
A timeframe that has already hit its exhaustion target (or magnitude, when no exhaustion level exists) has structurally finished its move — arguably it shouldn't keep blocking the reading. Turn this on and exhausted timeframes are dropped from the calculation: if five of six are bearish and the lone bullish one has already hit exhaustion, the cell reads FTFC Down instead of Conflict.
Display only (FIX P1-e): this changes the data-table cell and nothing else. Signal gating and alerts always use the raw reading, so toggling it will never make signals appear or disappear. Treat it as a read aid, not a filter.
Using it
- As context first. The row is a one-glance regime check: Conflict says pick your spots; full continuity says one side owns every timeframe right now.
- Continuations pair naturally.
2u-1-2uunder FTFC Up is a pullback resuming a move the whole stack agrees on — the classic use, and the recommended way to tame 2-2 Continuations' noise (signals.md). - On reversals it trims, not chokes. Because the filter is a veto, a bullish reversal survives Conflict — it only disappears while every timeframe is against it, which is precisely when fading is most expensive.
- Reclaims + FTFC is strict on purpose. Turn it on only if you want F2s exclusively when the full stack backs the fade.
- FTFC Shifted as a regime ping. One alert that fires whenever the stack changes its answer — useful even if you never enable a single filter checkbox.
The close-vs-open channel and the u/d overload: bar-types.md. The families each FTFC checkbox filters: signals.md. Where the gate sits in the draw pipeline: ../engineering/drawing-decisions.md.
Targets & Stops
A signal names the trade; targets and stops price it. For every actionable signal the Suite can project three derived levels — magnitude (the measured target), exhaustion (the extended target), and a stop — plus a shaded Take Action Window between trigger and target. This doc covers what each level means in TheStrat method, how the Suite computes it, how it renders, and every setting that touches it.
Bar-type notation is defined in bar-types.md; signal types and in-force rules in signals.md — this doc assumes both.
The levels at a glance
| Level | Price | Label | Default | One line |
|---|---|---|---|---|
| Trigger | C1 high / low | combo (2d-1-2u) |
per family | Entry — covered in signals.md |
| Magnitude | C2 high / low | MAG (TARGET) |
On | The measured move: traverse the range the setup formed against |
| Exhaustion | Nearest untested swing beyond | EXH (FINAL) |
On | Where the move may structurally finish |
| Stop | Opposite extreme of CC or C1 | STOP |
Off | Where the signal's premise is dead |
| Take Action Window | trigger → target, shaded | — | On | The zone where entering still leaves room |
Labels in parentheses are the Universal-mode names. All levels are computed per timeframe slot from that timeframe's own candles — a 1H magnitude and a Daily magnitude are independent levels that happen to share a chart.
Magnitude — the measured move
TheStrat's target logic is mechanical: a triggered signal is expected to traverse the range it broke out against. Enter a 2d-1-2u over the inside bar's high, and the target is the high of the 2d bar — C2's extreme on the break side. No projection math, no multipliers; the level already printed.
The Suite snapshots C2's high and low at period open (served from completed bars — see ../engineering/repaint-prevention.md for why that matters). The line draws from C2's candle forward to the projected end of the current period, in the signal color, solid.
When magnitude doesn't draw, it's one of these — each is deliberate:
- C1 already traded through C2's extreme (
shouldDrawMagnitude). The "target" would sit behind the entry. - The signal is a continuation. A
2u-2uor3-2ubroke C2's extreme by definition, so magnitude is already spent — exhaustion takes over as the target. - A Failing 2 points the other way. An
F2uis a failed upside break; projecting an upside target off it is a contradiction. - Dedup. Magnitude equal to the trigger price: the trigger wins. Equal to the exhaustion level: exhaustion wins. One line per price.
- Only When In-Force is on and the signal hasn't triggered yet.
The full gate battery, with the engineering rationale for each, is in ../engineering/drawing-decisions.md (Stage 4).
Exhaustion — where the move may be done
Exhaustion is the closest prior swing high (or low) that price hasn't been back through since it printed — the nearest level that still holds untested structure beyond the magnitude. A move that reaches it has taken out every nearer extreme; expecting more from the same signal is asking a lot.
findExhaustionLevels runs inside each timeframe's own bar context: it scans up to ~48 completed bars back for the first pivot (an extreme higher than both neighbors) that also stands beyond every bar between it and now. Each side is independent, and either can legitimately not exist — price near its recent extreme has no untested swing above it, and the Suite draws nothing rather than inventing a level. Wherever the Suite needs "the far target" and no exhaustion exists, magnitude serves the same role (break-even triggers, the Advanced exhaustion behaviors below).
Details that follow from the definition:
- The level is a snapshot taken at period open and holds for the whole period (
FIX P0-2in../engineering/repaint-prevention.mdcovers why it's seeded from completed bars only). - The line starts at the pivot candle that defines it, so you can see which swing you're targeting.
- An exhaustion equal to the trigger, or colliding with the opposite side's trigger or magnitude, is discarded as invalid.
- Only After Magnitude Hit (off by default) hides exhaustion until price has actually reached magnitude — first target first.
Hit levels turn grey, not off
When price touches a magnitude or exhaustion level, a per-period latch sets and the line changes to the Crossed color (Style → Bullish/Bearish Levels; grey by default). The level stays on the chart for the rest of the period — including exhaustion levels whose trigger line is already gone. A target you reached is a record, not clutter; removing it would hide the most important fact of the session.
The latches are monotone — once crossed, crossed for the period — and they feed everything downstream: line color, Take Action Window removal, break-even stops, and the Advanced exhaustion behaviors.
Take Action Windows
The shaded box between the trigger and the target is the take-action zone: while price is inside it, entering still leaves room to the objective. The box is drawn from the setup candle forward, filled and bordered in the signal color at low opacity (Fill 8 / Border 30 by default).
Behavior worth internalizing:
- The window extends to exhaustion when it exists and qualifies (Extend to Exhaustion, on by default), otherwise to magnitude.
- It renders only while price is actually beyond the trigger in the signal's direction, and disappears once the destination level is crossed — the window's job is done, the grey line takes over as the record.
- Only When In-Force defaults on for windows (unlike the target lines), so a potential setup shows lines but no shading until it triggers.
- Failing 2 windows are different: the zone is the reclaimed C1 range itself (Advanced → Take Action Windows → Include Failing 2s), drawn until price escapes out the far side of the range — matching the F2's in-force-from-detection semantics in
signals.md. - Outside bars get a trigger→magnitude window on the side they're closing toward (Include Outside Bars), separate from the main path.
Each direction's box checks only its own trigger line — the opposite trigger is often legitimately suppressed exactly when a signal is in force (FIX TAW-1).
Stops
Off by default — the Suite doesn't presume to manage your risk until asked (Stops → Stop Levels → Enable). When on, a stop draws on the opposite side of every signal that goes in force, as a solid line from the current period's open.
Reference — the one setting to actually think about:
| Mode | Stop price | Character |
|---|---|---|
| CC (default) | The breakout bar's own opposite extreme — tracks live while the candle forms, final when it closes | Tighter. Invalidated when the breakout candle itself is fully reversed |
| C1 | The prior bar's opposite side, locked the moment the signal goes in force | Wider. Invalidated when the whole setup range is taken out |
Failing 2s always use CC regardless of the setting — the failed candle is the trade, so its extreme is the only honest invalidation.
The rules that make stops trustworthy:
- Sticky for the period. The stop latches the first time the signal goes in force and persists to period end even if the signal falls back out of force — price dipping under the trigger doesn't mean your fill went away. Surviving a chart reload with this lock intact is
FIX P1-ain../engineering/repaint-prevention.md. - Immune to exhaustion gating. The latch keys off raw in-force before the "Exhaustion Disables In Force" behavior is applied — an Advanced display preference can never unhook a protective stop.
- Never cosmetically hidden. Stop lines are excluded from cross-timeframe price-collision suppression (see the comment in
collectLinesFromTF, and../DESIGN_CONSTRAINTS.md). The only thing that removes a stop mid-period is the Lead Signal filter suppressing the counter-trend side it belongs to — a stop never vanishes on its own.
Break even (both off by default): move the stop to the entry trigger when price crosses magnitude or exhaustion respectively. Each falls back to the other level when its own doesn't exist, so "break even at magnitude" still works on a signal that only has an exhaustion target. At break even the stop coincides with the trigger line, so the separate stop line and label fold away and the trigger label gains a + STOP suffix.
Color: Opposite Signal (default — a long's stop wears the bearish color, which reads correctly: crossing it is the bearish event), Match Signal, or Custom with a single color.
Targets and "in force"
Three interactions, all opt-in:
- Only When In-Force exists independently for magnitude, exhaustion, the base window, and the window's exhaustion extension (the Failing 2 and outside-bar windows reuse the base window's toggle). Defaults: target lines show on potential setups (
*-prefixed), windows don't. - Exhaustion Disables 'In Force' (Advanced → Exhaustion Behavior, off): once a signal hits exhaustion — or magnitude, when no exhaustion level exists — it stops counting as in force. The default (off) says a move past its targets is still a move; turning it on says the move is structurally complete, which releases the Lead Signal filter to allow lower-timeframe reversals against it.
- Exhaustion Excludes from FTFC (same group, off): a timeframe whose move is complete stops blocking Full Timeframe Continuity from flipping. Same fallback: magnitude stands in when exhaustion doesn't exist.
Detection vs signal nuances
signals.md defers these; here they are:
- Outside bars (3 Exp) get their own target toggles (Advanced → Outside Bars → Show Magnitude / Show Exhaustion for Outside Bars, both on) underneath the global toggles (
FIX CSS-2made that gating symmetric). Targets project only on the side the bar is closing toward — an outside bar is a two-sided event, but its targets are not. - An inside bar that becomes a 3 (CC engulfs C1) projects magnitude only when 3-Exp display is on — no orphan targets from a signal family you've hidden.
- Failing 2s never draw targets on the failed side; their actionable geometry is the reclaimed C1 range (the F2 window above), and their stop is always CC.
- A flat (doji) C2 counts as the reversal side for in-force purposes (
FIX CSS-1), which matters here because the sticky stop latch and every Only-When-In-Force gate read that same in-force verdict — table, lines, windows, and stops agree by construction.
Every setting that touches these levels
| Group | Setting | Default |
|---|---|---|
| Targets — Magnitude & Exhaustion | Show Magnitude Levels / Only When In-Force | On / Off |
| Targets — Magnitude & Exhaustion | Show Exhaustion Levels / Only When In-Force / Only After Magnitude Hit | On / Off / Off |
| Targets — Take Action Windows | Show Take Action Windows / Only When In-Force | On / On |
| Targets — Take Action Windows | Extend to Exhaustion / Only When In-Force / Fill & Border Opacity | On / On / 8 & 30 |
| Stops — Stop Levels | Enable / Reference / Break Even at Mag / at Exh / Color Mode / Custom Color | Off / CC / Off / Off / Opposite Signal / white |
| Style — Bullish & Bearish Levels | Crossed colors (hit-level grey) | greys |
| Advanced — Exhaustion Behavior | Exhaustion Disables 'In Force' / Excludes from FTFC | Off / Off |
| Advanced — Outside Bars | Show Magnitude / Show Exhaustion for Outside Bars | On / On |
| Advanced — Take Action Windows | Include Failing 2s / Include Outside Bars | On / On |
| Alerts — Detailed | MAG / EXH / STOP fields appended to the consolidated alert | Off |
One performance note: with both target toggles off, the exhaustion pivot scan doesn't run at all (see ../engineering/performance.md).
Why a specific level isn't drawing: the gate-by-gate walkthrough and diagnostic table in ../engineering/drawing-decisions.md. Why none of these levels move on reload: ../engineering/repaint-prevention.md.
Reading the Chart
Everything the Suite draws — labels, lines, the data table, alert messages — is a rendering of the same underlying state. This doc is the decoder: what each piece of text means, what the colors say, and how to read the table row by row. Notation itself (what 2u or F2d means) is defined in bar-types.md; signal semantics live in signals.md. This doc assumes both.
Applies to v2.2.7-split and later. Code references are function names and FIX tags; grep the source for them.
Label anatomy
Every level label is assembled from the same ordered parts (collectTimeframeLabels). A fully-loaded example:
1H *2d-1-2u HAM H 542.10
| Part | Example | Meaning | Always shown? |
|---|---|---|---|
| Timeframe | 1H |
Which monitored timeframe owns this level (getTimeframeLabel: 15m, 30m, 1H, D, W, M, …) |
Always |
| Combo / word | *2d-1-2u |
The setup — or a level word (MAG, EXH, OPEN, STOP) |
Always |
| Pattern suffix | HAM / SHO |
C1 is a hammer/shooter. Universal mode uses ◆ instead |
When detected |
| Side | H / L |
High-side or low-side level | Only with Show H/L in Labels on |
+ STOP |
The stop sits at break-even — exactly on this trigger — so its label merged in | When stop = trigger price | |
| Price | 542.10 |
The level's price | Only with Show Price in Label on |
Two more assembly rules:
- Same price, one label. Any labels landing on the same tick-rounded price merge, joined with
+(consolidateAndCreate).1H MAG + 15m 2u-2uis one price where the hourly magnitude and a 15m trigger coincide — confluence, made legible. Parts join in collection order, TF slot 6 down to slot 1 — with the standard presets, that's highest timeframe first. - Two placements. Timeline labels (default on) sit at each line's end. Floating labels (default off) hold a fixed offset from the current bar so they never scroll away. Same text either way.
The level vocabulary
Six kinds of level, six words:
| Label text | Universal mode | The level | Line style |
|---|---|---|---|
combo (*2d-1-2u, 2u-F2u, …) |
words (↑REVERSAL, …) |
Trigger — C1's high or low | Dashed (solid when the structure itself is the event: confirmed F2 reclaim side, committed outside bar) |
MAG |
TARGET |
Magnitude — first target, C2's high/low | Solid |
EXH |
FINAL |
Exhaustion — extended target | Solid |
OPEN |
OPEN |
The current period's open | Dotted, gray |
STOP |
STOP |
Protective stop (drawn on the opposite side of the trigger) | Solid |
*F2d / *F2u, then F2d / F2u |
OPEN |
The CC open during a Failing 2 — the reclaim reference level | Dotted |
The shaded boxes between trigger and target are Take Action Windows — the tradeable zone for an in-force signal, filled with the same bullish/bearish Signal colors as the lines.
Combo strings and the canonical tuple
A combo reads oldest → newest, and the last token is always the forming candle: in 2d-1-2u, the 2u is happening right now. Full reading rules are in bar-types.md; the ones you need constantly:
*prefix = potential.*2d-1-2uis a setup whose trigger hasn't broken yet. When price crosses, the*drops and the line takes the signal color.Fmarks a slot that was a Failing 2.F2d-2u— the prior bar failed its downside break, the current bar is following through up.- Dashes are cosmetic. Use Dash Separator (default on) turns
2d12uinto2d-1-2u(formatCombo). Same string. - During a live Failing 2, both sides tell the story. The failing side reads the F2 combo (
2u-F2u); the opposite trigger reads a potential outside bar (*2u-3d) — because a full traversal through the other side would print a 3.
Casing carries structure, never tense (NOTATION-1)
Failing 2s are always uppercase F2u/F2d, live or closed. Earlier versions wrote a forming Failing 2 in lowercase (f2u); that was retired in v2.2.7 because case-significant notation can't be spoken, doesn't survive small label sizes, and marked liveness for exactly one bar type. Liveness is carried where every bar type carries it:
- Slot position — the last token of a combo is the forming candle.
*prefix — potential, not yet confirmed (*F2don a pre-F2 open level).- Words — Universal mode says
FAILINGwhile live; past tense is failed.
The tuple is the truth
Every string on the chart is a projection of one four-field tuple per candle — structure (1 / 2u / 2d / 3), above_open, failed, live (the full table is in bar-types.md). When two displays seem to disagree — a table cell versus a label, a label versus an alert — resolve both back to the tuple before assuming a bug. They cannot actually conflict; they can only be rendering different fields.
Universal mode
With Label Style = Universal, combos become words: ↑REVERSAL, ↓CONTINUATION, ↑INSIDE, ↑EXP, *OUTSIDE, with FAILING appended to the side a live F2 is failing and ◆ replacing HAM/ SHO. The * prefix still means potential. MAG/EXH become TARGET/FINAL. Same engine, same triggers, same colors — only the vocabulary changes.
Colors
All configurable under Style — these are the defaults, and each has one job:
| Color | Default | Means |
|---|---|---|
| Green (Signal) | color.green |
Bullish level, live — trigger broken or in force |
| Red (Signal) | color.red |
Bearish level, live |
| Yellow | #ffeb3b |
Inside-bar potential, high side — break not yet known |
| Orange | #ff9800 |
Inside-bar potential, low side |
| Teal | #089981 |
Bullish outside-bar (3) expansion level |
| Pink | #e91e63 |
Bearish outside-bar expansion level |
| Soft green | #81c784 |
Range Reclaim F2d — failed downside break, bullish |
| Soft red | #f77c80 |
Range Reclaim F2u — failed upside break, bearish |
| Light / dark gray | #9c9c9c / #4a4a4a |
Crossed — a magnitude or exhaustion level already hit, visually receding |
The read at a glance: yellow/orange = waiting, green/red = live, gray = done. An inside-bar setup draws both sides (yellow above, orange below) because the break direction isn't known yet; the moment one side breaks, that line goes signal-colored and the * drops.
The data table
The multi-timeframe scoreboard, updated on the live bar only. Two modes (Display → Data Table → Mode).
Full (TheStrat) mode
One row per enabled timeframe, five columns (populateBarTypeRow):
| Column | Contents |
|---|---|
| TF | The timeframe label. Its background lights up in the CC bar-type color when a signal is in force on that timeframe — a live 2/F2 signal or an enabled outside-bar expansion. Inside bars never light it (FIX TFCOLOR-1): an inside bar is by definition not in force. |
| C2 | Two bars back, completed — colored text on black |
| C1 | The prior bar, completed — colored text on black |
| AS | Actionable signal on C1: HAM, SHO, or INS (inside), else blank |
| CC | The forming bar — black text on a solid color block. The one cell that changes as price moves |
The typography is deliberate: completed bars are colored text, the forming bar is a filled cell. Your eye goes to the live column.
Cell text and colors (getBarTypeColor, getBarTypeTextColor) follow the same scheme as the chart: 1u yellow / 1d orange (inside, closed above/below open), 2u green / 2d red, 3u/3d and F2u/F2d in your Style colors for outside bars and Range Reclaims. Two special cases:
?in the CC cell — the slot is previewing a period boundary and its forming candle could not be rebuilt from real daily sub-bars, so the Suite shows "unknown" rather than a guess (FIX HTF-STRADDLE-1). A?slot never fires alerts.- Dimmed, empty row — that timeframe is lower than your chart timeframe, so its bars can't be resolved from this chart. Switch to a lower chart TF to activate it.
The footer rows
Below the timeframe rows, in order, each spanning the table width:
| Row | Appears when | Reads |
|---|---|---|
| Domino | Stacked same-direction inside-bar setups ≥ your minimum (white background) | Domino: 15m+30m+1H — the coiled timeframes, lowest first |
| FTFC | Always | FTFC Up (green) / FTFC Down (red) / Conflict (gray). Universal: Trend Up / Trend Down / No Trend. With Exhaustion Excludes from FTFC on, this cell — and only this cell — drops exhausted TFs from the calculation (FIX P1-e); signals and alerts always use raw FTFC |
| Lead | Lead Signal filter enabled | Lead: D BULL — the anchor timeframe and its direction, or No Lead |
| PREVIEW MODE | Preview is active | Yellow banner — some slots are showing shifted/estimated periods |
Compact (Universal) mode
The whole table collapses to one colored chip per timeframe in a horizontal row, plus the same footer rows. Chip colors: yellow = inside and above open, orange = inside and below open, green/red = signal in force in that direction (Range Reclaim and outside-bar signals use their own Style colors), dark with white text = nothing live, yellow text = previewed slot with no rebuilt data (the ? equivalent).
Alert messages
The consolidated alert() message is built per signal by buildAlertLabel, one label per newly in-force event, joined with |:
<TF> <combo><pattern><H/L> <marker> | <detail fields> | <FTFC>
A fully-enriched single-signal push:
1H 2d-1-2u HAM 🟢 | @ 542.10 MAG 545.30 EXH 548.20 STOP 538.10 | FTFC Up
<TF> <combo>— same timeframe label and combo notation as the chart labels (Universal mode sends the word forms:1H ↑REVERSAL◆ 🟢). Failing 2 alerts use the canonical combo (2u-F2u), uppercase perNOTATION-1.<marker>— 🟢 bullish, 🔴 bearish. Always present; it's the direction read when the combo is ambiguous at a glance.- Detail fields —
@(trigger price),MAG,EXH,STOP, each opt-in under Alerts and each omitted entirely when its value doesn't exist for that signal. The|separator only appears when at least one field is present — no dangling pipes, no blankMAG(FIX P1-j). - FTFC verdict — appended once at the end of the whole message when Include FTFC is on:
FTFC Up/FTFC Down/Conflict. - Multiple simultaneous events chain with
|:15m F2d-2u 🟢 | 1H 2d-1-2u 🟢 | FTFC Up.
Domino alerts are their own message: Domino: 15m+30m+1H.
Two things worth knowing about delivery:
- The rich message only travels the
alert()path — create one TradingView alert with condition "Any alert() function call". The namedalertcondition()entries ("Signal In-Force (TF1)", "FTFC Shifted", …) fire with fixed text, a Pine limitation; use them for targeted routing, the consolidated alert for content. - What alerts is what displays. Alerts obey the same signal toggles as the chart (
FIX P0-3), fire only on a signal becoming in force — not on every bar it stays in force — and never fire from a previewed or straddled slot (FIX HTF-STRADDLE-1). If the table shows?, that slot is silent.
Every knob referenced here (label placement, table mode, alert enrichment, colors) is documented in ../TheStratSuite_v2.2.7_Settings_Reference.md. Notation rules and the canonical tuple: bar-types.md. What makes a signal fire in the first place: signals.md.
Feature Reference
Every feature in TheStrat Suite: what it does, where its settings live, and which doc covers it in depth.
Applies to v3.0.0. Settings paths refer to the indicator's settings panel groups.
Detection
Six-timeframe engine. The Suite monitors up to six timeframes at once from a single chart — bar classification, signals, and continuity on each. Presets configure all six in one click: TheStrat Classic, Scalp, Day Trade, Futures/Crypto, Swing Trade, Investing, or Custom for your own set. Settings: Timeframe Preset, Timeframes. Deep dive: architecture, htf-correctness.
Bar classification. Every candle typed against the prior candle's range: inside (1), directional (2u/2d), outside (3u/3d), with Failing 2s (F2u/F2d) as a modifier. The same classifier feeds the chart, the table, and the alerts, so they can never disagree. Deep dive: bar-types.
Strat combo labels. Signals are labeled in standard Strat notation (2d-1-2u HAM), oldest to newest, with * marking potential setups that haven't triggered. Settings: Display - Labels. Deep dive: reading-labels.
Inside Reversals. C1 inside, current candle breaks against C2's direction. On by default. Filters: HAM/SHO, FTFC. Deep dive: signals.
2-2 Reversals. Direct reversal with no inside-bar pause. On by default. Filters: HAM/SHO, FTFC, and F2 (require the prior bar to have been a Failing 2 — trapped traders fuel the move). Deep dive: signals.
Inside Continuations. Pause, then the prior move resumes. On by default. Filters: HAM/SHO, FTFC. Deep dive: signals.
2-2 Continuations. Sustained push, two directional bars the same way. Off by default because it's noisy unfiltered. Filters: HAM/SHO, FTFC. Deep dive: signals.
Failing 2s (Range Reclaims). A breakout that fails and reclaims the range — the one-bar reversal. Four detection methods (Reclaim, Open, both, either). Detection is always on for classification; the signal toggle decides whether they trade. Settings: Signals - Reclaims, Advanced - Failing 2 Detection. Deep dive: signals, bar-types.
3-2 Expansions. An outside bar's indecision resolved by the next candle committing to a side. Off by default. Deep dive: signals.
Outside Bars (3 Exp). The current candle engulfs the prior range — volatility expansion, direction from close vs open. Off by default. Deep dive: signals.
Hammers & Shooters. Candle-proportion patterns used as conviction filters or standalone signals, with three selectable definitions (Broad, Classic, Pin Bar) and an optional color match. Settings: Filters - Hammer/Shooter Detection. Deep dive: bar-types.
Full Timeframe Continuity (FTFC). Are all monitored timeframes trading above (or below) their opens right now? Shown in the table, usable as a per-signal filter, and alertable on shifts. Off as a filter by default — it filters hard, so I made it opt-in. Deep dive: ftfc.
Lead Signal filter. The highest timeframe with a signal in force becomes the Lead; lower timeframes only show setups that agree with it. Settings: Filters - Lead Signal. Deep dive: signals.
Domino detection. Consecutive inside bars stacked across timeframes — a market coiled on several scales at once. Shown in the table, alertable. Settings: Filters - Domino Setups. Deep dive: signals.
Levels and risk
Trigger levels. The prior candle's high/low drawn as entry lines, styled by state: yellow/orange potential, green/red in force, gray crossed. Deep dive: reading-labels, drawing-decisions.
Magnitude targets. The measured first target (C2's extreme) for every actionable signal, with an "only when in force" mode. Settings: Targets - Magnitude & Exhaustion. Deep dive: targets-and-stops.
Exhaustion levels. The extended target: the prior intact swing beyond magnitude, found by pivot scan. Optional "only after magnitude hit." Deep dive: targets-and-stops.
Take Action Windows. The zone between trigger and target, shaded while an entry or add still works. Extends to exhaustion if you want. Settings: Targets - Take Action Windows. Deep dive: targets-and-stops.
Stop losses. A stop on the opposite side of the trigger, CC or C1 referenced, locked for the period once a signal fires, with optional break-even moves at magnitude or exhaustion, and a Smallest Timeframe Only display mode that draws just the tightest active stop when several timeframes are in force. Off by default. Settings: Stops - Stop Levels. Deep dive: targets-and-stops.
Display and workflow
Data table. Live multi-timeframe state: C2, C1, actionable-setup and current-candle columns in Strat notation (Full mode), or a compact color-coded row (Compact mode), plus FTFC, Domino, and Lead rows. Settings: Display - Data Table. Deep dive: reading-labels.
Universal labels. The same signals in plain language — REVERSAL, CONTINUATION, INSIDE, OUTSIDE, FAILING — for traders who don't speak Strat notation yet. Settings: Timeframe Preset → Label Style. Deep dive: reading-labels.
Bar coloring (new in v3). Chart candles painted by their Strat classification or by FTFC direction, with optional Failing-2 flip highlighting. History is graded honestly — the deep dive covers how. Off by default. Settings: Display - Bar Coloring. Deep dive: settings-reference.
Preview mode. When the market is closed, the Suite shifts to next period's levels so you can plan before the open — automatic per asset class (equities, futures, crypto), with holiday handling. Settings: Display - Preview Mode. Deep dive: htf-correctness.
Alerts. One consolidated alert covering every enabled timeframe with optional trigger/target/stop prices in the message, plus per-timeframe, direction, potential-setup, Domino, and FTFC-shift alert conditions. Alerts fire on state changes, never on every bar. Settings: Alerts - Detailed. Deep dive: settings-reference, repaint-prevention.
No repainting. Not a toggle — a contract. Completed-bar signals never change on reload; the rules and their receipts are documented. Deep dive: repaint-prevention.
Debug panel. Per-timeframe internals (classification flags, draw decisions) on the chart for troubleshooting a setup that didn't draw. Settings: Debug. Deep dive: drawing-decisions.
Settings Reference
Version 3.0.0 | Last updated 2026-07-31
This reference documents every setting in the TheStrat Suite indicator, grouped in the order the panels appear in the TradingView settings dialog. Each entry lists the default value and available options, what the setting does, and when to use it. New to TheStrat? Start with the Overview and TheStrat Concepts (Primer) below.
Written against pine/TheStratSuite_v3.0.0.pine. Where behavior traces to a specific fix, the FIX tag is cited; grep the source for it.
Contents
- Overview
- TheStrat Concepts (Primer)
- Timeframe Preset
- Timeframes (Custom)
- Signals - Reversals
- Signals - Continuations
- Signals - Reclaims
- Signals - Expansions
- Filters - Hammer/Shooter Detection
- Filters - Domino Setups
- Filters - Lead Signal
- Targets - Magnitude & Exhaustion
- Targets - Take Action Windows
- Stops - Stop Levels
- Display - Preview Mode
- Display - Data Table
- Display - Labels
- Display - Bar Coloring
- Style - Level Colors
- Alerts
- Advanced - Exhaustion Behavior
- Advanced - Outside Bars
- Advanced - Failing 2 Detection
- Advanced - Data Table
- Advanced - Take Action Windows
- Advanced - Domino Setups
- Debug
- Troubleshooting & FAQ
Overview
TheStrat Suite is a multi-timeframe price-action indicator that implements TheStrat methodology on your TradingView chart. It watches up to six timeframes at once, classifies every candle by its TheStrat bar type (inside, directional, or outside), and draws the key structural levels (triggers, magnitude targets, exhaustion targets, and stops) that those bars create. On top of the raw structure it detects the named setups traders actually trade (inside reversals and continuations, 2-2 reversals and continuations, Failing 2 Range Reclaims, and 3-bar expansions), each with optional Hammer/Shooter, FTFC, and Failed-2 confluence filters so you see only the setups that match your style.
Beyond signals, the Suite gives you a live read on the whole timeframe stack. A data table reports the current bar type on each timeframe in either full Strat notation or a plain-language Universal view, Full Timeframe Continuity (FTFC) tells you when every monitored timeframe agrees on direction, the Lead Signal filter keeps lower-timeframe entries aligned with the highest timeframe already in force, and Domino detection flags stacked inside bars that are coiling across timeframes. Take Action Windows shade the zone between trigger and target so the tradeable area is obvious at a glance.
Everything is alert-ready and plan-ahead friendly. Consolidated and per-timeframe alerts can be enriched with trigger, magnitude, exhaustion, stop, and FTFC context, and Preview Mode projects next-period levels while the market is closed so you can prepare before the open. The Suite is built to be non-repainting, so the levels you see while a bar is live are the levels that remain after it closes (see docs/engineering/repaint-prevention.md for how that guarantee is kept).
Quick Start
- Add TheStrat Suite to your chart. It overlays directly on price.
- Open the settings and pick a Timeframe Preset (TheStrat Classic, Scalp, Day Trade, Futures/Crypto, Swing Trade, or Investing). The preset auto-configures which timeframes are monitored and their line widths, so you do not need to touch the individual Timeframe rows. Choose Custom only if you want to set the six timeframes yourself. Then set your chart at or below the preset's fastest timeframe - the Suite reads higher timeframes from a lower chart, never the reverse. On TheStrat Classic that means a 30m chart or lower (Scalp: 5m, Day Trade: 15m). Any slot faster than your chart timeframe shows as a dimmed, empty table row.
- Choose a Label Style. Leave it on TheStrat for standard Strat combo notation (for example 2d-1-2u HAM), or switch to Universal for plain-language labels (REVERSAL, CONTINUATION, INSIDE, OUTSIDE) if you are new to TheStrat.
- Optionally tune which signals show under the Signals panels, then create one TradingView alert on the indicator with the condition "Any alert() function call" - the consolidated alert, and the only path that carries the full message (trigger and target prices, FTFC). Use the named conditions ("Signal In-Force (TF1)", "Domino Setup", and so on) only when you want separate routing; they fire with fixed text.
TheStrat Concepts (Primer)
These definitions describe the vocabulary used throughout the rest of this documentation, grounded in how TheStrat Suite actually computes each one.
- Bar type 1 (inside bar) - A candle whose high did not exceed the prior bar's high and whose low did not undercut the prior bar's low. It sits entirely inside the previous bar's range, meaning the market paused and coiled. Type 1 bars are the setup state that precedes reversals and continuations.
- Bar type 2u - A candle that broke above the prior bar's high but did not break below its low. It is a one-sided directional (up) bar. A bar that has gone 2u can only ever close as 2u or 3, never back to 1 or 2d.
- Bar type 2d - A candle that broke below the prior bar's low but did not break above its high. It is a one-sided directional (down) bar, and like 2u it can only resolve to 2d or 3.
- Bar type 3 (outside bar) - A candle that broke both the prior bar's high and its low, engulfing the entire prior range. It is graded directionally by its close versus open: 3u if it closed above its open (bullish), 3d if it closed below (bearish). A type 3 bar represents range expansion and volatility.
- C1 / C2 / CC notation - These name the candles relative to the current one, as used in the data table and combo labels. CC is the current (forming) candle, C1 is the prior completed candle, and C2 is the candle before C1. A combo like 2d-1-2u reads left to right as C2-C1-CC. In the Full data table the columns are TF, C2, C1, AS, and CC, where the AS column flags whether C1 is actionable (HAM, SHO, or INS).
- In-Force vs setup - A setup is a structure that has not yet triggered (for example C1 is an inside bar but CC has not broken out). A signal is In-Force once CC has actually broken the trigger and price is currently beyond it (or, for Failing 2s, once the reclaim is confirmed). In-Force means the trade is live, not merely potential; many display options (magnitude, exhaustion, take action windows) can be restricted to In-Force only.
- Failing 2 (Range Reclaim, F2u / F2d) - A type 2 bar that broke out but then failed: price reclaimed back inside the prior range or closed against the breakout direction. F2u is a failed upside breakout (bearish), F2d is a failed downside breakout (bullish). Trapped breakout traders fuel the reversal. Also called a Potential 3 or 1-bar Rev Strat. Notation is always uppercase
F2u/F2dand is tense-free (NOTATION-1): whether the F2 is forming or completed is carried by its slot in the combo (last token = forming candle), the*potential prefix, and words like FAILING - not by letter case. - Failing 2 detection methods - How a fail is confirmed: Reclaim (the candle closes back inside C1's range), Open (the candle closes against its own breakout direction), Reclaim + Open (both conditions required), or Reclaim OR Open (either one). Reclaim is the default.
- FTFC (Full Timeframe Continuity) - Agreement across every monitored timeframe on direction, measured by each bar's close versus its open. FTFC Up means all enabled timeframes closed above their open; FTFC Down means all closed below. If they disagree it reads as Conflict. Used as an optional filter to keep signals aligned with the broader structure.
- Magnitude vs Exhaustion targets - Magnitude is the nearest take-profit target, the opposite extreme of C2's range projected from the trigger. Exhaustion is an extended target: the first intact prior swing high or low (a pivot) found by scanning back beyond C2. Magnitude is the first objective; exhaustion is where the move is structurally likely to run out.
- Hammer / Shooter - Single-candle rejection patterns. A Hammer tested lower and got rejected (long lower wick), implying bullish backing; a Shooter tested higher and got rejected (long upper wick), implying bearish backing. Detection strictness is configurable (Broad, Classic, or Pin Bar), and you can optionally require the candle's color to match its direction. Used as a confluence filter on most signals.
- Domino - A stack of consecutive inside bars across adjacent timeframes that all carry a visible setup in the same direction. When the run reaches your minimum-timeframe threshold it flags coiled, multi-timeframe energy primed to release. Domino respects your signal filters and de-duplicates timeframes that share the same underlying candle.
- Lead signal - The highest enabled timeframe that currently has a signal In-Force. When the Lead Signal filter is on, lower-timeframe signals are only shown if they align with the Lead's direction, keeping your entries in step with the dominant structure. If no higher timeframe is In-Force, all signals are shown.
Timeframe Preset
This panel controls how the indicator's six timeframe slots are configured. Choosing a built-in preset is the fastest way to get a sensible multi-timeframe setup; choosing Custom hands full control to the Timeframes panel below.
Preset (default: TheStrat Classic | options: Custom / TheStrat Classic / Scalp / Day Trade / Futures/Crypto / Swing Trade / Investing) - Auto-configures which timeframes are monitored, which ones are enabled, and the line width used to draw each one. When set to anything other than Custom, your manual Timeframe 1-6 rows are ignored and greyed out (a direct visual signal that the manual config is inert), so you only edit those rows after switching to Custom. Whichever preset you pick, set your chart at or below its fastest timeframe - any slot faster than the chart timeframe shows as a dimmed, empty table row. What each preset sets:
- Custom - Disables the preset override entirely and makes the Timeframes (Custom) panel editable. The six rows then use whatever you set them to.
- TheStrat Classic - 30m, 1H (60), Daily, Weekly, Monthly, Monthly. TF1-TF5 are enabled (TF5 Monthly on), TF6 off. Widths increase with timeframe (1 / 1 / 2 / 3 / 4).
- Scalp - 5m, 15m, 30m, 1H (60), 1H (60), 1H (60). TF1-TF4 enabled, TF5 and TF6 off. Widths 1 / 2 / 3 / 4 / 4.
- Day Trade - 15m, 30m, 1H (60), 4H (240), Daily, Daily. TF1-TF5 enabled (TF5 Daily on), TF6 off. Widths 1 / 1 / 2 / 3 / 4.
- Futures/Crypto - 1H (60), 4H (240), 12H (720), Daily, Weekly, Weekly. TF1-TF5 enabled (TF5 Weekly on), TF6 off. Widths 1 / 2 / 2 / 3 / 4.
- Swing Trade - 4H (240), Daily, Weekly, Monthly, Monthly, Monthly. TF1-TF4 enabled, TF5 and TF6 off. Widths 1 / 2 / 3 / 4 / 4.
- Investing - Weekly, Monthly, Quarterly (3M), Yearly (12M), Yearly (12M), Yearly (12M). TF1-TF4 enabled, TF5 and TF6 off. Widths 1 / 2 / 3 / 4 / 4.
Note: under any preset, the per-timeframe Open lines are forced off and TF6 is always off, regardless of the disabled manual rows behind them.
Label Style (default: TheStrat | options: TheStrat / Universal) - Controls the notation used on chart signal labels. TheStrat shows standard Strat combo notation (for example 2d-1-2u HAM). Universal shows plain-language labels (REVERSAL, CONTINUATION, INSIDE, OUTSIDE) with a direction arrow and a diamond marker for hammer/shooter confidence. Choose Universal if you are new to TheStrat or prefer a simplified read; switch to TheStrat once the combo notation is second nature. This setting only changes label wording, not which signals fire.
Timeframes (Custom)
These six rows define your own multi-timeframe stack. They are only editable when Preset = Custom; selecting any other preset greys out every field here (FIX MOD-3), since the preset supplies the values instead. Each timeframe has four controls on two inline rows: an Enabled toggle, the Timeframe itself, a line Width, and an Open toggle.
- Timeframe 1 (Enabled default: off | Timeframe default: 15 | Width default: 1 | Open default: off)
- Timeframe 2 (Enabled default: off | Timeframe default: 30 | Width default: 1 | Open default: off)
- Timeframe 3 (Enabled default: off | Timeframe default: 60 | Width default: 1 | Open default: off)
- Timeframe 4 (Enabled default: off | Timeframe default: D | Width default: 2 | Open default: off)
- Timeframe 5 (Enabled default: off | Timeframe default: W | Width default: 3 | Open default: off)
- Timeframe 6 (Enabled default: off | Timeframe default: M | Width default: 4 | Open default: off)
For each row:
- Enabled - Turns monitoring of that timeframe on or off. A disabled timeframe contributes nothing: no levels, no signals, no data-table row, and no part in FTFC. Note that all six rows default to off in Custom mode, so a fresh Custom setup shows nothing until you enable at least one timeframe.
- Timeframe - The TradingView timeframe string for that slot (minutes as numbers, or D / W / M, and so on). The defaults above climb from 15m through Monthly so a Custom stack starts ordered from fast to slow.
- Width (minval: 1 | maxval: 5) - Line thickness, in pixels, for every level this timeframe draws on the chart. Higher numbers make a timeframe visually dominant; the defaults deliberately make slower timeframes thicker (1 up to 4) so the higher-timeframe structure stands out from lower-timeframe noise.
- Open - Draws that timeframe's period Open as a reference line on the chart. Off by default for every slot. Turn it on for the timeframes whose open you trade against (for example the Daily or Weekly open as an above/below pivot); leave it off elsewhere to avoid clutter. Under any preset this line is forced off, and it is also suppressed while a slot is previewed or straddle-shifted, because the served open would be a proxy rather than the real period open (
FIX HTF-STRADDLE-1).
Signals - Reversals
Reversal signals fire when the current bar (CC) breaks the prior bar's range in the direction opposite to the prior trend. Each signal has its own per-signal filters that you can stack to tighten the setup. The filter checkboxes sit on the same row as the signal toggle (HAM/SHO, F2, and FTFC, where applicable). All filters are off by default, so out of the box you see the raw pattern.
Inside Reversals (default: on | options: on / off) - Detects the C1-inside reversal, where the prior bar (C1) is an inside bar and the current bar (CC) breaks in the opposite direction to the bar before the inside bar (C2), for example 2d-1-2u. This is the classic coiled-then-reversed Strat pattern: price consolidated inside a prior range, then broke out against the preceding move. A C2 with no direction (a flat doji) counts as the reversal side, so the setup still qualifies (
FIX CSS-1). On by default because it is the bread-and-butter setup. Turn it off if you only want direct reversals or are decluttering the chart.HAM/SHO (default: off) - Per-signal momentum filter for Inside Reversals. Requires C1 (the inside bar) to be a hammer or shooter, meaning the bar tested one side and got rejected, which adds conviction that the breakout direction has structural backing. Turn it on to keep only inside reversals that coil off a rejection candle. What counts as a hammer or shooter is set globally under Filters - Hammer/Shooter Detection (Definition, Match Candle Color), so this filter inherits that definition.
FTFC (default: off) - Per-signal Full Timeframe Continuity filter for Inside Reversals. Requires all monitored timeframes to agree on direction, removing setups that fight the broader structural alignment. Turn it on when you only want reversals that are swimming with the higher-timeframe current. Note that FTFC can be loosened by the Advanced - Exhaustion Behavior setting "Exhaustion Excludes from FTFC," which drops timeframes whose move is structurally complete from the calculation.
2-2 Reversals (default: on | options: on / off) - Detects the direct reversal with no inside-bar pause, where CC immediately breaks opposite to C1, for example 2d-2u. This is a faster signal than an inside reversal but carries less coiled energy because there was no consolidation. On by default. This signal has three filters: HAM/SHO, F2, and FTFC.
HAM/SHO (default: off) - Per-signal momentum filter for 2-2 Reversals. Requires C1 to be a hammer or shooter (tested and rejected one side) before the reversal qualifies. Uses the global Hammer/Shooter Detection definition. When this filter admits a reversal, that reversal still obeys this panel's own FTFC toggle (
FIX BTC-1).F2 (default: off) - Per-signal Failed-2 filter unique to 2-2 Reversals. Requires C1 to itself be a Failed 2, for example F2d-2u. This adds structural confluence: the prior bar already reversed a failed breakout, so traders trapped by that failure fuel the current move. Turn it on to demand that the bar feeding the reversal was itself a trap. This filter interacts with Advanced - Failing 2 Detection: F2 classification depends on Enable Failing 2 Detection being on and on the Detection Method you choose (Reclaim, Open, etc.), so the F2 filter only finds setups that the detection settings actually flag as Failed 2s.
FTFC (default: off) - Per-signal Full Timeframe Continuity filter for 2-2 Reversals. Requires all monitored timeframes to agree on direction. Same behavior and Exhaustion-Excludes interaction as the Inside Reversals FTFC filter.
Signals - Continuations
Continuation signals fire when CC breaks the prior range in the same direction as the preceding move. They are trend-following setups. Filters work the same way as on the reversal panel and are off by default. 2-2 Continuation setups where C2 was an outside bar are excluded - the "trend" was one bar old.
Inside Continuations (default: on | options: on / off) - Detects the C1-inside continuation, where C1 is an inside bar and CC breaks in the same direction as C2, for example 2u-1-2u. Price paused to consolidate and then continued the prior move, a trend continuation with a built-in pullback. On by default. Has HAM/SHO and FTFC filters.
HAM/SHO (default: off) - Per-signal momentum filter for Inside Continuations. Requires C1 (the inside bar) to be a hammer or shooter, adding conviction from a tested-and-rejected side. Uses the global Hammer/Shooter Detection definition.
FTFC (default: off) - Per-signal Full Timeframe Continuity filter for Inside Continuations. Requires all monitored timeframes to agree on direction. Because a continuation already runs with the prior move, this filter keeps only those that also align across every monitored timeframe.
2-2 Continuations (default: off | options: on / off) - Detects sustained momentum with no pause, where CC keeps pushing in C1's direction, for example 2u-2u. Off by default because any two trending bars qualify, so it can be noisy without filters. Turn it on when you want to catch running momentum, and consider pairing it with HAM/SHO or FTFC to cut the noise. Has HAM/SHO and FTFC filters.
HAM/SHO (default: off) - Per-signal momentum filter for 2-2 Continuations. Requires C1 to be a hammer or shooter. Uses the global Hammer/Shooter Detection definition. This is the primary noise-reduction filter for this signal.
FTFC (default: off) - Per-signal Full Timeframe Continuity filter for 2-2 Continuations. Requires all monitored timeframes to agree on direction, the second main way to filter the noise on this signal. Applies to HAM/SHO-admitted continuations as well (
FIX BTC-1).
Signals - Reclaims
- Failing 2s (Range Reclaims) (default: off | options: on / off) - Shows Range Reclaim signals, where price breaks out and then reverses back, also known as Failing 2s, Potential 3s, or 1-bar Rev Strat. A bullish Range Reclaim is a failed bearish breakout (F2d) and a bearish Range Reclaim is a failed bullish breakout (F2u). Off by default. Turn it on to trade the reversal of a failed breakout. A Failing 2 is in force from detection - the failure itself is the event, not a trigger cross - and a not-yet-confirmed one carries the standard
*potential prefix (*F2u/*F2d,NOTATION-1). This toggle controls whether the signals are displayed; the actual detection and what qualifies as a Failing 2 is governed by Advanced - Failing 2 Detection (Enable Failing 2 Detection plus the Detection Method). If Failing 2 Detection is disabled there, no Range Reclaims will appear here regardless of this toggle. - FTFC (default: off) - Per-signal Full Timeframe Continuity filter for Failing 2s. Requires all monitored timeframes to agree on direction, keeping only reclaims that align with the broader structure. Note: a Range Reclaim by nature fades a just-failed breakout, so demanding full continuity can sharply reduce how many signals appear.
Signals - Expansions
Expansion signals deal with outside bars (3-bars), which take out both sides of the prior bar's range. Both signals are off by default.
3-2 Expansions (default: off | options: on / off) - Detects the case where C1 was an outside bar (3-bar) and CC then commits to one direction, for example 3-2u. After C1 took both sides of its prior bar, CC resolves the indecision, which is often powerful when the 3-bar shook out weak hands first. Off by default. Has HAM/SHO and FTFC filters.
HAM/SHO (default: off) - Per-signal momentum filter for 3-2 Expansions. Requires C1 (the 3-bar) to also be a hammer or shooter, meaning it tested and rejected one side despite the range expansion, which adds directional conviction. Uses the global Hammer/Shooter Detection definition.
FTFC (default: off) - Per-signal Full Timeframe Continuity filter for 3-2 Expansions. Requires all monitored timeframes to agree on direction.
Outside Bars (3 Exp) (default: off | options: on / off) - Detects when CC itself is a 3-bar, engulfing C1's entire range. This represents range expansion and rising volatility, with direction set by close versus open (3u is bullish, 3d is bearish). Off by default. Has only an FTFC filter (no HAM/SHO). Target behavior for this signal is configured separately under Advanced - Outside Bars (Show Magnitude for Outside Bars, Show Exhaustion for Outside Bars), and its take-action zone under Advanced - Take Action Windows (Include Outside Bars).
FTFC (default: off) - Per-signal Full Timeframe Continuity filter for Outside Bars. Requires all monitored timeframes to agree on direction before the outside-bar signal (and, by extension, its magnitude and exhaustion targets) is shown.
Filters - Hammer/Shooter Detection
A hammer or shooter (collectively "actionable" candles, also called pivot machines) is a single bar that pushed hard into one side of its range and got rejected, closing back near the other end. Hammers reject lows (bullish rejection), shooters reject highs (bearish rejection). Several signal filters in the Signals panels (the HAM/SHO toggles) depend on these definitions, so the strictness you pick here directly controls how many of those filtered signals appear.
Definition (default: Broad (Loose) | options: Broad (Loose) / Classic / Pin Bar (Strict)) - sets how demanding the indicator is before it will call a candle a hammer or shooter. Each level is a different geometric test:
- Broad (Loose) is purely about where the open and close sit within the bar's range. A hammer needs both the open and the close strictly above the range midpoint; a shooter needs both strictly below it. The comparison is strict, so a body centered exactly on the midpoint qualifies as neither (
FIX BTC-2), and a flat, zero-range bar never qualifies. It ignores body size and wick length, so it catches the most candles, including ones with sizable bodies. Best when you want maximum signal flow and treat the HAM/SHO filter as a light directional lean rather than a strict gate. - Classic is the textbook TheStrat actionable signal: a small body (body at most 30% of the range), a dominant rejection wick at least 3x the body, the opposite wick kept short (at most 35% of the range), the body center sitting in the rejection third of the bar (within 33% of the relevant extreme), and the close within 25% of that extreme. This is the balanced choice and matches how most TheStrat traders define a hammer or shooter.
- Pin Bar (Strict) only checks that both the open and the close finish very near the rejecting extreme (each within 25% of the high for a hammer, within 25% of the low for a shooter). It does not require a small body or a 3x wick ratio, but because it demands the open AND close both crowd into the last quarter of the range, it isolates the cleanest, most decisive rejection candles and produces the fewest signals. Use it when you only want the highest-conviction pivots.
Switching this setting changes which bars satisfy every HAM/SHO toggle across the Reversals, Continuations, and Expansions panels at once, so loosen it if HAM/SHO-filtered signals dry up, and tighten it if you are getting too many marginal ones.
- Broad (Loose) is purely about where the open and close sit within the bar's range. A hammer needs both the open and the close strictly above the range midpoint; a shooter needs both strictly below it. The comparison is strict, so a body centered exactly on the midpoint qualifies as neither (
Match Candle Color (default: off) - when on, a hammer must also be a green (bullish, close above open) candle and a shooter must also be a red (bearish, close below open) candle to qualify. When off, color is ignored and the geometric test alone decides. Turn it on to demand that the candle's close direction agrees with its rejection direction (an extra confirmation layer); leave it off if you accept, for example, a long lower-wick rejection that still closed slightly red as a valid hammer. Note this acts as an additional gate on top of the Definition setting, so combining Match Candle Color with Pin Bar (Strict) is the most restrictive possible configuration.
Filters - Domino Setups
A Domino is a stacking signal: it fires when several consecutive timeframes (scanning from your highest enabled TF down to your lowest) all currently show an inside bar (a Type 1, where the forming bar is still trading inside the prior bar's range) that also carries a visible setup in the SAME direction. Because the indicator only counts a timeframe if it actually has a drawn signal under your current filter settings, the Domino "respects your signal filters" - a TF with an inside bar but no enabled/visible setup breaks the chain. The result is a read on how many timeframes are coiled and leaning the same way at once, which is the classic "dominoes lined up" pre-breakout condition. The indicator computes the best bullish run and the best bearish run separately and reports whichever is longer. It also de-duplicates: if two adjacent timeframes are reading the exact same underlying candle (same start time, common at TF coupling moments), that shared candle is not double-counted.
Minimum Timeframes (default: 2 | range: 2 to 6) - the minimum length of the consecutive inside-bar run required before a Domino is recognized. At 2, any two stacked, same-direction inside-bar setups trigger it; raising it (up to 6) demands a longer, more aligned stack and produces rarer, higher-conviction Dominoes. This threshold governs both the data table row and the Domino alert, so it is the single dial for how selective Domino detection is.
Show in Data Table (default: on) - adds a Domino row to the data table whenever the detected run meets or exceeds Minimum Timeframes, listing the participating timeframes lowest-first (for example "Domino: 15m+30m+1H"). Turn it off to keep the table compact if you would rather rely on alerts alone. This only controls the table display; it does not affect whether Domino alerts fire (see Advanced - Domino Setups for that) or the underlying detection.
Filters - Lead Signal
The Lead is the highest enabled timeframe that currently has a signal in-force (a breakout that has triggered and is holding). In TheStrat's cascade logic, that highest in-force timeframe sets the dominant directional bias, and lower timeframes should be traded with it, not against it. This filter enforces that: it finds the Lead by scanning from your top timeframe downward, takes its direction (bullish if its high-side signal is in force, bearish if its low-side signal is in force), and then filters the timeframes below the Lead so only setups aligned with the Lead's direction remain visible. If no higher timeframe has a signal in force, there is no Lead and nothing is filtered (all signals show).
- Only Show Signals Following the Lead (default: off) - turns the Lead filter on. When on, counter-trend setups on timeframes below the Lead are suppressed, with one nuance worth knowing from the logic: a confirmed counter-trend Failing 2 (a lower-TF F2 fighting the Lead) has all its flags, lines, and in-force status removed entirely, while a trend-aligned Failing 2 keeps its trigger and just drops its counter-side targets/stop; a regular counter-trend signal has its counter-trend side suppressed. Lower-TF signals that already point the Lead's way pass through untouched. When off, every enabled timeframe's signals draw independently with no cross-timeframe gating. Turn it on to stop fading the higher-timeframe move and to focus on with-trend entries; leave it off if you intentionally trade counter-trend reversals or want to see the full unfiltered picture. The table shows a "Lead:" row (for example "Lead: 1H BULL", or "No Lead") so you can see which timeframe is driving. Interaction worth knowing: the Advanced setting "Exhaustion Disables 'In Force'" affects this filter directly - with it on, once the Lead's move hits its target it stops counting as in-force, which releases the suppression and lets opposite (reversal) setups appear on the lower timeframes.
Targets - Magnitude & Exhaustion
In TheStrat, every actionable signal carries built-in profit targets drawn from the structure of the candles themselves. The Suite plots two kinds:
- Magnitude is the C2 high or low (the extreme of the candle two bars back, the one whose break defines the setup). It is the first, nearest, most reliable target: the level the move is structurally "expected" to reach.
- Exhaustion is the first intact prior swing high or low located beyond C2. It is the extended target: where the move is likely to run out of room. Exhaustion is found by scanning back for an untouched pivot (
findExhaustionLevels), so it only appears when such a level exists.
This panel controls whether each target type is drawn and when.
Show Magnitude Levels (default: on | options: on / off) - Draws the magnitude (C2 high/low) take-profit target for actionable signals. Leave this on for the core "where is my first target" read on every setup. Turn it off if you only want to see entry triggers and exhaustion, or to declutter a busy chart. Note the exact scope of "off": potential setups and outside-bar expansions lose their magnitude lines (outside-bar magnitude requires this switch), but a signal that is already in-force still draws its magnitude target through an in-force fallback path, so live trades keep their first target on the chart.
Only When In-Force (default: off | options: on / off) - When off, magnitude is shown on potential setups (before the breakout) as well as live ones. When on, magnitude only appears once the signal has actually triggered (price broke and held beyond the trigger). Turn it on if you want a clean chart that shows targets only on trades that are actually live, and not on every candle that might become a setup. Has no effect when Show Magnitude Levels is off.
Show Exhaustion Levels (default: on | options: on / off) - Draws the exhaustion level (prior swing high/low beyond C2) as the extended target. Keep it on to see how much runway a move has past its first target, which is useful for deciding whether to hold for a second target or trail. Turn it off if you trade to magnitude only. Exhaustion is also what the Take Action Window can extend to, and outside-bar exhaustion requires it too (
FIX CSS-2), so turning this off limits those features.Only When In-Force (default: off | options: on / off) - The exhaustion counterpart to the magnitude toggle above. Off shows exhaustion on potential setups; on hides it until the signal triggers, so you only see extended targets on live trades. Has no effect when Show Exhaustion Levels is off.
Only After Magnitude Hit (default: off | options: on / off) - When on, an exhaustion level stays hidden until price has actually reached the magnitude level first, then it appears. This mirrors how the move develops: first target, then extended target. Turn it on to avoid being distracted by a far-off exhaustion level while price is still working toward magnitude. When off, exhaustion is drawn as soon as it otherwise qualifies, alongside magnitude. This setting has no effect when Show Exhaustion Levels is off.
Targets - Take Action Windows
A Take Action Window is the shaded zone between the entry trigger and the target. It gives you an at-a-glance picture of the trade's "working area": where price has room to travel from the trigger toward magnitude (or, optionally, all the way to exhaustion). The window is colored with the signal's bullish or bearish color.
Show Take Action Windows (default: on | options: on / off) - Master switch for the shaded trigger-to-target zone on actionable signals. Leave it on for an immediate visual of each setup's runway. Turn it off if you prefer just the lines without fills. With this off, the zone-to-magnitude fill is suppressed even if the extend-to-exhaustion options below are on (those govern only the exhaustion portion).
Only When In-Force (default: on | options: on / off) - When on (the default), the window is only drawn once the signal has triggered, so you see a working zone only on live trades. Turn it off to also shade the zone on potential setups before the breakout. This is the master in-force gate for the windows: it applies to the magnitude window, to Failing 2 windows, and to Outside Bar windows (see Advanced - Take Action Windows). It has no effect when Show Take Action Windows is off (except that it still gates the Failing 2 and Outside Bar windows, which have their own enable toggles).
Extend to Exhaustion (default: on | options: on / off) - When on, the window stretches all the way to the exhaustion level instead of stopping at magnitude, showing the full potential runway of the move. Turn it off to keep the window tight to the first (magnitude) target. This draws independently of the main Show Take Action Windows switch, so you can have the exhaustion extension on even with the magnitude fill off. It relies on a valid exhaustion level existing for that signal.
Only When In-Force (default: on | options: on / off) - The in-force gate specific to the exhaustion extension. When on (default), the window only extends to exhaustion after the signal triggers; when off, it can extend on potential setups too. Has no effect when Extend to Exhaustion is off.
Fill Opacity (default: 8 | range: 0 to 100) - How solid the shaded interior of the window is. 0 is fully transparent (no visible fill), 100 is fully solid. The low default of 8 keeps the zone subtle so it does not bury the candles. Raise it if the window is hard to see against your chart background.
Border Opacity (default: 30 | range: 0 to 100) - How solid the window's outline is. 0 hides the border, 100 makes it fully opaque. The default of 30 gives a faint edge that defines the zone without dominating. Pair a higher border opacity with a low fill opacity if you want the zone outlined but not filled in.
Stops - Stop Levels
Stop levels mark the price where your trade thesis is invalidated, drawn on the opposite side of the trigger from your targets. This panel controls whether they appear, how they are anchored, when they move to break even, and how they are colored.
Enable (default: off | options: on / off) - Turns stop level lines and labels on. Stops are drawn on the opposite side of the trigger from your magnitude/exhaustion targets. Once a signal goes in-force (the breakout occurs), the stop is locked and persists for the remainder of that period even if the signal later stops being in-force, so a stop you were managing does not vanish mid-trade. Locked stops also survive a chart reload: with stops enabled, the lock is rebuilt from bar history so a refresh cannot reset it (
FIX P1-a). While this is off, the greyed-out controls below cannot take effect.Reference (default: CC | options: CC / C1) - Chooses which bar anchors the stop. CC places the stop at the breakout bar's opposite extreme: it updates in real time as the candle forms and locks when the candle closes, giving tighter risk that is invalidated when the breakout candle itself is reversed. C1 places the stop at the prior bar's opposite side and locks it when the signal goes in-force, giving wider risk that is invalidated only when the full setup range is taken out. Choose CC for tighter, more reactive stops and C1 for more room to let the structure play out. Important interaction: Failing 2 (Range Reclaim) signals always use CC regardless of this setting, because their invalidation is defined by the reclaim bar itself.
Smallest Timeframe Only (default: off | options: on / off) - Draws only the stop belonging to the smallest enabled timeframe that currently has one; stops on larger timeframes are hidden. This is a display filter, nothing else changes: larger-timeframe stops keep tracking underneath, their break-even mechanics still arm, and alert messages still carry their prices. Useful when several timeframes are in force at once and stacked stop lines clutter the chart - the tightest stop is usually the one being managed. "Smallest" follows slot order, the same convention the Lead Signal filter uses. New in v3.0.0 (
STOP-SMALLEST-1).Break Even at Magnitude (default: off | options: on / off) - When price reaches the magnitude (first target) level, the stop moves to the entry trigger so the trade can no longer lose. If the signal has no magnitude level, it uses the exhaustion level as the move-to-break-even trigger instead. Turn this on to bank risk-free runners early; leave it off if you prefer to let the original stop ride to its structural invalidation.
Break Even at Exhaustion (default: off | options: on / off) - The same break-even mechanic, but triggered when price reaches the exhaustion (extended target) level. If no exhaustion level exists, it falls back to using magnitude as the trigger. This is a later, more conservative break-even point than Break Even at Magnitude; you can enable both, in which case the stop moves to break even at whichever level price reaches.
Color Mode (default: Opposite Signal | options: Opposite Signal / Match Signal / Custom) - Sets how stop lines and labels are colored. Opposite Signal colors a bullish trade's stop with the bearish signal color and a bearish trade's stop with the bullish signal color, so the stop visually reads as the "danger" side. Match Signal colors the stop the same as its own signal, keeping each trade monochromatic. Custom uses the single Custom Color below for all stops regardless of direction.
Custom Color (default: #FFFFFF white | color picker) - The single color applied to all stop lines and labels when Color Mode is set to Custom. This control is greyed out unless stops are enabled AND Color Mode is set to Custom, so it only appears active when it can actually take effect (
FIX MOD-3).
Display - Preview Mode
Preview Mode (default: Auto | options: Off / On / Auto) - Controls whether the indicator projects next-period levels onto the chart before the new period begins. Off never previews and must be selected to use TradingView's Bar Replay mode. On always previews the next period's levels. Auto previews only when the market is likely not actively trading, and detects this per asset type:
- Stocks, ETFs and funds, ADRs, indices, and options - before 9:30 AM ET, after 4:00 PM ET, and on weekends.
- Futures, forex, and commodities - over the weekend close (Friday 5 PM through Sunday 6 PM ET) and during the daily 5-6 PM ET maintenance window (Monday-Thursday).
- Crypto (trades 24/7) - only via the outage fallback below.
- Holiday / outage fallback (all asset types) - preview engages when the current time is well past the live bar's scheduled close (three chart periods or four hours, whichever is larger). Measuring from the scheduled close rather than the bar's open stamp matters: some exchanges glue holiday half-sessions into the next trade date, giving a live bar a days-old open stamp, and an open-stamp measurement would flip preview on in the middle of a live session (
FIX GLUE-1; the full story is indocs/v2.2.3_holiday_glue_fix.md). A live bar's scheduled close is always in the future, so this fallback can never misfire while a session trades.
Bar-replay caveat: Auto automatically disables itself during bar replay, but if you specifically need clean replay behavior, set Preview Mode to Off.
Straddle handling (related, automatic). Calendar timeframes coarser than your chart (Weekly, Monthly, Quarterly, Yearly) can go stale mid-bar when a chart bar straddles a period boundary - for example a weekly chart bar spanning a mid-week quarter roll. When that happens on the live bar, the Suite applies the same shift preview uses, and where possible rebuilds the true forming candle from completed daily sub-bars so the slot shows real data instead of an estimate (FIX HTF-STRADDLE-1, with period-key normalization from FIX RECON-KEY-1). A previewed or straddled slot that could not be rebuilt shows ? in the data table's CC cell; any previewed or straddled slot - rebuilt or not - never fires alerts. Setting Preview Mode to Off disables straddle handling along with preview - that is part of why Off is the required setting for Bar Replay.
Display - Data Table
The data table is the multi-timeframe scoreboard showing each monitored timeframe's bar state at a glance.
Show Data Table (default: on | options: on / off) - Shows or hides the multi-timeframe data table.
Mode (default: Full (TheStrat) | options: Full (TheStrat) / Compact (Universal)) - Full (TheStrat) renders the complete table using Strat notation with four columns per timeframe row: C2 (two-bars-back state), C1 (prior bar state), AS (the actionable signal, if any: HAM, SHO, or INS), and CC (the current forming bar's state). This is the detailed view for traders fluent in the cascade. Compact (Universal) collapses each timeframe to a single colored label in a horizontal row, where yellow means the bar is inside and above the open, orange means inside and below the open, and green/red means a signal is in-force in that direction. Compact is the simplified view for newer traders or a cleaner chart.
In both modes, Failing 2s render in canonical uppercase (
F2u/F2d) whether forming or completed (NOTATION-1). While preview is active the table shows a yellow "PREVIEW MODE" banner, and a previewed slot whose forming candle could not be rebuilt from real daily sub-bars shows?in its CC cell instead of a guessed bar type (FIX HTF-STRADDLE-1).Position (default: Top Right | options: Bottom Center / Bottom Left / Bottom Right / Middle Center / Middle Left / Middle Right / Top Center / Top Left / Top Right) - Places the table in any of the nine standard chart anchor positions. Move it to keep it clear of your price action or other indicators.
Text Size (default: Small | options: Tiny / Small / Normal / Large) - Sizes the table text. Use Tiny to minimize footprint or Large for readability on high-resolution or distant displays.
Use Dash Separator (default: on | options: on / off) - Separates candle values with a dash for legibility (for example 2u-2d instead of 2u2d). Turn off for a more compact, run-together notation.
Display - Labels
These settings govern the text labels attached to level lines, including their placement, content, and styling.
Show Timeline Labels (default: on | options: on / off) - Shows labels at the end of each level line, anchored to that line's end time so the label sits where the line stops.
Offset (bars) (default: 0 | range: 0 to 20) - Shifts timeline labels by the given number of bars away from the line end, useful for nudging labels off a busy line cluster. Greyed out when Show Timeline Labels is off (
FIX MOD-3).Show Floating Labels (default: off | options: on / off) - Shows labels at a fixed offset from the current bar so they stay visible on screen as price moves, rather than being pinned to a line's end. Useful when you want level names always in view at the right edge.
Offset (bars) (default: 1 | range: -20 to 50) - Shifts floating labels relative to the current bar, where negative values move them left (into history) and positive values move them right (into the future area past the last bar). Greyed out when Show Floating Labels is off (
FIX MOD-3).Show Price in Label (default: off | options: on / off) - Appends the actual price level to each label so you can read the exact value without hovering the line.
Show H/L in Labels (default: off | options: on / off) - Adds "H" for high levels and "L" for low levels in both the on-chart labels and the alert messages, clarifying which side of a setup each level represents.
Text Size (default: Small | options: Tiny / Small / Normal / Large) - Sizes the label text independently of the data table text.
Background Color (default: #2e2e2e dark gray | color picker) - Sets the fill color behind label text.
Transparency (default: 20 | range: 0 to 100) - Controls how see-through the label background is, where 0 is solid and 100 is fully invisible. Raise it to let chart content show through behind labels.
Display - Bar Coloring
New in v3.0.0 (BARCOLOR-1). These settings paint the chart candles themselves - either by each bar's Strat classification or by Full Timeframe Continuity. Candle paint is off by default, one mode is active at a time, and each mode's controls below are greyed out unless that mode is selected.
Color Chart Candles (default: Off | options: Off / Strat Candles / FTFC Candles) - Paints the chart's candles.
- Strat Candles colors each chart bar by its Strat classification against the prior bar - inside 1, directional 2u/2d, outside 3u/3d, and Failing 2s - using the same classification (and Failing 2 Detection Method) as the data table's CC cell, so candle paint and table notation cannot disagree.
- FTFC Candles colors each bar by Full Timeframe Continuity - all monitored timeframes above their open (up), all below (down), or conflicting. Historical bars are graded by that bar's close against each timeframe's open as it stood at the time, so painted history shows continuity as it actually developed, not as the periods finally closed.
1 (Inside) / 2 (Directional) / 3 (Outside) / F2 (Failing) (default: all four on | options: on / off) - Strat Candles mode only: toggle candle paint per bar family. Colors follow the Style sections, so a custom palette applies to lines, labels, and candles together:
- 1s use the 1 (Inside Bar) colors -
1u(closed above its open) takes the bullish yellow,1dthe bearish orange. - 2s use the Signal colors -
2ubullish,2dbearish. - 3s use the 3 (Outside Bar) colors -
3ubullish,3dbearish. - Failing 2s use the Range Reclaim colors -
F2ubearish,F2dbullish.
With F2 (Failing) unchecked, or Failing 2 Detection disabled, a Failing 2 paints as its plain
2u/2d(anF2uis a2u, shown without failure paint), so it follows the 2 (Directional) toggle and Signal colors instead.- 1s use the 1 (Inside Bar) colors -
Up / Down / Conflict (defaults: #4caf50 green / #f23645 red / #808080 gray | three color pickers) - FTFC Candles mode only: the candle colors for full timeframe continuity up, down, and conflict.
Color Conflict Bars (default: on | options: on / off) - FTFC Candles mode only. When off, conflicted bars keep the chart's normal candle colors, so only full up/down agreement is painted.
Highlight Failing 2 Flips (default: on | options: on / off) - FTFC Candles mode only. When continuity flips while a Failing 2 (Range Reclaim) is live on a monitored timeframe, the flip bar is painted with the Range Reclaim color instead of the plain continuity color. A reclaim that flips continuity is the highest-conviction version of the signal - trapped traders from the failed breakout fuel the new direction.
Cross-panel note: what counts as a Failing 2 - for Strat Candles paint and the FTFC flip highlight alike - follows Advanced - Failing 2 Detection (the Enable toggle and the Detection Method). With detection disabled there, no candle paints or highlights as an F2 anywhere in the Suite. And because the family colors are read from the Style panels below, changing a Style color also recolors the candles.
Style - Level Colors
These inputs set the colors used to draw level lines and labels. There are two panels, one for bullish (high-side) levels and one for bearish (low-side) levels, with matching roles in each. Colors are purely cosmetic; they do not change which signals fire.
Style - Bullish Levels
- Signal (default: green / color.green) - The color for an active, in-force bullish level (a triggered bullish signal). This is the main "live long setup" color.
- Crossed (default: #9c9c9c, light gray) - The color a bullish level changes to once price has crossed (hit) it, marking the level as already taken out so it visually recedes.
- 1 (Inside Bar) (default: #ffeb3b, yellow) - The color for a bullish potential level coming from an inside bar (a type 1), that is, a setup that has not yet triggered on the high side.
- 3 (Outside Bar) (default: #089981, teal-green) - The color for a bullish outside-bar (type 3) expansion level (3u, the high side of an outside bar).
- Range Reclaim (F2d) (default: #81c784, soft green) - The color for a bullish Range Reclaim signal, that is, a failed bearish breakout (F2d) where price broke the low and reclaimed back up.
Style - Bearish Levels
- Signal (default: red / color.red) - The color for an active, in-force bearish level (a triggered bearish signal). The main "live short setup" color.
- Crossed (default: #4a4a4a, dark gray) - The color a bearish level changes to once price has crossed (hit) it, marking it as already taken out.
- 1 (Inside Bar) (default: #ff9800, orange) - The color for a bearish potential level from an inside bar (type 1) that has not yet triggered on the low side.
- 3 (Outside Bar) (default: #e91e63, pink/magenta) - The color for a bearish outside-bar (type 3) expansion level (3d, the low side of an outside bar).
- Range Reclaim (F2u) (default: #f77c80, soft red) - The color for a bearish Range Reclaim signal, that is, a failed bullish breakout (F2u) where price broke the high and reclaimed back down.
Note: these Signal colors are reused elsewhere. The Take Action Window fills derive from the bullish/bearish Signal colors, and stop-level lines can be tied to them through the Stops panel's Color Mode (Opposite Signal or Match Signal), so changing a Signal color here also shifts those elements.
Alerts
The Suite condenses all multi-timeframe activity into one push so you do not have to watch the chart. There is one consolidated alert() that fires automatically, plus a full set of named alertcondition() entries you can wire to your own TradingView alerts. The settings below shape only the consolidated message.
Alerts - Detailed
This panel controls which timeframes feed the consolidated alert message and which enrichment fields get appended to it.
- TF1 / TF2 / TF3 / TF4 / TF5 / TF6 (default: all six on | options: on / off) - Per-timeframe include checkboxes for the consolidated alert. Uncheck a TF to drop its signals from the consolidated message body so a busy lower timeframe does not flood your phone. Important interaction: these checkboxes gate the consolidated
alert()text (and, because they share its detection loop, the "Signal In-Force (Any)" and "New Potential (Any)" alertconditions). The per-TF alertconditions (for example "Signal In-Force (TF1)") and the Bullish/Bearish ones read their own pre-loop state snapshots and are completely independent of these boxes, so a per-TF alert still fires even if that TF is unchecked here (FIX U2keeps their state updated regardless). - Include FTFC (default: off | options: on / off) - When a consolidated alert fires, appends the live full-timeframe-continuity verdict to the end of the message as one of
FTFC Up,FTFC Down, orConflict. Turn it on when you want directional context attached to every push. - Trigger (default: off | options: on / off) - Appends the entry trigger price as
@ <price>(bull = breakout high, bear = breakout low). Omitted automatically if no trigger price exists for that signal. - MAG (default: off | options: on / off) - Appends the magnitude (first take-profit) level as
MAG <price>. Omitted when the signal has no magnitude. - EXH (default: off | options: on / off) - Appends the exhaustion (extended target) level as
EXH <price>. Omitted when no exhaustion level exists. - STOP (default: off | options: on / off) - Appends the stop level as
STOP <price>. Omitted when no stop exists for the signal. Note the stop value is only computed while the Stops panel's Enable is on, so with stops disabled this field is always omitted.
The four enrichment toggles (Trigger / MAG / EXH / STOP) join with single spaces and only attach when their value is actually present, so you never get a dangling field or a blank MAG (FIX P1-j).
How alerts work
There are two delivery paths, and you can use either or both.
1. The consolidated alert() (automatic). On every bar the indicator checks each enabled timeframe for a NEW in-force event, meaning a signal (or a Failing 2) that was not present last bar and is now. For each new event on an included TF it builds one label and joins multiple labels with |. If anything fired and Include FTFC is on, the FTFC verdict is appended. The whole thing is sent once per bar via alert.freq_once_per_bar. To use it: create a TradingView alert on the indicator and choose the "Any alert() function call" condition. This single alert respects the TF include checkboxes and the enrichment toggles above. (The Domino setup is added to this consolidated stream only when "Advanced - Domino Setups > Include in Consolidated Alerts" is on; otherwise use the dedicated Domino alertcondition below.)
2. The named alertcondition() list (you choose). These appear in TradingView's alert "Condition" dropdown so you can create targeted alerts. The per-TF and Bullish/Bearish conditions are driven by their own state snapshots and are NOT affected by the TF include checkboxes; the two "(Any)" conditions share the consolidated stream's detection loop, so they DO respect the TF include checkboxes. The full list:
- Signal In-Force (Any) - any monitored TF prints a new in-force signal. Respects the TF include checkboxes above.
- Signal In-Force (TF1) through Signal In-Force (TF6) - a new in-force signal on that specific timeframe slot (TF1 = first timeframe in your settings, and so on). Active for a slot only when that timeframe is enabled.
- Signal In-Force (Bullish) - a new bullish in-force signal on any TF.
- Signal In-Force (Bearish) - a new bearish in-force signal on any TF.
- New Potential (Any) - a new potential setup appears on any TF (the inside/forming state, before it triggers). Respects the TF include checkboxes above.
- Domino Setup - the required number of consecutive inside-bar timeframes line up (a Domino). Fires regardless of the consolidated-alert toggle.
- FTFC Shifted - full-timeframe continuity changed state on this bar.
- FTFC Up - continuity flipped to fully bullish.
- FTFC Down - continuity flipped to fully bearish.
- FTFC Conflict - continuity moved into a conflicted (mixed) state.
Neither path ever alerts from a previewed or straddled slot: their forming candle is synthetic or estimate-grade, so both the consolidated alert and the per-TF alertconditions skip them, and the slot re-arms cleanly when real data resumes (FIX HTF-STRADDLE-1). Alerts also obey the same signal toggles as the display, so a signal type you have turned off cannot alert (FIX P0-3).
Message format. Each signal label in the consolidated message follows:
<TF> <combo><pattern><H/L><marker> | <enrichment fields>
<TF>is the timeframe label (for example1H).<combo>is the Strat combo when Label Style is TheStrat (for example2d-1-2u,3-2u, or a live Failing 2 in canonical notation like2u-F2d), or plain language plus a direction arrow when Label Style is Universal (for example↑REVERSAL,↓CONTINUATION,↑EXP,↓FAILING).<pattern>isHAMorSHOwhen C1 is a hammer/shooter (a◆diamond in Universal mode), omitted otherwise.<H/L>appears only when "Show H/L in Labels" is enabled.<marker>is a green or red circle indicating bull or bear.- The enrichment fields (
@ trigger,MAG,EXH,STOP, and the FTFC verdict) follow after a|separator, joined by single spaces, each present only when its toggle is on and its value exists.
A push with every enrichment on looks like: 1H 2d-1-2u HAM 🟢 | @ 542.10 MAG 545.30 EXH 548.20 STOP 538.10 | FTFC Up, and several simultaneous events are chained with | between each TF's label.
Advanced - Exhaustion Behavior
These two settings change how a timeframe whose move has already hit its target is treated downstream. Both fall back to magnitude when no exhaustion level exists (a TF that crossed magnitude with no exhaustion counts as "exhausted" for these purposes). Read these carefully: each affects a specific, limited part of the indicator, not the chart at large.
Exhaustion Disables 'In Force' (default: off | options: on / off) - Controls whether a signal that has reached its target still counts as "in force" for the data table and the Lead Signal filter. When off (default), a signal stays in force even after hitting exhaustion (or magnitude when there is no exhaustion): the data table keeps showing it as active, and the Lead Signal filter keeps using it for directional bias. When on, once the signal hits exhaustion (or its magnitude fallback) it is no longer treated as in force in those two places. The practical payoff is on the Lead filter: an exhausted higher-timeframe signal stops suppressing opposite setups below it, so (for example) bearish 15m/1H setups can appear again after a Daily bullish signal has already run to exhaustion. Note the precise scope: this affects the data table's in-force state and the Lead Signal filter only. It does NOT change protective stop behavior, which deliberately uses the raw in-force state so a locked stop is not dropped just because a target was reached. It also does not, by itself, add or remove signal lines on the chart.
Exhaustion Excludes from FTFC (default: off | options: on / off) - Controls whether an exhausted timeframe still counts in the FTFC reading shown in the data table. When off (default), every timeframe counts toward FTFC regardless of whether its target was hit. When on, a timeframe that has hit exhaustion (or its magnitude fallback) is dropped from the FTFC calculation, so a structurally finished move no longer blocks FTFC from flipping. Example: if 5 of 6 timeframes are bearish but the 6th is bullish and has already hit exhaustion, turning this on excludes that spent bullish TF, so the cell reads "FTFC Down" instead of "Conflict". Important scope note: this affects the data table's FTFC cell only. It does NOT gate which signals draw, and it does NOT change alert content; signal drawing and alerts use the raw FTFC value, so this setting will not cause new signals to appear or disappear. Treat it as a display/read aid for the table, not a signal filter.
Advanced - Outside Bars
Outside bars (3-bars) engulf the prior candle's full range and represent a burst of expansion. Because they behave differently from clean 2-bar setups, their targets have their own toggles here. Both require the "Outside Bars (3 Exp)" signal to be enabled before they have any effect, and both sit under the global toggles in Targets - Magnitude & Exhaustion: outside-bar magnitude also requires Show Magnitude Levels, and outside-bar exhaustion also requires Show Exhaustion Levels (FIX CSS-2 made the exhaustion side match the magnitude side).
Show Magnitude for Outside Bars (default: on | options: on / off) - Draws the C2 high/low as a magnitude target on outside-bar (3-bar) expansions. Leave it on to get a first target on these volatile setups. Turn it off if you find 3-bar magnitude targets unreliable and prefer to manage outside bars manually.
Show Exhaustion for Outside Bars (default: on | options: on / off) - Draws exhaustion (the prior swing beyond C2) as an extended target on outside-bar expansions. Keep it on to see the full runway on a 3-bar move; turn it off to suppress extended targets specifically for outside bars. Like all exhaustion, it only appears when a valid exhaustion level exists.
Advanced - Failing 2 Detection
This panel defines what the indicator treats as a Failing 2 (F2u / F2d). It is the engine behind the Failing 2s (Range Reclaims) signal and the F2 filter on 2-2 Reversals, so settings here ripple into both of those panels. Note the two-layer design: detection (here, on by default) classifies F2s everywhere - table, labels, line styling - while the signal toggle (Signals - Reclaims, off by default) decides whether they trade: levels, windows, alerts. Detection on + signal off means you see F2s but are not prompted to act on them.
- Enable Failing 2 Detection (default: on | options: on / off) - When on, the indicator detects Failing 2s (F2u / F2d) where price reclaims the prior range or crosses back against the breakout direction. When off, only standard 2u / 2d signals are shown and no bar is classified as a Failed 2. Leave it on (the default) if you want Range Reclaims or the 2-2 Reversal F2 filter to work, since both depend on this detection. Turning it off makes the Failing 2s (Range Reclaims) signal produce nothing and makes the 2-2 Reversal F2 filter match nothing. This switch also keeps the data table and labels consistent with the signal engine, so with detection off the table no longer shows F2u / F2d.
- Detection Method (default: Reclaim | options: Reclaim / Open / Reclaim + Open / Reclaim OR Open) - Chooses the rule that decides when a 2u / 2d has failed:
- Reclaim: the bar closes back inside C1's range after breaking out. The most common definition; price gave back the breakout and returned into the prior range.
- Open: the bar closes against its breakout direction (a 2u that closes below its open, or a 2d that closes above its open), regardless of whether it re-entered the prior range. Catches failures by close direction even when price held part of the breakout.
- Reclaim + Open: requires both conditions at once (closed back inside C1's range and closed against the breakout direction). The strictest setting, producing the fewest but highest-conviction Failing 2s.
- Reclaim OR Open: requires either condition. The loosest setting, flagging the most Failing 2s, useful if you want to see every potential reclaim.
- Show Failing 2 Open Level (default: off | options: on / off) - When on, draws the CC open price as a dotted reference line whenever a Failing 2 is detected. The open acts as the trigger level for the reclaim, so this line shows where price would need to reclaim through. Turn it on if you trade off the open as your trigger; leave it off to keep the chart clean. It only draws when a Failing 2 is actually detected, so it has no effect while Enable Failing 2 Detection is off.
Advanced - Data Table
- Color TF When In-Force (default: on | options: on / off) - Highlights a timeframe's TF column using the current-bar (CC) color whenever a signal you have enabled is in-force on that timeframe, making active timeframes pop out of the table. "In force" here means a live 2/F2 signal, or an enabled outside-bar (3) expansion - which is in force by definition, having broken both sides of C1. A plain inside bar never lights the TF column, because an inside bar is by definition not in force; it stays colored in the CC cell only (
FIX TFCOLOR-1- earlier builds highlighted the TF column on every inside bar, which lit up most of the table). This applies in Full (TheStrat) mode only; it has no effect in Compact mode (where in-force coloring is already built into the single colored label).
Advanced - Take Action Windows
These extend the shaded trigger-to-target zone to two signal types that are handled specially. Each still respects the main "Only When In-Force" gate from the Take Action Windows panel and requires its parent signal to be enabled.
Include Failing 2s (default: on | options: on / off) - Shades the C1 range as a take action zone for Failing 2 (Range Reclaim) setups, where price broke out and then reclaimed back inside. The reclaimed range is the working zone for the reversal. Turn it off to keep windows on standard 2-bar setups only. Has no effect unless the Failing 2s (Range Reclaims) signal is enabled, and it still obeys the Take Action Windows "Only When In-Force" setting.
Include Outside Bars (default: on | options: on / off) - Shades the zone between trigger and magnitude for outside-bar (3-bar) expansions. Turn it off if you want windows on cleaner setups but not on volatile 3-bars. Has no effect unless the Outside Bars (3 Exp) signal is enabled; it also requires a valid, not-yet-crossed magnitude level, and it still obeys the Take Action Windows "Only When In-Force" setting.
Advanced - Domino Setups
- Include in Consolidated Alerts (default: off | options: on / off) - when on, Domino events are appended to the indicator's main consolidated
alert()stream, so a single TradingView alert built on the main alert function will also notify you of Dominoes. When off (the default), the main alert stays free of Domino messages. Either way, you can always create a dedicated Domino notification using the separate "Domino Setup" alertcondition when setting up the alert in TradingView, which fires independently of this toggle. Note that what counts as a Domino (and therefore what triggers either alert path) is still governed by Minimum Timeframes in the Filters - Domino Setups panel.
Debug
This panel is a troubleshooting aid for inspecting the indicator's signal logic on a single timeframe. Most traders can leave it off; use it only when verifying behavior or reporting an issue.
Show Debug Panel (default: off | options: on / off) - Shows a detailed debug panel with internal signal-logic information for the selected timeframe, so you can see why a signal did or did not fire.
Timeframe to Debug (default: TF1 | options: TF1 / TF2 / TF3 / TF4 / TF5 / TF6) - Selects which timeframe slot the debug panel reports on, where TF1 is the first timeframe in your settings and so on through TF6. Greyed out when Show Debug Panel is off (
FIX MOD-3).
Troubleshooting & FAQ
I added the Suite and see almost nothing. Three checks, in order: (1) Preset - Custom leaves all six timeframe rows disabled until you enable them; start from a built-in preset. (2) Chart timeframe - slots faster than your chart show as dimmed, empty rows and draw nothing; move your chart to Timeframe 1 or lower. (3) Signal toggles - 2-2 Continuations, Failing 2s, and both expansion signals are off by default; out of the box only reversals and inside continuations draw.
I updated the indicator and my alerts stopped working or send old messages. TradingView alerts bind to the version of the script that existed when the alert was created; they do not automatically pick up a new release. After updating the Suite, delete your existing alerts and recreate them so they bind to the new version.
The chart visuals look stale, frozen, or wrong after an update or settings change. This is a TradingView rendering quirk, not a data error. Remove the indicator from the chart and re-add it to force a clean redraw.
Bar Replay is not drawing anything (or shows preview levels instead). Preview Mode must be Off for Bar Replay. The "Auto" setting is designed to disable preview during bar replay, but if you still see issues, set Display - Preview Mode > Preview Mode to Off explicitly while using Bar Replay.
A data-table row shows
?in the CC cell. That slot is previewed or straddle-shifted and its forming candle could not be rebuilt from real daily data, so the Suite marks it unknown rather than guessing (FIX HTF-STRADDLE-1). It resolves on its own when the period opens or the reconstruction data arrives. A slot in this state also never fires alerts.Preview engaged during a live session around a holiday. Fixed in v2.2.3 (
FIX GLUE-1): the holiday fallback now measures staleness from the bar's scheduled close, so exchange-glued holiday half-sessions (which give a live bar a days-old open stamp) can no longer flip Auto preview on mid-session. If you are on v2.2.3 or later and still see a mid-session PREVIEW MODE banner, please report it with the symbol, chart timeframe, and time.My labels are overlapping and hard to read. Label collision is a known TradingView limitation; the platform does not auto-space labels. Reduce clutter by lowering the number of enabled timeframes, turning off levels you are not using, or adjusting the label offset settings. There is no way for the indicator to fully prevent overlap.
The indicator shows nothing on past/historical bars. This is by design. The Suite draws only the current live state (the levels, targets, and signals in force right now), not a history of every signal that ever printed. Scroll to the right edge / current bar to see active drawings.
Engine Architecture
The pipeline a bar of data travels from request.security to pixels — and where each engineering invariant lives along the way. If you are forking or contributing, read this first; the companion docs go deep on the two hardest stages.
Written against pine/TheStratSuite_v3.0.0.pine. Code references are function names, SECTION banners, and FIX tags; grep the source for them. Anti-repaint rules: repaint-prevention.md. Security-call traps: htf-correctness.md. Intentional design decisions a reviewer must not "fix": ../DESIGN_CONSTRAINTS.md.
The pipeline at a glance
inputs ─ preset resolution (rTF1..rTF6, rEN1..rEN6)
│
▼
SECURITY LAYER six request.security tuple calls (getDataWithExhaustion)
│ + one request.security_lower_tf("D") for reconstruction
▼
SHIFT LAYER applyPreviewShift → TFRawData[6] (preview / straddle promotion)
│ + daily-sub-bar reconstruction of the forming HTF candle
▼
COMPUTE for i = 5 to 0: computeSignalState → ProcessingResult[6]
│ (latches mutate TimeframeData; classification + decision logic)
▼
FILTER Lead Signal anchor scan; mutates ProcessingResult flags only
│
▼
RENDER (islast only) renderSignalLevels → label collection → line suppression
→ label consolidation → data table + Domino → debug panel
│
▼
ALERTS edge detection on persisted was-state arrays
│
▼
PAINT (every bar) chart-bar Strat classification / FTFC re-grade → barcolor()
Two cadences run through everything (../DESIGN_CONSTRAINTS.md item 5):
- State tracking runs on every bar. The compute loop executes for all of history so
varlatches (crossed flags, sticky stops, period bookkeeping) rebuild identically on reload. - Rendering runs only on
barstate.islast. No drawing object is ever created on a historical bar — there is no historical signal trail to repaint (repaint-prevention.md). The one stated exception is Stage 7's bar coloring, which paints (but never draws objects on) historical candles under the repaint doc's reload-stability rule.
The heavy classification work inside computeSignalState is additionally gated barstate.islast or showStopLevels (FIX P1-a): with stops off (the default) history pays only for the latches; with stops on, detection runs every bar because the sticky-stop lock must be rebuildable from history.
Source layout
The file is organized into grep-able SECTION banners. Mapping to pipeline stages:
| Banner | Contents | Stage |
|---|---|---|
SECTION 1 / 2 / 2A |
drawing helpers, inputs, preset resolution | inputs |
SECTION 3 / 3B |
UDT definitions, suppress*Flags methods |
(types) |
SECTION 4 |
calcMetrics, hammer/shooter detectors, findExhaustionLevels, calcBarType, getDataWithExhaustion, calculateFTFC |
security layer |
SECTION 5 |
detectFailed2, shouldDrawC1Level and wrappers |
compute |
SECTION 6 |
slot arrays, the six security calls, applyPreviewShift, reconstruction |
security + shift |
SECTION 7 |
computeSignalState, renderSignalLevels |
compute + render |
SECTION 8 |
label pool, collectTimeframeLabels, consolidateAndCreate |
render |
SECTION 9 |
the 6-slot processing loop, Lead Signal filter, render phase | compute + filter + render |
SECTION 10 |
collectLinesFromTF, suppressLowerTFLines |
render |
SECTION 11 |
data table, Domino calculation | render |
SECTION 12 |
debug panel | render |
SECTION 13 |
was-state arrays, alert(), alertconditions |
alerts |
SECTION 14 |
getChartBarColor, paintSlotFailed2, FTFC re-grade, barcolor() |
paint |
Stage 1 — Security layer: six slots, one tuple call each
Everything is built around six timeframe slots. Inputs resolve to rTF1–rTF6 / rEN1–rEN6 (a preset overrides the manual rows, which grey out via active — FIX MOD-3), packed into tfConfigs (TFConfig: enabled, tf string, line width, show-open, debug label).
Each enabled slot costs exactly one request.security call — getDataWithExhaustion() with lookahead = barmerge.lookahead_on — returning a 23-value tuple:
- CC (forming bar, offset
[0]): time/OHLC — live-updating by design. - C1 (offset
[1]) and C2 ([2]): time/OHLC — settled, non-repainting. - C3/C4 (
[3]/[4]): high/low only — enough to classify C2 and C3's bar numbers for the combo labels and the table's C2 cell. - Exhaustion pivots: computed inside the HTF security context by
findExhaustionLevels(a pivot scan seeded from completed bars —FIX P0-2), because the scan needs HTF bar history, not chart-bar history.
Why a tuple and not something nicer: request.security cannot return arrays, so the six calls are unrolled, and calculateFTFC takes all eighteen open/close/enabled values as flat parameters (../DESIGN_CONSTRAINTS.md item 4 — do not propose array-based returns).
Each call is budgeted with a tiered calc_bars_count (cb1–cb6): coarser slots request fewer bars, with a floor of 100 so the exhaustion scan's history requirement is always met. The tiers are inlined ternaries because the value must be a simple int — a function return would be series and rejected.
One more request sits beside the six: request.security_lower_tf(syminfo.tickerid, "D", …) fetches the current chart bar's completed daily sub-bars, gated by the constant canRecon (chart above D). It exists solely for straddle reconstruction (Stage 2).
The offset-[0]-vs-[1] semantics under lookahead_on — what repaints, what doesn't, and why signals anchor to C1/C2 — are the subject of repaint-prevention.md Rules 1–2. The chart-bar→HTF-bar mapping and its failure modes are htf-correctness.md.
Stage 2 — Shift layer: preview, straddle, reconstruction
Raw tuples never reach the engine directly. applyPreviewShift normalizes each slot into a TFRawData value and stores it in tfRawArr. In the normal case this is a straight repack. In two cases it promotes the chain — served CC becomes C1, C1 becomes C2, and so on — and synthesizes a neutral placeholder CC:
- Preview (
shouldApplyPreview): market closed, new calendar period begun; traders see next period's levels before the open. - Straddle (
isStraddleTF+shouldStraddle): a calendar TF rolled mid-chart-bar, so the served data is one period stale during live trading (FIX HTF-STRADDLE-1,FIX GLUE-2b).
Either way the slot is flagged isPreview = true, and realPeriodTime preserves the unshifted served period-open — the one identity that survives the rewrite. Everything downstream that must detect a genuine period change keys off realPeriodTime, never off shifted fields (FIX P0-1, FIX P1-c).
A follow-up pass (islast only) replaces the synthetic placeholder CC for straddled slots with the true forming HTF candle, rebuilt from the daily sub-bars whose pkeyCal(+12h) matches the current period, plus the live close. Success is recorded per slot in reconOKArr; the slot stays isPreview = true regardless — reconstructed data renders but never alerts. Full rules and the bug history: htf-correctness.md Rules 3–5.
Stage 3 — Compute: computeSignalState, once per slot
The main loop (SECTION 9) iterates for i = 5 to 0 — highest slot first — calling computeSignalState(data, tf, raw, …) with the slot's persistent TimeframeData and this bar's TFRawData. It runs on every bar. Inside, in order:
- New-period detection and latch.
raw.realPeriodTime != data.lastPeriodStart(plus the two preview re-latch triggers,FIX P1-d) resets all period-scoped state and snapshots C1/C2/exhaustion intodata.prevHigh/prevLow/prevMag*/exh*(repaint-prevention.mdRule 4 has the full state inventory). This runs unconditionally, even for a slot the next step refuses. - Refusal gate.
validTimeframe = timeframe.in_seconds() <= timeframe.in_seconds(tf)— a slot below the chart TF skips everything after the latch and returns an empty result (htf-correctness.mdRule 1). - Monotone latches.
magHighCrossed,exhLowCrossed, etc. latch from CC extremes — safe because they are monotone predicates (repaint-prevention.mdRule 2). - Classification.
detectPatterns(hammer/shooter),isInsideBar,detectFailed2(CC type + F2 + pre-F2),calcBarType/calcBarNumfor the C-chain,detectBarTypeAndFailedfor "C1/C2 was itself an F2". - Decision logic. The centralized
shouldDrawC1High/shouldDrawC1Low(both thin wrappers over oneshouldDrawC1Level— one decision, both directions), a bank of named suppression predicates, thesignal_in_force_high/lowcomputation (FIX CSS-1), magnitude/exhaustion draw decisions (shouldDrawMagnitudeplus forced OB/3-exp paths,FIX CSS-2), and level colors/styles (NOTATION-1governs label text). - Sticky stops. First in-force latches
stopHighTriggered/Priceper the stop-reference mode; break-even logic may ratchet the price to the trigger (FIX P1-aexplains why this runs every bar when stops are on).
The output is a ProcessingResult: prices, colors, draw flags, in-force flags, classification strings. It contains decisions, not drawings — nothing has touched the chart yet. Results land in tfResults.
Stage 4 — Filter: mutate flags, never draw
The Lead Signal filter (islast only, when enabled) scans slots top-down with getSignalDirection (F2 direction resolved explicitly first — FIX LEAD-F2-TIEBREAK-2), takes the first in-force slot as the anchor, then walks the lower slots and mutates their ProcessingResult flags: suppressHighFlags/suppressLowFlags/suppressAllFlags, plus clearing signalInForceHigh/Low so the table and Lead row agree with the hidden lines (FIX P1-g).
This is the pattern every filter must follow: filters edit the decision record; only the render phase reads it. Because lines, labels, the table, and alerts all consume the same flags, one mutation stays consistent across every surface (../DESIGN_CONSTRAINTS.md item 3 — flag any path where lines and labels can diverge).
Stage 5 — Render: islast only, update-in-place
All drawing happens under barstate.islast, in source order:
renderSignalLevelsper slot: every level follows the same shape — flag true →updateOrCreateLine(create once, then move via setters); flag false →deleteLine. Conditional creation, never create-then-hide (../DESIGN_CONSTRAINTS.mditem 2). Take-action-window boxes are rebuilt here too (FIX TAW-1: each direction checks only its own trigger).- Label collection:
collectTimeframeLabelspushes price/text/color into theSimpleLabelDatabuffers (highLabels/lowLabels/openLabels), applying the same directional-filter suppression the lines got. - Line suppression (
SECTION 10):collectLinesFromTFgathers live lines asLineInfo(price, tfSeconds, owner slot, kind);suppressLowerTFLineskeeps only the highest-TF line at each rounded price, deleting losers and nulling the owner's field vianullSuppressedLineso the render phase recreates it cleanly if the collision clears (FIX P1-h). Stop lines are deliberately exempt — risk management stays visible. - Label consolidation:
consolidateAndCreatemerges same-price labels ("W + D"style) and materializes them through the label pool — update-in-place, surplus trimmed after render (FIX P1-i). - Data table + Domino (
SECTION 11):populateBarTypeRowclassifies viadetectBarTypeAndFailed— a deliberately separate detection path from the engine'sdetectFailed2(../DESIGN_CONSTRAINTS.mditem 7; logical disagreement is a bug, duplication is not). The Domino run (consecutive inside-CC slots, deduped byccStartTime) is computed just before the table and shared with alerts. - Debug panel (
SECTION 12): renders theDebugInfosnapshot the compute stage populated for the selected slot.
Stage 6 — Alerts: edges of persisted state
SECTION 13 never asks "is it active?" — only "did it just become active?". Per-slot was* arrays reset on realPeriodTime change (FIX P1-c), update unconditionally even when a TF's alert checkbox is off (FIX U2), and are snapshotted before the update loop so the per-TF alertconditions see true edges. Preview/straddled slots never alert (slotPreview, okA1–okA6). The consolidated alert() fires once per bar with the assembled message. The full rule set is repaint-prevention.md Rule 6.
Stage 7 — Bar coloring: every bar, no drawing objects
SECTION 14 (BARCOLOR-1, off by default) runs in the main context on every bar — barcolor() paints history, so its classification cannot be islast-gated. Strat Candles mode reuses detectBarTypeAndFailed — the data table's CC classifier — on the chart bar itself, so candle paint and table notation cannot disagree. FTFC Candles mode re-runs calculateFTFC with the chart bar's own close against the served HTF opens (never the lookahead-final HTF closes), and paintSlotFailed2 rebuilds each slot's intra-period range from chart bars for the flip highlight. No lines, labels, or boxes are involved, so the object budget is untouched. This is the one stated exception to "rendering runs only on islast," and it is safe because it obeys repaint-prevention.md Rule 8: historical paint reads only series that evaluate identically live and on reload.
The major state arrays
Six-element arrays, indexed by slot, carry everything across stages:
| Array | Element type | Lifetime | Role |
|---|---|---|---|
tfConfigs |
TFConfig |
init once (barstate.isfirst) |
resolved per-slot settings |
tfDataArr |
TimeframeData |
persistent (var), mutated by compute + render |
the slot's memory: latched C1/C2/exhaustion snapshots, crossed flags, sticky stops, period bookkeeping — plus all drawing handles (lines/boxes) |
tfRawArr |
TFRawData |
rebuilt every bar | this bar's shift-layer output: the C-chain the engine actually sees, isPreview, realPeriodTime |
tfResults |
ProcessingResult |
rebuilt every bar | this bar's decisions: prices, colors, draw/in-force flags, classification strings |
reconOKArr |
bool |
rebuilt every islast | straddle reconstruction succeeded this tick |
wasSetupBull/Bear, wasF2Bull/Bear, wasPotentialBull/Bear, alertLastPeriodStart |
bool/int |
persistent (var) |
alert edge-detection memory |
The one-way flow is the thing to preserve: TFRawData (per-bar input) → TimeframeData (persistent memory) → ProcessingResult (per-bar decisions) → drawings. Filters touch only ProcessingResult. Render touches only drawing handles. Nothing downstream writes back into TFRawData.
Render-side scratch (label buffers, LineInfo arrays, the label pool, the suppression maps) is var-allocated once and cleared each islast tick — reuse over reallocation throughout.
Where each invariant is enforced
The conscious decisions in ../DESIGN_CONSTRAINTS.md, mapped to the code that enforces them:
| Invariant | Enforced at |
|---|---|
No-repaint contract (lookahead_on + [1] offsets; live [0] intentional) |
the six security calls; latch discipline in computeSignalState and findExhaustionLevels (FIX P0-2); drawing only on islast — repaint-prevention.md |
| Conditional creation over create-then-hide | renderSignalLevels via updateOrCreateLine/deleteLine; updateOrCreateBox/deleteBox |
| Centralized decision logic; lines and labels cannot diverge | shouldDrawC1Level single decision; ProcessingResult flags consumed by render, labels, table, and alerts alike; filter mutations via suppress*Flags (FIX P1-g) |
| One security call per TF; unrolled FTFC | getDataWithExhaustion tuple; calculateFTFC flat-parameter chain |
| State every bar, render only islast | compute loop ungated; every drawing block gated barstate.islast; FIX P1-a for the stop-latch exception |
| UDT-per-timeframe, 6-slot loop | the tfDataArr/tfRawArr/tfResults triple and for i = 5 to 0 |
| Table detection is a separate path (agreement required, not unification) | detectBarTypeAndFailed vs detectFailed2 |
| Stop lines exempt from suppression | collectLinesFromTF (stops never collected) |
Contributor checklist
Before merging anything that adds a feature, a level, or a filter:
- Does new per-slot data flow follow the one-way chain — security tuple →
TFRawData→TimeframeData/ProcessingResult→ render? Anything writing upstream, or drawing outside the islast render phase, is wrong. - Does a new filter mutate
ProcessingResultflags (includingsignalInForceHigh/Low) rather than deleting drawings directly, so lines, labels, table, and alerts stay consistent (FIX P1-g)? - Does a new level follow the render pattern — flag-driven
updateOrCreateLine/deleteLine, registered incollectLinesFromTFwith a new kind and handled innullSuppressedLine(FIX P1-h) — and should it be exempt from suppression like stops? - Does new period-scoped or latched state live in
TimeframeData, reset on therealPeriodTimekey, and rebuild from history on reload (FIX P0-1,FIX P1-a,repaint-prevention.mdchecklist)? - Does anything added to the security tuple keep the call count at one per slot, and is every new consumer of
[0]data a monotone latch, not a snapshot (FIX P0-2)? - If the change touches preview, straddle, or calendar logic — run the
htf-correctness.mdchecklist too.
Repaint Prevention
How TheStrat Suite guarantees that what you saw live is what you see after a reload — and the rules any contribution must follow to keep it that way. Every rule here came out of a bug I shipped, found, and fixed; the FIX tags are the receipts.
Written against pine/TheStratSuite_v3.0.0.pine. Code references are function names and FIX tags; grep the source for them.
What "repaint" means here
Three distinct failure modes, only two of which are bugs:
- Live/reload divergence. The chart said one thing during the session; a refresh says something different about the same moment. This is the trust-killer — an alert fired on a level that no longer exists.
- State loss on reload. A locked stop or a "magnitude already hit" flag vanishes after a refresh because the state was only ever computed on realtime ticks.
- Forming-bar movement. The current candle's trigger status changes as price moves. This is not a bug — a live trigger is the product. But forming-bar data must be fenced off from anything the indicator remembers.
Every rule below exists to eliminate 1 and 2 while keeping 3 deliberate and contained.
The architectural stance: no historical drawing objects
All lines, boxes, labels, and table cells are created or updated only on the last bar (barstate.islast), from state latched per higher-timeframe (HTF) period. The Suite never draws signal objects onto past bars.
This is the single biggest anti-repaint decision. There is no historical trail of drawing objects that could silently differ after a reload. The only object surface that must be reproducible is the current period's state — and every rule below exists to make that state rebuildable from bar history alone.
One deliberate historical surface exists as of v3: bar coloring (BARCOLOR-1, SECTION 14) paints classification onto every historical candle via barcolor(). It creates no drawing objects, and it is allowed precisely because it obeys Rule 8 — historical paint may read only series that evaluate identically live and on reload.
Rule 1 — Completed HTF bars come from lookahead_on plus offsets
Each timeframe slot makes one request.security call (getDataWithExhaustion) returning a 23-value tuple: the forming bar at offset [0] (called CC), and completed bars at [1] (C1), [2] (C2), [3], [4], plus exhaustion pivots.
With barmerge.lookahead_on:
- Offsets
[1]and beyond are final, confirmed values. This is the canonical non-repainting HTF idiom: by the time a chart bar exists inside period N, period N−1's OHLC is settled and identical live or on reload. - Offset
[0]behaves differently by context. For a closed period, historical chart bars receive the period's final values. For the open period, every chart bar receives the period's running values — which on reload means early-session bars see the whole-session-so-far extremes, not what they saw at the time.
Consequence: signals are anchored to C1 and C2. Trigger prices, magnitude targets, and combo classification all come from completed bars. CC participates only as live trigger state — which brings in Rule 2.
Rule 2 — CC may drive monotone latches, never snapshots
State derived from security data falls into two categories:
Monotone predicates — "has the period high crossed magnitude yet?" Once true, stays true for the rest of the period. These are safe to latch from CC extremes on any bar: live, the flag latches progressively tick by tick; on reload, it latches from period-extremes-so-far. Both paths converge to the same value now, and only now is displayed. The crossed flags (magHighCrossed, exhHighCrossed, …) and the stop trigger latches work this way.
Snapshots — "what was the exhaustion pivot at period open?" Point-in-time values. These must be taken exclusively from completed bars ([1]+), because a [0] term evaluates differently live versus on reload.
Case study: FIX P0-2
findExhaustionLevels seeded its running max/min from high[0]/low[0] inside the security context. Live, [0] was the developing bar; on reload it was the finished bar — so the latched exhaustion level moved after a refresh. The fix seeds from high[1]/low[1] and starts the pivot scan at i = 2 so no term ever touches the forming bar.
The fix comment records a deliberate tradeoff: the reload-stable seed can leave a rare mid-period re-latch slightly behind live price, and that is accepted. When freshness and reload-stability conflict, choose reload-stability.
Rule 3 — No ta.* calls in per-slot code paths
Pine ta.* history buffers belong to the lexical call site, not to the loop iteration executing it. A function called once per bar has one buffer; the same function called six times per bar (once per timeframe slot) still has one buffer, now interleaving six unrelated series.
Case study: FIX P0-1
computeSignalState detected a new HTF period with ta.change(time(tf)). The function has a single call site inside for i = 5 to 0, so all six slots shared one buffer: on each iteration, [1] held the previous slot's value from the previous bar. "New period" fired on nearly every bar, wiping the crossed flags and sticky stops constantly.
The fix is explicit per-slot state: the raw tuple carries realPeriodTime (the served, unshifted period-open), and each slot compares it against its own stored lastPeriodStart. Per-slot logic uses explicitly stored state, never ta.* implicit series.
Rule 4 — Period-scoped state resets only on a real period change
The reset key is raw.realPeriodTime != data.lastPeriodStart — the served period-open, immune to preview rewrites (preview reuses ccTime as a synthetic C1, so keying off shifted fields would reset every bar).
Two extra re-latch triggers exist for preview mode (FIX P1-d):
- Every tick while previewing (
isInPreview and barstate.islast). This is also the defense against Pine's realtime rollback: mutations made during a tick are rolled back before the next tick's calc, so synthetic preview state must be re-derived each tick to stay alive. - On preview exit without a rollover (
prevWasPreview and not isInPreview), so preview-shifted values don't leak into the real period's latched state.
Period-scoped state inventory (TimeframeData)
| Field(s) | Kind | Reset on |
|---|---|---|
prevHigh/Low/Time (C1), prevMagHigh/Low/Time (C2) |
Snapshot | new period / preview transition |
exhHighPrice/Time, exhLowPrice/Time |
Snapshot | new period / preview transition |
magHighCrossed, magLowCrossed, exhHighCrossed, exhLowCrossed |
Monotone latch | new period / preview transition |
stopHighTriggered/Price, stopLowTriggered/Price |
Monotone latch | new period / preview transition |
lastPeriodStart, wasPreview |
Bookkeeping | continuously |
Rule 5 — Latched state must be reproducible from historical bars
A reload turns yesterday's realtime ticks into historical bars. Any latch that only runs under barstate.islast never executes for those bars on reload — the state silently vanishes.
Case study: FIX P1-a
Sticky stops ("once a signal goes in force, the stop locks for the period") latched inside an islast-only block. Live: signal fires mid-session, stop locks, everything looks right. Reload: the bars where the signal fired are now historical, the latch never ran, the locked stop is gone.
The fix widens the gate to if barstate.islast or showStopLevels: when stops are enabled, detection and latching run on every bar so history rebuilds the lock; when they're off, the cheap islast-only path is kept. For every var latch, ask: "if the chart reloads right now, does bar history rebuild this?" Pay the every-bar cost only when the feature demands it.
Rule 6 — Alerts fire on edges of persisted state
Alert logic never asks "is the signal active?" — it asks "did it just become active?" That requires last-bar state, and last-bar state is where repaint bugs breed:
- Edge detection:
wasSetupBull/Bear,wasF2Bull/Bear,wasPotentialBull/Beararrays hold the prior bar's classification; alerts fire only on false→true. - Snapshot before update: the per-TF
alertconditions read pre-loop snapshots (wasSetBull0…), because the main loop overwrites the arrays beforealertconditionevaluates. - Reset per period (
FIX P1-c): was-state resets whenrealPeriodTimechanges, so a signal already in force at the open of a new period still produces an edge. Keyed offrealPeriodTime, notccStartTime— the latter is rewritten in preview and would reset every bar. - Update unconditionally (
FIX U2): even when a TF's checkbox excludes it from the consolidated alert message, its was-state still updates. An earlycontinuehere once made the per-TFalertconditions fire every bar instead of on edges. - Never alert from preview or straddled slots: their CC is synthetic or estimate-grade (
slotPreview,okA1–okA6gates). The period reset re-arms cleanly when the slot exits preview, so the first real bar alerts normally. - Alerts obey the same toggles as display (
FIX P0-3):isF2Bull/Beargate onshow_p3_toggle, so a signal type the user disabled can't alert while the table says nothing is in force.
Rule 7 — Wall-clock logic touches only the last bar
timenow drives exactly one subsystem: market-closed detection for Auto preview (session windows, weekends, the holiday fallback). All of it is gated on barstate.islast and not isBarReplay. Historical computation never consults the clock, so a reload reproduces history identically at 3 AM or noon.
Two sharp edges, both learned the hard way:
- Measure staleness from the scheduled close, never the open stamp (
FIX GLUE-1). CME glues holiday half-sessions into the next trade date, so a live Monday bar can open with a Thursday stamp;timenow - timeread ~87h and engaged preview mid-session.timenow - time_closecannot misfire — a live bar's scheduled close is always in the future. Full story inhtf-correctness.mdand../v2.2.3_holiday_glue_fix.md. - Bar replay is detected (
bar_index < last_bar_index) and disables Auto preview;previewMode = Offis the manual override for replay testing.
Rule 8 — Historical paint must be reload-stable
v3's bar coloring (BARCOLOR-1, SECTION 14) is the one surface that touches historical bars: barcolor() paints every candle by its Strat classification or FTFC grade. Painted history is only honest if every input evaluates identically live and on reload, which restricts what historical paint may read:
- Safe: the chart bar's own OHLC; completed HTF bars at offset
[1]+ (already settled per Rule 1); and served HTF period opens — an open never changes after its period starts, so it is period-stable underlookahead_on. - Unsafe: served
[0]HTF closes and extremes. On historical bars,lookahead_onserves the period's final values — painting history withftfc_up/ftfc_downwould color every bar of an up-closing day green from the open. Prophetic hindsight, not what the trader saw.
The two consequences in SECTION 14:
- FTFC Candles re-runs
calculateFTFCwith the chart bar's own close against the served HTF opens, grading every historical bar exactly as continuity stood at that bar's close. On the live bar the served HTF close is the live close, so the candle always agrees with the table's FTFC row. paintSlotFailed2rebuilds each slot's intra-period range from chart bars — a running high/low keyed on the served period-open time — because the served CC high/low are final period values on historical bars, the same lookahead leak.detectFailed2then grades the honestly-reconstructed range with the same engine the signal path uses.
Strat Candles needs no reconstruction: detectBarTypeAndFailed reads only the chart bar and its predecessor, identical live and on reload by definition.
Contributor checklist
Before merging anything that adds state, reads security data, or emits alerts:
- Does any latched value read offset
[0]in a security context? If it isn't a monotone predicate, it repaints (FIX P0-2). - Does new code call
ta.*anywhere reachable per-slot, per-loop-iteration, or conditionally? Replace with explicitly stored state (FIX P0-1). - Is every new
varlatch rebuilt from historical bars on reload — or is itislast-only on purpose and display-only (FIX P1-a)? - Do new alerts edge off persisted was-state, reset on
realPeriodTimechange, update state even when muted, and stay silent in preview (FIX P1-c,FIX U2)? - Does anything consult
timenowoutside abarstate.islastgate — and if it measures staleness, does it measure fromtime_close(FIX GLUE-1)? - Does new historical paint read anything that differs live vs on reload — a served
[0]close or extreme instead of chart-bar OHLC, completed[1]+ bars, or served period opens (BARCOLOR-1, Rule 8)?
Higher-Timeframe Correctness
How the Suite reads six timeframes from one chart without serving stale, misaligned, or fabricated data — and the traps in request.security that make this the hardest part of the codebase.
Written against pine/TheStratSuite_v3.0.0.pine. Companion postmortem: ../v2.2.3_holiday_glue_fix.md. Repaint fundamentals live in repaint-prevention.md; this doc assumes them.
The two directions of failure
- Chart TF above the slot's TF (asking a daily chart for 15-minute structure): the data is structurally unavailable — plain
request.securitysamples the lower TF at chart-bar boundaries, so you'd classify one arbitrary 15m bar per daily candle. The Suite refuses this direction. - Chart TF below the slot's TF (the normal case): the data is available but the mapping between chart bars and HTF bars has sharp edges — session-stamped opens, mid-bar calendar rolls, and exchange trade-date gluing. The Suite corrects this direction. Most of this doc is about those corrections.
Rule 1 — Refuse lower-TF reads; grey them, don't hide them
computeSignalState gates all per-slot work on:
validTimeframe = timeframe.in_seconds() <= timeframe.in_seconds(tf)
A slot only computes when the chart timeframe is at or below the slot's timeframe. There is no fallback path — an invalid slot produces an empty result, draws nothing, and alerts nothing.
The data table makes the refusal visible instead of silent: populateBarTypeRow renders lower-TF rows greyed out (isLowerTF), so the user sees the slot exists but is inert on this chart, rather than wondering where it went. The compact table does the same. Grey, don't hide — a disappeared row reads as a bug; a greyed row reads as "zoom out."
The mapping fact everything else follows from
request.security maps each chart bar to the HTF bar in effect at the chart bar's open. That single sentence generates every problem below:
- If an HTF period ends mid-chart-bar, the served data stays one period stale until the next chart bar opens (the straddle problem).
- If an instrument's sessions open before the calendar day of the period they belong to, the bar's open timestamp lies about which calendar period it is (the session-stamp problem).
- If an exchange glues a holiday session into the next trade date, the open timestamp can lie by days, not hours (the glue problem).
Rule 2 — Only calendar TFs can straddle, and staleness is a time test, not a calendar test
Who can straddle (isCalendarTF, isStraddleTF): only weekly-and-above ("W", "M", "3M", "6M", "12M") — their period boundaries don't have to align with chart-bar opens (a month or quarter rolls mid-week under a weekly chart bar). Intraday and daily requests from lower charts always align on session opens. And only a calendar TF strictly coarser than the chart qualifies — the chart's own TF can never straddle itself.
When it's straddling (shouldStraddle, final GLUE-2b form):
shouldStraddle(string tf) =>
int tc = time_close(tf)
not na(tc) and timenow > tc
One line: the served HTF bar is stale exactly when now is past its scheduled close. This replaced an entire calendar-key comparison apparatus, and the history of getting here is the most instructive bug chain in the project (Rule 4).
What happens on straddle: the same shift the preview system uses (applyPreviewShift) — promote the served forming bar to C1, C1 to C2, and so on down the chain, then synthesize a neutral placeholder CC spanning the promoted bar's range (open = its close, close = its midpoint). The placeholder deliberately classifies as an inside bar, and the table masks it with a yellow ? unless reconstruction (Rule 5) replaces it with real data.
The whole straddle path is armed by straddleWindowOK = previewMode != "Off" and barstate.islast and not isBarReplay — so previewMode = Off remains a full kill-switch (the bar-replay workaround), and nothing straddle-related ever touches historical bars.
Rule 3 — Normalize session-stamped opens by +12h before any calendar parsing
Futures periods open around 18:00 ET on the prior calendar day. Parse a quarterly bar's open stamp naively and you get the wrong quarter — the probe confirmed a Q3 bar stamped 2026-06-30 18:00 parsing as Q2.
The fix is OFF_MS (12 hours): every timestamp entering pkeyCal (the calendar-period keying function) gets +12h first. That lands any session-stamped open in its true calendar period across futures (18:00 prior day → next morning), equities (9:30 → same day), and crypto (00:00 → same day) — without ever overshooting the day.
Case study: FIX RECON-KEY-1
The reconstruction loop keyed the served daily bars with +12h but keyed now raw. For the ~7 hours between a period's session close (~17:00 ET) and calendar midnight, the two readings disagreed by one period — so at each roll, reconstruction matched the prior period's dailies and painted a wrong forming candle as current. The rule that came out of it: every timestamp entering the same comparison gets the same normalization. Mixed readings are a one-period-off bug waiting for a roll boundary.
Rule 4 — Never infer staleness or calendar position from an open stamp
The bug chain that produced this rule (full narrative in ../v2.2.3_holiday_glue_fix.md):
FIX GLUE-1— CME glued the Friday July 3 2026 holiday half-session into Monday July 6's trade date. Monday's live bar opened with a Thursday-evening stamp, sotimenow - timeread ~87h and Auto preview engaged mid-session: phantom PREVIEW MODE banner, D/W table rows showing?, and the forming week demoted into C1 — "weekly levels on the current day." Fix: measure the holiday fallback fromtime_close(isHolidayClosed). A live bar's scheduled close is always in the future, so the check cannot fire during a session; genuinely halted markets still trip it once now passes the last scheduled close.FIX GLUE-2(superseded same day) — first attempt keyed the straddle test off the served bar's open stamp instead of the chart bar's. Better, but still calendar-parsing an open stamp.FIX GLUE-2b— the glue reaches the weekly bar too: the week's bar opened Thursday 18:00 ET, ~84h before its true period — beyond what+12hcan normalize. The calendar-key approach was unsalvageable for glued bars, so it was replaced with the pure time test in Rule 2.
The distilled principle: open stamps encode the exchange's trade-date bookkeeping, not the calendar. time_close is computed from the same trade-date calendar that stamps the bars, so it is immune to early opens, needs no week-numbering or timezone parsing, and is direction-aware — a glued session running ahead of the calendar can never fire it. When you need staleness: compare timenow to time_close. When you need calendar identity and only have an open stamp: +12h normalize (Rule 3) — and know that gluing can still defeat it, which is why staleness must never depend on it.
Rule 5 — Reconstruct the forming HTF candle from daily sub-bars; keep it flagged as preview
A straddled slot during live trading shouldn't show a synthetic placeholder — the true forming HTF candle exists, just not through the stale mapping. The T3 reconstruction rebuilds it:
request.security_lower_tf(syminfo.tickerid, "D", …)returns the current chart bar's completed daily sub-bars (canRecongates this to charts above D — a constant gate, which Pine v6 dynamic requests permit).- Filter the dailies to those whose
pkeyCal(+12h)matches now's period; aggregate open/high/low from them. - Splice the live close on top:
ccC := close,ccH := max(reconHigh, close),ccL := min(reconLow, close).
Probe-validated against finer charts — the BTC 1W case reproduced the forming week's f2d exactly, including a below-C1 wick the earlier midpoint estimate missed.
Three deliberate conservatisms:
n == 0leaves the?candle. A classic weekend preview projects a period that has no dailies yet; reconstruction must never override it with nothing.isPreviewstaystrueeven when reconstruction succeeds. The every-tick preview re-latch is what keeps the shifted C1/C2 alive across realtime rollback (seerepaint-prevention.md, Rule 4), and alerts stay suppressed — reconstructed data is good, but it's still estimate-grade until the mapping catches up.- The table trusts it visually but not semantically: a reconstructed slot renders its CC normally (
reconOKArr), while a non-reconstructed preview slot shows the yellow?(ccUnknown).
Rule 6 — Preview mode is the same shift, armed by the market calendar
The market-closed cousin of the straddle: when nothing is trading and a new calendar period has begun, shouldApplyPreview applies the identical promotion so traders can plan next period's levels before the open (weekly slots also preview through Fri–Sun even before the calendar week flips).
Auto-detection is per asset class (isEquityLike / isFuturesLike / crypto):
- Equities/ETFs/options/indices: before 9:30 ET, after 16:00, weekends.
- Futures/forex/commodities: weekend closure (Fri 17:00 → Sun 18:00 ET) and the daily 17:00–18:00 maintenance window.
- Crypto: 24/7 — only the holiday fallback can trigger it.
- Everything: the holiday fallback — now is
max(3 × chart period, 4h)past the last bar's scheduled close (isHolidayClosed, Rule 4).
All of it is wall-clock logic, so all of it is gated barstate.islast and not isBarReplay and never touches history. Preview slots suppress their open line (the open is a proxy), and every preview/straddled slot is excluded from alerts (slotPreview, okA1–okA6) — the period reset re-arms alerting cleanly on the first real bar of the new period.
Contributor checklist
Before merging anything that touches timeframes, sessions, or the preview path:
- Does new per-slot logic run behind
validTimeframe? In the UI, does an unavailable slot grey out rather than disappear? - Does any code parse calendar fields (month/week/quarter) from a bar's open timestamp?
+12hnormalize it — and normalize every timestamp entering the same comparison identically (FIX RECON-KEY-1). - Does any code judge data staleness? Compare
timenowagainsttime_close(...), never againsttime(FIX GLUE-1,FIX GLUE-2b). - Does the change preserve the reconstruction conservatisms —
n == 0keeps the?,isPreviewstays true for reconstructed slots, preview slots never alert? - Does
previewMode = Offstill disable the entire preview and straddle apparatus (bar-replay workflow)? - Adding a calendar TF? Extend
isCalendarTFandpkeyCal, then verify on a session-stamped instrument (CME futures around a quarter roll), not just crypto.
Drawing Decisions
Why a level or label is on your chart — and, more often the interesting question, why it isn't. Every line and label passes through one decision funnel; this doc walks the funnel stage by stage and names the rule (and the FIX tag or design constraint) behind each suppression.
Written against pine/TheStratSuite_v3.0.0.pine. Code references are function names and FIX tags; grep the source for them. Signal semantics are in ../concepts/signals.md; bar-type notation in ../concepts/bar-types.md.
Two design constraints from ../DESIGN_CONSTRAINTS.md frame everything here:
- One decision per level, applied to both lines and labels. Any path where a line and its label can disagree is a bug.
- Conditional creation over create-then-hide. A suppressed level is never drawn invisibly — it is not created, or it is deleted and its handle nulled.
The funnel
Every drawn object passes through up to six stages:
- Slot gates — the timeframe slot is enabled and valid on this chart.
- Classification tree —
shouldDrawC1Leveldecides whether the C1/CC structure is a drawable setup at all, honoring the per-family signal toggles and FTFC filters. - Structural suppressors — post-tree rules that drop one side when the market has already resolved it.
- Derived-level gates — period open, magnitude, exhaustion, and F2 open each ride on a surviving trigger, with their own gates layered on top.
- Lead Signal filter — cross-timeframe directional suppression, mutating draw flags before render.
- Price-collision suppression — two lines at the same price: the higher timeframe wins.
Stops are the deliberate exception — they skip most of the funnel (see Stage 4).
Stage 1 — the slot gates
Nothing in a slot computes unless the slot is enabled (TFConfig.enabled) and the chart timeframe is at or below the slot's timeframe:
validTimeframe = timeframe.in_seconds() <= timeframe.in_seconds(tf)
An invalid slot draws nothing and is greyed in the data table rather than hidden. The full reasoning lives in htf-correctness.md (Rule 1).
Stage 2 — the classification tree: shouldDrawC1Level
One function, called twice per slot with mirrored parameters: shouldDrawC1High passes isBullish = true, shouldDrawC1Low passes isBullish = false with hammer and shooter swapped, so inside the tree momo_pattern always means "the conviction candle for this side" (hammer for the high, shooter for the low). Every directional variable (cc_in_force, c1_was_same, failed_opp, ftfc_blocks, …) is resolved through the same isBullish ? mirror, so the tree is written once and cannot go asymmetric.
It returns a tuple [result, is_continuation]. result is the draw verdict; is_continuation classifies the setup for the Universal label vocabulary (↑CONTINUATION vs ↑REVERSAL, via c1IsHighContinuation/c1IsLowContinuation) — it does not gate drawing.
Branches evaluate in priority order; the first match wins, and the default is false — nothing draws unless a branch affirms it.
| # | Branch (first match wins) | Family toggle | Conviction filter | FTFC toggle |
|---|---|---|---|---|
| 1 | CC is an outside bar (cc_is_3) |
show3Expansions |
— | threeExpRequireFTFC |
| 2 | Confirmed F2, either direction (is_f2u/is_f2d) |
show_p3_toggle |
— | p3RequireFTFC |
| 3 | C1 inside — reversal (C2 opposite or flat) | showInsideReversals |
insideRevOnlyMomo |
insideRevRequireFTFC |
| 3 | C1 inside — continuation (C2 same direction) | showInsideContinuations |
insideContOnlyMomo |
insideContRequireFTFC |
| 4 | C1 was a 3, CC broke this side (3-2 in force) | showRangeExpansions |
rangeExpOnlyMomo |
rangeExpansionsRequireFTFC |
| 5 | C1 was a 3, CC still inside (potential 3-2) | showRangeExpansions |
rangeExpOnlyMomo |
rangeExpansionsRequireFTFC |
| 6 | C1 is a hammer/shooter (momo branch) | — / showContinuations |
(is itself the filter) | reversalsRequireFTFC / continuationsRequireFTFC |
| 7 | C1 was 2 opposite (2-2 reversal), CC in force or not opposite | showAllReversals |
reversalsRequireActionable |
reversalsRequireFTFC |
| 8 | C1 was 2 same (2-2 continuation), CC in force or inside | showContinuations |
twoTwoContOnlyMomo |
continuationsRequireFTFC |
Sharp edges, in branch order:
- An outside CC preempts everything (branch 1). When CC engulfs C1, both of C1's triggers have already broken — no C1-based setup is meaningful. If
show3Expansionsis off, an outside CC therefore draws no trigger levels at all. This is the most common "my signal vanished" report, and it is by design. - A confirmed F2 draws both sides (branch 2). The reclaimed C1 range is the trade zone, so
failed_sameandfailed_oppboth return true — each side's call takes this branch. If the Reclaims family (show_p3_toggle) is off, the candle falls through and may still draw under a later branch as an ordinary 2. - A flat (doji) C2 counts as a reversal context (branch 3).
inside_is_reversalaccepts "C2 opposite or C2 flat."FIX CSS-1aligned the in-force computation to this same rule after the two paths disagreed — the line drew and alerted while the table, sticky stops, and only-when-in-force gates said nothing was live. The tree's definition is canonical; anything classifying inside reversals must match it. - The momo branch is guarded by
not (c1_is_3 and not showRangeExpansions)(branch 6). Without the guard, a hammer that was also an outside bar would sneak a 3-2 level in through the back door while that family is toggled off. - The momo reversal side has no family toggle (branch 6). A hammer/shooter reversal level draws whenever the pattern context is reversal-shaped and CC is not already a 2 the opposite way (
not cc_opposite) — beyond that, subject only to FTFC. The momo continuation side requiresshowContinuations. One hard suppression heads the branch:cc_is_opp_continuation(cc_opposite and c1_was_opp— CC already continuing the opposite way through the pattern) returns false before either outcome is considered. FIX BTC-1(branch 6): the momo branch used to test the inside-reversal FTFC toggle for both of its outcomes. It now obeys the toggle of the family it resolves to — reversal →reversalsRequireFTFC, continuation →continuationsRequireFTFC. When adding a branch, wire the FTFC gate to the family the branch is, not the one it sits near.- The 2-2 reversal's F2 filter is not in the tree (branch 7).
reversalsRequireC1F2is enforced post-tree (Stage 3) because it needsc1_was_f2u/c1_was_f2d, computed alongside the other per-slot classification.reversalsRequireActionableis bypassed when C1 is itself a hammer/shooter — the conviction requirement is already met. - 2-2 continuations exclude an outside C2 (branch 8).
not c2_was_3: two bars in a row is not a trend when the first of them went both ways.
Stage 3 — structural suppressors
The tree says "this setup family is enabled and qualifies." A second layer then drops one side when price action has already resolved it:
drawHigh = drawHighResult and not three_2d_suppress_high and not inside_breakout_suppress_high and not twotwo_rev_f2_suppress_high
(and the mirrored drawLow.) The suppressors:
three_2u_suppress_low/three_2d_suppress_high— C1 was a 3 and CC committed to one side. The losing side's trigger drops: once the 3-2 resolves upward, the low trigger is noise. Exception: CC is itself failing (is_f2u/is_f2d) — the failure keeps both sides relevant.inside_breakout_suppress_low/inside_breakout_suppress_high— inside setup, CC broke one side. The coil resolved; the unbroken side's trigger drops. Exceptions: an active F2 (the reclaim draws both sides) or CC going 3 (branch 1 owns that case).twotwo_rev_f2_suppress_high/_low— the F2 filter for 2-2 reversals: withreversalsRequireC1F2on, a reversal against a C1 that was not itself a Failed 2 is suppressed. Carve-outs: C1 is 3, C1 is inside, or CC has an active F2 — those are different setups that merely share the geometry.cc_3u_suppress_low/cc_3d_suppress_high— CC is an outside bar closing one way. These do not touch the trigger lines (an outside bar broke both, both stay) — they only enterhigh_suppress_base/low_suppress_base, which gate magnitude and exhaustion: targets project only on the side the expansion is closing toward.
Stage 4 — derived levels ride their trigger
Each derived level requires a surviving parent and adds gates of its own. The recurring theme: no orphans — a target with no visible signal behind it is treated as a bug, so every fallback path re-requires drawHigh/drawLow.
Period open line
Draws only when showOpen and (drawHigh or drawLow) — an open line with no signal is an orphan. Suppressed additionally when:
- the slot is in preview or straddled (
FIX HTF-STRADDLE-1): a straddled slot's open is a proxy value, and drawing it would present an estimate as data; - an F2/pre-F2 is active with
showF2OpenLineon (f2SuppressesOpen): the F2 open reference line replaces it; - the Lead filter suppressed the only side that justified it (Stage 5's "orphaned open" cleanup).
Magnitude (drawMagHigh / drawMagLow)
Two positive paths, then a battery of suppressors. Positive: force_mag_* (global showMagnitude plus a qualifying trigger, inside→3 setup, or outside-bar expansion side), or the fallback drawHigh and shouldDrawMagnitude(...). The fallback deliberately requires drawHigh/drawLow — without it, signal_in_force firing while the family toggle is off produced orphan magnitude lines.
shouldDrawMagnitude(c1_broke_c2, is_signal_in_force) is not c1_broke_c2 and is_signal_in_force: if C1 already traded through C2's extreme, the "target" is behind the entry — meaningless, so suppressed.
The suppressors, each with its reason:
| Suppressor | Why the level is hidden |
|---|---|
high_suppress_base / low_suppress_base |
Side structurally resolved away (the three_2* and inside_breakout suppressors plus the outside-CC closing direction — the twotwo_rev_f2 suppressor is not part of the base) |
p3_suppress_mag_high = is_f2u (mirror for low) |
An F2u is a failed upside — projecting an upside target off a failed upside is a contradiction |
cont_suppress_mag_high/low (is_22_cont_*, is_32_cont_*) |
Continuations broke C2's extreme by definition; magnitude is already behind price. Exhaustion takes over as the target (cont_show_exh_*) |
exh_high_same_as_mag / exh_low_same_as_mag |
Dedup: exhaustion at the same price wins, one line not two |
mag_high_same_as_trigger / mag_low_same_as_trigger |
Dedup: equal highs/lows make C2's extreme identical to the trigger; the trigger wins |
magOnlyWhenInForce and not rawInForce* |
User opted to see targets only on live trades |
Exhaustion (drawExhHigh / drawExhLow)
Base condition (exh_*_base_condition): global showExhaustion, a valid pivot (exh_*_valid — exists and differs from the trigger), the optional exhRequiresMagHit gate, dedup against the opposite side's trigger and magnitude, *_suppress_base, and the same F2 directional suppression as magnitude. Then one of four positive paths: the parent trigger draws; the level was already crossed (data.exh*Crossed); a continuation is using exhaustion as its target (cont_show_exh_*); or an outside-bar expansion projects it (threeExp_show_exh_*, which per FIX CSS-2 requires the global showExhaustion — it once drew with the master toggle off).
The "already crossed" path implements the product decision that hit levels turn grey rather than disappear (COLOR_HIT_HIGH/COLOR_HIT_LOW): a target you reached stays on the chart as a record even after its trigger line is gone. Same rule colors crossed magnitude.
Outside-bar (3 Exp) targets
Separately toggled (show3ExpMagnitude, show3ExpExhaustion) and one-sided: direction comes from CC's close vs open, and only the expansion side projects (threeExp_show_mag_high requires the bar closing up, etc.). The inside→3 case (inside_3u_setup/inside_3d_setup) is gated behind show3Expansions for the same anti-orphan reason as the magnitude fallback.
Stops — the deliberate exemption
drawStopHigh/Low needs only showStopLevels and the sticky latch (stopHighTriggered) — no FTFC, no structural suppressors, and stops are excluded from Stage 6's line suppression (see the comment in collectLinesFromTF). One deliberate exception: Stage 5's Lead filter does clear stop flags on a suppressed counter-trend side (suppressHighFlags/suppressLowFlags include drawStop*, and a trend-aligned F2 drops the counter-side stop). Per ../DESIGN_CONSTRAINTS.md: stop lines are intentionally excluded from line suppression (risk management). Do not route stops through any cosmetic suppression you add.
Stage 5 — the Lead Signal filter
With enableDirectionalFilter on, the highest enabled timeframe with a signal in force becomes the anchor (getSignalDirection; when both sides are in force, FIX LEAD-F2-TIEBREAK-2 resolves F2s explicitly first — F2u is bearish, F2d bullish — because the generic "ends in u" test reads F2u as bullish and would invert the whole filter). Slots below the anchor then have their flags mutated before render:
- Counter-trend confirmed F2 →
suppressAllFlags(): everything goes, including the open line, andsignalInForceHigh/Loware cleared (FIX P1-g) so the data table and Lead row cannot keep highlighting a timeframe whose lines were hidden. - Trend-aligned confirmed F2 → the signal stays; only the counter-side extras (mag/exh/stop flags on the suppressed side) drop.
- Regular signal or pre-F2 → the counter-trend side's flags and in-force are suppressed (
FIX P1-gagain); pre-F2 flags are cleared (not yet a confirmed reversal); and if the surviving side isn't drawing, the open line is cleaned up as an orphan.
Two invariants make this stage safe:
- Filter first, render second.
renderSignalLevelsruns after the filter has settled the flags, so there is no draw-then-delete flash and noline.set_*on something about to be deleted. - Labels read the same verdict. The label pass rebuilds the identical suppression decision (
skipHighLabels/skipLowLabels) and feedscollectTimeframeLabelsamodifiedResultwith the suppressed side's fields nulled. One decision, two surfaces — a line without its label, or a label without its line, is a Stage 5 bug.
exhDisablesInForce interacts here: with it on, a signal that hit exhaustion stops counting as in force, which releases the Lead anchor and lets lower-timeframe reversals draw again.
Stage 6 — price-collision suppression across timeframes
Six slots frequently produce lines at the same price (a daily C1 high that is also the weekly C1 high). collectLinesFromTF gathers every live line with its price, timeframe seconds, owner slot, and a kind code; suppressLowerTFLines rounds prices to syminfo.mintick and keeps only the highest-timeframe line at each price, deleting the rest. High-side, low-side, and open lines are pooled separately.
FIX P1-h is the load-bearing detail: deleting the line is not enough — the owning TimeframeData field must be nulled (nullSuppressedLine), for two reasons. First, the render phase would otherwise call line.set_* on a deleted id (undefined behavior). Second, nulling the field makes the render phase recreate the line via line.new on the next tick, so a suppressed line reappears automatically once the collision clears. Suppression here is a per-tick verdict, not a permanent deletion.
Stop lines are never collected into the pools — the risk-management exemption again.
Why is my level not showing?
The diagnostic path, roughly cheapest-first. The Debug Panel (the Debug settings group) surfaces most of these flags (drawHigh, drawLow, drawMagHigh, force_mag_high, the classification booleans) for one chosen slot.
| Symptom | Likely rule | Where |
|---|---|---|
| No lines at all on a slot | Chart TF above slot TF, or slot disabled | validTimeframe, Stage 1 |
| Signal candle, no trigger line | Family toggle off, family FTFC toggle blocking, or HAM/SHO filter | Stage 2 table |
| Everything vanished on a big candle | CC went outside bar with show3Expansions off |
Stage 2, branch 1 |
| One side's line disappeared mid-bar | Inside breakout or 3-2 resolution suppressed the losing side | Stage 3 |
| Trigger draws, no magnitude | C1 broke C2 (shouldDrawMagnitude), continuation (exhaustion is the target), F2 direction, dedup vs trigger/exhaustion, or magOnlyWhenInForce |
Stage 4 |
| Exhaustion missing | No valid pivot, exhRequiresMagHit, dedup, or exhOnlyWhenInForce |
Stage 4 |
| Lower TF blank while higher TF signals | Lead Signal filter suppressing counter-trend | Stage 5 |
| Line missing on one TF but present on another at the same price | Collision suppression — higher TF won | Stage 6 |
| Open line missing | Preview/straddled slot, F2 open line replacing it, or orphan cleanup | Stage 4 / Stage 5 |
| F2 levels absent though F2s show in the table | Detection on, Reclaims signal family off — see the two-layer note in ../concepts/signals.md |
show_p3_toggle vs enableFailed2Detection |
Contributor checklist
Before merging anything that adds, gates, or suppresses a drawn object:
- Is the decision centralized — one predicate, consumed by both the line path and the label path? Any way for them to diverge fails review (
../DESIGN_CONSTRAINTS.md). - Is it conditional creation, not create-then-hide? If you suppress an existing object outside
computeSignalState, do you delete it and null the owning field so render recreates it cleanly later (FIX P1-h)? - If you hide a signal's lines, do you also clear its in-force flags so the data table and Lead anchor agree with the chart (
FIX P1-g)? - Does your FTFC gate belong to the family your branch resolves to, not a neighboring one (
FIX BTC-1)? Does your in-force logic classify edge structures (flat C2) identically to the draw tree (FIX CSS-1)? - Does any new gate require the opposite side's level to exist? The opposite trigger is suppressed exactly when a signal goes in force — that requirement made the Take Action Window vanish for the default config (
FIX TAW-1). - Can your feature produce an orphan — a magnitude, exhaustion, or open line whose parent trigger isn't drawn? Re-require the parent in every fallback path.
- Did you leave stops alone? Stop levels bypass the structural, FTFC, and price-collision suppression by design (the Lead filter is the one sanctioned exception).
Rendering
How the Suite turns resolved signal state into chart objects — anchoring, object lifetimes, the label pool, and the consolidation passes — inside TradingView's drawing-object limits and without breaking the no-repaint contract.
Written against pine/TheStratSuite_v3.0.0.pine. Code references are function names and FIX tags; grep the source for them. This doc covers how drawing executes; which levels qualify to draw is decision logic, covered in drawing-decisions.md. Repaint fundamentals live in repaint-prevention.md; this doc assumes them.
The object budget
TradingView caps drawing objects per script. The defaults are ~50 of each kind; the hard maximum is 500. The Suite declares its budget in the indicator() header: max_labels_count = 200, max_lines_count = 200, max_boxes_count = 200.
The platform behavior that makes this matter: exceeding a cap is not an error — the oldest objects are silently garbage-collected. For a script that drew a historical trail, that would mean levels quietly vanishing off the left edge. The Suite's answer is architectural: there is no trail. Every object on screen describes now — the current period's state across up to six timeframe slots — so the live set is small and bounded:
| Kind | Worst case | Where it comes from |
|---|---|---|
| Lines | 60 | 10 owned line fields per TimeframeData slot (trigger high/low, open, magnitude ×2, exhaustion ×2, stop ×2, F2 open) × 6 slots |
| Boxes | 12 | 2 Take Action Window boxes per slot × 6 |
| Labels | ~120 | ≤10 level labels per slot before consolidation, × 2 render modes (timeline + floating) |
200 of each covers the worst case with headroom. The data table and debug panel are table objects — a separate class with its own platform limits that does not consume this budget (created once on barstate.isfirst, cells rewritten on barstate.islast).
The pooling and update-in-place machinery below exists to keep the churn as bounded as the count: no delete-all-and-recreate storms on every tick, no flicker, no id turnover for the garbage collector to feed on.
The pipeline: decide fully, then draw once
Rendering is the last phase of a strict sequence:
- Compute —
computeSignalStateproduces aProcessingResultper slot: prices, colors, styles, and onedraw*flag per level. It is called on every bar (state latching needs the full history); its flag-compute phase is gatedbarstate.islast or showStopLevels— with stops off (the default) it runs only on the last bar, with stops on it must also run on historical bars so the sticky stop latch survives a reload (see the comment above that gate). The filter and render phases below run only underbarstate.islast. - Filter — the Lead Signal directional filter mutates those flags in place (
suppressHighFlags/suppressLowFlags/suppressAllFlags). - Render —
renderSignalLevelsreads the final flags and draws. The comment above it states the rule: it is called after the filter has modified flags, so no draw-then-delete is needed.
Two invariants fall out of this ordering:
- One decision per level, applied to both lines and labels. The label-suppression logic in the
collectTimeframeLabelscall site mirrors the line-suppression flags exactly (building amodifiedResultwith the suppressed sides blanked). Any path where a line and its label could disagree is a bug. - Conditional creation over create-then-hide. An object exists if and only if its draw flag is true this tick; otherwise it is deleted. Nothing is ever "hidden" with transparent colors or off-screen coordinates. (This is a stated design constraint — see
../DESIGN_CONSTRAINTS.md— do not propose hide-based schemes.)
X/Y anchoring rules
Lines and boxes anchor in time, not bar index
Every line and box uses xloc.bar_time (updateOrCreateLine, updateOrCreateBox). Two reasons:
- HTF geometry doesn't live on chart bars. A weekly level's start is the weekly period's open timestamp, which need not coincide with any chart bar's index — and must land in the same place on a 15m chart and a 4H chart.
- The right edge is in the future. Lines extend to
lineEndTime = raw.ccTime + periodDuration— the current HTF period's projected close.xloc.bar_timeaccepts future timestamps freely;xloc.bar_indexis capped a few hundred bars ahead.
Every line is horizontal — y1 == y2 == the level price. The x-anchors per level:
| Object | x1 anchor | y |
|---|---|---|
| Trigger high/low | data.prevTime (C1 period open) |
C1 high / low |
| Magnitude high/low | data.prevMagTime (C2 period open) |
C2 high / low |
| Exhaustion high/low | data.exhHighTime / data.exhLowTime (pivot bar time) |
pivot price |
| Open, stops, F2 open | r.ccStartTime (CC period open) |
open / locked stop / CC open |
| Take Action Window boxes | data.prevTime → lineEndTime |
trigger ↔ magnitude (or exhaustion); P3 windows span trigger ↔ trigger |
One honest caveat: periodDuration is timeframe.in_seconds(tf) * 1000, a fixed-duration approximation — a month is not a constant number of seconds, and session gaps are not modeled. The right edge is cosmetic. Only the y (the price) is load-bearing; nothing computes off a line's x2.
Labels: two render modes, two anchor systems
consolidateAndCreate can emit each consolidated label in either or both modes:
- Timeline labels —
xloc.bar_time, anchored at the level'slineEndTimepluslabelOffsetchart-bars (converted to milliseconds). They sit at the line's right edge and stay with it. - Floating labels —
xloc.bar_index, anchored atbar_index + floatingLabelOffset. Re-stamped every tick, so they ride along with the latest bar.
Both use label.style_label_left: the anchor is the point, text extends right.
Price identity is always mintick-rounded
Every same-price comparison in the render path rounds through syminfo.mintick first: label consolidation, line suppression, stop-at-break-even detection (stopHighAtBE), and F2-open overlap checks. Raw float equality is never used. Add a new comparison, inherit the rounding.
Two lifetime models
The Suite deliberately uses different object lifetimes for lines and labels, matched to how stable each object's identity is.
Owned lines and boxes: update in place
Each slot's TimeframeData owns its line and box references as named fields (highLine, magLowLine, actionWindowBullish, …). The render phase runs every field through one of two helpers:
updateOrCreateLine/updateOrCreateBox— if the reference isna, create; otherwise mutate the existing object with setters (line.set_xy1/xy2/color/width/style). The object keeps its id across ticks; only its attributes move.deleteLine/deleteBox— delete if present, returnna, and the caller always assigns the result back to the owning field.
That assign-back protocol is the safety rail. Its violation is a recorded bug class:
Case study: FIX P1-h. The line-suppression pass (below) deleted collision-losing lines but left the owning TimeframeData field pointing at the dead id. Next tick, the render phase called line.set_* on a deleted object — undefined behavior — and the suppressed line could never come back. The fix threads owner + kind through LineInfo so nullSuppressedLine can null the originating field: the render phase then recreates via line.new next tick, and a suppressed line reappears once the price collision clears. Every path that deletes an owned object must null its field, no exceptions.
Update-in-place is also what makes cheap state transitions possible: a magnitude level that gets hit takes its "Crossed" color (COLOR_HIT_HIGH / COLOR_HIT_LOW, gray by default) rather than disappearing — same object, one set_color.
Pooled labels: rebuilt every tick (FIX P1-i)
Labels can't be owned the same way. Consolidation merges an arbitrary, changing set of levels into an arbitrary, changing set of labels — text, price, color, and even count shift tick to tick. There is no stable identity to update in place, and the old code's answer (delete every label, recreate every label, every tick) was pure churn.
The pool (LabelPoolT: an array<label> plus a used cursor) reuses by position instead of identity:
- Reset — at the top of the
barstate.islastblock,lblPool.used := 0. No labels are deleted; the cursor just rewinds. - Acquire —
acquireLabelhands outitems[used]if one exists, re-stamping every attribute via setters (set_xloc,set_y,set_text,set_color,set_textcolor,set_size,set_style) so no residue survives from the label's previous life. Past the end of the array, it creates vialabel.newand pushes — the pool grows to the session's high-water mark. Either way the cursor advances. (The pool object is passed as a parameter so the function can mutate the cursor — Pine functions cannot assign globals.) - Trim — after all consolidation runs, a
whileloop pops and deletes every pooled label beyondused. The trim runs on everyislastregardless of whether labels are enabled — with labels toggled off,usedstays 0 and the trim clears the entire pool. Toggling a feature off must visibly remove its objects.
Allocation is therefore demand-driven, reuse is total, and the budget exposure is exactly the maximum number of labels simultaneously visible — never a function of how many ticks have elapsed.
Consolidation and suppression
Two passes keep six timeframes' worth of levels readable when they land on the same price. Both key on mintick-rounded price; both run only on the last bar.
Label consolidation (consolidateAndCreate)
Labels are collected per tick into three side-buckets — highLabels, lowLabels, openLabels — grouped by which side of price they sit on, not by signal direction (a bullish stop sits below at C1's low, so it collects into lowLabels; the bearish stop into highLabels). Within a bucket, a map from rounded price to index merges collisions: the first entry at a price keeps its color and anchor time, later entries append their text with " + ". One price, one label: 60m 2d-1-2u + D MAG 512.50 instead of two labels stacked unreadably.
A related fold happens earlier, at collection: a stop sitting exactly at its trigger price (break-even) skips its own label and instead appends " + STOP" to the trigger label (stopHighAtBE / stopLowAtBE in collectTimeframeLabels).
What consolidation does not do is collision layout for labels at nearby-but-different prices. Label overlap is a known platform limitation, deliberately sidelined (../DESIGN_CONSTRAINTS.md) — do not propose collision systems.
Line suppression (suppressLowerTFLines)
Lines can't merge text, so the same collision is resolved by rank: when two timeframes draw a line at the same rounded price, the higher timeframe wins and the lower-TF line is deleted. collectLinesFromTF gathers every live line into LineInfo records (price, tfSeconds, line ref, owner slot, kind code); a map from rounded price to best-so-far settles each collision, deleting the loser and nulling its owner field via nullSuppressedLine (FIX P1-h, above) so it can return when prices diverge.
One deliberate exemption, commented at the collection site: stop lines never enter suppression. A stop must stay visible for risk management regardless of what trigger or magnitude line shares its price. Intentional — do not "fix" it.
How rendering respects the no-repaint contract
The drawing layer inherits its guarantees from repaint-prevention.md; rendering adds nothing that could violate them:
- Nothing historical is drawn. Every create/update/delete in this doc sits under
barstate.islast. State tracking runs on every bar; rendering does not. There is no painted trail whose past could differ after a reload. - Rendering is a pure function of resolved current state. Each tick fully re-derives every object — position, color, text, existence — from this tick's
ProcessingResultand latched slot state. No drawing decision depends on what was drawn last tick. Reload rebuilds the same state from bar history (the repaint doc's Rules 4–5), so it redraws the same objects. - Realtime rollback is harmless here. Pine rolls back intra-bar mutations between ticks; because every object is re-stamped on every
islasttick anyway, rolled-back drawing state is simply re-applied on the next tick. The pool's reset→acquire→trim cycle is idempotent by construction. - Anchors come from served data, not the clock. Line x-anchors are period-open timestamps out of the security tuple (
data.prevTime,r.ccStartTime), nevertimenow. The only forward projection is the cosmeticlineEndTime. - Suppression is display-only. The directional filter and line suppression flip draw flags and delete objects; where hiding a line would desynchronize the table or alerts, the same pass clears the semantic flags too (
FIX P1-g). Alerts edge off persisted was-state, not off what happens to be drawn.
Contributor checklist
Before merging anything that creates, mutates, or deletes a drawing object:
- Is creation conditional on a resolved draw flag, and does the flag dropping delete the object — never hide it via transparency or off-screen coordinates?
- Does every delete path assign
naback to the owning field (deleteLine/deleteBoxreturn value, ornullSuppressedLinefor suppression) so no later tick can callset_*on a dead id (FIX P1-h)? - Do new labels go through
acquireLabeland the correct side-bucket — never a barelabel.newin the render path — and doesacquireLabelstill re-stamp every attribute your label varies (FIX P1-i)? - Is every new same-price comparison rounded through
syminfo.mintickbefore comparing? - Does the worst-case live-object count still clear the 200/200/200 header budget with headroom? Redo the inventory math in this doc if you added a per-slot object.
- Is all new drawing under
barstate.islast, anchoredxloc.bar_timefrom served period timestamps (floating labels excepted — they ridebar_index) — with nothing load-bearing on a projected right edge?
Performance
Where the Suite spends its execution budget, the bounds designed into each cost center, and the gates that keep expensive work off the hot paths.
Written against pine/TheStratSuite_v3.0.0.pine. Code references are function names and FIX tags; grep the source for them. Repaint rules decide when work is allowed to run — repaint-prevention.md — and this doc assumes them.
The cost model
A Pine indicator pays in four places, and the Suite's architecture assigns each one a strategy:
- Security contexts. Every
request.securitycall replays its expression over the requested timeframe's history in a separate execution context. Cost is per call, not per returned value. Strategy: few calls, fat tuples, bounded depth. - Per-bar main-context work. Runs once for every chart bar at load, then on every realtime tick. Strategy: keep the every-bar path to state maintenance only.
- Last-bar work. Runs once per tick, never across history. Strategy: everything visual lives here.
- Drawing-object churn. Creating and deleting lines, labels, boxes every tick causes flicker and garbage-collection pressure. Strategy: persistent references, update in place.
The one-sentence version, codified in ../DESIGN_CONSTRAINTS.md: state tracking runs every bar; rendering runs only on barstate.islast; objects are conditionally created, never created-then-hidden.
One security call per slot — tuples, because calls are the unit of cost
Each of the six timeframe slots makes exactly one request.security call, and each call returns the 23-value tuple from getDataWithExhaustion(): the forming bar (CC), completed bars C1–C4 (time and OHLC for C1 and C2; only high/low for C3 and C4), and the two exhaustion pivots.
One more request exists: request.security_lower_tf fetching completed daily sub-bars for straddled-slot reconstruction (FIX HTF-STRADDLE-1, T3). It sits behind the constant gate canRecon (chart timeframe above daily), so it either always executes or never does — both legal under Pine v6 dynamic-request rules, and charts at or below daily never pay for it.
Total: at most 7 of TradingView's 40-call request budget.
Why tuples and not more calls: adding a field to the tuple is nearly free — one more series copied out of a context that already exists. Adding a call spins up an entire new context that replays HTF history. The tuple is flat (23 positional values, no arrays) because arrays cannot pass through request.security; the same constraint is why calculateFTFC takes an unrolled 18-parameter per-TF signature instead of arrays. Both are deliberate — see ../DESIGN_CONSTRAINTS.md.
The rule for contributions: need more HTF data? Extend the tuple. Never add a call.
calc_bars_count tiers
Without calc_bars_count, each security context computes over the symbol's entire history at the requested timeframe. The Suite draws nothing historical, so deep history buys nothing — the only depth that matters is warmup for the C1–C4 offsets and the exhaustion scan's window. Each slot therefore caps its context (cb1–cb6, tiered by the slot's timeframe string):
| Timeframe family | calc_bars_count |
|---|---|
Monthly and above (M, 1M, 3M, 6M, 12M) |
100 |
Weekly (W, 1W) |
100 |
Daily (D, 1D) |
150 |
4H–12H (240, 480, 720) |
200 |
| Finer intraday (everything else) | 250 |
Two constraints shape the numbers:
- The floor is 100. The exhaustion scan requires
bar_index > 50of warmup plus its 100-bar display window (see next section). Lowering any tier below 100 silently kills exhaustion levels on that slot. - Finer timeframes get more headroom. A fine-resolution bar is cheap to compute, and session gaps and holidays consume proportionally more bars at intraday resolutions; coarse timeframes need nothing beyond the floor (100 monthly bars is over eight years).
Implementation note, and the reason the six tier expressions are copy-pasted ternaries rather than a helper: calc_bars_count demands a simple int, and a Pine function's return is a series — request.security rejects it. The inline form is ugly on purpose. Grep cb1 for the comment.
Inside the security context: the exhaustion scan is triple-gated
findExhaustionLevels is the only loop the Suite ships into its security contexts, so it carries three gates:
showAnyTargets— targets disabled means no scan runs anywhere, on any bar.bar_index > last_bar_index - 100— only the most recent 100 HTF bars scan. Historical exhaustion is never displayed, so every older bar returnsnaimmediately.bar_index > 50— warmup, guaranteeing the 49-offset lookback never underflows.
The scan itself is for i = 2 to 48 with an early break once both pivots are found — a designed worst case of 100 bars × ~47 iterations of comparisons per context, and typically far less. (The i = 2 start and [1] seed are repaint constraints, not perf ones — FIX P0-2 in repaint-prevention.md.)
Everything else in the tuple is raw series offsets (high[1], time[2], …): free.
Every bar vs last bar: the two-speed engine
The main context runs at two speeds. Every-bar work exists only where reload-rebuildability demands it (repaint-prevention.md, Rule 5):
- Tuple unpack into
TFRawData, per-slot new-period detection, and the monotone crossed-flag latches — the head ofcomputeSignalState(FIX P0-1). - Alert was-state maintenance (
FIX P1-c,FIX U2) — boolean array writes. The expensive part, message assembly viabuildAlertLabel, runs only when an edge actually fires, and its detail string is built lazily field-by-field (FIX P1-j). - Conditionally: full C1 pattern and F2 detection when
showStopLevelsis on (FIX P1-a). The gate isbarstate.islast or showStopLevels— the sticky-stop latch must have run on the bars where the signal fired or a reload loses the lock, so the every-bar cost is paid, but only behind the toggle. Stops off (the default) keeps the cheapislast-only path. - Conditionally: bar coloring (
BARCOLOR-1, off by default) —barcolor()paints history, so its classification must run on every bar. Strat mode costs onedetectBarTypeAndFailedcall per chart bar; FTFC mode onecalculateFTFCre-run plus, with flip highlighting on, sixpaintSlotFailed2range updates.
Last-bar-only work is everything else:
- Daily-sub-bar reconstruction for straddled slots (
FIX HTF-STRADDLE-1, T3) — 6 slots × the dailies inside the current chart bar (≤ ~23 even on a monthly chart). - The directional-filter anchor scan,
renderSignalLevels, take-action window boxes. - Label collection, consolidation, and pool render; line collection and
suppressLowerTFLines. - The data table, the debug panel, and all
timenow/ market-closed logic (Rule 7 of the repaint doc).
The two docs meet in one rule, read from opposite sides. Repaint says: a latch that must survive reload cannot be islast-only. Performance says: new work defaults to islast-only, and widening a gate to every-bar is a correctness decision that must be justified and, where possible, paid for behind a feature toggle — exactly the FIX P1-a pattern.
Drawing objects: update in place, delete only on state change
One principle — a drawing object's lifetime matches its level's lifetime, not a tick. The cost claims:
- Lines and boxes hold persistent references, mutated via setters on last-bar ticks; deletion happens only when a level stops qualifying.
- Labels are pooled (
FIX P1-i): update-in-place, surplus trimmed after render. The pool replaced a delete-all-and-recreate-every-tick pattern — the single largest per-tick object churn removed in the 2.2.x line. - Line suppression is the one sanctioned delete path (
FIX P1-h): a suppressed line is deleted and its owner's field nulled so the next tick recreates it cleanly.
No drawing object is ever created on a historical bar, so the live population is bounded by enabled slots × level types — order tens against the declared 200-per-type caps (boundaries table below). Mechanics, the budget inventory, and the P1-h/P1-i case studies: rendering.md.
Tables: allocate once, write cells only on the last bar
barTypeTable and debugTable are var references created exactly once, on barstate.isfirst — the bar-type table with dimensions frozen from enabledTFCount and the table mode, the debug panel at a fixed size. No runtime resize path exists or is needed — changing any input re-instantiates the script from bar zero, so "once" means once per instantiation.
Every table.cell write sits under a barstate.islast gate. A Pine table only ever displays its last-bar state, so a cell write on a historical bar is pure waste; the gate makes the table's cost across history exactly zero and its per-tick cost a few dozen cell writes (populateBarTypeRow and the compact-mode path).
Designed boundaries
| Boundary | Value | Where |
|---|---|---|
request.* calls |
≤ 7 (6 slots + conditional daily recon) of 40 | getDataWithExhaustion, canRecon |
| Security-context depth | 100–250 bars per slot | cb1–cb6 tiers |
| Exhaustion scan | last 100 HTF bars × ≤ 47 iterations, early break | findExhaustionLevels |
| Straddle reconstruction | last bar only; 6 slots × ≤ ~23 dailies | FIX HTF-STRADDLE-1 (T3) |
| Drawing objects | 200 per type declared; live population order tens | indicator() declaration |
| Bar coloring | constant per-bar work; no request.* calls, no drawing objects (barcolor sits outside the 200-object caps) |
SECTION 14 |
| Main-context history | max_bars_back = 200 |
indicator() declaration |
These are designed bounds, not benchmark results — the repo records no Pine Profiler measurements. A contribution claiming a performance win should come with before/after evidence from TradingView's Pine Profiler, not reasoning alone.
Contributor checklist
Before merging anything that requests data, loops, draws, or writes tables:
- Does new HTF data extend the existing tuple, or add a
request.*call? Extend the tuple — the call count is the budget (grepgetDataWithExhaustion). - Does new code loop inside a security context? Gate it like
findExhaustionLevels: feature toggle, recent-bars window, warmup — and keep everycbtier at or above the 100-bar floor the scan requires. - Must new per-bar work really run on historical bars for reload rebuild (
repaint-prevention.md, Rule 5)? If not, gate onbarstate.islast; if yes, put the cost behind its feature toggle likeFIX P1-a. - Do new drawing objects hold persistent refs updated via setters (
updateOrCreateLine,acquireLabel), deleting only on state change — and does every delete path null the owning field (FIX P1-h)? - Are new tables allocated once on
barstate.isfirstand their cells written only underbarstate.islast? - If you touched
cb1–cb6: stillsimple int, still inline (no function wrapper —request.securityrejects series), still ≥ 100?
Changelog
All notable changes to TheStrat Suite. The format follows Keep a Changelog; the project predates this file, so earlier entries are reconstructed from the version snapshots in pine/, the inline // FIX <id> comments, and docs/v2.2.3_holiday_glue_fix.md.
How to read this file:
- One entry per released version, newest first. "Released" means a
.pinesnapshot is preserved inpine/. Three versions — 2.2.1, 2.2.3, and 2.2.5 — existed only as working states in the TradingView Pine editor and left no snapshot; each is folded into the entry of the release that shipped (or superseded) its changes, and marked as such. - Every fix cites its
FIXtag. Grep the current source for the tag to find the exact code and the full inline rationale. Code references are function names and tags, never line numbers. - Dates come from dated
FIXcomments and file names. Where a snapshot carries no date of its own (2.2.2), the newest dated comment introduced in it is used.
[3.0.0] — 2026-08-02
Published on TradingView as the open-source listing ("TheStrat Suite [Open Source] — Entries, Targets, and Stop Loss"), superseding the invite-only publication. Source: pine/TheStratSuite_v3.0.0.pine.
Added
- Stops: Smallest Timeframe Only — a display filter under Stops - Stop Levels that draws only the stop belonging to the smallest enabled timeframe that currently has one, hiding stops on larger timeframes. Display-only: stop prices, sticky in-force latches, break-even mechanics, and alert message content are all unchanged; the pass runs after the Lead Signal filter (so a lead-suppressed stop cannot claim the smallest slot) and before the render phase (so hidden lines are deleted, not skipped). "Smallest" follows slot order, matching the Lead Signal filter's convention. Off by default. (
STOP-SMALLEST-1) - Bar coloring — chart candles painted by their Strat classification (inside 1, directional 2u/2d, outside 3u/3d, Failing 2s), toggleable per family, or by Full Timeframe Continuity (up / down / conflict, with optional Failing-2 flip highlighting). The one price-action feature from the pre-Suite standalone scripts that hadn't been folded in. New settings group Display - Bar Coloring; off by default. Strat mode reuses the data table's CC classifier (
detectBarTypeAndFailed) on the chart bar, so candles and table can never disagree and the Failing 2 method/enable settings govern candles automatically; family colors follow the existing Style inputs. FTFC mode deliberately re-grades each historical bar by its own close against the served period opens (calculateFTFCre-run with chart close) instead of readingftfc_up/ftfc_down, whose lookahead closes would paint history with each period's final direction; on the live bar the two are identical, so candles always match the table's FTFC row. The flip highlight rebuilds each slot's intra-period range from chart bars (running H/L keyed on the served period-open time; seepaintSlotFailed2) so historical flips are graded honestly too. (BARCOLOR-1)
[2.2.7-split] — 2026-07-21
Current build: pine/TheStratSuite_v2.2.7-split.pine.
Added
- The source file now carries the standard MPL-2.0 license header, matching the repository
LICENSE.
Changed
- Failing 2 notation is canonical uppercase everywhere:
F2u/F2din the data table, labels, and alerts. The lowercase live-f(f2u/f2dmarking the forming candle) is retired — notation is now tense-free, with liveness carried by slot position (the last token is the forming candle), the*potential prefix, and words (FAILING/Failed). Display-string change only; no detection or signal logic touched. (NOTATION-1)
[2.2.6-split] — 2026-07-07
Ships the complete delta since 2.2.4 — exactly the two fixes below. 2.2.5-split was an editor-only intermediate step inside this pair; no snapshot was preserved, and nothing beyond these two fixes existed in it.
Fixed
- Daily reconstruction no longer paints the wrong forming candle around period rolls. The T3 reconstruction keys daily sub-bars by calendar period with a +12h normalization (session-stamped opens land in their true calendar day), but "now" was keyed raw — valid only under the old double-reading straddle test that
GLUE-2bremoved. For roughly the seven hours between a period's session close and calendar midnight, raw "now" keyed one period behind the daily-bar keys, so reconstruction matched the prior period's dailies at each roll. "Now" is normalized +12h too; see thepkeyCalcall site in the T3 recon block. (RECON-KEY-1) - "Color TF When In-Force" no longer highlights the timeframe column on every inside bar. An inside bar is by definition not in force, but the old test lit the TF column for it — the common case, so effectively "coloring everything." In force now means a live 2/F2 signal or an enabled outside-bar (3) expansion, which is in force by definition. Inside bars stay colored in the CC cell only. (
TFCOLOR-1)
[2.2.4-split] — 2026-07-06
The GLUE milestone: CME holiday-glued bars can no longer trip the preview or straddle machinery during a live session. Supersedes 2.2.3-split, released earlier the same day and never preserved as a file — the full incident narrative, including 2.2.3's edits, is in docs/v2.2.3_holiday_glue_fix.md.
The incident: CME glues holiday half-sessions into the next trade date (the Friday July 3 half-session lives inside Monday July 6's bar), so a live Monday bar can open with a Thursday-evening stamp. Two pieces of logic treated "old bar stamp" as "market closed / data stale," and both misfired mid-session — a phantom PREVIEW MODE banner, ? in the D/W table cells, and weekly trigger levels hugging the current day's range instead of last week's.
Fixed
- Auto preview no longer engages during a live session on a holiday-glued bar.
isHolidayClosedmeasures staleness from the bar's scheduled close (time_close), not its open stamp — a live bar's scheduled close is always in the future, so the fallback cannot fire while a session trades, while genuinely halted markets still trip it. Shipped in 2.2.3, carried forward here. (GLUE-1) - Phantom straddle shifts on glued bars are gone.
shouldStraddlenow asks a single question: is now past the served HTF bar's scheduled close (time_close(tf))? No week numbering, no timezone parsing, no +12h assumptions — and direction-aware, so a trade-date bar running ahead of the calendar cannot fire it. The July 1 mid-week quarter-roll case the straddle exists for still works. This replaces 2.2.3's calendar-key comparison (GLUE-2): that version fixed the daily symptom, but live testing the same day showed the glue reaches the weekly bar too (opening ~84h before its true period), defeating the +12h normalization's one-session-early assumption — soGLUE-2was superseded within hours and never shipped in a preserved file. (GLUE-2b)
[2.2.2-split] — 2026-07-02
The audit release. The 2.2.0 baseline went through an external correctness review (the pre-registered review brief is docs/DESIGN_CONSTRAINTS.md); the accepted findings landed here as the P0/P1/P2, BTC, CSS, TAW, MOD, and U2 fix families, alongside new HTF-straddle handling. 2.2.1-split was an editor-only intermediate on this path — no snapshot preserved; the settings reference (now docs/TheStratSuite_v2.2.7_Settings_Reference.md) was first written against it.
Added
- HTF-straddle handling: a chart bar can straddle a higher-timeframe boundary (a weekly chart bar spanning a mid-week month/quarter roll), leaving the served HTF data one period behind. T1 shifts the affected slot's period assignments live; T3 rebuilds the true forming candle from completed daily sub-bars via
request.security_lower_tf. Session-aware, probe-validated (the probe scripts are preserved indiagnostics/), hardened 2026-07-02 against period-end-afternoon phantoms. Straddled slots draw no open line and never alert — their forming candle is estimate-grade. (HTF-STRADDLE-1)
Fixed
Repaint and reload correctness (the case studies behind docs/engineering/repaint-prevention.md):
- New-period detection is per-slot explicit state instead of a
ta.changecall shared across all six timeframe slots — the shared buffer made "new period" fire on nearly every bar, constantly wiping magnitude-crossed flags and sticky stops. SeecomputeSignalStateandlastPeriodStart. (P0-1) - Exhaustion levels no longer move after a reload.
findExhaustionLevelsseeded its scan from the forming bar, which evaluates differently live versus on reload; it now seeds from completed bars only. (P0-2) - Sticky stops survive a reload. The lock latched only under
barstate.islast, so bars that were realtime when the signal fired became historical on reload and the latch never re-ran; with stops enabled, detection and latching now run on every bar so history rebuilds the lock. (P1-a) - Preview-mode state re-latches every tick while previewing and re-latches on preview exit, so preview-shifted values cannot leak into the real period's latched state. (
P1-d)
Alerts:
- F2 (Range Reclaim) alerts obey the "Failing 2s (Range Reclaims)" signal toggle — a signal type you disabled can no longer alert while the table shows nothing in force. (
P0-3) - Alert edge-state resets on a real period change (keyed off the served period-open,
realPeriodTime), so a signal already in force at a new period's open still produces an alertable edge. (P1-c) - Per-timeframe
alertconditions fired on every bar when a timeframe was enabled but excluded from the consolidated alert message — an earlycontinueskipped the was-state update. Was-state now updates unconditionally; only the consolidatedalert()message is gated by the checkbox. (U2) - Consolidated alert messages only append the
|separator when detail fields actually exist. (P1-j)
Signal and display correctness:
- The magnitude/exhaustion Take Action Window could not render for in-force signals — the default configuration. The old guards required both triggers to exist, but the opposite trigger is suppressed exactly when a signal goes in force. Each direction's box now checks only its own trigger. (
TAW-1) - Inside reversals with a flat (doji) C2 now mark in force, matching the draw path: the line drew and the alert fired, but the table, the sticky-stop latch, and the "only when in force" target/TAW gates never saw it. (
CSS-1) - Outside-bar exhaustion targets respect the global Show Exhaustion Levels toggle, mirroring how outside-bar magnitude already respected Show Magnitude Levels. (
CSS-2) - Hammer/shooter reversals and continuations obey their own families' FTFC toggles — both wrongly read the inside-reversal's. (
BTC-1) - A candle body centered exactly at the range midpoint classifies as neither hammer nor shooter instead of both, which drew a spurious wrong-side line. See
isBroadHammer/isBroadShooter. (BTC-2) - With Failing 2 Detection off, the table/label path (
detectBarTypeAndFailed) no longer shows F2 notation while the main engine (detectFailed2) emits plain2u/2d. (P1-f) - The Lead Signal filter now clears in-force state on the timeframes it suppresses, so the data table and Lead row stop highlighting a counter-trend timeframe whose lines, labels, and alerts were already hidden. (
P1-g) - When both directions are in force at once (rare), the Lead Signal direction resolver no longer reads
F2uas bullish. F2u is a bearish signal and F2d bullish, but the generic close-suffix test mapped theuto bullish — inverted, flipping the Lead anchor and all counter-trend suppression. F2 cases now resolve explicitly before the suffix test (getSignalDirection). (LEAD-F2-TIEBREAK-2) - Line suppression nulls the owning line field after deleting a suppressed line (each
LineInfocarries its owner slot and kind; seenullSuppressedLine), so the render phase recreates the line instead of mutating a deleted id — and a suppressed level reappears once the price collision clears. (P1-h) - Debug panel boolean cells render
falsedimmed instead of white-on-white (dead ternary indebugBoolText). (P2-3)
Changed
- Settings dialog rows grey out when not relevant: timeframe rows while a preset is active, custom color pickers outside Custom color mode, offsets under disabled label features, and the like. (
MOD-3) - Labels are pooled and updated in place (
LabelPoolT/acquireLabel) instead of deleted and recreated every tick; surplus pooled labels are trimmed after each render. (P1-i) - In-source documentation clarifies that the data table's FTFC cell tint is display-only, not signal gating. (
P1-e)
Removed
- Five transitively dead cleanup methods whose only callers were each other. (
P2-1) - Unreachable
"1u"/"1d"comparisons in CC-type checks —ccTypeonly ever emits1/2u/2d/3u/3d/F2 values. (P2-5)
[2.2.0] — 2026-06-16
The pre-audit baseline, preserved twice: pine/TheStratSuite_v180_June16_2026.pine (the original June 16 export) and pine/TheStratSuite_v2.2.0.pine (the same content retitled v2.2.0 — identical apart from the version string). Everything the Suite is dates from here or earlier: six configurable timeframes with presets, bar classification, the seven signal types with magnitude and exhaustion targets, Full Timeframe Continuity, hammer/shooter filters, Domino detection, sticky stop levels, preview mode, consolidated alerts, and the multi-timeframe data table. The v1.x line predates this repository and is not reconstructed here.
Contributor checklist
Before merging anything that changes shipped behavior:
- Is there an entry under
[Unreleased]describing the user-visible behavior first, with theFIXtag (or feature tag) in parentheses after it? - Does the matching
// FIX <id>comment exist in the source, dated, at the code it explains — and does the entry reference code by function name, never line number? - On release: new dated version heading here, new snapshot in
pine/, version string bumped in both the header comment and theindicator()title. - If an editor-only intermediate version existed and left no snapshot, does the shipping release's entry say so explicitly?
v2.2.3-split: holiday-glue fix (2026-07-06)
Root cause
Friday July 3 was the observed July 4 holiday. CME ran the half-session and glued it into Monday July 6's trade date, so Monday's live daily bar carries a Thursday-evening open stamp (that's also why the ES daily chart shows no separate Friday candle). Two pieces of logic treat "old bar stamp" as "market closed / data stale," and both misfired during Monday's LIVE session:
isHolidayClosedmeasurestimenow - time(the bar OPEN). The glued Monday bar reads ~87h old, so Auto preview engaged mid-session: PREVIEW MODE banner, D and W rows showing "?", and the classic preview shift demoting the FORMING period into C1. That is the ES symptom: weekly trigger levels equal to the current day's range instead of last week's.shouldStraddlecompares the CHART BAR's open period to now. The glued Monday bar opens in last week, now is this week, so it declared a phantom straddle for the W slot on the daily chart and shifted live weekly data the same way.
Neither check asks the question that actually matters: is the SERVED HTF data stale? On the live bar the security feed was serving the current week just fine. The July 1 quarter-roll case (the reason the straddle exists) is different: there the served bar really is the prior period, and that behavior is preserved below.
The CL 1h chart is the same event seen from the other side: with Friday glued into Monday's trade date, the daily series' prior bar is Thursday July 2 (the F2d hammer), so the D row reads C1 = F2d with the trigger at Thursday's high and the low line at 67.00. Verify against the CL DAILY chart after reloading: its prior candle should be that same Thursday hammer and its current candle should contain Friday's range. If the daily chart instead shows a separate Friday candle while the 1h D row still says F2d/Thursday, that is a different bug; flag it.
Edits (apply in the TradingView Pine editor to v2.2.2-split)
Edit 1: version bump
indicator("TheStrat Suite - v2.2.2-split", ...) -> "TheStrat Suite - v2.2.3-split"
(and the // TheStrat Suite - v2.2.2-split header comment line).
Edit 2: isHolidayClosed measures from the bar's scheduled CLOSE
Old:
// Holiday fallback: no new bar for 3x chart period or 4 hours, whichever is larger
isHolidayClosed = (timenow - time) > math.max(chartPeriodMs * 3, 4 * 60 * 60 * 1000)
New:
// Holiday fallback: now is 3x chart period or 4 hours (whichever is larger) past the bar's
// SCHEDULED CLOSE.
// FIX GLUE-1 (2026-07-06): measure from time_close, not time (the open). CME glues holiday
// half-sessions into the next trade date (Fri Jul 3 2026 lives inside Mon Jul 6's bar), so
// the LIVE Monday bar opened with a Thursday-evening stamp and (timenow - time) read ~87h:
// Auto preview engaged during a live session and demoted the forming week/day into C1
// ("weekly levels on the current day", D/W = "?", phantom PREVIEW MODE banner). A live bar's
// scheduled close is always in the future, so this can never fire while a session trades;
// genuinely halted markets still trip it once now exceeds the last scheduled close.
isHolidayClosed = not na(time_close) and (timenow - time_close) > math.max(chartPeriodMs * 3, 4 * 60 * 60 * 1000)
Edit 3: shouldStraddle keys off the SERVED HTF bar, not the chart bar
Replace the whole shouldStraddle function (keep the body logic; only the comment, the
signature, and the tB line change):
// FIX GLUE-2 (2026-07-06): staleness is a property of the SERVED HTF data, not of the chart
// bar's open stamp. shouldStraddle now takes the served current HTF period-open (pre-shift)
// and compares ITS calendar period to now. If the feed already serves the new period, no
// shift is needed no matter how old the chart bar's stamp is. Holiday-glued Monday bars
// (open stamped Thursday evening) made the old chart-bar comparison fire a phantom straddle
// during a live session, demoting the LIVE forming week into C1. The mid-bar calendar-roll
// case this fix was built for still works: there the served bar really is the prior period,
// so servedTime's period differs from now and the shift proceeds exactly as before.
// HARDENED 2026-07-02 (unchanged): "now" is evaluated under BOTH the raw and the +12h
// reading, and BOTH must differ from the served bar's period, killing period-end-afternoon
// phantoms.
shouldStraddle(string tf, int servedTime) =>
tB = servedTime + OFF_MS
tN = timenow + OFF_MS
tR = timenow
mB = month(tB, "America/New_York"), yB = year(tB, "America/New_York"), wB = weekofyear(tB, "America/New_York")
mN = month(tN, "America/New_York"), yN = year(tN, "America/New_York"), wN = weekofyear(tN, "America/New_York")
mR = month(tR, "America/New_York"), yR = year(tR, "America/New_York"), wR = weekofyear(tR, "America/New_York")
qB = math.ceil(mB / 3), qN = math.ceil(mN / 3), qR = math.ceil(mR / 3)
saB = mB <= 6 ? 1 : 2, saN = mN <= 6 ? 1 : 2, saR = mR <= 6 ? 1 : 2
result = false
if tf == "W" or tf == "1W"
result := (wN != wB or yN != yB) and (wR != wB or yR != yB)
else if tf == "12M"
result := yN != yB and yR != yB
else if tf == "6M"
result := (saN != saB or yN != yB) and (saR != saB or yR != yB)
else if tf == "3M"
result := (qN != qB or yN != yB) and (qR != qB or yR != yB)
else if tf == "M" or tf == "1M"
result := (mN != mB or yN != yB) and (mR != mB or yR != yB)
result
Edit 4: the two callsites pass the served time
In applyPreviewShift (the ccTime parameter is the served current HTF period-open):
doPreview = shouldApplyPreview(tf, previewEnabled) or (straddleEnabled and isStraddleTF(tf) and shouldStraddle(tf, ccTime))
In the T3 recon block (use realPeriodTime, NOT rawS.ccTime, which is rewritten by the shift):
if cfgS.enabled and not na(rawS) and rawS.isPreview and canRecon and not na(reconDT) and array.size(reconDT) > 0 and isStraddleTF(cfgS.tf) and shouldStraddle(cfgS.tf, rawS.realPeriodTime)
Expected after applying + reloading
- ES 1D: PREVIEW MODE banner gone during the live session. W and D table cells show real bar types instead of "?". Weekly trigger lines sit at LAST week's high/low, drawn from last week's open, no longer hugging the current day's range. D levels anchor to the last completed daily bar (the glued feed's prior bar, i.e. Thursday's).
- CL 1h: unchanged by the code edits (nothing was shifting there); its D row reflects the glued daily feed. Confirm it matches the CL daily chart's candles.
- The July 1 mid-week quarter/month straddle behavior on weekly charts is preserved: when the feed genuinely serves the prior M/3M bar mid-chart-bar, the shift plus daily reconstruction still runs.
SUPERSEDED: v2.2.4 (2026-07-06, later same day)
Live testing on CL 1D confirmed GLUE-1 worked (banner gone, D row correct) but the W slot still shifted. Root cause: the glue reaches the WEEKLY bar too. This week's weekly bar opens Thursday Jul 2 18:00 ET (Friday's half-session belongs to Monday's trade date), ~84h before its true period, so even the SERVED bar's open stamp parsed as last week. The +12h normalization assumes opens are at most a session early; the glue is 3.5 days early.
GLUE-2b replaces the calendar-key test entirely. A served HTF bar is stale exactly when now is past its scheduled close:
shouldStraddle(string tf) =>
int tc = time_close(tf)
not na(tc) and timenow > tc
Why this is robust:
- A live bar's scheduled close is always in the future, so it can never fire mid-session (glued or not). No week-numbering, no timezone parsing, no +12h assumptions.
- Direction-aware: a trade-date bar running AHEAD of the calendar (the glued Friday session living inside next week's bar) cannot fire it.
- The Jul 1 mid-week quarter roll still works: once the served Q2 bar's Jun 30 17:00 close passes, the shift arms for the rest of the straddling weekly chart bar.
- time_close(tf) comes from the same trade-date calendar that stamps the bars, so it tracks exactly the bar request.security serves.
Both callsites drop the servedTime argument. Shipped as TheStratSuite_v2.2.4-split.pine.