SHARE
TWEET

BTC-E Trade API v1 & Public API v3

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