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.
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 periodt
Pt,0
exchange_rate corresponds to YYYYMMDDT00:00:00ZPt,1
exchange_rate corresponds to YYYYMMDDT01:00:00Z
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