import sys import requests import fin_depo from . import secrets import fin_defs from decimal import Decimal from collections import deque from fin_depo.data import * import datetime import dataclasses import logging from .data import TaxReport, BoughtAndSold, BoughtAndNotYetSold from pathlib import Path import openpyxl import openpyxl.styles import openpyxl.worksheet.table from openpyxl.utils import get_column_letter TAX_TYPES: dict[fin_defs.Asset, str] = { fin_defs.DKK: 'Fiat', fin_defs.EUR: 'Fiat', fin_defs.BTC: 'Kryptovaluta', fin_defs.MPC: 'Kryptovaluta', fin_defs.WELL_KNOWN_SYMBOLS['MATIC']: 'Kryptovaluta', fin_defs.USDT: 'Stable Coin', } NOW = datetime.datetime.now(tz=datetime.UTC) def mult_a(a, b): if a == 0 or b == 0: return 0 if a is None or b is None: return 'Unknown' return a * b FONT_BOLD = openpyxl.styles.Font(bold=True) FONT_HEADER = openpyxl.styles.Font(size=20) ALIGN_CENTER = openpyxl.styles.Alignment(horizontal="center", vertical="center") ALIGN_WRAP = openpyxl.styles.Alignment(wrap_text=True) BORDER_BOTTOM = openpyxl.styles.Border(bottom=openpyxl.styles.Side(border_style='medium', color='FF000000')) BORDER_TOP = openpyxl.styles.Border(top=openpyxl.styles.Side(border_style='thin', color='FF000000')) BORDER_SUM = openpyxl.styles.Border(top=openpyxl.styles.Side(border_style='thin', color='FF000000'), bottom=openpyxl.styles.Side(border_style='double', color='FF000000')) def add_headers(sheet, column_headers): sheet.append(column_headers) end_column = get_column_letter(len(column_headers)) sheet.row_dimensions[1].height = 30 for row in sheet[f'A1:{end_column}1']: for cell in row: cell.font = FONT_BOLD cell.alignment = ALIGN_CENTER cell.border = BORDER_BOTTOM def set_number_format(sheet, range): for row in sheet[range]: for cell in row: cell.number_format = "0.00" def write_current_assets(sheet, tax_report: TaxReport): column_headers = ['Værdipapir', 'Mængde', 'Kurs', 'Værdi (DKK)', 'Beskatningstype'] add_headers(sheet, column_headers) row_idx = 2 for asset, positions in tax_report.current_assets.items(): total_amount: Decimal = sum((p.amount.amount for p in positions), start=Decimal(0)) exchange_rate_asset_to_dkk = tax_report.exchange_rate_at_time(asset, fin_defs.DKK, NOW) #assert exchange_rate_asset_to_dkk is not None, asset row = [ asset.raw_short_name(), total_amount, exchange_rate_asset_to_dkk, f'=B{row_idx}*C{row_idx}', TAX_TYPES[asset], ] sheet.append(row) row_idx += 1 del asset, positions, row, total_amount sheet.column_dimensions['A'].width = 14 sheet.column_dimensions['B'].width = 12 sheet.column_dimensions['C'].width = 12 sheet.column_dimensions['D'].width = 15 sheet.column_dimensions['E'].width = 20 set_number_format(sheet, 'C1:D6') def write_ledger_sheet(sheet, tax_report: TaxReport): assets = list({e.amount.asset for e in tax_report.ledger_entries}) column_headers = [ 'Tidspunkt (UTC)', 'Type', 'Værdipapir', 'Mængde', 'Kontoudbyder', '' ] + [a.raw_short_name() for a in assets] add_headers(sheet, column_headers) row_idx = 2 prev_entry_time = None start_of_same_entry = 2 for ledger_entry in tax_report.ledger_entries: entry_time = ledger_entry.time.replace(tzinfo=None,microsecond=0,fold=0) row = [ entry_time, ledger_entry.type, ledger_entry.amount.asset.raw_short_name(), ledger_entry.amount.amount, ledger_entry.account_provider, '', ] for a_idx, a in enumerate(assets): above_cell = f'{get_column_letter(7+a_idx)}{row_idx-1}' if row_idx == 2: above_cell = 0 if ledger_entry.amount.asset == a: row.append(f'={above_cell} + D{row_idx}') else: row.append(f'={above_cell}') del a sheet.append(row) if entry_time != prev_entry_time and prev_entry_time is not None: # TODO: sheet.merge_cells(f'A{start_of_same_entry}:A{row_idx-1}') sheet[f'A{start_of_same_entry}'].alignment = ALIGN_CENTER start_of_same_entry = row_idx for row in sheet[f'A{row_idx}:J{row_idx}']: for cell in row: cell.border = BORDER_TOP prev_entry_time = entry_time row_idx+= 1 del ledger_entry, row # Styling sheet.column_dimensions['A'].width = 20 sheet.column_dimensions['B'].width = 12 sheet.column_dimensions['C'].width = 13 sheet.column_dimensions['D'].width = 12 sheet.column_dimensions['E'].width = 12 sheet.column_dimensions['F'].width = 16 def write_fifo_sheet(sheet, bought_and_sold_for: list, exchange_rate_at_time) -> tuple[str,str]: # TODO: Account for transfers column_headers = [ 'Købsdato (UTC)', 'Salgsdato (UTC)', 'Værdipapir', 'Mængde', 'Anskaffelseskurs', 'Salgskurs', 'Anskaffelsesværdi (DKK)', 'Salgsværdi (DKK)', 'Profit (DKK)', 'Tab (DKK)', ] add_headers(sheet, column_headers) row_idx = 2 for fifo_entry in bought_and_sold_for: asset = fifo_entry.amount.asset exchange_rate_dkk_bought = exchange_rate_at_time(asset, fin_defs.DKK, fifo_entry.time_bought) #assert exchange_rate_dkk_bought is not None, asset exchange_rate_dkk_sold = exchange_rate_at_time(asset, fin_defs.DKK, fifo_entry.time_sold) #assert exchange_rate_dkk_bought is not None, asset row = [ fifo_entry.time_bought.replace(tzinfo=None), fifo_entry.time_sold.replace(tzinfo=None), asset.raw_short_name(), fifo_entry.amount.amount, exchange_rate_dkk_bought, exchange_rate_dkk_sold, f'=F{row_idx}*D{row_idx}', f'=E{row_idx}*D{row_idx}', f'=max(0, G{row_idx} - H{row_idx})', f'=max(0, H{row_idx} - G{row_idx})', ] sheet.append(row) row_idx+= 1 del fifo_entry, asset, row # Sums sum_profit = f'I{row_idx}' sum_loss = f'J{row_idx}' sheet.append([ '', '', '', '', '', '', '', '', f'=sum(I2:I{row_idx-1})', f'=sum(J2:J{row_idx-1})' ]) for row in sheet[f'A{row_idx}:{sum_loss}']: for cell in row: cell.font = FONT_BOLD cell.border = BORDER_SUM # Styling sheet.column_dimensions['A'].width = 20 sheet.column_dimensions['B'].width = 20 sheet.column_dimensions['C'].width = 16 sheet.column_dimensions['D'].width = 10 sheet.column_dimensions['E'].width = 18 sheet.column_dimensions['F'].width = 15 sheet.column_dimensions['G'].width = 26 sheet.column_dimensions['H'].width = 20 sheet.column_dimensions['I'].width = 15 sheet.column_dimensions['J'].width = 15 set_number_format(sheet, f'G1:J{row_idx}') return (sum_profit, sum_loss) def write_overview(sheet, tax_report: TaxReport, sums): sheet['A1'] = 'Krypto-Skatterapport 2024' sheet.merge_cells('A1:B1') sheet['A1'].font = FONT_HEADER sheet['A1'].alignment = ALIGN_CENTER sheet['A2'] = 'Jon Michael Aanes' sheet.merge_cells('A2:B2') sheet['A2'].font = FONT_HEADER sheet['A2'].alignment = ALIGN_CENTER # Forklaring sheet['A4'] = 'Denne rapport afdækker aktiviteten på en række krypto-børser, med henblik på at give en komplet oversigt af handler og nuværende beholdning.' sheet.merge_cells('A4:B4') sheet['A4'].alignment = ALIGN_WRAP sheet['A6'] = 'Rapporten er splittet op I følgende ark:' sheet.merge_cells('A6:B6') sheet['A7'] = 'Overview' sheet['B7'] = 'Denne oversigt' sheet['A8'] = 'Current Assets' sheet['B8'] = 'Afdækker nuværende beholdning.' sheet['A9'] = 'Ledger' sheet['B9'] = 'Afdækker alle handler og overførsler.' sheet['A10'] = 'FIFO Stablecoin' sheet['B10'] = 'Beregner profit og tab for stablecoins ved FIFO-pricippet.' sheet['A11'] = 'FIFO Kryptovaluta' sheet['B11'] = 'Beregner profit og tab for kryptovaluta ved FIFO-pricippet.' sheet['A12'] = 'FIFO Fiat' sheet['B12'] = 'Beregner profit og tab for udenlandsk valuta ved FIFO-pricippet.' # Beregnet kolonner sheet['D6'] = 'Beregnet Skatte Kolonnner' # TODO sheet.merge_cells('D6:E6') sheet['A4'].alignment = ALIGN_CENTER sheet['D7'] = 'Kolonne' # TODO sheet['E7'] = 'Værdi' sheet['D8'] = 'Profit Krypto' sheet['E8'] = '=$\'FIFO Kryptovaluta\'.'+sums['Kryptovaluta'][0] sheet['D9'] = 'Tab Krypto' sheet['E9'] = '=$\'FIFO Kryptovaluta\'.'+sums['Kryptovaluta'][1] sheet['D10'] = 'Profit Stable Coin' sheet['E10'] = '=$\'FIFO Stable Coin\'.'+sums['Stable Coin'][0] sheet['D11'] = 'Profit Stable Coin' sheet['E11'] = '=$\'FIFO Stable Coin\'.'+sums['Stable Coin'][1] # Disclaimer #sheet['A13'] = 'Bemærk at rapporten kan være ufyldtesgørende, af forskellige årsager, inklusiv: Fejl i data fra krypto-børser' # Styling sheet.column_dimensions['A'].width = 20 sheet.column_dimensions['B'].width = 70 sheet.row_dimensions[4].height = 60 def produce_excel_report(report: TaxReport, output_path: Path): workbook = openpyxl.Workbook() # Oversigt workbook.active.title = 'Oversigt' workbook.active.page_setup.fitToWidth = 1 # Current assets sheet_ledger = workbook.create_sheet( 'Current Assets') workbook.active.page_setup.fitToWidth = 1 write_current_assets(sheet_ledger, report) # Ledger sheet_ledger = workbook.create_sheet('Ledger') sheet_ledger.page_setup.fitToWidth = 1 write_ledger_sheet(sheet_ledger , report) # FIFO sums = {} for tax_type in set(TAX_TYPES.values()): sheet_fifo = workbook.create_sheet('FIFO '+tax_type) sheet_fifo.page_setup.fitToWidth = 1 bought_and_sold_for = [entry for entry in report.bought_and_sold_for if TAX_TYPES[entry.amount.asset] == tax_type] sums[tax_type] = write_fifo_sheet(sheet_fifo, bought_and_sold_for, report.exchange_rate_at_time) del tax_type, sheet_fifo write_overview(workbook.active, report, sums) workbook.save(output_path)