24 agost 2025
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user