1
0
crypto-tax/crypto_tax/__init__.py

226 lines
7.5 KiB
Python
Raw Normal View History

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.
2024-12-28 00:14:16 +00:00
Produces an Excel report containing the following sheets:
- **Overview Sheet**: An overview sheet with the suggested report values.
- **Current Assets**: Containing currently held assets.
- **Ledger**: Ledger of all registered transactions.
- Various **FIFO** sheets for the different asset classes.
## Usage
Make a `/secrets` folder, and place relevant secrets there.
Run `python -m crypto_tax`
## Disclaimer
This tool is a work in progress, and may contain outdated or plain faulty
logic. The information gathered from crypto exchanges may be incorrect.
When using the produced reports, you acknowledge that you bear the full
responsiblity of reporting the correct information to SKAT, and that you have
validated the generated output.
The report suggestions are informal suggestions, and is not professional
guidance. I bear no responsiblity for faults in the generated tax reports.
2024-12-22 05:25:07 +00:00
## TODO
This tool is a work in progress:
- [ ] Support Partisia Blockchain
- [ ] Fix Kucoin issues.
2024-12-28 00:14:16 +00:00
- [ ] Adjust the account provider column.
- [X] Produce Excel output file:
2024-12-22 05:25:07 +00:00
* 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
2024-12-28 00:02:37 +00:00
import dataclassabc
2024-12-22 05:10:23 +00:00
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-28 00:02:37 +00:00
@dataclassabc.dataclassabc(frozen=True)
class TransferDetails(fin_depo.data.DoubleRegister):
withdrawal: fin_depo.data.WithdrawalDetails
deposit: fin_depo.data.DepositDetails
@property
def fee(self) -> AssetAmount | None:
return self.withdrawal.fee # TODO?
@property
def executed_time(self) -> datetime.datetime:
return self.withdrawal.executed_time
@property
def input(self) -> None:
return None
@property
def output(self) -> None:
return None
def collate_interaccount_transfers(transfers: list[fin_depo.data.DoubleRegister]) -> list[fin_depo.data.DoubleRegister]:
new_transfers = []
for t in transfers:
if isinstance(t, fin_depo.data.DepositDetails):
prev_transfer = new_transfers[-1] if len(new_transfers) > 0 else None
if isinstance(prev_transfer , fin_depo.data.WithdrawalDetails):
if prev_transfer.withdrawn == t.deposit:
del new_transfers[-1]
new_transfers.append(TransferDetails(prev_transfer, t))
continue
del prev_transfer
new_transfers.append(t)
del t
return new_transfers
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)
2024-12-28 00:02:37 +00:00
transfers = collate_interaccount_transfers(transfers)
2024-12-22 05:10:23 +00:00
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-28 00:02:37 +00:00
if True:
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:
2024-12-28 00:02:37 +00:00
msg = f'Miscalculation: {amount} of {amount_to_sell}'
raise Exception(msg)
2024-12-22 05:10:23 +00:00
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:
2024-12-28 00:02:37 +00:00
2024-12-22 05:10:23 +00:00
# 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
2024-12-28 00:02:37 +00:00
# TODO: Output line for transfer.
2024-12-24 19:12:50 +00:00
# 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
)