From ec15ef85d6bd8dfb57288299e6d1f4fc3beff275 Mon Sep 17 00:00:00 2001
From: Davis Thames
Date: Tue, 3 Jul 2018 23:09:15 -0400
Subject: [PATCH 1/3] Initial commit
---
README.rst | 2 ++
tests/fixtures/intraday_price.yaml | 24 ++++++++++++++++
tests/test_tiingo.py | 9 ++++++
tests/test_tiingo_pandas.py | 9 ++++++
tiingo/api.py | 44 ++++++++++++++++++++++++++++--
5 files changed, 85 insertions(+), 3 deletions(-)
create mode 100644 tests/fixtures/intraday_price.yaml
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']
From 3fbb6e512103869c13737ae718db982abecf8378 Mon Sep 17 00:00:00 2001
From: Davis Thames
Date: Wed, 4 Jul 2018 07:34:50 -0400
Subject: [PATCH 2/3] Updated frequency error check
---
tests/test_tiingo.py | 8 ++++++++
tiingo/api.py | 12 ++++++------
2 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/tests/test_tiingo.py b/tests/test_tiingo.py
index bab5587..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
@@ -124,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/tiingo/api.py b/tiingo/api.py
index c8b7e22..dead54f 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
@@ -86,6 +88,8 @@ 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)
@@ -139,12 +143,8 @@ class TiingoClient(RestClient):
: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():
+ if frequency.lower() in daily_freqs or re.match(self._frequency_pattern, frequency):
return False
else:
return True
@@ -159,7 +159,7 @@ class TiingoClient(RestClient):
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))
+ raise InvalidFrequencyError(etext.format(frequency))
else:
if frequency.lower() in ['daily', 'weekly', 'monthly', 'annually']:
return "tiingo/daily/{}/prices"
From 7672b2f2facce28b5b757fdb090a1c2b3dfe1c6e Mon Sep 17 00:00:00 2001
From: Davis Thames
Date: Wed, 4 Jul 2018 13:24:04 -0400
Subject: [PATCH 3/3] Requested changes
---
AUTHORS.rst | 1 +
tiingo/api.py | 28 ++++++++++++++--------------
2 files changed, 15 insertions(+), 14 deletions(-)
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/tiingo/api.py b/tiingo/api.py
index dead54f..f2ec61b 100644
--- a/tiingo/api.py
+++ b/tiingo/api.py
@@ -88,11 +88,14 @@ class TiingoClient(RestClient):
'User-Agent': 'tiingo-python-client {}'.format(VERSION)
}
- self._frequency_pattern = re.compile('^[0-9]+(min)$|(hour)$', re.IGNORECASE)
+ 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):
@@ -142,17 +145,14 @@ class TiingoClient(RestClient):
:param frequency (string): frequency string
:return (boolean):
"""
- daily_freqs = ['daily', 'weekly', 'monthly', 'annually']
+ is_valid = self._is_eod_frequency(frequency) or re.match(self._frequency_pattern, frequency)
+ return not is_valid
- if frequency.lower() in daily_freqs or re.match(self._frequency_pattern, frequency):
- return False
- else:
- return True
-
- def _get_url(self, frequency):
+ 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
"""
@@ -161,10 +161,10 @@ class TiingoClient(RestClient):
"for valid EOD or intraday frequency format.")
raise InvalidFrequencyError(etext.format(frequency))
else:
- if frequency.lower() in ['daily', 'weekly', 'monthly', 'annually']:
- return "tiingo/daily/{}/prices"
+ if self._is_eod_frequency(frequency):
+ return "tiingo/daily/{}/prices".format(ticker)
else:
- return "iex/{}/prices"
+ return "iex/{}/prices".format(ticker)
def get_ticker_price(self, ticker,
startDate=None, endDate=None,
@@ -182,7 +182,7 @@ class TiingoClient(RestClient):
fmt (string): 'csv' or 'json'
frequency (string): Resample frequency
"""
- url = self._get_url(frequency).format(ticker)
+ url = self._get_url(ticker, frequency)
params = {
'format': fmt if fmt != "object" else 'json', # conversion local
'resampleFreq': frequency
@@ -245,7 +245,7 @@ class TiingoClient(RestClient):
if pandas_is_installed:
if type(tickers) is str:
stock = tickers
- url = self._get_url(frequency).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:
@@ -258,7 +258,7 @@ class TiingoClient(RestClient):
else:
prices = pd.DataFrame()
for stock in tickers:
- url = self._get_url(frequency).format(stock)
+ url = self._get_url(stock, frequency)
response = self._request('GET', url, params=params)
df = pd.DataFrame(response.json())
df.index = df['date']