24 agost 2025

This commit is contained in:
Roger Oriol
2025-08-24 12:02:46 +02:00
parent 8f282307b7
commit 8e92bee44b
2 changed files with 300 additions and 185 deletions

View File

@@ -9,6 +9,7 @@ from beancount.core.amount import Amount, add, sub, mul
from math import floor from math import floor
from datetime import datetime, timedelta from datetime import datetime, timedelta
class bcolors: class bcolors:
HEADER = '\033[95m' HEADER = '\033[95m'
OKBLUE = '\033[94m' OKBLUE = '\033[94m'
@@ -20,237 +21,309 @@ class bcolors:
BOLD = '\033[1m' BOLD = '\033[1m'
UNDERLINE = '\033[4m' UNDERLINE = '\033[4m'
def draw_line(): def draw_line():
print('─' * 30) print('─' * 30)
def get_last_month_timestamps(date): def get_last_month_timestamps(date):
month = int(date.split("-")[1]) month = int(date.split("-")[1])
year = int(date.split("-")[0]) year = int(date.split("-")[0])
d = datetime(year, month, 1) d = datetime(year, month, 1)
end_date = d - timedelta(days=1) end_date = d - timedelta(days=1)
start_date = f"{end_date.year}-{end_date.month:02d}-01" start_date = f"{end_date.year}-{end_date.month:02d}-01"
return start_date, end_date.strftime("%Y-%m-%d") return start_date, end_date.strftime("%Y-%m-%d")
def get_sum_balances(balances, account_prefix): def get_sum_balances(balances, account_prefix):
sum = 0 sum = 0
for account, balance in balances.items(): for account, balance in balances.items():
if account.startswith(account_prefix): if account.startswith(account_prefix):
sum = balance if sum == 0 else sum + balance sum = balance if sum == 0 else sum + balance
if sum == 0 or sum.get_only_position() == None: if sum == 0 or sum.get_only_position() == None:
return Amount(Decimal(0), "EUR").to_string() return Amount(Decimal(0), "EUR").to_string()
result = sum.get_only_position().units result = sum.get_only_position().units
return Amount(Decimal(round(result.number, 2)), result.currency).to_string() return Amount(Decimal(round(result.number, 2)), result.currency).to_string()
def get_net_worth(balances): def get_net_worth(balances):
total_assets = Amount.from_string(get_sum_balances(balances, "Assets:")) total_assets = Amount.from_string(get_sum_balances(balances, "Assets:"))
total_liabilities = Amount.from_string(get_sum_balances(balances, "Liabilities:")) total_liabilities = Amount.from_string(
return add(total_assets, total_liabilities) get_sum_balances(balances, "Liabilities:"))
return add(total_assets, total_liabilities)
def get_debt_to_assets_ratio(balances, max): def get_debt_to_assets_ratio(balances, max):
total_assets = Amount.from_string(get_sum_balances(balances, "Assets:")) total_assets = Amount.from_string(get_sum_balances(balances, "Assets:"))
total_liabilities = Amount.from_string(get_sum_balances(balances, "Liabilities:")) total_liabilities = Amount.from_string(
result = round(((total_liabilities.number * -1) / total_assets.number) * 100, 2) get_sum_balances(balances, "Liabilities:"))
return f"{bcolors.FAIL if result >= max else bcolors.OKGREEN}{result} %{bcolors.ENDC}" result = round(((total_liabilities.number * -1) /
total_assets.number) * 100, 2)
return f"{bcolors.FAIL if result >= max else bcolors.OKGREEN}{result} %{bcolors.ENDC}"
def get_emergency_fund_ratio(balances, expenses, low, mid): def get_emergency_fund_ratio(balances, expenses, low, mid):
liquid = 0 liquid = 0
living_expenses = expenses[0].position.get_only_position().units.number living_expenses = expenses[0].position.get_only_position().units.number
for account, balance in balances.items(): for account, balance in balances.items():
if account.startswith("Assets:Liquid"): if account.startswith("Assets:Liquid"):
liquid = balance if liquid == 0 else liquid + balance 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 total_liquid = Amount(Decimal(0), "EUR") if liquid.get_only_position(
result = round(total_liquid.number / living_expenses, 2) ) == None else liquid.get_only_position().units
color = bcolors.FAIL if result < low else bcolors.OKGREEN if result > mid else bcolors.WARNING result = round(total_liquid.number / living_expenses, 2)
return f"{color}{result}{bcolors.ENDC}" color = bcolors.FAIL if result < low else bcolors.OKGREEN if result > mid else bcolors.WARNING
return f"{color}{result}{bcolors.ENDC}"
def get_investment_assets_to_net_worth_ratio(balances, min): def get_investment_assets_to_net_worth_ratio(balances, min):
total_investment = Amount.from_string(get_sum_balances(balances, "Assets:Invest:")) total_investment = Amount.from_string(
result = round((total_investment.number / get_net_worth(balances).number) * 100, 2) get_sum_balances(balances, "Assets:Invest:"))
return f"{bcolors.FAIL if result < min else bcolors.OKGREEN}{result} %{bcolors.ENDC}" result = round((total_investment.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): def get_liquid_assets_to_net_worth_ratio(balances, min):
liquid = 0 liquid = 0
for account, balance in balances.items(): for account, balance in balances.items():
if account.startswith("Assets:Liquid") or account.startswith("Assets:Invest"): if account.startswith("Assets:Liquid") or account.startswith("Assets:Invest"):
liquid = balance if liquid == 0 else liquid + balance 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 total_liquid = Amount(Decimal(0), "EUR") if liquid.get_only_position(
result = round((total_liquid.number / get_net_worth(balances).number) * 100, 2) ) == None else liquid.get_only_position().units
return f"{bcolors.FAIL if result < min else bcolors.OKGREEN}{result} %{bcolors.ENDC}" 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, gross_monthly_income, monthly_savings, min): def get_savings_ratio(balances, gross_monthly_income, monthly_savings, min):
result = round((monthly_savings.number / gross_monthly_income) * 100, 2) result = round((monthly_savings.number / gross_monthly_income) * 100, 2)
return f"{bcolors.FAIL if result < min else bcolors.OKGREEN}{result} %{bcolors.ENDC}" return f"{bcolors.FAIL if result < min else bcolors.OKGREEN}{result} %{bcolors.ENDC}"
def get_debt_service_ratio(balances, gross_monthly_income, debt_payments, max): def get_debt_service_ratio(balances, gross_monthly_income, debt_payments, max):
result = round((debt_payments.number / gross_monthly_income) * 100, 2) result = round((debt_payments.number / gross_monthly_income) * 100, 2)
return f"{bcolors.FAIL if result >= max else bcolors.OKGREEN}{result} %{bcolors.ENDC}" return f"{bcolors.FAIL if result >= max else bcolors.OKGREEN}{result} %{bcolors.ENDC}"
def get_non_mortgage_debt_service_ratio(balances, gross_monthly_income, mortgage_payments, max): def get_non_mortgage_debt_service_ratio(balances, gross_monthly_income, mortgage_payments, max):
result = round((mortgage_payments.number / gross_monthly_income) * 100, 2) result = round((mortgage_payments.number / gross_monthly_income) * 100, 2)
return f"{bcolors.FAIL if result >= max else bcolors.OKGREEN}{result} %{bcolors.ENDC}" return f"{bcolors.FAIL if result >= max else bcolors.OKGREEN}{result} %{bcolors.ENDC}"
def get_solvency_ratio(balances, min): def get_solvency_ratio(balances, min):
total_assets = Amount.from_string(get_sum_balances(balances, "Assets:")) total_assets = Amount.from_string(get_sum_balances(balances, "Assets:"))
result = round((get_net_worth(balances).number / total_assets.number) * 100, 2) result = round((get_net_worth(balances).number /
return f"{bcolors.FAIL if result < min else bcolors.OKGREEN}{result} %{bcolors.ENDC}" total_assets.number) * 100, 2)
return f"{bcolors.FAIL if result < min else bcolors.OKGREEN}{result} %{bcolors.ENDC}"
def get_interest_coverage_ratio(gross_monthly_income, expenses, debt_payments, mortgage_payments, min): def get_interest_coverage_ratio(gross_monthly_income, expenses, debt_payments, mortgage_payments, min):
living_expenses = expenses[0].position.get_only_position().units.number living_expenses = expenses[0].position.get_only_position().units.number
interest = debt_payments.number + mortgage_payments.number interest = debt_payments.number + mortgage_payments.number
interest = interest if interest > 0 else 1 interest = interest if interest > 0 else 1
result = round((gross_monthly_income - living_expenses) / interest, 2) result = round((gross_monthly_income - living_expenses) / interest, 2)
return f"{bcolors.FAIL if result < min else bcolors.OKGREEN}{result}{bcolors.ENDC}" return f"{bcolors.FAIL if result < min else bcolors.OKGREEN}{result}{bcolors.ENDC}"
def get_max_leveraged_investment(balances): def get_max_leveraged_investment(balances):
total_assets = Amount.from_string(get_sum_balances(balances, "Assets:")) total_assets = Amount.from_string(get_sum_balances(balances, "Assets:"))
return Amount(round(total_assets.number * Decimal(0.9), 2), total_assets.currency).to_string() return Amount(round(total_assets.number * Decimal(0.9), 2), total_assets.currency).to_string()
def get_position_as_str(inventory): def get_position_as_str(inventory):
position = inventory.get_only_position() position = inventory.get_only_position()
if position is None: if position is None:
return position return position
else: else:
return Amount(Decimal(round(position.units.number, 2)), position.units.currency).to_string() return Amount(Decimal(round(position.units.number, 2)), position.units.currency).to_string()
def print_report(date, balances, expenses, income, debt_payments, mortgage_payments, savings): def print_report(date, balances, expenses, income, debt_payments, mortgage_payments, savings):
print(f"{bcolors.BOLD}Balance Sheet (date={date}){bcolors.ENDC}") print(f"{bcolors.BOLD}Balance Sheet (date={date}){bcolors.ENDC}")
draw_line() draw_line()
print(f"{bcolors.BOLD}Assets{bcolors.ENDC}") print(f"{bcolors.BOLD}Assets{bcolors.ENDC}")
print(f"\t{bcolors.BOLD}Liquids{bcolors.ENDC}") print(f"\t{bcolors.BOLD}Liquids{bcolors.ENDC}")
print(tabulate([ print(tabulate([
["Corrent", get_sum_balances(balances, "Assets:Liquid:Caixabank:Corrent")], ["Corrent", get_sum_balances(
["Estalvi", get_sum_balances(balances, "Assets:Liquid:Estalvi")], balances, "Assets:Liquid:Caixabank:Corrent")],
["Compte d'inversió", get_sum_balances(balances, "Assets:Liquid:R4:EUR")], ["Estalvi", get_sum_balances(balances, "Assets:Liquid:Estalvi")],
["Total líquids", get_sum_balances(balances, "Assets:Liquid:")], ["Compte d'inversió", get_sum_balances(
])) balances, "Assets:Liquid:R4:EUR")],
print(f"\t{bcolors.BOLD}Inversions{bcolors.ENDC}") ["Total líquids", get_sum_balances(balances, "Assets:Liquid:")],
print(tabulate([ ]))
["Fons d'inversió", get_sum_balances(balances, "Assets:Invest:Fund:")], print(f"\t{bcolors.BOLD}Inversions{bcolors.ENDC}")
["ETFs", get_sum_balances(balances, "Assets:Invest:ETF:")], print(tabulate([
["Accions", get_sum_balances(balances, "Assets:Invest:Stock:")], ["Fons d'inversió", get_sum_balances(balances, "Assets:Invest:Fund:")],
["Renta fixa", get_sum_balances(balances, "Assets:Invest:Fixed:")], ["ETFs", get_sum_balances(balances, "Assets:Invest:ETF:")],
["Total inversions", get_sum_balances(balances, "Assets:Invest:")], ["Accions", get_sum_balances(balances, "Assets:Invest:Stock:")],
])) ["Renta fixa", get_sum_balances(balances, "Assets:Invest:Fixed:")],
print(f"\t{bcolors.BOLD}Propietat personal{bcolors.ENDC}") ["Total inversions", get_sum_balances(balances, "Assets:Invest:")],
print(tabulate([ ]))
["Vivenda principal", get_sum_balances(balances, "Assets:PersonalProperty:VivendaPrincipal")], print(f"\t{bcolors.BOLD}Propietat personal{bcolors.ENDC}")
["Cotxes", get_sum_balances(balances, "Assets:PersonalProperty:Cotxe")], print(tabulate([
["Joies, Art, Col·leccionables", get_sum_balances(balances, "Assets:PersonalProperty:JoiesArtCollecionables")], ["Vivenda principal", get_sum_balances(
["Metalls preciosos", get_sum_balances(balances, "Assets:PersonalProperty:MetallsPreciosos")], balances, "Assets:PersonalProperty:VivendaPrincipal")],
["Altres propietats", get_sum_balances(balances, "Assets:PersonalProperty:AltresPropietats")], ["Cotxes", get_sum_balances(
["Total propietats", get_sum_balances(balances, "Assets:PersonalProperty:")], balances, "Assets:PersonalProperty:Cotxe")],
])) ["Joies, Art, Col·leccionables", get_sum_balances(
print(f"\t{bcolors.BOLD}Deutes{bcolors.ENDC}") balances, "Assets:PersonalProperty:JoiesArtCollecionables")],
print(tabulate([ ["Metalls preciosos", get_sum_balances(
["Deutes per cobrar", get_position_as_str(balances["Assets:Debt:DeutesPerCobrar"])], balances, "Assets:PersonalProperty:MetallsPreciosos")],
["Total deutes", get_sum_balances(balances, "Assets:Debt:")], ["Altres propietats", get_sum_balances(
])) balances, "Assets:PersonalProperty:AltresPropietats")],
print(f"\t{bcolors.BOLD}Beneficis laborals{bcolors.ENDC}") ["Total propietats", get_sum_balances(
print(tabulate([ balances, "Assets:PersonalProperty:")],
["Tickets Restaurant", get_position_as_str(balances["Assets:Benefits:Edenred:TicketsRestaurant"])], ]))
["Targeta Transport", get_position_as_str(balances["Assets:Benefits:Edenred:TargetaTransport"])], print(f"\t{bcolors.BOLD}Deutes{bcolors.ENDC}")
["Pla Pensions Empleados Zurich", get_position_as_str(balances["Assets:Benefits:DZP:PPEZurich"]) if "Assets:Benefits:DZP:PPEZurich" in balances else "-"], print(tabulate([
["Total beneficis", get_sum_balances(balances, "Assets:Benefits:")], ["Deutes per cobrar", get_position_as_str(
])) balances["Assets:Debt:DeutesPerCobrar"])],
print(tabulate([ ["Total deutes", get_sum_balances(balances, "Assets:Debt:")],
[f"\t{bcolors.BOLD}Total Assets", get_sum_balances(balances, "Assets:")] ]))
])) 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"])],
["Pla Pensions Empleados Zurich", get_position_as_str(
balances["Assets:Benefits:DZP:PPEZurich"]) if "Assets:Benefits:DZP:PPEZurich" in balances else "-"],
["Total beneficis", get_sum_balances(balances, "Assets:Benefits:")],
]))
print(tabulate([
[f"\t{bcolors.BOLD}Total Assets",
get_sum_balances(balances, "Assets:")]
]))
draw_line() draw_line()
print(f"{bcolors.BOLD}Liabilites{bcolors.ENDC}") print(f"{bcolors.BOLD}Liabilites{bcolors.ENDC}")
print(tabulate([ print(tabulate([
["Hipoteques en vivenda principal", get_position_as_str(balances["Liabilities:Hipoteca:VivendaPrincipal"] * Decimal(-1))], ["Hipoteques en vivenda principal", get_position_as_str(
["Hipoteques en vivenda d'inversió", Amount(Decimal(0), "EUR").to_string()], balances["Liabilities:Hipoteca:VivendaPrincipal"] * Decimal(-1))],
["Crèdit", get_position_as_str(balances["Liabilities:Credit:Caixabank:TargetaCredit"] * Decimal(-1))], ["Hipoteques en vivenda d'inversió",
["Factures impagades", get_position_as_str(balances["Liabilities:Factures:FacturesPendents"] * Decimal(-1))], Amount(Decimal(0), "EUR").to_string()],
["Préstecs personals", Amount(Decimal(0), "EUR").to_string()], ["Crèdit", get_position_as_str(
["Impostos no pagats", get_position_as_str(balances["Liabilities:Taxes:IRPF"] * Decimal(-1))], balances["Liabilities:Credit:Caixabank:TargetaCredit"] * Decimal(-1))],
["Altres passius", Amount(Decimal(0), "EUR").to_string()] ["Factures impagades", get_position_as_str(
])) balances["Liabilities:Factures:FacturesPendents"] * Decimal(-1))],
print(tabulate([ ["Préstecs personals", Amount(Decimal(0), "EUR").to_string()],
[f"{bcolors.BOLD}Total passius{bcolors.ENDC}", f"{bcolors.BOLD}{get_sum_balances(balances, "Liabilities:")}{bcolors.ENDC}"], ["Impostos no pagats", get_position_as_str(
])) balances["Liabilities:Taxes:IRPF"] * Decimal(-1))],
["Altres passius", Amount(Decimal(0), "EUR").to_string()]
]))
print(tabulate([
[f"{bcolors.BOLD}Total passius{bcolors.ENDC}", f"{bcolors.BOLD}{
get_sum_balances(balances, "Liabilities:")}{bcolors.ENDC}"],
]))
draw_line() draw_line()
print(f"{bcolors.BOLD}Net Worth\t{get_net_worth(balances)}{bcolors.ENDC}") 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 %"],
["Emergency Fund", get_emergency_fund_ratio(
balances, expenses, 3, 6), "3-6"],
["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, income, savings, 20), "20 %"],
["Debt-Service Ratio",
get_debt_service_ratio(balances, income, debt_payments, 35), "35 %"],
["Non-Mortgage Debt-Service Ratio",
get_non_mortgage_debt_service_ratio(balances, income, mortgage_payments, 15), "15 %"],
["Interest Coverage Ratio", get_interest_coverage_ratio(
income, expenses, debt_payments, mortgage_payments, 1.5), "1.5"]
]))
draw_line()
print(f"{bcolors.BOLD}Financial Ratios{bcolors.ENDC}")
print(tabulate([
["Debt-to-Assets Ratio", get_debt_to_assets_ratio(balances, 50), "50 %"],
["Emergency Fund", get_emergency_fund_ratio(balances, expenses, 3, 6), "3-6"],
["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, income, savings, 20), "20 %"],
["Debt-Service Ratio", get_debt_service_ratio(balances, income, debt_payments, 35), "35 %"],
["Non-Mortgage Debt-Service Ratio", get_non_mortgage_debt_service_ratio(balances, income, mortgage_payments, 15), "15 %"],
["Interest Coverage Ratio", get_interest_coverage_ratio(income, expenses, debt_payments, mortgage_payments, 1.5), "1.5"]
]))
def get_balances(entries, options, date): def get_balances(entries, options, date):
balance_query = f"SELECT account, convert(sum(position), \"EUR\") as position FROM date <= {date} WHERE account ~ '^(Liabilities|Assets)'" balance_query = f"SELECT account, convert(sum(position), \"EUR\") as position FROM date <= {
rtypes, rrows = query.run_query( date} WHERE account ~ '^(Liabilities|Assets)'"
entries, options, balance_query) rtypes, rrows = query.run_query(
balances = {} entries, options, balance_query)
for row in rrows: balances = {}
balances[row.account] = row.position for row in rrows:
return balances balances[row.account] = row.position
return balances
def get_expenses(entries, options, date): def get_expenses(entries, options, date):
start_date, end_date = get_last_month_timestamps(date) start_date, end_date = get_last_month_timestamps(date)
expenses_query = f"SELECT convert(sum(position), \"EUR\") as position FROM date <= {end_date} WHERE account ~ 'Expenses:' AND date >= {start_date}" expenses_query = f"SELECT convert(sum(position), \"EUR\") as position FROM date <= {
rtypes, rrows = query.run_query( end_date} WHERE account ~ 'Expenses:' AND date >= {start_date}"
entries, options, expenses_query) rtypes, rrows = query.run_query(
return rrows entries, options, expenses_query)
return rrows
def get_income(entries, options, date): def get_income(entries, options, date):
start_date, end_date = get_last_month_timestamps(date) start_date, end_date = get_last_month_timestamps(date)
income_query = f"SELECT convert(sum(position), \"EUR\") as position FROM date <= {end_date} WHERE account ~ '^(Income:Work|Income:Savings|Income:Invest)' AND date >= {start_date}" income_query = f"SELECT convert(sum(position), \"EUR\") as position FROM date <= {
rtypes, rrows = query.run_query( end_date} WHERE account ~ '^(Income:Work|Income:Savings|Income:Invest)' AND date >= {start_date}"
entries, options, income_query) rtypes, rrows = query.run_query(
return rrows entries, options, income_query)
return rrows
def get_debt_payments(entries, options, date): def get_debt_payments(entries, options, date):
# FIX: Agafar nomes els pagaments de deute, enlloc de també les addicions de deute # FIX: Agafar nomes els pagaments de deute, enlloc de també les addicions de deute
start_date, end_date = get_last_month_timestamps(date) start_date, end_date = get_last_month_timestamps(date)
debt_payments_query = f"SELECT convert(sum(position), \"EUR\") as position FROM date <= {end_date} WHERE account ~ 'Expenses:R4:Interessos' AND date >= {start_date}" debt_payments_query = f"SELECT convert(sum(position), \"EUR\") as position FROM date <= {
mortgage_payments_query = f"SELECT convert(sum(position), \"EUR\") as position FROM date <= {end_date} WHERE account ~ 'Liabilities:Hipoteca:' AND date >= {start_date}" end_date} WHERE account ~ 'Expenses:R4:Interessos' AND date >= {start_date}"
rtypes, rrows_debt = query.run_query( mortgage_payments_query = f"SELECT convert(sum(position), \"EUR\") as position FROM date <= {
entries, options, debt_payments_query) end_date} WHERE account ~ 'Liabilities:Hipoteca:' AND date >= {start_date}"
rtypes, rrows_mortgage = query.run_query( rtypes, rrows_debt = query.run_query(
entries, options, mortgage_payments_query) entries, options, debt_payments_query)
debt_payments = rrows_debt[0].position.get_only_position().units if len(rrows_debt) > 0 else Amount(Decimal(0), "EUR") rtypes, rrows_mortgage = query.run_query(
mortgage_payments = rrows_mortgage[0].position.get_only_position().units if len(rrows_mortgage) > 0 else Amount(Decimal(0), "EUR") entries, options, mortgage_payments_query)
return debt_payments, mortgage_payments debt_payments = rrows_debt[0].position.get_only_position().units if len(
rrows_debt) > 0 else Amount(Decimal(0), "EUR")
mortgage_payments = rrows_mortgage[0].position.get_only_position(
).units if len(rrows_mortgage) > 0 else Amount(Decimal(0), "EUR")
return debt_payments, mortgage_payments
def get_savings(entries, options, date): def get_savings(entries, options, date):
start_date, end_date = get_last_month_timestamps(date) start_date, end_date = get_last_month_timestamps(date)
savings_query = f"SELECT convert(sum(position), \"EUR\") as position FROM date <= {end_date} WHERE account ~ '^Assets:Invest:' AND date >= {start_date}" savings_query = f"SELECT convert(sum(position), \"EUR\") as position FROM date <= {
rtypes, rrows = query.run_query( end_date} WHERE account ~ '^Assets:Invest:' AND date >= {start_date}"
entries, options, savings_query) rtypes, rrows = query.run_query(
result = rrows[0].position.get_only_position().units if len(rrows) > 0 else Amount(Decimal(0), "EUR") entries, options, savings_query)
return result result = rrows[0].position.get_only_position().units if len(
rrows) > 0 else Amount(Decimal(0), "EUR")
return result
def main(): def main():
parser = argparse.ArgumentParser(description='Generate balance sheet report') parser = argparse.ArgumentParser(
parser.add_argument('date', metavar='date', type=str, nargs=1, description='Generate balance sheet report')
help='Report date in ISO format (e.g. 1970-01-01)') 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() args = parser.parse_args()
date = args.date[0] date = args.date[0]
filename = "ledger/main.beancount" filename = "ledger/main.beancount"
entries, errors, options = loader.load_file(filename) entries, errors, options = loader.load_file(filename)
if errors: if errors:
printer.print_errors(errors) printer.print_errors(errors)
balances = get_balances(entries, options, date) balances = get_balances(entries, options, date)
expenses = get_expenses(entries, options, date) expenses = get_expenses(entries, options, date)
income = get_income(entries, options, date) income = get_income(entries, options, date)
gross_monthly_income = income[0].position.get_only_position().units.number * -1 gross_monthly_income = income[0].position.get_only_position(
debt_payments, mortgage_payments = get_debt_payments(entries, options, date) ).units.number * -1
savings = get_savings(entries, options, date) debt_payments, mortgage_payments = get_debt_payments(
print_report(date, balances, expenses, gross_monthly_income, debt_payments, mortgage_payments, savings) entries, options, date)
savings = get_savings(entries, options, date)
print_report(date, balances, expenses, gross_monthly_income,
debt_payments, mortgage_payments, savings)
main()
main()

