balance sheet report

This commit is contained in:
Roger Oriol
2023-12-26 19:49:05 +01:00
parent 04efd94e9f
commit 3fb7ec5d7d
3 changed files with 345 additions and 22 deletions

View File

@@ -1,4 +1,275 @@
#!/usr/bin/env bash
now="$(date +'%Y%m%d')"
mkdir -p reports/balsheet/$now
bean-report ledger/main.beancount balsheet > reports/balsheet/$now/balsheet.html
#!/usr/bin/env python3
from beancount import loader
from beancount.query import query
from beancount.parser import printer
import argparse
from tabulate import tabulate
from decimal import Decimal
from beancount.core.amount import Amount, sub, mul
from math import floor
class bcolors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKCYAN = '\033[96m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
def draw_line():
print('─' * 30)
def get_sum_invest_funds(balances):
sum = 0
for account, balance in balances.items():
if account.startswith("Assets:Invest:R4:"):
parts = account.split(":")
if len(parts) == 5:
sum = balance if sum == 0 else sum + balance
result = sum.get_only_position().units
return Amount(Decimal(round(result.number, 2)), result.currency).to_string()
def get_sum_stocks(balances):
sum = 0
for account, balance in balances.items():
if account.startswith("Assets:Invest:R4:"):
parts = account.split(":")
if len(parts) == 4:
sum = balance if sum == 0 else sum + balance
result = sum.get_only_position().units
return Amount(Decimal(round(result.number, 2)), result.currency).to_string()
def get_total_inversions(balances):
sum = 0
for account, balance in balances.items():
if account.startswith("Assets:Invest:R4:"):
sum = balance if sum == 0 else sum + balance
result = sum.get_only_position().units
return Amount(Decimal(round(result.number, 2)), result.currency)
def get_total_propietats(balances):
sum = 0
for account, balance in balances.items():
if account.startswith("Assets:PersonalProperty:"):
sum = balance if sum == 0 else sum + balance
result = sum.get_only_position().units
return Amount(Decimal(round(result.number, 2)), result.currency).to_string()
def get_total_debt_assets(balances):
sum = 0
for account, balance in balances.items():
if account.startswith("Assets:Debt:"):
sum = balance if sum == 0 else sum + balance
if sum != 0 and sum.get_only_position() != None:
result = sum.get_only_position().units
return Amount(Decimal(round(result.number, 2)), result.currency).to_string()
else:
return Amount(Decimal(0), "EUR").to_string()
def get_total_benefits(balances):
sum = 0
for account, balance in balances.items():
if account.startswith("Assets:Benefits:"):
sum = balance if sum == 0 else sum + balance
if sum != 0 and sum.get_only_position() != None:
result = sum.get_only_position().units
return Amount(Decimal(round(result.number, 2)), result.currency).to_string()
else:
return Amount(Decimal(0), "EUR").to_string()
def get_total_assets(balances):
sum = 0
for account, balance in balances.items():
if account.startswith("Assets:"):
sum = balance if sum == 0 else sum + balance
if sum != 0 and sum.get_only_position() != None:
result = sum.get_only_position().units
return Amount(Decimal(round(result.number, 2)), result.currency)
else:
return Amount(Decimal(0), "EUR")
def get_total_liabilites(balances):
sum = 0
for account, balance in balances.items():
if account.startswith("Liabilities:"):
sum = balance if sum == 0 else sum + balance
if sum != 0 and sum.get_only_position() != None:
result = sum.get_only_position().units
return Amount(Decimal(round(result.number, 2)), result.currency)
else:
return Amount(Decimal(0), "EUR")
def get_net_worth(balances):
return sub(get_total_assets(balances), get_total_liabilites(balances))
def get_debt_to_assets_ratio(balances, max):
assets = 0
liabilities = 0
for account, balance in balances.items():
if account.startswith("Assets:"):
assets = balance if assets == 0 else assets + balance
elif account.startswith("Liabilities:"):
liabilities = balance if liabilities == 0 else liabilities + balance
total_liabilities = Amount(Decimal(0), "EUR") if liabilities.get_only_position() == None else liabilities.get_only_position().units
total_assets = Amount(Decimal(0), "EUR") if assets.get_only_position() == None else assets.get_only_position().units
result = round((total_liabilities.number / total_assets.number) * 100, 2)
return f"{bcolors.FAIL if result >= max else bcolors.OKGREEN}{result} %{bcolors.ENDC}"
def get_basic_liquidity_ratio(balances, min):
liquid = 0
living_expenses = 2000 # TODO: Hardcoded
for account, balance in balances.items():
if account.startswith("Assets:Liquid"):
liquid = balance if liquid == 0 else liquid + balance
total_liquid = Amount(Decimal(0), "EUR") if liquid.get_only_position() == None else liquid.get_only_position().units
result = round(total_liquid.number / living_expenses, 2)
return f"{bcolors.FAIL if result < min else bcolors.OKGREEN}{result}{bcolors.ENDC}"
def get_investment_assets_to_net_worth_ratio(balances, min):
result = round((get_total_inversions(balances).number / get_net_worth(balances).number) * 100, 2)
return f"{bcolors.FAIL if result < min else bcolors.OKGREEN}{result} %{bcolors.ENDC}"
def get_liquid_assets_to_net_worth_ratio(balances, min):
liquid = 0
for account, balance in balances.items():
if account.startswith("Assets:Liquid"):
liquid = balance if liquid == 0 else liquid + balance
total_liquid = Amount(Decimal(0), "EUR") if liquid.get_only_position() == None else liquid.get_only_position().units
result = round((total_liquid.number / get_net_worth(balances).number) * 100, 2)
return f"{bcolors.FAIL if result < min else bcolors.OKGREEN}{result} %{bcolors.ENDC}"
def get_savings_ratio(balances, min):
monthly_savings_for_investment = 1200 # TODO: Hardcoded
gross_monthly_income = 3300 # TODO: Hardcoded
result = round((monthly_savings_for_investment / gross_monthly_income) * 100, 2)
return f"{bcolors.FAIL if result < min else bcolors.OKGREEN}{result} %{bcolors.ENDC}"
def get_debt_service_ratio(balances, max):
monthly_debt_repayment = 0 # TODO: Hardcoded
gross_monthly_income = 3300 # TODO: Hardcoded
result = round((monthly_debt_repayment / gross_monthly_income) * 100, 2)
return f"{bcolors.FAIL if result >= max else bcolors.OKGREEN}{result} %{bcolors.ENDC}"
def get_non_mortgage_debt_service_ratio(balances, max):
monthly_mortgage_debt_repayment = 0 # TODO: Hardcoded
gross_monthly_income = 3300 # TODO: Hardcoded
result = round((monthly_mortgage_debt_repayment / gross_monthly_income) * 100, 2)
return f"{bcolors.FAIL if result >= max else bcolors.OKGREEN}{result} %{bcolors.ENDC}"
def get_solvency_ratio(balances, min):
result = round((get_net_worth(balances).number / get_total_assets(balances).number) * 100, 2)
return f"{bcolors.FAIL if result < min else bcolors.OKGREEN}{result} %{bcolors.ENDC}"
def get_max_leveraged_investment(balances):
assets = get_total_assets(balances)
return Amount(round(assets.number * Decimal(0.9), 2), assets.currency).to_string()
def get_position_as_str(inventory):
position = inventory.get_only_position()
if position is None:
return position
else:
return position.to_string()
def print_report(date, balances):
print(f"{bcolors.BOLD}Balance Sheet (date={date}){bcolors.ENDC}")
draw_line()
print(f"{bcolors.BOLD}Assets{bcolors.ENDC}")
print(f"\t{bcolors.BOLD}Liquids{bcolors.ENDC}")
print(tabulate([
["Corrent", get_position_as_str(balances["Assets:Liquid:Caixabank:Corrent"])],
["Estalvi", get_position_as_str(balances["Assets:Liquid:Caixabank:Estalvi"])],
["Compte d'inversió", get_position_as_str(balances["Assets:Liquid:R4:EUR"])],
["Total líquids", get_position_as_str(balances["Assets:Liquid:R4:EUR"] + balances["Assets:Liquid:Caixabank:Estalvi"] + balances["Assets:Liquid:Caixabank:Corrent"])],
]))
print(f"\t{bcolors.BOLD}Inversions{bcolors.ENDC}")
print(tabulate([
["Fons d'inversió", get_sum_invest_funds(balances)],
["Accions", get_sum_stocks(balances)],
["Renta fixa", Amount(Decimal(0), "EUR").to_string()],
["Total inversions", get_total_inversions(balances).to_string()],
]))
print(f"\t{bcolors.BOLD}Propietat personal{bcolors.ENDC}")
print(tabulate([
["Vivenda principal", get_position_as_str(balances["Assets:PersonalProperty:VivendaPrincipal"])],
["Cotxes", get_position_as_str(balances["Assets:PersonalProperty:Cotxe"])],
["Joies, Art, Col·leccionables", get_position_as_str(balances["Assets:PersonalProperty:JoiesArtCollecionables"])],
["Metalls preciosos", get_position_as_str(balances["Assets:PersonalProperty:MetallsPreciosos"])],
["Altres propietats", get_position_as_str(balances["Assets:PersonalProperty:AltresPropietats"])],
["Total propietats", get_total_propietats(balances)],
]))
print(f"\t{bcolors.BOLD}Deutes{bcolors.ENDC}")
print(tabulate([
["Deutes per cobrar", get_position_as_str(balances["Assets:Debt:DeutesPerCobrar"])],
["Total deutes", get_total_debt_assets(balances)],
]))
print(f"\t{bcolors.BOLD}Beneficis laborals{bcolors.ENDC}")
print(tabulate([
["Tickets Restaurant", get_position_as_str(balances["Assets:Benefits:Edenred:TicketsRestaurant"])],
["Targeta Transport", get_position_as_str(balances["Assets:Benefits:Edenred:TargetaTransport"])],
["Total beneficis", get_total_benefits(balances)],
]))
print(tabulate([
[f"\t{bcolors.BOLD}Total Assets", get_total_assets(balances).to_string()]
]))
draw_line()
print(f"{bcolors.BOLD}Liabilites{bcolors.ENDC}")
print(tabulate([
["Hipoteques en vivenda principal", get_position_as_str(balances["Liabilities:Hipoteca:VivendaPrincipal"])],
["Hipoteques en vivenda d'inversió", Amount(Decimal(0), "EUR").to_string()],
["Crèdit", get_position_as_str(balances["Liabilities:Credit:Caixabank:TargetaCredit"])],
["Factures impagades", get_position_as_str(balances["Liabilities:Factures:FacturesPendents"])],
["Préstecs personals", Amount(Decimal(0), "EUR").to_string()],
["Impostos no pagats", get_position_as_str(balances["Liabilities:Taxes:IRPF"])],
["Altres passius", Amount(Decimal(0), "EUR").to_string()],
["Total passius", get_total_liabilites(balances).to_string()],
]))
draw_line()
print(f"{bcolors.BOLD}Net Worth\t{get_net_worth(balances)}{bcolors.ENDC}")
draw_line()
print(f"{bcolors.BOLD}Financial Ratios{bcolors.ENDC}")
print(tabulate([
["Debt-to-Assets Ratio", get_debt_to_assets_ratio(balances, 50), "50 %"],
["Basic Liquidity Ratio", get_basic_liquidity_ratio(balances, 12), "12"],
["Investment Assets to Net Worth Ratio", get_investment_assets_to_net_worth_ratio(balances, 50), "50 %"],
["Liquid Assets to Net Worth Ratio", get_liquid_assets_to_net_worth_ratio(balances, 15), "15 %"],
["Savings Ratio", get_savings_ratio(balances, 20), "20 %"],
["Debt-Service Ratio", get_debt_service_ratio(balances, 35), "35 %"],
["Non-Mortgage Debt-Service Ratio", get_non_mortgage_debt_service_ratio(balances, 15), "15 %"],
["Solvency Ratio", get_solvency_ratio(balances, 50), "50 %"],
["Max Leveraged Investment", get_max_leveraged_investment(balances), ""]
]))
def get_balances(entries, options, date):
balance_query = f"SELECT account, convert(sum(position), \"EUR\") as position FROM date <= {date} WHERE account ~ '^(Liabilities|Assets)'"
rtypes, rrows = query.run_query(
entries, options, balance_query)
balances = {}
for row in rrows:
balances[row.account] = row.position
return balances
def main():
parser = argparse.ArgumentParser(description='Generate balance sheet report')
parser.add_argument('date', metavar='date', type=str, nargs=1,
help='Report date in ISO format (e.g. 1970-01-01)')
args = parser.parse_args()
date = args.date[0]
filename = "ledger/main.beancount"
entries, errors, options = loader.load_file(filename)
if errors:
printer.print_errors(errors)
balances = get_balances(entries, options, date)
print_report(date, balances)
main()

