nautilus_databento/
factories.rs1use std::{any::Any, cell::RefCell, path::PathBuf, rc::Rc};
19
20use nautilus_common::{cache::Cache, clock::Clock};
21use nautilus_core::time::{AtomicTime, get_atomic_clock_realtime};
22use nautilus_data::client::DataClient;
23use nautilus_model::identifiers::ClientId;
24use nautilus_system::factories::{ClientConfig, DataClientFactory};
25
26use crate::{
27 data::{DatabentoDataClient, DatabentoDataClientConfig},
28 historical::DatabentoHistoricalClient,
29};
30
31#[derive(Debug, Clone)]
33pub struct DatabentoLiveClientConfig {
34 pub api_key: String,
36 pub publishers_filepath: PathBuf,
38 pub use_exchange_as_venue: bool,
40 pub bars_timestamp_on_close: bool,
42}
43
44impl DatabentoLiveClientConfig {
45 #[must_use]
47 pub const fn new(
48 api_key: String,
49 publishers_filepath: PathBuf,
50 use_exchange_as_venue: bool,
51 bars_timestamp_on_close: bool,
52 ) -> Self {
53 Self {
54 api_key,
55 publishers_filepath,
56 use_exchange_as_venue,
57 bars_timestamp_on_close,
58 }
59 }
60}
61
62impl ClientConfig for DatabentoLiveClientConfig {
63 fn as_any(&self) -> &dyn Any {
64 self
65 }
66}
67
68#[derive(Debug)]
70pub struct DatabentoDataClientFactory;
71
72impl DatabentoDataClientFactory {
73 #[must_use]
75 pub const fn new() -> Self {
76 Self
77 }
78
79 pub fn create_live_data_client(
85 client_id: ClientId,
86 api_key: String,
87 publishers_filepath: PathBuf,
88 use_exchange_as_venue: bool,
89 bars_timestamp_on_close: bool,
90 clock: &'static AtomicTime,
91 ) -> anyhow::Result<DatabentoDataClient> {
92 let config = DatabentoDataClientConfig::new(
93 api_key,
94 publishers_filepath,
95 use_exchange_as_venue,
96 bars_timestamp_on_close,
97 );
98
99 DatabentoDataClient::new(client_id, config, clock)
100 }
101
102 pub fn create_live_data_client_with_config(
108 client_id: ClientId,
109 config: DatabentoDataClientConfig,
110 clock: &'static AtomicTime,
111 ) -> anyhow::Result<DatabentoDataClient> {
112 DatabentoDataClient::new(client_id, config, clock)
113 }
114}
115
116impl Default for DatabentoDataClientFactory {
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122impl DataClientFactory for DatabentoDataClientFactory {
123 fn create(
124 &self,
125 name: &str,
126 config: &dyn ClientConfig,
127 _cache: Rc<RefCell<Cache>>,
128 _clock: Rc<RefCell<dyn Clock>>,
129 ) -> anyhow::Result<Box<dyn DataClient>> {
130 let databento_config = config
131 .as_any()
132 .downcast_ref::<DatabentoLiveClientConfig>()
133 .ok_or_else(|| {
134 anyhow::anyhow!(
135 "Invalid config type for DatabentoDataClientFactory. Expected DatabentoLiveClientConfig, got {:?}",
136 config
137 )
138 })?;
139
140 let client_id = ClientId::from(name);
141 let config = DatabentoDataClientConfig::new(
142 databento_config.api_key.clone(),
143 databento_config.publishers_filepath.clone(),
144 databento_config.use_exchange_as_venue,
145 databento_config.bars_timestamp_on_close,
146 );
147
148 let client = DatabentoDataClient::new(client_id, config, get_atomic_clock_realtime())?;
149 Ok(Box::new(client))
150 }
151
152 fn name(&self) -> &'static str {
153 "DATABENTO"
154 }
155
156 fn config_type(&self) -> &'static str {
157 "DatabentoLiveClientConfig"
158 }
159}
160
161#[derive(Debug)]
163pub struct DatabentoHistoricalClientFactory;
164
165impl DatabentoHistoricalClientFactory {
166 pub fn create(
172 api_key: String,
173 publishers_filepath: PathBuf,
174 use_exchange_as_venue: bool,
175 clock: &'static AtomicTime,
176 ) -> anyhow::Result<DatabentoHistoricalClient> {
177 DatabentoHistoricalClient::new(api_key, publishers_filepath, clock, use_exchange_as_venue)
178 }
179}
180
181#[derive(Debug, Default)]
183pub struct DatabentoDataClientConfigBuilder {
184 api_key: Option<String>,
185 dataset: Option<String>,
186 publishers_filepath: Option<PathBuf>,
187 use_exchange_as_venue: bool,
188 bars_timestamp_on_close: bool,
189}
190
191impl DatabentoDataClientConfigBuilder {
192 #[must_use]
194 pub fn new() -> Self {
195 Self::default()
196 }
197
198 #[must_use]
200 pub fn api_key(mut self, api_key: String) -> Self {
201 self.api_key = Some(api_key);
202 self
203 }
204
205 #[must_use]
207 pub fn dataset(mut self, dataset: String) -> Self {
208 self.dataset = Some(dataset);
209 self
210 }
211
212 #[must_use]
214 pub fn publishers_filepath(mut self, filepath: PathBuf) -> Self {
215 self.publishers_filepath = Some(filepath);
216 self
217 }
218
219 #[must_use]
221 pub const fn use_exchange_as_venue(mut self, use_exchange: bool) -> Self {
222 self.use_exchange_as_venue = use_exchange;
223 self
224 }
225
226 #[must_use]
228 pub const fn bars_timestamp_on_close(mut self, timestamp_on_close: bool) -> Self {
229 self.bars_timestamp_on_close = timestamp_on_close;
230 self
231 }
232
233 pub fn build(self) -> anyhow::Result<DatabentoDataClientConfig> {
239 let api_key = self
240 .api_key
241 .ok_or_else(|| anyhow::anyhow!("API key is required"))?;
242 let publishers_filepath = self
243 .publishers_filepath
244 .ok_or_else(|| anyhow::anyhow!("Publishers filepath is required"))?;
245
246 Ok(DatabentoDataClientConfig::new(
247 api_key,
248 publishers_filepath,
249 self.use_exchange_as_venue,
250 self.bars_timestamp_on_close,
251 ))
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use std::env;
258
259 use nautilus_core::time::get_atomic_clock_realtime;
260
261 use super::*;
262
263 #[test]
264 fn test_config_builder() {
265 let config = DatabentoDataClientConfigBuilder::new()
266 .api_key("test_key".to_string())
267 .dataset("GLBX.MDP3".to_string())
268 .publishers_filepath(PathBuf::from("test_publishers.json"))
269 .use_exchange_as_venue(true)
270 .bars_timestamp_on_close(false)
271 .build();
272
273 assert!(config.is_ok());
274 let config = config.unwrap();
275 assert_eq!(config.api_key, "test_key");
276 assert!(config.use_exchange_as_venue);
277 assert!(!config.bars_timestamp_on_close);
278 }
279
280 #[test]
281 fn test_config_builder_missing_required_fields() {
282 let config = DatabentoDataClientConfigBuilder::new()
283 .api_key("test_key".to_string())
284 .build();
286
287 assert!(config.is_err());
288 }
289
290 #[test]
291 fn test_historical_client_factory() {
292 let api_key = env::var("DATABENTO_API_KEY").unwrap_or_else(|_| "test_key".to_string());
293 let publishers_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("publishers.json");
294 let clock = get_atomic_clock_realtime();
295
296 let result =
298 DatabentoHistoricalClientFactory::create(api_key, publishers_path, false, clock);
299
300 assert!(result.is_err() || result.is_ok());
303 }
304
305 #[test]
306 fn test_live_data_client_factory() {
307 let client_id = ClientId::from("DATABENTO-001");
308 let api_key = "test_key".to_string();
309 let publishers_path = PathBuf::from("test_publishers.json");
310 let clock = get_atomic_clock_realtime();
311
312 let result = DatabentoDataClientFactory::create_live_data_client(
314 client_id,
315 api_key,
316 publishers_path,
317 false,
318 true,
319 clock,
320 );
321
322 assert!(result.is_err() || result.is_ok());
325 }
326}