Market holidayJapan will be closed on 2026-11-03 (Culture Day), 5 days from now.All announcements
Article

Data Architecture for Multi-Market Portfolio Dashboards: Sessions, Cadence, and Freshness

The hard part of a multi-market dashboard is the data layer, not the charts. We break down three problems single-market dashboards never face — non-overlapping sessions, non-uniform refresh cadence, and mixed freshness — plus a state-driven subscription architecture in Python.

Building a dashboard that shows US equities, China A-shares, Hong Kong stocks, FX, and crypto side by side is not hard because of the front-end charts. It is hard because of how the data layer pulls different markets onto one screen. Multi-market dashboards hit three problems single-market ones never do: mismatched sessions, mismatched refresh cadence, and mismatched data freshness.

This article covers how to solve all three architecturally.

Problem one: sessions do not overlap

The first thing users notice is that half the symbols show "no data" when they open the page.

The reason is straightforward — US equities, A-shares, Hong Kong, FX, and crypto barely overlap in trading hours. Open the dashboard at 10am Beijing time and A-shares are trading, US equities closed ten hours ago, and crypto has been running around the clock.

If the dashboard renders "no data" as blank or zero, users conclude the system is broken. The right approach is to model market state as a first-class piece of data:

StateMeaningFront-end treatment
OPENTrading nowLive price, normal refresh
CLOSEDSession overLast close plus a closed indicator
PRE / POSTPre- or post-marketShow price, label the session
HALTEDTrading haltedHalt indicator; suppress "last updated"
UNAVAILABLEData unavailableExplicit error state, never blank

The crucial distinction: a closed market is not a failure. Render it as blank and users will refresh repeatedly or file a bug. Render it as "last close plus a closed badge" and they understand immediately.

Problem two: refresh cadence should not be uniform

A single refresh policy does not work, because the sensible frequency varies enormously by market:

  • Crypto — trades 24/7, so second-level refresh is meaningful.
  • Equities — refresh only during the session; stop requesting when closed.
  • FX — nearly 24/7, but closed at weekends.

If the dashboard polls every symbol every second, two things happen: requests for closed markets are pure waste, and quota drains meaninglessly. With 200 symbols, round-the-clock second-level polling is hundreds of millions of requests a month, most of them during closed sessions.

The right architecture is subscription driven by market state:

  1. Use exchange calendars to determine whether each market is currently in session.
  2. In session: subscribe over WebSocket for real-time updates.
  3. Out of session: unsubscribe and retain only the last close snapshot.
  4. A few minutes before the open: restore subscriptions early to avoid a gap at the bell.

Quota consumption then scales with actual trading hours rather than with the wall clock.

Problem three: freshness varies across the screen

This is the most easily overlooked issue and the one most damaging to user trust.

Symbols shown side by side may come from sources with different freshness: crypto live, US equities delayed, some market a cached snapshot. If the front end does not distinguish them, users assume the whole screen is real-time.

Market data responses carry delayed and unavailable flags, and the dashboard should pass them all the way through to the interface:

  • Show the data timestamp on each symbol card, not just "updated just now".
  • Mark delayed data visually — a "delayed" badge, for instance.
  • If freshness varies widely within one view, surface an overall notice.

The value here is preventing users from acting on a wrong assumption about time. On a mixed-market dashboard, that matters more than the precision of the prices themselves.

Architectural layers

Translating the principles into structure gives four layers:

LayerResponsibilityKey design
IngestionSubscribe / unsubscribe by market stateWebSocket driven by exchange calendars
StateHold the latest snapshot per symbolIn-memory or Redis, with freshness metadata
AggregationOrganise data by dashboard viewBy market, by group, by watchlist
DeliveryPush deltas to the front endSend only changes; avoid full redraws

Delta-only delivery is the key optimisation. Multi-market dashboards often carry dozens to hundreds of symbols, yet only a couple change each second — a full push makes the front end redraw everything for nothing.

