Browse Source

Capital gains: Let user enter fiat value of transactions.

master
ThomasV 8 years ago
parent
commit
4cbdd25c93
  1. 34
      gui/qt/history_list.py
  2. 65
      lib/wallet.py

34
gui/qt/history_list.py

@ -63,6 +63,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop):
if fx and fx.show_history(): if fx and fx.show_history():
headers.extend(['%s '%fx.ccy + _('Amount'), '%s '%fx.ccy + _('Balance')]) headers.extend(['%s '%fx.ccy + _('Amount'), '%s '%fx.ccy + _('Balance')])
headers.extend(['%s '%fx.ccy + _('Capital Gains')]) headers.extend(['%s '%fx.ccy + _('Capital Gains')])
self.editable_columns.extend([6])
self.update_headers(headers) self.update_headers(headers)
def get_domain(self): def get_domain(self):
@ -87,14 +88,20 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop):
balance_str = self.parent.format_amount(balance, whitespaces=True) balance_str = self.parent.format_amount(balance, whitespaces=True)
label = self.wallet.get_label(tx_hash) label = self.wallet.get_label(tx_hash)
entry = ['', tx_hash, status_str, label, v_str, balance_str] entry = ['', tx_hash, status_str, label, v_str, balance_str]
fiat_value = None
if fx and fx.show_history(): if fx and fx.show_history():
date = timestamp_to_datetime(time.time() if conf <= 0 else timestamp) date = timestamp_to_datetime(time.time() if conf <= 0 else timestamp)
for amount in [value, balance]: fiat_value = self.wallet.get_fiat_value(tx_hash, fx.ccy)
text = fx.historical_value_str(amount, date) if not fiat_value:
entry.append(text) value_str = fx.historical_value_str(value, date)
else:
value_str = str(fiat_value)
entry.append(value_str)
balance_str = fx.historical_value_str(balance, date)
entry.append(balance_str)
# fixme: should use is_mine # fixme: should use is_mine
if value < 0: if value < 0:
cg = self.wallet.capital_gain(tx_hash, self.parent.fx.timestamp_rate) cg = self.wallet.capital_gain(tx_hash, fx.timestamp_rate, fx.ccy)
entry.append("%.2f"%cg if cg is not None else _('No data')) entry.append("%.2f"%cg if cg is not None else _('No data'))
item = QTreeWidgetItem(entry) item = QTreeWidgetItem(entry)
item.setIcon(0, icon) item.setIcon(0, icon)
@ -109,12 +116,27 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop):
if value and value < 0: if value and value < 0:
item.setForeground(3, QBrush(QColor("#BC1E1E"))) item.setForeground(3, QBrush(QColor("#BC1E1E")))
item.setForeground(4, QBrush(QColor("#BC1E1E"))) item.setForeground(4, QBrush(QColor("#BC1E1E")))
if fiat_value:
item.setForeground(6, QBrush(QColor("#1E1EFF")))
if tx_hash: if tx_hash:
item.setData(0, Qt.UserRole, tx_hash) item.setData(0, Qt.UserRole, tx_hash)
self.insertTopLevelItem(0, item) self.insertTopLevelItem(0, item)
if current_tx == tx_hash: if current_tx == tx_hash:
self.setCurrentItem(item) self.setCurrentItem(item)
def on_edited(self, item, column, prior):
'''Called only when the text actually changes'''
key = item.data(0, Qt.UserRole)
text = item.text(column)
# fixme
if column == 3:
self.parent.wallet.set_label(key, text)
self.update_labels()
self.parent.update_completions()
elif column == 6:
self.parent.wallet.set_fiat_value(key, self.parent.fx.ccy, text)
self.on_update()
def on_doubleclick(self, item, column): def on_doubleclick(self, item, column):
if self.permit_edit(item, column): if self.permit_edit(item, column):
super(HistoryList, self).on_doubleclick(item, column) super(HistoryList, self).on_doubleclick(item, column)
@ -170,8 +192,8 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop):
menu.addAction(_("Remove"), lambda: self.remove_local_tx(tx_hash)) menu.addAction(_("Remove"), lambda: self.remove_local_tx(tx_hash))
menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data)) menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data))
if column in self.editable_columns: for c in self.editable_columns:
menu.addAction(_("Edit {}").format(column_title), lambda: self.editItem(item, column)) menu.addAction(_("Edit {}").format(self.headerItem().text(c)), lambda: self.editItem(item, c))
menu.addAction(_("Details"), lambda: self.parent.show_transaction(tx)) menu.addAction(_("Details"), lambda: self.parent.show_transaction(tx))

65
lib/wallet.py

