diff --git a/AUTHORS.rst b/AUTHORS.rst index c21598e..c39b6a0 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -13,3 +13,4 @@ Contributors * Dmitry Budaev * Bharat Kalluri * Stephen Clark +* Davis Thames diff --git a/README.rst b/README.rst index 3e905f8..c209725 100644 --- a/README.rst +++ b/README.rst @@ -125,6 +125,8 @@ To receive results in ``pandas`` format, use the ``get_dataframe()`` method: 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". + Further Docs -------- diff --git a/tests/fixtures/intraday_price.yaml b/tests/fixtures/intraday_price.yaml new file mode 100644 index 0000000..ddcc0bc --- /dev/null +++ b/tests/fixtures/intraday_price.yaml @@ -0,0 +1,24 @@ +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.6.0] + method: GET + uri: https://api.tiingo.com/iex/GOOGL/prices?format=json&resampleFreq=30Min&startDate=2018-01-02&endDate=2018-01-02 + response: + body: {string: '[{"date":"2018-01-02T14:30:00.000Z","open":1057.47,"high":1061.91,"low":1054.17,"close":1061.91},{"date":"2018-01-02T15:00:00.000Z","open":1061.91,"high":1067.345,"low":1061.91,"close":1066.945},{"date":"2018-01-02T15:30:00.000Z","open":1067.29,"high":1070.47,"low":1065.71,"close":1070.26},{"date":"2018-01-02T16:00:00.000Z","open":1071.355,"high":1073.775,"low":1070.235,"close":1073.26},{"date":"2018-01-02T16:30:00.000Z","open":1073.26,"high":1074.33,"low":1073.1,"close":1073.905},{"date":"2018-01-02T17:00:00.000Z","open":1073.905,"high":1073.905,"low":1072.68,"close":1073.08},{"date":"2018-01-02T17:30:00.000Z","open":1073.54,"high":1073.605,"low":1072.16,"close":1072.335},{"date":"2018-01-02T18:00:00.000Z","open":1073.0,"high":1073.86,"low":1071.89,"close":1073.86},{"date":"2018-01-02T18:30:00.000Z","open":1073.73,"high":1074.37,"low":1072.54,"close":1072.97},{"date":"2018-01-02T19:00:00.000Z","open":1073.15,"high":1074.93,"low":1072.705,"close":1072.705},{"date":"2018-01-02T19:30:00.000Z","open":1072.06,"high":1074.07,"low":1071.655,"close":1074.07},{"date":"2018-01-02T20:00:00.000Z","open":1074.55,"high":1075.08,"low":1072.78,"close":1073.83},{"date":"2018-01-02T20:30:00.000Z","open":1073.83,"high":1075.87,"low":1073.23,"close":1073.315}]'} + headers: + Allow: ['GET, HEAD, OPTIONS'] + Content-Length: ['1261'] + Content-Type: [application/json] + Date: ['Wed, 04 Jul 2018 02:53:33 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 4a0c2f1..0d0da23 100644 --- a/tests/test_tiingo.py +++ b/tests/test_tiingo.py @@ -6,6 +6,7 @@ from unittest import TestCase import vcr from tiingo import TiingoClient +from tiingo.api import InvalidFrequencyError from tiingo.restclient import RestClientError @@ -97,6 +98,15 @@ class TestTickerPrices(TestCase): rows = list(reader) assert len(rows) > 2 # more than 1 day of data + @vcr.use_cassette('tests/fixtures/intraday_price.yaml') + def test_intraday_ticker_price(self): + """Test the EOD Prices Endpoint with data param""" + prices = self._client.get_ticker_price("GOOGL", + startDate="2018-01-02", + endDate="2018-01-02", + frequency="30Min") + self.assertGreater(len(prices), 1) + @vcr.use_cassette('tests/fixtures/list_stock_tickers.yaml') def test_list_stock_tickers(self): tickers = self._client.list_stock_tickers() @@ -115,6 +125,13 @@ class TestTickerPrices(TestCase): assert len(tickers) > 1 assert all(ticker['assetType'] == 'ETF' for ticker in tickers) + def test_invalid_frequency_error(self): + with self.assertRaises(InvalidFrequencyError): + prices = self._client.get_ticker_price("GOOGL", + startDate="2018-01-02", + endDate="2018-01-02", + frequency="1.5mins") + # tiingo/news class TestNews(TestCase): diff --git a/tests/test_tiingo_pandas.py b/tests/test_tiingo_pandas.py index f36123d..54ed91e 100644 --- a/tests/test_tiingo_pandas.py +++ b/tests/test_tiingo_pandas.py @@ -55,6 +55,15 @@ class TestTiingoWithPython(TestCase): self.assertTrue(isinstance(prices, pd.Series)) assert len(prices.index) == 10 + @vcr.use_cassette('tests/fixtures/intraday_price.yaml') + def test_intraday_ticker_price(self): + """Test the EOD Prices Endpoint with data param""" + prices = self._client.get_dataframe("GOOGL", + startDate="2018-01-02", + endDate="2018-01-02", + frequency="30Min") + self.assertGreater(len(prices), 1) + 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 427d217..f2ec61b 100644 --- a/tiingo/api.py +++ b/tiingo/api.py @@ -9,6 +9,8 @@ from collections import namedtuple from zipfile import ZipFile from tiingo.restclient import RestClient import requests +import re + try: import pandas as pd pandas_is_installed = True @@ -55,6 +57,10 @@ class APIColumnNameError(Exception): pass +class InvalidFrequencyError(Exception): + pass + + class TiingoClient(RestClient): """Class for managing interactions with the Tiingo REST API @@ -82,9 +88,14 @@ class TiingoClient(RestClient): 'User-Agent': 'tiingo-python-client {}'.format(VERSION) } + self._frequency_pattern = re.compile('^[0-9]+(min|hour)$', re.IGNORECASE) + def __repr__(self): return ''.format(self._base_url) + def _is_eod_frequency(self,frequency): + return frequency.lower() in ['daily', 'weekly', 'monthly', 'annually'] + # TICKER PRICE ENDPOINTS # https://api.tiingo.com/docs/tiingo/daily def list_tickers(self, assetType): @@ -128,6 +139,33 @@ class TiingoClient(RestClient): elif fmt == 'object': return dict_to_object(data, "Ticker") + def _invalid_frequency(self, frequency): + """ + Check to see that frequency was specified correctly + :param frequency (string): frequency string + :return (boolean): + """ + is_valid = self._is_eod_frequency(frequency) or re.match(self._frequency_pattern, frequency) + return not is_valid + + def _get_url(self, ticker, frequency): + """ + Return url based on frequency. Daily, weekly, or yearly use Tiingo + EOD api; anything less than daily uses the iex intraday api. + :param ticker (string): ticker to be embedded in the url + :param frequency (string): valid frequency per Tiingo api + :return (string): url + """ + if self._invalid_frequency(frequency): + etext = ("Error: {} is an invalid frequency. Check Tiingo API documentation " + "for valid EOD or intraday frequency format.") + raise InvalidFrequencyError(etext.format(frequency)) + else: + if self._is_eod_frequency(frequency): + return "tiingo/daily/{}/prices".format(ticker) + else: + return "iex/{}/prices".format(ticker) + def get_ticker_price(self, ticker, startDate=None, endDate=None, fmt='json', frequency='daily'): @@ -144,7 +182,7 @@ class TiingoClient(RestClient): fmt (string): 'csv' or 'json' frequency (string): Resample frequency """ - url = "tiingo/daily/{}/prices".format(ticker) + url = self._get_url(ticker, frequency) params = { 'format': fmt if fmt != "object" else 'json', # conversion local 'resampleFreq': frequency @@ -207,7 +245,7 @@ class TiingoClient(RestClient): if pandas_is_installed: if type(tickers) is str: stock = tickers - url = "tiingo/daily/{}/prices".format(stock) + url = self._get_url(stock, frequency) response = self._request('GET', url, params=params) df = pd.DataFrame(response.json()) if metric_name is not None: @@ -220,7 +258,7 @@ class TiingoClient(RestClient): else: prices = pd.DataFrame() for stock in tickers: - url = "tiingo/daily/{}/prices".format(stock) + url = self._get_url(stock, frequency) response = self._request('GET', url, params=params) df = pd.DataFrame(response.json()) df.index = df['date']