2
\$\begingroup\$

I am working on a stock analysis application in Python and I have the need to determine whether an input ticker symbol is an Exchange Traded Fund (ETF) or not. First I need to determine if the given ticker is even valid. Second I need to determine if it is an ETF or a company stock. So, I have some simple functions to request info from Yahoo! Finance (modified from ystockquote)

from urllib.request import Request, urlopen

def _yahoo_request(symbol, tag):
    """
    Retrieve stock data from Yahoo! Finance.

    Args:
        symbol (str): Stock ticker symbol for company of interest.
        tag (str): Yahoo! finance tag for a given property.

    Returns:
        str: The property requested.
    """
    url = 'http://finance.yahoo.com/d/quotes.csv?s=%s&f=%s' % (symbol, tag)
    request = Request(url)
    response = urlopen(request)
    content = response.read().decode().strip().strip('"')
    return content

So to determine if a stock is valid, I check to see if it's name exists in Yahoo! Finance. Getting the name:

def get_name(symbol):
    """
    Retrieve company name for given ticker.

    Args:
        symbol (str): Ticker symbol for a given company's stock.

    Returns:
        str: The company name. Returns 'N/A' if ticker not in Yahoo! Finance.
    """
    return _yahoo_request(symbol, 'n')

and checking if it is a valid ticker:

def valid_ticker(ticker):
    """
    Check whether given ticker is a valid stock symbol.

    NOTE: Assumes that a stock is valid IF Yahoo! Finance returns a
    name for the given ticker.

    Args:
        ticker (str): Ticker symbol in question.

    Returns:
        bool: True if valid ticker (returned name != 'N/A'),
            otherwise False.
    """
    ticker = ticker.upper()
    return True if get_name(ticker) != 'N/A' else False

Finally, to determine if a given ticker is for an ETF or not, I will check the number of shares outstanding.

def get_shares_outstanding(symbol):
    """
    Retrieve number of shares outstanding for given stock ticker.

    Args:
        symbol (str): Ticker symbol for a given company's stock.

    Returns:
        str: The company name.
    """
    return _yahoo_request(symbol, 'j2')

This is not a valid data point for ETF's, so again, Yahoo! will return 'N/A'. So, if the response can be cast as an integer, the ticker is not an ETF.

def is_etf(ticker):
    """
    Determine whether a given ticker symbol is for an Exchange
    Traded Fund (ETF).

    NOTE: Assumes that ticker is an ETF IF it is a valid ticker AND
    Yahoo! Finance does not return a numeric value for 'shares
    outstanding.'

    Args:
        ticker (str): Ticker symbol in question.

    Returns:
        bool: None if not a valid ticker, True if an etf (ticker is
        valid and returned shares outstanding cannot be converted to
        an int), otherwise False.
    """
    ticker = ticker.upper()
    if valid_ticker(ticker):
        try:
            int(get_shares_outstanding(ticker))
            return False
        except ValueError:
            return True
    else:
        return None

Code is working as expected. Does this seem a reasonable way to check this? In particular the last function. Should I just use if get_shares_outstanding(ticker) == 'N/A': return True ?

\$\endgroup\$
3
\$\begingroup\$

Any time you're dealing with structured data, use a proper parser for it.

url = 'http://finance.yahoo.com/d/quotes.csv?s=%s&f=%s' % (symbol, tag)

In this case, you want urllib.parse.

Please use a context manager for:

request = Request(url)
response = urlopen(request)

eg.

with urlopen(Request(url)) as response:
    ...

Context managers are good. It's a shame they're so under-used. On older Pythons, you should use contextlib.closing instead.

I would generally merge these lines into a long return:

content = response.read().decode().strip().strip('"')
return content

Consider that in

ticker = ticker.upper()
return True if get_name(ticker) != 'N/A' else False

True if some_bool else False is just some_bool, so write

return get_name(ticker.upper()) != 'N/A'

instead.

\$\endgroup\$
  • \$\begingroup\$ Good point on the context manager. Since this was a 'borrowed' function, I didn't even think about it. However, making that change and combining to a long return actually really trimmed down that function. Also, many thanks on the some_bool, That looks much better. The one item in your answer I don't understand is how you are proposing I use the urllib.parse. Could you add a quick snippet or explanation? Great stuff! \$\endgroup\$ – Christopher Pearson Jun 15 '15 at 2:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.