use sep_40_oracle::PriceData;
use soroban_sdk::Vec;
pub(crate) fn order_prices_by_timestamp(prices: &Vec<PriceData>) -> Vec<PriceData> {
let mut ordered_prices: Vec<PriceData> = Vec::new(prices.env());
for price in prices.iter() {
if ordered_prices.is_empty()
|| price.timestamp
>= ordered_prices
.get_unchecked(ordered_prices.len() - 1)
.timestamp
{
ordered_prices.push_back(price.clone());
continue;
}
for (i, ordered_price) in ordered_prices.iter().enumerate() {
if price.timestamp < ordered_price.timestamp {
ordered_prices.insert(i as u32, price.clone());
break;
}
}
}
ordered_prices
}
pub(crate) fn fixed_div_floor(dividend: i128, divisor: i128, decimals: u32) -> Option<i128> {
if dividend <= 0 || divisor <= 0 {
return None;
}
let ashift = core::cmp::min(38 - dividend.ilog10(), decimals);
let bshift = core::cmp::max(decimals - ashift, 0);
let mut vdividend = dividend;
let mut vdivisor = divisor;
if ashift > 0 {
let svdividend = vdividend.checked_mul(10_i128.pow(ashift));
if svdividend.is_none() {
return None;
}
vdividend = svdividend?;
}
if bshift > 0 {
vdivisor /= 10_i128.pow(bshift);
}
if vdivisor <= 0 {
return None;
}
Some(vdividend / vdivisor)
}
pub fn twap(prices: &Vec<PriceData>) -> Option<i128> {
if prices.is_empty() {
return None;
}
let mut sum = 0i128;
let mut count = 0i128;
for price_data in prices.iter() {
if price_data.price > 0 && price_data.timestamp > 0 {
sum += price_data.price;
count += 1;
}
}
if count == 0 {
return None;
}
Some(sum / count)
}
pub fn x_twap(prices_a: &Vec<PriceData>, prices_b: &Vec<PriceData>, decimals: u32) -> Option<i128> {
if prices_a.is_empty() || prices_b.is_empty() {
return None;
}
let x_prices =
x_prices(prices_a, prices_b, decimals).unwrap_or_else(|| Vec::new(prices_a.env()));
if x_prices.is_empty() {
return None;
}
twap(&x_prices)
}
pub fn x_price(price_a: &PriceData, price_b: &PriceData, decimals: u32) -> Option<i128> {
if price_a.price <= 0 || price_b.price <= 0 {
return None;
}
fixed_div_floor(price_a.price, price_b.price, decimals)
}
pub fn x_prices(
prices_a: &Vec<PriceData>,
prices_b: &Vec<PriceData>,
decimals: u32,
) -> Option<Vec<PriceData>> {
let mut result = Vec::new(prices_a.env());
let sorted_prices_a = order_prices_by_timestamp(prices_a);
let sorted_prices_b = order_prices_by_timestamp(prices_b);
for i in 0..prices_a.len().max(prices_b.len()) {
let mut price_a = sorted_prices_a.get(i).unwrap_or_else(|| PriceData {
price: 0,
timestamp: 0,
});
let mut price_b = sorted_prices_b.get(i).unwrap_or_else(|| PriceData {
price: 0,
timestamp: 0,
});
fn find_closest_price(target_timestamp: u64, sorted_prices: &Vec<PriceData>) -> PriceData {
let mut price = PriceData {
price: 0,
timestamp: 0,
};
for i in 0..sorted_prices.len() {
let candidate = sorted_prices.get_unchecked(i);
if candidate.timestamp > target_timestamp {
break;
}
price = candidate;
}
price
}
if price_a.timestamp > price_b.timestamp {
price_b = find_closest_price(price_a.timestamp, &sorted_prices_b);
} else if price_b.timestamp > price_a.timestamp {
price_a = find_closest_price(price_b.timestamp, &sorted_prices_a);
}
let cross_price = x_price(&price_a, &price_b, decimals);
if cross_price.is_none() {
continue;
}
result.push_back(PriceData {
price: cross_price.unwrap(),
timestamp: price_a.timestamp.max(price_b.timestamp),
});
}
Some(result)
}
mod tests;