Spaces:
Sleeping
Sleeping
| import pandas as pd | |
| import numpy as np | |
| import gradio as gr | |
| # ========== Helpers ========== | |
| try: | |
| from numpy_financial import irr | |
| except ImportError: | |
| def irr(cashflows): | |
| rate = 0.1 | |
| for _ in range(1000): | |
| npv = sum(cf / (1 + rate) ** i for i, cf in enumerate(cashflows)) | |
| d_npv = sum(-i * cf / (1 + rate) ** (i + 1) for i, cf in enumerate(cashflows)) | |
| rate -= npv / d_npv if d_npv else 0 | |
| return rate | |
| def format_huf(x): | |
| return f"{x:,.0f} HUF".replace(",", " ") | |
| def format_pct(x): | |
| return f"{x*100:.2f}%" | |
| # ========== Core Model ========== | |
| def build_property_projection(purchase_price, equity, loan_years, annual_interest_rate, | |
| monthly_payment, monthly_rent, vacancy_months, | |
| annual_renovation, inflation_rate): | |
| loan_balance = purchase_price - equity | |
| monthly_interest = annual_interest_rate / 12 | |
| schedule = [] | |
| for year in range(1, loan_years + 1): | |
| interest_paid_year = 0 | |
| principal_paid_year = 0 | |
| for _ in range(12): | |
| interest = loan_balance * monthly_interest | |
| principal = monthly_payment - interest | |
| if principal > loan_balance: | |
| principal = loan_balance | |
| loan_balance -= principal | |
| interest_paid_year += interest | |
| principal_paid_year += principal | |
| # Rent with inflation | |
| effective_rent_months = 12 - vacancy_months | |
| rent_income_year = monthly_rent * ((1 + inflation_rate) ** (year - 1)) * effective_rent_months | |
| net_cash_flow = rent_income_year - (monthly_payment * 12) - annual_renovation | |
| property_value = purchase_price * ((1 + inflation_rate) ** year) | |
| net_worth = property_value - loan_balance | |
| schedule.append({ | |
| "Year": year, | |
| "InterestPaid": round(interest_paid_year), | |
| "PrincipalPaid": round(principal_paid_year), | |
| "LoanBalance": round(loan_balance), | |
| "RentIncome": round(rent_income_year), | |
| "NetCashFlow": round(net_cash_flow), | |
| "PropertyValue": round(property_value), | |
| "NetWorth": round(net_worth) | |
| }) | |
| return pd.DataFrame(schedule) | |
| def compute_property_irr(df, equity, sell_property_at_end=True): | |
| cashflows = [-equity] | |
| for i, row in df.iterrows(): | |
| cf = row["NetCashFlow"] | |
| if i == len(df) - 1 and sell_property_at_end: | |
| cf += row["PropertyValue"] - row["LoanBalance"] | |
| cashflows.append(cf) | |
| return irr(cashflows) | |
| def build_etf_projection(principal, years, annual_return): | |
| value = principal | |
| projection = [] | |
| for year in range(1, years + 1): | |
| value *= (1 + annual_return) | |
| projection.append({"Year": year, "ETF_Value": round(value)}) | |
| return pd.DataFrame(projection) | |
| def compute_etf_irr(principal, years, annual_return): | |
| final_value = principal * ((1 + annual_return) ** years) | |
| cashflows = [-principal] + [0] * (years - 1) + [round(final_value)] | |
| return irr(cashflows) | |
| # ========== Gradio App ========== | |
| def simulate( | |
| purchase_price, equity, loan_years, annual_interest_rate_pct, | |
| monthly_payment, monthly_rent, vacancy_months, annual_renovation, | |
| inflation_rate_pct, etf_annual_return_pct | |
| ): | |
| # Convert % to decimals | |
| annual_interest_rate = annual_interest_rate_pct / 100 | |
| inflation_rate = inflation_rate_pct / 100 | |
| etf_annual_return = etf_annual_return_pct / 100 | |
| # Property | |
| df_property = build_property_projection(purchase_price, equity, loan_years, | |
| annual_interest_rate, monthly_payment, | |
| monthly_rent, vacancy_months, | |
| annual_renovation, inflation_rate) | |
| prop_irr = compute_property_irr(df_property, equity) | |
| # ETF | |
| df_etf = build_etf_projection(equity, loan_years, etf_annual_return) | |
| etf_irr = compute_etf_irr(equity, loan_years, etf_annual_return) | |
| # Results summary | |
| summary = f""" | |
| π **Results after {loan_years} years**: | |
| - Property IRR: {format_pct(prop_irr)} | |
| - ETF IRR: {format_pct(etf_irr)} | |
| π° Final Values: | |
| - Property Net Worth: {format_huf(df_property.iloc[-1]['NetWorth'])} | |
| - ETF Value: {format_huf(df_etf.iloc[-1]['ETF_Value'])} | |
| """ | |
| # Format tables for readability | |
| df_property_fmt = df_property.copy() | |
| for col in ["InterestPaid", "PrincipalPaid", "LoanBalance", | |
| "RentIncome", "NetCashFlow", "PropertyValue", "NetWorth"]: | |
| df_property_fmt[col] = df_property_fmt[col].apply(lambda x: format_huf(x)) | |
| df_etf_fmt = df_etf.copy() | |
| df_etf_fmt["ETF_Value"] = df_etf_fmt["ETF_Value"].apply(lambda x: format_huf(x)) | |
| return summary, df_property_fmt, df_etf_fmt | |
| with gr.Blocks(title="Investment Simulator") as demo: | |
| gr.Markdown("# π vs π Investment Simulator") | |
| gr.Markdown("Compare buying a property with renting vs investing in ETFs.") | |
| with gr.Row(): | |
| with gr.Column(): | |
| purchase_price = gr.Number(value=60_000_000, label="Purchase Price (HUF)", precision=0) | |
| equity = gr.Number(value=20_000_000, label="Equity (HUF)", precision=0) | |
| loan_years = gr.Number(value=20, label="Loan Term (years)", precision=0) | |
| annual_interest_rate = gr.Number(value=3.0, label="Loan Interest (THM %)", precision=2) | |
| monthly_payment = gr.Number(value=220_000, label="Monthly Loan Payment (HUF)", precision=0) | |
| monthly_rent = gr.Number(value=170_000, label="Monthly Rent (HUF)", precision=0) | |
| vacancy_months = gr.Number(value=1, label="Vacancy (months/year)", precision=0) | |
| annual_renovation = gr.Number(value=100_000, label="Annual Renovation (HUF)", precision=0) | |
| inflation_rate = gr.Number(value=4.5, label="Inflation Rate (%)", precision=2) | |
| etf_annual_return = gr.Number(value=11.0, label="ETF Annual Return (%)", precision=2) | |
| run_button = gr.Button("Run Simulation") | |
| with gr.Column(): | |
| results = gr.Markdown() | |
| df_property_out = gr.Dataframe(interactive=False, wrap=True) | |
| df_etf_out = gr.Dataframe(interactive=False, wrap=True) | |
| run_button.click( | |
| simulate, | |
| inputs=[ | |
| purchase_price, equity, loan_years, annual_interest_rate, | |
| monthly_payment, monthly_rent, vacancy_months, annual_renovation, | |
| inflation_rate, etf_annual_return | |
| ], | |
| outputs=[results, df_property_out, df_etf_out] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |