Merge pull request #125 from dcwtx/issue#117

[Issue#117] Add IEX endpoint and support intraday frequencies
This commit is contained in:
Cameron Yick
2018-07-04 20:24:46 -04:00
committed by GitHub
6 changed files with 94 additions and 3 deletions

View File

@@ -13,3 +13,4 @@ Contributors
* Dmitry Budaev <condemil@gmail.com>
* Bharat Kalluri
* Stephen Clark <steveclarkcode@gmail.com>
* Davis Thames

View File

@@ -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
--------

24
tests/fixtures/intraday_price.yaml vendored Normal file
View File

@@ -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

View File

@@ -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):

View File

@@ -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',

View File

@@ -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 '<TiingoClient(url="{}")>'.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']