@ -1,38 +1,71 @@
from PyQt4 . QtGui import *
from PyQt4 . QtGui import *
from PyQt4 . QtCore import *
from PyQt4 . QtCore import *
import datetime
from datetime import datetime , date
import inspect
import inspect
import requests
import requests
import sys
import sys
import threading
import threading
import time
import time
import traceback
from decimal import Decimal
from decimal import Decimal
from electrum . bitcoin import COIN
from electrum . bitcoin import COIN
from electrum . plugins import BasePlugin , hook
from electrum . plugins import BasePlugin , hook
from electrum . i18n import _
from electrum . i18n import _
from electrum . util import ThreadJob
from electrum . util import print_error , ThreadJob , timestamp_to_datetime
from electrum . util import format_satoshis
from electrum_gui . qt . util import *
from electrum_gui . qt . util import *
from electrum_gui . qt . amountedit import AmountEdit
from electrum_gui . qt . amountedit import AmountEdit
class ExchangeBase :
class ExchangeBase :
history = { }
quotes = { }
def get_json ( self , site , get_string ) :
def get_json ( self , site , get_string ) :
response = requests . request ( ' GET ' , ' https:// ' + site + get_string ,
response = requests . request ( ' GET ' , ' https:// ' + site + get_string ,
headers = { ' User-Agent ' : ' Electrum ' } )
headers = { ' User-Agent ' : ' Electrum ' } )
return response . json ( )
return response . json ( )
def print_error ( self , * msg ) :
print_error ( " [ %s ] " % self . name ( ) , * msg )
def name ( self ) :
def name ( self ) :
return self . __class__ . __name__
return self . __class__ . __name__
def update ( self , ccy ) :
def update ( self , ccy ) :
return { }
self . quotes = self . get_rates ( ccy )
return self . quotes
def history_ccys ( self ) :
def history_ccys ( self ) :
return [ ]
return [ ]
def historical_rates ( self , ccy , minstr , maxstr ) :
def set_history ( self , ccy , history ) :
return { }
''' History is a map of " % Y- % m- %d " strings to values '''
self . history [ ccy ] = history
def get_historical_rates ( self , ccy ) :
result = self . history . get ( ccy )
if not result :
self . print_error ( " requesting historical rates for " , ccy )
t = threading . Thread ( target = self . historical_rates , args = ( ccy , ) )
t . setDaemon ( True )
t . start ( )
return result
def historical_rate ( self , ccy , d_t ) :
if d_t . date ( ) == datetime . today ( ) . date ( ) :
rate = self . quotes . get ( ccy )
else :
rate = self . history . get ( ccy , { } ) . get ( d_t . strftime ( ' % Y- % m- %d ' ) )
return rate
def historical_value_str ( self , ccy , satoshis , d_t ) :
rate = self . historical_rate ( ccy , d_t )
if rate :
value = round ( Decimal ( satoshis ) / COIN * Decimal ( rate ) , 2 )
return " " . join ( [ " {:,.2f} " . format ( value ) , ccy ] )
return _ ( " No data " )
class BitcoinAverage ( ExchangeBase ) :
class BitcoinAverage ( ExchangeBase ) :
def update ( self , ccy ) :
def update ( self , ccy ) :
@ -41,63 +74,63 @@ class BitcoinAverage(ExchangeBase):
for r in json if r != ' timestamp ' ] )
for r in json if r != ' timestamp ' ] )
class BitcoinVenezuela ( ExchangeBase ) :
class BitcoinVenezuela ( ExchangeBase ) :
def update ( self , ccy ) :
def get_rates ( self , ccy ) :
json = self . get_json ( ' api.bitcoinvenezuela.com ' , ' / ' )
json = self . get_json ( ' api.bitcoinvenezuela.com ' , ' / ' )
return dict ( [ ( r , Decimal ( json [ ' BTC ' ] [ r ] ) )
return dict ( [ ( r , Decimal ( json [ ' BTC ' ] [ r ] ) )
for r in json [ ' BTC ' ] ] )
for r in json [ ' BTC ' ] ] )
def history_ccys ( self ) :
def history_ccys ( self ) :
return [ ' ARS ' , ' VEF ' ]
return [ ' ARS ' , ' EUR ' , ' USD ' , ' VEF' ]
def historical_rates ( self , ccy , minstr , maxstr ) :
def historical_rates ( self , ccy ) :
return self . get_json ( ' api.bitcoinvenezuela.com ' ,
return self . get_json ( ' api.bitcoinvenezuela.com ' ,
" /historical/index.php?coin=BTC " ) [ ccy + ' _BTC ' ]
" /historical/index.php?coin=BTC " ) [ ccy + ' _BTC ' ]
class BTCParalelo ( ExchangeBase ) :
class BTCParalelo ( ExchangeBase ) :
def update ( self , ccy ) :
def get_rates ( self , ccy ) :
json = self . get_json ( ' btcparalelo.com ' , ' /api/price ' )
json = self . get_json ( ' btcparalelo.com ' , ' /api/price ' )
return { ' VEF ' : Decimal ( json [ ' price ' ] ) }
return { ' VEF ' : Decimal ( json [ ' price ' ] ) }
class Bitcurex ( ExchangeBase ) :
class Bitcurex ( ExchangeBase ) :
def update ( self , ccy ) :
def get_rates ( self , ccy ) :
json = self . get_json ( ' pln.bitcurex.com ' , ' /data/ticker.json ' )
json = self . get_json ( ' pln.bitcurex.com ' , ' /data/ticker.json ' )
pln_price = json [ ' last ' ]
pln_price = json [ ' last ' ]
return { ' PLN ' : Decimal ( pln_price ) }
return { ' PLN ' : Decimal ( pln_price ) }
class Bitmarket ( ExchangeBase ) :
class Bitmarket ( ExchangeBase ) :
def update ( self , ccy ) :
def get_rates ( self , ccy ) :
json = self . get_json ( ' www.bitmarket.pl ' , ' /json/BTCPLN/ticker.json ' )
json = self . get_json ( ' www.bitmarket.pl ' , ' /json/BTCPLN/ticker.json ' )
return { ' PLN ' : Decimal ( json [ ' last ' ] ) }
return { ' PLN ' : Decimal ( json [ ' last ' ] ) }
class BitPay ( ExchangeBase ) :
class BitPay ( ExchangeBase ) :
def update ( self , ccy ) :
def get_rates ( self , ccy ) :
json = self . get_json ( ' bitpay.com ' , ' /api/rates ' )
json = self . get_json ( ' bitpay.com ' , ' /api/rates ' )
return dict ( [ ( r [ ' code ' ] , Decimal ( r [ ' rate ' ] ) ) for r in json ] )
return dict ( [ ( r [ ' code ' ] , Decimal ( r [ ' rate ' ] ) ) for r in json ] )
class Blockchain ( ExchangeBase ) :
class Blockchain ( ExchangeBase ) :
def update ( self , ccy ) :
def get_rates ( self , ccy ) :
json = self . get_json ( ' blockchain.info ' , ' /ticker ' )
json = self . get_json ( ' blockchain.info ' , ' /ticker ' )
return dict ( [ ( r , Decimal ( json [ r ] [ ' 15m ' ] ) ) for r in json ] )
return dict ( [ ( r , Decimal ( json [ r ] [ ' 15m ' ] ) ) for r in json ] )
class BTCChina ( ExchangeBase ) :
class BTCChina ( ExchangeBase ) :
def update ( self , ccy ) :
def get_rates ( self , ccy ) :
json = self . get_json ( ' data.btcchina.com ' , ' /data/ticker ' )
json = self . get_json ( ' data.btcchina.com ' , ' /data/ticker ' )
return { ' CNY ' : Decimal ( json [ ' ticker ' ] [ ' last ' ] ) }
return { ' CNY ' : Decimal ( json [ ' ticker ' ] [ ' last ' ] ) }
class CaVirtEx ( ExchangeBase ) :
class CaVirtEx ( ExchangeBase ) :
def update ( self , ccy ) :
def get_rates ( self , ccy ) :
json = self . get_json ( ' www.cavirtex.com ' , ' /api/CAD/ticker.json ' )
json = self . get_json ( ' www.cavirtex.com ' , ' /api/CAD/ticker.json ' )
return { ' CAD ' : Decimal ( json [ ' last ' ] ) }
return { ' CAD ' : Decimal ( json [ ' last ' ] ) }
class Coinbase ( ExchangeBase ) :
class Coinbase ( ExchangeBase ) :
def update ( self , ccy ) :
def get_rates ( self , ccy ) :
json = self . get_json ( ' coinbase.com ' ,
json = self . get_json ( ' coinbase.com ' ,
' /api/v1/currencies/exchange_rates ' )
' /api/v1/currencies/exchange_rates ' )
return dict ( [ ( r [ 7 : ] . upper ( ) , Decimal ( json [ r ] ) )
return dict ( [ ( r [ 7 : ] . upper ( ) , Decimal ( json [ r ] ) )
for r in json if r . startswith ( ' btc_to_ ' ) ] )
for r in json if r . startswith ( ' btc_to_ ' ) ] )
class CoinDesk ( ExchangeBase ) :
class CoinDesk ( ExchangeBase ) :
def update ( self , ccy ) :
def get_rates ( self , ccy ) :
dicts = self . get_json ( ' api.coindesk.com ' ,
dicts = self . get_json ( ' api.coindesk.com ' ,
' /v1/bpi/supported-currencies.json ' )
' /v1/bpi/supported-currencies.json ' )
json = self . get_json ( ' api.coindesk.com ' ,
json = self . get_json ( ' api.coindesk.com ' ,
@ -107,16 +140,23 @@ class CoinDesk(ExchangeBase):
result [ ccy ] = Decimal ( json [ ' bpi ' ] [ ccy ] [ ' rate ' ] )
result [ ccy ] = Decimal ( json [ ' bpi ' ] [ ccy ] [ ' rate ' ] )
return result
return result
def history_starts ( self ) :
return { ' USD ' : ' 2012-11-30 ' }
def history_ccys ( self ) :
def history_ccys ( self ) :
return [ ' USD ' ]
return self . history_starts ( ) . keys ( )
def historical_rates ( self , ccy , minstr , maxstr ) :
def historical_rates ( self , ccy ) :
return self . get_json ( ' api.coindesk.com ' ,
start = self . history_starts ( ) [ ccy ]
" /v1/bpi/historical/close.json?start= "
end = datetime . today ( ) . strftime ( ' % Y- % m- %d ' )
+ minstr + " &end= " + maxstr )
# Note ?currency and ?index don't work as documented. Sigh.
query = ( ' /v1/bpi/historical/close.json?start= %s &end= %s '
% ( start , end ) )
json = self . get_json ( ' api.coindesk.com ' , query )
self . set_history ( ccy , json [ ' bpi ' ] )
class itBit ( ExchangeBase ) :
class itBit ( ExchangeBase ) :
def update ( self , ccy ) :
def get_rates ( self , ccy ) :
ccys = [ ' USD ' , ' EUR ' , ' SGD ' ]
ccys = [ ' USD ' , ' EUR ' , ' SGD ' ]
json = self . get_json ( ' api.itbit.com ' , ' /v1/markets/XBT %s /ticker ' % ccy )
json = self . get_json ( ' api.itbit.com ' , ' /v1/markets/XBT %s /ticker ' % ccy )
result = dict . fromkeys ( ccys )
result = dict . fromkeys ( ccys )
@ -124,20 +164,20 @@ class itBit(ExchangeBase):
return result
return result
class LocalBitcoins ( ExchangeBase ) :
class LocalBitcoins ( ExchangeBase ) :
def update ( self , ccy ) :
def get_rates ( self , ccy ) :
json = self . get_json ( ' localbitcoins.com ' ,
json = self . get_json ( ' localbitcoins.com ' ,
' /bitcoinaverage/ticker-all-currencies/ ' )
' /bitcoinaverage/ticker-all-currencies/ ' )
return dict ( [ ( r , Decimal ( json [ r ] [ ' rates ' ] [ ' last ' ] ) ) for r in json ] )
return dict ( [ ( r , Decimal ( json [ r ] [ ' rates ' ] [ ' last ' ] ) ) for r in json ] )
class Winkdex ( ExchangeBase ) :
class Winkdex ( ExchangeBase ) :
def update ( self , ccy ) :
def get_rates ( self , ccy ) :
json = self . get_json ( ' winkdex.com ' , ' /api/v0/price ' )
json = self . get_json ( ' winkdex.com ' , ' /api/v0/price ' )
return { ' USD ' : Decimal ( json [ ' price ' ] / 100.0 ) }
return { ' USD ' : Decimal ( json [ ' price ' ] / 100.0 ) }
def history_ccys ( self ) :
def history_ccys ( self ) :
return [ ' USD ' ]
return [ ' USD ' ]
def historical_rates ( self , ccy , minstr , maxstr ) :
def historical_rates ( self , ccy ) :
json = self . get_json ( ' winkdex.com ' ,
json = self . get_json ( ' winkdex.com ' ,
" /api/v0/series?start_time=1342915200 " )
" /api/v0/series?start_time=1342915200 " )
return json [ ' series ' ] [ 0 ] [ ' results ' ]
return json [ ' series ' ] [ 0 ] [ ' results ' ]
@ -147,7 +187,6 @@ class Exchanger(ThreadJob):
def __init__ ( self , parent ) :
def __init__ ( self , parent ) :
self . parent = parent
self . parent = parent
self . quotes = { }
self . timeout = 0
self . timeout = 0
def get_json ( self , site , get_string ) :
def get_json ( self , site , get_string ) :
@ -159,9 +198,8 @@ class Exchanger(ThreadJob):
try :
try :
rates = self . parent . exchange . update ( self . parent . fiat_unit ( ) )
rates = self . parent . exchange . update ( self . parent . fiat_unit ( ) )
except Exception as e :
except Exception as e :
self . parent . print_error ( e )
traceback . print_exc ( file = sys . stderr )
rates = { }
return
self . quotes = rates
self . parent . set_currencies ( rates )
self . parent . set_currencies ( rates )
self . parent . refresh_fields ( )
self . parent . refresh_fields ( )
@ -182,9 +220,9 @@ class Plugin(BasePlugin):
self . set_exchange ( self . config_exchange ( ) )
self . set_exchange ( self . config_exchange ( ) )
self . currencies = [ self . fiat_unit ( ) ]
self . currencies = [ self . fiat_unit ( ) ]
self . exchanger = Exchanger ( self )
self . exchanger = Exchanger ( self )
self . resp_ hist = { }
self . history = { }
self . btc_rate = Decimal ( " 0.0 " )
self . btc_rate = Decimal ( " 0.0 " )
self . wallet_tx_list = { }
self . get_historical_rates ( )
def config_exchange ( self ) :
def config_exchange ( self ) :
return self . config . get ( ' use_exchange ' , ' Blockchain ' )
return self . config . get ( ' use_exchange ' , ' Blockchain ' )
@ -216,7 +254,6 @@ class Plugin(BasePlugin):
self . add_send_edit ( window )
self . add_send_edit ( window )
self . add_receive_edit ( window )
self . add_receive_edit ( window )
window . update_status ( )
window . update_status ( )
self . new_wallets ( [ window . wallet ] )
def close ( self ) :
def close ( self ) :
BasePlugin . close ( self )
BasePlugin . close ( self )
@ -235,7 +272,7 @@ class Plugin(BasePlugin):
def exchange_rate ( self ) :
def exchange_rate ( self ) :
''' Returns None, or the exchange rate as a Decimal '''
''' Returns None, or the exchange rate as a Decimal '''
rate = self . exchanger . quotes . get ( self . fiat_unit ( ) )
rate = self . exchange . quotes . get ( self . fiat_unit ( ) )
if rate :
if rate :
return Decimal ( rate )
return Decimal ( rate )
@ -279,49 +316,17 @@ class Plugin(BasePlugin):
@hook
@hook
def load_wallet ( self , wallet , window ) :
def load_wallet ( self , wallet , window ) :
self . new_wallets ( [ wallet ] )
self . get_historical_rates ( )
def new_wallets ( self , wallets ) :
if wallets :
# For mid-session plugin loads
self . set_network ( wallets [ 0 ] . network )
for wallet in wallets :
if wallet not in self . wallet_tx_list :
self . wallet_tx_list [ wallet ] = None
self . get_historical_rates ( )
def get_historical_rates ( self ) :
def get_historical_rates ( self ) :
''' Request historic rates for all wallets for which they haven ' t yet
if self . config_history ( ) :
been requested
self . exchange . get_historical_rates ( self . fiat_unit ( ) )
'''
if not self . config_history ( ) :
return
all_txs = { }
new = False
for wallet in self . wallet_tx_list :
if self . wallet_tx_list [ wallet ] is None :
new = True
tx_list = { }
for item in wallet . get_history ( wallet . storage . get ( " current_account " , None ) ) :
tx_hash , conf , value , timestamp , balance = item
tx_list [ tx_hash ] = { ' value ' : value , ' timestamp ' : timestamp }
# FIXME: not robust to request failure
self . wallet_tx_list [ wallet ] = tx_list
all_txs . update ( self . wallet_tx_list [ wallet ] )
if new :
self . print_error ( " requesting historical FX rates " )
t = threading . Thread ( target = self . request_historical_rates ,
args = ( all_txs , ) )
t . setDaemon ( True )
t . start ( )
def request_historical_rates ( self , tx_list ) :
def request_historical_rates ( self ) :
try :
try :
mintimestr = datetime . datetime . fromtimestamp ( int ( min ( tx_list . items ( ) , key = lambda x : x [ 1 ] [ ' timestamp ' ] ) [ 1 ] [ ' timestamp ' ] ) ) . strftime ( ' % Y- % m- %d ' )
self . history = self . exchange . historical_rates ( self . fiat_unit ( ) )
maxtimestr = datetime . datetime . now ( ) . strftime ( ' % Y- % m- %d ' )
self . resp_hist = self . exchange . historical_rates (
self . fiat_unit ( ) , mintimestr , maxtimestr )
except Exception :
except Exception :
traceback . print_exc ( file = sys . stderr )
return
return
for window in self . parent . windows :
for window in self . parent . windows :
window . need_update . set ( )
window . need_update . set ( )
@ -330,67 +335,25 @@ class Plugin(BasePlugin):
return True
return True
@hook
@hook
def history_tab_update ( self , window ) :
def history_tab_update ( self , window , entries ) :
if self . config . get ( ' history_rates ' ) != " checked " :
if not self . config_history ( ) :
return
if not self . resp_hist :
return
wallet = window . wallet
tx_list = self . wallet_tx_list . get ( wallet )
if not wallet or not tx_list :
return
return
window . is_edit = True
history_list = window . history_list
window . history_list . setColumnCount ( 7 )
history_list . setColumnCount ( 7 )
window . history_list . setHeaderLabels ( [ ' ' , ' ' , _ ( ' Date ' ) , _ ( ' Description ' ) , _ ( ' Amount ' ) , _ ( ' Balance ' ) , _ ( ' Fiat Amount ' ) ] )
history_list . header ( ) . setResizeMode ( 6 , QHeaderView . ResizeToContents )
root = window . history_list . invisibleRootItem ( )
history_list . setHeaderLabels ( [ ' ' , ' ' , _ ( ' Date ' ) , _ ( ' Description ' ) , _ ( ' Amount ' ) , _ ( ' Balance ' ) , _ ( ' Fiat Amount ' ) ] )
childcount = root . childCount ( )
for item , tx in entries :
exchange = self . exchange . name ( )
tx_hash , conf , value , timestamp , balance = tx
for i in range ( childcount ) :
date = timestamp_to_datetime ( timestamp )
item = root . child ( i )
if not date :
try :
date = timestmap_to_datetime ( 0 )
tx_info = tx_list [ str ( item . data ( 0 , Qt . UserRole ) . toPyObject ( ) ) ]
text = self . exchange . historical_value_str ( self . fiat_unit ( ) ,
except Exception :
value , date )
newtx = wallet . get_history ( )
item . setText ( 6 , " %16s " % text )
v = newtx [ [ x [ 0 ] for x in newtx ] . index ( str ( item . data ( 0 , Qt . UserRole ) . toPyObject ( ) ) ) ] [ 2 ]
tx_info = { ' timestamp ' : int ( time . time ( ) ) , ' value ' : v }
pass
tx_time = int ( tx_info [ ' timestamp ' ] )
tx_value = Decimal ( str ( tx_info [ ' value ' ] ) ) / COIN
if exchange == " CoinDesk " :
tx_time_str = datetime . datetime . fromtimestamp ( tx_time ) . strftime ( ' % Y- % m- %d ' )
try :
tx_fiat_val = " %.2f %s " % ( tx_value * Decimal ( self . resp_hist [ ' bpi ' ] [ tx_time_str ] ) , " USD " )
except KeyError :
tx_fiat_val = " %.2f %s " % ( self . btc_rate * Decimal ( str ( tx_info [ ' value ' ] ) ) / COIN , " USD " )
elif exchange == " Winkdex " :
tx_time_str = datetime . datetime . fromtimestamp ( tx_time ) . strftime ( ' % Y- % m- %d ' ) + " T16:00:00-04:00 "
try :
tx_rate = self . resp_hist [ [ x [ ' timestamp ' ] for x in self . resp_hist ] . index ( tx_time_str ) ] [ ' price ' ]
tx_fiat_val = " %.2f %s " % ( tx_value * Decimal ( tx_rate ) / Decimal ( " 100.0 " ) , " USD " )
except ValueError :
tx_fiat_val = " %.2f %s " % ( self . btc_rate * Decimal ( tx_info [ ' value ' ] ) / COIN , " USD " )
except KeyError :
tx_fiat_val = _ ( " No data " )
elif exchange == " BitcoinVenezuela " :
tx_time_str = datetime . datetime . fromtimestamp ( tx_time ) . strftime ( ' % Y- % m- %d ' )
try :
num = self . resp_hist [ tx_time_str ] . replace ( ' , ' , ' ' )
tx_fiat_val = " %.2f %s " % ( tx_value * Decimal ( num ) , self . fiat_unit ( ) )
except KeyError :
tx_fiat_val = _ ( " No data " )
tx_fiat_val = " " * ( 12 - len ( tx_fiat_val ) ) + tx_fiat_val
item . setText ( 6 , tx_fiat_val )
item . setFont ( 6 , QFont ( MONOSPACE_FONT ) )
item . setFont ( 6 , QFont ( MONOSPACE_FONT ) )
if Decimal ( str ( tx_info [ ' value' ] ) ) < 0 :
if value < 0 :
item . setForeground ( 6 , QBrush ( QColor ( " #BC1E1E " ) ) )
item . setForeground ( 6 , QBrush ( QColor ( " #BC1E1E " ) ) )
# We autosize but in some cases QT doesn't handle that
# properly for new columns it seems
window . history_list . setColumnWidth ( 6 , 120 )
window . is_edit = False
def settings_widget ( self , window ) :
def settings_widget ( self , window ) :
return EnterButton ( _ ( ' Settings ' ) , self . settings_dialog )
return EnterButton ( _ ( ' Settings ' ) , self . settings_dialog )