@ -185,6 +185,7 @@ class Abstract_Wallet(PrintError):
self.labels = storage.get('labels', {}) self.labels = storage.get('labels', {})
self.frozen_addresses = set(storage.get('frozen_addresses',[])) self.frozen_addresses = set(storage.get('frozen_addresses',[]))
self.history = storage.get('addr_history',{}) # address -> list(txid, height) self.history = storage.get('addr_history',{}) # address -> list(txid, height)
self.fiat_value = storage.get('fiat_value', {})
self.load_keystore() self.load_keystore()
self.load_addresses() self.load_addresses()
@ -342,13 +343,37 @@ class Abstract_Wallet(PrintError):
if old_text: if old_text:
self.labels.pop(name) self.labels.pop(name)
changed = True changed = True
if changed: if changed:
run_hook('set_label', self, name, text) run_hook('set_label', self, name, text)
self.storage.put('labels', self.labels) self.storage.put('labels', self.labels)
return changed return changed
def set_fiat_value(self, txid, ccy, text):
if txid not in self.transactions:
return
if not text:
d = self.fiat_value.get(ccy, {})
if d and txid in d:
d.pop(txid)
else:
return
else:
try:
Decimal(text)
except:
return
if ccy not in self.fiat_value:
self.fiat_value[ccy] = {}
self.fiat_value[ccy][txid] = text
self.storage.put('fiat_value', self.fiat_value)
def get_fiat_value(self, txid, ccy):
fiat_value = self.fiat_value.get(ccy, {}).get(txid)
try:
return Decimal(fiat_value)
except:
return
def is_mine(self, address): def is_mine(self, address):
return address in self.get_addresses() return address in self.get_addresses()
@ -1597,33 +1622,49 @@ class Abstract_Wallet(PrintError):
return v return v
raise BaseException('unknown txin value') raise BaseException('unknown txin value')
def capital_gain(self, txid, price_func): def price_at_timestamp(self, txid, price_func):
height, conf, timestamp = self.get_tx_height(txid)
return price_func(timestamp)
def capital_gain(self, txid, price_func, ccy):
""" """
Difference between the fiat price of coins leaving the wallet because of transaction txid, Difference between the fiat price of coins leaving the wallet because of transaction txid,
and the price of these coins when they entered the wallet. and the price of these coins when they entered the wallet.
price_func: function that returns the fiat price given a timestamp price_func: function that returns the fiat price given a timestamp
""" """
height, conf, timestamp = self.get_tx_height(txid)
tx = self.transactions[txid] tx = self.transactions[txid]
out_value = sum([ (value if not self.is_mine(address) else 0) for otype, address, value in tx.outputs() ]) ir, im, v, fee = self.get_wallet_delta(tx)
out_value = -v
fiat_value = self.get_fiat_value(txid, ccy)
if fiat_value is None:
p = self.price_at_timestamp(txid, price_func)
liquidation_price = None if p is None else out_value/Decimal(COIN) * p
else:
liquidation_price = - fiat_value
try: try:
return out_value/Decimal(COIN) * (price_func(timestamp) - self.average_price(tx, price_func)) return liquidation_price - out_value/Decimal(COIN) * self.average_price(tx, price_func, ccy)
except: except:
return None return None
def average_price(self, tx, price_func): def average_price(self, tx, price_func, ccy):
""" average price of the inputs of a transaction """ """ average price of the inputs of a transaction """
return sum(self.coin_price(txin, price_func) * self.txin_value(txin) for txin in tx.inputs()) / sum(self.txin_value(txin) for txin in tx.inputs()) input_value = sum(self.txin_value(txin) for txin in tx.inputs()) / Decimal(COIN)
total_price = sum(self.coin_price(txin, price_func, ccy, self.txin_value(txin)) for txin in tx.inputs())
return total_price / input_value
def coin_price(self, coin, price_func): def coin_price(self, coin, price_func, ccy, txin_value):
""" fiat price of acquisition of coin """ """ fiat price of acquisition of coin """
txid = coin['prevout_hash'] txid = coin['prevout_hash']
tx = self.transactions[txid] tx = self.transactions[txid]
if all([self.is_mine(txin['address']) for txin in tx.inputs()]): if all([self.is_mine(txin['address']) for txin in tx.inputs()]):
return self.average_price(tx, price_func) return self.average_price(tx, price_func, ccy) * txin_value/Decimal(COIN)
elif all([ not self.is_mine(txin['address']) for txin in tx.inputs()]): elif all([ not self.is_mine(txin['address']) for txin in tx.inputs()]):
height, conf, timestamp = self.get_tx_height(txid) fiat_value = self.get_fiat_value(txid, ccy)
return price_func(timestamp) if fiat_value is not None:
return fiat_value
else:
return self.price_at_timestamp(txid, price_func) * txin_value/Decimal(COIN)
else: else:
# could be some coinjoin transaction.. # could be some coinjoin transaction..
return None return None

Loading…
Cancel
Save