Skip to content

Commit c6db4b2

Browse files
[SPT-1407] Add last_updated/timeframe to IndicesSnapshot and list_snapshot_indices (#1038)
- Adds `last_updated` and `timeframe` to `IndicesSnapshot`. The API returns them, but the model was missing them. - Adds `list_snapshot_indices`, which paginates. `get_snapshot_indices` only ever returns the first page (250 indices) and is unchanged. No breaking changes. Tests, docs and an example are included.
1 parent f6da4a5 commit c6db4b2

7 files changed

Lines changed: 254 additions & 1 deletion

File tree

‎docs/source/Snapshot.rst‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ Get crypto L2 book snapshot
6060

6161
.. automethod:: massive.RESTClient.get_snapshot_crypto_book
6262

63+
=================================
64+
Get indices snapshot
65+
=================================
66+
67+
- `Indices snapshot`_
68+
69+
.. automethod:: massive.RESTClient.list_snapshot_indices
70+
71+
.. automethod:: massive.RESTClient.get_snapshot_indices
72+
6373
.. _Stocks snapshot all tickers: https://massive.com/docs/stocks/get_v3_snapshot
6474
.. _Options snapshot all tickers: https://massive.com/docs/options/get_v3_snapshot
6575
.. _Forex snapshot all tickers: https://massive.com/docs/forex/get_v3_snapshot
@@ -75,4 +85,5 @@ Get crypto L2 book snapshot
7585
.. _Forex snapshot ticker: https://massive.com/docs/forex/get_v2_snapshot_locale_global_markets_forex_tickers__ticker
7686
.. _Crypto snapshot ticker: https://massive.com/docs/crypto/get_v2_snapshot_locale_global_markets_crypto_tickers__ticker
7787
.. _Options snapshot option contract: https://massive.com/docs/options/get_v3_snapshot_options__underlyingasset___optioncontract
78-
.. _Crypto snapshot ticker full book (L2): https://massive.com/docs/crypto/get_v2_snapshot_locale_global_markets_crypto_tickers__ticker__book
88+
.. _Crypto snapshot ticker full book (L2): https://massive.com/docs/crypto/get_v2_snapshot_locale_global_markets_crypto_tickers__ticker__book
89+
.. _Indices snapshot: https://massive.com/docs/indices/get_v3_snapshot_indices
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from massive import RESTClient
2+
3+
# docs
4+
# https://massive.com/docs/indices/get_v3_snapshot_indices
5+
# https://github.com/massive-com/client-python/blob/master/massive/rest/snapshot.py#
6+
7+
# client = RESTClient("XXXXXX") # hardcoded api_key is used
8+
client = RESTClient() # MASSIVE_API_KEY environment variable is used
9+
10+
# list_snapshot_indices follows pagination, so this iterates over every index
11+
snapshots = []
12+
for s in client.list_snapshot_indices(limit=250):
13+
snapshots.append(s)
14+
15+
print(len(snapshots))
16+
print(snapshots[0])

‎massive/rest/models/snapshot.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ class IndicesSnapshot:
6767
session: Optional[IndicesSession] = None
6868
error: Optional[str] = None
6969
message: Optional[str] = None
70+
last_updated: Optional[int] = None
71+
timeframe: Optional[str] = None
7072

7173
@staticmethod
7274
def from_dict(d):
@@ -81,6 +83,8 @@ def from_dict(d):
8183
),
8284
error=d.get("error", None),
8385
message=d.get("message", None),
86+
last_updated=d.get("last_updated", None),
87+
timeframe=d.get("timeframe", None),
8488
)
8589

8690

‎massive/rest/snapshot.py‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,17 @@ def get_snapshot_indices(
244244
raw: bool = False,
245245
options: Optional[RequestOptionBuilder] = None,
246246
) -> Union[List[IndicesSnapshot], HTTPResponse]:
247+
"""
248+
Get snapshots for indices. Returns a single page of results; use
249+
list_snapshot_indices to paginate through all of them.
250+
- https://massive.com/docs/indices/get_v3_snapshot_indices
251+
252+
:param ticker_any_of: Comma-separated list of tickers, up to a maximum of 250.
253+
:param params: Any additional query params
254+
:param raw: returns the raw HTTP response if true, else the response is deserialized into a structured object
255+
:param options: request options
256+
:return: list of IndicesSnapshot
257+
"""
247258
url = f"/v3/snapshot/indices"
248259