View File

@@ -9,7 +9,7 @@
1970-01-01 open Assets:Invest:R4:PLTR PLTR
1970-01-01 open Assets:Invest:R4:MSFT MSFT
1970-01-01 open Assets:Benefits:Edenred:TicketsRestaurant EUR
1970-01-01 open Assets:Benefits:Edenred:TarjetaTransport EUR
1970-01-01 open Assets:Benefits:Edenred:TargetaTransport EUR
1970-01-01 open Assets:PersonalProperty:VivendaPrincipal EUR
1970-01-01 open Assets:PersonalProperty:Cotxe EUR
1970-01-01 open Assets:PersonalProperty:JoiesArtCollecionables EUR
@@ -20,11 +20,11 @@
1970-01-01 open Liabilities:Credit:Caixabank:TargetaCredit EUR
1970-01-01 open Liabilities:Factures:FacturesPendents EUR
1970-01-01 open Liabilities:Taxes:IRPF EUR
;1970-01-01 open Liabilities:Hipoteca:VivendaPrincipal EUR
1970-01-01 open Liabilities:Hipoteca:VivendaPrincipal EUR
1970-01-01 open Income:Work:Zurich:Salari EUR
1970-01-01 open Income:Work:Zurich:TicketsRestaurant EUR
1970-01-01 open Income:Work:Zurich:TarjetaTransport EUR
1970-01-01 open Income:Work:Zurich:TargetaTransport EUR
1970-01-01 open Income:Work:Zurich:SeguroMedic EUR
1970-01-01 open Income:Work:Zurich:Gimnas EUR
1970-01-01 open Income:Other:Caixabank:Transferencia EUR

