ccxt_exchanges/binance/
mod.rs

1//! Binance exchange implementation.
2//!
3//! Supports spot trading, futures trading, and options trading with complete REST API and WebSocket support.
4
5use ccxt_core::{BaseExchange, ExchangeConfig, Result};
6use std::collections::HashMap;
7
8pub mod auth;
9pub mod builder;
10pub mod error;
11mod exchange_impl;
12pub mod parser;
13pub mod rest;
14pub mod symbol;
15pub mod ws;
16mod ws_exchange_impl;
17
18pub use builder::BinanceBuilder;
19
20/// Binance exchange structure.
21#[derive(Debug)]
22pub struct Binance {
23    /// Base exchange instance.
24    base: BaseExchange,
25    /// Binance-specific options.
26    options: BinanceOptions,
27}
28
29/// Binance-specific options.
30#[derive(Debug, Clone)]
31pub struct BinanceOptions {
32    /// Enables time synchronization.
33    pub adjust_for_time_difference: bool,
34    /// Receive window in milliseconds.
35    pub recv_window: u64,
36    /// Default trading type (spot/margin/future/delivery/option).
37    pub default_type: String,
38    /// Enables testnet mode.
39    pub test: bool,
40    /// Enables demo environment.
41    pub demo: bool,
42}
43
44impl Default for BinanceOptions {
45    fn default() -> Self {
46        Self {
47            adjust_for_time_difference: false,
48            recv_window: 5000,
49            default_type: "spot".to_string(),
50            test: false,
51            demo: false,
52        }
53    }
54}
55
56impl Binance {
57    /// Creates a new Binance instance using the builder pattern.
58    ///
59    /// This is the recommended way to create a Binance instance.
60    ///
61    /// # Example
62    ///
63    /// ```no_run
64    /// use ccxt_exchanges::binance::Binance;
65    ///
66    /// let binance = Binance::builder()
67    ///     .api_key("your-api-key")
68    ///     .secret("your-secret")
69    ///     .sandbox(true)
70    ///     .build()
71    ///     .unwrap();
72    /// ```
73    pub fn builder() -> BinanceBuilder {
74        BinanceBuilder::new()
75    }
76
77    /// Creates a new Binance instance.
78    ///
79    /// # Arguments
80    ///
81    /// * `config` - Exchange configuration.
82    ///
83    /// # Example
84    ///
85    /// ```no_run
86    /// use ccxt_exchanges::binance::Binance;
87    /// use ccxt_core::ExchangeConfig;
88    ///
89    /// let config = ExchangeConfig {
90    ///     id: "binance".to_string(),
91    ///     name: "Binance".to_string(),
92    ///     api_key: Some("your-api-key".to_string()),
93    ///     secret: Some("your-secret".to_string()),
94    ///     ..Default::default()
95    /// };
96    ///
97    /// let binance = Binance::new(config).unwrap();
98    /// ```
99    pub fn new(config: ExchangeConfig) -> Result<Self> {
100        let base = BaseExchange::new(config)?;
101        let options = BinanceOptions::default();
102
103        Ok(Self { base, options })
104    }
105
106    /// Creates a new Binance instance with custom options.
107    ///
108    /// This is used internally by the builder pattern.
109    ///
110    /// # Arguments
111    ///
112    /// * `config` - Exchange configuration.
113    /// * `options` - Binance-specific options.
114    ///
115    /// # Example
116    ///
117    /// ```no_run
118    /// use ccxt_exchanges::binance::{Binance, BinanceOptions};
119    /// use ccxt_core::ExchangeConfig;
120    ///
121    /// let config = ExchangeConfig::default();
122    /// let options = BinanceOptions {
123    ///     default_type: "future".to_string(),
124    ///     ..Default::default()
125    /// };
126    ///
127    /// let binance = Binance::new_with_options(config, options).unwrap();
128    /// ```
129    pub fn new_with_options(config: ExchangeConfig, options: BinanceOptions) -> Result<Self> {
130        let base = BaseExchange::new(config)?;
131        Ok(Self { base, options })
132    }
133
134    /// Creates a new Binance futures instance for perpetual contracts.
135    ///
136    /// # Arguments
137    ///
138    /// * `config` - Exchange configuration.
139    ///
140    /// # Example
141    ///
142    /// ```no_run
143    /// use ccxt_exchanges::binance::Binance;
144    /// use ccxt_core::ExchangeConfig;
145    ///
146    /// let config = ExchangeConfig::default();
147    /// let futures = Binance::new_futures(config).unwrap();
148    /// ```
149    pub fn new_futures(config: ExchangeConfig) -> Result<Self> {
150        let base = BaseExchange::new(config)?;
151        let mut options = BinanceOptions::default();
152        options.default_type = "future".to_string();
153
154        Ok(Self { base, options })
155    }
156
157    /// Returns a reference to the base exchange.
158    pub fn base(&self) -> &BaseExchange {
159        &self.base
160    }
161
162    /// Returns a mutable reference to the base exchange.
163    pub fn base_mut(&mut self) -> &mut BaseExchange {
164        &mut self.base
165    }
166
167    /// Returns the Binance options.
168    pub fn options(&self) -> &BinanceOptions {
169        &self.options
170    }
171
172    /// Sets the Binance options.
173    pub fn set_options(&mut self, options: BinanceOptions) {
174        self.options = options;
175    }
176
177    /// Returns the exchange ID.
178    pub fn id(&self) -> &str {
179        "binance"
180    }
181
182    /// Returns the exchange name.
183    pub fn name(&self) -> &str {
184        "Binance"
185    }
186
187    /// Returns the API version.
188    pub fn version(&self) -> &str {
189        "v3"
190    }
191
192    /// Returns `true` if the exchange is CCXT-certified.
193    pub fn certified(&self) -> bool {
194        true
195    }
196
197    /// Returns `true` if Pro version (WebSocket) is supported.
198    pub fn pro(&self) -> bool {
199        true
200    }
201
202    /// Returns the rate limit in requests per second.
203    pub fn rate_limit(&self) -> f64 {
204        50.0
205    }
206
207    /// Returns the supported timeframes.
208    pub fn timeframes(&self) -> HashMap<String, String> {
209        let mut timeframes = HashMap::new();
210        timeframes.insert("1s".to_string(), "1s".to_string());
211        timeframes.insert("1m".to_string(), "1m".to_string());
212        timeframes.insert("3m".to_string(), "3m".to_string());
213        timeframes.insert("5m".to_string(), "5m".to_string());
214        timeframes.insert("15m".to_string(), "15m".to_string());
215        timeframes.insert("30m".to_string(), "30m".to_string());
216        timeframes.insert("1h".to_string(), "1h".to_string());
217        timeframes.insert("2h".to_string(), "2h".to_string());
218        timeframes.insert("4h".to_string(), "4h".to_string());
219        timeframes.insert("6h".to_string(), "6h".to_string());
220        timeframes.insert("8h".to_string(), "8h".to_string());
221        timeframes.insert("12h".to_string(), "12h".to_string());
222        timeframes.insert("1d".to_string(), "1d".to_string());
223        timeframes.insert("3d".to_string(), "3d".to_string());
224        timeframes.insert("1w".to_string(), "1w".to_string());
225        timeframes.insert("1M".to_string(), "1M".to_string());
226        timeframes
227    }
228
229    /// Returns the API URLs.
230    pub fn urls(&self) -> BinanceUrls {
231        let mut urls = if self.base().config.sandbox {
232            BinanceUrls::testnet()
233        } else if self.options.demo {
234            BinanceUrls::demo()
235        } else {
236            BinanceUrls::production()
237        };
238
239        // Apply URL overrides if present
240        if let Some(public_url) = self.base().config.url_overrides.get("public") {
241            urls.public = public_url.clone();
242        }
243        if let Some(private_url) = self.base().config.url_overrides.get("private") {
244            urls.private = private_url.clone();
245        }
246        if let Some(fapi_public_url) = self.base().config.url_overrides.get("fapiPublic") {
247            urls.fapi_public = fapi_public_url.clone();
248        }
249        if let Some(fapi_private_url) = self.base().config.url_overrides.get("fapiPrivate") {
250            urls.fapi_private = fapi_private_url.clone();
251        }
252        if let Some(dapi_public_url) = self.base().config.url_overrides.get("dapiPublic") {
253            urls.dapi_public = dapi_public_url.clone();
254        }
255        if let Some(dapi_private_url) = self.base().config.url_overrides.get("dapiPrivate") {
256            urls.dapi_private = dapi_private_url.clone();
257        }
258
259        urls
260    }
261
262    /// Creates a WebSocket client for public data streams.
263    ///
264    /// Used for subscribing to public data streams (ticker, orderbook, trades, etc.).
265    ///
266    /// # Returns
267    ///
268    /// Returns a `BinanceWs` instance.
269    ///
270    /// # Example
271    /// ```no_run
272    /// use ccxt_exchanges::binance::Binance;
273    /// use ccxt_core::ExchangeConfig;
274    ///
275    /// # async fn example() -> ccxt_core::error::Result<()> {
276    /// let binance = Binance::new(ExchangeConfig::default())?;
277    /// let ws = binance.create_ws();
278    /// ws.connect().await?;
279    /// # Ok(())
280    /// # }
281    /// ```
282    pub fn create_ws(&self) -> ws::BinanceWs {
283        let urls = self.urls();
284        let ws_url = if self.options.default_type == "future" {
285            urls.ws_fapi
286        } else {
287            urls.ws
288        };
289        ws::BinanceWs::new(ws_url)
290    }
291
292    /// Creates an authenticated WebSocket client for user data streams.
293    ///
294    /// Used for subscribing to private data streams (account balance, order updates, trade history, etc.).
295    /// Requires API key configuration.
296    ///
297    /// # Returns
298    ///
299    /// Returns a `BinanceWs` instance with listen key manager.
300    ///
301    /// # Example
302    /// ```no_run
303    /// use ccxt_exchanges::binance::Binance;
304    /// use ccxt_core::ExchangeConfig;
305    /// use std::sync::Arc;
306    ///
307    /// # async fn example() -> ccxt_core::error::Result<()> {
308    /// let config = ExchangeConfig {
309    ///     api_key: Some("your-api-key".to_string()),
310    ///     secret: Some("your-secret".to_string()),
311    ///     ..Default::default()
312    /// };
313    /// let binance = Arc::new(Binance::new(config)?);
314    /// let ws = binance.create_authenticated_ws();
315    /// ws.connect_user_stream().await?;
316    /// # Ok(())
317    /// # }
318    /// ```
319    pub fn create_authenticated_ws(self: &std::sync::Arc<Self>) -> ws::BinanceWs {
320        let urls = self.urls();
321        let ws_url = if self.options.default_type == "future" {
322            urls.ws_fapi
323        } else {
324            urls.ws
325        };
326        ws::BinanceWs::new_with_auth(ws_url, self.clone())
327    }
328}
329
330/// Binance API URLs.
331#[derive(Debug, Clone)]
332pub struct BinanceUrls {
333    /// Public API URL.
334    pub public: String,
335    /// Private API URL.
336    pub private: String,
337    /// SAPI URL (Spot API).
338    pub sapi: String,
339    /// SAPI V2 URL.
340    pub sapi_v2: String,
341    /// FAPI URL (Futures API - short form).
342    pub fapi: String,
343    /// FAPI URL (Futures API).
344    pub fapi_public: String,
345    /// FAPI Private URL.
346    pub fapi_private: String,
347    /// DAPI URL (Delivery API - short form).
348    pub dapi: String,
349    /// DAPI URL (Delivery API).
350    pub dapi_public: String,
351    /// DAPI Private URL.
352    pub dapi_private: String,
353    /// EAPI URL (Options API - short form).
354    pub eapi: String,
355    /// EAPI URL (Options API).
356    pub eapi_public: String,
357    /// EAPI Private URL.
358    pub eapi_private: String,
359    /// PAPI URL (Portfolio Margin API).
360    pub papi: String,
361    /// WebSocket URL.
362    pub ws: String,
363    /// WebSocket Futures URL.
364    pub ws_fapi: String,
365}
366
367impl BinanceUrls {
368    /// Returns production environment URLs.
369    pub fn production() -> Self {
370        Self {
371            public: "https://api.binance.com/api/v3".to_string(),
372            private: "https://api.binance.com/api/v3".to_string(),
373            sapi: "https://api.binance.com/sapi/v1".to_string(),
374            sapi_v2: "https://api.binance.com/sapi/v2".to_string(),
375            fapi: "https://fapi.binance.com/fapi/v1".to_string(),
376            fapi_public: "https://fapi.binance.com/fapi/v1".to_string(),
377            fapi_private: "https://fapi.binance.com/fapi/v1".to_string(),
378            dapi: "https://dapi.binance.com/dapi/v1".to_string(),
379            dapi_public: "https://dapi.binance.com/dapi/v1".to_string(),
380            dapi_private: "https://dapi.binance.com/dapi/v1".to_string(),
381            eapi: "https://eapi.binance.com/eapi/v1".to_string(),
382            eapi_public: "https://eapi.binance.com/eapi/v1".to_string(),
383            eapi_private: "https://eapi.binance.com/eapi/v1".to_string(),
384            papi: "https://papi.binance.com/papi/v1".to_string(),
385            ws: "wss://stream.binance.com:9443/ws".to_string(),
386            ws_fapi: "wss://fstream.binance.com/ws".to_string(),
387        }
388    }
389
390    /// Returns testnet URLs.
391    pub fn testnet() -> Self {
392        Self {
393            public: "https://testnet.binance.vision/api/v3".to_string(),
394            private: "https://testnet.binance.vision/api/v3".to_string(),
395            sapi: "https://testnet.binance.vision/sapi/v1".to_string(),
396            sapi_v2: "https://testnet.binance.vision/sapi/v2".to_string(),
397            fapi: "https://testnet.binancefuture.com/fapi/v1".to_string(),
398            fapi_public: "https://testnet.binancefuture.com/fapi/v1".to_string(),
399            fapi_private: "https://testnet.binancefuture.com/fapi/v1".to_string(),
400            dapi: "https://testnet.binancefuture.com/dapi/v1".to_string(),
401            dapi_public: "https://testnet.binancefuture.com/dapi/v1".to_string(),
402            dapi_private: "https://testnet.binancefuture.com/dapi/v1".to_string(),
403            eapi: "https://testnet.binanceops.com/eapi/v1".to_string(),
404            eapi_public: "https://testnet.binanceops.com/eapi/v1".to_string(),
405            eapi_private: "https://testnet.binanceops.com/eapi/v1".to_string(),
406            papi: "https://testnet.binance.vision/papi/v1".to_string(),
407            ws: "wss://testnet.binance.vision/ws".to_string(),
408            ws_fapi: "wss://stream.binancefuture.com/ws".to_string(),
409        }
410    }
411
412    /// Returns demo environment URLs.
413    pub fn demo() -> Self {
414        Self {
415            public: "https://demo-api.binance.com/api/v3".to_string(),
416            private: "https://demo-api.binance.com/api/v3".to_string(),
417            sapi: "https://demo-api.binance.com/sapi/v1".to_string(),
418            sapi_v2: "https://demo-api.binance.com/sapi/v2".to_string(),
419            fapi: "https://demo-fapi.binance.com/fapi/v1".to_string(),
420            fapi_public: "https://demo-fapi.binance.com/fapi/v1".to_string(),
421            fapi_private: "https://demo-fapi.binance.com/fapi/v1".to_string(),
422            dapi: "https://demo-dapi.binance.com/dapi/v1".to_string(),
423            dapi_public: "https://demo-dapi.binance.com/dapi/v1".to_string(),
424            dapi_private: "https://demo-dapi.binance.com/dapi/v1".to_string(),
425            eapi: "https://demo-eapi.binance.com/eapi/v1".to_string(),
426            eapi_public: "https://demo-eapi.binance.com/eapi/v1".to_string(),
427            eapi_private: "https://demo-eapi.binance.com/eapi/v1".to_string(),
428            papi: "https://demo-papi.binance.com/papi/v1".to_string(),
429            ws: "wss://demo-stream.binance.com/ws".to_string(),
430            ws_fapi: "wss://demo-fstream.binance.com/ws".to_string(),
431        }
432    }
433}
434
435#[cfg(test)]
436mod tests {
437    use super::*;
438
439    #[test]
440    fn test_binance_creation() {
441        let config = ExchangeConfig {
442            id: "binance".to_string(),
443            name: "Binance".to_string(),
444            ..Default::default()
445        };
446
447        let binance = Binance::new(config);
448        assert!(binance.is_ok());
449
450        let binance = binance.unwrap();
451        assert_eq!(binance.id(), "binance");
452        assert_eq!(binance.name(), "Binance");
453        assert_eq!(binance.version(), "v3");
454        assert!(binance.certified());
455        assert!(binance.pro());
456    }
457
458    #[test]
459    fn test_timeframes() {
460        let config = ExchangeConfig::default();
461        let binance = Binance::new(config).unwrap();
462        let timeframes = binance.timeframes();
463
464        assert!(timeframes.contains_key("1m"));
465        assert!(timeframes.contains_key("1h"));
466        assert!(timeframes.contains_key("1d"));
467        assert_eq!(timeframes.len(), 16);
468    }
469
470    #[test]
471    fn test_urls() {
472        let config = ExchangeConfig::default();
473        let binance = Binance::new(config).unwrap();
474        let urls = binance.urls();
475
476        assert!(urls.public.contains("api.binance.com"));
477        assert!(urls.ws.contains("stream.binance.com"));
478    }
479
480    #[test]
481    fn test_sandbox_urls() {
482        let config = ExchangeConfig {
483            sandbox: true,
484            ..Default::default()
485        };
486        let binance = Binance::new(config).unwrap();
487        let urls = binance.urls();
488
489        assert!(urls.public.contains("testnet"));
490    }
491}