use cfg_primitives::SECONDS_PER_YEAR;
use cfg_traits::{interest::InterestRate, Seconds};
use frame_support::{
pallet_prelude::RuntimeDebug,
traits::tokens::{self},
};
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_arithmetic::traits::checked_pow;
use sp_runtime::{
traits::{EnsureDiv, EnsureFixedPointNumber, EnsureInto, EnsureMul, EnsureSub, One},
ArithmeticError, FixedPointNumber, FixedPointOperand,
};
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
pub struct DiscountedCashFlow<Rate> {
pub probability_of_default: Rate,
pub loss_given_default: Rate,
pub discount_rate: InterestRate<Rate>,
}
impl<Rate: FixedPointNumber> DiscountedCashFlow<Rate> {
pub fn new(
probability_of_default: Rate,
loss_given_default: Rate,
discount_rate: InterestRate<Rate>,
) -> Result<Self, ArithmeticError> {
Ok(Self {
probability_of_default,
loss_given_default,
discount_rate,
})
}
pub fn compute_present_value<Balance: tokens::Balance + FixedPointOperand>(
&self,
debt: Balance,
when: Seconds,
interest_rate: &InterestRate<Rate>,
maturity_date: Seconds,
origination_date: Seconds,
) -> Result<Balance, ArithmeticError> {
if when > maturity_date {
return Ok(debt);
}
let tel = Rate::saturating_from_rational(
maturity_date.ensure_sub(origination_date)?,
SECONDS_PER_YEAR,
)
.ensure_mul(self.probability_of_default)?
.ensure_mul(self.loss_given_default)?
.min(One::one());
let tel_inv = Rate::one().ensure_sub(tel)?;
let exp = maturity_date.ensure_sub(when)?.ensure_into()?;
let interest_rate_per_sec = interest_rate.per_sec()?;
let acc_rate = checked_pow(interest_rate_per_sec, exp).ok_or(ArithmeticError::Overflow)?;
let ecf = acc_rate.ensure_mul_int(debt)?;
let ra_ecf = tel_inv.ensure_mul_int(ecf)?;
let discount_rate_per_sec = self.discount_rate.per_sec()?;
let rate = checked_pow(discount_rate_per_sec, exp).ok_or(ArithmeticError::Overflow)?;
let d = Rate::one().ensure_div(rate)?;
d.ensure_mul_int(ra_ecf)
}
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebug, MaxEncodedLen)]
pub enum ValuationMethod<Rate> {
DiscountedCashFlow(DiscountedCashFlow<Rate>),
OutstandingDebt,
Cash,
}
impl<Rate> ValuationMethod<Rate>
where
Rate: FixedPointNumber,
{
pub fn is_valid(&self) -> bool {
match self {
ValuationMethod::DiscountedCashFlow(dcf) => dcf.discount_rate.per_year() <= One::one(),
ValuationMethod::OutstandingDebt | ValuationMethod::Cash => true,
}
}
}