├── .gitignore ├── README.md ├── bpx ├── __init__.py ├── __version__.py ├── bpx.py └── bpx_pub.py ├── example ├── __init__.py ├── bpx_pub_test.py ├── bpx_test.py └── bpx_ws_test.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | .idea 6 | venv 7 | setup.py 8 | bpx.egg-info 9 | build 10 | dist 11 | *.egg-info/* 12 | _build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bpx-api-py 2 | Backpack Exchange Python code 3 | 4 | 5 | # SDK Docs 6 | https://sndmndss.github.io/bpx-api-py/index.html 7 | 8 | 9 | # API Docs 10 | https://docs.backpack.exchange/ 11 | 12 | 13 | `pip install bpx-api` 14 | 15 | ### 好用请Star 16 | 17 | 18 | ### 19 | 20 | -------------------------------------------------------------------------------- /bpx/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syp25815/bpx-api-py/1e57af3e73e7eafa28a72483fd4f43532a9d3cc9/bpx/__init__.py -------------------------------------------------------------------------------- /bpx/__version__.py: -------------------------------------------------------------------------------- 1 | __title__ = "bpx-api-python" 2 | __description__ = "Backpack Exchange api for python" 3 | __version__ = "1.0.2" 4 | __build__ = 0x000005 5 | __author__ = "syp25815" 6 | __author_email__ = "syp25815@gmail.com" 7 | -------------------------------------------------------------------------------- /bpx/bpx.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import time 4 | import requests 5 | from cryptography.hazmat.primitives.asymmetric import ed25519 6 | 7 | 8 | class BpxClient: 9 | url = 'https://api.backpack.exchange/' 10 | private_key: ed25519.Ed25519PrivateKey 11 | 12 | def __init__(self): 13 | self.debug = False 14 | self.proxies = { 15 | 'http': '', 16 | 'https': '' 17 | } 18 | self.api_key = '' 19 | self.api_secret = '' 20 | self.debugTs = 0 21 | self.window = 5000 22 | 23 | def init(self, api_key, api_secret): 24 | self.api_key = api_key 25 | self.api_secret = api_secret 26 | self.private_key = ed25519.Ed25519PrivateKey.from_private_bytes( 27 | base64.b64decode(api_secret) 28 | ) 29 | 30 | def _handle_bpx_request(self, url, headers, params=None, r_type='GET'): 31 | if r_type == 'GET': 32 | response = requests.get(url=url, proxies=self.proxies, headers=headers, params=params) 33 | elif r_type == 'POST': 34 | response = requests.post(url=url, proxies=self.proxies, headers=headers, data=json.dumps(params)) 35 | else: 36 | response = requests.delete(url=url, proxies=self.proxies, headers=headers, data=json.dumps(params)) 37 | try: 38 | return response.json() 39 | except json.JSONDecodeError: 40 | return response.text 41 | 42 | # capital 43 | def balances(self): 44 | return self._handle_bpx_request(url=f'{self.url}api/v1/capital', 45 | headers=self.sign('balanceQuery')) 46 | 47 | def deposits(self): 48 | return self._handle_bpx_request(url=f'{self.url}wapi/v1/capital/deposits', 49 | headers=self.sign('depositQueryAll')) 50 | 51 | def depositAddress(self, chain: str): 52 | params = {'blockchain': chain} 53 | return self._handle_bpx_request(url=f'{self.url}wapi/v1/capital/deposit/address', 54 | headers=self.sign('depositAddressQuery', params), 55 | params=params) 56 | 57 | # set withdrawal address: 58 | # https://backpack.exchange/settings/withdrawal-addresses?twoFactorWithdrawalAddress=true 59 | def withdrawals(self, limit: int, offset: int): 60 | params = {'limit': limit, 'offset': offset} 61 | return self._handle_bpx_request(url=f'{self.url}wapi/v1/capital/withdrawals', 62 | headers=self.sign('withdrawalQueryAll', params), 63 | params=params) 64 | 65 | def withdrawal(self, address: str, symbol: str, blockchain: str, quantity: str): 66 | params = { 67 | 'address': address, 68 | 'blockchain': blockchain, 69 | 'quantity': quantity, 70 | 'symbol': symbol, 71 | } 72 | return self._handle_bpx_request(url=f'{self.url}wapi/v1/capital/withdrawals', 73 | headers=self.sign('withdraw', params), 74 | params=params, 75 | r_type='POST') 76 | 77 | # history 78 | 79 | def orderHistoryQuery(self, symbol: str, limit: int, offset: int): 80 | params = {'symbol': symbol, 'limit': limit, 'offset': offset} 81 | return self._handle_bpx_request(url=f'{self.url}wapi/v1/history/orders', params=params, 82 | headers=self.sign('orderHistoryQueryAll', params)) 83 | 84 | def fillHistoryQuery(self, symbol: str, limit: int, offset: int): 85 | params = {'limit': limit, 'offset': offset} 86 | if len(symbol) > 0: 87 | params['symbol'] = symbol 88 | return self._handle_bpx_request(url=f'{self.url}wapi/v1/history/fills', params=params, 89 | headers=self.sign('fillHistoryQueryAll', params)) 90 | 91 | # order 92 | 93 | def orderQuery(self, symbol: str, orderId: str, clientId: int = -1): 94 | params = {'symbol': symbol} 95 | if len(orderId) > 0: 96 | params['orderId'] = orderId 97 | if clientId > -1: 98 | params['clientId'] = clientId 99 | return self._handle_bpx_request(url=f'{self.url}api/v1/order', params=params, 100 | headers=self.sign('orderQuery', params)) 101 | 102 | def ExeOrder(self, symbol, side, orderType, timeInForce, quantity, price): 103 | params = { 104 | 'symbol': symbol, 105 | 'side': side, 106 | 'orderType': orderType, 107 | 'quantity': quantity, 108 | 'price': price 109 | } 110 | 111 | if len(timeInForce) < 1: 112 | params['postOnly'] = True 113 | else: 114 | params['timeInForce'] = timeInForce 115 | return self._handle_bpx_request(url=f'{self.url}api/v1/order', params=params, 116 | headers=self.sign('orderExecute', params), r_type='POST') 117 | 118 | def orderCancel(self, symbol: str, orderId: str, clientId: int = -1): 119 | params = {'symbol': symbol} 120 | if len(orderId) > 0: 121 | params['orderId'] = orderId 122 | if clientId > -1: 123 | params['clientId'] = clientId 124 | return self._handle_bpx_request(url=f'{self.url}api/v1/order', params=params, 125 | headers=self.sign('orderCancel', params), r_type='DELETE') 126 | 127 | def ordersQuery(self, symbol: str): 128 | params = {} 129 | if len(symbol) > 0: 130 | params['symbol'] = symbol 131 | 132 | return self._handle_bpx_request(url=f'{self.url}api/v1/orders', params=params, 133 | headers=self.sign('orderQueryAll', params)) 134 | 135 | def ordersCancel(self, symbol: str): 136 | params = {'symbol': symbol} 137 | return self._handle_bpx_request(url=f'{self.url}api/v1/orders', params=params, 138 | headers=self.sign('orderCancelAll', params), r_type='DELETE') 139 | 140 | def sign(self, instruction: str, params=None): 141 | ts = int(time.time() * 1e3) 142 | encoded_signature = self.build_sign(instruction, ts, params) 143 | headers = { 144 | "X-API-Key": self.api_key, 145 | "X-Signature": encoded_signature, 146 | "X-Timestamp": str(ts), 147 | "X-Window": str(self.window), 148 | "Content-Type": "application/json; charset=utf-8", 149 | } 150 | return headers 151 | 152 | def ws_sign(self, instruction: str, params=None): 153 | ts = int(time.time() * 1e3) 154 | encoded_signature = self.build_sign(instruction, ts, params) 155 | # 必须将ts、window转为字符串,不然报错: Parse error 156 | result = [self.api_key, encoded_signature, str(ts), str(self.window)] 157 | return result 158 | 159 | def build_sign(self, instruction: str, ts: int, params=None): 160 | sign_str = f"instruction={instruction}" if instruction else "" 161 | if params is None: 162 | params = {} 163 | if 'postOnly' in params: 164 | params = params.copy() 165 | params['postOnly'] = str(params['postOnly']).lower() 166 | sorted_params = "&".join( 167 | f"{key}={value}" for key, value in sorted(params.items()) 168 | ) 169 | if sorted_params: 170 | sign_str += "&" + sorted_params 171 | if self.debug and self.debugTs > 0: 172 | ts = self.debugTs 173 | sign_str += f"×tamp={ts}&window={self.window}" 174 | signature_bytes = self.private_key.sign(sign_str.encode()) 175 | encoded_signature = base64.b64encode(signature_bytes).decode() 176 | if self.debug: 177 | print(f'Waiting Sign Str: {sign_str}') 178 | print(f"Signature: {encoded_signature}") 179 | return encoded_signature 180 | -------------------------------------------------------------------------------- /bpx/bpx_pub.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | BP_BASE_URL = ' https://api.backpack.exchange/' 4 | 5 | 6 | # Markets 7 | 8 | def Assets(): 9 | return requests.get(url=f'{BP_BASE_URL}api/v1/assets').json() 10 | 11 | 12 | def Markets(): 13 | return requests.get(url=f'{BP_BASE_URL}api/v1/markets').json() 14 | 15 | 16 | def Ticker(symbol: str): 17 | return requests.get(url=f'{BP_BASE_URL}api/v1/ticker?symbol={symbol}').json() 18 | 19 | 20 | def Depth(symbol: str): 21 | return requests.get(url=f'{BP_BASE_URL}api/v1/depth?symbol={symbol}').json() 22 | 23 | 24 | def KLines(symbol: str, interval: str, startTime: int = 0, endTime: int = 0): 25 | url = f'{BP_BASE_URL}api/v1/klines?symbol={symbol}&interval={interval}' 26 | 27 | if startTime > 0: 28 | url = f'{url}&startTime={startTime}' 29 | if endTime > 0: 30 | url = f'{url}&endTime={endTime}' 31 | 32 | return requests.get(url).json() 33 | 34 | 35 | # System 36 | def Status(): 37 | return requests.get(url=f'{BP_BASE_URL}api/v1/status').json() 38 | 39 | 40 | def Ping(): 41 | return requests.get(url=f'{BP_BASE_URL}api/v1/ping').text 42 | 43 | 44 | def Time(): 45 | return requests.get(url=f'{BP_BASE_URL}api/v1/time').text 46 | 47 | 48 | # Trades 49 | def recentTrades(symbol: str, limit: int = 100): 50 | return requests.get(url=f'{BP_BASE_URL}api/v1/trades?symbol={symbol}&limit={limit}').json() 51 | 52 | 53 | def historyTrades(symbol: str, limit: int = 100, offset: int = 0): 54 | return requests.get(url=f'{BP_BASE_URL}api/v1/trades/history?symbol={symbol}&limit={limit}&offset={offset}').json() 55 | 56 | 57 | if __name__ == '__main__': 58 | # print(Assets()) 59 | print(Markets()) 60 | # print(Ticker('SOL_USDC')) 61 | # print(Depth('SOL_USDC')) 62 | # print(KLines('SOL_USDC', '1m')) 63 | # print(Status()) 64 | # print(Ping()) 65 | # print(Time()) 66 | # print(recentTrades('SOL_USDC', 10)) 67 | # print(historyTrades('SOL_USDC', 10)) 68 | pass 69 | -------------------------------------------------------------------------------- /example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syp25815/bpx-api-py/1e57af3e73e7eafa28a72483fd4f43532a9d3cc9/example/__init__.py -------------------------------------------------------------------------------- /example/bpx_pub_test.py: -------------------------------------------------------------------------------- 1 | from bpx.bpx_pub import * 2 | 3 | if __name__ == '__main__': 4 | print(Assets()) 5 | print(Markets()) 6 | print(Ticker('SOL_USDC')) 7 | print(Depth('SOL_USDC')) 8 | print(KLines('SOL_USDC', '1m')) 9 | print(Status()) 10 | print(Ping()) 11 | print(Time()) 12 | print(recentTrades('SOL_USDC', 10)) 13 | print(historyTrades('SOL_USDC', 10)) 14 | -------------------------------------------------------------------------------- /example/bpx_test.py: -------------------------------------------------------------------------------- 1 | from bpx.bpx import * 2 | 3 | if __name__ == '__main__': 4 | bpx = BpxClient() 5 | 6 | bpx.init("T/NyhImyTeSHIUwblbxCGi9GhQHuwIPchW5Uqv91CFc=", "") 7 | 8 | print(bpx.depositAddress('Bitcoin')) 9 | # 10 | # print(bpx.balances()) 11 | # print(bpx.deposits()) 12 | # # 13 | # print(bpx.withdrawals(10, 0)) 14 | 15 | bpx.debug = True 16 | 17 | # print(bpx.withdrawal("", "USDC", "Solana", "600")) 18 | 19 | # print(bpx.orderQuery('SOL_USDC', '111948072781414400')) 20 | # print(bpx.ordersQuery('')) 21 | 22 | # print(bpx.orderCancel('SOL_USDC','111947854837907456')) 23 | # print(bpx.ordersCancel('SOL_USDC')) 24 | 25 | # 26 | # print(bpx.orderHistoryQuery('SOL_USDC', 10, 0)) 27 | # print(bpx.fillHistoryQuery('SOL_USDC', 10, 0)) 28 | # bpx.proxies = {'http': 'http://127.0.0', 'https': 'http://127.0.0.'} 29 | # 30 | # print(bpx.ExeOrder('SOL_USDC', 'Bid', 'Limit', 'IOC', '0.1', '116.35')) 31 | # print(bpx.ExeOrder('SOL_USDC', 'Bid', 'Limit', '', '1', '13')) 32 | # 33 | -------------------------------------------------------------------------------- /example/bpx_ws_test.py: -------------------------------------------------------------------------------- 1 | import rel 2 | import websocket 3 | from bpx.bpx import * 4 | 5 | 6 | def on_message(ws, message): 7 | print('Received message: {}'.format(message)) 8 | # data = json.loads(message) 9 | # 10 | # print(f'流事件:{data["stream"]} 流数据: {data["data"]["e"]} 代币: {data["data"]["s"]}') 11 | # # print(message['stream']) 12 | # print(message) 13 | 14 | 15 | def on_error(ws, error): 16 | print(error) 17 | 18 | 19 | def on_close(ws, close_status_code, close_msg): 20 | print("### closed ###") 21 | 22 | 23 | def on_open(ws): 24 | print("Opened connection") 25 | 26 | 27 | if __name__ == '__main__': 28 | bpx = BpxClient() 29 | 30 | bpx.init("", "") 31 | 32 | print(bpx.depositAddress('Solana')) 33 | 34 | # # websocket.WebSocket 35 | ws = websocket.WebSocketApp("wss://ws.backpack.exchange", 36 | on_open=on_open, 37 | on_message=on_message, 38 | on_error=on_error, 39 | on_close=on_close) 40 | # 41 | ws.run_forever(dispatcher=rel, 42 | reconnect=5) 43 | 44 | params = { 45 | "method": "SUBSCRIBE", 46 | "params": ["account.orderUpdate"], 47 | "signature": bpx.ws_sign('subscribe'), 48 | 49 | } 50 | 51 | ws.send(json.dumps(params)) 52 | # 53 | # 54 | rel.signal(2, rel.abort) # Keyboard Interrupt 55 | rel.dispatch() 56 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests~=2.31.0 2 | cryptography~=42.0.5 3 | pyotp~=2.9.0 4 | 5 | requests[socks] 6 | websocket-client~=1.6.4 7 | rel~=0.4.9.6 --------------------------------------------------------------------------------