def validate_data_against(data: dict, model: FrozenBaseModel): try: validated = model(**data) #type: ignore return Ok(validated) except ValidationError as e: return Err(e) except Exception as e: raise e
def validate_parsed(msg, parsed_msg): try: validated_msg = NoobitResponseTrades(trades=parsed_msg, rawJson=msg, exchange="BINANCE") return Ok(validated_msg) except ValidationError as e: return Err(e)
def validate_parsed(msg, parsed_msg): try: validated_msg = NoobitResponseSpread(spread=(parsed_msg, ), rawJson=msg, exchange="KRAKEN") return Ok(validated_msg) except ValidationError as e: print(e) return Err(e)
def validate_parsed(msg, parsed_msg): try: validated_msg = NoobitResponseOhlc(ohlc=parsed_msg, rawJson=msg, exchange="KRAKEN") return Ok(validated_msg) except ValidationError as e: # print(e) return Err(e)
def _validate_data( model: typing.Type[BaseModel], fields: pyrsistent.PMap # PRecord sublasses PMap so its also acceptable ) -> Result: try: validated = model(**fields) #type: ignore return Ok(validated) except ValidationError as e: return Err(e) except Exception as e: raise e
def validate_parsed(msg: BinanceBookMsg, parsed_msg: dict): try: validated_msg = NoobitResponseOrderBook( rawJson=msg, exchange="BINANCE", **parsed_msg ) return Ok(validated_msg) except ValidationError as e: return Err(e)
async def result_or_err(resp_obj: httpx.Response) -> Result: content = await resp_json(resp_obj) errors: list = content.get("error", None) if errors: err_dict = {err: err.split(":")[1] for err in errors} return Err(err_dict) else: # no error return Ok(content["result"])
async def get_req_content( result_or_err: result_or_err_sig, parse_err_content: parse_err_content_sig, client: ntypes.CLIENT, method: str, url: pydantic.AnyHttpUrl, valid_req: FrozenBaseModel, headers: typing.Mapping, ) -> Result: """meant to be derived using functools.partial in `exchange`.rest.base.py """ payload = {"method": method, "url": url, "headers": headers} query = valid_req.dict(exclude_none=True) if method in ["GET"]: payload["params"] = query elif method in ["POST", "DELETE"]: payload["data"] = query else: raise NotImplementedError(f"Unsupported method : {method}") # TODO handle timeout (httpx._exceptions.ConnectTimeout) try: resp = await client.request(**payload) #type: ignore except Exception as e: req_url = urllib.parse.urljoin("url", urllib.parse.urlencode(payload)) return Err(RequestTimeout(str(e), f"<{method} {req_url}>")) content = await result_or_err(resp) if content.is_err(): parsed_err_content = parse_err_content(content.value, get_sent_request(resp)) return Err(parsed_err_content) else: return content
def validate_parsed(msg, parsed_msg): try: validated_msg = NoobitResponseOrderBook( **parsed_msg, utcTime=time.time() * 10**3, rawJson=msg, exchange="KRAKEN" ) return Ok(validated_msg) except ValidationError as e: return Err(e)
async def result_or_err(resp_obj: httpx.Response) -> Result: # example of ftx error response content: # {"error":"Not logged in","success":false} # example of ftx orderbook response content # {"success": true, "result": {"asks": [[4114.25, 6.263]], "bids": [[4112.25, 49.]]}} content = await resp_json(resp_obj) if content["success"]: return Ok(content["result"]) else: # needs to be a dict return Err({"error": content})
async def fetch_trades(self, exchange: str, symbol: str, since: typing.Optional[int]=None): from noobit_markets.base.models.rest.response import NTrades self.log_field.log("CALLED fetch_trades") if not symbol: if not settings.SYMBOL or settings.SYMBOL.isspace(): return Err("Please set or pass <symbol> argument") else: symbol = settings.SYMBOL else: symbol=symbol.upper() interface = globals()[exchange] _res = await interface.rest.public.trades(self.client, symbol, self.symbols[exchange].value, since) _trd = NTrades(_res) return _trd
async def result_or_err(resp_obj: httpx.Response) -> Result: # Example of error content (note no key to indicate it is an error) # {"code":-1105,"msg":"Parameter \'startTime\' was empty."} content = await resp_json(resp_obj) if "code" in content: # we have an error message error_msg = content.get("msg", None) error_key = int(content["code"]) * -1 return Err({error_key: error_msg}) else: # no error return Ok(content)
def validate_nreq_orderbook( symbol: ntypes.SYMBOL, symbol_mapping: ntypes.SYMBOL_TO_EXCHANGE, depth: ntypes.DEPTH ) -> Result[NoobitRequestOrderBook, ValidationError]: try: valid_req = NoobitRequestOrderBook(symbol=symbol, symbol_mapping=symbol_mapping, depth=depth) return Ok(valid_req) except ValidationError as e: return Err(e) except Exception as e: raise e
def validate_nreq_trades( symbol: ntypes.SYMBOL, symbol_mapping: ntypes.SYMBOL_TO_EXCHANGE, since: typing.Optional[ntypes.TIMESTAMP] ) -> Result[NoobitRequestTrades, ValidationError]: try: valid_req = NoobitRequestTrades(symbol=symbol, symbol_mapping=symbol_mapping, since=since) return Ok(valid_req) except ValidationError as e: return Err(e) except Exception as e: raise e
async def fetch_orderbook(self, exchange: str, symbol: str, depth: int): from noobit_markets.base.models.rest.response import NOrderBook self.log_field.log("CALLED fetch_orderbook") if not symbol: if not settings.SYMBOL or settings.SYMBOL.isspace(): return Err("Please set or pass <symbol> argument") else: symbol = settings.SYMBOL else: symbol=symbol.upper() interface = globals()[exchange] _res = await interface.rest.public.orderbook(self.client, symbol.upper(), self.symbols[exchange].value, depth) _book = NOrderBook(_res) return _book
def validate_nreq_ohlc( symbol: ntypes.SYMBOL, symbol_mapping: ntypes.SYMBOL_TO_EXCHANGE, timeframe: ntypes.TIMEFRAME, since: ntypes.TIMESTAMP) -> Result[NoobitRequestOhlc, ValidationError]: try: valid_req = NoobitRequestOhlc(symbol=symbol, symbol_mapping=symbol_mapping, timeframe=timeframe, since=since) return Ok(valid_req) except ValidationError as e: return Err(e) except Exception as e: raise e
def validate_nreq_instrument( symbol: ntypes.SYMBOL, symbol_mapping: ntypes.SYMBOL_TO_EXCHANGE, ) -> Result[NoobitRequestInstrument, ValidationError]: try: valid_req = NoobitRequestInstrument( symbol=symbol, symbol_mapping=symbol_mapping, ) return Ok(valid_req) except ValidationError as e: return Err(e) except Exception as e: raise e
def validate_sub(symbol_to_exchange: SYMBOL_TO_EXCHANGE, symbol: SYMBOL) -> Result[BinanceSubModel, ValidationError]: msg = { "id": 1, "method": "SUBSCRIBE", "params": (f"{symbol_to_exchange(symbol)}@aggTrade", ) } try: submodel = BinanceSubModel(exchange="binance", feed="trade", msg=msg) return Ok(submodel) except ValidationError as e: return Err(e) except Exception as e: raise e
async def fetch_openorders(self, exchange: str, symbol: str): from noobit_markets.base.models.rest.response import NOrders self.log_field.log("CALLED fetch_openorders") if not symbol: if not settings.SYMBOL or settings.SYMBOL.isspace(): return Err("Please set or pass <symbol> argument") else: symbol = settings.SYMBOL else: symbol=symbol.upper() self.log_field.log(f"Requested Symbol : {symbol}") self.log_field.log(f"Requested Exchange : {exchange}") interface = globals()[exchange] _res = await interface.rest.private.open_orders(self.client, symbol, self.symbols[exchange].value) _opo = NOrders(_res) return _opo
def validate_sub(token) -> Result[KrakenSubModel, Exception]: msg = { "event": "subscribe", "pair": None, "subscription": {"name": "openOrders", "token": token} } try: submodel = KrakenSubModel( exchange="kraken", feed="user_orders", msg=msg ) return Ok(submodel) except ValidationError as e: return Err(e) except Exception as e: raise e
def get_response_status_code( resp_obj: httpx.Response) -> Result[pydantic.PositiveInt, BaseError]: # status_code = response_json.status_code # err_msg = f"HTTP Status Error: {status_code}" # return Ok(status_code) if status_code == 200 else Err(err_msg) status_keys = [k for k in resp_obj.__dict__.keys() if "status" in k] if len(status_keys) > 1: raise KeyError( f"Found multiple <status> keys in {resp_obj.__dict__.keys()}") status = getattr(resp_obj, status_keys[0]) if status == 200: return Ok(status) else: msg = f"Http Status Error: {status}" return Err( BadRequest(raw_error=msg, sent_request=get_sent_request(resp_obj)))
def validate_sub(symbol_to_exchange: SYMBOL_TO_EXCHANGE, symbol: SYMBOL, depth: DEPTH) -> Result[KrakenSubModel, ValidationError]: msg = { "event": "subscribe", "pair": [symbol_to_exchange(symbol), ], "subscription": {"name": "book", "depth": depth} } try: submodel = KrakenSubModel( exchange="kraken", feed="orderbook", msg=msg ) return Ok(submodel) except ValidationError as e: return Err(e) except Exception as e: raise e
def validate_sub( symbol_to_exchange: SYMBOL_TO_EXCHANGE, symbol: SYMBOL, timeframe: TIMEFRAME) -> Result[KrakenSubModel, ValidationError]: msg = { "event": "subscribe", "pair": [ symbol_to_exchange(symbol), ], "subscription": { "name": "ohlc", "interval": K_TIMEFRAME_FROM_N[timeframe] } } try: submodel = KrakenSubModel(exchange="kraken", feed="trade", msg=msg) return Ok(submodel) except ValidationError as e: return Err(e)
async def confirm_subscription(self): msg = await self.client.recv() if not 'subscription' in msg: return Err()
async def cancel_openorder_kraken( client: ntypes.CLIENT, symbol: ntypes.SYMBOL, symbols_resp: NoobitResponseSymbols, orderID: str, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, auth=KrakenAuth(), base_url: pydantic.AnyHttpUrl = endpoints.KRAKEN_ENDPOINTS.private.url, endpoint: str = endpoints.KRAKEN_ENDPOINTS.private.endpoints.remove_order, ) -> Result[NoobitResponseItemOrder, Exception]: symbol_to_exchange = lambda x: { k: v.exchange_pair for k, v in symbols_resp.asset_pairs.items() }[x] req_url = urljoin(base_url, endpoint) method = "POST" payload = {"txid": orderID} data = {"nonce": auth.nonce, **payload} valid_kraken_req = _validate_data(KrakenRequestCancelOpenOrder, pmap(data)) if valid_kraken_req.is_err(): return valid_kraken_req headers = auth.headers(endpoint, data) result_content = await get_result_content_from_req(client, method, req_url, valid_kraken_req.value, headers) if result_content.is_err(): return result_content if logger: logger(f"Cancel Open Order - Result content : {result_content.value}") valid_result_content = _validate_data(KrakenResponseCancelOpenOrder, result_content.value) if valid_result_content.is_err(): return valid_result_content cl_ord = await get_closedorders_kraken(client, symbol, symbols_resp, logger=logger, auth=auth) if isinstance(cl_ord, Ok): try: [order_info] = [ order for order in cl_ord.value.orders if order.orderID == orderID ] return Ok(order_info) except ValueError as e: return Err(e) except Exception as e: return Err(e) else: return cl_ord
async def cancel_order( self, exchange: str, symbol: str, slice: str, all: bool ): self.log_field.log("CALLED cancel_order") # testlist = [x for x in range(0, 100)] # regex = re.compile(r"^\[[-]?[0-9]+:[-]?[0-9]+\]$") regex = "^\[[-]?[0-9]+:[-]?[0-9]+:[-]?[0-9]\]$" match = re.match(regex, slice) if not match: self.log(match) return Err(f"Argument position did not match regex - Given {slice}") else: # sliced = eval(f"testlist{position}") # self.log(sliced) # return Ok("SUCCESS") if not symbol: if not settings.SYMBOL or settings.SYMBOL.isspace(): return Err("Please set or pass <symbol> argument") else: symbol = settings.SYMBOL else: symbol=symbol.upper() self.log_field.log(f"Requested Symbol : {symbol}") self.log_field.log(f"Requested Exchange : {exchange}") interface = globals()[exchange] _res = await interface.rest.private.open_orders(self.client, symbol, self.symbols[exchange].value) if _res.is_err(): return _res.value else: _acc = [] _all_orders = sorted([order for order in _res.value.orders], key=lambda x: getattr(x, "price"), reverse=False) _sliced_orders = eval(f"_all_orders{slice}") for _order in _sliced_orders: _ord = await interface.rest.private.remove_order(self.client, symbol, self.symbols[exchange].value, _order.orderID) if _ord.is_ok(): _acc.append(_ord.value) else: return _ord await asyncio.sleep(1) try: _canceled_orders = NoobitResponseClosedOrders( exchange="KRAKEN", rawjson={}, orders=_acc ) # request coros always return a result # so we wrap the validated model in an OK container _nords = NOrders(Ok(_canceled_orders)) return _nords except ValidationError as e: return Err(e)
async def create_neworder( self, exchange: str, symbol: str, ordType: str, clOrdID, orderQty: float, price: float, timeInForce: str = "GOOD-TIL-CANCEL", quoteOrderQty: typing.Optional[float] = None, stopPrice: typing.Optional[float] = None, *, side: str, blind: bool, split: typing.Optional[int] = None, delay: typing.Optional[int] = None, step: typing.Optional[float] = None, ): from noobit_markets.base.models.rest.response import NSingleOrder self.log_field.log("CALLED create_neworder") if not symbol: if not settings.SYMBOL or settings.SYMBOL.isspace(): return Err("Please set or pass <symbol> argument") else: symbol = settings.SYMBOL else: symbol=symbol.upper() if not ordType: if not settings.ORDTYPE or settings.ORDTYPE.isspace(): return Err("Please set or pass <ordType> argument") else: ordType = settings.ORDTYPE # TODO be consistent: either all noobit types in capital or in lowercase else: ordType = ordType.upper() if not orderQty: if not settings.ORDQTY: return Err("Please set or pass <orderQty> argument") else: orderQty = settings.ORDQTY if not timeInForce and ordType in ["LIMIT", "STOP-LOSS-LIMIT", "TAKE-PROFIT-LIMIT"]: self.log("WARNING : <timeInForce> not provided, defaulting to <GOOD-TIL-CANCEL>") timeInForce = "GOOD-TIL-CANCEL" interface = globals()[exchange] if split: # step is only for limit orders if step: if not ordType in ["LIMIT", "STOP_LIMIT", "TAKE_PROFIT"]: return Err(f"Argument <step>: Ordertype can not be {ordType}") # only one of delay or step if not any([delay, step]) or all([delay, step]): return Err("Please set only one of <delay> or <step> argument to split orders") else: _acc = [] acc_price = price for i in range(split): _res = await interface.rest.private.new_order( client=self.client, symbol=symbol, symbols_resp=self.symbols[exchange].value, side=side, ordType=ordType, clOrdID=clOrdID, # orderQty=round(orderQty/split, self.symbols[exchange].value.asset_pairs[symbol].volume_decimals), # TODO atm we limit to 2 decimal places, could check max decimals for pair, ALSO this can lead to keyerror if symbol is not on exchange orderQty=round(orderQty/split, 2), # TODO atm we limit to 2 decimal places, could check max decimals for pair, ALSO this can lead to keyerror if symbol is not on exchange price=acc_price, timeInForce=timeInForce, quoteOrderQty=quoteOrderQty, stopPrice=stopPrice ) if _res.is_ok(): if blind: _res.value.price = None _acc.append(_res.value) self.log_field.log(f"Successful order, count {len(_acc)}") else: self.log_field.log(f"Failed order") return _res if step: acc_price = round(float(acc_price + step), 2) # FIXME this will create decimal place precision errors sometimes, we need to round somehow (use context ??) await asyncio.sleep(1) else: await asyncio.sleep(delay) # avoid rate limiting try: if blind: self.log("Argument <blind>: Setting Order Price to <None>") _splitorders = NoobitResponseClosedOrders( exchange="KRAKEN", rawjson={}, orders=_acc ) # request coros always return a result # so we wrap the validated model in an OK container _nords = NOrders(Ok(_splitorders)) return _nords except ValidationError as e: return Err(e) else: if any([delay, step]): self.log_field.log("Argument <delay> or <step> require <split>") return Err("Argument <delay> or <step> require <split>") _res = await interface.rest.private.new_order( client=self.client, symbol=symbol, symbols_resp=self.symbols[exchange].value, side=side, ordType=ordType, clOrdID=clOrdID, orderQty=orderQty, price=price, timeInForce=timeInForce, quoteOrderQty=quoteOrderQty, stopPrice=stopPrice ) if blind: self.log("Argument <blind>: Setting Order Price to <None>") if _res.is_ok(): _res.value.price = None _nord = NSingleOrder(_res) return _nord