rusty_model/data/
bar.rs

1//! Bar data structures for various time aggregations
2
3use rust_decimal::Decimal;
4use rusty_common::collections::FxHashMap;
5use smartstring::alias::String;
6
7/// Bar aggregation type
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum BarAggregation {
10    /// Time-based aggregation.
11    Time,
12    /// Tick-based aggregation.
13    Tick,
14    /// Volume-based aggregation.
15    Volume,
16    /// Dollar-based aggregation.
17    Dollar,
18    /// Second-based aggregation.
19    Second,
20    /// Minute-based aggregation.
21    Minute,
22    /// Hour-based aggregation.
23    Hour,
24    /// Day-based aggregation.
25    Day,
26}
27
28/// Bar specification
29#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30pub struct BarSpecification {
31    /// The aggregation type.
32    pub aggregation: BarAggregation,
33    /// The aggregation step.
34    pub step: u64,
35}
36
37impl BarSpecification {
38    /// Create a new bar specification
39    #[must_use]
40    pub const fn new(aggregation: BarAggregation, step: u64) -> Self {
41        Self { aggregation, step }
42    }
43}
44
45/// Bar type combining symbol and specification
46#[derive(Debug, Clone, PartialEq, Eq, Hash)]
47pub struct BarType {
48    /// The trading symbol.
49    pub symbol: String,
50    /// The bar specification.
51    pub specification: BarSpecification,
52}
53
54impl BarType {
55    /// Create a new bar type
56    #[must_use]
57    pub const fn new(symbol: String, specification: BarSpecification) -> Self {
58        Self {
59            symbol,
60            specification,
61        }
62    }
63
64    /// Get the bar specification
65    #[must_use]
66    pub const fn get_spec(&self) -> &BarSpecification {
67        &self.specification
68    }
69
70    /// Create a standard time-based bar type
71    #[must_use]
72    pub const fn new_standard(symbol: String, aggregation: BarAggregation, step: u64) -> Self {
73        Self {
74            symbol,
75            specification: BarSpecification::new(aggregation, step),
76        }
77    }
78}
79
80impl std::fmt::Display for BarType {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        write!(
83            f,
84            "{}[{:?}:{}]",
85            self.symbol, self.specification.aggregation, self.specification.step
86        )
87    }
88}
89
90/// OHLCV Bar data
91#[repr(align(64))] // Cache-line aligned for HFT performance
92#[derive(Debug, Clone)]
93pub struct Bar {
94    /// The bar type.
95    pub bar_type: BarType,
96    /// The opening price.
97    pub open: Decimal,
98    /// The highest price.
99    pub high: Decimal,
100    /// The lowest price.
101    pub low: Decimal,
102    /// The closing price.
103    pub close: Decimal,
104    /// The trading volume.
105    pub volume: Decimal,
106    /// The bar close time in nanoseconds.
107    pub timestamp_ns: u64,
108}
109
110impl Bar {
111    /// Create a new bar
112    #[must_use]
113    pub const fn new(
114        bar_type: BarType,
115        open: Decimal,
116        high: Decimal,
117        low: Decimal,
118        close: Decimal,
119        volume: Decimal,
120        timestamp_ns: u64,
121    ) -> Self {
122        Self {
123            bar_type,
124            open,
125            high,
126            low,
127            close,
128            volume,
129            timestamp_ns,
130        }
131    }
132}
133
134/// Get the interval in nanoseconds for a bar type
135#[must_use]
136pub const fn get_bar_interval_ns(bar_type: &BarType) -> u64 {
137    match bar_type.specification.aggregation {
138        BarAggregation::Second => bar_type.specification.step * 1_000_000_000,
139        BarAggregation::Minute => bar_type.specification.step * 60 * 1_000_000_000,
140        BarAggregation::Hour => bar_type.specification.step * 3600 * 1_000_000_000,
141        BarAggregation::Day => bar_type.specification.step * 86400 * 1_000_000_000,
142        _ => 0, // Other aggregations don't have fixed time intervals
143    }
144}
145
146/// Bar cache for storing historical bars
147#[derive(Debug)]
148pub struct BarCache {
149    bars: FxHashMap<BarType, Vec<Bar>>,
150}
151
152impl BarCache {
153    /// Create a new bar cache
154    #[must_use]
155    pub fn new() -> Self {
156        Self {
157            bars: FxHashMap::default(),
158        }
159    }
160
161    /// Add a bar to the cache
162    pub fn add_bar(&mut self, bar: Bar) {
163        self.bars.entry(bar.bar_type.clone()).or_default().push(bar);
164    }
165
166    /// Get bars for a specific bar type
167    #[must_use]
168    pub fn get_bars(&self, bar_type: &BarType) -> Option<&Vec<Bar>> {
169        self.bars.get(bar_type)
170    }
171
172    /// Clear the cache
173    pub fn clear(&mut self) {
174        self.bars.clear();
175    }
176}
177
178impl Default for BarCache {
179    fn default() -> Self {
180        Self::new()
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    #[test]
189    fn test_bar_specification_new() {
190        let spec = BarSpecification::new(BarAggregation::Minute, 5);
191        assert_eq!(spec.aggregation, BarAggregation::Minute);
192        assert_eq!(spec.step, 5);
193    }
194
195    #[test]
196    fn test_bar_specification_new_const_context() {
197        // Verify that BarSpecification::new can be used in const contexts
198        const TIME_SPEC: BarSpecification = BarSpecification::new(BarAggregation::Time, 1);
199        const MINUTE_SPEC: BarSpecification = BarSpecification::new(BarAggregation::Minute, 5);
200        const HOUR_SPEC: BarSpecification = BarSpecification::new(BarAggregation::Hour, 1);
201        const DAY_SPEC: BarSpecification = BarSpecification::new(BarAggregation::Day, 1);
202
203        assert_eq!(TIME_SPEC.aggregation, BarAggregation::Time);
204        assert_eq!(TIME_SPEC.step, 1);
205        assert_eq!(MINUTE_SPEC.aggregation, BarAggregation::Minute);
206        assert_eq!(MINUTE_SPEC.step, 5);
207        assert_eq!(HOUR_SPEC.aggregation, BarAggregation::Hour);
208        assert_eq!(HOUR_SPEC.step, 1);
209        assert_eq!(DAY_SPEC.aggregation, BarAggregation::Day);
210        assert_eq!(DAY_SPEC.step, 1);
211    }
212
213    #[test]
214    fn test_bar_type_new_requires_string() {
215        // Note: BarType::new is marked as const fn but takes a String parameter
216        // This should not compile in const context due to String allocation
217        // This test just verifies it works in normal context
218        let bar_type = BarType::new(
219            String::from("BTC-USD"),
220            BarSpecification::new(BarAggregation::Minute, 1),
221        );
222        assert_eq!(bar_type.symbol.as_str(), "BTC-USD");
223        assert_eq!(bar_type.specification.aggregation, BarAggregation::Minute);
224        assert_eq!(bar_type.specification.step, 1);
225    }
226}