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