Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions openbb_platform/dev_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
openbb-alpha-vantage = { path = "./providers/alpha_vantage", optional = true, develop = true }
openbb-biztoc = { path = "./providers/biztoc", optional = true, develop = true }
openbb-cboe = { path = "./providers/cboe", optional = true, develop = true }
openbb-cme = { path = "./providers/cme", optional = true, develop = true }
openbb-deribit = { path = "./providers/deribit", optional = true, develop = true }
openbb-ecb = { path = "./providers/ecb", optional = true, develop = true }
openbb-famafrench = { path = "./providers/famafrench", optional = true, develop = true }
Expand Down
112 changes: 112 additions & 0 deletions openbb_platform/providers/cme/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# OpenBB CME Provider

CME Group equity index futures data for OpenBB. No API key required.

## Supported Symbols

| Symbol | Name | Exchange | Daily Volume (approx.) |
|--------|------|----------|------------------------|
| ES | E-mini S&P 500 | CME/Globex | ~1.5M |
| NQ | E-mini Nasdaq-100 | CME/Globex | ~800K |
| MES | Micro E-mini S&P 500 | CME/Globex | ~1.5M |
| MNQ | Micro E-mini Nasdaq-100 | CME/Globex | ~1M |
| RTY | E-mini Russell 2000 | CME/Globex | ~200K |
| YM | E-mini Dow ($5) | CBOT/Globex | ~100K |

## Installation

```bash
pip install openbb-cme
# or via poetry inside the platform
poetry add openbb-cme
```

## Data Source

Public settlement data from CME Group:

```
https://www.cmegroup.com/CmeWS/mvc/Settlements/Futures/Settlements/{product_id}/FUT
```

Settlement data is published at end-of-day (T+1 availability). No authentication required.

## Fetchers

### `FuturesHistorical`

Daily OHLCV + official settlement price and open interest.

```python
from openbb import obb

# Front-month ES settlement history (last 30 days by default)
df = obb.derivatives.futures.historical("ES", provider="cme").to_df()

# Specific contract month
df = obb.derivatives.futures.historical(
"NQ", provider="cme",
start_date="2025-01-01", end_date="2025-06-25",
expiration="2025-06"
).to_df()
```

**Extra fields vs. standard model:**
- `settlement_price`: official CME end-of-day settlement (distinct from last trade)
- `open_interest`: daily outstanding contracts
- `expiration`: contract month in YYYY-MM format
- `symbol`: CME root symbol

### FuturesCurve

Term structure (all active contract months) for a given date.

```python
# Current term structure
df = obb.derivatives.futures.curve("ES", provider="cme").to_df()

# Historical term structure on a specific date
df = obb.derivatives.futures.curve(
"ES", provider="cme", date="2025-03-21"
).to_df()
```

**Extra fields:** `open_interest`, `volume`, `symbol`

### `FuturesInfo`

Contract specifications + latest settlement summary. Accepts multiple symbols.

```python
# Single symbol
info = obb.derivatives.futures.info("ES", provider="cme").to_df()

# Multiple symbols
info = obb.derivatives.futures.info("ES,NQ,MES", provider="cme").to_df()
```

**Fields:** `tick_size`, `point_value`, `multiplier`, `exchange`, `currency`, `settlement_price`, `open_interest`, `volume`, `front_month`, `trade_date`

### `FuturesInstruments`

Listed contracts with expiration dates. Accepts multiple symbols.

```python
# All listed ES contracts
instruments = obb.derivatives.futures.instruments("ES", provider="cme").to_df()

# Multiple symbols
instruments = obb.derivatives.futures.instruments("ES,NQ", provider="cme").to_df()
```

## Running Tests

```bash
# Unit tests (no network required) (11 tests)
pytest openbb_platform/providers/cme/tests/test_cme_fetchers.py -v -k "not record_http"

# Live HTTP tests (requires internet access to cmegroup.com)
pytest openbb_platform/providers/cme/tests/test_cme_fetchers.py -v -k record_http
```

