1use 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#[derive(Debug)]
22pub struct Binance {
23 base: BaseExchange,
25 options: BinanceOptions,
27}
28
29#[derive(Debug, Clone)]
31pub struct BinanceOptions {
32 pub adjust_for_time_difference: bool,
34 pub recv_window: u64,
36 pub default_type: String,
38 pub test: bool,
40 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 pub fn builder() -> BinanceBuilder {
74 BinanceBuilder::new()
75 }
76
77 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 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 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 pub fn base(&self) -> &BaseExchange {
159 &self.base
160 }
161
162 pub fn base_mut(&mut self) -> &mut BaseExchange {
164 &mut self.base
165 }
166
167 pub fn options(&self) -> &BinanceOptions {
169 &self.options
170 }
171
172 pub fn set_options(&mut self, options: BinanceOptions) {
174 self.options = options;
175 }
176
177 pub fn id(&self) -> &str {
179 "binance"
180 }
181
182 pub fn name(&self) -> &str {
184 "Binance"
185 }
186
187 pub fn version(&self) -> &str {
189 "v3"
190 }
191
192 pub fn certified(&self) -> bool {
194 true
195 }
196
197 pub fn pro(&self) -> bool {
199 true
200 }
201
202 pub fn rate_limit(&self) -> f64 {
204 50.0
205 }
206
207 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 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 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 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 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#[derive(Debug, Clone)]
332pub struct BinanceUrls {
333 pub public: String,
335 pub private: String,
337 pub sapi: String,
339 pub sapi_v2: String,
341 pub fapi: String,
343 pub fapi_public: String,
345 pub fapi_private: String,
347 pub dapi: String,
349 pub dapi_public: String,
351 pub dapi_private: String,
353 pub eapi: String,
355 pub eapi_public: String,
357 pub eapi_private: String,
359 pub papi: String,
361 pub ws: String,
363 pub ws_fapi: String,
365}
366
367impl BinanceUrls {
368 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 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 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}