Trading Sessions and Time Zones: The Bug That Comes Back Twice a Year

Published on: August 10, 2026 | By: Mariusz Skobel

Twice a year, on the weekends when clocks change, a quiet class of bug enters quantitative pipelines everywhere. Nothing crashes, no error is logged, and every dashboard keeps rendering. An hour of the wrong data simply starts flowing into features that were supposed to describe regular trading hours.

Night trading floor with market data terminals beside server racks

Markets do not trade around the clock, and the hours they do trade are not interchangeable. Pre-market prints come from a thin book with wide spreads; auction crossings concentrate enormous volume into a single instant; overnight futures sessions run with a fraction of daytime participation. A feature computed across all of it describes a market that does not exist — an average of regimes rather than any one of them.

Filtering by session sounds like a formatting detail. It is where three separate traps live.

Trap one: daylight saving

Exchange sessions are defined in local time. Data is stored — correctly — in UTC. The offset between the two is not constant.

A 09:30 open on the New York Stock Exchange is 13:30 UTC in July and 14:30 UTC in January. Write your filter as a fixed UTC range and it is wrong for roughly four months a year: for part of the year you silently include an hour of pre-market, for another part you silently discard the first hour of regular trading — the hour that carries the most volume of the day.

Nothing warns you. The pipeline runs, the bars form, the backtest returns a number. The only correct approach is to express the window in exchange-local time and let a timezone database resolve it for each date, because the transition dates themselves change between years and jurisdictions — Europe and the United States do not switch on the same weekend.

Trap two: sessions that cross midnight

Futures venues commonly open at 18:00 and close at 17:00 the next afternoon. Expressed naively, that window is nonsense: the start time is later than the end time, so a filter testing whether a timestamp falls between them rejects everything.

Handled properly, an overnight window means two things at once — after the open, a print belongs to the session that started today; before the close, it belongs to the session that started yesterday. The second half of that rule is the one implementations forget, and forgetting it silently deletes every trade made between midnight and the close.

Trap three: which day does the night belong to?

Once the overnight session is captured, a second question follows immediately: what trading day is a 02:00 print part of?

Group by calendar date and a single trading session is split across two buckets. Daily volume, daily range and daily returns all become fiction — not wrong by a rounding error, but structurally wrong, with the evening half of every session attributed to the wrong day. The correct grouping key is the date on which the session opened, which means the trading day is a property of the session definition, not of the timestamp.

This is the same discipline we described at the infrastructure layer in What HFT Infrastructure Looks Like, where hardware timestamps and PTP clock synchronisation exist precisely because time is not a detail. Here it reappears one layer up: capturing time accurately is worthless if you then attribute it to the wrong day.

What breaks downstream

These failures do not announce themselves; they show up as results that are slightly too good or inexplicably unstable. Volatility estimates inflate because thin overnight prints add noise the strategy will never trade against. Volume profiles distort because an hour of pre-market activity is counted as regular hours for half the year. Daily aggregates disagree with the exchange's own published figures, and someone spends a week reconciling them. And backtests inherit an edge that exists only in the extended-hours book — the kind of phantom signal we catalogued in The Overfitting Trap.

How to do it correctly

The rules are short. Store timestamps as integer UTC. Define sessions in exchange-local time with an IANA timezone identifier, never a fixed offset. Treat a window whose end precedes its start as overnight. Attribute each session to its opening date. Apply the filter before aggregation, so bars are built only from the data you meant to include. And record how many events the filter removed, because a session filter that silently discards 80% of a file is telling you something about the file.

In our open-source tooling that looks like this:

from mdnorm import US_EQUITY_RTH, Pipeline, group_by_session_date

bars = (
    Pipeline()
    .dedupe()
    .clean()
    .session(US_EQUITY_RTH)      # 09:30-16:00 America/New_York, DST-aware
    .time_bars(60_000_000_000)   # 1-minute bars
    .run(events)
)

by_day = group_by_session_date(events, US_EQUITY_RTH)

Or, without writing any Python at all:

