Taking into account the precious considerations exposed in YT: Rent vs Buy by Ben Felix, I execute the following computations to determine the cases in which buying a house is going to be an advantage.
import matplotlib.pyplot as plt
import numpy as np
# =============================================================================
# CONFIGURATION CONSTANTS
# =============================================================================
# ... (Your existing configuration constants) ...
# --- PROPERTY DETAILS ---
PRICE_PROPERTY = 15000 # Purchase price of the house (EUR)
# Restructuring costs (materials only)
RESTRUCTURING_COSTS = [
#("bathroom_wc_suite", 350),
#("bathroom_shower_enclosure_tray_taps", 500),
#("bathroom_sink_vanity_tap", 250),
#("bathroom_boiler_electric_80l", 350), # Added boiler
#("kitchen_induction_hob", 500),
#("kitchen_sink_tap", 200),
#("washing_machine", 300),
#("refrigerator_freezer", 300),
#("microwave_oven", 150),
#("windows_replacement_pvc_double_glazing_materials", 2000), # Assuming a few windows
#("internal_doors_replacement_materials", 500), # Assuming a few doors
#("wall_plastering_materials", 300),
#("wall_painting_materials_interior_white", 250),
#("wardrobe_basic_freestanding", 400), # Added wardrobe
#("electricity_system_upgrade_materials_more_sockets_power", 400), # Added for more watts
("unforeseen", 0) # Added unforeseen costs
]
# --- FINANCIAL PARAMETERS ---
MARKET_TAEG = 0.037 # Annual percentage rate for general investment loans
# 0.037 IBKR: https://www.interactivebrokers.ie/it/trading/margin-rates.php
FIRST_HOME_TAEG = 0.025 # Preferential TAEG for first home buyers (under 36)
# --- RENTAL ASSUMPTIONS ---
USER_STUDY_RENT_MONTHS = 12 # Duration user lives in the house (S1) or rents elsewhere (S2) initially
USER_STUDY_MONTHLY_RENT_EXTERNAL = 400 # Rent user pays if not in the new house (S2), or saves (S1)
# --- INVESTMENT ASSUMPTIONS ---
ANNUAL_GROSS_INVESTMENT_RETURN = 0.09
CAPITAL_GAINS_TAX_RATE = 0.26
# --- LOAN PRODUCT DETAILS ---
HOUSE_LOAN_MAX_CAPACITY = 4000 # Max loan amount for house purchase (EUR)
HOUSE_LOAN_DURATION_MONTHS = 60
INVESTMENT_LOAN_MAX_CAPACITY = 25000 # Max loan for pure investment (EUR)
INVESTMENT_LOAN_DURATION_MONTHS = 60
# --- PURCHASED HOUSE OPERATIONALS (SCENARIO 1) ---
RENTAL_INCOME_MONTHLY_HOUSE_RECEIVED = 150 # If user rents out the purchased house (after user study period)
RENTAL_INCOME_TAX_RATE = 0.21 # Tax on rental income
ANNUAL_NOMINAL_HOUSE_APPRECIATION_RATE = 0.010 # Modest annual appreciation (1%)
# --- PURCHASED HOUSE FIXED COSTS (SCENARIO 1) ---
IMU_ANNUAL_TAX = 300 # Annual property tax
UTILITIES_MONTHLY_OWNER_OCCUPIED_OR_RENTED = 100 # Monthly utilities for the owned house
# --- VALUATION & STARTING NET LIQUIDATION VALUE ---
RENOVATION_VALUE_ADD_FIXED = 0
ASSUME_CGT_ON_HOUSE_APPRECIATION_FOR_NLV = True
STARTING_NET_LIQUIDATION_VALUE = 38000
# =============================================================================
# HELPER FUNCTIONS
# =============================================================================
# ... (Your existing helper functions: calc_total_restruct, fv, pmnt, balance) ...
def calc_total_restruct(costs):
return sum(cost for _, cost in costs)
def fv(principal, annual_rate, total_months):
if total_months <= 0: return principal
if principal == 0: return 0
monthly_rate = annual_rate / 12
return principal * (1 + monthly_rate) ** total_months
def pmnt(principal, annual_rate, total_months):
if total_months <= 0 or principal == 0: return 0
if annual_rate == 0: return principal / total_months if total_months > 0 else 0
monthly_rate = annual_rate / 12
if monthly_rate == 0: return principal / total_months if total_months > 0 else 0
if abs(monthly_rate) < 1e-9:
return principal / total_months if total_months > 0 else 0
try:
payment = principal * (monthly_rate * (1 + monthly_rate)**total_months) / ((1 + monthly_rate)**total_months - 1)
except OverflowError:
payment = principal * monthly_rate
# print(f"Warning: Overflow encountered in pmnt. Principal: {principal}, Rate: {annual_rate}, Months: {total_months}. Approximating.")
return payment
def balance(principal, annual_rate, total_months, months_passed):
if months_passed <= 0: return principal
if principal == 0: return 0
if months_passed >= total_months : return 0
payment_val = pmnt(principal, annual_rate, total_months)
if annual_rate == 0 or abs(annual_rate / 12) < 1e-9 :
return max(0, principal - payment_val * months_passed)
monthly_rate = annual_rate / 12
current_balance = principal * (1 + monthly_rate)**months_passed - \
payment_val * \
(((1 + monthly_rate)**months_passed - 1) / monthly_rate)
return max(0, current_balance)
# =============================================================================
# INITIAL SETUP
# =============================================================================
TOTAL_RESTRUCT = calc_total_restruct(RESTRUCTURING_COSTS)
TOTAL_HOUSE_COST_INITIAL = PRICE_PROPERTY + TOTAL_RESTRUCT # This is the initial CASH OUTLAY for house + readiness
HOUSE_VALUE_AFTER_RENOVATION = TOTAL_HOUSE_COST_INITIAL + RENOVATION_VALUE_ADD_FIXED # This is the MARKET VALUE after initial costs
ACTUAL_HOUSE_LOAN_TAKEN_SCENARIO_1 = HOUSE_LOAN_MAX_CAPACITY
# Calculate how much of the initial house cost is NOT covered by the loan
cash_from_initial_needed_for_house_s1 = max(0, TOTAL_HOUSE_COST_INITIAL - ACTUAL_HOUSE_LOAN_TAKEN_SCENARIO_1)
# Calculate the loan surplus (if loan > cost) - this is invested
SURPLUS_INVESTED_SCENARIO_1 = max(0, ACTUAL_HOUSE_LOAN_TAKEN_SCENARIO_1 - TOTAL_HOUSE_COST_INITIAL)
INVESTMENT_PRINCIPAL_SCENARIO_2 = INVESTMENT_LOAN_MAX_CAPACITY
# =============================================================================
# CAPITAL EVOLUTION (NET LIQUIDATION VALUE)
# =============================================================================
COMPARISON_DURATION_MONTHS = max(HOUSE_LOAN_DURATION_MONTHS, INVESTMENT_LOAN_DURATION_MONTHS)
months_array = np.arange(COMPARISON_DURATION_MONTHS + 1)
nlv_s1 = np.zeros(len(months_array))
nlv_s2 = np.zeros(len(months_array))
nlv_initial_external_growth = np.zeros(len(months_array))
# --- NLV at t=0 ---
# Scenario 1: NLV at t=0 is sum of assets (house value, invested surplus, remaining initial capital) minus liabilities (house loan)
# Remaining initial capital = STARTING_NET_LIQUIDATION_VALUE - amount used for house cost
remaining_initial_capital_s1_at_t0 = STARTING_NET_LIQUIDATION_VALUE - cash_from_initial_needed_for_house_s1
nlv_s1[0] = (
HOUSE_VALUE_AFTER_RENOVATION + # Asset: Market value of the house
SURPLUS_INVESTED_SCENARIO_1 + # Asset: Loan surplus that is invested
remaining_initial_capital_s1_at_t0 - # Asset: Portion of initial capital remaining after covering house cost
ACTUAL_HOUSE_LOAN_TAKEN_SCENARIO_1 # Liability: House loan principal
)
# Scenario 2: Initial NLV includes starting capital, less initial rent, plus loan-funded investment minus loan liability
initial_rent_paid_s2_at_t0 = USER_STUDY_MONTHLY_RENT_EXTERNAL if USER_STUDY_RENT_MONTHS > 0 else 0
# Initial capital remaining after t=0 rent
remaining_initial_capital_s2_at_t0 = STARTING_NET_LIQUIDATION_VALUE - initial_rent_paid_s2_at_t0
nlv_s2[0] = (
remaining_initial_capital_s2_at_t0 + # Asset: Portion of initial capital remaining after paying t=0 rent
INVESTMENT_PRINCIPAL_SCENARIO_2 - # Asset: Loan principal invested (at t=0, before growth)
INVESTMENT_PRINCIPAL_SCENARIO_2 # Liability: Investment loan principal
)
# Benchmark: NLV at t=0 is the starting capital, less any t=0 rent
nlv_initial_external_growth[0] = STARTING_NET_LIQUIDATION_VALUE - initial_rent_paid_s2_at_t0 # Assuming t=0 rent applies to benchmark too
# --- Initialize running values for the loop ---
# Scenario 1 running values
# The initial running capital is the amount that WASN'T used for the house upfront cost
running_s1_initial_capital_gross_value = remaining_initial_capital_s1_at_t0
contributions_to_s1_initial_capital = max(0, remaining_initial_capital_s1_at_t0) # Basis is the cash amount contributed/remaining initially
running_invested_surplus_s1_value = SURPLUS_INVESTED_SCENARIO_1
contributions_to_surplus_s1 = max(0, SURPLUS_INVESTED_SCENARIO_1) # Basis is the loan surplus amount invested
running_reinvested_op_cash_s1_value = 0.0
contributions_to_reinvested_op_cash_s1 = 0.0
# Scenario 2 running values
# The running initial capital starts as the initial capital less rent paid at t=0
running_s2_initial_capital_gross_value = remaining_initial_capital_s2_at_t0
contributions_to_s2_initial_capital = max(0, remaining_initial_capital_s2_at_t0) # Basis is the initial capital less t=0 rent
# The loan principal is fully invested from the start
running_investment_value_s2 = INVESTMENT_PRINCIPAL_SCENARIO_2
contributions_to_investment_s2 = max(0, INVESTMENT_PRINCIPAL_SCENARIO_2) # Basis is the loan amount invested
# Benchmark running value
# Benchmark starts with initial capital less any t=0 rent, consistent with nlv_initial_external_growth[0]
running_benchmark_capital_gross_value = STARTING_NET_LIQUIDATION_VALUE - initial_rent_paid_s2_at_t0
# --- Other loop parameters ---
# cumulative_rent_paid_s2_for_printout already includes the t=0 rent from Scenario 2 NLV calculation
cumulative_rent_paid_s2_for_printout = initial_rent_paid_s2_at_t0
monthly_imu_cost = IMU_ANNUAL_TAX / 12
monthly_investment_rate = ANNUAL_GROSS_INVESTMENT_RETURN / 12
monthly_house_payment_s1 = pmnt(ACTUAL_HOUSE_LOAN_TAKEN_SCENARIO_1, FIRST_HOME_TAEG, HOUSE_LOAN_DURATION_MONTHS)
monthly_investment_loan_payment_s2 = pmnt(INVESTMENT_PRINCIPAL_SCENARIO_2, MARKET_TAEG, INVESTMENT_LOAN_DURATION_MONTHS)
# --- Failure Tracking Initialization ---
s1_failed = False
s1_fail_idx = -1
s1_fail_value = np.nan
s2_failed = False
s2_fail_idx = -1
s2_fail_value = np.nan
# Define failure thresholds (NLV < -0.5 * Loan Principal). Only if loan > 0.
s1_failure_threshold = -0.5 * ACTUAL_HOUSE_LOAN_TAKEN_SCENARIO_1 if ACTUAL_HOUSE_LOAN_TAKEN_SCENARIO_1 > 0 else -np.inf
s2_failure_threshold = -0.5 * INVESTMENT_PRINCIPAL_SCENARIO_2 if INVESTMENT_PRINCIPAL_SCENARIO_2 > 0 else -np.inf
# --- Temp storage for end-of-period values for printing ---
# These will store the components contributing to the final NLV
# Initialize these with the t=0 values calculated above
# For S1
final_net_liquidated_s1_initial_capital_loop = remaining_initial_capital_s1_at_t0 # Will be updated monthly
final_net_liquidated_house_value_s1_loop = HOUSE_VALUE_AFTER_RENOVATION # Will be updated monthly
final_net_liquidated_invested_surplus_s1_loop = SURPLUS_INVESTED_SCENARIO_1 # Will be updated monthly
final_net_liquidated_reinvested_op_cash_s1_loop = 0.0 # Will be updated monthly
final_house_loan_liability_s1_loop = ACTUAL_HOUSE_LOAN_TAKEN_SCENARIO_1 # Will be updated monthly
# For S2
final_net_liquidated_s2_initial_capital_loop = remaining_initial_capital_s2_at_t0 # Will be updated monthly
final_net_liquidated_investment_s2_loop = INVESTMENT_PRINCIPAL_SCENARIO_2 # This is loan-funded investment, will be updated monthly
final_investment_loan_liability_s2_loop = INVESTMENT_PRINCIPAL_SCENARIO_2 # Will be updated monthly
for m_idx, m_val in enumerate(months_array):
if m_val == 0:
# NLV[0] is already set. Check initial failure.
if not s1_failed and nlv_s1[0] < s1_failure_threshold:
s1_failed = True; s1_fail_idx = 0; s1_fail_value = nlv_s1[0]
if not s2_failed and nlv_s2[0] < s2_failure_threshold:
s2_failed = True; s2_fail_idx = 0; s2_fail_value = nlv_s2[0]
# Update temp storage for t=0 state (before any gains/CGT on initial capital, but after initial transactions)
# These running_ values and their net liquidated counterparts are the same at t=0 before any growth or CGT calculation is applied *in the loop*.
final_net_liquidated_s1_initial_capital_loop = running_s1_initial_capital_gross_value # This is initial capital net of cost shortfall
final_net_liquidated_house_value_s1_loop = HOUSE_VALUE_AFTER_RENOVATION # House value at t=0
final_net_liquidated_invested_surplus_s1_loop = running_invested_surplus_s1_value # Surplus value at t=0
final_net_liquidated_reinvested_op_cash_s1_loop = running_reinvested_op_cash_s1_value # Starts at 0
final_house_loan_liability_s1_loop = balance(ACTUAL_HOUSE_LOAN_TAKEN_SCENARIO_1, FIRST_HOME_TAEG, HOUSE_LOAN_DURATION_MONTHS, m_val) # Liability at t=0
final_net_liquidated_s2_initial_capital_loop = running_s2_initial_capital_gross_value # Initial capital net of t=0 rent
final_net_liquidated_investment_s2_loop = running_investment_value_s2 # Loan investment at t=0
final_investment_loan_liability_s2_loop = balance(INVESTMENT_PRINCIPAL_SCENARIO_2, MARKET_TAEG, INVESTMENT_LOAN_DURATION_MONTHS, m_val) # Liability at t=0
continue # Skip to the next month (m_val = 1) for monthly calculations
# ... rest of your loop logic for m_val > 0 ...
# The existing logic within the loop for growing and drawing from the running_ capital variables remains correct
# as they now start with the correct post-transaction initial balances and basis.
# --- SCENARIO 1: Buy & Invest Surplus ---
# ... (rest of Scenario 1 logic, remains mostly the same as last modification) ... #
if s1_failed:
nlv_s1[m_idx] = np.nan
else:
# 1.0 Grow S1 Initial Capital (amount remaining after house cost covered)
if running_s1_initial_capital_gross_value > 0:
running_s1_initial_capital_gross_value *= (1 + monthly_investment_rate)
# 1.1 House Value
current_house_value = fv(HOUSE_VALUE_AFTER_RENOVATION, ANNUAL_NOMINAL_HOUSE_APPRECIATION_RATE, m_val)
house_gain = current_house_value - HOUSE_VALUE_AFTER_RENOVATION
house_appreciation_cgt = max(0, house_gain) * CAPITAL_GAINS_TAX_RATE if ASSUME_CGT_ON_HOUSE_APPRECIATION_FOR_NLV else 0
net_liquidated_house_value_s1 = current_house_value - house_appreciation_cgt
# 1.2 Grow Other Invested Assets for S1 (Surplus from loan, Reinvested operational cash)
if running_invested_surplus_s1_value > 0:
running_invested_surplus_s1_value *= (1 + monthly_investment_rate)
if running_reinvested_op_cash_s1_value > 0: # This pool grows before drawdowns for costs in the current month
running_reinvested_op_cash_s1_value *= (1 + monthly_investment_rate)
# 1.3 S1 Cashflows & Costs
# Calculate net operational cash available for the month BEFORE any loan payments
rent_cashflow_s1 = 0
if m_val <= USER_STUDY_RENT_MONTHS:
rent_cashflow_s1 = USER_STUDY_MONTHLY_RENT_EXTERNAL
else:
rent_cashflow_s1 = RENTAL_INCOME_MONTHLY_HOUSE_RECEIVED * (1 - RENTAL_INCOME_TAX_RATE)
monthly_fixed_operational_costs_s1 = monthly_imu_cost + UTILITIES_MONTHLY_OWNER_OCCUPIED_OR_RENTED
# net_operational_cash_s1: cash from operations after operational costs, but before house loan payment
net_operational_cash_s1 = rent_cashflow_s1 - monthly_fixed_operational_costs_s1
# --- MODIFIED LOAN PAYMENT AND OPERATIONAL DEFICIT/SURPLUS HANDLING FOR SCENARIO 1 ---
# A. Prioritize House Loan Payment
payment_due_for_loan_s1 = monthly_house_payment_s1
# A.1. Try to pay from (positive) net operational cash first
if net_operational_cash_s1 > 0:
draw_from_op_cash_for_loan = min(payment_due_for_loan_s1, net_operational_cash_s1)
net_operational_cash_s1 -= draw_from_op_cash_for_loan # Reduce op_cash by amount used for loan
payment_due_for_loan_s1 -= draw_from_op_cash_for_loan
# A.2. If loan payment still due, draw from invested capital pools (these pools have already grown for the month)
if payment_due_for_loan_s1 > 0:
# From reinvested operational cash (which has already grown this month)
draw_from_reinvested_op = min(payment_due_for_loan_s1, running_reinvested_op_cash_s1_value)
running_reinvested_op_cash_s1_value -= draw_from_reinvested_op
payment_due_for_loan_s1 -= draw_from_reinvested_op
if payment_due_for_loan_s1 > 0:
# From invested surplus (already grown)
draw_from_surplus = min(payment_due_for_loan_s1, running_invested_surplus_s1_value)
running_invested_surplus_s1_value -= draw_from_surplus
payment_due_for_loan_s1 -= draw_from_surplus
if payment_due_for_loan_s1 > 0:
# From initial capital (already grown)
draw_from_initial = min(payment_due_for_loan_s1, running_s1_initial_capital_gross_value)
running_s1_initial_capital_gross_value -= draw_from_initial
# payment_due_for_loan_s1 -= draw_from_initial # Tracking remaining loan due not strictly needed after this point for S1
# as loan balance func assumes payment made. NLV shows asset depletion.
# B. Handle Remaining Operational Cash (Surplus or Deficit)
# At this point, net_operational_cash_s1 is what remains *after* its positive part (if any) contributed to the loan.
# If it was negative initially (operational deficit), it's still negative.
if net_operational_cash_s1 > 0: # Operational surplus remains AFTER loan contribution
operational_surplus_to_reinvest_s1 = net_operational_cash_s1
running_reinvested_op_cash_s1_value += operational_surplus_to_reinvest_s1
contributions_to_reinvested_op_cash_s1 += operational_surplus_to_reinvest_s1 # Basis increases by new cash added
elif net_operational_cash_s1 < 0: # Operational deficit exists (or was created if rent was positive but less than fixed costs)
operational_deficit_to_cover_s1 = abs(net_operational_cash_s1)
# Cover this deficit from the (potentially already reduced by loan payment) invested capital pools
draw_from_reinvested_op_for_deficit = min(operational_deficit_to_cover_s1, running_reinvested_op_cash_s1_value)
running_reinvested_op_cash_s1_value -= draw_from_reinvested_op_for_deficit
operational_deficit_to_cover_s1 -= draw_from_reinvested_op_for_deficit
if operational_deficit_to_cover_s1 > 0:
draw_from_surplus_for_deficit = min(operational_deficit_to_cover_s1, running_invested_surplus_s1_value)
running_invested_surplus_s1_value -= draw_from_surplus_for_deficit
operational_deficit_to_cover_s1 -= draw_from_surplus_for_deficit
if operational_deficit_to_cover_s1 > 0:
draw_from_initial_for_deficit = min(operational_deficit_to_cover_s1, running_s1_initial_capital_gross_value)
running_s1_initial_capital_gross_value -= draw_from_initial_for_deficit
# operational_deficit_to_cover_s1 -= draw_from_initial_for_deficit
# --- END OF MODIFIED HANDLING ---
# Ensure all running capital values are non-negative after all drawdowns
running_s1_initial_capital_gross_value = max(0, running_s1_initial_capital_gross_value)
running_invested_surplus_s1_value = max(0, running_invested_surplus_s1_value)
running_reinvested_op_cash_s1_value = max(0, running_reinvested_op_cash_s1_value)
# 1.4 Net Liquidated Value of S1 Investments (CGT calculations on remaining balances)
# S1 Initial Capital
s1_initial_capital_gain = max(0, running_s1_initial_capital_gross_value - contributions_to_s1_initial_capital)
s1_initial_capital_cgt = s1_initial_capital_gain * CAPITAL_GAINS_TAX_RATE
net_liquidated_s1_initial_capital = running_s1_initial_capital_gross_value - s1_initial_capital_cgt
# Invested Surplus
invested_surplus_gain_s1 = max(0, running_invested_surplus_s1_value - contributions_to_surplus_s1)
invested_surplus_cgt_s1 = invested_surplus_gain_s1 * CAPITAL_GAINS_TAX_RATE
net_liquidated_invested_surplus_s1 = running_invested_surplus_s1_value - invested_surplus_cgt_s1
# Reinvested Operational Cash
reinvested_op_cash_gain_s1 = max(0, running_reinvested_op_cash_s1_value - contributions_to_reinvested_op_cash_s1)
reinvested_op_cash_cgt_s1 = reinvested_op_cash_gain_s1 * CAPITAL_GAINS_TAX_RATE
net_liquidated_reinvested_op_cash_s1 = running_reinvested_op_cash_s1_value - reinvested_op_cash_cgt_s1
# 1.5 House Loan Liability
# Assumes payments are made as per schedule; asset depletion above reflects cost of making them.
house_loan_liability_s1 = balance(ACTUAL_HOUSE_LOAN_TAKEN_SCENARIO_1, FIRST_HOME_TAEG, HOUSE_LOAN_DURATION_MONTHS, m_val)
# 1.6 Calculate NLV for S1
nlv_s1[m_idx] = (
net_liquidated_s1_initial_capital + # Evolved initial capital (amount remaining after initial cost + monthly draws)
net_liquidated_house_value_s1 + # House asset
net_liquidated_invested_surplus_s1 + # Surplus investment asset
net_liquidated_reinvested_op_cash_s1 - # Reinvested operational cash asset
house_loan_liability_s1 # House loan liability
)
final_net_liquidated_s1_initial_capital_loop = net_liquidated_s1_initial_capital
final_net_liquidated_house_value_s1_loop = net_liquidated_house_value_s1
final_net_liquidated_invested_surplus_s1_loop = net_liquidated_invested_surplus_s1
final_net_liquidated_reinvested_op_cash_s1_loop = net_liquidated_reinvested_op_cash_s1
final_house_loan_liability_s1_loop = house_loan_liability_s1
if nlv_s1[m_idx] < s1_failure_threshold:
s1_failed = True; s1_fail_idx = m_idx; s1_fail_value = nlv_s1[m_idx]
# --- SCENARIO 2: Rent & Invest ---
# ... (rest of Scenario 2 logic, remains the same as last modification) ... #
if s2_failed:
nlv_s2[m_idx] = np.nan
else:
# 2.0 Grow S2 Initial Capital (amount remaining after t=0 rent)
if running_s2_initial_capital_gross_value > 0:
running_s2_initial_capital_gross_value *= (1 + monthly_investment_rate)
# 2.1 Grow S2 Loan-Funded Investment
if running_investment_value_s2 > 0: # This is the investment funded by the loan
running_investment_value_s2 *= (1 + monthly_investment_rate)
# 2.2 S2 Costs & Cashflows
rent_payment_this_month_s2 = 0
if m_val <= USER_STUDY_RENT_MONTHS: # Rent paid from m_val=1 up to USER_STUDY_RENT_MONTHS
rent_payment_this_month_s2 = USER_STUDY_MONTHLY_RENT_EXTERNAL
# cumulative_rent_paid_s2_for_printout is updated if m_val > 0
if m_val > 0 : # t=0 rent already accounted for in initialization
cumulative_rent_paid_s2_for_printout += USER_STUDY_MONTHLY_RENT_EXTERNAL
# Pay rent from S2 initial capital first
if rent_payment_this_month_s2 > 0:
actual_rent_draw = min(rent_payment_this_month_s2, running_s2_initial_capital_gross_value)
running_s2_initial_capital_gross_value -= actual_rent_draw
running_s2_initial_capital_gross_value = max(0, running_s2_initial_capital_gross_value)
# If rent cannot be fully paid from initial capital, it's a shortfall for rent for now.
# The model doesn't currently have a mechanism to pull rent from loan-funded investments.
# --- MODIFIED LOAN PAYMENT LOGIC FOR SCENARIO 2 ---
# The loan payment must be made. Draw from available capital pools.
if monthly_investment_loan_payment_s2 > 0:
payment_due = monthly_investment_loan_payment_s2
# 1. Attempt to pay from the loan-funded investment pool first
draw_from_loan_investment = min(payment_due, running_investment_value_s2)
running_investment_value_s2 -= draw_from_loan_investment
running_investment_value_s2 = max(0, running_investment_value_s2)
payment_due -= draw_from_loan_investment
# 2. If payment still due, attempt to pay from S2 initial capital
if payment_due > 0:
draw_from_s2_initial_capital = min(payment_due, running_s2_initial_capital_gross_value)
running_s2_initial_capital_gross_value -= draw_from_s2_initial_capital
running_s2_initial_capital_gross_value = max(0, running_s2_initial_capital_gross_value)
payment_due -= draw_from_s2_initial_capital
# If payment_due is still > 0 after attempting to draw from all pools,
# it means there isn't enough liquid capital to cover the full payment.
# However, the loan liability calculation (balance function) implicitly assumes
# the payment is made on schedule. The NLV will reflect the depleted assets.
# --- END OF MODIFIED LOAN PAYMENT LOGIC ---
# 2.4 Net Liquidated Value of S2 Investments
# S2 Initial Capital
s2_initial_capital_gain = max(0, running_s2_initial_capital_gross_value - contributions_to_s2_initial_capital)
s2_initial_capital_cgt = s2_initial_capital_gain * CAPITAL_GAINS_TAX_RATE
net_liquidated_s2_initial_capital = running_s2_initial_capital_gross_value - s2_initial_capital_cgt
# Loan-Funded Investment
investment_gain_s2 = max(0, running_investment_value_s2 - contributions_to_investment_s2)
investment_cgt_s2 = investment_gain_s2 * CAPITAL_GAINS_TAX_RATE
net_liquidated_investment_s2 = running_investment_value_s2 - investment_cgt_s2
# 2.5 Investment Loan Liability
# This function calculates remaining balance assuming regular payments are made.
# The asset side depletion above reflects the "cost" of making these payments.
investment_loan_liability_s2 = balance(INVESTMENT_PRINCIPAL_SCENARIO_2, MARKET_TAEG, INVESTMENT_LOAN_DURATION_MONTHS, m_val)
# 2.6 Calculate NLV for S2
nlv_s2[m_idx] = (
net_liquidated_s2_initial_capital + # Evolved initial capital (after rent & possibly part of loan payment)
net_liquidated_investment_s2 - # Loan-funded investment (after part of loan payment)
investment_loan_liability_s2 # Investment loan liability
)
final_net_liquidated_s2_initial_capital_loop = net_liquidated_s2_initial_capital
final_net_liquidated_investment_s2_loop = net_liquidated_investment_s2
final_investment_loan_liability_s2_loop = investment_loan_liability_s2
if nlv_s2[m_idx] < s2_failure_threshold:
s2_failed = True; s2_fail_idx = m_idx; s2_fail_value = nlv_s2[m_idx]
# --- External Growth Benchmark (Net Liquidation Value of STARTING_NET_LIQUIDATION_VALUE) ---
# Benchmark also starts with initial capital less any t=0 rent
if running_benchmark_capital_gross_value > 0:
running_benchmark_capital_gross_value *= (1 + monthly_investment_rate)
rent_payment_this_month_benchmark = 0
if m_val > 0 and m_val <= USER_STUDY_RENT_MONTHS: # Rent paid from m_val=1 up to USER_STUDY_RENT_MONTHS (t=0 already handled)
rent_payment_this_month_benchmark = USER_STUDY_MONTHLY_RENT_EXTERNAL
running_benchmark_capital_gross_value -= rent_payment_this_month_benchmark
running_benchmark_capital_gross_value = max(0, running_benchmark_capital_gross_value)
# Benchmark gain and CGT calculation based on the initial capital *after* t=0 rent
benchmark_gain = max(0, running_benchmark_capital_gross_value - (STARTING_NET_LIQUIDATION_VALUE - initial_rent_paid_s2_at_t0))
benchmark_cgt = benchmark_gain * CAPITAL_GAINS_TAX_RATE
nlv_initial_external_growth[m_idx] = running_benchmark_capital_gross_value - benchmark_cgt
# =============================================================================
# FINAL SNAPSHOT, LOAN COSTS & TAEG SPREAD ANALYSIS
# =============================================================================
t_final_idx = COMPARISON_DURATION_MONTHS
print(f"\n--- End-of-Period Financial Snapshot (after up to {COMPARISON_DURATION_MONTHS/12:.1f} years) ---")
print("\nScenario 1 (Buy House & Invest Surplus):")
print(f" Starting NLV contribution: €{STARTING_NET_LIQUIDATION_VALUE:,.2f}")
# Component values (final_..._loop) will reflect state at t_final_idx or at failure point
print(f" Net Liquidated Final House Value (after CGT): €{final_net_liquidated_house_value_s1_loop:,.2f}")
print(f" Net Liquidated Value of Invested Surplus (after CGT): €{final_net_liquidated_invested_surplus_s1_loop:,.2f}")
print(f" Net Liquidated Value of Reinvested Operational Cashflow (after CGT): €{final_net_liquidated_reinvested_op_cash_s1_loop:,.2f}")
print(f" Remaining House Loan Liability: €{final_house_loan_liability_s1_loop:,.2f}")
if s1_failed and s1_fail_idx >=0:
print(f" **SCENARIO 1 FAILED at month {s1_fail_idx} (Year {s1_fail_idx/12:.1f}) due to NLV dropping below {s1_failure_threshold:,.2f}.**")
print(f" **Net Liquidation Value (Scenario 1 at failure): €{s1_fail_value:,.2f}**\n")
else:
print(f" **Final Net Liquidation Value (Scenario 1): €{nlv_s1[t_final_idx]:,.2f}**\n")
print("Scenario 2 (Rent & Invest via Loan):")
print(f" Starting NLV contribution: €{STARTING_NET_LIQUIDATION_VALUE:,.2f}")
print(f" Net Liquidated Value of Investment (after CGT): €{final_net_liquidated_investment_s2_loop:,.2f}")
print(f" Remaining Investment Loan Liability: €{final_investment_loan_liability_s2_loop:,.2f}")
# Recalculate final_total_rent_paid_s2 based on actual duration before potential failure
# cumulative_rent_paid_s2_for_printout should reflect the actual rent paid up to the point data is valid
# If S2 failed, cumulative_rent_paid_s2_for_printout would have stopped accumulating correctly if we stopped its update.
# Let's ensure cumulative_rent_paid_s2_for_printout is accurate.
# It was updated inside the !s2_failed block, so it's correct for the duration S2 was active.
print(f" Total Rent Paid (during active period of S2 up to {USER_STUDY_RENT_MONTHS} months): €{cumulative_rent_paid_s2_for_printout:,.2f}")
if s2_failed and s2_fail_idx >=0:
print(f" **SCENARIO 2 FAILED at month {s2_fail_idx} (Year {s2_fail_idx/12:.1f}) due to NLV dropping below {s2_failure_threshold:,.2f}.**")
print(f" **Net Liquidation Value (Scenario 2 at failure): €{s2_fail_value:,.2f}**\n")
else:
print(f" **Final Net Liquidation Value (Scenario 2): €{nlv_s2[t_final_idx]:,.2f}**\n")
# ... (Rest of your "Total Loan Cost Analysis" and "TAEG Spread Benefit Analysis" code remains the same) ...
payment_s1 = pmnt(ACTUAL_HOUSE_LOAN_TAKEN_SCENARIO_1, FIRST_HOME_TAEG, HOUSE_LOAN_DURATION_MONTHS)
# For total paid and interest, consider if scenario failed early
duration_s1_months = COMPARISON_DURATION_MONTHS
if s1_failed and s1_fail_idx >=0 and s1_fail_idx < COMPARISON_DURATION_MONTHS:
duration_s1_months = s1_fail_idx
total_paid_s1 = payment_s1 * min(HOUSE_LOAN_DURATION_MONTHS, duration_s1_months)
principal_repaid_s1 = ACTUAL_HOUSE_LOAN_TAKEN_SCENARIO_1 - balance(ACTUAL_HOUSE_LOAN_TAKEN_SCENARIO_1, FIRST_HOME_TAEG, HOUSE_LOAN_DURATION_MONTHS, min(HOUSE_LOAN_DURATION_MONTHS, duration_s1_months))
total_interest_s1 = total_paid_s1 - principal_repaid_s1
duration_s2_months = COMPARISON_DURATION_MONTHS
if s2_failed and s2_fail_idx >=0 and s2_fail_idx < COMPARISON_DURATION_MONTHS:
duration_s2_months = s2_fail_idx
payment_s2 = pmnt(INVESTMENT_PRINCIPAL_SCENARIO_2, MARKET_TAEG, INVESTMENT_LOAN_DURATION_MONTHS)
total_paid_s2 = payment_s2 * min(INVESTMENT_LOAN_DURATION_MONTHS, duration_s2_months)
principal_repaid_s2 = INVESTMENT_PRINCIPAL_SCENARIO_2 - balance(INVESTMENT_PRINCIPAL_SCENARIO_2, MARKET_TAEG, INVESTMENT_LOAN_DURATION_MONTHS, min(INVESTMENT_LOAN_DURATION_MONTHS, duration_s2_months))
total_interest_s2 = total_paid_s2 - principal_repaid_s2
print("--- Total Loan Cost Analysis (Over Active Period or Full Loan Term if Shorter) ---")
print("Scenario 1 (House Loan):")
print(f" Principal: €{ACTUAL_HOUSE_LOAN_TAKEN_SCENARIO_1:,.2f}, Monthly Payment: €{payment_s1:,.2f}")
print(f" Total Repayments: €{total_paid_s1:,.2f}, Total Interest Paid: €{total_interest_s1:,.2f} (over {min(HOUSE_LOAN_DURATION_MONTHS, duration_s1_months)/12:.1f} years)")
print(f" House Initial Cost for reference: €{TOTAL_HOUSE_COST_INITIAL:,.2f}\n")
print("Scenario 2 (Investment Loan):")
print(f" Principal: €{INVESTMENT_PRINCIPAL_SCENARIO_2:,.2f}, Monthly Payment: €{payment_s2:,.2f}")
print(f" Total Repayments: €{total_paid_s2:,.2f}, Total Interest Paid: €{total_interest_s2:,.2f} (over {min(INVESTMENT_LOAN_DURATION_MONTHS, duration_s2_months)/12:.1f} years)\n")
# --- Revised TAEG Spread Benefit Analysis ---
print("--- TAEG Spread Benefit Analysis (Investment Leverage Focused) ---")
explanation = """
**Understanding the TAEG Spread Benefit for Investment Leverage:**
This analysis explores a specific financial advantage: using a lower-interest first-home loan
to (partially) fund investments, compared to using a standard higher-interest investment loan.
The core idea is to see how much the interest rate difference (the 'spread') on the *invested surplus*
(money borrowed via the house loan beyond the actual house cost) can contribute towards
offsetting the initial cost of the house itself. We calculate the total interest saved on this
surplus over the loan term, due to the preferential `FIRST_HOME_TAEG` versus the `MARKET_TAEG`.
This saving is then compared to the house's initial cost.
This specific analysis is most meaningful when the surplus capital potentially available for
investment from the house loan (Scenario 1 leveraging) is broadly comparable to the capital
that would be borrowed for a dedicated investment (Scenario 2 leveraging).
"""
print(explanation)
potential_surplus_from_house_loan_max_cap = max(0, HOUSE_LOAN_MAX_CAPACITY - TOTAL_HOUSE_COST_INITIAL)
investment_loan_max_cap_s2 = INVESTMENT_LOAN_MAX_CAPACITY
duration_years_house_loan = HOUSE_LOAN_DURATION_MONTHS / 12
rate_spread_val = MARKET_TAEG - FIRST_HOME_TAEG
are_different_for_analysis = False
if max(potential_surplus_from_house_loan_max_cap, investment_loan_max_cap_s2) == 0:
are_different_for_analysis = False
elif potential_surplus_from_house_loan_max_cap == 0 and investment_loan_max_cap_s2 > 0:
are_different_for_analysis = True
elif investment_loan_max_cap_s2 == 0 and potential_surplus_from_house_loan_max_cap > 0:
are_different_for_analysis = True
else:
diff_ratio = abs(potential_surplus_from_house_loan_max_cap - investment_loan_max_cap_s2) / \
max(1e-9, potential_surplus_from_house_loan_max_cap, investment_loan_max_cap_s2) # Avoid division by zero
if diff_ratio > 0.01:
are_different_for_analysis = True
if are_different_for_analysis:
print(f"**Warning for TAEG Spread Analysis Applicability:**")
print(f" The maximum potential surplus from the house loan for investment (€{potential_surplus_from_house_loan_max_cap:,.2f})")
print(f" and the maximum dedicated investment loan capacity (€{investment_loan_max_cap_s2:,.2f}) differ by more than 1%.")
print(f" Therefore, this specific TAEG spread benefit analysis, which assumes these amounts are")
print(f" closely comparable for evaluating investment leverage, may not directly reflect your configured loan capacities.\n")
else:
print(f"Note: Max potential surplus from house loan (€{potential_surplus_from_house_loan_max_cap:,.2f}) and max investment loan capacity (€{investment_loan_max_cap_s2:,.2f}) are comparable.\n")
if rate_spread_val <= 0 or HOUSE_LOAN_DURATION_MONTHS <= 0 or TOTAL_HOUSE_COST_INITIAL <= 0:
print("TAEG spread is not positive, loan term is zero, or house cost is zero. This specific benefit analysis is not applicable.")
else:
print(f"TAEG Spread (Market Rate - First Home Rate): {rate_spread_val:.2%}")
print(f"House Loan Duration Considered: {duration_years_house_loan:.0f} years\n")
actual_surplus_s1 = SURPLUS_INVESTED_SCENARIO_1
if actual_surplus_s1 > 0:
pmnt_surplus_market = pmnt(actual_surplus_s1, MARKET_TAEG, HOUSE_LOAN_DURATION_MONTHS)
total_interest_surplus_market = (pmnt_surplus_market * HOUSE_LOAN_DURATION_MONTHS) - actual_surplus_s1
pmnt_surplus_first_home = pmnt(actual_surplus_s1, FIRST_HOME_TAEG, HOUSE_LOAN_DURATION_MONTHS)
total_interest_surplus_first_home = (pmnt_surplus_first_home * HOUSE_LOAN_DURATION_MONTHS) - actual_surplus_s1
total_interest_saved_on_actual_surplus = total_interest_surplus_market - total_interest_surplus_first_home
print(f"**Analysis of Actual Invested Surplus from House Loan (Scenario 1):**")
print(f" Your actual house loan taken is €{ACTUAL_HOUSE_LOAN_TAKEN_SCENARIO_1:,.2f}, resulting in an investable surplus of €{actual_surplus_s1:,.2f}.")
print(f" The total interest saved on this surplus of €{actual_surplus_s1:,.2f} over {duration_years_house_loan:.0f} years,")
print(f" due to the {rate_spread_val:.2%} TAEG spread, amounts to: **€{total_interest_saved_on_actual_surplus:,.2f}**.")
print(f" This saving compares to the initial house cost of: €{TOTAL_HOUSE_COST_INITIAL:,.2f}.")
if TOTAL_HOUSE_COST_INITIAL > 0: # Avoid issues if house cost is zero
if total_interest_saved_on_actual_surplus >= TOTAL_HOUSE_COST_INITIAL:
print(f" Therefore, the interest saved on your actual invested surplus IS sufficient to cover the initial house cost over the loan term.")
else:
print(f" Therefore, the interest saved on your actual invested surplus IS NOT sufficient by itself to cover the initial house cost (shortfall of €{TOTAL_HOUSE_COST_INITIAL - total_interest_saved_on_actual_surplus:,.2f}).")
else:
print(" Initial house cost is zero, comparison of saving to cost is not applicable.")
else:
print("**Analysis of Actual Invested Surplus from House Loan (Scenario 1):**")
print(" No surplus was invested from the house loan in Scenario 1, so no specific interest saving on surplus to calculate here.")
print("\n")
print("**Hypothetical Calculation for Break-Even Investment Leverage:**")
print(f" To find the amount of *invested surplus* where the total interest saved by the {rate_spread_val:.2%} spread")
print(f" would exactly equal the initial house cost of €{TOTAL_HOUSE_COST_INITIAL:,.2f}:")
k_market_unit = pmnt(1, MARKET_TAEG, HOUSE_LOAN_DURATION_MONTHS)
k_first_home_unit = pmnt(1, FIRST_HOME_TAEG, HOUSE_LOAN_DURATION_MONTHS)
if k_market_unit <= k_first_home_unit:
print(" Cannot calculate hypothetical surplus: Market rate unit cost is not greater than first home rate unit cost.")
else:
# Interest Saved = Surplus * (TotalInterestFactor_Market - TotalInterestFactor_FirstHome)
# TotalInterestFactor = (Payment_per_unit * N_months) - 1
tif_market = (k_market_unit * HOUSE_LOAN_DURATION_MONTHS) - 1
tif_first_home = (k_first_home_unit * HOUSE_LOAN_DURATION_MONTHS) - 1
interest_factor_difference = tif_market - tif_first_home
if interest_factor_difference <= 1e-9 or TOTAL_HOUSE_COST_INITIAL < 0: # Avoid division by zero/small or negative house cost
print(" Cannot calculate hypothetical surplus: Effective interest saving factor is zero or negative, or house cost is invalid.")
elif TOTAL_HOUSE_COST_INITIAL == 0:
print(" Hypothetical surplus is €0.00 as initial house cost is €0.00.")
s_critical = 0
p_hl_critical_total_loan = TOTAL_HOUSE_COST_INITIAL # which is 0
else:
s_critical = TOTAL_HOUSE_COST_INITIAL / interest_factor_difference
p_hl_critical_total_loan = s_critical + TOTAL_HOUSE_COST_INITIAL
print(f" Required Invested Surplus for this break-even: €{s_critical:,.2f}")
print(f" This would require a total House Loan Principal of: €{p_hl_critical_total_loan:,.2f}")
if TOTAL_HOUSE_COST_INITIAL > 0:
print(f" (This total loan is {p_hl_critical_total_loan / TOTAL_HOUSE_COST_INITIAL:.2%} of the initial house cost).")
if p_hl_critical_total_loan > HOUSE_LOAN_MAX_CAPACITY:
print(f" WARNING: This required hypothetical loan (€{p_hl_critical_total_loan:,.2f}) exceeds your max house loan capacity (€{HOUSE_LOAN_MAX_CAPACITY:,.2f}).")
elif p_hl_critical_total_loan < TOTAL_HOUSE_COST_INITIAL: # This implies s_critical is negative
print(f" NOTE: This calculation results in a negative required surplus (€{s_critical:,.2f}), suggesting the house cost might be covered even without surplus by other factors or an anomaly in rates making spread benefit unusually high.")
print("\n")
# =============================================================================
# PLOTTING
# =============================================================================
plt.style.use('seaborn-v0_8-whitegrid') # Consider updating if 'seaborn-v0_8-whitegrid' is deprecated
fig1, ax1 = plt.subplots(figsize=(12, 7))
years_array_plot = months_array / 12
# Plot Scenario 1
s1_label_plot = 'Scenario 1: Buy House (NLV)'
if s1_failed:
s1_label_plot += ' (Failed)'
ax1.plot(years_array_plot, nlv_s1, label=s1_label_plot, color='red', linewidth=2)
if s1_failed and s1_fail_idx >= 0:
ax1.plot(years_array_plot[s1_fail_idx], s1_fail_value, 'rx', markersize=10, markeredgewidth=2, zorder=5) # zorder to ensure marker is on top
# Plot Scenario 2
s2_label_plot = 'Scenario 2: Rent & Invest (NLV)'
if s2_failed:
s2_label_plot += ' (Failed)'
ax1.plot(years_array_plot, nlv_s2, label=s2_label_plot, color='darkgray', linewidth=2)
if s2_failed and s2_fail_idx >= 0:
ax1.plot(years_array_plot[s2_fail_idx], s2_fail_value, 'rx', markersize=10, markeredgewidth=2, zorder=5) # zorder for visibility
# Plot Benchmark
ax1.plot(years_array_plot, nlv_initial_external_growth, label=f'Initial External NLV (Invested at {ANNUAL_GROSS_INVESTMENT_RETURN:.1%})', color='blue', linestyle=':', linewidth=1.5)
ax1.set_xlabel("Years")
ax1.set_ylabel("Net Liquidation Value (EUR)")
ax1.set_title("Capital Evolution (Net Liquidation Value): Buy vs. Rent & Invest")
ax1.legend(loc='best') # Changed to 'best' for potentially better placement with new labels
ax1.grid(True, which='both', linestyle='--', linewidth=0.7)
ax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: format(int(x), ',')))
plt.tight_layout()
plt.show()
# =============================================================================
# FINAL REMARKS (USER PROVIDED)
# =============================================================================
print("\n--- Important Non-Quantifiable Considerations ---")
final_comment = """
Beyond the numbers, purchasing this particular house offers several advantages
that are more challenging to quantify. Firstly, it could provide a stable base
for the 'Luna project', which might benefit significantly from a dedicated physical space.
Secondly, owning a home that can be personalised to one's tastes, situated in a
location with a strong and supportive social network, would undoubtedly enhance
the quality of life. Lastly, it presents a valuable opportunity for learning new skills
through renovation and provides a secure, personal space for storing belongings,
offering independence from reliance on others for such needs.
"""
print(final_comment)