/python/ccxt/async_support/base/exchange.py

https://github.com/kroitor/ccxt · Python · 316 lines · 235 code · 60 blank · 21 comment · 51 complexity · c4810ad4c658ea9893f4c329822f0ed6 MD5 · raw file

  1. # -*- coding: utf-8 -*-
  2. # -----------------------------------------------------------------------------
  3. __version__ = '1.34.89'
  4. # -----------------------------------------------------------------------------
  5. import asyncio
  6. import concurrent
  7. import socket
  8. import certifi
  9. import aiohttp
  10. import ssl
  11. import sys
  12. import yarl
  13. # -----------------------------------------------------------------------------
  14. from ccxt.async_support.base.throttle import throttle
  15. # -----------------------------------------------------------------------------
  16. from ccxt.base.errors import ExchangeError
  17. from ccxt.base.errors import ExchangeNotAvailable
  18. from ccxt.base.errors import RequestTimeout
  19. from ccxt.base.errors import NotSupported
  20. # -----------------------------------------------------------------------------
  21. from ccxt.base.exchange import Exchange as BaseExchange
  22. # -----------------------------------------------------------------------------
  23. __all__ = [
  24. 'BaseExchange',
  25. 'Exchange',
  26. ]
  27. # -----------------------------------------------------------------------------
  28. class Exchange(BaseExchange):
  29. def __init__(self, config={}):
  30. if 'asyncio_loop' in config:
  31. self.asyncio_loop = config['asyncio_loop']
  32. self.asyncio_loop = self.asyncio_loop or asyncio.get_event_loop()
  33. self.aiohttp_trust_env = config.get('aiohttp_trust_env', self.aiohttp_trust_env)
  34. self.verify = config.get('verify', self.verify)
  35. self.own_session = 'session' not in config
  36. self.cafile = config.get('cafile', certifi.where())
  37. super(Exchange, self).__init__(config)
  38. self.init_rest_rate_limiter()
  39. self.markets_loading = None
  40. self.reloading_markets = False
  41. def init_rest_rate_limiter(self):
  42. self.throttle = throttle(self.extend({
  43. 'loop': self.asyncio_loop,
  44. }, self.tokenBucket))
  45. def __del__(self):
  46. if self.session is not None:
  47. self.logger.warning(self.id + " requires to release all resources with an explicit call to the .close() coroutine. If you are using the exchange instance with async coroutines, add exchange.close() to your code into a place when you're done with the exchange and don't need the exchange instance anymore (at the end of your async coroutine).")
  48. if sys.version_info >= (3, 5):
  49. async def __aenter__(self):
  50. self.open()
  51. return self
  52. async def __aexit__(self, exc_type, exc, tb):
  53. await self.close()
  54. def open(self):
  55. if self.own_session and self.session is None:
  56. # Create our SSL context object with our CA cert file
  57. context = ssl.create_default_context(cafile=self.cafile) if self.verify else self.verify
  58. # Pass this SSL context to aiohttp and create a TCPConnector
  59. connector = aiohttp.TCPConnector(ssl=context, loop=self.asyncio_loop, enable_cleanup_closed=True)
  60. self.session = aiohttp.ClientSession(loop=self.asyncio_loop, connector=connector, trust_env=self.aiohttp_trust_env)
  61. async def close(self):
  62. if self.session is not None:
  63. if self.own_session:
  64. await self.session.close()
  65. self.session = None
  66. async def fetch2(self, path, api='public', method='GET', params={}, headers=None, body=None):
  67. """A better wrapper over request for deferred signing"""
  68. if self.enableRateLimit:
  69. await self.throttle(self.rateLimit)
  70. self.lastRestRequestTimestamp = self.milliseconds()
  71. request = self.sign(path, api, method, params, headers, body)
  72. return await self.fetch(request['url'], request['method'], request['headers'], request['body'])
  73. async def fetch(self, url, method='GET', headers=None, body=None):
  74. """Perform a HTTP request and return decoded JSON data"""
  75. request_headers = self.prepare_request_headers(headers)
  76. url = self.proxy + url
  77. if self.verbose:
  78. self.print("\nRequest:", method, url, headers, body)
  79. self.logger.debug("%s %s, Request: %s %s", method, url, headers, body)
  80. request_body = body
  81. encoded_body = body.encode() if body else None
  82. self.open()
  83. session_method = getattr(self.session, method.lower())
  84. http_response = None
  85. http_status_code = None
  86. http_status_text = None
  87. json_response = None
  88. try:
  89. async with session_method(yarl.URL(url, encoded=True),
  90. data=encoded_body,
  91. headers=request_headers,
  92. timeout=(self.timeout / 1000),
  93. proxy=self.aiohttp_proxy) as response:
  94. http_response = await response.text()
  95. http_status_code = response.status
  96. http_status_text = response.reason
  97. json_response = self.parse_json(http_response)
  98. headers = response.headers
  99. if self.enableLastHttpResponse:
  100. self.last_http_response = http_response
  101. if self.enableLastResponseHeaders:
  102. self.last_response_headers = headers
  103. if self.enableLastJsonResponse:
  104. self.last_json_response = json_response
  105. if self.verbose:
  106. self.print("\nResponse:", method, url, http_status_code, headers, http_response)
  107. self.logger.debug("%s %s, Response: %s %s %s", method, url, http_status_code, headers, http_response)
  108. except socket.gaierror as e:
  109. raise ExchangeNotAvailable(method + ' ' + url)
  110. except concurrent.futures._base.TimeoutError as e:
  111. raise RequestTimeout(method + ' ' + url)
  112. except aiohttp.client_exceptions.ClientConnectionError as e:
  113. raise ExchangeNotAvailable(method + ' ' + url)
  114. except aiohttp.client_exceptions.ClientError as e: # base exception class
  115. raise ExchangeError(method + ' ' + url)
  116. self.handle_errors(http_status_code, http_status_text, url, method, headers, http_response, json_response, request_headers, request_body)
  117. self.handle_rest_errors(http_status_code, http_status_text, http_response, url, method)
  118. if json_response is not None:
  119. return json_response
  120. if self.is_text_response(headers):
  121. return http_response
  122. return response.content
  123. async def load_markets_helper(self, reload=False, params={}):
  124. if not reload:
  125. if self.markets:
  126. if not self.markets_by_id:
  127. return self.set_markets(self.markets)
  128. return self.markets
  129. currencies = None
  130. if self.has['fetchCurrencies']:
  131. currencies = await self.fetch_currencies()
  132. markets = await self.fetch_markets(params)
  133. return self.set_markets(markets, currencies)
  134. async def load_markets(self, reload=False, params={}):
  135. if (reload and not self.reloading_markets) or not self.markets_loading:
  136. self.reloading_markets = True
  137. coroutine = self.load_markets_helper(reload, params)
  138. # coroutines can only be awaited once so we wrap it in a task
  139. self.markets_loading = asyncio.ensure_future(coroutine)
  140. try:
  141. result = await self.markets_loading
  142. except Exception as e:
  143. self.reloading_markets = False
  144. self.markets_loading = None
  145. raise e
  146. self.reloading_markets = False
  147. return result
  148. async def fetch_fees(self):
  149. trading = {}
  150. funding = {}
  151. if self.has['fetchTradingFees']:
  152. trading = await self.fetch_trading_fees()
  153. if self.has['fetchFundingFees']:
  154. funding = await self.fetch_funding_fees()
  155. return {
  156. 'trading': trading,
  157. 'funding': funding,
  158. }
  159. async def load_fees(self, reload=False):
  160. if not reload:
  161. if self.loaded_fees != Exchange.loaded_fees:
  162. return self.loaded_fees
  163. self.loaded_fees = self.deep_extend(self.loaded_fees, await self.fetch_fees())
  164. return self.loaded_fees
  165. async def fetch_markets(self, params={}):
  166. # markets are returned as a list
  167. # currencies are returned as a dict
  168. # this is for historical reasons
  169. # and may be changed for consistency later
  170. return self.to_array(self.markets)
  171. async def fetch_currencies(self, params={}):
  172. # markets are returned as a list
  173. # currencies are returned as a dict
  174. # this is for historical reasons
  175. # and may be changed for consistency later
  176. return self.currencies
  177. async def fetch_status(self, params={}):
  178. if self.has['fetchTime']:
  179. updated = await self.fetch_time(params)
  180. self.status['updated'] = updated
  181. return self.status
  182. async def fetch_order_status(self, id, symbol=None, params={}):
  183. order = await self.fetch_order(id, symbol, params)
  184. return order['status']
  185. async def fetch_partial_balance(self, part, params={}):
  186. balance = await self.fetch_balance(params)
  187. return balance[part]
  188. async def fetch_l2_order_book(self, symbol, limit=None, params={}):
  189. orderbook = await self.fetch_order_book(symbol, limit, params)
  190. return self.extend(orderbook, {
  191. 'bids': self.sort_by(self.aggregate(orderbook['bids']), 0, True),
  192. 'asks': self.sort_by(self.aggregate(orderbook['asks']), 0),
  193. })
  194. async def perform_order_book_request(self, market, limit=None, params={}):
  195. raise NotSupported(self.id + ' performOrderBookRequest not supported yet')
  196. async def fetch_order_book(self, symbol, limit=None, params={}):
  197. await self.load_markets()
  198. market = self.market(symbol)
  199. orderbook = await self.perform_order_book_request(market, limit, params)
  200. return self.parse_order_book(orderbook, market, limit, params)
  201. async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
  202. if not self.has['fetchTrades']:
  203. raise NotSupported('fetch_ohlcv() not implemented yet')
  204. await self.load_markets()
  205. trades = await self.fetch_trades(symbol, since, limit, params)
  206. return self.build_ohlcv(trades, timeframe, since, limit)
  207. async def fetchOHLCV(self, symbol, timeframe='1m', since=None, limit=None, params={}):
  208. return await self.fetch_ohlcv(symbol, timeframe, since, limit, params)
  209. async def fetch_full_tickers(self, symbols=None, params={}):
  210. return await self.fetch_tickers(symbols, params)
  211. async def edit_order(self, id, symbol, *args):
  212. if not self.enableRateLimit:
  213. raise ExchangeError('updateOrder() requires enableRateLimit = true')
  214. await self.cancel_order(id, symbol)
  215. return await self.create_order(symbol, *args)
  216. async def create_order(self, symbol, type, side, amount, price=None, params={}):
  217. raise NotSupported('create_order() not supported yet')
  218. async def cancel_order(self, id, symbol=None, params={}):
  219. raise NotSupported('cancel_order() not supported yet')
  220. async def fetch_trading_fees(self, params={}):
  221. raise NotSupported('fetch_trading_fees() not supported yet')
  222. async def fetch_trading_fee(self, symbol, params={}):
  223. if not self.has['fetchTradingFees']:
  224. raise NotSupported('fetch_trading_fee() not supported yet')
  225. return await self.fetch_trading_fees(params)
  226. async def load_trading_limits(self, symbols=None, reload=False, params={}):
  227. if self.has['fetchTradingLimits']:
  228. if reload or not('limitsLoaded' in list(self.options.keys())):
  229. response = await self.fetch_trading_limits(symbols)
  230. for i in range(0, len(symbols)):
  231. symbol = symbols[i]
  232. self.markets[symbol] = self.deep_extend(self.markets[symbol], response[symbol])
  233. self.options['limitsLoaded'] = self.milliseconds()
  234. return self.markets
  235. async def load_accounts(self, reload=False, params={}):
  236. if reload:
  237. self.accounts = await self.fetch_accounts(params)
  238. else:
  239. if self.accounts:
  240. return self.accounts
  241. else:
  242. self.accounts = await self.fetch_accounts(params)
  243. self.accountsById = self.index_by(self.accounts, 'id')
  244. return self.accounts
  245. async def fetch_ticker(self, symbol, params={}):
  246. raise NotSupported('fetch_ticker() not supported yet')
  247. async def fetch_transactions(self, code=None, since=None, limit=None, params={}):
  248. raise NotSupported('fetch_transactions() is not supported yet')
  249. async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
  250. raise NotSupported('fetch_deposits() is not supported yet')
  251. async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
  252. raise NotSupported('fetch_withdrawals() is not supported yet')
  253. async def fetch_deposit_address(self, code=None, since=None, limit=None, params={}):
  254. raise NotSupported('fetch_deposit_address() is not supported yet')
  255. async def sleep(self, milliseconds):
  256. return await asyncio.sleep(milliseconds / 1000)