1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
use anchor_lang::prelude::*;
use derivative::Derivative;
use static_assertions::const_assert_eq;
use std::mem::size_of;
#[zero_copy]
#[derive(Derivative, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct StablePriceModel {
pub stable_price: f64,
pub last_update_timestamp: u64,
pub delay_prices: [f64; 24],
pub delay_accumulator_price: f64,
pub delay_accumulator_time: u32,
pub delay_interval_seconds: u32,
pub delay_growth_limit: f32,
pub stable_growth_limit: f32,
pub last_delay_interval_index: u8,
#[derivative(Debug = "ignore")]
pub padding: [u8; 7],
#[derivative(Debug = "ignore")]
pub reserved: [u8; 48],
}
const_assert_eq!(
size_of::<StablePriceModel>(),
8 + 8 + 8 * 24 + 8 + 4 + 4 + 4 + 4 + 1 + 7 + 48
);
const_assert_eq!(size_of::<StablePriceModel>(), 288);
const_assert_eq!(size_of::<StablePriceModel>() % 8, 0);
impl Default for StablePriceModel {
fn default() -> Self {
Self {
stable_price: 0.0,
last_update_timestamp: 0,
delay_prices: [0.0; 24],
delay_accumulator_price: 0.0,
delay_accumulator_time: 0,
delay_interval_seconds: 60 * 60, delay_growth_limit: 0.06, stable_growth_limit: 0.0003, last_delay_interval_index: 0,
padding: Default::default(),
reserved: [0; 48],
}
}
}
impl StablePriceModel {
pub fn reset_to_price(&mut self, oracle_price: f64, now_ts: u64) {
self.stable_price = oracle_price;
self.delay_prices = [oracle_price; 24];
self.delay_accumulator_price = 0.0;
self.delay_accumulator_time = 0;
self.last_update_timestamp = now_ts;
}
pub fn delay_interval_index(&self, timestamp: u64) -> u8 {
((timestamp / self.delay_interval_seconds as u64) % self.delay_prices.len() as u64) as u8
}
#[inline(always)]
fn growth_clamped(target: f64, prev: f64, growth_limit: f64) -> f64 {
let max = prev * (1.0 + growth_limit);
let min = prev * (1.0 - growth_limit);
target.clamp(min, max)
}
pub fn update(&mut self, now_ts: u64, oracle_price: f64) {
let dt = now_ts.saturating_sub(self.last_update_timestamp);
let min_dt = 10;
let max_dt = 10 * 60; if dt < min_dt {
return;
}
let full_delay_passed =
dt > self.delay_prices.len() as u64 * self.delay_interval_seconds as u64;
let dt_limited = dt.min(max_dt) as f64;
self.last_update_timestamp = now_ts;
self.delay_accumulator_time += dt as u32;
self.delay_accumulator_price += oracle_price * dt_limited;
let delay_interval_index = self.delay_interval_index(now_ts);
if delay_interval_index != self.last_delay_interval_index {
let new_delay_price = {
let prev = if self.last_delay_interval_index == 0 {
self.delay_prices[self.delay_prices.len() - 1]
} else {
self.delay_prices[self.last_delay_interval_index as usize - 1]
};
let avg = self.delay_accumulator_price / (self.delay_accumulator_time as f64);
Self::growth_clamped(avg, prev, self.delay_growth_limit as f64)
};
if full_delay_passed {
self.delay_prices.fill(new_delay_price);
} else if delay_interval_index > self.last_delay_interval_index {
self.delay_prices
[self.last_delay_interval_index as usize..delay_interval_index as usize]
.fill(new_delay_price);
} else {
self.delay_prices[self.last_delay_interval_index as usize..].fill(new_delay_price);
self.delay_prices[..delay_interval_index as usize].fill(new_delay_price);
}
self.delay_accumulator_price = 0.0;
self.delay_accumulator_time = 0;
self.last_delay_interval_index = delay_interval_index;
}
let delay_price = self.delay_prices[delay_interval_index as usize];
self.stable_price = {
let prev_stable_price = self.stable_price;
let fraction = if delay_price >= prev_stable_price {
prev_stable_price / delay_price
} else {
delay_price / prev_stable_price
};
let growth_limit = (self.stable_growth_limit as f64) * fraction * fraction * dt_limited;
Self::growth_clamped(oracle_price, prev_stable_price, growth_limit)
};
}
}
#[cfg(test)]
mod tests {
use super::*;
fn run_and_print(
model: &mut StablePriceModel,
start: u64,
dt: u64,
steps: u64,
price: fn(u64) -> f64,
) -> u64 {
println!("step,timestamp,stable_price,delay_price");
for i in 0..steps {
let time = start + dt * (i + 1);
model.update(time, price(time));
println!(
"{i},{time},{},{}",
model.stable_price, model.delay_prices[model.last_delay_interval_index as usize]
);
}
start + dt * steps
}
#[test]
fn test_stable_price_10x() {
let mut model = StablePriceModel::default();
model.reset_to_price(1.0, 0);
let mut t;
t = run_and_print(&mut model, 0, 60, 60, |_| 10.0);
assert!((model.stable_price - 1.8).abs() < 0.1);
assert_eq!(model.delay_prices[1..], [1.0; 23]);
assert!((model.delay_prices[0] - 1.06).abs() < 0.01);
assert_eq!(model.last_delay_interval_index, 1);
assert_eq!(model.delay_accumulator_time, 0);
assert_eq!(model.delay_accumulator_price, 0.0);
t = run_and_print(&mut model, t, 10, 6 * 60, |_| 10.0);
assert!((model.stable_price - 2.3).abs() < 0.1);
assert_eq!(model.delay_prices[2..], [1.0; 22]);
assert!((model.delay_prices[0] - 1.06).abs() < 0.01);
assert!((model.delay_prices[1] - 1.06 * 1.06).abs() < 0.01);
assert_eq!(model.last_delay_interval_index, 2);
assert_eq!(model.delay_accumulator_time, 0);
assert_eq!(model.delay_accumulator_price, 0.0);
t = run_and_print(&mut model, t, 300, 12 * 23, |_| 10.0);
assert!((model.stable_price - 7.4).abs() < 0.1);
assert!(model.delay_prices[0] > model.delay_prices[23]);
assert!(model.delay_prices[23] > model.delay_prices[22]);
assert!(model.delay_prices[1] < model.delay_prices[0]);
assert!(model.delay_prices[1] < model.delay_prices[2]);
assert_eq!(model.last_delay_interval_index, 1);
println!("{t}");
}
#[test]
fn test_stable_price_characteristics_upwards() {
let mut model = StablePriceModel::default();
model.reset_to_price(1.0, 0);
let mut last = 1;
for i in 0..100000 {
model.update(60 * (i + 1), 1000.0);
let now = model.stable_price as i32;
if now > last {
last = now;
println!("reached {now}x after {i} steps, {} hours", i as f64 / 60.0);
if now == 10 {
break;
}
}
}
}
#[test]
fn test_stable_price_characteristics_downwards() {
let mut model = StablePriceModel::default();
let init = 10000.0;
model.reset_to_price(init, 0);
let mut last = 1;
for i in 0..100000 {
model.update(60 * (i + 1), 0.0);
let now = (init / model.stable_price) as i32;
if now > last {
last = now;
println!(
"reached 1/{now}x after {i} steps, {} hours",
i as f64 / 60.0
);
if now == 10 {
break;
}
}
}
}
#[test]
fn test_stable_price_average() {
let mut model = StablePriceModel {
delay_growth_limit: 10.00,
..StablePriceModel::default()
};
model.reset_to_price(1.0, 0);
run_and_print(&mut model, 0, 60, 60, |t| if t > 1800 { 2.0 } else { 1.0 });
println!("{}", model.delay_prices[0]);
assert!((model.delay_prices[0] - 1.5).abs() < 0.01);
}
}