nautilus_lighter/
factories.rs1use std::{any::Any, cell::RefCell, rc::Rc};
19
20use nautilus_common::{
21 cache::CacheView,
22 clients::{DataClient, ExecutionClient},
23 clock::Clock,
24 factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
25};
26use nautilus_live::ExecutionClientCore;
27use nautilus_model::{
28 enums::{AccountType, OmsType},
29 identifiers::ClientId,
30};
31
32use crate::{
33 common::consts::{LIGHTER, LIGHTER_VENUE},
34 config::{LighterDataClientConfig, LighterExecClientConfig},
35 data::LighterDataClient,
36 execution::LighterExecutionClient,
37};
38
39impl ClientConfig for LighterDataClientConfig {
40 fn as_any(&self) -> &dyn Any {
41 self
42 }
43}
44
45impl ClientConfig for LighterExecClientConfig {
46 fn as_any(&self) -> &dyn Any {
47 self
48 }
49}
50
51#[derive(Debug, Clone, Default)]
53#[cfg_attr(
54 feature = "python",
55 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.lighter", from_py_object,)
56)]
57#[cfg_attr(
58 feature = "python",
59 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.lighter")
60)]
61pub struct LighterDataClientFactory;
62
63impl LighterDataClientFactory {
64 #[must_use]
66 pub const fn new() -> Self {
67 Self
68 }
69}
70
71impl DataClientFactory for LighterDataClientFactory {
72 fn create(
73 &self,
74 name: &str,
75 config: &dyn ClientConfig,
76 _cache: CacheView,
77 _clock: Rc<RefCell<dyn Clock>>,
78 ) -> anyhow::Result<Box<dyn DataClient>> {
79 let lighter_config = config
80 .as_any()
81 .downcast_ref::<LighterDataClientConfig>()
82 .ok_or_else(|| {
83 anyhow::anyhow!(
84 "Invalid config type for LighterDataClientFactory. Expected LighterDataClientConfig, was {config:?}",
85 )
86 })?
87 .clone();
88
89 let client_id = ClientId::from(name);
90 let client = LighterDataClient::new(client_id, lighter_config)?;
91 Ok(Box::new(client))
92 }
93
94 fn name(&self) -> &'static str {
95 LIGHTER
96 }
97
98 fn config_type(&self) -> &'static str {
99 "LighterDataClientConfig"
100 }
101}
102
103#[derive(Debug, Clone, Default)]
105#[cfg_attr(
106 feature = "python",
107 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.lighter", from_py_object,)
108)]
109#[cfg_attr(
110 feature = "python",
111 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.lighter")
112)]
113pub struct LighterExecutionClientFactory;
114
115impl LighterExecutionClientFactory {
116 #[must_use]
118 pub const fn new() -> Self {
119 Self
120 }
121}
122
123impl ExecutionClientFactory for LighterExecutionClientFactory {
124 fn create(
125 &self,
126 name: &str,
127 config: &dyn ClientConfig,
128 cache: CacheView,
129 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
130 let lighter_config = config
131 .as_any()
132 .downcast_ref::<LighterExecClientConfig>()
133 .ok_or_else(|| {
134 anyhow::anyhow!(
135 "Invalid config type for LighterExecutionClientFactory. Expected LighterExecClientConfig, was {config:?}",
136 )
137 })?
138 .clone();
139
140 let core = ExecutionClientCore::new(
143 lighter_config.trader_id,
144 ClientId::from(name),
145 *LIGHTER_VENUE,
146 OmsType::Netting,
147 lighter_config.account_id,
148 AccountType::Margin,
149 None,
150 cache,
151 );
152
153 let client = LighterExecutionClient::new(core, lighter_config)?;
154 Ok(Box::new(client))
155 }
156
157 fn name(&self) -> &'static str {
158 LIGHTER
159 }
160
161 fn config_type(&self) -> &'static str {
162 "LighterExecClientConfig"
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use std::{cell::RefCell, rc::Rc};
169
170 use nautilus_common::{
171 cache::Cache,
172 clock::TestClock,
173 factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
174 };
175 use nautilus_model::identifiers::{AccountId, TraderId};
176 use rstest::rstest;
177
178 use super::*;
179
180 fn exec_config() -> LighterExecClientConfig {
181 LighterExecClientConfig::builder()
182 .trader_id(TraderId::from("TRADER-001"))
183 .account_id(AccountId::from("LIGHTER-001"))
184 .build()
185 }
186
187 #[rstest]
188 fn test_lighter_data_client_factory_creation() {
189 let factory = LighterDataClientFactory::new();
190 assert_eq!(factory.name(), LIGHTER);
191 assert_eq!(factory.config_type(), "LighterDataClientConfig");
192 }
193
194 #[rstest]
195 fn test_lighter_execution_client_factory_creation() {
196 let factory = LighterExecutionClientFactory::new();
197 assert_eq!(factory.name(), LIGHTER);
198 assert_eq!(factory.config_type(), "LighterExecClientConfig");
199 }
200
201 #[rstest]
202 fn test_lighter_exec_client_config_implements_client_config() {
203 let config = exec_config();
204 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
205 let downcasted = boxed_config
206 .as_any()
207 .downcast_ref::<LighterExecClientConfig>();
208
209 assert!(downcasted.is_some());
210 }
211
212 #[rstest]
213 fn test_lighter_execution_client_factory_rejects_wrong_config_type() {
214 let factory = LighterExecutionClientFactory::new();
215 let wrong_config = LighterDataClientConfig::default();
216
217 let cache = Rc::new(RefCell::new(Cache::default()));
218
219 let result = factory.create("LIGHTER-TEST", &wrong_config, cache.into());
220 assert!(result.is_err());
221 assert!(
222 result
223 .err()
224 .unwrap()
225 .to_string()
226 .contains("Invalid config type")
227 );
228 }
229
230 #[rstest]
231 fn test_lighter_execution_client_factory_constructs_without_credentials() {
232 let factory = LighterExecutionClientFactory::new();
233 let config = exec_config();
234 let cache = Rc::new(RefCell::new(Cache::default()));
235
236 let client = factory
237 .create("LIGHTER-TEST", &config, cache.into())
238 .expect("expected client to construct without credentials");
239
240 assert!(!client.is_connected());
241 }
242
243 #[rstest]
244 fn test_lighter_data_client_factory_rejects_wrong_config_type() {
245 let factory = LighterDataClientFactory::new();
246 let wrong_config = exec_config();
247 let cache = Rc::new(RefCell::new(Cache::default()));
248 let clock = Rc::new(RefCell::new(TestClock::new()));
249
250 let result = factory.create("LIGHTER-TEST", &wrong_config, cache.into(), clock);
251 assert!(result.is_err());
252 assert!(
253 result
254 .err()
255 .unwrap()
256 .to_string()
257 .contains("Invalid config type")
258 );
259 }
260}