quant_primitives/
candle.rs1use chrono::{DateTime, Utc};
4use rust_decimal::Decimal;
5use serde::{Deserialize, Serialize};
6use std::fmt;
7use thiserror::Error;
8
9#[derive(Debug, Error, PartialEq, Eq)]
10pub enum CandleError {
11 #[error("high ({high}) must be >= low ({low})")]
12 HighBelowLow { high: Decimal, low: Decimal },
13
14 #[error("aggregation received empty OHLC data")]
15 EmptyAggregation,
16}
17
18#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23pub enum VolumeSource {
24 TradeSize,
26 TickCount,
28 ExchangeReported,
30 #[default]
32 Unknown,
33}
34
35impl VolumeSource {
36 #[must_use]
38 pub fn as_str(&self) -> &str {
39 match self {
40 Self::TradeSize => "trade_size",
41 Self::TickCount => "tick_count",
42 Self::ExchangeReported => "exchange_reported",
43 Self::Unknown => "unknown",
44 }
45 }
46
47 #[must_use]
49 pub fn from_str_value(s: &str) -> Self {
50 match s {
51 "trade_size" => Self::TradeSize,
52 "tick_count" => Self::TickCount,
53 "exchange_reported" => Self::ExchangeReported,
54 _ => Self::Unknown,
55 }
56 }
57}
58
59impl fmt::Display for VolumeSource {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 f.write_str(self.as_str())
62 }
63}
64
65#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
70pub enum CandleQuality {
71 #[default]
73 Complete,
74 Partial { reason: String },
76}
77
78impl CandleQuality {
79 #[must_use]
81 pub fn as_str(&self) -> &str {
82 match self {
83 Self::Complete => "complete",
84 Self::Partial { .. } => "partial",
85 }
86 }
87
88 #[must_use]
93 pub fn from_str_value(s: &str) -> Self {
94 match s {
95 "complete" => Self::Complete,
96 "partial" => Self::Partial {
97 reason: String::new(),
98 },
99 _ => Self::Complete,
100 }
101 }
102}
103
104impl fmt::Display for CandleQuality {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 f.write_str(self.as_str())
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
111pub struct Candle {
112 open: Decimal,
113 high: Decimal,
114 low: Decimal,
115 close: Decimal,
116 volume: Decimal,
117 timestamp: DateTime<Utc>,
118 #[serde(default)]
119 volume_source: VolumeSource,
120 #[serde(default)]
121 quality: CandleQuality,
122}
123
124impl Candle {
125 pub fn new(
127 open: Decimal,
128 high: Decimal,
129 low: Decimal,
130 close: Decimal,
131 volume: Decimal,
132 timestamp: DateTime<Utc>,
133 ) -> Result<Self, CandleError> {
134 if high < low {
135 return Err(CandleError::HighBelowLow { high, low });
136 }
137 Ok(Self {
138 open,
139 high,
140 low,
141 close,
142 volume,
143 timestamp,
144 volume_source: VolumeSource::Unknown,
145 quality: CandleQuality::Complete,
146 })
147 }
148
149 #[must_use]
151 pub fn with_volume_source(mut self, source: VolumeSource) -> Self {
152 self.volume_source = source;
153 self
154 }
155
156 #[must_use]
158 pub fn with_quality(mut self, quality: CandleQuality) -> Self {
159 self.quality = quality;
160 self
161 }
162
163 #[must_use]
164 pub fn open(&self) -> Decimal {
165 self.open
166 }
167
168 #[must_use]
169 pub fn high(&self) -> Decimal {
170 self.high
171 }
172
173 #[must_use]
174 pub fn low(&self) -> Decimal {
175 self.low
176 }
177
178 #[must_use]
179 pub fn close(&self) -> Decimal {
180 self.close
181 }
182
183 #[must_use]
184 pub fn volume(&self) -> Decimal {
185 self.volume
186 }
187
188 #[must_use]
189 pub fn timestamp(&self) -> DateTime<Utc> {
190 self.timestamp
191 }
192
193 #[must_use]
194 pub fn volume_source(&self) -> VolumeSource {
195 self.volume_source
196 }
197
198 #[must_use]
199 pub fn quality(&self) -> &CandleQuality {
200 &self.quality
201 }
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206pub struct CandleRange {
207 pub earliest: DateTime<Utc>,
208 pub latest: DateTime<Utc>,
209}
210
211impl CandleRange {
212 #[must_use]
215 pub fn from_candles(candles: &[Candle]) -> Option<Self> {
216 let earliest = candles.first()?.timestamp;
217 let latest = candles.last()?.timestamp;
218 Some(Self { earliest, latest })
219 }
220}
221
222#[cfg(test)]
223#[path = "candle_tests.rs"]
224mod tests;