mirror of
https://github.com/hydrosquall/tiingo-python.git
synced 2026-02-01 17:04:18 +01:00
Merge pull request #125 from dcwtx/issue#117
[Issue#117] Add IEX endpoint and support intraday frequencies
This commit is contained in:
@@ -13,3 +13,4 @@ Contributors
|
||||
* Dmitry Budaev <condemil@gmail.com>
|
||||
* Bharat Kalluri
|
||||
* Stephen Clark <steveclarkcode@gmail.com>
|
||||
* Davis Thames
|
||||
|
||||
@@ -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
24
tests/fixtures/intraday_price.yaml
vendored
Normal 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
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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']
|
||||
|
||||
Reference in New Issue
Block a user