Intraday Seasonality Patterns in Systematic Trading

Intraday seasonality is the recurring pattern of volume, volatility, spreads, and sometimes returns across the trading day. Equities show the familiar U-shape in volume and volatility — busy open, quiet midday, busy close. Ignoring it mis-sizes risk, mis-schedules TWAP/VWAP, and creates false alpha from time-of-day artifacts. This article shows how to estimate profiles and use them without overfitting.

What is seasonal vs what is noise

Stable patterns (many markets, many years):

  • Volume/vol U-shape in equity RTH
  • Spread wider at open, tighter midday
  • Auction concentration at open/close

(auction mechanics)

Unstable / often spurious:

  • "Monday 10:35 long edge" from data mining
  • Session effects that flip after a microstructure regime change
  • Crypto patterns that shift with US/EU/Asia participation mix

Use deflated Sharpe thinking whenever you test many minute-of-day buckets.

Estimating a diurnal profile

import pandas as pd
import numpy as np

def diurnal_profile(df: pd.DataFrame, value_col: str,
                    minute_col: str = "minute_of_day") -> pd.Series:
    """Median value by minute-of-day (robust to outliers)."""
    return df.groupby(minute_col)[value_col].median()

def normalize_by_profile(values: pd.Series, profile: pd.Series,
                         minutes: pd.Series) -> pd.Series:
    """De-seasonalize a series using the diurnal profile."""
    expected = minutes.map(profile)
    return values / expected.replace(0, np.nan)

Apply to volume, absolute returns, and spread separately. De-seasonalized residuals are what belong in feature engineering.

Uses in execution

VWAP algorithms need a volume curve forecast:

V̂(t) = f_diurnal(minute) × day_scale × event_adjust

Without a curve, "VWAP" is a random schedule. Adjust for:

  • Half days / early closes
  • FOMC / CPI days (different shape)
  • Month-end / OPEX (pin risk,

seasonality)

Participation rates should be vs expected volume in that bucket, not vs full-day ADV alone (capacity).

Uses in risk and signals

  1. Vol scaling — size positions by time-of-day expected σ, not only daily σ
  2. Signal filters — disable fragile mean-reversion into the open auction mess
  3. Overnight vs intraday — link to overnight premium
  4. Crypto sessions — build profiles by UTC hour; re-estimate quarterly
def time_of_day_vol_scale(sigma_day: float, profile_vol: pd.Series,
                          minute: int) -> float:
    """Allocate daily vol budget across minutes using profile weights."""
    w = profile_vol / profile_vol.sum()
    return sigma_day * np.sqrt(w.loc[minute] * len(w))

Return seasonality: be skeptical

Academic and practitioner papers find small average return differences by hour. Most die after costs. If you trade them:

  • Require OOS stability across years and regimes
  • Include spread + impact at that hour (open is expensive)
  • Forbid large multiple-testing grids over minutes × weekdays × months

Prefer using intraday patterns for risk and execution, not as primary alpha.

Crypto and FX

FX has session handoffs (Tokyo/London/NY) with vol spikes at opens — true seasonality tied to information flow (lead-lag).

Crypto is 24/7; "seasonality" is often:

  • US equity hours spillover
  • Funding timestamps

(funding)

  • Weekend liquidity drop

Re-estimate often; do not freeze a 2021 UTC profile into 2026.

Key takeaways

  • Volume and vol U-shapes are real; use them for execution and risk
  • De-seasonalize features before calling residuals "alpha"
  • VWAP curves must adjust for events and half-days
  • Intraday return seasonality is usually too weak after costs
  • Re-estimate crypto/FX session profiles — they drift
#intraday seasonality #time of day #U-shape volume #calendar effects