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..bab5587 100644 --- a/tests/test_tiingo.py +++ b/tests/test_tiingo.py @@ -97,6 +97,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() 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..c8b7e22 100644 --- a/tiingo/api.py +++ b/tiingo/api.py @@ -55,6 +55,10 @@ class APIColumnNameError(Exception): pass +class InvalidFrequencyError(Exception): + pass + + class TiingoClient(RestClient): """Class for managing interactions with the Tiingo REST API @@ -128,6 +132,40 @@ 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): + """ + daily_freqs = ['daily', 'weekly', 'monthly', 'annually'] + intraday_freqs = ['min', 'hour'] + + if frequency.lower() in daily_freqs: + return False + elif intraday_freqs[0] in frequency.lower() or \ + intraday_freqs[1] in frequency.lower(): + return False + else: + return True + + def _get_url(self, frequency): + """ + Return url based on frequency. Daily, weekly, or yearly use Tiingo + EOD api; anything less than daily uses the iex intraday api. + :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 self.InvalidFrequencyError(etext.format(frequency)) + else: + if frequency.lower() in ['daily', 'weekly', 'monthly', 'annually']: + return "tiingo/daily/{}/prices" + else: + return "iex/{}/prices" + 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(frequency).format(ticker) 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(frequency).format(stock) 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(frequency).format(stock) response = self._request('GET', url, params=params) df = pd.DataFrame(response.json()) df.index = df['date']