def connect(ping_timeout=20, ping_interval=70): if cryptowatch.api_key: DSN = "{}?apikey={}&format=binary".format(cryptowatch.ws_endpoint, cryptowatch.api_key) else: raise APIKeyError( "An API key is required to use the Cryptowatch Websocket API.\n" "You can create one at https://cryptowat.ch/account/api-access") log("DSN used: {}".format(DSN), is_debug=True) websocket.enableTrace(False) global _ws _ws = websocket.WebSocketApp( DSN, on_message=on_market_update, on_error=_on_error, on_close=_on_close, on_open=_on_open, ) wst = threading.Thread( target=_ws.run_forever, kwargs={ "ping_timeout": ping_timeout, "ping_interval": ping_interval }, ) wst.daemon = False wst.start() log( "Ping timeout used: {}. Ping interval used: {}".format( ping_timeout, ping_interval), is_debug=True, )
def __init__(self, api_endpoint, user_agent, opts={}): # Must have options self.user_agent = user_agent self.api_endpoint = api_endpoint if not api_endpoint.startswith("https"): log('Warning: API endpoint must start with "https".', is_error=True) if not user_agent: log("Warning: User-Agent header must be set.", is_error=True) # Options with defaults, overridden by kwargs self.verify_ssl = opts.get("verify_ssl", True) self.connect_timeout = opts.get("connect_timeout", 5) self.read_timeout = opts.get("read_timeout", 20) self.max_retries = opts.get("max_retries", 5) # Make all API calls share the same session self.api_client = requests.Session() # Apply a backoff for failing requests, up to self.max_retries # 1st waiting will be 0.1s, then 0.2s, 0.4s, etc, following this formula: # {backoff factor} * (2 ** ({number of retries so far} - 1)) retries = Retry( total=self.max_retries, backoff_factor=0.1, status_forcelist=[500, 502, 503, 504], ) a = requests.adapters.HTTPAdapter(max_retries=retries) self.api_client.mount("https://", a)
def connect(ping_timeout=20, ping_interval=70): if cryptowatch.api_key: DSN = "{}?apikey={}".format(cryptowatch.ws_endpoint, cryptowatch.api_key) else: DSN = cryptowatch.ws_endpoint log("DSN used: {}".format(DSN), is_debug=True) websocket.enableTrace(True) global _ws _ws = websocket.WebSocketApp( DSN, on_message=on_market_update, on_error=_on_error, on_close=_on_close, on_open=_on_open, ) wst = threading.Thread( target=_ws.run_forever, kwargs={"ping_timeout": ping_timeout, "ping_interval": ping_interval}, ) wst.daemon = False wst.start() log( "Ping timeout used: {}. Ping interval used: {}".format( ping_timeout, ping_interval ), is_debug=True, )
def _on_open(ws): subs_payload = forge_stream_subscription_payload(subscriptions, client_pb2) log( "Connection established. Sending subscriptions payload: {}".format( subs_payload), is_debug=True, ) ws.send(subs_payload)
def list(self): log("Getting all exchanges") data = self.client.get_resource("/exchanges") exchange_resp = json.loads(data) schema = ExchangeListAPIResponseSchema() exchanges_obj = schema.load(exchange_resp) log("API Allowance: cost={} remaining={}".format( exchanges_obj._allowance.cost, exchanges_obj._allowance.remaining)) return exchanges_obj
def get(self, asset): log("Getting asset {}".format(asset)) data, http_resp = self.client.get_resource("/assets/{}".format(asset)) asset_resp = json.loads(data) schema = AssetAPIResponseSchema() asset_obj = schema.load(asset_resp) log("API Allowance: cost={} remaining={}".format( asset_obj._allowance.cost, asset_obj._allowance.remaining)) asset_obj._http_response = http_resp return asset_obj
def list(self): log("Listing all assets") data, http_resp = self.client.get_resource("/assets") asset_resp = json.loads(data) schema = AssetListAPIResponseSchema() assets_obj = schema.load(asset_resp) log("API Allowance: cost={} remaining={}".format( assets_obj._allowance.cost, assets_obj._allowance.remaining)) assets_obj._http_response = http_resp return assets_obj
def list(self): log("Getting instruments") data = self.client.get_resource("/pairs") instrument_resp = json.loads(data) schema = InstrumentListAPIResponseSchema() instruments_obj = schema.load(instrument_resp) log("API Allowance: cost={} remaining={}".format( instruments_obj._allowance.cost, instruments_obj._allowance.remaining)) return instruments_obj
def get(self, instrument): log("Getting instrument {}".format(instrument)) data, http_resp = self.client.get_resource( "/pairs/{}".format(instrument)) instrument_resp = json.loads(data) schema = InstrumentAPIResponseSchema() instrument_obj = schema.load(instrument_resp) log("API Allowance: cost={} remaining={}".format( instrument_obj._allowance.cost, instrument_obj._allowance.remaining)) instrument_obj._http_response = http_resp return instrument_obj
def get(self, exchange): log("Getting exchange {}".format(exchange)) data, http_resp = self.client.get_resource( "/exchanges/{}".format(exchange)) exchange_resp = json.loads(data) schema = ExchangeAPIResponseSchema() exchange_obj = schema.load(exchange_resp) if exchange_obj._allowance: log("API Allowance: cost={} remaining={}".format( exchange_obj._allowance.cost, exchange_obj._allowance.remaining)) exchange_obj._http_response = http_resp return exchange_obj
def on_market_update(ws, message): try: message = message.decode("utf-8") message = json.loads(message) if "marketUpdate" in message: if "intervalsUpdate" in message.get("marketUpdate", ""): schema = CandleMarketUpdateSchema() candle_obj = schema.load(message) on_intervals_update(candle_obj) elif "tradesUpdate" in message.get("marketUpdate", ""): schema = TradeMarketUpdateSchema() trade_obj = schema.load(message) on_trades_update(trade_obj) elif "orderBookUpdate" in message.get("marketUpdate", ""): schema = OrderbookSnapshotMarketUpdateSchema() orderbook_snapshot_obj = schema.load(message) on_orderbook_snapshot_update(orderbook_snapshot_obj) elif "orderBookDeltaUpdate" in message.get("marketUpdate", ""): schema = OrderbookDeltaMarketUpdateSchema() orderbook_delta_obj = schema.load(message) on_orderbook_delta_update(orderbook_delta_obj) elif "orderBookSpreadUpdate" in message.get("marketUpdate", ""): schema = OrderbookSpreadMarketUpdateSchema() orderbook_spread_obj = schema.load(message) on_orderbook_spread_update(orderbook_spread_obj) else: log(message, is_debug=True) else: log(message, is_debug=True) except Exception as ex: log(ex, is_error=True) log(traceback.format_exc(), is_error=True)
def on_market_update(ws, message): try: stream_message = stream_pb2.StreamMessage() stream_message.ParseFromString(message) if str(stream_message.marketUpdate.intervalsUpdate): on_intervals_update(stream_message) elif str(stream_message.marketUpdate.tradesUpdate): on_trades_update(stream_message) elif str(stream_message.marketUpdate.orderBookUpdate): on_orderbook_snapshot_update(stream_message) elif str(stream_message.marketUpdate.orderBookDeltaUpdate): on_orderbook_delta_update(stream_message) elif str(stream_message.marketUpdate.orderBookSpreadUpdate): on_orderbook_spread_update(stream_message) else: log(stream_message, is_debug=True) except protobuf.message.DecodeError as ex: log("Could not decode this message: {}".format(message), is_error=True) log(traceback.format_exc(), is_error=True) except Exception as ex: log(traceback.format_exc(), is_error=True)
def list(self, exchange=None): if exchange: resource = "/markets/{}".format(exchange) log("Getting all markets for {}".format(exchange)) else: resource = "/markets" log("Getting all markets for all exchanges") data = self.client.get_resource(resource) market_resp = json.loads(data) schema = MarketListAPIResponseSchema() markets_obj = schema.load(market_resp) log("API Allowance: cost={} remaining={}".format( markets_obj._allowance.cost, markets_obj._allowance.remaining)) return markets_obj
def on_orderbook_snapshot_update(orderbook_snapshot_update): log(orderbook_snapshot_update, is_debug=True)
def get(self, market, liquidity=False, orderbook=False, trades=False, ohlc=False, periods=[], before=None, after=None): exchange, pair = market.split(":") if ohlc: log("Getting market OHLC candles {}".format(market)) resource = "/markets/{}/{}/ohlc".format(exchange, pair) query = [] if periods: sec_periods = translate_periods(periods) #resource += "?periods={}".format(",".join(sec_periods)) query.append(f"periods={','.join(sec_periods)}") if before: query.append(f'before={before}') if after: query.append(f'after={after}') if len(query) > 0: resource += f"?{'&'.join(query)}" schema = MarketOHLCAPIResponseSchema() elif trades: log("Getting market trades {}".format(market)) resource = "/markets/{}/{}/trades".format(exchange, pair) schema = MarketTradesAPIResponseSchema() elif orderbook: log("Getting market orderbook {}".format(market)) resource = "/markets/{}/{}/orderbook".format(exchange, pair) schema = MarketOrderBookAPIResponseSchema() elif liquidity: log("Getting market liquidity {}".format(market)) resource = "/markets/{}/{}/orderbook/liquidity".format( exchange, pair) schema = MarketLiquidityAPIResponseSchema() else: log("Getting market summary {}".format(market)) resource = "/markets/{}/{}/summary".format(exchange, pair) schema = MarketSummaryAPIResponseSchema() data, http_resp = self.client.get_resource(resource) market_resp = json.loads(data) market_obj = schema.load(market_resp) if market_obj._allowance: log("API Allowance: cost={} remaining={}".format( market_obj._allowance.cost, market_obj._allowance.remaining)) market_obj._http_response = http_resp return market_obj
def get_resource(self, resource): try: headers = {"User-Agent": self.user_agent, "Accept": "application/json"} if cryptowatch.api_key: headers["X-CW-API-Key"] = cryptowatch.api_key url = "{}{}".format(self.api_endpoint, resource) log("HTTP GET {}\n\twith headers: {}".format(url, headers), is_debug=True) resp = self.api_client.get( url, headers=headers, verify=self.verify_ssl, timeout=(self.connect_timeout, self.read_timeout), allow_redirects=True, ) log( "Received HTTP Status {} for {}".format(resp.status_code, url), is_debug=True, ) resp.raise_for_status() # Only catch API Errors, let other Exceptions bubble up except requests.exceptions.HTTPError as ex: # Resource not found if resp.status_code == 404: msg = resp.json().get("error", "No such resource at {}".format(url)) raise cryptowatch.errors.APIResourceNotFoundError( msg, resp.text, resp.status_code, resp.request.headers, ) # Allowance exceeded elif resp.status_code == 429: msg = resp.json().get( "error", "You have exceeded your current API allowance. " "Upgrade for a higher allowance at https://cryptowat.ch/pricing", ) raise cryptowatch.errors.APIRateLimitError( msg, resp.text, resp.status_code, resp.request.headers, ) # Any HTTP 4XX Error elif str(resp.status_code).startswith("4"): raise cryptowatch.errors.APIRequestError( "Your request failed. Please try again in a moment.", resp.text, resp.status_code, resp.request.headers, ) # Any HTTP 5XX Error elif str(resp.status_code).startswith("5"): raise cryptowatch.errors.APIServerError( "The Cryptowatch API is having some issues. " "Please try again in a moment.", resp.text, resp.status_code, resp.request.headers, ) # Any other HTTP Error else: raise cryptowatch.errors.APIError( "Error connecting to the API. Please try again in a moment.", resp.text, resp.status_code, resp.request.headers, ) else: return resp.text, resp
def _on_error(ws, error): log(str(error), is_error=True)
def _on_close(ws): log("Connection closed.", is_debug=True)
def get( self, market, liquidity=False, orderbook=False, trades=False, ohlc=False, periods=[], before=False, after=False ): exchange, pair = market.split(":") if ohlc: log("Getting market OHLC candles {}".format(market)) resource = "/markets/{}/{}/ohlc".format(exchange, pair) if periods: sec_periods = translate_periods(periods) resource += "?periods={}".format(",".join(sec_periods)) if before: sep = "&" if "?" in resource else "?" resource += "{}before={}".format(sep, before) if after: sep = "&" if "?" in resource else "?" resource += "{}after={}".format(sep, after) schema = MarketOHLCAPIResponseSchema() elif trades: log("Getting market trades {}".format(market)) resource = "/markets/{}/{}/trades".format(exchange, pair) schema = MarketTradesAPIResponseSchema() elif orderbook: log("Getting market orderbook {}".format(market)) resource = "/markets/{}/{}/orderbook".format(exchange, pair) schema = MarketOrderBookAPIResponseSchema() elif liquidity: log("Getting market liquidity {}".format(market)) resource = "/markets/{}/{}/orderbook/liquidity".format(exchange, pair) schema = MarketLiquidityAPIResponseSchema() else: log("Getting market summary {}".format(market)) resource = "/markets/{}/{}/summary".format(exchange, pair) schema = MarketSummaryAPIResponseSchema() data, http_resp = self.client.get_resource(resource) market_resp = json.loads(data) market_obj = schema.load(market_resp) if market_obj._allowance: log( "API Allowance: cost={} remaining={}".format( market_obj._allowance.cost, market_obj._allowance.remaining ) ) market_obj._http_response = http_resp return market_obj
def read_config(): user_home_dir = os.environ.get("HOME") filepath = "{}/.cw/credentials.yml".format(user_home_dir) api_key = None rest_url = None stream_url = None try: with open(filepath, "r") as config_file: config = yaml.safe_load(config_file) # A config_file empty or with all lines commented out will return None if not config: log("Your configuration file at {} is empty".format(filepath), is_debug=True) else: # Look for the (public) API Key if not config.get("apikey") and not config.get("api_key"): log("No API key seen in credential file", is_debug=True) else: api_key = config.get("apikey") if api_key is None: api_key = config.get("api_key") # Look for a stream (websocket) URL if not config.get("stream_url"): log("No stream URL seen in credential file", is_debug=True) else: stream_url = config.get("stream_url") # Look for a REST API URL if not config.get("rest_url"): log("No rest URL seen in credential file", is_debug=True) else: rest_url = config.get("rest_url") except FileNotFoundError as ex: log("No credential file found", is_debug=True) except yaml.YAMLError as ex: log("Credential is not a valid YAML file", is_error=True) raise error.CredentialsFileError( "Your Cryptowatch credentials file at " "{} is not properly formatted.".format(filepath) ) except Exception as ex: log(traceback.format_exc(), is_error=True) finally: return api_key, rest_url, stream_url
def on_trades_update(trades_update): log(trades_update, is_debug=True)
def on_intervals_update(intervals_update): log(intervals_update, is_debug=True)
def on_orderbook_spread_update(orderbook_spread_update): log(orderbook_spread_update, is_debug=True)
def on_orderbook_delta_update(orderbook_delta_update): log(orderbook_delta_update, is_debug=True)
def _on_close(ws, close_status_code, close_reason): log("Connection closed.", is_debug=True)