#!/usr/bin/python3
# googlestockquotes.py by <NAME>
"""
Python library to obtain stock data from Google Finance

Python library to obtain stock data from Google Finance in one of two ways:
1.  Using a sort of Google Finance API, which for a few years has not been maintained by Google and could stop
    functioning in the (near) future. See googlecode.blogspot.com/2011/05/spring-cleaning-for-some-of-our-apis.html.
    Makes use of of requests and json libraries
2.  By parsing it from the stock's Google Finance web page. Makes use of bs4, requests and re libraries

After importing this module, you can construct a Stock object by calling the constructor in the format
stock = Stock('MARKET:SYMBOL'), e.g. google = Stock('NASDAQ:GOOG') or heineken = Stock('AMS:HEIA').  For stocks
listed on the Nasdaq or on the Dow Jones, markets do not have to be indicated, i.e. you can simply call
goog = Stock('GOOG').

A Stock object containing stock information will be generated by calling stock.get_info. This method will in turn call
stock.get_info_gfapi to retrieve the information via the Google Finance api or stock.get_info_gfpage to parse the info
from the Google Finance page. You can specify which solution you would like to use by calling
stock = Stock('MARKET:SYMBOL', solution) where solution = 'api' or 'page'. If this argument is omitted, it defaults to
'api'.

The information is stored in a dictionary in stock.info. The translations of the keys can be found below. Calling
stock.get_quote will return the latest stock quote as an integer, but since it calls stock.get_info, will also populate
stock.info. If the script is run directly, it will prompt for a stock symbol.

Stock.info dictionary keys:
PC: previous close stock price || SP: stock price || CH: change in SP since previous close || CHP: CH in percentage ||
DT: date and time of last trade during normal market hours || EHSP: extra hours SP || EHCH: extra hours change in SP
since closing of regular markets || EHCHP: EHCH in percentage || EHDT: date and time of last trade during extra hours

Note: not all of these types of information will always be obtained when `stock.get_info` is called, depending on the
retrieval method and on the stock itself. Google Finance does not provide extra hours pricing information for every
stock, for example. The two solutions aim to provide the following information:
- 'api' (default): PC, SP, CH, CHP, DT, EHSP, EHCH, EHCHP and EHDT
- 'page': SP, CH, CHP, EHSP, EHCH and EHCP
"""

