1
0
crypto-tax/crypto_tax/output_excel.py

320 lines
11 KiB
Python
Raw Permalink Normal View History

2024-12-22 22:25:24 +00:00
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
2024-12-27 14:58:38 +00:00
import openpyxl.styles
2024-12-27 21:14:39 +00:00
import openpyxl.worksheet.table
from openpyxl.utils import get_column_letter
2024-12-22 22:25:24 +00:00
TAX_TYPES: dict[fin_defs.Asset, str] = {
fin_defs.DKK: 'Fiat',
fin_defs.EUR: 'Fiat',
2024-12-27 21:14:39 +00:00
fin_defs.BTC: 'Kryptovaluta',
fin_defs.MPC: 'Kryptovaluta',
fin_defs.WELL_KNOWN_SYMBOLS['MATIC']: 'Kryptovaluta',
fin_defs.USDT: 'Stable Coin',
2024-12-22 22:25:24 +00:00
}
NOW = datetime.datetime.now(tz=datetime.UTC)
def mult_a(a, b):
2024-12-22 23:08:24 +00:00
if a == 0 or b == 0:
return 0
2024-12-22 22:25:24 +00:00
if a is None or b is None:
return 'Unknown'
return a * b
2024-12-27 21:14:39 +00:00
FONT_BOLD = openpyxl.styles.Font(bold=True)
FONT_HEADER = openpyxl.styles.Font(size=20)
2024-12-27 14:58:38 +00:00
ALIGN_CENTER = openpyxl.styles.Alignment(horizontal="center", vertical="center")
2024-12-27 21:14:39 +00:00
ALIGN_WRAP = openpyxl.styles.Alignment(wrap_text=True)
BORDER_BOTTOM = openpyxl.styles.Border(bottom=openpyxl.styles.Side(border_style='medium', color='FF000000'))
2024-12-27 21:38:28 +00:00
BORDER_TOP = openpyxl.styles.Border(top=openpyxl.styles.Side(border_style='thin', color='FF000000'))
2024-12-27 21:14:39 +00:00
BORDER_SUM = openpyxl.styles.Border(top=openpyxl.styles.Side(border_style='thin', color='FF000000'),
bottom=openpyxl.styles.Side(border_style='double', color='FF000000'))
2024-12-27 14:58:38 +00:00
def add_headers(sheet, column_headers):
sheet.append(column_headers)
2024-12-27 21:14:39 +00:00
end_column = get_column_letter(len(column_headers))
sheet.row_dimensions[1].height = 30
for row in sheet[f'A1:{end_column}1']:
2024-12-27 14:58:38 +00:00
for cell in row:
2024-12-27 21:14:39 +00:00
cell.font = FONT_BOLD
cell.alignment = ALIGN_CENTER
cell.border = BORDER_BOTTOM
2024-12-28 00:02:37 +00:00
def set_number_format(sheet, range, format="0.00"):
2024-12-27 21:14:39 +00:00
for row in sheet[range]:
for cell in row:
2024-12-28 00:02:37 +00:00
cell.number_format = format
2024-12-27 14:58:38 +00:00
2024-12-22 22:25:24 +00:00
def write_current_assets(sheet, tax_report: TaxReport):
2024-12-28 00:02:37 +00:00
column_headers = ['Værdipapir', 'Mængde', 'Kurs', 'Sum (DKK)',
2024-12-22 22:25:24 +00:00
'Beskatningstype']
2024-12-27 14:58:38 +00:00
add_headers(sheet, column_headers)
2024-12-27 21:14:39 +00:00
row_idx = 2
2024-12-22 22:25:24 +00:00
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)
2024-12-22 23:08:24 +00:00
#assert exchange_rate_asset_to_dkk is not None, asset
2024-12-22 22:25:24 +00:00
row = [
asset.raw_short_name(),
total_amount,
2024-12-28 00:02:37 +00:00
exchange_rate_asset_to_dkk or 'Unknown',
2024-12-27 21:14:39 +00:00
f'=B{row_idx}*C{row_idx}',
2024-12-22 22:25:24 +00:00
TAX_TYPES[asset],
]
sheet.append(row)
2024-12-27 21:14:39 +00:00
row_idx += 1
2024-12-22 22:25:24 +00:00
del asset, positions, row, total_amount
2024-12-27 21:38:28 +00:00
sheet.column_dimensions['A'].width = 14
sheet.column_dimensions['B'].width = 12
sheet.column_dimensions['C'].width = 12
2024-12-27 21:14:39 +00:00
sheet.column_dimensions['D'].width = 15
2024-12-27 21:38:28 +00:00
sheet.column_dimensions['E'].width = 20
2024-12-27 21:14:39 +00:00
set_number_format(sheet, 'C1:D6')
2024-12-24 19:12:50 +00:00
def write_ledger_sheet(sheet, tax_report: TaxReport):
2024-12-27 14:58:38 +00:00
assets = list({e.amount.asset for e in tax_report.ledger_entries})
2024-12-24 19:12:50 +00:00
column_headers = [
'Tidspunkt (UTC)',
'Type',
'Værdipapir',
'Mængde',
'Kontoudbyder',
2024-12-27 14:58:38 +00:00
''
2024-12-28 00:02:37 +00:00
] + [a.raw_short_name() for a in assets] + ['', 'Kommentarer']
2024-12-27 14:58:38 +00:00
add_headers(sheet, column_headers)
2024-12-24 19:12:50 +00:00
row_idx = 2
2024-12-27 14:58:38 +00:00
prev_entry_time = None
start_of_same_entry = 2
2024-12-24 19:12:50 +00:00
for ledger_entry in tax_report.ledger_entries:
2024-12-27 14:58:38 +00:00
entry_time = ledger_entry.time.replace(tzinfo=None,microsecond=0,fold=0)
2024-12-24 19:12:50 +00:00
row = [
2024-12-27 14:58:38 +00:00
entry_time,
2024-12-24 19:12:50 +00:00
ledger_entry.type,
ledger_entry.amount.asset.raw_short_name(),
ledger_entry.amount.amount,
ledger_entry.account_provider,
2024-12-27 14:58:38 +00:00
'',
2024-12-27 21:38:28 +00:00
]
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
2024-12-24 19:12:50 +00:00
sheet.append(row)
2024-12-27 14:58:38 +00:00
2024-12-27 21:14:39 +00:00
if entry_time != prev_entry_time and prev_entry_time is not None:
2024-12-27 14:58:38 +00:00
# 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
2024-12-27 21:38:28 +00:00
for row in sheet[f'A{row_idx}:J{row_idx}']:
for cell in row:
cell.border = BORDER_TOP
2024-12-27 21:14:39 +00:00
prev_entry_time = entry_time
row_idx+= 1
2024-12-24 19:12:50 +00:00
del ledger_entry, row
2024-12-27 21:14:39 +00:00
# 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
2024-12-27 14:58:38 +00:00
2024-12-27 21:14:39 +00:00
def write_fifo_sheet(sheet, bought_and_sold_for: list, exchange_rate_at_time) -> tuple[str,str]:
2024-12-24 19:12:50 +00:00
# TODO: Account for transfers
2024-12-22 22:25:24 +00:00
column_headers = [
2024-12-27 21:14:39 +00:00
'Købsdato (UTC)',
'Salgsdato (UTC)',
'Værdipapir',
'Mængde',
'Anskaffelseskurs',
'Salgskurs',
2024-12-28 00:02:37 +00:00
'Købssum (DKK)',
'Salgssum (DKK)',
'Gevinst (DKK)', 'Tab (DKK)',
2024-12-22 22:25:24 +00:00
]
2024-12-27 14:58:38 +00:00
add_headers(sheet, column_headers)
2024-12-22 22:25:24 +00:00
row_idx = 2
2024-12-27 21:14:39 +00:00
for fifo_entry in bought_and_sold_for:
2024-12-22 22:25:24 +00:00
asset = fifo_entry.amount.asset
2024-12-27 21:14:39 +00:00
exchange_rate_dkk_bought = exchange_rate_at_time(asset, fin_defs.DKK, fifo_entry.time_bought)
2024-12-22 22:25:24 +00:00
#assert exchange_rate_dkk_bought is not None, asset
2024-12-27 21:14:39 +00:00
exchange_rate_dkk_sold = exchange_rate_at_time(asset, fin_defs.DKK, fifo_entry.time_sold)
2024-12-22 22:25:24 +00:00
#assert exchange_rate_dkk_bought is not None, asset
row = [
fifo_entry.time_bought.replace(tzinfo=None),
fifo_entry.time_sold.replace(tzinfo=None),
2024-12-27 21:14:39 +00:00
asset.raw_short_name(),
fifo_entry.amount.amount,
2024-12-28 00:02:37 +00:00
exchange_rate_dkk_bought or 'Unknown',
exchange_rate_dkk_sold or 'Unknown',
f'=D{row_idx}*E{row_idx}',
f'=D{row_idx}*F{row_idx}',
2024-12-27 21:14:39 +00:00
f'=max(0, H{row_idx} - G{row_idx})',
2024-12-28 00:02:37 +00:00
f'=max(0, G{row_idx} - H{row_idx})',
2024-12-22 22:25:24 +00:00
]
sheet.append(row)
row_idx+= 1
del fifo_entry, asset, row
2024-12-27 21:14:39 +00:00
# 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
2024-12-27 21:38:28 +00:00
sheet.column_dimensions['E'].width = 18
2024-12-27 21:14:39 +00:00
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'
2024-12-28 00:02:37 +00:00
sheet['B10'] = 'Beregner gevinst og tab for stablecoins ved FIFO-pricippet.'
2024-12-27 21:14:39 +00:00
sheet['A11'] = 'FIFO Kryptovaluta'
2024-12-28 00:02:37 +00:00
sheet['B11'] = 'Beregner gevinst og tab for kryptovaluta ved FIFO-pricippet.'
2024-12-27 21:14:39 +00:00
sheet['A12'] = 'FIFO Fiat'
2024-12-28 00:02:37 +00:00
sheet['B12'] = 'Beregner gevinst og tab for udenlandsk valuta ved FIFO-pricippet.'
2024-12-27 21:14:39 +00:00
# Beregnet kolonner
2024-12-28 00:02:37 +00:00
sheet['D6'] = 'Beregnet Skatte Rubrikker' # TODO
2024-12-27 21:14:39 +00:00
sheet.merge_cells('D6:E6')
sheet['A4'].alignment = ALIGN_CENTER
2024-12-28 00:02:37 +00:00
sheet['D7'] = 'Rubrik' # TODO
2024-12-27 21:14:39 +00:00
sheet['E7'] = 'Værdi'
2024-12-28 00:02:37 +00:00
sheet['D8'] = 'Rubrik 20'
2024-12-27 21:14:39 +00:00
sheet['E8'] = '=$\'FIFO Kryptovaluta\'.'+sums['Kryptovaluta'][0]
2024-12-28 00:02:37 +00:00
sheet['F8'] = '(Gevinst ved kryptospekulation)'
sheet['G8'] = 'Kilde: https://skat.dk/borger/aktier-og-andre-vaerdipapirer/skat-paa-krypto-kend-reglerne-saa-du-undgaar-et-skattesmaek/beregn-og-oplys-gevinst-og-tab-paa-krypto'
sheet['D9'] = 'Rubrik 58'
2024-12-27 21:14:39 +00:00
sheet['E9'] = '=$\'FIFO Kryptovaluta\'.'+sums['Kryptovaluta'][1]
2024-12-28 00:02:37 +00:00
sheet['F9'] = '(Tab ved kryptospekulation)'
sheet['G10'] = 'Kilde: https://skat.dk/borger/aktier-og-andre-vaerdipapirer/skat-paa-krypto-kend-reglerne-saa-du-undgaar-et-skattesmaek/beregn-og-oplys-gevinst-og-tab-paa-krypto'
sheet['D10'] = 'Rubrik 346'
sheet['E10'] = f"=$'FIFO Stable Coin'.{sums['Stable Coin'][0]}'-$'FIFO Stable Coin'.{sums['Stable Coin'][1]}'"
sheet['F10'] = '(Gevinst og Tab ved Stable Coin / Financiel Kontrakt)'
sheet['G10'] = 'Kilde: https://skat.dk/borger/aktier-og-andre-vaerdipapirer/skat-paa-krypto-kend-reglerne-saa-du-undgaar-et-skattesmaek/stablecoins-finansielle-kontrakter'
2024-12-27 21:14:39 +00:00
# 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
2024-12-28 00:02:37 +00:00
sheet.column_dimensions['D'].width = 20
sheet.column_dimensions['E'].width = 20
sheet.column_dimensions['F'].width = 50
sheet.column_dimensions['G'].width = 80
2024-12-27 21:14:39 +00:00
2024-12-22 22:25:24 +00:00
def produce_excel_report(report: TaxReport, output_path: Path):
workbook = openpyxl.Workbook()
2024-12-27 21:14:39 +00:00
# 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)
2024-12-22 22:25:24 +00:00
2024-12-27 21:14:39 +00:00
# Ledger
2024-12-24 19:12:50 +00:00
sheet_ledger = workbook.create_sheet('Ledger')
2024-12-27 21:14:39 +00:00
sheet_ledger.page_setup.fitToWidth = 1
2024-12-24 19:12:50 +00:00
write_ledger_sheet(sheet_ledger , report)
2024-12-27 21:14:39 +00:00
# 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)
2024-12-22 22:25:24 +00:00
workbook.save(output_path)