1import os
2
3import pandas as pd
4
5from pandas_datareader.base import _BaseReader
6
7
8def get_tiingo_symbols():
9    """
10    Get the set of stock symbols supported by Tiingo
11
12    Returns
13    -------
14    symbols : DataFrame
15        DataFrame with symbols (ticker), exchange, asset type, currency and
16        start and end dates
17
18    Notes
19    -----
20    Reads https://apimedia.tiingo.com/docs/tiingo/daily/supported_tickers.zip
21    """
22    url = "https://apimedia.tiingo.com/docs/tiingo/daily/supported_tickers.zip"
23    return pd.read_csv(url)
24
25
26class TiingoIEXHistoricalReader(_BaseReader):
27    """
28        Historical data from Tiingo on equities, ETFs and mutual funds,
29        with re-sampling capability. This query is limited to the last
30        1,000 bars based in the endDate. So the startDate is moved if
31        it goes past the limit.
32
33        Parameters
34        ----------
35        symbols : {str, List[str]}
36            String symbol or list of symbols
37        start : string, int, date, datetime, Timestamp
38            Starting date. Parses many different kind of date
39            representations (e.g., 'JAN-01-2010', '1/1/10', 'Jan, 1, 1980'). Defaults to
40            20 years before current date.
41        end : string, int, date, datetime, Timestamp
42            Ending date
43        retry_count : int, default 3
44            Number of times to retry query request.
45        pause : float, default 0.1
46            Time, in seconds, of the pause between retries.
47        session : Session, default None
48            requests.sessions.Session instance to be used
49        freq : {str, None}
50        Re-sample frequency. Format is # + (min/hour); e.g. "15min" or "4hour".
51        If no value is provided, defaults to 5min. The minimum value is "1min".
52        Units in minutes (min) and hours (hour) are accepted.
53        api_key : str, optional
54            Tiingo API key . If not provided the environmental variable
55            TIINGO_API_KEY is read. The API key is *required*.
56    """
57
58    def __init__(
59        self,
60        symbols,
61        start=None,
62        end=None,
63        retry_count=3,
64        pause=0.1,
65        timeout=30,
66        session=None,
67        freq=None,
68        api_key=None,
69    ):
70        super().__init__(
71            symbols, start, end, retry_count, pause, timeout, session, freq
72        )
73
74        if isinstance(self.symbols, str):
75            self.symbols = [self.symbols]
76        self._symbol = ""
77        if api_key is None:
78            api_key = os.getenv("TIINGO_API_KEY")
79        if not api_key or not isinstance(api_key, str):
80            raise ValueError(
81                "The tiingo API key must be provided either "
82                "through the api_key variable or through the "
83                "environmental variable TIINGO_API_KEY."
84            )
85        self.api_key = api_key
86        self._concat_axis = 0
87
88    @property
89    def url(self):
90        """API URL"""
91        _url = "https://api.tiingo.com/iex/{ticker}/prices"
92        return _url.format(ticker=self._symbol)
93
94    @property
95    def params(self):
96        """Parameters to use in API calls"""
97        return {
98            "startDate": self.start.strftime("%Y-%m-%d"),
99            "endDate": self.end.strftime("%Y-%m-%d"),
100            "resampleFreq": self.freq,
101            "format": "json",
102        }
103
104    def _get_crumb(self, *args):
105        pass
106
107    def _read_one_data(self, url, params):
108        """ read one data from specified URL """
109        headers = {
110            "Content-Type": "application/json",
111            "Authorization": "Token " + self.api_key,
112        }
113        out = self._get_response(url, params=params, headers=headers).json()
114        return self._read_lines(out)
115
116    def _read_lines(self, out):
117        df = pd.DataFrame(out)
118        df["symbol"] = self._symbol
119        df["date"] = pd.to_datetime(df["date"])
120        df = df.set_index(["symbol", "date"])
121        return df
122
123    def read(self):
124        """Read data from connector"""
125        dfs = []
126        for symbol in self.symbols:
127            self._symbol = symbol
128            try:
129                dfs.append(self._read_one_data(self.url, self.params))
130            finally:
131                self.close()
132        return pd.concat(dfs, self._concat_axis)
133
134
135class TiingoDailyReader(_BaseReader):
136    """
137    Historical daily data from Tiingo on equities, ETFs and mutual funds
138
139    Parameters
140    ----------
141    symbols : {str, List[str]}
142        String symbol or list of symbols
143    start : string, int, date, datetime, Timestamp
144        Starting date, timestamp. Parses many different kind of date
145        representations (e.g., 'JAN-01-2010', '1/1/10', 'Jan, 1, 1980').
146        Default starting date is 5 years before current date.
147    end : string, int, date, datetime, Timestamp
148        Ending date, timestamp. Same format as starting date.
149    retry_count : int, default 3
150        Number of times to retry query request.
151    pause : float, default 0.1
152        Time, in seconds, of the pause between retries.
153    session : Session, default None
154        requests.sessions.Session instance to be used
155    freq : {str, None}
156        Not used.
157    api_key : str, optional
158        Tiingo API key . If not provided the environmental variable
159        TIINGO_API_KEY is read. The API key is *required*.
160    """
161
162    def __init__(
163        self,
164        symbols,
165        start=None,
166        end=None,
167        retry_count=3,
168        pause=0.1,
169        timeout=30,
170        session=None,
171        freq=None,
172        api_key=None,
173    ):
174        super(TiingoDailyReader, self).__init__(
175            symbols, start, end, retry_count, pause, timeout, session, freq
176        )
177        if isinstance(self.symbols, str):
178            self.symbols = [self.symbols]
179        self._symbol = ""
180        if api_key is None:
181            api_key = os.getenv("TIINGO_API_KEY")
182        if not api_key or not isinstance(api_key, str):
183            raise ValueError(
184                "The tiingo API key must be provided either "
185                "through the api_key variable or through the "
186                "environmental variable TIINGO_API_KEY."
187            )
188        self.api_key = api_key
189        self._concat_axis = 0
190
191    @property
192    def url(self):
193        """API URL"""
194        _url = "https://api.tiingo.com/tiingo/daily/{ticker}/prices"
195        return _url.format(ticker=self._symbol)
196
197    @property
198    def params(self):
199        """Parameters to use in API calls"""
200        return {
201            "startDate": self.start.strftime("%Y-%m-%d"),
202            "endDate": self.end.strftime("%Y-%m-%d"),
203            "format": "json",
204        }
205
206    def _get_crumb(self, *args):
207        pass
208
209    def _read_one_data(self, url, params):
210        """ read one data from specified URL """
211        headers = {
212            "Content-Type": "application/json",
213            "Authorization": "Token " + self.api_key,
214        }
215        out = self._get_response(url, params=params, headers=headers).json()
216        return self._read_lines(out)
217
218    def _read_lines(self, out):
219        df = pd.DataFrame(out)
220        df["symbol"] = self._symbol
221        df["date"] = pd.to_datetime(df["date"])
222        df = df.set_index(["symbol", "date"])
223        return df
224
225    def read(self):
226        """Read data from connector"""
227        dfs = []
228        for symbol in self.symbols:
229            self._symbol = symbol
230            try:
231                dfs.append(self._read_one_data(self.url, self.params))
232            finally:
233                self.close()
234        return pd.concat(dfs, self._concat_axis)
235
236
237class TiingoMetaDataReader(TiingoDailyReader):
238    """
239    Read metadata about symbols from Tiingo
240
241    Parameters
242    ----------
243    symbols : {str, List[str]}
244        String symbol or list of symbols
245    start : string, int, date, datetime, Timestamp
246        Not used.
247    end : string, int, date, datetime, Timestamp
248        Not used.
249    retry_count : int, default 3
250        Number of times to retry query request.
251    pause : float, default 0.1
252        Time, in seconds, of the pause between retries.
253    session : Session, default None
254        requests.sessions.Session instance to be used
255    freq : {str, None}
256        Not used.
257    api_key : str, optional
258        Tiingo API key . If not provided the environmental variable
259        TIINGO_API_KEY is read. The API key is *required*.
260    """
261
262    def __init__(
263        self,
264        symbols,
265        start=None,
266        end=None,
267        retry_count=3,
268        pause=0.1,
269        timeout=30,
270        session=None,
271        freq=None,
272        api_key=None,
273    ):
274        super(TiingoMetaDataReader, self).__init__(
275            symbols, start, end, retry_count, pause, timeout, session, freq, api_key
276        )
277        self._concat_axis = 1
278
279    @property
280    def url(self):
281        """API URL"""
282        _url = "https://api.tiingo.com/tiingo/daily/{ticker}"
283        return _url.format(ticker=self._symbol)
284
285    @property
286    def params(self):
287        return None
288
289    def _read_lines(self, out):
290        s = pd.Series(out)
291        s.name = self._symbol
292        return s
293
294
295class TiingoQuoteReader(TiingoDailyReader):
296    """
297    Read quotes (latest prices) from Tiingo
298
299    Parameters
300    ----------
301    symbols : {str, List[str]}
302        String symbol or list of symbols
303    start : string, int, date, datetime, Timestamp
304        Not used.
305    end : string, int, date, datetime, Timestamp
306        Not used.
307    retry_count : int, default 3
308        Number of times to retry query request.
309    pause : float, default 0.1
310        Time, in seconds, of the pause between retries.
311    session : Session, default None
312        requests.sessions.Session instance to be used
313    freq : {str, None}
314        Not used.
315    api_key : str, optional
316        Tiingo API key . If not provided the environmental variable
317        TIINGO_API_KEY is read. The API key is *required*.
318
319    Notes
320    -----
321    This is a special case of the daily reader which automatically selected
322    the latest data available for each symbol.
323    """
324
325    @property
326    def params(self):
327        return None
328