ccxt_exchanges/binance/
builder.rs

1//! Binance exchange builder pattern implementation.
2//!
3//! Provides a fluent API for constructing Binance exchange instances with
4//! type-safe configuration options.
5
6use super::{Binance, BinanceOptions};
7use ccxt_core::types::default_type::{DefaultSubType, DefaultType};
8use ccxt_core::{ExchangeConfig, Result};
9use serde_json::Value;
10use std::collections::HashMap;
11
12/// Builder for creating Binance exchange instances.
13///
14/// Provides a fluent API for configuring all aspects of the Binance exchange,
15/// including authentication, connection settings, and Binance-specific options.
16///
17/// # Example
18///
19/// ```no_run
20/// use ccxt_exchanges::binance::BinanceBuilder;
21///
22/// let binance = BinanceBuilder::new()
23///     .api_key("your-api-key")
24///     .secret("your-secret")
25///     .sandbox(true)
26///     .timeout(30)
27///     .build()
28///     .unwrap();
29/// ```
30#[derive(Debug, Clone)]
31pub struct BinanceBuilder {
32    /// Exchange configuration
33    config: ExchangeConfig,
34    /// Binance-specific options
35    options: BinanceOptions,
36}
37
38impl Default for BinanceBuilder {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44impl BinanceBuilder {
45    /// Creates a new builder with default configuration.
46    ///
47    /// # Example
48    ///
49    /// ```no_run
50    /// use ccxt_exchanges::binance::BinanceBuilder;
51    ///
52    /// let builder = BinanceBuilder::new();
53    /// ```
54    pub fn new() -> Self {
55        Self {
56            config: ExchangeConfig {
57                id: "binance".to_string(),
58                name: "Binance".to_string(),
59                ..Default::default()
60            },
61            options: BinanceOptions::default(),
62        }
63    }
64
65    /// Sets the API key for authentication.
66    ///
67    /// # Arguments
68    ///
69    /// * `key` - The API key string.
70    ///
71    /// # Example
72    ///
73    /// ```no_run
74    /// use ccxt_exchanges::binance::BinanceBuilder;
75    ///
76    /// let builder = BinanceBuilder::new()
77    ///     .api_key("your-api-key");
78    /// ```
79    pub fn api_key(mut self, key: impl Into<String>) -> Self {
80        self.config.api_key = Some(key.into());
81        self
82    }
83
84    /// Sets the API secret for authentication.
85    ///
86    /// # Arguments
87    ///
88    /// * `secret` - The API secret string.
89    ///
90    /// # Example
91    ///
92    /// ```no_run
93    /// use ccxt_exchanges::binance::BinanceBuilder;
94    ///
95    /// let builder = BinanceBuilder::new()
96    ///     .secret("your-secret");
97    /// ```
98    pub fn secret(mut self, secret: impl Into<String>) -> Self {
99        self.config.secret = Some(secret.into());
100        self
101    }
102
103    /// Enables or disables sandbox/testnet mode.
104    ///
105    /// When enabled, the exchange will connect to Binance's testnet
106    /// instead of the production environment.
107    ///
108    /// # Arguments
109    ///
110    /// * `enabled` - Whether to enable sandbox mode.
111    ///
112    /// # Example
113    ///
114    /// ```no_run
115    /// use ccxt_exchanges::binance::BinanceBuilder;
116    ///
117    /// let builder = BinanceBuilder::new()
118    ///     .sandbox(true);
119    /// ```
120    pub fn sandbox(mut self, enabled: bool) -> Self {
121        self.config.sandbox = enabled;
122        self.options.test = enabled;
123        self
124    }
125
126    /// Sets the request timeout in seconds.
127    ///
128    /// # Arguments
129    ///
130    /// * `seconds` - Timeout duration in seconds.
131    ///
132    /// # Example
133    ///
134    /// ```no_run
135    /// use ccxt_exchanges::binance::BinanceBuilder;
136    ///
137    /// let builder = BinanceBuilder::new()
138    ///     .timeout(60);
139    /// ```
140    pub fn timeout(mut self, seconds: u64) -> Self {
141        self.config.timeout = seconds;
142        self
143    }
144
145    /// Sets the receive window for signed requests.
146    ///
147    /// The receive window specifies how long a request is valid after
148    /// the timestamp. Default is 5000 milliseconds.
149    ///
150    /// # Arguments
151    ///
152    /// * `millis` - Receive window in milliseconds.
153    ///
154    /// # Example
155    ///
156    /// ```no_run
157    /// use ccxt_exchanges::binance::BinanceBuilder;
158    ///
159    /// let builder = BinanceBuilder::new()
160    ///     .recv_window(10000);
161    /// ```
162    pub fn recv_window(mut self, millis: u64) -> Self {
163        self.options.recv_window = millis;
164        self
165    }
166
167    /// Sets the default trading type.
168    ///
169    /// Accepts both `DefaultType` enum values and string values for backward compatibility.
170    /// Valid string values: "spot", "margin", "swap", "futures", "option".
171    /// Legacy values "future" and "delivery" are also supported.
172    ///
173    /// # Arguments
174    ///
175    /// * `trading_type` - The default trading type (DefaultType or string).
176    ///
177    /// # Example
178    ///
179    /// ```no_run
180    /// use ccxt_exchanges::binance::BinanceBuilder;
181    /// use ccxt_core::types::default_type::DefaultType;
182    ///
183    /// // Using DefaultType enum (recommended)
184    /// let builder = BinanceBuilder::new()
185    ///     .default_type(DefaultType::Swap);
186    ///
187    /// // Using string (backward compatible)
188    /// let builder = BinanceBuilder::new()
189    ///     .default_type("swap");
190    /// ```
191    pub fn default_type(mut self, trading_type: impl Into<DefaultType>) -> Self {
192        self.options.default_type = trading_type.into();
193        self
194    }
195
196    /// Sets the default sub-type for contract settlement.
197    ///
198    /// - `Linear`: USDT-margined contracts (FAPI)
199    /// - `Inverse`: Coin-margined contracts (DAPI)
200    ///
201    /// Only applicable when `default_type` is `Swap`, `Futures`, or `Option`.
202    ///
203    /// # Arguments
204    ///
205    /// * `sub_type` - The default sub-type for contract settlement.
206    ///
207    /// # Example
208    ///
209    /// ```no_run
210    /// use ccxt_exchanges::binance::BinanceBuilder;
211    /// use ccxt_core::types::default_type::{DefaultType, DefaultSubType};
212    ///
213    /// let builder = BinanceBuilder::new()
214    ///     .default_type(DefaultType::Swap)
215    ///     .default_sub_type(DefaultSubType::Linear);
216    /// ```
217    pub fn default_sub_type(mut self, sub_type: DefaultSubType) -> Self {
218        self.options.default_sub_type = Some(sub_type);
219        self
220    }
221
222    /// Enables or disables time synchronization adjustment.
223    ///
224    /// When enabled, the exchange will adjust for time differences
225    /// between the local system and Binance servers.
226    ///
227    /// # Arguments
228    ///
229    /// * `enabled` - Whether to enable time adjustment.
230    ///
231    /// # Example
232    ///
233    /// ```no_run
234    /// use ccxt_exchanges::binance::BinanceBuilder;
235    ///
236    /// let builder = BinanceBuilder::new()
237    ///     .adjust_for_time_difference(true);
238    /// ```
239    pub fn adjust_for_time_difference(mut self, enabled: bool) -> Self {
240        self.options.adjust_for_time_difference = enabled;
241        self
242    }
243
244    /// Sets the time sync interval in seconds.
245    ///
246    /// Controls how often the time offset is refreshed when auto sync is enabled.
247    /// Default is 30 seconds.
248    ///
249    /// # Arguments
250    ///
251    /// * `seconds` - Sync interval in seconds.
252    ///
253    /// # Example
254    ///
255    /// ```no_run
256    /// use ccxt_exchanges::binance::BinanceBuilder;
257    ///
258    /// let builder = BinanceBuilder::new()
259    ///     .time_sync_interval(60); // Sync every 60 seconds
260    /// ```
261    pub fn time_sync_interval(mut self, seconds: u64) -> Self {
262        self.options.time_sync_interval_secs = seconds;
263        self
264    }
265
266    /// Enables or disables automatic periodic time sync.
267    ///
268    /// When enabled, the time offset will be automatically refreshed
269    /// based on the configured sync interval.
270    /// Default is true.
271    ///
272    /// # Arguments
273    ///
274    /// * `enabled` - Whether to enable automatic time sync.
275    ///
276    /// # Example
277    ///
278    /// ```no_run
279    /// use ccxt_exchanges::binance::BinanceBuilder;
280    ///
281    /// // Disable automatic sync for manual control
282    /// let builder = BinanceBuilder::new()
283    ///     .auto_time_sync(false);
284    /// ```
285    pub fn auto_time_sync(mut self, enabled: bool) -> Self {
286        self.options.auto_time_sync = enabled;
287        self
288    }
289
290    /// Enables or disables rate limiting.
291    ///
292    /// # Arguments
293    ///
294    /// * `enabled` - Whether to enable rate limiting.
295    ///
296    /// # Example
297    ///
298    /// ```no_run
299    /// use ccxt_exchanges::binance::BinanceBuilder;
300    ///
301    /// let builder = BinanceBuilder::new()
302    ///     .enable_rate_limit(true);
303    /// ```
304    pub fn enable_rate_limit(mut self, enabled: bool) -> Self {
305        self.config.enable_rate_limit = enabled;
306        self
307    }
308
309    /// Sets the HTTP proxy server URL.
310    ///
311    /// # Arguments
312    ///
313    /// * `proxy` - The proxy server URL.
314    ///
315    /// # Example
316    ///
317    /// ```no_run
318    /// use ccxt_exchanges::binance::BinanceBuilder;
319    ///
320    /// let builder = BinanceBuilder::new()
321    ///     .proxy("http://proxy.example.com:8080");
322    /// ```
323    pub fn proxy(mut self, proxy: impl Into<String>) -> Self {
324        self.config.proxy = Some(proxy.into());
325        self
326    }
327
328    /// Enables or disables verbose logging.
329    ///
330    /// # Arguments
331    ///
332    /// * `enabled` - Whether to enable verbose logging.
333    ///
334    /// # Example
335    ///
336    /// ```no_run
337    /// use ccxt_exchanges::binance::BinanceBuilder;
338    ///
339    /// let builder = BinanceBuilder::new()
340    ///     .verbose(true);
341    /// ```
342    pub fn verbose(mut self, enabled: bool) -> Self {
343        self.config.verbose = enabled;
344        self
345    }
346
347    /// Sets a custom option.
348    ///
349    /// # Arguments
350    ///
351    /// * `key` - Option key.
352    /// * `value` - Option value as JSON.
353    ///
354    /// # Example
355    ///
356    /// ```no_run
357    /// use ccxt_exchanges::binance::BinanceBuilder;
358    /// use serde_json::json;
359    ///
360    /// let builder = BinanceBuilder::new()
361    ///     .option("customOption", json!("value"));
362    /// ```
363    pub fn option(mut self, key: impl Into<String>, value: Value) -> Self {
364        self.config.options.insert(key.into(), value);
365        self
366    }
367
368    /// Sets multiple custom options.
369    ///
370    /// # Arguments
371    ///
372    /// * `options` - HashMap of option key-value pairs.
373    ///
374    /// # Example
375    ///
376    /// ```no_run
377    /// use ccxt_exchanges::binance::BinanceBuilder;
378    /// use serde_json::json;
379    /// use std::collections::HashMap;
380    ///
381    /// let mut options = HashMap::new();
382    /// options.insert("option1".to_string(), json!("value1"));
383    /// options.insert("option2".to_string(), json!(42));
384    ///
385    /// let builder = BinanceBuilder::new()
386    ///     .options(options);
387    /// ```
388    pub fn options(mut self, options: HashMap<String, Value>) -> Self {
389        self.config.options.extend(options);
390        self
391    }
392
393    /// Builds the Binance exchange instance.
394    ///
395    /// # Returns
396    ///
397    /// Returns a `Result` containing the configured `Binance` instance.
398    ///
399    /// # Errors
400    ///
401    /// Returns an error if the exchange cannot be initialized.
402    ///
403    /// # Example
404    ///
405    /// ```no_run
406    /// use ccxt_exchanges::binance::BinanceBuilder;
407    ///
408    /// let binance = BinanceBuilder::new()
409    ///     .api_key("your-api-key")
410    ///     .secret("your-secret")
411    ///     .build()
412    ///     .unwrap();
413    /// ```
414    pub fn build(self) -> Result<Binance> {
415        Binance::new_with_options(self.config, self.options)
416    }
417}
418
419#[cfg(test)]
420mod tests {
421    use super::*;
422
423    #[test]
424    fn test_builder_default() {
425        let builder = BinanceBuilder::new();
426        assert_eq!(builder.config.id, "binance");
427        assert_eq!(builder.config.name, "Binance");
428        assert!(!builder.config.sandbox);
429        assert_eq!(builder.options.default_type, DefaultType::Spot);
430    }
431
432    #[test]
433    fn test_builder_api_key() {
434        let builder = BinanceBuilder::new().api_key("test-key");
435        assert_eq!(builder.config.api_key, Some("test-key".to_string()));
436    }
437
438    #[test]
439    fn test_builder_secret() {
440        let builder = BinanceBuilder::new().secret("test-secret");
441        assert_eq!(builder.config.secret, Some("test-secret".to_string()));
442    }
443
444    #[test]
445    fn test_builder_sandbox() {
446        let builder = BinanceBuilder::new().sandbox(true);
447        assert!(builder.config.sandbox);
448        assert!(builder.options.test);
449    }
450
451    #[test]
452    fn test_builder_timeout() {
453        let builder = BinanceBuilder::new().timeout(60);
454        assert_eq!(builder.config.timeout, 60);
455    }
456
457    #[test]
458    fn test_builder_recv_window() {
459        let builder = BinanceBuilder::new().recv_window(10000);
460        assert_eq!(builder.options.recv_window, 10000);
461    }
462
463    #[test]
464    fn test_builder_default_type_with_enum() {
465        let builder = BinanceBuilder::new().default_type(DefaultType::Swap);
466        assert_eq!(builder.options.default_type, DefaultType::Swap);
467    }
468
469    #[test]
470    fn test_builder_default_type_with_string() {
471        // Test backward compatibility with string values
472        let builder = BinanceBuilder::new().default_type("swap");
473        assert_eq!(builder.options.default_type, DefaultType::Swap);
474    }
475
476    #[test]
477    fn test_builder_default_type_legacy_future() {
478        // Test backward compatibility with legacy "future" value
479        let builder = BinanceBuilder::new().default_type("future");
480        assert_eq!(builder.options.default_type, DefaultType::Swap);
481    }
482
483    #[test]
484    fn test_builder_default_sub_type() {
485        let builder = BinanceBuilder::new()
486            .default_type(DefaultType::Swap)
487            .default_sub_type(DefaultSubType::Linear);
488        assert_eq!(builder.options.default_type, DefaultType::Swap);
489        assert_eq!(
490            builder.options.default_sub_type,
491            Some(DefaultSubType::Linear)
492        );
493    }
494
495    #[test]
496    fn test_builder_chaining() {
497        let builder = BinanceBuilder::new()
498            .api_key("key")
499            .secret("secret")
500            .sandbox(true)
501            .timeout(30)
502            .recv_window(5000)
503            .default_type(DefaultType::Spot);
504
505        assert_eq!(builder.config.api_key, Some("key".to_string()));
506        assert_eq!(builder.config.secret, Some("secret".to_string()));
507        assert!(builder.config.sandbox);
508        assert_eq!(builder.config.timeout, 30);
509        assert_eq!(builder.options.recv_window, 5000);
510        assert_eq!(builder.options.default_type, DefaultType::Spot);
511    }
512
513    #[test]
514    fn test_builder_build() {
515        let result = BinanceBuilder::new().build();
516        assert!(result.is_ok());
517
518        let binance = result.unwrap();
519        assert_eq!(binance.id(), "binance");
520        assert_eq!(binance.name(), "Binance");
521    }
522
523    #[test]
524    fn test_builder_build_with_credentials() {
525        let result = BinanceBuilder::new()
526            .api_key("test-key")
527            .secret("test-secret")
528            .build();
529
530        assert!(result.is_ok());
531    }
532
533    #[test]
534    fn test_builder_time_sync_interval() {
535        let builder = BinanceBuilder::new().time_sync_interval(60);
536        assert_eq!(builder.options.time_sync_interval_secs, 60);
537    }
538
539    #[test]
540    fn test_builder_auto_time_sync() {
541        let builder = BinanceBuilder::new().auto_time_sync(false);
542        assert!(!builder.options.auto_time_sync);
543    }
544
545    #[test]
546    fn test_builder_time_sync_chaining() {
547        let builder = BinanceBuilder::new()
548            .time_sync_interval(120)
549            .auto_time_sync(false);
550
551        assert_eq!(builder.options.time_sync_interval_secs, 120);
552        assert!(!builder.options.auto_time_sync);
553    }
554
555    #[test]
556    fn test_builder_build_with_time_sync_config() {
557        let result = BinanceBuilder::new()
558            .time_sync_interval(60)
559            .auto_time_sync(true)
560            .build();
561
562        assert!(result.is_ok());
563        let binance = result.unwrap();
564
565        // Verify the TimeSyncManager was created with correct config
566        let time_sync = binance.time_sync();
567        assert_eq!(
568            time_sync.config().sync_interval,
569            std::time::Duration::from_secs(60)
570        );
571        assert!(time_sync.config().auto_sync);
572    }
573
574    #[test]
575    fn test_builder_build_time_sync_disabled() {
576        let result = BinanceBuilder::new().auto_time_sync(false).build();
577
578        assert!(result.is_ok());
579        let binance = result.unwrap();
580
581        // Verify auto_sync is disabled
582        let time_sync = binance.time_sync();
583        assert!(!time_sync.config().auto_sync);
584    }
585}