File size: 6,644 Bytes
bb2b2aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bf1c4f6
 
 
 
 
 
bb2b2aa
 
bf1c4f6
 
bb2b2aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bf1c4f6
bb2b2aa
bf1c4f6
bb2b2aa
bf1c4f6
 
 
 
 
bb2b2aa
 
 
 
 
 
 
 
 
 
 
 
 
 
bf1c4f6
 
bb2b2aa
 
bf1c4f6
 
bb2b2aa
 
bf1c4f6
 
 
 
 
 
 
 
 
 
bb2b2aa
 
 
 
 
 
 
bf1c4f6
 
 
 
 
 
 
 
 
 
bb2b2aa
 
 
 
 
bf1c4f6
 
bb2b2aa
 
 
 
 
 
 
 
bf1c4f6
bb2b2aa
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
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()