├── README.md ├── pybybit ├── __init__.py ├── api.py ├── rest.py ├── util │ ├── auth.py │ ├── bybit_doc_scraping.py │ └── store.py └── ws.py └── setup.py /README.md: -------------------------------------------------------------------------------- 1 | # pybybit 2 | pybybit - Bybit API client library for Python 3 | 4 | ✅ REST API 5 | 6 | ✅ WebSocket API 7 | 8 | ✅ Public API 9 | 10 | ✅ Private API 11 | 12 | ✅ Inverse Contract (BTCUSD/ETHUSD/XRPUSD/EOSUSD) 13 | 14 | ✅ Linear Contract (BTCUSDT) 15 | 16 | Install 17 | `pip install git+https://github.com/MtkN1/pybybit.git` 18 | 19 | Japanese Document 20 | https://note.com/mtkn1/n/n9ef3460e4085 21 | 22 | Sample Code 23 | https://gist.github.com/MtkN1/6fbdf8e6629e52b36bb3b72ecaea2ebc 24 | 25 | Author 26 | https://twitter.com/MtkN1XBt 27 | -------------------------------------------------------------------------------- /pybybit/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | pybybit - Bybit API client library for Python 3 | 4 | """ 5 | 6 | from .api import API 7 | from .rest import RESTAPI 8 | from .ws import WebScoketAPI 9 | from .util.store import DataStore 10 | 11 | __VERSION__ = '2.0.4' 12 | __API_VERSION__ = '2021-06-18' 13 | -------------------------------------------------------------------------------- /pybybit/api.py: -------------------------------------------------------------------------------- 1 | from .rest import RESTAPI 2 | from .ws import WebScoketAPI 3 | from .util.auth import Authentication 4 | 5 | class API: 6 | def __init__(self, key: str='', secret: str='', testnet: bool=False): 7 | auth = Authentication(key, secret) 8 | self.rest = RESTAPI(auth, testnet) 9 | self.ws = WebScoketAPI(auth, testnet) 10 | -------------------------------------------------------------------------------- /pybybit/rest.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | class RESTAPI: 4 | _MAINNET = 'https://api.bybit.com' 5 | _TESTNET = 'https://api-testnet.bybit.com' 6 | 7 | def __init__(self, auth, testnet): 8 | self._session = requests.Session() 9 | self._auth = auth 10 | self._url = self._MAINNET if not testnet else self._TESTNET 11 | self._callbacks = [] 12 | self.inverse = Inverse(self._request) 13 | self.linear = Linear(self._request) 14 | self.futures = Futures(self._request) 15 | 16 | def _prepare(self, method: str, url: str, query: dict, private: bool) -> dict: 17 | for k in list(query): 18 | if query[k] is None: 19 | del query[k] 20 | if private: 21 | query = self._auth._prepare(query) 22 | query_str = '&'.join(f'{k}={v}' for k, v in query.items()) 23 | if private: 24 | sign = self._auth._sign(query_str) 25 | query_str += f'&{sign}' 26 | req_args = {'method': method, 'url': url} 27 | if method == 'GET': 28 | if len(query_str) > 0: 29 | req_args['url'] += f'?{query_str}' 30 | else: # method == 'POST' or other 31 | req_args['data'] = query_str 32 | req_args['headers'] = {'Content-Type': 'application/x-www-form-urlencoded'} 33 | return req_args 34 | 35 | def _request(self, method: str, path: str, query: dict, private: bool) -> requests.Response: 36 | req_args = self._prepare(method, self._url + path, query, private) 37 | resp = self._session.request(**req_args) 38 | for cb in self._callbacks: 39 | cb(resp, self._session) 40 | return resp 41 | 42 | def add_callback(self, func) -> None: 43 | if callable(func): 44 | self._callbacks.append(func) 45 | 46 | def initialize_request_inverse(self, symbol: str): 47 | return ( 48 | self.inverse.private_order(symbol=symbol), 49 | self.inverse.private_stoporder(symbol=symbol), 50 | self.inverse.private_position_list(symbol=symbol), 51 | self.inverse.private_wallet_balance(), 52 | ) 53 | 54 | def initialize_request_linear(self, symbol: str): 55 | return ( 56 | self.linear.private_order_search(symbol=symbol), 57 | self.linear.private_stoporder_search(symbol=symbol), 58 | self.linear.private_position_list(symbol=symbol), 59 | self.inverse.private_wallet_balance(), 60 | ) 61 | 62 | def initialize_request_futures(self, symbol: str): 63 | return ( 64 | self.futures.private_order(symbol=symbol), 65 | self.futures.private_stoporder(symbol=symbol), 66 | self.futures.private_position_list(symbol=symbol), 67 | self.inverse.private_wallet_balance(), 68 | ) 69 | 70 | class Inverse: 71 | def __init__(self, request: RESTAPI._request): 72 | self._request = request 73 | 74 | def public_orderbook_l2( 75 | self, 76 | symbol: str=None, 77 | ) -> requests.Response: 78 | """ 79 | Order book 80 | """ 81 | method = 'GET' 82 | path = '/v2/public/orderBook/L2' 83 | query = { 84 | 'symbol': symbol, 85 | } 86 | return self._request(method, path, query, private=False) 87 | 88 | def public_kline_list( 89 | self, 90 | symbol: str=None, 91 | interval: str=None, 92 | from_: int=None, 93 | limit: int=None, 94 | ) -> requests.Response: 95 | """ 96 | Query Kline 97 | """ 98 | method = 'GET' 99 | path = '/v2/public/kline/list' 100 | query = { 101 | 'symbol': symbol, 102 | 'interval': interval, 103 | 'from': from_, 104 | 'limit': limit, 105 | } 106 | return self._request(method, path, query, private=False) 107 | 108 | def public_tickers( 109 | self, 110 | symbol: str=None, 111 | ) -> requests.Response: 112 | """ 113 | Latest Information for Symbol 114 | """ 115 | method = 'GET' 116 | path = '/v2/public/tickers' 117 | query = { 118 | 'symbol': symbol, 119 | } 120 | return self._request(method, path, query, private=False) 121 | 122 | def public_tradingrecords( 123 | self, 124 | symbol: str=None, 125 | from_: int=None, 126 | limit: int=None, 127 | ) -> requests.Response: 128 | """ 129 | Public Trading Records 130 | """ 131 | method = 'GET' 132 | path = '/v2/public/trading-records' 133 | query = { 134 | 'symbol': symbol, 135 | 'from': from_, 136 | 'limit': limit, 137 | } 138 | return self._request(method, path, query, private=False) 139 | 140 | def public_symbols( 141 | self, 142 | ) -> requests.Response: 143 | """ 144 | Query Symbol 145 | """ 146 | method = 'GET' 147 | path = '/v2/public/symbols' 148 | query = { 149 | } 150 | return self._request(method, path, query, private=False) 151 | 152 | def public_liqrecords( 153 | self, 154 | symbol: str=None, 155 | from_: int=None, 156 | limit: int=None, 157 | start_time: int=None, 158 | end_time: int=None, 159 | ) -> requests.Response: 160 | """ 161 | Liquidated Orders 162 | """ 163 | method = 'GET' 164 | path = '/v2/public/liq-records' 165 | query = { 166 | 'symbol': symbol, 167 | 'from': from_, 168 | 'limit': limit, 169 | 'start_time': start_time, 170 | 'end_time': end_time, 171 | } 172 | return self._request(method, path, query, private=False) 173 | 174 | def public_markpricekline( 175 | self, 176 | symbol: str=None, 177 | interval: str=None, 178 | from_: int=None, 179 | limit: int=None, 180 | ) -> requests.Response: 181 | """ 182 | Query Mark Price Kline 183 | """ 184 | method = 'GET' 185 | path = '/v2/public/mark-price-kline' 186 | query = { 187 | 'symbol': symbol, 188 | 'interval': interval, 189 | 'from': from_, 190 | 'limit': limit, 191 | } 192 | return self._request(method, path, query, private=False) 193 | 194 | def public_indexpricekline( 195 | self, 196 | symbol: str=None, 197 | interval: str=None, 198 | from_: int=None, 199 | limit: int=None, 200 | ) -> requests.Response: 201 | """ 202 | Query Index Price Kline 203 | """ 204 | method = 'GET' 205 | path = '/v2/public/index-price-kline' 206 | query = { 207 | 'symbol': symbol, 208 | 'interval': interval, 209 | 'from': from_, 210 | 'limit': limit, 211 | } 212 | return self._request(method, path, query, private=False) 213 | 214 | def public_premiumindexkline( 215 | self, 216 | symbol: str=None, 217 | interval: str=None, 218 | from_: int=None, 219 | limit: int=None, 220 | ) -> requests.Response: 221 | """ 222 | Query Premium Index Kline 223 | """ 224 | method = 'GET' 225 | path = '/v2/public/premium-index-kline' 226 | query = { 227 | 'symbol': symbol, 228 | 'interval': interval, 229 | 'from': from_, 230 | 'limit': limit, 231 | } 232 | return self._request(method, path, query, private=False) 233 | 234 | def public_openinterest( 235 | self, 236 | symbol: str=None, 237 | period: str=None, 238 | limit: int=None, 239 | ) -> requests.Response: 240 | """ 241 | Open Interest 242 | """ 243 | method = 'GET' 244 | path = '/v2/public/open-interest' 245 | query = { 246 | 'symbol': symbol, 247 | 'period': period, 248 | 'limit': limit, 249 | } 250 | return self._request(method, path, query, private=False) 251 | 252 | def public_bigdeal( 253 | self, 254 | symbol: str=None, 255 | limit: int=None, 256 | ) -> requests.Response: 257 | """ 258 | Latest Big Deal 259 | """ 260 | method = 'GET' 261 | path = '/v2/public/big-deal' 262 | query = { 263 | 'symbol': symbol, 264 | 'limit': limit, 265 | } 266 | return self._request(method, path, query, private=False) 267 | 268 | def public_accountratio( 269 | self, 270 | symbol: str=None, 271 | period: str=None, 272 | limit: int=None, 273 | ) -> requests.Response: 274 | """ 275 | Long-Short Ratio 276 | """ 277 | method = 'GET' 278 | path = '/v2/public/account-ratio' 279 | query = { 280 | 'symbol': symbol, 281 | 'period': period, 282 | 'limit': limit, 283 | } 284 | return self._request(method, path, query, private=False) 285 | 286 | def private_order_create( 287 | self, 288 | side: str=None, 289 | symbol: str=None, 290 | order_type: str=None, 291 | qty: int=None, 292 | price: float=None, 293 | time_in_force: str=None, 294 | take_profit: float=None, 295 | stop_loss: float=None, 296 | tp_trigger_by: str=None, 297 | sl_trigger_by: str=None, 298 | reduce_only: bool=None, 299 | close_on_trigger: bool=None, 300 | order_link_id: str=None, 301 | ) -> requests.Response: 302 | """ 303 | Place Active Order 304 | """ 305 | method = 'POST' 306 | path = '/v2/private/order/create' 307 | query = { 308 | 'side': side, 309 | 'symbol': symbol, 310 | 'order_type': order_type, 311 | 'qty': qty, 312 | 'price': price, 313 | 'time_in_force': time_in_force, 314 | 'take_profit': take_profit, 315 | 'stop_loss': stop_loss, 316 | 'tp_trigger_by': tp_trigger_by, 317 | 'sl_trigger_by': sl_trigger_by, 318 | 'reduce_only': reduce_only, 319 | 'close_on_trigger': close_on_trigger, 320 | 'order_link_id': order_link_id, 321 | } 322 | return self._request(method, path, query, private=True) 323 | 324 | def private_order_list( 325 | self, 326 | symbol: str=None, 327 | order_status: str=None, 328 | direction: str=None, 329 | limit: int=None, 330 | cursor: str=None, 331 | ) -> requests.Response: 332 | """ 333 | Get Active Order 334 | """ 335 | method = 'GET' 336 | path = '/v2/private/order/list' 337 | query = { 338 | 'symbol': symbol, 339 | 'order_status': order_status, 340 | 'direction': direction, 341 | 'limit': limit, 342 | 'cursor': cursor, 343 | } 344 | return self._request(method, path, query, private=True) 345 | 346 | def private_order_cancel( 347 | self, 348 | symbol: str=None, 349 | order_id: str=None, 350 | order_link_id: str=None, 351 | ) -> requests.Response: 352 | """ 353 | Cancel Active Order 354 | """ 355 | method = 'POST' 356 | path = '/v2/private/order/cancel' 357 | query = { 358 | 'symbol': symbol, 359 | 'order_id': order_id, 360 | 'order_link_id': order_link_id, 361 | } 362 | return self._request(method, path, query, private=True) 363 | 364 | def private_order_cancelall( 365 | self, 366 | symbol: str=None, 367 | ) -> requests.Response: 368 | """ 369 | Cancel All Active Orders 370 | """ 371 | method = 'POST' 372 | path = '/v2/private/order/cancelAll' 373 | query = { 374 | 'symbol': symbol, 375 | } 376 | return self._request(method, path, query, private=True) 377 | 378 | def private_order_replace( 379 | self, 380 | order_id: str=None, 381 | order_link_id: str=None, 382 | symbol: str=None, 383 | p_r_qty: str=None, 384 | p_r_price: str=None, 385 | take_profit: float=None, 386 | stop_loss: float=None, 387 | tp_trigger_by: str=None, 388 | sl_trigger_by: str=None, 389 | ) -> requests.Response: 390 | """ 391 | Replace Active Order 392 | """ 393 | method = 'POST' 394 | path = '/v2/private/order/replace' 395 | query = { 396 | 'order_id': order_id, 397 | 'order_link_id': order_link_id, 398 | 'symbol': symbol, 399 | 'p_r_qty': p_r_qty, 400 | 'p_r_price': p_r_price, 401 | 'take_profit': take_profit, 402 | 'stop_loss': stop_loss, 403 | 'tp_trigger_by': tp_trigger_by, 404 | 'sl_trigger_by': sl_trigger_by, 405 | } 406 | return self._request(method, path, query, private=True) 407 | 408 | def private_order( 409 | self, 410 | symbol: str=None, 411 | order_id: str=None, 412 | order_link_id: str=None, 413 | ) -> requests.Response: 414 | """ 415 | Query Active Order (real-time) 416 | """ 417 | method = 'GET' 418 | path = '/v2/private/order' 419 | query = { 420 | 'symbol': symbol, 421 | 'order_id': order_id, 422 | 'order_link_id': order_link_id, 423 | } 424 | return self._request(method, path, query, private=True) 425 | 426 | def private_stoporder_create( 427 | self, 428 | side: str=None, 429 | symbol: str=None, 430 | order_type: str=None, 431 | qty: str=None, 432 | price: str=None, 433 | base_price: str=None, 434 | stop_px: str=None, 435 | time_in_force: str=None, 436 | trigger_by: str=None, 437 | close_on_trigger: bool=None, 438 | order_link_id: str=None, 439 | take_profit: float=None, 440 | stop_loss: float=None, 441 | tp_trigger_by: str=None, 442 | sl_trigger_by: str=None, 443 | ) -> requests.Response: 444 | """ 445 | Place Conditional Order 446 | """ 447 | method = 'POST' 448 | path = '/v2/private/stop-order/create' 449 | query = { 450 | 'side': side, 451 | 'symbol': symbol, 452 | 'order_type': order_type, 453 | 'qty': qty, 454 | 'price': price, 455 | 'base_price': base_price, 456 | 'stop_px': stop_px, 457 | 'time_in_force': time_in_force, 458 | 'trigger_by': trigger_by, 459 | 'close_on_trigger': close_on_trigger, 460 | 'order_link_id': order_link_id, 461 | 'take_profit': take_profit, 462 | 'stop_loss': stop_loss, 463 | 'tp_trigger_by': tp_trigger_by, 464 | 'sl_trigger_by': sl_trigger_by, 465 | } 466 | return self._request(method, path, query, private=True) 467 | 468 | def private_stoporder_list( 469 | self, 470 | symbol: str=None, 471 | stop_order_status: str=None, 472 | direction: str=None, 473 | limit: int=None, 474 | cursor: str=None, 475 | ) -> requests.Response: 476 | """ 477 | Get Conditional Order 478 | """ 479 | method = 'GET' 480 | path = '/v2/private/stop-order/list' 481 | query = { 482 | 'symbol': symbol, 483 | 'stop_order_status': stop_order_status, 484 | 'direction': direction, 485 | 'limit': limit, 486 | 'cursor': cursor, 487 | } 488 | return self._request(method, path, query, private=True) 489 | 490 | def private_stoporder_cancel( 491 | self, 492 | symbol: str=None, 493 | stop_order_id: str=None, 494 | order_link_id: str=None, 495 | ) -> requests.Response: 496 | """ 497 | Cancel Conditional Order 498 | """ 499 | method = 'POST' 500 | path = '/v2/private/stop-order/cancel' 501 | query = { 502 | 'symbol': symbol, 503 | 'stop_order_id': stop_order_id, 504 | 'order_link_id': order_link_id, 505 | } 506 | return self._request(method, path, query, private=True) 507 | 508 | def private_stoporder_cancelall( 509 | self, 510 | symbol: str=None, 511 | ) -> requests.Response: 512 | """ 513 | Cancel All Conditional Orders 514 | """ 515 | method = 'POST' 516 | path = '/v2/private/stop-order/cancelAll' 517 | query = { 518 | 'symbol': symbol, 519 | } 520 | return self._request(method, path, query, private=True) 521 | 522 | def private_stoporder_replace( 523 | self, 524 | stop_order_id: str=None, 525 | order_link_id: str=None, 526 | symbol: str=None, 527 | p_r_qty: int=None, 528 | p_r_price: str=None, 529 | p_r_trigger_price: str=None, 530 | take_profit: float=None, 531 | stop_loss: float=None, 532 | tp_trigger_by: str=None, 533 | sl_trigger_by: str=None, 534 | ) -> requests.Response: 535 | """ 536 | Replace Conditional Order 537 | """ 538 | method = 'POST' 539 | path = '/v2/private/stop-order/replace' 540 | query = { 541 | 'stop_order_id': stop_order_id, 542 | 'order_link_id': order_link_id, 543 | 'symbol': symbol, 544 | 'p_r_qty': p_r_qty, 545 | 'p_r_price': p_r_price, 546 | 'p_r_trigger_price': p_r_trigger_price, 547 | 'take_profit': take_profit, 548 | 'stop_loss': stop_loss, 549 | 'tp_trigger_by': tp_trigger_by, 550 | 'sl_trigger_by': sl_trigger_by, 551 | } 552 | return self._request(method, path, query, private=True) 553 | 554 | def private_stoporder( 555 | self, 556 | symbol: str=None, 557 | stop_order_id: str=None, 558 | order_link_id: str=None, 559 | ) -> requests.Response: 560 | """ 561 | Query Conditional Order (real-time) 562 | """ 563 | method = 'GET' 564 | path = '/v2/private/stop-order' 565 | query = { 566 | 'symbol': symbol, 567 | 'stop_order_id': stop_order_id, 568 | 'order_link_id': order_link_id, 569 | } 570 | return self._request(method, path, query, private=True) 571 | 572 | def private_position_list( 573 | self, 574 | symbol: str=None, 575 | ) -> requests.Response: 576 | """ 577 | My Position 578 | """ 579 | method = 'GET' 580 | path = '/v2/private/position/list' 581 | query = { 582 | 'symbol': symbol, 583 | } 584 | return self._request(method, path, query, private=True) 585 | 586 | def private_position_changepositionmargin( 587 | self, 588 | symbol: str=None, 589 | margin: str=None, 590 | ) -> requests.Response: 591 | """ 592 | Change Margin 593 | """ 594 | method = 'POST' 595 | path = '/v2/private/position/change-position-margin' 596 | query = { 597 | 'symbol': symbol, 598 | 'margin': margin, 599 | } 600 | return self._request(method, path, query, private=True) 601 | 602 | def private_position_tradingstop( 603 | self, 604 | symbol: str=None, 605 | take_profit: float=None, 606 | stop_loss: float=None, 607 | trailing_stop: float=None, 608 | tp_trigger_by: str=None, 609 | sl_trigger_by: str=None, 610 | new_trailing_active: float=None, 611 | sl_size: float=None, 612 | tp_size: float=None, 613 | ) -> requests.Response: 614 | """ 615 | Set Trading-Stop 616 | """ 617 | method = 'POST' 618 | path = '/v2/private/position/trading-stop' 619 | query = { 620 | 'symbol': symbol, 621 | 'take_profit': take_profit, 622 | 'stop_loss': stop_loss, 623 | 'trailing_stop': trailing_stop, 624 | 'tp_trigger_by': tp_trigger_by, 625 | 'sl_trigger_by': sl_trigger_by, 626 | 'new_trailing_active': new_trailing_active, 627 | 'sl_size': sl_size, 628 | 'tp_size': tp_size, 629 | } 630 | return self._request(method, path, query, private=True) 631 | 632 | def private_position_leverage_save( 633 | self, 634 | symbol: str=None, 635 | leverage: float=None, 636 | ) -> requests.Response: 637 | """ 638 | Set Leverage 639 | """ 640 | method = 'POST' 641 | path = '/v2/private/position/leverage/save' 642 | query = { 643 | 'symbol': symbol, 644 | 'leverage': leverage, 645 | } 646 | return self._request(method, path, query, private=True) 647 | 648 | def private_execution_list( 649 | self, 650 | order_id: str=None, 651 | symbol: str=None, 652 | start_time: int=None, 653 | page: int=None, 654 | limit: int=None, 655 | order: str=None, 656 | ) -> requests.Response: 657 | """ 658 | User Trade Records 659 | """ 660 | method = 'GET' 661 | path = '/v2/private/execution/list' 662 | query = { 663 | 'order_id': order_id, 664 | 'symbol': symbol, 665 | 'start_time': start_time, 666 | 'page': page, 667 | 'limit': limit, 668 | 'order': order, 669 | } 670 | return self._request(method, path, query, private=True) 671 | 672 | def private_trade_closedpnl_list( 673 | self, 674 | symbol: str=None, 675 | start_time: int=None, 676 | end_time: int=None, 677 | exec_type: str=None, 678 | page: int=None, 679 | limit: int=None, 680 | ) -> requests.Response: 681 | """ 682 | Closed Profit and Loss 683 | """ 684 | method = 'GET' 685 | path = '/v2/private/trade/closed-pnl/list' 686 | query = { 687 | 'symbol': symbol, 688 | 'start_time': start_time, 689 | 'end_time': end_time, 690 | 'exec_type': exec_type, 691 | 'page': page, 692 | 'limit': limit, 693 | } 694 | return self._request(method, path, query, private=True) 695 | 696 | def private_tpsl_switchmode( 697 | self, 698 | symbol: str=None, 699 | tp_sl_mode: str=None, 700 | ) -> requests.Response: 701 | """ 702 | Full/Partial Position SL/TP Switch 703 | """ 704 | method = 'POST' 705 | path = '/v2/private/tpsl/switch-mode' 706 | query = { 707 | 'symbol': symbol, 708 | 'tp_sl_mode': tp_sl_mode, 709 | } 710 | return self._request(method, path, query, private=True) 711 | 712 | def private_position_switchisolated( 713 | self, 714 | symbol: str=None, 715 | is_isolated: bool=None, 716 | buy_leverage: float=None, 717 | sell_leverage: float=None, 718 | ) -> requests.Response: 719 | """ 720 | Cross/Isolated Margin Switch 721 | """ 722 | method = 'POST' 723 | path = '/v2/private/position/switch-isolated' 724 | query = { 725 | 'symbol': symbol, 726 | 'is_isolated': is_isolated, 727 | 'buy_leverage': buy_leverage, 728 | 'sell_leverage': sell_leverage, 729 | } 730 | return self._request(method, path, query, private=True) 731 | 732 | def public_risklimit_list( 733 | self, 734 | symbol: str=None, 735 | ) -> requests.Response: 736 | """ 737 | Get Risk Limit 738 | """ 739 | method = 'GET' 740 | path = '/v2/public/risk-limit/list' 741 | query = { 742 | 'symbol': symbol, 743 | } 744 | return self._request(method, path, query, private=False) 745 | 746 | def private_position_risklimit( 747 | self, 748 | symbol: str=None, 749 | risk_id: int=None, 750 | ) -> requests.Response: 751 | """ 752 | Set Risk Limit 753 | """ 754 | method = 'POST' 755 | path = '/v2/private/position/risk-limit' 756 | query = { 757 | 'symbol': symbol, 758 | 'risk_id': risk_id, 759 | } 760 | return self._request(method, path, query, private=True) 761 | 762 | def public_funding_prevfundingrate( 763 | self, 764 | symbol: str=None, 765 | ) -> requests.Response: 766 | """ 767 | Get the Last Funding Rate 768 | """ 769 | method = 'GET' 770 | path = '/v2/public/funding/prev-funding-rate' 771 | query = { 772 | 'symbol': symbol, 773 | } 774 | return self._request(method, path, query, private=False) 775 | 776 | def private_funding_prevfunding( 777 | self, 778 | symbol: str=None, 779 | ) -> requests.Response: 780 | """ 781 | My Last Funding Fee 782 | """ 783 | method = 'GET' 784 | path = '/v2/private/funding/prev-funding' 785 | query = { 786 | 'symbol': symbol, 787 | } 788 | return self._request(method, path, query, private=True) 789 | 790 | def private_funding_predictedfunding( 791 | self, 792 | symbol: str=None, 793 | ) -> requests.Response: 794 | """ 795 | Predicted Funding Rate and My Funding Fee 796 | """ 797 | method = 'GET' 798 | path = '/v2/private/funding/predicted-funding' 799 | query = { 800 | 'symbol': symbol, 801 | } 802 | return self._request(method, path, query, private=True) 803 | 804 | def private_account_apikey( 805 | self, 806 | ) -> requests.Response: 807 | """ 808 | API Key Info 809 | """ 810 | method = 'GET' 811 | path = '/v2/private/account/api-key' 812 | query = { 813 | } 814 | return self._request(method, path, query, private=True) 815 | 816 | def private_account_lcp( 817 | self, 818 | symbol: str=None, 819 | ) -> requests.Response: 820 | """ 821 | LCP Info 822 | """ 823 | method = 'GET' 824 | path = '/v2/private/account/lcp' 825 | query = { 826 | 'symbol': symbol, 827 | } 828 | return self._request(method, path, query, private=True) 829 | 830 | def private_wallet_balance( 831 | self, 832 | coin: str=None, 833 | ) -> requests.Response: 834 | """ 835 | Get Wallet Balance 836 | """ 837 | method = 'GET' 838 | path = '/v2/private/wallet/balance' 839 | query = { 840 | 'coin': coin, 841 | } 842 | return self._request(method, path, query, private=True) 843 | 844 | def private_wallet_fund_records( 845 | self, 846 | start_date: str=None, 847 | end_date: str=None, 848 | currency: str=None, 849 | coin: str=None, 850 | wallet_fund_type: str=None, 851 | page: int=None, 852 | limit: int=None, 853 | ) -> requests.Response: 854 | """ 855 | Wallet Fund Records 856 | """ 857 | method = 'GET' 858 | path = '/v2/private/wallet/fund/records' 859 | query = { 860 | 'start_date': start_date, 861 | 'end_date': end_date, 862 | 'currency': currency, 863 | 'coin': coin, 864 | 'wallet_fund_type': wallet_fund_type, 865 | 'page': page, 866 | 'limit': limit, 867 | } 868 | return self._request(method, path, query, private=True) 869 | 870 | def private_wallet_withdraw_list( 871 | self, 872 | start_date: str=None, 873 | end_date: str=None, 874 | coin: str=None, 875 | status: str=None, 876 | page: int=None, 877 | limit: int=None, 878 | ) -> requests.Response: 879 | """ 880 | Withdraw Records 881 | """ 882 | method = 'GET' 883 | path = '/v2/private/wallet/withdraw/list' 884 | query = { 885 | 'start_date': start_date, 886 | 'end_date': end_date, 887 | 'coin': coin, 888 | 'status': status, 889 | 'page': page, 890 | 'limit': limit, 891 | } 892 | return self._request(method, path, query, private=True) 893 | 894 | def private_exchangeorder_list( 895 | self, 896 | limit: int=None, 897 | from_: int=None, 898 | direction: str=None, 899 | ) -> requests.Response: 900 | """ 901 | Asset Exchange Records 902 | """ 903 | method = 'GET' 904 | path = '/v2/private/exchange-order/list' 905 | query = { 906 | 'limit': limit, 907 | 'from': from_, 908 | 'direction': direction, 909 | } 910 | return self._request(method, path, query, private=True) 911 | 912 | def public_time( 913 | self, 914 | ) -> requests.Response: 915 | """ 916 | Server Time 917 | """ 918 | method = 'GET' 919 | path = '/v2/public/time' 920 | query = { 921 | } 922 | return self._request(method, path, query, private=False) 923 | 924 | def public_announcement( 925 | self, 926 | ) -> requests.Response: 927 | """ 928 | Announcement 929 | """ 930 | method = 'GET' 931 | path = '/v2/public/announcement' 932 | query = { 933 | } 934 | return self._request(method, path, query, private=False) 935 | 936 | class Linear: 937 | def __init__(self, request: RESTAPI._request): 938 | self._request = request 939 | 940 | def public_kline( 941 | self, 942 | symbol: str=None, 943 | interval: str=None, 944 | from_: int=None, 945 | limit: int=None, 946 | ) -> requests.Response: 947 | """ 948 | Query Kline 949 | """ 950 | method = 'GET' 951 | path = '/public/linear/kline' 952 | query = { 953 | 'symbol': symbol, 954 | 'interval': interval, 955 | 'from': from_, 956 | 'limit': limit, 957 | } 958 | return self._request(method, path, query, private=False) 959 | 960 | def public_recenttradingrecords( 961 | self, 962 | symbol: str=None, 963 | limit: int=None, 964 | ) -> requests.Response: 965 | """ 966 | Public Trading Records 967 | """ 968 | method = 'GET' 969 | path = '/public/linear/recent-trading-records' 970 | query = { 971 | 'symbol': symbol, 972 | 'limit': limit, 973 | } 974 | return self._request(method, path, query, private=False) 975 | 976 | def public_funding_prevfundingrate( 977 | self, 978 | symbol: str=None, 979 | ) -> requests.Response: 980 | """ 981 | Get the Last Funding Rate 982 | """ 983 | method = 'GET' 984 | path = '/public/linear/funding/prev-funding-rate' 985 | query = { 986 | 'symbol': symbol, 987 | } 988 | return self._request(method, path, query, private=False) 989 | 990 | def public_markpricekline( 991 | self, 992 | symbol: str=None, 993 | interval: str=None, 994 | from_: int=None, 995 | limit: int=None, 996 | ) -> requests.Response: 997 | """ 998 | Query Mark Price Kline 999 | """ 1000 | method = 'GET' 1001 | path = '/public/linear/mark-price-kline' 1002 | query = { 1003 | 'symbol': symbol, 1004 | 'interval': interval, 1005 | 'from': from_, 1006 | 'limit': limit, 1007 | } 1008 | return self._request(method, path, query, private=False) 1009 | 1010 | def public_indexpricekline( 1011 | self, 1012 | symbol: str=None, 1013 | interval: str=None, 1014 | from_: int=None, 1015 | limit: int=None, 1016 | ) -> requests.Response: 1017 | """ 1018 | Query Index Price Kline 1019 | """ 1020 | method = 'GET' 1021 | path = '/public/linear/index-price-kline' 1022 | query = { 1023 | 'symbol': symbol, 1024 | 'interval': interval, 1025 | 'from': from_, 1026 | 'limit': limit, 1027 | } 1028 | return self._request(method, path, query, private=False) 1029 | 1030 | def public_premiumindexkline( 1031 | self, 1032 | symbol: str=None, 1033 | interval: str=None, 1034 | from_: int=None, 1035 | limit: int=None, 1036 | ) -> requests.Response: 1037 | """ 1038 | Query Premium Index Kline 1039 | """ 1040 | method = 'GET' 1041 | path = '/public/linear/premium-index-kline' 1042 | query = { 1043 | 'symbol': symbol, 1044 | 'interval': interval, 1045 | 'from': from_, 1046 | 'limit': limit, 1047 | } 1048 | return self._request(method, path, query, private=False) 1049 | 1050 | def private_order_create( 1051 | self, 1052 | side: str=None, 1053 | symbol: str=None, 1054 | order_type: str=None, 1055 | qty: float=None, 1056 | price: float=None, 1057 | time_in_force: str=None, 1058 | take_profit: float=None, 1059 | stop_loss: float=None, 1060 | tp_trigger_by: str=None, 1061 | sl_trigger_by: str=None, 1062 | reduce_only: bool=None, 1063 | close_on_trigger: bool=None, 1064 | order_link_id: str=None, 1065 | ) -> requests.Response: 1066 | """ 1067 | Place Active Order 1068 | """ 1069 | method = 'POST' 1070 | path = '/private/linear/order/create' 1071 | query = { 1072 | 'side': side, 1073 | 'symbol': symbol, 1074 | 'order_type': order_type, 1075 | 'qty': qty, 1076 | 'price': price, 1077 | 'time_in_force': time_in_force, 1078 | 'take_profit': take_profit, 1079 | 'stop_loss': stop_loss, 1080 | 'tp_trigger_by': tp_trigger_by, 1081 | 'sl_trigger_by': sl_trigger_by, 1082 | 'reduce_only': reduce_only, 1083 | 'close_on_trigger': close_on_trigger, 1084 | 'order_link_id': order_link_id, 1085 | } 1086 | return self._request(method, path, query, private=True) 1087 | 1088 | def private_order_list( 1089 | self, 1090 | order_id: str=None, 1091 | order_link_id: str=None, 1092 | symbol: str=None, 1093 | order: str=None, 1094 | page: int=None, 1095 | limit: int=None, 1096 | order_status: str=None, 1097 | ) -> requests.Response: 1098 | """ 1099 | Get Active Order 1100 | """ 1101 | method = 'GET' 1102 | path = '/private/linear/order/list' 1103 | query = { 1104 | 'order_id': order_id, 1105 | 'order_link_id': order_link_id, 1106 | 'symbol': symbol, 1107 | 'order': order, 1108 | 'page': page, 1109 | 'limit': limit, 1110 | 'order_status': order_status, 1111 | } 1112 | return self._request(method, path, query, private=True) 1113 | 1114 | def private_order_cancel( 1115 | self, 1116 | symbol: str=None, 1117 | order_id: str=None, 1118 | order_link_id: str=None, 1119 | ) -> requests.Response: 1120 | """ 1121 | Cancel Active Order 1122 | """ 1123 | method = 'POST' 1124 | path = '/private/linear/order/cancel' 1125 | query = { 1126 | 'symbol': symbol, 1127 | 'order_id': order_id, 1128 | 'order_link_id': order_link_id, 1129 | } 1130 | return self._request(method, path, query, private=True) 1131 | 1132 | def private_order_cancelall( 1133 | self, 1134 | symbol: str=None, 1135 | ) -> requests.Response: 1136 | """ 1137 | Cancel All Active Orders 1138 | """ 1139 | method = 'POST' 1140 | path = '/private/linear/order/cancel-all' 1141 | query = { 1142 | 'symbol': symbol, 1143 | } 1144 | return self._request(method, path, query, private=True) 1145 | 1146 | def private_order_replace( 1147 | self, 1148 | order_id: str=None, 1149 | order_link_id: str=None, 1150 | symbol: str=None, 1151 | p_r_qty: str=None, 1152 | p_r_price: float=None, 1153 | take_profit: float=None, 1154 | stop_loss: float=None, 1155 | tp_trigger_by: str=None, 1156 | sl_trigger_by: str=None, 1157 | ) -> requests.Response: 1158 | """ 1159 | Replace Active Order 1160 | """ 1161 | method = 'POST' 1162 | path = '/private/linear/order/replace' 1163 | query = { 1164 | 'order_id': order_id, 1165 | 'order_link_id': order_link_id, 1166 | 'symbol': symbol, 1167 | 'p_r_qty': p_r_qty, 1168 | 'p_r_price': p_r_price, 1169 | 'take_profit': take_profit, 1170 | 'stop_loss': stop_loss, 1171 | 'tp_trigger_by': tp_trigger_by, 1172 | 'sl_trigger_by': sl_trigger_by, 1173 | } 1174 | return self._request(method, path, query, private=True) 1175 | 1176 | def private_order_search( 1177 | self, 1178 | order_id: str=None, 1179 | order_link_id: str=None, 1180 | symbol: str=None, 1181 | ) -> requests.Response: 1182 | """ 1183 | Query Active Order (real-time) 1184 | """ 1185 | method = 'GET' 1186 | path = '/private/linear/order/search' 1187 | query = { 1188 | 'order_id': order_id, 1189 | 'order_link_id': order_link_id, 1190 | 'symbol': symbol, 1191 | } 1192 | return self._request(method, path, query, private=True) 1193 | 1194 | def private_stoporder_create( 1195 | self, 1196 | side: str=None, 1197 | symbol: str=None, 1198 | order_type: str=None, 1199 | qty: float=None, 1200 | price: float=None, 1201 | base_price: float=None, 1202 | stop_px: float=None, 1203 | time_in_force: str=None, 1204 | trigger_by: str=None, 1205 | close_on_trigger: bool=None, 1206 | order_link_id: str=None, 1207 | reduce_only: bool=None, 1208 | take_profit: float=None, 1209 | stop_loss: float=None, 1210 | tp_trigger_by: str=None, 1211 | sl_trigger_by: str=None, 1212 | ) -> requests.Response: 1213 | """ 1214 | Place Conditional Order 1215 | """ 1216 | method = 'POST' 1217 | path = '/private/linear/stop-order/create' 1218 | query = { 1219 | 'side': side, 1220 | 'symbol': symbol, 1221 | 'order_type': order_type, 1222 | 'qty': qty, 1223 | 'price': price, 1224 | 'base_price': base_price, 1225 | 'stop_px': stop_px, 1226 | 'time_in_force': time_in_force, 1227 | 'trigger_by': trigger_by, 1228 | 'close_on_trigger': close_on_trigger, 1229 | 'order_link_id': order_link_id, 1230 | 'reduce_only': reduce_only, 1231 | 'take_profit': take_profit, 1232 | 'stop_loss': stop_loss, 1233 | 'tp_trigger_by': tp_trigger_by, 1234 | 'sl_trigger_by': sl_trigger_by, 1235 | } 1236 | return self._request(method, path, query, private=True) 1237 | 1238 | def private_stoporder_list( 1239 | self, 1240 | stop_order_id: str=None, 1241 | order_link_id: str=None, 1242 | symbol: str=None, 1243 | stop_order_status: str=None, 1244 | order: str=None, 1245 | page: int=None, 1246 | limit: int=None, 1247 | ) -> requests.Response: 1248 | """ 1249 | Get Conditional Order 1250 | """ 1251 | method = 'GET' 1252 | path = '/private/linear/stop-order/list' 1253 | query = { 1254 | 'stop_order_id': stop_order_id, 1255 | 'order_link_id': order_link_id, 1256 | 'symbol': symbol, 1257 | 'stop_order_status': stop_order_status, 1258 | 'order': order, 1259 | 'page': page, 1260 | 'limit': limit, 1261 | } 1262 | return self._request(method, path, query, private=True) 1263 | 1264 | def private_stoporder_cancel( 1265 | self, 1266 | symbol: str=None, 1267 | stop_order_id: str=None, 1268 | order_link_id: str=None, 1269 | ) -> requests.Response: 1270 | """ 1271 | Cancel Conditional Order 1272 | """ 1273 | method = 'POST' 1274 | path = '/private/linear/stop-order/cancel' 1275 | query = { 1276 | 'symbol': symbol, 1277 | 'stop_order_id': stop_order_id, 1278 | 'order_link_id': order_link_id, 1279 | } 1280 | return self._request(method, path, query, private=True) 1281 | 1282 | def private_stoporder_cancelall( 1283 | self, 1284 | symbol: str=None, 1285 | ) -> requests.Response: 1286 | """ 1287 | Cancel All Conditional Orders 1288 | """ 1289 | method = 'POST' 1290 | path = '/private/linear/stop-order/cancel-all' 1291 | query = { 1292 | 'symbol': symbol, 1293 | } 1294 | return self._request(method, path, query, private=True) 1295 | 1296 | def private_stoporder_replace( 1297 | self, 1298 | stop_order_id: str=None, 1299 | order_link_id: str=None, 1300 | symbol: str=None, 1301 | p_r_qty: str=None, 1302 | p_r_price: float=None, 1303 | p_r_trigger_price: float=None, 1304 | take_profit: float=None, 1305 | stop_loss: float=None, 1306 | tp_trigger_by: str=None, 1307 | sl_trigger_by: str=None, 1308 | ) -> requests.Response: 1309 | """ 1310 | Replace Conditional Order 1311 | """ 1312 | method = 'POST' 1313 | path = '/private/linear/stop-order/replace' 1314 | query = { 1315 | 'stop_order_id': stop_order_id, 1316 | 'order_link_id': order_link_id, 1317 | 'symbol': symbol, 1318 | 'p_r_qty': p_r_qty, 1319 | 'p_r_price': p_r_price, 1320 | 'p_r_trigger_price': p_r_trigger_price, 1321 | 'take_profit': take_profit, 1322 | 'stop_loss': stop_loss, 1323 | 'tp_trigger_by': tp_trigger_by, 1324 | 'sl_trigger_by': sl_trigger_by, 1325 | } 1326 | return self._request(method, path, query, private=True) 1327 | 1328 | def private_stoporder_search( 1329 | self, 1330 | symbol: str=None, 1331 | stop_order_id: str=None, 1332 | order_link_id: str=None, 1333 | ) -> requests.Response: 1334 | """ 1335 | Query Conditional Order (real-time) 1336 | """ 1337 | method = 'GET' 1338 | path = '/private/linear/stop-order/search' 1339 | query = { 1340 | 'symbol': symbol, 1341 | 'stop_order_id': stop_order_id, 1342 | 'order_link_id': order_link_id, 1343 | } 1344 | return self._request(method, path, query, private=True) 1345 | 1346 | def private_position_list( 1347 | self, 1348 | symbol: str=None, 1349 | ) -> requests.Response: 1350 | """ 1351 | My Position 1352 | """ 1353 | method = 'GET' 1354 | path = '/private/linear/position/list' 1355 | query = { 1356 | 'symbol': symbol, 1357 | } 1358 | return self._request(method, path, query, private=True) 1359 | 1360 | def private_position_setautoaddmargin( 1361 | self, 1362 | symbol: str=None, 1363 | side: str=None, 1364 | auto_add_margin: bool=None, 1365 | ) -> requests.Response: 1366 | """ 1367 | Set Auto Add Margin 1368 | """ 1369 | method = 'POST' 1370 | path = '/private/linear/position/set-auto-add-margin' 1371 | query = { 1372 | 'symbol': symbol, 1373 | 'side': side, 1374 | 'auto_add_margin': auto_add_margin, 1375 | } 1376 | return self._request(method, path, query, private=True) 1377 | 1378 | def private_position_switchisolated( 1379 | self, 1380 | symbol: str=None, 1381 | is_isolated: bool=None, 1382 | buy_leverage: float=None, 1383 | sell_leverage: float=None, 1384 | ) -> requests.Response: 1385 | """ 1386 | Cross/Isolated Margin Switch 1387 | """ 1388 | method = 'POST' 1389 | path = '/private/linear/position/switch-isolated' 1390 | query = { 1391 | 'symbol': symbol, 1392 | 'is_isolated': is_isolated, 1393 | 'buy_leverage': buy_leverage, 1394 | 'sell_leverage': sell_leverage, 1395 | } 1396 | return self._request(method, path, query, private=True) 1397 | 1398 | def private_tpsl_switchmode( 1399 | self, 1400 | symbol: str=None, 1401 | tp_sl_mode: str=None, 1402 | ) -> requests.Response: 1403 | """ 1404 | Full/Partial Position SL/TP Switch 1405 | """ 1406 | method = 'POST' 1407 | path = '/private/linear/tpsl/switch-mode' 1408 | query = { 1409 | 'symbol': symbol, 1410 | 'tp_sl_mode': tp_sl_mode, 1411 | } 1412 | return self._request(method, path, query, private=True) 1413 | 1414 | def private_position_addmargin( 1415 | self, 1416 | symbol: str=None, 1417 | side: str=None, 1418 | margin: float=None, 1419 | ) -> requests.Response: 1420 | """ 1421 | Add/Reduce Margin 1422 | """ 1423 | method = 'POST' 1424 | path = '/private/linear/position/add-margin' 1425 | query = { 1426 | 'symbol': symbol, 1427 | 'side': side, 1428 | 'margin': margin, 1429 | } 1430 | return self._request(method, path, query, private=True) 1431 | 1432 | def private_position_setleverage( 1433 | self, 1434 | symbol: str=None, 1435 | buy_leverage: float=None, 1436 | sell_leverage: float=None, 1437 | ) -> requests.Response: 1438 | """ 1439 | Set Leverage 1440 | """ 1441 | method = 'POST' 1442 | path = '/private/linear/position/set-leverage' 1443 | query = { 1444 | 'symbol': symbol, 1445 | 'buy_leverage': buy_leverage, 1446 | 'sell_leverage': sell_leverage, 1447 | } 1448 | return self._request(method, path, query, private=True) 1449 | 1450 | def private_position_tradingstop( 1451 | self, 1452 | symbol: str=None, 1453 | side: str=None, 1454 | take_profit: float=None, 1455 | stop_loss: float=None, 1456 | trailing_stop: float=None, 1457 | tp_trigger_by: str=None, 1458 | sl_trigger_by: str=None, 1459 | sl_size: float=None, 1460 | tp_size: float=None, 1461 | ) -> requests.Response: 1462 | """ 1463 | Set Trading-Stop 1464 | """ 1465 | method = 'POST' 1466 | path = '/private/linear/position/trading-stop' 1467 | query = { 1468 | 'symbol': symbol, 1469 | 'side': side, 1470 | 'take_profit': take_profit, 1471 | 'stop_loss': stop_loss, 1472 | 'trailing_stop': trailing_stop, 1473 | 'tp_trigger_by': tp_trigger_by, 1474 | 'sl_trigger_by': sl_trigger_by, 1475 | 'sl_size': sl_size, 1476 | 'tp_size': tp_size, 1477 | } 1478 | return self._request(method, path, query, private=True) 1479 | 1480 | def private_trade_execution_list( 1481 | self, 1482 | symbol: str=None, 1483 | start_time: int=None, 1484 | end_time: int=None, 1485 | exec_type: str=None, 1486 | page: int=None, 1487 | limit: int=None, 1488 | ) -> requests.Response: 1489 | """ 1490 | User Trade Records 1491 | """ 1492 | method = 'GET' 1493 | path = '/private/linear/trade/execution/list' 1494 | query = { 1495 | 'symbol': symbol, 1496 | 'start_time': start_time, 1497 | 'end_time': end_time, 1498 | 'exec_type': exec_type, 1499 | 'page': page, 1500 | 'limit': limit, 1501 | } 1502 | return self._request(method, path, query, private=True) 1503 | 1504 | def private_trade_closedpnl_list( 1505 | self, 1506 | symbol: str=None, 1507 | start_time: int=None, 1508 | end_time: int=None, 1509 | exec_type: str=None, 1510 | page: int=None, 1511 | limit: int=None, 1512 | ) -> requests.Response: 1513 | """ 1514 | Closed Profit and Loss 1515 | """ 1516 | method = 'GET' 1517 | path = '/private/linear/trade/closed-pnl/list' 1518 | query = { 1519 | 'symbol': symbol, 1520 | 'start_time': start_time, 1521 | 'end_time': end_time, 1522 | 'exec_type': exec_type, 1523 | 'page': page, 1524 | 'limit': limit, 1525 | } 1526 | return self._request(method, path, query, private=True) 1527 | 1528 | def public_risklimit( 1529 | self, 1530 | symbol: str=None, 1531 | ) -> requests.Response: 1532 | """ 1533 | Get Risk Limit 1534 | """ 1535 | method = 'GET' 1536 | path = '/public/linear/risk-limit' 1537 | query = { 1538 | 'symbol': symbol, 1539 | } 1540 | return self._request(method, path, query, private=False) 1541 | 1542 | def private_position_setrisk( 1543 | self, 1544 | symbol: str=None, 1545 | side: str=None, 1546 | risk_id: int=None, 1547 | ) -> requests.Response: 1548 | """ 1549 | Set Risk Limit 1550 | """ 1551 | method = 'POST' 1552 | path = '/private/linear/position/set-risk' 1553 | query = { 1554 | 'symbol': symbol, 1555 | 'side': side, 1556 | 'risk_id': risk_id, 1557 | } 1558 | return self._request(method, path, query, private=True) 1559 | 1560 | def private_funding_predictedfunding( 1561 | self, 1562 | symbol: str=None, 1563 | ) -> requests.Response: 1564 | """ 1565 | Predicted Funding Rate and My Funding Fee 1566 | """ 1567 | method = 'GET' 1568 | path = '/private/linear/funding/predicted-funding' 1569 | query = { 1570 | 'symbol': symbol, 1571 | } 1572 | return self._request(method, path, query, private=True) 1573 | 1574 | def private_funding_prevfunding( 1575 | self, 1576 | symbol: str=None, 1577 | ) -> requests.Response: 1578 | """ 1579 | My Last Funding Fee 1580 | """ 1581 | method = 'GET' 1582 | path = '/private/linear/funding/prev-funding' 1583 | query = { 1584 | 'symbol': symbol, 1585 | } 1586 | return self._request(method, path, query, private=True) 1587 | 1588 | def public_time( 1589 | self, 1590 | ) -> requests.Response: 1591 | """ 1592 | Server Time 1593 | """ 1594 | method = 'GET' 1595 | path = '/v2/public/time' 1596 | query = { 1597 | } 1598 | return self._request(method, path, query, private=False) 1599 | 1600 | def public_announcement( 1601 | self, 1602 | ) -> requests.Response: 1603 | """ 1604 | Announcement 1605 | """ 1606 | method = 'GET' 1607 | path = '/v2/public/announcement' 1608 | query = { 1609 | } 1610 | return self._request(method, path, query, private=False) 1611 | 1612 | class Futures: 1613 | def __init__(self, request: RESTAPI._request): 1614 | self._request = request 1615 | 1616 | def private_order_create( 1617 | self, 1618 | position_idx: int=None, 1619 | side: str=None, 1620 | symbol: str=None, 1621 | order_type: str=None, 1622 | qty: int=None, 1623 | price: float=None, 1624 | time_in_force: str=None, 1625 | reduce_only: bool=None, 1626 | close_on_trigger: bool=None, 1627 | order_link_id: str=None, 1628 | take_profit: float=None, 1629 | stop_loss: float=None, 1630 | tp_trigger_by: str=None, 1631 | sl_trigger_by: str=None, 1632 | ) -> requests.Response: 1633 | """ 1634 | Place Active Order 1635 | """ 1636 | method = 'POST' 1637 | path = '/futures/private/order/create' 1638 | query = { 1639 | 'position_idx': position_idx, 1640 | 'side': side, 1641 | 'symbol': symbol, 1642 | 'order_type': order_type, 1643 | 'qty': qty, 1644 | 'price': price, 1645 | 'time_in_force': time_in_force, 1646 | 'reduce_only': reduce_only, 1647 | 'close_on_trigger': close_on_trigger, 1648 | 'order_link_id': order_link_id, 1649 | 'take_profit': take_profit, 1650 | 'stop_loss': stop_loss, 1651 | 'tp_trigger_by': tp_trigger_by, 1652 | 'sl_trigger_by': sl_trigger_by, 1653 | } 1654 | return self._request(method, path, query, private=True) 1655 | 1656 | def private_order_list( 1657 | self, 1658 | symbol: str=None, 1659 | order_status: str=None, 1660 | direction: str=None, 1661 | limit: int=None, 1662 | cursor: str=None, 1663 | ) -> requests.Response: 1664 | """ 1665 | Get Active Order 1666 | """ 1667 | method = 'GET' 1668 | path = '/futures/private/order/list' 1669 | query = { 1670 | 'symbol': symbol, 1671 | 'order_status': order_status, 1672 | 'direction': direction, 1673 | 'limit': limit, 1674 | 'cursor': cursor, 1675 | } 1676 | return self._request(method, path, query, private=True) 1677 | 1678 | def private_order_cancel( 1679 | self, 1680 | symbol: str=None, 1681 | order_id: str=None, 1682 | order_link_id: str=None, 1683 | ) -> requests.Response: 1684 | """ 1685 | Cancel Active Order 1686 | """ 1687 | method = 'POST' 1688 | path = '/futures/private/order/cancel' 1689 | query = { 1690 | 'symbol': symbol, 1691 | 'order_id': order_id, 1692 | 'order_link_id': order_link_id, 1693 | } 1694 | return self._request(method, path, query, private=True) 1695 | 1696 | def private_order_cancelall( 1697 | self, 1698 | symbol: str=None, 1699 | ) -> requests.Response: 1700 | """ 1701 | Cancel All Active Orders 1702 | """ 1703 | method = 'POST' 1704 | path = '/futures/private/order/cancelAll' 1705 | query = { 1706 | 'symbol': symbol, 1707 | } 1708 | return self._request(method, path, query, private=True) 1709 | 1710 | def private_order_replace( 1711 | self, 1712 | order_id: str=None, 1713 | order_link_id: str=None, 1714 | symbol: str=None, 1715 | p_r_qty: str=None, 1716 | p_r_price: str=None, 1717 | take_profit: float=None, 1718 | stop_loss: float=None, 1719 | tp_trigger_by: str=None, 1720 | sl_trigger_by: str=None, 1721 | ) -> requests.Response: 1722 | """ 1723 | Replace Active Order 1724 | """ 1725 | method = 'POST' 1726 | path = '/futures/private/order/replace' 1727 | query = { 1728 | 'order_id': order_id, 1729 | 'order_link_id': order_link_id, 1730 | 'symbol': symbol, 1731 | 'p_r_qty': p_r_qty, 1732 | 'p_r_price': p_r_price, 1733 | 'take_profit': take_profit, 1734 | 'stop_loss': stop_loss, 1735 | 'tp_trigger_by': tp_trigger_by, 1736 | 'sl_trigger_by': sl_trigger_by, 1737 | } 1738 | return self._request(method, path, query, private=True) 1739 | 1740 | def private_order( 1741 | self, 1742 | symbol: str=None, 1743 | order_id: str=None, 1744 | order_link_id: str=None, 1745 | ) -> requests.Response: 1746 | """ 1747 | Query Active Order (real-time) 1748 | """ 1749 | method = 'GET' 1750 | path = '/futures/private/order' 1751 | query = { 1752 | 'symbol': symbol, 1753 | 'order_id': order_id, 1754 | 'order_link_id': order_link_id, 1755 | } 1756 | return self._request(method, path, query, private=True) 1757 | 1758 | def private_stoporder_create( 1759 | self, 1760 | position_idx: int=None, 1761 | side: str=None, 1762 | symbol: str=None, 1763 | order_type: str=None, 1764 | qty: str=None, 1765 | price: str=None, 1766 | base_price: str=None, 1767 | stop_px: str=None, 1768 | time_in_force: str=None, 1769 | trigger_by: str=None, 1770 | close_on_trigger: bool=None, 1771 | order_link_id: str=None, 1772 | take_profit: float=None, 1773 | stop_loss: float=None, 1774 | tp_trigger_by: str=None, 1775 | sl_trigger_by: str=None, 1776 | ) -> requests.Response: 1777 | """ 1778 | Place Conditional Order 1779 | """ 1780 | method = 'POST' 1781 | path = '/futures/private/stop-order/create' 1782 | query = { 1783 | 'position_idx': position_idx, 1784 | 'side': side, 1785 | 'symbol': symbol, 1786 | 'order_type': order_type, 1787 | 'qty': qty, 1788 | 'price': price, 1789 | 'base_price': base_price, 1790 | 'stop_px': stop_px, 1791 | 'time_in_force': time_in_force, 1792 | 'trigger_by': trigger_by, 1793 | 'close_on_trigger': close_on_trigger, 1794 | 'order_link_id': order_link_id, 1795 | 'take_profit': take_profit, 1796 | 'stop_loss': stop_loss, 1797 | 'tp_trigger_by': tp_trigger_by, 1798 | 'sl_trigger_by': sl_trigger_by, 1799 | } 1800 | return self._request(method, path, query, private=True) 1801 | 1802 | def private_stoporder_list( 1803 | self, 1804 | symbol: str=None, 1805 | stop_order_status: str=None, 1806 | direction: str=None, 1807 | limit: int=None, 1808 | cursor: str=None, 1809 | ) -> requests.Response: 1810 | """ 1811 | Get Conditional Order 1812 | """ 1813 | method = 'GET' 1814 | path = '/futures/private/stop-order/list' 1815 | query = { 1816 | 'symbol': symbol, 1817 | 'stop_order_status': stop_order_status, 1818 | 'direction': direction, 1819 | 'limit': limit, 1820 | 'cursor': cursor, 1821 | } 1822 | return self._request(method, path, query, private=True) 1823 | 1824 | def private_stoporder_cancel( 1825 | self, 1826 | symbol: str=None, 1827 | stop_order_id: str=None, 1828 | order_link_id: str=None, 1829 | ) -> requests.Response: 1830 | """ 1831 | Cancel Conditional Order 1832 | """ 1833 | method = 'POST' 1834 | path = '/futures/private/stop-order/cancel' 1835 | query = { 1836 | 'symbol': symbol, 1837 | 'stop_order_id': stop_order_id, 1838 | 'order_link_id': order_link_id, 1839 | } 1840 | return self._request(method, path, query, private=True) 1841 | 1842 | def private_stoporder_cancelall( 1843 | self, 1844 | symbol: str=None, 1845 | ) -> requests.Response: 1846 | """ 1847 | Cancel All Conditional Orders 1848 | """ 1849 | method = 'POST' 1850 | path = '/futures/private/stop-order/cancelAll' 1851 | query = { 1852 | 'symbol': symbol, 1853 | } 1854 | return self._request(method, path, query, private=True) 1855 | 1856 | def private_stoporder_replace( 1857 | self, 1858 | stop_order_id: str=None, 1859 | order_link_id: str=None, 1860 | symbol: str=None, 1861 | p_r_qty: int=None, 1862 | p_r_price: str=None, 1863 | p_r_trigger_price: str=None, 1864 | take_profit: float=None, 1865 | stop_loss: float=None, 1866 | tp_trigger_by: str=None, 1867 | sl_trigger_by: str=None, 1868 | ) -> requests.Response: 1869 | """ 1870 | Replace Conditional Order 1871 | """ 1872 | method = 'POST' 1873 | path = '/futures/private/stop-order/replace' 1874 | query = { 1875 | 'stop_order_id': stop_order_id, 1876 | 'order_link_id': order_link_id, 1877 | 'symbol': symbol, 1878 | 'p_r_qty': p_r_qty, 1879 | 'p_r_price': p_r_price, 1880 | 'p_r_trigger_price': p_r_trigger_price, 1881 | 'take_profit': take_profit, 1882 | 'stop_loss': stop_loss, 1883 | 'tp_trigger_by': tp_trigger_by, 1884 | 'sl_trigger_by': sl_trigger_by, 1885 | } 1886 | return self._request(method, path, query, private=True) 1887 | 1888 | def private_stoporder( 1889 | self, 1890 | symbol: str=None, 1891 | stop_order_id: str=None, 1892 | order_link_id: str=None, 1893 | ) -> requests.Response: 1894 | """ 1895 | Query Conditional Order (real-time) 1896 | """ 1897 | method = 'GET' 1898 | path = '/futures/private/stop-order' 1899 | query = { 1900 | 'symbol': symbol, 1901 | 'stop_order_id': stop_order_id, 1902 | 'order_link_id': order_link_id, 1903 | } 1904 | return self._request(method, path, query, private=True) 1905 | 1906 | def private_position_list( 1907 | self, 1908 | symbol: str=None, 1909 | ) -> requests.Response: 1910 | """ 1911 | My Position 1912 | """ 1913 | method = 'GET' 1914 | path = '/futures/private/position/list' 1915 | query = { 1916 | 'symbol': symbol, 1917 | } 1918 | return self._request(method, path, query, private=True) 1919 | 1920 | def private_position_changepositionmargin( 1921 | self, 1922 | symbol: str=None, 1923 | position_idx: int=None, 1924 | margin: str=None, 1925 | ) -> requests.Response: 1926 | """ 1927 | Change Margin 1928 | """ 1929 | method = 'POST' 1930 | path = '/futures/private/position/change-position-margin' 1931 | query = { 1932 | 'symbol': symbol, 1933 | 'position_idx': position_idx, 1934 | 'margin': margin, 1935 | } 1936 | return self._request(method, path, query, private=True) 1937 | 1938 | def private_position_tradingstop( 1939 | self, 1940 | symbol: str=None, 1941 | position_idx: int=None, 1942 | take_profit: float=None, 1943 | stop_loss: float=None, 1944 | trailing_stop: float=None, 1945 | tp_trigger_by: str=None, 1946 | sl_trigger_by: str=None, 1947 | new_trailing_active: float=None, 1948 | sl_size: float=None, 1949 | tp_size: float=None, 1950 | ) -> requests.Response: 1951 | """ 1952 | Set Trading-Stop 1953 | """ 1954 | method = 'POST' 1955 | path = '/futures/private/position/trading-stop' 1956 | query = { 1957 | 'symbol': symbol, 1958 | 'position_idx': position_idx, 1959 | 'take_profit': take_profit, 1960 | 'stop_loss': stop_loss, 1961 | 'trailing_stop': trailing_stop, 1962 | 'tp_trigger_by': tp_trigger_by, 1963 | 'sl_trigger_by': sl_trigger_by, 1964 | 'new_trailing_active': new_trailing_active, 1965 | 'sl_size': sl_size, 1966 | 'tp_size': tp_size, 1967 | } 1968 | return self._request(method, path, query, private=True) 1969 | 1970 | def private_position_leverage_save( 1971 | self, 1972 | symbol: str=None, 1973 | buy_leverage: float=None, 1974 | sell_leverage: float=None, 1975 | ) -> requests.Response: 1976 | """ 1977 | Set Leverage 1978 | """ 1979 | method = 'POST' 1980 | path = '/futures/private/position/leverage/save' 1981 | query = { 1982 | 'symbol': symbol, 1983 | 'buy_leverage': buy_leverage, 1984 | 'sell_leverage': sell_leverage, 1985 | } 1986 | return self._request(method, path, query, private=True) 1987 | 1988 | def private_position_switchmode( 1989 | self, 1990 | symbol: str=None, 1991 | mode: int=None, 1992 | ) -> requests.Response: 1993 | """ 1994 | Position Mode Switch 1995 | """ 1996 | method = 'POST' 1997 | path = '/futures/private/position/switch-mode' 1998 | query = { 1999 | 'symbol': symbol, 2000 | 'mode': mode, 2001 | } 2002 | return self._request(method, path, query, private=True) 2003 | 2004 | def private_tpsl_switchmode( 2005 | self, 2006 | symbol: str=None, 2007 | tp_sl_mode: str=None, 2008 | ) -> requests.Response: 2009 | """ 2010 | Full/Partial Position SL/TP Switch 2011 | """ 2012 | method = 'POST' 2013 | path = '/futures/private/tpsl/switch-mode' 2014 | query = { 2015 | 'symbol': symbol, 2016 | 'tp_sl_mode': tp_sl_mode, 2017 | } 2018 | return self._request(method, path, query, private=True) 2019 | 2020 | def private_position_switchisolated( 2021 | self, 2022 | symbol: str=None, 2023 | is_isolated: bool=None, 2024 | buy_leverage: float=None, 2025 | sell_leverage: float=None, 2026 | ) -> requests.Response: 2027 | """ 2028 | Cross/Isolated Margin Switch 2029 | """ 2030 | method = 'POST' 2031 | path = '/futures/private/position/switch-isolated' 2032 | query = { 2033 | 'symbol': symbol, 2034 | 'is_isolated': is_isolated, 2035 | 'buy_leverage': buy_leverage, 2036 | 'sell_leverage': sell_leverage, 2037 | } 2038 | return self._request(method, path, query, private=True) 2039 | 2040 | def private_execution_list( 2041 | self, 2042 | order_id: str=None, 2043 | symbol: str=None, 2044 | start_time: int=None, 2045 | page: int=None, 2046 | limit: int=None, 2047 | order: str=None, 2048 | ) -> requests.Response: 2049 | """ 2050 | User Trade Records 2051 | """ 2052 | method = 'GET' 2053 | path = '/futures/private/execution/list' 2054 | query = { 2055 | 'order_id': order_id, 2056 | 'symbol': symbol, 2057 | 'start_time': start_time, 2058 | 'page': page, 2059 | 'limit': limit, 2060 | 'order': order, 2061 | } 2062 | return self._request(method, path, query, private=True) 2063 | 2064 | def private_trade_closedpnl_list( 2065 | self, 2066 | symbol: str=None, 2067 | start_time: int=None, 2068 | end_time: int=None, 2069 | exec_type: str=None, 2070 | page: int=None, 2071 | limit: int=None, 2072 | ) -> requests.Response: 2073 | """ 2074 | Closed Profit and Loss 2075 | """ 2076 | method = 'GET' 2077 | path = '/futures/private/trade/closed-pnl/list' 2078 | query = { 2079 | 'symbol': symbol, 2080 | 'start_time': start_time, 2081 | 'end_time': end_time, 2082 | 'exec_type': exec_type, 2083 | 'page': page, 2084 | 'limit': limit, 2085 | } 2086 | return self._request(method, path, query, private=True) 2087 | 2088 | def private_position_risklimit( 2089 | self, 2090 | symbol: str=None, 2091 | risk_id: int=None, 2092 | position_idx: int=None, 2093 | ) -> requests.Response: 2094 | """ 2095 | Set Risk Limit 2096 | """ 2097 | method = 'POST' 2098 | path = '/futures/private/position/risk-limit' 2099 | query = { 2100 | 'symbol': symbol, 2101 | 'risk_id': risk_id, 2102 | 'position_idx': position_idx, 2103 | } 2104 | return self._request(method, path, query, private=True) 2105 | 2106 | def public_time( 2107 | self, 2108 | ) -> requests.Response: 2109 | """ 2110 | Server Time 2111 | """ 2112 | method = 'GET' 2113 | path = '/v2/public/time' 2114 | query = { 2115 | } 2116 | return self._request(method, path, query, private=False) 2117 | 2118 | def public_announcement( 2119 | self, 2120 | ) -> requests.Response: 2121 | """ 2122 | Announcement 2123 | """ 2124 | method = 'GET' 2125 | path = '/v2/public/announcement' 2126 | query = { 2127 | } 2128 | return self._request(method, path, query, private=False) 2129 | -------------------------------------------------------------------------------- /pybybit/util/auth.py: -------------------------------------------------------------------------------- 1 | import hmac 2 | import hashlib 3 | import time 4 | 5 | class Authentication: 6 | def __init__(self, key: str='', secret: str='') -> None: 7 | self._key = key 8 | self._secret = secret.encode() 9 | 10 | def _prepare(self, req_args: dict) -> dict: 11 | auth_args = {'api_key': self._key, 'timestamp': int(time.time() * 1000)} 12 | return dict(sorted({**req_args, **auth_args}.items())) 13 | 14 | def _sign(self, query_str: str) -> str: 15 | hexdigest = hmac.new(self._secret, query_str.encode(), hashlib.sha256).hexdigest() 16 | return f'sign={hexdigest}' 17 | 18 | def _wssign(self) -> str: 19 | expires = int((time.time() + 5.0) * 1000) 20 | signature = hmac.new(self._secret, f'GET/realtime{expires}'.encode(), hashlib.sha256).hexdigest() 21 | return f'api_key={self._key}&expires={expires}&signature={signature}' 22 | -------------------------------------------------------------------------------- /pybybit/util/bybit_doc_scraping.py: -------------------------------------------------------------------------------- 1 | import keyword 2 | 3 | import bs4 4 | import requests 5 | 6 | # pip isntall requests bs4 7 | 8 | # Running the script will create a bybit_doc_scraping.txt and md file in the current directory. 9 | 10 | urls = { 11 | 'Inverse': 'https://bybit-exchange.github.io/docs/inverse', 12 | 'Linear': 'https://bybit-exchange.github.io/docs/linear', 13 | 'Futures': 'https://bybit-exchange.github.io/docs/inverse_futures', 14 | # 'Spot': 'https://bybit-exchange.github.io/docs/spot', 15 | } 16 | 17 | rm_pathname = ['v2', 'linear', 'futures'] 18 | repl_pathname = {'open-api': 'private'} 19 | 20 | type_mapping = { 21 | 'string': str, 22 | 'integer': int, 23 | 'int': int, 24 | 'number': float, 25 | 'bool': bool, 26 | } 27 | 28 | table = {} 29 | with open('bybit_doc_scraping.txt', 'w') as f: 30 | for cont, url in urls.items(): 31 | r = requests.get(url) 32 | soup = bs4.BeautifulSoup(r.text, 'lxml') 33 | text = '' 34 | text += f'class {cont}:\n' 35 | text += f' def __init__(self, request: RESTAPI._request):\n' 36 | text += f' self._request = request\n' 37 | text += f'\n' 38 | print(text) 39 | f.write(text) 40 | 41 | http_request = False 42 | request_parameters = False 43 | # response_parameters = False 44 | desc = None 45 | method = None 46 | path = None 47 | params = [] 48 | table[cont] = [] 49 | for element in soup.select_one('body > div.page-wrapper > div.content'): 50 | if isinstance(element, bs4.element.Tag): 51 | if element.name in {'h1', 'h2', 'h3', }: 52 | # changed section 53 | if method and path: 54 | if path[0] != '/': 55 | path = '/' + path 56 | p_list = path[1:].split('/') 57 | for rm in rm_pathname: 58 | if rm in p_list: 59 | p_list.remove(rm) 60 | for k, v in repl_pathname.items(): 61 | if k in p_list: 62 | idx = p_list.index(k) 63 | p_list[idx] = p_list[idx].replace(k, v) 64 | funcname = '_'.join(p_list).replace('-', '').lower() 65 | private = 'True' if 'private' in funcname else 'False' 66 | 67 | text = '' 68 | text += f' def {funcname}(\n' 69 | text += f' self,\n' 70 | for p, t in params: 71 | p = p if not keyword.iskeyword(p) else f'{p}_' 72 | text += f' {p}: {t.__name__}=None,\n' 73 | text += f' ) -> requests.Response:\n' 74 | text += f' """\n' 75 | text += f' {desc}\n' 76 | text += f' """\n' 77 | text += f" method = '{method}'\n" 78 | text += f" path = '{path}'\n" 79 | text += f" query = {{\n" 80 | for p, t in params: 81 | _p = p if not keyword.iskeyword(p) else f'{p}_' 82 | text += f" '{p}': {_p},\n" 83 | text += f" }}\n" 84 | text += f' return self._request(method, path, query, private={private})\n' 85 | text += '\n' 86 | print(text) 87 | f.write(text) 88 | 89 | table[cont] += [(funcname, method, path, desc, )] 90 | 91 | method = None 92 | path = None 93 | params.clear() 94 | desc = element.text 95 | if desc == 'Abandoned Endpoints': 96 | break 97 | elif element.name == 'p': 98 | if element.text == 'HTTP Request': 99 | http_request = True 100 | elif element.text == 'Request Parameters': 101 | request_parameters = True 102 | # elif element.text == 'Response Parameters': 103 | # response_parameters = True 104 | if http_request: 105 | if element.name == 'p': 106 | if element.select_one('code > span'): 107 | http_request = False 108 | strings = element.strings 109 | method: str = next(strings)[:-1] 110 | path: str = next(strings) 111 | if request_parameters: 112 | if element.name == 'table': 113 | request_parameters = False 114 | for tr in element.select('tbody > tr'): 115 | tr: bs4.element.Tag 116 | tds: list[bs4.Tag] = list(tr.select('td')) 117 | params.append((tds[0].text, type_mapping[tds[2].text], )) 118 | # if response_parameters: 119 | # pass 120 | 121 | text = '' 122 | text += '## メソッド名⇔エンドポイント名 対応表\n' 123 | for cont in table: 124 | text += f'### {cont}\n' 125 | header = ['Method Name', 'Http Method', 'Endpoint URL', 'Description', ] 126 | text += f"| {' | '.join(header)} |\n" 127 | text += f"| {' | '.join(['---'] * len(header))} |\n" 128 | for row in table[cont]: 129 | text += f"| {' | '.join(row)} |\n" 130 | print(text) 131 | with open('bybit_doc_scraping.md', 'w') as f: 132 | f.write(text) -------------------------------------------------------------------------------- /pybybit/util/store.py: -------------------------------------------------------------------------------- 1 | import json 2 | import urllib.parse 3 | from threading import Event 4 | from typing import Any, Dict, List, Optional, Union 5 | from requests import Response, Session 6 | from websocket import WebSocket 7 | 8 | class DataStore: 9 | def __init__(self) -> None: 10 | self.orderbook = OrderBook() 11 | self.trade = Trade() 12 | self.insurance = Insurance() 13 | self.instrument = Instrument() 14 | self.kline = Kline() 15 | self.position = Position() 16 | self.execution = Execution() 17 | self.order = Order() 18 | self.stoporder = StopOrder() 19 | self.wallet = Wallet() 20 | self._events: List[Event] = [] 21 | 22 | def onresponse(self, resp: Response, session: Session) -> None: 23 | content: Dict[str, Any] = resp.json() 24 | if content.get('ret_code') == 0: 25 | # order 26 | if any([ 27 | resp.request.path_url.startswith('/v2/private/order'), 28 | resp.request.path_url.startswith('/private/linear/order/search'), 29 | resp.request.path_url.startswith('/futures/private/order'), 30 | ]): 31 | if isinstance(content['result'], list): 32 | self.order._onresponse(content['result']) 33 | # stoporder 34 | elif any([ 35 | resp.request.path_url.startswith('/v2/private/stop-order'), 36 | resp.request.path_url.startswith('/private/linear/order/search'), 37 | resp.request.path_url.startswith('/futures/private/order'), 38 | ]): 39 | if isinstance(content['result'], list): 40 | self.stoporder._onresponse(content['result']) 41 | # position 42 | elif any([ 43 | resp.request.path_url.startswith('/v2/private/position/list'), 44 | resp.request.path_url.startswith('/futures/private/position/list'), 45 | ]): 46 | self.position.inverse._onresponse(content['result']) 47 | elif resp.request.path_url.startswith('/private/linear/position/list'): 48 | self.position.linear._onresponse(content['result']) 49 | # wallet 50 | elif resp.request.path_url.startswith('/v2/private/wallet/balance'): 51 | self.wallet._onresponse(content['result']) 52 | 53 | def onmessage(self, msg: str, ws: WebSocket) -> None: 54 | content: Dict[str, Any] = json.loads(msg) 55 | if 'topic' in content: 56 | topic: str = content['topic'] 57 | data: Union[List[Item], Item] = content['data'] 58 | type_: Optional[str] = content.get('type') 59 | if any([ 60 | topic.startswith('orderBookL2_25'), 61 | topic.startswith('orderBook_200'), 62 | ]): 63 | self.orderbook._onmessage(type_, data) 64 | elif topic.startswith('trade'): 65 | self.trade._onmessage(data) 66 | elif topic.startswith('insurance'): 67 | self.insurance._onmessage(data) 68 | elif topic.startswith('instrument_info'): 69 | self.instrument._onmessage(type_, data) 70 | if any([ 71 | topic.startswith('klineV2'), 72 | topic.startswith('candle'), 73 | ]): 74 | self.kline._onmessage(topic, data) 75 | elif topic == 'position': 76 | self.position._onmessage(data) 77 | self.wallet._onposition(data) 78 | elif topic == 'execution': 79 | self.execution._onmessage(data) 80 | elif topic == 'order': 81 | self.order._onmessage(data) 82 | elif topic == 'stop_order': 83 | self.stoporder._onmessage(data) 84 | elif topic == 'wallet': 85 | self.wallet._onmessage(data) 86 | for event in self._events: 87 | event.set() 88 | self._events.clear() 89 | 90 | def wait(self) -> None: 91 | event = Event() 92 | self._events.append(event) 93 | event.wait() 94 | 95 | class DefaultDataStore(DataStore): ... 96 | 97 | Item = Dict[str, Any] 98 | 99 | class _KeyValueStore: 100 | _KEYS: List[str] 101 | _MAXLEN: Optional[int] 102 | 103 | def __init__(self) -> None: 104 | self._data: Dict[str, Item] = {} 105 | self._events: List[Event] = [] 106 | 107 | def get(self, **kwargs) -> Optional[Item]: 108 | try: 109 | dumps = self._dumps(kwargs) 110 | if dumps in self._data: 111 | return self._data[dumps] 112 | except KeyError: 113 | if kwargs: 114 | for item in self._data.values(): 115 | for k, v, in kwargs.items(): 116 | if not k in item: 117 | break 118 | if v != item[k]: 119 | break 120 | else: 121 | return item 122 | else: 123 | for item in self._data.values(): 124 | return item 125 | 126 | def getlist(self, **kwargs) -> List[Item]: 127 | if kwargs: 128 | result = [] 129 | for item in self._data.values(): 130 | for k, v in kwargs.items(): 131 | if not k in item: 132 | break 133 | if v != item[k]: 134 | break 135 | else: 136 | result.append(item) 137 | return result 138 | else: 139 | return list(self._data.values()) 140 | 141 | def __len__(self): 142 | return len(self._data) 143 | 144 | def _dumps(self, item: Item) -> str: 145 | keyitem = {k: item[k] for k in self._KEYS} 146 | return urllib.parse.urlencode(keyitem) 147 | 148 | def _update(self, items: List[Item]) -> None: 149 | for item in items: 150 | try: 151 | key = self._dumps(item) 152 | if key in self._data: 153 | self._data[key].update(item) 154 | else: 155 | self._data[key] = item 156 | except KeyError: 157 | pass 158 | if self._MAXLEN is not None: 159 | len_data = len(self._data) 160 | if len_data > self._MAXLEN: 161 | over = len_data - self._MAXLEN 162 | keys = [] 163 | for i, k in enumerate(self._data.keys()): 164 | if i < over: 165 | keys.append(k) 166 | else: 167 | break 168 | for k in keys: 169 | self._data.pop(k) 170 | for event in self._events: 171 | event.set() 172 | self._events.clear() 173 | 174 | def _pop(self, items: List[Item]) -> None: 175 | for item in items: 176 | try: 177 | key = self._dumps(item) 178 | if key in self._data: 179 | self._data.pop(key) 180 | except KeyError: 181 | pass 182 | for event in self._events: 183 | event.set() 184 | self._events.clear() 185 | 186 | def wait(self) -> None: 187 | event = Event() 188 | self._events.append(event) 189 | event.wait() 190 | 191 | class OrderBook(_KeyValueStore): 192 | _KEYS = ['symbol', 'id', 'side'] 193 | _MAXLEN = None 194 | 195 | def getbest(self, symbol: str) -> Dict[str, Optional[Item]]: 196 | result = {'Sell': {}, 'Buy': {}} 197 | for item in self._data.values(): 198 | if item['symbol'] == symbol: 199 | result[item['side']][float(item['price'])] = item 200 | return { 201 | 'Sell': result['Sell'][min(result['Sell'])] if result['Sell'] else None, 202 | 'Buy': result['Buy'][max(result['Buy'])] if result['Buy'] else None 203 | } 204 | 205 | def getsorted(self, symbol: str) -> Dict[str, List[Item]]: 206 | result = {'Sell': [], 'Buy': []} 207 | for item in self._data.values(): 208 | if item['symbol'] == symbol: 209 | result[item['side']].append(item) 210 | return { 211 | 'Sell': sorted(result['Sell'], key=lambda x: float(x['price'])), 212 | 'Buy': sorted(result['Buy'], key=lambda x: float(x['price']), reverse=True) 213 | } 214 | 215 | def _onmessage(self, type_: str, data: Union[List[Item], Item]) -> None: 216 | if type_ == 'snapshot': 217 | if isinstance(data, dict): 218 | data = data['order_book'] 219 | self._update(data) 220 | elif type_ == 'delta': 221 | self._pop(data['delete']) 222 | self._update(data['update']) 223 | self._update(data['insert']) 224 | 225 | class Trade(_KeyValueStore): 226 | _KEYS = ['trade_id'] 227 | _MAXLEN = 10000 228 | 229 | def _onmessage(self, data: List[Item]) -> None: 230 | self._update(data) 231 | 232 | class Insurance(_KeyValueStore): 233 | _KEYS = ['currency'] 234 | _MAXLEN = None 235 | 236 | def _onmessage(self, data: List[Item]) -> None: 237 | self._update(data) 238 | 239 | class Instrument(_KeyValueStore): 240 | _KEYS = ['symbol'] 241 | _MAXLEN = None 242 | 243 | def _onmessage(self, type_: str, data: Item) -> None: 244 | if type_ == 'snapshot': 245 | self._update([data]) 246 | elif type_ == 'delta': 247 | self._update(data['update']) 248 | 249 | class Kline(_KeyValueStore): 250 | _KEYS = ['symbol', 'start'] 251 | _MAXLEN = 5000 252 | 253 | def _onmessage(self, topic: str, data: List[Item]) -> None: 254 | symbol = topic.split('.')[2] # ex:'klineV2.1.BTCUSD' 255 | for item in data: 256 | item['symbol'] = symbol 257 | self._update(data) 258 | 259 | class Position: 260 | def __init__(self): 261 | self.inverse = PositionInverse() 262 | self.linear = PositionLinear() 263 | 264 | def _onmessage(self, data: List[Item]) -> None: 265 | if len(data): 266 | symbol: str = data[0]['symbol'] 267 | if symbol.endswith('USDT'): 268 | self.linear._onmessage(data) 269 | else: 270 | self.inverse._onmessage(data) 271 | 272 | class PositionInverse(_KeyValueStore): 273 | _KEYS = ['symbol', 'position_idx'] 274 | _MAXLEN = None 275 | 276 | def getone(self, symbol: str) -> Optional[Item]: 277 | return self.get(symbol=symbol, position_idx=0) 278 | 279 | def getboth(self, symbol: str) -> Dict[str, Optional[Item]]: 280 | return { 281 | 'Sell': self.get(symbol=symbol, position_idx=2), 282 | 'Buy': self.get(symbol=symbol, position_idx=1), 283 | } 284 | 285 | def _onresponse(self, data: Union[Item, List[Item]]) -> None: 286 | if isinstance(data, dict): 287 | self._update([data]) 288 | elif isinstance(data, list): 289 | if len(data) and 'data' in data[0]: 290 | self._update([item['data'] for item in data]) 291 | else: 292 | self._update(data) 293 | 294 | def _onmessage(self, data: List[Item]) -> None: 295 | self._update(data) 296 | 297 | class PositionLinear(_KeyValueStore): 298 | _KEYS = ['symbol', 'side'] 299 | _MAXLEN = None 300 | 301 | def getboth(self, symbol: str) -> Dict[str, Optional[Item]]: 302 | return { 303 | 'Sell': self.get(symbol=symbol, side='Sell'), 304 | 'Buy': self.get(symbol=symbol, side='Buy'), 305 | } 306 | 307 | def _onresponse(self, data: List[Item]) -> None: 308 | if len(data) and 'data' in data[0]: 309 | self._update([item['data'] for item in data]) 310 | else: 311 | self._update(data) 312 | 313 | def _onmessage(self, data: List[Item]) -> None: 314 | self._update(data) 315 | 316 | class Execution(_KeyValueStore): 317 | _KEYS = ['exec_id'] 318 | _MAXLEN = 5000 319 | 320 | def _onmessage(self, data: List[Item]) -> None: 321 | self._update(data) 322 | 323 | class Order(_KeyValueStore): 324 | _KEYS = ['order_id'] 325 | _MAXLEN = None 326 | 327 | def _onresponse(self, data: List[Item]) -> None: 328 | self._update(data) 329 | 330 | def _onmessage(self, data: List[Item]) -> None: 331 | for item in data: 332 | if item['order_status'] in ('Created', 'New', 'PartiallyFilled', ): 333 | self._update([item]) 334 | else: 335 | self._pop([item]) 336 | 337 | class StopOrder(_KeyValueStore): 338 | _KEYS = ['stop_order_id'] 339 | _MAXLEN = None 340 | 341 | def _onresponse(self, data: List[Item]) -> None: 342 | self._update(data) 343 | 344 | def _onmessage(self, data: List[Item]) -> None: 345 | for item in data: 346 | if 'order_id' in item: 347 | item['stop_order_id'] = item.pop('order_id') 348 | if 'order_status' in item: 349 | item['stop_order_status'] = item.pop('order_status') 350 | if item['stop_order_status'] in ('Active', 'Untriggered', ): 351 | self._update([item]) 352 | else: 353 | self._pop([item]) 354 | 355 | class Wallet(_KeyValueStore): 356 | _KEYS = ['coin'] 357 | _MAXLEN = None 358 | 359 | def _onresponse(self, data: Dict[str, Item]) -> None: 360 | for coin, item in data.items(): 361 | _item = {} 362 | _item['coin'] = coin 363 | _item['wallet_balance'] = item['wallet_balance'] 364 | _item['available_balance'] = item['available_balance'] 365 | self._update([_item]) 366 | 367 | def _onposition(self, data: List[Item]) -> None: 368 | if len(data) and 'position_idx' in data[0]: 369 | for item in data: 370 | _item = {} 371 | symbol: str = item['symbol'] 372 | if symbol.endswith('USD'): 373 | _item['coin'] = symbol[:-3] # ex:'BTCUSD' 374 | else: 375 | _item['coin'] = symbol[:-6] # ex:'BTCUSDM21' 376 | _item['wallet_balance'] = item['wallet_balance'] 377 | _item['available_balance'] = item['available_balance'] 378 | self._update([_item]) 379 | 380 | def _onmessage(self, data: List[Item]) -> None: 381 | for item in data: 382 | _item = {} 383 | _item['coin'] = 'USDT' 384 | _item['wallet_balance'] = item['wallet_balance'] 385 | _item['available_balance'] = item['available_balance'] 386 | self._update([item]) 387 | -------------------------------------------------------------------------------- /pybybit/ws.py: -------------------------------------------------------------------------------- 1 | import time 2 | import websocket 3 | from threading import Thread 4 | 5 | class WebScoketAPI: 6 | _MAINNET_INVERSE = 'wss://stream.bybit.com/realtime' 7 | _TESTNET_INVERSE = 'wss://stream-testnet.bybit.com/realtime' 8 | _MAINNET_LINEAR_PUBLIC = 'wss://stream.bybit.com/realtime_public' 9 | _MAINNET_LINEAR_PRIVATE = 'wss://stream.bybit.com/realtime_private' 10 | _TESTNET_LINEAR_PUBLIC = 'wss://stream-testnet.bybit.com/realtime_public' 11 | _TESTNET_LINEAR_PRIVATE = 'wss://stream-testnet.bybit.com/realtime_private' 12 | _COMMAND = '{{"op":"{op}","args":[{args}]}}' 13 | _ORDERBOOKL2_25 = 'orderBookL2_25' 14 | _ORDERBOOK_200 = 'orderBook_200.100ms' 15 | _TRADE = 'trade' 16 | _INSURANCE = 'insurance' 17 | _INSTRUMENT_INFO = 'instrument_info.100ms' 18 | _KLINEV2 = 'klineV2' 19 | _CANDLE = 'candle' 20 | _POSITION = 'position' 21 | _EXECUTION = 'execution' 22 | _ORDER = 'order' 23 | _STOPORDER = 'stop_order' 24 | _WALLET = 'wallet' 25 | _PUBLIC_TOPICS = [_ORDERBOOKL2_25, _ORDERBOOK_200, _TRADE, _INSTRUMENT_INFO, _KLINEV2, _CANDLE] 26 | _PRIVATE_TOPICS = [_POSITION, _EXECUTION, _ORDER, _STOPORDER, _WALLET] 27 | _HEARTBEAT_SEC = 30.0 28 | _MINRECONECT_SEC = 60.0 29 | 30 | def __init__(self, auth, testnet) -> None: 31 | self._auth = auth 32 | self._testnet = testnet 33 | self._callbacks = [] 34 | 35 | def _subscribe(self, topics: list, ws: websocket.WebSocket) -> None: 36 | args = ','.join(f'"{t}"' for t in topics) 37 | cmd = self._COMMAND.format(op='subscribe', args=args) 38 | ws.send(cmd) 39 | 40 | def _onmessage(self, ws: websocket.WebSocket) -> None: 41 | Thread(target=self._heartbeat, args=[ws], daemon=True).start() 42 | while True: 43 | try: 44 | msg: str = ws.recv() 45 | except Exception: 46 | break 47 | else: 48 | for cb in self._callbacks: 49 | cb(msg, ws) 50 | 51 | def _heartbeat(self, ws: websocket.WebSocket) -> None: 52 | while True: 53 | time.sleep(self._HEARTBEAT_SEC) 54 | try: 55 | ws.send('{"op":"ping"}') 56 | except Exception: 57 | break 58 | 59 | def _loop(self, wsurl: str, topics: list) -> None: 60 | while True: 61 | t = time.time() 62 | try: 63 | if any(t in self._PRIVATE_TOPICS for t in topics): 64 | param = self._auth._wssign() 65 | wsurl += f'?{param}' 66 | ws = websocket.create_connection(wsurl) 67 | self._subscribe(topics, ws) 68 | self._onmessage(ws) 69 | except KeyboardInterrupt: 70 | break 71 | time.sleep(max(self._MINRECONECT_SEC - (time.time() - t), 0)) 72 | 73 | def add_callback(self, func) -> None: 74 | if callable(func): 75 | self._callbacks.append(func) 76 | 77 | def run_forever_inverse(self, topics: list) -> None: 78 | wsurl = self._MAINNET_INVERSE if not self._testnet else self._TESTNET_INVERSE 79 | Thread(target=self._loop, args=[wsurl, topics], daemon=True).start() 80 | 81 | def run_forever_linear_public(self, topics: list) -> None: 82 | wsurl = self._MAINNET_LINEAR_PUBLIC if not self._testnet else self._TESTNET_LINEAR_PUBLIC 83 | Thread(target=self._loop, args=[wsurl, topics], daemon=True).start() 84 | 85 | def run_forever_linear_private(self, topics: list) -> None: 86 | wsurl = self._MAINNET_LINEAR_PRIVATE if not self._testnet else self._TESTNET_LINEAR_PRIVATE 87 | Thread(target=self._loop, args=[wsurl, topics], daemon=True).start() 88 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup 4 | 5 | setup( 6 | name='pybybit', 7 | packages=['pybybit', 'pybybit.util'], 8 | version='2.0.4', 9 | description='Bybit API client library for Python', 10 | author='MtkN1XBt', 11 | url='https://github.com/MtkN1/pybybit', 12 | install_requires=['requests', 'websocket_client'] 13 | ) 14 | --------------------------------------------------------------------------------