DEV Community

EmilyL
EmilyL

Posted on

Stock API Minute‑to‑Daily Bar Aggregation: How We Solved Timezone Offset Issues in Our Trading Data Pipeline

Hey devs! Today I want to share a data engineering war story that will resonate with anyone who has ever built a financial data pipeline. Our team supports a group of cross‑border quantitative traders who analyze US equities. To keep infrastructure costs lean, we fetch minute‑level OHLC data from a stock API and aggregate it into daily bars ourselves. Everything was fine until one Monday morning when a strategist asked, “Why does the daily open for AAPL on July 1 not match the exchange?” That question kicked off a deep dive into exchange timezones, daylight saving rules, and trading calendars.

Scenario: Building a Cost‑Efficient Daily Bar Factory

We work with independent investors and small trading desks who operate across multiple markets. Paying for premium daily bar feeds for every region would eat up most of their research budget. So we architected an internal data service that consumes an affordable stock API for minute data and produces daily bars on‑the‑fly. The design is simple: ingest minute bars, group by date, compute OHLC. But that simplicity hides a critical assumption—that the date attached to each minute bar already belongs to the correct trading session.

The Core Pain: UTC Grouping Splits Trading Sessions

The stock API we use returns timestamps in UTC. For US stocks, trading happens in Eastern Time. In daylight saving, the market opens at 09:30 ET (13:30 UTC) and closes at 16:00 ET (20:00 UTC). If we group by the UTC date, the bars between 19:00 and 20:00 UTC belong to the next UTC day. The result: a single trading day gets split across two daily bars. Here’s the simple illustration that made everything click for us:

Time Type Corresponding Time
Eastern Trading Time 2026-07-01 09:30
UTC Time 2026-07-01 13:30

Once you see this, it’s obvious. But in a large pipeline that processes hundreds of symbols, the error manifests as sporadic opening‑price jumps that are maddeningly hard to trace.

Solution: A Time‑Normalization Layer Before Any Aggregation

We refactored the pipeline to treat time conversion as a mandatory preprocessing step. No piece of code that computes OHLC ever sees a raw UTC timestamp. The flow now looks like:

  • Parse the original timestamp from the API response.
  • Load the target exchange timezone from a configuration map (for US stocks, America/New_York).
  • Use Python’s zoneinfo to convert to local time, fully accounting for DST.
  • Assign a trading date based on the converted timestamp and session rules.

The core conversion logic is self‑contained:

from datetime import datetime
from zoneinfo import ZoneInfo

utc_time = datetime.strptime(
    "2026-07-01 13:30:00",
    "%Y-%m-%d %H:%M:%S"
)

utc_time = utc_time.replace(
    tzinfo=ZoneInfo("UTC")
)

market_time = utc_time.astimezone(
    ZoneInfo("America/New_York")
)

print(market_time)
Enter fullscreen mode Exit fullscreen mode

This snippet runs on every batch load, guaranteeing that all minute bars are aligned to the exchange clock before aggregation.

Extending the Solution: Trading Sessions and Calendars

Timezone conversion alone isn’t enough. Many APIs include pre‑market and after‑hours trades. If those get mixed into the daily bar, your technical indicators will quietly degrade. We therefore added a session filter:

  • Only minute bars between 09:30 and 16:00 Eastern are used for the standard daily bar.
  • Extended‑hours data is routed to a separate analytics store.
  • A trading calendar service dynamically adjusts for half‑days and holidays, so the system never assumes a fixed bar count.

Real‑Time Streaming Consistency

For live trading dashboards, we ingest real‑time ticks through a WebSocket. To keep real‑time bars identical to historical ones, the tick processor reuses the same time‑normalization module. We use a low‑latency, budget‑friendly feed from AllTick for our US equity streams, and the integration was seamless because we had already solved the time problem generically.

Here is our real‑time tick handler. Note how time conversion is the very first operation:

import websocket
import json
from datetime import datetime
from zoneinfo import ZoneInfo

def on_message(ws, message):
    data = json.loads(message)

    trade_time = data["tradeTime"]

    dt = datetime.strptime(
        trade_time,
        "%Y-%m-%d %H:%M:%S"
    )

    market_time = dt.replace(
        tzinfo=ZoneInfo("America/New_York")
    )

    print(
        data["symbol"],
        data["price"],
        market_time
    )

ws = websocket.WebSocketApp(
    "wss://api.alltick.co/stock/websocket",
    on_message=on_message
)

ws.run_forever()
Enter fullscreen mode Exit fullscreen mode

Lessons Learned and Practical Tips

Throughout this project, we’ve built a small checklist that might save you some pain:

  • Normalize early. If you consume data from multiple stock APIs, agree on a single timezone standard before any merge.
  • Preserve raw timestamps. Store them as a debug column. When an analyst questions a daily bar, the raw value is your audit trail.
  • Never hard‑code bar counts. Markets have early closes. Use a calendar to determine expected bar counts dynamically.
  • Encapsulate time logic. A shared time‑conversion module used by batch and real‑time paths eliminates whole classes of bugs.

Conclusion

What started as a minor discrepancy in a daily bar turned into a comprehensive refinement of our entire data architecture. The fix wasn’t in the aggregation math; it was in the invisible timestamp semantics that preceded it. For any developer building financial tools on top of a stock API, my honest advice is to obsess over timezone correctness early. Once your temporal foundation is solid, the candlesticks you generate will finally reflect the true market narrative, and your traders can focus on strategy instead of data forensics.

Top comments (0)