oroszgy's picture
feat: minor cosmetic changes
bf1c4f6 verified
raw
history blame
6.64 kB
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()