templar_proxy_oracle_kernel/proxy/circuit_breaker/rule/
monotonic_run.rs

1#[cfg(feature = "schemars")]
2use alloc::borrow::ToOwned;
3#[cfg(any(feature = "borsh", feature = "schemars"))]
4use alloc::string::ToString;
5use templar_primitives::Decimal;
6
7use crate::proxy::circuit_breaker::{
8    math::{classify_step_change, StepChange},
9    CircuitBreakerRule, Observation, RingBuffer,
10};
11
12serialize! {
13    #[derive(Debug, Clone, PartialEq, Eq)]
14    /// Trips when recent price changes form a long enough same-direction run.
15    pub struct MonotonicRun {
16        /// Number of consecutive significant same-direction steps required to trip.
17        pub max_streak: u32,
18        /// Minimum relative absolute change for a step to count toward the streak.
19        pub min_relative_step_change: Decimal,
20    }
21}
22
23impl CircuitBreakerRule for MonotonicRun {
24    fn should_trip(&self, history: &RingBuffer<Observation>) -> bool {
25        if self.max_streak == 0 || history.len() < 2 {
26            return false;
27        }
28
29        let mut direction = None;
30        let mut streak = 0_u32;
31
32        for current_index in (1..history.len()).rev() {
33            let (Some(previous), Some(current)) =
34                (history.get(current_index - 1), history.get(current_index))
35            else {
36                break;
37            };
38            let step_change = classify_step_change(
39                &previous.price,
40                &current.price,
41                self.min_relative_step_change,
42            );
43
44            if step_change == StepChange::Minor {
45                break;
46            }
47
48            if let Some(direction) = direction {
49                if direction != step_change {
50                    break;
51                }
52            } else {
53                direction = Some(step_change);
54            }
55
56            streak = streak.saturating_add(1);
57            if streak >= self.max_streak {
58                return true;
59            }
60        }
61
62        false
63    }
64}