SHARE
TWEET

[btcelib.py] BTC-e Library: Public API v3 and Trade API v1

stozher Oct 9th, 2014 (edited) 8,153 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. # coding: utf-8
  2. # python: 2.7.8 [PyPy 2.4.0 with GCC 4.9.2]
  3. # module: btcelib.py <https://pastebin.com/kABSEyYB>
  4. # import: simplejson <https://pypi.python.org/pypi/simplejson>
  5. # copyright: (c)2017, John Saturday <saturday.6524@gmail.com>
  6.  
  7. # BTC: 1FfcxZUTyms1uNRy49m8kgjBFT3m9CsmdJ
  8. # ETH: 0x10b4E5330DAdF1c9aaE66724Accfd81E9cFD996a
  9. # LTC: LaPeosd3pBQ7UrnajEHjVRBKDkev1RhC6g
  10. # DASH: XhDBC3wdjB3qTgvW1XbAHGyHwfA2RDg3s3
  11.  
  12. """BTC-e Library: Public API v3 and Trade API v1
  13.  
  14. The MIT License <https://opensource.org/licenses/MIT>.
  15. Copyright (c)2017, John Saturday <saturday.6524@gmail.com>.
  16.  
  17. THE BTC-E IS NOT AFFILIATED WITH THIS PROJECT. THIS IS A COMPLETELY
  18. INDEPENDENT IMPLEMENTATION BASED ON THE PUBLIC/TRADE API DESCRIPTION.
  19.  
  20. BTC-e Public API v3 <https://btc-e.com/api/3/docs>.
  21. BTC-e Trade API v1 <https://btc-e.com/tapi/docs>.
  22.  
  23. EXCEPTIONS:
  24.    btcelib.APIError, httplib.HTTPException, socket.error
  25.  
  26. CLASSES:
  27.    __builtin__.object
  28.        Connection
  29.            PublicAPIv3
  30.            TradeAPIv1
  31.    exceptions.Exception(exceptions.BaseException)
  32.        APIError
  33.  
  34. EXAMPLE (Public API):
  35.    >>> import btcelib
  36.    >>> papi = btcelib.PublicAPIv3()
  37.    >>> data = papi.call('ticker', ignore_invalid=1)
  38.    >>> print data    # all pairs
  39.    >>> data = papi.call('depth', 'btc_usd', limit=5)
  40.    >>> print data    # btc_usd only
  41.    >>> data = papi.call('depth', 'eth_btc-ltc_btc, limit=5)
  42.    >>> print data    # two pairs (or more)
  43.  
  44. EXAMPLE (Trade API):
  45.    >>> import btcelib
  46.    >>> api_key = {'Key': 'YOUR-KEY',
  47.    ...            'Secret': 'YOUR-SECRET'}
  48.    >>> tapi = btcelib.TradeAPIv1(api_key)
  49.    >>> data = tapi.call('TradeHistory', pair='btc_usd', count=1)
  50.    >>> print data    """
  51.  
  52. __date__ = "2017-06-29T17:41:37+0300"
  53. __author__ = "John Saturday <saturday.6524@gmail.com>"
  54. __credits__ = "Alan McIntyre <https://github.com/alanmcintyre>"
  55.  
  56. from httplib import OK as _HTTP_OK
  57. from zlib import MAX_WBITS as _MAX_WBITS
  58.  
  59. from Cookie import CookieError
  60. from httplib import HTTPException, BadStatusLine
  61. from socket import error as SocketError
  62.  
  63. from Cookie import SimpleCookie
  64. from datetime import datetime
  65. from decimal import Decimal
  66. from hashlib import sha512 as _sha512
  67. from hmac import new as newhash
  68. from httplib import HTTPSConnection
  69. from re import search
  70. from urllib import urlencode
  71. from zlib import decompress as _zdecompress
  72.  
  73. try:
  74.     from simplejson import loads as jsonloads
  75. except ImportError:
  76.     from json import loads as jsonloads
  77.  
  78. API_HOST = 'btc-e.com'     # API host (HTTP/SSL)
  79. CF_COOKIE = '__cfduid'     # CloudFlare security cookie
  80.  
  81. TIMEOUT = 2                # Public API refresh time
  82. HTTP_TIMEOUT = 30          # connection timeout (max: 60 sec)
  83.  
  84.  
  85. class APIError(Exception):
  86.     "Raise exception when the BTC-e API returned an error."
  87.     pass
  88.  
  89. class Connection(object):
  90.     """BTC-e API persistent HTTPS connection.
  91.    @cvar conn: shared httplib.HTTPSConnection between instances"""
  92.     _headers = {         # common HTTPS headers
  93.         'Accept': 'application/json',
  94.         'Accept-Charset': 'utf-8',
  95.         'Accept-Encoding': 'identity',
  96.         'Cache-Control': 'no-cache',
  97.         'Connection': 'keep-alive',
  98.         }
  99.     _post_headers = {    # common and POST headers
  100.         'Content-Type': 'application/x-www-form-urlencoded',
  101.         }
  102.     conn = None    # type httplib.HTTPSConnection
  103.     resp = None    # type httplib.HTTPResponse
  104.  
  105.     @classmethod
  106.     def __init__(cls, compr=True, timeout=HTTP_TIMEOUT):
  107.         """Initialization of shared HTTPS connection.
  108.        @param compr: HTTP compression (default: identity)
  109.        @param timeout: HTTP timeout (default: 30 sec / max 60)"""
  110.         if compr is False:
  111.             compr = 'identity'
  112.         elif compr is True:
  113.             compr = 'gzip, deflate'
  114.  
  115.         if not cls.conn:
  116.             # Create a new connection.
  117.             cls.conn = HTTPSConnection(API_HOST, strict=True, timeout=timeout)
  118.             cls._post_headers.update(cls._headers)
  119.         elif timeout != cls.conn.timeout:
  120.             # Update the connection timeout.
  121.             cls.conn.timeout = timeout
  122.             cls.conn.close()
  123.         if compr and compr != cls._headers['Accept-Encoding']:
  124.             # Update the connection compression.
  125.             cls._headers['Accept-Encoding'] = compr
  126.             cls._post_headers.update(cls._headers)
  127.             cls.conn.close()
  128.  
  129.     @classmethod
  130.     def _signature(cls, apikey, msg):
  131.         """Calculation of the SHA-512 authentication signature.
  132.        @param apikey: API-key dict {'Key': '...', 'Secret': '...'}
  133.        @param msg: method and parameters (BTC-e Trade API)"""
  134.         sign = newhash(apikey['Secret'], msg=msg, digestmod=_sha512)
  135.         cls._post_headers['Key'] = apikey['Key']
  136.         cls._post_headers['Sign'] = sign.hexdigest()
  137.  
  138.     @classmethod
  139.     def _setcookie(cls):
  140.         "Get the CloudFlare cookie and update security."
  141.         cookie_header = cls.resp.getheader('Set-Cookie')
  142.         try:
  143.             cf_cookie = SimpleCookie(cookie_header)[CF_COOKIE]
  144.         except (CookieError, KeyError):
  145.             pass    # with/out previous cookie
  146.         else:
  147.             cf_value = cf_cookie.OutputString('value')
  148.             cls._headers['Cookie'] = cls._post_headers['Cookie'] = cf_value
  149.  
  150.     @classmethod
  151.     def _decompress(cls, data):
  152.         """Decompress connection response (data).
  153.        @return: decompressed data <type 'str'>"""
  154.         encoding = cls.resp.getheader('Content-Encoding')
  155.         if encoding == 'gzip':
  156.             data = _zdecompress(data, _MAX_WBITS+16)
  157.         elif encoding == 'deflate':
  158.             data = _zdecompress(data, -_MAX_WBITS)
  159.         # else: failback to 'identity' encoding
  160.         return data
  161.  
  162.     @classmethod
  163.     def jsonrequest(cls, url, apikey=None, **params):
  164.         """Create query to the BTC-e API (JSON response).
  165.        @raise httplib.HTTPException, socket.error: HTTP errors
  166.        @param url: plain URL (without parameters)
  167.        @param apikey: API-key dict {'Key': '...', 'Secret': '...'}
  168.        @param **params: API method and/or parameters
  169.        @return: API response (JSON data) <type 'str'>"""
  170.         if apikey:    # args: Trade API
  171.             method = 'POST'
  172.             body = urlencode(params)
  173.             cls._signature(apikey, body)
  174.             headers = cls._post_headers
  175.         else:         # args: Public API
  176.             method = 'GET'
  177.             if params:
  178.                 url = '{}?{}'.format(url, urlencode(params))
  179.             body = None
  180.             headers = cls._headers
  181.         while True:
  182.             # Make a HTTPS request.
  183.             try:
  184.                 cls.conn.request(method, url, body=body, headers=headers)
  185.                 cls.resp = cls.conn.getresponse()
  186.             except BadStatusLine:
  187.                 cls.conn.close()
  188.                 continue
  189.             except (HTTPException, SocketError):
  190.                 cls.conn.close()
  191.                 raise
  192.             cls._setcookie()
  193.             break
  194.         return cls._decompress(cls.resp.read())
  195.  
  196.     @classmethod
  197.     def apirequest(cls, url, apikey=None, **params):
  198.         """Create query to the BTC-e API (decoded response).
  199.        @raise APIError, httplib.HTTPException: API & CloudFlare errors
  200.        @param url: plain URL (without parameters)
  201.        @param apikey: API-key dict {'Key': '...', 'Secret': '...'}
  202.        @param **params: API method and/or parameters
  203.        @return: API response (decoded data) <type 'dict'>"""
  204.         data = cls.jsonrequest(url, apikey, **params)
  205.         try:
  206.             data = jsonloads(data, parse_float=Decimal, parse_int=Decimal)
  207.         except ValueError:
  208.             if cls.resp.status == _HTTP_OK:
  209.                 # The API unknown errors.
  210.                 raise APIError(str(data) or "Unknown Error")
  211.             else:
  212.                 # HTTP/CloudFlare errors.
  213.                 raise HTTPException("{} {}".format(
  214.                     cls.resp.status, cls.resp.reason))
  215.         else:
  216.             if 'error' in data:
  217.                 # The API standard errors.
  218.                 raise APIError(str(data['error']))
  219.         return data
  220.  
  221. class PublicAPIv3(Connection):
  222.     "BTC-e Public API v3 <https://btc-e.com/api/3/docs>."
  223.     def __init__(self, *pairs, **connkw):
  224.         """Initialization of the BTC-e Public API.
  225.        @param *pairs: [btc_usd[-btc_rur[-...]]] or arguments
  226.        @param **connkw: compr, timeout (see: Connection class)"""
  227.         super(PublicAPIv3, self).__init__(**connkw)
  228.         self.pairs = pairs    # one str w/ delimiter '-' or params
  229.  
  230.         # Get and/or join all pairs.
  231.         if not self.pairs:
  232.             self.pairs = self.call('info')['pairs'].keys()
  233.         if not isinstance(self.pairs, str):
  234.             self.pairs = '-'.join(self.pairs)
  235.  
  236.     def call(self, method, pairs=None, **params):
  237.         """Create query to the BTC-e Public API.
  238.        @param method: info | ticker | depth | trades
  239.        @param pairs: [btc_usd[-btc_rur[-...]]] <type 'str'>
  240.        @param **params: limit=150 (max: 5000), ignore_invalid=1
  241.        @return: API response (see: online documentation) <type 'dict'>"""
  242.         if method == 'info':
  243.             url = '/api/3/{}'.format(method)
  244.         else:    # method: ticker, depth, trades
  245.             pairs = pairs or self.pairs
  246.             url = '/api/3/{}/{}'.format(method, pairs)
  247.         return self.apirequest(url, **params)
  248.  
  249. class TradeAPIv1(Connection):
  250.     "BTC-e Trade API v1 <https://btc-e.com/tapi/docs>."
  251.     def __init__(self, apikey, **connkw):
  252.         """Initialization of the BTC-e Trade API.
  253.        @raise APIError: where not an invalid nonce error
  254.        @param apikey: API-key dict {'Key': '...', 'Secret': '...'}
  255.        @param **connkw: compr, timeout (see: Connection class)"""
  256.         super(TradeAPIv1, self).__init__(**connkw)
  257.         self.apikey = apikey
  258.         self.nonce = self._getnonce()
  259.  
  260.     def _getnonce(self):
  261.         """Get nonce value from BTC-e API error.
  262.        @return: nonce parameter <type 'long'>"""
  263.         try:
  264.             self.apirequest('/tapi', self.apikey, nonce=None)
  265.         except APIError as error:
  266.             if 'invalid nonce' not in str(error.message).lower():
  267.                 raise
  268.             try:
  269.                 nonce = search(r'\d+', str(error.message)).group()
  270.             except AttributeError:
  271.                 nonce = datetime.now().strftime('%s')
  272.         return long(nonce)
  273.  
  274.     def _nextnonce(self):
  275.         """Increase and return nonce parameter.
  276.        @return: nonce parameter <type 'long'>"""
  277.         self.nonce += 1
  278.         return self.nonce
  279.  
  280.     def call(self, method, **params):
  281.         """Create query to the BTC-e Trade API.
  282.        @param method: getInfo | Trade | ActiveOrders | OrderInfo |
  283.            CancelOrder | TradeHistory (max: 2000) | TransHistory (max: 2000)
  284.        @param method*: WithdrawCoin | CreateCoupon | RedeemCoupon
  285.        @param **params: param1=value1, param2=value2, ..., paramN=valueN
  286.        @return: API response (see: online documentation) <type 'dict'>"""
  287.         params['method'] = method
  288.         params['nonce'] = self._nextnonce()
  289.         return self.apirequest('/tapi', self.apikey, **params)['return']
RAW Paste Data
Top