View File

@@ -1,19 +1,24 @@
2023-12-31 * "Balanç inicial EUR"
Assets:Liquid:Caixabank:Corrent 18903.80 EUR
Assets:Liquid:Caixabank:Estalvi 12666.49 EUR
Assets:Liquid:R4:EUR 44.04 EUR
Assets:Invest:R4:Amundi:MSCIWRLD 86.005 AMNDMSCIWRLD {237.62 EUR}
Assets:Invest:R4:Vanguard:EMMK 14.99 VANEMMK {177.773 EUR}
Assets:Invest:R4:Fidelity:GLTECH 344.47 FIGLTECH {41.06 EUR}
Assets:Invest:R4:Amundi:SUSTINC 11.295 AMNDSUSINC {62.84 EUR}
Assets:Benefits:Edenred:TicketsRestaurant 0 EUR
Assets:Benefits:Edenred:TarjetaTransport 0 EUR
Assets:PersonalProperty:VivendaPrincipal 0 EUR
Assets:PersonalProperty:Cotxe 10000 EUR
Assets:PersonalProperty:JoiesArtCollecionables 1250 EUR
Assets:PersonalProperty:MetallsPreciosos 0 EUR
Assets:PersonalProperty:AltresPropietats 0 EUR
Assets:Debt:DeutesPerCobrar 0 EUR
Assets:Liquid:Caixabank:Corrent 18903.80 EUR
Assets:Liquid:Caixabank:Estalvi 12666.49 EUR
Assets:Liquid:R4:EUR 44.04 EUR
Assets:Invest:R4:Amundi:MSCIWRLD 86.005 AMNDMSCIWRLD {237.62 EUR}
Assets:Invest:R4:Vanguard:EMMK 14.99 VANEMMK {177.773 EUR}
Assets:Invest:R4:Fidelity:GLTECH 344.47 FIGLTECH {41.06 EUR}
Assets:Invest:R4:Amundi:SUSTINC 11.295 AMNDSUSINC {62.84 EUR}
Assets:Benefits:Edenred:TicketsRestaurant 0 EUR
Assets:Benefits:Edenred:TargetaTransport 0 EUR
Assets:PersonalProperty:VivendaPrincipal 0 EUR
Assets:PersonalProperty:Cotxe 10000 EUR
Assets:PersonalProperty:JoiesArtCollecionables 1250 EUR
Assets:PersonalProperty:MetallsPreciosos 0 EUR
Assets:PersonalProperty:AltresPropietats 0 EUR
Assets:Debt:DeutesPerCobrar 0 EUR
Liabilities:Hipoteca:VivendaPrincipal 0 EUR
Liabilities:Credit:Caixabank:TargetaCredit 0 EUR
Liabilities:Credit:Caixabank:TargetaCredit 0 EUR
Liabilities:Factures:FacturesPendents 0 EUR
Liabilities:Taxes:IRPF 0 EUR
Equity:Opening-Balances
2023-12-31 * "Balanç inicial USD"
Assets:Invest:R4:BNP:DISTECH 0.359 BNPDISTECH {2195.55 USD}
@@ -21,6 +26,7 @@
Assets:Invest:R4:MSFT 4 MSFT {341.8 USD}
Equity:Opening-Balances:USD
<<<<<<< HEAD
2024-01-01 balance Assets:Liquid:Caixabank:Corrent 18903.80 EUR
2024-01-01 balance Assets:Liquid:Caixabank:Estalvi 12666.49 EUR
2024-01-01 balance Assets:Liquid:R4:EUR 44.04 EUR
@@ -39,6 +45,26 @@
2024-01-01 balance Assets:PersonalProperty:MetallsPreciosos 0 EUR
2024-01-01 balance Assets:PersonalProperty:AltresPropietats 0 EUR
2024-01-01 balance Assets:Debt:DeutesPerCobrar 0 EUR
=======
2024-01-01 balance Assets:Liquid:Caixabank:Corrent 18903.80 EUR
2024-01-01 balance Assets:Liquid:Caixabank:Estalvi 12666.49 EUR
2024-01-01 balance Assets:Liquid:R4:EUR 44.04 EUR
2024-01-01 balance Assets:Invest:R4:Amundi:MSCIWRLD 86.005 AMNDMSCIWRLD
2024-01-01 balance Assets:Invest:R4:Vanguard:EMMK 14.99 VANEMMK
2024-01-01 balance Assets:Invest:R4:Fidelity:GLTECH 344.47 FIGLTECH
2024-01-01 balance Assets:Invest:R4:Amundi:SUSTINC 11.295 AMNDSUSINC
2024-01-01 balance Assets:Invest:R4:BNP:DISTECH 0.359 BNPDISTECH
2024-01-01 balance Assets:Invest:R4:PLTR 10 PLTR
2024-01-01 balance Assets:Invest:R4:MSFT 4 MSFT
2024-01-01 balance Assets:Benefits:Edenred:TicketsRestaurant 0 EUR
2024-01-01 balance Assets:Benefits:Edenred:TargetaTransport 0 EUR
2024-01-01 balance Assets:PersonalProperty:VivendaPrincipal 0 EUR
2024-01-01 balance Assets:PersonalProperty:Cotxe 10000 EUR
2024-01-01 balance Assets:PersonalProperty:JoiesArtCollecionables 1250 EUR
2024-01-01 balance Assets:PersonalProperty:MetallsPreciosos 0 EUR
2024-01-01 balance Assets:PersonalProperty:AltresPropietats 0 EUR
2024-01-01 balance Assets:Debt:DeutesPerCobrar 0 EUR
>>>>>>> ac34f2f (balance sheet report)
2024-01-01 * "Zurich" "Cuota gimnàs Andjoy"
amortize_months: 12
@@ -49,12 +75,18 @@
Expenses:Medic 414.60 EUR
Income:Work:Zurich:SeguroMedic
2024-01-01 * "Zurich" "Targeta Transport"
<<<<<<< HEAD
Assets:Benefits:Edenred:TarjetaTransport 40 EUR
Income:Work:Zurich:TarjetaTransport
=======
Assets:Benefits:Edenred:TargetaTransport 40 EUR
Income:Work:Zurich:TargetaTransport
>>>>>>> ac34f2f (balance sheet report)
2024-01-01 * "Zurich" "Targeta Restaurant"
Assets:Benefits:Edenred:TicketsRestaurant 209 EUR
Income:Work:Zurich:TicketsRestaurant
<<<<<<< HEAD
2024-02-01 balance Assets:Liquid:Caixabank:Corrent 18903.80 EUR
2024-02-01 balance Assets:Liquid:Caixabank:Estalvi 12666.49 EUR
;2024-02-01 balance Assets:Liquid:R4:EUR 44.04 EUR
@@ -73,3 +105,23 @@
2024-02-01 balance Assets:PersonalProperty:MetallsPreciosos 0 EUR
2024-02-01 balance Assets:PersonalProperty:AltresPropietats 0 EUR
2024-02-01 balance Assets:Debt:DeutesPerCobrar 0 EUR
=======
2024-02-01 balance Assets:Liquid:Caixabank:Corrent 18903.80 EUR
2024-02-01 balance Assets:Liquid:Caixabank:Estalvi 12666.49 EUR
2024-02-01 balance Assets:Liquid:R4:EUR 44.04 EUR
2024-02-01 balance Assets:Invest:R4:Amundi:MSCIWRLD 86.005 AMNDMSCIWRLD
2024-02-01 balance Assets:Invest:R4:Vanguard:EMMK 14.99 VANEMMK
2024-02-01 balance Assets:Invest:R4:Fidelity:GLTECH 344.47 FIGLTECH
2024-02-01 balance Assets:Invest:R4:Amundi:SUSTINC 11.295 AMNDSUSINC
2024-02-01 balance Assets:Invest:R4:BNP:DISTECH 0.359 BNPDISTECH
2024-02-01 balance Assets:Invest:R4:PLTR 10 PLTR
2024-02-01 balance Assets:Invest:R4:MSFT 4 MSFT
2024-02-01 balance Assets:Benefits:Edenred:TicketsRestaurant 209 EUR
2024-02-01 balance Assets:Benefits:Edenred:TargetaTransport 40 EUR
2024-02-01 balance Assets:PersonalProperty:VivendaPrincipal 0 EUR
2024-02-01 balance Assets:PersonalProperty:Cotxe 10000 EUR
2024-02-01 balance Assets:PersonalProperty:JoiesArtCollecionables 1250 EUR
2024-02-01 balance Assets:PersonalProperty:MetallsPreciosos 0 EUR
2024-02-01 balance Assets:PersonalProperty:AltresPropietats 0 EUR
2024-02-01 balance Assets:Debt:DeutesPerCobrar 0 EUR
>>>>>>> ac34f2f (balance sheet report)