├── .gitignore ├── .vscode └── launch.json ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── account.rs ├── account_ws.rs ├── market.rs └── market_ws.rs └── src ├── client.rs ├── client ├── account.rs └── market.rs ├── error.rs ├── lib.rs ├── models.rs └── websocket.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug", 11 | "program": "${workspaceFolder}/target/debug/examples/account", 12 | "args": [], 13 | "cwd": "${workspaceFolder}" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hbdm_swap" 3 | version = "0.1.0" 4 | authors = ["QiaoXiaofeng"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | reqwest = { version = "0.10", features = ["blocking", "json"] } 9 | serde = { version = "^1.0", features = ["derive"] } 10 | serde_json = "^1.0" 11 | time = "0.1.38" 12 | ring = "0.13.0-alpha" 13 | hex = "0.3" 14 | base64 = "~0.6.0" 15 | crypto = { version = "0.2.36", package = "rust-crypto" } 16 | serialize = { version = "^0.3", package = "rustc-serialize" } 17 | data-encoding = "2.1.2" 18 | chrono = "^0.4" 19 | percent-encoding = "1.0.1" 20 | log = "0.4.5" 21 | simple_logger = "1.0.1" 22 | tungstenite = "0.9" 23 | url = "2.1" 24 | flate2 = "1.0" 25 | lazy_static = "1.4.0" 26 | rand = "0.7.3" 27 | 28 | [lib] 29 | name = "hbdm_swap" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 (andyjoe318@gmail.com) 2 | 3 | Licensed under either of 4 | 5 | * Apache License, Version 2.0, (http://www.apache.org/licenses/LICENSE-2.0) 6 | * MIT license (http://opensource.org/licenses/MIT) 7 | 8 | at your option. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hbdm_swap 2 | 3 | Rust Library for the [HBDM SWAP API](https://huobiapi.github.io/docs/coin_margined_swap/v1/cn/) 4 | 5 | [![MIT licensed](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE-MIT) 6 | [![Apache-2.0 licensed](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE-APACHE) 7 | 8 | ## Risk Warning 9 | 10 | Use at your own risk. We will not be responsible for your investment losses. 11 | 12 | Cryptocurrency investment is subject to high market risk. 13 | 14 | ## Usage 15 | 16 | Add this to your Cargo.toml 17 | 18 | ```toml 19 | [dependencies] 20 | hbdm_swap = { git = "https://github.com/hbdmapi/hbdm_swap_Rust.git" } 21 | ``` 22 | 23 | ## Rust >= 1.37 24 | 25 | ```rust 26 | rustup install stable 27 | ``` 28 | 29 | ### MARKET DATA 30 | 31 | ```rust 32 | extern crate hbdm_swap; 33 | use hbdm_swap::*; 34 | // extern crate simple_logger; 35 | 36 | fn main() { 37 | // simple_logger::init().unwrap(); 38 | let client = Client::new("", ""); 39 | 40 | // get contract info 41 | match client.get_contract_info() { 42 | Ok(pairs) => println!( 43 | "contract info: {:?}", 44 | pairs, 45 | ), 46 | Err(why) => println!("error: {}", why), 47 | } 48 | 49 | //get orderbook 50 | match client.get_orderbook("BTC-USD", "step0") { 51 | Ok(orderbook) => println!( 52 | "orderbook info : {:?}", 53 | orderbook, 54 | ), 55 | Err(why) => println!("error:{}", why), 56 | } 57 | 58 | //get klines 59 | match client.get_klines("BTC-USD", "1min", 10, None, None) { 60 | Ok(klines) => println!( 61 | "klines: {:?} ", 62 | klines, 63 | ), 64 | Err(why) => println!("error: {}", why), 65 | } 66 | 67 | // get index data 68 | match client.get_swap_index("BTC-USD".to_string()) { 69 | Ok(index) => println!( 70 | "index: {:?} ", 71 | index, 72 | ), 73 | Err(why) => println!("error: {}", why), 74 | } 75 | 76 | // get price limit 77 | match client.get_price_limit("BTC-USD".to_string()) { 78 | Ok(index) => println!( 79 | "price limit: {:?}", 80 | index, 81 | ), 82 | Err(why) => println!("error: {}", why), 83 | } 84 | 85 | // get open interest 86 | match client.get_open_interest("BTC-USD".to_string()) { 87 | Ok(index) => println!( 88 | "open interest: {:?}", 89 | index, 90 | ), 91 | Err(why) => println!("error: {}", why), 92 | } 93 | 94 | // get merged market data 95 | match client.get_market_merged("BTC-USD") { 96 | Ok(market_merged) => println!( 97 | "merged data: {:?}", 98 | market_merged, 99 | ), 100 | 101 | Err(why) => println!("error: {}", why), 102 | } 103 | 104 | // get market data 105 | match client.get_market_trade("BTC-USD") { 106 | Ok(market_trade) => println!( 107 | "market trade: {:?}", 108 | market_trade 109 | ), 110 | 111 | Err(why) => println!("error: {}", why), 112 | } 113 | 114 | // get market history data 115 | match client.get_market_history_trade("BTC-USD", 2) { 116 | Ok(history_trade) => println!( 117 | "history_trade: {:?}", 118 | history_trade 119 | ), 120 | 121 | Err(why) => println!("error: {}", why), 122 | } 123 | 124 | // get risk info 125 | match client.get_risk_info("BTC-USD".to_string()) { 126 | Ok(risk_info) => println!( 127 | "risk_info: {:?}", risk_info 128 | ), 129 | 130 | Err(why) => println!("error: {}", why), 131 | } 132 | 133 | // get insurance fund 134 | match client.get_insurance_fund("BTC-USD") { 135 | Ok(insurance_fund) => println!( 136 | "insurance_fund: {:?}", insurance_fund 137 | ), 138 | Err(why) => println!("error: {}", why), 139 | } 140 | 141 | // get adjust factor 142 | match client.get_adjust_factor("BTC-USD".to_string()) { 143 | Ok(adjust_factor) => println!( 144 | "adjust_factor: {:?}", adjust_factor 145 | ), 146 | 147 | Err(why) => println!("error: {}", why), 148 | } 149 | 150 | // get history open interest 151 | match client.get_his_open_interest("BTC-USD", "1day", None, 1) { 152 | Ok(open_interest) => println!( 153 | "open_interest: {:?}", open_interest 154 | ), 155 | 156 | Err(why) => println!("error: {}", why), 157 | } 158 | 159 | // get elite account ratio 160 | match client.get_elite_account_ratio("BTC-USD", "1day") { 161 | Ok(elite_account_ratio) => println!( 162 | "elite_account_ratio: {:?}", elite_account_ratio 163 | ), 164 | 165 | Err(why) => println!("error: {}", why), 166 | } 167 | 168 | // get elite position ratio 169 | match client.get_elite_position_ratio("BTC-USD", "1day") { 170 | Ok(elite_position_ratio) => println!( 171 | "elite_position_ratio: {:?}", elite_position_ratio 172 | ), 173 | 174 | Err(why) => println!("error: {}", why), 175 | } 176 | 177 | // get api state 178 | match client.get_api_state("BTC-USD".to_string()) { 179 | Ok(api_state) => println!( 180 | "api_state: {:?}", api_state 181 | ), 182 | 183 | Err(why) => println!("error: {}", why), 184 | } 185 | 186 | // get funding rate 187 | match client.get_funding_rate("BTC-USD".to_string()) { 188 | Ok(funding_rate) => println!( 189 | "funding_rate: {:?}", funding_rate 190 | ), 191 | 192 | Err(why) => println!("error: {}", why), 193 | } 194 | 195 | // get history funding rate 196 | match client.get_his_funding_rate("BTC-USD", None, None) { 197 | Ok(his_funding_rate) => println!( 198 | "his_funding_rate: {:?}", his_funding_rate 199 | ), 200 | 201 | Err(why) => println!("error: {}", why), 202 | } 203 | 204 | // get liquidation orders 205 | match client.get_liquidation_orders("BTC-USD", 0, 7, None, None) { 206 | Ok(liquidation_order) => println!( 207 | "liquidation_order: {:?}", liquidation_order 208 | ), 209 | 210 | Err(why) => println!("error: {}", why), 211 | } 212 | 213 | } 214 | 215 | ``` 216 | 217 | ### ACCOUNT DATA 218 | 219 | ```rust 220 | extern crate log; 221 | extern crate rand; 222 | // extern crate simple_logger; 223 | extern crate hbdm_swap; 224 | use hbdm_swap::*; 225 | use rand::Rng; 226 | 227 | fn main() { 228 | // simple_logger::init().unwrap(); 229 | let client = Client::new("YOUR_API_ACCESSKEY", "YOUR_API_SECRETKEY"); 230 | 231 | let masteraccount_uid = "your master account uid".to_string(); 232 | 233 | // your subaccount uid 234 | let subaccount_uid = 100000; 235 | 236 | let mut rng = rand::thread_rng(); 237 | 238 | let client_order_id: u32 = rng.gen(); 239 | 240 | 241 | //transfer between spot and swap 242 | match client.spot_account_transfer("spot", "swap", "btc", 0.0009) { 243 | Ok(transfer_result) => println!( 244 | "transfer_result:\n {:?}", 245 | transfer_result 246 | ), 247 | 248 | Err(why)=> println!("error: {}", why), 249 | } 250 | 251 | // transfer between master and subaccount 252 | match client.swap_master_sub_transfer(masteraccount_uid, "btc-usd".to_string(), 0.0001, "master_to_sub".to_string()) { 253 | Ok(transfer) => 254 | println!( 255 | "transfer: \n {:?}", 256 | transfer 257 | ), 258 | Err(why) => println!("error: {}", why), 259 | } 260 | 261 | // place an order 262 | match client.place_order("btc-usd", client_order_id, 9999.1, 1, "sell", "open", 20, "limit" ) { 263 | Ok(order) => println!( 264 | "order: \n {:?}", 265 | order 266 | ), 267 | Err(why) => println!("{}", why), 268 | } 269 | 270 | //place orders 271 | let orders = BatchOrderRequest { 272 | orders_data: vec![ 273 | OrderRequest{ 274 | contract_code: "btc-usd".to_string(), 275 | client_order_id: None, 276 | price: Some(9999.1), 277 | volume: 1, 278 | direction: "sell".to_string(), 279 | offset: "open".to_string(), 280 | lever_rate: 20, 281 | order_price_type: "limit".to_string(), 282 | } 283 | ] 284 | 285 | }; 286 | 287 | match client.place_orders(orders) { 288 | Ok(orders) => println!( 289 | "orders: \n {:?}", 290 | orders 291 | ), 292 | Err(why) => println!("{}", why), 293 | } 294 | 295 | // get account info 296 | match client.get_account("btc-usd".to_string()) { 297 | Ok(accounts) => println!( 298 | "accounts:\n{:?}", 299 | accounts 300 | ), 301 | Err(why) => println!("{}", why), 302 | } 303 | 304 | //get position info 305 | match client.get_position_info("BTC-USD".to_string()) { 306 | Ok(position_info) => println!( 307 | "position_info:\n {:?}", 308 | position_info 309 | ), 310 | Err(why) => println!("{}", why), 311 | } 312 | 313 | // get sub account list 314 | match client.get_sub_account_list("btc-usd".to_string()) { 315 | Ok(subaccountinfo) => 316 | println!( 317 | "subaccountinfo: \n {:?}", 318 | subaccountinfo 319 | ), 320 | Err(why) => println!("{}", why), 321 | } 322 | 323 | // cancel orders 324 | match client.cancel_orders("".to_string(), client_order_id.to_string(), "btc-usd".to_string()) { 325 | Ok(orders) => println!( 326 | "orders: \n {:?}", 327 | orders 328 | ), 329 | Err(why) => println!("{}", why), 330 | } 331 | 332 | // cancel all orders 333 | match client.cancel_all("BTC-USD".to_string()) { 334 | Ok(orders) => println!( 335 | "orders: \n {:?}", 336 | orders 337 | ), 338 | Err(why) => println!("{}", why), 339 | } 340 | 341 | // get subaccountinfo 342 | match client.get_sub_account_info("btc-usd".to_string(), subaccount_uid) { 343 | Ok(subaccountinfo) => 344 | println!( 345 | "subaccountinfo: \n {:?}", 346 | subaccountinfo 347 | ), 348 | Err(why) => println!("{}", why), 349 | } 350 | 351 | // get sub account position info 352 | match client.get_sub_position_info("btc-usd".to_string(), subaccount_uid) { 353 | Ok(subpositioninfo) => 354 | println!( 355 | "subpositioninfo: \n {:?}", 356 | subpositioninfo 357 | ), 358 | Err(why) => println!("{}", why), 359 | } 360 | 361 | // get financial record 362 | match client.get_financial_record("btc-usd".to_string(), None, None, None, None) { 363 | Ok(financialrecord) => 364 | println!( 365 | "financial_record: \n {:?}", 366 | financialrecord 367 | ), 368 | Err(why) => println!("{}", why), 369 | } 370 | 371 | // get order limit 372 | match client.get_order_limit("btc-usd".to_string(), "limit".to_string() ) { 373 | Ok(orderlimit) => 374 | println!( 375 | "orderlimit: \n {:?}", 376 | orderlimit 377 | ), 378 | Err(why) => println!("{}", why), 379 | } 380 | 381 | // get transfer limit 382 | match client.get_transfer_limit("btc-usd".to_string()) { 383 | Ok(transfer_limit) => 384 | println!( 385 | "transfer_limit: \n {:?}", 386 | transfer_limit 387 | ), 388 | Err(why) => println!("{}", why), 389 | } 390 | 391 | // get position limit 392 | match client.get_position_limit("btc-usd".to_string()) { 393 | Ok(position_limit) => 394 | println!( 395 | "position_limit: \n {:?}", 396 | position_limit 397 | ), 398 | Err(why) => println!("{}", why), 399 | } 400 | 401 | 402 | // get api trading status 403 | match client.get_api_trading_status() { 404 | Ok(api_trading_status) => println!( 405 | "api_trading_status: \n {:?}", 406 | api_trading_status 407 | ), 408 | Err(why) => println!("error: {}", why), 409 | } 410 | 411 | // get transfer records of master and subaccounts. 412 | match client.get_master_sub_transfer_record("btc-usd".to_string(), None, 1, None, None){ 413 | Ok(transfer_records) => println!( 414 | "transfer records: \n {:?}", 415 | transfer_records 416 | ), 417 | Err(why) => println!("error: {}", why), 418 | } 419 | 420 | //get swap order info 421 | match client.get_order_info(None, client_order_id.to_string(), "btc-usd".to_string()) { 422 | Ok(order_info) => println!( 423 | "order info:\n {:?}", 424 | order_info 425 | ), 426 | Err(why) => println!("error: {}", why), 427 | } 428 | 429 | //get swap order detail 430 | match client.get_order_detail("BTC-USD".to_string(), 699204501151698944, None, 1, None, None) { 431 | Ok(order_info) => println!( 432 | "order info:\n {:?}", 433 | order_info 434 | ), 435 | Err(why) => println!("error: {}", why), 436 | } 437 | 438 | //get swap open orders 439 | match client.get_open_orders("btc-usd".to_string(), None, None) { 440 | Ok(open_orders) => println!( 441 | "open orders:\n {:?}", 442 | open_orders 443 | ), 444 | Err(why) => println!("error: {}", why), 445 | } 446 | 447 | // get swap history orders 448 | match client.get_his_orders("btc-usd".to_string(), 0, 1, "0".to_string(), 1, None, None) { 449 | Ok(his_orders) => println!( 450 | "history orders:\n {:?}", 451 | his_orders 452 | ), 453 | Err(why) => println!("error: {}", why), 454 | } 455 | 456 | // get swap match results 457 | match client.get_match_results("BTC-USD".to_string(), 0, 1, None, None) { 458 | Ok(match_results) => println!( 459 | "match_results:\n {:?}", 460 | match_results 461 | ), 462 | Err(why) => println!("error: {}", why), 463 | } 464 | 465 | //lightning close position 466 | match client.lightning_close("btc-usd".to_string(), 1, "sell".to_string(), None, None) { 467 | Ok(lightning_close) => println!( 468 | "lightning_close:\n {:?}", 469 | lightning_close 470 | ), 471 | Err(why) => println!("error: {}", why), 472 | } 473 | 474 | 475 | 476 | } 477 | 478 | ``` 479 | 480 | ### Websocket Subscription of Market Data 481 | 482 | ```rust 483 | extern crate hbdm_swap; 484 | use hbdm_swap::*; 485 | use std::sync::atomic::{AtomicBool}; 486 | // extern crate simple_logger; 487 | 488 | fn main() { 489 | market_websocket(); 490 | } 491 | 492 | fn market_websocket() { 493 | // simple_logger::init().unwrap(); 494 | let keep_running = AtomicBool::new(true); // Used to control the event loop 495 | let marketws: String = "/swap-ws".to_string(); 496 | let mut web_socket: websocket::WebSockets = WebSockets::new(|event: WebsocketEvent| { 497 | match event { 498 | WebsocketEvent::OrderBook(order_book) => { 499 | println!( 500 | "orderbook:{:?}", order_book 501 | ); 502 | }, 503 | WebsocketEvent::TradeDetail(trade_detail) => { 504 | println!( 505 | "trade_detail:{:?}", trade_detail 506 | ); 507 | }, 508 | WebsocketEvent::Klines(klines) => { 509 | println!( 510 | "Klines:{:?}", klines 511 | ); 512 | }, 513 | _ => (), 514 | }; 515 | 516 | Ok(()) 517 | }); 518 | 519 | web_socket.connect(&marketws, vec!["BTC-USD"], vec!["orderbook", "kline.1min", "trade", "partial_orderbook"]).unwrap(); // check error 520 | if let Err(e) = web_socket.event_loop(&keep_running) { 521 | match e { 522 | err => { 523 | println!("Error: {}", err); 524 | } 525 | } 526 | } 527 | web_socket.disconnect().unwrap(); 528 | println!("disconnected"); 529 | } 530 | ``` 531 | 532 | ### Websocket Subscription of User Stream 533 | 534 | ```rust 535 | extern crate hbdm_swap; 536 | use hbdm_swap::*; 537 | use std::sync::atomic::{AtomicBool}; 538 | // extern crate simple_logger; 539 | 540 | fn main() { 541 | account_websocket(); 542 | } 543 | 544 | fn account_websocket() { 545 | // simple_logger::init().unwrap(); 546 | let keep_running = AtomicBool::new(true); 547 | let access_key = "YOUR_API_ACCESSKEY"; 548 | let secret_key = "YOUR_API_SECRETKEY"; 549 | let accountws: String = "/swap-notification".to_string(); 550 | let mut web_socket: WebSockets = WebSockets::new( | event: WebsocketEvent | { 551 | match event { 552 | WebsocketEvent::AccountUpdate(account_info) => { 553 | println!( 554 | "account: {:?}", account_info 555 | ); 556 | }, 557 | WebsocketEvent::OrderUpdate(order_info) => { 558 | println!( 559 | "order update: {:?}", order_info 560 | ); 561 | }, 562 | WebsocketEvent::PositionUpdate(position_info) => { 563 | println!( 564 | "position update: {:?}", position_info 565 | ); 566 | }, 567 | WebsocketEvent::LiquidationUpdate(liquidation_info) => { 568 | println!( 569 | "Liquidation update: {:?}", liquidation_info 570 | ); 571 | }, 572 | WebsocketEvent::FundingRateUpdate(funding_rate) => { 573 | println!( 574 | "FundingRate update: {:?}", funding_rate 575 | ); 576 | }, 577 | _ => (), 578 | }; 579 | 580 | Ok(()) 581 | }); 582 | 583 | web_socket.connect_auth(&accountws, vec!["BTC-USD"], vec!["account", "order", "position", "liquidation_order", "funding_rate"], access_key, secret_key).unwrap(); 584 | if let Err(e) = web_socket.event_loop(&keep_running) { 585 | match e { 586 | err => { 587 | println!("Error: {}", err); 588 | } 589 | } 590 | } 591 | 592 | web_socket.disconnect().unwrap(); 593 | println!("disconnected"); 594 | } 595 | ``` 596 | -------------------------------------------------------------------------------- /examples/account.rs: -------------------------------------------------------------------------------- 1 | extern crate log; 2 | extern crate rand; 3 | // extern crate simple_logger; 4 | extern crate hbdm_swap; 5 | use hbdm_swap::*; 6 | use rand::Rng; 7 | 8 | fn main() { 9 | // simple_logger::init().unwrap(); 10 | let client = Client::new("YOUR_API_KEY", "YOUR_SECRET_KEY"); 11 | 12 | let masteraccount_uid = "51031836".to_string(); 13 | let subaccount_uid = 112916395; 14 | 15 | let mut rng = rand::thread_rng(); 16 | 17 | let client_order_id: u32 = rng.gen(); 18 | 19 | 20 | //transfer between spot and swap 21 | match client.spot_account_transfer("spot", "swap", "btc", 0.0009) { 22 | Ok(transfer_result) => println!( 23 | "transfer_result:\n {:?}", 24 | transfer_result 25 | ), 26 | 27 | Err(why)=> println!("error: {}", why), 28 | } 29 | 30 | // transfer between master and subaccount 31 | match client.swap_master_sub_transfer(masteraccount_uid, "btc-usd".to_string(), 0.0001, "master_to_sub".to_string()) { 32 | Ok(transfer) => 33 | println!( 34 | "transfer: \n {:?}", 35 | transfer 36 | ), 37 | Err(why) => println!("error: {}", why), 38 | } 39 | 40 | // place an order 41 | match client.place_order("btc-usd", client_order_id, 9999.1, 1, "sell", "open", 125, "limit" ) { 42 | Ok(order) => println!( 43 | "order: \n {:?}", 44 | order 45 | ), 46 | Err(why) => println!("{}", why), 47 | } 48 | 49 | //place orders 50 | let orders = BatchOrderRequest { 51 | orders_data: vec![ 52 | OrderRequest{ 53 | contract_code: "btc-usd".to_string(), 54 | client_order_id: None, 55 | price: Some(9999.1), 56 | volume: 1, 57 | direction: "sell".to_string(), 58 | offset: "open".to_string(), 59 | lever_rate: 20, 60 | order_price_type: "limit".to_string(), 61 | } 62 | ] 63 | 64 | }; 65 | 66 | match client.place_orders(orders) { 67 | Ok(orders) => println!( 68 | "orders: \n {:?}", 69 | orders 70 | ), 71 | Err(why) => println!("{}", why), 72 | } 73 | 74 | // get account info 75 | match client.get_account("btc-usd".to_string()) { 76 | Ok(accounts) => println!( 77 | "accounts:\n{:?}", 78 | accounts 79 | ), 80 | Err(why) => println!("{}", why), 81 | } 82 | 83 | //get position info 84 | match client.get_position_info("BTC-USD".to_string()) { 85 | Ok(position_info) => println!( 86 | "position_info:\n {:?}", 87 | position_info 88 | ), 89 | Err(why) => println!("{}", why), 90 | } 91 | 92 | // get sub account list 93 | match client.get_sub_account_list("btc-usd".to_string()) { 94 | Ok(subaccountinfo) => 95 | println!( 96 | "subaccountinfo: \n {:?}", 97 | subaccountinfo 98 | ), 99 | Err(why) => println!("{}", why), 100 | } 101 | 102 | // cancel orders 103 | match client.cancel_orders("".to_string(), client_order_id.to_string(), "btc-usd".to_string()) { 104 | Ok(orders) => println!( 105 | "orders: \n {:?}", 106 | orders 107 | ), 108 | Err(why) => println!("{}", why), 109 | } 110 | 111 | // cancel all orders 112 | match client.cancel_all("BTC-USD".to_string()) { 113 | Ok(orders) => println!( 114 | "orders: \n {:?}", 115 | orders 116 | ), 117 | Err(why) => println!("{}", why), 118 | } 119 | 120 | // get subaccountinfo 121 | match client.get_sub_account_info("btc-usd".to_string(), subaccount_uid) { 122 | Ok(subaccountinfo) => 123 | println!( 124 | "subaccountinfo: \n {:?}", 125 | subaccountinfo 126 | ), 127 | Err(why) => println!("{}", why), 128 | } 129 | 130 | // get sub account position info 131 | match client.get_sub_position_info("btc-usd".to_string(), subaccount_uid) { 132 | Ok(subpositioninfo) => 133 | println!( 134 | "subpositioninfo: \n {:?}", 135 | subpositioninfo 136 | ), 137 | Err(why) => println!("{}", why), 138 | } 139 | 140 | // get financial record 141 | match client.get_financial_record("btc-usd".to_string(), None, None, None, None) { 142 | Ok(financialrecord) => 143 | println!( 144 | "financial_record: \n {:?}", 145 | financialrecord 146 | ), 147 | Err(why) => println!("{}", why), 148 | } 149 | 150 | // get order limit 151 | match client.get_order_limit("btc-usd".to_string(), "limit".to_string() ) { 152 | Ok(orderlimit) => 153 | println!( 154 | "orderlimit: \n {:?}", 155 | orderlimit 156 | ), 157 | Err(why) => println!("{}", why), 158 | } 159 | 160 | // get transfer limit 161 | match client.get_transfer_limit("btc-usd".to_string()) { 162 | Ok(transfer_limit) => 163 | println!( 164 | "transfer_limit: \n {:?}", 165 | transfer_limit 166 | ), 167 | Err(why) => println!("{}", why), 168 | } 169 | 170 | // get position limit 171 | match client.get_position_limit("btc-usd".to_string()) { 172 | Ok(position_limit) => 173 | println!( 174 | "position_limit: \n {:?}", 175 | position_limit 176 | ), 177 | Err(why) => println!("{}", why), 178 | } 179 | 180 | 181 | // get api trading status 182 | match client.get_api_trading_status() { 183 | Ok(api_trading_status) => println!( 184 | "api_trading_status: \n {:?}", 185 | api_trading_status 186 | ), 187 | Err(why) => println!("error: {}", why), 188 | } 189 | 190 | // get transfer records of master and subaccounts. 191 | match client.get_master_sub_transfer_record("btc-usd".to_string(), None, 1, None, None){ 192 | Ok(transfer_records) => println!( 193 | "transfer records: \n {:?}", 194 | transfer_records 195 | ), 196 | Err(why) => println!("error: {}", why), 197 | } 198 | 199 | //get swap order info 200 | match client.get_order_info(None, client_order_id.to_string(), "btc-usd".to_string()) { 201 | Ok(order_info) => println!( 202 | "order info:\n {:?}", 203 | order_info 204 | ), 205 | Err(why) => println!("error: {}", why), 206 | } 207 | 208 | //get swap order detail 209 | match client.get_order_detail("BTC-USD".to_string(), 699204501151698944, None, 1, None, None) { 210 | Ok(order_info) => println!( 211 | "order info:\n {:?}", 212 | order_info 213 | ), 214 | Err(why) => println!("error: {}", why), 215 | } 216 | 217 | //get swap open orders 218 | match client.get_open_orders("btc-usd".to_string(), None, None) { 219 | Ok(open_orders) => println!( 220 | "open orders:\n {:?}", 221 | open_orders 222 | ), 223 | Err(why) => println!("error: {}", why), 224 | } 225 | 226 | // get swap history orders 227 | match client.get_his_orders("btc-usd".to_string(), 0, 1, "0".to_string(), 1, None, None) { 228 | Ok(his_orders) => println!( 229 | "history orders:\n {:?}", 230 | his_orders 231 | ), 232 | Err(why) => println!("error: {}", why), 233 | } 234 | 235 | // get swap match results 236 | match client.get_match_results("BTC-USD".to_string(), 0, 1, None, None) { 237 | Ok(match_results) => println!( 238 | "match_results:\n {:?}", 239 | match_results 240 | ), 241 | Err(why) => println!("error: {}", why), 242 | } 243 | 244 | //lightning close position 245 | match client.lightning_close("btc-usd".to_string(), 1, "sell".to_string(), None, None) { 246 | Ok(lightning_close) => println!( 247 | "lightning_close:\n {:?}", 248 | lightning_close 249 | ), 250 | Err(why) => println!("error: {}", why), 251 | } 252 | 253 | 254 | 255 | } 256 | -------------------------------------------------------------------------------- /examples/account_ws.rs: -------------------------------------------------------------------------------- 1 | extern crate hbdm_swap; 2 | use hbdm_swap::*; 3 | use std::sync::atomic::{AtomicBool}; 4 | // extern crate simple_logger; 5 | 6 | fn main() { 7 | account_websocket(); 8 | } 9 | 10 | fn account_websocket() { 11 | // simple_logger::init().unwrap(); 12 | let keep_running = AtomicBool::new(true); 13 | let access_key = "YOUR_ACCESS_KEY"; 14 | let secret_key = "YOUR_SECRET_KEY"; 15 | let accountws: String = "/swap-notification".to_string(); 16 | let mut web_socket: WebSockets = WebSockets::new( | event: WebsocketEvent | { 17 | match event { 18 | WebsocketEvent::AccountUpdate(account_info) => { 19 | println!( 20 | "account: {:?}", account_info 21 | ); 22 | }, 23 | WebsocketEvent::OrderUpdate(order_info) => { 24 | println!( 25 | "order update: {:?}", order_info 26 | ); 27 | }, 28 | WebsocketEvent::PositionUpdate(position_info) => { 29 | println!( 30 | "position update: {:?}", position_info 31 | ); 32 | }, 33 | WebsocketEvent::LiquidationUpdate(liquidation_info) => { 34 | println!( 35 | "Liquidation update: {:?}", liquidation_info 36 | ); 37 | }, 38 | WebsocketEvent::FundingRateUpdate(funding_rate) => { 39 | println!( 40 | "FundingRate update: {:?}", funding_rate 41 | ); 42 | }, 43 | _ => (), 44 | }; 45 | 46 | Ok(()) 47 | }); 48 | 49 | web_socket.connect_auth(&accountws, vec!["BTC-USD"], vec!["account", "order", "position", "liquidation_order", "funding_rate"], access_key, secret_key).unwrap(); 50 | if let Err(e) = web_socket.event_loop(&keep_running) { 51 | match e { 52 | err => { 53 | println!("Error: {}", err); 54 | } 55 | } 56 | } 57 | 58 | web_socket.disconnect().unwrap(); 59 | println!("disconnected"); 60 | } -------------------------------------------------------------------------------- /examples/market.rs: -------------------------------------------------------------------------------- 1 | extern crate hbdm_swap; 2 | use hbdm_swap::*; 3 | // extern crate simple_logger; 4 | 5 | fn main() { 6 | // simple_logger::init().unwrap(); 7 | let client = Client::new("YOUR_API_KEY", "YOUR_SECRET_KEY"); 8 | 9 | // get contract info 10 | match client.get_contract_info() { 11 | Ok(pairs) => println!( 12 | "contract info: {:?}", 13 | pairs, 14 | ), 15 | Err(why) => println!("error: {}", why), 16 | } 17 | 18 | //get orderbook 19 | match client.get_orderbook("BTC-USD", "step0") { 20 | Ok(orderbook) => println!( 21 | "orderbook info : {:?}", 22 | orderbook, 23 | ), 24 | Err(why) => println!("error:{}", why), 25 | } 26 | 27 | //get klines 28 | match client.get_klines("BTC-USD", "1min", 10, None, None) { 29 | Ok(klines) => println!( 30 | "klines: {:?} ", 31 | klines, 32 | ), 33 | Err(why) => println!("error: {}", why), 34 | } 35 | 36 | // get index data 37 | match client.get_swap_index("BTC-USD".to_string()) { 38 | Ok(index) => println!( 39 | "index: {:?} ", 40 | index, 41 | ), 42 | Err(why) => println!("error: {}", why), 43 | } 44 | 45 | // get price limit 46 | match client.get_price_limit("BTC-USD".to_string()) { 47 | Ok(index) => println!( 48 | "price limit: {:?}", 49 | index, 50 | ), 51 | Err(why) => println!("error: {}", why), 52 | } 53 | 54 | // get open interest 55 | match client.get_open_interest("BTC-USD".to_string()) { 56 | Ok(index) => println!( 57 | "open interest: {:?}", 58 | index, 59 | ), 60 | Err(why) => println!("error: {}", why), 61 | } 62 | 63 | // get merged market data 64 | match client.get_market_merged("BTC-USD") { 65 | Ok(market_merged) => println!( 66 | "merged data: {:?}", 67 | market_merged, 68 | ), 69 | 70 | Err(why) => println!("error: {}", why), 71 | } 72 | 73 | // get market data 74 | match client.get_market_trade("BTC-USD") { 75 | Ok(market_trade) => println!( 76 | "market trade: {:?}", 77 | market_trade 78 | ), 79 | 80 | Err(why) => println!("error: {}", why), 81 | } 82 | 83 | // get market history data 84 | match client.get_market_history_trade("BTC-USD", 2) { 85 | Ok(history_trade) => println!( 86 | "history_trade: {:?}", 87 | history_trade 88 | ), 89 | 90 | Err(why) => println!("error: {}", why), 91 | } 92 | 93 | // get risk info 94 | match client.get_risk_info("BTC-USD".to_string()) { 95 | Ok(risk_info) => println!( 96 | "risk_info: {:?}", risk_info 97 | ), 98 | 99 | Err(why) => println!("error: {}", why), 100 | } 101 | 102 | // get insurance fund 103 | match client.get_insurance_fund("BTC-USD") { 104 | Ok(insurance_fund) => println!( 105 | "insurance_fund: {:?}", insurance_fund 106 | ), 107 | Err(why) => println!("error: {}", why), 108 | } 109 | 110 | // get adjust factor 111 | match client.get_adjust_factor("BTC-USD".to_string()) { 112 | Ok(adjust_factor) => println!( 113 | "adjust_factor: {:?}", adjust_factor 114 | ), 115 | 116 | Err(why) => println!("error: {}", why), 117 | } 118 | 119 | // get history open interest 120 | match client.get_his_open_interest("BTC-USD", "1day", None, 1) { 121 | Ok(open_interest) => println!( 122 | "open_interest: {:?}", open_interest 123 | ), 124 | 125 | Err(why) => println!("error: {}", why), 126 | } 127 | 128 | // get elite account ratio 129 | match client.get_elite_account_ratio("BTC-USD", "1day") { 130 | Ok(elite_account_ratio) => println!( 131 | "elite_account_ratio: {:?}", elite_account_ratio 132 | ), 133 | 134 | Err(why) => println!("error: {}", why), 135 | } 136 | 137 | // get elite position ratio 138 | match client.get_elite_position_ratio("BTC-USD", "1day") { 139 | Ok(elite_position_ratio) => println!( 140 | "elite_position_ratio: {:?}", elite_position_ratio 141 | ), 142 | 143 | Err(why) => println!("error: {}", why), 144 | } 145 | 146 | // get api state 147 | match client.get_api_state("BTC-USD".to_string()) { 148 | Ok(api_state) => println!( 149 | "api_state: {:?}", api_state 150 | ), 151 | 152 | Err(why) => println!("error: {}", why), 153 | } 154 | 155 | // get funding rate 156 | match client.get_funding_rate("BTC-USD".to_string()) { 157 | Ok(funding_rate) => println!( 158 | "funding_rate: {:?}", funding_rate 159 | ), 160 | 161 | Err(why) => println!("error: {}", why), 162 | } 163 | 164 | // get history funding rate 165 | match client.get_his_funding_rate("BTC-USD", None, None) { 166 | Ok(his_funding_rate) => println!( 167 | "his_funding_rate: {:?}", his_funding_rate 168 | ), 169 | 170 | Err(why) => println!("error: {}", why), 171 | } 172 | 173 | // get liquidation orders 174 | match client.get_liquidation_orders("BTC-USD", 0, 7, None, None) { 175 | Ok(liquidation_order) => println!( 176 | "liquidation_order: {:?}", liquidation_order 177 | ), 178 | 179 | Err(why) => println!("error: {}", why), 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /examples/market_ws.rs: -------------------------------------------------------------------------------- 1 | extern crate hbdm_swap; 2 | use hbdm_swap::*; 3 | use std::sync::atomic::{AtomicBool}; 4 | // extern crate simple_logger; 5 | 6 | fn main() { 7 | market_websocket(); 8 | } 9 | 10 | fn market_websocket() { 11 | // simple_logger::init().unwrap(); 12 | let keep_running = AtomicBool::new(true); // Used to control the event loop 13 | let marketws: String = "/swap-ws".to_string(); 14 | let mut web_socket: websocket::WebSockets = WebSockets::new(|event: WebsocketEvent| { 15 | match event { 16 | WebsocketEvent::OrderBook(order_book) => { 17 | println!( 18 | "orderbook:{:?}", order_book 19 | ); 20 | }, 21 | WebsocketEvent::TradeDetail(trade_detail) => { 22 | println!( 23 | "trade_detail:{:?}", trade_detail 24 | ); 25 | }, 26 | WebsocketEvent::Klines(klines) => { 27 | println!( 28 | "Klines:{:?}", klines 29 | ); 30 | }, 31 | _ => (), 32 | }; 33 | 34 | Ok(()) 35 | }); 36 | 37 | web_socket.connect(&marketws, vec!["BTC-USD"], vec!["orderbook", "kline.1min", "trade", "partial_orderbook"]).unwrap(); // check error 38 | if let Err(e) = web_socket.event_loop(&keep_running) { 39 | match e { 40 | err => { 41 | println!("Error: {}", err); 42 | } 43 | } 44 | } 45 | web_socket.disconnect().unwrap(); 46 | println!("disconnected"); 47 | } -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::*, models::*}; 2 | use ring::{digest, hmac}; 3 | use std::collections::BTreeMap; 4 | use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT, CONTENT_TYPE, ACCEPT}; 5 | use serde::{Serialize}; 6 | 7 | mod account; 8 | mod market; 9 | 10 | // re-exports 11 | pub use self::account::*; 12 | pub use self::market::*; 13 | 14 | #[derive(Clone)] 15 | pub struct Client { 16 | api_key: String, 17 | secret_key: String, 18 | } 19 | 20 | #[derive(Clone)] 21 | pub struct APIKey { 22 | api_key: String, 23 | secret_key: String, 24 | } 25 | 26 | static API_HOST: &'static str = "api.hbdm.com"; 27 | static SPOT_API_HOST: &'static str = "api.huobi.pro"; 28 | 29 | impl Client { 30 | pub fn new(api_key: &str, secret_key: &str) -> Self { 31 | Client { 32 | api_key: api_key.into(), 33 | secret_key: secret_key.into(), 34 | } 35 | } 36 | 37 | 38 | pub fn build_request(parameters: &BTreeMap) -> String { 39 | let mut request = String::new(); 40 | for (key, value) in parameters { 41 | let param = format!("{}={}&", key, value); 42 | request.push_str(param.as_ref()); 43 | } 44 | request.pop(); // remove last & 45 | 46 | request 47 | } 48 | 49 | pub fn get(&self, endpoint: &str, parameters: &BTreeMap) -> APIResult { 50 | let mut request_o = String::new(); 51 | for (key, value) in parameters { 52 | let param = format!("{}={}&", key, value); 53 | request_o.push_str(param.as_ref()); 54 | } 55 | request_o.pop(); // remove last & 56 | 57 | let request = format!("https://{}{}?{}", API_HOST, endpoint, request_o,); 58 | 59 | let body = reqwest::blocking::get(request.as_str())?.text()?; 60 | // ::log::info!("result: {:?}", body.clone()); 61 | 62 | // check for errors 63 | let err_response: APIErrorResponse = serde_json::from_str(body.as_str())?; 64 | 65 | ::log::info!("err_response: {:?}", err_response); 66 | 67 | 68 | match &err_response.status { 69 | Some(status) => { 70 | if status == "error" 71 | { 72 | return Err(Box::new(HuobiError::ApiError(format!( 73 | "result dump: {:?}", err_response 74 | )))); 75 | } 76 | }, 77 | None => ::log::info!("err_response: {:?}", err_response), 78 | } 79 | 80 | Ok(body) 81 | } 82 | 83 | pub fn get_signed( 84 | &self, 85 | endpoint: &str, 86 | mut params: BTreeMap, 87 | ) -> APIResult { 88 | params.insert("AccessKeyId".to_string(), self.api_key.clone()); 89 | params.insert("SignatureMethod".to_string(), "HmacSHA256".to_string()); 90 | params.insert("SignatureVersion".to_string(), "2".to_string()); 91 | params.insert("Timestamp".to_string(), get_timestamp()); 92 | 93 | 94 | println!("params: {:?}", params.clone()); 95 | 96 | let params = build_query_string(params); 97 | let signature = sign_hmac_sha256_base64( 98 | &self.secret_key, 99 | &format!("{}\n{}\n{}\n{}", "GET", API_HOST, endpoint, params,), 100 | ) 101 | .to_string(); 102 | 103 | let request = format!( 104 | "https://{}{}?{}&Signature={}", 105 | API_HOST, 106 | endpoint, 107 | params, 108 | percent_encode(&signature.clone()) 109 | ); 110 | 111 | ::log::info!("request: {:?}", request.clone()); 112 | 113 | let response = reqwest::blocking::get(request.as_str())?; 114 | let body = response.text()?; 115 | 116 | ::log::info!("body: {:?}", body.clone()); 117 | 118 | // check for errors 119 | let err_response : APIErrorResponse = serde_json::from_str(body.as_str())?; 120 | 121 | 122 | match &err_response.status { 123 | Some(status) => { 124 | if status == "error" 125 | { 126 | return Err(Box::new(HuobiError::ApiError(format!( 127 | "result dump: {:?}", err_response 128 | )))); 129 | } 130 | }, 131 | None => ::log::info!("err_response: {:?}", err_response), 132 | } 133 | 134 | Ok(body) 135 | } 136 | 137 | pub fn post_signed( 138 | &self, 139 | endpoint: &str, 140 | mut params: BTreeMap, 141 | payload: &T, 142 | ) -> APIResult { 143 | params.insert("AccessKeyId".to_string(), self.api_key.clone()); 144 | params.insert("SignatureMethod".to_string(), "HmacSHA256".to_string()); 145 | params.insert("SignatureVersion".to_string(), "2".to_string()); 146 | params.insert("Timestamp".to_string(), get_timestamp()); 147 | 148 | let api_host; 149 | 150 | if endpoint.to_string() == "/v2/account/transfer".to_string() { 151 | api_host = SPOT_API_HOST; 152 | } 153 | else{ 154 | api_host = API_HOST; 155 | } 156 | 157 | let params = build_query_string(params); 158 | let signature = sign_hmac_sha256_base64( 159 | &self.secret_key, 160 | &format!("{}\n{}\n{}\n{}", "POST", api_host, endpoint, params,), 161 | ) 162 | .to_string(); 163 | 164 | 165 | let request = format!( 166 | "https://{}{}?{}&Signature={}", 167 | api_host, 168 | endpoint, 169 | params, 170 | percent_encode(&signature.clone()) 171 | ); 172 | ::log::debug!("request: {:?}", request.clone()); 173 | 174 | let client = reqwest::blocking::Client::new(); 175 | let response = client 176 | .post(request.as_str()) 177 | .headers(build_headers(true)?) 178 | .json(&payload) 179 | .send()? 180 | ; 181 | 182 | // let body = response.text()?; 183 | let body = response.text()?; 184 | 185 | ::log::info!("body: {:?}", body.clone()); 186 | 187 | // check for errors 188 | let err_response: APIErrorResponse = serde_json::from_str(body.as_str())?; 189 | 190 | ::log::info!("err_response: {:?}", err_response); 191 | 192 | 193 | match &err_response.status { 194 | Some(status) => { 195 | if status == "error" 196 | { 197 | return Err(Box::new(HuobiError::ApiError(format!( 198 | "result dump: {:?}", err_response 199 | )))); 200 | } 201 | }, 202 | None => ::log::info!("err_response: {:?}", err_response), 203 | } 204 | 205 | 206 | Ok(body) 207 | } 208 | 209 | } 210 | 211 | pub fn build_query_string(parameters: BTreeMap) -> String { 212 | parameters 213 | .into_iter() 214 | .map(|(key, value)| format!("{}={}", key, percent_encode(&value.clone()))) 215 | .collect::>() 216 | .join("&") 217 | } 218 | 219 | pub fn sign_hmac_sha256_base64(secret: &str, digest: &str) -> String { 220 | use data_encoding::BASE64; 221 | 222 | let signed_key = hmac::SigningKey::new(&digest::SHA256, secret.as_bytes()); 223 | let signature = hmac::sign(&signed_key, digest.as_bytes()); 224 | let b64_encoded_sig = BASE64.encode(signature.as_ref()); 225 | 226 | b64_encoded_sig 227 | } 228 | 229 | pub fn percent_encode(source: &str) -> String { 230 | use percent_encoding::{define_encode_set, utf8_percent_encode, USERINFO_ENCODE_SET}; 231 | define_encode_set! { 232 | pub CUSTOM_ENCODE_SET = [USERINFO_ENCODE_SET] | { '+', ',' } 233 | } 234 | let signature = utf8_percent_encode(&source, CUSTOM_ENCODE_SET).to_string(); 235 | signature 236 | } 237 | 238 | pub fn get_timestamp() -> String { 239 | let utc_time = chrono::Utc::now(); 240 | let formatted_time = utc_time.format("%Y-%m-%dT%H:%M:%S").to_string(); 241 | 242 | formatted_time 243 | } 244 | 245 | 246 | pub fn build_headers(post_method: bool ) -> APIResult { 247 | let mut custom_headers = HeaderMap::new(); 248 | 249 | custom_headers.insert(USER_AGENT, HeaderValue::from_static("hbdm-rs")); 250 | if post_method { 251 | custom_headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); 252 | custom_headers.insert(ACCEPT, HeaderValue::from_static("application/json")); 253 | } 254 | else { 255 | custom_headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/x-www-form-urlencoded")); 256 | } 257 | 258 | Ok(custom_headers) 259 | } -------------------------------------------------------------------------------- /src/client/account.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use serde_json::from_str; 3 | 4 | impl Client { 5 | // Get Account Information 6 | pub fn get_account(&self, symbol: S) -> APIResult 7 | where S: Into> 8 | { 9 | let mut body: BTreeMap = BTreeMap::new(); 10 | let params: BTreeMap = BTreeMap::new(); 11 | if let Some(code) = symbol.into() { 12 | body.insert("contract_code".into(), format!("{}", code)); 13 | } 14 | println!("body: {:?}", body.clone()); 15 | let data = self.post_signed("/swap-api/v1/swap_account_info", params, &body)?; 16 | 17 | let account_info: AccountInfo = from_str(data.as_str())?; 18 | 19 | Ok(account_info) 20 | } 21 | 22 | // Get Position Information 23 | pub fn get_position_info(&self, symbol: S) -> APIResult 24 | where S: Into> 25 | { 26 | let mut body:BTreeMap = BTreeMap::new(); 27 | let params: BTreeMap = BTreeMap::new(); 28 | if let Some(code) = symbol.into() { 29 | body.insert("contract_code".into(), format!("{}", code)); 30 | } 31 | let data = self.post_signed("/swap-api/v1/swap_position_info", params, &body)?; 32 | 33 | let position_info: PositionInfo = from_str(data.as_str())?; 34 | 35 | Ok(position_info) 36 | } 37 | 38 | // get sub-account info by master-account 39 | pub fn get_sub_account_list(&self, contract_code: S) -> APIResult 40 | where S: Into> 41 | { 42 | let mut body: BTreeMap = BTreeMap::new(); 43 | let params: BTreeMap = BTreeMap::new(); 44 | 45 | if let Some(code) = contract_code.into() { body.insert("contract_code".into(), format!("{}", code));} 46 | 47 | let data = self.post_signed("/swap-api/v1/swap_sub_account_list", params, &body)?; 48 | 49 | let subaccountinfo: SubAccountList = from_str(data.as_str())?; 50 | 51 | Ok(subaccountinfo) 52 | } 53 | 54 | // get sub account info of the sub-uid by master 55 | pub fn get_sub_account_info(&self, contract_code: S, sub_uid: u64) -> APIResult 56 | where S: Into> 57 | { 58 | let mut body: BTreeMap = BTreeMap::new(); 59 | let params: BTreeMap = BTreeMap::new(); 60 | 61 | if let Some(code) = contract_code.into() { body.insert("contract_code".into(), format!("{}", code));} 62 | 63 | body.insert("sub_uid".to_string(), format!{"{}", sub_uid}); 64 | 65 | let data = self.post_signed("/swap-api/v1/swap_sub_account_info", params, &body)?; 66 | 67 | let subaccountinfo: SubAccountInfo = from_str(data.as_str())?; 68 | 69 | Ok(subaccountinfo) 70 | } 71 | 72 | 73 | // get sub position info of the sub-uid by master 74 | pub fn get_sub_position_info(&self, contract_code: S, sub_uid: u64) -> APIResult 75 | where S: Into> 76 | { 77 | let mut body: BTreeMap = BTreeMap::new(); 78 | let params: BTreeMap = BTreeMap::new(); 79 | 80 | if let Some(code) = contract_code.into() { body.insert("contract_code".into(), format!("{}", code));} 81 | 82 | body.insert("sub_uid".to_string(), format!{"{}", sub_uid}); 83 | 84 | let data = self.post_signed("/swap-api/v1/swap_sub_position_info", params, &body)?; 85 | 86 | let subpositioninfo: SubPositionInfo = from_str(data.as_str())?; 87 | 88 | Ok(subpositioninfo) 89 | } 90 | 91 | // get financial record 92 | pub fn get_financial_record(&self, contract_code: S1, trade_type: S2, created_date: S3, page_index: S4, page_size: S5) -> APIResult 93 | where S1: Into, S2: Into>, S3: Into>, S4: Into>, S5: Into> 94 | { 95 | let mut body: BTreeMap = BTreeMap::new(); 96 | let params: BTreeMap = BTreeMap::new(); 97 | 98 | body.insert("contract_code".into(), contract_code.into()); 99 | 100 | if let Some(t_type) = trade_type.into() { body.insert("type".into(), format!("{}", t_type));} 101 | if let Some(c_date) = created_date.into() { body.insert("created_date".into(), format!("{}", c_date)); } 102 | if let Some(offset) = page_index.into() { body.insert("page_index".into(), format!("{}", offset)); } 103 | if let Some(limit) = page_size.into() { body.insert("page_size".into(), format!("{}", limit));} 104 | 105 | let data = self.post_signed("/swap-api/v1/swap_financial_record", params, &body)?; 106 | 107 | let financialrecord: FinancialRecord = from_str(data.as_str())?; 108 | 109 | Ok(financialrecord) 110 | } 111 | 112 | // get order limit 113 | pub fn get_order_limit(&self, contract_code: S1, order_price_type: S2) -> APIResult 114 | where S1: Into>, S2: Into 115 | { 116 | let mut body: BTreeMap = BTreeMap::new(); 117 | let params: BTreeMap = BTreeMap::new(); 118 | 119 | body.insert("order_price_type".into(), order_price_type.into()); 120 | 121 | if let Some(code) = contract_code.into() { body.insert("contract_code".into(), format!("{}", code));} 122 | 123 | let data = self.post_signed("/swap-api/v1/swap_order_limit", params, &body)?; 124 | 125 | let orderlimit: OrderLimitInfo = from_str(data.as_str())?; 126 | 127 | Ok(orderlimit) 128 | } 129 | 130 | // get fee info 131 | pub fn get_fee_info(&self, contract_code: S1) -> APIResult 132 | where S1: Into> 133 | { 134 | let mut body: BTreeMap = BTreeMap::new(); 135 | let params: BTreeMap = BTreeMap::new(); 136 | 137 | if let Some(code) = contract_code.into() { body.insert("contract_code".into(), format!("{}", code));} 138 | 139 | let data = self.post_signed("/swap-api/v1/swap_fee", params, &body)?; 140 | 141 | let fee_info: FeeInfo = from_str(data.as_str())?; 142 | 143 | Ok(fee_info) 144 | } 145 | 146 | 147 | // get transfer limit 148 | pub fn get_transfer_limit(&self, contract_code: S1) -> APIResult 149 | where S1: Into> 150 | { 151 | let mut body: BTreeMap = BTreeMap::new(); 152 | let params: BTreeMap = BTreeMap::new(); 153 | 154 | if let Some(code) = contract_code.into() { body.insert("contract_code".into(), format!("{}", code));} 155 | 156 | let data = self.post_signed("/swap-api/v1/swap_transfer_limit", params, &body)?; 157 | 158 | let transfer_limit: TransferLimitInfo = from_str(data.as_str())?; 159 | 160 | Ok(transfer_limit) 161 | } 162 | 163 | // get position limit 164 | pub fn get_position_limit(&self, contract_code: S1) -> APIResult 165 | where S1: Into> 166 | { 167 | let mut body: BTreeMap = BTreeMap::new(); 168 | let params: BTreeMap = BTreeMap::new(); 169 | 170 | if let Some(code) = contract_code.into() { body.insert("contract_code".into(), format!("{}", code));} 171 | 172 | let data = self.post_signed("/swap-api/v1/swap_position_limit", params, &body)?; 173 | 174 | let position_limit: PositionLimitInfo = from_str(data.as_str())?; 175 | 176 | Ok(position_limit) 177 | } 178 | 179 | // transfer between master and sub account 180 | pub fn swap_master_sub_transfer(&self, sub_uid: String, contract_code: String, amount: f64, transfer_type: String) -> APIResult 181 | { 182 | let mut body: BTreeMap = BTreeMap::new(); 183 | let params: BTreeMap = BTreeMap::new(); 184 | 185 | body.insert("sub_uid".into(), sub_uid.to_string()); 186 | body.insert("contract_code".into(), contract_code.to_string()); 187 | body.insert("amount".into(), amount.to_string()); 188 | body.insert("type".into(), transfer_type.to_string()); 189 | 190 | let data = self.post_signed("/swap-api/v1/swap_master_sub_transfer", params, &body)?; 191 | 192 | let transfer_info: MasterSubTransferInfo = from_str(data.as_str())?; 193 | 194 | Ok(transfer_info) 195 | } 196 | 197 | // query swap master and sub account records 198 | pub fn get_master_sub_transfer_record(&self, contract_code: S1, transfer_type: S2, days: u32, page_index: S3, page_size: S4) -> APIResult 199 | where S1: Into, S2: Into>, S3: Into>, S4: Into> 200 | { 201 | let params: BTreeMap = BTreeMap::new(); 202 | let mut body: BTreeMap = BTreeMap::new(); 203 | 204 | body.insert("contract_code".to_string(), contract_code.into()); 205 | if let Some(trans_type) = transfer_type.into() { body.insert("transfer_type".into(), format!("{}", trans_type)); } 206 | if let Some(day) = days.into() { body.insert("create_date".into(), format!("{}", day));} 207 | if let Some(offset) = page_index.into() { body.insert("page_index".into(), format!("{}", offset));} 208 | if let Some(limit) = page_size.into() { body.insert("page_size".into(), format!("{}", limit));} 209 | 210 | let data = self.post_signed("/swap-api/v1/swap_master_sub_transfer_record", params, &body)?; 211 | 212 | let transfer_record: MasterSubTransferRecordInfo = from_str(data.as_str())?; 213 | 214 | Ok(transfer_record) 215 | 216 | } 217 | 218 | // query api status 219 | pub fn get_api_trading_status(&self) -> APIResult 220 | { 221 | let params: BTreeMap = BTreeMap::new(); 222 | 223 | let data = self.get_signed("/swap-api/v1/swap_api_trading_status", params)?; 224 | 225 | let api_status: ApiTradeStatus = from_str(data.as_str())?; 226 | 227 | Ok(api_status) 228 | } 229 | 230 | // place order 231 | pub fn place_order(&self, contract_code: S1, client_order_id: S2, price: S3, volume: u32, 232 | direction: S5, offset: S6, lever_rate: u32, order_price_type: S8) -> APIResult 233 | where S1: Into, S2: Into>, S3: Into>, S5: Into, S6: Into, 234 | S8: Into 235 | { 236 | let mut body:BTreeMap = BTreeMap::new(); 237 | let params: BTreeMap = BTreeMap::new(); 238 | 239 | body.insert("contract_code".into(), contract_code.into()); 240 | body.insert("volume".into(), format!("{}", volume)); 241 | body.insert("direction".into(), direction.into()); 242 | body.insert("offset".into(), offset.into()); 243 | body.insert("lever_rate".into(), lever_rate.to_string()); 244 | body.insert("order_price_type".into(), order_price_type.into()); 245 | 246 | if let Some(client_id) = client_order_id.into() { body.insert("client_order_id".into(), format!("{}", client_id)); } 247 | if let Some(p) = price.into() { body.insert("price".into(), format!("{}", p)); } 248 | 249 | println!("body: {:?}", body.clone()); 250 | 251 | let data = self.post_signed("/swap-api/v1/swap_order", params, &body)?; 252 | 253 | let order: OrderInfo = from_str(data.as_str())?; 254 | 255 | Ok(order) 256 | 257 | } 258 | 259 | // place batch order 260 | pub fn place_orders(&self, orders_data: BatchOrderRequest) -> APIResult 261 | { 262 | let params: BTreeMap = BTreeMap::new(); 263 | 264 | let data = self.post_signed("/swap-api/v1/swap_batchorder", params, &orders_data)?; 265 | 266 | let order: BatchOrder = from_str(data.as_str())?; 267 | 268 | Ok(order) 269 | } 270 | 271 | // cancel orders 272 | pub fn cancel_orders(&self, order_id: S1, client_order_id: S2, contract_code: S3) -> APIResult 273 | where S1: Into>, S2: Into>, S3: Into 274 | { 275 | let params: BTreeMap = BTreeMap::new(); 276 | let mut body: BTreeMap = BTreeMap::new(); 277 | 278 | body.insert("contract_code".into(), contract_code.into()); 279 | 280 | if let Some(oid) = order_id.into() {body.insert("order_id".into(), format!("{}", oid)); } 281 | if let Some(cid) = client_order_id.into() { body.insert("client_order_id".into(), cid); } 282 | 283 | let data = self.post_signed("/swap-api/v1/swap_cancel", params, &body)?; 284 | 285 | let cancel: OrderCancelInfo = from_str(data.as_str())?; 286 | 287 | Ok(cancel) 288 | } 289 | 290 | // cancel all orders 291 | pub fn cancel_all(&self, contract_code: String) -> APIResult { 292 | let params: BTreeMap = BTreeMap::new(); 293 | let mut body: BTreeMap = BTreeMap::new(); 294 | 295 | body.insert("contract_code".to_string(), contract_code); 296 | 297 | let data = self.post_signed("/swap-api/v1/swap_cancelall", params, &body)?; 298 | 299 | let cancel_all: OrderCancelInfo = from_str(data.as_str())?; 300 | 301 | Ok(cancel_all) 302 | } 303 | 304 | // get order info 305 | pub fn get_order_info(&self, order_id: S1, client_order_id: S2, contract_code: S3) -> APIResult 306 | where S1: Into>, S2: Into>, S3: Into 307 | { 308 | let params: BTreeMap = BTreeMap::new(); 309 | let mut body: BTreeMap = BTreeMap::new(); 310 | 311 | body.insert("contract_code".into(), contract_code.into()); 312 | if let Some(oid) = order_id.into() { body.insert("order_id".into(), format!("{}", oid));} 313 | if let Some(cid) = client_order_id.into() { body.insert("client_order_id".into(), format!("{}", cid));} 314 | 315 | let data = self.post_signed("/swap-api/v1/swap_order_info", params, &body)?; 316 | 317 | let order_info: GOrderInfo = from_str(data.as_str())?; 318 | 319 | Ok(order_info) 320 | } 321 | 322 | // get order detail information 323 | pub fn get_order_detail(&self, contract_code: S1, order_id: u64, created_at: S2, order_type: u32, 324 | page_index: S3, page_size: S4) -> APIResult 325 | where S1: Into, S2: Into>, S3: Into>, S4: Into> 326 | { 327 | let params: BTreeMap = BTreeMap::new(); 328 | let mut body: BTreeMap = BTreeMap::new(); 329 | 330 | body.insert("contract_code".into(), contract_code.into()); 331 | body.insert("order_id".into(), format!("{}", order_id)); 332 | body.insert("order_type".to_string(), format!("{}", order_type)); 333 | if let Some(ct) = created_at.into() { body.insert("created_at".into(),format!("{}", ct));} 334 | if let Some(offset) = page_index.into() { body.insert("page_index".into(), format!("{}", offset));} 335 | if let Some(limit) = page_size.into() { body.insert("page_size".into(), format!("{}", limit));} 336 | 337 | let data = self.post_signed("/swap-api/v1/swap_order_detail", params, &body)?; 338 | 339 | let order_detail: OrderDetailInfo = from_str(data.as_str())?; 340 | 341 | Ok(order_detail) 342 | 343 | } 344 | // get open orders 345 | pub fn get_open_orders(&self, contract_code: S1, page_index: S2, page_size: S3) -> APIResult 346 | where S1: Into, S2: Into>, S3: Into> 347 | { 348 | let params: BTreeMap = BTreeMap::new(); 349 | let mut body: BTreeMap = BTreeMap::new(); 350 | 351 | body.insert("contract_code".into(), contract_code.into()); 352 | if let Some(offset) = page_index.into() { body.insert("page_index".into(), format!("{}", offset));} 353 | if let Some(limit) = page_size.into() { body.insert("page_size".into(), format!("{}", limit));} 354 | 355 | let data = self.post_signed("/swap-api/v1/swap_openorders", params, &body)?; 356 | 357 | let open_orders: OpenOrders = from_str(data.as_str())?; 358 | 359 | Ok(open_orders) 360 | 361 | } 362 | 363 | // get history orders 364 | pub fn get_his_orders(&self, contract_code: S1, trade_type: u32, r_type: u32, status: String, create_date: u32, page_index: S2, page_size: S3) 365 | -> APIResult 366 | where S1: Into, S2: Into>, S3: Into> 367 | { 368 | let params: BTreeMap = BTreeMap::new(); 369 | let mut body: BTreeMap = BTreeMap::new(); 370 | 371 | body.insert("contract_code".into(), contract_code.into()); 372 | body.insert("trade_type".into(), format!("{}", trade_type)); 373 | body.insert("type".into(), format!("{}", r_type)); 374 | body.insert("status".into(), format!("{}", status)); 375 | body.insert("create_date".into(), format!("{}", create_date)); 376 | if let Some(offset) = page_index.into() { body.insert("page_index".into(), format!("{}", offset));} 377 | if let Some(limit) = page_size.into() { body.insert("page_size".into(), format!("{}", limit));} 378 | 379 | let data = self.post_signed("/swap-api/v1/swap_hisorders", params, &body)?; 380 | 381 | let his_orders: HisOrders = from_str(data.as_str())?; 382 | 383 | Ok(his_orders) 384 | 385 | } 386 | 387 | // get match results 388 | pub fn get_match_results(&self, contract_code: S1, trade_type: u32, days: u32, page_index: S2, page_size: S3) 389 | -> APIResult 390 | where S1: Into, S2: Into>, S3: Into> 391 | { 392 | let params: BTreeMap = BTreeMap::new(); 393 | let mut body: BTreeMap = BTreeMap::new(); 394 | 395 | body.insert("contract_code".into(), contract_code.into()); 396 | body.insert("trade_type".into(), format!("{}", trade_type)); 397 | body.insert("create_date".into(), format!("{}", days)); 398 | if let Some(offset) = page_index.into() { body.insert("page_index".into(), format!("{}", offset));} 399 | if let Some(limit) = page_size.into() { body.insert("page_size".into(), format!("{}", limit));} 400 | 401 | let data = self.post_signed("/swap-api/v1/swap_matchresults", params, &body)?; 402 | 403 | let match_results: MatchResults = from_str(data.as_str())?; 404 | 405 | Ok(match_results) 406 | } 407 | 408 | // lightning close 409 | pub fn lightning_close(&self, contract_code: S1, volume: u32, direction: String, client_order_id: S2, order_price_type: S3) -> APIResult 410 | where S1: Into, S2: Into>, S3: Into> 411 | { 412 | let params: BTreeMap = BTreeMap::new(); 413 | let mut body: BTreeMap = BTreeMap::new(); 414 | 415 | body.insert("contract_code".into(), contract_code.into()); 416 | body.insert("volume".into(), format!("{}", volume)); 417 | body.insert("direction".into(), format!("{}", direction)); 418 | 419 | if let Some(cid) = client_order_id.into() { body.insert("client_order_id".into(), format!("{}", cid)); } 420 | if let Some(otype) = order_price_type.into() { body.insert("order_price_type".into(), format!("{}", otype)); } 421 | 422 | let data = self.post_signed("/swap-api/v1/swap_lightning_close_position", params, &body)?; 423 | 424 | let result: LightningCloseResult = from_str(data.as_str())?; 425 | 426 | Ok(result) 427 | 428 | } 429 | 430 | pub fn spot_account_transfer(&self, from: S1, to: S2, currency: S3, amount: f64) -> APIResult 431 | where S1: Into, S2: Into, S3: Into 432 | { 433 | let params: BTreeMap = BTreeMap::new(); 434 | let mut body: BTreeMap = BTreeMap::new(); 435 | 436 | body.insert("from".into(), from.into()); 437 | body.insert("to".into(), to.into()); 438 | body.insert("currency".into(), currency.into()); 439 | body.insert("amount".into(), format!("{}", amount)); 440 | 441 | let data = self.post_signed("/v2/account/transfer", params, &body)?; 442 | 443 | let account_transfer: AccountTransferResult = from_str(data.as_str())?; 444 | 445 | Ok(account_transfer) 446 | } 447 | 448 | // pub fn balance(&self, id: u32) -> APIResult { 449 | // let params: BTreeMap = BTreeMap::new(); 450 | // let data = self.get_signed(&format!("/v1/account/accounts/{}/balance", id), params)?; 451 | // let response: APIResponse = from_str(data.as_str())?; 452 | // Ok(response.data) 453 | // } 454 | 455 | // pub fn orders(&self, symbol: &str, states: &str) -> APIResult> { 456 | // let mut params: BTreeMap = BTreeMap::new(); 457 | // params.insert("symbol".to_string(), symbol.to_string()); 458 | // params.insert( 459 | // "states".to_string(), 460 | // states.to_string(), 461 | // ); 462 | // params.insert("types".to_string(), "buy-limit".to_string()); 463 | // let data = self.get_signed("/v1/order/orders", params)?; 464 | // let response: APIResponse> = from_str(data.as_str())?; 465 | 466 | // Ok(response.data) 467 | // } 468 | } 469 | -------------------------------------------------------------------------------- /src/client/market.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | //use crate::error::*; 3 | //use crate::models::*; 4 | use serde_json::from_str; 5 | 6 | impl Client { 7 | // Get contract information (contract metadata etc) 8 | pub fn get_contract_info(&self) -> APIResult { 9 | let parameters: BTreeMap = BTreeMap::new(); 10 | let data: String = self.get("/swap-api/v1/swap_contract_info", ¶meters)?; 11 | 12 | let info: ContractInfo = from_str(data.as_str())?; 13 | 14 | Ok(info) 15 | } 16 | 17 | // Get swap index 18 | pub fn get_swap_index(&self, contract_code: S1) -> APIResult 19 | where S1: Into> 20 | { 21 | let mut parameters: BTreeMap = BTreeMap::new(); 22 | 23 | if let Some(code) = contract_code.into() { parameters.insert("contract_code".into(), code); } 24 | 25 | let data: String = self.get("/swap-api/v1/swap_index", ¶meters)?; 26 | 27 | let info: IndexInfo = from_str(data.as_str())?; 28 | 29 | Ok(info) 30 | } 31 | 32 | // Get swap price limit 33 | pub fn get_price_limit(&self, contract_code: S1) -> APIResult 34 | where S1: Into 35 | { 36 | let mut parameters: BTreeMap = BTreeMap::new(); 37 | 38 | parameters.insert("contract_code".into(), contract_code.into()); 39 | 40 | let data: String = self.get("/swap-api/v1/swap_price_limit", ¶meters)?; 41 | 42 | let info: PriceLimit = from_str(data.as_str())?; 43 | 44 | Ok(info) 45 | } 46 | 47 | // Get open interest 48 | pub fn get_open_interest(&self, contract_code: S1) -> APIResult 49 | where S1: Into> 50 | { 51 | let mut parameters: BTreeMap = BTreeMap::new(); 52 | 53 | if let Some(code) = contract_code.into() { parameters.insert("contract_code".into(), format!("{}", code));} 54 | 55 | let data: String = self.get("/swap-api/v1/swap_open_interest", ¶meters)?; 56 | 57 | let info: OpenInterest = from_str(data.as_str())?; 58 | 59 | Ok(info) 60 | } 61 | 62 | // Get Orderbook 63 | pub fn get_orderbook(&self, contract_code: S1, orderbook_type: S2) -> APIResult 64 | where S1: Into, S2: Into 65 | { 66 | let mut parameters: BTreeMap = BTreeMap::new(); 67 | 68 | parameters.insert("contract_code".into(), contract_code.into()); 69 | parameters.insert("type".into(), orderbook_type.into()); 70 | 71 | let data: String = self.get("/swap-ex/market/depth", ¶meters)?; 72 | 73 | let info: OrderBook = from_str(data.as_str())?; 74 | 75 | Ok(info) 76 | } 77 | 78 | // Get Kline 79 | pub fn get_klines(&self, symbol: S1, interval: S2, limit: S3, start_time: S4, end_time: S5) -> APIResult 80 | where S1: Into, S2: Into, S3: Into>, S4: Into>, S5: Into> 81 | { 82 | let mut parameters: BTreeMap = BTreeMap::new(); 83 | 84 | parameters.insert("contract_code".into(), symbol.into()); 85 | parameters.insert("period".into(), interval.into()); 86 | 87 | if let Some(lt) = limit.into() { parameters.insert("size".into(), format!{"{}", lt});} 88 | if let Some(st) = start_time.into() { parameters.insert("from".into(), format!("{}", st));} 89 | if let Some(et) = end_time.into() { parameters.insert("to".into(), format!("{}", et));} 90 | 91 | let data: String = self.get("/swap-ex/market/history/kline", ¶meters)?; 92 | let klines: Klines = from_str(data.as_str())?; 93 | 94 | Ok(klines) 95 | } 96 | 97 | // Get Merged data 98 | pub fn get_market_merged(&self, contract_code: S1) -> APIResult 99 | where S1: Into 100 | { 101 | let mut parameters: BTreeMap = BTreeMap::new(); 102 | 103 | parameters.insert("contract_code".into(), contract_code.into()); 104 | 105 | let data: String = self.get("/swap-ex/market/detail/merged", ¶meters)?; 106 | 107 | let info: MergedInfo = from_str(data.as_str())?; 108 | 109 | Ok(info) 110 | } 111 | 112 | 113 | //Get market trade 114 | pub fn get_market_trade(&self, contract_code: S1) -> APIResult 115 | where S1: Into 116 | { 117 | let mut parameters: BTreeMap = BTreeMap::new(); 118 | 119 | parameters.insert("contract_code".into(), contract_code.into()); 120 | 121 | let data: String = self.get("/swap-ex/market/trade", ¶meters)?; 122 | 123 | let info: Trade = from_str(data.as_str())?; 124 | 125 | Ok(info) 126 | } 127 | 128 | // Get market history trade 129 | pub fn get_market_history_trade(&self, contract_code: S1, size: u32) -> APIResult 130 | where S1: Into 131 | { 132 | let mut parameters: BTreeMap = BTreeMap::new(); 133 | 134 | parameters.insert("contract_code".into(), contract_code.into()); 135 | parameters.insert("size".into(), format!{"{}", size}); 136 | 137 | let data: String = self.get("/swap-ex/market/history/trade", ¶meters)?; 138 | 139 | let info: HistoryTrade = from_str(data.as_str())?; 140 | 141 | Ok(info) 142 | } 143 | 144 | // Get Risk Info 145 | pub fn get_risk_info(&self, contract_code: S1) -> APIResult 146 | where S1: Into> 147 | { 148 | let mut parameters: BTreeMap = BTreeMap::new(); 149 | 150 | if let Some(code)= contract_code.into() { parameters.insert("contract_code".into(), code); } 151 | 152 | let data: String = self.get("/swap-api/v1/swap_risk_info", ¶meters)?; 153 | 154 | let info: RiskInfo = from_str(data.as_str())?; 155 | 156 | Ok(info) 157 | } 158 | 159 | // Get Insurance Fund 160 | pub fn get_insurance_fund(&self, contract_code: S1) -> APIResult 161 | where S1: Into 162 | { 163 | let mut parameters: BTreeMap = BTreeMap::new(); 164 | 165 | parameters.insert("contract_code".into(), contract_code.into()); 166 | 167 | let data: String = self.get("/swap-api/v1/swap_insurance_fund", ¶meters)?; 168 | 169 | let info: InsuranceFund = from_str(data.as_str())?; 170 | 171 | Ok(info) 172 | } 173 | 174 | // Get adjust factor 175 | pub fn get_adjust_factor(&self, contract_code: S1) -> APIResult 176 | where S1: Into> 177 | { 178 | let mut parameters: BTreeMap = BTreeMap::new(); 179 | 180 | if let Some(code) = contract_code.into() { parameters.insert("contract_code".into(), code);} 181 | 182 | let data: String = self.get("/swap-api/v1/swap_adjustfactor", ¶meters)?; 183 | 184 | let info: AdjustFactor = from_str(data.as_str())?; 185 | 186 | Ok(info) 187 | } 188 | 189 | // Get open interest 190 | pub fn get_his_open_interest(&self, contract_code: S1, period: S2, size: S3, amount_type: u32) -> APIResult 191 | where S1: Into, S2: Into, S3: Into> 192 | { 193 | let mut parameters: BTreeMap = BTreeMap::new(); 194 | 195 | parameters.insert("contract_code".into(), contract_code.into()); 196 | 197 | parameters.insert("period".into(), period.into()); 198 | 199 | if let Some(sz) = size.into() { parameters.insert("size".into(), format!{"{}", sz});} 200 | 201 | parameters.insert("amount_type".into(), format!{"{}", amount_type}); 202 | 203 | let data: String = self.get("/swap-api/v1/swap_his_open_interest", ¶meters)?; 204 | 205 | let info: HisOpenInterest = from_str(data.as_str())?; 206 | 207 | Ok(info) 208 | } 209 | 210 | // Get elite account ratio 211 | pub fn get_elite_account_ratio(&self, contract_code: S1, period: S2) -> APIResult 212 | where S1: Into, S2: Into 213 | { 214 | let mut parameters: BTreeMap = BTreeMap::new(); 215 | 216 | parameters.insert("contract_code".into(), contract_code.into()); 217 | parameters.insert("period".into(), period.into()); 218 | 219 | let data: String = self.get("/swap-api/v1/swap_elite_account_ratio", ¶meters)?; 220 | 221 | let info: EliteAccountRatio = from_str(data.as_str())?; 222 | 223 | Ok(info) 224 | } 225 | 226 | // Get elite account position 227 | pub fn get_elite_position_ratio(&self, contract_code: S1, period: S2) -> APIResult 228 | where S1: Into, S2: Into 229 | { 230 | let mut parameters: BTreeMap = BTreeMap::new(); 231 | 232 | parameters.insert("contract_code".into(), contract_code.into()); 233 | parameters.insert("period".into(), period.into()); 234 | 235 | let data: String = self.get("/swap-api/v1/swap_elite_position_ratio", ¶meters)?; 236 | 237 | let info: ElitePositionRatio = from_str(data.as_str())?; 238 | 239 | Ok(info) 240 | } 241 | 242 | // Get api state of system 243 | pub fn get_api_state(&self, contract_code: S1) -> APIResult 244 | where S1: Into> 245 | { 246 | let mut parameters: BTreeMap = BTreeMap::new(); 247 | 248 | if let Some(code) = contract_code.into() { parameters.insert("contract_code".into(), code); } 249 | 250 | let data: String = self.get("/swap-api/v1/swap_api_state", ¶meters)?; 251 | 252 | let info: ApiState = from_str(data.as_str())?; 253 | 254 | Ok(info) 255 | } 256 | 257 | // Get funding rate 258 | pub fn get_funding_rate(&self, contract_code: S1) -> APIResult 259 | where S1: Into 260 | { 261 | let mut parameters: BTreeMap = BTreeMap::new(); 262 | 263 | parameters.insert("contract_code".into(), contract_code.into()); 264 | 265 | let data: String = self.get("/swap-api/v1/swap_funding_rate", ¶meters)?; 266 | 267 | let info: FundingRate = from_str(data.as_str())?; 268 | 269 | Ok(info) 270 | } 271 | 272 | // Get historical funding rate 273 | pub fn get_his_funding_rate(&self, contract_code: S1, page_index: S2, page_size: S3) -> APIResult 274 | where S1: Into, S2: Into>, S3: Into> 275 | { 276 | let mut parameters: BTreeMap = BTreeMap::new(); 277 | 278 | parameters.insert("contract_code".into(), contract_code.into()); 279 | if let Some(offset) = page_index.into() { parameters.insert("page_index".into(), format!("{}",offset)); } 280 | if let Some(limit) = page_size.into() { parameters.insert("page_size".into(), format!("{}",limit)); } 281 | 282 | let data: String = self.get("/swap-api/v1/swap_historical_funding_rate", ¶meters)?; 283 | 284 | let info: HisFundingRate = from_str(data.as_str())?; 285 | 286 | Ok(info) 287 | } 288 | 289 | // Get liquidation orders 290 | pub fn get_liquidation_orders(&self, contract_code: S1, trade_type: u32, days: u32, page_index: S2, page_size: S3) -> APIResult 291 | where S1: Into, S2: Into>, S3: Into> 292 | { 293 | let mut parameters: BTreeMap = BTreeMap::new(); 294 | 295 | parameters.insert("contract_code".into(), contract_code.into()); 296 | parameters.insert("trade_type".into(), format!{"{}", trade_type}); 297 | parameters.insert("create_date".into(), format!{"{}", days}); 298 | 299 | if let Some(offset) = page_index.into() { parameters.insert("page_index".into(), format!("{}", offset));} 300 | if let Some(limit) = page_size.into() { parameters.insert("page_size".into(), format!("{}", limit)); } 301 | 302 | let data: String = self.get("/swap-api/v1/swap_liquidation_orders", ¶meters)?; 303 | 304 | let info: LiquidationOrdersInfo = from_str(data.as_str())?; 305 | 306 | Ok(info) 307 | } 308 | 309 | } 310 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_variables)] 3 | 4 | //use serde_json; 5 | use core::fmt; 6 | use std::error::Error; 7 | 8 | pub type APIResult = Result>; 9 | 10 | #[derive(Debug, Clone)] 11 | pub enum HuobiError { 12 | ApiError(String), 13 | } 14 | 15 | impl fmt::Display for HuobiError { 16 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 17 | match self.clone() { 18 | HuobiError::ApiError(why) => write!(f, "ApiError: {}", why), 19 | } 20 | } 21 | } 22 | 23 | impl Error for HuobiError { 24 | fn description(&self) -> &str { 25 | "HBDM Swap Error" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Huobi API 2 | // 3 | // references: 4 | // - https://github.com/huobiapi/API_Docs_en/wiki/Huobi.pro-API 5 | 6 | mod client; 7 | pub mod error; 8 | pub mod models; 9 | pub mod websocket; 10 | 11 | pub use crate::client::Client; 12 | pub use crate::models::*; 13 | pub use crate::error::*; 14 | pub use crate::websocket::*; 15 | -------------------------------------------------------------------------------- /src/models.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_variables)] 3 | 4 | use serde::de::{self, Unexpected, Visitor}; 5 | use serde::{Deserialize, Deserializer, Serialize}; 6 | use std::fmt::{self, Display}; 7 | use std::str::FromStr; 8 | 9 | fn from_str<'de, T, D>(deserializer: D) -> Result 10 | where 11 | T: FromStr, 12 | T::Err: Display, 13 | D: Deserializer<'de>, 14 | { 15 | let s = String::deserialize(deserializer)?; 16 | T::from_str(&s).map_err(de::Error::custom) 17 | } 18 | 19 | #[derive(Serialize, Deserialize, Debug)] 20 | pub struct APIResponse { 21 | pub status: String, 22 | pub data: R, 23 | } 24 | 25 | #[derive(Serialize, Deserialize, Debug)] 26 | pub struct APIErrorResponse { 27 | pub status: Option, 28 | 29 | pub err_code: Option, 30 | 31 | pub err_msg: Option, 32 | 33 | pub ts: Option, 34 | 35 | pub data: Option, 36 | 37 | pub tick: Option, 38 | } 39 | 40 | #[derive(Debug, Serialize, Deserialize, Clone)] 41 | pub struct ContractInfo { 42 | pub status: String, 43 | pub data: Vec, 44 | pub ts: u64, 45 | } 46 | 47 | #[derive(Debug, Serialize, Deserialize, Clone)] 48 | pub struct Symbol { 49 | pub symbol: String, 50 | pub contract_code: String, 51 | pub contract_size: f64, 52 | pub price_tick: f64, 53 | pub settlement_date: String, 54 | pub create_date: String, 55 | pub contract_status: u32, 56 | } 57 | 58 | #[derive(Debug, Serialize, Deserialize, Clone)] 59 | pub struct AccountInfo { 60 | pub status: Option, 61 | pub data: Vec, 62 | pub ts: u64, 63 | 64 | pub op: Option, 65 | pub topic: Option, 66 | pub event: Option, 67 | } 68 | 69 | #[derive(Debug, Serialize, Deserialize, Clone)] 70 | pub struct Account { 71 | pub symbol: String, 72 | pub contract_code: String, 73 | pub margin_balance: f64, 74 | pub margin_static: f64, 75 | pub margin_position: f64, 76 | pub margin_frozen: f64, 77 | pub margin_available: f64, 78 | pub profit_real: f64, 79 | pub profit_unreal: f64, 80 | pub risk_rate: Option, 81 | pub liquidation_price: Option, 82 | pub withdraw_available: f64, 83 | pub lever_rate: f64, 84 | pub adjust_factor: f64, 85 | pub ts: Option, 86 | pub event: Option, 87 | } 88 | 89 | 90 | #[derive(Debug, Serialize, Deserialize, Clone)] 91 | pub struct PositionInfo { 92 | pub status: String, 93 | pub data: Vec, 94 | pub ts: u64, 95 | } 96 | 97 | #[derive(Debug, Serialize, Deserialize, Clone)] 98 | pub struct Position { 99 | pub symbol: String, 100 | pub contract_code: String, 101 | pub volume: f64, 102 | pub available: f64, 103 | pub frozen: f64, 104 | pub cost_open: f64, 105 | pub cost_hold: f64, 106 | pub profit_unreal: f64, 107 | pub profit_rate: f64, 108 | pub profit: f64, 109 | pub position_margin: f64, 110 | pub lever_rate: u32, 111 | pub direction: String, 112 | pub last_price: f64, 113 | } 114 | 115 | #[derive(Debug, Serialize, Deserialize, Clone)] 116 | pub struct GOrderInfo { 117 | pub status: String, 118 | pub data: Vec, 119 | pub ts: u64, 120 | } 121 | 122 | 123 | #[derive(Debug, Serialize, Deserialize, Clone)] 124 | pub struct OrderItem { 125 | pub symbol: String, 126 | pub contract_code: String, 127 | pub volume: f64, 128 | pub price: f64, 129 | pub order_price_type: String, 130 | pub direction: String, 131 | pub offset: String, 132 | pub lever_rate: u32, 133 | pub order_id: u64, 134 | pub order_id_str: String, 135 | pub client_order_id: u64, 136 | pub created_at: u64, 137 | pub trade_volume: u32, 138 | pub trade_turnover: f64, 139 | pub fee: f64, 140 | pub fee_asset: String, 141 | pub trade_avg_price: Option, 142 | pub margin_frozen: f64, 143 | pub profit: f64, 144 | pub status: u32, 145 | pub order_type: u32, 146 | pub order_source: String, 147 | } 148 | 149 | #[derive(Debug, Serialize, Deserialize, Clone)] 150 | pub struct OrderDetailInfo { 151 | pub status: String, 152 | pub data: OrderDetail, 153 | pub ts: u64, 154 | } 155 | 156 | #[derive(Debug, Serialize, Deserialize, Clone)] 157 | pub struct OrderDetail { 158 | pub symbol: String, 159 | pub contract_code: String, 160 | pub lever_rate: u32, 161 | pub direction: String, 162 | pub offset: String, 163 | pub volume: f64, 164 | pub price: f64, 165 | pub created_at: u64, 166 | pub canceled_at: u64, 167 | pub order_source: String, 168 | pub order_price_type: String, 169 | pub margin_frozen: f64, 170 | pub profit: f64, 171 | pub total_page: u32, 172 | pub current_page: u32, 173 | pub total_size: u32, 174 | pub instrument_price: f64, 175 | pub final_interest: f64, 176 | pub adjust_value: f64, 177 | pub fee: f64, 178 | pub fee_asset: String, 179 | pub liquidation_type: String, 180 | pub trades: Vec, 181 | } 182 | 183 | #[derive(Debug, Serialize, Deserialize, Clone)] 184 | pub struct TradeItem { 185 | pub trade_id: u64, 186 | pub id: String, 187 | pub trade_price: f64, 188 | pub trade_volume: f64, 189 | pub trade_fee: f64, 190 | pub fee_asset: String, 191 | pub role: String, 192 | pub created_at: u64, 193 | } 194 | 195 | #[derive(Debug, Serialize, Deserialize, Clone)] 196 | pub struct OpenOrders { 197 | pub status: String, 198 | pub data: OpenOrderData, 199 | pub ts: u64, 200 | } 201 | 202 | #[derive(Debug, Serialize, Deserialize, Clone)] 203 | pub struct OpenOrderData { 204 | pub orders: Vec, 205 | pub total_page: u32, 206 | pub current_page: u32, 207 | pub total_size: u32 208 | } 209 | 210 | #[derive(Debug, Serialize, Deserialize, Clone)] 211 | pub struct OpenOrderItem { 212 | pub symbol: String, 213 | pub contract_code: String, 214 | pub volume: f64, 215 | pub price: f64, 216 | pub order_price_type: String, 217 | pub order_type: u32, 218 | pub direction: String, 219 | pub offset: String, 220 | pub lever_rate: u32, 221 | pub order_id: u64, 222 | pub order_id_str: String, 223 | pub client_order_id: u64, 224 | pub created_at: u64, 225 | pub trade_volume: f64, 226 | pub trade_turnover: f64, 227 | pub fee: f64, 228 | pub fee_asset: String, 229 | pub trade_avg_price: Option, 230 | pub margin_frozen: f64, 231 | pub profit: f64, 232 | pub status: u32, 233 | pub order_source: String, 234 | } 235 | 236 | #[derive(Debug, Serialize, Deserialize, Clone)] 237 | pub struct OrderInfo { 238 | pub status: String, 239 | pub data: Order, 240 | pub ts: u64, 241 | } 242 | 243 | #[derive(Debug, Serialize, Deserialize, Clone)] 244 | pub struct Order { 245 | pub order_id: u64, 246 | pub order_id_str: String, 247 | pub client_order_id: Option, 248 | } 249 | 250 | #[derive(Debug, Serialize, Deserialize, Clone)] 251 | pub struct BatchOrder { 252 | pub status: String, 253 | pub data: BatchOrderResult, 254 | pub ts: u64, 255 | } 256 | 257 | #[derive(Debug, Serialize, Deserialize, Clone)] 258 | pub struct BatchOrderResult { 259 | pub errors: Vec, 260 | pub success: Vec, 261 | } 262 | 263 | #[derive(Debug, Serialize, Deserialize, Clone)] 264 | pub struct BatchOrderErrors { 265 | pub index: u32, 266 | pub err_code: u32, 267 | pub err_msg: String, 268 | } 269 | 270 | #[derive(Debug, Serialize, Deserialize, Clone)] 271 | pub struct BatchOrderSuccess { 272 | pub index: u32, 273 | pub order_id: u64, 274 | pub order_id_str: String, 275 | pub client_order_id: Option, 276 | } 277 | 278 | #[derive(Debug, Serialize, Deserialize, Clone)] 279 | pub struct OrderCancelInfo { 280 | pub status: String, 281 | pub ts: u64, 282 | pub data: OrderCancel, 283 | } 284 | 285 | #[derive(Debug, Serialize, Deserialize, Clone)] 286 | pub struct OrderCancel { 287 | pub errors: Vec, 288 | pub successes: String, 289 | } 290 | 291 | #[derive(Debug, Serialize, Deserialize, Clone)] 292 | pub struct CancelError { 293 | pub order_id: String, 294 | pub err_code: u32, 295 | pub err_msg: String, 296 | } 297 | 298 | #[derive(Debug, Serialize, Deserialize, Clone)] 299 | pub struct HisOrders { 300 | pub status: String, 301 | pub data: HisOrderList, 302 | pub ts: u64, 303 | } 304 | 305 | #[derive(Debug, Serialize, Deserialize, Clone)] 306 | pub struct HisOrderList { 307 | pub orders: Vec, 308 | pub total_page: u32, 309 | pub current_page: u32, 310 | pub total_size: u32, 311 | } 312 | 313 | 314 | #[derive(Debug, Serialize, Deserialize, Clone)] 315 | pub struct HisOrderItem { 316 | pub order_id: u64, 317 | pub order_id_str: String, 318 | pub symbol: String, 319 | pub contract_code: String, 320 | pub lever_rate: u32, 321 | pub direction: String, 322 | pub offset: String, 323 | pub volume: u32, 324 | pub price: f64, 325 | pub create_date: u64, 326 | pub order_source: String, 327 | pub order_price_type: u32, 328 | pub margin_frozen: f64, 329 | pub profit: f64, 330 | pub trade_volume: u32, 331 | pub trade_turnover: f64, 332 | pub fee: f64, 333 | pub fee_asset: String, 334 | pub trade_avg_price: Option, 335 | pub status: u32, 336 | pub order_type: u32, 337 | pub liquidation_type: String 338 | } 339 | 340 | 341 | #[derive(Debug, Serialize, Deserialize, Clone)] 342 | pub struct MatchResults { 343 | pub status: String, 344 | pub data: MatchTrades, 345 | pub ts: u64, 346 | } 347 | 348 | #[derive(Debug, Serialize, Deserialize, Clone)] 349 | pub struct MatchTrades { 350 | pub trades: Vec, 351 | pub total_page: u32, 352 | pub current_page: u32, 353 | pub total_size: u32, 354 | } 355 | 356 | #[derive(Debug, Serialize, Deserialize, Clone)] 357 | pub struct MatchTradeItem { 358 | pub match_id: u64, 359 | pub id: String, 360 | pub order_id: u64, 361 | pub order_id_str: String, 362 | pub symbol: String, 363 | pub order_source: String, 364 | pub contract_code: String, 365 | pub direction: String, 366 | pub offset: String, 367 | pub trade_volume: u32, 368 | pub trade_price: f64, 369 | pub trade_turnover: u32, 370 | pub create_date: u64, 371 | pub offset_profitloss: f64, 372 | pub trade_fee: f64, 373 | pub fee_asset: String, 374 | pub role: String, 375 | } 376 | 377 | 378 | #[derive(Debug, Serialize, Deserialize, Clone)] 379 | pub struct LightningCloseResult { 380 | pub status: String, 381 | pub ts: u64, 382 | pub data: LightningCloseItem, 383 | } 384 | 385 | #[derive(Debug, Serialize, Deserialize, Clone)] 386 | pub struct LightningCloseItem { 387 | pub order_id: u64, 388 | pub order_id_str: String, 389 | pub client_order_id: Option, 390 | } 391 | 392 | #[derive(Debug, Serialize, Deserialize, Clone)] 393 | pub struct AccountTransfer { 394 | pub code: u64, 395 | pub success: u64, 396 | pub message: String, 397 | pub data: u64, 398 | } 399 | 400 | #[derive(Debug, Serialize, Deserialize, Clone)] 401 | pub struct AccountTransferResult { 402 | pub code: u64, 403 | pub data: Option, 404 | pub success: bool, 405 | pub message: String, 406 | } 407 | 408 | #[derive(Debug, Serialize, Deserialize, Clone)] 409 | pub struct OrderBook { 410 | pub ch: String, 411 | pub status: Option, 412 | pub tick: Tick, 413 | pub ts: u64, 414 | } 415 | 416 | #[derive(Debug, Serialize, Deserialize, Clone)] 417 | pub struct Tick { 418 | pub bids: Vec, 419 | pub asks: Vec, 420 | pub mrid: Option, 421 | pub id: Option, 422 | pub ts: Option, 423 | pub version: Option, 424 | pub ch: Option, 425 | pub event: Option, 426 | } 427 | 428 | #[derive(Debug, Serialize, Deserialize, Clone)] 429 | pub struct Bids { 430 | pub price: f64, 431 | pub qty: f64, 432 | } 433 | 434 | #[derive(Debug, Serialize, Deserialize, Clone)] 435 | pub struct Asks { 436 | pub price: f64, 437 | pub qty: f64, 438 | } 439 | 440 | #[derive(Debug, Serialize, Deserialize, Clone)] 441 | pub struct Klines { 442 | pub ch: String, 443 | pub data: Option>, 444 | pub tick: Option, 445 | pub status: Option, 446 | pub ts: u64, 447 | } 448 | 449 | #[derive(Debug, Serialize, Deserialize, Clone)] 450 | pub struct Kline { 451 | #[serde(rename = "id")] 452 | pub timestamp: u64, 453 | #[serde(rename = "vol")] 454 | pub volume: f64, 455 | pub count: f64, 456 | pub open: f64, 457 | pub close: f64, 458 | pub low: f64, 459 | pub high: f64, 460 | pub amount: f64, 461 | pub mrid: Option, 462 | } 463 | 464 | #[derive(Debug, Serialize, Deserialize, Clone)] 465 | pub struct Trade { 466 | pub ch: String, 467 | pub ts: u64, 468 | pub tick: TradeDetail, 469 | pub status: Option, 470 | } 471 | 472 | #[derive(Debug, Serialize, Deserialize, Clone)] 473 | pub struct TradeDetail { 474 | pub id: u64, 475 | pub ts: u64, 476 | pub data: Vec, 477 | } 478 | 479 | #[derive(Debug, Serialize, Deserialize, Clone)] 480 | pub struct TradeDetailItem { 481 | pub amount: u32, 482 | pub ts: u64, 483 | pub id: u64, 484 | pub price: f64, 485 | pub direction: String, 486 | } 487 | 488 | fn string_as_f64<'de, D>(deserializer: D) -> Result 489 | where 490 | D: Deserializer<'de>, 491 | { 492 | deserializer.deserialize_any(F64Visitor) 493 | } 494 | 495 | struct F64Visitor; 496 | impl<'de> Visitor<'de> for F64Visitor { 497 | type Value = f64; 498 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 499 | formatter.write_str("a string representation of a f64") 500 | } 501 | fn visit_str(self, value: &str) -> Result 502 | where 503 | E: de::Error, 504 | { 505 | if let Ok(integer) = value.parse::() { 506 | Ok(integer as f64) 507 | } else { 508 | value.parse::().map_err(|err| { 509 | E::invalid_value(Unexpected::Str(value), &"a string representation of a f64") 510 | }) 511 | } 512 | } 513 | } 514 | 515 | #[derive(Debug, Serialize, Deserialize, Clone)] 516 | pub struct BatchOrderRequest { 517 | pub orders_data: Vec, 518 | } 519 | 520 | #[derive(Debug, Serialize, Deserialize, Clone)] 521 | pub struct OrderRequest { 522 | pub contract_code: String, 523 | pub client_order_id: Option, 524 | pub price: Option, 525 | pub volume: u32, 526 | pub direction: String, 527 | pub offset: String, 528 | pub lever_rate: u32, 529 | pub order_price_type: String, 530 | } 531 | 532 | #[derive(Debug, Serialize, Deserialize, Clone)] 533 | pub struct SubAccountList { 534 | pub status: String, 535 | pub ts: u64, 536 | pub data: Vec, 537 | } 538 | 539 | #[derive(Debug, Serialize, Deserialize, Clone)] 540 | pub struct SubAccount { 541 | pub sub_uid: u64, 542 | pub list: Vec, 543 | } 544 | 545 | #[derive(Debug, Serialize, Deserialize, Clone)] 546 | pub struct SubAccountItem { 547 | pub symbol: String, 548 | pub contract_code: String, 549 | pub margin_balance: f64, 550 | pub liquidation_price: Option, 551 | pub risk_rate: Option, 552 | } 553 | 554 | #[derive(Debug, Serialize, Deserialize, Clone)] 555 | pub struct SubAccountInfo { 556 | pub status: String, 557 | pub ts: u64, 558 | pub data: Vec, 559 | } 560 | 561 | #[derive(Debug, Serialize, Deserialize, Clone)] 562 | pub struct SubAccountDetail { 563 | pub symbol: String, 564 | pub contract_code: String, 565 | pub margin_balance: f64, 566 | pub margin_position: f64, 567 | pub margin_frozen: f64, 568 | pub margin_available: f64, 569 | pub profit_real: f64, 570 | pub profit_unreal: f64, 571 | pub risk_rate: Option, 572 | pub liquidation_price: Option, 573 | pub withdraw_available: f64, 574 | pub lever_rate: u32, 575 | pub adjust_factor: f64, 576 | pub margin_static: f64, 577 | } 578 | 579 | #[derive(Debug, Serialize, Deserialize, Clone)] 580 | pub struct SubPositionInfo { 581 | pub status: String, 582 | pub ts: u64, 583 | pub data: Vec, 584 | } 585 | 586 | #[derive(Debug, Serialize, Deserialize, Clone)] 587 | pub struct SubPositionDetail { 588 | pub symbol: String, 589 | pub contract_code: String, 590 | pub volume: f64, 591 | pub available: f64, 592 | pub frozen: f64, 593 | pub cost_open: f64, 594 | pub cost_hold: f64, 595 | pub profit_unreal: f64, 596 | pub profit_rate: f64, 597 | pub profit: f64, 598 | pub position_margin: f64, 599 | pub lever_rate: u32, 600 | pub direction: String, 601 | pub last_price: f64, 602 | } 603 | 604 | #[derive(Debug, Serialize, Deserialize, Clone)] 605 | pub struct FinancialRecord { 606 | pub status: String, 607 | pub ts: u64, 608 | pub data: FinancialRecordList, 609 | } 610 | 611 | #[derive(Debug, Serialize, Deserialize, Clone)] 612 | pub struct FinancialRecordList { 613 | pub financial_record: Vec, 614 | pub total_page: u32, 615 | pub current_page: u32, 616 | pub total_size: u32, 617 | } 618 | 619 | #[derive(Debug, Serialize, Deserialize, Clone)] 620 | pub struct FinancialRecordItem { 621 | pub id: u64, 622 | pub ts: u64, 623 | pub symbol: String, 624 | pub contract_code: String, 625 | #[serde(rename = "type")] 626 | pub trade_type: u32, 627 | pub amount: f64, 628 | 629 | } 630 | 631 | #[derive(Debug, Serialize, Deserialize, Clone)] 632 | pub struct OrderLimitInfo { 633 | pub status: String, 634 | pub ts: u64, 635 | pub data: OrderLimitList, 636 | } 637 | 638 | #[derive(Debug, Serialize, Deserialize, Clone)] 639 | pub struct OrderLimitList { 640 | pub order_price_type: String, 641 | pub list: Vec, 642 | } 643 | 644 | #[derive(Debug, Serialize, Deserialize, Clone)] 645 | pub struct OrderLimitItem { 646 | pub symbol: String, 647 | pub contract_code: String, 648 | pub open_limit: f64, 649 | pub close_limit: f64, 650 | } 651 | 652 | #[derive(Debug, Serialize, Deserialize, Clone)] 653 | pub struct FeeInfo { 654 | pub status: String, 655 | pub ts: u64, 656 | pub data: Vec, 657 | } 658 | 659 | #[derive(Debug, Serialize, Deserialize, Clone)] 660 | pub struct Fee { 661 | pub symbol: String, 662 | pub contract_code: String, 663 | pub fee_asset: String, 664 | pub open_maker_fee: String, 665 | pub open_taker_fee: String, 666 | pub close_maker_fee: String, 667 | pub close_taker_fee: String, 668 | } 669 | 670 | #[derive(Debug, Serialize, Deserialize, Clone)] 671 | pub struct TransferLimitInfo { 672 | pub status: String, 673 | pub ts: u64, 674 | pub data: Vec, 675 | } 676 | 677 | #[derive(Debug, Serialize, Deserialize, Clone)] 678 | pub struct TransferLimit { 679 | pub symbol: String, 680 | pub contract_code: String, 681 | pub transfer_in_max_each: f64, 682 | pub transfer_in_min_each: f64, 683 | pub transfer_out_max_each: f64, 684 | pub transfer_out_min_each: f64, 685 | pub transfer_in_max_daily: f64, 686 | pub transfer_out_max_daily: f64, 687 | pub net_transfer_in_max_daily: f64, 688 | pub net_transfer_out_max_daily: f64, 689 | } 690 | 691 | #[derive(Debug, Serialize, Deserialize, Clone)] 692 | pub struct PositionLimitInfo { 693 | pub status: String, 694 | pub ts: u64, 695 | pub data: Vec, 696 | } 697 | 698 | #[derive(Debug, Serialize, Deserialize, Clone)] 699 | pub struct PositionLimit { 700 | pub symbol: String, 701 | pub contract_code: String, 702 | pub buy_limit: f64, 703 | pub sell_limit: f64, 704 | } 705 | 706 | #[derive(Debug, Serialize, Deserialize, Clone)] 707 | pub struct MasterSubTransferInfo { 708 | pub status: String, 709 | pub ts: u64, 710 | pub data: MasterSubTransfer, 711 | } 712 | 713 | #[derive(Debug, Serialize, Deserialize, Clone)] 714 | pub struct MasterSubTransfer { 715 | pub order_id: String, 716 | } 717 | 718 | #[derive(Debug, Serialize, Deserialize, Clone)] 719 | pub struct MasterSubTransferRecordInfo { 720 | pub status: String, 721 | pub ts: u64, 722 | pub data: Option, 723 | } 724 | 725 | #[derive(Debug, Serialize, Deserialize, Clone)] 726 | pub struct MasterSubTransferRecord { 727 | pub transfer_record: Vec, 728 | pub total_page: u32, 729 | pub current_page: u32, 730 | pub total_size: u32, 731 | } 732 | 733 | #[derive(Debug, Serialize, Deserialize, Clone)] 734 | pub struct MasterSubTransferRecordItem { 735 | pub id: String, 736 | pub ts: u64, 737 | pub symbol: String, 738 | pub contract_code: String, 739 | pub sub_uid: String, 740 | pub sub_account_name: String, 741 | pub transfer_type: u32, 742 | pub amount: f64, 743 | } 744 | 745 | #[derive(Debug, Serialize, Deserialize, Clone)] 746 | pub struct ApiTradeStatus { 747 | pub status: String, 748 | pub ts: u64, 749 | pub data: ApiTradeStatusItem, 750 | } 751 | 752 | #[derive(Debug, Serialize, Deserialize, Clone)] 753 | pub struct ApiTradeStatusItem { 754 | pub is_disable: u32, 755 | pub order_price_types: String, 756 | pub disable_reason: String, 757 | pub disable_interval: u64, 758 | pub recovery_time: u64, 759 | #[serde(rename = "COR")] 760 | pub cor: COR, 761 | #[serde(rename = "TDN")] 762 | pub tdn: TDN, 763 | } 764 | 765 | #[derive(Debug, Serialize, Deserialize, Clone)] 766 | pub struct COR { 767 | pub orders_threshold: u64, 768 | pub orders: u64, 769 | pub invalid_cancel_orders: u64, 770 | pub cancel_ratio_threshold: f64, 771 | pub cancel_ratio: f64, 772 | pub is_trigger: u32, 773 | pub is_active: u32, 774 | } 775 | 776 | #[derive(Debug, Serialize, Deserialize, Clone)] 777 | pub struct TDN { 778 | pub disables_threshold: u64, 779 | pub disables: u64, 780 | pub is_trigger: u32, 781 | pub is_active: u32, 782 | } 783 | 784 | #[derive(Debug, Serialize, Deserialize, Clone)] 785 | pub struct OrderSubs { 786 | pub op: String, 787 | pub topic: String, 788 | pub ts: u64, 789 | pub symbol: String, 790 | pub contract_code: String, 791 | pub volume: u32, 792 | pub price: f64, 793 | pub order_price_type: String, 794 | pub direction: String, 795 | pub offset: String, 796 | pub status: u32, 797 | pub lever_rate: u32, 798 | pub order_id: u64, 799 | pub order_id_str: String, 800 | pub client_order_id: Option, 801 | pub order_source: String, 802 | pub order_type: u32, 803 | pub created_at: u64, 804 | pub trade_volume: u32, 805 | pub trade_turnover: f64, 806 | pub fee: f64, 807 | pub trade_avg_price: f64, 808 | pub margin_frozen: f64, 809 | pub profit: f64, 810 | pub liquidation_type: String, 811 | pub trade: Vec, 812 | } 813 | 814 | #[derive(Debug, Serialize, Deserialize, Clone)] 815 | pub struct TradeSubItem { 816 | pub trade_id: u64, 817 | pub id: String, 818 | pub trade_volume: u32, 819 | pub trade_price: f64, 820 | pub trade_fee: f64, 821 | pub fee_asset: String, 822 | pub trade_turnover: f64, 823 | pub created_at: u64, 824 | pub role: String, 825 | } 826 | 827 | #[derive(Debug, Serialize, Deserialize, Clone)] 828 | pub struct PositionSubs 829 | { 830 | pub op: String, 831 | pub topic: String, 832 | pub ts: u64, 833 | pub event: String, 834 | pub data: Vec, 835 | } 836 | 837 | #[derive(Debug, Serialize, Deserialize, Clone)] 838 | pub struct LiquidationSubs{ 839 | pub op: String, 840 | pub topic: String, 841 | pub ts: u64, 842 | pub data: Vec, 843 | } 844 | 845 | #[derive(Debug, Serialize, Deserialize, Clone)] 846 | pub struct LiquidationItem { 847 | pub symbol: String, 848 | pub contract_code: String, 849 | pub direction: String, 850 | pub offset: String, 851 | pub volume: f64, 852 | pub price: f64, 853 | pub created_at: u64, 854 | } 855 | 856 | #[derive(Debug, Serialize, Deserialize, Clone)] 857 | pub struct FundingRateSubs { 858 | pub op: String, 859 | pub topic: String, 860 | pub ts: u64, 861 | pub data: Vec, 862 | } 863 | 864 | #[derive(Debug, Serialize, Deserialize, Clone)] 865 | pub struct FundingRateItem { 866 | pub symbol: String, 867 | pub contract_code: String, 868 | pub fee_asset: String, 869 | pub funding_time: String, 870 | pub funding_rate: String, 871 | pub estimated_rate: Option, 872 | pub settlement_time: Option, 873 | pub next_funding_time: Option, 874 | pub realized_rate: Option, 875 | } 876 | 877 | 878 | #[derive(Debug, Serialize, Deserialize, Clone)] 879 | pub struct IndexInfo { 880 | pub status: String, 881 | pub data: Vec, 882 | pub ts: u64, 883 | } 884 | 885 | #[derive(Debug, Serialize, Deserialize, Clone)] 886 | pub struct IndexInfoItem { 887 | pub contract_code: String, 888 | pub index_price: f64, 889 | pub index_ts: f64, 890 | } 891 | 892 | #[derive(Debug, Serialize, Deserialize, Clone)] 893 | pub struct PriceLimit { 894 | pub status: String, 895 | pub data: Vec, 896 | pub ts: u64, 897 | } 898 | 899 | #[derive(Debug, Serialize, Deserialize, Clone)] 900 | pub struct PriceLimitItem { 901 | pub symbol: String, 902 | pub high_limit: f64, 903 | pub low_limit: f64, 904 | pub contract_code: String, 905 | } 906 | 907 | #[derive(Debug,Serialize, Deserialize, Clone)] 908 | pub struct OpenInterest { 909 | pub status: String, 910 | pub data: Vec, 911 | pub ts: u64, 912 | } 913 | 914 | #[derive(Debug, Serialize, Deserialize, Clone)] 915 | pub struct OpenInterestItem { 916 | pub symbol: String, 917 | pub volume: f64, 918 | pub amount: f64, 919 | pub contract_code: String, 920 | } 921 | 922 | #[derive(Debug, Serialize, Deserialize, Clone)] 923 | pub struct MergedInfo { 924 | pub ch: String, 925 | pub status: String, 926 | pub tick: MergedInfoItem, 927 | } 928 | 929 | #[derive(Debug, Serialize, Deserialize, Clone)] 930 | pub struct MergedInfoItem { 931 | pub id: u64, 932 | pub vol: String, 933 | pub count: u32, 934 | pub open: String, 935 | pub close: String, 936 | pub low: String, 937 | pub high: String, 938 | pub amount: String, 939 | pub ask: Vec, 940 | pub bid: Vec, 941 | } 942 | 943 | #[derive(Debug, Serialize, Deserialize, Clone)] 944 | pub struct HistoryTrade { 945 | pub ch: String, 946 | pub status: String, 947 | pub ts: u64, 948 | pub data: Vec, 949 | } 950 | 951 | #[derive(Debug, Serialize, Deserialize, Clone)] 952 | pub struct HistoryTradeItem { 953 | pub data: Vec, 954 | pub id: u64, 955 | pub ts: u64, 956 | } 957 | 958 | #[derive(Debug, Serialize, Deserialize, Clone)] 959 | pub struct RiskInfo { 960 | pub status: String, 961 | pub ts: u64, 962 | pub data: Vec, 963 | } 964 | 965 | #[derive(Debug, Serialize, Deserialize, Clone)] 966 | pub struct RiskInfoItem { 967 | pub contract_code: String, 968 | pub insurance_fund: f64, 969 | pub estimated_clawback: f64, 970 | } 971 | 972 | #[derive(Debug, Serialize, Deserialize, Clone)] 973 | pub struct InsuranceFund { 974 | pub status: String, 975 | pub ts: u64, 976 | pub data: InsuranceFundItem, 977 | } 978 | 979 | #[derive(Debug, Serialize, Deserialize, Clone)] 980 | pub struct InsuranceFundItem { 981 | pub symbol: String, 982 | pub contract_code: String, 983 | pub tick: Vec, 984 | } 985 | 986 | #[derive(Debug, Serialize, Deserialize, Clone)] 987 | pub struct InsuranceFundTick { 988 | pub insurance_fund: f64, 989 | pub ts: u64, 990 | } 991 | 992 | #[derive(Debug, Serialize, Deserialize, Clone)] 993 | pub struct AdjustFactor { 994 | pub status: String, 995 | pub ts: u64, 996 | pub data: Vec, 997 | } 998 | 999 | #[derive(Debug, Serialize, Deserialize, Clone)] 1000 | pub struct AdjustFactorItem { 1001 | pub symbol: String, 1002 | pub contract_code: String, 1003 | pub list: Vec, 1004 | } 1005 | 1006 | #[derive(Debug, Serialize, Deserialize, Clone)] 1007 | pub struct AdjustFactorDetailList { 1008 | pub lever_rate: u32, 1009 | pub ladders: Vec, 1010 | } 1011 | 1012 | #[derive(Debug, Serialize, Deserialize, Clone)] 1013 | pub struct AdjustFactorDetail { 1014 | pub min_size: u32, 1015 | pub max_size: Option, 1016 | pub ladder: u32, 1017 | pub adjust_factor: f64, 1018 | } 1019 | 1020 | #[derive(Debug, Serialize, Deserialize, Clone)] 1021 | pub struct HisOpenInterest { 1022 | pub status: String, 1023 | pub ts: u64, 1024 | pub data: HisOpenInterestItem, 1025 | } 1026 | 1027 | #[derive(Debug, Serialize, Deserialize, Clone)] 1028 | pub struct HisOpenInterestItem { 1029 | pub symbol: String, 1030 | pub contract_code: String, 1031 | pub tick: Vec, 1032 | } 1033 | 1034 | #[derive(Debug, Serialize, Deserialize, Clone)] 1035 | pub struct HisOpenInterestTick { 1036 | pub volume: f64, 1037 | pub amount_type: u32, 1038 | pub ts: u64, 1039 | } 1040 | 1041 | #[derive(Debug, Serialize, Deserialize, Clone)] 1042 | pub struct EliteAccountRatio { 1043 | pub status: String, 1044 | pub ts: u64, 1045 | pub data: EliteAccountRatioItem, 1046 | } 1047 | 1048 | #[derive(Debug, Serialize, Deserialize, Clone)] 1049 | pub struct EliteAccountRatioItem { 1050 | pub symbol: String, 1051 | pub contract_code: String, 1052 | pub list: Vec, 1053 | } 1054 | 1055 | #[derive(Debug, Serialize, Deserialize, Clone)] 1056 | pub struct EliteAccountRatioList { 1057 | pub buy_ratio: f64, 1058 | pub sell_ratio: f64, 1059 | pub locked_ratio: f64, 1060 | pub ts: u64, 1061 | } 1062 | 1063 | #[derive(Debug, Serialize, Deserialize, Clone)] 1064 | pub struct ElitePositionRatio { 1065 | pub status: String, 1066 | pub ts: u64, 1067 | pub data: ElitePositionRatioItem, 1068 | } 1069 | 1070 | #[derive(Debug, Serialize, Deserialize, Clone)] 1071 | pub struct ElitePositionRatioItem { 1072 | pub symbol: String, 1073 | pub contract_code: String, 1074 | pub list: Vec, 1075 | } 1076 | 1077 | #[derive(Debug, Serialize, Deserialize, Clone)] 1078 | pub struct ElitePositionRatioList { 1079 | pub buy_ratio: f64, 1080 | pub sell_ratio: f64, 1081 | pub ts: u64, 1082 | } 1083 | 1084 | #[derive(Debug, Serialize, Deserialize, Clone)] 1085 | pub struct ApiState { 1086 | pub status: String, 1087 | pub ts: u64, 1088 | pub data: Vec, 1089 | } 1090 | 1091 | #[derive(Debug, Serialize, Deserialize, Clone)] 1092 | pub struct ApiStateItem { 1093 | pub symbol: String, 1094 | pub contract_code: String, 1095 | pub open: u32, 1096 | pub close: u32, 1097 | pub cancel: u32, 1098 | pub transfer_in: u32, 1099 | pub transfer_out: u32, 1100 | pub master_transfer_sub: u32, 1101 | pub sub_transfer_master: u32, 1102 | } 1103 | 1104 | #[derive(Debug, Serialize, Deserialize, Clone)] 1105 | pub struct FundingRate { 1106 | pub status: String, 1107 | pub ts: u64, 1108 | pub data: FundingRateItem, 1109 | } 1110 | 1111 | #[derive(Debug, Serialize, Deserialize, Clone)] 1112 | pub struct HisFundingRate { 1113 | pub status: String, 1114 | pub ts: u64, 1115 | pub data: HisFundingRateList, 1116 | } 1117 | 1118 | #[derive(Debug, Serialize, Deserialize, Clone)] 1119 | pub struct HisFundingRateList { 1120 | pub data: Vec, 1121 | pub total_page: u32, 1122 | pub current_page: u32, 1123 | pub total_size: u32, 1124 | } 1125 | 1126 | #[derive(Debug, Serialize, Deserialize, Clone)] 1127 | pub struct LiquidationOrdersInfo { 1128 | pub status: String, 1129 | pub data: LiquidationOrders, 1130 | pub ts: u64, 1131 | } 1132 | 1133 | #[derive(Debug, Serialize, Deserialize, Clone)] 1134 | pub struct LiquidationOrders { 1135 | pub orders: Vec, 1136 | pub total_page: u32, 1137 | pub current_page: u32, 1138 | pub total_size: u32, 1139 | } -------------------------------------------------------------------------------- /src/websocket.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::*, models::*, client::*}; 2 | use std::io::prelude::*; 3 | use std::collections::BTreeMap; 4 | use url::Url; 5 | use serde_json::{json}; 6 | 7 | use std::sync::atomic::{AtomicBool, Ordering}; 8 | use tungstenite::{connect, Message}; 9 | use tungstenite::protocol::WebSocket; 10 | use tungstenite::client::AutoStream; 11 | use tungstenite::handshake::client::Response; 12 | use flate2::read::GzDecoder; 13 | use lazy_static::lazy_static; 14 | use std::sync::Mutex; 15 | 16 | static WEBSOCKET_URL: &'static str = "wss://api.hbdm.com"; 17 | static WS_HOST: &'static str = "api.hbdm.com"; 18 | 19 | static KLINE: &'static str = "kline"; 20 | static TRADE: &'static str = "trade.detail"; 21 | static DEPTH_ORDERBOOK : &'static str = "depth"; 22 | static PARTIAL_ORDERBOOK : &'static str = "high_freq"; 23 | 24 | lazy_static! { 25 | static ref SYMBOLS: Mutex> = Mutex::new(vec![]); 26 | static ref CHANNELS: Mutex> = Mutex::new(vec![]); 27 | } 28 | 29 | pub enum WebsocketEvent { 30 | Klines(Klines), 31 | TradeDetail(Trade), 32 | OrderBook(OrderBook), 33 | AccountUpdate(AccountInfo), 34 | OrderUpdate(OrderSubs), 35 | PositionUpdate(PositionSubs), 36 | LiquidationUpdate(LiquidationSubs), 37 | FundingRateUpdate(FundingRateSubs), 38 | } 39 | 40 | pub struct WebSockets<'a> { 41 | pub socket: Option<(WebSocket, Response)>, 42 | handler: Box APIResult<()> + 'a>, 43 | } 44 | 45 | impl<'a> WebSockets<'a> { 46 | pub fn new(handler: Callback) -> WebSockets<'a> 47 | where 48 | Callback: FnMut(WebsocketEvent) -> APIResult<()> + 'a 49 | { 50 | WebSockets { 51 | socket: None, 52 | handler: Box::new(handler), 53 | } 54 | } 55 | 56 | pub fn connect(&mut self, endpoint: &str, symbols: Vec<&str>, channels: Vec<&str>) -> APIResult<()> { 57 | let wss: String = format!("{}{}", WEBSOCKET_URL, endpoint); 58 | let url = Url::parse(&wss)?; 59 | ::log::info!("url:{}", url); 60 | match connect(url) { 61 | Ok(answer) => { 62 | self.socket = Some(answer); 63 | for channel in &channels { 64 | if channel.contains("orderbook") && !channel.contains("partial_orderbook") { 65 | for symbol in &symbols { 66 | let message = json!({ 67 | "sub": format!("market.{}.depth.step6", symbol), 68 | "id": "rustclient" 69 | }); 70 | if let Some(ref mut socket) = self.socket { 71 | socket.0.write_message(tungstenite::Message::Text(message.to_string()))?; 72 | ::log::info!("Write message {}", message.to_string()); 73 | }; 74 | } 75 | } 76 | if channel.contains("partial_orderbook") { 77 | for symbol in &symbols { 78 | let message = json!({ 79 | "sub": format!("market.{}.depth.size_150.high_freq", symbol), 80 | "data_type": "incremental", 81 | "id": "rustclient" 82 | }); 83 | if let Some(ref mut socket) = self.socket { 84 | socket.0.write_message(tungstenite::Message::Text(message.to_string()))?; 85 | ::log::info!("Write message {}", message.to_string()); 86 | }; 87 | } 88 | } 89 | 90 | if channel.contains("kline") { 91 | for symbol in &symbols { 92 | let message = json!({ 93 | "sub": format!("market.{}.{}", symbol, channel), 94 | "id": "rustclient" 95 | }); 96 | if let Some(ref mut socket) = self.socket { 97 | socket.0.write_message(tungstenite::Message::Text(message.to_string()))?; 98 | ::log::info!("Write message {}", message.to_string()); 99 | }; 100 | } 101 | } 102 | 103 | 104 | else if channel.contains("trade") { 105 | for symbol in &symbols { 106 | let message = json!({ 107 | "sub": format!("market.{}.trade.detail", symbol), 108 | "id": "rustclient" 109 | }); 110 | if let Some(ref mut socket) = self.socket { 111 | socket.0.write_message(tungstenite::Message::Text(message.to_string()))?; 112 | ::log::info!("Write message {}", message.to_string()); 113 | }; 114 | } 115 | } 116 | } 117 | Ok(()) 118 | } 119 | Err(e) => { 120 | ::log::info!("Error during handshake {}", e); 121 | Err(Box::new(e)) 122 | } 123 | } 124 | } 125 | 126 | pub fn connect_auth(&mut self, endpoint: &str,symbols: Vec<&str>, channels: Vec<&str>, access_key: &str, secret_key: &str ) -> APIResult<()> { 127 | let wss: String = format!("{}{}", WEBSOCKET_URL, endpoint); 128 | let url = Url::parse(&wss)?; 129 | ::log::info!("url:{}", url); 130 | 131 | for symbol in symbols { 132 | SYMBOLS.lock().unwrap().push(symbol.to_string()); 133 | } 134 | 135 | for channel in channels { 136 | CHANNELS.lock().unwrap().push(channel.to_string()); 137 | } 138 | 139 | match connect(url) { 140 | Ok(answer) => { 141 | self.socket = Some(answer); 142 | let mut params: BTreeMap = BTreeMap::new(); 143 | params.insert("AccessKeyId".to_string(), access_key.to_string()); 144 | params.insert("SignatureMethod".to_string(), "HmacSHA256".to_string()); 145 | params.insert("SignatureVersion".to_string(), "2".to_string()); 146 | let utctime = get_timestamp(); 147 | params.insert("Timestamp".to_string(), utctime.clone()); 148 | // println!("params: {:?}", params.clone()); 149 | 150 | let build_params = build_query_string(params.clone()); 151 | 152 | let format_str = format!("{}\n{}\n{}\n{}", "GET", WS_HOST, endpoint, build_params,); 153 | 154 | // println!("format str:{}", format_str.clone()); 155 | 156 | let signature = sign_hmac_sha256_base64( 157 | &secret_key, 158 | &format_str, 159 | ) 160 | .to_string(); 161 | 162 | ::log::info!("signature: {}",signature.clone()); 163 | 164 | let message = json!({ 165 | "AccessKeyId": params.get(&"AccessKeyId".to_string()), 166 | "SignatureMethod": params.get(&"SignatureMethod".to_string()), 167 | "SignatureVersion": params.get(&"SignatureVersion".to_string()), 168 | "Timestamp": params.get(&"Timestamp".to_string()), 169 | "Signature": signature, 170 | "op": "auth".to_string(), 171 | "type": "api".to_string(), 172 | }); 173 | if let Some(ref mut socket) = self.socket { 174 | socket.0.write_message(tungstenite::Message::Text(message.to_string()))?; 175 | ::log::info!("Write message {}", message.to_string()); 176 | }; 177 | Ok(()) 178 | } 179 | Err(e) => { 180 | ::log::info!("Error during handshake {}", e); 181 | Err(Box::new(e)) 182 | } 183 | } 184 | } 185 | 186 | 187 | pub fn disconnect(&mut self) -> APIResult<()> { 188 | if let Some(ref mut socket) = self.socket { 189 | socket.0.close(None)?; 190 | Ok(()) 191 | } else { 192 | ::log::info!("Not able to close the connection"); 193 | Ok(()) 194 | } 195 | } 196 | 197 | pub fn event_loop(&mut self, running: &AtomicBool) -> APIResult<()> { 198 | while running.load(Ordering::Relaxed) { 199 | if let Some(ref mut socket) = self.socket { 200 | let message = socket.0.read_message()?; 201 | 202 | match message { 203 | Message::Text(_) => {} 204 | Message::Ping(bin) | 205 | Message::Pong(bin) | 206 | Message::Binary(bin) => { 207 | let mut d = GzDecoder::new(&*bin); 208 | let mut s = String::new(); 209 | d.read_to_string(&mut s).unwrap(); 210 | let msg: serde_json::Value = serde_json::from_str(&s)?; 211 | ::log::debug!("####:{:?}", msg); 212 | if msg.get("ping") != None { 213 | ::log::info!("####:{:?}", msg); 214 | if let Some(ref mut socket) = self.socket { 215 | let message = json!({ 216 | "pong": msg.get("ping"), 217 | }); 218 | socket.0.write_message(tungstenite::Message::Text(message.to_string()))?; 219 | ::log::info!("Write message {}", message.to_string()); 220 | }; 221 | } 222 | if let Some(op) = msg.get("op") { 223 | if op == "ping" { 224 | if let Some(ref mut socket) = self.socket { 225 | let message = json!({ 226 | "op": "pong", 227 | "ts": msg.get("ts"), 228 | }); 229 | socket.0.write_message(tungstenite::Message::Text(message.to_string()))?; 230 | ::log::info!("Write message {}", message.to_string()); 231 | } 232 | } 233 | 234 | if op == "auth" { 235 | if let Some(err_code) = msg.get("err-code") { 236 | if err_code == 0 { 237 | for channel in &*CHANNELS.lock().unwrap() { 238 | if channel.contains("account") { 239 | for symbol in &*SYMBOLS.lock().unwrap() { 240 | let message = json!({ 241 | "op": "sub", 242 | "cid": "hbdm-rust", 243 | "topic": format!("accounts.{}", symbol), 244 | }); 245 | 246 | if let Some(ref mut socket) = self.socket { 247 | socket.0.write_message(tungstenite::Message::Text(message.to_string()))?; 248 | ::log::info!("Write message {}", message.to_string()); 249 | } 250 | 251 | } 252 | } 253 | 254 | if channel.contains("order") { 255 | for symbol in &*SYMBOLS.lock().unwrap() { 256 | let message = json!({ 257 | "op": "sub", 258 | "cid": "hbdm-rust", 259 | "topic": format!("orders.{}", symbol), 260 | }); 261 | 262 | if let Some(ref mut socket) = self.socket { 263 | socket.0.write_message(tungstenite::Message::Text(message.to_string()))?; 264 | ::log::info!("Write message {}", message.to_string()); 265 | } 266 | } 267 | } 268 | 269 | if channel.contains("position") { 270 | for symbol in &*SYMBOLS.lock().unwrap() { 271 | let message = json!({ 272 | "op": "sub", 273 | "cid": "hbdm-rust", 274 | "topic": format!("positions.{}", symbol), 275 | }); 276 | 277 | if let Some(ref mut socket) = self.socket { 278 | socket.0.write_message(tungstenite::Message::Text(message.to_string()))?; 279 | ::log::info!("Write message {}", message.to_string()); 280 | } 281 | } 282 | } 283 | 284 | if channel.contains("liquidation_order") { 285 | for symbol in &*SYMBOLS.lock().unwrap() { 286 | let message = json!({ 287 | "op": "sub", 288 | "cid": "hbdm-rust", 289 | "topic": format!("public.{}.liquidation_orders", symbol), 290 | }); 291 | 292 | if let Some(ref mut socket) = self.socket { 293 | socket.0.write_message(tungstenite::Message::Text(message.to_string()))?; 294 | ::log::info!("Write message {}", message.to_string()); 295 | } 296 | } 297 | } 298 | 299 | if channel.contains("funding_rate") { 300 | for symbol in &*SYMBOLS.lock().unwrap() { 301 | let message = json!({ 302 | "op": "sub", 303 | "cid": "hbdm-rust", 304 | "topic": format!("public.{}.funding_rate", symbol), 305 | }); 306 | 307 | if let Some(ref mut socket) = self.socket { 308 | socket.0.write_message(tungstenite::Message::Text(message.to_string()))?; 309 | ::log::info!("Write message {}", message.to_string()); 310 | } 311 | } 312 | } 313 | 314 | 315 | } 316 | 317 | 318 | } 319 | } 320 | } 321 | 322 | if op == "notify" { 323 | if let Some(topic) = msg.get("topic") { 324 | if topic.to_string().contains("accounts") { 325 | let account_update: AccountInfo = serde_json::from_str(&s)?; 326 | (self.handler)(WebsocketEvent::AccountUpdate(account_update))?; 327 | 328 | } 329 | 330 | if topic.to_string().contains("orders") { 331 | let order_update: OrderSubs = serde_json::from_str(&s)?; 332 | (self.handler)(WebsocketEvent::OrderUpdate(order_update))?; 333 | } 334 | 335 | if topic.to_string().contains("positions") { 336 | let position_update: PositionSubs = serde_json::from_str(&s)?; 337 | (self.handler)(WebsocketEvent::PositionUpdate(position_update))?; 338 | } 339 | 340 | if topic.to_string().contains("liquidation_orders") { 341 | let liquidation_orders: LiquidationSubs = serde_json::from_str(&s)?; 342 | (self.handler)(WebsocketEvent::LiquidationUpdate(liquidation_orders))?; 343 | } 344 | 345 | if topic.to_string().contains("funding_rate") { 346 | let funding_rate: FundingRateSubs = serde_json::from_str(&s)?; 347 | (self.handler)(WebsocketEvent::FundingRateUpdate(funding_rate))?; 348 | } 349 | 350 | } 351 | } 352 | } 353 | 354 | if let Some(data) = msg.get("ch") { 355 | if let Some(topic) = data.as_str() { 356 | if topic.contains(DEPTH_ORDERBOOK) == true { 357 | let depth_orderbook: OrderBook = serde_json::from_str(&s)?; 358 | (self.handler)(WebsocketEvent::OrderBook(depth_orderbook))?; 359 | } 360 | 361 | if topic.contains(PARTIAL_ORDERBOOK) == true { 362 | let depth_orderbook: OrderBook = serde_json::from_str(&s)?; 363 | (self.handler)(WebsocketEvent::OrderBook(depth_orderbook))?; 364 | } 365 | 366 | if topic.contains(TRADE) == true { 367 | let trade_detail: Trade = serde_json::from_str(&s)?; 368 | (self.handler)(WebsocketEvent::TradeDetail(trade_detail))?; 369 | } 370 | 371 | if topic.contains(KLINE) == true { 372 | let klines: Klines = serde_json::from_str(&s)?; 373 | (self.handler)(WebsocketEvent::Klines(klines))?; 374 | } 375 | } 376 | }; 377 | 378 | 379 | } 380 | Message::Close(e) => { 381 | ::log::info!("Disconnected {:?}", e); 382 | } 383 | } 384 | } 385 | } 386 | Ok(()) 387 | } 388 | } 389 | --------------------------------------------------------------------------------