Capital gains: Let user enter fiat value of transactions.
This commit is contained in:
@@ -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))
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user