Exception caused by tx_list not defined
[electrum-nvc.git] / plugins / exchange_rate.py
1 from PyQt4.QtGui import *
2 from PyQt4.QtCore import *
3
4 import datetime
5 import decimal
6 import httplib
7 import json
8 import threading
9 import re
10 from decimal import Decimal
11 from electrum.plugins import BasePlugin
12 from electrum.i18n import _
13 from electrum_gui.qt.util import *
14
15
16 EXCHANGES = ["BitcoinAverage",
17              "BitcoinVenezuela",
18              "BitPay",
19              "Blockchain",
20              "BTCChina",
21              "CaVirtEx",
22              "Coinbase",
23              "CoinDesk",
24              "LocalBitcoins",
25              "Winkdex"]
26              
27
28 class Exchanger(threading.Thread):
29
30     def __init__(self, parent):
31         threading.Thread.__init__(self)
32         self.daemon = True
33         self.parent = parent
34         self.quote_currencies = None
35         self.lock = threading.Lock()
36         self.query_rates = threading.Event()
37         self.use_exchange = self.parent.config.get('use_exchange', "Blockchain")
38         self.parent.exchanges = EXCHANGES
39         self.parent.currencies = ["EUR","GBP","USD"]
40         self.parent.win.emit(SIGNAL("refresh_exchanges_combo()"))
41         self.parent.win.emit(SIGNAL("refresh_currencies_combo()"))
42         self.is_running = False
43
44     def get_json(self, site, get_string):
45         try:
46             connection = httplib.HTTPSConnection(site)
47             connection.request("GET", get_string)
48         except Exception:
49             raise
50         resp = connection.getresponse()
51         if resp.reason == httplib.responses[httplib.NOT_FOUND]:
52             raise
53         try:
54             json_resp = json.loads(resp.read())
55         except Exception:
56             raise
57         return json_resp
58
59
60     def exchange(self, btc_amount, quote_currency):
61         with self.lock:
62             if self.quote_currencies is None:
63                 return None
64             quote_currencies = self.quote_currencies.copy()
65         if quote_currency not in quote_currencies:
66             return None
67         if self.use_exchange == "CoinDesk":
68             try:
69                 resp_rate = self.get_json('api.coindesk.com', "/v1/bpi/currentprice/" + str(quote_currency) + ".json")
70             except Exception:
71                 return
72             return btc_amount * decimal.Decimal(str(resp_rate["bpi"][str(quote_currency)]["rate_float"]))
73         return btc_amount * decimal.Decimal(quote_currencies[quote_currency])
74
75     def stop(self):
76         self.is_running = False
77
78     def update_rate(self):
79         self.use_exchange = self.parent.config.get('use_exchange', "Blockchain")
80         update_rates = {
81             "BitcoinAverage": self.update_ba,
82             "BitcoinVenezuela": self.update_bv,
83             "BitPay": self.update_bp,
84             "Blockchain": self.update_bc,
85             "BTCChina": self.update_CNY,
86             "CaVirtEx": self.update_cv,
87             "CoinDesk": self.update_cd,
88             "Coinbase": self.update_cb,
89             "LocalBitcoins": self.update_lb,
90             "Winkdex": self.update_wd,
91         }
92         try:
93             update_rates[self.use_exchange]()
94         except KeyError:
95             return
96
97     def run(self):
98         self.is_running = True
99         while self.is_running:
100             self.query_rates.clear()
101             self.update_rate()
102             self.query_rates.wait(150)
103
104
105     def update_cd(self):
106         try:
107             resp_currencies = self.get_json('api.coindesk.com', "/v1/bpi/supported-currencies.json")
108         except Exception:
109             return
110
111         quote_currencies = {}
112         for cur in resp_currencies:
113             quote_currencies[str(cur["currency"])] = 0.0
114         with self.lock:
115             self.quote_currencies = quote_currencies
116         self.parent.set_currencies(quote_currencies)
117     
118     def update_wd(self):
119         try:
120             winkresp = self.get_json('winkdex.com', "/static/data/0_600_288.json")
121             ####could need nonce value in GET, no Docs available
122         except Exception:
123             return
124         quote_currencies = {"USD": 0.0}
125         ####get y of highest x in "prices"
126         lenprices = len(winkresp["prices"])
127         usdprice = winkresp["prices"][lenprices-1]["y"]
128         try:
129             quote_currencies["USD"] = decimal.Decimal(usdprice)
130             with self.lock:
131                 self.quote_currencies = quote_currencies
132         except KeyError:
133             pass
134         self.parent.set_currencies(quote_currencies)
135             
136     def update_cv(self):
137         try:
138             jsonresp = self.get_json('www.cavirtex.com', "/api/CAD/ticker.json")
139         except Exception:
140             return
141         quote_currencies = {"CAD": 0.0}
142         cadprice = jsonresp["last"]
143         try:
144             quote_currencies["CAD"] = decimal.Decimal(cadprice)
145             with self.lock:
146                 self.quote_currencies = quote_currencies
147         except KeyError:
148             pass
149         self.parent.set_currencies(quote_currencies)
150
151     def update_CNY(self):
152         try:
153             jsonresp = self.get_json('data.btcchina.com', "/data/ticker")
154         except Exception:
155             return
156         quote_currencies = {"CNY": 0.0}
157         cnyprice = jsonresp["ticker"]["last"]
158         try:
159             quote_currencies["CNY"] = decimal.Decimal(cnyprice)
160             with self.lock:
161                 self.quote_currencies = quote_currencies
162         except KeyError:
163             pass
164         self.parent.set_currencies(quote_currencies)
165
166     def update_bp(self):
167         try:
168             jsonresp = self.get_json('bitpay.com', "/api/rates")
169         except Exception:
170             return
171         quote_currencies = {}
172         try:
173             for r in jsonresp:
174                 quote_currencies[str(r["code"])] = decimal.Decimal(r["rate"])
175             with self.lock:
176                 self.quote_currencies = quote_currencies
177         except KeyError:
178             pass
179         self.parent.set_currencies(quote_currencies)
180
181     def update_cb(self):
182         try:
183             jsonresp = self.get_json('coinbase.com', "/api/v1/currencies/exchange_rates")
184         except Exception:
185             return
186
187         quote_currencies = {}
188         try:
189             for r in jsonresp:
190                 if r[:7] == "btc_to_":
191                     quote_currencies[r[7:].upper()] = self._lookup_rate_cb(jsonresp, r)
192             with self.lock:
193                 self.quote_currencies = quote_currencies
194         except KeyError:
195             pass
196         self.parent.set_currencies(quote_currencies)
197
198
199     def update_bc(self):
200         try:
201             jsonresp = self.get_json('blockchain.info', "/ticker")
202         except Exception:
203             return
204         quote_currencies = {}
205         try:
206             for r in jsonresp:
207                 quote_currencies[r] = self._lookup_rate(jsonresp, r)
208             with self.lock:
209                 self.quote_currencies = quote_currencies
210         except KeyError:
211             pass
212         self.parent.set_currencies(quote_currencies)
213         # print "updating exchange rate", self.quote_currencies["USD"]
214
215     def update_lb(self):
216         try:
217             jsonresp = self.get_json('localbitcoins.com', "/bitcoinaverage/ticker-all-currencies/")
218         except Exception:
219             return
220         quote_currencies = {}
221         try:
222             for r in jsonresp:
223                 quote_currencies[r] = self._lookup_rate_lb(jsonresp, r)
224             with self.lock:
225                 self.quote_currencies = quote_currencies
226         except KeyError:
227             pass
228         self.parent.set_currencies(quote_currencies)
229                 
230
231     def update_bv(self):
232         try:
233             jsonresp = self.get_json('api.bitcoinvenezuela.com', "/")
234         except Exception:
235             return
236         quote_currencies = {}
237         try:
238             for r in jsonresp["BTC"]:
239                 quote_currencies[r] = Decimal(jsonresp["BTC"][r])
240             with self.lock:
241                 self.quote_currencies = quote_currencies
242         except KeyError:
243             pass
244         self.parent.set_currencies(quote_currencies)
245
246
247     def update_ba(self):
248         try:
249             jsonresp = self.get_json('api.bitcoinaverage.com', "/ticker/global/all")
250         except Exception:
251             return
252         quote_currencies = {}
253         try:
254             for r in jsonresp:
255                 if not r == "timestamp":
256                     quote_currencies[r] = self._lookup_rate_ba(jsonresp, r)
257             with self.lock:
258                 self.quote_currencies = quote_currencies
259         except KeyError:
260             pass
261         self.parent.set_currencies(quote_currencies)
262
263
264     def get_currencies(self):
265         return [] if self.quote_currencies == None else sorted(self.quote_currencies.keys())
266
267     def _lookup_rate(self, response, quote_id):
268         return decimal.Decimal(str(response[str(quote_id)]["15m"]))
269     def _lookup_rate_cb(self, response, quote_id):
270         return decimal.Decimal(str(response[str(quote_id)]))
271     def _lookup_rate_ba(self, response, quote_id):
272         return decimal.Decimal(response[str(quote_id)]["last"])
273     def _lookup_rate_lb(self, response, quote_id):
274         return decimal.Decimal(response[str(quote_id)]["rates"]["last"])
275
276
277 class Plugin(BasePlugin):
278
279     def fullname(self):
280         return "Exchange rates"
281
282     def description(self):
283         return """exchange rates, retrieved from blockchain.info, CoinDesk, or Coinbase"""
284
285
286     def __init__(self,a,b):
287         BasePlugin.__init__(self,a,b)
288         self.currencies = [self.config.get('currency', "EUR")]
289         self.exchanges = [self.config.get('use_exchange', "Blockchain")]
290
291     def init(self):
292         self.win = self.gui.main_window
293         self.win.connect(self.win, SIGNAL("refresh_currencies()"), self.win.update_status)
294         self.btc_rate = Decimal(0.0)
295         # Do price discovery
296         self.exchanger = Exchanger(self)
297         self.exchanger.start()
298         self.gui.exchanger = self.exchanger #
299
300     def set_currencies(self, currency_options):
301         self.currencies = sorted(currency_options)
302         self.win.emit(SIGNAL("refresh_currencies()"))
303         self.win.emit(SIGNAL("refresh_currencies_combo()"))
304
305
306     def set_quote_text(self, btc_balance, r):
307         r[0] = self.create_quote_text(Decimal(btc_balance) / 100000000)
308
309     def create_quote_text(self, btc_balance):
310         quote_currency = self.config.get("currency", "EUR")
311         self.exchanger.use_exchange = self.config.get("use_exchange", "Blockchain")
312         cur_rate = self.exchanger.exchange(Decimal(1.0), quote_currency)
313         if cur_rate is None:
314             quote_text = ""
315         else:
316             quote_balance = btc_balance * Decimal(cur_rate)
317             self.btc_rate = cur_rate
318             quote_text = "%.2f %s" % (quote_balance, quote_currency)
319         return quote_text
320
321     def load_wallet(self, wallet):
322         self.wallet = wallet
323         tx_list = {}
324         for item in self.wallet.get_tx_history(self.wallet.storage.get("current_account", None)):
325             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
326             tx_list[tx_hash] = {'value': value, 'timestamp': timestamp, 'balance': balance}
327             
328         self.tx_list = tx_list
329         
330
331     def requires_settings(self):
332         return True
333
334
335     def toggle(self):
336         out = BasePlugin.toggle(self)
337         self.win.update_status()
338         return out
339
340
341     def close(self):
342         self.exchanger.stop()
343
344     def history_tab_update(self):
345         if self.config.get('history_rates', 'unchecked') == "checked":
346             try:
347                 tx_list = self.tx_list
348             except Exception:
349                 return
350
351             try:
352                 mintimestr = datetime.datetime.fromtimestamp(int(min(tx_list.items(), key=lambda x: x[1]['timestamp'])[1]['timestamp'])).strftime('%Y-%m-%d')
353             except ValueError:
354                 return
355             maxtimestr = datetime.datetime.now().strftime('%Y-%m-%d')
356             try:
357                 resp_hist = self.exchanger.get_json('api.coindesk.com', "/v1/bpi/historical/close.json?start=" + mintimestr + "&end=" + maxtimestr)
358             except Exception:
359                 return
360
361             self.gui.main_window.is_edit = True
362             self.gui.main_window.history_list.setColumnCount(6)
363             self.gui.main_window.history_list.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance'), _('Fiat Amount')] )
364             root = self.gui.main_window.history_list.invisibleRootItem()
365             childcount = root.childCount()
366             for i in range(childcount):
367                 item = root.child(i)
368                 try:
369                     tx_info = tx_list[str(item.data(0, Qt.UserRole).toPyObject())]
370                 except Exception:
371                     newtx = self.wallet.get_tx_history()
372                     v = newtx[[x[0] for x in newtx].index(str(item.data(0, Qt.UserRole).toPyObject()))][3]
373                    
374                     tx_info = {'timestamp':int(datetime.datetime.now().strftime("%s")), 'value': v }
375                     pass
376                 tx_time = int(tx_info['timestamp'])
377                 tx_time_str = datetime.datetime.fromtimestamp(tx_time).strftime('%Y-%m-%d')
378                 try:
379                     tx_USD_val = "%.2f %s" % (Decimal(tx_info['value']) / 100000000 * Decimal(resp_hist['bpi'][tx_time_str]), "USD")
380                 except KeyError:
381                     tx_USD_val = "%.2f %s" % (self.btc_rate * Decimal(tx_info['value'])/100000000 , "USD")
382
383                 item.setText(5, tx_USD_val)
384                 if Decimal(tx_info['value']) < 0:
385                     item.setForeground(5, QBrush(QColor("#BC1E1E")))
386
387             for i, width in enumerate(self.gui.main_window.column_widths['history']):
388                 self.gui.main_window.history_list.setColumnWidth(i, width)
389             self.gui.main_window.history_list.setColumnWidth(4, 140)
390             self.gui.main_window.history_list.setColumnWidth(5, 120)
391             self.gui.main_window.is_edit = False
392        
393
394     def settings_widget(self, window):
395         return EnterButton(_('Settings'), self.settings_dialog)
396
397     def settings_dialog(self):
398         d = QDialog()
399         layout = QGridLayout(d)
400         layout.addWidget(QLabel(_('Exchange rate API: ')), 0, 0)
401         layout.addWidget(QLabel(_('Currency: ')), 1, 0)
402         layout.addWidget(QLabel(_('History Rates: ')), 2, 0)
403         combo = QComboBox()
404         combo_ex = QComboBox()
405         hist_checkbox = QCheckBox()
406         hist_checkbox.setEnabled(False)
407         if self.config.get('history_rates', 'unchecked') == 'unchecked':
408             hist_checkbox.setChecked(False)
409         else:
410             hist_checkbox.setChecked(True)
411         ok_button = QPushButton(_("OK"))
412
413         def on_change(x):
414             try:
415                 cur_request = str(self.currencies[x])
416             except Exception:
417                 return
418             if cur_request != self.config.get('currency', "EUR"):
419                 self.config.set_key('currency', cur_request, True)
420                 if cur_request == "USD" and self.config.get('use_exchange', "Blockchain") == "CoinDesk":
421                     hist_checkbox.setEnabled(True)
422                 else:
423                     hist_checkbox.setChecked(False)
424                     hist_checkbox.setEnabled(False)
425                 self.win.update_status()
426
427         def disable_check():
428             hist_checkbox.setChecked(False)
429             hist_checkbox.setEnabled(False)
430
431         def on_change_ex(x):
432             cur_request = str(self.exchanges[x])
433             if cur_request != self.config.get('use_exchange', "Blockchain"):
434                 self.config.set_key('use_exchange', cur_request, True)
435                 self.currencies = []
436                 combo.clear()
437                 self.exchanger.query_rates.set()
438                 if cur_request == "CoinDesk":
439                     if self.config.get('currency', "EUR") == "USD":
440                         hist_checkbox.setEnabled(True)
441                     else:
442                         disable_check()
443                 else:
444                     disable_check()
445                 set_currencies(combo)
446                 self.win.update_status()
447
448         def on_change_hist(checked):
449             if checked:
450                 self.config.set_key('history_rates', 'checked')
451                 self.history_tab_update()
452             else:
453                 self.config.set_key('history_rates', 'unchecked')
454                 self.gui.main_window.history_list.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
455                 self.gui.main_window.history_list.setColumnCount(5)
456                 for i,width in enumerate(self.gui.main_window.column_widths['history']):
457                     self.gui.main_window.history_list.setColumnWidth(i, width)
458
459         def set_hist_check(hist_checkbox):
460             if self.config.get('use_exchange', "Blockchain") == "CoinDesk":
461                 hist_checkbox.setEnabled(True)
462             else:
463                 hist_checkbox.setEnabled(False) 
464         
465         def set_currencies(combo):
466             current_currency = self.config.get('currency', "EUR")
467             try:
468                 combo.clear()
469             except Exception:
470                 return
471             combo.addItems(self.currencies)
472             try:
473                 index = self.currencies.index(current_currency)
474             except Exception:
475                 index = 0
476             combo.setCurrentIndex(index)
477
478         def set_exchanges(combo_ex):
479             try:
480                 combo_ex.clear()
481             except Exception:
482                 return
483             combo_ex.addItems(self.exchanges)
484             try:
485                 index = self.exchanges.index(self.config.get('use_exchange', "Blockchain"))
486             except Exception:
487                 index = 0
488             combo_ex.setCurrentIndex(index)
489
490         def ok_clicked():
491             d.accept();
492
493         set_exchanges(combo_ex)
494         set_currencies(combo)
495         set_hist_check(hist_checkbox)
496         combo.currentIndexChanged.connect(on_change)
497         combo_ex.currentIndexChanged.connect(on_change_ex)
498         hist_checkbox.stateChanged.connect(on_change_hist)
499         combo.connect(self.win, SIGNAL('refresh_currencies_combo()'), lambda: set_currencies(combo))
500         combo_ex.connect(d, SIGNAL('refresh_exchanges_combo()'), lambda: set_exchanges(combo_ex))
501         ok_button.clicked.connect(lambda: ok_clicked())
502         layout.addWidget(combo,1,1)
503         layout.addWidget(combo_ex,0,1)
504         layout.addWidget(hist_checkbox,2,1)
505         layout.addWidget(ok_button,3,1)
506         
507         if d.exec_():
508             return True
509         else:
510             return False
511
512
513