diff --git a/docs/source/Snapshot.rst b/docs/source/Snapshot.rst index 7268e209..14db2212 100644 --- a/docs/source/Snapshot.rst +++ b/docs/source/Snapshot.rst @@ -60,6 +60,16 @@ Get crypto L2 book snapshot .. automethod:: massive.RESTClient.get_snapshot_crypto_book +================================= +Get indices snapshot +================================= + +- `Indices snapshot`_ + +.. automethod:: massive.RESTClient.list_snapshot_indices + +.. automethod:: massive.RESTClient.get_snapshot_indices + .. _Stocks snapshot all tickers: https://massive.com/docs/stocks/get_v3_snapshot .. _Options snapshot all tickers: https://massive.com/docs/options/get_v3_snapshot .. _Forex snapshot all tickers: https://massive.com/docs/forex/get_v3_snapshot @@ -75,4 +85,5 @@ Get crypto L2 book snapshot .. _Forex snapshot ticker: https://massive.com/docs/forex/get_v2_snapshot_locale_global_markets_forex_tickers__ticker .. _Crypto snapshot ticker: https://massive.com/docs/crypto/get_v2_snapshot_locale_global_markets_crypto_tickers__ticker .. _Options snapshot option contract: https://massive.com/docs/options/get_v3_snapshot_options__underlyingasset___optioncontract -.. _Crypto snapshot ticker full book (L2): https://massive.com/docs/crypto/get_v2_snapshot_locale_global_markets_crypto_tickers__ticker__book \ No newline at end of file +.. _Crypto snapshot ticker full book (L2): https://massive.com/docs/crypto/get_v2_snapshot_locale_global_markets_crypto_tickers__ticker__book +.. _Indices snapshot: https://massive.com/docs/indices/get_v3_snapshot_indices diff --git a/examples/rest/indices-snapshots_list.py b/examples/rest/indices-snapshots_list.py new file mode 100644 index 00000000..f85d109c --- /dev/null +++ b/examples/rest/indices-snapshots_list.py @@ -0,0 +1,16 @@ +from massive import RESTClient + +# docs +# https://massive.com/docs/indices/get_v3_snapshot_indices +# https://github.com/massive-com/client-python/blob/master/massive/rest/snapshot.py# + +# client = RESTClient("XXXXXX") # hardcoded api_key is used +client = RESTClient() # MASSIVE_API_KEY environment variable is used + +# list_snapshot_indices follows pagination, so this iterates over every index +snapshots = [] +for s in client.list_snapshot_indices(limit=250): + snapshots.append(s) + +print(len(snapshots)) +print(snapshots[0]) diff --git a/massive/rest/models/snapshot.py b/massive/rest/models/snapshot.py index f9a0432d..80b0bdb7 100644 --- a/massive/rest/models/snapshot.py +++ b/massive/rest/models/snapshot.py @@ -67,6 +67,8 @@ class IndicesSnapshot: session: Optional[IndicesSession] = None error: Optional[str] = None message: Optional[str] = None + last_updated: Optional[int] = None + timeframe: Optional[str] = None @staticmethod def from_dict(d): @@ -81,6 +83,8 @@ def from_dict(d): ), error=d.get("error", None), message=d.get("message", None), + last_updated=d.get("last_updated", None), + timeframe=d.get("timeframe", None), ) diff --git a/massive/rest/snapshot.py b/massive/rest/snapshot.py index 6953090e..1cf4e368 100644 --- a/massive/rest/snapshot.py +++ b/massive/rest/snapshot.py @@ -244,6 +244,17 @@ def get_snapshot_indices( raw: bool = False, options: Optional[RequestOptionBuilder] = None, ) -> Union[List[IndicesSnapshot], HTTPResponse]: + """ + Get snapshots for indices. Returns a single page of results; use + list_snapshot_indices to paginate through all of them. + - https://massive.com/docs/indices/get_v3_snapshot_indices + + :param ticker_any_of: Comma-separated list of tickers, up to a maximum of 250. + :param params: Any additional query params + :param raw: returns the raw HTTP response if true, else the response is deserialized into a structured object + :param options: request options + :return: list of IndicesSnapshot + """ url = f"/v3/snapshot/indices" return self._get( @@ -254,3 +265,47 @@ def get_snapshot_indices( result_key="results", options=options, ) + + def list_snapshot_indices( + self, + ticker: Optional[str] = None, + ticker_any_of: Optional[Union[str, List[str]]] = None, + ticker_lt: Optional[str] = None, + ticker_lte: Optional[str] = None, + ticker_gt: Optional[str] = None, + ticker_gte: Optional[str] = None, + order: Optional[Union[str, Order]] = None, + limit: Optional[int] = None, + sort: Optional[str] = None, + params: Optional[Dict[str, Any]] = None, + raw: bool = False, + options: Optional[RequestOptionBuilder] = None, + ) -> Union[Iterator[IndicesSnapshot], HTTPResponse]: + """ + Get snapshots for indices, following pagination. + - https://massive.com/docs/indices/get_v3_snapshot_indices + + :param ticker: A single index ticker, e.g. I:SPX. + :param ticker_any_of: Comma-separated list of tickers, up to a maximum of 250. If no tickers are passed then all + results will be returned in a paginated manner. + :param ticker_lt: search for tickers less than + :param ticker_lte: search for tickers less than or equal to + :param ticker_gt: search for tickers greater than + :param ticker_gte: search for tickers greater than or equal to + :param order: Order results based on the sort field. + :param limit: Limit the size of the response per-page, max is 250. + :param sort: Sort field used for ordering. Default is ticker. + :param params: Any additional query params + :param raw: returns the raw HTTP response if true, else the response is deserialized into a structured object + :param options: request options + :return: iterator of IndicesSnapshot + """ + url = f"/v3/snapshot/indices" + return self._paginate( + path=url, + params=self._get_params(self.list_snapshot_indices, locals()), + result_key="results", + deserializer=IndicesSnapshot.from_dict, + raw=raw, + options=options, + ) diff --git a/test_rest/mocks/v3/snapshot/indices&cursor=bGltaXQ9MTAmc29ydD10aWNrZXImdGlja2VyLmd0PUklM0FVWUdJViZ0aWNrZXIubHRlPQ.json b/test_rest/mocks/v3/snapshot/indices&cursor=bGltaXQ9MTAmc29ydD10aWNrZXImdGlja2VyLmd0PUklM0FVWUdJViZ0aWNrZXIubHRlPQ.json new file mode 100644 index 00000000..8a2bd878 --- /dev/null +++ b/test_rest/mocks/v3/snapshot/indices&cursor=bGltaXQ9MTAmc29ydD10aWNrZXImdGlja2VyLmd0PUklM0FVWUdJViZ0aWNrZXIubHRlPQ.json @@ -0,0 +1,42 @@ +{ + "results": [ + { + "value": 28.685000000000002, + "last_updated": 1792081393146000000, + "timeframe": "REAL-TIME", + "name": "PROSHARES ULTRA BASIC MATERIALS ETF", + "ticker": "I:UYMIV", + "market_status": "regular_trading", + "type": "indices", + "session": { + "change": 0.924, + "change_percent": 3.328, + "close": 28.6859, + "high": 29.0102, + "low": 27.8834, + "open": 27.8855, + "previous_close": 27.761200000000002 + } + }, + { + "value": 19.25, + "last_updated": 1792008000219000000, + "timeframe": "REAL-TIME", + "name": "Cboe VIX Jan Futures 4pm EST Mark Index", + "ticker": "I:UZF", + "market_status": "regular_trading", + "type": "indices", + "session": { + "change": 0, + "change_percent": 0, + "close": 19.25, + "high": 19.25, + "low": 19.25, + "open": 19.25, + "previous_close": 19.25 + } + } + ], + "status": "OK", + "request_id": "0ade2056b75a64c1bbf1fe9fca8f20ed" +} \ No newline at end of file diff --git a/test_rest/mocks/v3/snapshot/indices&ticker.gte=I%3ASPX.json b/test_rest/mocks/v3/snapshot/indices&ticker.gte=I%3ASPX.json new file mode 100644 index 00000000..f1cf3867 --- /dev/null +++ b/test_rest/mocks/v3/snapshot/indices&ticker.gte=I%3ASPX.json @@ -0,0 +1,43 @@ +{ + "results": [ + { + "value": 7717.17, + "last_updated": 1792081393031000000, + "timeframe": "REAL-TIME", + "name": "Standard & Poor's 500", + "ticker": "I:SPX", + "market_status": "regular_trading", + "type": "indices", + "session": { + "change": -1.77, + "change_percent": -0.0229, + "close": 7718.56, + "high": 7738.21, + "low": 7669.66, + "open": 7687.31, + "previous_close": 7718.9400000000005 + } + }, + { + "value": 82.5721, + "last_updated": 1792081393415000000, + "timeframe": "REAL-TIME", + "name": "PROSHARES S&P 500 EX-ENERGY ETF", + "ticker": "I:SPXEIV", + "market_status": "regular_trading", + "type": "indices", + "session": { + "change": 0.0058, + "change_percent": 0.00702, + "close": 82.583, + "high": 82.8018, + "low": 81.96520000000001, + "open": 81.96520000000001, + "previous_close": 82.5663 + } + } + ], + "status": "OK", + "request_id": "62a572d0d04de8a7001f3c01a6d861ad", + "next_url": "https://api.massive.com/v3/snapshot/indices?cursor=bGltaXQ9MTAmc29ydD10aWNrZXImdGlja2VyLmd0PUklM0FVWUdJViZ0aWNrZXIubHRlPQ" +} \ No newline at end of file diff --git a/test_rest/test_snapshots.py b/test_rest/test_snapshots.py index 3b016d71..f81c0593 100644 --- a/test_rest/test_snapshots.py +++ b/test_rest/test_snapshots.py @@ -426,6 +426,88 @@ def test_get_snapshot_crypto_book(self): ) self.assertEqual(snapshots, expected) + def test_list_snapshot_indices(self): + # two pages: the first page's next_url points at the second + snapshots = [s for s in self.c.list_snapshot_indices(ticker_gte="I:SPX")] + expected = [ + IndicesSnapshot( + value=7717.17, + name="Standard & Poor's 500", + type="indices", + ticker="I:SPX", + market_status="regular_trading", + session=IndicesSession( + change=-1.77, + change_percent=-0.0229, + close=7718.56, + high=7738.21, + low=7669.66, + open=7687.31, + previous_close=7718.9400000000005, + ), + last_updated=1792081393031000000, + timeframe="REAL-TIME", + ), + IndicesSnapshot( + value=82.5721, + name="PROSHARES S&P 500 EX-ENERGY ETF", + type="indices", + ticker="I:SPXEIV", + market_status="regular_trading", + session=IndicesSession( + change=0.0058, + change_percent=0.00702, + close=82.583, + high=82.8018, + low=81.96520000000001, + open=81.96520000000001, + previous_close=82.5663, + ), + last_updated=1792081393415000000, + timeframe="REAL-TIME", + ), + IndicesSnapshot( + value=28.685000000000002, + name="PROSHARES ULTRA BASIC MATERIALS ETF", + type="indices", + ticker="I:UYMIV", + market_status="regular_trading", + session=IndicesSession( + change=0.924, + change_percent=3.328, + close=28.6859, + high=29.0102, + low=27.8834, + open=27.8855, + previous_close=27.761200000000002, + ), + last_updated=1792081393146000000, + timeframe="REAL-TIME", + ), + IndicesSnapshot( + value=19.25, + name="Cboe VIX Jan Futures 4pm EST Mark Index", + type="indices", + ticker="I:UZF", + market_status="regular_trading", + session=IndicesSession( + change=0, + change_percent=0, + close=19.25, + high=19.25, + low=19.25, + open=19.25, + previous_close=19.25, + ), + last_updated=1792008000219000000, + timeframe="REAL-TIME", + ), + ] + self.assertEqual(snapshots, expected) + # modelclass drops unknown kwargs, so check the fields exist explicitly + self.assertEqual(snapshots[0].last_updated, 1792081393031000000) + self.assertEqual(snapshots[0].timeframe, "REAL-TIME") + def test_get_snapshot_indices(self): ticker_any_of = ["SPX", "APx", "APy"] summary_results = self.c.get_snapshot_indices(ticker_any_of)