# Define class to collect stock information
class Stock:
    '''Objects of this class represent stocks and information on these stocks like current stock price or the change in
    stock price during extra hours trading'''
    def __init__(self, symbol, *args):
        import warnings
        self.set_symbol(symbol)
        self.set_solution(args)
        self.info = {}

    def set_solution(self, args):
        '''Sets a flag for the information retrieval method, 'api' (default) or 'page' '''
        if len(args) == 1:
            if args[0]=='api' or args[0]=='page':
                self.solution = args[0]
            else:
                raise ValueError("The provided solution argument is not equal to 'api' or 'page'")
        elif len(args) > 1:
            raise ValueError('Too many arguments provided')
        else:
            print("No solution argument provided. Will default to 'api' solution")
            self.solution = 'api'

    def set_symbol(self, symbol):
        '''Assigns the provided market and stock symbol as attributes of the object'''
        symbollist = symbol.split(':')
        if len(symbollist) == 1:
            self.symbol = symbollist[0]
            self.market = 'NA'
        elif len(symbollist) == 2:
            self.market = symbollist[0]
            self.symbol = symbollist[1]
        else:
            raise ValueError('Please provide the symbol in the format MARKET:SYMBOL')

    def get_quote(self):
        '''Returns the latest stock quote as a float'''
        self.get_info_gfpage()
        return self.info['SP']

    def get_info(self):
        '''Calls one of the two information retrieval methods, based on the 'solution' attribute of the object'''
        if self.solution == 'api':
            self.get_info_gfapi()
        elif self.solution == 'page':
            self.get_info_gfpage()

    def get_info_gfpage(self):
        '''Retrieves stock data by parsing a stock's Google Finance page, and assigns this data to the 'info'
        dictionary'''
        from bs4 import BeautifulSoup
        import requests, re

        # reset object's currently held stock information (if any)
        self.info = {}

        # generate query and retrieve html code of stock's Google Finance page
        if self.market == 'NA':
            stockhtml = requests.get('https://www.google.com/finance?q=' + self.symbol).content
        else:
            stockhtml = requests.get('https://www.google.com/finance?q=' + self.market + '%3A' + self.symbol).content

        # convert html code into BeautifulSoup object and parse the code
        stocksoup = BeautifulSoup(stockhtml, "html.parser")

        # create a string from that part of the html code containing current stock quote
        stockquotestr = str(stocksoup.find_all('span', {'class': 'pr'}))
        # extract stock quote from string and put it in the stock.info dictionary
        self.info['SP'] = float(re.search('\d?\d?\,?\d+\.\d\d', stockquotestr).group(0).replace(',', ''))
        # create a string from that part of the html code containing quote change from previous close
        stockchangestr = str(stocksoup.find_all('span', {'class': 'ch bld'}))
        # extract quote change from string and put it in the stock.info dictionary
        self.info['CH'] = float(re.search('([+-]\d+\.\d\d)(?=\<)', stockchangestr).group(0).replace(',', ''))
        # extract quote change percentage from string and put it in the stock.info dictionary
        self.info['CHP'] = float(re.search('(-?\d+\.\d\d)(?=\%)', stockchangestr).group(0).replace(',', ''))\

        # create a string from that part of the html code containing after hours stock quote
        ahstockquotestr = str(stocksoup.find_all('div', {'id': 'price-panel'}))
        # check if the words 'After Hours' are present in the string; if not, Google Finance does not provide AH data
        # for this stock
        if ahstockquotestr.find('After Hours') != -1:
            # extract certain sub string from ah stock quote string
            ahstockquotestr = re.search('(After Hours[\s\S]+)', ahstockquotestr).group(0)
            #  extract after hour stock quote from the sub string and put it in the Stock.info dictionary
            self.info['EHSP'] = float(re.search('(?<![+-])(\d+\.\d\d)(?=\<)', ahstockquotestr).group(0).replace(',', ''))
            # extract after hour quote change from string and put it in the stock.info dictionary
            self.info['EHCH'] = float(re.search('([+-]\d+\.\d\d)(?=\<)', ahstockquotestr).group(0).replace(',', ''))
            # extract after hour quote change percentage from string and put it in the stock.info dictionary
            self.info['EHCHP'] = float(re.search('(-?\d+\.\d\d)(?=\%)', ahstockquotestr).group(0).replace(',', ''))

    def get_info_gfapi(self):
        '''Retrieves stock data by parsing a json object obtained via a Google Finance api, and assigns this data to the
        'info' dictionary'''
        import json, requests
        # reset object's stock information
        self.info = {}

        # generate query and retrieve json file from the Google Finance api
        if self.market == 'NA':
            stockinfo = requests.get('http://finance.google.com/finance/info?client=ig&q='+self.symbol)
        else:
            stockinfo = requests.get('http://finance.google.com/finance/info?client=ig&q='+self.market+'%3A'+self.symbol)

        # split string at each new line
        stockjson = json.loads(stockinfo.text[3:])[0]
        # map json elements to stock.info dictionary
        self.info.update({
            'SP':stockjson['l'], 'CH':stockjson['c'], 'CHP':stockjson['cp'], 'DT':stockjson['lt_dts'],
            'PC':stockjson['pcls_fix']})
        # check if extra hours data is provided for this stock; if so, map the elements to the stock.info dictionary
        if 'el' in stockjson:
            self.info.update({
                'EHSP': stockjson['el'], 'EHCH': stockjson['ec'], 'EHCHP': stockjson['ecp'],
                'EHDT': stockjson['elt']})

def main():
    '''When run directly as a main script, the user is prompted for a stock. Data for this stock will be gathered with
    the 'api' retrieval method and printed'''
    symbol = input('Please provide a stock in the format MARKET:SYMBOL : ')
    stock = Stock(symbol)
    stock.get_info()
    print('Current stock price: {}\n'.format(stock.get_quote()))
    print('All stock information that was retrieved: {}'.format(stock.info))

def test():
    '''For testing the script. Checks both data retrieval methods for a Nasdaq-listed stock (Google) and a Euronext
    (Amsterdam)-listed stock (Heineken)'''
    stock = Stock('AMS:HEIA', 'api')
    stock.get_info()
    print('Info dictionary for {} using solution {}:\n{} \n'.format(stock.symbol, stock.solution, stock.info))

    stock = Stock('AMS:HEIA', 'page')
    stock.get_info()
    print('Info dictionary for {} using solution {}:\n{} \n'.format(stock.symbol, stock.solution, stock.info))

    stock = Stock('GOOG', 'api')
    stock.get_info()
    print('Info dictionary for {} using solution {}:\n{} \n'.format(stock.symbol, stock.solution, stock.info))

    stock = Stock('GOOG', 'page')
    stock.get_info()
    print('Info dictionary for {} using solution {}:\n{} \n'.format(stock.symbol, stock.solution, stock.info))

#if __name__ == "__main__": main()