View File

@@ -91,8 +91,50 @@
2025-08-14 * "Aldi" "Compra" 2025-08-14 * "Aldi" "Compra"
Expenses:Supermercat 13.50 EUR Expenses:Supermercat 13.50 EUR
Assets:Liquid:Caixabank:Corrent Assets:Liquid:Caixabank:Corrent
2025-08-17 * "Ocine Porto Pi" "Pel·licula 4 Fantasticos"
Expenses:Entreteniment 32.30 EUR
Assets:Liquid:Caixabank:Corrent
2025-08-17 * "Buga Barbacoa" "Sopar barbacoa coreana"
Expenses:MenjarFora 69.20 EUR
Assets:Liquid:Caixabank:Corrent
2025-08-19 * "Blät" "Esmorzar"
Expenses:MenjarFora 5.85 EUR
Assets:Benefits:Edenred:TicketsRestaurant
2025-08-19 * "Plusfresc" "Aigua i fruita"
Expenses:Supermercat 2.86 EUR
Assets:Liquid:Caixabank:Corrent
2025-08-19 * "Amazon" "Llibre Superintelligence, electrolits i xampú"
Expenses:Altres 70.71 EUR
Assets:Liquid:Caixabank:Corrent
2025-08-19 * "Daal Roti" "Dinar hindú"
Expenses:MenjarFora 12.95 EUR
Assets:Liquid:Caixabank:Corrent
2025-08-19 * "Blät" "Cafè"
Expenses:MenjarFora 1.55 EUR
Assets:Benefits:Edenred:TicketsRestaurant
2025-08-19 * "Ikea" "Caixes organització + làmpara"
Expenses:Llar 113.95 EUR
Assets:Liquid:Caixabank:Corrent
2025-08-19 * "Plusfresc" "Compra de la setmana"
Expenses:Supermercat 35.17 EUR
Assets:Liquid:Caixabank:Corrent
2025-08-22 * "Just Eat" "Burrito"
Expenses:MenjarFora 23.67 EUR
Assets:Liquid:Caixabank:Corrent
2025-08-23 * "BonPreu" "Aigues"
Expenses:Supermercat 2.18 EUR
Assets:Liquid:Caixabank:Corrent
2025-08-23 * "Esclat" "Croissants"
Expenses:Supermercat 2.97 EUR
Assets:Liquid:Caixabank:Corrent
2025-08-23 * "Genís" "Bizum torneig volley"
Expenses:Entreteniment 6 EUR
Assets:Liquid:Caixabank:Corrent
2025-08-23 * "David Tuneu" "Bizum pizzes"
Expenses:MenjarFora 29 EUR
Assets:Liquid:Caixabank:Corrent
2025-09-01 balance Assets:Liquid:Caixabank:Corrent 11920.90 EUR 2025-09-01 balance Assets:Liquid:Caixabank:Corrent 11519.94 EUR
2025-09-01 balance Assets:Liquid:R4:EUR 0 EUR 2025-09-01 balance Assets:Liquid:R4:EUR 0 EUR
2025-09-01 balance Assets:Invest:Fund:Vanguard:EMMK 14.99 VANEMMK 2025-09-01 balance Assets:Invest:Fund:Vanguard:EMMK 14.99 VANEMMK
2025-09-01 balance Assets:Invest:Fund:Vanguard:GL 755.40 VANGL 2025-09-01 balance Assets:Invest:Fund:Vanguard:GL 755.40 VANGL
@@ -100,7 +142,7 @@
2025-09-01 balance Assets:Invest:ETF:IWVL 404 IWVL 2025-09-01 balance Assets:Invest:ETF:IWVL 404 IWVL
2025-09-01 balance Assets:Invest:Fixed:R4RF 1518.57004 R4RF 2025-09-01 balance Assets:Invest:Fixed:R4RF 1518.57004 R4RF
2025-08-01 balance Assets:Invest:ETF:XDEQ 264 XDEQ 2025-08-01 balance Assets:Invest:ETF:XDEQ 264 XDEQ
2025-09-01 balance Assets:Benefits:Edenred:TicketsRestaurant 17.71 EUR 2025-09-01 balance Assets:Benefits:Edenred:TicketsRestaurant 10.31 EUR
2025-09-01 balance Assets:Benefits:Edenred:TargetaTransport 204.10 EUR 2025-09-01 balance Assets:Benefits:Edenred:TargetaTransport 204.10 EUR
2025-09-01 balance Assets:Benefits:DZP:PPEZurich 3454.12 EUR 2025-09-01 balance Assets:Benefits:DZP:PPEZurich 3454.12 EUR
2025-09-01 balance Assets:PersonalProperty:VivendaPrincipal 0 EUR 2025-09-01 balance Assets:PersonalProperty:VivendaPrincipal 0 EUR