diff --git a/HISTORY.rst b/HISTORY.rst index 3f010f5..d05389e 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,6 +5,7 @@ History -------------------------------- * Dev: New config for readthedocs +* Feature: Add 'columns' parameter to 'get_dataframe' and 'get_ticker_price' func (#1057) 0.15.6 (2024-05-25) -------------------------------- diff --git a/README.rst b/README.rst index 66c5cba..bb15735 100644 --- a/README.rst +++ b/README.rst @@ -90,6 +90,9 @@ for each function for details.). # Get latest prices, based on 3+ sources as JSON, sampled weekly ticker_price = client.get_ticker_price("GOOGL", frequency="weekly") + # Get 1 min prices, including the "open", "close" and "volume" columns + ticker_price = client.get_ticker_price("GOOGL", frequency="1min", columns="open,close,volume") + # Get historical GOOGL prices from August 2017 as JSON, sampled daily historical_prices = client.get_ticker_price("GOOGL", fmt='json', @@ -154,11 +157,22 @@ To receive results in ``pandas`` format, use the ``get_dataframe()`` method: endDate='2018-05-31') + #Get a pd.DataFrame for a list of symbols for "close" and "volume" columns: + ticker_history = client.get_dataframe(['GOOGL', 'AAPL'], + frequency='weekly', + columns="close,volume" + startDate='2017-01-01', + endDate='2018-05-31') + + You can specify any of the end of day frequencies (daily, weekly, monthly, and annually) or any intraday frequency for both the ``get_ticker_price`` and ``get_dataframe`` methods. Weekly frequencies resample to the end of day on Friday, monthly frequencies resample to the last day of the month, and annually frequencies resample to the end of day on 12-31 of each year. The intraday frequencies are specified using an integer followed by "Min" or "Hour", for example "30Min" or "1Hour". +It's also possible to specify which columns you're interested in, for example: "open", "close", "low", "high" and "volume" (see `End of Day response docs `_ for future columns). + + Cryptocurrency ----------------- diff --git a/tests/fixtures/ticker_price_with_multiple_columns.yaml b/tests/fixtures/ticker_price_with_multiple_columns.yaml new file mode 100644 index 0000000..352cc78 --- /dev/null +++ b/tests/fixtures/ticker_price_with_multiple_columns.yaml @@ -0,0 +1,27 @@ +interactions: +- request: + body: null + headers: + Accept: ['*/*'] + Accept-Encoding: ['gzip, deflate'] + Authorization: [Token 0000000000000000000000000000000000000000] + Connection: [keep-alive] + Content-Type: [application/json] + User-Agent: [tiingo-python-client 0.5.0] + method: GET + uri: https://api.tiingo.com/tiingo/daily/GOOGL/prices?format=json&resampleFreq=daily&columns=open,high,low,close,volume + response: + body: {string: '[{"close":165.14,"date":"2024-10-22T00:00:00+00:00","high":165.77,"low":162.98,"open":162.98,"volume":16568121}]'} + headers: + Allow: ['GET, HEAD, OPTIONS'] + Content-Length: ['1982'] + Content-Type: [application/json] + Date: ['Wed, 23 Oct 2024 02:42:06 GMT'] + Server: [nginx/1.10.1] + Vary: ['Accept, Cookie'] + X-Frame-Options: [SAMEORIGIN] + status: {code: 200, message: OK} +version: 1 + + + diff --git a/tests/fixtures/ticker_price_with_volume_column.yaml b/tests/fixtures/ticker_price_with_volume_column.yaml new file mode 100644 index 0000000..fe8f1a7 --- /dev/null +++ b/tests/fixtures/ticker_price_with_volume_column.yaml @@ -0,0 +1,27 @@ +interactions: +- request: + body: null + headers: + Accept: ['*/*'] + Accept-Encoding: ['gzip, deflate'] + Authorization: [Token 0000000000000000000000000000000000000000] + Connection: [keep-alive] + Content-Type: [application/json] + User-Agent: [tiingo-python-client 0.5.0] + method: GET + uri: https://api.tiingo.com/tiingo/daily/GOOGL/prices?format=json&resampleFreq=daily&columns=volume + response: + body: {string: '[{"date":"2024-10-22T00:00:00+00:00","volume":16568121}]'} + headers: + Allow: ['GET, HEAD, OPTIONS'] + Content-Length: ['1001'] + Content-Type: [application/json] + Date: ['Wed, 23 Oct 2024 02:42:06 GMT'] + Server: [nginx/1.10.1] + Vary: ['Accept, Cookie'] + X-Frame-Options: [SAMEORIGIN] + status: {code: 200, message: OK} +version: 1 + + + diff --git a/tests/test_tiingo.py b/tests/test_tiingo.py index 5500141..4d56b5f 100644 --- a/tests/test_tiingo.py +++ b/tests/test_tiingo.py @@ -55,7 +55,7 @@ class TestTickerPrices(TestCase): def test_ticker_metadata_as_object(self): metadata = self._client.get_ticker_metadata("GOOGL", fmt="object") assert metadata.ticker == "GOOGL" # Access property via ATTRIBUTE - assert metadata.name # (contrast with key access above + assert metadata.name # (contrast with key access above @vcr.use_cassette('tests/fixtures/ticker_price.yaml') def test_ticker_price(self): @@ -68,7 +68,7 @@ class TestTickerPrices(TestCase): def test_ticker_price(self): """Test that weekly frequency works""" prices = self._client.get_ticker_price("GOOGL", startDate='2018-01-05', - endDate='2018-01-19', frequency='weekly') + endDate='2018-01-19', frequency='weekly') assert len(prices) == 3 assert prices[0].get('adjClose') @@ -98,6 +98,36 @@ class TestTickerPrices(TestCase): rows = list(reader) assert len(rows) > 2 # more than 1 day of data + @vcr.use_cassette('tests/fixtures/ticker_price_with_volume_column.yaml') + def test_ticker_price_with_volume_column(self): + """Confirm that requesting a single column works""" + prices = self._client.get_ticker_price("GOOGL", + columns="volume", + fmt='json') + assert len(prices) == 1 + assert prices[0].get('date') + assert not prices[0].get('high') + assert not prices[0].get('low') + assert not prices[0].get('open') + assert not prices[0].get('close') + assert prices[0].get('volume') + + @vcr.use_cassette('tests/fixtures/ticker_price_with_multiple_columns.yaml') + def test_ticker_price_with_multiple_columns(self): + """Confirm that requesting specific columns works""" + requested_columns = "open,high,low,close,volume" + prices = self._client.get_ticker_price("GOOGL", + columns=requested_columns, + fmt='json') + assert len(prices) == 1 + assert len(prices[0]) == len(requested_columns.split(',')) + 1 + assert prices[0].get('date') + assert prices[0].get('high') + assert prices[0].get('low') + assert prices[0].get('open') + assert prices[0].get('close') + assert prices[0].get('volume') + @vcr.use_cassette('tests/fixtures/intraday_price.yaml') def test_intraday_ticker_price(self): """Test the EOD Prices Endpoint with data param""" @@ -149,6 +179,7 @@ class TestTickerPrices(TestCase): endDate="2018-01-02", frequency="1.5mins") + # tiingo/news class TestNews(TestCase): @@ -227,6 +258,7 @@ class TestNews(TestCase): with self.assertRaises(RestClientError): assert self._client.get_bulk_news(file_id="1", fmt="object") + # FUNDAMENTALS ENDPOINTS class TestFundamentals(TestCase): diff --git a/tests/test_tiingo_pandas.py b/tests/test_tiingo_pandas.py index add9731..d2d2eae 100644 --- a/tests/test_tiingo_pandas.py +++ b/tests/test_tiingo_pandas.py @@ -114,6 +114,26 @@ class TestTiingoWithPython(TestCase): frequency="30Min") self.assertGreater(len(prices), 1) + @vcr.use_cassette('tests/fixtures/ticker_price_with_volume_column.yaml') + def test_get_dataframe_with_volume_column(self): + """Confirm that requesting a single column works""" + requested_column = "volume" + prices = self._client.get_dataframe("GOOGL", + columns=requested_column, + fmt='json') + assert len(prices) == 1 + assert len(prices.columns) == 1 + + @vcr.use_cassette('tests/fixtures/ticker_price_with_multiple_columns.yaml') + def test_get_dataframe_with_multiple_columns(self): + """Confirm that requesting specific columns works""" + requested_columns = "open,high,low,close,volume" + prices = self._client.get_dataframe("GOOGL", + columns=requested_columns, + fmt='json') + assert len(prices) == 1 + assert len(prices.columns) == len(requested_columns.split(',')) + def test_metric_name_column_error(self): with self.assertRaises(APIColumnNameError): self._client.get_dataframe(['GOOGL', 'AAPL'], startDate='2018-01-05', diff --git a/tiingo/api.py b/tiingo/api.py index 11f647c..dca1a22 100644 --- a/tiingo/api.py +++ b/tiingo/api.py @@ -219,7 +219,13 @@ class TiingoClient(RestClient): return prices def get_ticker_price( - self, ticker, startDate=None, endDate=None, fmt="json", frequency="daily" + self, + ticker, + startDate=None, + endDate=None, + columns=None, + fmt="json", + frequency="daily", ): """By default, return latest EOD Composite Price for a stock ticker. On average, each feed contains 3 data sources. @@ -231,6 +237,8 @@ class TiingoClient(RestClient): ticker (string): Unique identifier for stock ticker startDate (string): Start of ticker range in YYYY-MM-DD format endDate (string): End of ticker range in YYYY-MM-DD format + columns (string): Optional comma separated parameter specifying which columns to retrieve. + By default, 'date', 'open', 'close', 'high' and 'low' are retrieved. 'volume' is an extra option. fmt (string): 'csv' or 'json' frequency (string): Resample frequency """ @@ -244,6 +252,8 @@ class TiingoClient(RestClient): params["startDate"] = startDate if endDate: params["endDate"] = endDate + if columns: + params["columns"] = columns # TODO: evaluate whether to stream CSV to cache on disk, or # load as array in memory, or just pass plain text @@ -262,6 +272,7 @@ class TiingoClient(RestClient): startDate=None, endDate=None, metric_name=None, + columns=None, frequency="daily", fmt="json", ): @@ -278,6 +289,8 @@ class TiingoClient(RestClient): tickers (string/list): One or more unique identifiers for a stock ticker. startDate (string): Start of ticker range in YYYY-MM-DD format. endDate (string): End of ticker range in YYYY-MM-DD format. + columns (string): Optional comma separated parameter specifying which columns to retrieve. + By default, 'date', 'open', 'close', 'high' and 'low' are retrieved. 'volume' is an extra option. metric_name (string): Optional parameter specifying metric to be returned for each ticker. In the event of a single ticker, this is optional and if not specified all of the available data will be returned. In the event of a list of tickers, @@ -315,6 +328,8 @@ class TiingoClient(RestClient): params["startDate"] = startDate if endDate: params["endDate"] = endDate + if columns: + params["columns"] = columns if pandas_is_installed: if type(tickers) is str: