Layout more or less done
This commit is contained in:
parent
63e6beb711
commit
9b337c455e
|
@ -30,6 +30,14 @@ import fin_data
|
||||||
|
|
||||||
from .data import TaxReport, BoughtAndSold, BoughtAndNotYetSold, LedgerEntry
|
from .data import TaxReport, BoughtAndSold, BoughtAndNotYetSold, LedgerEntry
|
||||||
|
|
||||||
|
try:
|
||||||
|
import logging_color
|
||||||
|
|
||||||
|
logging_color.monkey_patch()
|
||||||
|
except ImportError as e:
|
||||||
|
logging.warning('Color logging not enabled')
|
||||||
|
del e
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
FIN_DB = fin_data.FinancialDatabase(enable_kucoin=True, enable_nationalbanken_dk=True)
|
FIN_DB = fin_data.FinancialDatabase(enable_kucoin=True, enable_nationalbanken_dk=True)
|
||||||
|
@ -44,7 +52,7 @@ def compute_fifo(transfers: list) -> TaxReport:
|
||||||
ledger_entries = []
|
ledger_entries = []
|
||||||
|
|
||||||
prices_bought_for: dict[Asset, deque[BoughtAndNotYetSold]] = {}
|
prices_bought_for: dict[Asset, deque[BoughtAndNotYetSold]] = {}
|
||||||
if True:
|
if False:
|
||||||
prices_bought_for[MPC] = deque()
|
prices_bought_for[MPC] = deque()
|
||||||
# TODO:
|
# TODO:
|
||||||
prices_bought_for[MPC].append(BoughtAndNotYetSold(AssetAmount(MPC, Decimal(80)), datetime.datetime(2024, 4,1,1,1,1,1)))
|
prices_bought_for[MPC].append(BoughtAndNotYetSold(AssetAmount(MPC, Decimal(80)), datetime.datetime(2024, 4,1,1,1,1,1)))
|
||||||
|
@ -55,7 +63,7 @@ def compute_fifo(transfers: list) -> TaxReport:
|
||||||
return sum((p.amount for p in prices_bought_for[asset]), start=AssetAmount.ZERO)
|
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'):
|
def sell(amount_to_sell: AssetAmount, executed_time: datetime.datetime, variant: str = 'SELL'):
|
||||||
logger.info('%s: %s', variant, amount_to_sell)
|
logger.debug('%s: %s', variant, amount_to_sell)
|
||||||
amount = amount_to_sell
|
amount = amount_to_sell
|
||||||
while amount.amount > 0:
|
while amount.amount > 0:
|
||||||
if len(prices_bought_for[amount.asset]) == 0:
|
if len(prices_bought_for[amount.asset]) == 0:
|
||||||
|
@ -108,8 +116,6 @@ def compute_fifo(transfers: list) -> TaxReport:
|
||||||
|
|
||||||
|
|
||||||
for transfer in transfers:
|
for transfer in transfers:
|
||||||
logger.info(transfer)
|
|
||||||
|
|
||||||
# Sell
|
# Sell
|
||||||
if _input := transfer.input:
|
if _input := transfer.input:
|
||||||
variant = 'WITHDRAW' if transfer.output is None else 'SELL'
|
variant = 'WITHDRAW' if transfer.output is None else 'SELL'
|
||||||
|
|
|
@ -36,8 +36,6 @@ def main():
|
||||||
#TRANSFERS += list(KUCOIN_CLIENT._get_double_registers())
|
#TRANSFERS += list(KUCOIN_CLIENT._get_double_registers())
|
||||||
tax_report = compute_fifo(TRANSFERS)
|
tax_report = compute_fifo(TRANSFERS)
|
||||||
|
|
||||||
logger.info('-'*80)
|
|
||||||
|
|
||||||
output_path = Path('./output/report.xlsx')
|
output_path = Path('./output/report.xlsx')
|
||||||
output_excel.produce_excel_report(tax_report, output_path)
|
output_excel.produce_excel_report(tax_report, output_path)
|
||||||
|
|
||||||
|
|
|
@ -15,14 +15,16 @@ from pathlib import Path
|
||||||
|
|
||||||
import openpyxl
|
import openpyxl
|
||||||
import openpyxl.styles
|
import openpyxl.styles
|
||||||
|
import openpyxl.worksheet.table
|
||||||
|
from openpyxl.utils import get_column_letter
|
||||||
|
|
||||||
TAX_TYPES: dict[fin_defs.Asset, str] = {
|
TAX_TYPES: dict[fin_defs.Asset, str] = {
|
||||||
fin_defs.DKK: 'Fiat',
|
fin_defs.DKK: 'Fiat',
|
||||||
fin_defs.EUR: 'Fiat',
|
fin_defs.EUR: 'Fiat',
|
||||||
fin_defs.BTC: 'Krypto-valuta',
|
fin_defs.BTC: 'Kryptovaluta',
|
||||||
fin_defs.MPC: 'Krypto-valuta',
|
fin_defs.MPC: 'Kryptovaluta',
|
||||||
fin_defs.WELL_KNOWN_SYMBOLS['MATIC']: 'Krypto-valuta',
|
fin_defs.WELL_KNOWN_SYMBOLS['MATIC']: 'Kryptovaluta',
|
||||||
fin_defs.USDT: 'Stable Coin (Financiel Kontrakt)',
|
fin_defs.USDT: 'Stable Coin',
|
||||||
}
|
}
|
||||||
|
|
||||||
NOW = datetime.datetime.now(tz=datetime.UTC)
|
NOW = datetime.datetime.now(tz=datetime.UTC)
|
||||||
|
@ -34,19 +36,34 @@ def mult_a(a, b):
|
||||||
return 'Unknown'
|
return 'Unknown'
|
||||||
return a * b
|
return a * b
|
||||||
|
|
||||||
BOLD_FONT = openpyxl.styles.Font(bold=True)
|
FONT_BOLD = openpyxl.styles.Font(bold=True)
|
||||||
|
FONT_HEADER = openpyxl.styles.Font(size=20)
|
||||||
ALIGN_CENTER = openpyxl.styles.Alignment(horizontal="center", vertical="center")
|
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_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):
|
def add_headers(sheet, column_headers):
|
||||||
sheet.append(column_headers)
|
sheet.append(column_headers)
|
||||||
for row in sheet['A1:J1']:
|
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:
|
for cell in row:
|
||||||
cell.font = BOLD_FONT
|
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):
|
def write_current_assets(sheet, tax_report: TaxReport):
|
||||||
column_headers = ['Værdipapir', 'Mængde', 'Estimeret værdi (DKK)',
|
column_headers = ['Værdipapir', 'Mængde', 'Kurs', 'Værdi (DKK)',
|
||||||
'Beskatningstype']
|
'Beskatningstype']
|
||||||
add_headers(sheet, column_headers)
|
add_headers(sheet, column_headers)
|
||||||
|
row_idx = 2
|
||||||
for asset, positions in tax_report.current_assets.items():
|
for asset, positions in tax_report.current_assets.items():
|
||||||
total_amount: Decimal = sum((p.amount.amount for p in positions), start=Decimal(0))
|
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)
|
exchange_rate_asset_to_dkk = tax_report.exchange_rate_at_time(asset, fin_defs.DKK, NOW)
|
||||||
|
@ -54,12 +71,21 @@ def write_current_assets(sheet, tax_report: TaxReport):
|
||||||
row = [
|
row = [
|
||||||
asset.raw_short_name(),
|
asset.raw_short_name(),
|
||||||
total_amount,
|
total_amount,
|
||||||
mult_a(total_amount, exchange_rate_asset_to_dkk),
|
exchange_rate_asset_to_dkk,
|
||||||
|
f'=B{row_idx}*C{row_idx}',
|
||||||
TAX_TYPES[asset],
|
TAX_TYPES[asset],
|
||||||
]
|
]
|
||||||
sheet.append(row)
|
sheet.append(row)
|
||||||
|
row_idx += 1
|
||||||
del asset, positions, row, total_amount
|
del asset, positions, row, total_amount
|
||||||
|
|
||||||
|
sheet.column_dimensions['A'].width = 16
|
||||||
|
sheet.column_dimensions['B'].width = 16
|
||||||
|
sheet.column_dimensions['C'].width = 15
|
||||||
|
sheet.column_dimensions['D'].width = 15
|
||||||
|
sheet.column_dimensions['E'].width = 15
|
||||||
|
set_number_format(sheet, 'C1:D6')
|
||||||
|
|
||||||
def write_ledger_sheet(sheet, tax_report: TaxReport):
|
def write_ledger_sheet(sheet, tax_report: TaxReport):
|
||||||
assets = list({e.amount.asset for e in tax_report.ledger_entries})
|
assets = list({e.amount.asset for e in tax_report.ledger_entries})
|
||||||
|
|
||||||
|
@ -68,7 +94,6 @@ def write_ledger_sheet(sheet, tax_report: TaxReport):
|
||||||
'Type',
|
'Type',
|
||||||
'Værdipapir',
|
'Værdipapir',
|
||||||
'Mængde',
|
'Mængde',
|
||||||
'Balance',
|
|
||||||
'Kontoudbyder',
|
'Kontoudbyder',
|
||||||
''
|
''
|
||||||
] + [a.raw_short_name() for a in assets]
|
] + [a.raw_short_name() for a in assets]
|
||||||
|
@ -84,64 +109,183 @@ def write_ledger_sheet(sheet, tax_report: TaxReport):
|
||||||
ledger_entry.type,
|
ledger_entry.type,
|
||||||
ledger_entry.amount.asset.raw_short_name(),
|
ledger_entry.amount.asset.raw_short_name(),
|
||||||
ledger_entry.amount.amount,
|
ledger_entry.amount.amount,
|
||||||
ledger_entry.balance.amount,
|
|
||||||
ledger_entry.account_provider,
|
ledger_entry.account_provider,
|
||||||
'',
|
'',
|
||||||
] + [(ledger_entry.balance.amount if ledger_entry.balance.asset == a else '') for a in assets]
|
] + [(ledger_entry.balance.amount if ledger_entry.amount.asset == a else '') for a in assets]
|
||||||
sheet.append(row)
|
sheet.append(row)
|
||||||
row_idx+= 1
|
|
||||||
|
|
||||||
if entry_time != prev_entry_time:
|
if entry_time != prev_entry_time and prev_entry_time is not None:
|
||||||
# TODO:
|
# TODO:
|
||||||
sheet.merge_cells(f'A{start_of_same_entry}:A{row_idx-1}')
|
sheet.merge_cells(f'A{start_of_same_entry}:A{row_idx-1}')
|
||||||
sheet[f'A{start_of_same_entry}'].alignment = ALIGN_CENTER
|
sheet[f'A{start_of_same_entry}'].alignment = ALIGN_CENTER
|
||||||
start_of_same_entry = row_idx
|
start_of_same_entry = row_idx
|
||||||
prev_entry_time = entry_time
|
prev_entry_time = entry_time
|
||||||
|
row_idx+= 1
|
||||||
del ledger_entry, row
|
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, tax_report: TaxReport):
|
def write_fifo_sheet(sheet, bought_and_sold_for: list, exchange_rate_at_time) -> tuple[str,str]:
|
||||||
# TODO: Account for transfers
|
# TODO: Account for transfers
|
||||||
column_headers = [
|
column_headers = [
|
||||||
'Værdipapir', 'Mængde', 'Anskaffelsesværdi (DKK)', 'Købsdato (UTC)',
|
'Købsdato (UTC)',
|
||||||
'Salgsværdi (DKK)', 'Salgsdato (UTC)', 'Profit (DKK)', 'Tab (DKK)',
|
'Salgsdato (UTC)',
|
||||||
'Beskatningstype',
|
'Værdipapir',
|
||||||
|
'Mængde',
|
||||||
|
'Anskaffelseskurs',
|
||||||
|
'Salgskurs',
|
||||||
|
'Anskaffelsesværdi (DKK)',
|
||||||
|
'Salgsværdi (DKK)',
|
||||||
|
'Profit (DKK)', 'Tab (DKK)',
|
||||||
]
|
]
|
||||||
add_headers(sheet, column_headers)
|
add_headers(sheet, column_headers)
|
||||||
row_idx = 2
|
row_idx = 2
|
||||||
for fifo_entry in tax_report.bought_and_sold_for:
|
for fifo_entry in bought_and_sold_for:
|
||||||
asset = fifo_entry.amount.asset
|
asset = fifo_entry.amount.asset
|
||||||
|
|
||||||
exchange_rate_dkk_bought = tax_report.exchange_rate_at_time(asset, fin_defs.DKK, fifo_entry.time_bought)
|
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
|
#assert exchange_rate_dkk_bought is not None, asset
|
||||||
exchange_rate_dkk_sold = tax_report.exchange_rate_at_time(asset, fin_defs.DKK, fifo_entry.time_sold)
|
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
|
#assert exchange_rate_dkk_bought is not None, asset
|
||||||
|
|
||||||
row = [
|
row = [
|
||||||
|
fifo_entry.time_bought.replace(tzinfo=None),
|
||||||
|
fifo_entry.time_sold.replace(tzinfo=None),
|
||||||
asset.raw_short_name(),
|
asset.raw_short_name(),
|
||||||
fifo_entry.amount.amount,
|
fifo_entry.amount.amount,
|
||||||
mult_a(fifo_entry.amount.amount , exchange_rate_dkk_bought),
|
exchange_rate_dkk_bought,
|
||||||
fifo_entry.time_bought.replace(tzinfo=None),
|
exchange_rate_dkk_sold,
|
||||||
mult_a(fifo_entry.amount.amount , exchange_rate_dkk_sold),
|
f'=F{row_idx}*D{row_idx}',
|
||||||
fifo_entry.time_sold.replace(tzinfo=None),
|
f'=E{row_idx}*D{row_idx}',
|
||||||
f'=max(0, E{row_idx} - C{row_idx})',
|
f'=max(0, G{row_idx} - H{row_idx})',
|
||||||
f'=max(0, C{row_idx} - E{row_idx})',
|
f'=max(0, H{row_idx} - G{row_idx})',
|
||||||
TAX_TYPES[asset],
|
|
||||||
]
|
]
|
||||||
sheet.append(row)
|
sheet.append(row)
|
||||||
row_idx+= 1
|
row_idx+= 1
|
||||||
del fifo_entry, asset, row
|
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 = 15
|
||||||
|
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):
|
def produce_excel_report(report: TaxReport, output_path: Path):
|
||||||
workbook = openpyxl.Workbook()
|
workbook = openpyxl.Workbook()
|
||||||
|
|
||||||
workbook.active.title = 'Current Assets'
|
# Oversigt
|
||||||
write_current_assets(workbook.active, report)
|
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 = workbook.create_sheet('Ledger')
|
||||||
|
sheet_ledger.page_setup.fitToWidth = 1
|
||||||
write_ledger_sheet(sheet_ledger , report)
|
write_ledger_sheet(sheet_ledger , report)
|
||||||
|
|
||||||
sheet_fifo = workbook.create_sheet('FIFO')
|
# FIFO
|
||||||
write_fifo_sheet(sheet_fifo, report)
|
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)
|
workbook.save(output_path)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user