11"""
22Futures slug normalization.
33
4- The OilPriceAPI futures endpoints are keyed by *slug* , not by exchange
5- contract code . The latest-curve route is ``GET /v1/futures/{slug}`` and the
6- sub-resources are ``/v1/futures/{slug}/curve``, ``/historical``, ``/ohlc ``,
7- ``/intraday`` and ``/spread-history``. There is no ``?contract=`` route, so a
8- caller passing a raw ticker such as ``"CL.1"`` would hit
4+ The OilPriceAPI futures endpoints are keyed by instrument-generic slugs , not by
5+ exchange contract codes . The latest-curve route is ``GET /v1/futures/{slug}``
6+ and the sub-resources are ``/v1/futures/{slug}/curve``, ``/historical``,
7+ ``/ohlc``, ``/ intraday`` and ``/spread-history``. There is no ``?contract=``
8+ route, so a caller passing a raw ticker such as ``"CL.1"`` would hit
99``/v1/futures/CL.1`` and get a 404.
1010
1111To keep the SDK friendly, callers may pass either:
1212
13- * a canonical slug (``"ice-brent"``, ``"ice-wti"``, ``"natural-gas"``, ...), or
13+ * a canonical slug (``"brent"``, ``"wti"``, ``"natural-gas"``, ...),
14+ * a legacy venue slug (``"ice-brent"``, ``"ice-wti"``, ...), or
1415* a familiar exchange/contract code (``"BZ"``, ``"CL"``, ``"NG"``, ...),
1516
1617and :func:`normalize_futures_slug` resolves it to the canonical slug the API
2324
2425from typing import Dict , Set
2526
26- # Canonical slugs accepted by the API ( latest-curve routes) .
27+ # Canonical slugs emitted by the SDK for latest-curve routes.
2728VALID_SLUGS : Set [str ] = {
28- "ice- brent" ,
29- "ice- wti" ,
30- "ice- gasoil" ,
29+ "brent" ,
30+ "wti" ,
31+ "gasoil" ,
3132 "natural-gas" ,
3233 "ttf-gas" ,
3334 "lng-jkm" ,
34- "eua -carbon" ,
35+ "eu -carbon" ,
3536 "uk-carbon" ,
3637 "continuous/brent" ,
3738 "continuous/wti" ,
3839}
3940
40- # Friendly exchange/contract codes -> canonical slug.
41+ # Older public route names remain valid caller inputs, but the SDK emits the
42+ # instrument-generic route so new traffic does not encode a reporting venue.
43+ LEGACY_SLUG_TO_CANONICAL : Dict [str , str ] = {
44+ "ice-brent" : "brent" ,
45+ "ice-wti" : "wti" ,
46+ "ice-gasoil" : "gasoil" ,
47+ "eua-carbon" : "eu-carbon" ,
48+ }
49+
50+ # Friendly exchange/contract codes -> canonical instrument slug.
4151# Keys are matched case-insensitively against the leading contract symbol
42- # (e.g. "CL", "CL.1", "CL1!" all resolve to ice- wti).
52+ # (e.g. "CL", "CL.1", "CL1!" all resolve to wti).
4353CONTRACT_CODE_TO_SLUG : Dict [str , str ] = {
44- "BZ" : "ice- brent" , # ICE Brent
45- "BRENT" : "ice- brent" ,
46- "CL" : "ice- wti" , # WTI (NYMEX/ICE ticker)
47- "WTI" : "ice- wti" ,
48- "G" : "ice- gasoil" , # ICE Gas Oil
49- "QS" : "ice- gasoil" , # ICE Gas Oil (alt ticker)
50- "GASOIL" : "ice- gasoil" ,
54+ "BZ" : "brent" ,
55+ "BRENT" : "brent" ,
56+ "CL" : "wti" ,
57+ "WTI" : "wti" ,
58+ "G" : "gasoil" ,
59+ "QS" : "gasoil" ,
60+ "GASOIL" : "gasoil" ,
5161 "NG" : "natural-gas" , # NYMEX Henry Hub natural gas
5262 "NATGAS" : "natural-gas" ,
5363 "TTF" : "ttf-gas" , # ICE TTF natural gas
5464 "JKM" : "lng-jkm" , # ICE/CME JKM LNG
5565 "LNG" : "lng-jkm" ,
56- "EUA" : "eua -carbon" , # ICE EUA carbon
57- "EU_CARBON" : "eua -carbon" ,
66+ "EUA" : "eu -carbon" ,
67+ "EU_CARBON" : "eu -carbon" ,
5868 "UKA" : "uk-carbon" , # ICE UKA (UK) carbon
5969 "UK_CARBON" : "uk-carbon" ,
6070}
@@ -67,10 +77,10 @@ def normalize_futures_slug(contract: str) -> str:
6777 friendly exchange/contract code (e.g. ``"BZ"``, ``"CL.1"``, ``"NG"``).
6878
6979 Args:
70- contract: A slug (``"ice- brent"``) or a contract code (``"BZ"``).
80+ contract: A slug (``"brent"``) or a contract code (``"BZ"``).
7181
7282 Returns:
73- The canonical slug the API expects (e.g. ``"ice- brent"``).
83+ The instrument-generic slug the API expects (e.g. ``"brent"``).
7484
7585 Raises:
7686 ValueError: If ``contract`` is empty or cannot be resolved.
@@ -85,9 +95,17 @@ def normalize_futures_slug(contract: str) -> str:
8595 if lowered in VALID_SLUGS :
8696 return lowered
8797
98+ legacy_slug = LEGACY_SLUG_TO_CANONICAL .get (lowered )
99+ if legacy_slug is not None :
100+ return legacy_slug
101+
102+ symbol = raw .upper ()
103+ exact_code_slug = CONTRACT_CODE_TO_SLUG .get (symbol )
104+ if exact_code_slug is not None :
105+ return exact_code_slug
106+
88107 # Contract code form: take the leading symbol before any month/order
89108 # suffix such as ".1", "1!", "-2025-12", "_2025_12".
90- symbol = raw .upper ()
91109 for sep in ("." , "!" , "-" , "_" , " " ):
92110 if sep in symbol :
93111 symbol = symbol .split (sep , 1 )[0 ]
0 commit comments