Python: subscription management driven by market state

Python
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo

# Session windows in exchange-local time (simplified)
SESSIONS = {
    "us": ("America/New_York", "09:30", "16:00"),
    "hk": ("Asia/Hong_Kong", "09:30", "16:00"),
    "cn": ("Asia/Shanghai", "09:30", "15:00"),
    "crypto": (None, "00:00", "23:59"),          # 24/7
}

WARMUP_MINUTES = 5      # subscribe early before the open


def market_state(market: str, now_utc: datetime | None = None) -> str:
    """Return OPEN / CLOSED to decide whether to subscribe."""
    now_utc = now_utc or datetime.now(timezone.utc)
    tz_name, start, end = SESSIONS[market]

    if tz_name is None:                          # 24/7 market
        return "OPEN"

    local = now_utc.astimezone(ZoneInfo(tz_name))
    if local.weekday() >= 5:                     # weekend
        return "CLOSED"

    hhmm = local.strftime("%H:%M")
    warm = (datetime.strptime(start, "%H:%M")
            - timedelta(minutes=WARMUP_MINUTES)).strftime("%H:%M")
    return "OPEN" if warm <= hhmm <= end else "CLOSED"


def desired_symbols(portfolio: dict[str, list[str]], now_utc=None) -> list[str]:
    """Derive the symbols that should be subscribed right now."""
    wanted = []
    for market, symbols in portfolio.items():
        if market_state(market, now_utc) == "OPEN":
            wanted.extend(symbols)
    return wanted


PORTFOLIO = {
    "crypto": ["BTCUSDT", "ETHUSDT"],
    "us": ["AAPL", "MSFT"],
    "hk": ["00700"],
    "cn": ["600519"],
}

# The subscription set changes with market state, not every second
print("should subscribe:", desired_symbols(PORTFOLIO))
for m in PORTFOLIO:
    print(f"  {m:8} {market_state(m)}")

The core is desired_symbols(): it turns "which symbols to subscribe" into a function of market state. The front end can call it periodically and diff the result, adjusting WebSocket subscriptions only when the set changes. Subscription churn drops to a minimum and quota consumption aligns with actual trading hours.

Three dashboard details that matter

  1. Show data timestamps, not "just now". Relative time misleads in a multi-market context — "2 minutes ago" on a closed market may really be ten hours.
  2. Retain the last close for closed markets. Blank cells read as load failures; last close plus a badge is the correct rendering.
  3. Separate "halted" from "no data". The first is a market event, the second a system problem — different UI treatment and different alerting.

FAQ

Does the dashboard need one WebSocket per market?

Not necessarily. Streaming subscriptions support per-market channels, so a modest number of connections can cover several markets. What matters is adjusting the subscription set by market state, not the connection count.

Should polling continue when markets are closed?

No. Requests during a closed session return no new data and simply burn quota. Unsubscribe when closed, retain the last snapshot, and resume a few minutes before the open.

How should differing freshness be handled?

Pass the API's delayed and unavailable flags all the way to the front end and show data timestamps on each card. Never let users consume delayed data believing it is live.

What refresh rate should the dashboard use?

Set it per market: seconds for crypto, session-only for equities, near-continuous for FX but closed at weekends. Uniform high-frequency refresh is almost always wasteful in a multi-market setup.

Should the front end do full refreshes or incremental updates?

Incremental. A multi-market dashboard typically holds dozens to hundreds of symbols while only a few change each second. Full pushes cause needless redraws and bandwidth use.

Next steps

The heart of a multi-market dashboard is making the subscription set follow market state rather than the clock. The subscription protocol is in the WebSocket streaming docs; broader design is in the multi-market integration guide. Session times for specific markets are on pages such as US stocks and Hong Kong stocks, and calendars can be checked with the trading calendar tool. Subscription scale depends on plan entitlements, so confirm the symbol cap before you plan.