Last active
September 21, 2017 20:22
-
-
Save rayhamel/58f6118b9d6460b0efd17aed372ebbd2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# | |
# Version 2 | |
# 9 July 2013 | |
# | |
# Modified to use xml format output from timegenie instead of text file | |
# | |
# Version 3 | |
# 7 March 2014 | |
# | |
# Added checks for unicode currency names | |
# | |
# Version 3.1 | |
# 23 June 2014 | |
# | |
# Added test for non-unicode strings to prevent a runtime warning | |
# | |
# Version 4 | |
# 15 September 2017 (Ray Hamel) | |
# | |
# Rewritten to use Yahoo YQL API due to removal of TimeGenie RSS feed | |
# Now makes requests over HTTPS | |
# Now uses JSON version of the Packetizer spotprices API | |
# | |
# For Python 2 & 3 compatibility | |
from __future__ import absolute_import, division, print_function | |
try: | |
from urllib.parse import quote | |
from urllib.request import urlopen | |
except ImportError: | |
from urllib import quote | |
from urllib2 import urlopen | |
# Normal imports | |
import codecs | |
import json | |
from argparse import ArgumentParser | |
from collections import OrderedDict | |
from datetime import date | |
from decimal import Decimal | |
from os import linesep | |
from sys import exit, stderr, stdout | |
# The Yahoo API is excellent, but unfortunately requires we explicitly request | |
# each currency we want | |
# | |
# EU members that may join the Eurozone in the future, and so may need to be | |
# removed from this list | |
# | |
# ('BGN', 'bulgarialev'), | |
# ('CZK', 'czechkoruna'), | |
# ('DKK', 'denmarkkrona'), | |
# ('HRK', 'croatiakuna'), | |
# ('HUF', 'hungariaforint'), | |
# ('PLN', 'polandzloty'), | |
# ('RON', 'romanianewlei'), | |
# ('SEK', 'swedenkrona'), | |
currency_list = OrderedDict([ | |
('USD', 'usdollar'), # Yahoo API returns fractions of USD, so always 1.0 | |
('AED', 'uaedirham'), | |
('AFN', 'afghanafghani'), | |
('ALL', 'albanialek'), | |
('AMD', 'armeniadram'), | |
('AOA', 'angolakwanza'), | |
('ARS', 'argentinapeso'), | |
('AUD', 'australiadollar'), | |
('AWG', 'arubaflorin'), | |
('AZN', 'azerbaijanmanat'), | |
('BAM', 'bosniaconvertiblemark'), | |
('BBD', 'barbadosdollar'), | |
('BDT', 'bangladeshtaka'), | |
('BGN', 'bulgarialev'), | |
('BHD', 'bahraindinar'), | |
('BIF', 'burundifranc'), | |
('BMD', 'bermudadollar'), | |
('BND', 'bruneidollar'), | |
('BOB', 'boliviaboliviano'), | |
('BRL', 'brazilreal'), | |
('BSD', 'bahamasdollar'), | |
('BTN', 'bhutanngultrum'), | |
('BWP', 'botswanapula'), | |
('BYR', 'belarusruble'), | |
('BZD', 'belizedollar'), | |
('CAD', 'canadadollar'), | |
('CDF', 'drcfranccongolais'), | |
('CHF', 'swissfranc'), | |
('CLP', 'chilepeso'), | |
# ('CMG', 'sintmaartencaribbeanguilder'), # Not supported by Yahoo API | |
('CNY', 'chinayuan'), | |
('COP', 'colombiapeso'), | |
('CRC', 'costaricacolon'), | |
('CUP', 'cubapeso'), | |
('CVE', 'capeverdeescudo'), | |
('CZK', 'czechkoruna'), | |
('DJF', 'djiboutifranc'), | |
('DKK', 'denmarkkrona'), | |
('DOP', 'dominicanrepublicpeso'), | |
('DZD', 'algeriadinar'), | |
('EGP', 'egyptpound'), | |
('ERN', 'eritreanakfa'), | |
('ETB', 'ethiopianbirr'), | |
('EUR', 'euro'), | |
('FJD', 'fijidollar'), | |
('FKP', 'falklandislandspound'), | |
('GBP', 'ukpound'), | |
('GEL', 'georgialari'), | |
# ('GGP', 'guernseypound'), # Not supported by Yahoo API | |
('GHS', 'ghanacedi'), | |
('GIP', 'gibraltarpound'), | |
('GMD', 'gambiadalasi'), | |
('GNF', 'guineafranc'), | |
('GTQ', 'guatemalaquetzal'), | |
('GYD', 'guyanadollar'), | |
('HKD', 'hongkongdollar'), | |
('HNL', 'honduraslempira'), | |
('HRK', 'croatiakuna'), | |
('HTG', 'haitigourde'), | |
('HUF', 'hungariaforint'), | |
('IDR', 'indonesiarupiah'), | |
('ILS', 'israelnewshekel'), | |
# ('IMP', 'manxpound'), # Not supported by Yahoo API | |
('INR', 'indiarupee'), | |
('IQD', 'iraqdinar'), | |
('IRR', 'iranrial'), | |
('ISK', 'icelandkrona'), | |
# ('JEP', 'jerseypound'), # Not supported by Yahoo API | |
('JMD', 'jamaicadollar'), | |
('JOD', 'jordandinar'), | |
('JPY', 'japanyen'), | |
('KES', 'kenyaschilling'), | |
('KGS', 'kyrgyzstansom'), | |
('KHR', 'cambodiariel'), | |
('KMF', 'comorosfranc'), | |
('KPW', 'northkoreawon'), | |
('KRW', 'southkoreawon'), | |
('KWD', 'kuwaitdinar'), | |
('KYD', 'caymanislandsdollar'), | |
('KZT', 'kazakhstantenge'), | |
('LAK', 'laokip'), | |
('LBP', 'lebanonpound'), | |
('LKR', 'srilankanrupee'), | |
('LRD', 'liberiadollar'), | |
('LTL', 'lithuanialita'), | |
('LVL', 'latvialat'), | |
('LYD', 'libyadinar'), | |
('MAD', 'moroccodirham'), | |
('MDL', 'moldovaleu'), | |
('MGA', 'madagascarariary'), | |
('MKD', 'macedoniadenar'), | |
('MMK', 'myanmarkyat'), | |
('MNT', 'mongoliatugrik'), | |
('MOP', 'macaupataca'), | |
('MRO', 'mauritaniaouguiya'), | |
('MUR', 'mauritiusrupee'), | |
('MVR', 'maldiverufiyaa'), | |
('MWK', 'malawikwacha'), | |
('MXN', 'mexicopeso'), | |
('MYR', 'malaysiaringgit'), | |
('MZN', 'mozambicanmetical'), | |
('NAD', 'namibiadollar'), | |
('NGN', 'nigerianaira'), | |
('NIO', 'nicaraguacordobaoro'), | |
('NOK', 'norwaykrone'), | |
('NPR', 'nepalrupee'), | |
('NZD', 'newzealanddollar'), | |
('OMR', 'omanrial'), | |
('PAB', 'panamabalboa'), | |
('PEN', 'perunuevosol'), | |
('PGK', 'papuanewguineakina'), | |
('PHP', 'philippinepeso'), | |
('PLN', 'polandzloty'), | |
('PKR', 'pakistanrupee'), | |
('PYG', 'paraguayguarani'), | |
('QAR', 'qatarrial'), | |
('RON', 'romanianewlei'), | |
('RSD', 'serbiadinar'), | |
('RUB', 'russiarouble'), | |
('RWF', 'rwandafranc'), | |
('SAR', 'saudiarabiariyal'), | |
('SBD', 'solomonislandsdollar'), | |
('SCR', 'seychellesrupee'), | |
('SDG', 'sudanpound'), | |
('SEK', 'swedenkrona'), | |
('SGD', 'singaporedollar'), | |
('SHP', 'sainthelenapound'), | |
('SLL', 'sierraleoneleone'), | |
('SOS', 'somaliaschilling'), | |
('SRD', 'surinamedollar'), | |
('STD', 'saotome&principedobra'), | |
('SVC', 'elsalvadorcolon'), | |
('SYP', 'syriapound'), | |
('SZL', 'swazilandlilangeni'), | |
('THB', 'thailandbaht'), | |
('TJS', 'tajikistansomoni'), | |
('TMT', 'turkmenistanmanat'), | |
('TND', 'tunisiadinar'), | |
('TOP', "tongapa'anga"), | |
('TRY', 'turkeylira'), | |
('TTD', 'trinidadandtobagodollar'), | |
# ('TVD', 'tuvaludollar'), # Not supported by Yahoo API | |
('TWD', 'taiwandollar'), | |
('TZS', 'tanzaniashilling'), | |
('UAH', 'ukrainehryvnia'), | |
('UGX', 'ugandaschilling'), | |
('UYU', 'uruguaypeso'), | |
('UZS', 'uzbekistansum'), | |
('VEF', 'venezuelabolivar'), | |
('VND', 'vietnamdong'), | |
('VUV', 'vanuatuvatu'), | |
('WST', 'samoatala'), | |
('XAF', 'centralafricancfafranc'), | |
('XCD', 'eastcaribbeandollar'), | |
('XOF', 'westafricanfranc'), | |
('XPF', 'cfpfranc'), | |
('YER', 'yemenrial'), | |
('ZAR', 'southafricarand'), | |
('ZMW', 'zambiakwacha'), | |
('ZWL', 'zimbabwedollar'), | |
# _IMF Special Drawing Rights_ | |
('XDR', 'specialdrawingrights'), | |
# _Cryptocurrencies_ | |
# ('BTC', 'bitcoin'), | |
# ('ETH', 'ethereum'), | |
# _Precious metals_ | |
# ('XAG', 'silverprice'), # Yahoo API is less accurate than Packetizer | |
# ('XAU', 'goldprice'), # Yahoo API is less accurate than Packetizer | |
# ('XPD', 'palladiumprice'), # Yahoo API is less accurate than Packetizer | |
# ('XPT', 'platinumprice'), # Yahoo API is less accurate than Packetizer | |
]) | |
outfile_name = 'currency.units' | |
ap = ArgumentParser( | |
description="Update currency information for 'units' " | |
"into the specified filename or if no filename is " | |
"given, the default: '{}'".format(outfile_name), | |
) | |
ap.add_argument( | |
'output_file', | |
default=outfile_name, | |
help='the file to update', | |
metavar='filename', | |
nargs='?', | |
type=str, | |
) | |
outfile_name = ap.parse_args().output_file | |
# Can't use 'urlencode' here because it replaces ' ' with '+' | |
params = '&'.join(( | |
'env=store://datatables.org/alltableswithkeys', | |
'format=json', | |
'q=' + quote('SELECT Rate FROM yahoo.finance.xchange ' | |
# Request USD first and last to check these assumptions | |
# remain true: the Yahoo API returns exchange rates in | |
# the order they're requested, and in USD terms | |
'WHERE pair IN ("{}","USD")'.format( | |
'","'.join(currency_list.keys()), | |
)), | |
)) | |
try: | |
rates = [c['Rate'] for c in json.loads(urlopen( | |
# Can't use the 'data=' parameter here because we're making a GET | |
# request, not a POST | |
'https://query.yahooapis.com/v1/public/yql?{}'.format(params) | |
).read().decode('utf8'))['query']['results']['rate']] | |
except IOError as e: | |
stderr.write('Error connecting to currency server:\n{}\n'.format(e)) | |
exit(1) | |
assert all(Decimal(r) == 1 for r in (rates[0], rates[-1])), ( | |
"Yahoo API didn't return exchange rates in the order they were requested, " | |
"or didn't return them in USD terms" | |
) | |
del rates[-1] # Extra USD | |
assert len(currency_list) == len(rates), ( | |
"Yahoo API returned fewer results than the number of currencies requested" | |
) | |
try: | |
metals = json.loads(urlopen( | |
'https://services.packetizer.com/spotprices/?f=json', | |
).read().decode('utf8')) | |
except IOError as e: | |
stderr.write('Error connecting to spotprices server:\n{}\n'.format(e)) | |
exit(1) | |
del metals['date'] | |
class Currency(object): | |
""" | |
Contains the information necessary to list a currency or similar | |
(cryptocurrency, precious metal) in a 'currency.units' file | |
'inverse_usd' is the amount 1 USD buys (what the Yahoo API returns) | |
""" | |
def __init__(self, iso_code, unit, inverse_usd): | |
self.iso_code = iso_code | |
self.unit = unit | |
self.inverse_usd = inverse_usd | |
currencies = [] | |
for (iso_code, unit), inverse_usd in zip(currency_list.items(), rates): | |
if inverse_usd == 'N/A': | |
stderr.write('no rate for "{}" ({})'.format(unit, iso_code)) | |
continue | |
currencies.append(Currency(iso_code, unit, inverse_usd)) | |
codestr = '\n'.join('{:23}{}'.format(c.iso_code, c.unit) for c in currencies) | |
datestr = date.today().isoformat() | |
del currencies[0] # USD, usdollar, 1.0000 | |
ratestr = '\n'.join('1|US$ {rate:>{max_ratelen}} {unit}'.format( | |
rate=c.inverse_usd, | |
max_ratelen=max(len(c.inverse_usd) for c in currencies), | |
unit=c.unit, | |
) for c in currencies) | |
ozzystr = '\n'.join('{:19} {} US$/troyounce'.format( | |
metal + 'price', | |
price, | |
) for metal, price in metals.items()) | |
outstr = ( | |
"""ISO Currency Codes | |
{codestr} | |
# Currency exchange rates from Yahoo Finance (finance.yahoo.com) | |
!message Currency exchange rates from finance.yahoo.com on {datestr} | |
{ratestr} | |
# Precious metals prices from Packetizer (services.packetizer.com/spotprices) | |
{ozzystr} | |
""".format(codestr=codestr, datestr=datestr, ratestr=ratestr, ozzystr=ozzystr) | |
).replace('\n', linesep) | |
try: | |
if outfile_name == '-': | |
codecs.StreamReader(stdout, codecs.getreader('utf8')).write(outstr) | |
else: | |
with codecs.open(outfile_name, 'w', 'utf8') as of: | |
of.write(outstr) | |
except IOError as e: | |
stderr.write('Unable to write to output file:\n{}\n'.format(e)) | |
exit(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment