electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 4cbdd25c93eb25be18cebdb99085921d046277d2
parent f8df8d60c44b40b63bbeb5dbd61fbfbdb1abd7b4
Author: ThomasV <thomasv@electrum.org>
Date:   Sun, 11 Feb 2018 17:26:13 +0100

Capital gains: Let user enter fiat value of transactions.

Diffstat:
Mgui/qt/history_list.py | 34++++++++++++++++++++++++++++------
Mlib/wallet.py | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++------------
2 files changed, 81 insertions(+), 18 deletions(-)

diff --git a/gui/qt/history_list.py b/gui/qt/history_list.py @@ -63,6 +63,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): if fx and fx.show_history(): headers.extend(['%s '%fx.ccy + _('Amount'), '%s '%fx.ccy + _('Balance')]) headers.extend(['%s '%fx.ccy + _('Capital Gains')]) + self.editable_columns.extend([6]) self.update_headers(headers) def get_domain(self): @@ -87,14 +88,20 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): balance_str = self.parent.format_amount(balance, whitespaces=True) label = self.wallet.get_label(tx_hash) entry = ['', tx_hash, status_str, label, v_str, balance_str] + fiat_value = None if fx and fx.show_history(): date = timestamp_to_datetime(time.time() if conf <= 0 else timestamp) - for amount in [value, balance]: - text = fx.historical_value_str(amount, date) - entry.append(text) + fiat_value = self.wallet.get_fiat_value(tx_hash, fx.ccy) + if not fiat_value: + 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 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')) item = QTreeWidgetItem(entry) item.setIcon(0, icon) @@ -109,12 +116,27 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): if value and value < 0: item.setForeground(3, QBrush(QColor("#BC1E1E"))) item.setForeground(4, QBrush(QColor("#BC1E1E"))) + if fiat_value: + item.setForeground(6, QBrush(QColor("#1E1EFF"))) if tx_hash: item.setData(0, Qt.UserRole, tx_hash) self.insertTopLevelItem(0, item) if current_tx == tx_hash: 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): if self.permit_edit(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(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data)) - if column in self.editable_columns: - menu.addAction(_("Edit {}").format(column_title), lambda: self.editItem(item, column)) + for c in self.editable_columns: + menu.addAction(_("Edit {}").format(self.headerItem().text(c)), lambda: self.editItem(item, c)) menu.addAction(_("Details"), lambda: self.parent.show_transaction(tx)) diff --git a/lib/wallet.py b/lib/wallet.py @@ -185,6 +185,7 @@ class Abstract_Wallet(PrintError): self.labels = storage.get('labels', {}) self.frozen_addresses = set(storage.get('frozen_addresses',[])) self.history = storage.get('addr_history',{}) # address -> list(txid, height) + self.fiat_value = storage.get('fiat_value', {}) self.load_keystore() self.load_addresses() @@ -342,13 +343,37 @@ class Abstract_Wallet(PrintError): if old_text: self.labels.pop(name) changed = True - if changed: run_hook('set_label', self, name, text) self.storage.put('labels', self.labels) - 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): return address in self.get_addresses() @@ -1597,33 +1622,49 @@ class Abstract_Wallet(PrintError): return v 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, and the price of these coins when they entered the wallet. price_func: function that returns the fiat price given a timestamp """ - height, conf, timestamp = self.get_tx_height(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: - 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: 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 """ - 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 """ txid = coin['prevout_hash'] tx = self.transactions[txid] 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()]): - height, conf, timestamp = self.get_tx_height(txid) - return price_func(timestamp) + fiat_value = self.get_fiat_value(txid, ccy) + if fiat_value is not None: + return fiat_value + else: + return self.price_at_timestamp(txid, price_func) * txin_value/Decimal(COIN) else: # could be some coinjoin transaction.. return None