nautilus_databento/
factories.rs1use std::{any::Any, cell::RefCell, path::PathBuf, rc::Rc};
19
20use nautilus_common::{
21 cache::CacheView,
22 clients::DataClient,
23 clock::Clock,
24 factories::{ClientConfig, DataClientFactory},
25};
26#[cfg(test)]
27use nautilus_core::string::secret::REDACTED;
28use nautilus_core::{
29 string::secret::SecretString,
30 time::{AtomicTime, get_atomic_clock_realtime},
31};
32use nautilus_model::identifiers::ClientId;
33
34use crate::{
35 common::{Credential, DATABENTO},
36 data::{DatabentoDataClient, DatabentoDataClientConfig},
37 historical::DatabentoHistoricalClient,
38};
39
40impl ClientConfig for DatabentoDataClientConfig {
41 fn as_any(&self) -> &dyn Any {
42 self
43 }
44}
45
46#[derive(Debug, Clone)]
48#[cfg_attr(
49 feature = "python",
50 pyo3::pyclass(module = "nautilus_trader.adapters.databento", from_py_object)
51)]
52#[cfg_attr(
53 feature = "python",
54 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.databento")
55)]
56pub struct DatabentoDataClientFactory;
57
58impl DatabentoDataClientFactory {
59 #[must_use]
61 pub const fn new() -> Self {
62 Self
63 }
64
65 pub fn create_live_data_client(
71 client_id: ClientId,
72 api_key: impl Into<String>,
73 publishers_filepath: PathBuf,
74 use_exchange_as_venue: bool,
75 bars_timestamp_on_close: bool,
76 clock: &'static AtomicTime,
77 ) -> anyhow::Result<DatabentoDataClient> {
78 let config = DatabentoDataClientConfig::new(
79 api_key,
80 publishers_filepath,
81 use_exchange_as_venue,
82 bars_timestamp_on_close,
83 );
84
85 DatabentoDataClient::new(client_id, config, clock)
86 }
87
88 pub fn create_live_data_client_with_config(
94 client_id: ClientId,
95 config: DatabentoDataClientConfig,
96 clock: &'static AtomicTime,
97 ) -> anyhow::Result<DatabentoDataClient> {
98 DatabentoDataClient::new(client_id, config, clock)
99 }
100}
101
102impl Default for DatabentoDataClientFactory {
103 fn default() -> Self {
104 Self::new()
105 }
106}
107
108impl DataClientFactory for DatabentoDataClientFactory {
109 fn create(
110 &self,
111 name: &str,
112 config: &dyn ClientConfig,
113 _cache: CacheView,
114 _clock: Rc<RefCell<dyn Clock>>,
115 ) -> anyhow::Result<Box<dyn DataClient>> {
116 let databento_config = config
117 .as_any()
118 .downcast_ref::<DatabentoDataClientConfig>()
119 .ok_or_else(|| {
120 anyhow::anyhow!(
121 "Invalid config type for DatabentoDataClientFactory. Expected DatabentoDataClientConfig, was {config:?}"
122 )
123 })?
124 .clone();
125
126 let client_id = ClientId::from(name);
127 let client =
128 DatabentoDataClient::new(client_id, databento_config, get_atomic_clock_realtime())?;
129 Ok(Box::new(client))
130 }
131
132 fn name(&self) -> &'static str {
133 DATABENTO
134 }
135
136 fn config_type(&self) -> &'static str {
137 "DatabentoDataClientConfig"
138 }
139}
140
141#[derive(Debug)]
143pub struct DatabentoHistoricalClientFactory;
144
145impl DatabentoHistoricalClientFactory {
146 pub fn create(
152 api_key: String,
153 publishers_filepath: PathBuf,
154 use_exchange_as_venue: bool,
155 clock: &'static AtomicTime,
156 ) -> anyhow::Result<DatabentoHistoricalClient> {
157 DatabentoHistoricalClient::new(
158 Credential::new(api_key),
159 publishers_filepath,
160 clock,
161 use_exchange_as_venue,
162 )
163 }
164}
165
166#[derive(Debug, Default)]
168pub struct DatabentoDataClientConfigBuilder {
169 api_key: Option<SecretString>,
170 dataset: Option<String>,
171 publishers_filepath: Option<PathBuf>,
172 use_exchange_as_venue: bool,
173 bars_timestamp_on_close: bool,
174}
175
176impl DatabentoDataClientConfigBuilder {
177 #[must_use]
179 pub fn new() -> Self {
180 Self::default()
181 }
182
183 #[must_use]
185 pub fn api_key(mut self, api_key: String) -> Self {
186 self.api_key = Some(SecretString::from(api_key));
187 self
188 }
189
190 #[must_use]
192 pub fn dataset(mut self, dataset: String) -> Self {
193 self.dataset = Some(dataset);
194 self
195 }
196
197 #[must_use]
199 pub fn publishers_filepath(mut self, filepath: PathBuf) -> Self {
200 self.publishers_filepath = Some(filepath);
201 self
202 }
203
204 #[must_use]
206 pub const fn use_exchange_as_venue(mut self, use_exchange: bool) -> Self {
207 self.use_exchange_as_venue = use_exchange;
208 self
209 }
210
211 #[must_use]
213 pub const fn bars_timestamp_on_close(mut self, timestamp_on_close: bool) -> Self {
214 self.bars_timestamp_on_close = timestamp_on_close;
215 self
216 }
217
218 pub fn build(self) -> anyhow::Result<DatabentoDataClientConfig> {
224 let api_key = self
225 .api_key
226 .ok_or_else(|| anyhow::anyhow!("API key is required"))?;
227 let publishers_filepath = self
228 .publishers_filepath
229 .ok_or_else(|| anyhow::anyhow!("Publishers filepath is required"))?;
230
231 Ok(DatabentoDataClientConfig::new(
232 api_key.into_inner(),
233 publishers_filepath,
234 self.use_exchange_as_venue,
235 self.bars_timestamp_on_close,
236 ))
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use nautilus_core::time::get_atomic_clock_realtime;
243 use rstest::rstest;
244
245 use super::*;
246
247 #[rstest]
248 fn test_config_builder() {
249 let builder = DatabentoDataClientConfigBuilder::new()
250 .api_key("test_key".to_string())
251 .dataset("GLBX.MDP3".to_string())
252 .publishers_filepath(PathBuf::from("test_publishers.json"))
253 .use_exchange_as_venue(true)
254 .bars_timestamp_on_close(false);
255
256 let debug = format!("{builder:?}");
257 assert!(debug.contains(REDACTED));
258 assert!(!debug.contains("test_key"));
259
260 let config = builder.build();
261
262 assert!(config.is_ok());
263 let config = config.unwrap();
264 assert_eq!(config.api_key(), "test_key");
265 assert!(config.use_exchange_as_venue);
266 assert!(!config.bars_timestamp_on_close);
267 }
268
269 #[rstest]
270 fn test_config_builder_missing_required_fields() {
271 let config = DatabentoDataClientConfigBuilder::new()
272 .api_key("test_key".to_string())
273 .build();
275
276 assert!(config.is_err());
277 }
278
279 #[rstest]
280 fn test_historical_client_factory() {
281 let api_key = "test-000000000000000000000000000".to_string();
282 let publishers_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("publishers.json");
283 let clock = get_atomic_clock_realtime();
284
285 let result =
286 DatabentoHistoricalClientFactory::create(api_key, publishers_path, false, clock);
287
288 assert!(result.is_ok());
289 }
290
291 #[rstest]
292 fn test_live_data_client_factory_missing_publishers() {
293 let client_id = ClientId::from("DATABENTO-001");
294 let api_key = "test_key".to_string();
295 let publishers_path = PathBuf::from("nonexistent_publishers.json");
296 let clock = get_atomic_clock_realtime();
297
298 let result = DatabentoDataClientFactory::create_live_data_client(
299 client_id,
300 api_key,
301 publishers_path,
302 false,
303 true,
304 clock,
305 );
306
307 assert!(result.is_err());
308 }
309}