├── LICENSE ├── README.md ├── binance ├── __init__.py ├── client.py ├── depthcache.py ├── enums.py ├── exceptions.py ├── helpers.py └── websockets.py └── example.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 pnpn521521 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-binance-with-futures-websocket 2 | 3 | Binance Exchange API python with futures websocket 4 | 5 | 6 | Based on [sammchardy/python-binance](https://github.com/sammchardy/python-binance) 7 | 8 | # usage 9 | 10 | See [example.py](https://github.com/pnpn521521/python-binance-with-futures-websocket/blob/master/example.py) 11 | -------------------------------------------------------------------------------- /binance/__init__.py: -------------------------------------------------------------------------------- 1 | """An unofficial Python wrapper for the Binance exchange API v3 2 | 3 | .. moduleauthor:: Sam McHardy 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /binance/client.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import hashlib 4 | import hmac 5 | import requests 6 | import time 7 | from operator import itemgetter 8 | from .helpers import date_to_milliseconds, interval_to_milliseconds 9 | from .exceptions import BinanceAPIException, BinanceRequestException, BinanceWithdrawException 10 | 11 | 12 | class Client(object): 13 | 14 | API_URL = 'https://api.binance.{}/api' 15 | WITHDRAW_API_URL = 'https://api.binance.{}/wapi' 16 | MARGIN_API_URL = 'https://api.binance.{}/sapi' 17 | WEBSITE_URL = 'https://www.binance.{}' 18 | FUTURES_URL = 'https://fapi.binance.{}/fapi' 19 | PUBLIC_API_VERSION = 'v1' 20 | PRIVATE_API_VERSION = 'v3' 21 | WITHDRAW_API_VERSION = 'v3' 22 | MARGIN_API_VERSION = 'v1' 23 | FUTURES_API_VERSION = 'v1' 24 | 25 | SYMBOL_TYPE_SPOT = 'SPOT' 26 | 27 | ORDER_STATUS_NEW = 'NEW' 28 | ORDER_STATUS_PARTIALLY_FILLED = 'PARTIALLY_FILLED' 29 | ORDER_STATUS_FILLED = 'FILLED' 30 | ORDER_STATUS_CANCELED = 'CANCELED' 31 | ORDER_STATUS_PENDING_CANCEL = 'PENDING_CANCEL' 32 | ORDER_STATUS_REJECTED = 'REJECTED' 33 | ORDER_STATUS_EXPIRED = 'EXPIRED' 34 | 35 | KLINE_INTERVAL_1MINUTE = '1m' 36 | KLINE_INTERVAL_3MINUTE = '3m' 37 | KLINE_INTERVAL_5MINUTE = '5m' 38 | KLINE_INTERVAL_15MINUTE = '15m' 39 | KLINE_INTERVAL_30MINUTE = '30m' 40 | KLINE_INTERVAL_1HOUR = '1h' 41 | KLINE_INTERVAL_2HOUR = '2h' 42 | KLINE_INTERVAL_4HOUR = '4h' 43 | KLINE_INTERVAL_6HOUR = '6h' 44 | KLINE_INTERVAL_8HOUR = '8h' 45 | KLINE_INTERVAL_12HOUR = '12h' 46 | KLINE_INTERVAL_1DAY = '1d' 47 | KLINE_INTERVAL_3DAY = '3d' 48 | KLINE_INTERVAL_1WEEK = '1w' 49 | KLINE_INTERVAL_1MONTH = '1M' 50 | 51 | SIDE_BUY = 'BUY' 52 | SIDE_SELL = 'SELL' 53 | 54 | ORDER_TYPE_LIMIT = 'LIMIT' 55 | ORDER_TYPE_MARKET = 'MARKET' 56 | ORDER_TYPE_STOP_LOSS = 'STOP_LOSS' 57 | ORDER_TYPE_STOP_LOSS_LIMIT = 'STOP_LOSS_LIMIT' 58 | ORDER_TYPE_TAKE_PROFIT = 'TAKE_PROFIT' 59 | ORDER_TYPE_TAKE_PROFIT_LIMIT = 'TAKE_PROFIT_LIMIT' 60 | ORDER_TYPE_LIMIT_MAKER = 'LIMIT_MAKER' 61 | 62 | TIME_IN_FORCE_GTC = 'GTC' # Good till cancelled 63 | TIME_IN_FORCE_IOC = 'IOC' # Immediate or cancel 64 | TIME_IN_FORCE_FOK = 'FOK' # Fill or kill 65 | 66 | ORDER_RESP_TYPE_ACK = 'ACK' 67 | ORDER_RESP_TYPE_RESULT = 'RESULT' 68 | ORDER_RESP_TYPE_FULL = 'FULL' 69 | 70 | # For accessing the data returned by Client.aggregate_trades(). 71 | AGG_ID = 'a' 72 | AGG_PRICE = 'p' 73 | AGG_QUANTITY = 'q' 74 | AGG_FIRST_TRADE_ID = 'f' 75 | AGG_LAST_TRADE_ID = 'l' 76 | AGG_TIME = 'T' 77 | AGG_BUYER_MAKES = 'm' 78 | AGG_BEST_MATCH = 'M' 79 | 80 | def __init__(self, api_key=None, api_secret=None, requests_params=None, tld='com'): 81 | """Binance API Client constructor 82 | 83 | :param api_key: Api Key 84 | :type api_key: str. 85 | :param api_secret: Api Secret 86 | :type api_secret: str. 87 | :param requests_params: optional - Dictionary of requests params to use for all calls 88 | :type requests_params: dict. 89 | 90 | """ 91 | 92 | self.API_URL = self.API_URL.format(tld) 93 | self.WITHDRAW_API_URL = self.WITHDRAW_API_URL.format(tld) 94 | self.MARGIN_API_URL = self.MARGIN_API_URL.format(tld) 95 | self.WEBSITE_URL = self.WEBSITE_URL.format(tld) 96 | self.FUTURES_URL = self.FUTURES_URL.format(tld) 97 | 98 | self.API_KEY = api_key 99 | self.API_SECRET = api_secret 100 | self.session = self._init_session() 101 | self._requests_params = requests_params 102 | self.response = None 103 | 104 | # init DNS and SSL cert 105 | self.ping() 106 | 107 | def _init_session(self): 108 | 109 | session = requests.session() 110 | session.headers.update({'Accept': 'application/json', 111 | 'User-Agent': 'binance/python', 112 | 'X-MBX-APIKEY': self.API_KEY}) 113 | return session 114 | 115 | def _create_api_uri(self, path, signed=True, version=PUBLIC_API_VERSION): 116 | v = self.PRIVATE_API_VERSION if signed else version 117 | return self.API_URL + '/' + v + '/' + path 118 | 119 | def _create_withdraw_api_uri(self, path): 120 | return self.WITHDRAW_API_URL + '/' + self.WITHDRAW_API_VERSION + '/' + path 121 | 122 | def _create_margin_api_uri(self, path): 123 | return self.MARGIN_API_URL + '/' + self.MARGIN_API_VERSION + '/' + path 124 | 125 | def _create_website_uri(self, path): 126 | return self.WEBSITE_URL + '/' + path 127 | 128 | def _create_futures_api_uri(self, path): 129 | return self.FUTURES_URL + '/' + self.FUTURES_API_VERSION + '/' + path 130 | 131 | def _generate_signature(self, data): 132 | 133 | ordered_data = self._order_params(data) 134 | query_string = '&'.join(["{}={}".format(d[0], d[1]) for d in ordered_data]) 135 | m = hmac.new(self.API_SECRET.encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256) 136 | return m.hexdigest() 137 | 138 | def _order_params(self, data): 139 | """Convert params to list with signature as last element 140 | 141 | :param data: 142 | :return: 143 | 144 | """ 145 | has_signature = False 146 | params = [] 147 | for key, value in data.items(): 148 | if key == 'signature': 149 | has_signature = True 150 | else: 151 | params.append((key, value)) 152 | # sort parameters by key 153 | params.sort(key=itemgetter(0)) 154 | if has_signature: 155 | params.append(('signature', data['signature'])) 156 | return params 157 | 158 | def _request(self, method, uri, signed, force_params=False, **kwargs): 159 | 160 | # set default requests timeout 161 | kwargs['timeout'] = 10 162 | 163 | # add our global requests params 164 | if self._requests_params: 165 | kwargs.update(self._requests_params) 166 | 167 | data = kwargs.get('data', None) 168 | if data and isinstance(data, dict): 169 | kwargs['data'] = data 170 | 171 | # find any requests params passed and apply them 172 | if 'requests_params' in kwargs['data']: 173 | # merge requests params into kwargs 174 | kwargs.update(kwargs['data']['requests_params']) 175 | del(kwargs['data']['requests_params']) 176 | 177 | if signed: 178 | # generate signature 179 | kwargs['data']['timestamp'] = int(time.time() * 1000) 180 | kwargs['data']['signature'] = self._generate_signature(kwargs['data']) 181 | 182 | # sort get and post params to match signature order 183 | if data: 184 | # sort post params 185 | kwargs['data'] = self._order_params(kwargs['data']) 186 | # Remove any arguments with values of None. 187 | null_args = [i for i, (key, value) in enumerate(kwargs['data']) if value is None] 188 | for i in reversed(null_args): 189 | del kwargs['data'][i] 190 | 191 | # if get request assign data array to params value for requests lib 192 | if data and (method == 'get' or force_params): 193 | kwargs['params'] = '&'.join('%s=%s' % (data[0], data[1]) for data in kwargs['data']) 194 | del(kwargs['data']) 195 | 196 | self.response = getattr(self.session, method)(uri, **kwargs) 197 | return self._handle_response() 198 | 199 | def _request_api(self, method, path, signed=False, version=PUBLIC_API_VERSION, **kwargs): 200 | uri = self._create_api_uri(path, signed, version) 201 | 202 | return self._request(method, uri, signed, **kwargs) 203 | 204 | def _request_withdraw_api(self, method, path, signed=False, **kwargs): 205 | uri = self._create_withdraw_api_uri(path) 206 | 207 | return self._request(method, uri, signed, True, **kwargs) 208 | 209 | def _request_margin_api(self, method, path, signed=False, **kwargs): 210 | uri = self._create_margin_api_uri(path) 211 | 212 | return self._request(method, uri, signed, **kwargs) 213 | 214 | def _request_website(self, method, path, signed=False, **kwargs): 215 | uri = self._create_website_uri(path) 216 | 217 | return self._request(method, uri, signed, **kwargs) 218 | 219 | def _request_futures_api(self, method, path, signed=False, **kwargs): 220 | uri = self._create_futures_api_uri(path) 221 | 222 | return self._request(method, uri, signed, True, **kwargs) 223 | 224 | def _handle_response(self): 225 | """Internal helper for handling API responses from the Binance server. 226 | Raises the appropriate exceptions when necessary; otherwise, returns the 227 | response. 228 | """ 229 | if not str(self.response.status_code).startswith('2'): 230 | raise BinanceAPIException(self.response) 231 | try: 232 | return self.response.json() 233 | except ValueError: 234 | raise BinanceRequestException('Invalid Response: %s' % self.response.text) 235 | 236 | def _get(self, path, signed=False, version=PUBLIC_API_VERSION, **kwargs): 237 | return self._request_api('get', path, signed, version, **kwargs) 238 | 239 | def _post(self, path, signed=False, version=PUBLIC_API_VERSION, **kwargs): 240 | return self._request_api('post', path, signed, version, **kwargs) 241 | 242 | def _put(self, path, signed=False, version=PUBLIC_API_VERSION, **kwargs): 243 | return self._request_api('put', path, signed, version, **kwargs) 244 | 245 | def _delete(self, path, signed=False, version=PUBLIC_API_VERSION, **kwargs): 246 | return self._request_api('delete', path, signed, version, **kwargs) 247 | 248 | # Exchange Endpoints 249 | 250 | def get_products(self): 251 | """Return list of products currently listed on Binance 252 | 253 | Use get_exchange_info() call instead 254 | 255 | :returns: list - List of product dictionaries 256 | 257 | :raises: BinanceRequestException, BinanceAPIException 258 | 259 | """ 260 | 261 | products = self._request_website('get', 'exchange/public/product') 262 | return products 263 | 264 | def get_exchange_info(self): 265 | """Return rate limits and list of symbols 266 | 267 | :returns: list - List of product dictionaries 268 | 269 | .. code-block:: python 270 | 271 | { 272 | "timezone": "UTC", 273 | "serverTime": 1508631584636, 274 | "rateLimits": [ 275 | { 276 | "rateLimitType": "REQUESTS", 277 | "interval": "MINUTE", 278 | "limit": 1200 279 | }, 280 | { 281 | "rateLimitType": "ORDERS", 282 | "interval": "SECOND", 283 | "limit": 10 284 | }, 285 | { 286 | "rateLimitType": "ORDERS", 287 | "interval": "DAY", 288 | "limit": 100000 289 | } 290 | ], 291 | "exchangeFilters": [], 292 | "symbols": [ 293 | { 294 | "symbol": "ETHBTC", 295 | "status": "TRADING", 296 | "baseAsset": "ETH", 297 | "baseAssetPrecision": 8, 298 | "quoteAsset": "BTC", 299 | "quotePrecision": 8, 300 | "orderTypes": ["LIMIT", "MARKET"], 301 | "icebergAllowed": false, 302 | "filters": [ 303 | { 304 | "filterType": "PRICE_FILTER", 305 | "minPrice": "0.00000100", 306 | "maxPrice": "100000.00000000", 307 | "tickSize": "0.00000100" 308 | }, { 309 | "filterType": "LOT_SIZE", 310 | "minQty": "0.00100000", 311 | "maxQty": "100000.00000000", 312 | "stepSize": "0.00100000" 313 | }, { 314 | "filterType": "MIN_NOTIONAL", 315 | "minNotional": "0.00100000" 316 | } 317 | ] 318 | } 319 | ] 320 | } 321 | 322 | :raises: BinanceRequestException, BinanceAPIException 323 | 324 | """ 325 | 326 | return self._get('exchangeInfo') 327 | 328 | def get_symbol_info(self, symbol): 329 | """Return information about a symbol 330 | 331 | :param symbol: required e.g BNBBTC 332 | :type symbol: str 333 | 334 | :returns: Dict if found, None if not 335 | 336 | .. code-block:: python 337 | 338 | { 339 | "symbol": "ETHBTC", 340 | "status": "TRADING", 341 | "baseAsset": "ETH", 342 | "baseAssetPrecision": 8, 343 | "quoteAsset": "BTC", 344 | "quotePrecision": 8, 345 | "orderTypes": ["LIMIT", "MARKET"], 346 | "icebergAllowed": false, 347 | "filters": [ 348 | { 349 | "filterType": "PRICE_FILTER", 350 | "minPrice": "0.00000100", 351 | "maxPrice": "100000.00000000", 352 | "tickSize": "0.00000100" 353 | }, { 354 | "filterType": "LOT_SIZE", 355 | "minQty": "0.00100000", 356 | "maxQty": "100000.00000000", 357 | "stepSize": "0.00100000" 358 | }, { 359 | "filterType": "MIN_NOTIONAL", 360 | "minNotional": "0.00100000" 361 | } 362 | ] 363 | } 364 | 365 | :raises: BinanceRequestException, BinanceAPIException 366 | 367 | """ 368 | 369 | res = self._get('exchangeInfo') 370 | 371 | for item in res['symbols']: 372 | if item['symbol'] == symbol.upper(): 373 | return item 374 | 375 | return None 376 | 377 | # General Endpoints 378 | 379 | def ping(self): 380 | """Test connectivity to the Rest API. 381 | 382 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#test-connectivity 383 | 384 | :returns: Empty array 385 | 386 | .. code-block:: python 387 | 388 | {} 389 | 390 | :raises: BinanceRequestException, BinanceAPIException 391 | 392 | """ 393 | return self._get('ping') 394 | 395 | def get_server_time(self): 396 | """Test connectivity to the Rest API and get the current server time. 397 | 398 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#check-server-time 399 | 400 | :returns: Current server time 401 | 402 | .. code-block:: python 403 | 404 | { 405 | "serverTime": 1499827319559 406 | } 407 | 408 | :raises: BinanceRequestException, BinanceAPIException 409 | 410 | """ 411 | return self._get('time') 412 | 413 | # Market Data Endpoints 414 | 415 | def get_all_tickers(self): 416 | """Latest price for all symbols. 417 | 418 | https://www.binance.com/restapipub.html#symbols-price-ticker 419 | 420 | :returns: List of market tickers 421 | 422 | .. code-block:: python 423 | 424 | [ 425 | { 426 | "symbol": "LTCBTC", 427 | "price": "4.00000200" 428 | }, 429 | { 430 | "symbol": "ETHBTC", 431 | "price": "0.07946600" 432 | } 433 | ] 434 | 435 | :raises: BinanceRequestException, BinanceAPIException 436 | 437 | """ 438 | return self._get('ticker/allPrices') 439 | 440 | def get_orderbook_tickers(self): 441 | """Best price/qty on the order book for all symbols. 442 | 443 | https://www.binance.com/restapipub.html#symbols-order-book-ticker 444 | 445 | :returns: List of order book market entries 446 | 447 | .. code-block:: python 448 | 449 | [ 450 | { 451 | "symbol": "LTCBTC", 452 | "bidPrice": "4.00000000", 453 | "bidQty": "431.00000000", 454 | "askPrice": "4.00000200", 455 | "askQty": "9.00000000" 456 | }, 457 | { 458 | "symbol": "ETHBTC", 459 | "bidPrice": "0.07946700", 460 | "bidQty": "9.00000000", 461 | "askPrice": "100000.00000000", 462 | "askQty": "1000.00000000" 463 | } 464 | ] 465 | 466 | :raises: BinanceRequestException, BinanceAPIException 467 | 468 | """ 469 | return self._get('ticker/allBookTickers') 470 | 471 | def get_order_book(self, **params): 472 | """Get the Order Book for the market 473 | 474 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#order-book 475 | 476 | :param symbol: required 477 | :type symbol: str 478 | :param limit: Default 100; max 1000 479 | :type limit: int 480 | 481 | :returns: API response 482 | 483 | .. code-block:: python 484 | 485 | { 486 | "lastUpdateId": 1027024, 487 | "bids": [ 488 | [ 489 | "4.00000000", # PRICE 490 | "431.00000000", # QTY 491 | [] # Can be ignored 492 | ] 493 | ], 494 | "asks": [ 495 | [ 496 | "4.00000200", 497 | "12.00000000", 498 | [] 499 | ] 500 | ] 501 | } 502 | 503 | :raises: BinanceRequestException, BinanceAPIException 504 | 505 | """ 506 | return self._get('depth', data=params) 507 | 508 | def get_recent_trades(self, **params): 509 | """Get recent trades (up to last 500). 510 | 511 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#recent-trades-list 512 | 513 | :param symbol: required 514 | :type symbol: str 515 | :param limit: Default 500; max 500. 516 | :type limit: int 517 | 518 | :returns: API response 519 | 520 | .. code-block:: python 521 | 522 | [ 523 | { 524 | "id": 28457, 525 | "price": "4.00000100", 526 | "qty": "12.00000000", 527 | "time": 1499865549590, 528 | "isBuyerMaker": true, 529 | "isBestMatch": true 530 | } 531 | ] 532 | 533 | :raises: BinanceRequestException, BinanceAPIException 534 | 535 | """ 536 | return self._get('trades', data=params) 537 | 538 | def get_historical_trades(self, **params): 539 | """Get older trades. 540 | 541 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#recent-trades-list 542 | 543 | :param symbol: required 544 | :type symbol: str 545 | :param limit: Default 500; max 500. 546 | :type limit: int 547 | :param fromId: TradeId to fetch from. Default gets most recent trades. 548 | :type fromId: str 549 | 550 | :returns: API response 551 | 552 | .. code-block:: python 553 | 554 | [ 555 | { 556 | "id": 28457, 557 | "price": "4.00000100", 558 | "qty": "12.00000000", 559 | "time": 1499865549590, 560 | "isBuyerMaker": true, 561 | "isBestMatch": true 562 | } 563 | ] 564 | 565 | :raises: BinanceRequestException, BinanceAPIException 566 | 567 | """ 568 | return self._get('historicalTrades', data=params) 569 | 570 | def get_aggregate_trades(self, **params): 571 | """Get compressed, aggregate trades. Trades that fill at the time, 572 | from the same order, with the same price will have the quantity aggregated. 573 | 574 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list 575 | 576 | :param symbol: required 577 | :type symbol: str 578 | :param fromId: ID to get aggregate trades from INCLUSIVE. 579 | :type fromId: str 580 | :param startTime: Timestamp in ms to get aggregate trades from INCLUSIVE. 581 | :type startTime: int 582 | :param endTime: Timestamp in ms to get aggregate trades until INCLUSIVE. 583 | :type endTime: int 584 | :param limit: Default 500; max 500. 585 | :type limit: int 586 | 587 | :returns: API response 588 | 589 | .. code-block:: python 590 | 591 | [ 592 | { 593 | "a": 26129, # Aggregate tradeId 594 | "p": "0.01633102", # Price 595 | "q": "4.70443515", # Quantity 596 | "f": 27781, # First tradeId 597 | "l": 27781, # Last tradeId 598 | "T": 1498793709153, # Timestamp 599 | "m": true, # Was the buyer the maker? 600 | "M": true # Was the trade the best price match? 601 | } 602 | ] 603 | 604 | :raises: BinanceRequestException, BinanceAPIException 605 | 606 | """ 607 | return self._get('aggTrades', data=params) 608 | 609 | def aggregate_trade_iter(self, symbol, start_str=None, last_id=None): 610 | """Iterate over aggregate trade data from (start_time or last_id) to 611 | the end of the history so far. 612 | 613 | If start_time is specified, start with the first trade after 614 | start_time. Meant to initialise a local cache of trade data. 615 | 616 | If last_id is specified, start with the trade after it. This is meant 617 | for updating a pre-existing local trade data cache. 618 | 619 | Only allows start_str or last_id—not both. Not guaranteed to work 620 | right if you're running more than one of these simultaneously. You 621 | will probably hit your rate limit. 622 | 623 | See dateparser docs for valid start and end string formats http://dateparser.readthedocs.io/en/latest/ 624 | 625 | If using offset strings for dates add "UTC" to date string e.g. "now UTC", "11 hours ago UTC" 626 | 627 | :param symbol: Symbol string e.g. ETHBTC 628 | :type symbol: str 629 | :param start_str: Start date string in UTC format or timestamp in milliseconds. The iterator will 630 | return the first trade occurring later than this time. 631 | :type start_str: str|int 632 | :param last_id: aggregate trade ID of the last known aggregate trade. 633 | Not a regular trade ID. See https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list. 634 | 635 | :returns: an iterator of JSON objects, one per trade. The format of 636 | each object is identical to Client.aggregate_trades(). 637 | 638 | :type last_id: int 639 | """ 640 | if start_str is not None and last_id is not None: 641 | raise ValueError( 642 | 'start_time and last_id may not be simultaneously specified.') 643 | 644 | # If there's no last_id, get one. 645 | if last_id is None: 646 | # Without a last_id, we actually need the first trade. Normally, 647 | # we'd get rid of it. See the next loop. 648 | if start_str is None: 649 | trades = self.get_aggregate_trades(symbol=symbol, fromId=0) 650 | else: 651 | # The difference between startTime and endTime should be less 652 | # or equal than an hour and the result set should contain at 653 | # least one trade. 654 | if type(start_str) == int: 655 | start_ts = start_str 656 | else: 657 | start_ts = date_to_milliseconds(start_str) 658 | # If the resulting set is empty (i.e. no trades in that interval) 659 | # then we just move forward hour by hour until we find at least one 660 | # trade or reach present moment 661 | while True: 662 | end_ts = start_ts + (60 * 60 * 1000) 663 | trades = self.get_aggregate_trades( 664 | symbol=symbol, 665 | startTime=start_ts, 666 | endTime=end_ts) 667 | if len(trades) > 0: 668 | break 669 | # If we reach present moment and find no trades then there is 670 | # nothing to iterate, so we're done 671 | if end_ts > int(time.time() * 1000): 672 | return 673 | start_ts = end_ts 674 | for t in trades: 675 | yield t 676 | last_id = trades[-1][self.AGG_ID] 677 | 678 | while True: 679 | # There is no need to wait between queries, to avoid hitting the 680 | # rate limit. We're using blocking IO, and as long as we're the 681 | # only thread running calls like this, Binance will automatically 682 | # add the right delay time on their end, forcing us to wait for 683 | # data. That really simplifies this function's job. Binance is 684 | # fucking awesome. 685 | trades = self.get_aggregate_trades(symbol=symbol, fromId=last_id) 686 | # fromId=n returns a set starting with id n, but we already have 687 | # that one. So get rid of the first item in the result set. 688 | trades = trades[1:] 689 | if len(trades) == 0: 690 | return 691 | for t in trades: 692 | yield t 693 | last_id = trades[-1][self.AGG_ID] 694 | 695 | def get_klines(self, **params): 696 | """Kline/candlestick bars for a symbol. Klines are uniquely identified by their open time. 697 | 698 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#klinecandlestick-data 699 | 700 | :param symbol: required 701 | :type symbol: str 702 | :param interval: - 703 | :type interval: str 704 | :param limit: - Default 500; max 500. 705 | :type limit: int 706 | :param startTime: 707 | :type startTime: int 708 | :param endTime: 709 | :type endTime: int 710 | 711 | :returns: API response 712 | 713 | .. code-block:: python 714 | 715 | [ 716 | [ 717 | 1499040000000, # Open time 718 | "0.01634790", # Open 719 | "0.80000000", # High 720 | "0.01575800", # Low 721 | "0.01577100", # Close 722 | "148976.11427815", # Volume 723 | 1499644799999, # Close time 724 | "2434.19055334", # Quote asset volume 725 | 308, # Number of trades 726 | "1756.87402397", # Taker buy base asset volume 727 | "28.46694368", # Taker buy quote asset volume 728 | "17928899.62484339" # Can be ignored 729 | ] 730 | ] 731 | 732 | :raises: BinanceRequestException, BinanceAPIException 733 | 734 | """ 735 | return self._get('klines', data=params) 736 | 737 | def _get_earliest_valid_timestamp(self, symbol, interval): 738 | """Get earliest valid open timestamp from Binance 739 | 740 | :param symbol: Name of symbol pair e.g BNBBTC 741 | :type symbol: str 742 | :param interval: Binance Kline interval 743 | :type interval: str 744 | 745 | :return: first valid timestamp 746 | 747 | """ 748 | kline = self.get_klines( 749 | symbol=symbol, 750 | interval=interval, 751 | limit=1, 752 | startTime=0, 753 | endTime=None 754 | ) 755 | return kline[0][0] 756 | 757 | def get_historical_klines(self, symbol, interval, start_str, end_str=None, 758 | limit=500): 759 | """Get Historical Klines from Binance 760 | 761 | See dateparser docs for valid start and end string formats http://dateparser.readthedocs.io/en/latest/ 762 | 763 | If using offset strings for dates add "UTC" to date string e.g. "now UTC", "11 hours ago UTC" 764 | 765 | :param symbol: Name of symbol pair e.g BNBBTC 766 | :type symbol: str 767 | :param interval: Binance Kline interval 768 | :type interval: str 769 | :param start_str: Start date string in UTC format or timestamp in milliseconds 770 | :type start_str: str|int 771 | :param end_str: optional - end date string in UTC format or timestamp in milliseconds (default will fetch everything up to now) 772 | :type end_str: str|int 773 | :param limit: Default 500; max 1000. 774 | :type limit: int 775 | 776 | :return: list of OHLCV values 777 | 778 | """ 779 | # init our list 780 | output_data = [] 781 | 782 | # setup the max limit 783 | limit = limit 784 | 785 | # convert interval to useful value in seconds 786 | timeframe = interval_to_milliseconds(interval) 787 | 788 | # convert our date strings to milliseconds 789 | if type(start_str) == int: 790 | start_ts = start_str 791 | else: 792 | start_ts = date_to_milliseconds(start_str) 793 | 794 | # establish first available start timestamp 795 | first_valid_ts = self._get_earliest_valid_timestamp(symbol, interval) 796 | start_ts = max(start_ts, first_valid_ts) 797 | 798 | # if an end time was passed convert it 799 | end_ts = None 800 | if end_str: 801 | if type(end_str) == int: 802 | end_ts = end_str 803 | else: 804 | end_ts = date_to_milliseconds(end_str) 805 | 806 | idx = 0 807 | while True: 808 | # fetch the klines from start_ts up to max 500 entries or the end_ts if set 809 | temp_data = self.get_klines( 810 | symbol=symbol, 811 | interval=interval, 812 | limit=limit, 813 | startTime=start_ts, 814 | endTime=end_ts 815 | ) 816 | 817 | # handle the case where exactly the limit amount of data was returned last loop 818 | if not len(temp_data): 819 | break 820 | 821 | # append this loops data to our output data 822 | output_data += temp_data 823 | 824 | # set our start timestamp using the last value in the array 825 | start_ts = temp_data[-1][0] 826 | 827 | idx += 1 828 | # check if we received less than the required limit and exit the loop 829 | if len(temp_data) < limit: 830 | # exit the while loop 831 | break 832 | 833 | # increment next call by our timeframe 834 | start_ts += timeframe 835 | 836 | # sleep after every 3rd call to be kind to the API 837 | if idx % 3 == 0: 838 | time.sleep(1) 839 | 840 | return output_data 841 | 842 | def get_historical_klines_generator(self, symbol, interval, start_str, end_str=None): 843 | """Get Historical Klines from Binance 844 | 845 | See dateparser docs for valid start and end string formats http://dateparser.readthedocs.io/en/latest/ 846 | 847 | If using offset strings for dates add "UTC" to date string e.g. "now UTC", "11 hours ago UTC" 848 | 849 | :param symbol: Name of symbol pair e.g BNBBTC 850 | :type symbol: str 851 | :param interval: Binance Kline interval 852 | :type interval: str 853 | :param start_str: Start date string in UTC format or timestamp in milliseconds 854 | :type start_str: str|int 855 | :param end_str: optional - end date string in UTC format or timestamp in milliseconds (default will fetch everything up to now) 856 | :type end_str: str|int 857 | 858 | :return: generator of OHLCV values 859 | 860 | """ 861 | 862 | # setup the max limit 863 | limit = 500 864 | 865 | # convert interval to useful value in seconds 866 | timeframe = interval_to_milliseconds(interval) 867 | 868 | # convert our date strings to milliseconds 869 | if type(start_str) == int: 870 | start_ts = start_str 871 | else: 872 | start_ts = date_to_milliseconds(start_str) 873 | 874 | # establish first available start timestamp 875 | first_valid_ts = self._get_earliest_valid_timestamp(symbol, interval) 876 | start_ts = max(start_ts, first_valid_ts) 877 | 878 | # if an end time was passed convert it 879 | end_ts = None 880 | if end_str: 881 | if type(end_str) == int: 882 | end_ts = end_str 883 | else: 884 | end_ts = date_to_milliseconds(end_str) 885 | 886 | idx = 0 887 | while True: 888 | # fetch the klines from start_ts up to max 500 entries or the end_ts if set 889 | output_data = self.get_klines( 890 | symbol=symbol, 891 | interval=interval, 892 | limit=limit, 893 | startTime=start_ts, 894 | endTime=end_ts 895 | ) 896 | 897 | # handle the case where exactly the limit amount of data was returned last loop 898 | if not len(output_data): 899 | break 900 | 901 | # yield data 902 | for o in output_data: 903 | yield o 904 | 905 | # set our start timestamp using the last value in the array 906 | start_ts = output_data[-1][0] 907 | 908 | idx += 1 909 | # check if we received less than the required limit and exit the loop 910 | if len(output_data) < limit: 911 | # exit the while loop 912 | break 913 | 914 | # increment next call by our timeframe 915 | start_ts += timeframe 916 | 917 | # sleep after every 3rd call to be kind to the API 918 | if idx % 3 == 0: 919 | time.sleep(1) 920 | 921 | def get_avg_price(self, **params): 922 | """Current average price for a symbol. 923 | 924 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#current-average-price 925 | 926 | :param symbol: 927 | :type symbol: str 928 | 929 | :returns: API response 930 | 931 | .. code-block:: python 932 | 933 | { 934 | "mins": 5, 935 | "price": "9.35751834" 936 | } 937 | """ 938 | return self._get('avgPrice', data=params, version=self.PRIVATE_API_VERSION) 939 | 940 | def get_ticker(self, **params): 941 | """24 hour price change statistics. 942 | 943 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#24hr-ticker-price-change-statistics 944 | 945 | :param symbol: 946 | :type symbol: str 947 | 948 | :returns: API response 949 | 950 | .. code-block:: python 951 | 952 | { 953 | "priceChange": "-94.99999800", 954 | "priceChangePercent": "-95.960", 955 | "weightedAvgPrice": "0.29628482", 956 | "prevClosePrice": "0.10002000", 957 | "lastPrice": "4.00000200", 958 | "bidPrice": "4.00000000", 959 | "askPrice": "4.00000200", 960 | "openPrice": "99.00000000", 961 | "highPrice": "100.00000000", 962 | "lowPrice": "0.10000000", 963 | "volume": "8913.30000000", 964 | "openTime": 1499783499040, 965 | "closeTime": 1499869899040, 966 | "fristId": 28385, # First tradeId 967 | "lastId": 28460, # Last tradeId 968 | "count": 76 # Trade count 969 | } 970 | 971 | OR 972 | 973 | .. code-block:: python 974 | 975 | [ 976 | { 977 | "priceChange": "-94.99999800", 978 | "priceChangePercent": "-95.960", 979 | "weightedAvgPrice": "0.29628482", 980 | "prevClosePrice": "0.10002000", 981 | "lastPrice": "4.00000200", 982 | "bidPrice": "4.00000000", 983 | "askPrice": "4.00000200", 984 | "openPrice": "99.00000000", 985 | "highPrice": "100.00000000", 986 | "lowPrice": "0.10000000", 987 | "volume": "8913.30000000", 988 | "openTime": 1499783499040, 989 | "closeTime": 1499869899040, 990 | "fristId": 28385, # First tradeId 991 | "lastId": 28460, # Last tradeId 992 | "count": 76 # Trade count 993 | } 994 | ] 995 | 996 | :raises: BinanceRequestException, BinanceAPIException 997 | 998 | """ 999 | return self._get('ticker/24hr', data=params) 1000 | 1001 | def get_symbol_ticker(self, **params): 1002 | """Latest price for a symbol or symbols. 1003 | 1004 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#24hr-ticker-price-change-statistics 1005 | 1006 | :param symbol: 1007 | :type symbol: str 1008 | 1009 | :returns: API response 1010 | 1011 | .. code-block:: python 1012 | 1013 | { 1014 | "symbol": "LTCBTC", 1015 | "price": "4.00000200" 1016 | } 1017 | 1018 | OR 1019 | 1020 | .. code-block:: python 1021 | 1022 | [ 1023 | { 1024 | "symbol": "LTCBTC", 1025 | "price": "4.00000200" 1026 | }, 1027 | { 1028 | "symbol": "ETHBTC", 1029 | "price": "0.07946600" 1030 | } 1031 | ] 1032 | 1033 | :raises: BinanceRequestException, BinanceAPIException 1034 | 1035 | """ 1036 | return self._get('ticker/price', data=params, version=self.PRIVATE_API_VERSION) 1037 | 1038 | def get_orderbook_ticker(self, **params): 1039 | """Latest price for a symbol or symbols. 1040 | 1041 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#symbol-order-book-ticker 1042 | 1043 | :param symbol: 1044 | :type symbol: str 1045 | 1046 | :returns: API response 1047 | 1048 | .. code-block:: python 1049 | 1050 | { 1051 | "symbol": "LTCBTC", 1052 | "bidPrice": "4.00000000", 1053 | "bidQty": "431.00000000", 1054 | "askPrice": "4.00000200", 1055 | "askQty": "9.00000000" 1056 | } 1057 | 1058 | OR 1059 | 1060 | .. code-block:: python 1061 | 1062 | [ 1063 | { 1064 | "symbol": "LTCBTC", 1065 | "bidPrice": "4.00000000", 1066 | "bidQty": "431.00000000", 1067 | "askPrice": "4.00000200", 1068 | "askQty": "9.00000000" 1069 | }, 1070 | { 1071 | "symbol": "ETHBTC", 1072 | "bidPrice": "0.07946700", 1073 | "bidQty": "9.00000000", 1074 | "askPrice": "100000.00000000", 1075 | "askQty": "1000.00000000" 1076 | } 1077 | ] 1078 | 1079 | :raises: BinanceRequestException, BinanceAPIException 1080 | 1081 | """ 1082 | return self._get('ticker/bookTicker', data=params, version=self.PRIVATE_API_VERSION) 1083 | 1084 | # Account Endpoints 1085 | 1086 | def create_order(self, **params): 1087 | """Send in a new order 1088 | 1089 | Any order with an icebergQty MUST have timeInForce set to GTC. 1090 | 1091 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#new-order--trade 1092 | 1093 | :param symbol: required 1094 | :type symbol: str 1095 | :param side: required 1096 | :type side: str 1097 | :param type: required 1098 | :type type: str 1099 | :param timeInForce: required if limit order 1100 | :type timeInForce: str 1101 | :param quantity: required 1102 | :type quantity: decimal 1103 | :param quoteOrderQty: amount the user wants to spend (when buying) or receive (when selling) 1104 | of the quote asset, applicable to MARKET orders 1105 | :type quoteOrderQty: decimal 1106 | :param price: required 1107 | :type price: str 1108 | :param newClientOrderId: A unique id for the order. Automatically generated if not sent. 1109 | :type newClientOrderId: str 1110 | :param icebergQty: Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order. 1111 | :type icebergQty: decimal 1112 | :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. 1113 | :type newOrderRespType: str 1114 | :param recvWindow: the number of milliseconds the request is valid for 1115 | :type recvWindow: int 1116 | 1117 | :returns: API response 1118 | 1119 | Response ACK: 1120 | 1121 | .. code-block:: python 1122 | 1123 | { 1124 | "symbol":"LTCBTC", 1125 | "orderId": 1, 1126 | "clientOrderId": "myOrder1" # Will be newClientOrderId 1127 | "transactTime": 1499827319559 1128 | } 1129 | 1130 | Response RESULT: 1131 | 1132 | .. code-block:: python 1133 | 1134 | { 1135 | "symbol": "BTCUSDT", 1136 | "orderId": 28, 1137 | "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP", 1138 | "transactTime": 1507725176595, 1139 | "price": "0.00000000", 1140 | "origQty": "10.00000000", 1141 | "executedQty": "10.00000000", 1142 | "status": "FILLED", 1143 | "timeInForce": "GTC", 1144 | "type": "MARKET", 1145 | "side": "SELL" 1146 | } 1147 | 1148 | Response FULL: 1149 | 1150 | .. code-block:: python 1151 | 1152 | { 1153 | "symbol": "BTCUSDT", 1154 | "orderId": 28, 1155 | "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP", 1156 | "transactTime": 1507725176595, 1157 | "price": "0.00000000", 1158 | "origQty": "10.00000000", 1159 | "executedQty": "10.00000000", 1160 | "status": "FILLED", 1161 | "timeInForce": "GTC", 1162 | "type": "MARKET", 1163 | "side": "SELL", 1164 | "fills": [ 1165 | { 1166 | "price": "4000.00000000", 1167 | "qty": "1.00000000", 1168 | "commission": "4.00000000", 1169 | "commissionAsset": "USDT" 1170 | }, 1171 | { 1172 | "price": "3999.00000000", 1173 | "qty": "5.00000000", 1174 | "commission": "19.99500000", 1175 | "commissionAsset": "USDT" 1176 | }, 1177 | { 1178 | "price": "3998.00000000", 1179 | "qty": "2.00000000", 1180 | "commission": "7.99600000", 1181 | "commissionAsset": "USDT" 1182 | }, 1183 | { 1184 | "price": "3997.00000000", 1185 | "qty": "1.00000000", 1186 | "commission": "3.99700000", 1187 | "commissionAsset": "USDT" 1188 | }, 1189 | { 1190 | "price": "3995.00000000", 1191 | "qty": "1.00000000", 1192 | "commission": "3.99500000", 1193 | "commissionAsset": "USDT" 1194 | } 1195 | ] 1196 | } 1197 | 1198 | :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException 1199 | 1200 | """ 1201 | return self._post('order', True, data=params) 1202 | 1203 | def order_limit(self, timeInForce=TIME_IN_FORCE_GTC, **params): 1204 | """Send in a new limit order 1205 | 1206 | Any order with an icebergQty MUST have timeInForce set to GTC. 1207 | 1208 | :param symbol: required 1209 | :type symbol: str 1210 | :param side: required 1211 | :type side: str 1212 | :param quantity: required 1213 | :type quantity: decimal 1214 | :param price: required 1215 | :type price: str 1216 | :param timeInForce: default Good till cancelled 1217 | :type timeInForce: str 1218 | :param newClientOrderId: A unique id for the order. Automatically generated if not sent. 1219 | :type newClientOrderId: str 1220 | :param icebergQty: Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order. 1221 | :type icebergQty: decimal 1222 | :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. 1223 | :type newOrderRespType: str 1224 | :param recvWindow: the number of milliseconds the request is valid for 1225 | :type recvWindow: int 1226 | 1227 | :returns: API response 1228 | 1229 | See order endpoint for full response options 1230 | 1231 | :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException 1232 | 1233 | """ 1234 | params.update({ 1235 | 'type': self.ORDER_TYPE_LIMIT, 1236 | 'timeInForce': timeInForce 1237 | }) 1238 | return self.create_order(**params) 1239 | 1240 | def order_limit_buy(self, timeInForce=TIME_IN_FORCE_GTC, **params): 1241 | """Send in a new limit buy order 1242 | 1243 | Any order with an icebergQty MUST have timeInForce set to GTC. 1244 | 1245 | :param symbol: required 1246 | :type symbol: str 1247 | :param quantity: required 1248 | :type quantity: decimal 1249 | :param price: required 1250 | :type price: str 1251 | :param timeInForce: default Good till cancelled 1252 | :type timeInForce: str 1253 | :param newClientOrderId: A unique id for the order. Automatically generated if not sent. 1254 | :type newClientOrderId: str 1255 | :param stopPrice: Used with stop orders 1256 | :type stopPrice: decimal 1257 | :param icebergQty: Used with iceberg orders 1258 | :type icebergQty: decimal 1259 | :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. 1260 | :type newOrderRespType: str 1261 | :param recvWindow: the number of milliseconds the request is valid for 1262 | :type recvWindow: int 1263 | 1264 | :returns: API response 1265 | 1266 | See order endpoint for full response options 1267 | 1268 | :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException 1269 | 1270 | """ 1271 | params.update({ 1272 | 'side': self.SIDE_BUY, 1273 | }) 1274 | return self.order_limit(timeInForce=timeInForce, **params) 1275 | 1276 | def order_limit_sell(self, timeInForce=TIME_IN_FORCE_GTC, **params): 1277 | """Send in a new limit sell order 1278 | 1279 | :param symbol: required 1280 | :type symbol: str 1281 | :param quantity: required 1282 | :type quantity: decimal 1283 | :param price: required 1284 | :type price: str 1285 | :param timeInForce: default Good till cancelled 1286 | :type timeInForce: str 1287 | :param newClientOrderId: A unique id for the order. Automatically generated if not sent. 1288 | :type newClientOrderId: str 1289 | :param stopPrice: Used with stop orders 1290 | :type stopPrice: decimal 1291 | :param icebergQty: Used with iceberg orders 1292 | :type icebergQty: decimal 1293 | :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. 1294 | :type newOrderRespType: str 1295 | :param recvWindow: the number of milliseconds the request is valid for 1296 | :type recvWindow: int 1297 | 1298 | :returns: API response 1299 | 1300 | See order endpoint for full response options 1301 | 1302 | :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException 1303 | 1304 | """ 1305 | params.update({ 1306 | 'side': self.SIDE_SELL 1307 | }) 1308 | return self.order_limit(timeInForce=timeInForce, **params) 1309 | 1310 | def order_market(self, **params): 1311 | """Send in a new market order 1312 | 1313 | :param symbol: required 1314 | :type symbol: str 1315 | :param side: required 1316 | :type side: str 1317 | :param quantity: required 1318 | :type quantity: decimal 1319 | :param quoteOrderQty: amount the user wants to spend (when buying) or receive (when selling) 1320 | of the quote asset 1321 | :type quoteOrderQty: decimal 1322 | :param newClientOrderId: A unique id for the order. Automatically generated if not sent. 1323 | :type newClientOrderId: str 1324 | :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. 1325 | :type newOrderRespType: str 1326 | :param recvWindow: the number of milliseconds the request is valid for 1327 | :type recvWindow: int 1328 | 1329 | :returns: API response 1330 | 1331 | See order endpoint for full response options 1332 | 1333 | :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException 1334 | 1335 | """ 1336 | params.update({ 1337 | 'type': self.ORDER_TYPE_MARKET 1338 | }) 1339 | return self.create_order(**params) 1340 | 1341 | def order_market_buy(self, **params): 1342 | """Send in a new market buy order 1343 | 1344 | :param symbol: required 1345 | :type symbol: str 1346 | :param quantity: required 1347 | :type quantity: decimal 1348 | :param quoteOrderQty: the amount the user wants to spend of the quote asset 1349 | :type quoteOrderQty: decimal 1350 | :param newClientOrderId: A unique id for the order. Automatically generated if not sent. 1351 | :type newClientOrderId: str 1352 | :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. 1353 | :type newOrderRespType: str 1354 | :param recvWindow: the number of milliseconds the request is valid for 1355 | :type recvWindow: int 1356 | 1357 | :returns: API response 1358 | 1359 | See order endpoint for full response options 1360 | 1361 | :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException 1362 | 1363 | """ 1364 | params.update({ 1365 | 'side': self.SIDE_BUY 1366 | }) 1367 | return self.order_market(**params) 1368 | 1369 | def order_market_sell(self, **params): 1370 | """Send in a new market sell order 1371 | 1372 | :param symbol: required 1373 | :type symbol: str 1374 | :param quantity: required 1375 | :type quantity: decimal 1376 | :param quoteOrderQty: the amount the user wants to receive of the quote asset 1377 | :type quoteOrderQty: decimal 1378 | :param newClientOrderId: A unique id for the order. Automatically generated if not sent. 1379 | :type newClientOrderId: str 1380 | :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. 1381 | :type newOrderRespType: str 1382 | :param recvWindow: the number of milliseconds the request is valid for 1383 | :type recvWindow: int 1384 | 1385 | :returns: API response 1386 | 1387 | See order endpoint for full response options 1388 | 1389 | :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException 1390 | 1391 | """ 1392 | params.update({ 1393 | 'side': self.SIDE_SELL 1394 | }) 1395 | return self.order_market(**params) 1396 | 1397 | def create_oco_order(self, **params): 1398 | """Send in a new OCO order 1399 | 1400 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#new-oco-trade 1401 | 1402 | :param symbol: required 1403 | :type symbol: str 1404 | :param listClientOrderId: A unique id for the list order. Automatically generated if not sent. 1405 | :type listClientOrderId: str 1406 | :param side: required 1407 | :type side: str 1408 | :param quantity: required 1409 | :type quantity: decimal 1410 | :param limitClientOrderId: A unique id for the limit order. Automatically generated if not sent. 1411 | :type limitClientOrderId: str 1412 | :param price: required 1413 | :type price: str 1414 | :param limitIcebergQty: Used to make the LIMIT_MAKER leg an iceberg order. 1415 | :type limitIcebergQty: decimal 1416 | :param stopClientOrderId: A unique id for the stop order. Automatically generated if not sent. 1417 | :type stopClientOrderId: str 1418 | :param stopPrice: required 1419 | :type stopPrice: str 1420 | :param stopLimitPrice: If provided, stopLimitTimeInForce is required. 1421 | :type stopLimitPrice: str 1422 | :param stopIcebergQty: Used with STOP_LOSS_LIMIT leg to make an iceberg order. 1423 | :type stopIcebergQty: decimal 1424 | :param stopLimitTimeInForce: Valid values are GTC/FOK/IOC. 1425 | :type stopLimitTimeInForce: str 1426 | :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. 1427 | :type newOrderRespType: str 1428 | :param recvWindow: the number of milliseconds the request is valid for 1429 | :type recvWindow: int 1430 | 1431 | :returns: API response 1432 | 1433 | Response ACK: 1434 | 1435 | .. code-block:: python 1436 | 1437 | { 1438 | } 1439 | 1440 | Response RESULT: 1441 | 1442 | .. code-block:: python 1443 | 1444 | { 1445 | } 1446 | 1447 | Response FULL: 1448 | 1449 | .. code-block:: python 1450 | 1451 | { 1452 | } 1453 | 1454 | :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException 1455 | 1456 | """ 1457 | return self._post('order/oco', True, data=params) 1458 | 1459 | def order_oco_buy(self, **params): 1460 | """Send in a new OCO buy order 1461 | 1462 | :param symbol: required 1463 | :type symbol: str 1464 | :param listClientOrderId: A unique id for the list order. Automatically generated if not sent. 1465 | :type listClientOrderId: str 1466 | :param quantity: required 1467 | :type quantity: decimal 1468 | :param limitClientOrderId: A unique id for the limit order. Automatically generated if not sent. 1469 | :type limitClientOrderId: str 1470 | :param price: required 1471 | :type price: str 1472 | :param limitIcebergQty: Used to make the LIMIT_MAKER leg an iceberg order. 1473 | :type limitIcebergQty: decimal 1474 | :param stopClientOrderId: A unique id for the stop order. Automatically generated if not sent. 1475 | :type stopClientOrderId: str 1476 | :param stopPrice: required 1477 | :type stopPrice: str 1478 | :param stopLimitPrice: If provided, stopLimitTimeInForce is required. 1479 | :type stopLimitPrice: str 1480 | :param stopIcebergQty: Used with STOP_LOSS_LIMIT leg to make an iceberg order. 1481 | :type stopIcebergQty: decimal 1482 | :param stopLimitTimeInForce: Valid values are GTC/FOK/IOC. 1483 | :type stopLimitTimeInForce: str 1484 | :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. 1485 | :type newOrderRespType: str 1486 | :param recvWindow: the number of milliseconds the request is valid for 1487 | :type recvWindow: int 1488 | 1489 | :returns: API response 1490 | 1491 | See OCO order endpoint for full response options 1492 | 1493 | :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException 1494 | 1495 | """ 1496 | params.update({ 1497 | 'side': self.SIDE_BUY 1498 | }) 1499 | return self.create_oco_order(**params) 1500 | 1501 | def order_oco_sell(self, **params): 1502 | """Send in a new OCO sell order 1503 | 1504 | :param symbol: required 1505 | :type symbol: str 1506 | :param listClientOrderId: A unique id for the list order. Automatically generated if not sent. 1507 | :type listClientOrderId: str 1508 | :param quantity: required 1509 | :type quantity: decimal 1510 | :param limitClientOrderId: A unique id for the limit order. Automatically generated if not sent. 1511 | :type limitClientOrderId: str 1512 | :param price: required 1513 | :type price: str 1514 | :param limitIcebergQty: Used to make the LIMIT_MAKER leg an iceberg order. 1515 | :type limitIcebergQty: decimal 1516 | :param stopClientOrderId: A unique id for the stop order. Automatically generated if not sent. 1517 | :type stopClientOrderId: str 1518 | :param stopPrice: required 1519 | :type stopPrice: str 1520 | :param stopLimitPrice: If provided, stopLimitTimeInForce is required. 1521 | :type stopLimitPrice: str 1522 | :param stopIcebergQty: Used with STOP_LOSS_LIMIT leg to make an iceberg order. 1523 | :type stopIcebergQty: decimal 1524 | :param stopLimitTimeInForce: Valid values are GTC/FOK/IOC. 1525 | :type stopLimitTimeInForce: str 1526 | :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. 1527 | :type newOrderRespType: str 1528 | :param recvWindow: the number of milliseconds the request is valid for 1529 | :type recvWindow: int 1530 | 1531 | :returns: API response 1532 | 1533 | See OCO order endpoint for full response options 1534 | 1535 | :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException 1536 | 1537 | """ 1538 | params.update({ 1539 | 'side': self.SIDE_SELL 1540 | }) 1541 | return self.create_oco_order(**params) 1542 | 1543 | def create_test_order(self, **params): 1544 | """Test new order creation and signature/recvWindow long. Creates and validates a new order but does not send it into the matching engine. 1545 | 1546 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#test-new-order-trade 1547 | 1548 | :param symbol: required 1549 | :type symbol: str 1550 | :param side: required 1551 | :type side: str 1552 | :param type: required 1553 | :type type: str 1554 | :param timeInForce: required if limit order 1555 | :type timeInForce: str 1556 | :param quantity: required 1557 | :type quantity: decimal 1558 | :param price: required 1559 | :type price: str 1560 | :param newClientOrderId: A unique id for the order. Automatically generated if not sent. 1561 | :type newClientOrderId: str 1562 | :param icebergQty: Used with iceberg orders 1563 | :type icebergQty: decimal 1564 | :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. 1565 | :type newOrderRespType: str 1566 | :param recvWindow: The number of milliseconds the request is valid for 1567 | :type recvWindow: int 1568 | 1569 | :returns: API response 1570 | 1571 | .. code-block:: python 1572 | 1573 | {} 1574 | 1575 | :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException 1576 | 1577 | 1578 | """ 1579 | return self._post('order/test', True, data=params) 1580 | 1581 | def get_order(self, **params): 1582 | """Check an order's status. Either orderId or origClientOrderId must be sent. 1583 | 1584 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#query-order-user_data 1585 | 1586 | :param symbol: required 1587 | :type symbol: str 1588 | :param orderId: The unique order id 1589 | :type orderId: int 1590 | :param origClientOrderId: optional 1591 | :type origClientOrderId: str 1592 | :param recvWindow: the number of milliseconds the request is valid for 1593 | :type recvWindow: int 1594 | 1595 | :returns: API response 1596 | 1597 | .. code-block:: python 1598 | 1599 | { 1600 | "symbol": "LTCBTC", 1601 | "orderId": 1, 1602 | "clientOrderId": "myOrder1", 1603 | "price": "0.1", 1604 | "origQty": "1.0", 1605 | "executedQty": "0.0", 1606 | "status": "NEW", 1607 | "timeInForce": "GTC", 1608 | "type": "LIMIT", 1609 | "side": "BUY", 1610 | "stopPrice": "0.0", 1611 | "icebergQty": "0.0", 1612 | "time": 1499827319559 1613 | } 1614 | 1615 | :raises: BinanceRequestException, BinanceAPIException 1616 | 1617 | """ 1618 | return self._get('order', True, data=params) 1619 | 1620 | def get_all_orders(self, **params): 1621 | """Get all account orders; active, canceled, or filled. 1622 | 1623 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#all-orders-user_data 1624 | 1625 | :param symbol: required 1626 | :type symbol: str 1627 | :param orderId: The unique order id 1628 | :type orderId: int 1629 | :param limit: Default 500; max 500. 1630 | :type limit: int 1631 | :param recvWindow: the number of milliseconds the request is valid for 1632 | :type recvWindow: int 1633 | 1634 | :returns: API response 1635 | 1636 | .. code-block:: python 1637 | 1638 | [ 1639 | { 1640 | "symbol": "LTCBTC", 1641 | "orderId": 1, 1642 | "clientOrderId": "myOrder1", 1643 | "price": "0.1", 1644 | "origQty": "1.0", 1645 | "executedQty": "0.0", 1646 | "status": "NEW", 1647 | "timeInForce": "GTC", 1648 | "type": "LIMIT", 1649 | "side": "BUY", 1650 | "stopPrice": "0.0", 1651 | "icebergQty": "0.0", 1652 | "time": 1499827319559 1653 | } 1654 | ] 1655 | 1656 | :raises: BinanceRequestException, BinanceAPIException 1657 | 1658 | """ 1659 | return self._get('allOrders', True, data=params) 1660 | 1661 | def cancel_order(self, **params): 1662 | """Cancel an active order. Either orderId or origClientOrderId must be sent. 1663 | 1664 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#cancel-order-trade 1665 | 1666 | :param symbol: required 1667 | :type symbol: str 1668 | :param orderId: The unique order id 1669 | :type orderId: int 1670 | :param origClientOrderId: optional 1671 | :type origClientOrderId: str 1672 | :param newClientOrderId: Used to uniquely identify this cancel. Automatically generated by default. 1673 | :type newClientOrderId: str 1674 | :param recvWindow: the number of milliseconds the request is valid for 1675 | :type recvWindow: int 1676 | 1677 | :returns: API response 1678 | 1679 | .. code-block:: python 1680 | 1681 | { 1682 | "symbol": "LTCBTC", 1683 | "origClientOrderId": "myOrder1", 1684 | "orderId": 1, 1685 | "clientOrderId": "cancelMyOrder1" 1686 | } 1687 | 1688 | :raises: BinanceRequestException, BinanceAPIException 1689 | 1690 | """ 1691 | return self._delete('order', True, data=params) 1692 | 1693 | def get_open_orders(self, **params): 1694 | """Get all open orders on a symbol. 1695 | 1696 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#current-open-orders-user_data 1697 | 1698 | :param symbol: optional 1699 | :type symbol: str 1700 | :param recvWindow: the number of milliseconds the request is valid for 1701 | :type recvWindow: int 1702 | 1703 | :returns: API response 1704 | 1705 | .. code-block:: python 1706 | 1707 | [ 1708 | { 1709 | "symbol": "LTCBTC", 1710 | "orderId": 1, 1711 | "clientOrderId": "myOrder1", 1712 | "price": "0.1", 1713 | "origQty": "1.0", 1714 | "executedQty": "0.0", 1715 | "status": "NEW", 1716 | "timeInForce": "GTC", 1717 | "type": "LIMIT", 1718 | "side": "BUY", 1719 | "stopPrice": "0.0", 1720 | "icebergQty": "0.0", 1721 | "time": 1499827319559 1722 | } 1723 | ] 1724 | 1725 | :raises: BinanceRequestException, BinanceAPIException 1726 | 1727 | """ 1728 | return self._get('openOrders', True, data=params) 1729 | 1730 | # User Stream Endpoints 1731 | def get_account(self, **params): 1732 | """Get current account information. 1733 | 1734 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#account-information-user_data 1735 | 1736 | :param recvWindow: the number of milliseconds the request is valid for 1737 | :type recvWindow: int 1738 | 1739 | :returns: API response 1740 | 1741 | .. code-block:: python 1742 | 1743 | { 1744 | "makerCommission": 15, 1745 | "takerCommission": 15, 1746 | "buyerCommission": 0, 1747 | "sellerCommission": 0, 1748 | "canTrade": true, 1749 | "canWithdraw": true, 1750 | "canDeposit": true, 1751 | "balances": [ 1752 | { 1753 | "asset": "BTC", 1754 | "free": "4723846.89208129", 1755 | "locked": "0.00000000" 1756 | }, 1757 | { 1758 | "asset": "LTC", 1759 | "free": "4763368.68006011", 1760 | "locked": "0.00000000" 1761 | } 1762 | ] 1763 | } 1764 | 1765 | :raises: BinanceRequestException, BinanceAPIException 1766 | 1767 | """ 1768 | return self._get('account', True, data=params) 1769 | 1770 | def get_asset_balance(self, asset, **params): 1771 | """Get current asset balance. 1772 | 1773 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#account-information-user_data 1774 | 1775 | :param asset: required 1776 | :type asset: str 1777 | :param recvWindow: the number of milliseconds the request is valid for 1778 | :type recvWindow: int 1779 | 1780 | :returns: dictionary or None if not found 1781 | 1782 | .. code-block:: python 1783 | 1784 | { 1785 | "asset": "BTC", 1786 | "free": "4723846.89208129", 1787 | "locked": "0.00000000" 1788 | } 1789 | 1790 | :raises: BinanceRequestException, BinanceAPIException 1791 | 1792 | """ 1793 | res = self.get_account(**params) 1794 | # find asset balance in list of balances 1795 | if "balances" in res: 1796 | for bal in res['balances']: 1797 | if bal['asset'].lower() == asset.lower(): 1798 | return bal 1799 | return None 1800 | 1801 | def get_my_trades(self, **params): 1802 | """Get trades for a specific symbol. 1803 | 1804 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#account-trade-list-user_data 1805 | 1806 | :param symbol: required 1807 | :type symbol: str 1808 | :param limit: Default 500; max 500. 1809 | :type limit: int 1810 | :param fromId: TradeId to fetch from. Default gets most recent trades. 1811 | :type fromId: int 1812 | :param recvWindow: the number of milliseconds the request is valid for 1813 | :type recvWindow: int 1814 | 1815 | :returns: API response 1816 | 1817 | .. code-block:: python 1818 | 1819 | [ 1820 | { 1821 | "id": 28457, 1822 | "price": "4.00000100", 1823 | "qty": "12.00000000", 1824 | "commission": "10.10000000", 1825 | "commissionAsset": "BNB", 1826 | "time": 1499865549590, 1827 | "isBuyer": true, 1828 | "isMaker": false, 1829 | "isBestMatch": true 1830 | } 1831 | ] 1832 | 1833 | :raises: BinanceRequestException, BinanceAPIException 1834 | 1835 | """ 1836 | return self._get('myTrades', True, data=params) 1837 | 1838 | def get_system_status(self): 1839 | """Get system status detail. 1840 | 1841 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#system-status-system 1842 | 1843 | :returns: API response 1844 | 1845 | .. code-block:: python 1846 | 1847 | { 1848 | "status": 0, # 0: normal,1:system maintenance 1849 | "msg": "normal" # normal or System maintenance. 1850 | } 1851 | 1852 | :raises: BinanceAPIException 1853 | 1854 | """ 1855 | return self._request_withdraw_api('get', 'systemStatus.html') 1856 | 1857 | def get_account_status(self, **params): 1858 | """Get account status detail. 1859 | 1860 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#account-status-user_data 1861 | 1862 | :param recvWindow: the number of milliseconds the request is valid for 1863 | :type recvWindow: int 1864 | 1865 | :returns: API response 1866 | 1867 | .. code-block:: python 1868 | 1869 | { 1870 | "msg": "Order failed:Low Order fill rate! Will be reactivated after 5 minutes.", 1871 | "success": true, 1872 | "objs": [ 1873 | "5" 1874 | ] 1875 | } 1876 | 1877 | :raises: BinanceWithdrawException 1878 | 1879 | """ 1880 | res = self._request_withdraw_api('get', 'accountStatus.html', True, data=params) 1881 | if not res['success']: 1882 | raise BinanceWithdrawException(res['msg']) 1883 | return res 1884 | 1885 | def get_dust_log(self, **params): 1886 | """Get log of small amounts exchanged for BNB. 1887 | 1888 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#dustlog-user_data 1889 | 1890 | :param recvWindow: the number of milliseconds the request is valid for 1891 | :type recvWindow: int 1892 | 1893 | :returns: API response 1894 | 1895 | .. code-block:: python 1896 | 1897 | { 1898 | "success": true, 1899 | "results": { 1900 | "total": 2, //Total counts of exchange 1901 | "rows": [ 1902 | { 1903 | "transfered_total": "0.00132256", # Total transfered BNB amount for this exchange. 1904 | "service_charge_total": "0.00002699", # Total service charge amount for this exchange. 1905 | "tran_id": 4359321, 1906 | "logs": [ # Details of this exchange. 1907 | { 1908 | "tranId": 4359321, 1909 | "serviceChargeAmount": "0.000009", 1910 | "uid": "10000015", 1911 | "amount": "0.0009", 1912 | "operateTime": "2018-05-03 17:07:04", 1913 | "transferedAmount": "0.000441", 1914 | "fromAsset": "USDT" 1915 | }, 1916 | { 1917 | "tranId": 4359321, 1918 | "serviceChargeAmount": "0.00001799", 1919 | "uid": "10000015", 1920 | "amount": "0.0009", 1921 | "operateTime": "2018-05-03 17:07:04", 1922 | "transferedAmount": "0.00088156", 1923 | "fromAsset": "ETH" 1924 | } 1925 | ], 1926 | "operate_time": "2018-05-03 17:07:04" //The time of this exchange. 1927 | }, 1928 | { 1929 | "transfered_total": "0.00058795", 1930 | "service_charge_total": "0.000012", 1931 | "tran_id": 4357015, 1932 | "logs": [ // Details of this exchange. 1933 | { 1934 | "tranId": 4357015, 1935 | "serviceChargeAmount": "0.00001", 1936 | "uid": "10000015", 1937 | "amount": "0.001", 1938 | "operateTime": "2018-05-02 13:52:24", 1939 | "transferedAmount": "0.00049", 1940 | "fromAsset": "USDT" 1941 | }, 1942 | { 1943 | "tranId": 4357015, 1944 | "serviceChargeAmount": "0.000002", 1945 | "uid": "10000015", 1946 | "amount": "0.0001", 1947 | "operateTime": "2018-05-02 13:51:11", 1948 | "transferedAmount": "0.00009795", 1949 | "fromAsset": "ETH" 1950 | } 1951 | ], 1952 | "operate_time": "2018-05-02 13:51:11" 1953 | } 1954 | ] 1955 | } 1956 | } 1957 | 1958 | :raises: BinanceWithdrawException 1959 | 1960 | """ 1961 | res = self._request_withdraw_api('get', 'userAssetDribbletLog.html', True, data=params) 1962 | if not res['success']: 1963 | raise BinanceWithdrawException(res['msg']) 1964 | return res 1965 | 1966 | def transfer_dust(self, **params): 1967 | """Convert dust assets to BNB. 1968 | 1969 | https://github.com/binance-exchange/binance-official-api-docs/blob/9dbe0e961b80557bb19708a707c7fad08842b28e/wapi-api.md#dust-transfer-user_data 1970 | 1971 | :param asset: The asset being converted. e.g: 'ONE' 1972 | :type asset: str 1973 | :param recvWindow: the number of milliseconds the request is valid for 1974 | :type recvWindow: int 1975 | 1976 | .. code:: python 1977 | 1978 | result = client.transfer_dust(asset='ONE') 1979 | 1980 | :returns: API response 1981 | 1982 | .. code-block:: python 1983 | 1984 | { 1985 | "totalServiceCharge":"0.02102542", 1986 | "totalTransfered":"1.05127099", 1987 | "transferResult":[ 1988 | { 1989 | "amount":"0.03000000", 1990 | "fromAsset":"ETH", 1991 | "operateTime":1563368549307, 1992 | "serviceChargeAmount":"0.00500000", 1993 | "tranId":2970932918, 1994 | "transferedAmount":"0.25000000" 1995 | } 1996 | ] 1997 | } 1998 | 1999 | :raises: BinanceRequestException, BinanceAPIException 2000 | 2001 | """ 2002 | return self._request_margin_api('post', 'asset/dust', True, data=params) 2003 | 2004 | def get_asset_dividend_history(self, **params): 2005 | """Query asset dividend record. 2006 | 2007 | https://github.com/binance-exchange/binance-official-api-docs/blob/9dbe0e961b80557bb19708a707c7fad08842b28e/wapi-api.md#asset-dividend-record-user_data 2008 | 2009 | :param asset: optional 2010 | :type asset: str 2011 | :param startTime: optional 2012 | :type startTime: long 2013 | :param endTime: optional 2014 | :type endTime: long 2015 | :param recvWindow: the number of milliseconds the request is valid for 2016 | :type recvWindow: int 2017 | 2018 | .. code:: python 2019 | 2020 | result = client.get_asset_dividend_history() 2021 | 2022 | :returns: API response 2023 | 2024 | .. code-block:: python 2025 | 2026 | { 2027 | "rows":[ 2028 | { 2029 | "amount":"10.00000000", 2030 | "asset":"BHFT", 2031 | "divTime":1563189166000, 2032 | "enInfo":"BHFT distribution", 2033 | "tranId":2968885920 2034 | }, 2035 | { 2036 | "amount":"10.00000000", 2037 | "asset":"BHFT", 2038 | "divTime":1563189165000, 2039 | "enInfo":"BHFT distribution", 2040 | "tranId":2968885920 2041 | } 2042 | ], 2043 | "total":2 2044 | } 2045 | 2046 | :raises: BinanceRequestException, BinanceAPIException 2047 | 2048 | """ 2049 | return self._request_margin_api('post', 'asset/assetDividend', True, data=params) 2050 | 2051 | def get_trade_fee(self, **params): 2052 | """Get trade fee. 2053 | 2054 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#trade-fee-user_data 2055 | 2056 | :param symbol: optional 2057 | :type symbol: str 2058 | :param recvWindow: the number of milliseconds the request is valid for 2059 | :type recvWindow: int 2060 | 2061 | :returns: API response 2062 | 2063 | .. code-block:: python 2064 | 2065 | { 2066 | "tradeFee": [ 2067 | { 2068 | "symbol": "ADABNB", 2069 | "maker": 0.9000, 2070 | "taker": 1.0000 2071 | }, { 2072 | "symbol": "BNBBTC", 2073 | "maker": 0.3000, 2074 | "taker": 0.3000 2075 | } 2076 | ], 2077 | "success": true 2078 | } 2079 | 2080 | :raises: BinanceWithdrawException 2081 | 2082 | """ 2083 | res = self._request_withdraw_api('get', 'tradeFee.html', True, data=params) 2084 | if not res['success']: 2085 | raise BinanceWithdrawException(res['msg']) 2086 | return res 2087 | 2088 | def get_asset_details(self, **params): 2089 | """Fetch details on assets. 2090 | 2091 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#asset-detail-user_data 2092 | 2093 | :param recvWindow: the number of milliseconds the request is valid for 2094 | :type recvWindow: int 2095 | 2096 | :returns: API response 2097 | 2098 | .. code-block:: python 2099 | 2100 | { 2101 | "success": true, 2102 | "assetDetail": { 2103 | "CTR": { 2104 | "minWithdrawAmount": "70.00000000", //min withdraw amount 2105 | "depositStatus": false,//deposit status 2106 | "withdrawFee": 35, // withdraw fee 2107 | "withdrawStatus": true, //withdraw status 2108 | "depositTip": "Delisted, Deposit Suspended" //reason 2109 | }, 2110 | "SKY": { 2111 | "minWithdrawAmount": "0.02000000", 2112 | "depositStatus": true, 2113 | "withdrawFee": 0.01, 2114 | "withdrawStatus": true 2115 | } 2116 | } 2117 | } 2118 | 2119 | :raises: BinanceWithdrawException 2120 | 2121 | """ 2122 | res = self._request_withdraw_api('get', 'assetDetail.html', True, data=params) 2123 | if not res['success']: 2124 | raise BinanceWithdrawException(res['msg']) 2125 | return res 2126 | 2127 | # Withdraw Endpoints 2128 | 2129 | def withdraw(self, **params): 2130 | """Submit a withdraw request. 2131 | 2132 | https://www.binance.com/restapipub.html 2133 | 2134 | Assumptions: 2135 | 2136 | - You must have Withdraw permissions enabled on your API key 2137 | - You must have withdrawn to the address specified through the website and approved the transaction via email 2138 | 2139 | :param asset: required 2140 | :type asset: str 2141 | :type address: required 2142 | :type address: str 2143 | :type addressTag: optional - Secondary address identifier for coins like XRP,XMR etc. 2144 | :type address: str 2145 | :param amount: required 2146 | :type amount: decimal 2147 | :param name: optional - Description of the address, default asset value passed will be used 2148 | :type name: str 2149 | :param recvWindow: the number of milliseconds the request is valid for 2150 | :type recvWindow: int 2151 | 2152 | :returns: API response 2153 | 2154 | .. code-block:: python 2155 | 2156 | { 2157 | "msg": "success", 2158 | "success": true, 2159 | "id":"7213fea8e94b4a5593d507237e5a555b" 2160 | } 2161 | 2162 | :raises: BinanceRequestException, BinanceAPIException, BinanceWithdrawException 2163 | 2164 | """ 2165 | # force a name for the withdrawal if one not set 2166 | if 'asset' in params and 'name' not in params: 2167 | params['name'] = params['asset'] 2168 | res = self._request_withdraw_api('post', 'withdraw.html', True, data=params) 2169 | if not res['success']: 2170 | raise BinanceWithdrawException(res['msg']) 2171 | return res 2172 | 2173 | def get_deposit_history(self, **params): 2174 | """Fetch deposit history. 2175 | 2176 | https://www.binance.com/restapipub.html 2177 | 2178 | :param asset: optional 2179 | :type asset: str 2180 | :type status: 0(0:pending,1:success) optional 2181 | :type status: int 2182 | :param startTime: optional 2183 | :type startTime: long 2184 | :param endTime: optional 2185 | :type endTime: long 2186 | :param recvWindow: the number of milliseconds the request is valid for 2187 | :type recvWindow: int 2188 | 2189 | :returns: API response 2190 | 2191 | .. code-block:: python 2192 | 2193 | { 2194 | "depositList": [ 2195 | { 2196 | "insertTime": 1508198532000, 2197 | "amount": 0.04670582, 2198 | "asset": "ETH", 2199 | "status": 1 2200 | } 2201 | ], 2202 | "success": true 2203 | } 2204 | 2205 | :raises: BinanceRequestException, BinanceAPIException 2206 | 2207 | """ 2208 | return self._request_withdraw_api('get', 'depositHistory.html', True, data=params) 2209 | 2210 | def get_withdraw_history(self, **params): 2211 | """Fetch withdraw history. 2212 | 2213 | https://www.binance.com/restapipub.html 2214 | 2215 | :param asset: optional 2216 | :type asset: str 2217 | :type status: 0(0:Email Sent,1:Cancelled 2:Awaiting Approval 3:Rejected 4:Processing 5:Failure 6Completed) optional 2218 | :type status: int 2219 | :param startTime: optional 2220 | :type startTime: long 2221 | :param endTime: optional 2222 | :type endTime: long 2223 | :param recvWindow: the number of milliseconds the request is valid for 2224 | :type recvWindow: int 2225 | 2226 | :returns: API response 2227 | 2228 | .. code-block:: python 2229 | 2230 | { 2231 | "withdrawList": [ 2232 | { 2233 | "amount": 1, 2234 | "address": "0x6915f16f8791d0a1cc2bf47c13a6b2a92000504b", 2235 | "asset": "ETH", 2236 | "applyTime": 1508198532000 2237 | "status": 4 2238 | }, 2239 | { 2240 | "amount": 0.005, 2241 | "address": "0x6915f16f8791d0a1cc2bf47c13a6b2a92000504b", 2242 | "txId": "0x80aaabed54bdab3f6de5868f89929a2371ad21d666f20f7393d1a3389fad95a1", 2243 | "asset": "ETH", 2244 | "applyTime": 1508198532000, 2245 | "status": 4 2246 | } 2247 | ], 2248 | "success": true 2249 | } 2250 | 2251 | :raises: BinanceRequestException, BinanceAPIException 2252 | 2253 | """ 2254 | return self._request_withdraw_api('get', 'withdrawHistory.html', True, data=params) 2255 | 2256 | def get_deposit_address(self, **params): 2257 | """Fetch a deposit address for a symbol 2258 | 2259 | https://www.binance.com/restapipub.html 2260 | 2261 | :param asset: required 2262 | :type asset: str 2263 | :param recvWindow: the number of milliseconds the request is valid for 2264 | :type recvWindow: int 2265 | 2266 | :returns: API response 2267 | 2268 | .. code-block:: python 2269 | 2270 | { 2271 | "address": "0x6915f16f8791d0a1cc2bf47c13a6b2a92000504b", 2272 | "success": true, 2273 | "addressTag": "1231212", 2274 | "asset": "BNB" 2275 | } 2276 | 2277 | :raises: BinanceRequestException, BinanceAPIException 2278 | 2279 | """ 2280 | return self._request_withdraw_api('get', 'depositAddress.html', True, data=params) 2281 | 2282 | # User Stream Endpoints 2283 | 2284 | def stream_get_listen_key(self): 2285 | """Start a new user data stream and return the listen key 2286 | If a stream already exists it should return the same key. 2287 | If the stream becomes invalid a new key is returned. 2288 | 2289 | Can be used to keep the user stream alive. 2290 | 2291 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#start-user-data-stream-user_stream 2292 | 2293 | :returns: API response 2294 | 2295 | .. code-block:: python 2296 | 2297 | { 2298 | "listenKey": "pqia91ma19a5s61cv6a81va65sdf19v8a65a1a5s61cv6a81va65sdf19v8a65a1" 2299 | } 2300 | 2301 | :raises: BinanceRequestException, BinanceAPIException 2302 | 2303 | """ 2304 | res = self._post('userDataStream', False, data={}) 2305 | return res['listenKey'] 2306 | 2307 | def stream_keepalive(self, listenKey): 2308 | """PING a user data stream to prevent a time out. 2309 | 2310 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#keepalive-user-data-stream-user_stream 2311 | 2312 | :param listenKey: required 2313 | :type listenKey: str 2314 | 2315 | :returns: API response 2316 | 2317 | .. code-block:: python 2318 | 2319 | {} 2320 | 2321 | :raises: BinanceRequestException, BinanceAPIException 2322 | 2323 | """ 2324 | params = { 2325 | 'listenKey': listenKey 2326 | } 2327 | return self._put('userDataStream', False, data=params) 2328 | 2329 | def stream_close(self, listenKey): 2330 | """Close out a user data stream. 2331 | 2332 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#close-user-data-stream-user_stream 2333 | 2334 | :param listenKey: required 2335 | :type listenKey: str 2336 | 2337 | :returns: API response 2338 | 2339 | .. code-block:: python 2340 | 2341 | {} 2342 | 2343 | :raises: BinanceRequestException, BinanceAPIException 2344 | 2345 | """ 2346 | params = { 2347 | 'listenKey': listenKey 2348 | } 2349 | return self._delete('userDataStream', False, data=params) 2350 | 2351 | # Margin Trading Endpoints 2352 | 2353 | def get_margin_account(self, **params): 2354 | """Query margin account details 2355 | 2356 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#query-margin-account-details-user_data 2357 | 2358 | :returns: API response 2359 | 2360 | .. code-block:: python 2361 | 2362 | { 2363 | "borrowEnabled": true, 2364 | "marginLevel": "11.64405625", 2365 | "totalAssetOfBtc": "6.82728457", 2366 | "totalLiabilityOfBtc": "0.58633215", 2367 | "totalNetAssetOfBtc": "6.24095242", 2368 | "tradeEnabled": true, 2369 | "transferEnabled": true, 2370 | "userAssets": [ 2371 | { 2372 | "asset": "BTC", 2373 | "borrowed": "0.00000000", 2374 | "free": "0.00499500", 2375 | "interest": "0.00000000", 2376 | "locked": "0.00000000", 2377 | "netAsset": "0.00499500" 2378 | }, 2379 | { 2380 | "asset": "BNB", 2381 | "borrowed": "201.66666672", 2382 | "free": "2346.50000000", 2383 | "interest": "0.00000000", 2384 | "locked": "0.00000000", 2385 | "netAsset": "2144.83333328" 2386 | }, 2387 | { 2388 | "asset": "ETH", 2389 | "borrowed": "0.00000000", 2390 | "free": "0.00000000", 2391 | "interest": "0.00000000", 2392 | "locked": "0.00000000", 2393 | "netAsset": "0.00000000" 2394 | }, 2395 | { 2396 | "asset": "USDT", 2397 | "borrowed": "0.00000000", 2398 | "free": "0.00000000", 2399 | "interest": "0.00000000", 2400 | "locked": "0.00000000", 2401 | "netAsset": "0.00000000" 2402 | } 2403 | ] 2404 | } 2405 | 2406 | :raises: BinanceRequestException, BinanceAPIException 2407 | 2408 | """ 2409 | return self._request_margin_api('get', 'margin/account', True, data=params) 2410 | 2411 | def get_margin_asset(self, **params): 2412 | """Query margin asset 2413 | 2414 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#query-margin-asset-market_data 2415 | 2416 | :param asset: name of the asset 2417 | :type asset: str 2418 | 2419 | .. code:: python 2420 | 2421 | asset_details = client.get_margin_asset(asset='BNB') 2422 | 2423 | :returns: API response 2424 | 2425 | .. code-block:: python 2426 | 2427 | { 2428 | "assetFullName": "Binance Coin", 2429 | "assetName": "BNB", 2430 | "isBorrowable": false, 2431 | "isMortgageable": true, 2432 | "userMinBorrow": "0.00000000", 2433 | "userMinRepay": "0.00000000" 2434 | } 2435 | 2436 | :raises: BinanceRequestException, BinanceAPIException 2437 | 2438 | """ 2439 | return self._request_margin_api('get', 'margin/asset', data=params) 2440 | 2441 | def get_margin_symbol(self, **params): 2442 | """Query margin symbol info 2443 | 2444 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#query-margin-pair-market_data 2445 | 2446 | :param symbol: name of the symbol pair 2447 | :type symbol: str 2448 | 2449 | .. code:: python 2450 | 2451 | pair_details = client.get_margin_symbol(symbol='BTCUSDT') 2452 | 2453 | :returns: API response 2454 | 2455 | .. code-block:: python 2456 | 2457 | { 2458 | "id":323355778339572400, 2459 | "symbol":"BTCUSDT", 2460 | "base":"BTC", 2461 | "quote":"USDT", 2462 | "isMarginTrade":true, 2463 | "isBuyAllowed":true, 2464 | "isSellAllowed":true 2465 | } 2466 | 2467 | 2468 | :raises: BinanceRequestException, BinanceAPIException 2469 | 2470 | """ 2471 | return self._request_margin_api('get', 'margin/pair', data=params) 2472 | 2473 | def get_margin_price_index(self, **params): 2474 | """Query margin priceIndex 2475 | 2476 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#query-margin-priceindex-market_data 2477 | 2478 | :param symbol: name of the symbol pair 2479 | :type symbol: str 2480 | 2481 | .. code:: python 2482 | 2483 | price_index_details = client.get_margin_pair(symbol='BTCUSDT') 2484 | 2485 | :returns: API response 2486 | 2487 | .. code-block:: python 2488 | 2489 | { 2490 | "calcTime": 1562046418000, 2491 | "price": "0.00333930", 2492 | "symbol": "BNBBTC" 2493 | } 2494 | 2495 | :raises: BinanceRequestException, BinanceAPIException 2496 | 2497 | """ 2498 | return self._request_margin_api('get', 'margin/priceIndex', data=params) 2499 | 2500 | def transfer_margin_to_spot(self, **params): 2501 | """Execute transfer between margin account and spot account. 2502 | 2503 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#margin-account-transfer-margin 2504 | 2505 | :param asset: name of the asset 2506 | :type asset: str 2507 | :param amount: amount to transfer 2508 | :type amount: str 2509 | :param recvWindow: the number of milliseconds the request is valid for 2510 | :type recvWindow: int 2511 | 2512 | .. code:: python 2513 | 2514 | transfer = client.transfer_margin_to_spot(asset='BTC', amount='1.1') 2515 | 2516 | :returns: API response 2517 | 2518 | .. code-block:: python 2519 | 2520 | { 2521 | "tranId": 100000001 2522 | } 2523 | 2524 | :raises: BinanceRequestException, BinanceAPIException 2525 | 2526 | """ 2527 | params['type'] = 2 2528 | return self._request_margin_api('post', 'margin/transfer', signed=True, data=params) 2529 | 2530 | def transfer_spot_to_margin(self, **params): 2531 | """Execute transfer between spot account and margin account. 2532 | 2533 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#margin-account-transfer-margin 2534 | 2535 | :param asset: name of the asset 2536 | :type asset: str 2537 | :param amount: amount to transfer 2538 | :type amount: str 2539 | :param recvWindow: the number of milliseconds the request is valid for 2540 | :type recvWindow: int 2541 | 2542 | .. code:: python 2543 | 2544 | transfer = client.transfer_spot_to_margin(asset='BTC', amount='1.1') 2545 | 2546 | :returns: API response 2547 | 2548 | .. code-block:: python 2549 | 2550 | { 2551 | "tranId": 100000001 2552 | } 2553 | 2554 | :raises: BinanceRequestException, BinanceAPIException 2555 | 2556 | """ 2557 | params['type'] = 1 2558 | return self._request_margin_api('post', 'margin/transfer', signed=True, data=params) 2559 | 2560 | def create_margin_loan(self, **params): 2561 | """Apply for a loan. 2562 | 2563 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#margin-account-borrow-margin 2564 | 2565 | :param asset: name of the asset 2566 | :type asset: str 2567 | :param amount: amount to transfer 2568 | :type amount: str 2569 | :param recvWindow: the number of milliseconds the request is valid for 2570 | :type recvWindow: int 2571 | 2572 | .. code:: python 2573 | 2574 | transaction = client.margin_create_loan(asset='BTC', amount='1.1') 2575 | 2576 | :returns: API response 2577 | 2578 | .. code-block:: python 2579 | 2580 | { 2581 | "tranId": 100000001 2582 | } 2583 | 2584 | :raises: BinanceRequestException, BinanceAPIException 2585 | 2586 | """ 2587 | return self._request_margin_api('post', 'margin/loan', signed=True, data=params) 2588 | 2589 | def repay_margin_loan(self, **params): 2590 | """Repay loan for margin account. 2591 | 2592 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#margin-account-repay-margin 2593 | 2594 | :param asset: name of the asset 2595 | :type asset: str 2596 | :param amount: amount to transfer 2597 | :type amount: str 2598 | :param recvWindow: the number of milliseconds the request is valid for 2599 | :type recvWindow: int 2600 | 2601 | .. code:: python 2602 | 2603 | transaction = client.margin_repay_loan(asset='BTC', amount='1.1') 2604 | 2605 | :returns: API response 2606 | 2607 | .. code-block:: python 2608 | 2609 | { 2610 | "tranId": 100000001 2611 | } 2612 | 2613 | :raises: BinanceRequestException, BinanceAPIException 2614 | 2615 | """ 2616 | return self._request_margin_api('post', 'margin/repay', signed=True, data=params) 2617 | 2618 | def create_margin_order(self, **params): 2619 | """Post a new order for margin account. 2620 | 2621 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#margin-account-new-order-trade 2622 | 2623 | :param symbol: required 2624 | :type symbol: str 2625 | :param side: required 2626 | :type side: str 2627 | :param type: required 2628 | :type type: str 2629 | :param quantity: required 2630 | :type quantity: decimal 2631 | :param price: required 2632 | :type price: str 2633 | :param stopPrice: Used with STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, and TAKE_PROFIT_LIMIT orders. 2634 | :type stopPrice: str 2635 | :param timeInForce: required if limit order GTC,IOC,FOK 2636 | :type timeInForce: str 2637 | :param newClientOrderId: A unique id for the order. Automatically generated if not sent. 2638 | :type newClientOrderId: str 2639 | :param icebergQty: Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order. 2640 | :type icebergQty: str 2641 | :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; MARKET and LIMIT order types default to 2642 | FULL, all other orders default to ACK. 2643 | :type newOrderRespType: str 2644 | :param recvWindow: the number of milliseconds the request is valid for 2645 | :type recvWindow: int 2646 | 2647 | :returns: API response 2648 | 2649 | Response ACK: 2650 | 2651 | .. code-block:: python 2652 | 2653 | { 2654 | "symbol": "BTCUSDT", 2655 | "orderId": 28, 2656 | "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP", 2657 | "transactTime": 1507725176595 2658 | } 2659 | 2660 | Response RESULT: 2661 | 2662 | .. code-block:: python 2663 | 2664 | { 2665 | "symbol": "BTCUSDT", 2666 | "orderId": 28, 2667 | "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP", 2668 | "transactTime": 1507725176595, 2669 | "price": "1.00000000", 2670 | "origQty": "10.00000000", 2671 | "executedQty": "10.00000000", 2672 | "cummulativeQuoteQty": "10.00000000", 2673 | "status": "FILLED", 2674 | "timeInForce": "GTC", 2675 | "type": "MARKET", 2676 | "side": "SELL" 2677 | } 2678 | 2679 | Response FULL: 2680 | 2681 | .. code-block:: python 2682 | 2683 | { 2684 | "symbol": "BTCUSDT", 2685 | "orderId": 28, 2686 | "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP", 2687 | "transactTime": 1507725176595, 2688 | "price": "1.00000000", 2689 | "origQty": "10.00000000", 2690 | "executedQty": "10.00000000", 2691 | "cummulativeQuoteQty": "10.00000000", 2692 | "status": "FILLED", 2693 | "timeInForce": "GTC", 2694 | "type": "MARKET", 2695 | "side": "SELL", 2696 | "fills": [ 2697 | { 2698 | "price": "4000.00000000", 2699 | "qty": "1.00000000", 2700 | "commission": "4.00000000", 2701 | "commissionAsset": "USDT" 2702 | }, 2703 | { 2704 | "price": "3999.00000000", 2705 | "qty": "5.00000000", 2706 | "commission": "19.99500000", 2707 | "commissionAsset": "USDT" 2708 | }, 2709 | { 2710 | "price": "3998.00000000", 2711 | "qty": "2.00000000", 2712 | "commission": "7.99600000", 2713 | "commissionAsset": "USDT" 2714 | }, 2715 | { 2716 | "price": "3997.00000000", 2717 | "qty": "1.00000000", 2718 | "commission": "3.99700000", 2719 | "commissionAsset": "USDT" 2720 | }, 2721 | { 2722 | "price": "3995.00000000", 2723 | "qty": "1.00000000", 2724 | "commission": "3.99500000", 2725 | "commissionAsset": "USDT" 2726 | } 2727 | ] 2728 | } 2729 | 2730 | :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, 2731 | BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, 2732 | BinanceOrderInactiveSymbolException 2733 | 2734 | """ 2735 | return self._request_margin_api('post', 'margin/order', signed=True, data=params) 2736 | 2737 | def cancel_margin_order(self, **params): 2738 | """Cancel an active order for margin account. 2739 | 2740 | Either orderId or origClientOrderId must be sent. 2741 | 2742 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#margin-account-cancel-order-trade 2743 | 2744 | :param symbol: required 2745 | :type symbol: str 2746 | :param orderId: 2747 | :type orderId: str 2748 | :param origClientOrderId: 2749 | :type origClientOrderId: str 2750 | :param newClientOrderId: Used to uniquely identify this cancel. Automatically generated by default. 2751 | :type newClientOrderId: str 2752 | :param recvWindow: the number of milliseconds the request is valid for 2753 | :type recvWindow: int 2754 | 2755 | :returns: API response 2756 | 2757 | { 2758 | "symbol": "LTCBTC", 2759 | "orderId": 28, 2760 | "origClientOrderId": "myOrder1", 2761 | "clientOrderId": "cancelMyOrder1", 2762 | "transactTime": 1507725176595, 2763 | "price": "1.00000000", 2764 | "origQty": "10.00000000", 2765 | "executedQty": "8.00000000", 2766 | "cummulativeQuoteQty": "8.00000000", 2767 | "status": "CANCELED", 2768 | "timeInForce": "GTC", 2769 | "type": "LIMIT", 2770 | "side": "SELL" 2771 | } 2772 | 2773 | :raises: BinanceRequestException, BinanceAPIException 2774 | 2775 | """ 2776 | return self._request_margin_api('delete', 'margin/order', signed=True, data=params) 2777 | 2778 | def get_margin_loan_details(self, **params): 2779 | """Query loan record 2780 | 2781 | txId or startTime must be sent. txId takes precedence. 2782 | 2783 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#margin-account-cancel-order-trade 2784 | 2785 | :param asset: required 2786 | :type asset: str 2787 | :param txId: the tranId in of the created loan 2788 | :type txId: str 2789 | :param startTime: 2790 | :type startTime: str 2791 | :param endTime: Used to uniquely identify this cancel. Automatically generated by default. 2792 | :type endTime: str 2793 | :param current: Currently querying page. Start from 1. Default:1 2794 | :type current: str 2795 | :param size: Default:10 Max:100 2796 | :type size: int 2797 | :param recvWindow: the number of milliseconds the request is valid for 2798 | :type recvWindow: int 2799 | 2800 | :returns: API response 2801 | 2802 | { 2803 | "rows": [ 2804 | { 2805 | "asset": "BNB", 2806 | "principal": "0.84624403", 2807 | "timestamp": 1555056425000, 2808 | //one of PENDING (pending to execution), CONFIRMED (successfully loaned), FAILED (execution failed, nothing happened to your account); 2809 | "status": "CONFIRMED" 2810 | } 2811 | ], 2812 | "total": 1 2813 | } 2814 | 2815 | :raises: BinanceRequestException, BinanceAPIException 2816 | 2817 | """ 2818 | return self._request_margin_api('get', 'margin/loan', signed=True, data=params) 2819 | 2820 | def get_margin_repay_details(self, **params): 2821 | """Query repay record 2822 | 2823 | txId or startTime must be sent. txId takes precedence. 2824 | 2825 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#margin-account-cancel-order-trade 2826 | 2827 | :param asset: required 2828 | :type asset: str 2829 | :param txId: the tranId in of the created loan 2830 | :type txId: str 2831 | :param startTime: 2832 | :type startTime: str 2833 | :param endTime: Used to uniquely identify this cancel. Automatically generated by default. 2834 | :type endTime: str 2835 | :param current: Currently querying page. Start from 1. Default:1 2836 | :type current: str 2837 | :param size: Default:10 Max:100 2838 | :type size: int 2839 | :param recvWindow: the number of milliseconds the request is valid for 2840 | :type recvWindow: int 2841 | 2842 | :returns: API response 2843 | 2844 | { 2845 | "rows": [ 2846 | { 2847 | //Total amount repaid 2848 | "amount": "14.00000000", 2849 | "asset": "BNB", 2850 | //Interest repaid 2851 | "interest": "0.01866667", 2852 | //Principal repaid 2853 | "principal": "13.98133333", 2854 | //one of PENDING (pending to execution), CONFIRMED (successfully loaned), FAILED (execution failed, nothing happened to your account); 2855 | "status": "CONFIRMED", 2856 | "timestamp": 1563438204000, 2857 | "txId": 2970933056 2858 | } 2859 | ], 2860 | "total": 1 2861 | } 2862 | 2863 | :raises: BinanceRequestException, BinanceAPIException 2864 | 2865 | """ 2866 | return self._request_margin_api('get', 'margin/repay', signed=True, data=params) 2867 | 2868 | def get_margin_order(self, **params): 2869 | """Query margin accounts order 2870 | 2871 | Either orderId or origClientOrderId must be sent. 2872 | 2873 | For some historical orders cummulativeQuoteQty will be < 0, meaning the data is not available at this time. 2874 | 2875 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#query-margin-accounts-order-user_data 2876 | 2877 | :param symbol: required 2878 | :type symbol: str 2879 | :param orderId: 2880 | :type orderId: str 2881 | :param origClientOrderId: 2882 | :type origClientOrderId: str 2883 | :param recvWindow: the number of milliseconds the request is valid for 2884 | :type recvWindow: int 2885 | 2886 | :returns: API response 2887 | 2888 | { 2889 | "clientOrderId": "ZwfQzuDIGpceVhKW5DvCmO", 2890 | "cummulativeQuoteQty": "0.00000000", 2891 | "executedQty": "0.00000000", 2892 | "icebergQty": "0.00000000", 2893 | "isWorking": true, 2894 | "orderId": 213205622, 2895 | "origQty": "0.30000000", 2896 | "price": "0.00493630", 2897 | "side": "SELL", 2898 | "status": "NEW", 2899 | "stopPrice": "0.00000000", 2900 | "symbol": "BNBBTC", 2901 | "time": 1562133008725, 2902 | "timeInForce": "GTC", 2903 | "type": "LIMIT", 2904 | "updateTime": 1562133008725 2905 | } 2906 | 2907 | :raises: BinanceRequestException, BinanceAPIException 2908 | 2909 | """ 2910 | return self._request_margin_api('get', 'margin/order', signed=True, data=params) 2911 | 2912 | def get_open_margin_orders(self, **params): 2913 | """Query margin accounts open orders 2914 | 2915 | If the symbol is not sent, orders for all symbols will be returned in an array. 2916 | 2917 | When all symbols are returned, the number of requests counted against the rate limiter is equal to the number 2918 | of symbols currently trading on the exchange. 2919 | 2920 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#query-margin-accounts-open-order-user_data 2921 | 2922 | :param symbol: optional 2923 | :type symbol: str 2924 | :param recvWindow: the number of milliseconds the request is valid for 2925 | :type recvWindow: int 2926 | 2927 | :returns: API response 2928 | 2929 | [ 2930 | { 2931 | "clientOrderId": "qhcZw71gAkCCTv0t0k8LUK", 2932 | "cummulativeQuoteQty": "0.00000000", 2933 | "executedQty": "0.00000000", 2934 | "icebergQty": "0.00000000", 2935 | "isWorking": true, 2936 | "orderId": 211842552, 2937 | "origQty": "0.30000000", 2938 | "price": "0.00475010", 2939 | "side": "SELL", 2940 | "status": "NEW", 2941 | "stopPrice": "0.00000000", 2942 | "symbol": "BNBBTC", 2943 | "time": 1562040170089, 2944 | "timeInForce": "GTC", 2945 | "type": "LIMIT", 2946 | "updateTime": 1562040170089 2947 | } 2948 | ] 2949 | 2950 | :raises: BinanceRequestException, BinanceAPIException 2951 | 2952 | """ 2953 | return self._request_margin_api('get', 'margin/openOrders', signed=True, data=params) 2954 | 2955 | def get_all_margin_orders(self, **params): 2956 | """Query all margin accounts orders 2957 | 2958 | If orderId is set, it will get orders >= that orderId. Otherwise most recent orders are returned. 2959 | 2960 | For some historical orders cummulativeQuoteQty will be < 0, meaning the data is not available at this time. 2961 | 2962 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#query-margin-accounts-open-order-user_data 2963 | 2964 | :param symbol: required 2965 | :type symbol: str 2966 | :param orderId: optional 2967 | :type orderId: str 2968 | :param startTime: optional 2969 | :type startTime: str 2970 | :param endTime: optional 2971 | :type endTime: str 2972 | :param limit: Default 500; max 1000 2973 | :type limit: int 2974 | :param recvWindow: the number of milliseconds the request is valid for 2975 | :type recvWindow: int 2976 | 2977 | :returns: API response 2978 | 2979 | [ 2980 | { 2981 | "id": 43123876, 2982 | "price": "0.00395740", 2983 | "qty": "4.06000000", 2984 | "quoteQty": "0.01606704", 2985 | "symbol": "BNBBTC", 2986 | "time": 1556089977693 2987 | }, 2988 | { 2989 | "id": 43123877, 2990 | "price": "0.00395740", 2991 | "qty": "0.77000000", 2992 | "quoteQty": "0.00304719", 2993 | "symbol": "BNBBTC", 2994 | "time": 1556089977693 2995 | }, 2996 | { 2997 | "id": 43253549, 2998 | "price": "0.00428930", 2999 | "qty": "23.30000000", 3000 | "quoteQty": "0.09994069", 3001 | "symbol": "BNBBTC", 3002 | "time": 1556163963504 3003 | } 3004 | ] 3005 | 3006 | 3007 | :raises: BinanceRequestException, BinanceAPIException 3008 | 3009 | """ 3010 | return self._request_margin_api('get', 'margin/allOrders', signed=True, data=params) 3011 | 3012 | def get_margin_trades(self, **params): 3013 | """Query margin accounts trades 3014 | 3015 | If fromId is set, it will get orders >= that fromId. Otherwise most recent orders are returned. 3016 | 3017 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#query-margin-accounts-trade-list-user_data 3018 | 3019 | :param symbol: required 3020 | :type symbol: str 3021 | :param fromId: optional 3022 | :type fromId: str 3023 | :param startTime: optional 3024 | :type startTime: str 3025 | :param endTime: optional 3026 | :type endTime: str 3027 | :param limit: Default 500; max 1000 3028 | :type limit: int 3029 | :param recvWindow: the number of milliseconds the request is valid for 3030 | :type recvWindow: int 3031 | 3032 | :returns: API response 3033 | 3034 | [ 3035 | { 3036 | "commission": "0.00006000", 3037 | "commissionAsset": "BTC", 3038 | "id": 34, 3039 | "isBestMatch": true, 3040 | "isBuyer": false, 3041 | "isMaker": false, 3042 | "orderId": 39324, 3043 | "price": "0.02000000", 3044 | "qty": "3.00000000", 3045 | "symbol": "BNBBTC", 3046 | "time": 1561973357171 3047 | }, { 3048 | "commission": "0.00002950", 3049 | "commissionAsset": "BTC", 3050 | "id": 32, 3051 | "isBestMatch": true, 3052 | "isBuyer": false, 3053 | "isMaker": true, 3054 | "orderId": 39319, 3055 | "price": "0.00590000", 3056 | "qty": "5.00000000", 3057 | "symbol": "BNBBTC", 3058 | "time": 1561964645345 3059 | } 3060 | ] 3061 | 3062 | 3063 | :raises: BinanceRequestException, BinanceAPIException 3064 | 3065 | """ 3066 | return self._request_margin_api('get', 'margin/myTrades', signed=True, data=params) 3067 | 3068 | def get_max_margin_loan(self, **params): 3069 | """Query max borrow amount for an asset 3070 | 3071 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#query-max-borrow-user_data 3072 | 3073 | :param asset: required 3074 | :type asset: str 3075 | :param recvWindow: the number of milliseconds the request is valid for 3076 | :type recvWindow: int 3077 | 3078 | :returns: API response 3079 | 3080 | { 3081 | "amount": "1.69248805" 3082 | } 3083 | 3084 | :raises: BinanceRequestException, BinanceAPIException 3085 | 3086 | """ 3087 | return self._request_margin_api('get', 'margin/maxBorrowable', signed=True, data=params) 3088 | 3089 | def get_max_margin_transfer(self, **params): 3090 | """Query max transfer-out amount 3091 | 3092 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#query-max-transfer-out-amount-user_data 3093 | 3094 | :param asset: required 3095 | :type asset: str 3096 | :param recvWindow: the number of milliseconds the request is valid for 3097 | :type recvWindow: int 3098 | 3099 | :returns: API response 3100 | 3101 | { 3102 | "amount": "3.59498107" 3103 | } 3104 | 3105 | :raises: BinanceRequestException, BinanceAPIException 3106 | 3107 | """ 3108 | return self._request_margin_api('get', 'margin/maxTransferable', signed=True, data=params) 3109 | 3110 | def margin_stream_get_listen_key(self): 3111 | """Start a new margin data stream and return the listen key 3112 | If a stream already exists it should return the same key. 3113 | If the stream becomes invalid a new key is returned. 3114 | 3115 | Can be used to keep the user stream alive. 3116 | 3117 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#start-user-data-stream-for-margin-account-user_stream 3118 | 3119 | :returns: API response 3120 | 3121 | .. code-block:: python 3122 | 3123 | { 3124 | "listenKey": "pqia91ma19a5s61cv6a81va65sdf19v8a65a1a5s61cv6a81va65sdf19v8a65a1" 3125 | } 3126 | 3127 | :raises: BinanceRequestException, BinanceAPIException 3128 | 3129 | """ 3130 | res = self._request_margin_api('post', 'userDataStream', signed=True, data={}) 3131 | return res['listenKey'] 3132 | 3133 | def margin_stream_keepalive(self, listenKey): 3134 | """PING a margin data stream to prevent a time out. 3135 | 3136 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#ping-user-data-stream-for-margin-account--user_stream 3137 | 3138 | :param listenKey: required 3139 | :type listenKey: str 3140 | 3141 | :returns: API response 3142 | 3143 | .. code-block:: python 3144 | 3145 | {} 3146 | 3147 | :raises: BinanceRequestException, BinanceAPIException 3148 | 3149 | """ 3150 | params = { 3151 | 'listenKey': listenKey 3152 | } 3153 | return self._request_margin_api('put', 'userDataStream', signed=True, data=params) 3154 | 3155 | def margin_stream_close(self, listenKey): 3156 | """Close out a margin data stream. 3157 | 3158 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#delete-user-data-stream-for-margin-account--user_stream 3159 | 3160 | :param listenKey: required 3161 | :type listenKey: str 3162 | 3163 | :returns: API response 3164 | 3165 | .. code-block:: python 3166 | 3167 | {} 3168 | 3169 | :raises: BinanceRequestException, BinanceAPIException 3170 | 3171 | """ 3172 | params = { 3173 | 'listenKey': listenKey 3174 | } 3175 | return self._request_margin_api('delete', 'userDataStream', signed=True, data=params) 3176 | 3177 | # Lending Endpoints 3178 | 3179 | def get_lending_product_list(self, **params): 3180 | """Get Lending Product List 3181 | 3182 | https://binance-docs.github.io/apidocs/spot/en/#get-flexible-product-list-user_data 3183 | 3184 | """ 3185 | return self._request_margin_api('get', 'lending/daily/product/list ', signed=True, data=params) 3186 | 3187 | def get_lending_daily_quota_left(self, **params): 3188 | """Get Left Daily Purchase Quota of Flexible Product. 3189 | 3190 | https://binance-docs.github.io/apidocs/spot/en/#get-left-daily-purchase-quota-of-flexible-product-user_data 3191 | 3192 | """ 3193 | return self._request_margin_api('get', 'lending/daily/userLeftQuota', signed=True, data=params) 3194 | 3195 | def purchase_lending_product(self, **params): 3196 | """Purchase Flexible Product 3197 | 3198 | https://binance-docs.github.io/apidocs/spot/en/#purchase-flexible-product-user_data 3199 | 3200 | """ 3201 | return self._request_margin_api('post', 'lending/daily/purchase', signed=True, data=params) 3202 | 3203 | def get_lending_daily_redemption_quota(self, **params): 3204 | """Get Left Daily Redemption Quota of Flexible Product 3205 | 3206 | https://binance-docs.github.io/apidocs/spot/en/#get-left-daily-redemption-quota-of-flexible-product-user_data 3207 | 3208 | """ 3209 | return self._request_margin_api('get', 'lending/daily/userRedemptionQuota', signed=True, data=params) 3210 | 3211 | def redeem_lending_product(self, **params): 3212 | """Redeem Flexible Product 3213 | 3214 | https://binance-docs.github.io/apidocs/spot/en/#redeem-flexible-product-user_data 3215 | 3216 | """ 3217 | return self._request_margin_api('post', 'lending/daily/redeem', signed=True, data=params) 3218 | 3219 | def get_lending_position(self, **params): 3220 | """Get Flexible Product Position 3221 | 3222 | https://binance-docs.github.io/apidocs/spot/en/#get-flexible-product-position-user_data 3223 | 3224 | """ 3225 | return self._request_margin_api('get', 'lending/daily/token/position', signed=True, data=params) 3226 | 3227 | def get_lending_account(self, **params): 3228 | """Get Lending Account Details 3229 | 3230 | https://binance-docs.github.io/apidocs/spot/en/#lending-account-user_data 3231 | 3232 | """ 3233 | return self._request_margin_api('get', 'lending/union/account', signed=True, data=params) 3234 | 3235 | def get_lending_purchase_history(self, **params): 3236 | """Get Lending Purchase History 3237 | 3238 | https://binance-docs.github.io/apidocs/spot/en/#get-purchase-record-user_data 3239 | 3240 | """ 3241 | return self._request_margin_api('get', 'lending/union/purchaseRecord', signed=True, data=params) 3242 | 3243 | def get_lending_redemption_history(self, **params): 3244 | """Get Lending Redemption History 3245 | 3246 | https://binance-docs.github.io/apidocs/spot/en/#get-redemption-record-user_data 3247 | 3248 | """ 3249 | return self._request_margin_api('get', 'lending/union/redemptionRecord', signed=True, data=params) 3250 | 3251 | def get_lending_interest_history(self, **params): 3252 | """Get Lending Interest History 3253 | 3254 | https://binance-docs.github.io/apidocs/spot/en/#get-interest-history-user_data-2 3255 | 3256 | """ 3257 | return self._request_margin_api('get', 'lending/union/interestHistory', signed=True, data=params) 3258 | 3259 | # Sub Accounts 3260 | 3261 | def get_sub_account_list(self, **params): 3262 | """Query Sub-account List. 3263 | 3264 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#query-sub-account-listfor-master-account 3265 | 3266 | :param email: optional 3267 | :type email: str 3268 | :param startTime: optional 3269 | :type startTime: int 3270 | :param endTime: optional 3271 | :type endTime: int 3272 | :param page: optional 3273 | :type page: int 3274 | :param limit: optional 3275 | :type limit: int 3276 | :param recvWindow: optional 3277 | :type recvWindow: int 3278 | 3279 | :returns: API response 3280 | 3281 | .. code-block:: python 3282 | 3283 | { 3284 | "success":true, 3285 | "subAccounts":[ 3286 | { 3287 | "email":"123@test.com", 3288 | "status":"enabled", 3289 | "activated":true, 3290 | "mobile":"91605290", 3291 | "gAuth":true, 3292 | "createTime":1544433328000 3293 | }, 3294 | { 3295 | "email":"321@test.com", 3296 | "status":"disabled", 3297 | "activated":true, 3298 | "mobile":"22501238", 3299 | "gAuth":true, 3300 | "createTime":1544433328000 3301 | } 3302 | ] 3303 | } 3304 | 3305 | :raises: BinanceRequestException, BinanceAPIException 3306 | 3307 | """ 3308 | return self._request_withdraw_api('get', 'sub-account/list.html', True, data=params) 3309 | 3310 | def get_sub_account_transfer_history(self, **params): 3311 | """Query Sub-account Transfer History. 3312 | 3313 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#query-sub-account-transfer-historyfor-master-account 3314 | 3315 | :param email: required 3316 | :type email: str 3317 | :param startTime: optional 3318 | :type startTime: int 3319 | :param endTime: optional 3320 | :type endTime: int 3321 | :param page: optional 3322 | :type page: int 3323 | :param limit: optional 3324 | :type limit: int 3325 | :param recvWindow: optional 3326 | :type recvWindow: int 3327 | 3328 | :returns: API response 3329 | 3330 | .. code-block:: python 3331 | 3332 | { 3333 | "success":true, 3334 | "transfers":[ 3335 | { 3336 | "from":"aaa@test.com", 3337 | "to":"bbb@test.com", 3338 | "asset":"BTC", 3339 | "qty":"1", 3340 | "time":1544433328000 3341 | }, 3342 | { 3343 | "from":"bbb@test.com", 3344 | "to":"ccc@test.com", 3345 | "asset":"ETH", 3346 | "qty":"2", 3347 | "time":1544433328000 3348 | } 3349 | ] 3350 | } 3351 | 3352 | :raises: BinanceRequestException, BinanceAPIException 3353 | 3354 | """ 3355 | return self._request_withdraw_api('get', 'sub-account/transfer/history.html', True, data=params) 3356 | 3357 | def create_sub_account_transfer(self, **params): 3358 | """Execute sub-account transfer 3359 | 3360 | https://github.com/binance-exchange/binance-official-api-docs/blob/9dbe0e961b80557bb19708a707c7fad08842b28e/wapi-api.md#sub-account-transferfor-master-account 3361 | 3362 | :param fromEmail: required - Sender email 3363 | :type fromEmail: str 3364 | :param toEmail: required - Recipient email 3365 | :type toEmail: str 3366 | :param asset: required 3367 | :type asset: str 3368 | :param amount: required 3369 | :type amount: decimal 3370 | :param recvWindow: optional 3371 | :type recvWindow: int 3372 | 3373 | :returns: API response 3374 | 3375 | .. code-block:: python 3376 | 3377 | { 3378 | "success":true, 3379 | "txnId":"2966662589" 3380 | } 3381 | 3382 | :raises: BinanceRequestException, BinanceAPIException 3383 | 3384 | """ 3385 | return self._request_withdraw_api('post', 'sub-account/transfer.html', True, data=params) 3386 | 3387 | def get_sub_account_assets(self, **params): 3388 | """Fetch sub-account assets 3389 | 3390 | https://github.com/binance-exchange/binance-official-api-docs/blob/9dbe0e961b80557bb19708a707c7fad08842b28e/wapi-api.md#query-sub-account-assetsfor-master-account 3391 | 3392 | :param email: required 3393 | :type email: str 3394 | :param symbol: optional 3395 | :type symbol: str 3396 | :param recvWindow: optional 3397 | :type recvWindow: int 3398 | 3399 | :returns: API response 3400 | 3401 | .. code-block:: python 3402 | 3403 | { 3404 | "success":true, 3405 | "balances":[ 3406 | { 3407 | "asset":"ADA", 3408 | "free":10000, 3409 | "locked":0 3410 | }, 3411 | { 3412 | "asset":"BNB", 3413 | "free":10003, 3414 | "locked":0 3415 | }, 3416 | { 3417 | "asset":"BTC", 3418 | "free":11467.6399, 3419 | "locked":0 3420 | }, 3421 | { 3422 | "asset":"ETH", 3423 | "free":10004.995, 3424 | "locked":0 3425 | }, 3426 | { 3427 | "asset":"USDT", 3428 | "free":11652.14213, 3429 | "locked":0 3430 | } 3431 | ] 3432 | } 3433 | 3434 | :raises: BinanceRequestException, BinanceAPIException 3435 | 3436 | """ 3437 | return self._request_withdraw_api('get', 'sub-account/assets.html', True, data=params) 3438 | 3439 | # Futures API 3440 | 3441 | def futures_ping(self): 3442 | """Test connectivity to the Rest API 3443 | 3444 | https://binance-docs.github.io/apidocs/futures/en/#test-connectivity 3445 | 3446 | """ 3447 | return self._request_futures_api('get', 'ping') 3448 | 3449 | def futures_time(self): 3450 | """Test connectivity to the Rest API and get the current server time. 3451 | 3452 | https://binance-docs.github.io/apidocs/futures/en/#check-server-time 3453 | 3454 | """ 3455 | return self._request_futures_api('get', 'time') 3456 | 3457 | def futures_exchange_info(self): 3458 | """Current exchange trading rules and symbol information 3459 | 3460 | https://binance-docs.github.io/apidocs/futures/en/#exchange-information-market_data 3461 | 3462 | """ 3463 | return self._request_futures_api('get', 'exchangeInfo') 3464 | 3465 | def futures_order_book(self, **params): 3466 | """Get the Order Book for the market 3467 | 3468 | https://binance-docs.github.io/apidocs/futures/en/#order-book-market_data 3469 | 3470 | """ 3471 | return self._request_futures_api('get', 'depth', data=params) 3472 | 3473 | def futures_recent_trades(self, **params): 3474 | """Get recent trades (up to last 500). 3475 | 3476 | https://binance-docs.github.io/apidocs/futures/en/#recent-trades-list-market_data 3477 | 3478 | """ 3479 | return self._request_futures_api('get', 'trades', data=params) 3480 | 3481 | def futures_historical_trades(self, **params): 3482 | """Get older market historical trades. 3483 | 3484 | https://binance-docs.github.io/apidocs/futures/en/#old-trades-lookup-market_data 3485 | 3486 | """ 3487 | return self._request_futures_api('get', 'historicalTrades', data=params) 3488 | 3489 | def futures_aggregate_trades(self, **params): 3490 | """Get compressed, aggregate trades. Trades that fill at the time, from the same order, with the same 3491 | price will have the quantity aggregated. 3492 | 3493 | https://binance-docs.github.io/apidocs/futures/en/#compressed-aggregate-trades-list-market_data 3494 | 3495 | """ 3496 | return self._request_futures_api('get', 'aggTrades', data=params) 3497 | 3498 | def futures_klines(self, **params): 3499 | """Kline/candlestick bars for a symbol. Klines are uniquely identified by their open time. 3500 | 3501 | https://binance-docs.github.io/apidocs/futures/en/#kline-candlestick-data-market_data 3502 | 3503 | """ 3504 | return self._request_futures_api('get', 'klines', data=params) 3505 | 3506 | def futures_mark_price(self, **params): 3507 | """Get Mark Price and Funding Rate 3508 | 3509 | https://binance-docs.github.io/apidocs/futures/en/#mark-price-market_data 3510 | 3511 | """ 3512 | return self._request_futures_api('get', 'premiumIndex', data=params) 3513 | 3514 | def futures_funding_rate(self, **params): 3515 | """Get funding rate history 3516 | 3517 | https://binance-docs.github.io/apidocs/futures/en/#get-funding-rate-history-market_data 3518 | 3519 | """ 3520 | return self._request_futures_api('get', 'fundingRate', data=params) 3521 | 3522 | def futures_ticker(self, **params): 3523 | """24 hour rolling window price change statistics. 3524 | 3525 | https://binance-docs.github.io/apidocs/futures/en/#24hr-ticker-price-change-statistics-market_data 3526 | 3527 | """ 3528 | return self._request_futures_api('get', 'ticker/24hr', data=params) 3529 | 3530 | def futures_symbol_ticker(self, **params): 3531 | """Latest price for a symbol or symbols. 3532 | 3533 | https://binance-docs.github.io/apidocs/futures/en/#symbol-price-ticker-market_data 3534 | 3535 | """ 3536 | return self._request_futures_api('get', 'ticker/price', data=params) 3537 | 3538 | def futures_orderbook_ticker(self, **params): 3539 | """Best price/qty on the order book for a symbol or symbols. 3540 | 3541 | https://binance-docs.github.io/apidocs/futures/en/#symbol-order-book-ticker-market_data 3542 | 3543 | """ 3544 | return self._request_futures_api('get', 'ticker/bookTicker', data=params) 3545 | 3546 | def futures_liquidation_orders(self, **params): 3547 | """Get all liquidation orders 3548 | 3549 | https://binance-docs.github.io/apidocs/futures/en/#get-all-liquidation-orders-market_data 3550 | 3551 | """ 3552 | return self._request_futures_api('get', 'ticker/allForceOrders', data=params) 3553 | 3554 | def futures_open_interest(self, **params): 3555 | """Get present open interest of a specific symbol. 3556 | 3557 | https://binance-docs.github.io/apidocs/futures/en/#open-interest-market_data 3558 | 3559 | """ 3560 | return self._request_futures_api('get', 'ticker/openInterest', data=params) 3561 | 3562 | def futures_leverage_bracket(self, **params): 3563 | """Notional and Leverage Brackets 3564 | 3565 | https://binance-docs.github.io/apidocs/futures/en/#notional-and-leverage-brackets-market_data 3566 | 3567 | """ 3568 | return self._request_futures_api('get', 'ticker/leverageBracket', data=params) 3569 | 3570 | def transfer_history(self, **params): 3571 | """Get future account transaction history list 3572 | 3573 | https://binance-docs.github.io/apidocs/futures/en/#new-future-account-transfer 3574 | 3575 | """ 3576 | return self._request_margin_api('get', 'futures/transfer', True, data=params) 3577 | 3578 | def futures_create_order(self, **params): 3579 | """Send in a new order. 3580 | 3581 | https://binance-docs.github.io/apidocs/futures/en/#new-order-trade 3582 | 3583 | """ 3584 | return self._request_futures_api('post', 'order', True, data=params) 3585 | 3586 | def futures_get_order(self, **params): 3587 | """Check an order's status. 3588 | 3589 | https://binance-docs.github.io/apidocs/futures/en/#query-order-user_data 3590 | 3591 | """ 3592 | return self._request_futures_api('get', 'order', True, data=params) 3593 | 3594 | def futures_get_open_orders(self, **params): 3595 | """Get all open orders on a symbol. 3596 | 3597 | https://binance-docs.github.io/apidocs/futures/en/#current-open-orders-user_data 3598 | 3599 | """ 3600 | return self._request_futures_api('get', 'openOrders', True, data=params) 3601 | 3602 | def futures_get_all_orders(self, **params): 3603 | """Get all futures account orders; active, canceled, or filled. 3604 | 3605 | https://binance-docs.github.io/apidocs/futures/en/#all-orders-user_data 3606 | 3607 | """ 3608 | return self._request_futures_api('get', 'allOrders', True, data=params) 3609 | 3610 | def futures_cancel_order(self, **params): 3611 | """Cancel an active futures order. 3612 | 3613 | https://binance-docs.github.io/apidocs/futures/en/#cancel-order-trade 3614 | 3615 | """ 3616 | return self._request_futures_api('delete', 'order', True, data=params) 3617 | 3618 | def futures_cancel_all_open_orders(self, **params): 3619 | """Cancel all open futures orders 3620 | 3621 | https://binance-docs.github.io/apidocs/futures/en/#cancel-all-open-orders-trade 3622 | 3623 | """ 3624 | return self._request_futures_api('delete', 'allOpenOrders', True, data=params) 3625 | 3626 | def futures_cancel_orders(self, **params): 3627 | """Cancel multiple futures orders 3628 | 3629 | https://binance-docs.github.io/apidocs/futures/en/#cancel-multiple-orders-trade 3630 | 3631 | """ 3632 | return self._request_futures_api('delete', 'batchOrders', True, data=params) 3633 | 3634 | def futures_account_balance(self, **params): 3635 | """Get futures account balance 3636 | 3637 | https://binance-docs.github.io/apidocs/futures/en/#future-account-balance-user_data 3638 | 3639 | """ 3640 | return self._request_futures_api('get', 'balance', True, data=params) 3641 | 3642 | def futures_account(self, **params): 3643 | """Get current account information. 3644 | 3645 | https://binance-docs.github.io/apidocs/futures/en/#account-information-user_data 3646 | 3647 | """ 3648 | return self._request_futures_api('get', 'account', True, data=params) 3649 | 3650 | def futures_change_leverage(self, **params): 3651 | """Change user's initial leverage of specific symbol market 3652 | 3653 | https://binance-docs.github.io/apidocs/futures/en/#change-initial-leverage-trade 3654 | 3655 | """ 3656 | return self._request_futures_api('post', 'leverage', True, data=params) 3657 | 3658 | def futures_change_margin_type(self, **params): 3659 | """Change the margin type for a symbol 3660 | 3661 | https://binance-docs.github.io/apidocs/futures/en/#change-margin-type-trade 3662 | 3663 | """ 3664 | return self._request_futures_api('post', 'marginType', True, data=params) 3665 | 3666 | def futures_change_position_margin(self, **params): 3667 | """Change the position margin for a symbol 3668 | 3669 | https://binance-docs.github.io/apidocs/futures/en/#modify-isolated-position-margin-trade 3670 | 3671 | """ 3672 | return self._request_futures_api('post', 'positionMargin', True, data=params) 3673 | 3674 | def futures_position_margin_history(self, **params): 3675 | """Get position margin change history 3676 | 3677 | https://binance-docs.github.io/apidocs/futures/en/#get-postion-margin-change-history-trade 3678 | 3679 | """ 3680 | return self._request_futures_api('get', 'positionMargin/history', True, data=params) 3681 | 3682 | def futures_position_information(self, **params): 3683 | """Get position information 3684 | 3685 | https://binance-docs.github.io/apidocs/futures/en/#position-information-user_data 3686 | 3687 | """ 3688 | return self._request_futures_api('get', 'positionRisk', True, data=params) 3689 | 3690 | def futures_account_trades(self, **params): 3691 | """Get trades for the authenticated account and symbol. 3692 | 3693 | https://binance-docs.github.io/apidocs/futures/en/#account-trade-list-user_data 3694 | 3695 | """ 3696 | return self._request_futures_api('get', 'userTrades', True, data=params) 3697 | 3698 | def futures_income_history(self, **params): 3699 | """Get income history for authenticated account 3700 | 3701 | https://binance-docs.github.io/apidocs/futures/en/#get-income-history-user_data 3702 | 3703 | """ 3704 | return self._request_futures_api('get', 'income', True, data=params) 3705 | 3706 | def futures_stream_get_listen_key(self): 3707 | res = self._request_futures_api("post", "listenKey", signed=False, data={}) 3708 | return res["listenKey"] -------------------------------------------------------------------------------- /binance/depthcache.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from operator import itemgetter 4 | import time 5 | 6 | from .websockets import BinanceSocketManager 7 | 8 | 9 | class DepthCache(object): 10 | 11 | def __init__(self, symbol): 12 | """Initialise the DepthCache 13 | 14 | :param symbol: Symbol to create depth cache for 15 | :type symbol: string 16 | 17 | """ 18 | self.symbol = symbol 19 | self._bids = {} 20 | self._asks = {} 21 | self.update_time = None 22 | 23 | def add_bid(self, bid): 24 | """Add a bid to the cache 25 | 26 | :param bid: 27 | :return: 28 | 29 | """ 30 | self._bids[bid[0]] = float(bid[1]) 31 | if bid[1] == "0.00000000": 32 | del self._bids[bid[0]] 33 | 34 | def add_ask(self, ask): 35 | """Add an ask to the cache 36 | 37 | :param ask: 38 | :return: 39 | 40 | """ 41 | self._asks[ask[0]] = float(ask[1]) 42 | if ask[1] == "0.00000000": 43 | del self._asks[ask[0]] 44 | 45 | def get_bids(self): 46 | """Get the current bids 47 | 48 | :return: list of bids with price and quantity as floats 49 | 50 | .. code-block:: python 51 | 52 | [ 53 | [ 54 | 0.0001946, # Price 55 | 45.0 # Quantity 56 | ], 57 | [ 58 | 0.00019459, 59 | 2384.0 60 | ], 61 | [ 62 | 0.00019158, 63 | 5219.0 64 | ], 65 | [ 66 | 0.00019157, 67 | 1180.0 68 | ], 69 | [ 70 | 0.00019082, 71 | 287.0 72 | ] 73 | ] 74 | 75 | """ 76 | return DepthCache.sort_depth(self._bids, reverse=True) 77 | 78 | def get_asks(self): 79 | """Get the current asks 80 | 81 | :return: list of asks with price and quantity as floats 82 | 83 | .. code-block:: python 84 | 85 | [ 86 | [ 87 | 0.0001955, # Price 88 | 57.0' # Quantity 89 | ], 90 | [ 91 | 0.00019699, 92 | 778.0 93 | ], 94 | [ 95 | 0.000197, 96 | 64.0 97 | ], 98 | [ 99 | 0.00019709, 100 | 1130.0 101 | ], 102 | [ 103 | 0.0001971, 104 | 385.0 105 | ] 106 | ] 107 | 108 | """ 109 | return DepthCache.sort_depth(self._asks, reverse=False) 110 | 111 | @staticmethod 112 | def sort_depth(vals, reverse=False): 113 | """Sort bids or asks by price 114 | """ 115 | lst = [[float(price), quantity] for price, quantity in vals.items()] 116 | lst = sorted(lst, key=itemgetter(0), reverse=reverse) 117 | return lst 118 | 119 | 120 | class DepthCacheManager(object): 121 | 122 | _default_refresh = 60 * 30 # 30 minutes 123 | 124 | def __init__(self, client, symbol, callback=None, refresh_interval=_default_refresh, bm=None, limit=500): 125 | """Initialise the DepthCacheManager 126 | 127 | :param client: Binance API client 128 | :type client: binance.Client 129 | :param symbol: Symbol to create depth cache for 130 | :type symbol: string 131 | :param callback: Optional function to receive depth cache updates 132 | :type callback: function 133 | :param refresh_interval: Optional number of seconds between cache refresh, use 0 or None to disable 134 | :type refresh_interval: int 135 | :param limit: Optional number of orders to get from orderbook 136 | :type limit: int 137 | 138 | """ 139 | self._client = client 140 | self._symbol = symbol 141 | self._limit = limit 142 | self._callback = callback 143 | self._last_update_id = None 144 | self._depth_message_buffer = [] 145 | self._bm = bm 146 | self._depth_cache = DepthCache(self._symbol) 147 | self._refresh_interval = refresh_interval 148 | self._conn_key = None 149 | 150 | self._start_socket() 151 | self._init_cache() 152 | 153 | def _init_cache(self): 154 | """Initialise the depth cache calling REST endpoint 155 | 156 | :return: 157 | """ 158 | self._last_update_id = None 159 | self._depth_message_buffer = [] 160 | 161 | res = self._client.get_order_book(symbol=self._symbol, limit=self._limit) 162 | 163 | # process bid and asks from the order book 164 | for bid in res['bids']: 165 | self._depth_cache.add_bid(bid) 166 | for ask in res['asks']: 167 | self._depth_cache.add_ask(ask) 168 | 169 | # set first update id 170 | self._last_update_id = res['lastUpdateId'] 171 | 172 | # set a time to refresh the depth cache 173 | if self._refresh_interval: 174 | self._refresh_time = int(time.time()) + self._refresh_interval 175 | 176 | # Apply any updates from the websocket 177 | for msg in self._depth_message_buffer: 178 | self._process_depth_message(msg, buffer=True) 179 | 180 | # clear the depth buffer 181 | self._depth_message_buffer = [] 182 | 183 | def _start_socket(self): 184 | """Start the depth cache socket 185 | 186 | :return: 187 | """ 188 | if self._bm is None: 189 | self._bm = BinanceSocketManager(self._client) 190 | 191 | self._conn_key = self._bm.start_depth_socket(self._symbol, self._depth_event) 192 | if not self._bm.is_alive(): 193 | self._bm.start() 194 | 195 | # wait for some socket responses 196 | while not len(self._depth_message_buffer): 197 | time.sleep(1) 198 | 199 | def _depth_event(self, msg): 200 | """Handle a depth event 201 | 202 | :param msg: 203 | :return: 204 | 205 | """ 206 | 207 | if 'e' in msg and msg['e'] == 'error': 208 | # close the socket 209 | self.close() 210 | 211 | # notify the user by returning a None value 212 | if self._callback: 213 | self._callback(None) 214 | 215 | if self._last_update_id is None: 216 | # Initial depth snapshot fetch not yet performed, buffer messages 217 | self._depth_message_buffer.append(msg) 218 | else: 219 | self._process_depth_message(msg) 220 | 221 | def _process_depth_message(self, msg, buffer=False): 222 | """Process a depth event message. 223 | 224 | :param msg: Depth event message. 225 | :return: 226 | 227 | """ 228 | 229 | if buffer and msg['u'] <= self._last_update_id: 230 | # ignore any updates before the initial update id 231 | return 232 | elif msg['U'] != self._last_update_id + 1: 233 | # if not buffered check we get sequential updates 234 | # otherwise init cache again 235 | self._init_cache() 236 | 237 | # add any bid or ask values 238 | for bid in msg['b']: 239 | self._depth_cache.add_bid(bid) 240 | for ask in msg['a']: 241 | self._depth_cache.add_ask(ask) 242 | 243 | # keeping update time 244 | self._depth_cache.update_time = msg['E'] 245 | 246 | # call the callback with the updated depth cache 247 | if self._callback: 248 | self._callback(self._depth_cache) 249 | 250 | self._last_update_id = msg['u'] 251 | 252 | # after processing event see if we need to refresh the depth cache 253 | if self._refresh_interval and int(time.time()) > self._refresh_time: 254 | self._init_cache() 255 | 256 | def get_depth_cache(self): 257 | """Get the current depth cache 258 | 259 | :return: DepthCache object 260 | 261 | """ 262 | return self._depth_cache 263 | 264 | def close(self, close_socket=False): 265 | """Close the open socket for this manager 266 | 267 | :return: 268 | """ 269 | self._bm.stop_socket(self._conn_key) 270 | if close_socket: 271 | self._bm.close() 272 | time.sleep(1) 273 | self._depth_cache = None 274 | -------------------------------------------------------------------------------- /binance/enums.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | SYMBOL_TYPE_SPOT = 'SPOT' 4 | 5 | ORDER_STATUS_NEW = 'NEW' 6 | ORDER_STATUS_PARTIALLY_FILLED = 'PARTIALLY_FILLED' 7 | ORDER_STATUS_FILLED = 'FILLED' 8 | ORDER_STATUS_CANCELED = 'CANCELED' 9 | ORDER_STATUS_PENDING_CANCEL = 'PENDING_CANCEL' 10 | ORDER_STATUS_REJECTED = 'REJECTED' 11 | ORDER_STATUS_EXPIRED = 'EXPIRED' 12 | 13 | KLINE_INTERVAL_1MINUTE = '1m' 14 | KLINE_INTERVAL_3MINUTE = '3m' 15 | KLINE_INTERVAL_5MINUTE = '5m' 16 | KLINE_INTERVAL_15MINUTE = '15m' 17 | KLINE_INTERVAL_30MINUTE = '30m' 18 | KLINE_INTERVAL_1HOUR = '1h' 19 | KLINE_INTERVAL_2HOUR = '2h' 20 | KLINE_INTERVAL_4HOUR = '4h' 21 | KLINE_INTERVAL_6HOUR = '6h' 22 | KLINE_INTERVAL_8HOUR = '8h' 23 | KLINE_INTERVAL_12HOUR = '12h' 24 | KLINE_INTERVAL_1DAY = '1d' 25 | KLINE_INTERVAL_3DAY = '3d' 26 | KLINE_INTERVAL_1WEEK = '1w' 27 | KLINE_INTERVAL_1MONTH = '1M' 28 | 29 | SIDE_BUY = 'BUY' 30 | SIDE_SELL = 'SELL' 31 | 32 | ORDER_TYPE_LIMIT = 'LIMIT' 33 | ORDER_TYPE_MARKET = 'MARKET' 34 | ORDER_TYPE_STOP_LOSS = 'STOP_LOSS' 35 | ORDER_TYPE_STOP_LOSS_LIMIT = 'STOP_LOSS_LIMIT' 36 | ORDER_TYPE_TAKE_PROFIT = 'TAKE_PROFIT' 37 | ORDER_TYPE_TAKE_PROFIT_LIMIT = 'TAKE_PROFIT_LIMIT' 38 | ORDER_TYPE_LIMIT_MAKER = 'LIMIT_MAKER' 39 | 40 | TIME_IN_FORCE_GTC = 'GTC' # Good till cancelled 41 | TIME_IN_FORCE_IOC = 'IOC' # Immediate or cancel 42 | TIME_IN_FORCE_FOK = 'FOK' # Fill or kill 43 | 44 | ORDER_RESP_TYPE_ACK = 'ACK' 45 | ORDER_RESP_TYPE_RESULT = 'RESULT' 46 | ORDER_RESP_TYPE_FULL = 'FULL' 47 | 48 | WEBSOCKET_DEPTH_5 = '5' 49 | WEBSOCKET_DEPTH_10 = '10' 50 | WEBSOCKET_DEPTH_20 = '20' 51 | -------------------------------------------------------------------------------- /binance/exceptions.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | class BinanceAPIException(Exception): 5 | 6 | def __init__(self, response): 7 | self.code = 0 8 | try: 9 | json_res = response.json() 10 | except ValueError: 11 | self.message = 'Invalid JSON error message from Binance: {}'.format(response.text) 12 | else: 13 | self.code = json_res['code'] 14 | self.message = json_res['msg'] 15 | self.status_code = response.status_code 16 | self.response = response 17 | self.request = getattr(response, 'request', None) 18 | 19 | def __str__(self): # pragma: no cover 20 | return 'APIError(code=%s): %s' % (self.code, self.message) 21 | 22 | 23 | class BinanceRequestException(Exception): 24 | def __init__(self, message): 25 | self.message = message 26 | 27 | def __str__(self): 28 | return 'BinanceRequestException: %s' % self.message 29 | 30 | 31 | class BinanceOrderException(Exception): 32 | 33 | def __init__(self, code, message): 34 | self.code = code 35 | self.message = message 36 | 37 | def __str__(self): 38 | return 'BinanceOrderException(code=%s): %s' % (self.code, self.message) 39 | 40 | 41 | class BinanceOrderMinAmountException(BinanceOrderException): 42 | 43 | def __init__(self, value): 44 | message = "Amount must be a multiple of %s" % value 45 | super(BinanceOrderMinAmountException, self).__init__(-1013, message) 46 | 47 | 48 | class BinanceOrderMinPriceException(BinanceOrderException): 49 | 50 | def __init__(self, value): 51 | message = "Price must be at least %s" % value 52 | super(BinanceOrderMinPriceException, self).__init__(-1013, message) 53 | 54 | 55 | class BinanceOrderMinTotalException(BinanceOrderException): 56 | 57 | def __init__(self, value): 58 | message = "Total must be at least %s" % value 59 | super(BinanceOrderMinTotalException, self).__init__(-1013, message) 60 | 61 | 62 | class BinanceOrderUnknownSymbolException(BinanceOrderException): 63 | 64 | def __init__(self, value): 65 | message = "Unknown symbol %s" % value 66 | super(BinanceOrderUnknownSymbolException, self).__init__(-1013, message) 67 | 68 | 69 | class BinanceOrderInactiveSymbolException(BinanceOrderException): 70 | 71 | def __init__(self, value): 72 | message = "Attempting to trade an inactive symbol %s" % value 73 | super(BinanceOrderInactiveSymbolException, self).__init__(-1013, message) 74 | 75 | 76 | class BinanceWithdrawException(Exception): 77 | def __init__(self, message): 78 | if message == u'参数异常': 79 | message = 'Withdraw to this address through the website first' 80 | self.message = message 81 | 82 | def __str__(self): 83 | return 'BinanceWithdrawException: %s' % self.message 84 | -------------------------------------------------------------------------------- /binance/helpers.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import dateparser 4 | import pytz 5 | 6 | from datetime import datetime 7 | 8 | 9 | def date_to_milliseconds(date_str): 10 | """Convert UTC date to milliseconds 11 | 12 | If using offset strings add "UTC" to date string e.g. "now UTC", "11 hours ago UTC" 13 | 14 | See dateparse docs for formats http://dateparser.readthedocs.io/en/latest/ 15 | 16 | :param date_str: date in readable format, i.e. "January 01, 2018", "11 hours ago UTC", "now UTC" 17 | :type date_str: str 18 | """ 19 | # get epoch value in UTC 20 | epoch = datetime.utcfromtimestamp(0).replace(tzinfo=pytz.utc) 21 | # parse our date string 22 | d = dateparser.parse(date_str) 23 | # if the date is not timezone aware apply UTC timezone 24 | if d.tzinfo is None or d.tzinfo.utcoffset(d) is None: 25 | d = d.replace(tzinfo=pytz.utc) 26 | 27 | # return the difference in time 28 | return int((d - epoch).total_seconds() * 1000.0) 29 | 30 | 31 | def interval_to_milliseconds(interval): 32 | """Convert a Binance interval string to milliseconds 33 | 34 | :param interval: Binance interval string, e.g.: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w 35 | :type interval: str 36 | 37 | :return: 38 | int value of interval in milliseconds 39 | None if interval prefix is not a decimal integer 40 | None if interval suffix is not one of m, h, d, w 41 | 42 | """ 43 | seconds_per_unit = { 44 | "m": 60, 45 | "h": 60 * 60, 46 | "d": 24 * 60 * 60, 47 | "w": 7 * 24 * 60 * 60, 48 | } 49 | try: 50 | return int(interval[:-1]) * seconds_per_unit[interval[-1]] * 1000 51 | except (ValueError, KeyError): 52 | return None 53 | -------------------------------------------------------------------------------- /binance/websockets.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import json 4 | import threading 5 | 6 | from autobahn.twisted.websocket import WebSocketClientFactory, \ 7 | WebSocketClientProtocol, \ 8 | connectWS 9 | from twisted.internet import reactor, ssl 10 | from twisted.internet.protocol import ReconnectingClientFactory 11 | from twisted.internet.error import ReactorAlreadyRunning 12 | 13 | from binance.client import Client 14 | 15 | 16 | class BinanceClientProtocol(WebSocketClientProtocol): 17 | 18 | def __init__(self): 19 | super(WebSocketClientProtocol, self).__init__() 20 | 21 | def onConnect(self, response): 22 | # reset the delay after reconnecting 23 | self.factory.resetDelay() 24 | 25 | def onMessage(self, payload, isBinary): 26 | if not isBinary: 27 | try: 28 | payload_obj = json.loads(payload.decode('utf8')) 29 | except ValueError: 30 | pass 31 | else: 32 | self.factory.callback(payload_obj) 33 | 34 | 35 | class BinanceReconnectingClientFactory(ReconnectingClientFactory): 36 | 37 | # set initial delay to a short time 38 | initialDelay = 0.1 39 | 40 | maxDelay = 10 41 | 42 | maxRetries = 5 43 | 44 | 45 | class BinanceClientFactory(WebSocketClientFactory, BinanceReconnectingClientFactory): 46 | 47 | protocol = BinanceClientProtocol 48 | _reconnect_error_payload = { 49 | 'e': 'error', 50 | 'm': 'Max reconnect retries reached' 51 | } 52 | 53 | def clientConnectionFailed(self, connector, reason): 54 | self.retry(connector) 55 | if self.retries > self.maxRetries: 56 | self.callback(self._reconnect_error_payload) 57 | 58 | def clientConnectionLost(self, connector, reason): 59 | self.retry(connector) 60 | if self.retries > self.maxRetries: 61 | self.callback(self._reconnect_error_payload) 62 | 63 | 64 | class BinanceSocketManager(threading.Thread): 65 | 66 | STREAM_URL = 'wss://stream.binance.com:9443/' 67 | 68 | WEBSOCKET_DEPTH_5 = '5' 69 | WEBSOCKET_DEPTH_10 = '10' 70 | WEBSOCKET_DEPTH_20 = '20' 71 | 72 | DEFAULT_USER_TIMEOUT = 30 * 60 # 30 minutes 73 | 74 | def __init__(self, client, user_timeout=DEFAULT_USER_TIMEOUT): 75 | """Initialise the BinanceSocketManager 76 | 77 | :param client: Binance API client 78 | :type client: binance.Client 79 | :param user_timeout: Custom websocket timeout 80 | :type user_timeout: int 81 | 82 | """ 83 | threading.Thread.__init__(self) 84 | self._conns = {} 85 | self._client = client 86 | self._user_timeout = user_timeout 87 | self._timers = {'user': None, 'margin': None} 88 | self._listen_keys = {'user': None, 'margin': None} 89 | self._account_callbacks = {'user': None, 'margin': None} 90 | 91 | def _start_socket(self, path, callback, prefix='ws/'): 92 | if path in self._conns: 93 | return False 94 | 95 | factory_url = self.STREAM_URL + prefix + path 96 | factory = BinanceClientFactory(factory_url) 97 | factory.protocol = BinanceClientProtocol 98 | factory.callback = callback 99 | factory.reconnect = True 100 | context_factory = ssl.ClientContextFactory() 101 | 102 | self._conns[path] = connectWS(factory, context_factory) 103 | return path 104 | 105 | def start_depth_socket(self, symbol, callback, depth=None): 106 | """Start a websocket for symbol market depth returning either a diff or a partial book 107 | 108 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#partial-book-depth-streams 109 | 110 | :param symbol: required 111 | :type symbol: str 112 | :param callback: callback function to handle messages 113 | :type callback: function 114 | :param depth: optional Number of depth entries to return, default None. If passed returns a partial book instead of a diff 115 | :type depth: str 116 | 117 | :returns: connection key string if successful, False otherwise 118 | 119 | Partial Message Format 120 | 121 | .. code-block:: python 122 | 123 | { 124 | "lastUpdateId": 160, # Last update ID 125 | "bids": [ # Bids to be updated 126 | [ 127 | "0.0024", # price level to be updated 128 | "10", # quantity 129 | [] # ignore 130 | ] 131 | ], 132 | "asks": [ # Asks to be updated 133 | [ 134 | "0.0026", # price level to be updated 135 | "100", # quantity 136 | [] # ignore 137 | ] 138 | ] 139 | } 140 | 141 | 142 | Diff Message Format 143 | 144 | .. code-block:: python 145 | 146 | { 147 | "e": "depthUpdate", # Event type 148 | "E": 123456789, # Event time 149 | "s": "BNBBTC", # Symbol 150 | "U": 157, # First update ID in event 151 | "u": 160, # Final update ID in event 152 | "b": [ # Bids to be updated 153 | [ 154 | "0.0024", # price level to be updated 155 | "10", # quantity 156 | [] # ignore 157 | ] 158 | ], 159 | "a": [ # Asks to be updated 160 | [ 161 | "0.0026", # price level to be updated 162 | "100", # quantity 163 | [] # ignore 164 | ] 165 | ] 166 | } 167 | 168 | """ 169 | socket_name = symbol.lower() + '@depth' 170 | if depth and depth != '1': 171 | socket_name = '{}{}'.format(socket_name, depth) 172 | return self._start_socket(socket_name, callback) 173 | 174 | def start_kline_socket(self, symbol, callback, interval=Client.KLINE_INTERVAL_1MINUTE): 175 | """Start a websocket for symbol kline data 176 | 177 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#klinecandlestick-streams 178 | 179 | :param symbol: required 180 | :type symbol: str 181 | :param callback: callback function to handle messages 182 | :type callback: function 183 | :param interval: Kline interval, default KLINE_INTERVAL_1MINUTE 184 | :type interval: str 185 | 186 | :returns: connection key string if successful, False otherwise 187 | 188 | Message Format 189 | 190 | .. code-block:: python 191 | 192 | { 193 | "e": "kline", # event type 194 | "E": 1499404907056, # event time 195 | "s": "ETHBTC", # symbol 196 | "k": { 197 | "t": 1499404860000, # start time of this bar 198 | "T": 1499404919999, # end time of this bar 199 | "s": "ETHBTC", # symbol 200 | "i": "1m", # interval 201 | "f": 77462, # first trade id 202 | "L": 77465, # last trade id 203 | "o": "0.10278577", # open 204 | "c": "0.10278645", # close 205 | "h": "0.10278712", # high 206 | "l": "0.10278518", # low 207 | "v": "17.47929838", # volume 208 | "n": 4, # number of trades 209 | "x": false, # whether this bar is final 210 | "q": "1.79662878", # quote volume 211 | "V": "2.34879839", # volume of active buy 212 | "Q": "0.24142166", # quote volume of active buy 213 | "B": "13279784.01349473" # can be ignored 214 | } 215 | } 216 | """ 217 | socket_name = '{}@kline_{}'.format(symbol.lower(), interval) 218 | return self._start_socket(socket_name, callback) 219 | 220 | def start_miniticker_socket(self, callback, update_time=1000): 221 | """Start a miniticker websocket for all trades 222 | 223 | This is not in the official Binance api docs, but this is what 224 | feeds the right column on a ticker page on Binance. 225 | 226 | :param callback: callback function to handle messages 227 | :type callback: function 228 | :param update_time: time between callbacks in milliseconds, must be 1000 or greater 229 | :type update_time: int 230 | 231 | :returns: connection key string if successful, False otherwise 232 | 233 | Message Format 234 | 235 | .. code-block:: python 236 | 237 | [ 238 | { 239 | 'e': '24hrMiniTicker', # Event type 240 | 'E': 1515906156273, # Event time 241 | 's': 'QTUMETH', # Symbol 242 | 'c': '0.03836900', # close 243 | 'o': '0.03953500', # open 244 | 'h': '0.04400000', # high 245 | 'l': '0.03756000', # low 246 | 'v': '147435.80000000', # volume 247 | 'q': '5903.84338533' # quote volume 248 | } 249 | ] 250 | """ 251 | 252 | return self._start_socket('!miniTicker@arr@{}ms'.format(update_time), callback) 253 | 254 | def start_trade_socket(self, symbol, callback): 255 | """Start a websocket for symbol trade data 256 | 257 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#trade-streams 258 | 259 | :param symbol: required 260 | :type symbol: str 261 | :param callback: callback function to handle messages 262 | :type callback: function 263 | 264 | :returns: connection key string if successful, False otherwise 265 | 266 | Message Format 267 | 268 | .. code-block:: python 269 | 270 | { 271 | "e": "trade", # Event type 272 | "E": 123456789, # Event time 273 | "s": "BNBBTC", # Symbol 274 | "t": 12345, # Trade ID 275 | "p": "0.001", # Price 276 | "q": "100", # Quantity 277 | "b": 88, # Buyer order Id 278 | "a": 50, # Seller order Id 279 | "T": 123456785, # Trade time 280 | "m": true, # Is the buyer the market maker? 281 | "M": true # Ignore. 282 | } 283 | 284 | """ 285 | return self._start_socket(symbol.lower() + '@trade', callback) 286 | 287 | def start_aggtrade_socket(self, symbol, callback): 288 | """Start a websocket for symbol trade data 289 | 290 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#aggregate-trade-streams 291 | 292 | :param symbol: required 293 | :type symbol: str 294 | :param callback: callback function to handle messages 295 | :type callback: function 296 | 297 | :returns: connection key string if successful, False otherwise 298 | 299 | Message Format 300 | 301 | .. code-block:: python 302 | 303 | { 304 | "e": "aggTrade", # event type 305 | "E": 1499405254326, # event time 306 | "s": "ETHBTC", # symbol 307 | "a": 70232, # aggregated tradeid 308 | "p": "0.10281118", # price 309 | "q": "8.15632997", # quantity 310 | "f": 77489, # first breakdown trade id 311 | "l": 77489, # last breakdown trade id 312 | "T": 1499405254324, # trade time 313 | "m": false, # whether buyer is a maker 314 | "M": true # can be ignored 315 | } 316 | 317 | """ 318 | return self._start_socket(symbol.lower() + '@aggTrade', callback) 319 | 320 | def start_symbol_ticker_socket(self, symbol, callback): 321 | """Start a websocket for a symbol's ticker data 322 | 323 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#individual-symbol-ticker-streams 324 | 325 | :param symbol: required 326 | :type symbol: str 327 | :param callback: callback function to handle messages 328 | :type callback: function 329 | 330 | :returns: connection key string if successful, False otherwise 331 | 332 | Message Format 333 | 334 | .. code-block:: python 335 | 336 | { 337 | "e": "24hrTicker", # Event type 338 | "E": 123456789, # Event time 339 | "s": "BNBBTC", # Symbol 340 | "p": "0.0015", # Price change 341 | "P": "250.00", # Price change percent 342 | "w": "0.0018", # Weighted average price 343 | "x": "0.0009", # Previous day's close price 344 | "c": "0.0025", # Current day's close price 345 | "Q": "10", # Close trade's quantity 346 | "b": "0.0024", # Best bid price 347 | "B": "10", # Bid bid quantity 348 | "a": "0.0026", # Best ask price 349 | "A": "100", # Best ask quantity 350 | "o": "0.0010", # Open price 351 | "h": "0.0025", # High price 352 | "l": "0.0010", # Low price 353 | "v": "10000", # Total traded base asset volume 354 | "q": "18", # Total traded quote asset volume 355 | "O": 0, # Statistics open time 356 | "C": 86400000, # Statistics close time 357 | "F": 0, # First trade ID 358 | "L": 18150, # Last trade Id 359 | "n": 18151 # Total number of trades 360 | } 361 | 362 | """ 363 | return self._start_socket(symbol.lower() + '@ticker', callback) 364 | 365 | def start_ticker_socket(self, callback): 366 | """Start a websocket for all ticker data 367 | 368 | By default all markets are included in an array. 369 | 370 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#all-market-tickers-stream 371 | 372 | :param callback: callback function to handle messages 373 | :type callback: function 374 | 375 | :returns: connection key string if successful, False otherwise 376 | 377 | Message Format 378 | 379 | .. code-block:: python 380 | 381 | [ 382 | { 383 | 'F': 278610, 384 | 'o': '0.07393000', 385 | 's': 'BCCBTC', 386 | 'C': 1509622420916, 387 | 'b': '0.07800800', 388 | 'l': '0.07160300', 389 | 'h': '0.08199900', 390 | 'L': 287722, 391 | 'P': '6.694', 392 | 'Q': '0.10000000', 393 | 'q': '1202.67106335', 394 | 'p': '0.00494900', 395 | 'O': 1509536020916, 396 | 'a': '0.07887800', 397 | 'n': 9113, 398 | 'B': '1.00000000', 399 | 'c': '0.07887900', 400 | 'x': '0.07399600', 401 | 'w': '0.07639068', 402 | 'A': '2.41900000', 403 | 'v': '15743.68900000' 404 | } 405 | ] 406 | """ 407 | return self._start_socket('!ticker@arr', callback) 408 | 409 | def start_symbol_book_ticker_socket(self, symbol, callback): 410 | """Start a websocket for the best bid or ask's price or quantity for a specified symbol. 411 | 412 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#individual-symbol-book-ticker-streams 413 | 414 | :param symbol: required 415 | :type symbol: str 416 | :param callback: callback function to handle messages 417 | :type callback: function 418 | 419 | :returns: connection key string if successful, False otherwise 420 | 421 | Message Format 422 | 423 | .. code-block:: python 424 | 425 | { 426 | "u":400900217, // order book updateId 427 | "s":"BNBUSDT", // symbol 428 | "b":"25.35190000", // best bid price 429 | "B":"31.21000000", // best bid qty 430 | "a":"25.36520000", // best ask price 431 | "A":"40.66000000" // best ask qty 432 | } 433 | 434 | """ 435 | return self._start_socket(symbol.lower() + '@bookTicker', callback) 436 | 437 | def start_book_ticker_socket(self, callback): 438 | """Start a websocket for the best bid or ask's price or quantity for all symbols. 439 | 440 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#all-book-tickers-stream 441 | 442 | :param callback: callback function to handle messages 443 | :type callback: function 444 | 445 | :returns: connection key string if successful, False otherwise 446 | 447 | Message Format 448 | 449 | .. code-block:: python 450 | 451 | { 452 | // Same as @bookTicker payload 453 | } 454 | 455 | """ 456 | return self._start_socket('!bookTicker', callback) 457 | 458 | def start_multiplex_socket(self, streams, callback): 459 | """Start a multiplexed socket using a list of socket names. 460 | User stream sockets can not be included. 461 | 462 | Symbols in socket name must be lowercase i.e bnbbtc@aggTrade, neobtc@ticker 463 | 464 | Combined stream events are wrapped as follows: {"stream":"","data":} 465 | 466 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md 467 | 468 | :param streams: list of stream names in lower case 469 | :type streams: list 470 | :param callback: callback function to handle messages 471 | :type callback: function 472 | 473 | :returns: connection key string if successful, False otherwise 474 | 475 | Message Format - see Binance API docs for all types 476 | 477 | """ 478 | stream_path = 'streams={}'.format('/'.join(streams)) 479 | return self._start_socket(stream_path, callback, 'stream?') 480 | 481 | def start_user_socket(self, callback): 482 | """Start a websocket for user data 483 | 484 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md 485 | 486 | :param callback: callback function to handle messages 487 | :type callback: function 488 | 489 | :returns: connection key string if successful, False otherwise 490 | 491 | Message Format - see Binance API docs for all types 492 | """ 493 | # Get the user listen key 494 | user_listen_key = self._client.stream_get_listen_key() 495 | # and start the socket with this specific key 496 | return self._start_account_socket('user', user_listen_key, callback) 497 | 498 | def start_margin_socket(self, callback): 499 | """Start a websocket for margin data 500 | 501 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md 502 | 503 | :param callback: callback function to handle messages 504 | :type callback: function 505 | 506 | :returns: connection key string if successful, False otherwise 507 | 508 | Message Format - see Binance API docs for all types 509 | """ 510 | # Get the user margin listen key 511 | margin_listen_key = self._client.margin_stream_get_listen_key() 512 | # and start the socket with this specific key 513 | return self._start_account_socket('margin', margin_listen_key, callback) 514 | 515 | def _start_account_socket(self, socket_type, listen_key, callback): 516 | """Starts one of user or margin socket""" 517 | self._check_account_socket_open(listen_key) 518 | self._listen_keys[socket_type] = listen_key 519 | self._account_callbacks[socket_type] = callback 520 | conn_key = self._start_socket(listen_key, callback) 521 | if conn_key: 522 | # start timer to keep socket alive 523 | self._start_socket_timer(socket_type) 524 | return conn_key 525 | 526 | def _check_account_socket_open(self, listen_key): 527 | if not listen_key: 528 | return 529 | for conn_key in self._conns: 530 | if len(conn_key) >= 60 and conn_key[:60] == listen_key: 531 | self.stop_socket(conn_key) 532 | break 533 | 534 | def _start_socket_timer(self, socket_type): 535 | callback = self._keepalive_account_socket 536 | 537 | self._timers[socket_type] = threading.Timer(self._user_timeout, callback, [socket_type]) 538 | self._timers[socket_type].setDaemon(True) 539 | self._timers[socket_type].start() 540 | 541 | def _keepalive_account_socket(self, socket_type): 542 | if socket_type == 'user': 543 | listen_key_func = self._client.stream_get_listen_key 544 | callback = self._account_callbacks[socket_type] 545 | else: 546 | listen_key_func = self._client.margin_stream_get_listen_key 547 | callback = self._account_callbacks[socket_type] 548 | listen_key = listen_key_func() 549 | if listen_key != self._listen_keys[socket_type]: 550 | self._start_account_socket(socket_type, listen_key, callback) 551 | 552 | def stop_socket(self, conn_key): 553 | """Stop a websocket given the connection key 554 | 555 | :param conn_key: Socket connection key 556 | :type conn_key: string 557 | 558 | :returns: connection key string if successful, False otherwise 559 | """ 560 | if conn_key not in self._conns: 561 | return 562 | 563 | # disable reconnecting if we are closing 564 | self._conns[conn_key].factory = WebSocketClientFactory(self.STREAM_URL + 'tmp_path') 565 | self._conns[conn_key].disconnect() 566 | del(self._conns[conn_key]) 567 | 568 | # check if we have a user stream socket 569 | if len(conn_key) >= 60 and conn_key[:60] == self._listen_keys['user']: 570 | self._stop_account_socket('user') 571 | 572 | # or a margin stream socket 573 | if len(conn_key) >= 60 and conn_key[:60] == self._listen_keys['margin']: 574 | self._stop_account_socket('margin') 575 | 576 | def _stop_account_socket(self, socket_type): 577 | if not self._listen_keys[socket_type]: 578 | return 579 | if self._timers[socket_type]: 580 | self._timers[socket_type].cancel() 581 | self._timers[socket_type] = None 582 | self._listen_keys[socket_type] = None 583 | 584 | def run(self): 585 | try: 586 | reactor.run(installSignalHandlers=False) 587 | except ReactorAlreadyRunning: 588 | # Ignore error about reactor already running 589 | pass 590 | 591 | def close(self): 592 | """Close all connections 593 | 594 | """ 595 | keys = set(self._conns.keys()) 596 | for key in keys: 597 | self.stop_socket(key) 598 | 599 | self._conns = {} 600 | 601 | class BinanceFuturesSocketManager(threading.Thread): 602 | 603 | STREAM_URL = "wss://fstream.binance.com/" 604 | 605 | WEBSOCKET_DEPTH_5 = "5" 606 | WEBSOCKET_DEPTH_10 = "10" 607 | WEBSOCKET_DEPTH_20 = "20" 608 | 609 | DEFAULT_USER_TIMEOUT = 30 * 60 # 30 minutes 610 | 611 | def __init__(self, client, user_timeout=DEFAULT_USER_TIMEOUT): 612 | """Initialise the BinanceSocketManager 613 | 614 | :param client: Binance API client 615 | :type client: binance.Client 616 | :param user_timeout: Custom websocket timeout 617 | :type user_timeout: int 618 | 619 | """ 620 | threading.Thread.__init__(self) 621 | self._conns = {} 622 | self._client = client 623 | self._user_timeout = user_timeout 624 | self._timers = {"user": None, "margin": None, "futures": None} 625 | self._listen_keys = {"user": None, "margin": None, "futures": None} 626 | self._account_callbacks = {"user": None, "margin": None, "futures": None} 627 | 628 | def _start_socket(self, path, callback, prefix="ws/"): 629 | if path in self._conns: 630 | return False 631 | 632 | factory_url = self.STREAM_URL + prefix + path 633 | factory = BinanceClientFactory(factory_url) 634 | factory.protocol = BinanceClientProtocol 635 | factory.callback = callback 636 | factory.reconnect = True 637 | context_factory = ssl.ClientContextFactory() 638 | 639 | self._conns[path] = connectWS(factory, context_factory) 640 | return path 641 | 642 | def start_futures_aggtrade_socket(self, symbol, callback): 643 | return self._start_socket(symbol.lower() + "@aggTrade", callback) 644 | 645 | def start_futures_symbol_markprice_socket(self, symbol, callback, update_time=None): 646 | socket_name = symbol.lower() + "@markPrice" 647 | if update_time: 648 | socket_name = "{}@{}s".format(socket_name, update_time) 649 | return self._start_socket(socket_name, callback) 650 | 651 | def start_futures_markprice_socket(self, callback, update_time=None): 652 | socket_name = "!markPrice@arr" 653 | if update_time: 654 | socket_name = "{}@{}s".format(socket_name, update_time) 655 | return self._start_socket(socket_name, callback) 656 | 657 | def start_futures_kline_socket( 658 | self, symbol, callback, interval=Client.KLINE_INTERVAL_1MINUTE 659 | ): 660 | socket_name = "{}@kline_{}".format(symbol.lower(), interval) 661 | return self._start_socket(socket_name, callback) 662 | 663 | def start_futures_symbol_miniticker_socket(self, symbol, callback): 664 | return self._start_socket(symbol.lower() + "@miniTicker", callback) 665 | 666 | def start_futures_miniticker_socket(self, callback): 667 | return self._start_socket("!miniTicker@arr", callback) 668 | 669 | def start_futures_symbol_ticker_socket(self, symbol, callback): 670 | return self._start_socket(symbol.lower() + "@ticker", callback) 671 | 672 | def start_futures_ticker_socket(self, callback): 673 | return self._start_socket("!ticker@arr", callback) 674 | 675 | def start_futures_symbol_book_ticker_socket(self, symbol, callback): 676 | return self._start_socket(symbol.lower() + "@bookTicker", callback) 677 | 678 | def startt_futures_book_ticker_socket(self, callback): 679 | return self._start_socket("!bookTicker", callback) 680 | 681 | def start_futures_symbol_force_order_socket(self, symbol, callback): 682 | return self._start_socket(symbol.lower() + "@forceOrder", callback) 683 | 684 | def start_futures_force_order_socket(self, callback): 685 | return self._start_socket("!forceOrder@arr", callback) 686 | 687 | def start_futures_depth_socket( 688 | self, symbol, callback, depth=None, update_time=None 689 | ): 690 | socket_name = symbol.lower() + "@depth" 691 | if depth: 692 | socket_name = "{}{}".format(socket_name, depth) 693 | if update_time: 694 | socket_name = "{}@{}ms".format(socket_name, update_time) 695 | return self._start_socket(socket_name, callback) 696 | 697 | def start_futures_multiplex_socket(self, streams, callback): 698 | """Start a multiplexed socket using a list of socket names. 699 | User stream sockets can not be included. 700 | 701 | Symbols in socket name must be lowercase i.e bnbbtc@aggTrade, neobtc@ticker 702 | 703 | Combined stream events are wrapped as follows: {"stream":"","data":} 704 | 705 | https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md 706 | 707 | :param streams: list of stream names in lower case 708 | :type streams: list 709 | :param callback: callback function to handle messages 710 | :type callback: function 711 | 712 | :returns: connection key string if successful, False otherwise 713 | 714 | Message Format - see Binance API docs for all types 715 | 716 | """ 717 | stream_path = "streams={}".format("/".join(streams)) 718 | return self._start_socket(stream_path, callback, "stream?") 719 | 720 | def start_futures_socket(self, callback): 721 | # Get the user listen key 722 | futures_listen_key = self._client.futures_stream_get_listen_key() 723 | # and start the socket with this specific key 724 | return self._start_account_socket("futures", futures_listen_key, callback) 725 | 726 | def _start_account_socket(self, socket_type, listen_key, callback): 727 | """Starts one of user or margin socket""" 728 | self._check_account_socket_open(listen_key) 729 | self._listen_keys[socket_type] = listen_key 730 | self._account_callbacks[socket_type] = callback 731 | conn_key = self._start_socket(listen_key, callback) 732 | if conn_key: 733 | # start timer to keep socket alive 734 | self._start_socket_timer(socket_type) 735 | return conn_key 736 | 737 | def _check_account_socket_open(self, listen_key): 738 | if not listen_key: 739 | return 740 | for conn_key in self._conns: 741 | if len(conn_key) >= 60 and conn_key[:60] == listen_key: 742 | self.stop_socket(conn_key) 743 | break 744 | 745 | def _start_socket_timer(self, socket_type): 746 | callback = self._keepalive_account_socket 747 | 748 | self._timers[socket_type] = threading.Timer( 749 | self._user_timeout, callback, [socket_type] 750 | ) 751 | self._timers[socket_type].setDaemon(True) 752 | self._timers[socket_type].start() 753 | 754 | def _keepalive_account_socket(self, socket_type): 755 | if socket_type == "user": 756 | listen_key_func = self._client.stream_get_listen_key 757 | callback = self._account_callbacks[socket_type] 758 | elif socket_type == "margin": 759 | listen_key_func = self._client.margin_stream_get_listen_key 760 | callback = self._account_callbacks[socket_type] 761 | else: 762 | listen_key_func = self._client.futures_stream_get_listen_key 763 | callback = self._account_callbacks[socket_type] 764 | listen_key = listen_key_func() 765 | if listen_key != self._listen_keys[socket_type]: 766 | self._start_account_socket(socket_type, listen_key, callback) 767 | 768 | def stop_socket(self, conn_key): 769 | """Stop a websocket given the connection key 770 | 771 | :param conn_key: Socket connection key 772 | :type conn_key: string 773 | 774 | :returns: connection key string if successful, False otherwise 775 | """ 776 | if conn_key not in self._conns: 777 | return 778 | 779 | # disable reconnecting if we are closing 780 | self._conns[conn_key].factory = WebSocketClientFactory( 781 | self.STREAM_URL + "tmp_path" 782 | ) 783 | self._conns[conn_key].disconnect() 784 | del self._conns[conn_key] 785 | 786 | # check if we have a user stream socket 787 | if len(conn_key) >= 60 and conn_key[:60] == self._listen_keys["user"]: 788 | self._stop_account_socket("user") 789 | 790 | # or a margin stream socket 791 | if len(conn_key) >= 60 and conn_key[:60] == self._listen_keys["margin"]: 792 | self._stop_account_socket("margin") 793 | 794 | if len(conn_key) >= 60 and conn_key[:60] == self._listen_keys["futures"]: 795 | self._stop_account_socket("futures") 796 | 797 | def _stop_account_socket(self, socket_type): 798 | if not self._listen_keys[socket_type]: 799 | return 800 | if self._timers[socket_type]: 801 | self._timers[socket_type].cancel() 802 | self._timers[socket_type] = None 803 | self._listen_keys[socket_type] = None 804 | 805 | def run(self): 806 | try: 807 | reactor.run(installSignalHandlers=False) 808 | except ReactorAlreadyRunning: 809 | # Ignore error about reactor already running 810 | pass 811 | 812 | def close(self): 813 | """Close all connections 814 | 815 | """ 816 | keys = set(self._conns.keys()) 817 | for key in keys: 818 | self.stop_socket(key) 819 | 820 | self._conns = {} -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | from binance.client import Client 2 | from binance.websockets import BinanceFuturesSocketManager 3 | from binance.enums import * 4 | 5 | 6 | def process_message(msg): 7 | # Do Something 8 | print(msg) 9 | 10 | 11 | api_key = "" 12 | api_secret = "" 13 | client = Client(api_key, api_secret) 14 | 15 | bfm = BinanceFuturesSocketManager(client) 16 | 17 | # Aggregate Trade Streams 18 | # bfm.start_futures_aggtrade_socket('BTCUSDT',process_message) 19 | 20 | # Mark Price Stream 21 | # bfm.start_futures_symbol_markprice_socket('BTCUSDT',process_message) 22 | # bfm.start_futures_symbol_markprice_socket('BTCUSDT',process_message,update_time=1) 23 | 24 | # Mark Price Stream for All market 25 | # bfm.start_futures_markprice_socket(process_message) 26 | # bfm.start_futures_markprice_socket(process_message,update_time=1) 27 | 28 | # Kline/Candlestick Streams 29 | # bfm.start_futures_kline_socket('BTCUSDT',process_message,interval=KLINE_INTERVAL_1MINUTE) 30 | 31 | # Individual Symbol Mini Ticker Stream 32 | # bfm.start_futures_symbol_miniticker_socket('BTCUSDT',process_message) 33 | 34 | # All Market Mini Tickers Stream 35 | # bfm.start_futures_miniticker_socket(process_message) 36 | 37 | # Individual Symbol Ticker Streams 38 | # bfm.start_futures_symbol_ticker_socket('BTCUSDT',process_message) 39 | 40 | # All Market Tickers Streams 41 | # bfm.start_futures_ticker_socket(process_message) 42 | 43 | # Individual Symbol Book Ticker Streams 44 | # bfm.start_futures_symbol_book_ticker_socket('BTCUSDT',process_message) 45 | 46 | # All Book Tickers Stream 47 | # bfm.startt_futures_book_ticker_socket(process_message) 48 | 49 | # Liquidation Order Streams 50 | # Not tested 51 | # bfm.start_futures_symbol_force_order_socket('BTCUSDT',process_message) 52 | 53 | # All Market Liquidation Order Streams 54 | # Not tested 55 | # bfm.start_futures_force_order_socket(process_message) 56 | 57 | # Partial Book Depth Streams 58 | # bfm.start_futures_depth_socket('BTCUSDT',process_message,depth=5) 59 | # bfm.start_futures_depth_socket('BTCUSDT',process_message,depth=5,update_time=500) 60 | 61 | # Diff. Book Depth Streams 62 | # bfm.start_futures_depth_socket('BTCUSDT',process_message) 63 | # bfm.start_futures_depth_socket('BTCUSDT',process_message,update_time=0) 64 | 65 | # Multiplex Socket 66 | # bfm.start_futures_multiplex_socket(['btcusdt@aggTrade', 'bnbusdt@ticker'],process_message) 67 | 68 | # User Data Streams 69 | # bfm.start_futures_socket(process_message) 70 | 71 | bfm.start() 72 | --------------------------------------------------------------------------------