SHARE
TWEET

BTC-e: Trade API v1 and Public API v3 Library

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