Why your session levels are an hour off, twice a year
Session boxes, fix lines and gap levels are only as good as the timezone logic behind them. Daylight savings is where most session indicators quietly break. Here is why, and what correct looks like in Pine.
Here’s a fault almost every trader meets and almost nobody diagnoses on the first try. For most of the year your session levels sit exactly where they belong. The Asian range, the London open, a fix line, yesterday’s gap. Then one weekend they’re an hour out. A few months later they snap back on their own. You didn’t touch a setting. Nothing changed on the chart. The levels are simply wrong now, and they stay wrong until the next clock change quietly fixes them.
That’s daylight savings, and it’s the single most common reason a session indicator can’t be trusted.
The root cause is a timezone hidden inside a number
Most session tools get built the lazy way. Pick a time, “Asian session starts at midnight,” and hardcode it against the chart or against one fixed offset like UTC. That works flawlessly right up until one of the regions involved changes its clocks and the other doesn’t.
And they never change together. The US, the UK, Europe and Australia all shift on different weekends, and large parts of Asia don’t shift at all. So a level defined as “New York 11am” and a level defined as “Hong Kong open” slide relative to each other several times a year, depending on which side of which clock change you’re standing on. An indicator that baked in an offset instead of a place has no way to know any of this happened.
The signature is unmistakable once you’ve seen it. A level that’s exactly one hour late for a few months, then correct, then late again. Looks random. Isn’t.
Correct means naming the place, not the offset
Pine actually makes the right way easy, and the wrong way just as easy, which is why so
many scripts get it wrong. The time() and timestamp() functions both accept a
timezone, and you’re meant to give them a named region, not a number:
//@version=6
indicator("London session", overlay = true)
// Anchor the session to London's actual clock, not the chart's.
inLondon = not na(time(timeframe.period, "0800-1600", "Europe/London"))
bgcolor(inLondon ? color.new(color.blue, 90) : na)
Because the session is tied to "Europe/London", that region’s own daylight-savings
rules do the adjusting for you. The shading lands on the right candles in summer and in
winter, whatever your chart is set to and whatever your broker’s server time happens to
be. Swap in "Asia/Tokyo" or "America/New_York" and each one follows its own clock,
independently, exactly as the real sessions do.
The trap is the tempting shortcut of writing a fixed offset like "GMT+1". That pins
you to summer time forever, and come winter you’re the one who’s an hour off. Name the
city. Let the city’s rules run.
Two cousins of the same bug
Chart time versus exchange time. Your chart’s timezone is a display preference, nothing more. The exchange’s session is a fact about the world. A good indicator keeps them apart: it shows levels in your view but computes them in the exchange’s time. Mix the two and you get levels that move when you change your display, which should never happen.
Broker rollover. Separate problem, same bruise. Some brokers nudge the daily candle boundary by a minute or two to dodge junk prints at the reset, so an “hourly” candle opens at xx:02 rather than xx:00. That isn’t a timezone bug, but it lands a level slightly off for reasons that have nothing to do with the market, and it’ll drive you mad if you assume it’s your code.
None of this is glamorous work. It’s the kind of correctness you only notice in its absence, when you hesitate at a level because some part of you no longer trusts where it printed. And a level you don’t trust is worse than no level, because it slows you down at exactly the wrong moment. Getting the clocks right is most of the job. It’s the first thing to nail down and the last thing you should ever hardcode.
The useful version
Session bugs are often timezone bugs disguised as Pine bugs.
The common failure is hard-coding an offset like UTC-5 or UTC+1 and forgetting daylight saving time. New York, London, and the exchange calendar do not all shift on the same dates. Twice a year, the script looks fine in one market and wrong in another.
The code-level fix
Prefer named timezones for session logic:
inNySession = not na(time(timeframe.period, "0930-1600", "America/New_York"))
That lets TradingView handle the daylight-saving rules for the named zone. Also decide whose session matters: broker server time, chart exchange time, or the market centre you trade. “London open” is not a universal timestamp. It is a rule that needs a timezone.