abstract_std/objects/
time_weighted_average.rs

1//! # Time Weighted Average (TWA) helper
2//!
3//! A time weighted average is an accumulating value that is updated irregularly.
4//! Whenever an update is applied, the time between the current update and the last update is used, along with the current value,
5//! to accumulate the cumulative value.
6//!
7//!
8
9use cosmwasm_std::{Addr, Decimal, Env, QuerierWrapper, Storage, Timestamp, Uint128};
10use cw_storage_plus::Item;
11use schemars::JsonSchema;
12use serde::{Deserialize, Serialize};
13
14use crate::AbstractResult;
15
16pub const DEFAULT_PRECISION: u8 = 6;
17
18/// Time Weighted Average (TWA) helper
19pub struct TimeWeightedAverage(Item<TimeWeightedAverageData>);
20
21impl TimeWeightedAverage {
22    pub const fn new(key: &'static str) -> Self {
23        Self(Item::new(key))
24    }
25    pub fn instantiate(
26        &self,
27        store: &mut dyn Storage,
28        env: &Env,
29        precision: Option<u8>,
30        averaging_period: u64,
31    ) -> AbstractResult<()> {
32        let block_time = env.block.time;
33
34        let twa = TimeWeightedAverageData {
35            cumulative_value: 0,
36            last_block_time: block_time,
37            precision: precision.unwrap_or(DEFAULT_PRECISION),
38            average_value: Decimal::zero(),
39            averaging_period,
40            last_averaging_cumulative_value: 0,
41            last_averaging_block_time: block_time,
42            last_averaging_block_height: env.block.height,
43        };
44        self.0.save(store, &twa).map_err(Into::into)
45    }
46
47    /// Applies the current value to the TWA for the duration since the last update
48    /// and returns the cumulative value and block time.
49    pub fn accumulate(
50        &self,
51        env: &Env,
52        store: &mut dyn Storage,
53        current_value: Decimal,
54    ) -> AbstractResult<Option<u128>> {
55        let mut twa = self.0.load(store)?;
56        let block_time = env.block.time;
57        if block_time <= twa.last_block_time {
58            return Ok(None);
59        }
60
61        let time_elapsed = Uint128::from(block_time.seconds() - twa.last_block_time.seconds());
62        twa.last_block_time = block_time;
63
64        if !current_value.is_zero() {
65            twa.cumulative_value = twa
66                .cumulative_value
67                .wrapping_add(time_elapsed.mul_floor(current_value).u128());
68        };
69        self.0.save(store, &twa)?;
70        Ok(Some(twa.cumulative_value))
71    }
72
73    pub fn get_value(&self, store: &dyn Storage) -> AbstractResult<Decimal> {
74        Ok(self.0.load(store)?.average_value)
75    }
76
77    pub fn load(&self, store: &dyn Storage) -> AbstractResult<TimeWeightedAverageData> {
78        self.0.load(store).map_err(Into::into)
79    }
80
81    pub fn query(
82        &self,
83        querier: &QuerierWrapper,
84        remote_contract_addr: Addr,
85    ) -> AbstractResult<TimeWeightedAverageData> {
86        self.0
87            .query(querier, remote_contract_addr)
88            .map_err(Into::into)
89    }
90
91    /// Get average value, updates when possible
92    pub fn try_update_value(
93        &self,
94        env: &Env,
95        store: &mut dyn Storage,
96    ) -> AbstractResult<Option<Decimal>> {
97        let mut twa = self.0.load(store)?;
98
99        let block_time = env.block.time;
100
101        let time_elapsed = block_time.seconds() - twa.last_averaging_block_time.seconds();
102
103        // Ensure that at least one full period has passed since the last update
104        if time_elapsed < twa.averaging_period {
105            return Ok(None);
106        }
107
108        // (current_cum - last_cum) / time
109        let new_average_value = Decimal::from_ratio(
110            twa.cumulative_value
111                .wrapping_sub(twa.last_averaging_cumulative_value),
112            time_elapsed,
113        );
114
115        twa = TimeWeightedAverageData {
116            average_value: new_average_value,
117            last_averaging_block_time: block_time,
118            last_averaging_cumulative_value: twa.cumulative_value,
119            ..twa
120        };
121        self.0.save(store, &twa)?;
122        Ok(Some(new_average_value))
123    }
124
125    pub fn update_settings(
126        &self,
127        _env: &Env,
128        store: &mut dyn Storage,
129        averaging_period: u64,
130    ) -> AbstractResult<()> {
131        let mut twa = self.0.load(store)?;
132        twa.averaging_period = averaging_period;
133        self.0.save(store, &twa).map_err(Into::into)
134    }
135}
136
137#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
138pub struct TimeWeightedAverageData {
139    // settings for accumulating value data
140    pub precision: u8,
141    pub last_block_time: Timestamp,
142    pub cumulative_value: u128,
143
144    // Data to get average value
145    pub last_averaging_block_time: Timestamp,
146    pub last_averaging_block_height: u64,
147    pub last_averaging_cumulative_value: u128,
148    pub averaging_period: u64,
149    /// The requested average value
150    pub average_value: Decimal,
151}
152
153impl TimeWeightedAverageData {
154    pub fn needs_refresh(&self, env: &Env) -> bool {
155        let block_time = env.block.time;
156
157        let time_elapsed = block_time.seconds() - self.last_averaging_block_time.seconds();
158
159        // At least one full period has passed since the last update
160        time_elapsed >= self.averaging_period
161    }
162}