diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..5399958e --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,27 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.12" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs + # builder: "dirhtml" + # Fail on all warnings to avoid broken references + # fail_on_warning: true + + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt diff --git a/HISTORY.rst b/HISTORY.rst index 10e1e87a..946a5bee 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,9 +1,18 @@ ======= History ======= -0.16.0 (2024-XX - Unreleased) +0.17.0 (2025-XX - Unreleased) -------------------------------- +* WIP + + +0.16.0 (2025-04-05) +-------------------------------- + +* 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 66c5cba9..bb15735d 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/binder/requirements.txt b/binder/requirements.txt index 05f56ee7..32a210d0 100644 --- a/binder/requirements.txt +++ b/binder/requirements.txt @@ -1,5 +1,5 @@ # This file is used by mybinder.org to power the runnable examples. -tiingo==0.14.0 +tiingo==0.15.6 # You technically don't have to use tiingo with pandas, but # we included it since most data analysts will have it installed already. diff --git a/docs/requirements.txt b/docs/requirements.txt index da654963..d7086ba3 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1 @@ -tiingo==0.14.0 +tiingo==0.15.6 diff --git a/requirements_dev.txt b/requirements_dev.txt index 350b5260..fec4fcfe 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,14 +1,14 @@ -pip==24.0 +pip==24.3.1 bumpversion==0.6.0 -wheel==0.43.0 -watchdog==4.0.1 -flake8==7.0.0 -tox==4.15.0 -coverage==7.5.1 -cryptography==42.0.7 -Sphinx==7.3.7 -PyYAML==6.0.1 -pytest==8.2.1 +wheel==0.45.1 +watchdog==6.0.0 +flake8==7.1.1 +tox==4.23.0 +coverage==7.6.8 +cryptography==44.0.0 +Sphinx==8.1.3 +PyYAML==6.0.2 +pytest==8.3.4 vcrpy==2.1.1 -twine==5.1.0 -black==24.4.0 +twine==6.0.1 +black==24.10.0 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 00000000..352cc780 --- /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 00000000..fe8f1a74 --- /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 5500141c..4d56b5f8 100644 --- a/tests/test_tiingo.py +++ b/tests/test_tiingo.py @@ -55,7 +55,7 @@ def test_ticker_metadata(self): 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 @@ def test_ticker_price(self): 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 @@ def test_ticker_price_with_csv(self): 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 @@ def test_invalid_frequency_error(self): endDate="2018-01-02", frequency="1.5mins") + # tiingo/news class TestNews(TestCase): @@ -227,6 +258,7 @@ def test_news_bulk_as_objects(self): 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 add97319..d2d2eaee 100644 --- a/tests/test_tiingo_pandas.py +++ b/tests/test_tiingo_pandas.py @@ -114,6 +114,26 @@ def test_intraday_ticker_price(self): 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/__version__.py b/tiingo/__version__.py index 68f49816..89544a89 100644 --- a/tiingo/__version__.py +++ b/tiingo/__version__.py @@ -1,2 +1,2 @@ # -*- coding: utf-8 -*- -__version__ = "0.15.6" +__version__ = "0.16.0" diff --git a/tiingo/api.py b/tiingo/api.py index 11f647ce..72e0dae6 100644 --- a/tiingo/api.py +++ b/tiingo/api.py @@ -6,7 +6,6 @@ import os import re import sys -import pkg_resources from zipfile import ZipFile import requests @@ -19,6 +18,8 @@ MissingRequiredArgumentError, ) +from tiingo.__version__ import __version__ as VERSION + try: import pandas as pd @@ -26,8 +27,6 @@ except ImportError: pandas_is_installed = False -VERSION = pkg_resources.get_distribution("tiingo").version - # These methods enable python 2 + 3 compatibility. def get_zipfile_from_response(response): @@ -219,7 +218,13 @@ def _request_pandas(self, ticker, metric_name, params): 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 +236,8 @@ def get_ticker_price( 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 +251,8 @@ def get_ticker_price( 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 +271,7 @@ def get_dataframe( startDate=None, endDate=None, metric_name=None, + columns=None, frequency="daily", fmt="json", ): @@ -278,6 +288,8 @@ def get_dataframe( 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 +327,8 @@ def get_dataframe( params["startDate"] = startDate if endDate: params["endDate"] = endDate + if columns: + params["columns"] = columns if pandas_is_installed: if type(tickers) is str: diff --git a/tiingo/restclient.py b/tiingo/restclient.py index 869baade..4a03ee1d 100644 --- a/tiingo/restclient.py +++ b/tiingo/restclient.py @@ -8,6 +8,7 @@ # TODO: Possibly print HTTP json response if available? class RestClientError(Exception): "Wrapper around HTTP Errors" + pass