├── .gitignore ├── setup.py ├── LICENSE ├── README.md └── bitfinexpy └── __init__.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.pyc 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from distutils.core import setup 4 | 5 | setup(name='bitfinexpy', 6 | version='1.0', 7 | url='https://github.com/jimako1989/bitfinexpy', 8 | packages=['bitfinexpy'], 9 | ) 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 jimako 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | bitfinexpy 2 | ====== 3 | Python wrapper for Bitfinex API. 4 | 5 | Dependencies 6 | ====== 7 | Requests and Pandas libraries are required. 8 | 9 | Usage 10 | ====== 11 | 12 | Include the bitfinexpy module and create an bitfinexpy instance with your account credentials. For trading, a key and a secret key must be provided. 13 | 14 | import bitfinexpy 15 | 16 | bitfinex = bitfinexpy.API(environment="live", key="AaBbCc012...", secret_key="123a456...") 17 | 18 | **Method names are referred by the part of HTML label name after #, which you can see [Bitfinex API web page](http://docs.bitfinex.com/).** 19 | 20 | **In the label name, you don't forget to replace all '-'s with '_'.** (e.g. multiple-new-orders -> multiple_new_orders) 21 | 22 | 23 | Examples 24 | ====== 25 | 26 | ### Get the latest BTCUSD price 27 | bitfinex.ticker(symbol='BTCUSD') 28 | 29 | ### View your active orders 30 | bitfinex.active_orders() 31 | 32 | ### See your balances 33 | bitfinex.wallet_balances() 34 | 35 | ### Submit a new order 36 | For example, if you'd like to buy 0.001 BTC as 0.01 BTC/USD, you need to specify following parameters. 37 | 38 | bitfinex.new_order(symbol="BTCUSD", amount=0.001, price=0.01, side="buy", type="market") 39 | 40 | 41 | BTC Price Streaming 42 | ====== 43 | Create a custom streamer class to setup how you want to handle the data. 44 | Each tick is sent through the `on_success` and `on_error` functions. 45 | You can override these functions to handle the streaming data. 46 | 47 | Initialize an instance of your custom streamer, and start connecting to the stream. 48 | 49 | stream = bitfinexpy.Streamer(environment=DOMAIN, heartbeat=1.0) 50 | stream.start() 51 | 52 | 53 | 54 | Copyright (c) 2015 jimako1989 55 | -------------------------------------------------------------------------------- /bitfinexpy/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ BITFINEX API wrapper """ 3 | 4 | """ 5 | AUTHOR: @jimako1989 6 | GITHUB: github.com/jimako1989/bitfinexpy 7 | LICENSE: MIT 8 | """ 9 | 10 | import json,time,hmac,hashlib,requests,datetime,base64 11 | 12 | """EndpointsMixin provides a mixin for the API instance """ 13 | class EndpointsMixin(object): 14 | 15 | """Public API""" 16 | def ticker(self, **params): 17 | """ Gives innermost bid and asks and information on the most recent trade, as well as high, low and volume of the last 24 hours. 18 | Docs: http://docs.bitfinex.com/#ticker 19 | """ 20 | symbol = params.pop('symbol') 21 | endpoint = '/pubticker/'+symbol 22 | return self.request(endpoint, auth=False, params=params) 23 | 24 | def stats(self, **params): 25 | """ Various statistics about the requested pair. 26 | Docs: http://docs.bitfinex.com/#stats 27 | """ 28 | symbol = params.pop('symbol') 29 | endpoint = '/stats/'+symbol 30 | return self.request(endpoint, auth=False, params=params) 31 | 32 | def fundingbook(self, **params): 33 | """ Get the full margin funding book. 34 | Docs: http://docs.bitfinex.com/#fundingbook 35 | """ 36 | symbol = params.pop('symbol') 37 | endpoint = '/lendbook/'+symbol 38 | return self.request(endpoint, auth=False, params=params) 39 | 40 | def orderbook(self,**params): 41 | """ Get the full order book. 42 | Docs: http://docs.bitfinex.com/#orderbook 43 | """ 44 | symbol = params.pop('symbol') 45 | endpoint = '/book/'+symbol 46 | return self.request(endpoint, auth=False, params=params) 47 | 48 | def trades(self,**params): 49 | """ Get a list of the most recent trades for the given symbol. 50 | Docs: http://docs.bitfinex.com/#trades 51 | """ 52 | symbol = params.pop('symbol') 53 | endpoint = '/trades/'+symbol 54 | return self.request(endpoint, auth=False, params=params) 55 | 56 | def lends(self,**params): 57 | """ Get a list of the most recent funding data for the given currency: total amount lent and Flash Return Rate (in % by 365 days) over time. 58 | Docs: http://docs.bitfinex.com/#lends 59 | """ 60 | currency = params.pop('currency') 61 | endpoint = '/lends/'+currency 62 | return self.request(endpoint, auth=False, params=params) 63 | 64 | def symbols(self,**params): 65 | """ Get a list of valid symbol IDs. 66 | Docs: http://docs.bitfinex.com/#symbols 67 | """ 68 | endpoint = '/symbols' 69 | return self.request(endpoint, auth=False, params=params) 70 | 71 | def symbol_details(self,**params): 72 | """ Get a list of valid symbol IDs and the pair details. 73 | Docs: http://docs.bitfinex.com/#symbol-details 74 | """ 75 | endpoint = '/symbols_details' 76 | return self.request(endpoint, auth=False, params=params) 77 | 78 | 79 | """ Private API """ 80 | """ Account """ 81 | def account_infos(self, **params): 82 | """ Check the balance. 83 | Docs: http://docs.bitfinex.com/#account-info 84 | """ 85 | endpoint = '/account_infos' 86 | return self.request(endpoint, params=params) 87 | 88 | """ Deposit """ 89 | def deposit(self, **params): 90 | """ Return your deposit address to make a new deposit. 91 | Docs: http://docs.bitfinex.com/#deposit 92 | """ 93 | endpoint = '/deposit/new' 94 | return self.request(endpoint, payload_params=params) 95 | 96 | """ Order """ 97 | def new_order(self, symbol, amount, price, side, order_type, **params): 98 | """ Submit a new order. 99 | Docs: http://docs.bitfinex.com/#new-order 100 | """ 101 | endpoint = '/order/new' 102 | params['symbol'] = symbol 103 | params['amount'] = amount 104 | params['price'] = price 105 | params['side'] = side 106 | params['type'] = order_type 107 | params['exchange'] = 'bitfinex' 108 | return self.request(endpoint, method='POST', payload_params=params) 109 | 110 | def multiple_new_orders(self, orders, **params): 111 | """ Submit several new orders at once. 112 | Docs: http://docs.bitfinex.com/#new-order 113 | """ 114 | endpoint = '/order/new/multi' 115 | params['orders']=orders 116 | return self.request(endpoint, method='POST', payload_params=params) 117 | 118 | def cancel_order(self, order_id, **params): 119 | """ Cancel an order. 120 | Docs: http://docs.bitfinex.com/#cancel-order 121 | """ 122 | endpoint = '/order/cancel' 123 | params['order_id'] = order_id 124 | return self.request(endpoint, method='POST', payload_params=params) 125 | 126 | def cancel_multiple_orders(self, order_ids, **params): 127 | """ Cancel multiples orders at once. 128 | Docs: http://docs.bitfinex.com/#cancel-multiple-orders 129 | """ 130 | endpoint = '/order/cancel/multi' 131 | params['order_ids'] = order_ids 132 | return self.request(endpoint, method='POST', payload_params=params) 133 | 134 | def cancel_all_orders(self, **params): 135 | """ Cancel multiples orders at once. 136 | Docs: http://docs.bitfinex.com/#cancel-all-orders 137 | """ 138 | endpoint = '/order/cancel/all' 139 | return self.request(endpoint, method='POST', payload_params=params) 140 | 141 | def replace_order(self, order_id, symbol, amount, price, side, order_type): 142 | """ Replace an orders with a new one. 143 | Docs: http://docs.bitfinex.com/#replace-orders 144 | """ 145 | endpoint = '/order/cancel/replace' 146 | params['order_id'] = order_id 147 | params['symbol'] = symbol 148 | params['amount'] = amount 149 | params['price'] = price 150 | params['side'] = side 151 | params['type'] = order_type 152 | params['exchange'] = 'bitfinex' 153 | return self.request(endpoint, method='POST', payload_params=params) 154 | 155 | def order_status(self, order_id, **params): 156 | """ Get the status of an order. Is it active? Was it cancelled? To what extent has it been executed? etc. 157 | Docs: http://docs.bitfinex.com/#order-status 158 | """ 159 | endpoint = '/order/status' 160 | params['order_id'] = order_id 161 | return self.request(endpoint, method='POST', payload_params=params) 162 | 163 | def active_orders(self, **params): 164 | """ View your active orders. 165 | Docs: http://docs.bitfinex.com/#active-orders 166 | """ 167 | endpoint = '/orders' 168 | return self.request(endpoint, method='POST', payload_params=params) 169 | 170 | """ Positions """ 171 | def active_positions(self, **params): 172 | """ View your active positions. 173 | Docs: http://docs.bitfinex.com/#active-positions 174 | """ 175 | endpoint = '/positions' 176 | return self.request(endpoint, method='POST', payload_params=params) 177 | 178 | def claim_position(self, position_id, **params): 179 | """ A position can be claimed if: 180 | It is a long position: 181 | The amount in the last unit of the position pair that you have in your trading wallet AND/OR the realized profit of the position is greater or equal to the purchase amount of the position (base price * position amount) and the funds which need to be returned. 182 | For example, for a long BTCUSD position, you can claim the position if the amount of USD you have in the trading wallet is greater than the base price * the position amount and the funds used. 183 | It is a short position: 184 | The amount in the first unit of the position pair that you have in your trading wallet is greater or equal to the amount of the position and the margin funding used. 185 | Docs: http://docs.bitfinex.com/#claim-position 186 | """ 187 | endpoint = '/position/claim' 188 | params['position_id'] = position_id 189 | return self.request(endpoint, method='POST', payload_params=params) 190 | 191 | """ Historical Data """ 192 | def balance_history(self, currency, **params): 193 | """ View all of your balance ledger entries. 194 | Docs: http://docs.bitfinex.com/#balance-history 195 | """ 196 | endpoint = '/history' 197 | params['currency'] = currency 198 | return self.request(endpoint, method='POST', payload_params=params) 199 | 200 | def deposit_withdrawal_history(self, currency, **params): 201 | """ View all of your balance ledger entries. 202 | Docs: http://docs.bitfinex.com/#balance-history 203 | """ 204 | endpoint = '/history/movements' 205 | params['currency'] = currency 206 | return self.request(endpoint, method='POST', payload_params=params) 207 | 208 | def past_trades(self, symbol, **params): 209 | """ View all of your balance ledger entries. 210 | Docs: http://docs.bitfinex.com/#balance-history 211 | """ 212 | endpoint = '/mytrades' 213 | params['symbol'] = symbol 214 | return self.request(endpoint, method='POST', payload_params=params) 215 | 216 | """ Margin Funding """ 217 | def new_offer(self, currency, amount, rate, period, direction, **params): 218 | """ Submit a new offer. 219 | Docs: http://docs.bitfinex.com/#new-offer 220 | """ 221 | endpoint = '/offer/new' 222 | params['currency'] = currency 223 | params['amount'] = amount 224 | params['rate'] = rate 225 | params['period'] = period 226 | params['direction'] = direction 227 | return self.request(endpoint, method='POST', payload_params=params) 228 | 229 | def cancel_offer(self, offer_id, **params): 230 | """ Cancel an offer. 231 | Docs: http://docs.bitfinex.com/#cancel-offer 232 | """ 233 | endpoint = '/offer/cancel' 234 | params['offer_id'] = offer_id 235 | return self.request(endpoint, method='POST', payload_params=params) 236 | 237 | def offer_status(self, offer_id, **params): 238 | """ Get the status of an offer. Is it active? Was it cancelled? To what extent has it been executed? etc. 239 | Docs: http://docs.bitfinex.com/#offer-status 240 | """ 241 | endpoint = '/offer/status' 242 | params['offer_id'] = offer_id 243 | return self.request(endpoint, method='POST', payload_params=params) 244 | 245 | def active_credits(self, **params): 246 | """ View your active offers. 247 | Docs: http://docs.bitfinex.com/#active-credits 248 | """ 249 | endpoint = '/offers' 250 | return self.request(endpoint, method='POST', payload_params=params) 251 | 252 | def active_funding_used_in_a_margin_position(self, **params): 253 | """ View your funding currently borrowed and used in a margin position. 254 | Docs: http://docs.bitfinex.com/#active-funding-used-in-a-margin-position 255 | """ 256 | endpoint = '/taken_funds' 257 | return self.request(endpoint, method='POST', payload_params=params) 258 | 259 | def total_taken_funds(self, **params): 260 | """ View the total of your active-funding used in your position(s). 261 | Docs: http://docs.bitfinex.com/#total-taken-funds 262 | """ 263 | endpoint = '/total_taken_funds' 264 | return self.request(endpoint, method='POST', payload_params=params) 265 | 266 | def close_margin_funding(self, **params): 267 | """ Return the funding taken in a margin position. 268 | Docs: http://docs.bitfinex.com/#total-taken-funds 269 | """ 270 | endpoint = '/funding/close' 271 | return self.request(endpoint, method='POST', payload_params=params) 272 | 273 | """ Wallet Balances """ 274 | def wallet_balances(self, **params): 275 | """ See your balances. 276 | Docs: http://docs.bitfinex.com/#wallet-balances 277 | """ 278 | endpoint = '/balances' 279 | return self.request(endpoint, method='POST', payload_params=params) 280 | 281 | """ Margin Information """ 282 | def margin_information(self, **params): 283 | """ See your trading wallet information for margin trading. 284 | Docs: http://docs.bitfinex.com/#margin-information 285 | """ 286 | endpoint = '/margin_infos' 287 | return self.request(endpoint, method='POST', payload_params=params) 288 | 289 | """ Transfer Between Wallets """ 290 | def offer_status(self, amount, currency, walletfrom, walletto, **params): 291 | """ Allow you to move available balances between your wallets. 292 | Docs: http://docs.bitfinex.com/#margin-information 293 | """ 294 | endpoint = '/transfer' 295 | params['amount'] = amount 296 | params['currency'] = currency 297 | params['walletfrom'] = walletfrom 298 | params['walletto'] = walletto 299 | return self.request(endpoint, method='POST', payload_params=params) 300 | 301 | """ Withdrawal """ 302 | def withdrawal(self, withdraw_type, walletselected, amount, **params): 303 | """ Allow you to request a withdrawal from one of your wallet. 304 | Docs: http://docs.bitfinex.com/#withdrawal 305 | """ 306 | endpoint = '/withdraw' 307 | params['withdraw_type'] = withdraw_type 308 | params['walletselected'] = walletselected 309 | params['amount'] = amount 310 | return self.request(endpoint, method='POST', payload_params=params) 311 | 312 | 313 | 314 | """ Provides functionality for access to core BITFINEX API calls """ 315 | 316 | class API(EndpointsMixin, object): 317 | def __init__(self, environment='live', key=None, secret_key=None): 318 | """ Instantiates an instance of BitfinexPy's API wrapper """ 319 | 320 | if environment == 'live': 321 | self.api_url = 'https://api.bitfinex.com/v1' 322 | else: 323 | # for future, access to a demo account. 324 | pass 325 | 326 | self.key = key 327 | self.secret_key = bytes(secret_key, 'utf-8') 328 | self.nonce = str(time.time() * 100000) 329 | 330 | self.client = requests.Session() 331 | 332 | def request(self, endpoint, method='GET', auth=True, params=None, payload_params=None): 333 | """ Returns dict of response from Bitfinex's open API """ 334 | method = method.lower() 335 | 336 | url = '%s%s' % ( self.api_url, endpoint) 337 | 338 | request_args = {'params':params} 339 | 340 | if auth: 341 | payloadObject = { 342 | "request": "/v1%s" % endpoint, 343 | "nonce": self.nonce 344 | } 345 | if payload_params is not None: 346 | payloadObject.update(payload_params) 347 | payload = base64.b64encode(bytes(json.dumps(payloadObject), "utf-8")) 348 | signature = hmac.new(self.secret_key, msg=payload, digestmod=hashlib.sha384).hexdigest() 349 | request_args['headers'] = { 350 | 'X-BFX-APIKEY': self.key, 351 | 'X-BFX-PAYLOAD': payload, 352 | 'X-BFX-SIGNATURE': signature 353 | } 354 | #request_args['data'] = {} 355 | 356 | func = getattr(self.client, method) 357 | try: 358 | response = func(url, **request_args) 359 | except requests.RequestException as e: 360 | print (str(e)) 361 | 362 | content = response.json() 363 | 364 | # error message 365 | if response.status_code >= 400: 366 | print("error_response : %s" % content) 367 | raise BitfinexError(response.status_code,content) 368 | 369 | return content 370 | 371 | 372 | """HTTPS Streaming""" 373 | class Streamer(): 374 | """ Provides functionality for HTTPS Streaming """ 375 | 376 | def __init__(self, symbol, environment='live', heartbeat=1.0): 377 | """ Instantiates an instance of BitfinexPy's streaming API wrapper. """ 378 | 379 | if environment == 'live': 380 | self.api_url = 'https://api.bitfinex.com/v1/pubticker/'+symbol 381 | else: 382 | # for future, access to a demo account. 383 | pass 384 | 385 | self.heartbeat = heartbeat 386 | 387 | self.client = requests.Session() 388 | 389 | 390 | def start(self, **params): 391 | """ Starts the stream with the given parameters """ 392 | self.connected = True 393 | 394 | request_args = {} 395 | 396 | content_ = {'last':None,'bid':None,'volume':None,'ask':None,'low':None,'high':None} 397 | 398 | while self.connected: 399 | response = self.client.get(self.api_url, **request_args) 400 | content = response.content.decode('ascii') 401 | content = json.loads(content) 402 | 403 | if response.status_code != 200: 404 | self.on_error(content) 405 | 406 | # when the tick is updated 407 | if any([content[key] != content_[key] for key in ['last', 'bid', 'volume', 'ask', 'low', 'high']]): 408 | self.on_success(content) 409 | content_ = content 410 | 411 | time.sleep(self.heartbeat) 412 | 413 | def on_success(self, content): 414 | """ Called when data is successfully retrieved from the stream """ 415 | print(content) 416 | return True 417 | 418 | def on_error(self, content): 419 | """ Called when stream returns non-200 status code 420 | Override this to handle your streaming data. 421 | """ 422 | self.connected = False 423 | return 424 | 425 | 426 | """ Contains BITFINEX exception """ 427 | class BitfinexError(Exception): 428 | """ Generic error class, catches bitfinex response errors 429 | """ 430 | 431 | def __init__(self, status_code, error_response): 432 | this.status_code = status_code 433 | this.error_response = error_response 434 | 435 | msg = "BITFINEX API returned error code %s (%s)" % (status_code, error_response.get('error', error_response.get('message'))) 436 | super(BitfinexError, self).__init__(msg) --------------------------------------------------------------------------------