249260
return self._get(
@@ -254,3 +265,47 @@ def get_snapshot_indices(
254265
result_key="results",
255266
options=options,
256267
)
268+
269+
def list_snapshot_indices(
270+
self,
271+
ticker: Optional[str] = None,
272+
ticker_any_of: Optional[Union[str, List[str]]] = None,
273+
ticker_lt: Optional[str] = None,
274+
ticker_lte: Optional[str] = None,
275+
ticker_gt: Optional[str] = None,
276+
ticker_gte: Optional[str] = None,
277+
order: Optional[Union[str, Order]] = None,
278+
limit: Optional[int] = None,
279+
sort: Optional[str] = None,
280+
params: Optional[Dict[str, Any]] = None,
281+
raw: bool = False,
282+
options: Optional[RequestOptionBuilder] = None,
283+
) -> Union[Iterator[IndicesSnapshot], HTTPResponse]:
284+
"""
285+
Get snapshots for indices, following pagination.
286+
- https://massive.com/docs/indices/get_v3_snapshot_indices
287+
288+
:param ticker: A single index ticker, e.g. I:SPX.
289+
:param ticker_any_of: Comma-separated list of tickers, up to a maximum of 250. If no tickers are passed then all
290+
results will be returned in a paginated manner.
291+
:param ticker_lt: search for tickers less than
292+
:param ticker_lte: search for tickers less than or equal to
293+
:param ticker_gt: search for tickers greater than
294+
:param ticker_gte: search for tickers greater than or equal to
295+
:param order: Order results based on the sort field.
296+
:param limit: Limit the size of the response per-page, max is 250.
297+
:param sort: Sort field used for ordering. Default is ticker.
298+
:param params: Any additional query params
299+
:param raw: returns the raw HTTP response if true, else the response is deserialized into a structured object
300+
:param options: request options
301+
:return: iterator of IndicesSnapshot
302+
"""
303+
url = f"/v3/snapshot/indices"
304+
return self._paginate(
305+
path=url,
306+
params=self._get_params(self.list_snapshot_indices, locals()),
307+
result_key="results",
308+
deserializer=IndicesSnapshot.from_dict,
309+
raw=raw,
310+
options=options,
311+
)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"results": [
3+
{
4+
"value": 28.685000000000002,
5+
"last_updated": 1792081393146000000,
6+
"timeframe": "REAL-TIME",
7+
"name": "PROSHARES ULTRA BASIC MATERIALS ETF",
8+
"ticker": "I:UYMIV",
9+
"market_status": "regular_trading",
10+
"type": "indices",
11+
"session": {
12+
"change": 0.924,
13+
"change_percent": 3.328,
14+
"close": 28.6859,
15+
"high": 29.0102,
16+
"low": 27.8834,
17+
"open": 27.8855,
18+
"previous_close": 27.761200000000002
19+
}
20+
},
21+
{
22+
"value": 19.25,
23+
"last_updated": 1792008000219000000,
24+
"timeframe": "REAL-TIME",
25+
"name": "Cboe VIX Jan Futures 4pm EST Mark Index",
26+
"ticker": "I:UZF",
27+
"market_status": "regular_trading",
28+
"type": "indices",
29+
"session": {
30+
"change": 0,
31+
"change_percent": 0,
32+
"close": 19.25,
33+
"high": 19.25,
34+
"low": 19.25,
35+
"open": 19.25,
36+
"previous_close": 19.25
37+
}
38+
}
39+
],
40+
"status": "OK",
41+
"request_id": "0ade2056b75a64c1bbf1fe9fca8f20ed"
42+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"results": [
3+
{
4+
"value": 7717.17,
5+
"last_updated": 1792081393031000000,
6+
"timeframe": "REAL-TIME",
7+
"name": "Standard & Poor's 500",
8+
"ticker": "I:SPX",
9+
"market_status": "regular_trading",
10+
"type": "indices",
11+
"session": {
12+
"change": -1.77,
13+
"change_percent": -0.0229,
14+
"close": 7718.56,
15+
"high": 7738.21,
16+
"low": 7669.66,
17+
"open": 7687.31,
18+
"previous_close": 7718.9400000000005
19+
}
20+
},
21+
{
22+
"value": 82.5721,
23+
"last_updated": 1792081393415000000,
24+
"timeframe": "REAL-TIME",
25+
"name": "PROSHARES S&P 500 EX-ENERGY ETF",
26+
"ticker": "I:SPXEIV",
27+
"market_status": "regular_trading",
28+
"type": "indices",
29+
"session": {
30+
"change": 0.0058,
31+
"change_percent": 0.00702,
32+
"close": 82.583,
33+
"high": 82.8018,
34+
"low": 81.96520000000001,
35+
"open": 81.96520000000001,
36+
"previous_close": 82.5663
37+
}
38+
}
39+
],
40+
"status": "OK",
41+
"request_id": "62a572d0d04de8a7001f3c01a6d861ad",
42+
"next_url": "https://api.massive.com/v3/snapshot/indices?cursor=bGltaXQ9MTAmc29ydD10aWNrZXImdGlja2VyLmd0PUklM0FVWUdJViZ0aWNrZXIubHRlPQ"
43+
}

