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 use rstest::rstest;
261
262 use super::*;
263
264 #[rstest]
265 fn test_config_builder() {
266 let config = DatabentoDataClientConfigBuilder::new()
267 .api_key("test_key".to_string())
268 .dataset("GLBX.MDP3".to_string())
269 .publishers_filepath(PathBuf::from("test_publishers.json"))
270 .use_exchange_as_venue(true)
271 .bars_timestamp_on_close(false)
272 .build();
273
274 assert!(config.is_ok());
275 let config = config.unwrap();
276 assert_eq!(config.api_key, "test_key");
277 assert!(config.use_exchange_as_venue);
278 assert!(!config.bars_timestamp_on_close);
279 }
280
281 #[rstest]
282 fn test_config_builder_missing_required_fields() {
283 let config = DatabentoDataClientConfigBuilder::new()
284 .api_key("test_key".to_string())
285 .build();
287
288 assert!(config.is_err());
289 }
290
291 #[rstest]
292 fn test_historical_client_factory() {
293 let api_key = env::var("DATABENTO_API_KEY").unwrap_or_else(|_| "test_key".to_string());
294 let publishers_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("publishers.json");
295 let clock = get_atomic_clock_realtime();
296
297 let result =
299 DatabentoHistoricalClientFactory::create(api_key, publishers_path, false, clock);
300
301 assert!(result.is_err() || result.is_ok());
304 }
305
306 #[rstest]
307 fn test_live_data_client_factory() {
308 let client_id = ClientId::from("DATABENTO-001");
309 let api_key = "test_key".to_string();
310 let publishers_path = PathBuf::from("test_publishers.json");
311 let clock = get_atomic_clock_realtime();
312
313 let result = DatabentoDataClientFactory::create_live_data_client(
315 client_id,
316 api_key,
317 publishers_path,
318 false,
319 true,
320 clock,
321 );
322
323 assert!(result.is_err() || result.is_ok());
326 }
327}