2024-12-14 20:12:18 +00:00
|
|
|
"""# Crypto Tax.
|
|
|
|
|
2024-12-22 05:25:07 +00:00
|
|
|
Tool to automatically perform tax computations of crypto transactions from the danish perspective.
|
|
|
|
|
|
|
|
## TODO
|
|
|
|
|
|
|
|
This tool is a work in progress:
|
|
|
|
|
|
|
|
- [ ] Support Partisia Blockchain
|
|
|
|
- [ ] Fix Kucoin issues.
|
|
|
|
- [ ] Produce Excel output file:
|
|
|
|
* Sheet 1: Gives a basic overview, including computed tax values from the rest of
|
|
|
|
the sheet, and reasonings for these. Gives an overview of the other sheets.
|
|
|
|
* Sheet 2: Current assets, including FIFO acquisition prices.
|
|
|
|
* Sheet 3: Historic sales, with FIFO.
|
2024-12-14 20:12:18 +00:00
|
|
|
"""
|
2024-12-22 05:10:23 +00:00
|
|
|
|
|
|
|
import sys
|
|
|
|
import requests
|
|
|
|
import fin_depo
|
|
|
|
from . import secrets
|
|
|
|
from fin_defs import CryptoCurrency, AssetAmount, MPC, Asset, USDT
|
|
|
|
from decimal import Decimal
|
|
|
|
from collections import deque
|
|
|
|
from fin_depo.data import *
|
|
|
|
import datetime
|
|
|
|
import dataclasses
|
|
|
|
import logging
|
2024-12-22 22:25:24 +00:00
|
|
|
import fin_data
|
2024-12-22 05:10:23 +00:00
|
|
|
|
2024-12-24 19:12:50 +00:00
|
|
|
from .data import TaxReport, BoughtAndSold, BoughtAndNotYetSold, LedgerEntry
|
2024-12-22 05:10:23 +00:00
|
|
|
|
2024-12-27 21:14:39 +00:00
|
|
|
try:
|
|
|
|
import logging_color
|
|
|
|
|
|
|
|
logging_color.monkey_patch()
|
|
|
|
except ImportError as e:
|
|
|
|
logging.warning('Color logging not enabled')
|
|
|
|
del e
|
|
|
|
|
2024-12-22 22:25:24 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
2024-12-22 05:10:23 +00:00
|
|
|
|
2024-12-22 22:25:24 +00:00
|
|
|
FIN_DB = fin_data.FinancialDatabase(enable_kucoin=True, enable_nationalbanken_dk=True)
|
2024-12-22 05:10:23 +00:00
|
|
|
|
|
|
|
|
2024-12-24 19:12:50 +00:00
|
|
|
def compute_fifo(transfers: list) -> TaxReport:
|
2024-12-22 05:10:23 +00:00
|
|
|
transfers.sort(key=lambda t:t.executed_time)
|
|
|
|
|
|
|
|
|
|
|
|
bought_and_sold_for: list[BoughtAndSold] = []
|
|
|
|
bought_and_spent_for: list[BoughtAndSold] = []
|
2024-12-24 19:12:50 +00:00
|
|
|
ledger_entries = []
|
2024-12-22 05:10:23 +00:00
|
|
|
|
|
|
|
prices_bought_for: dict[Asset, deque[BoughtAndNotYetSold]] = {}
|
2024-12-27 21:14:39 +00:00
|
|
|
if False:
|
2024-12-22 05:10:23 +00:00
|
|
|
prices_bought_for[MPC] = deque()
|
2024-12-24 19:55:16 +00:00
|
|
|
# TODO:
|
2024-12-27 14:58:38 +00:00
|
|
|
prices_bought_for[MPC].append(BoughtAndNotYetSold(AssetAmount(MPC, Decimal(80)), datetime.datetime(2024, 4,1,1,1,1,1)))
|
2024-12-22 05:10:23 +00:00
|
|
|
prices_bought_for[USDT] = deque()
|
2024-12-24 19:55:16 +00:00
|
|
|
prices_bought_for[USDT].append(BoughtAndNotYetSold(AssetAmount(USDT, Decimal(20)), datetime.datetime(2020, 1,1,1,1,1,1)))
|
2024-12-22 05:10:23 +00:00
|
|
|
|
2024-12-24 19:12:50 +00:00
|
|
|
def current_balance(asset: Asset) -> AssetAmount:
|
|
|
|
return sum((p.amount for p in prices_bought_for[asset]), start=AssetAmount.ZERO)
|
|
|
|
|
|
|
|
def sell(amount_to_sell: AssetAmount, executed_time: datetime.datetime, variant: str = 'SELL'):
|
2024-12-27 21:14:39 +00:00
|
|
|
logger.debug('%s: %s', variant, amount_to_sell)
|
2024-12-24 19:12:50 +00:00
|
|
|
amount = amount_to_sell
|
2024-12-22 05:10:23 +00:00
|
|
|
while amount.amount > 0:
|
|
|
|
if len(prices_bought_for[amount.asset]) == 0:
|
|
|
|
raise Exception('Miscalculation: ' + str(amount))
|
|
|
|
partial = prices_bought_for[amount.asset].popleft()
|
|
|
|
|
|
|
|
amount_covered_by_partial = min(partial.amount, amount)
|
|
|
|
amount -= amount_covered_by_partial
|
|
|
|
new_partial_amount = partial.amount - amount_covered_by_partial
|
|
|
|
|
|
|
|
# Re-add partial
|
|
|
|
if new_partial_amount.amount != 0:
|
|
|
|
new_partial = BoughtAndNotYetSold(new_partial_amount, partial.time_bought)
|
|
|
|
prices_bought_for[amount.asset].appendleft(new_partial)
|
|
|
|
del new_partial
|
|
|
|
|
|
|
|
# Add FIFO like
|
2024-12-24 19:12:50 +00:00
|
|
|
if variant == 'FEE':
|
2024-12-22 05:10:23 +00:00
|
|
|
bought_and_spent_for.append(BoughtAndSold(amount_covered_by_partial, partial.time_bought, executed_time))
|
|
|
|
else:
|
|
|
|
bought_and_sold_for.append(BoughtAndSold(amount_covered_by_partial, partial.time_bought, executed_time))
|
|
|
|
|
|
|
|
del partial, amount_covered_by_partial
|
|
|
|
|
2024-12-24 19:12:50 +00:00
|
|
|
ledger_entries.append(LedgerEntry(
|
|
|
|
time = executed_time,
|
|
|
|
type = variant,
|
|
|
|
account_provider = 'Kraken (TODO)',
|
2024-12-24 19:55:16 +00:00
|
|
|
amount = -amount_to_sell,
|
2024-12-24 19:12:50 +00:00
|
|
|
balance = current_balance(amount.asset),
|
|
|
|
))
|
|
|
|
|
2024-12-22 05:10:23 +00:00
|
|
|
def spend(amount: AssetAmount, executed_time: datetime.datetime):
|
2024-12-24 19:12:50 +00:00
|
|
|
if amount.amount > 0:
|
|
|
|
sell(amount, executed_time, 'FEE')
|
|
|
|
|
|
|
|
def buy(bought: AssetAmount, executed_time: datetime.datetime, variant: str):
|
|
|
|
logger.debug('Bought: %s', bought)
|
|
|
|
if bought.asset not in prices_bought_for:
|
|
|
|
prices_bought_for[bought.asset] = deque()
|
|
|
|
prices_bought_for[bought.asset].append(BoughtAndNotYetSold(bought, executed_time))
|
|
|
|
|
|
|
|
ledger_entries.append(LedgerEntry(
|
|
|
|
time = executed_time,
|
|
|
|
type = variant,
|
|
|
|
account_provider = 'Kraken (TODO)',
|
|
|
|
amount = bought,
|
|
|
|
balance = current_balance(bought.asset),
|
|
|
|
))
|
|
|
|
|
2024-12-22 05:10:23 +00:00
|
|
|
|
|
|
|
for transfer in transfers:
|
|
|
|
# Sell
|
|
|
|
if _input := transfer.input:
|
2024-12-24 19:12:50 +00:00
|
|
|
variant = 'WITHDRAW' if transfer.output is None else 'SELL'
|
|
|
|
sell(_input, transfer.executed_time, variant)
|
|
|
|
del variant
|
2024-12-22 05:10:23 +00:00
|
|
|
del _input
|
|
|
|
|
2024-12-24 19:12:50 +00:00
|
|
|
# Buy
|
|
|
|
if output := transfer.output:
|
|
|
|
variant = 'DEPOSIT' if transfer.input is None else 'BUY'
|
|
|
|
buy(output, transfer.executed_time, variant)
|
|
|
|
del variant
|
|
|
|
del output
|
|
|
|
|
|
|
|
# Fee
|
2024-12-22 05:10:23 +00:00
|
|
|
if fee := transfer.fee:
|
|
|
|
spend(fee, transfer.executed_time)
|
2024-12-24 19:12:50 +00:00
|
|
|
del fee
|
|
|
|
|
|
|
|
del transfer
|
2024-12-22 05:10:23 +00:00
|
|
|
|
2024-12-22 22:25:24 +00:00
|
|
|
def exchange_rate_at_time(a, b, t):
|
|
|
|
try:
|
|
|
|
sample = FIN_DB.get_exchange_rate_at_time(a, b, t)
|
|
|
|
return sample.average
|
|
|
|
except Exception:
|
|
|
|
logger.exception('Error when fetching exchange rate (%s,%s) at %s', a, b, t)
|
|
|
|
return None
|
|
|
|
|
2024-12-22 05:10:23 +00:00
|
|
|
return TaxReport(
|
|
|
|
bought_and_sold_for=bought_and_sold_for,
|
|
|
|
bought_and_spent_for=bought_and_spent_for,
|
|
|
|
current_assets=prices_bought_for,
|
2024-12-22 22:25:24 +00:00
|
|
|
exchange_rate_at_time=exchange_rate_at_time,
|
2024-12-24 19:12:50 +00:00
|
|
|
ledger_entries = ledger_entries,
|
2024-12-22 05:10:23 +00:00
|
|
|
)
|