MRE-module/__init__.py
2025-04-10 20:09:19 -06:00

126 lines
4.6 KiB
Python

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
MIN_MRE: float = 0.05
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:
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')
down_payment *= home_value
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
mre = max(mre, MIN_MRE)
return mre
def compute_from_target_dti(consumer_income: float,
home_price: float,
comsumer_fico: int,
target_dti: float) -> float:
return None
#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)}')