#![cfg_attr(not(feature = "std"), no_std)]
mod mock;
mod tests;
use core::slice::Iter;
use eq_oracle::OnNewPrice;
use eq_primitives::currency::Currency;
use eq_utils::math::*;
use frame_support::{
codec::{Decode, Encode},
debug, decl_error, decl_event, decl_module, decl_storage, ensure,
traits::{Get, UnixTime},
};
use sp_arithmetic::{traits::Saturating, FixedI64, FixedPointNumber};
use sp_runtime::traits::CheckedAdd;
use sp_runtime::RuntimeDebug;
use sp_std::prelude::*;
#[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug)]
pub enum PricePeriod {
Min,
TenMin,
Hour,
FourHour,
Day,
}
impl PricePeriod {
pub fn iterator() -> Iter<'static, PricePeriod> {
static PRICE_PERIOD: [PricePeriod; 5] = [
PricePeriod::Min,
PricePeriod::TenMin,
PricePeriod::Hour,
PricePeriod::FourHour,
PricePeriod::Day,
];
PRICE_PERIOD.iter()
}
fn as_secs(&self) -> u64 {
match self {
PricePeriod::Min => 60,
PricePeriod::TenMin => 600,
PricePeriod::Hour => 3600,
PricePeriod::FourHour => 14_400,
PricePeriod::Day => 86_400,
}
}
}
pub trait Trait: system::Trait {
type UnixTime: UnixTime;
type MaxPricePoints: Get<usize>;
type PeriodTypeForAggregates: Get<PricePeriod>;
}
decl_storage! {
trait Store for Module<T: Trait> as EqVolatility {
pub Prices get(fn prices): double_map hasher(blake2_128_concat) Currency, hasher(blake2_128_concat) PricePeriod => Vec<FixedI64>;
pub LastUpdate get(fn last_update): double_map hasher(blake2_128_concat) Currency, hasher(blake2_128_concat) PricePeriod => u64;
pub Volatility get(fn volatility): map hasher(blake2_128_concat) Currency => FixedI64;
pub Corellation get(fn corellation): double_map hasher(blake2_128_concat) Currency, hasher(blake2_128_concat) Currency => FixedI64;
}
add_extra_genesis {
config(volatilities): Vec<(u8, u64, u64)>;
config(corellations): Vec<(u8, u8, u64, u64)>;
config(prices): Vec<(u8, u64, u64)>;
config(update_date): u64;
build(|config: &GenesisConfig| {
for &(currency, nom, denom) in config.volatilities.iter() {
let currency_typed: Currency = currency.into();
let value: FixedI64 = FixedI64::saturating_from_rational(nom, denom);
<Volatility>::insert(currency_typed, value);
}
for &(currency1, currency2, nom, denom) in config.corellations.iter() {
let c1_typed: Currency = currency1.into();
let c2_typed: Currency = currency2.into();
let value: FixedI64 = FixedI64::saturating_from_rational(nom, denom);
<Corellation>::insert(c1_typed, c2_typed, value);
if currency1 != currency2 {
<Corellation>::insert(c2_typed, c1_typed, value);
}
}
let mut currency_cached = 0;
for &(currency, nom, denom) in config.prices.iter() {
let currency_typed: Currency = currency.into();
let period = PricePeriod::Day;
if currency != currency_cached
{
let vec: Vec<FixedI64> = Vec::new();
<Prices>::insert(currency_typed, period.clone(), vec);
<LastUpdate>::insert(currency_typed, period.clone(), config.update_date);
currency_cached = currency;
}
let price: FixedI64 = FixedI64::saturating_from_rational(nom, denom);
<Prices>::mutate(currency_typed, period, |curr_vec| {
curr_vec.push(price);
});
}
});
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {}
}
impl<T: Trait> Module<T> {
fn calv_log_return_diff_mid(currency: &Currency) -> Option<Vec<FixedI64>> {
let prices = <Prices>::get(currency, T::PeriodTypeForAggregates::get());
if prices.len() != T::MaxPricePoints::get() {
return None;
}
let log_returns: Result<Vec<FixedI64>, ()> = (0..prices.len() - 1)
.enumerate()
.map(|(index, _)| -> Result<FixedI64, ()> {
ensure!(prices[index].into_inner() != 0, ());
let diff = prices[index + 1] / prices[index];
diff.ln()
})
.collect();
if log_returns.is_err() {
debug::error!("{}:{}. log_returns.is_err()", file!(), line!());
return None;
}
let log_returns_unwrapped = log_returns.unwrap();
let log_returns_sum_option = log_returns_unwrapped
.iter()
.try_fold(FixedI64::saturating_from_integer(0), |acc, x| {
acc.checked_add(x)
});
if log_returns_sum_option.is_none() {
debug::error!("{}:{}. log_returns_average.is_none()", file!(), line!());
return None;
}
let log_returns_average = log_returns_sum_option.unwrap()
/ FixedI64::saturating_from_integer(log_returns_unwrapped.len() as i64);
let result: Vec<FixedI64> = log_returns_unwrapped
.iter()
.map(|&x| (x - log_returns_average))
.collect();
Some(result)
}
fn recalc_aggregates(currency: &Currency, price_period: &PricePeriod) {
if *price_period != T::PeriodTypeForAggregates::get() {
return;
}
let prices = <Prices>::get(currency, price_period);
if prices.len() != T::MaxPricePoints::get() {
return;
}
if let Some(log_returns_diff) = Self::calv_log_return_diff_mid(currency) {
let volatility =
FixedI64::saturating_from_rational(1, log_returns_diff.len() as u64 - 1)
.saturating_mul(
log_returns_diff
.iter()
.map(|&x| x.sqr())
.fold(FixedI64::saturating_from_integer(0), |acc, x| acc + x),
)
.sqrt();
<Volatility>::insert(currency, volatility.unwrap());
for acurrency in Currency::iterator() {
if acurrency == currency {
continue;
}
if let Some(log_returns_diff_another) = Self::calv_log_return_diff_mid(acurrency) {
let another_volatility = <Volatility>::get(&acurrency);
if another_volatility.into_inner() == 0 {
continue;
}
let corr =
(FixedI64::saturating_from_rational(1, log_returns_diff.len() as u64 - 1)
/ volatility.unwrap()
/ another_volatility)
.saturating_mul(
log_returns_diff
.iter()
.enumerate()
.map(|(index, item)| {
item.saturating_mul(log_returns_diff_another[index])
})
.fold(FixedI64::saturating_from_integer(0), |acc, x| acc + x),
);
<Corellation>::insert(currency, acurrency, corr);
<Corellation>::insert(acurrency, currency, corr);
}
}
} else {
return;
}
}
}
impl<T: Trait> OnNewPrice for Module<T> {
fn on_new_price(currency: &Currency, price: FixedI64) {
let current_time = T::UnixTime::now().as_secs();
for aperiod in PricePeriod::iterator() {
let period_ms = aperiod.as_secs();
let last_update = <LastUpdate>::get(currency, aperiod);
let mut ms_passed = current_time - last_update;
if ms_passed == 0 {
<Prices>::mutate(currency, aperiod, |curr_vec| {
let len = curr_vec.len();
curr_vec[len - 1] = price;
});
Self::recalc_aggregates(¤cy, &aperiod);
} else if last_update == 0 {
<Prices>::mutate(currency, aperiod, |curr_vec| {
curr_vec.push(price);
});
<LastUpdate>::insert(currency, aperiod, current_time);
Self::recalc_aggregates(¤cy, &aperiod);
} else if ms_passed >= period_ms {
<Prices>::mutate(currency, aperiod, |curr_vec| {
let last_price: FixedI64;
{
last_price = *curr_vec.last().unwrap();
}
while ms_passed >= period_ms * 2 {
curr_vec.push(last_price);
ms_passed -= period_ms;
}
curr_vec.push(price);
if curr_vec.len() > T::MaxPricePoints::get() {
curr_vec.drain(..(curr_vec.len() - T::MaxPricePoints::get()));
}
});
<LastUpdate>::insert(currency, aperiod, current_time);
Self::recalc_aggregates(¤cy, &aperiod);
}
}
}
}
pub trait VolatilityGetter {
fn get_volatility(currency: &Currency) -> FixedI64;
fn get_correlation(c1: &Currency, c2: &Currency) -> FixedI64;
fn get_covariance(c1: &Currency, c2: &Currency) -> FixedI64 {
let v1 = Self::get_volatility(c1);
let v2 = Self::get_volatility(c2);
let covariance = v1 * v2 * Self::get_correlation(c1, c2);
covariance
}
}
impl<T: Trait> VolatilityGetter for Module<T> {
fn get_volatility(currency: &Currency) -> FixedI64 {
Self::volatility(currency)
}
fn get_correlation(c1: &Currency, c2: &Currency) -> FixedI64 {
Self::corellation(c1, c2)
}
}