URI: 
       tCapital gains: Let user enter fiat value of transactions. - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 4cbdd25c93eb25be18cebdb99085921d046277d2
   DIR parent f8df8d60c44b40b63bbeb5dbd61fbfbdb1abd7b4
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Sun, 11 Feb 2018 17:26:13 +0100
       
       Capital gains: Let user enter fiat value of transactions.
       
       Diffstat:
         M gui/qt/history_list.py              |      34 +++++++++++++++++++++++++------
         M lib/wallet.py                       |      65 +++++++++++++++++++++++++------
       
       2 files changed, 81 insertions(+), 18 deletions(-)
       ---
   DIR diff --git a/gui/qt/history_list.py b/gui/qt/history_list.py
       t@@ -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):
       t@@ -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)
       t@@ -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)
       t@@ -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))
        
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -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()
       t@@ -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()
        
       t@@ -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