Google

Thursday, January 04, 2007

Online Stock Quotes - Using Python S60


I recently came across an article about a service provided by the Yahoo! finance website, which return details about stock quotes in comma separated values (CSVs). Many folks have used this service in their programs to display (almost) real time stock data. Of course, I had to make my phone do the same :) As we will see, Python makes it look easy - no, Python makes it look GOOD!

Background

The Yahoo! finance service http://quote.yahoo.com/d/quotes.csv is a very simple yet powerful service. The user can control the fields in the stock quote data through a format string. The return data contains stock data fields in CSV format. The fields and their order is based on the format string. This is another example of the usage of this service.

Cache Management

The application on the phone should minimize the network traffic. However, care should be taken that the data is updated as frequently as possible to keep the real-time nature of the data. To achieve this, there is a very rudimentary cache management technique implemented in the module. The cache management checks how old the stock data is and if it is older than a certain expiry. The network traffic is generated only if the data is expired.

Chart

This module also allows downloading the chart for a given symbol. The chart is downloaded from yet another Yahoo! finance service http://ichart.finance.yahoo.com/t The response of this service is a intra-day chart for the latest day. If a chart exists, the response is of type image/png. This can be used as a test.

Implementation

The following code snippet is from the quotes.py module. This piece works well on 3rd edition phones:
# These times should be set to minimize the network traffic
QUOTEEXPIRY = 60 # 1 Minute
CHARTEXPIRY = 300 # 5 Minutes
CACHEDIR = 'c:\\cache\\'

# If the file does not exist or if the creation time of the
# existing file is older than expiry, then return True
def is_expired(filename, expiry):
    expired = False
    if not os.path.exists(filename):
        expired = True
    else:
        lastmod = os.stat(filename)[ST_MTIME]
        currtime = int(time.time())
        if currtime > lastmod + expiry:
            expired = True
    return expired

# This function downloads the chart for a given symbol
# If the symbol does not have a chart, the function returns
# False, else returns True
def get_chart(symbol):
    retval = True
    filename = CACHEDIR+symbol.upper()+'chart.png'
    if is_expired(filename, CHARTEXPIRY):
        name, status = urllib.urlretrieve('http://ichart.finance.yahoo.com/t?s='+symbol.upper(), filename)
        if status.type != 'image/png':
            retval = False
            os.remove(filename)
    return retval

# Get the CSV from yahoo server and return parsed values
# Format is known and hence the cryptic retval parsing
def get_quote(symbol):
    filename = CACHEDIR+symbol.upper()+'quote.txt'
    if is_expired(filename, QUOTEEXPIRY):
        urllib.urlretrieve('http://quote.yahoo.com/d/quotes.csv?s='+symbol.upper()+'&d=t&f=sl1d1t1c1ohgvj1pp2xwenr', filename)
    f = file(filename)
    data = f.read()
    f.close()
    names = ['sym', 'last', 'date', 'curtime', 'change', 'open', 'high',
             'low', 'vol', 'mcap', 'close', 'pctchg', 'exchange', 'annrange',
             'earnings', 'name', 'peratio']
    values = map(lambda s: s.strip('"'), data.split('\n')[0].split(','))
    return dict(zip(names, values))

The meaning of the format string can be found from the correspondance between the string and the names array.

The complete implementation is located here

Summary

This log is yet another example of power and elegance of Python. The way zip() function simplifies creation of a very usable dictionary out of an array.