$ pip install market-data-normalizer
$ mdnorm bars trades.csv --interval 5m \
      --session 09:30-16:00 --tz America/New_York -o rth.csv

A window such as 18:00-17:00 is recognised as overnight automatically, and group_by_session_date keeps a whole night in one bucket. The full normalization pipeline this fits into — schema, deduplication, quality checks and bar sampling — is described in our market data normalization guide, and the underlying data problems in The Hidden Cost of Dirty Market Data.

The part that needs a calendar

Honesty about limits matters more than a tidy abstraction. Weekday rules describe the regular pattern, but no algorithm derives a national holiday or an early close from first principles. Those require an actual exchange calendar, maintained per venue and per year. The practical division of labour: use session windows to remove the bulk of out-of-hours data automatically, then apply a holiday calendar for the exceptions that matter to your study. Anyone claiming their library handles holidays without shipping a maintained calendar is claiming something impossible.

The tooling described here is public and free to inspect: GitHub and PyPI. For the hardware end of the same problem — where clock discipline is measured in nanoseconds rather than hours — see our note on FPGA technology in ultra-low latency trading, and on the true cost of latency. If you are building on these foundations, our partnership page is where to start a conversation.

Frequently asked questions

What are regular trading hours (RTH)?

Regular trading hours are the main continuous session of an exchange, expressed in the exchange's local time: 09:30 to 16:00 America/New_York for US equities, for example. Activity outside that window — pre-market, after-hours, auctions — has different liquidity, different spreads and different participants, which is why most research restricts itself to RTH rather than mixing regimes.

Why does daylight saving time break market data pipelines?

Because exchange sessions are defined in local time while data is stored in UTC, and the offset between them changes twice a year. A 09:30 New York open is 13:30 UTC during daylight saving and 14:30 UTC outside it. Any filter written as a fixed UTC range is therefore wrong for several months every year — and the failure is silent: you simply get an hour of the wrong data, with no error raised anywhere.

What is an overnight trading session?

An overnight session is a trading window that opens on one calendar day and closes on the next — futures venues commonly run from 18:00 to 17:00 the following afternoon, with a short daily maintenance break. Because the window crosses midnight, naive local-time filters that test whether a timestamp falls between a start and an end time reject the entire post-midnight portion of the session.

How should overnight sessions be assigned to a trading day?

By the day the session opened, not by the calendar date of each print. Trades made at 02:00 belong to the session that started the previous evening, so grouping by calendar date splits a single trading day across two buckets and distorts every daily aggregate — daily volume, daily range, daily returns. The correct grouping key is the session's opening date.

Should you filter out pre-market and after-hours data?

For most research, yes — but the decision should be explicit rather than accidental. Extended-hours activity is thin, spreads are wide and single prints move the recorded price disproportionately, so features computed across the full 24 hours describe a different market than the one a strategy will trade. Keep extended hours when you are specifically studying them; otherwise filter and say so.

How do you handle exchange holidays and half-days?

Weekday rules cover the regular pattern, but holidays and early closes require an actual exchange calendar — there is no algorithm that derives Thanksgiving or a half-day session from first principles. A practical approach is to filter by session window first, which removes the bulk of out-of-hours data, and then apply a venue holiday calendar on top for the exceptions that matter to your study.

Why store timestamps in UTC if sessions are defined in local time?

Because UTC is unambiguous and monotonic, while local time is neither: it repeats an hour in autumn and skips one in spring. Storing integer UTC timestamps and converting to exchange-local time only at the moment you evaluate a session boundary gives you both properties — a clean ordering for merging and arithmetic, and correct human-facing session logic.

Does HarvestGroup360's open-source library support session filtering?

Yes. market-data-normalizer includes a sessions module that handles intraday windows, overnight sessions crossing midnight and daylight-saving transitions through the standard IANA timezone database, with ready-made definitions for US regular equity hours and a CME-style overnight futures session. It is available with pip install market-data-normalizer (the import name is mdnorm), is MIT-licensed, has no runtime dependencies, and the source is public at github.com/Harvestgroup360/market-data-normalizer.

Empowering quantitative research with high-frequency market data and analytics.