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};
26use nautilus_core::time::{AtomicTime, get_atomic_clock_realtime};
27use nautilus_model::identifiers::ClientId;
28
29use crate::{
30 common::{Credential, DATABENTO},
31 data::{DatabentoDataClient, DatabentoDataClientConfig},
32 historical::DatabentoHistoricalClient,
33};
34
35impl ClientConfig for DatabentoDataClientConfig {
36 fn as_any(&self) -> &dyn Any {
37 self
38 }
39}
40
41#[derive(Debug, Clone)]
43#[cfg_attr(
44 feature = "python",
45 pyo3::pyclass(module = "nautilus_trader.adapters.databento", from_py_object)
46)]
47#[cfg_attr(
48 feature = "python",
49 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.databento")
50)]
51pub struct DatabentoDataClientFactory;
52
53impl DatabentoDataClientFactory {
54 #[must_use]
56 pub const fn new() -> Self {
57 Self
58 }
59
60 pub fn create_live_data_client(
66 client_id: ClientId,
67 api_key: impl Into<String>,
68 publishers_filepath: PathBuf,
69 use_exchange_as_venue: bool,
70 bars_timestamp_on_close: bool,
71 clock: &'static AtomicTime,
72 ) -> anyhow::Result<DatabentoDataClient> {
73 let config = DatabentoDataClientConfig::new(
74 api_key,
75 publishers_filepath,
76 use_exchange_as_venue,
77 bars_timestamp_on_close,
78 );
79
80 DatabentoDataClient::new(client_id, config, clock)
81 }
82
83 pub fn create_live_data_client_with_config(
89 client_id: ClientId,
90 config: DatabentoDataClientConfig,
91 clock: &'static AtomicTime,
92 ) -> anyhow::Result<DatabentoDataClient> {
93 DatabentoDataClient::new(client_id, config, clock)
94 }
95}
96
97impl Default for DatabentoDataClientFactory {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103impl DataClientFactory for DatabentoDataClientFactory {
104 fn create(
105 &self,
106 name: &str,
107 config: &dyn ClientConfig,
108 _cache: CacheView,
109 _clock: Rc<RefCell<dyn Clock>>,
110 ) -> anyhow::Result<Box<dyn DataClient>> {
111 let databento_config = config
112 .as_any()
113 .downcast_ref::<DatabentoDataClientConfig>()
114 .ok_or_else(|| {
115 anyhow::anyhow!(
116 "Invalid config type for DatabentoDataClientFactory. Expected DatabentoDataClientConfig, was {config:?}"
117 )
118 })?
119 .clone();
120
121 let client_id = ClientId::from(name);
122 let client =
123 DatabentoDataClient::new(client_id, databento_config, get_atomic_clock_realtime())?;
124 Ok(Box::new(client))
125 }
126
127 fn name(&self) -> &'static str {
128 DATABENTO
129 }
130
131 fn config_type(&self) -> &'static str {
132 "DatabentoDataClientConfig"
133 }
134}
135
136#[derive(Debug)]
138pub struct DatabentoHistoricalClientFactory;
139
140impl DatabentoHistoricalClientFactory {
141 pub fn create(
147 api_key: String,
148 publishers_filepath: PathBuf,
149 use_exchange_as_venue: bool,
150 clock: &'static AtomicTime,
151 ) -> anyhow::Result<DatabentoHistoricalClient> {
152 DatabentoHistoricalClient::new(
153 Credential::new(api_key),
154 publishers_filepath,
155 clock,
156 use_exchange_as_venue,
157 )
158 }
159}
160
161#[derive(Debug, Default)]
163pub struct DatabentoDataClientConfigBuilder {
164 api_key: Option<String>,
165 dataset: Option<String>,
166 publishers_filepath: Option<PathBuf>,
167 use_exchange_as_venue: bool,
168 bars_timestamp_on_close: bool,
169}
170
171impl DatabentoDataClientConfigBuilder {
172 #[must_use]
174 pub fn new() -> Self {
175 Self::default()
176 }
177
178 #[must_use]
180 pub fn api_key(mut self, api_key: String) -> Self {
181 self.api_key = Some(api_key);
182 self
183 }
184
185 #[must_use]
187 pub fn dataset(mut self, dataset: String) -> Self {
188 self.dataset = Some(dataset);
189 self
190 }
191
192 #[must_use]
194 pub fn publishers_filepath(mut self, filepath: PathBuf) -> Self {
195 self.publishers_filepath = Some(filepath);
196 self
197 }
198
199 #[must_use]
201 pub const fn use_exchange_as_venue(mut self, use_exchange: bool) -> Self {
202 self.use_exchange_as_venue = use_exchange;
203 self
204 }
205
206 #[must_use]
208 pub const fn bars_timestamp_on_close(mut self, timestamp_on_close: bool) -> Self {
209 self.bars_timestamp_on_close = timestamp_on_close;
210 self
211 }
212
213 pub fn build(self) -> anyhow::Result<DatabentoDataClientConfig> {
219 let api_key = self
220 .api_key
221 .ok_or_else(|| anyhow::anyhow!("API key is required"))?;
222 let publishers_filepath = self
223 .publishers_filepath
224 .ok_or_else(|| anyhow::anyhow!("Publishers filepath is required"))?;
225
226 Ok(DatabentoDataClientConfig::new(
227 api_key,
228 publishers_filepath,
229 self.use_exchange_as_venue,
230 self.bars_timestamp_on_close,
231 ))
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use nautilus_core::time::get_atomic_clock_realtime;
238 use rstest::rstest;
239
240 use super::*;
241
242 #[rstest]
243 fn test_config_builder() {
244 let config = DatabentoDataClientConfigBuilder::new()
245 .api_key("test_key".to_string())
246 .dataset("GLBX.MDP3".to_string())
247 .publishers_filepath(PathBuf::from("test_publishers.json"))
248 .use_exchange_as_venue(true)
249 .bars_timestamp_on_close(false)
250 .build();
251
252 assert!(config.is_ok());
253 let config = config.unwrap();
254 assert_eq!(config.api_key(), "test_key");
255 assert!(config.use_exchange_as_venue);
256 assert!(!config.bars_timestamp_on_close);
257 }
258
259 #[rstest]
260 fn test_config_builder_missing_required_fields() {
261 let config = DatabentoDataClientConfigBuilder::new()
262 .api_key("test_key".to_string())
263 .build();
265
266 assert!(config.is_err());
267 }
268
269 #[rstest]
270 fn test_historical_client_factory() {
271 let api_key = "test-000000000000000000000000000".to_string();
272 let publishers_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("publishers.json");
273 let clock = get_atomic_clock_realtime();
274
275 let result =
276 DatabentoHistoricalClientFactory::create(api_key, publishers_path, false, clock);
277
278 assert!(result.is_ok());
279 }
280
281 #[rstest]
282 fn test_live_data_client_factory_missing_publishers() {
283 let client_id = ClientId::from("DATABENTO-001");
284 let api_key = "test_key".to_string();
285 let publishers_path = PathBuf::from("nonexistent_publishers.json");
286 let clock = get_atomic_clock_realtime();
287
288 let result = DatabentoDataClientFactory::create_live_data_client(
289 client_id,
290 api_key,
291 publishers_path,
292 false,
293 true,
294 clock,
295 );
296
297 assert!(result.is_err());
298 }
299}