◆ New — Strategy Validator, Code Fixer & Prop Compliance Checker are live. Try one free →
Home / Learn / Sessions and Timezones in Pine Script: time(), Session Strings, and DST Traps
Pine Script

Sessions and Timezones in Pine Script: time(), Session Strings, and DST Traps

How to restrict a Pine Script v6 strategy to specific trading sessions using time() with session strings, and how to avoid the timezone bugs that quietly corrupt session-based backtests — especially the daylight-saving trap that makes a 'London open' strategy test on the wrong hour for weeks every year. Covers exchange time vs chart display time vs UTC, IANA timezone names, overnight sessions, and day-of-week filters. Educational only, not financial advice: a session filter shapes when a strategy trades, it does not make it profitable, and trading carries a real risk of loss.

How do I restrict a Pine Script strategy to specific trading sessions or hours?

Use the time() function with a session string: time(timeframe.period, "0800-1630", "Europe/London") returns the bar's time when the bar falls inside that session and na when it does not. Wrapping it in not na(...) gives you a boolean session filter you can gate entries with. That third argument — the timezone — is the part most scripts omit, and omitting it is where session bugs come from.

//@version=6
// Educational only — validate before trading; not financial advice.
indicator("Session filter", overlay = true)

sess = input.session("0800-1630", "Session")
tz   = input.string("Europe/London", "Session timezone")

inSession = not na(time(timeframe.period, sess, tz))

bgcolor(inSession ? color.new(color.blue, 90) : na)

The bgcolor() highlight is not decoration — it is the verification step. Before trusting any session-gated backtest, load the script, look at where the shading actually falls, and check it against the hours you intended, in the timezone you intended. Session bugs are invisible in the Strategy Tester's summary numbers; they are obvious on a shaded chart.

The session string format is "HHMM-HHMM" in 24-hour time. input.session() gives users a proper session picker in the settings dialog rather than a free-text field. Note that time() evaluates per bar: a bar belongs to the session if its opening time falls within it, so on higher timeframes a session filter becomes coarse — a 4-hour bar either is or is not "in" a 90-minute window, which is one reason session strategies are usually built on intraday charts and why the same script can behave very differently across timeframes.

Which timezone does my session string use? Exchange vs chart vs UTC

There are three timezones in play whenever you look at a TradingView chart, and conflating them is the root of nearly every session bug.

Exchange time is the timezone of the venue the symbol trades on, exposed to your script as syminfo.timezone. When you call time() with a session string and no timezone argument, the session is interpreted in exchange time. The built-in hour and minute variables are also reported in exchange time. For a NASDAQ stock that means New York time; for many forex and CFD feeds the "exchange" timezone is whatever the data vendor uses, which you should check per symbol rather than assume.

Chart display time is whatever timezone you picked in the chart's settings. It affects only what the time axis shows you. It has zero effect on script execution — changing it will never change a backtest. Plenty of authors set their chart to London time, see their shading in the right place, and conclude the script is correct, when the script is actually computing in exchange time and merely happens to agree for part of the year.

UTC (and named zones generally) is what you opt into explicitly by passing a timezone argument: time(timeframe.period, sess, "UTC") or "Europe/London" or "America/New_York".

The rule that follows: a session definition is only meaningful together with a timezone, so state it explicitly. "0800-1630" alone is not "8am London" — it is "8am wherever this symbol's exchange lives," which changes when you drop the same script onto a different symbol. Writing the timezone into the time() call makes the script mean the same thing on every chart, every symbol, and every user's display settings.

The DST trap: why 'London open' tests on the wrong hour for part of the year

Here is the specific bug the angle of this article exists for. Suppose you want a strategy active during London hours, your chart symbol is on a feed whose exchange timezone is America/New_York, and you write the session in exchange time:

// ❌ WRONG — session interpreted in the SYMBOL's exchange timezone.
// If that is America/New_York, this window is 03:00–11:30 New York
// time — and its mapping to London shifts when the US and UK change
// daylight saving on different dates.
inLondon = not na(time(timeframe.period, "0300-1130"))

For much of the year New York is 5 hours behind London, so 03:00 New York is 08:00 London and the numbers look right. But the United States changes daylight saving in mid-March and early November, while the United Kingdom changes in late March and late October. During the weeks in between, the offset is 4 hours, not 5 — and your "London open" filter is testing an hour that is not the London open. A multi-year backtest silently mixes correct weeks and shifted weeks, and nothing in the tester output tells you.

