Jan 18, 2024

Chainsight Showcase Description, Building a complex computational measure (RVOL) for Algorithm Lens

by Megared

In the previous installment of our Deep Dive into Showcase series, we discussed Price Oracle. It represented the simplest design pattern in Chainsight. This time, instead of propagating the acquired values as they are, we will introduce Realized Volatility from Showcase as an example of using some data source as a calculation input and constructing a new indicator using Algorithm Lens logic.

chainsight_deepdive_into_showcase-rvol.png

This Realized Volatility Oracle is built from a single Snapshot Indexer EVM (or HTTPS), Algorithm Lens, and Relayer, for a total of three components.

https://github.com/horizonx-tech/chainsight-showcase/tree/main/volatility/volatility_eth

Snapshot Indexer EVM and Relayer have the same role and equivalent usage as those introduced in the previous Price Oracle article. This time, we will focus on Algorithm Lens.

What is Realized Volatility?

Realized volatility (RVOL), as one method of calculating volatility, has attracted attention in the financial community as a measure of price stability. There is also Implied Volatility, which will be discussed in another article.

RVOL is derived by using the exchange_rate Pt corresponding to a certain time range t calculated by the above formula, using N number of data for the period under consideration, and applying the following formula.

ex: Calculate daily RVOL

  • t: 1day
  • N: 24
  • Pt,i: The i th exchange rate belonging to period t
    • Pt,0 exchange_rate corresponds to YYYYMMDDT00:00:00Z
    • Pt,1 exchange_rate corresponds to YYYYMMDDT01:00:00Z
formula-rvol.png

where P is the asset price, and N is the number of observations per day.

About RVOL Project

In one example in this Showcase, this RVOL is calculated from exchange rate data. First, what would be the logic for calculating RVOL from a list of prices to be calculated?

Code to calculate Realized Volatility

use primitive_types::U256;
fn calc_realized_volatility(prices: Vec<U256>) -> f64 {
    let mut squared_r: Vec<U256> = Vec::new();
		// [1] Calculate all r_ti
    for i in 0..prices.len() - 1 {
				let pt = prices.get(i + 1).unwrap()
				let pt_minus_1 = prices.get(i).unwrap()
        let r = (pt / pt_minus_1).ln().mul(100_f64);
        squared_r.push(r.mul(r));
    }
		// [2] Calculate RVOL
    let sum_of_squared_r: f64 = squared_r.iter().map(|x| x.as_u128() as f64).sum();
    (sum_of_squared_r / squared_r.len() as f64).sqrt() * (prices_len as f64).sqrt()
}

The formula can be written in Rust code as shown above. f64 in Rust is easy to write because the logarithm calculation is built in.

We are using primitive_types::U256 as input to the RVOL calculation function, but we need to get continuous price data, not single data, from the Snapshot Indexer and then convert that data to primitive_types::U256. Let's look at how to do this next.

Code to convert to U256

pub async fn calculate(targets: Vec<String>) -> LensValue {
    let prices = get_chainlink_ethusd(targets.get(0usize).unwrap().clone(), 24)
        .await
        .unwrap();

		...
}

fn convert_to_u256(prices: Vec<Snapshot>) -> Vec<U256> {
    let mut result: Vec<U256> = Vec::new();
    for i in 0..prices.len() {
        let price = prices.get(i).unwrap();
        result.push(U256::from_dec_str(price.value.as_str()).unwrap())
    }
    result
}

Vec<Snapshot> is the input to the calculation obtained from the data source, Snapshot Indexer. The type of the price value in this Snapshot is String, but the value is in hexadecimal and can be converted to U256 with U256::from_dec_str.

Let's take a deeper look at the data that can be retrieved from Snapshot Indexer. In this case, data is retrieved from Snapshot Indexer by specifying the following interface.

identifier: 'get_top_snapshots : (nat64) -> (vec Snapshot)'
candid_file_path: interfaces/chainlink_ethusd.did

About Query Functions in Snapshot Indexer

This is the Query function built into Snapshot Indexer to retrieve data. What functions are actually built in? Snapshot Indexer implements a number of query functions that can be used universally to facilitate retrieval of stored data.

Query Functions from Snapshot Indexer’s did file

get_last_snapshot : () -> (Snapshot) query;
get_last_snapshot_value : () -> (SnapshotValue) query;
get_snapshot : (nat64) -> (Snapshot) query;
get_snapshot_value : (nat64) -> (SnapshotValue) query;
get_snapshots : () -> (vec Snapshot) query;
get_top_snapshot_values : (nat64) -> (vec SnapshotValue) query;
get_top_snapshots : (nat64) -> (vec Snapshot) query;

get_snapshot can be used to retrieve data existing in a specified index. get_last_snapshot can be used to retrieve the latest data. get_top_snapshot can be used to retrieve a specified number of snaps from the newest data. Since Snapshot also includes execution time in the data, a function called xxx_value is also included to retrieve this data only.

type Snapshot = record { value : SnapshotValue; timestamp : nat64 };

In this RVOL, we use one day's worth of data from the Snapshot Indexer, which retrieves data every hour, as input for the calculation, so we use get_top_snapshots to retrieve 24 data points.

let prices = get_chainlink_ethusd(targets.get(0usize).unwrap().clone(), 24)

Now that all the necessary parts are in place, the following is the code for the completed version.

use lens_vol_ethusd_accessors::*;
use lens_vol_ethusd_bindings::chainlink_ethusd::Snapshot;
pub type LensValue = u128;
use primitive_types::U256;

pub async fn calculate(targets: Vec<String>) -> LensValue {
    let prices = get_chainlink_ethusd(targets.get(0usize).unwrap().clone(), 24)
        .await
        .unwrap();

    to_rvol(prices)
}

fn to_rvol(prices: Vec<Box<Snapshot>>) -> u128 {
    let rates = convert_to_u256(prices);
    let rvol = calc_realized_volatility(rates);
    (rvol * 1000000000000000000.0) as u128 // NOTE: to scale over integer
}

fn convert_to_u256(prices: Vec<Snapshot>) -> Vec<U256> {
    let mut result: Vec<U256> = Vec::new();
    for i in 0..prices.len() {
        let price = prices.get(i).unwrap();
        result.push(U256::from_dec_str(price.value.as_str()).unwrap())
    }
    result
}

fn calc_realized_volatility(prices: Vec<U256>) -> f64 {
    let mut squared_r: Vec<U256> = Vec::new();
		// [1] Calculate all r_ti
    for i in 0..prices.len() - 1 {
				let pt = prices.get(i + 1).unwrap()
				let pt_minus_1 = prices.get(i).unwrap()
        let r = (pt / pt_minus_1).ln().mul(100_f64);
        squared_r.push(r.mul(r));
    }
		// [2] Calculate RVOL
    let sum_of_squared_r: f64 = squared_r.iter().map(|x| x.as_u128() as f64).sum();
    (sum_of_squared_r / squared_r.len() as f64).sqrt() * (prices_len as f64).sqrt()
}

#[cfg(test)]
pub mod tests {
    use super::*;

    #[test]
    fn test_calcrealized_volatility() {...}
}

Thank you for your patience up to this point. As you can see, Algorithm Lens allows you to express your own computational logic from your own data source.


This article introduced a single data source and Algorithm Lens in RVOL's Showcase. Please look forward to the next article!

Follow us on Twitter and Medium for updates! We look forward to your participation.

Written by Megared@Chainsight