# Cme Futures Time Buckets

> Use when resampling intraday futures bars (e.g. 4h, 8h) or weekly/monthly bars to a coarser timeframe. CME globex futures sessions run Sunday 18:00 ET → Friday 17:00 ET, NOT calendar weeks; intraday buckets a futures trader expects align to the 18:00 ET session open, NOT UTC midnight. Naive resampling produces bars that look right but are wrong by 4-6 hours.

- Skill: `traderspost/cme-futures-time-buckets` (Agent Skill)
- Install (CLI): `npx skillmds@latest add traderspost/cme-futures-time-buckets`
- Raw SKILL.md: https://api.skillmd.com/api/skills/traderspost/cme-futures-time-buckets/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: traderspost (https://skillmd.com/u/traderspost)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/traderspost/cme-futures-time-buckets

---


# CME futures time-bucket alignment

Most data tools default to UTC-aligned buckets ("a 4-hour bar starts at 00:00, 04:00, 08:00, …"). For a futures trader looking at a 4h NQ chart, that's wrong — they expect the 4h bar at 18:00 ET to mark the start of the new trading session, the same way it's drawn on TradingView and every other futures-aware platform. Same for weekly: a futures week is Sunday 18:00 → Friday 17:00 ET, not ISO Monday → Sunday.

## When to use

Reach for this skill if you're:

- Building a resampler that turns 1m or 1h bars into 4h, 8h, weekly, or monthly bars
- Shipping a charting UI where the operator can pick any timeframe
- Hearing "the bars don't line up with TradingView" from a futures trader
- Implementing `group_by_dynamic` or any equivalent time-bucketing in Polars / pandas / DuckDB

## The pattern — Polars `group_by_dynamic` with NY-local timezone + offset

The trick is **convert to NY-local time, group with the right `offset` argument, convert back to UTC**. NY-local handles DST transitions automatically; the `offset` shifts the bucket origin so 18:00 ET lands on a bucket boundary.

```python
import polars as pl

# Intraday: 4h or 8h buckets, anchored to CME 18:00 ET
def resample_intraday(bars: pl.DataFrame, every: str) -> pl.DataFrame:
    # `every` is "4h", "8h", etc. Bucket origin: 02:00 NY-local → buckets at
    # 02, 06, 10, 14, 18, 22 NY-local. The 18:00 ET (= NY-local 18:00) bar
    # captures the first 4h of the futures trading day.
    return (
        bars.with_columns(
            pl.col("window_start").dt.convert_time_zone("America/New_York")
        )
        .group_by_dynamic(
            "window_start",
            every=every,
            offset="2h",          # 02:00 NY-local origin → 18:00 included
            closed="left",
            label="left",
        )
        .agg(
            pl.col("open").first().alias("open"),
            pl.col("high").max().alias("high"),
            pl.col("low").min().alias("low"),
            pl.col("close").last().alias("close"),
            pl.col("volume").sum().alias("volume"),
        )
        .with_columns(
            pl.col("window_start").dt.convert_time_zone("UTC")
        )
    )

# Weekly: Sunday 18:00 ET → Friday 17:00 ET. Polars' default 1w is Monday
# ISO-week; shift back 6 hours from Monday 00:00 to land on Sun 18:00.
WEEK_OFFSET_NY = "-6h"
```

### Why the magic numbers

- **4h, `offset="2h"`**: default 4h origin is 00:00. Adding 2h shifts to {02, 06, 10, 14, 18, 22} NY-local — 18:00 included.
- **8h, `offset="2h"`**: default 8h origin is 00:00. Adding 2h shifts to {02, 10, 18} NY-local — 18:00 included.
- **2h, `offset="0h"`**: default already at every even hour → 18:00 included naturally.
- **30m, no shift needed**: trader convention is :00/:30 boundaries.
- **1w, `offset="-6h"` on NY-local**: shifts Monday 00:00 → previous Sunday 18:00.

### What about 1d?

A daily bar on CME spans 18:00 ET (prev day) → 17:00 ET (current day). Most data providers already key daily bars by the session date with the right boundary baked in. If yours doesn't, apply the same NY-local + offset trick with `every="1d"` and `offset="-6h"` (NY-local 00:00 − 6h = previous day 18:00). Verify against the data provider's docs.

## Verifying alignment

Build a test that constructs a known-aligned bar series, resamples, and asserts the output bucket starts at 18:00 ET on a specific date:

```python
def test_4h_aligns_to_cme_session_start_18et() -> None:
    # 2026-01-05 23:00 UTC = 18:00 EST (winter). Spawn 8 hourly bars.
    bars = _mk_bars(datetime(2026, 1, 5, 23, 0), n=8, step_minutes=60)
    out = resample_bars(bars, "4hour").sort("window_start")
    assert out.height == 2
    # First bucket aggregates the 23:00, 00:00, 01:00, 02:00 UTC bars
    # (= 18:00, 19:00, 20:00, 21:00 EST)
    row0 = out.row(0, named=True)
    assert row0["open"] == 0.0
    assert row0["close"] == 3.25
```

This kind of test catches the "off by N hours" bugs that visual inspection misses.

## Gotchas

- **Polars `every="1w"` defaults to Monday-aligned ISO week.** Use the `-6h` NY-local offset trick above for CME-week, OR document the deviation as a known limitation if ISO-week is acceptable.
- **DST.** Don't try to bake "18:00 ET = 23:00 UTC" as a fixed offset — that's only true in EST. America/New_York handles DST automatically via `convert_time_zone`. Don't shortcut it.
- **`offset` is a string token, not seconds.** Polars wants `"2h"`, `"-6h"`, `"30m"`. Numeric seconds throw.
- **The `closed="left"`, `label="left"` combo matters.** Default `closed="right"` shifts which bar each timestamp falls into and produces off-by-one bucket counts. Always specify both.
- **Test data needs to live in a single CME session.** Constructing bars spanning a DST transition or a weekend gap will produce surprising bucket counts that aren't bugs but test-data issues.

## Reference implementation

This pattern was extracted from a working resampler module that translates 1m → {30m, 2h, 4h, 8h, weekly, monthly} for a CME futures charting platform. The structure looked like:

- A `resampler.py` exposing a single `resample_bars(df, target)` function
- An `_INTRADAY_OFFSETS_NY` dict mapping each intraday granularity to its NY-local offset string
- A `WEEK_OFFSET_NY` constant for the CME-week alignment
- A test file with ~12 parity tests covering every granularity with explicit "first bucket starts at X" assertions

## Related skills

- `partial-bar-graft` — once your resampler works, the in-progress bar still needs special handling
- `lightweight-charts-integration` — the chart side is unaware of timezones; the resampler is the right place for alignment logic