Fixed-offset zone strings like "GMT+1" have the same disease from the other direction: they never shift, while London does. The fix is an IANA zone name, which carries the full DST rule history:

//@version=6
// Educational only — validate before trading; not financial advice.
indicator("Session timezone fix", overlay = true)
// ✅ Pinned to a zone that tracks London's actual DST changes.
inLondon = not na(time(timeframe.period, "0800-1630", "Europe/London"))
bgcolor(inLondon ? color.new(color.green, 88) : na)

Now "08:00" means 08:00 in London on every bar of history, on any symbol. The verification habit from earlier applies with force here: scroll a shaded chart to late March or early November of any year and confirm the shading does not jump.

Session string syntax: overnight windows and day-of-week filters

Session strings support more than a simple daytime window, and two extensions matter in practice.

Overnight sessions. A window may cross midnight: "2200-0600" starts at 22:00 and runs into the next calendar day. time() handles the wrap for you — bars after 22:00 and bars before 06:00 both count as in-session. This is how you express futures-style evening sessions or an "Asia session" on a chart pinned to a European timezone without writing two separate windows and OR-ing them.

Day-of-week filters. Appending a colon and digits restricts the session to specific days: "0930-1600:23456" means 09:30–16:00, Monday through Friday. The digits use TradingView's convention of 1 = Sunday through 7 = Saturday — not ISO numbering — so 23456 is Mon–Fri and "1700-1500:23456"-style strings need care about which side of midnight a day digit applies to (the digit refers to the day the session ends for overnight windows). If you skip the suffix, the session applies on all days the symbol trades.

A few smaller mechanics worth knowing. Multiple windows in one day are comma-separated: "0930-1130,1330-1600". The related built-ins session.ismarket, session.ispremarket, and session.ispostmarket classify the current bar against the symbol's own trading session — useful for stocks with extended hours, but they answer a different question than a custom time() window and are not interchangeable with it. And if you need the session's boundaries as events rather than a state, derive them from the boolean itself: sessionStart = inSession and not inSession[1], sessionEnd = inSession[1] and not inSession. Those two edges are exactly what you need for opening-range logic and end-of-session flattening, which the final section wires into a strategy.

A session-gated strategy, with end-of-session flatten

Here is the full pattern assembled: entries only inside an explicitly-timezoned session, on confirmed bars; a declared stop input actually wired into strategy.exit(); and a forced flatten when the session closes so no position rides overnight into a window the strategy was never tested to hold through.

//@version=6
// Educational only — validate before trading; not financial advice.
strategy("Session-gated example", overlay = true)

sess      = input.session("0800-1630", "Trading session")
tz        = input.string("Europe/London", "Session timezone")
stopTicks = input.int(200, "Stop distance (ticks)", minval = 1)

inSession  = not na(time(timeframe.period, sess, tz))
sessionEnd = inSession[1] and not inSession

longSig = ta.crossover(close, ta.sma(close, 20))
if longSig and inSession and barstate.isconfirmed and strategy.position_size == 0
    strategy.entry("Long", strategy.long)

if strategy.position_size > 0
    strategy.exit("Exit", "Long",
         stop = strategy.position_avg_price - stopTicks * syminfo.mintick)

// Flatten on the first bar outside the session — no overnight exposure.
if sessionEnd
    strategy.close_all(comment = "Session end")

Reading the guards: inSession restricts when entries may fire; barstate.isconfirmed ensures the signal comes from a closed bar rather than a still-forming one; the stop input is converted from ticks to a price with syminfo.mintick and genuinely attached to the position, so the settings dialog and the engine agree. sessionEnd fires once, on the first bar after the window closes.

The honest caveats. A session filter is a hypothesis about when an edge exists, and it adds a degree of freedom: scan enough windows and one will look good on past data by chance alone — treat a session choice you optimized like any other overfit-prone parameter. Session boundaries in the tester are also idealized; live fills around opens carry spread and slippage the backtest does not see. None of this is financial advice, and a session-gated backtest remains a description of the past, not a forecast.

Key takeaways

Educational only — not financial advice. Trading involves substantial risk of loss.

Catch the bug that compiles.Run auditGet Pro