1use rust_decimal::Decimal;
4use rusty_common::collections::FxHashMap;
5use smartstring::alias::String;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum BarAggregation {
10 Time,
12 Tick,
14 Volume,
16 Dollar,
18 Second,
20 Minute,
22 Hour,
24 Day,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30pub struct BarSpecification {
31 pub aggregation: BarAggregation,
33 pub step: u64,
35}
36
37impl BarSpecification {
38 #[must_use]
40 pub const fn new(aggregation: BarAggregation, step: u64) -> Self {
41 Self { aggregation, step }
42 }
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Hash)]
47pub struct BarType {
48 pub symbol: String,
50 pub specification: BarSpecification,
52}
53
54impl BarType {
55 #[must_use]
57 pub const fn new(symbol: String, specification: BarSpecification) -> Self {
58 Self {
59 symbol,
60 specification,
61 }
62 }
63
64 #[must_use]
66 pub const fn get_spec(&self) -> &BarSpecification {
67 &self.specification
68 }
69
70 #[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#[repr(align(64))] #[derive(Debug, Clone)]
93pub struct Bar {
94 pub bar_type: BarType,
96 pub open: Decimal,
98 pub high: Decimal,
100 pub low: Decimal,
102 pub close: Decimal,
104 pub volume: Decimal,
106 pub timestamp_ns: u64,
108}
109
110impl Bar {
111 #[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#[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, }
144}
145
146#[derive(Debug)]
148pub struct BarCache {
149 bars: FxHashMap<BarType, Vec<Bar>>,
150}
151
152impl BarCache {
153 #[must_use]
155 pub fn new() -> Self {
156 Self {
157 bars: FxHashMap::default(),
158 }
159 }
160
161 pub fn add_bar(&mut self, bar: Bar) {
163 self.bars.entry(bar.bar_type.clone()).or_default().push(bar);
164 }
165
166 #[must_use]
168 pub fn get_bars(&self, bar_type: &BarType) -> Option<&Vec<Bar>> {
169 self.bars.get(bar_type)
170 }
171
172 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 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 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}