-
Notifications
You must be signed in to change notification settings - Fork 1
/
ledger_fx_rates.py
195 lines (158 loc) · 5.4 KB
/
ledger_fx_rates.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#!/usr/bin/env python
"""
Render current foreign exchange rates from the European Central Bank as ledger
market values.
"""
from __future__ import (
division,
print_function,
)
import argparse
from collections import namedtuple
from contextlib import closing
import datetime
import sys
import xml.etree.cElementTree as ET
try:
from urllib.request import urlopen
from io import StringIO
except ImportError:
from urllib2 import urlopen
from cStringIO import StringIO
__version__ = '1.0.0'
class EuroFXRef(object):
"""
Parser for European Central Bank foreign exchange reference (EuroFXRef)
data.
"""
NAMESPACE = {
'eurofxref': 'http://www.ecb.int/vocabulary/2002-08-01/eurofxref',
'gesmes': 'http://www.gesmes.org/xml/2002-08-01'
}
URL = 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'
def __init__(self, rates=None, date=None):
self._rates = rates or {}
self.date = date or datetime.date.today()
@classmethod
def from_file(cls, file_obj):
"""
Parse exchange rates from a file object.
Args:
file_obj (file): XML data as a file object
Returns: EuroFXRef
"""
data = ET.parse(file_obj)
root = data.getroot()
rates = {}
timestamp = root.find('./eurofxref:Cube/eurofxref:Cube[@time]', cls.NAMESPACE).get('time')
date = datetime.datetime.strptime(timestamp, '%Y-%m-%d').date()
for entry in root.findall('./eurofxref:Cube/eurofxref:Cube/eurofxref:Cube', cls.NAMESPACE):
currency = entry.get('currency')
rate = {
'base': 'EUR',
'currency': currency,
'date': date,
'rate': float(entry.get('rate')),
}
rates[currency] = Rate(**rate)
return cls(rates, date)
@classmethod
def from_string(cls, text):
"""
Parse exchange rates from an XML string.
Args:
text (str): XML data as a string
Returns: EuroFXRef
"""
file_obj = StringIO(text)
return cls.from_file(file_obj)
@classmethod
def from_url(cls, url=None):
"""
Fetch exchange rates from the European Central Bank (ECB) website.
Args:
url (str): URL to ECB data (default: EuroFXRef.URL)
Returns: EuroFXRef
"""
url = url if url else cls.URL
with closing(urlopen(url)) as response:
return cls.from_file(response)
def exchange(self, variable, fixed='EUR', amount=1):
"""
Exchange an amount of fixed currency into the given variable currency.
Args:
variable (str): symbol of variable currency (currency to buy)
fixed (str): symbol of fixed currency (currency to sell)
amount (int or float): amount of fixed currency to sell
Raises:
KeyError: either variable or fixed symbol is not known
Returns: float
"""
if variable == fixed:
rate = 1
elif variable == 'EUR':
rate = 1/self._rates[fixed].rate
elif fixed != 'EUR':
rate = self._rates[variable].rate * 1/self._rates[fixed].rate
return amount * rate
def rates(self, fixed='EUR'):
"""
Return rates based against the given fixed currency.
Args:
fixed (str): symbol of fixed currency (currency to sell)
Returns: list of Rates
"""
if fixed == 'EUR':
return self._rates.values()
rates = []
for rate in self._rates.values():
if rate.currency == fixed:
continue
rates.append(rate._replace(base=fixed, rate=self.exchange(rate.currency, fixed)))
euro = Rate(base=fixed, currency='EUR', rate=self.exchange('EUR', fixed), date=self.date)
rates.append(euro)
return rates
class Rate(namedtuple('Rate', ['base', 'currency', 'date', 'rate'])):
"""
A foreign currency exchange rate.
Fields:
base (str): symbol of base (i.e., fixed) currency
currency (str): symbol of variable currency
date (datetime.date): date of exchange rate
rate (float): exchange rate
"""
def __format__(self, fmt):
if fmt == 'ledger':
rate = self._replace(rate=1/self.rate)
return 'P {date} {currency} {base} {rate:.5f}'.format(**rate._asdict())
return repr(self)
class StoreUppercaseAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, values.upper())
def parse_args(argv):
"""
Parse command-line arguments.
Args:
argv (list): command-line arguments
Returns: argparse.Namespace
"""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
'currency',
action=StoreUppercaseAction,
nargs='?', default='EUR', help='base currency [%(default)s]'
)
return parser.parse_args(argv)
def main(argv=None):
if argv is None:
argv = sys.argv[1:]
fx = EuroFXRef.from_url()
args = parse_args(argv)
try:
for rate in sorted(fx.rates(args.currency)):
print('{:ledger}'.format(rate))
except KeyError as exc:
print('error: unknown symbol: {}'.format(exc.message), file=sys.stderr)
return 1
if __name__ == '__main__':
sys.exit(main())