-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathupdate_exchange_rates.py
More file actions
130 lines (104 loc) · 4.51 KB
/
update_exchange_rates.py
File metadata and controls
130 lines (104 loc) · 4.51 KB
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
#!/usr/bin/env python
"""A script to update the exchange rates for 1 EUR in SEK and 1 USD in SEK.
"""
import argparse
from ibmcloudant import CouchDbSessionAuthenticator, cloudant_v1
import datetime
import json
import requests
import yaml
class CurrencyRates(object):
"""A class to fetch currency rates from fixer.io."""
def __init__(self, config_file):
self.rates_fetched = False
self._source_url = "https://api.apilayer.com/fixer/latest"
with open(config_file, 'r') as fh:
config = yaml.load(fh, Loader=yaml.SafeLoader)
self._apikey = config.get('apikey')
def fetch_rates(self):
response = requests.get(self._source_url, params={'base': 'SEK', 'symbols': 'USD, EUR'}, headers={'apikey': self._apikey})
assert response.status_code == 200
self.data = json.loads(response.text)
self.rates = self.data['rates']
self.rates_fetched = True
def get_rate(self, currency):
"""Get the exchange rate for SEK to the given currency."""
if not self.rates_fetched:
self.fetch_rates()
return 1/self.rates[currency]
def get_current(db_conn, item):
rows = db_conn.post_view(
db="pricing_exchange_rates",
ddoc="entire_document",
view="by_date",
descending=True,
limit=1
).get_result()["rows"]
if len(rows) != 0:
value = rows[0]["value"]
return value[item]
return None
def check_financial_crisis(current_val, new_val, currency):
if current_val is not None:
rel_change = (new_val-current_val)/current_val
print("INFO: Change in {} "
"exchange rate: {:.3f}%".format(currency, 100*(rel_change)))
if abs(rel_change) > 0.20:
raise Exception("Financial crisis or rather; something is likely wrong!")
def main(config, fixer_io_config, push_to_server=False):
c = CurrencyRates(fixer_io_config)
# Will raise RatesNotAvailableError if not able to fetch from the api
usd_to_sek = c.get_rate('USD')
eur_to_sek = c.get_rate('EUR')
# Inconsistent results for Euro after broken API was updated
if isinstance(eur_to_sek, str):
eur_to_sek = float(eur_to_sek)
# Create the doc that will be uploaded
doc = {}
doc['Issued at'] = datetime.datetime.now().isoformat()
# I know it's bad practice to call the _source_url method,
# but if it breaks it breaks.
doc['Data source'] = "Fixer.io via ({})".format(c._source_url)
doc['USD_in_SEK'] = usd_to_sek
doc['EUR_in_SEK'] = eur_to_sek
# Load the statusdb database
with open(config) as settings_file:
server_settings = yaml.load(settings_file, Loader=yaml.SafeLoader)
couch = cloudant_v1.CloudantV1(
authenticator=CouchDbSessionAuthenticator(
server_settings["statusdb"].get("username"), server_settings["statusdb"].get("password")
)
)
couch.set_service_url(server_settings["statusdb"].get("url"))
# Check that new is not too strange compared to current.
# This is a safety measure so that we have lower risk of having erroneus
# exchange rates in the db.
current_usd = get_current(couch, 'USD_in_SEK')
current_eur = get_current(couch, 'EUR_in_SEK')
check_financial_crisis(current_usd, usd_to_sek, 'USD')
check_financial_crisis(current_eur, eur_to_sek, 'EUR')
# Completely conserved currencies are also strange.
if (current_eur is not None) and (current_usd is not None):
# This assumes the script is not ran too often
# (api udpates once per day)
small_number = 0.0000000001
if (abs(current_usd - usd_to_sek) < small_number) and \
(abs(current_eur - eur_to_sek) < small_number):
raise Exception("Super stable currencies? Stale api would be my guess.")
if push_to_server:
couch.post_document(
db="pricing_exchange_rates",
document=doc
)
else:
print(doc)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--statusdb_config', required=True,
help='The statusdb_cred.yaml file.')
parser.add_argument('--fixer_io_config', required=True,
help='The fixer_io.yaml file.')
parser.add_argument('--push', action='store_true', help='Use this tag to '
"make the script push the changes to statusdb")
args = parser.parse_args()
main(args.statusdb_config, args.fixer_io_config, push_to_server=args.push)