Tests marked `@pytest.mark.record_http` make real HTTP requests to CME's public API. They are excluded from CI runs (which use `-k "not record_http"`).
Empty file.
31 changes: 31 additions & 0 deletions openbb_platform/providers/cme/openbb_cme/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""OpenBB CME Provider Module."""

from openbb_cme.models.futures_curve import CMEFuturesCurveFetcher
from openbb_cme.models.futures_historical import CMEFuturesHistoricalFetcher
from openbb_cme.models.futures_info import CMEFuturesInfoFetcher
from openbb_cme.models.futures_instruments import CMEFuturesInstrumentsFetcher
from openbb_core.provider.abstract.provider import Provider

cme_provider = Provider(
name="cme",
website="https://www.cmegroup.com/",
description=(
"CME Group provider for equity index futures settlement data. "
"Provides daily settlement prices, term structure, contract specifications, "
"and listed instruments for ES, NQ, MES, MNQ, RTY, and YM futures. "
"Uses publicly available CME settlement APIs — no API key required."
),
credentials=None,
fetcher_dict={
"FuturesCurve": CMEFuturesCurveFetcher,
"FuturesHistorical": CMEFuturesHistoricalFetcher,
"FuturesInfo": CMEFuturesInfoFetcher,
"FuturesInstruments": CMEFuturesInstrumentsFetcher,
},
repr_name="CME Group Public Settlement Data",
instructions=(
"This provider uses CME Group's public settlement data endpoints."
" No credentials are required."
" Data is delayed (T+1 for settlements)."
),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""CME futures models."""
126 changes: 126 additions & 0 deletions openbb_platform/providers/cme/openbb_cme/models/futures_curve.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
"""CME Futures Curve Model."""

# pylint: disable=unused-argument

from datetime import date as dateType
from typing import Any

from openbb_cme.utils.helpers import (
CME_PRODUCT_MAP,
fetch_settlements,
last_business_day,
)
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.futures_curve import (
FuturesCurveData,
FuturesCurveQueryParams,
)
from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from openbb_core.provider.utils.errors import EmptyDataError
from pydantic import Field, field_validator


class CMEFuturesCurveQueryParams(FuturesCurveQueryParams):
"""
CME Futures Curve Query.

Source: https://www.cmegroup.com/CmeWS/mvc/Settlements/Futures/Settlements/{product_id}/FUT
"""

__json_schema_extra__ = {
"symbol": {"multiple_items_allowed": False, "choices": list(CME_PRODUCT_MAP)}
}

symbol: str = Field(
default="ES",
description=QUERY_DESCRIPTIONS.get("symbol", "")
+ " Supported symbols: "
+ ", ".join(CME_PRODUCT_MAP),
)

@field_validator("symbol", mode="before", check_fields=False)
@classmethod
def _validate_symbol(cls, v: str) -> str:
"""Uppercase and validate against known CME symbols."""
v = v.upper()
if v not in CME_PRODUCT_MAP:
raise ValueError(
f"Symbol '{v}' is not supported. Supported: {', '.join(CME_PRODUCT_MAP)}"
)
return v


class CMEFuturesCurveData(FuturesCurveData):
"""CME Futures Curve Data."""

symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
open_interest: float | None = Field(
default=None, description="Open interest for this contract month."
)
volume: float | None = Field(
default=None, description=DATA_DESCRIPTIONS.get("volume", "")
)


class CMEFuturesCurveFetcher(
Fetcher[CMEFuturesCurveQueryParams, list[CMEFuturesCurveData]]
):
"""CME Futures Curve Fetcher."""

@staticmethod
def transform_query(params: dict[str, Any]) -> CMEFuturesCurveQueryParams:
"""Transform the query."""
return CMEFuturesCurveQueryParams(**params)

@staticmethod
async def aextract_data(
query: CMEFuturesCurveQueryParams,
credentials: dict[str, str] | None,
**kwargs: Any,
) -> list[dict]:
"""Fetch the term structure for the given symbol on a specific date."""
if query.date:
trade_date = (
dateType.fromisoformat(query.date)
if isinstance(query.date, str)
else query.date
)
else:
trade_date = last_business_day()

rows = await fetch_settlements(query.symbol, trade_date)

if not rows:
raise EmptyDataError(
f"No settlement data found for {query.symbol} on {trade_date}."
)
return rows

@staticmethod
def transform_data(
query: CMEFuturesCurveQueryParams, data: list[dict], **kwargs: Any
) -> list[CMEFuturesCurveData]:
"""Transform the data into the FuturesCurveData model."""
results: list[CMEFuturesCurveData] = []
for row in data:
price = row.get("close") or row.get("settlement_price")
if price is None:
continue
results.append(
CMEFuturesCurveData.model_validate(
{
"date": row.get("date"),
"expiration": row.get("expiration"),
"price": price,
"symbol": row.get("symbol"),
"open_interest": row.get("open_interest"),
"volume": row.get("volume"),
}
)
)
if not results:
raise EmptyDataError("No settlement prices found in the response.")
return sorted(results, key=lambda x: x.expiration)
Loading