Ledger sheet
This commit is contained in:
parent
f53c24098c
commit
857eb9b2b4
|
@ -28,81 +28,107 @@ import dataclasses
|
||||||
import logging
|
import logging
|
||||||
import fin_data
|
import fin_data
|
||||||
|
|
||||||
from .data import TaxReport, BoughtAndSold, BoughtAndNotYetSold
|
from .data import TaxReport, BoughtAndSold, BoughtAndNotYetSold, LedgerEntry
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
def compute_tax(transfers: list) -> TaxReport:
|
def compute_fifo(transfers: list) -> TaxReport:
|
||||||
transfers.sort(key=lambda t:t.executed_time)
|
transfers.sort(key=lambda t:t.executed_time)
|
||||||
|
|
||||||
|
|
||||||
bought_and_sold_for: list[BoughtAndSold] = []
|
bought_and_sold_for: list[BoughtAndSold] = []
|
||||||
bought_and_spent_for: list[BoughtAndSold] = []
|
bought_and_spent_for: list[BoughtAndSold] = []
|
||||||
|
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()
|
||||||
prices_bought_for[MPC].append(BoughtAndNotYetSold(AssetAmount(MPC, Decimal(100)), datetime.datetime(2000, 1,1,1,1,1,1)))
|
prices_bought_for[MPC].append(BoughtAndNotYetSold(AssetAmount(MPC, Decimal(100)), datetime.datetime(2000, 1,1,1,1,1,1)))
|
||||||
prices_bought_for[USDT] = deque()
|
prices_bought_for[USDT] = deque()
|
||||||
prices_bought_for[USDT].append(BoughtAndNotYetSold(AssetAmount(USDT, Decimal(100)), datetime.datetime(2000, 1,1,1,1,1,1)))
|
prices_bought_for[USDT].append(BoughtAndNotYetSold(AssetAmount(USDT, Decimal(100)), datetime.datetime(2000, 1,1,1,1,1,1)))
|
||||||
|
|
||||||
def sell(amount: AssetAmount, executed_time: datetime.datetime, spent=False):
|
def current_balance(asset: Asset) -> AssetAmount:
|
||||||
logger.info(f'Selling: {amount}')
|
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'):
|
||||||
|
logger.info('%s: %s', variant, 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:
|
||||||
raise Exception('Miscalculation: ' + str(amount))
|
raise Exception('Miscalculation: ' + str(amount))
|
||||||
partial = prices_bought_for[amount.asset].popleft()
|
partial = prices_bought_for[amount.asset].popleft()
|
||||||
|
|
||||||
logger.info(f'Fuck : {amount} - {partial.amount}')
|
|
||||||
|
|
||||||
amount_covered_by_partial = min(partial.amount, amount)
|
amount_covered_by_partial = min(partial.amount, amount)
|
||||||
amount -= amount_covered_by_partial
|
amount -= amount_covered_by_partial
|
||||||
new_partial_amount = partial.amount - amount_covered_by_partial
|
new_partial_amount = partial.amount - amount_covered_by_partial
|
||||||
|
|
||||||
logger.info(f' => {amount} ({new_partial_amount}')
|
|
||||||
|
|
||||||
# Re-add partial
|
# Re-add partial
|
||||||
if new_partial_amount.amount != 0:
|
if new_partial_amount.amount != 0:
|
||||||
new_partial = BoughtAndNotYetSold(new_partial_amount, partial.time_bought)
|
new_partial = BoughtAndNotYetSold(new_partial_amount, partial.time_bought)
|
||||||
logger.info(f' => new partial: {new_partial}')
|
|
||||||
prices_bought_for[amount.asset].appendleft(new_partial)
|
prices_bought_for[amount.asset].appendleft(new_partial)
|
||||||
del new_partial
|
del new_partial
|
||||||
|
|
||||||
# Add FIFO like
|
# Add FIFO like
|
||||||
if spent:
|
if variant == 'FEE':
|
||||||
bought_and_spent_for.append(BoughtAndSold(amount_covered_by_partial, partial.time_bought, executed_time))
|
bought_and_spent_for.append(BoughtAndSold(amount_covered_by_partial, partial.time_bought, executed_time))
|
||||||
else:
|
else:
|
||||||
bought_and_sold_for.append(BoughtAndSold(amount_covered_by_partial, partial.time_bought, executed_time))
|
bought_and_sold_for.append(BoughtAndSold(amount_covered_by_partial, partial.time_bought, executed_time))
|
||||||
|
|
||||||
del partial, amount_covered_by_partial
|
del partial, amount_covered_by_partial
|
||||||
|
|
||||||
|
ledger_entries.append(LedgerEntry(
|
||||||
|
time = executed_time,
|
||||||
|
type = variant,
|
||||||
|
account_provider = 'Kraken (TODO)',
|
||||||
|
amount = amount_to_sell,
|
||||||
|
balance = current_balance(amount.asset),
|
||||||
|
))
|
||||||
|
|
||||||
def spend(amount: AssetAmount, executed_time: datetime.datetime):
|
def spend(amount: AssetAmount, executed_time: datetime.datetime):
|
||||||
logger.info(f'Spending: {amount}')
|
if amount.amount > 0:
|
||||||
sell(amount, executed_time, spent=True)
|
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),
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
for transfer in transfers:
|
for transfer in transfers:
|
||||||
logger.info(transfer)
|
logger.info(transfer)
|
||||||
# Buy
|
|
||||||
if output := transfer.output:
|
|
||||||
logger.info('Buy')
|
|
||||||
if output.asset not in prices_bought_for:
|
|
||||||
prices_bought_for[output.asset] = deque()
|
|
||||||
prices_bought_for[output.asset].append(BoughtAndNotYetSold(output, transfer.executed_time))
|
|
||||||
del output
|
|
||||||
|
|
||||||
# Sell
|
# Sell
|
||||||
if _input := transfer.input:
|
if _input := transfer.input:
|
||||||
logger.info('Sell')
|
variant = 'WITHDRAW' if transfer.output is None else 'SELL'
|
||||||
sell(_input, transfer.executed_time)
|
sell(_input, transfer.executed_time, variant)
|
||||||
|
del variant
|
||||||
del _input
|
del _input
|
||||||
|
|
||||||
|
# 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
|
||||||
if fee := transfer.fee:
|
if fee := transfer.fee:
|
||||||
logger.info('Sell')
|
|
||||||
spend(fee, transfer.executed_time)
|
spend(fee, transfer.executed_time)
|
||||||
del transfer, fee
|
del fee
|
||||||
|
|
||||||
|
del transfer
|
||||||
|
|
||||||
def exchange_rate_at_time(a, b, t):
|
def exchange_rate_at_time(a, b, t):
|
||||||
try:
|
try:
|
||||||
|
@ -117,4 +143,5 @@ def compute_tax(transfers: list) -> TaxReport:
|
||||||
bought_and_spent_for=bought_and_spent_for,
|
bought_and_spent_for=bought_and_spent_for,
|
||||||
current_assets=prices_bought_for,
|
current_assets=prices_bought_for,
|
||||||
exchange_rate_at_time=exchange_rate_at_time,
|
exchange_rate_at_time=exchange_rate_at_time,
|
||||||
|
ledger_entries = ledger_entries,
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,7 +11,7 @@ import dataclasses
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from . import compute_tax, output_excel
|
from . import compute_fifo, output_excel
|
||||||
|
|
||||||
KUCOIN_CLIENT = fin_depo.defi_kucoin.KucoinDepoFetcher(
|
KUCOIN_CLIENT = fin_depo.defi_kucoin.KucoinDepoFetcher(
|
||||||
secrets.KUCOIN_KEY,
|
secrets.KUCOIN_KEY,
|
||||||
|
@ -31,28 +31,13 @@ def main():
|
||||||
logger = logging.getLogger('crypto_tax')
|
logger = logging.getLogger('crypto_tax')
|
||||||
logger.setLevel('INFO')
|
logger.setLevel('INFO')
|
||||||
|
|
||||||
TRANSFERS = list(KRAKEN_CLIENT._get_double_registers())
|
TRANSFERS = []
|
||||||
TRANSFERS += list(KUCOIN_CLIENT._get_double_registers())
|
TRANSFERS += list(KRAKEN_CLIENT._get_double_registers())
|
||||||
tax_report = compute_tax(TRANSFERS)
|
#TRANSFERS += list(KUCOIN_CLIENT._get_double_registers())
|
||||||
|
tax_report = compute_fifo(TRANSFERS)
|
||||||
|
|
||||||
logger.info('-'*80)
|
logger.info('-'*80)
|
||||||
|
|
||||||
if True:
|
|
||||||
logger.info('Bought for:')
|
|
||||||
for asset, prices in tax_report.current_assets.items():
|
|
||||||
price_sum = sum((p.amount for p in prices), start=AssetAmount.ZERO)
|
|
||||||
sys.stdout.write(f' - {asset.raw_short_name():10} ({price_sum}): ')
|
|
||||||
for price in prices:
|
|
||||||
sys.stdout.write(str(price.amount.amount))
|
|
||||||
sys.stdout.write(', ')
|
|
||||||
del price
|
|
||||||
sys.stdout.write('\n')
|
|
||||||
del asset, prices
|
|
||||||
|
|
||||||
|
|
||||||
for bas in tax_report.bought_and_sold_for:
|
|
||||||
sys.stdout.write(f'{bas.amount} ({bas.time_bought} ----> {bas.time_sold})\n')
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
|
@ -23,12 +23,19 @@ class BoughtAndNotYetSold:
|
||||||
time_bought: datetime.datetime
|
time_bought: datetime.datetime
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class LedgerEntry:
|
||||||
|
time: datetime.datetime
|
||||||
|
type: str
|
||||||
|
account_provider: str
|
||||||
|
amount: AssetAmount
|
||||||
|
balance: AssetAmount
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class TaxReport:
|
class TaxReport:
|
||||||
bought_and_sold_for: list[BoughtAndSold]
|
bought_and_sold_for: list[BoughtAndSold]
|
||||||
bought_and_spent_for: list[BoughtAndSold]
|
bought_and_spent_for: list[BoughtAndSold]
|
||||||
current_assets: dict[Asset, deque[BoughtAndNotYetSold]]
|
current_assets: dict[Asset, deque[BoughtAndNotYetSold]]
|
||||||
exchange_rate_at_time: Callable[[Asset,Asset, datetime.datetime ], Decimal]
|
exchange_rate_at_time: Callable[[Asset,Asset, datetime.datetime ], Decimal]
|
||||||
|
ledger_entries: list[LedgerEntry]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,32 @@ def write_current_assets(sheet, tax_report: TaxReport):
|
||||||
del asset, positions, row, total_amount
|
del asset, positions, row, total_amount
|
||||||
|
|
||||||
|
|
||||||
|
def write_ledger_sheet(sheet, tax_report: TaxReport):
|
||||||
|
column_headers = [
|
||||||
|
'Tidspunkt (UTC)',
|
||||||
|
'Type',
|
||||||
|
'Værdipapir',
|
||||||
|
'Mængde',
|
||||||
|
'Balance',
|
||||||
|
'Kontoudbyder',
|
||||||
|
]
|
||||||
|
sheet.append(column_headers)
|
||||||
|
row_idx = 2
|
||||||
|
for ledger_entry in tax_report.ledger_entries:
|
||||||
|
row = [
|
||||||
|
ledger_entry.time.replace(tzinfo=None),
|
||||||
|
ledger_entry.type,
|
||||||
|
ledger_entry.amount.asset.raw_short_name(),
|
||||||
|
ledger_entry.amount.amount,
|
||||||
|
ledger_entry.balance.amount,
|
||||||
|
ledger_entry.account_provider,
|
||||||
|
]
|
||||||
|
sheet.append(row)
|
||||||
|
row_idx+= 1
|
||||||
|
del ledger_entry, row
|
||||||
|
|
||||||
def write_fifo_sheet(sheet, tax_report: TaxReport):
|
def write_fifo_sheet(sheet, tax_report: TaxReport):
|
||||||
|
# TODO: Account for transfers
|
||||||
column_headers = [
|
column_headers = [
|
||||||
'Værdipapir', 'Mængde', 'Anskaffelsesværdi (DKK)', 'Købsdato (UTC)',
|
'Værdipapir', 'Mængde', 'Anskaffelsesværdi (DKK)', 'Købsdato (UTC)',
|
||||||
'Salgsværdi (DKK)', 'Salgsdato (UTC)', 'Profit (DKK)', 'Tab (DKK)',
|
'Salgsværdi (DKK)', 'Salgsdato (UTC)', 'Profit (DKK)', 'Tab (DKK)',
|
||||||
|
@ -89,7 +114,10 @@ def produce_excel_report(report: TaxReport, output_path: Path):
|
||||||
workbook.active.title = 'Current Assets'
|
workbook.active.title = 'Current Assets'
|
||||||
write_current_assets(workbook.active, report)
|
write_current_assets(workbook.active, report)
|
||||||
|
|
||||||
sheet_buy_sales = workbook.create_sheet('FIFO')
|
sheet_ledger = workbook.create_sheet('Ledger')
|
||||||
write_fifo_sheet(sheet_buy_sales, report)
|
write_ledger_sheet(sheet_ledger , report)
|
||||||
|
|
||||||
|
sheet_fifo = workbook.create_sheet('FIFO')
|
||||||
|
write_fifo_sheet(sheet_fifo, report)
|
||||||
|
|
||||||
workbook.save(output_path)
|
workbook.save(output_path)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user