1use std::{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, TraderId},
30};
31
32use crate::{
33 common::{
34 consts::{BINANCE, BINANCE_VENUE},
35 enums::BinanceProductType,
36 },
37 config::{BinanceDataClientConfig, BinanceExecutionClientConfig},
38 futures::{data::BinanceFuturesDataClient, execution::BinanceFuturesExecutionClient},
39 spot::{data::BinanceSpotDataClient, execution::BinanceSpotExecutionClient},
40};
41
42#[derive(Debug, Clone)]
44#[cfg_attr(
45 feature = "python",
46 pyo3::pyclass(module = "nautilus_trader.adapters.binance", from_py_object)
47)]
48#[cfg_attr(
49 feature = "python",
50 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.binance")
51)]
52pub struct BinanceDataClientFactory;
53
54impl BinanceDataClientFactory {
55 #[must_use]
57 pub const fn new() -> Self {
58 Self
59 }
60}
61
62impl Default for BinanceDataClientFactory {
63 fn default() -> Self {
64 Self::new()
65 }
66}
67
68impl DataClientFactory for BinanceDataClientFactory {
69 fn create(
70 &self,
71 name: &str,
72 config: &dyn ClientConfig,
73 _cache: CacheView,
74 _clock: Rc<RefCell<dyn Clock>>,
75 ) -> anyhow::Result<Box<dyn DataClient>> {
76 let binance_config = config
77 .as_any()
78 .downcast_ref::<BinanceDataClientConfig>()
79 .ok_or_else(|| {
80 anyhow::anyhow!(
81 "Invalid config type for BinanceDataClientFactory. Expected BinanceDataClientConfig, was {config:?}",
82 )
83 })?
84 .clone();
85
86 let client_id = ClientId::from(name);
87
88 binance_config.validate()?;
89
90 let product_type = binance_config.product_type;
91
92 match product_type {
93 BinanceProductType::Spot => {
94 let client = BinanceSpotDataClient::new(client_id, binance_config)?;
95 Ok(Box::new(client))
96 }
97 BinanceProductType::UsdM | BinanceProductType::CoinM => {
98 let client =
99 BinanceFuturesDataClient::new(client_id, binance_config, product_type)?;
100 Ok(Box::new(client))
101 }
102 _ => {
103 anyhow::bail!("Unsupported product type for Binance data client: {product_type:?}")
104 }
105 }
106 }
107
108 fn name(&self) -> &'static str {
109 BINANCE
110 }
111
112 fn config_type(&self) -> &'static str {
113 stringify!(BinanceDataClientConfig)
114 }
115}
116
117#[derive(Debug, Clone)]
119#[cfg_attr(
120 feature = "python",
121 pyo3::pyclass(module = "nautilus_trader.adapters.binance", from_py_object)
122)]
123#[cfg_attr(
124 feature = "python",
125 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.binance")
126)]
127pub struct BinanceExecutionClientFactory;
128
129impl BinanceExecutionClientFactory {
130 #[must_use]
132 pub const fn new() -> Self {
133 Self
134 }
135}
136
137impl Default for BinanceExecutionClientFactory {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143impl ExecutionClientFactory for BinanceExecutionClientFactory {
144 fn create(
145 &self,
146 trader_id: TraderId,
147 name: &str,
148 config: &dyn ClientConfig,
149 cache: CacheView,
150 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
151 let binance_config = config
152 .as_any()
153 .downcast_ref::<BinanceExecutionClientConfig>()
154 .ok_or_else(|| {
155 anyhow::anyhow!(
156 "Invalid config type for BinanceExecutionClientFactory. Expected BinanceExecutionClientConfig, was {config:?}",
157 )
158 })?
159 .clone();
160
161 let product_type = binance_config.product_type;
162
163 binance_config.validate()?;
164
165 match product_type {
166 BinanceProductType::Spot => {
167 let account_type = AccountType::Cash;
169 let oms_type = OmsType::Hedging;
170
171 let core = ExecutionClientCore::new(
172 trader_id,
173 ClientId::from(name),
174 *BINANCE_VENUE,
175 oms_type,
176 binance_config.account_id,
177 account_type,
178 None, cache,
180 );
181
182 let client = BinanceSpotExecutionClient::new(core, binance_config)?;
183 Ok(Box::new(client))
184 }
185 BinanceProductType::UsdM | BinanceProductType::CoinM => {
186 let account_type = AccountType::Margin;
187 let oms_type = binance_config.oms_type.unwrap_or(OmsType::Netting);
188
189 let core = ExecutionClientCore::new(
190 trader_id,
191 ClientId::from(name),
192 *BINANCE_VENUE,
193 oms_type,
194 binance_config.account_id,
195 account_type,
196 None, cache,
198 );
199
200 let client = BinanceFuturesExecutionClient::new(core, binance_config)?;
201 Ok(Box::new(client))
202 }
203 _ => {
204 anyhow::bail!(
205 "Unsupported product type for Binance execution client: {product_type:?}"
206 )
207 }
208 }
209 }
210
211 fn name(&self) -> &'static str {
212 BINANCE
213 }
214
215 fn config_type(&self) -> &'static str {
216 stringify!(BinanceExecutionClientConfig)
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use std::{cell::RefCell, rc::Rc};
223
224 use nautilus_common::{
225 cache::Cache,
226 factories::{DataClientFactory, ExecutionClientFactory},
227 };
228 use rstest::rstest;
229
230 use super::*;
231
232 #[rstest]
233 fn test_binance_data_client_factory_creation() {
234 let factory = BinanceDataClientFactory::new();
235 assert_eq!(factory.name(), BINANCE);
236 assert_eq!(factory.config_type(), "BinanceDataClientConfig");
237 }
238
239 #[rstest]
240 fn test_binance_data_client_factory_default() {
241 let factory = BinanceDataClientFactory;
242 assert_eq!(factory.name(), BINANCE);
243 }
244
245 #[rstest]
246 #[case(BinanceProductType::Spot, Some(OmsType::Netting), OmsType::Hedging)]
247 #[case(BinanceProductType::UsdM, None, OmsType::Netting)]
248 #[case(BinanceProductType::UsdM, Some(OmsType::Hedging), OmsType::Hedging)]
249 fn test_binance_execution_client_factory_selects_oms_type(
250 #[case] product_type: BinanceProductType,
251 #[case] oms_type: Option<OmsType>,
252 #[case] expected: OmsType,
253 ) {
254 let factory = BinanceExecutionClientFactory::new();
255 let config = BinanceExecutionClientConfig {
256 product_type,
257 use_ws_trading: false,
258 oms_type,
259 api_key: Some("test_key".to_string()),
260 api_secret: Some("test_secret".to_string()),
261 ..Default::default()
262 };
263 let cache = Rc::new(RefCell::new(Cache::default()));
264
265 let client = factory
266 .create(
267 TraderId::from("TRADER-001"),
268 "BINANCE-TEST",
269 &config,
270 cache.into(),
271 )
272 .unwrap();
273
274 assert_eq!(client.oms_type(), expected);
275 }
276}