use eq_primitives::{currency::Currency, InterestRateError};
use eq_utils::{eq_ensure, log::eq_log, math::MathUtils};
#[allow(unused_imports)]
use frame_support::{debug, ensure};
use sp_arithmetic::{FixedI64, FixedPointNumber};
use sp_std::{cmp, fmt::Debug, prelude::*};
#[derive(Debug)]
pub struct TotalBalance {
issuance: FixedI64,
debt: FixedI64,
}
impl TotalBalance {
pub fn new(issuance: FixedI64, debt: FixedI64) -> TotalBalance {
TotalBalance { issuance, debt }
}
}
pub struct InterestRateSettings {
lover_bound: FixedI64,
upper_bound: FixedI64,
target: FixedI64,
n_sigma: FixedI64,
rho: FixedI64,
alpha: FixedI64,
}
impl InterestRateSettings {
pub fn new(
lover_bound: FixedI64,
upper_bound: FixedI64,
target: FixedI64,
n_sigma: FixedI64,
rho: FixedI64,
alpha: FixedI64,
) -> InterestRateSettings {
InterestRateSettings {
lover_bound,
upper_bound,
target,
n_sigma,
rho,
alpha,
}
}
}
pub trait InterestRateDataSource {
type AccountId: Debug;
fn get_settings() -> InterestRateSettings;
fn get_price(currency: &Currency) -> Result<FixedI64, sp_runtime::DispatchError>;
fn get_covariance(c1: &Currency, c2: &Currency) -> FixedI64;
fn get_bailsman_total_balance(currency: &Currency) -> TotalBalance;
fn get_balance(account_id: &Self::AccountId, currency: &Currency) -> TotalBalance;
fn get_total_balance(currency: &Currency) -> TotalBalance;
}
#[derive(PartialEq, Debug)]
pub struct Cdb<T> {
pub collateral: T,
pub debt: T,
pub bail: T,
}
fn currencies() -> Vec<Currency> {
Currency::iterator_with_usd().map(|x| *x).collect()
}
fn prices<T: InterestRateDataSource>(
currencies: &Vec<Currency>,
) -> Result<Vec<FixedI64>, InterestRateError> {
let prices = currencies.into_iter().map(|x| T::get_price(&x)).collect();
match prices {
Ok(ps) => Ok(ps),
Err(e) => {
debug::error!("{}:{}. Unable to fetch price: {:?}", file!(), line!(), e);
eq_log!("Unable to fetch price: {:?}", e);
Err(InterestRateError::ExternalError)
}
}
}
fn sumproduct<'a, I>(items: I) -> FixedI64
where
I: Iterator<Item = (&'a FixedI64, &'a FixedI64)>,
{
items.fold(FixedI64::zero(), |acc, (&x, &y)| acc + x * y)
}
pub fn total_usd(xs: &Vec<FixedI64>, prices: &Vec<FixedI64>) -> FixedI64 {
sumproduct(xs.into_iter().zip(prices.into_iter()))
}
pub fn total_weights(
xs: &Vec<FixedI64>,
prices: &Vec<FixedI64>,
total: FixedI64,
) -> Result<Vec<FixedI64>, InterestRateError> {
eq_ensure!(
total != FixedI64::zero(),
InterestRateError::ValueError,
"{}:{}. Total is equal to zero.",
file!(),
line!()
);
Ok(xs
.into_iter()
.zip(prices.into_iter())
.map(move |(&x, &p)| (x * p) / total)
.collect())
}
fn covariance_column<T: InterestRateDataSource>(
currency: Currency,
currencies: &Vec<Currency>,
) -> Vec<FixedI64> {
currencies
.into_iter()
.map(|c| T::get_covariance(¤cy, &c))
.collect()
}
pub fn total_interim(
weights: &Vec<FixedI64>,
covariance_matrix: &Vec<Vec<FixedI64>>,
) -> Vec<FixedI64> {
covariance_matrix
.into_iter()
.map(|covs| sumproduct(covs.into_iter().zip(weights.into_iter())))
.collect()
}
fn currency_totals<T: InterestRateDataSource>(currencies: &Vec<Currency>) -> Cdb<Vec<FixedI64>> {
let total_bails: Vec<_> = (¤cies)
.into_iter()
.map(|x| T::get_bailsman_total_balance(&x).issuance)
.collect();
let total_bail_debts: Vec<_> = (¤cies)
.into_iter()
.map(|x| T::get_bailsman_total_balance(&x).debt)
.collect();
let total_collaterals: Vec<_> = (¤cies)
.into_iter()
.map(|x| T::get_total_balance(x).issuance)
.zip((&total_bails).into_iter())
.map(|(all, &bail)| all - bail)
.collect();
let total_debts: Vec<_> = (¤cies)
.into_iter()
.map(|x| T::get_total_balance(x).debt)
.zip((&total_bail_debts).into_iter())
.map(|(all, &bail)| all - bail)
.collect();
Cdb::<_> {
collateral: total_collaterals,
debt: total_debts,
bail: total_bails,
}
}
fn totals_bailsman_debt<T: InterestRateDataSource>(
prices: &Vec<FixedI64>,
currencies: &Vec<Currency>,
) -> FixedI64 {
let total_bail_debt: Vec<_> = (¤cies)
.into_iter()
.map(|x| T::get_bailsman_total_balance(&x).debt)
.collect();
return total_usd(&total_bail_debt, &prices);
}
fn totals(prices: &Vec<FixedI64>, currency_totals: &Cdb<Vec<FixedI64>>) -> Cdb<FixedI64> {
let total_issuance = total_usd(¤cy_totals.collateral, &prices);
let total_debt = total_usd(¤cy_totals.debt, &prices);
let total_bail = total_usd(¤cy_totals.bail, &prices);
Cdb::<_> {
collateral: total_issuance,
debt: total_debt,
bail: total_bail,
}
}
pub fn aggregate_portfolio_volatilities<T: InterestRateDataSource>(
_currencies: &Vec<Currency>,
prices: &Vec<FixedI64>,
currency_totals: &Cdb<Vec<FixedI64>>,
covariance_matrix: &Vec<Vec<FixedI64>>,
) -> Result<Cdb<FixedI64>, InterestRateError> {
let totals = totals(prices, currency_totals);
let total_collateral_weights =
total_weights(¤cy_totals.collateral, &prices, totals.collateral)?;
let total_debt_weights = total_weights(¤cy_totals.debt, &prices, totals.debt)?;
let total_bail_weights = total_weights(¤cy_totals.bail, &prices, totals.bail)?;
let total_collateral_interim = total_interim(&total_collateral_weights, covariance_matrix);
let total_debt_interim = total_interim(&total_debt_weights, covariance_matrix);
let total_bail_interim = total_interim(&total_bail_weights, covariance_matrix);
let total_collateral_volatility = sumproduct(
(&total_collateral_weights)
.into_iter()
.zip((&total_collateral_interim).into_iter()),
)
.sqrt()
.map_err(|_| {
debug::error!("{}:{}", file!(), line!());
InterestRateError::MathError
})?;
let total_debt_volatility = sumproduct(
(&total_debt_weights)
.into_iter()
.zip((&total_debt_interim).into_iter()),
)
.sqrt()
.map_err(|_| {
debug::error!("{}:{}", file!(), line!());
InterestRateError::MathError
})?;
let total_bail_volatility = sumproduct(
(&total_bail_weights)
.into_iter()
.zip((&total_bail_interim).into_iter()),
)
.sqrt()
.map_err(|_| {
debug::error!("{}:{}", file!(), line!());
InterestRateError::MathError
})?;
Ok(Cdb::<_> {
collateral: total_collateral_volatility,
debt: total_debt_volatility,
bail: total_bail_volatility,
})
}
pub fn scale<T: InterestRateDataSource>(
currencies: &Vec<Currency>,
prices: &Vec<FixedI64>,
covariance_matrix: &Vec<Vec<FixedI64>>,
) -> Result<FixedI64, InterestRateError> {
let InterestRateSettings {
lover_bound,
upper_bound,
target: _,
n_sigma,
rho,
alpha: _,
} = T::get_settings();
let currency_totals = currency_totals::<T>(¤cies);
let totals = totals(&prices, ¤cy_totals);
let total_volatilities = aggregate_portfolio_volatilities::<T>(
currencies,
prices,
¤cy_totals,
covariance_matrix,
)?;
let collateral_discount = n_sigma * total_volatilities.collateral;
let debt_discount = n_sigma * total_volatilities.debt;
let bail_discount = n_sigma * total_volatilities.bail;
let totals_bailsman_debt = totals_bailsman_debt::<T>(&prices, ¤cies);
let insufficient_collateral = cmp::max(
FixedI64::zero(),
totals.debt * (FixedI64::one() + debt_discount)
- totals.collateral * (FixedI64::one() - collateral_discount),
);
let stressed_bail = (totals.bail - totals_bailsman_debt) * (FixedI64::one() - bail_discount);
let scale: FixedI64;
if stressed_bail <= FixedI64::zero() {
scale = upper_bound;
} else {
scale = cmp::max(
cmp::min(
(insufficient_collateral / stressed_bail)
.pow(rho)
.map_err(|_| {
debug::error!("{}:{}", file!(), line!());
InterestRateError::MathError
})?,
upper_bound,
),
lover_bound,
);
}
Ok(scale)
}
pub fn balances<T: InterestRateDataSource>(
account_id: &T::AccountId,
currencies: &Vec<Currency>,
) -> (Vec<FixedI64>, Vec<FixedI64>) {
let bs: Vec<TotalBalance> = currencies
.into_iter()
.map(|x| T::get_balance(account_id, x))
.collect();
(
bs.iter().map(|x| x.issuance).collect(),
bs.iter().map(|x| x.debt).collect(),
)
}
#[derive(PartialEq)]
pub enum LtvError {
ZeroCollateral,
ZeroDebt,
}
pub fn ltv(
positive_balances: &Vec<FixedI64>,
negative_balances: &Vec<FixedI64>,
prices: &Vec<FixedI64>,
) -> Result<FixedI64, LtvError> {
let collateral = sumproduct(positive_balances.into_iter().zip(prices.into_iter()));
let debt = sumproduct(negative_balances.into_iter().zip(prices.into_iter()));
match (collateral, debt) {
(c, _d) if c == FixedI64::zero() => {
debug::error!("{}:{}. Collateral is zero.", file!(), line!());
Err(LtvError::ZeroCollateral)
}
(_c, d) if d == FixedI64::zero() => Err(LtvError::ZeroDebt),
(c, d) => Ok(c / d),
}
}
pub fn borrower_volatility(
prices: &Vec<FixedI64>,
positive_balances: &Vec<FixedI64>,
covariance_matrix: &Vec<Vec<FixedI64>>,
) -> Result<FixedI64, InterestRateError> {
let total = sumproduct(prices.into_iter().zip(positive_balances.into_iter()));
let weights = total_weights(positive_balances, prices, total)?;
let interim = total_interim(&weights, covariance_matrix);
let volatility = sumproduct((&weights).into_iter().zip((&interim).into_iter()))
.sqrt()
.map_err(|_| {
debug::error!("{}:{}", file!(), line!());
InterestRateError::MathError
})?;
Ok(volatility)
}
pub fn interest_rate<T: InterestRateDataSource>(
account_id: &T::AccountId,
) -> Result<FixedI64, InterestRateError> {
let settings = T::get_settings();
let alpha = settings.alpha;
let currencies = currencies();
let prices = prices::<T>(¤cies)?;
let covariance_matrix: Vec<_> = (¤cies)
.into_iter()
.map(|&c1| covariance_column::<T>(c1, ¤cies))
.collect();
let scale = scale::<T>(¤cies, &prices, &covariance_matrix)?;
let (positive_balances, negative_balances) = balances::<T>(account_id, ¤cies);
let ltv = ltv(&positive_balances, &negative_balances, &prices).map_err(|err| {
if err != LtvError::ZeroDebt {
debug::error!("{}:{}", file!(), line!());
}
InterestRateError::ValueError
})?;
let sigma = borrower_volatility(&prices, &positive_balances, &covariance_matrix)?;
let interest_rate = alpha
* (ltv / (ltv - FixedI64::one()) * (scale * sigma).sqr()
/ FixedI64::saturating_from_integer(2)
* FixedI64::saturating_from_integer(365));
eq_log!("interest_rate({:?}) = alpha({:?}) * (ltv({:?} / (ltv - 1)) * (scale({:?}) * sigma({:?}))^2 / 2 * 365",
interest_rate, alpha, ltv, scale, sigma
);
Ok(interest_rate)
}