‎test_rest/test_snapshots.py‎

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,88 @@ def test_get_snapshot_crypto_book(self):
426426
)
427427
self.assertEqual(snapshots, expected)
428428

429+
def test_list_snapshot_indices(self):
430+
# two pages: the first page's next_url points at the second
431+
snapshots = [s for s in self.c.list_snapshot_indices(ticker_gte="I:SPX")]
432+
expected = [
433+
IndicesSnapshot(
434+
value=7717.17,
435+
name="Standard & Poor's 500",
436+
type="indices",
437+
ticker="I:SPX",
438+
market_status="regular_trading",
439+
session=IndicesSession(
440+
change=-1.77,
441+
change_percent=-0.0229,
442+
close=7718.56,
443+
high=7738.21,
444+
low=7669.66,
445+
open=7687.31,
446+
previous_close=7718.9400000000005,
447+
),
448+
last_updated=1792081393031000000,
449+
timeframe="REAL-TIME",
450+
),
451+
IndicesSnapshot(
452+
value=82.5721,
453+
name="PROSHARES S&P 500 EX-ENERGY ETF",
454+
type="indices",
455+
ticker="I:SPXEIV",
456+
market_status="regular_trading",
457+
session=IndicesSession(
458+
change=0.0058,
459+
change_percent=0.00702,
460+
close=82.583,
461+
high=82.8018,
462+
low=81.96520000000001,
463+
open=81.96520000000001,
464+
previous_close=82.5663,
465+
),
466+
last_updated=1792081393415000000,
467+
timeframe="REAL-TIME",
468+
),
469+
IndicesSnapshot(
470+
value=28.685000000000002,
471+
name="PROSHARES ULTRA BASIC MATERIALS ETF",
472+
type="indices",
473+
ticker="I:UYMIV",
474+
market_status="regular_trading",
475+
session=IndicesSession(
476+
change=0.924,
477+
change_percent=3.328,
478+
close=28.6859,
479+
high=29.0102,
480+
low=27.8834,
481+
open=27.8855,
482+
previous_close=27.761200000000002,
483+
),
484+
last_updated=1792081393146000000,
485+
timeframe="REAL-TIME",
486+
),
487+
IndicesSnapshot(
488+
value=19.25,
489+
name="Cboe VIX Jan Futures 4pm EST Mark Index",
490+
type="indices",
491+
ticker="I:UZF",
492+
market_status="regular_trading",
493+
session=IndicesSession(
494+
change=0,
495+
change_percent=0,
496+
close=19.25,
497+
high=19.25,
498+
low=19.25,
499+
open=19.25,
500+
previous_close=19.25,
501+
),
502+
last_updated=1792008000219000000,
503+
timeframe="REAL-TIME",
504+
),
505+
]
506+
self.assertEqual(snapshots, expected)
507+
# modelclass drops unknown kwargs, so check the fields exist explicitly
508+
self.assertEqual(snapshots[0].last_updated, 1792081393031000000)
509+
self.assertEqual(snapshots[0].timeframe, "REAL-TIME")
510+
429511
def test_get_snapshot_indices(self):
430512
ticker_any_of = ["SPX", "APx", "APy"]
431513
summary_results = self.c.get_snapshot_indices(ticker_any_of)

0 commit comments

Comments
 (0)