#!/usr/bin/env python3 # -*- coding: utf-8 -*- # This program is Copyright (C) 2017, Paul Lutus # and is released under the GPL: # https://www.gnu.org/licenses/gpl-3.0.en.html """ Acquire online stock quotes for GnuCash (04.13.2018 version). """ import re import urllib.request import datetime from piecash import open_book, GnucashException, Price # must: pip3 install piecash # http://piecash.readthedocs.io/en/latest/tutorial/index_existing.html # change value of DATABASE_PATH to match the path on your system: DATABASE_PATH = '/netbackup/data/FINANCE/gnucash/all_accounts.sqlite3.gnucash' def get_prices_for(commodities): """ Acquire online price quotes for provided commodity list. Args: commodities: list of commodities for which prices are required Returns: dict of commodity : price pairs """ symbols = [commodity.fullname for commodity in commodities] # acquire web page containing stock price quotations url = 'http://money.cnn.com/quote/quote.html?symb=%s' % ','.join(symbols) req = urllib.request.Request( url, data=None, headers={ "User-Agent": "Mozilla/5.0 (Macintosh; " \ "Intel Mac OS X 10_9_3) " \ "AppleWebKit/537.36 (KHTML like Gecko) " \ "Chrome/35.0.1916.47 Safari/537.36" } ) with urllib.request.urlopen(req) as response: data = str(response.read()) # filter prices from the web page content prices = {} for symbol in symbols: price = re.sub(r'(?is).*?>\s*%s\s*<.*?>\s*([0-9.]+)\s*<.*' \ % symbol, r'\1', data) if len(price) < 20: prices[symbol] = price return prices def acquire_online_prices(path): """ Acquire online prices for GnuCash commodities selected for price updates, append the results to the price table Args: path: file path to GnuCash sqlite3-format database Returns: True/False: all requested prices were acquired """ commodities = [] try: with open_book(path, readonly=False) as book: # phase 1 : acquire GnuCash commodities # for which prices are expected for commodity in book.commodities: if (commodity.namespace == 'FUND' \ or commodity.namespace == 'STOCK') \ and commodity.quote_flag: commodities += [commodity] # eliminate duplicate symbols commodities = list(set(commodities)) # phase 2 : fetch commodity prices online prices = get_prices_for(commodities) # assuming U.S. dollar currency currency = book.commodities(namespace="CURRENCY", mnemonic="USD") # phase 3 : update prices in database total = 0 for commodity in commodities: # did the online request succeed? if commodity.fullname in prices: print("%s : %6s" % (commodity.fullname, prices[commodity.fullname])) date = datetime.datetime.now().replace(microsecond=0).astimezone().date() price = Price(commodity, currency, date, \ prices[commodity.fullname], 'last', 'user:price') commodity.prices.append(price) total += 1 else: print('No online price found for symbol %s.' % commodity.fullname) # phase 4 : commit result to database book.save() print("GnuCash database updated with %d new prices." % total) return total == len(commodities) except GnucashException as err: print('GnuCashException: "%s"' % err) if re.search('(?i)lock on the file', str(err)) != None: print('This error means the GnuCash database is in use.') return False # run acquire_online_prices(path) if not module if __name__ == '__main__': acquire_online_prices(DATABASE_PATH)