2025-04-06 20:48:59 +00:00
|
|
|
import numpy as np
|
|
|
|
|
from multipolyfit import multipolyfit as mpf
|
|
|
|
|
|
|
|
|
|
GROSS_RENT_YIELD: float = 0.03686
|
|
|
|
|
RISK_POOL_ALLOCATION: float = 0.01
|
|
|
|
|
LOSS_SEVERITY: float = 0.19
|
|
|
|
|
RECOVERY_RATE: float = 0.9
|
2025-04-09 01:43:39 +00:00
|
|
|
MIN_MRE: float = 0.05
|
2025-04-06 20:48:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_default_risk_by_fico(consumer_fico: int, loan_to_value: float) -> float:
|
|
|
|
|
# remember that the credit score is the first variable and the ltv is the second
|
|
|
|
|
initial_fico_ltv_risks = [
|
|
|
|
|
[620, 0.01],
|
|
|
|
|
[620, 0.70],
|
|
|
|
|
[620, 0.85],
|
|
|
|
|
[620, 0.90],
|
|
|
|
|
[620, 0.95],
|
|
|
|
|
[660, 0.01],
|
|
|
|
|
[660, 0.70],
|
|
|
|
|
[660, 0.85],
|
|
|
|
|
[660, 0.90],
|
|
|
|
|
[660, 0.95],
|
|
|
|
|
[700, 0.01],
|
|
|
|
|
[700, 0.70],
|
|
|
|
|
[700, 0.85],
|
|
|
|
|
[700, 0.90],
|
|
|
|
|
[700, 0.95],
|
|
|
|
|
[740, 0.01],
|
|
|
|
|
[740, 0.70],
|
|
|
|
|
[740, 0.85],
|
|
|
|
|
[740, 0.90],
|
|
|
|
|
[740, 0.95]
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
default_rates_by_credit_score = {
|
|
|
|
|
620: 0.0303,
|
|
|
|
|
660: 0.0201,
|
|
|
|
|
700: 0.0150,
|
|
|
|
|
740: 0.0094
|
|
|
|
|
}
|
|
|
|
|
risk_factors_by_credit_score = {
|
|
|
|
|
620: [0.20, 0.61, 1.22, 1.48, 1.80],
|
|
|
|
|
660: [0.20, 0.62, 1.22, 1.48, 1.82],
|
|
|
|
|
700: [0.20, 0.62, 1.22, 1.49, 1.83],
|
|
|
|
|
740: [0.20, 0.63, 1.21, 1.47, 1.81]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# We should now fill out the table of default rates with the LTV risk factors included
|
|
|
|
|
initial_risk_factors_by_ltv_credit_score = []
|
|
|
|
|
for fico in risk_factors_by_credit_score:
|
|
|
|
|
initial_risk_factors_by_ltv_credit_score += list(np.multiply(default_rates_by_credit_score[fico],
|
|
|
|
|
risk_factors_by_credit_score[fico]))
|
|
|
|
|
|
|
|
|
|
# Compute a line of best fit
|
|
|
|
|
lobf_coefficients = mpf(initial_fico_ltv_risks, initial_risk_factors_by_ltv_credit_score, 1)
|
|
|
|
|
|
|
|
|
|
# the first coeffient is a constant offset, the second is the credit score factor, and the third is the ltv factor
|
|
|
|
|
const_offset = lobf_coefficients[0]
|
|
|
|
|
consumer_fico_factor = (lobf_coefficients[1] * consumer_fico)
|
|
|
|
|
loan_to_value_factor = (lobf_coefficients[2] * loan_to_value)
|
|
|
|
|
# now we just sum these up to get the result
|
|
|
|
|
result = consumer_fico_factor + loan_to_value_factor + const_offset
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
def get_risk_pool_health() -> float:
|
|
|
|
|
return 1.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def compute_mre(home_value: float,
|
|
|
|
|
down_payment: float,
|
|
|
|
|
consumer_fico: int) -> float:
|
2025-04-06 22:18:12 +00:00
|
|
|
if down_payment < 0 or down_payment > 1:
|
|
|
|
|
raise ValueError('The down_payment must be between 0 and 1')
|
|
|
|
|
|
|
|
|
|
if consumer_fico < 620 or consumer_fico > 850:
|
|
|
|
|
raise ValueError('The consumer_fico must between 620 and 850')
|
|
|
|
|
|
2025-04-06 22:15:42 +00:00
|
|
|
down_payment *= home_value
|
2025-04-06 20:48:59 +00:00
|
|
|
investor_value = home_value - down_payment
|
|
|
|
|
loan_to_value = float(investor_value/home_value)
|
|
|
|
|
# the monthly payment is made on the value from the investors
|
|
|
|
|
monthly_payment = investor_value * (GROSS_RENT_YIELD) / 12.0
|
|
|
|
|
# how much is at risk if the occupant "defaults"
|
|
|
|
|
at_risk_value = home_value * LOSS_SEVERITY
|
|
|
|
|
# how much is expected to be recovered?
|
|
|
|
|
recovery_value = at_risk_value * RECOVERY_RATE
|
|
|
|
|
# get the default rate with the risk-factors dealt with
|
|
|
|
|
default_rate = get_default_risk_by_fico(consumer_fico, loan_to_value)
|
|
|
|
|
# get the risk pool allocation
|
|
|
|
|
risk_pool_allocation = RISK_POOL_ALLOCATION * home_value
|
|
|
|
|
# we should add the 4 month buffer that effectively halves the default rate,
|
|
|
|
|
# this is already included in the get_default_risk_by_fico method.
|
|
|
|
|
income_interruption_buffer = 4.0 * monthly_payment
|
|
|
|
|
default_rate = get_default_risk_by_fico(consumer_fico, loan_to_value)
|
|
|
|
|
# the expected loss to the risk pool
|
|
|
|
|
default_rate_risk_pool_loss = default_rate * risk_pool_allocation
|
|
|
|
|
# adjust the risk pool loss by the risk pool's health
|
|
|
|
|
risk_pool_factor = get_risk_pool_health() * default_rate_risk_pool_loss
|
|
|
|
|
# since we don't actually lose anything in the risk pool, it does not
|
|
|
|
|
# get counted toward the loss
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
at_risk_value -= risk_pool_factor
|
|
|
|
|
# start putting together the pieces
|
|
|
|
|
mre = income_interruption_buffer
|
|
|
|
|
mre += at_risk_value - recovery_value
|
|
|
|
|
# make the mre a percentage of the home value
|
|
|
|
|
mre /= home_value
|
2025-04-09 01:43:39 +00:00
|
|
|
mre = max(mre, MIN_MRE)
|
2025-04-06 20:48:59 +00:00
|
|
|
return mre
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if __name__ == '__main__':
|
|
|
|
|
# print('Computing risk factor for consumer with 660 FICO and 70% LTV')
|
|
|
|
|
# print(f'Deault Rate: {get_default_risk_by_fico(660, 0.7)}\n')
|
|
|
|
|
# print('Comuting MRE for 742,500 home with 2.5% down payment and a 680 fico')
|
|
|
|
|
# print(f' MRE: {compute_mre(742500.0, 0.025*742500.0, 680)}')
|
|
|
|
|
|
|
|
|
|
|