-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexchange.py
executable file
·206 lines (160 loc) · 7.15 KB
/
exchange.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
196
197
198
199
200
201
202
203
204
205
206
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from json import loads
from os import environ
from hashlib import sha1
from signal import signal, SIGINT
from urllib.request import urlopen
from time import sleep, time, strftime, localtime
"""Monitor basket of currencies relative to the USD and highlight changes
> python3 exchange.py
**Note: Requires CL_KEY to be set in OS shell environment
See: https://currencylayer.com/documentation
Public domain by Michael OConnor <gmikeoc@gmail.com>
Also available under the terms of MIT license
Copyright (c) 2018 Michael E. O'Connor
"""
__version__ = "1.3"
# Ascii sequences used to control console terminal display colors
cur_col = {
'blue': '\033[94m',
'green': '\033[92m',
'yellow': '\033[93m',
'red': '\033[91m',
'endc': '\033[0m'
}
class CurrencyLayer:
def __init__(self, key, basket):
"""Build URL we will use to get latest exchange rates
Args:
key - Access Key provided when siging up for CUrrencyLayer Account
basket - Tuple of comma separated currency abbreviations
"""
base_url = 'http://www.apilayer.net/api/live?'
self.cl_url = base_url + 'access_key=' + key + '¤cies='
for c in basket:
self.cl_url += c + ',' # OK to leave trailing ','
def get_rates(self, url):
"""Open URL, read and decode JSON formatted response and confirm query
was successful. If not, exit the program with some helpful diagnostics.
Args:
- url: fully formed URL we want to open and process results from
"""
try:
webUrl = urlopen(url)
except:
print("Error: Not able to open: {}".format(url))
raise SystemExit()
rate_json = webUrl.read()
rate_dict = loads(rate_json.decode('utf-8'))
# Check to see if response if valid and display error info if not
if rate_dict['success'] is False:
print('Error: code = {}, type = {}, \ninfo = {}'.format(
rate_dict['error']['code'],
rate_dict['error']['type'],
rate_dict['error']['info']))
raise SystemExit()
else:
return (rate_dict)
def monitor(self, interval):
"""Query currency exchange data and output results to system console.
For each query, compare current time with timestamp of last quote and
use the delta to adjust delay until next query.
Args:
- interval: Desired query interval in minutes (typically 60)
"""
first_pass = True
while True:
# Open URL provided, read data and onfirm quote data is valid
rates = self.get_rates(self.cl_url)
# Calculate hash on quote data structure and use to detect changes
quote_hash = sha1(str(rates['quotes']).encode("ascii")).hexdigest()
# Determine number of minutes between last quote and current time
quote_time = rates['timestamp']
quote_delay = (time()-quote_time) / 60
# 1st time through initialize variables and display current rates
# then loop back to top of while() loop
if first_pass:
print('{} Begin monitoring'.format(t_stamp(time())))
prev_hash = quote_hash
prev_quote = rates['quotes']
print('Last quote updated: {}\n'.format(t_stamp(quote_time)))
for exch, cur_rate in prev_quote.items():
in_usd = exch[-3:] + '/USD'
in_for = 'USD/' + exch[3:]
print('{}: {:>8.5f} {}: {:>9.5f}'.format(
in_usd, 1/cur_rate, in_for, cur_rate))
first_pass = False
continue
# Compare hashs to determine if change has occured and, if so,
# display exchange rates, including % of change, using color coding
# such that a relative increase in USD strength is green,
# a decrease is red and no change is output in yellow text.
if quote_hash != prev_hash:
print('\n {}: Change(s) detected\n'.format(t_stamp(time())))
print(t_stamp(time()))
for exch, cur_rate in rates['quotes'].items():
prev_rate = prev_quote[exch]
delta = abs((1 - (cur_rate / prev_rate)) * 100)
if cur_rate == prev_rate:
color = 'yellow' # No change
elif cur_rate > prev_rate:
color = 'green' # Strong USD
else:
color = 'red' # Weaker USD
# Display both 'Foreign/USD' and 'USD/Foreign' results
in_usd = exch[-3:] + '/USD'
in_for = 'USD/' + exch[3:]
print('{}{}: {:>8.5f} {}: {:>9.5f} {:>5.2f}%'.format(
cur_col[color], in_usd, 1/cur_rate,
in_for, cur_rate, delta))
print(cur_col['endc'], end='') # Return cursor color to orig
prev_hash = quote_hash
prev_quote = rates['quotes']
# Use time delta between current time and last quote time to
# calculate number of minutes to wait until next query. Display
# progress bar to mark passage of time. If for some reason, delay
# is greater than interval, use absolute value of time delta
wait_time = int(abs(interval - quote_delay))
print('\nNext query in {} minutes '.format(wait_time), end='')
tbar_sleep(wait_time)
def t_stamp(t):
"""Timestamp utility formats date and time using UNIX styletime value."""
return(strftime('%y-%m-%d %H:%M %Z', localtime(t)))
def tbar_sleep(width):
"""Create a progress bar to mark passage of time in minutes"""
print('[' + '-'*width, end=']', flush=True)
for i in range(width+1):
print('\b', end='', flush=True)
sleep(0.02)
for i in range(width):
sleep(60)
print(u'\u2588', end='', flush=True) # Display BLOCK character
print('\n')
def signal_handler(signal, frame):
"""Signal handler for CTRL-C manual termination"""
print(cur_col['endc'] + '\nProgram terminated manually', '', '\n')
raise SystemExit()
def main():
"""
Read API key from from os.environ(), exit if not set. Define basket of
currencies we wish to monitor. Set monitoring interval, instantiate
CurrencyLayer() object and invoke monitoring() method with desired
interval.
"""
try:
key = environ['CL_KEY']
except KeyError:
print('Error: CL_KEY environment valiable not set')
print('Command: export CL_KEY=<key value>')
raise SystemExit()
basket = ('EUR', 'GBP', 'CNY', 'CAD', 'AUD', 'JPY')
interval = 60 # In minutes
c = CurrencyLayer(key, basket)
c.monitor(interval)
if __name__ == '__main__':
"""When invoked from shell, call signal() to handle CRTL-C from user
and invoke main() function
"""
signal(SIGINT, signal_handler)
main()