diff --git a/tests/fixtures/ticker_price_pandas_daily.yaml b/tests/fixtures/ticker_price_pandas_daily_metric_name.yaml similarity index 86% rename from tests/fixtures/ticker_price_pandas_daily.yaml rename to tests/fixtures/ticker_price_pandas_daily_metric_name.yaml index 510c79b..e714724 100644 --- a/tests/fixtures/ticker_price_pandas_daily.yaml +++ b/tests/fixtures/ticker_price_pandas_daily_metric_name.yaml @@ -2,19 +2,21 @@ interactions: - request: body: null headers: - Connection: [close] - Host: [api.tiingo.com] - User-Agent: [Python-urllib/3.6] + 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/tiingo/daily/GOOGL/prices?&startDate=2018-01-05&endDate=2018-01-19&format=json&resampleFreq=daily&token=0000000000000000000000000000000000000000 + uri: https://api.tiingo.com/tiingo/daily/GOOGL/prices?format=json&resampleFreq=daily&startDate=2018-01-05&endDate=2018-01-19 response: body: {string: '[{"date":"2018-01-05T00:00:00.000Z","close":1110.29,"high":1113.58,"low":1101.8,"open":1103.45,"volume":1493389,"adjClose":1110.29,"adjHigh":1113.58,"adjLow":1101.8,"adjOpen":1103.45,"adjVolume":1493389,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-08T00:00:00.000Z","close":1114.21,"high":1119.16,"low":1110.0,"open":1111.0,"volume":1148958,"adjClose":1114.21,"adjHigh":1119.16,"adjLow":1110.0,"adjOpen":1111.0,"adjVolume":1148958,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-09T00:00:00.000Z","close":1112.79,"high":1118.44,"low":1108.2,"open":1118.44,"volume":1335995,"adjClose":1112.79,"adjHigh":1118.44,"adjLow":1108.2,"adjOpen":1118.44,"adjVolume":1335995,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-10T00:00:00.000Z","close":1110.14,"high":1112.78,"low":1103.98,"open":1107.0,"volume":1027781,"adjClose":1110.14,"adjHigh":1112.78,"adjLow":1103.98,"adjOpen":1107.0,"adjVolume":1027781,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-11T00:00:00.000Z","close":1112.05,"high":1114.85,"low":1106.48,"open":1112.31,"volume":1102461,"adjClose":1112.05,"adjHigh":1114.85,"adjLow":1106.48,"adjOpen":1112.31,"adjVolume":1102461,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-12T00:00:00.000Z","close":1130.65,"high":1131.3,"low":1108.01,"open":1110.1,"volume":1914460,"adjClose":1130.65,"adjHigh":1131.3,"adjLow":1108.01,"adjOpen":1110.1,"adjVolume":1914460,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-16T00:00:00.000Z","close":1130.7,"high":1148.88,"low":1126.66,"open":1140.31,"volume":1783881,"adjClose":1130.7,"adjHigh":1148.88,"adjLow":1126.66,"adjOpen":1140.31,"adjVolume":1783881,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-17T00:00:00.000Z","close":1139.1,"high":1139.32,"low":1123.49,"open":1136.36,"volume":1353097,"adjClose":1139.1,"adjHigh":1139.32,"adjLow":1123.49,"adjOpen":1136.36,"adjVolume":1353097,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-18T00:00:00.000Z","close":1135.97,"high":1140.59,"low":1124.46,"open":1139.35,"volume":1333633,"adjClose":1135.97,"adjHigh":1140.59,"adjLow":1124.46,"adjOpen":1139.35,"adjVolume":1333633,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-19T00:00:00.000Z","close":1143.5,"high":1143.78,"low":1132.5,"open":1138.03,"volume":1418376,"adjClose":1143.5,"adjHigh":1143.78,"adjLow":1132.5,"adjOpen":1138.03,"adjVolume":1418376,"divCash":0.0,"splitFactor":1.0}]'} headers: Allow: ['GET, HEAD, OPTIONS'] - Connection: [close] Content-Length: ['2349'] Content-Type: [application/json] - Date: ['Thu, 10 May 2018 03:25:28 GMT'] + Date: ['Sun, 10 Jun 2018 18:24:41 GMT'] Server: [nginx/1.10.1] Vary: ['Accept, Cookie'] X-Frame-Options: [SAMEORIGIN] diff --git a/tests/fixtures/ticker_price_pandas_single.yaml b/tests/fixtures/ticker_price_pandas_single.yaml index 636eb1f..9320539 100644 --- a/tests/fixtures/ticker_price_pandas_single.yaml +++ b/tests/fixtures/ticker_price_pandas_single.yaml @@ -2,19 +2,21 @@ interactions: - request: body: null headers: - Connection: [close] - Host: [api.tiingo.com] - User-Agent: [Python-urllib/3.6] + 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/tiingo/daily/GOOGL/prices?&format=json&resampleFreq=daily&token=0000000000000000000000000000000000000000 + uri: https://api.tiingo.com/tiingo/daily/GOOGL/prices?format=json&resampleFreq=daily response: - body: {string: '[{"adjClose":1088.95,"adjHigh":1094.0,"adjLow":1062.11,"adjOpen":1064.1,"adjVolume":2357979,"close":1088.95,"date":"2018-05-09T00:00:00+00:00","divCash":0.0,"high":1094.0,"low":1062.11,"open":1064.1,"splitFactor":1.0,"volume":2357979}]'} + body: {string: '[{"adjClose":1132.71,"adjHigh":1138.78,"adjLow":1123.23,"adjOpen":1131.21,"adjVolume":1364226,"close":1132.71,"date":"2018-06-08T00:00:00+00:00","divCash":0.0,"high":1138.78,"low":1123.23,"open":1131.21,"splitFactor":1.0,"volume":1364226}]'} headers: Allow: ['GET, HEAD, OPTIONS'] - Connection: [close] - Content-Length: ['235'] + Content-Length: ['239'] Content-Type: [application/json] - Date: ['Thu, 10 May 2018 03:25:28 GMT'] + Date: ['Sun, 10 Jun 2018 18:21:17 GMT'] Server: [nginx/1.10.1] Vary: ['Accept, Cookie'] X-Frame-Options: [SAMEORIGIN] diff --git a/tests/fixtures/ticker_price_pandas_weekly.yaml b/tests/fixtures/ticker_price_pandas_weekly.yaml index 0c49b0c..59886a1 100644 --- a/tests/fixtures/ticker_price_pandas_weekly.yaml +++ b/tests/fixtures/ticker_price_pandas_weekly.yaml @@ -2,19 +2,21 @@ interactions: - request: body: null headers: - Connection: [close] - Host: [api.tiingo.com] - User-Agent: [Python-urllib/3.6] + 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/tiingo/daily/GOOGL/prices?&startDate=2018-01-05&endDate=2018-01-19&format=json&resampleFreq=weekly&token=0000000000000000000000000000000000000000 + uri: https://api.tiingo.com/tiingo/daily/GOOGL/prices?format=json&resampleFreq=weekly&startDate=2018-01-05&endDate=2018-01-19 response: body: {string: '[{"date":"2018-01-05T00:00:00.000Z","close":1110.29,"high":1113.58,"low":1053.02,"open":1053.02,"volume":5889084,"adjClose":1110.29,"adjHigh":1113.58,"adjLow":1053.02,"adjOpen":1053.02,"adjVolume":5889084,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-12T00:00:00.000Z","close":1130.65,"high":1131.3,"low":1103.98,"open":1111.0,"volume":6529655,"adjClose":1130.65,"adjHigh":1131.3,"adjLow":1103.98,"adjOpen":1111.0,"adjVolume":6529655,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-19T00:00:00.000Z","close":1135.97,"high":1148.88,"low":1123.49,"open":1140.31,"volume":4470611,"adjClose":1135.97,"adjHigh":1148.88,"adjLow":1123.49,"adjOpen":1140.31,"adjVolume":4470611,"divCash":0.0,"splitFactor":1.0}]'} headers: Allow: ['GET, HEAD, OPTIONS'] - Connection: [close] Content-Length: ['708'] Content-Type: [application/json] - Date: ['Thu, 10 May 2018 03:25:29 GMT'] + Date: ['Sun, 10 Jun 2018 18:25:46 GMT'] Server: [nginx/1.10.1] Vary: ['Accept, Cookie'] X-Frame-Options: [SAMEORIGIN] diff --git a/tests/fixtures/ticker_price_pandas_weekly_multiple_tickers.yaml b/tests/fixtures/ticker_price_pandas_weekly_multiple_tickers.yaml index c531d47..3247319 100644 --- a/tests/fixtures/ticker_price_pandas_weekly_multiple_tickers.yaml +++ b/tests/fixtures/ticker_price_pandas_weekly_multiple_tickers.yaml @@ -2,19 +2,21 @@ interactions: - request: body: null headers: - Connection: [close] - Host: [api.tiingo.com] - User-Agent: [Python-urllib/3.6] + 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/tiingo/daily/GOOGL/prices?&startDate=2018-01-05&endDate=2018-01-19&format=json&resampleFreq=weekly&token=0000000000000000000000000000000000000000 + uri: https://api.tiingo.com/tiingo/daily/GOOGL/prices?format=json&resampleFreq=weekly&startDate=2018-01-05&endDate=2018-01-19 response: body: {string: '[{"date":"2018-01-05T00:00:00.000Z","close":1110.29,"high":1113.58,"low":1053.02,"open":1053.02,"volume":5889084,"adjClose":1110.29,"adjHigh":1113.58,"adjLow":1053.02,"adjOpen":1053.02,"adjVolume":5889084,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-12T00:00:00.000Z","close":1130.65,"high":1131.3,"low":1103.98,"open":1111.0,"volume":6529655,"adjClose":1130.65,"adjHigh":1131.3,"adjLow":1103.98,"adjOpen":1111.0,"adjVolume":6529655,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-19T00:00:00.000Z","close":1135.97,"high":1148.88,"low":1123.49,"open":1140.31,"volume":4470611,"adjClose":1135.97,"adjHigh":1148.88,"adjLow":1123.49,"adjOpen":1140.31,"adjVolume":4470611,"divCash":0.0,"splitFactor":1.0}]'} headers: Allow: ['GET, HEAD, OPTIONS'] - Connection: [close] Content-Length: ['708'] Content-Type: [application/json] - Date: ['Thu, 10 May 2018 03:25:29 GMT'] + Date: ['Sun, 10 Jun 2018 18:33:00 GMT'] Server: [nginx/1.10.1] Vary: ['Accept, Cookie'] X-Frame-Options: [SAMEORIGIN] @@ -22,19 +24,21 @@ interactions: - request: body: null headers: - Connection: [close] - Host: [api.tiingo.com] - User-Agent: [Python-urllib/3.6] + 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/tiingo/daily/AAPL/prices?&startDate=2018-01-05&endDate=2018-01-19&format=json&resampleFreq=weekly&token=0000000000000000000000000000000000000000 + uri: https://api.tiingo.com/tiingo/daily/AAPL/prices?format=json&resampleFreq=weekly&startDate=2018-01-05&endDate=2018-01-19 response: - body: {string: '[{"date":"2018-01-05T00:00:00.000Z","close":175.0,"high":175.37,"low":169.26,"open":170.16,"volume":99095223,"adjClose":174.297949567,"adjHigh":174.6664652318,"adjLow":168.5809768212,"adjOpen":169.4773662761,"adjVolume":99095223,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-12T00:00:00.000Z","close":177.09,"high":177.36,"low":173.0,"open":174.35,"volume":107548622,"adjClose":176.379565079,"adjHigh":176.6484819154,"adjLow":172.3059730005,"adjOpen":173.6505571829,"adjVolume":107548622,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-19T00:00:00.000Z","close":179.26,"high":180.1,"low":175.07,"open":177.9,"volume":92146251,"adjClose":178.5408596536,"adjHigh":179.3774898115,"adjLow":174.3676687468,"adjOpen":177.1863155884,"adjVolume":92146251,"divCash":0.0,"splitFactor":1.0}]'} + body: {string: '[{"date":"2018-01-05T00:00:00.000Z","close":175.0,"high":175.37,"low":169.26,"open":170.16,"volume":99095223,"adjClose":173.6258731716,"adjHigh":173.9929678748,"adjLow":167.9309445315,"adjOpen":168.8238775936,"adjVolume":99095223,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-12T00:00:00.000Z","close":177.09,"high":177.36,"low":173.0,"open":174.35,"volume":107548622,"adjClose":175.6994621711,"adjHigh":175.9673420898,"adjLow":171.6415774782,"adjOpen":172.9809770712,"adjVolume":107548622,"divCash":0.0,"splitFactor":1.0},{"date":"2018-01-19T00:00:00.000Z","close":179.26,"high":180.1,"low":175.07,"open":177.9,"volume":92146251,"adjClose":177.8524229985,"adjHigh":178.6858271897,"adjLow":173.6953235208,"adjOpen":176.503101927,"adjVolume":92146251,"divCash":0.0,"splitFactor":1.0}]'} headers: Allow: ['GET, HEAD, OPTIONS'] - Connection: [close] - Content-Length: ['786'] + Content-Length: ['787'] Content-Type: [application/json] - Date: ['Thu, 10 May 2018 03:25:30 GMT'] + Date: ['Sun, 10 Jun 2018 18:33:01 GMT'] Server: [nginx/1.10.1] Vary: ['Accept, Cookie'] X-Frame-Options: [SAMEORIGIN] diff --git a/tests/test_tiingo_pandas.py b/tests/test_tiingo_pandas.py index ee451eb..f84e58d 100644 --- a/tests/test_tiingo_pandas.py +++ b/tests/test_tiingo_pandas.py @@ -26,6 +26,7 @@ class TestTiingoWithPython(TestCase): prices = self._client.get_dataframe("GOOGL", startDate='2018-01-05', endDate='2018-01-19', frequency='weekly') self.assertTrue(isinstance(prices, pd.DataFrame)) + assert len(prices.index) == 3 @vcr.use_cassette('tests/fixtures/ticker_price_pandas_weekly_multiple_tickers.yaml') def test_return_pandas_format_multiple(self): @@ -34,8 +35,8 @@ class TestTiingoWithPython(TestCase): prices = self._client.get_dataframe(tickers, startDate='2018-01-05', endDate='2018-01-19', metric_name='adjClose', frequency='weekly') self.assertTrue(isinstance(prices, pd.DataFrame)) - assert prices['GOOGL'].loc['2018-01-05'] == 1110.29 - self.assertAlmostEqual(prices['AAPL'].loc['2018-01-19'], 178.54, 2) + assert len(prices.columns) == 2 + assert len(prices.index) == 3 @vcr.use_cassette('tests/fixtures/ticker_price_pandas_daily.yaml') def test_return_pandas_daily(self): @@ -43,7 +44,16 @@ class TestTiingoWithPython(TestCase): prices = self._client.get_dataframe("GOOGL", startDate='2018-01-05', endDate='2018-01-19', frequency='daily') self.assertTrue(isinstance(prices, pd.DataFrame)) - assert prices['adjClose'].loc['2018-01-05'] == 1110.29 + assert len(prices.columns) == 12 + + @vcr.use_cassette('tests/fixtures/ticker_price_pandas_daily_metric_name.yaml') + def test_return_pandas_daily(self): + """Test that one column is returned when a metric name is specified""" + + prices = self._client.get_dataframe("GOOGL", startDate='2018-01-05', metric_name='adjClose', + endDate='2018-01-19', frequency='daily') + self.assertTrue(isinstance(prices, pd.Series)) + assert len(prices.index) == 10 def test_column_error(self): with self.assertRaises(APIColumnNameError): diff --git a/tiingo/api.py b/tiingo/api.py index d3698ad..ea865a5 100644 --- a/tiingo/api.py +++ b/tiingo/api.py @@ -177,7 +177,7 @@ class TiingoClient(RestClient): return url def get_dataframe(self, tickers, - startDate=None, endDate=None, metric_name='adjClose', frequency='daily'): + startDate=None, endDate=None, metric_name=None, frequency='daily'): """ Return a pandas.DataFrame of historical prices for one or more ticker symbols. @@ -200,24 +200,41 @@ class TiingoClient(RestClient): valid_columns = ['open', 'high', 'low', 'close', 'volume', 'adjOpen', 'adjHigh', 'adjLow', 'adjClose', 'adjVolume', 'divCash', 'splitFactor'] + + if metric_name is not None and metric_name not in valid_columns: + raise APIColumnNameError('Valid data items are: ' + str(valid_columns)) + + params = { + 'format': 'json', + 'resampleFreq': frequency + } + if startDate: + params['startDate'] = startDate + if endDate: + params['endDate'] = endDate + if pandas_is_installed: - prices = pd.DataFrame() if type(tickers) is str: stock = tickers - url = self._build_url(stock, startDate, endDate, frequency) - prices = pd.read_json(url) - prices.index = prices['date'] - del(prices['date']) + url = "tiingo/daily/{}/prices".format(stock) + response = self._request('GET', url, params=params) + df = pd.DataFrame(response.json()) + if metric_name is not None: + prices = df[metric_name] + else: + prices = df + prices.index = df['date'] + del (prices['date']) else: - if metric_name not in valid_columns: - raise APIColumnNameError('Valid data items are: '+str(valid_columns)) + prices = pd.DataFrame() for stock in tickers: - url = self._build_url(stock, startDate, endDate, frequency) - df = pd.read_json(url) + url = "tiingo/daily/{}/prices".format(stock) + response = self._request('GET', url, params=params) + df = pd.DataFrame(response.json()) df.index = df['date'] df.rename(index=str, columns={metric_name: stock}, inplace=True) prices = pd.concat([prices, df[stock]], axis=1) - prices.index = pd.to_datetime(prices.index) + prices.index = pd.to_datetime(prices.index) return prices else: error_message = ("Pandas is not installed, but .get_ticker_price() was " diff --git a/tools/api_key_tool.py b/tools/api_key_tool.py index dd72f10..04bea0e 100755 --- a/tools/api_key_tool.py +++ b/tools/api_key_tool.py @@ -12,11 +12,6 @@ zero_api_regex = r'(\[Token )0{40}(\])' real_api_regex = r'(\[Token ).{40}(\])' zero_token_string = '[Token ' + 40 * '0' + ']' -# pandas json api call configuration -pd_real_api_regex = r'&token=.{40}' -pd_zero_api_regex = r'&token=0{40}' -pd_zero_token_string = '&token=' + 40 * '0' - def has_api_key(file_name): """ @@ -30,9 +25,6 @@ def has_api_key(file_name): if re.search(real_api_regex, text) is not None and \ re.search(zero_api_regex, text) is None: return True - elif re.search(pd_real_api_regex, text) is not None and \ - re.search(pd_zero_api_regex, text) is None: - return True return False @@ -44,7 +36,6 @@ def remove_api_key(file_name): with open(file_name, 'r') as fp: text = fp.read() text = re.sub(real_api_regex, zero_token_string, text) - text = re.sub(pd_real_api_regex, pd_zero_token_string, text) with open(file_name, 'w') as fp: fp.write(text) return