From e3f26f609b0003ec373116227c7cae79952b465c Mon Sep 17 00:00:00 2001
From: Davis Thames
Date: Wed, 9 May 2018 22:26:18 -0500
Subject: [PATCH 1/9] issue#85 initial commit
---
setup.py | 1 +
tests/test_tiingo_pandas.py | 57 ++++++++++++++++++++++++++++
tiingo/api.py | 76 ++++++++++++++++++++++++++++++++++++-
tools/api_key_tool.py | 21 +++++++---
4 files changed, 148 insertions(+), 7 deletions(-)
create mode 100644 tests/test_tiingo_pandas.py
diff --git a/setup.py b/setup.py
index 71a43d3..36fc155 100644
--- a/setup.py
+++ b/setup.py
@@ -57,6 +57,7 @@ setup(
packages=find_packages(include=[NAME]),
include_package_data=True,
install_requires=requirements,
+ extras_require={'pandas': ['pandas>=0.18']},
license="MIT license",
zip_safe=False,
keywords=['tiingo', 'finance', 'stocks'],
diff --git a/tests/test_tiingo_pandas.py b/tests/test_tiingo_pandas.py
new file mode 100644
index 0000000..76d415f
--- /dev/null
+++ b/tests/test_tiingo_pandas.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+"""Unit tests for pandas functionality in tiingo"""
+
+import vcr
+from unittest import TestCase
+from tiingo import TiingoClient
+from tiingo.api import APIColumnNameError
+try:
+ import pandas as pd
+ pandas_is_installed = True
+except ImportError:
+ pandas_is_installed = False
+
+
+class TestTiingoWithPython(TestCase):
+
+ def setUp(self):
+ if pandas_is_installed:
+ self._client = TiingoClient()
+ else:
+ self.skipTest("test_tiingo_pandas: Pandas not installed.")
+
+ @vcr.use_cassette('tests/fixtures/ticker_price_pandas_weekly.yaml')
+ def test_return_pandas_format(self):
+ """Test that valid pandas format is returned when specified"""
+ prices = self._client.get_dataframe("GOOGL", startDate='2018-01-05',
+ endDate='2018-01-19', frequency='weekly')
+ self.assertTrue(isinstance(prices, pd.DataFrame))
+
+ @vcr.use_cassette('tests/fixtures/ticker_price_pandas_weekly_multiple_tickers.yaml')
+ def test_return_pandas_format_multiple(self):
+ """Test that valid pandas format is returned when specified"""
+ tickers = ["GOOGL", "AAPL"]
+ 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)
+
+ @vcr.use_cassette('tests/fixtures/ticker_price_pandas_daily.yaml')
+ def test_return_pandas_daily(self):
+ """Test that valid pandas format is returned when specified"""
+ 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
+
+ def test_column_error(self):
+ with self.assertRaises(APIColumnNameError):
+ self._client.get_dataframe(['GOOGL', 'AAPL'], startDate='2018-01-05',
+ endDate='2018-01-19', metric_name='xopen', frequency='weekly')
+ @vcr.use_cassette('tests/fixtures/ticker_price_pandas_single.yaml')
+ def test_pandas_edge_case(self):
+ """Test single price/date being returned as a frame"""
+ prices = self._client.get_dataframe("GOOGL")
+ assert len(prices) == 1
+ assert len(prices.index) == 1
diff --git a/tiingo/api.py b/tiingo/api.py
index 697fd47..d3698ad 100644
--- a/tiingo/api.py
+++ b/tiingo/api.py
@@ -7,10 +7,13 @@ import csv
import json
from collections import namedtuple
from zipfile import ZipFile
-
-
from tiingo.restclient import RestClient
import requests
+try:
+ import pandas as pd
+ pandas_is_installed = True
+except ImportError:
+ pandas_is_installed = False
VERSION = pkg_resources.get_distribution("tiingo").version
@@ -44,6 +47,13 @@ def dict_to_object(item, object_name):
object_hook=lambda d:
namedtuple(object_name, fields)(*values))
+class InstallPandasException(Exception):
+ pass
+
+
+class APIColumnNameError(Exception):
+ pass
+
class TiingoClient(RestClient):
"""Class for managing interactions with the Tiingo REST API
@@ -60,6 +70,7 @@ class TiingoClient(RestClient):
api_key = self._config['api_key']
except KeyError:
api_key = os.environ.get('TIINGO_API_KEY')
+ self._api_key = api_key
if not(api_key):
raise RuntimeError("Tiingo API Key not provided. Please provide"
@@ -155,6 +166,67 @@ class TiingoClient(RestClient):
else:
return response.content.decode("utf-8")
+ def _build_url(self, stock, startDate, endDate, frequency):
+ url = "https://api.tiingo.com/tiingo/"
+ url += "daily/{}/prices?".format(stock)
+ if startDate is not None and endDate is None:
+ url += "&startDate={}".format(startDate)
+ if startDate is not None and endDate is not None:
+ url += "&startDate={}&endDate={}".format(startDate, endDate)
+ url += "&format=json&resampleFreq={}&token={}".format(frequency, self._api_key)
+ return url
+
+ def get_dataframe(self, tickers,
+ startDate=None, endDate=None, metric_name='adjClose', frequency='daily'):
+
+ """ Return a pandas.DataFrame of historical prices for one or more ticker symbols.
+
+ By default, return latest EOD Composite Price for a list of stock tickers.
+ On average, each feed contains 3 data sources.
+
+ Supported tickers + Available Day Ranges are here:
+ https://apimedia.tiingo.com/docs/tiingo/daily/supported_tickers.zip
+
+ Args:
+ 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.
+ 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,
+ this parameter is required.
+ frequency (string): Resample frequency (defaults to daily).
+ """
+
+ valid_columns = ['open', 'high', 'low', 'close', 'volume', 'adjOpen', 'adjHigh', 'adjLow',
+ 'adjClose', 'adjVolume', 'divCash', 'splitFactor']
+ 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'])
+ else:
+ if metric_name not in valid_columns:
+ raise APIColumnNameError('Valid data items are: '+str(valid_columns))
+ for stock in tickers:
+ url = self._build_url(stock, startDate, endDate, frequency)
+ df = pd.read_json(url)
+ 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)
+ return prices
+ else:
+ error_message = ("Pandas is not installed, but .get_ticker_price() was "
+ "called with fmt=pandas. In order to install tiingo with "
+ "pandas, reinstall with pandas as an optional dependency. \n"
+ "Install tiingo with pandas dependency: \'pip install tiingo[pandas]\'\n"
+ "Alternatively, just install pandas: pip install pandas.")
+ raise InstallPandasException(error_message)
+
# NEWS FEEDS
# tiingo/news
def get_news(self, tickers=[], tags=[], sources=[], startDate=None,
diff --git a/tools/api_key_tool.py b/tools/api_key_tool.py
index 3d92246..dd72f10 100755
--- a/tools/api_key_tool.py
+++ b/tools/api_key_tool.py
@@ -6,35 +6,46 @@ import re
import argparse
fixtures_directory = 'tests/fixtures/'
+
+# restclient api header configuration
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):
+
+def has_api_key(file_name):
"""
Detect whether the file contains an api key in the Token object that is not 40*'0'.
See issue #86.
:param file: path-to-file to check
:return: boolean
"""
- f = open(file, 'r')
+ f = open(file_name, 'r')
text = f.read()
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
-def remove_api_key(file):
+def remove_api_key(file_name):
"""
Change the api key in the Token object to 40*'0'. See issue #86.
:param file: path-to-file to change
"""
- with open(file, 'r') as fp:
+ with open(file_name, 'r') as fp:
text = fp.read()
text = re.sub(real_api_regex, zero_token_string, text)
- with open(file, 'w') as fp:
+ text = re.sub(pd_real_api_regex, pd_zero_token_string, text)
+ with open(file_name, 'w') as fp:
fp.write(text)
return
From 3c369e82080f8e590d146f6afce07050c6c6a40c Mon Sep 17 00:00:00 2001
From: Davis Thames
Date: Wed, 9 May 2018 22:27:52 -0500
Subject: [PATCH 2/9] issue#85 add test fixtures
---
tests/fixtures/ticker_price_pandas_daily.yaml | 22 ++++++++++
.../fixtures/ticker_price_pandas_single.yaml | 22 ++++++++++
.../fixtures/ticker_price_pandas_weekly.yaml | 22 ++++++++++
..._price_pandas_weekly_multiple_tickers.yaml | 42 +++++++++++++++++++
4 files changed, 108 insertions(+)
create mode 100644 tests/fixtures/ticker_price_pandas_daily.yaml
create mode 100644 tests/fixtures/ticker_price_pandas_single.yaml
create mode 100644 tests/fixtures/ticker_price_pandas_weekly.yaml
create mode 100644 tests/fixtures/ticker_price_pandas_weekly_multiple_tickers.yaml
diff --git a/tests/fixtures/ticker_price_pandas_daily.yaml b/tests/fixtures/ticker_price_pandas_daily.yaml
new file mode 100644
index 0000000..510c79b
--- /dev/null
+++ b/tests/fixtures/ticker_price_pandas_daily.yaml
@@ -0,0 +1,22 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Connection: [close]
+ Host: [api.tiingo.com]
+ User-Agent: [Python-urllib/3.6]
+ 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
+ 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']
+ 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_pandas_single.yaml b/tests/fixtures/ticker_price_pandas_single.yaml
new file mode 100644
index 0000000..636eb1f
--- /dev/null
+++ b/tests/fixtures/ticker_price_pandas_single.yaml
@@ -0,0 +1,22 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Connection: [close]
+ Host: [api.tiingo.com]
+ User-Agent: [Python-urllib/3.6]
+ method: GET
+ uri: https://api.tiingo.com/tiingo/daily/GOOGL/prices?&format=json&resampleFreq=daily&token=0000000000000000000000000000000000000000
+ 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}]'}
+ headers:
+ Allow: ['GET, HEAD, OPTIONS']
+ Connection: [close]
+ Content-Length: ['235']
+ Content-Type: [application/json]
+ Date: ['Thu, 10 May 2018 03:25:28 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_pandas_weekly.yaml b/tests/fixtures/ticker_price_pandas_weekly.yaml
new file mode 100644
index 0000000..0c49b0c
--- /dev/null
+++ b/tests/fixtures/ticker_price_pandas_weekly.yaml
@@ -0,0 +1,22 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Connection: [close]
+ Host: [api.tiingo.com]
+ User-Agent: [Python-urllib/3.6]
+ 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
+ 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']
+ 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_pandas_weekly_multiple_tickers.yaml b/tests/fixtures/ticker_price_pandas_weekly_multiple_tickers.yaml
new file mode 100644
index 0000000..c531d47
--- /dev/null
+++ b/tests/fixtures/ticker_price_pandas_weekly_multiple_tickers.yaml
@@ -0,0 +1,42 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Connection: [close]
+ Host: [api.tiingo.com]
+ User-Agent: [Python-urllib/3.6]
+ 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
+ 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']
+ Server: [nginx/1.10.1]
+ Vary: ['Accept, Cookie']
+ X-Frame-Options: [SAMEORIGIN]
+ status: {code: 200, message: OK}
+- request:
+ body: null
+ headers:
+ Connection: [close]
+ Host: [api.tiingo.com]
+ User-Agent: [Python-urllib/3.6]
+ 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
+ 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}]'}
+ headers:
+ Allow: ['GET, HEAD, OPTIONS']
+ Connection: [close]
+ Content-Length: ['786']
+ Content-Type: [application/json]
+ Date: ['Thu, 10 May 2018 03:25:30 GMT']
+ Server: [nginx/1.10.1]
+ Vary: ['Accept, Cookie']
+ X-Frame-Options: [SAMEORIGIN]
+ status: {code: 200, message: OK}
+version: 1
From 1296b2469bca639c48e27ba0ee6dbc88c483cf9d Mon Sep 17 00:00:00 2001
From: Davis Thames
Date: Fri, 11 May 2018 11:31:11 -0500
Subject: [PATCH 3/9] Added matrix of tests with and without pandas for Travis
---
.travis.yml | 9 +++++++--
tests/test_tiingo_pandas.py | 16 +++++++++++++++-
tools/install_pandas.sh | 6 ++++++
3 files changed, 28 insertions(+), 3 deletions(-)
create mode 100755 tools/install_pandas.sh
diff --git a/.travis.yml b/.travis.yml
index 64f7337..03f5f4b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,10 +6,11 @@ deploy:
distributions: sdist bdist_wheel
password:
secure: VH1rgFV5XD0k3cMZxJwetBnETrFolBaM0U4K9CKQts38LF7f4xA8muY2FFFyeqR7VF2MQhYgGMcaAQQx/gRY+7AdSnqMYvxZ7p3P81aF7ruE3KjXgV6oPkTX4LkUlZf+co1rGs59TJ4UDJQM73+9EiYD3MJ/G0KeyMnAak73HkRHWdypOGLrINLPyUuuUAxQ8k4UJWskEMXjMgKIlGdCcdbCWqso/lZqvQ+/bFfv5M+yhQK57JMlXfXsM3YMxUGY7IhYRhxHL3nCpxXacut2FBU6H1GCU8MuR2bJ/aNNdl8elNzCEZcRq8+s94yrI095HQvVW/tLWaNJ4ojZSsGhmzvC/xAB+VbCp7ZvI0vHPGEOQgR/bIkDa9uhuwCWtfM81VCPpBrXgwupgznxtsjFcmBSUOpTFLTW5dKoPDWOe5K4wHKd9IKbWwk+mAY+aczV4T0uMX/eKhEOWJS03cjIPN4qwdCy6zSnBWpJBoLR7QyYZKNL1MiyQ0toUsJO0ln6vaPTXOo0K7EJIxKVOYf3xR8kNsgWWe0Z4orSt56O8+v5OIYbDcSeY1hwklcMvQdGHlLyMx01D2v346mL532MHA24nRmXfDUz0ixx5rn7g4Tykvy2LBLYfrfhV4G6kvimTk5Y6I7Rif4y68+r+NHmBlZwUju0AsK0a+s5/XAg5Nc=
- provider: pypi
- user: hydrosquall
+# provider: pypi
+# user: hydrosquall
install:
+ - tools/install_pandas.sh
- python setup.py develop
- pip install -U pytest-cov codecov vcrpy
# - pip install -U tox-travis pytest
@@ -22,6 +23,10 @@ python:
- 2.7
# - 2.6
+env:
+ - WITH_PANDAS=false
+ - WITH_PANDAS=true
+
script:
- export TIINGO_API_KEY=0000000000000000000000000000000000000000
- py.test --cov=./tiingo
diff --git a/tests/test_tiingo_pandas.py b/tests/test_tiingo_pandas.py
index 76d415f..c9a5f97 100644
--- a/tests/test_tiingo_pandas.py
+++ b/tests/test_tiingo_pandas.py
@@ -4,7 +4,7 @@
import vcr
from unittest import TestCase
from tiingo import TiingoClient
-from tiingo.api import APIColumnNameError
+from tiingo.api import APIColumnNameError, InstallPandasException
try:
import pandas as pd
pandas_is_installed = True
@@ -55,3 +55,17 @@ class TestTiingoWithPython(TestCase):
prices = self._client.get_dataframe("GOOGL")
assert len(prices) == 1
assert len(prices.index) == 1
+
+
+class TestTiingoWithoutPython(TestCase):
+
+ def setUp(self):
+ if pandas_is_installed:
+ self.skipTest("test_tiingo_without_pandas: Pandas not installed.")
+ else:
+ self._client = TiingoClient()
+
+ @vcr.use_cassette('tests/fixtures/ticker_price_pandas_single.yaml')
+ def test_get_dataframe_without_pandas(self):
+ with self.assertRaises(InstallPandasException):
+ self._client.get_dataframe("GOOGL")
diff --git a/tools/install_pandas.sh b/tools/install_pandas.sh
new file mode 100755
index 0000000..98ca923
--- /dev/null
+++ b/tools/install_pandas.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+if $WITH_PANDAS
+then
+ pip install pandas
+ echo "pandas installed"
+fi
From a4147d8793df17ec4b5e25d0b510bd8bc6d8fc6d Mon Sep 17 00:00:00 2001
From: Davis Thames
Date: Fri, 11 May 2018 12:04:07 -0500
Subject: [PATCH 4/9] Removed comments
---
.travis.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 03f5f4b..b7114d8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,8 +6,8 @@ deploy:
distributions: sdist bdist_wheel
password:
secure: VH1rgFV5XD0k3cMZxJwetBnETrFolBaM0U4K9CKQts38LF7f4xA8muY2FFFyeqR7VF2MQhYgGMcaAQQx/gRY+7AdSnqMYvxZ7p3P81aF7ruE3KjXgV6oPkTX4LkUlZf+co1rGs59TJ4UDJQM73+9EiYD3MJ/G0KeyMnAak73HkRHWdypOGLrINLPyUuuUAxQ8k4UJWskEMXjMgKIlGdCcdbCWqso/lZqvQ+/bFfv5M+yhQK57JMlXfXsM3YMxUGY7IhYRhxHL3nCpxXacut2FBU6H1GCU8MuR2bJ/aNNdl8elNzCEZcRq8+s94yrI095HQvVW/tLWaNJ4ojZSsGhmzvC/xAB+VbCp7ZvI0vHPGEOQgR/bIkDa9uhuwCWtfM81VCPpBrXgwupgznxtsjFcmBSUOpTFLTW5dKoPDWOe5K4wHKd9IKbWwk+mAY+aczV4T0uMX/eKhEOWJS03cjIPN4qwdCy6zSnBWpJBoLR7QyYZKNL1MiyQ0toUsJO0ln6vaPTXOo0K7EJIxKVOYf3xR8kNsgWWe0Z4orSt56O8+v5OIYbDcSeY1hwklcMvQdGHlLyMx01D2v346mL532MHA24nRmXfDUz0ixx5rn7g4Tykvy2LBLYfrfhV4G6kvimTk5Y6I7Rif4y68+r+NHmBlZwUju0AsK0a+s5/XAg5Nc=
-# provider: pypi
-# user: hydrosquall
+ provider: pypi
+ user: hydrosquall
install:
- tools/install_pandas.sh
From deb5285a74b7e1bed66d30fe8451298d3d2978c0 Mon Sep 17 00:00:00 2001
From: Davis Thames
Date: Sat, 9 Jun 2018 16:26:48 -0500
Subject: [PATCH 5/9] corrected error
---
.travis.yml | 2 +-
tests/test_tiingo_pandas.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index b7114d8..2e30c13 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,9 +10,9 @@ deploy:
user: hydrosquall
install:
- - tools/install_pandas.sh
- python setup.py develop
- pip install -U pytest-cov codecov vcrpy
+ - tools/install_pandas.sh
# - pip install -U tox-travis pytest
language: python
diff --git a/tests/test_tiingo_pandas.py b/tests/test_tiingo_pandas.py
index c9a5f97..ee451eb 100644
--- a/tests/test_tiingo_pandas.py
+++ b/tests/test_tiingo_pandas.py
@@ -61,7 +61,7 @@ class TestTiingoWithoutPython(TestCase):
def setUp(self):
if pandas_is_installed:
- self.skipTest("test_tiingo_without_pandas: Pandas not installed.")
+ self.skipTest("test_tiingo_without_pandas: Pandas is installed.")
else:
self._client = TiingoClient()
From f7b3da50a751f854a290a0b80d3683141e31bfaf Mon Sep 17 00:00:00 2001
From: Davis Thames
Date: Sun, 10 Jun 2018 14:12:26 -0500
Subject: [PATCH 6/9] addressed comments
---
...icker_price_pandas_daily_metric_name.yaml} | 14 ++++---
.../fixtures/ticker_price_pandas_single.yaml | 18 +++++----
.../fixtures/ticker_price_pandas_weekly.yaml | 14 ++++---
..._price_pandas_weekly_multiple_tickers.yaml | 32 ++++++++-------
tests/test_tiingo_pandas.py | 16 ++++++--
tiingo/api.py | 39 +++++++++++++------
tools/api_key_tool.py | 9 -----
7 files changed, 85 insertions(+), 57 deletions(-)
rename tests/fixtures/{ticker_price_pandas_daily.yaml => ticker_price_pandas_daily_metric_name.yaml} (86%)
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
From a2b4d34ab141ff1cbdb416ce720d843134dd3208 Mon Sep 17 00:00:00 2001
From: Davis Thames
Date: Sun, 10 Jun 2018 14:13:28 -0500
Subject: [PATCH 7/9] reverted to prior indexing method
---
tests/test_tiingo_pandas.py | 3 ++-
tiingo/api.py | 12 ++----------
2 files changed, 4 insertions(+), 11 deletions(-)
diff --git a/tests/test_tiingo_pandas.py b/tests/test_tiingo_pandas.py
index f84e58d..f36123d 100644
--- a/tests/test_tiingo_pandas.py
+++ b/tests/test_tiingo_pandas.py
@@ -55,10 +55,11 @@ class TestTiingoWithPython(TestCase):
self.assertTrue(isinstance(prices, pd.Series))
assert len(prices.index) == 10
- def test_column_error(self):
+ def test_metric_name_column_error(self):
with self.assertRaises(APIColumnNameError):
self._client.get_dataframe(['GOOGL', 'AAPL'], startDate='2018-01-05',
endDate='2018-01-19', metric_name='xopen', frequency='weekly')
+
@vcr.use_cassette('tests/fixtures/ticker_price_pandas_single.yaml')
def test_pandas_edge_case(self):
"""Test single price/date being returned as a frame"""
diff --git a/tiingo/api.py b/tiingo/api.py
index ea865a5..427d217 100644
--- a/tiingo/api.py
+++ b/tiingo/api.py
@@ -166,16 +166,6 @@ class TiingoClient(RestClient):
else:
return response.content.decode("utf-8")
- def _build_url(self, stock, startDate, endDate, frequency):
- url = "https://api.tiingo.com/tiingo/"
- url += "daily/{}/prices?".format(stock)
- if startDate is not None and endDate is None:
- url += "&startDate={}".format(startDate)
- if startDate is not None and endDate is not None:
- url += "&startDate={}&endDate={}".format(startDate, endDate)
- url += "&format=json&resampleFreq={}&token={}".format(frequency, self._api_key)
- return url
-
def get_dataframe(self, tickers,
startDate=None, endDate=None, metric_name=None, frequency='daily'):
@@ -186,6 +176,7 @@ class TiingoClient(RestClient):
Supported tickers + Available Day Ranges are here:
https://apimedia.tiingo.com/docs/tiingo/daily/supported_tickers.zip
+ or from the TiingoClient.list_tickers() method.
Args:
tickers (string/list): One or more unique identifiers for a stock ticker.
@@ -221,6 +212,7 @@ class TiingoClient(RestClient):
df = pd.DataFrame(response.json())
if metric_name is not None:
prices = df[metric_name]
+ prices.index = df['date']
else:
prices = df
prices.index = df['date']
From 5de3306eeaca1f3462e82d94767e6452bda13bf4 Mon Sep 17 00:00:00 2001
From: Davis Thames
Date: Mon, 11 Jun 2018 11:59:13 -0500
Subject: [PATCH 8/9] updated for pandas method
---
README.rst | 38 +++++++++++++++++++++++++++++++++-----
1 file changed, 33 insertions(+), 5 deletions(-)
diff --git a/README.rst b/README.rst
index 4cd8a4b..b568cd4 100644
--- a/README.rst
+++ b/README.rst
@@ -25,7 +25,7 @@ Tiingo Python
Tiingo is a financial data platform that makes high quality financial tools available to all. Tiingo has a REST and Real-Time Data API, which this library helps you to access. Presently, the API includes support for the following endpoints:
* Stock Market Ticker Closing Prices + Metadata. Data includes full distribution details and is validated using a proprietary EOD Price Engine.
-* Curated news from top financial news sources + blogs. Stories are tagged with topic tags and relevant stock tickers by Tiingo's algorithms.
+* Curated news from top financial news sources + blogs. Stories are tagged with topic tags and relevant stock tickers by Tiingo's algorithms.
Usage
@@ -37,6 +37,12 @@ First, install the library from PyPi:
pip install tiingo
+If you prefer to receive your results in ``pandas DataFrame`` or ``Series`` format, and you do not already have pandas installed, install it as an optional dependency:
+
+.. code-block:: shell
+
+ pip install tiingo[pandas]
+
Next, initialize your client. It is recommended to use an environment
variable to initialize your client for convenience.
@@ -67,7 +73,7 @@ Alternately, you may use a dictionary to customize/authorize your client.
Now you can use ``TiingoClient`` to make your API calls. (Other parameters are available for each endpoint beyond what is used in the below examples, inspect the docstring for each function for details.).
.. code-block:: python
-
+
# Get Ticker
ticker_metadata = client.get_ticker_metadata("GOOGL")
@@ -86,13 +92,35 @@ Now you can use ``TiingoClient`` to make your API calls. (Other parameters are a
tickers = client.list_stock_tickers()
# Get news articles about given tickers or search terms from given domains
- articles = client.get_news(tickers=['GOOGL', 'APPL'],
- tags=['Laptops'],
+ articles = client.get_news(tickers=['GOOGL', 'AAPL'],
+ tags=['Laptops'],
sources=['washingtonpost.com'],
startDate='2017-01-01',
endDate='2017-08-31')
+To receive results in ``pandas`` format, use the ``get_dataframe()`` method:
+
+.. code-block:: python
+
+ #Get a pd.DataFrame of the price history of a single symbol (default is daily):
+ ticker_history = client.get_dataframe("GOOGL")
+
+ #The method returns all of the available information on a symbol, such as open, high, low, close, adjusted close, etc. This page in the tiingo api documentation lists the available information on each symbol: https://api.tiingo.com/docs/tiingo/daily#priceData.
+
+ #Frequencies and start and end dates can be specified similarly to the json method above.
+
+ #Get a pd.Series of only one column of the available response data by specifying one of the valid the 'metric_name' parameters:
+ ticker_history = client.get_dataframe("GOOGL", metric_name='adjClose')
+
+ #Get a pd.DataFrame for a list of symbols for a specified metric_name (default is adjClose if no metric_name is specified):
+ ticker_history = client.get_dataframe(['GOOGL', 'AAPL'],
+ frequency='weekly',
+ metric_name='volume',
+ startDate='2017-01-01',
+ endDate='2018-05-31')
+
+
Further Docs
--------
@@ -110,7 +138,7 @@ Roadmap:
--------
* Client-side validation of tickers
-* Data validation of returned responses
+* Data validation of returned responses
* Case insensitivity for ticker names
* More documentation / code examples
From faa93e4146d4d213cca924f3761b6f2d6e25e3f2 Mon Sep 17 00:00:00 2001
From: Davis Thames
Date: Mon, 11 Jun 2018 12:04:19 -0500
Subject: [PATCH 9/9] fixed wordwraps
---
README.rst | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/README.rst b/README.rst
index b568cd4..68e9d1d 100644
--- a/README.rst
+++ b/README.rst
@@ -106,14 +106,18 @@ To receive results in ``pandas`` format, use the ``get_dataframe()`` method:
#Get a pd.DataFrame of the price history of a single symbol (default is daily):
ticker_history = client.get_dataframe("GOOGL")
- #The method returns all of the available information on a symbol, such as open, high, low, close, adjusted close, etc. This page in the tiingo api documentation lists the available information on each symbol: https://api.tiingo.com/docs/tiingo/daily#priceData.
+ #The method returns all of the available information on a symbol, such as open, high, low, close,
+ #adjusted close, etc. This page in the tiingo api documentation lists the available information on each
+ #symbol: https://api.tiingo.com/docs/tiingo/daily#priceData.
#Frequencies and start and end dates can be specified similarly to the json method above.
- #Get a pd.Series of only one column of the available response data by specifying one of the valid the 'metric_name' parameters:
+ #Get a pd.Series of only one column of the available response data by specifying one of the valid the
+ #'metric_name' parameters:
ticker_history = client.get_dataframe("GOOGL", metric_name='adjClose')
- #Get a pd.DataFrame for a list of symbols for a specified metric_name (default is adjClose if no metric_name is specified):
+ #Get a pd.DataFrame for a list of symbols for a specified metric_name (default is adjClose if no
+ #metric_name is specified):
ticker_history = client.get_dataframe(['GOOGL', 'AAPL'],
frequency='weekly',
metric_name='volume',