tawasuyu

tawasuyu/brahman

Rust 326TOML 123JSON 17Markdown 144606 Definiciones

⎇ 72758e75ce515f355f27615671c95ecaf8c2a8bc crates/modules/ui_engine/widgets/lapaloma-financial/src/ohlc_buffer.rs Rust · 186 líneas · 4.7 KB Historia Quién tocó qué crudo

feat(lapaloma-financial): OHLC + candlesticks con preserva-volatilidad d1ce4c897 · sergio ·

1//! `OhlcBuffer` — buffer plano de bars con stride 6 `f32`.
2//!
3//! Memoria contigua: `[t0, o0, h0, l0, c0, v0, t1, o1, …]`.
4//! Acceso O(1) por índice; un memcpy completo para hidratar desde
5//! una fuente externa.
6
7/// Una barra OHLC + volumen. Valor leído del buffer; no es la
8/// representación de almacenamiento (que vive como `[f32; 6]`).
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct Bar {
11    pub t: f32,
12    pub o: f32,
13    pub h: f32,
14    pub l: f32,
15    pub c: f32,
16    pub v: f32,
17}
18
19impl Bar {
20    pub fn is_bull(self) -> bool {
21        self.c > self.o
22    }
23    pub fn is_bear(self) -> bool {
24        self.c < self.o
25    }
26}
27
28pub const STRIDE: usize = 6;
29
30#[derive(Debug, Clone, Default)]
31pub struct OhlcBuffer {
32    bars: Vec<f32>,
33    revision: u64,
34}
35
36impl OhlcBuffer {
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    pub fn with_capacity(n: usize) -> Self {
42        Self {
43            bars: Vec::with_capacity(n * STRIDE),
44            revision: 0,
45        }
46    }
47
48    pub fn from_raw(bars: Vec<f32>) -> Self {
49        assert!(bars.len() % STRIDE == 0, "OhlcBuffer: stride 6 required");
50        Self { bars, revision: 0 }
51    }
52
53    pub fn push_bar(&mut self, b: Bar) {
54        self.bars.push(b.t);
55        self.bars.push(b.o);
56        self.bars.push(b.h);
57        self.bars.push(b.l);
58        self.bars.push(b.c);
59        self.bars.push(b.v);
60        self.revision = self.revision.wrapping_add(1);
61    }
62
63    pub fn push_values(&mut self, t: f32, o: f32, h: f32, l: f32, c: f32, v: f32) {
64        self.push_bar(Bar { t, o, h, l, c, v });
65    }
66
67    pub fn len(&self) -> usize {
68        self.bars.len() / STRIDE
69    }
70
71    pub fn is_empty(&self) -> bool {
72        self.bars.is_empty()
73    }
74
75    pub fn bar(&self, i: usize) -> Bar {
76        let off = i * STRIDE;
77        Bar {
78            t: self.bars[off],
79            o: self.bars[off + 1],
80            h: self.bars[off + 2],
81            l: self.bars[off + 3],
82            c: self.bars[off + 4],
83            v: self.bars[off + 5],
84        }
85    }
86
87    /// Slice plano del buffer subyacente.
88    pub fn bars(&self) -> &[f32] {
89        &self.bars
90    }
91
92    pub fn revision(&self) -> u64 {
93        self.revision
94    }
95
96    pub fn clear(&mut self) {
97        self.bars.clear();
98        self.revision = self.revision.wrapping_add(1);
99    }
100
101    /// Min/max de `low` y `high` sobre todo el buffer.
102    /// Útil para autoscale del Y axis.
103    pub fn price_range(&self) -> Option<(f32, f32)> {
104        if self.is_empty() {
105            return None;
106        }
107        let mut lo = f32::INFINITY;
108        let mut hi = f32::NEG_INFINITY;
109        for i in 0..self.len() {
110            let b = self.bar(i);
111            if b.l < lo {
112                lo = b.l;
113            }
114            if b.h > hi {
115                hi = b.h;
116            }
117        }
118        Some((lo, hi))
119    }
120
121    /// Rango temporal `[t_min, t_max]`. None si vacío.
122    pub fn time_range(&self) -> Option<(f32, f32)> {
123        if self.is_empty() {
124            return None;
125        }
126        let first = self.bars[0];
127        let last = self.bars[self.bars.len() - STRIDE];
128        Some((first, last))
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn push_y_lectura() {
138        let mut b = OhlcBuffer::with_capacity(2);
139        b.push_values(1.0, 10.0, 12.0, 9.0, 11.0, 100.0);
140        b.push_values(2.0, 11.0, 13.0, 10.0, 10.5, 80.0);
141        assert_eq!(b.len(), 2);
142        assert_eq!(b.bar(0).c, 11.0);
143        assert_eq!(b.bar(1).h, 13.0);
144    }
145
146    #[test]
147    fn bull_y_bear() {
148        let bull = Bar { t: 0.0, o: 10.0, h: 11.0, l: 9.0, c: 10.5, v: 0.0 };
149        let bear = Bar { t: 0.0, o: 10.0, h: 11.0, l: 9.0, c: 9.5, v: 0.0 };
150        assert!(bull.is_bull());
151        assert!(!bull.is_bear());
152        assert!(bear.is_bear());
153        assert!(!bear.is_bull());
154    }
155
156    #[test]
157    fn price_range_correcto() {
158        let mut b = OhlcBuffer::new();
159        b.push_values(0.0, 10.0, 15.0, 8.0, 12.0, 0.0);
160        b.push_values(1.0, 12.0, 14.0, 7.0, 9.0, 0.0);
161        b.push_values(2.0, 9.0, 11.0, 9.0, 10.0, 0.0);
162        let (lo, hi) = b.price_range().unwrap();
163        assert_eq!(lo, 7.0);
164        assert_eq!(hi, 15.0);
165    }
166
167    #[test]
168    fn time_range() {
169        let mut b = OhlcBuffer::new();
170        b.push_values(10.0, 0.0, 0.0, 0.0, 0.0, 0.0);
171        b.push_values(50.0, 0.0, 0.0, 0.0, 0.0, 0.0);
172        b.push_values(100.0, 0.0, 0.0, 0.0, 0.0, 0.0);
173        assert_eq!(b.time_range(), Some((10.0, 100.0)));
174    }
175
176    #[test]
177    fn revision_bumps_en_push_y_clear() {
178        let mut b = OhlcBuffer::new();
179        let r0 = b.revision();
180        b.push_values(0.0, 1.0, 1.0, 1.0, 1.0, 1.0);
181        assert_ne!(r0, b.revision());
182        let r1 = b.revision();
183        b.clear();
184        assert_ne!(r1, b.revision());
185    }
186}