21 desembre 2025

This commit is contained in:
Roger Oriol
2025-12-21 12:21:08 +01:00
parent b581e7590f
commit c0ebdf099a
4 changed files with 160 additions and 291 deletions

View File

@@ -26,7 +26,7 @@ def draw_line():
print('─' * 30)
def get_last_month_timestamps(date):
def get_month_to_date_timestamps(date):
month = int(date.split("-")[1])
year = int(date.split("-")[0])
d = datetime(year, month, 1)
@@ -35,6 +35,14 @@ def get_last_month_timestamps(date):
return start_date, end_date.strftime("%Y-%m-%d")
def get_last_year_timestamps(date):
date_parts = date.split("-")
day = int(date_parts[2])
month = int(date_parts[1])
year = int(date_parts[0])
return f"{year-1}-{month:02d}-{day:02d}", date
def get_sum_balances(balances, account_prefix):
sum = 0
for account, balance in balances.items():
@@ -64,7 +72,8 @@ def get_debt_to_assets_ratio(balances, max):
def get_emergency_fund_ratio(balances, expenses, low, mid):
liquid = 0
living_expenses = expenses[0].position.get_only_position().units.number
living_expenses = expenses[0].position.get_only_position(
).units.number / 12
for account, balance in balances.items():
if account.startswith("Assets:Liquid"):
liquid = balance if liquid == 0 else liquid + balance
@@ -72,7 +81,7 @@ def get_emergency_fund_ratio(balances, expenses, low, mid):
) == None else liquid.get_only_position().units
result = round(total_liquid.number / living_expenses, 2)
color = bcolors.FAIL if result < low else bcolors.OKGREEN if result > mid else bcolors.WARNING
return f"{color}{result}{bcolors.ENDC}"
return f"{color}{result} mth{bcolors.ENDC}"
def get_investment_assets_to_net_worth_ratio(balances, min):
@@ -95,18 +104,19 @@ def get_liquid_assets_to_net_worth_ratio(balances, min):
return f"{bcolors.FAIL if result < min else bcolors.OKGREEN}{result} %{bcolors.ENDC}"
def get_savings_ratio(balances, gross_monthly_income, monthly_savings, min):
result = round((monthly_savings.number / gross_monthly_income) * 100, 2)
def get_savings_ratio(balances, net_yearly_income, yearly_savings, min):
result = round((yearly_savings.number /
net_yearly_income) * 100, 2)
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):
result = round((debt_payments.number / gross_monthly_income) * 100, 2)
def get_debt_service_ratio(balances, net_yearly_income, debt_payments, max):
result = round((debt_payments.number / net_yearly_income) * 100, 2)
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):
result = round((mortgage_payments.number / gross_monthly_income) * 100, 2)
def get_non_mortgage_debt_service_ratio(balances, net_yearly_income, mortgage_payments, max):
result = round((mortgage_payments.number / net_yearly_income) * 100, 2)
return f"{bcolors.FAIL if result >= max else bcolors.OKGREEN}{result} %{bcolors.ENDC}"
@@ -117,11 +127,11 @@ def get_solvency_ratio(balances, min):
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(net_monthly_income, expenses, debt_payments, mortgage_payments, min):
living_expenses = expenses[0].position.get_only_position().units.number
interest = debt_payments.number + mortgage_payments.number
interest = interest if interest > 0 else 1
result = round((gross_monthly_income - living_expenses) / interest, 2)
result = round((net_monthly_income - living_expenses) / interest, 2)
return f"{bcolors.FAIL if result < min else bcolors.OKGREEN}{result}{bcolors.ENDC}"
@@ -138,7 +148,7 @@ def get_position_as_str(inventory):
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, net_monthly_income, net_yearly_income, debt_payments, mortgage_payments, savings):
print(f"{bcolors.BOLD}Balance Sheet (date={date}){bcolors.ENDC}")
draw_line()
print(f"{bcolors.BOLD}Assets{bcolors.ENDC}")
@@ -202,8 +212,10 @@ def print_report(date, balances, expenses, income, debt_payments, mortgage_payme
balances["Liabilities:Hipoteca:VivendaPrincipal"] * Decimal(-1))],
["Hipoteques en vivenda d'inversió",
Amount(Decimal(0), "EUR").to_string()],
["Crèdit", get_position_as_str(
["Targetes de crèdit", get_position_as_str(
balances["Liabilities:Credit:Caixabank:TargetaCredit"] * Decimal(-1))],
["Línies de crèdit per inversió", get_position_as_str(
balances["Liabilities:Credit:Renta4:PolissaCredit"] * Decimal(-1))],
["Factures impagades", get_position_as_str(
balances["Liabilities:Factures:FacturesPendents"] * Decimal(-1))],
["Préstecs personals", Amount(Decimal(0), "EUR").to_string()],
@@ -225,19 +237,19 @@ def print_report(date, balances, expenses, income, debt_payments, mortgage_payme
["Debt-to-Assets Ratio",
get_debt_to_assets_ratio(balances, 50), "50 %"],
["Emergency Fund", get_emergency_fund_ratio(
balances, expenses, 3, 6), "3-6"],
balances, expenses, 3, 6), "3-6 mth"],
["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 %"],
balances, net_yearly_income, savings, 20), "20 %"],
["Debt-Service Ratio",
get_debt_service_ratio(balances, income, debt_payments, 35), "35 %"],
get_debt_service_ratio(balances, net_yearly_income, debt_payments, 35), "35 %"],
["Non-Mortgage Debt-Service Ratio",
get_non_mortgage_debt_service_ratio(balances, income, mortgage_payments, 15), "15 %"],
get_non_mortgage_debt_service_ratio(balances, net_yearly_income, mortgage_payments, 15), "15 %"],
["Interest Coverage Ratio", get_interest_coverage_ratio(
income, expenses, debt_payments, mortgage_payments, 1.5), "1.5"]
net_yearly_income, expenses, debt_payments, mortgage_payments, 1.5), "1.5"]
]))
@@ -253,7 +265,7 @@ def get_balances(entries, options, date):
def get_expenses(entries, options, date):
start_date, end_date = get_last_month_timestamps(date)
start_date, end_date = get_last_year_timestamps(date)
expenses_query = f"SELECT convert(sum(position), \"EUR\") as position FROM date <= {
end_date} WHERE account ~ 'Expenses:' AND date >= {start_date}"
rtypes, rrows = query.run_query(
@@ -262,17 +274,27 @@ def get_expenses(entries, options, date):
def get_income(entries, options, date):
start_date, end_date = get_last_month_timestamps(date)
start_date, end_date = get_month_to_date_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}"
rtypes, rrows = query.run_query(
entries, options, income_query)
return rrows
net_monthly_income = rrows[0].position.get_only_position(
).units.number * -1
start_date, end_date = get_last_year_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}"
rtypes, rrows = query.run_query(
entries, options, income_query)
net_yearly_income = rrows[0].position.get_only_position(
).units.number * -1
return net_monthly_income, net_yearly_income
def get_debt_payments(entries, options, date):
# 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_year_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}"
mortgage_payments_query = f"SELECT convert(sum(position), \"EUR\") as position FROM date <= {
@@ -289,13 +311,20 @@ def get_debt_payments(entries, options, date):
def get_savings(entries, options, date):
start_date, end_date = get_last_month_timestamps(date)
savings_query = f"SELECT convert(sum(position), \"EUR\") as position FROM date <= {
start_date, end_date = get_last_year_timestamps(date)
investments_query = f"SELECT convert(sum(position), \"EUR\") as position FROM date <= {
end_date} WHERE account ~ '^Assets:Invest:' AND date >= {start_date}"
rtypes, rrows = query.run_query(
entries, options, savings_query)
entries, options, investments_query)
result = rrows[0].position.get_only_position().units if len(
rrows) > 0 else Amount(Decimal(0), "EUR")
liabilities_query = f"SELECT convert(sum(position), \"EUR\") as position FROM date <= {
end_date} WHERE account ~ '^Liabilities:Credit:Renta4:' AND date >= {start_date}"
rtypes, rrows = query.run_query(
entries, options, liabilities_query)
liabilities = rrows[0].position.get_only_position().units if len(
rrows) > 0 else Amount(Decimal(0), "EUR")
result = add(result, liabilities)
return result
@@ -316,13 +345,12 @@ def main():
balances = get_balances(entries, options, date)
expenses = get_expenses(entries, options, date)
income = get_income(entries, options, date)
gross_monthly_income = income[0].position.get_only_position(
).units.number * -1
net_monthly_income, net_yearly_income = get_income(
entries, options, date)
debt_payments, mortgage_payments = get_debt_payments(
entries, options, date)
savings = get_savings(entries, options, date)
print_report(date, balances, expenses, gross_monthly_income,
print_report(date, balances, expenses, net_monthly_income, net_yearly_income,
debt_payments, mortgage_payments, savings)

View File

@@ -127,8 +127,37 @@
2025-12-14 * "La Marató" "Donació La Marató de TV3"
Expenses:Donatiu 20 EUR
Assets:Liquid:Caixabank:Corrent
2025-12-14 * "Miquel Albareda" "Factures novembre"
Income:Other:Caixabank:Transferencia -49.23 EUR
Assets:Liquid:Caixabank:Corrent 49.23 EUR
Expenses:FacturesUtilitats -49.23 EUR
Equity:FacturesUtilitatsMiquel 49.23 EUR
2025-12-15 * "Plusfresc" "Compra de la setmana"
Expenses:Supermercat 30.09 EUR
Assets:Liquid:Caixabank:Corrent
2025-12-18 * "Naturgy" "Factura gas"
Expenses:FacturesUtilitats 29.66 EUR
Assets:Liquid:Caixabank:Corrent
2025-12-18 * "Don Zangano" "Esmorzar"
Expenses:MenjarFora 5.10 EUR
Assets:Benefits:Edenred:TicketsRestaurant
2025-12-18 * "Aerobus" "Bitllet anada i tornada aeroport"
Expenses:Mobilitat 12.85 EUR
Assets:Benefits:Edenred:TargetaTransport
2025-12-18 * "Burger King" "Sopar aeroport"
Expenses:MenjarFora 12.15 EUR
Assets:Benefits:Edenred:TicketsRestaurant
2025-12-19 * "TradeRepublic" "Tancament compte TradeRepublic"
Income:Savings:TradeRepublic:RentabilitatEstalvis -0.01 EUR
Assets:Liquid:Caixabank:Corrent
2025-12-20 * "Amazon" "Valissa DGT"
Expenses:Altres 41.99 EUR
Assets:Liquid:Caixabank:Corrent
2025-12-20 * "Che Argentino" "Sopar asador argentino palma"
Expenses:MenjarFora 51.70 EUR
Assets:Benefits:Edenred:TicketsRestaurant
2026-01-01 balance Assets:Liquid:Caixabank:Corrent 11919.33 EUR
2026-01-01 balance Assets:Liquid:Caixabank:Corrent 11866.83 EUR
2026-01-01 balance Assets:Liquid:R4:EUR 0 EUR
2026-01-01 balance Assets:Invest:Fund:Vanguard:EMMK 14.99 VANEMMK
2026-01-01 balance Assets:Invest:Fund:Vanguard:GL 776.93 VANGL
@@ -136,8 +165,8 @@
2026-01-01 balance Assets:Invest:ETF:IWVL 430 IWVL
2026-01-01 balance Assets:Invest:Fixed:R4RF 1518.57004 R4RF
2026-01-01 balance Assets:Invest:ETF:XDEQ 264 XDEQ
2026-01-01 balance Assets:Benefits:Edenred:TicketsRestaurant 114.87 EUR
2026-01-01 balance Assets:Benefits:Edenred:TargetaTransport 76.50 EUR
2026-01-01 balance Assets:Benefits:Edenred:TicketsRestaurant 45.92 EUR
2026-01-01 balance Assets:Benefits:Edenred:TargetaTransport 63.65 EUR
2026-01-01 balance Assets:Benefits:DZP:PPEZurich 4048.45 EUR
2026-01-01 balance Assets:PersonalProperty:VivendaPrincipal 0 EUR
2026-01-01 balance Assets:PersonalProperty:Cotxe 8174 EUR

View File

@@ -0,0 +1,71 @@
Balance Sheet (date=2025-12-21)
──────────────────────────────
Assets
Liquids
----------------- ------------
Corrent 11866.83 EUR
Estalvi 0 EUR
Compte d'inversió 0 EUR
Total líquids 11866.83 EUR
----------------- ------------
Inversions
---------------- -------------
Fons d'inversió 70353.30 EUR
ETFs 39129.40 EUR
Accions 0 EUR
Renta fixa 23712.25 EUR
Total inversions 133194.95 EUR
---------------- -------------
Propietat personal
---------------------------- -----------
Vivenda principal 0 EUR
Cotxes 8174.00 EUR
Joies, Art, Col·leccionables 50.00 EUR
Metalls preciosos 0 EUR
Altres propietats 0 EUR
Total propietats 8224.00 EUR
---------------------------- -----------
Deutes
----------------- -----
Deutes per cobrar
Total deutes 0 EUR
----------------- -----
Beneficis laborals
----------------------------- -----------
Tickets Restaurant 45.92 EUR
Targeta Transport 63.65 EUR
Pla Pensions Empleados Zurich 4048.45 EUR
Total beneficis 4158.02 EUR
----------------------------- -----------
------------ -------------
Total Assets 157443.80 EUR
------------ -------------
──────────────────────────────
Liabilites
-------------------------------- ------------
Hipoteques en vivenda principal
Hipoteques en vivenda d'inversió 0 EUR
Targetes de crèdit 445.91 EUR
Línies de crèdit per inversió 19971.26 EUR
Factures impagades
Préstecs personals 0 EUR
Impostos no pagats
Altres passius 0 EUR
-------------------------------- ------------
------------- -------------
Total passius -20417.17 EUR
------------- -------------
──────────────────────────────
Net Worth 137026.63 EUR
──────────────────────────────
Financial Ratios
------------------------------------ -------- -------
Debt-to-Assets Ratio 12.97 % 50 %
Emergency Fund 4.18 mth 3-6 mth
Investment Assets to Net Worth Ratio 97.20 % 50 %
Liquid Assets to Net Worth Ratio 105.86 % 15 %
Savings Ratio 35.58 % 20 %
Debt-Service Ratio 0.96 % 35 %
Non-Mortgage Debt-Service Ratio 0.00 % 15 %
Interest Coverage Ratio 33.34 1.5
------------------------------------ -------- -------

View File

@@ -1,259 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<title></title>
</head>
<body>
<div class="halfleft">
<div id="assets">
<h3>Assets</h3>
<table class="fullwidth tree-table">
<thead>
<tr>
<th class="first">Account</th>
<th>EUR</th>
<th>Other</th>
</tr>
</thead>
<tr class="">
<td class="tree-node-name">Assets</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Benefits</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Benefits:Edenred</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Benefits:Edenred:TarjetaTransport</td>
<td class="num">40</td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Benefits:Edenred:TicketsRestaurant</td>
<td class="num">209</td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Debt</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Debt:DeutesPerCobrar</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Invest</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Invest:R4</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Invest:R4:Amundi</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Invest:Fund:Amundi:MSCIWRLD</td>
<td class="num">20436.50810</td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Invest:Fund:Amundi:SUSTINC</td>
<td class="num">709.77780</td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Invest:R4:BNP</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Invest:Fund:BNP:DISTECH</td>
<td class="num"></td>
<td class="num">788.20245 USD</td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Invest:R4:Fidelity</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Invest:Fund:Fidelity:GLTECH</td>
<td class="num">14143.9382</td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Invest:Stock:MSFT</td>
<td class="num"></td>
<td class="num">1367.2 USD</td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Invest:Stock:PLTR</td>
<td class="num"></td>
<td class="num">160.250 USD</td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Invest:R4:Vanguard</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Invest:Fund:Vanguard:EMMK</td>
<td class="num">2664.81727</td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Liquid</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Liquid:Caixabank</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Liquid:Caixabank:Corrent</td>
<td class="num">18903.80</td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Liquid:Estalvi:Caixabank</td>
<td class="num">12666.49</td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Liquid:R4</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:Liquid:R4:EUR</td>
<td class="num">44.04</td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:PersonalProperty</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:PersonalProperty:AltresPropietats</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:PersonalProperty:Cotxe</td>
<td class="num">10000</td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:PersonalProperty:JoiesArtCollecionables</td>
<td class="num">1250</td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:PersonalProperty:MetallsPreciosos</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Assets:PersonalProperty:VivendaPrincipal</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="totals">
<td class="tree-node-name"><span class="totals-label"></span></td>
<td class="num">81068.37137</td>
<td class="num">2315.65245 USD</td>
</tr>
</table>
</div>
</div>
<div class="halfright">
<div id="liabilities">
<h3>Liabilities</h3>
<table class="fullwidth tree-table">
<thead>
<tr>
<th class="first">Account</th>
<th>EUR</th>
<th>Other</th>
</tr>
</thead>
<tr class="">
<td class="tree-node-name">Liabilities</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="totals">
<td class="tree-node-name"><span class="totals-label"></span></td>
<td class="num"></td>
<td class="num"></td>
</tr>
</table>
</div>
<div class="spacer">
</div>
<div id="equity">
<h3>Equity</h3>
<table class="fullwidth tree-table">
<thead>
<tr>
<th class="first">Account</th>
<th>EUR</th>
<th>Other</th>
</tr>
</thead>
<tr class="">
<td class="tree-node-name">Equity</td>
<td class="num"></td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Equity:Opening-Balances</td>
<td class="num">-80819.37</td>
<td class="num"></td>
</tr>
<tr class="">
<td class="tree-node-name">Equity:Opening-Balances:USD</td>
<td class="num"></td>
<td class="num">-2315.652 USD</td>
</tr>
<tr class="totals">
<td class="tree-node-name"><span class="totals-label"></span></td>
<td class="num">-80819.37</td>
<td class="num">-2315.652 USD</td>
</tr>
</table>
</div>
</div>
</body>
</html>