async def get_closedorders_kraken( client: ntypes.CLIENT, symbol: ntypes.SYMBOL, symbols_resp: NoobitResponseSymbols, # 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.closed_orders, ) -> Result[NoobitResponseClosedOrders, Exception]: # format: "ETHUSD" symbol_from_exchange = lambda x: { f"{v.noobit_base}{v.noobit_quote}": k for k, v in symbols_resp.asset_pairs.items() }[x] req_url = urljoin(base_url, endpoint) # Kraken Doc : Private methods must use POST method = "POST" data = {"nonce": auth.nonce, "trades": True} valid_kraken_req = _validate_data(KrakenRequestClosedOrders, pmap(data)) if valid_kraken_req.is_err(): return valid_kraken_req if logger: logger(f"Closed Orders - Parsed Request : {valid_kraken_req.value}") headers = auth.headers(endpoint, valid_kraken_req.value.dict()) 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"Closed Orders - Result content : {result_content.value}") valid_result_content = _validate_data(KrakenResponseClosedOrders, result_content.value) if valid_result_content.is_err(): return valid_result_content parsed_result_data = parse_result_closedorders( valid_result_content.value.closed, symbol_from_exchange, symbol) valid_parsed_result_data = _validate_data( NoobitResponseClosedOrders, pmap({ "orders": parsed_result_data, "rawJson": result_content.value, "exchange": "KRAKEN", }), ) return valid_parsed_result_data
async def get_usertrades_kraken( client: ntypes.CLIENT, symbol: ntypes.SYMBOL, symbols_resp: NoobitResponseSymbols, # 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. trades_history, ) -> Result[NoobitResponseTrades, pydantic.ValidationError]: # format: "DOTUSD" or "XETHZUSD" (doc incorrect on this one) symbol_from_exchange = lambda x: { v.exchange_pair: k for k, v in symbols_resp.asset_pairs.items() }[x] req_url = urljoin(base_url, endpoint) method = "POST" data = {"nonce": auth.nonce, "type": "all", "trades": True} valid_kraken_req = _validate_data(KrakenRequestUserTrades, pmap(data)) if valid_kraken_req.is_err(): return valid_kraken_req if logger: logger(f"User Trades - Parsed Request : {valid_kraken_req.value}") headers = auth.headers(endpoint, valid_kraken_req.value.dict()) 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"User Trades - Result content : {result_content.value}") valid_result_content = _validate_data(KrakenResponseUserTrades, result_content.value) if valid_result_content.is_err(): return valid_result_content parsed_result_data = parse_result(valid_result_content.value.trades, symbol_from_exchange, symbol) valid_parsed_result_data = _validate_data( NoobitResponseTrades, pmap({ "trades": parsed_result_data, "rawJson": result_content.value, "exchange": "KRAKEN", }), ) return valid_parsed_result_data
async def get_balances_binance( client: ntypes.CLIENT, symbols_resp: NoobitResponseSymbols, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, auth=BinanceAuth(), base_url: pydantic.AnyHttpUrl = endpoints.BINANCE_ENDPOINTS.private.url, endpoint: str = endpoints.BINANCE_ENDPOINTS.private.endpoints.balances ) -> Result[NoobitResponseBalances, ValidationError]: asset_from_exchange = lambda x: { v: k for k, v in symbols_resp.assets.items() }[x] req_url = urljoin(base_url, endpoint) method = "GET" headers: typing.Dict = auth.headers() nonce = auth.nonce data = {"timestamp": nonce} signed_params = auth._sign(data) valid_binance_req = _validate_data(BinancePrivateRequest, pmap(signed_params)) if logger: logger(f"Balances - Parsed Request : {valid_binance_req.value}") result_content = await get_result_content_from_req(client, method, req_url, valid_binance_req.value, headers) if result_content.is_err(): return result_content if logger: logger(f"Balances - Result Content : {result_content.value}") valid_result_content = _validate_data(BinanceResponseBalances, result_content.value) if valid_result_content.is_err(): return valid_result_content parsed_result = parse_result(valid_result_content.value, asset_from_exchange, exclude=["TWT"]) valid_parsed_response_data = _validate_data( NoobitResponseBalances, pmap({ "balances": parsed_result, "rawJson": result_content.value, "exchange": "BINANCE" })) return valid_parsed_response_data
async def get_balances_kraken( client: ntypes.CLIENT, symbols_resp: NoobitResponseSymbols, # 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.balances, ) -> Result[NoobitResponseBalances, Exception]: asset_from_exchange = lambda x: { v: k for k, v in symbols_resp.assets.items() }[x] req_url = urljoin(base_url, endpoint) # Kraken Doc : Private methods must use POST method = "POST" data = {"nonce": auth.nonce} valid_kraken_req = _validate_data(KrakenPrivateRequest, pmap(data)) if valid_kraken_req.is_err(): return valid_kraken_req if logger: logger(f"Balances - Parsed Request : {valid_kraken_req.value}") headers = auth.headers(endpoint, valid_kraken_req.value.dict()) 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"Balances - Result Content : {result_content.value}") valid_result_content = _validate_data( KrakenResponseBalances, pmap({"balances": result_content.value})) if valid_result_content.is_err(): return valid_result_content parsed_result_data = parse_result(valid_result_content.value.balances, asset_from_exchange) valid_parsed_result_data = _validate_data( NoobitResponseBalances, pmap({ "balances": parsed_result_data, "rawJson": result_content.value, "exchange": "KRAKEN", }), ) return valid_parsed_result_data
async def get_usertrades_binance( client: ntypes.CLIENT, symbol: ntypes.SYMBOL, symbols_resp: NoobitResponseSymbols, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, auth=BinanceAuth(), base_url: pydantic.AnyHttpUrl = endpoints.BINANCE_ENDPOINTS.private.url, endpoint: str = endpoints.BINANCE_ENDPOINTS.private.endpoints.trades_history ) -> Result[NoobitResponseTrades, ValidationError]: symbol_to_exchange = lambda x: {k: v.exchange_pair for k, v in symbols_resp.asset_pairs.items()}[x] symbol_from_exchange= lambda x: {v.exchange_pair: k for k, v in symbols_resp.asset_pairs.items()}[x] req_url = urljoin(base_url, endpoint) method = "GET" headers: typing.Dict = auth.headers() valid_noobit_req = _validate_data(NoobitRequestTrades, pmap({"symbol": symbol, "symbols_resp": symbols_resp, "since": None})) if isinstance(valid_noobit_req, Err): return valid_noobit_req if logger: logger(f"User Trades - Noobit Request : {valid_noobit_req.value}") parsed_req = parse_request(valid_noobit_req.value, symbol_to_exchange) parsed_req["timestamp"] = auth.nonce signed_req = auth._sign(parsed_req) valid_binance_req = _validate_data(BinanceRequestUserTrades, pmap(signed_req)) if valid_binance_req.is_err(): return valid_binance_req if logger: logger(f"User Trades - Parsed Request : {valid_binance_req.value}") result_content = await get_result_content_from_req(client, method, req_url, valid_binance_req.value, headers) if result_content.is_err(): return result_content valid_result_content = _validate_data(BinanceResponseTrades, pmap({"trades": result_content.value})) if valid_result_content.is_err(): return valid_result_content if logger: logger(f"User Trades - Result Content : {result_content.value}") parsed_result = parse_result(valid_result_content.value, symbol, symbol_from_exchange) # filled_orders = [item for item in parsed_result if item["ordStatus"] in ["filled"]] valid_parsed_response_data = _validate_data(NoobitResponseTrades, pmap({"trades": parsed_result, "rawJson": result_content.value, "exchange": "BINANCE"})) return valid_parsed_response_data
async def _get_all_orders( client: ntypes.CLIENT, symbol: ntypes.SYMBOL, symbols_resp: NoobitResponseSymbols, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, auth=BinanceAuth(), base_url: pydantic.AnyHttpUrl = endpoints.BINANCE_ENDPOINTS.private.url, endpoint: str = endpoints.BINANCE_ENDPOINTS.private.endpoints.closed_orders ) -> Result[_AllOrders, ValidationError]: symbol_to_exchange = lambda x: {k: v.exchange_pair for k, v in symbols_resp.asset_pairs.items()}[x] symbol_from_exchange = lambda x: {f"{v.noobit_base}{v.noobit_quote}": k for k, v in symbols_resp.asset_pairs.items()}[x] req_url = urljoin(base_url, endpoint) method = "GET" headers: typing.Dict = auth.headers() valid_noobit_req = _validate_data(NoobitRequestClosedOrders, pmap({"symbol": symbol, "symbols_resp": symbols_resp})) if valid_noobit_req.is_err(): return valid_noobit_req if logger: logger(f"Closed Orders - Noobit Request : {valid_noobit_req.value}") parsed_req = parse_request(valid_noobit_req.value, symbol_to_exchange) parsed_req["timestamp"] = auth.nonce signed_req = auth._sign(parsed_req) valid_binance_req = _validate_data(BinanceRequestClosedOrders, pmap(signed_req)) if valid_binance_req.is_err(): return valid_binance_req if logger: logger(f"Closed Orders - Parsed Request : {valid_binance_req.value}") result_content = await get_result_content_from_req(client, method, req_url, valid_binance_req.value, headers) if result_content.is_err(): return result_content if logger: logger(f"Closed Orders - Result Content : {result_content.value}") valid_result_content = _validate_data(BinanceResponseOrders, pmap({"orders": result_content.value})) if valid_result_content.is_err(): return valid_result_content parsed_result = parse_result(valid_result_content.value, symbol_from_exchange) return Ok({"orders": parsed_result, "rawJson": result_content.value})
async def get_instrument_binance( client: ntypes.CLIENT, symbol: ntypes.SYMBOL, symbols_resp: NoobitResponseSymbols, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, base_url: pydantic.AnyHttpUrl = endpoints.BINANCE_ENDPOINTS.public.url, endpoint: str = endpoints.BINANCE_ENDPOINTS.public.endpoints.instrument, ) -> Result[NoobitResponseInstrument, ValidationError]: 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 = "GET" headers: typing.Dict = {} valid_noobit_req = _validate_data(NoobitRequestInstrument, pmap({"symbol": symbol, "symbols_resp": symbols_resp})) if isinstance(valid_noobit_req, Err): return valid_noobit_req if logger: logger(f"Instrument - Noobit Request : {valid_noobit_req.value}") parsed_req = parse_request(valid_noobit_req.value, symbol_to_exchange) valid_binance_req = _validate_data(BinanceRequestInstrument, pmap(parsed_req)) if valid_binance_req.is_err(): return valid_binance_req if logger: logger(f"Instrument - Parsed Request : {valid_binance_req.value}") result_content = await get_result_content_from_req(client, method, req_url, valid_binance_req.value, headers) if result_content.is_err(): return result_content if logger: logger(f"Instrument - Result Content : {result_content.value}") valid_result_content = _validate_data(BinanceResponseInstrument, result_content.value) if valid_result_content.is_err(): return valid_result_content parsed_result = parse_result(valid_result_content.value, symbol) valid_parsed_response_data = _validate_data(NoobitResponseInstrument, pmap({**parsed_result, "rawJson": result_content.value, "exchange": "BINANCE"})) return valid_parsed_response_data
async def get_openorders_binance( client: ntypes.CLIENT, symbol: ntypes.SYMBOL, symbols_resp: NoobitResponseSymbols, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, auth=BinanceAuth(), base_url: pydantic.AnyHttpUrl = endpoints.BINANCE_ENDPOINTS.private.url, endpoint: str = endpoints.BINANCE_ENDPOINTS.private.endpoints.closed_orders ) -> Result[NoobitResponseOpenOrders, ValidationError]: parsed_result = await _get_all_orders( client, symbol, symbols_resp, logger=logger, auth=auth, base_url=base_url, endpoint=endpoint ) if isinstance(parsed_result, Ok): closed_orders = [item for item in parsed_result.value["orders"] if item["ordStatus"] in ["NEW", "PENDING-NEW", "PARTIALLY-FILLED"]] valid_parsed_response_data = _validate_data(NoobitResponseOpenOrders, pmap({"orders": closed_orders, "rawJson": parsed_result.value["rawJson"], "exchange": "BINANCE"})) return valid_parsed_response_data else: return parsed_result
async def get_exposure_kraken( client: ntypes.CLIENT, symbols_resp: typing.Optional[ NoobitResponseSymbols ] = None, # only there to have consistent signature with binance # 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.exposure, ) -> Result[NoobitResponseExposure, Exception]: req_url = urljoin(base_url, endpoint) # Kraken Doc : Private methods must use POST method = "POST" data = {"nonce": auth.nonce} valid_kraken_req = _validate_data(KrakenPrivateRequest, pmap(data)) if valid_kraken_req.is_err(): return valid_kraken_req if logger: logger(f"Exposure - Parsed Request : {valid_kraken_req.value}") headers = auth.headers(endpoint, valid_kraken_req.value.dict()) 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"Exposure - Result content : {result_content.value}") valid_result_content = _validate_data(KrakenResponseExposure, result_content.value) if valid_result_content.is_err(): return valid_result_content parsed_result = parse_result(valid_result_content.value) valid_parsed_result_data = _validate_data( NoobitResponseExposure, pmap({**parsed_result, "rawJson": result_content.value, "exchange": "KRAKEN"}), ) return valid_parsed_result_data
async def get_balances_ftx( client: ntypes.CLIENT, symbols_resp: NoobitResponseSymbols, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, auth=FtxAuth(), base_url: pydantic.AnyHttpUrl = endpoints.FTX_ENDPOINTS.private.url, endpoint: str = endpoints.FTX_ENDPOINTS.private.endpoints.balances, ) -> Result[NoobitResponseBalances, pydantic.ValidationError]: asset_from_exchange = lambda x: {v: k for k, v in symbols_resp.assets.items()}[x] req_url = "/".join([base_url, "wallet", "balances"]) method = "GET" headers = auth.headers(method, "/api/wallet/balances") valid_ftx_req = Ok(FtxPrivateRequest()) result_content = await get_result_content_from_req( client, method, req_url, valid_ftx_req.value, headers ) if isinstance(result_content, Err): return result_content valid_result_content = _validate_data( FtxResponseBalances, pmap({"balances": result_content.value}) ) if valid_result_content.is_err(): return valid_result_content parsed_result_balances = parse_result(valid_result_content.value) valid_parsed_response_data = _validate_data( NoobitResponseBalances, pmap( { "balances": parsed_result_balances, "rawJson": result_content.value, "exchange": "FTX", } ), ) return valid_parsed_response_data
async def get_symbols_ftx( client: ntypes.CLIENT, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, base_url: pydantic.AnyHttpUrl = endpoints.FTX_ENDPOINTS.public.url, endpoint: str = endpoints.FTX_ENDPOINTS.public.endpoints.symbols, ) -> Result[NoobitResponseSymbols, pydantic.ValidationError]: # ftx has variable urls besides query params # format: https://ftx.com/api/markets/ req_url = "/".join([base_url, endpoint]) method = "GET" headers: typing.Dict = {} # no query params but needs to wrapped in a result that contains an instance of FrozenBaseModel valid_ftx_req = Ok(FrozenBaseModel()) result_content = await get_result_content_from_req(client, method, req_url, valid_ftx_req.value, headers) if isinstance(result_content, Err): return result_content if logger: logger(f"Symbols - Result Content : {result_content.value}") valid_result_content = _validate_data( FtxResponseSymbols, pmap({"symbols": result_content.value})) if valid_result_content.is_err(): return valid_result_content parsed_result = parse_result(valid_result_content.value) valid_parsed_response_data = _validate_data( NoobitResponseSymbols, pmap({ **parsed_result, "rawJson": result_content.value, "exchange": "FTX" }), ) return valid_parsed_response_data
async def get_symbols_binance( client: ntypes.CLIENT, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, base_url: pydantic.AnyHttpUrl = endpoints.BINANCE_ENDPOINTS.public.url, endpoint: str = endpoints.BINANCE_ENDPOINTS.public.endpoints.symbols ) -> Result[NoobitResponseSymbols, Exception]: req_url = urljoin(base_url, endpoint) method = "GET" headers: typing.Dict = {} # no query params but needs to wrapped in a result that contains an instance of FrozenBaseModel valid_binance_req = Ok(FrozenBaseModel()) result_content = await get_result_content_from_req(client, method, req_url, valid_binance_req.value, headers) if result_content.is_err(): return result_content if logger: logger(f"Symbols - Result Content : {result_content.value}") valid_result_content = _validate_data(BinanceResponseSymbols, result_content.value) if valid_result_content.is_err(): return valid_result_content parsed_result = parse_result(valid_result_content.value) valid_parsed_response_data = _validate_data( NoobitResponseSymbols, pmap({ **parsed_result, "rawJson": result_content.value, "exchange": "BINANCE" })) return valid_parsed_response_data
async def get_exposure_binance( client: ntypes.CLIENT, symbols_resp: NoobitResponseSymbols, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, auth=BinanceAuth() ) -> Result[NoobitResponseExposure, ValidationError]: """ Binance does not have an endpoint for this, so we have to check fiat value of each asset in balances individually ==> slow """ totalNetValue = Decimal(0) bals = await get_balances_binance(client, symbols_resp, logger=logger, auth=auth) if isinstance(bals, Err): return bals else: for asset, amount in bals.value.balances.items(): if not asset in ["USDT", "USD", "TWT"]: symbol = str(f"{asset}-USDT") price = await get_instrument_binance( client=client, symbol=ntypes.PSymbol(symbol), # symbol_to_exchange=symbol_to_exchange symbols_resp=symbols_resp) if price.is_err(): raise price.value net_v = amount * price.value.last totalNetValue += net_v else: totalNetValue += amount valid_response = _validate_data( NoobitResponseExposure, pmap({ "totalNetValue": totalNetValue, "cashOutstanding": None, "marginExcess": 0, #TODO check what this corresponds to "exchange": "BINANCE", "rawJson": bals.value. rawJson #! isnt really corresponding json resp, but more relevant })) return valid_response
async def get_wstoken_kraken( client: ntypes.CLIENT, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, auth=KrakenAuth(), base_url: pydantic.AnyHttpUrl = endpoints.KRAKEN_ENDPOINTS.private.url, # intentionally not typed endpoint=endpoints.KRAKEN_ENDPOINTS.private.endpoints.ws_token, ) -> Result[KrakenResponseWsToken, pydantic.ValidationError]: req_url = urljoin(base_url, endpoint) # Kraken Doc : Private methods must use POST method = "POST" data = {"nonce": auth.nonce} valid_kraken_req = _validate_data(KrakenPrivateRequest, pmap(data)) if valid_kraken_req.is_err(): return valid_kraken_req if logger: logger(f"Ws Token - Parsed Request : {valid_kraken_req.value}") headers = auth.headers(endpoint, valid_kraken_req.value.dict()) 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"Ws Token - Result content : {result_content.value}") valid_result_content = _validate_data(KrakenResponseWsToken, result_content.value) return valid_result_content
async def get_exposure_ftx( client: ntypes.CLIENT, symbols_resp: NoobitResponseSymbols, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, auth=FtxAuth(), ) -> Result[NoobitResponseExposure, ValidationError]: totalNetValue = Decimal(0) bals = await get_balances_ftx(client, symbols_resp, logger=logger, auth=auth) if isinstance(bals, Err): return bals else: for asset, amount in bals.value.balances.items(): if not asset in ["USDT", "USD"]: symbol = str(f"{asset}-USD") trades = await get_trades_ftx(client, symbol, symbols_resp, None, logger=logger) if trades.is_err(): raise trades.value last_price = trades.value.trades[-1].avgPx net_v = amount * last_price totalNetValue += net_v else: totalNetValue += amount valid_response = _validate_data( NoobitResponseExposure, pmap({ "totalNetValue": totalNetValue, "cashOutstanding": None, "marginExcess": 0, "exchange": "FTX", "rawJson": bals.value.rawJson, }), ) return valid_response
async def get_wstoken_binance( client: ntypes.CLIENT, # prevent unintentional passing of following args *, auth=BinanceAuth(), base_url: pydantic.AnyHttpUrl = endpoints.BINANCE_ENDPOINTS.private.url, endpoint: str = endpoints.BINANCE_ENDPOINTS.private.endpoints.ws_token ) -> Result[BinanceResponseWsToken, pydantic.ValidationError]: req_url = urljoin(base_url, endpoint) method = "POST" headers: typing.Dict = auth.headers() result_content = await get_result_content_from_req(client, method, req_url, FrozenBaseModel(), headers) if result_content.is_err(): return result_content valid_result_content = _validate_data(BinanceResponseWsToken, result_content.value) return valid_result_content
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_openorder_binance( client: ntypes.CLIENT, symbol: ntypes.SYMBOL, symbols_resp: NoobitResponseSymbols, orderID: str, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, auth=BinanceAuth(), base_url: pydantic.AnyHttpUrl = endpoints.BINANCE_ENDPOINTS.private.url, endpoint: str = endpoints.BINANCE_ENDPOINTS.private.endpoints.remove_order ) -> Result[NoobitResponseItemOrder, ValidationError]: symbol_to_exchange = lambda x: { k: v.exchange_pair for k, v in symbols_resp.asset_pairs.items() }[x] symbol_from_exchange = lambda x: { f"{v.noobit_base}{v.noobit_quote}": k for k, v in symbols_resp.asset_pairs.items() }[x] req_url = urljoin(base_url, endpoint) method = "DELETE" headers: typing.Dict = auth.headers() valid_noobit_req = _validate_data( NoobitRequestCancelOpenOrder, pmap({ "exchange": "BINANCE", "symbol": symbol, "symbols_resp": symbols_resp, "orderID": orderID })) if valid_noobit_req.is_err(): return valid_noobit_req if logger: logger( f"Cancel Open Order - Noobit Request : {valid_noobit_req.value}") parsed_req = parse_request(valid_noobit_req.value, symbol_to_exchange) parsed_req["timestamp"] = auth.nonce signed_req = auth._sign(parsed_req) valid_binance_req = _validate_data(BinanceRequestCancelOpenOrder, pmap(signed_req)) if valid_binance_req.is_err(): return valid_binance_req if logger: logger( f"Cancel Open ORder - Parsed Request : {valid_binance_req.value}") result_content = await get_result_content_from_req(client, method, req_url, valid_binance_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(BinanceResponseCancelOpenOrder, result_content.value) if valid_result_content.is_err(): return valid_result_content parsed_result = parse_result(valid_result_content.value, symbol_from_exchange) return Ok(parsed_result)
async def get_closedorders_ftx( client: ntypes.CLIENT, symbol: ntypes.SYMBOL, symbols_resp: NoobitResponseSymbols, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, auth=FtxAuth(), base_url: pydantic.AnyHttpUrl = endpoints.FTX_ENDPOINTS.private.url, endpoint: str = endpoints.FTX_ENDPOINTS.private.endpoints.closed_orders, ) -> Result[NoobitResponseClosedOrders, pydantic.ValidationError]: symbol_from_exchange = lambda x: { f"{v.noobit_base}{v.noobit_quote}": k for k, v in symbols_resp.asset_pairs.items() }[x] symbol_to_exchange = lambda x: { k: v.exchange_pair for k, v in symbols_resp.asset_pairs.items() }[x] req_url = "/".join([base_url, "orders/history"]) method = "GET" valid_noobit_req = _validate_data( NoobitRequestClosedOrders, pmap({ "symbol": symbol, "symbols_resp": symbols_resp }), ) if valid_noobit_req.is_err(): return valid_noobit_req parsed_req = parse_request(valid_noobit_req.value, symbol_to_exchange) valid_ftx_req = _validate_data(FtxRequestClosedOrder, pmap(parsed_req)) if valid_ftx_req.is_err(): return valid_ftx_req querystr = f"?market={valid_ftx_req.value.market}" req_url += querystr headers = auth.headers(method, f"/api/orders/history{querystr}") if logger: logger(f"Closed Orders - Parsed Request : {valid_ftx_req.value}") result_content = await get_result_content_from_req(client, method, req_url, FrozenBaseModel(), headers) if result_content.is_err(): return result_content if logger: logger(f"Open Orders - Result content : {result_content.value}") valid_result_content = _validate_data( FtxResponseOrder, pmap({"orders": result_content.value})) if valid_result_content.is_err(): return valid_result_content parsed_result_data = parse_result(valid_result_content.value, symbol_from_exchange, symbol) valid_parsed_response = _validate_data( NoobitResponseClosedOrders, pmap({ "orders": parsed_result_data, "rawJson": result_content.value, "exchange": "FTX", }), ) return valid_parsed_response
async def get_spread_kraken( client: ntypes.CLIENT, symbol: ntypes.SYMBOL, symbols_resp: NoobitResponseSymbols, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, base_url: pydantic.AnyHttpUrl = endpoints.KRAKEN_ENDPOINTS.public.url, # intentionally not typed endpoint=endpoints.KRAKEN_ENDPOINTS.public.endpoints.spread, ) -> Result[NoobitResponseSpread, ValidationError]: 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 = "GET" headers: typing.Dict = {} valid_noobit_req = _validate_data( NoobitRequestSpread, pmap({ "symbol": symbol, "symbols_resp": symbols_resp })) if isinstance(valid_noobit_req, Err): return valid_noobit_req if logger: logger(f"Spread - Noobit Request : {valid_noobit_req.value}") parsed_req = parse_request(valid_noobit_req.value, symbol_to_exchange) valid_kraken_req = _validate_data(KrakenRequestSpread, pmap(parsed_req)) if valid_kraken_req.is_err(): return valid_kraken_req if logger: logger(f"Spread - Parsed Request : {valid_kraken_req.value}") 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"Spread - Result Content : {result_content.value}") valid_result_content = _validate_data( make_kraken_model_spread(symbol, symbol_to_exchange), result_content.value) if valid_result_content.is_err(): return valid_result_content parsed_result_spread = parse_result( getattr(valid_result_content.value, symbol_to_exchange(symbol)), symbol) valid_parsed_response_data = _validate_data( NoobitResponseSpread, pmap({ "spread": parsed_result_spread, "rawJson": result_content.value, "exchange": "KRAKEN", }), ) return valid_parsed_response_data
async def get_orderbook_ftx( client: ntypes.CLIENT, symbol: ntypes.SYMBOL, symbols_resp: NoobitResponseSymbols, depth: ntypes.DEPTH, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, base_url: pydantic.AnyHttpUrl = endpoints.FTX_ENDPOINTS.public.url, endpoint: str = endpoints.FTX_ENDPOINTS.public.endpoints.orderbook, ) -> Result[NoobitResponseOrderBook, pydantic.ValidationError]: symbol_to_exchange = lambda x: { k: v.exchange_pair for k, v in symbols_resp.asset_pairs.items() }[x] # ftx has variable urls besides query params # format: https://ftx.com/api/markets/{market_name}/candles req_url = "/".join( [base_url, "markets", symbol_to_exchange(symbol), endpoint]) method = "GET" headers: typing.Dict = {} valid_noobit_req = _validate_data( NoobitRequestOrderBook, pmap({ "symbol": symbol, "symbols_resp": symbols_resp, "depth": depth }), ) if isinstance(valid_noobit_req, Err): return valid_noobit_req if logger: logger(f"Orderbook - Noobit Request : {valid_noobit_req.value}") parsed_req = parse_request(valid_noobit_req.value) valid_ftx_req = _validate_data(FtxRequestOrderBook, pmap(parsed_req)) if valid_ftx_req.is_err(): return valid_ftx_req if logger: logger(f"Orderbook - Parsed Request : {valid_ftx_req.value}") result_content = await get_result_content_from_req(client, method, req_url, valid_ftx_req.value, headers) if result_content.is_err(): return result_content if logger: logger(f"Orderbook - Result Content : {result_content.value}") valid_result_content = _validate_data(FtxResponseOrderBook, result_content.value) if valid_result_content.is_err(): return valid_result_content parsed_result = parse_result(valid_result_content.value, symbol) valid_parsed_response_data = _validate_data( NoobitResponseOrderBook, pmap({ **parsed_result, "rawJson": result_content.value, "exchange": "FTX" }), ) return valid_parsed_response_data
async def post_neworder_kraken( client: ntypes.CLIENT, symbol: ntypes.SYMBOL, symbols_resp: NoobitResponseSymbols, side: ntypes.ORDERSIDE, ordType: ntypes.ORDERTYPE, clOrdID: str, orderQty: Decimal, price: Decimal, timeInForce: ntypes.TIMEINFORCE, stopPrice: typing.Optional[Decimal] = None, # until kraken enables use of `viqc` flag, always pass in None quoteOrderQty: typing.Optional[Decimal] = None, # 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.new_order, **kwargs, ) -> 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" valid_noobit_req = _validate_data( NoobitRequestAddOrder, pmap({ "exchange": "KRAKEN", "symbol": symbol, "symbols_resp": symbols_resp, "side": side, "ordType": ordType, "clOrdID": clOrdID, "orderQty": orderQty, "price": price, "timeInForce": timeInForce, "quoteOrderQty": quoteOrderQty, "stopPrice": stopPrice, **kwargs, }), ) if valid_noobit_req.is_err(): return valid_noobit_req if logger: logger(f"New Order - Noobit Request : {valid_noobit_req.value}") parsed_req = parse_request(valid_noobit_req.value, symbol_to_exchange) data = {"nonce": auth.nonce, **parsed_req} valid_kraken_req = _validate_data( KrakenRequestNewOrder, pmap({ "nonce": data["nonce"], **parsed_req })) if valid_kraken_req.is_err(): return valid_kraken_req if logger: logger(f"New Order - Parsed Request : {valid_kraken_req.value}") headers = auth.headers(endpoint, valid_kraken_req.value.dict(exclude_none=True)) 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"New Order - Result content : {result_content.value}") valid_result_content = _validate_data(KrakenResponseNewOrder, result_content.value) if valid_result_content.is_err(): return valid_result_content parsed_result = parse_result(valid_result_content.value) valid_parsed_response_data = _validate_data( NoobitResponseNewOrder, pmap({ "descr": parsed_result["descr"], "txid": parsed_result["txid"], "rawJson": result_content.value, "exchange": "KRAKEN", }), ) if valid_parsed_response_data.is_err(): return valid_parsed_response_data # return valid_parsed_response_data [newOrderID] = valid_parsed_response_data.value.txid # # TODO we want to return a more complete info on the trade: # # ? ==> should we fetch the order corresponding to the txid we get back ? # tx_info = await get_usertrades_kraken(client, symbol, lambda x : {"DOTUSD": ntypes.PSymbol("DOT-USD")}, auth) # return [trade for trade in tx_info.value.trades if trade.orderID == valid_parsed_response_data.value.txid] if ordType == "MARKET": # FIXME symbol_from_exchange lambda isnt entirely accurate # will be ok for most pairs but some have 4/5 letters for base cl_ord = await get_closedorders_kraken(client, symbol, symbols_resp, logger=logger, auth=auth) if isinstance(cl_ord, Ok): [order_info] = [ order for order in cl_ord.value.orders if order.orderID == newOrderID ] else: return cl_ord else: await asyncio.sleep(0.1) # FIXME symbol_from_exchange lambda isnt entirely accurate # will be ok for most pairs but some have 4/5 letters for base op_ord = await get_openorders_kraken(client, symbol, symbols_resp, logger=logger, auth=auth) if isinstance(op_ord, Ok): [order_info] = [ order for order in op_ord.value.orders if order.orderID == newOrderID #FIXME this causes errors because false sometimes ] else: return op_ord # return type will be : NoobitResponseItemOrder, same as binance trading.py, so should be ok return Ok(order_info)
async def orderbook( self, symbols_resp: NoobitResponseSymbols, symbol: ntypes.PSymbol, depth: ntypes.DEPTH, aggregate: bool = False ) -> typing.AsyncIterable[Result[NoobitResponseOrderBook, ValidationError]]: super()._ensure_dispatch() symbol_to_exchange = lambda x: { k: f"{v.noobit_base}/{v.noobit_quote}" for k, v in symbols_resp.asset_pairs.items() }[x] valid_sub_model = orderbook.validate_sub(symbol_to_exchange, symbol, depth) if isinstance(valid_sub_model, Err): yield valid_sub_model #! replace with: below sub_result = await subscribe(self.client, valid_sub_model.value) #type: ignore # subscription status is checked by a watcher coro await asyncio.sleep(1) if not symbol_to_exchange(symbol) in self._subd_feeds["orderbook"]: return self._subd_feeds["orderbook"].add(symbol_to_exchange(symbol)) #? should we stream full orderbook ? if not aggregate: # # stream udpates # async for msg in self.iterq(self._data_queues, "orderbook"): # if self._terminate: break # yield msg pass else: # reconstruct orderbook _count = 0 # async for msg in self.iterq(self._data_queues, "orderbook"): async for msg in self.aiter_book(): # print("From iterq :", msg) # !!!! this means if we are not subbed to spread feed, we will stall here sp_q: Result[ NoobitResponseSpread, Exception] = await self._data_queues["spread_copy"].get() if isinstance(sp_q, Ok): spreads = sp_q else: #? how should we handle this raise ValueError("Error in Spread") pair = msg.value.symbol #type: ignore pair_key = ntypes.PSymbol(pair) # only way to make mypy understand that `msg.value` is `Ok` # (msg.is_ok() will not work) # see: https://github.com/dbrgn/result/issues/17#issue-502950927 if isinstance(msg, Ok): if _count == 0: # snapshot print("orderbook snapshot") # TODO exchange shoudl be dynamic (add to model ?) self._full_books[pair_key] = {"asks": {}, "bids": {}} self._full_books[pair_key]["asks"] = msg.value.asks self._full_books[pair_key]["bids"] = msg.value.bids else: print("orderbook update") # update (self._full_books[pair_key]["asks"]).update( msg.value.asks) self._full_books[pair_key]["bids"].update( msg.value.bids) # filter out 0 values and bids/asks outside of spread self._full_books[pair_key]["asks"] = { k: v for k, v in self._full_books[pair]["asks"].items() if v > 0 and k >= spreads.value.spread[0].bestAskPrice } self._full_books[pair_key]["bids"] = { k: v for k, v in self._full_books[pair]["bids"].items() if v > 0 and k <= spreads.value.spread[0].bestBidPrice } _count += 1 valid_book = _validate_data( NoobitResponseOrderBook, pmap({ "exchange": "KRAKEN", "symbol": msg.value.symbol, "utcTime": msg.value.utcTime, "rawJson": msg.value.rawJson, "asks": self._full_books[pair_key]["asks"], "bids": self._full_books[pair_key]["bids"] })) yield valid_book else: #? or log it ? yield msg
def validate_parsed(msg, parsed_msg): return _validate_data(NoobitResponseTrades, { "trades": parsed_msg, "rawJson": msg })
async def post_neworder_binance( client: ntypes.CLIENT, symbol: ntypes.SYMBOL, symbols_resp: NoobitResponseSymbols, side: ntypes.ORDERSIDE, ordType: ntypes.ORDERTYPE, clOrdID: str, orderQty: Decimal, price: Decimal, timeInForce: ntypes.TIMEINFORCE, stopPrice: typing.Optional[Decimal] = None, quoteOrderQty: typing.Optional[Decimal] = None, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, auth=BinanceAuth(), base_url: pydantic.AnyHttpUrl = endpoints.BINANCE_ENDPOINTS.private.url, endpoint: str = endpoints.BINANCE_ENDPOINTS.private.endpoints.new_order, ) -> Result[NoobitResponseItemOrder, ValidationError]: 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" headers: typing.Dict = auth.headers() valid_noobit_req = _validate_data( NoobitRequestAddOrder, pmap({ "exchange": "BINANCE", "symbol": symbol, "symbols_resp": symbols_resp, "side": side, "ordType": ordType, "clOrdID": clOrdID, "orderQty": orderQty, "price": price, "timeInForce": timeInForce, "quoteOrderQty": quoteOrderQty, "stopPrice": stopPrice, })) if valid_noobit_req.is_err(): return valid_noobit_req if logger: logger(f"New Order - Noobit Request : {valid_noobit_req.value}") parsed_req = parse_request(valid_noobit_req.value, symbol_to_exchange) parsed_req["timestamp"] = auth.nonce valid_binance_req = _validate_data(BinanceRequestNewOrder, pmap({**parsed_req})) if valid_binance_req.is_err(): return valid_binance_req if logger: logger(f"New Order - Parsed Request : {valid_binance_req.value}") #! sign after validation, otherwise we aill get all the non values too signed_req: dict = auth._sign( valid_binance_req.value.dict(exclude_none=True)) #! we should not pass in "params" to the client, but construct the whole url + query string ourself, so we can make sure its sorted properly #! ====> qstrings = sorted([(k, v) for k, v in signed_req.items() if not "signature" in k], reverse=True) qstrings_join = urlencode(qstrings) full_url = "?".join([req_url, qstrings_join]) full_url += f"&signature={signed_req['signature']}" #! <===== result_content = await get_result_content_from_req(client, method, full_url, FrozenBaseModel(), headers) if result_content.is_err(): return result_content if logger: logger(f"New Order - Result Content : {result_content.value}") valid_result_content = _validate_data(BinanceResponseNewOrder, result_content.value) if valid_result_content.is_err(): return valid_result_content parsed_result = parse_result(valid_result_content.value, symbol) valid_parsed_response_data = _validate_data( NoobitResponseItemOrder, pmap({ **parsed_result, "rawJson": result_content.value, "exchange": "BINANCE" })) return valid_parsed_response_data
async def post_neworder_ftx( client: ntypes.CLIENT, symbol: ntypes.SYMBOL, symbols_resp: NoobitResponseSymbols, side: ntypes.ORDERSIDE, ordType: ntypes.ORDERTYPE, clOrdID: str, orderQty: Decimal, price: Decimal, timeInForce: ntypes.TIMEINFORCE, stopPrice: typing.Optional[Decimal] = None, quoteOrderQty: typing.Optional[Decimal] = None, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, auth=FtxAuth(), base_url: pydantic.AnyHttpUrl = endpoints.FTX_ENDPOINTS.private.url, endpoint: str = endpoints.FTX_ENDPOINTS.private.endpoints.new_order, ) -> Result[NoobitResponseItemOrder, pydantic.ValidationError]: symbol_from_exchange = lambda x: { f"{v.noobit_base}{v.noobit_quote}": k for k, v in symbols_resp.asset_pairs.items() }[x] symbol_to_exchange = lambda x: { k: v.exchange_pair for k, v in symbols_resp.asset_pairs.items() }[x] req_url = "/".join([base_url, "orders"]) method = "POST" valid_noobit_req = _validate_data( NoobitRequestAddOrder, pmap({ "exchange": "FTX", "symbol": symbol, "symbols_resp": symbols_resp, "side": side, "ordType": ordType, "clOrdID": clOrdID, "orderQty": orderQty, "price": price, "timeInForce": timeInForce, "quoteOrderQty": quoteOrderQty, "stopPrice": stopPrice, })) if valid_noobit_req.is_err(): return valid_noobit_req # if logger: # logger(f"New Order - Noobit Request : {valid_noobit_req.value}") parsed_req = parse_request(valid_noobit_req.value, symbol_to_exchange) valid_ftx_req = _validate_data(FtxRequestNewOrder, pmap(parsed_req)) if valid_ftx_req.is_err(): return valid_ftx_req # ? should be more elegant way to do this querystr = f"?market={valid_ftx_req.value.market}" req_url += querystr headers = auth.headers(method, f"/api/orders{querystr}") if logger: logger(f"New Order - Parsed Request : {valid_ftx_req.value}") result_content = await get_result_content_from_req(client, method, req_url, FrozenBaseModel(), headers) if result_content.is_err(): return result_content if logger: logger(f"New Order - Result content : {result_content.value}") valid_result_content = _validate_data(FtxResponseNewOrder, result_content.value) if valid_result_content.is_err(): return valid_result_content parsed_result = parse_result(valid_result_content.value, symbol_from_exchange) valid_parsed_response = _validate_data( NoobitResponseNewOrder, pmap({ **parsed_result, "rawJson": result_content.value, "exchange": "FTX", }), ) return valid_parsed_response
async def get_ohlc_kraken( client: ntypes.CLIENT, symbol: ntypes.SYMBOL, symbols_resp: NoobitResponseSymbols, timeframe: ntypes.TIMEFRAME, since: ntypes.TIMESTAMP, # prevent unintentional passing of following args *, logger: typing.Optional[typing.Callable] = None, base_url: pydantic.AnyHttpUrl = endpoints.KRAKEN_ENDPOINTS.public.url, endpoint: str = endpoints.KRAKEN_ENDPOINTS.public.endpoints.ohlc, ) -> Result[NoobitResponseOhlc, ValidationError]: 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 = "GET" headers: typing.Dict = {} valid_noobit_req = _validate_data( NoobitRequestOhlc, pmap({ "symbol": symbol, "symbols_resp": symbols_resp, "timeframe": timeframe, "since": since, }), ) if isinstance(valid_noobit_req, Err): return valid_noobit_req if logger: logger(f"Ohlc - Noobit Request : {valid_noobit_req.value}") parsed_req = parse_request(valid_noobit_req.value, symbol_to_exchange) valid_kraken_req = _validate_data(KrakenRequestOhlc, pmap(parsed_req)) if valid_kraken_req.is_err(): return valid_kraken_req if logger: logger(f"Ohlc - Parsed Request : {valid_kraken_req.value}") 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"Ohlc - Result Content : {result_content.value}") valid_result_content = _validate_data( make_kraken_model_ohlc(symbol, symbol_to_exchange), pmap({ symbol_to_exchange(symbol): result_content.value[symbol_to_exchange(symbol)], "last": result_content.value["last"], }), ) if valid_result_content.is_err(): return valid_result_content parsed_result = parse_result( getattr(valid_result_content.value, symbol_to_exchange(symbol)), symbol) valid_parsed_response_data = _validate_data( NoobitResponseOhlc, pmap({ "ohlc": parsed_result, "rawJson": result_content.value, "exchange": "KRAKEN", }), ) return valid_parsed_response_data
def validate_parsed(msg, parsed_msg): return _validate_data( NoobitResponseOpenOrders, {"orders": parsed_msg, "rawJson": msg} )