Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enhancement] Add new daily and IEX methods following the Tiingo API more closely #527

Open
2xmm opened this issue Oct 26, 2020 · 8 comments

Comments

@2xmm
Copy link
Contributor

2xmm commented Oct 26, 2020

Description

Tiingo daily data and IEX intraday data are both being returned by the get_dataframe method but I propose they would be better off with their own methods. This would follow the Tiingo API more closely and users would be more aware of the source of the underlying data.

I suggest adding the method names get_daily_data and get_iex_data. The suffix _data is more appropriate in this case because if a metric_name is passed then the method returns a pandas.Series not a pandas.DataFrame.

I recommend only returning data in csv format from these new methods since this return type is so much faster than json. If json is included as a return type, test of equality between csv and json should be included. I've noticed that intraday csv response data coming from Tiingo contains a timezone offset while json response data does not contain a timezone offset. I would recommend using the same offset as the one returned by the csv response for simplicity. Again, the decision about which offset to use could be avoided by only allowing these methods to return csv data.

@hydrosquall Looking forward to your thoughts.

@GenusGeoff
Copy link
Contributor

Sounds good to me, personally. I finally have a few moments to start looking into this code a bit more and see where I can help with the project.

@hydrosquall
Copy link
Owner

Hi @2xmm , thanks for filing this and organizing this proposal based on our discussions in this recent PR.

I think adding these two new methods you propose (get_daily_data and get_iex_data) without the connotations of dataframe makes sense. Given the multiple factors performance improvement with csv format compared to JSON, I think that opting for CSV format by default is well worthwhile.

If it turns out people have a strong need for the JSON format's timezone offsets for intraday data, they can still use the non-convenience mechanism (i.e. get_ticker_price) to request JSON format. Using CSV as the first-class network exchange format is a good simplicity <> comprehensiveness trade given the purpose of these _data methods is to provide good defaults for people wanting to get into doing data analysis quickly. We can talk to @tiingo at a later date to see if they may consider including timezone offsets in CSV format with the inclusion of a URL parameter in future releases.

@2xmm
Copy link
Contributor Author

2xmm commented Nov 14, 2020

@GenusGeoff Feel free to work on this if you are interested.

@GenusGeoff
Copy link
Contributor

@2xmm This looks like it's already implemented in the most recent code with the switch "fmt='csv'". Great work to @hydrosquall or to whomever took this one up.

@hydrosquall
Copy link
Owner

Hi @GenusGeoff ! @2xmm generously implemented the "fmt = 'csv'" option in get_dataframe in #524 .

I think the change request described in this issue is specifically for adding 2 new methods to the library, get_daily_data and get_iex_data.

@GenusGeoff
Copy link
Contributor

Please forgive my lack of understanding on the pull/fork/merge type thing. I've included some changes to api.py below. They're extremely rough but they do what is requested. Comments and all might need to be cleaned up a bit, though.

It's a simple fix that just borrows much of the code from the existing get_dataframe() function.

