TradingView Strategy Filters: Session, Time, ATR, Liquidity (No MTF)

The four Basic filters — marketSession, timeRange, ATR, liquidity — AND/OR combination, and why MTF is out of scope.

📖 5 min read

📝 924 words

🏷️ Pine Script and TradingView

Share this article:

Session string syntax, and its two traps

A time-based session string is <time_period>:<days> [1].

The time period is HHmm-HHmm on a 24-hour clock, and several periods can be comma-separated. The days part is digits, and omitting it means every day.

StringMeans [1]
"0930-1700"09:30 to 17:00, every day
"0800-0900,1230-1630"two separate windows in one day
"0930-1700:146"Sundays, Wednesdays and Fridays
"1700-1630:23456"an overnight session, Monday to Friday
"24x7"the same as "0000-0000:1234567"

Trap one: 1 is Sunday

Days are numbered 1 to 7 for Sunday through Saturday [1]. Not Monday through Sunday.

So the instinctive way to write "weekdays only" — :12345 — actually selects Sunday to Thursday. It includes a day most markets are shut and excludes Friday, one of the most active. Monday to Friday is :23456.

Nothing errors. The filter works, on the wrong days, and the backtest measures a strategy you did not design.

Trap two: the timezone is the exchange's, not your chart's

Session times are interpreted in the exchange's timezone unless you pass a timezone argument, and that is a different thing from the timezone your chart is displaying [1].

If you set your chart to local time, read 08:00 off the axis and write "0800-1600", you have filtered 08:00–16:00 exchange time. On a US equity symbol viewed from Europe that is a window in your afternoon and evening.

This one is worth testing rather than reasoning about: plot the filter and check the bars it actually keeps.

Only three functions take these strings

time(), time_close() and input.session() are the only functions that accept a time-based session string [1]. request.*() does not — it needs a named session instead, via ticker.new() or ticker.modify() with session.regular or session.extended.

pine
// na outside the session, a timestamp inside it
inSession = not na(time(timeframe.period, "0930-1600:23456"))

Align the session to your bars

TradingView states the requirement directly: align the start and end times of a time-based session with the start and end times of chart bars at the timeframe you expect [1]. A session boundary that falls mid-bar produces results that look arbitrary, because they are.


Session state, ATR and liquidity gates

Beyond the string syntax, Pine exposes the session as state you can read directly [1]:

VariableTrue when
session.ismarketinside regular hours
session.ispremarketinside pre-market extended hours
session.ispostmarketinside post-market extended hours
session.isfirstbarfirst bar of the day's session
session.isfirstbar_regularfirst bar of the regular session
session.islastbarlast bar of the day's session
session.islastbar_regularlast bar of the regular session

The last-bar variables can silently never fire

This is documented and it matters: session.islastbar and session.islastbar_regular might not be true for any bar in a session if no price or volume updates occur during the time period of that last bar [1].

So a strategy that closes its position on the last bar of the session can, on a quiet day in a thin instrument, simply not close. Nothing errors, the position carries overnight, and the log gives no reason.

If flattening at the end of the session is a requirement rather than a preference, gate on the session window itself — not na(time(...)) going false — rather than trusting a last-bar flag to arrive.

Extended hours are not what you assume on futures

For most US futures, Electronic Trading Hours is the default "regular" session, and session.extended is equivalent to session.regular [1]. A filter written to exclude "extended hours" on a futures symbol may therefore exclude nothing at all.

Volatility and liquidity gates

A session filter answers when. A volatility filter answers whether the market is moving enough for the strategy's assumptions to hold — a mean-reversion rule and a breakout rule want opposite answers from it.

The honest way to build one is relative, not absolute. ATR is denominated in the instrument's price, so a fixed threshold is meaningless across instruments and across time on the same instrument. Compare ATR to its own recent average instead:

pine
atrValue = ta.atr(14)
atrBaseline = ta.sma(atrValue, 100)
expanding = atrValue > atrBaseline

That comparison transfers. A published number like "ATR above 1.5" does not, and any article giving you one has not thought about the instrument you are trading.

The same logic applies to volume gates: compare volume to its own average rather than to a constant, because a constant encodes the liquidity of whatever instrument the author happened to be looking at.

Combining gates costs you sample size

Every filter added removes trades. That improves the statistics on paper and reduces the evidence underneath them at the same time — a strategy with 400 trades and a modest edge is stronger evidence than one with 25 trades and a spectacular one. Watch the trade count as you add gates, not only the return.



Tactix AI on this workflow

AlfaTactix includes Tactix AI: use Tactix Studio to describe your idea in plain language and draft timeframes, signals, filters, and risk into the same six-step Strategy Builder form — or open Tactix Guide on any step when you only need a field explained. Review every value, then export MQL5 or Pine Script from Code Generator (form-first — not untested prompt-to-code).

Frequently Asked Questions

Four: marketSession, timeRange, ATR, liquidityFilters (volume path). See market filters academy. No MTF filter stack.

No on Basic. This page replaces MTF chapters with session/ATR/liquidity. MT5 twin (different): Advanced EA MTF filters.

Those equity-style flags are UNSUPPORTED. Use FX/CFD session windows. Market session.

UNSUPPORTED. Liquidity = volume conditions. Liquidity.

Step 4 ATR filter saves indicator slots — ATR+MA playbook · ATR filter page.

AND/OR from Step 4 JSON — no NOT trees. Export joins filterOk with entries.