`####################### BEGIN Modifications (GenusGeoff)
### Add get_daily_data and get_iex_data
###
### The suffix _data is more appropriate in this case because if a metric_name is passed then the method returns a
### pandas.Series not a pandas.DataFrame.
###

# Get Daily Data
def get_daily_data(self, tickers,
                  startDate=None, endDate=None, metric_name=None,
                  frequency='daily', fmt='csv'):

    """ Returns historical prices for one or more ticker symbols.

        By default, return latest EOD Composite Adjusted Closing 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
        or from the TiingoClient.list_tickers() method.

        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).
            fmt (string): 'csv' or 'json'
    """
    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))

    if metric_name is None and isinstance(tickers, list):
        raise MissingRequiredArgumentError("""When tickers is provided as a list, metric_name is a required argument.
        Please provide a metric_name, or call this method with one ticker at a time.""")

    params = {
        'format': fmt,
        'resampleFreq': frequency
    }
    if startDate:
        params['startDate'] = startDate
    if endDate:
        params['endDate'] = endDate

    if pandas_is_installed:
        if type(tickers) is str:
            prices = self._request_pandas(
                ticker=tickers, params=params, metric_name=metric_name)
        else:
            prices = pd.DataFrame()
            for stock in tickers:
                ticker_series = self._request_pandas(
                    ticker=stock, params=params, metric_name=metric_name)
                ticker_series = ticker_series.rename(stock)
                prices = pd.concat([prices, ticker_series], axis=1, sort=True)

        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)

## Get IEX Data

def get_iex_data(self, tickers,
                  startDate=None, endDate=None, metric_name=None,
                  frequency='1hour', fmt='csv'):

    """ 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
        or from the TiingoClient.list_tickers() method.

        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).
            fmt (string): 'csv' or 'json'
    """

    valid_columns = {'open', 'high', 'low', 'close', 'volume'}

    if metric_name is not None and metric_name not in valid_columns:
        raise APIColumnNameError('Valid data items are: ' + str(valid_columns))

    if metric_name is None and isinstance(tickers, list):
        raise MissingRequiredArgumentError("""When tickers is provided as a list, metric_name is a required argument.
        Please provide a metric_name, or call this method with one ticker at a time.""")

    params = {
        'format': fmt,
        'resampleFreq': frequency
    }
    if startDate:
        params['startDate'] = startDate
    if endDate:
        params['endDate'] = endDate

    if pandas_is_installed:
        if type(tickers) is str:
            prices = self._request_pandas(
                ticker=tickers, params=params, metric_name=metric_name)
        else:
            prices = pd.DataFrame()
            for stock in tickers:
                ticker_series = self._request_pandas(
                    ticker=stock, params=params, metric_name=metric_name)
                ticker_series = ticker_series.rename(stock)
                prices = pd.concat([prices, ticker_series], axis=1, sort=True)

        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)

### End of Modifications (GenusGeoff)`

GenusGeoff added a commit to GenusGeoff/tiingo-python that referenced this issue Feb 27, 2021
Added functionality in rough form as requested in "[Enhancement] Add new daily and IEX methods following the Tiingo API more closely hydrosquall#527" from tiingo-python
@GenusGeoff
Copy link
Contributor

This is a bit more to add the IEX afterHours quotes to this code. BTW--Apparently, Tiingo requires True and False to be lowercase in the request which is incompatible with Python. A possible workaround might be to create a literal variable, e.g. true, false = (True, False); but I've no idea what unintended consequences might arise.

#537

`## Get IEX Data

def get_iex_data(self, tickers,
                  startDate=None, endDate=None, metric_name=None, afterHours='false',
                  frequency='1hour', fmt='csv'):

    """ 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
        or from the TiingoClient.list_tickers() method.

        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.
            afterHours (boolean): Optional parameter that selects whether to include extended hours quotes NOTE: MUST BE LOWERCASE
                                  AND THEREFORE ENCAPSULTED, e.g. 'true' or 'false'
            frequency (string): Resample frequency (defaults to daily).
            fmt (string): 'csv' or 'json'
    """

    valid_columns = {'open', 'high', 'low', 'close', 'volume'}

    if metric_name is not None and metric_name not in valid_columns:
        raise APIColumnNameError('Valid data items are: ' + str(valid_columns))

    if metric_name is None and isinstance(tickers, list):
        raise MissingRequiredArgumentError("""When tickers is provided as a list, metric_name is a required argument.
        Please provide a metric_name, or call this method with one ticker at a time.""")

    params = {
        'format': fmt,
        'resampleFreq': frequency,
        'afterHours': afterHours
    }
    if startDate:
        params['startDate'] = startDate
    if endDate:
        params['endDate'] = endDate

    if pandas_is_installed:
        if type(tickers) is str:
            prices = self._request_pandas(
                ticker=tickers, params=params, metric_name=metric_name)
        else:
            prices = pd.DataFrame()
            for stock in tickers:
                ticker_series = self._request_pandas(
                    ticker=stock, params=params, metric_name=metric_name)
                ticker_series = ticker_series.rename(stock)
                prices = pd.concat([prices, ticker_series], axis=1, sort=True)

        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)

`

@2xmm
Copy link
Contributor Author

2xmm commented Feb 28, 2021

Hi @GenusGeoff,

Glad you were able to make a contribution to this project. It's a great exercise to work through submitting a pull request and you can find out how to do so in the CONTRIBUTING.rst file in the root directory of this project. If you have any issues submitting the request feel free to comment here and I'll help you out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants