From 1def00cad8fe5e474642a7f610b9f48e8b9cf97b Mon Sep 17 00:00:00 2001 From: Sourcery AI <> Date: Sun, 27 Feb 2022 23:33:34 +0000 Subject: [PATCH] 'Refactored by Sourcery' --- bin/hummingbot_quickstart.py | 14 +- bin/path_util.py | 3 +- hummingbot/client/__init__.py | 7 +- hummingbot/client/command/balance_command.py | 54 ++-- hummingbot/client/command/config_command.py | 31 +- hummingbot/client/command/connect_command.py | 9 +- hummingbot/client/command/create_command.py | 5 +- hummingbot/client/command/exit_command.py | 2 +- hummingbot/client/command/export_command.py | 4 +- hummingbot/client/command/gateway_command.py | 23 +- hummingbot/client/command/history_command.py | 104 +++--- hummingbot/client/command/import_command.py | 9 +- .../client/command/order_book_command.py | 5 +- hummingbot/client/command/silly_commands.py | 28 +- hummingbot/client/command/start_command.py | 11 +- hummingbot/client/command/status_command.py | 62 ++-- hummingbot/client/config/config_helpers.py | 22 +- hummingbot/client/config/config_validators.py | 44 +-- hummingbot/client/config/config_var.py | 2 +- .../client/config/fee_overrides_config_map.py | 30 +- hummingbot/client/config/global_config_map.py | 2 +- hummingbot/client/hummingbot_application.py | 35 +- hummingbot/client/performance.py | 7 +- hummingbot/client/settings.py | 18 +- hummingbot/client/tab/order_book_tab.py | 5 +- hummingbot/client/ui/completer.py | 115 ++++--- hummingbot/client/ui/custom_widgets.py | 23 +- hummingbot/client/ui/hummingbot_cli.py | 3 +- hummingbot/client/ui/interface_utils.py | 58 ++-- hummingbot/client/ui/layout.py | 13 +- hummingbot/client/ui/parser.py | 3 +- hummingbot/client/ui/style.py | 70 ++-- hummingbot/connector/client_order_tracker.py | 27 +- .../connector/balancer/balancer_connector.py | 152 ++++----- .../connector/terra/terra_connector.py | 9 +- .../connector/uniswap/uniswap_connector.py | 153 ++++----- .../uniswap_v3/uniswap_v3_connector.py | 216 ++++++------- ...ce_perpetual_api_order_book_data_source.py | 4 +- .../binance_perpetual_auth.py | 3 +- .../binance_perpetual_derivative.py | 77 ++--- ...nance_perpetual_user_stream_data_source.py | 4 +- .../binance_perpetual_utils.py | 5 +- ...it_perpetual_api_order_book_data_source.py | 43 +-- .../bybit_perpetual/bybit_perpetual_auth.py | 12 +- .../bybit_perpetual_derivative.py | 75 ++--- .../bybit_perpetual_order_book_tracker.py | 7 +- ...bybit_perpetual_user_stream_data_source.py | 11 +- .../bybit_perpetual/bybit_perpetual_utils.py | 298 +++++++++++++----- .../bybit_perpetual_websocket_adaptor.py | 17 +- ...dx_perpetual_api_order_book_data_source.py | 31 +- .../dydx_perpetual/dydx_perpetual_auth.py | 4 +- .../dydx_perpetual_client_wrapper.py | 3 +- .../dydx_perpetual_derivative.py | 136 ++++---- .../dydx_perpetual_order_book.py | 8 +- .../dydx_perpetual_order_book_message.py | 5 +- .../dydx_perpetual_user_stream_data_source.py | 4 +- .../dydx_perpetual/dydx_perpetual_utils.py | 3 +- .../perpetual_finance_derivative.py | 204 ++++++------ .../ascend_ex_api_order_book_data_source.py | 6 +- .../ascend_ex_api_user_stream_data_source.py | 6 +- .../exchange/ascend_ex/ascend_ex_exchange.py | 24 +- .../ascend_ex/ascend_ex_order_book_message.py | 9 +- .../beaxy/beaxy_api_order_book_data_source.py | 10 +- .../exchange/beaxy/beaxy_constants.py | 7 +- .../beaxy/beaxy_order_book_message.py | 11 +- .../beaxy/beaxy_order_book_tracker.py | 8 +- .../binance_api_order_book_data_source.py | 5 +- .../binance_api_user_stream_data_source.py | 4 +- .../exchange/binance/binance_auth.py | 7 +- .../exchange/binance/binance_exchange.py | 21 +- .../binance/binance_order_book_tracker.py | 5 +- .../bitfinex_api_order_book_data_source.py | 10 +- .../exchange/bitfinex/bitfinex_auth.py | 7 +- .../bitfinex/bitfinex_order_book_tracker.py | 9 +- .../exchange/bitfinex/bitfinex_utils.py | 10 +- .../exchange/bitfinex/bitfinex_websocket.py | 5 +- .../bitmart_api_order_book_data_source.py | 3 +- .../bitmart_api_user_stream_data_source.py | 5 +- .../exchange/bitmart/bitmart_auth.py | 17 +- .../exchange/bitmart/bitmart_exchange.py | 14 +- .../bitmart/bitmart_order_book_message.py | 9 +- .../exchange/bitmart/bitmart_utils.py | 27 +- .../bittrex_api_order_book_data_source.py | 5 +- .../bittrex/bittrex_order_book_message.py | 5 +- .../bittrex/bittrex_order_book_tracker.py | 9 +- .../blocktane_api_order_book_data_source.py | 3 +- .../blocktane_api_user_stream_data_source.py | 5 +- .../exchange/blocktane/blocktane_auth.py | 6 +- ...coinbase_pro_api_order_book_data_source.py | 13 +- ...oinbase_pro_api_user_stream_data_source.py | 10 +- .../coinbase_pro_order_book_tracker.py | 5 +- .../coinbase_pro/coinbase_pro_utils.py | 3 +- .../coinzoom_api_order_book_data_source.py | 17 +- .../coinzoom_api_user_stream_data_source.py | 2 +- .../exchange/coinzoom/coinzoom_auth.py | 3 +- .../exchange/coinzoom/coinzoom_exchange.py | 43 +-- .../coinzoom/coinzoom_order_book_message.py | 9 +- .../exchange/coinzoom/coinzoom_websocket.py | 13 +- .../crypto_com/crypto_com_exchange.py | 14 +- .../crypto_com_order_book_message.py | 9 +- .../crypto_com/crypto_com_websocket.py | 2 +- .../exchange/digifinex/digifinex_auth.py | 6 +- .../exchange/digifinex/digifinex_exchange.py | 16 +- .../digifinex/digifinex_order_book_message.py | 5 +- .../exchange/digifinex/digifinex_websocket.py | 4 +- .../ftx/ftx_api_order_book_data_source.py | 51 +-- .../exchange/ftx/ftx_order_book_message.py | 5 +- .../exchange/ftx/ftx_order_book_tracker.py | 9 +- .../gate_io_api_order_book_data_source.py | 10 +- .../gate_io_api_user_stream_data_source.py | 5 +- .../exchange/gate_io/gate_io_auth.py | 6 +- .../exchange/gate_io/gate_io_exchange.py | 85 +++-- .../exchange/gate_io/gate_io_utils.py | 6 +- .../exchange/gate_io/gate_io_websocket.py | 11 +- .../hitbtc_api_order_book_data_source.py | 8 +- .../connector/exchange/hitbtc/hitbtc_auth.py | 5 +- .../exchange/hitbtc/hitbtc_exchange.py | 35 +- .../hitbtc/hitbtc_order_book_message.py | 5 +- .../exchange/hitbtc/hitbtc_websocket.py | 7 +- .../huobi/huobi_api_order_book_data_source.py | 2 +- .../huobi_api_user_stream_data_source.py | 4 +- .../connector/exchange/huobi/huobi_auth.py | 4 +- .../connector/exchange/huobi/huobi_utils.py | 8 +- hummingbot/connector/markets_recorder.py | 60 ++-- hummingbot/connector/utils.py | 6 +- 125 files changed, 1661 insertions(+), 1449 deletions(-) diff --git a/bin/hummingbot_quickstart.py b/bin/hummingbot_quickstart.py index 21f5148783..5cf32c673f 100755 --- a/bin/hummingbot_quickstart.py +++ b/bin/hummingbot_quickstart.py @@ -96,9 +96,12 @@ async def quick_start(args): if wallet and password: global_config_map.get("ethereum_wallet").value = wallet - if hb.strategy_name and hb.strategy_file_name: - if not all_configs_complete(hb.strategy_name): - hb.status() + if ( + hb.strategy_name + and hb.strategy_file_name + and not all_configs_complete(hb.strategy_name) + ): + hb.status() with patch_stdout(log_field=hb.app.log_field): dev_mode = check_dev_mode() @@ -135,9 +138,8 @@ def main(): args.config_password = os.environ["CONFIG_PASSWORD"] # If no password is given from the command line, prompt for one. - if args.config_password is None: - if not login_prompt(): - return + if args.config_password is None and not login_prompt(): + return asyncio.get_event_loop().run_until_complete(quick_start(args)) diff --git a/bin/path_util.py b/bin/path_util.py index 545d844650..687d78c646 100644 --- a/bin/path_util.py +++ b/bin/path_util.py @@ -1,9 +1,9 @@ #!/usr/bin/python +import sys if "hummingbot-dist" in __file__: # Dist environment. import os - import sys sys.path.append(sys.path.pop(0)) sys.path.insert(0, os.getcwd()) @@ -12,5 +12,4 @@ else: # Dev environment. from os.path import join, realpath - import sys sys.path.insert(0, realpath(join(__file__, "../../"))) diff --git a/hummingbot/client/__init__.py b/hummingbot/client/__init__.py index 8fa69c0f4c..566c0e05c6 100644 --- a/hummingbot/client/__init__.py +++ b/hummingbot/client/__init__.py @@ -14,11 +14,10 @@ def format_decimal(n): with decimal.localcontext() as ctx: if isinstance(n, float): n = ctx.create_decimal(n) - if isinstance(n, decimal.Decimal): - n = round(n, FLOAT_PRINTOUT_PRECISION) - return format(n.normalize(), 'f') - else: + if not isinstance(n, decimal.Decimal): return str(n) + n = round(n, FLOAT_PRINTOUT_PRECISION) + return format(n.normalize(), 'f') except Exception as e: logging.getLogger().error(str(e)) diff --git a/hummingbot/client/command/balance_command.py b/hummingbot/client/command/balance_command.py index 358530069f..f268d2659d 100644 --- a/hummingbot/client/command/balance_command.py +++ b/hummingbot/client/command/balance_command.py @@ -46,7 +46,7 @@ def balance(self, file_path = GLOBAL_CONFIG_PATH if option == "limit": config_var = config_map["balance_asset_limit"] - if args is None or len(args) == 0: + if args is None or not args: safe_ensure_future(self.show_asset_limits()) return if len(args) != 3 or validate_exchange(args[0]) is not None or validate_decimal(args[2]) is not None: @@ -68,7 +68,7 @@ def balance(self, elif option == "paper": config_var = config_map["paper_trade_account_balance"] - if args is None or len(args) == 0: + if args is None or not args: safe_ensure_future(self.show_paper_account_balance()) return if len(args) != 2 or validate_decimal(args[1]) is not None: @@ -108,7 +108,7 @@ async def show_balances(self): if df.empty: self._notify("You have no balance on this exchange.") else: - lines = [" " + line for line in df.to_string(index=False).split("\n")] + lines = [f" {line}" for line in df.to_string(index=False).split("\n")] self._notify("\n".join(lines)) self._notify(f"\n Total: {RateOracle.global_token_symbol} {PerformanceMetrics.smart_round(df[total_col_name].sum())} " f"Allocated: {allocated_total / df[total_col_name].sum():.2%}") @@ -122,7 +122,7 @@ async def show_balances(self): if not CeloCLI.unlocked: await self.validate_n_connect_celo() df = await self.celo_balances_df() - lines = [" " + line for line in df.to_string(index=False).split("\n")] + lines = [f" {line}" for line in df.to_string(index=False).split("\n")] self._notify("\ncelo:") self._notify("\n".join(lines)) except Exception as e: @@ -131,13 +131,13 @@ async def show_balances(self): eth_address = global_config_map["ethereum_wallet"].value if eth_address is not None: eth_df = await self.ethereum_balances_df() - lines = [" " + line for line in eth_df.to_string(index=False).split("\n")] + lines = [f" {line}" for line in eth_df.to_string(index=False).split("\n")] self._notify("\nethereum:") self._notify("\n".join(lines)) # XDAI balances xdai_df = await self.xdai_balances_df() - lines = [" " + line for line in xdai_df.to_string(index=False).split("\n")] + lines = [f" {line}" for line in xdai_df.to_string(index=False).split("\n")] self._notify("\nxdai:") self._notify("\n".join(lines)) @@ -167,10 +167,12 @@ async def exchange_balances_extra_df(self, # type: HummingbotApplication async def celo_balances_df(self, # type: HummingbotApplication ): - rows = [] bals = CeloCLI.balances() - for token, bal in bals.items(): - rows.append({"Asset": token.upper(), "Amount": round(bal.total, 4)}) + rows = [ + {"Asset": token.upper(), "Amount": round(bal.total, 4)} + for token, bal in bals.items() + ] + df = pd.DataFrame(data=rows, columns=["Asset", "Amount"]) df.sort_values(by=["Asset"], inplace=True) return df @@ -180,8 +182,11 @@ async def ethereum_balances_df(self, # type: HummingbotApplication rows = [] if ethereum_required_trading_pairs(): bals = await UserBalances.eth_n_erc20_balances() - for token, bal in bals.items(): - rows.append({"Asset": token, "Amount": round(bal, 4)}) + rows.extend( + {"Asset": token, "Amount": round(bal, 4)} + for token, bal in bals.items() + ) + else: eth_bal = UserBalances.ethereum_balance() rows.append({"Asset": "ETH", "Amount": round(eth_bal, 4)}) @@ -191,19 +196,22 @@ async def ethereum_balances_df(self, # type: HummingbotApplication async def xdai_balances_df(self, # type: HummingbotApplication ): - rows = [] bals = await UserBalances.xdai_balances() - for token, bal in bals.items(): - rows.append({"Asset": token, "Amount": round(bal, 4)}) + rows = [ + {"Asset": token, "Amount": round(bal, 4)} + for token, bal in bals.items() + ] + df = pd.DataFrame(data=rows, columns=["Asset", "Amount"]) df.sort_values(by=["Asset"], inplace=True) return df async def asset_limits_df(self, asset_limit_conf: Dict[str, str]): - rows = [] - for token, amount in asset_limit_conf.items(): - rows.append({"Asset": token, "Limit": round(Decimal(amount), 4)}) + rows = [ + {"Asset": token, "Limit": round(Decimal(amount), 4)} + for token, amount in asset_limit_conf.items() + ] df = pd.DataFrame(data=rows, columns=["Asset", "Limit"]) df.sort_values(by=["Asset"], inplace=True) @@ -229,15 +237,17 @@ async def show_asset_limits(self): if df.empty: self._notify("You have no limits on this exchange.") else: - lines = [" " + line for line in df.to_string(index=False).split("\n")] + lines = [f" {line}" for line in df.to_string(index=False).split("\n")] self._notify("\n".join(lines)) self._notify("\n") return async def paper_acccount_balance_df(self, paper_balances: Dict[str, Decimal]): - rows = [] - for asset, balance in paper_balances.items(): - rows.append({"Asset": asset, "Balance": round(Decimal(str(balance)), 4)}) + rows = [ + {"Asset": asset, "Balance": round(Decimal(str(balance)), 4)} + for asset, balance in paper_balances.items() + ] + df = pd.DataFrame(data=rows, columns=["Asset", "Balance"]) df.sort_values(by=["Asset"], inplace=True) return df @@ -260,7 +270,7 @@ async def show_paper_account_balance(self): return self._notify("Paper account balances:") df = await self.paper_acccount_balance_df(paper_balances) - lines = [" " + line for line in df.to_string(index=False).split("\n")] + lines = [f" {line}" for line in df.to_string(index=False).split("\n")] self._notify("\n".join(lines)) self._notify("\n") return diff --git a/hummingbot/client/command/config_command.py b/hummingbot/client/command/config_command.py index 2d80452aba..2d81c7de59 100644 --- a/hummingbot/client/command/config_command.py +++ b/hummingbot/client/command/config_command.py @@ -87,21 +87,33 @@ def list_configs(self, # type: HummingbotApplication if cv.key in global_configs_to_display and not cv.is_secure] df = map_df_to_str(pd.DataFrame(data=data, columns=columns)) self._notify("\nGlobal Configurations:") - lines = [" " + line for line in df.to_string(index=False, max_colwidth=50).split("\n")] + lines = [ + f" {line}" + for line in df.to_string(index=False, max_colwidth=50).split("\n") + ] + self._notify("\n".join(lines)) data = [[cv.key, cv.value] for cv in global_config_map.values() if cv.key in color_settings_to_display and not cv.is_secure] df = map_df_to_str(pd.DataFrame(data=data, columns=columns)) self._notify("\nColor Settings:") - lines = [" " + line for line in df.to_string(index=False, max_colwidth=50).split("\n")] + lines = [ + f" {line}" + for line in df.to_string(index=False, max_colwidth=50).split("\n") + ] + self._notify("\n".join(lines)) if self.strategy_name is not None: data = [[cv.printable_key or cv.key, cv.value] for cv in self.strategy_config_map.values() if not cv.is_secure] df = map_df_to_str(pd.DataFrame(data=data, columns=columns)) self._notify("\nStrategy Configurations:") - lines = [" " + line for line in df.to_string(index=False, max_colwidth=50).split("\n")] + lines = [ + f" {line}" + for line in df.to_string(index=False, max_colwidth=50).split("\n") + ] + self._notify("\n".join(lines)) def config_able_keys(self # type: HummingbotApplication @@ -118,11 +130,10 @@ def config_able_keys(self # type: HummingbotApplication async def check_password(self, # type: HummingbotApplication ): password = await self.app.prompt(prompt="Enter your password >>> ", is_password=True) - if password != Security.password: - self._notify("Invalid password, please try again.") - return False - else: + if password == Security.password: return True + self._notify("Invalid password, please try again.") + return False # Make this function static so unit testing can be performed. @staticmethod @@ -177,8 +188,10 @@ async def _config_single_key(self, # type: HummingbotApplication self.app.app.style = load_style() for config in missings: self._notify(f"{config.key}: {str(config.value)}") - if isinstance(self.strategy, PureMarketMakingStrategy) or \ - isinstance(self.strategy, PerpetualMarketMakingStrategy): + if isinstance( + self.strategy, + (PureMarketMakingStrategy, PerpetualMarketMakingStrategy), + ): updated = ConfigCommand.update_running_mm(self.strategy, key, config_var.value) if updated: self._notify(f"\nThe current {self.strategy_name} strategy has been updated " diff --git a/hummingbot/client/command/connect_command.py b/hummingbot/client/command/connect_command.py index bebf54e2bc..5c63a9d723 100644 --- a/hummingbot/client/command/connect_command.py +++ b/hummingbot/client/command/connect_command.py @@ -42,8 +42,9 @@ async def connect_exchange(self, # type: HummingbotApplication to_connect = True if Security.encrypted_file_exists(exchange_configs[0].key): await Security.wait_til_decryption_done() - api_key_config = [c for c in exchange_configs if "api_key" in c.key] - if api_key_config: + if api_key_config := [ + c for c in exchange_configs if "api_key" in c.key + ]: api_key_config = api_key_config[0] api_key = Security.decrypted_value(api_key_config.key) prompt = f"Would you like to replace your existing {exchange} API key {api_key} (Yes/No)? >>> " @@ -87,10 +88,10 @@ async def show_connections(self # type: HummingbotApplication self._notify("\nTesting connections, please wait...") await Security.wait_til_decryption_done() df, failed_msgs = await self.connection_df() - lines = [" " + line for line in df.to_string(index=False).split("\n")] + lines = [f" {line}" for line in df.to_string(index=False).split("\n")] if failed_msgs: lines.append("\nFailed connections:") - lines.extend([" " + k + ": " + v for k, v in failed_msgs.items()]) + lines.extend([f" {k}: {v}" for k, v in failed_msgs.items()]) self._notify("\n".join(lines)) async def connection_df(self # type: HummingbotApplication diff --git a/hummingbot/client/command/create_command.py b/hummingbot/client/command/create_command.py index 67f7499131..0fa893360c 100644 --- a/hummingbot/client/command/create_command.py +++ b/hummingbot/client/command/create_command.py @@ -57,10 +57,7 @@ async def prompt_for_configuration(self, # type: HummingbotApplication f"while setting up these below configuration.") # assign default values and reset those not required for config in config_map.values(): - if config.required: - config.value = config.default - else: - config.value = None + config.value = config.default if config.required else None for config in config_map.values(): if config.prompt_on_new and config.required: if not self.app.to_stop_config: diff --git a/hummingbot/client/command/exit_command.py b/hummingbot/client/command/exit_command.py index a18dc9ca97..09d5062a83 100644 --- a/hummingbot/client/command/exit_command.py +++ b/hummingbot/client/command/exit_command.py @@ -17,7 +17,7 @@ async def exit_loop(self, # type: HummingbotApplication force: bool = False): if self.strategy_task is not None and not self.strategy_task.cancelled(): self.strategy_task.cancel() - if force is False and self._trading_required: + if not force and self._trading_required: success = await self._cancel_outstanding_orders() if not success: self._notify('Wind down process terminated: Failed to cancel all outstanding orders. ' diff --git a/hummingbot/client/command/export_command.py b/hummingbot/client/command/export_command.py index 27595fbc80..d7dccd274b 100644 --- a/hummingbot/client/command/export_command.py +++ b/hummingbot/client/command/export_command.py @@ -58,7 +58,7 @@ async def prompt_new_export_file_name(self, # type: HummingbotApplication self._notify("Value is required.") return await self.prompt_new_export_file_name(path) if "." not in input: - input = input + ".csv" + input = f'{input}.csv' file_path = os.path.join(path, input) if os.path.exists(file_path): self._notify(f"{input} file already exists, please enter a new name.") @@ -72,7 +72,7 @@ async def export_trades(self, # type: HummingbotApplication trades: List[TradeFill] = self._get_trades_from_session( int(self.init_time * 1e3), session=session) - if len(trades) == 0: + if not trades: self._notify("No past trades to export.") return self.placeholder_mode = True diff --git a/hummingbot/client/command/gateway_command.py b/hummingbot/client/command/gateway_command.py index 1746a35e43..68aa30f87a 100644 --- a/hummingbot/client/command/gateway_command.py +++ b/hummingbot/client/command/gateway_command.py @@ -74,15 +74,14 @@ async def _api_request(self, url = f"{base_url}/{path_url}" client = await self._http_client() if method == "get": - if len(params) > 0: + if params: response = await client.get(url, params=params) else: response = await client.get(url) elif method == "post": response = await client.post(url, data=params) - parsed_response = json.loads(await response.text()) - return parsed_response + return json.loads(await response.text()) async def _http_client(self) -> aiohttp.ClientSession: """ @@ -102,7 +101,7 @@ async def _update_gateway(self, key, value): try: config = await self.get_gateway_connections() - core_keys = [key for key in sorted(config["config"]['CORE'])] + core_keys = list(sorted(config["config"]['CORE'])) other_keys = [key for key in sorted(config["config"]) if key not in ["CORE"]] all_keys = core_keys + other_keys except Exception: @@ -174,12 +173,24 @@ async def _show_gateway_connections(self): columns = ["Parameter", " Value"] core_data = data = [[key, config['CORE'][key]] for key in sorted(config['CORE'])] core_df = pd.DataFrame(data=core_data, columns=columns) - lines = [" " + line for line in core_df.to_string(index=False, max_colwidth=50).split("\n")] + lines = [ + f" {line}" + for line in core_df.to_string( + index=False, max_colwidth=50 + ).split("\n") + ] + self._notify("\n".join(lines)) self._notify("\nOther parameters:") data = [[key, config[key]] for key in sorted(config) if key not in ['CORE']] df = pd.DataFrame(data=data, columns=columns) - lines = [" " + line for line in df.to_string(index=False, max_colwidth=50).split("\n")] + lines = [ + f" {line}" + for line in df.to_string(index=False, max_colwidth=50).split( + "\n" + ) + ] + self._notify("\n".join(lines)) else: self._notify("\nError: Invalid return result") diff --git a/hummingbot/client/command/history_command.py b/hummingbot/client/command/history_command.py index fbff4dc88c..3d8672b23e 100644 --- a/hummingbot/client/command/history_command.py +++ b/hummingbot/client/command/history_command.py @@ -69,7 +69,7 @@ async def history_report(self, # type: HummingbotApplication trades: List[TradeFill], precision: Optional[int] = None, display_report: bool = True) -> Decimal: - market_info: Set[Tuple[str, str]] = set((t.market, t.symbol) for t in trades) + market_info: Set[Tuple[str, str]] = {(t.market, t.symbol) for t in trades} if display_report: self.report_header(start_time) return_pcts = [] @@ -87,7 +87,10 @@ async def history_report(self, # type: HummingbotApplication if display_report: self.report_performance_by_market(market, symbol, perf, precision) return_pcts.append(perf.return_pct) - avg_return = sum(return_pcts) / len(return_pcts) if len(return_pcts) > 0 else s_decimal_0 + avg_return = ( + sum(return_pcts) / len(return_pcts) if return_pcts else s_decimal_0 + ) + if display_report and len(return_pcts) > 1: self._notify(f"\nAveraged Return = {avg_return:.2%}") return avg_return @@ -101,16 +104,15 @@ async def get_current_balances(self, # type: HummingbotApplication if paper_balances is None: return {} return {token: Decimal(str(bal)) for token, bal in paper_balances.items()} - elif "perpetual_finance" == market: + elif market == "perpetual_finance": return await UserBalances.xdai_balances() else: gateway_eth_connectors = [cs.name for cs in AllConnectorSettings.get_connector_settings().values() if cs.use_ethereum_wallet and cs.type == ConnectorType.Connector] if market in gateway_eth_connectors: return await UserBalances.instance().eth_n_erc20_balances() - else: - await UserBalances.instance().update_exchange_balance(market) - return UserBalances.instance().all_balances(market) + await UserBalances.instance().update_exchange_balance(market) + return UserBalances.instance().all_balances(market) def report_header(self, # type: HummingbotApplication start_time: float): @@ -128,12 +130,8 @@ def report_performance_by_market(self, # type: HummingbotApplication trading_pair: str, perf: PerformanceMetrics, precision: int): - lines = [] base, quote = trading_pair.split("-") - lines.extend( - [f"\n{market} / {trading_pair}"] - ) - + lines = list([f"\n{market} / {trading_pair}"]) trades_columns = ["", "buy", "sell", "total"] trades_data = [ [f"{'Number of trades':<27}", perf.num_buys, perf.num_sells, perf.num_trades], @@ -151,31 +149,56 @@ def report_performance_by_market(self, # type: HummingbotApplication PerformanceMetrics.smart_round(perf.avg_tot_price, precision)], ] trades_df: pd.DataFrame = pd.DataFrame(data=trades_data, columns=trades_columns) - lines.extend(["", " Trades:"] + [" " + line for line in trades_df.to_string(index=False).split("\n")]) + lines.extend( + ["", " Trades:"] + + [ + f" {line}" + for line in trades_df.to_string(index=False).split("\n") + ] + ) + assets_columns = ["", "start", "current", "change"] assets_data = [ - [f"{base:<17}", "-", "-", "-"] if market in AllConnectorSettings.get_derivative_names() else # No base asset for derivatives because they are margined - [f"{base:<17}", - PerformanceMetrics.smart_round(perf.start_base_bal, precision), - PerformanceMetrics.smart_round(perf.cur_base_bal, precision), - PerformanceMetrics.smart_round(perf.tot_vol_base, precision)], - [f"{quote:<17}", - PerformanceMetrics.smart_round(perf.start_quote_bal, precision), - PerformanceMetrics.smart_round(perf.cur_quote_bal, precision), - PerformanceMetrics.smart_round(perf.tot_vol_quote, precision)], - [f"{trading_pair + ' price':<17}", - PerformanceMetrics.smart_round(perf.start_price), - PerformanceMetrics.smart_round(perf.cur_price), - PerformanceMetrics.smart_round(perf.cur_price - perf.start_price)], - [f"{'Base asset %':<17}", "-", "-", "-"] if market in AllConnectorSettings.get_derivative_names() else # No base asset for derivatives because they are margined - [f"{'Base asset %':<17}", - f"{perf.start_base_ratio_pct:.2%}", - f"{perf.cur_base_ratio_pct:.2%}", - f"{perf.cur_base_ratio_pct - perf.start_base_ratio_pct:.2%}"], + [f"{base:<17}", "-", "-", "-"] + if market in AllConnectorSettings.get_derivative_names() + else [ # No base asset for derivatives because they are margined + f"{base:<17}", + PerformanceMetrics.smart_round(perf.start_base_bal, precision), + PerformanceMetrics.smart_round(perf.cur_base_bal, precision), + PerformanceMetrics.smart_round(perf.tot_vol_base, precision), + ], + [ + f"{quote:<17}", + PerformanceMetrics.smart_round(perf.start_quote_bal, precision), + PerformanceMetrics.smart_round(perf.cur_quote_bal, precision), + PerformanceMetrics.smart_round(perf.tot_vol_quote, precision), + ], + [ + f'{f"{trading_pair} price":<17}', + PerformanceMetrics.smart_round(perf.start_price), + PerformanceMetrics.smart_round(perf.cur_price), + PerformanceMetrics.smart_round(perf.cur_price - perf.start_price), + ], + [f"{'Base asset %':<17}", "-", "-", "-"] + if market in AllConnectorSettings.get_derivative_names() + else [ # No base asset for derivatives because they are margined + f"{'Base asset %':<17}", + f"{perf.start_base_ratio_pct:.2%}", + f"{perf.cur_base_ratio_pct:.2%}", + f"{perf.cur_base_ratio_pct - perf.start_base_ratio_pct:.2%}", + ], ] + assets_df: pd.DataFrame = pd.DataFrame(data=assets_data, columns=assets_columns) - lines.extend(["", " Assets:"] + [" " + line for line in assets_df.to_string(index=False).split("\n")]) + lines.extend( + ["", " Assets:"] + + [ + f" {line}" + for line in assets_df.to_string(index=False).split("\n") + ] + ) + perf_data = [ ["Hold portfolio value ", f"{PerformanceMetrics.smart_round(perf.hold_value, precision)} {quote}"], @@ -191,8 +214,18 @@ def report_performance_by_market(self, # type: HummingbotApplication ["Return % ", f"{perf.return_pct:.2%}"]] ) perf_df: pd.DataFrame = pd.DataFrame(data=perf_data) - lines.extend(["", " Performance:"] + - [" " + line for line in perf_df.to_string(index=False, header=False).split("\n")]) + lines.extend( + ( + ["", " Performance:"] + + [ + f" {line}" + for line in perf_df.to_string(index=False, header=False).split( + "\n" + ) + ] + ) + ) + self._notify("\n".join(lines)) @@ -234,7 +267,7 @@ def list_trades(self, # type: HummingbotApplication config_file_path=self.strategy_file_name) if self.strategy_name == "celo_arb": celo_trades = self.strategy.celo_orders_to_trade_fills() - queried_trades = queried_trades + celo_trades + queried_trades += celo_trades df: pd.DataFrame = TradeFill.to_pandas(queried_trades) if len(df) > 0: @@ -245,8 +278,7 @@ def list_trades(self, # type: HummingbotApplication f"\n Showing last {MAXIMUM_TRADE_FILLS_DISPLAY_OUTPUT} trades in the current session.") else: df_lines = str(df).split("\n") - lines.extend(["", " Recent trades:"] + - [" " + line for line in df_lines]) + lines.extend((["", " Recent trades:"] + [f" {line}" for line in df_lines])) else: lines.extend(["\n No past trades in this session."]) self._notify("\n".join(lines)) diff --git a/hummingbot/client/command/import_command.py b/hummingbot/client/command/import_command.py index 2ac6b9cde6..44c8116f6b 100644 --- a/hummingbot/client/command/import_command.py +++ b/hummingbot/client/command/import_command.py @@ -57,14 +57,13 @@ async def import_config_file(self, # type: HummingbotApplication async def prompt_a_file_name(self # type: HummingbotApplication ): - example = f"{CONF_PREFIX}{short_strategy_name('pure_market_making')}_{1}.yml" + example = f'{CONF_PREFIX}{short_strategy_name("pure_market_making")}_1.yml' file_name = await self.app.prompt(prompt=f'Enter path to your strategy file (e.g. "{example}") >>> ') if self.app.to_stop_config: return file_path = os.path.join(CONF_FILE_PATH, file_name) err_msg = validate_strategy_file(file_path) - if err_msg is not None: - self._notify(f"Error: {err_msg}") - return await self.prompt_a_file_name() - else: + if err_msg is None: return file_name + self._notify(f"Error: {err_msg}") + return await self.prompt_a_file_name() diff --git a/hummingbot/client/command/order_book_command.py b/hummingbot/client/command/order_book_command.py index c5387c0e23..fe86da9432 100644 --- a/hummingbot/client/command/order_book_command.py +++ b/hummingbot/client/command/order_book_command.py @@ -47,7 +47,10 @@ def get_order_book(lines): asks = order_book.snapshot[1][['price', 'amount']].head(lines) asks.rename(columns={'price': 'ask_price', 'amount': 'ask_volume'}, inplace=True) joined_df = pd.concat([bids, asks], axis=1) - text_lines = [" " + line for line in joined_df.to_string(index=False).split("\n")] + text_lines = [ + f" {line}" for line in joined_df.to_string(index=False).split("\n") + ] + header = f" market: {market_connector.name} {trading_pair}\n" return header + "\n".join(text_lines) diff --git a/hummingbot/client/command/silly_commands.py b/hummingbot/client/command/silly_commands.py index 39b54cc521..6b72586daa 100644 --- a/hummingbot/client/command/silly_commands.py +++ b/hummingbot/client/command/silly_commands.py @@ -82,17 +82,17 @@ async def silly_hummingbot(self, # type: HummingbotApplication ): self.placeholder_mode = True self.app.hide_input = True - for _ in range(0, 3): + for _ in range(3): await self.cls_display_delay(self.display_alert()) hb_with_flower_1 = open(f"{RESOURCES_PATH}hb_with_flower_1.txt").readlines() hb_with_flower_2 = open(f"{RESOURCES_PATH}hb_with_flower_2.txt").readlines() hb_with_flower_up_close_1 = open(f"{RESOURCES_PATH}hb_with_flower_up_close_1.txt").readlines() hb_with_flower_up_close_2 = open(f"{RESOURCES_PATH}hb_with_flower_up_close_2.txt").readlines() - for _ in range(0, 2): - for _ in range(0, 5): + for _ in range(2): + for _ in range(5): await self.cls_display_delay(hb_with_flower_1, 0.125) await self.cls_display_delay(hb_with_flower_2, 0.125) - for _ in range(0, 5): + for _ in range(5): await self.cls_display_delay(hb_with_flower_up_close_1, 0.125) await self.cls_display_delay(hb_with_flower_up_close_2, 0.125) self.placeholder_mode = False @@ -102,14 +102,14 @@ async def silly_roger(self, # type: HummingbotApplication ): self.placeholder_mode = True self.app.hide_input = True - for _ in range(0, 3): + for _ in range(3): await self.cls_display_delay(self.display_alert("roger")) roger_1 = open(f"{RESOURCES_PATH}roger_1.txt").readlines() roger_2 = open(f"{RESOURCES_PATH}roger_2.txt").readlines() roger_3 = open(f"{RESOURCES_PATH}roger_3.txt").readlines() roger_4 = open(f"{RESOURCES_PATH}roger_4.txt").readlines() - for _ in range(0, 2): - for _ in range(0, 3): + for _ in range(2): + for _ in range(3): await self.cls_display_delay(roger_1, 0.1) # await asyncio.sleep(0.3) await self.cls_display_delay(roger_2, 0.35) @@ -117,7 +117,7 @@ async def silly_roger(self, # type: HummingbotApplication await self.cls_display_delay(roger_3, 0.35) await self.cls_display_delay(roger_1, 0.25) # await asyncio.sleep(0.4) - for _ in range(0, 2): + for _ in range(2): await self.cls_display_delay(roger_4, 0.125) await self.cls_display_delay(roger_1, 0.3) await self.cls_display_delay(roger_4, 0.2) @@ -131,8 +131,8 @@ async def silly_victor(self, # type: HummingbotApplication self.app.hide_input = True hb_with_flower_1 = open(f"{RESOURCES_PATH}money-fly_1.txt").readlines() hb_with_flower_2 = open(f"{RESOURCES_PATH}money-fly_2.txt").readlines() - for _ in range(0, 5): - for _ in range(0, 5): + for _ in range(5): + for _ in range(5): await self.cls_display_delay(hb_with_flower_1, 0.125) await self.cls_display_delay(hb_with_flower_2, 0.125) await asyncio.sleep(0.3) @@ -143,12 +143,12 @@ async def silly_rein(self, # type: HummingbotApplication ): self.placeholder_mode = True self.app.hide_input = True - for _ in range(0, 2): + for _ in range(2): await self.cls_display_delay(self.display_alert("rein")) rein_1 = open(f"{RESOURCES_PATH}rein_1.txt").readlines() rein_2 = open(f"{RESOURCES_PATH}rein_2.txt").readlines() rein_3 = open(f"{RESOURCES_PATH}rein_3.txt").readlines() - for _ in range(0, 2): + for _ in range(2): await self.cls_display_delay(rein_1, 0.5) await self.cls_display_delay(rein_2, 0.5) await self.cls_display_delay(rein_3, 0.5) @@ -168,7 +168,7 @@ async def silly_dennis(self, # type: HummingbotApplication dennis_loading_2 = open(f"{RESOURCES_PATH}dennis_loading_2.txt").readlines() dennis_loading_3 = open(f"{RESOURCES_PATH}dennis_loading_3.txt").readlines() dennis_loading_4 = open(f"{RESOURCES_PATH}dennis_loading_4.txt").readlines() - for _ in range(0, 1): + for _ in range(1): await self.cls_display_delay(dennis_loading_1, 1) await self.cls_display_delay(dennis_loading_2, 1) await self.cls_display_delay(dennis_loading_3, 1) @@ -178,7 +178,7 @@ async def silly_dennis(self, # type: HummingbotApplication dennis_2 = open(f"{RESOURCES_PATH}dennis_2.txt").readlines() dennis_3 = open(f"{RESOURCES_PATH}dennis_3.txt").readlines() dennis_4 = open(f"{RESOURCES_PATH}dennis_4.txt").readlines() - for _ in range(0, 1): + for _ in range(1): await self.cls_display_delay(dennis_1, 1) await self.cls_display_delay(dennis_2, 1) await self.cls_display_delay(dennis_3, 1) diff --git a/hummingbot/client/command/start_command.py b/hummingbot/client/command/start_command.py index 39f89854b7..e998ae3cae 100644 --- a/hummingbot/client/command/start_command.py +++ b/hummingbot/client/command/start_command.py @@ -42,7 +42,7 @@ async def _run_clock(self): async def wait_till_ready(self, # type: HummingbotApplication func: Callable, *args, **kwargs): while True: - all_ready = all([market.ready for market in self.markets.values()]) + all_ready = all(market.ready for market in self.markets.values()) if not all_ready: await asyncio.sleep(0.5) else: @@ -87,7 +87,10 @@ async def start_check(self, # type: HummingbotApplication self._initialize_notifiers() self._notify(f"\nStatus check complete. Starting '{self.strategy_name}' strategy...") - if any([str(exchange).endswith("paper_trade") for exchange in settings.required_exchanges]): + if any( + str(exchange).endswith("paper_trade") + for exchange in settings.required_exchanges + ): self._notify("\nPaper Trading Active: All orders are simulated, and no real orders are placed.") for exchange in settings.required_exchanges: @@ -162,7 +165,6 @@ async def start_market_making(self, # type: HummingbotApplication async def confirm_oracle_conversion_rate(self, # type: HummingbotApplication ) -> bool: try: - result = False self.app.clear_input() self.placeholder_mode = True self.app.hide_input = True @@ -176,8 +178,7 @@ async def confirm_oracle_conversion_rate(self, # type: HummingbotApplication required_if=lambda: True, validator=lambda v: validate_bool(v)) await self.prompt_a_config(config) - if config.value: - result = True + result = bool(config.value) except OracleRateUnavailable: self._notify("Oracle rate is not available.") finally: diff --git a/hummingbot/client/command/status_command.py b/hummingbot/client/command/status_command.py index 49011a1dad..8db63c3010 100644 --- a/hummingbot/client/command/status_command.py +++ b/hummingbot/client/command/status_command.py @@ -37,16 +37,17 @@ def _expire_old_application_warnings(self, # type: HummingbotApplication def _format_application_warnings(self, # type: HummingbotApplication ) -> str: - lines: List[str] = [] if len(self._app_warnings) < 1: return "" - lines.append("\n Warnings:") - + lines: List[str] = ["\n Warnings:"] if len(self._app_warnings) < self.APP_WARNING_STATUS_LIMIT: - for app_warning in reversed(self._app_warnings): - lines.append(f" * {pd.Timestamp(app_warning.timestamp, unit='s')} - " - f"({app_warning.logger_name}) - {app_warning.warning_msg}") + lines.extend( + f" * {pd.Timestamp(app_warning.timestamp, unit='s')} - " + f"({app_warning.logger_name}) - {app_warning.warning_msg}" + for app_warning in reversed(self._app_warnings) + ) + else: module_based_warnings: OrderedDict = OrderedDict() for app_warning in reversed(self._app_warnings): @@ -72,8 +73,12 @@ def _format_application_warnings(self, # type: HummingbotApplication async def strategy_status(self, live: bool = False): active_paper_exchanges = [exchange for exchange in self.markets.keys() if exchange.endswith("paper_trade")] - paper_trade = "\n Paper Trading Active: All orders are simulated, and no real orders are placed." if len(active_paper_exchanges) > 0 \ + paper_trade = ( + "\n Paper Trading Active: All orders are simulated, and no real orders are placed." + if active_paper_exchanges else "" + ) + app_warning = self.application_warning() app_warning = "" if app_warning is None else app_warning if inspect.iscoroutinefunction(self.strategy.format_status): @@ -81,7 +86,7 @@ async def strategy_status(self, live: bool = False): else: st_status = self.strategy.format_status() status = paper_trade + "\n" + st_status + "\n" + app_warning - if self._script_iterator is not None and live is False: + if self._script_iterator is not None and not live: self._script_iterator.request_status() return status @@ -99,7 +104,10 @@ async def validate_required_connections(self) -> Dict[str, str]: err_msg = await self.validate_n_connect_celo(True) if err_msg is not None: invalid_conns["celo"] = err_msg - if not any([str(exchange).endswith("paper_trade") for exchange in required_exchanges]): + if not any( + str(exchange).endswith("paper_trade") + for exchange in required_exchanges + ): await self.update_all_secure_configs() connections = await UserBalances.instance().update_exchanges(exchanges=required_exchanges) invalid_conns.update({ex: err_msg for ex, err_msg in connections.items() @@ -127,9 +135,9 @@ async def status_check_all(self, # type: HummingbotApplication if live: await self.stop_live_update() self.app.live_updates = True + script_status = '\n Status from script would not appear here. ' \ + 'Simply run the status command without "--live" to see script status.' while self.app.live_updates and self.strategy: - script_status = '\n Status from script would not appear here. ' \ - 'Simply run the status command without "--live" to see script status.' await self.cls_display_delay( await self.strategy_status(live=True) + script_status + "\n\n Press escape key to stop update.", 1 ) @@ -172,12 +180,11 @@ async def status_check_all(self, # type: HummingbotApplication if invalid_conns or missing_configs: return False - loading_markets: List[ConnectorBase] = [] - for market in self.markets.values(): - if not market.ready: - loading_markets.append(market) + loading_markets: List[ConnectorBase] = [ + market for market in self.markets.values() if not market.ready + ] - if len(loading_markets) > 0: + if loading_markets: self._notify(" - Connectors check: Waiting for connectors " + ",".join([m.name.capitalize() for m in loading_markets]) + " to get ready for trading. \n" " Please keep the bot running and try to start again in a few minutes. \n") @@ -185,13 +192,28 @@ async def status_check_all(self, # type: HummingbotApplication for market in loading_markets: market_status_df = pd.DataFrame(data=market.status_dict.items(), columns=["description", "status"]) self._notify( - f" - {market.display_name.capitalize()} connector status:\n" + - "\n".join([" " + line for line in market_status_df.to_string(index=False,).split("\n")]) + - "\n" + ( + ( + f" - {market.display_name.capitalize()} connector status:\n" + + "\n".join( + [ + f" {line}" + for line in market_status_df.to_string( + index=False, + ).split("\n") + ] + ) + ) + + "\n" + ) ) + return False - elif not all([market.network_status is NetworkStatus.CONNECTED for market in self.markets.values()]): + elif any( + market.network_status is not NetworkStatus.CONNECTED + for market in self.markets.values() + ): offline_markets: List[str] = [ market_name for market_name, market diff --git a/hummingbot/client/config/config_helpers.py b/hummingbot/client/config/config_helpers.py index 971ecf9517..04f543e8c7 100644 --- a/hummingbot/client/config/config_helpers.py +++ b/hummingbot/client/config/config_helpers.py @@ -52,14 +52,13 @@ def parse_cvar_value(cvar: ConfigVar, value: Any) -> Any: elif cvar.type == 'str': return str(value) elif cvar.type == 'list': - if isinstance(value, str): - if len(value) == 0: - return [] - filtered: filter = filter(lambda x: x not in ['[', ']', '"', "'"], list(value)) - value = "".join(filtered).split(",") # create csv and generate list - return [s.strip() for s in value] # remove leading and trailing whitespaces - else: + if not isinstance(value, str): return value + if len(value) == 0: + return [] + filtered: filter = filter(lambda x: x not in ['[', ']', '"', "'"], list(value)) + value = "".join(filtered).split(",") # create csv and generate list + return [s.strip() for s in value] # remove leading and trailing whitespaces elif cvar.type == 'json': if isinstance(value, str): value_json = value.replace("'", '"') # replace single quotes with double quotes for valid JSON @@ -102,10 +101,7 @@ def cvar_json_migration(cvar: ConfigVar, cvar_value: Any) -> Any: and min_quote_order_amount (deprecated), they were List but change to Dict. """ if cvar.key in ("paper_trade_account_balance", "min_quote_order_amount") and isinstance(cvar_value, List): - results = {} - for item in cvar_value: - results[item[0]] = item[1] - return results + return {item[0]: item[1] for item in cvar_value} return cvar_value @@ -420,9 +416,7 @@ def load_secure_values(config_map): def format_config_file_name(file_name): - if "." not in file_name: - return file_name + ".yml" - return file_name + return f'{file_name}.yml' if "." not in file_name else file_name def parse_config_default_to_text(config: ConfigVar) -> str: diff --git a/hummingbot/client/config/config_validators.py b/hummingbot/client/config/config_validators.py index b3394b7f11..324593eea6 100644 --- a/hummingbot/client/config/config_validators.py +++ b/hummingbot/client/config/config_validators.py @@ -55,22 +55,16 @@ def validate_decimal(value: str, min_value: Decimal = None, max_value: Decimal = decimal_value = Decimal(value) except Exception: return f"{value} is not in decimal format." - if inclusive: - if min_value is not None and max_value is not None: + if min_value is not None and max_value is not None: + if inclusive: if not (Decimal(str(min_value)) <= decimal_value <= Decimal(str(max_value))): return f"Value must be between {min_value} and {max_value}." - elif min_value is not None and not decimal_value >= Decimal(str(min_value)): - return f"Value cannot be less than {min_value}." - elif max_value is not None and not decimal_value <= Decimal(str(max_value)): - return f"Value cannot be more than {max_value}." - else: - if min_value is not None and max_value is not None: - if not (Decimal(str(min_value)) < decimal_value < Decimal(str(max_value))): - return f"Value must be between {min_value} and {max_value} (exclusive)." - elif min_value is not None and not decimal_value > Decimal(str(min_value)): - return f"Value must be more than {min_value}." - elif max_value is not None and not decimal_value < Decimal(str(max_value)): - return f"Value must be less than {max_value}." + elif not (Decimal(str(min_value)) < decimal_value < Decimal(str(max_value))): + return f"Value must be between {min_value} and {max_value} (exclusive)." + elif min_value is not None and not decimal_value >= Decimal(str(min_value)): + return f"Value cannot be less than {min_value}." + elif max_value is not None and not decimal_value <= Decimal(str(max_value)): + return f"Value cannot be more than {max_value}." def validate_market_trading_pair(market: str, value: str) -> Optional[str]: @@ -105,22 +99,16 @@ def validate_int(value: str, min_value: int = None, max_value: int = None, inclu int_value = int(value) except Exception: return f"{value} is not in integer format." - if inclusive: - if min_value is not None and max_value is not None: + if min_value is not None and max_value is not None: + if inclusive: if not (min_value <= int_value <= max_value): return f"Value must be between {min_value} and {max_value}." - elif min_value is not None and not int_value >= min_value: - return f"Value cannot be less than {min_value}." - elif max_value is not None and not int_value <= max_value: - return f"Value cannot be more than {max_value}." - else: - if min_value is not None and max_value is not None: - if not (min_value < int_value < max_value): - return f"Value must be between {min_value} and {max_value} (exclusive)." - elif min_value is not None and not int_value > min_value: - return f"Value must be more than {min_value}." - elif max_value is not None and not int_value < max_value: - return f"Value must be less than {max_value}." + elif not (min_value < int_value < max_value): + return f"Value must be between {min_value} and {max_value} (exclusive)." + elif min_value is not None and int_value < min_value: + return f"Value cannot be less than {min_value}." + elif max_value is not None and int_value > max_value: + return f"Value cannot be more than {max_value}." def validate_datetime_iso_string(value: str) -> Optional[str]: diff --git a/hummingbot/client/config/config_var.py b/hummingbot/client/config/config_var.py index 6bb9f456da..4b6aa2fef5 100644 --- a/hummingbot/client/config/config_var.py +++ b/hummingbot/client/config/config_var.py @@ -69,7 +69,7 @@ async def validate(self, value: str) -> Optional[str]: """ assert callable(self._validator) assert callable(self._on_validated) - if self.required and (value is None or value == ""): + if self.required and (value is None or not value): return "Value is required." err_msg = None if inspect.iscoroutinefunction(self._validator): diff --git a/hummingbot/client/config/fee_overrides_config_map.py b/hummingbot/client/config/fee_overrides_config_map.py index e1fdeeb1d2..920779de07 100644 --- a/hummingbot/client/config/fee_overrides_config_map.py +++ b/hummingbot/client/config/fee_overrides_config_map.py @@ -6,21 +6,31 @@ def fee_overrides_dict(): all_dict = {} # all_connector_types = get_exchanges_and_derivatives() for name in AllConnectorSettings.get_connector_settings().keys(): - all_dict.update({f"{name}_percent_fee_token": new_fee_config_var(f"{name}_percent_fee_token", type_str="str")}) - all_dict.update( - {f"{name}_maker_percent_fee": new_fee_config_var(f"{name}_maker_percent_fee", type_str="decimal")} + all_dict[f"{name}_percent_fee_token"] = new_fee_config_var( + f"{name}_percent_fee_token", type_str="str" ) - all_dict.update( - {f"{name}_taker_percent_fee": new_fee_config_var(f"{name}_taker_percent_fee", type_str="decimal")} + + all_dict[f"{name}_maker_percent_fee"] = new_fee_config_var( + f"{name}_maker_percent_fee", type_str="decimal" + ) + + all_dict[f"{name}_taker_percent_fee"] = new_fee_config_var( + f"{name}_taker_percent_fee", type_str="decimal" ) + fee_application = f"{name}_buy_percent_fee_deducted_from_returns" - all_dict.update({fee_application: new_fee_config_var(fee_application, type_str="bool")}) - all_dict.update( - {f"{name}_maker_fixed_fees": new_fee_config_var(f"{name}_maker_fixed_fees", type_str="list")} + all_dict[fee_application] = new_fee_config_var( + fee_application, type_str="bool" ) - all_dict.update( - {f"{name}_taker_fixed_fees": new_fee_config_var(f"{name}_taker_fixed_fees", type_str="list")} + + all_dict[f"{name}_maker_fixed_fees"] = new_fee_config_var( + f"{name}_maker_fixed_fees", type_str="list" + ) + + all_dict[f"{name}_taker_fixed_fees"] = new_fee_config_var( + f"{name}_taker_fixed_fees", type_str="list" ) + return all_dict diff --git a/hummingbot/client/config/global_config_map.py b/hummingbot/client/config/global_config_map.py index 849cd115d3..72a1a14551 100644 --- a/hummingbot/client/config/global_config_map.py +++ b/hummingbot/client/config/global_config_map.py @@ -14,7 +14,7 @@ def generate_client_id() -> str: - vals = [random.choice(range(0, 256)) for i in range(0, 20)] + vals = [random.choice(range(256)) for _ in range(20)] return "".join([f"{val:02x}" for val in vals]) diff --git a/hummingbot/client/hummingbot_application.py b/hummingbot/client/hummingbot_application.py index 1a3b6c5324..f2f2144e7d 100644 --- a/hummingbot/client/hummingbot_application.py +++ b/hummingbot/client/hummingbot_application.py @@ -142,9 +142,7 @@ def _handle_command(self, raw_command: str): else: command_split = raw_command.split() try: - if self.placeholder_mode: - pass - else: + if not self.placeholder_mode: # Check if help is requested, if yes, print & terminate if len(command_split) > 1 and any(arg in ["-h", "--help"] for arg in command_split[1:]): self.help(command_split[0]) @@ -165,18 +163,17 @@ def _handle_command(self, raw_command: str): num_shortcut_args = len(shortcut['arguments']) if len(command_split) == num_shortcut_args + 1: # notify each expansion if there's more than 1 - verbose = True if len(shortcut['output']) > 1 else False + verbose = len(shortcut['output']) > 1 # do argument replace and re-enter this function with the expanded command for output_cmd in shortcut['output']: final_cmd = output_cmd for i in range(1, num_shortcut_args + 1): final_cmd = final_cmd.replace(f'${i}', command_split[i]) - if verbose is True: + if verbose: self._notify(f' >>> {final_cmd}') self._handle_command(final_cmd) else: self._notify('Invalid number of arguments for shortcut') - # regular command else: args = self.parser.parse_args(args=command_split) kwargs = vars(args) @@ -202,8 +199,9 @@ async def _cancel_outstanding_orders(self) -> bool: for market_name, market in self.markets.items(): cancellation_results = await market.cancel_all(kill_timeout) - uncancelled = list(filter(lambda cr: cr.success is False, cancellation_results)) - if len(uncancelled) > 0: + if uncancelled := list( + filter(lambda cr: cr.success is False, cancellation_results) + ): success = False uncancelled_order_ids = list(map(lambda cr: cr.order_id, uncancelled)) self._notify("\nFailed to cancel the following orders on %s:\n%s" % ( @@ -230,8 +228,7 @@ def clear_application_warning(self): @staticmethod def _initialize_market_assets(market_name: str, trading_pairs: List[str]) -> List[Tuple[str, str]]: - market_trading_pairs: List[Tuple[str, str]] = [(trading_pair.split('-')) for trading_pair in trading_pairs] - return market_trading_pairs + return [(trading_pair.split('-')) for trading_pair in trading_pairs] def _initialize_markets(self, market_names: List[Tuple[str, List[str]]]): # aggregate trading_pairs if there are duplicate markets @@ -275,16 +272,16 @@ def _initialize_markets(self, market_names: List[Tuple[str, List[str]]]): self.markets_recorder.start() def _initialize_notifiers(self): - if global_config_map.get("telegram_enabled").value: - # TODO: refactor to use single instance - if not any([isinstance(n, TelegramNotifier) for n in self.notifiers]): - self.notifiers.append( - TelegramNotifier( - token=global_config_map["telegram_token"].value, - chat_id=global_config_map["telegram_chat_id"].value, - hb=self, - ) + if global_config_map.get("telegram_enabled").value and not any( + isinstance(n, TelegramNotifier) for n in self.notifiers + ): + self.notifiers.append( + TelegramNotifier( + token=global_config_map["telegram_token"].value, + chat_id=global_config_map["telegram_chat_id"].value, + hb=self, ) + ) for notifier in self.notifiers: notifier.start() diff --git a/hummingbot/client/performance.py b/hummingbot/client/performance.py index 7f5e5fb0e3..3fb91d1a6a 100644 --- a/hummingbot/client/performance.py +++ b/hummingbot/client/performance.py @@ -130,11 +130,8 @@ def derivative_pnl(long: list, short: list) -> List[Decimal]: :param short: a list containing pairs of open and closed short position orders :return: A list containing PnL for each closed positions """ - pnls = [] - for lg in long: - pnls.append((lg[1].price - lg[0].price) * lg[1].amount) - for st in short: - pnls.append((st[0].price - st[1].price) * st[1].amount) + pnls = [(lg[1].price - lg[0].price) * lg[1].amount for lg in long] + pnls.extend((st[0].price - st[1].price) * st[1].amount for st in short) return pnls @staticmethod diff --git a/hummingbot/client/settings.py b/hummingbot/client/settings.py index cff7a8873d..e391e37653 100644 --- a/hummingbot/client/settings.py +++ b/hummingbot/client/settings.py @@ -87,23 +87,17 @@ def class_name(self) -> str: def conn_init_parameters(self, api_keys: Dict[str, Any]) -> Dict[str, Any]: if not self.is_sub_domain: return api_keys - else: - params = {k.replace(self.name, self.parent_name): v for k, v in api_keys.items()} - params["domain"] = self.domain_parameter - return params + params = {k.replace(self.name, self.parent_name): v for k, v in api_keys.items()} + params["domain"] = self.domain_parameter + return params def add_domain_parameter(self, params: Dict[str, Any]) -> Dict[str, Any]: - if not self.is_sub_domain: - return params - else: + if self.is_sub_domain: params["domain"] = self.domain_parameter - return params + return params def base_name(self) -> str: - if self.is_sub_domain: - return self.parent_name - else: - return self.name + return self.parent_name if self.is_sub_domain else self.name class AllConnectorSettings: diff --git a/hummingbot/client/tab/order_book_tab.py b/hummingbot/client/tab/order_book_tab.py index 3bbb75ca83..0f0335e719 100644 --- a/hummingbot/client/tab/order_book_tab.py +++ b/hummingbot/client/tab/order_book_tab.py @@ -60,7 +60,10 @@ def get_order_book_text(no_lines: int): asks = order_book.snapshot[1][['price', 'amount']].head(no_lines) asks.rename(columns={'price': 'ask_price', 'amount': 'ask_volume'}, inplace=True) joined_df = pd.concat([bids, asks], axis=1) - text_lines = ["" + line for line in joined_df.to_string(index=False).split("\n")] + text_lines = [ + f"{line}" for line in joined_df.to_string(index=False).split("\n") + ] + header = f"market: {market_connector.name} {trading_pair}\n" return header + "\n".join(text_lines) diff --git a/hummingbot/client/ui/completer.py b/hummingbot/client/ui/completer.py index 2b68eda4ea..9daced3e2c 100644 --- a/hummingbot/client/ui/completer.py +++ b/hummingbot/client/ui/completer.py @@ -68,11 +68,19 @@ def get_subcommand_completer(self, first_word: str) -> Completer: @property def _trading_pair_completer(self) -> Completer: trading_pair_fetcher = TradingPairFetcher.get_instance() - market = "" - for exchange in sorted(list(AllConnectorSettings.get_connector_settings().keys()), key=len, reverse=True): - if exchange in self.prompt_text: - market = exchange - break + market = next( + ( + exchange + for exchange in sorted( + list(AllConnectorSettings.get_connector_settings().keys()), + key=len, + reverse=True, + ) + if exchange in self.prompt_text + ), + "", + ) + trading_pairs = trading_pair_fetcher.trading_pairs.get(market, []) if trading_pair_fetcher.ready and market else [] return WordCompleter(trading_pairs, ignore_case=True, sentence=True) @@ -160,7 +168,7 @@ def _complete_command(self, document: Document) -> bool: def _complete_subcommand(self, document: Document) -> bool: text_before_cursor: str = document.text_before_cursor index: int = text_before_cursor.index(' ') - return text_before_cursor[0:index] in self.parser.commands + return text_before_cursor[:index] in self.parser.commands def _complete_balance_limit_exchanges(self, document: Document): text_before_cursor: str = document.text_before_cursor @@ -177,99 +185,82 @@ def get_completions(self, document: Document, complete_event: CompleteEvent): :param complete_event: """ if self._complete_script_files(document): - for c in self._py_file_completer.get_completions(document, complete_event): - yield c - + yield from self._py_file_completer.get_completions(document, complete_event) elif self._complete_paths(document): - for c in self._path_completer.get_completions(document, complete_event): - yield c - + yield from self._path_completer.get_completions(document, complete_event) elif self._complete_strategies(document): - for c in self._strategy_completer.get_completions(document, complete_event): - yield c - + yield from self._strategy_completer.get_completions(document, complete_event) elif self._complete_wallet_addresses(document): - for c in self._wallet_address_completer.get_completions(document, complete_event): - yield c + yield from self._wallet_address_completer.get_completions( + document, complete_event + ) elif self._complete_spot_connectors(document): if "(Exchange/AMM)" in self.prompt_text: - for c in self._spot_completer.get_completions(document, complete_event): - yield c + yield from self._spot_completer.get_completions(document, complete_event) else: - for c in self._spot_exchange_completer.get_completions(document, complete_event): - yield c + yield from self._spot_exchange_completer.get_completions( + document, complete_event + ) elif self._complete_trading_timeframe(document): - for c in self._trading_timeframe_completer.get_completions(document, complete_event): - yield c + yield from self._trading_timeframe_completer.get_completions( + document, complete_event + ) elif self._complete_connect_options(document): - for c in self._connect_option_completer.get_completions(document, complete_event): - yield c + yield from self._connect_option_completer.get_completions( + document, complete_event + ) elif self._complete_export_options(document): - for c in self._export_completer.get_completions(document, complete_event): - yield c - + yield from self._export_completer.get_completions(document, complete_event) elif self._complete_balance_limit_exchanges(document): - for c in self._connect_option_completer.get_completions(document, complete_event): - yield c + yield from self._connect_option_completer.get_completions( + document, complete_event + ) elif self._complete_balance_options(document): - for c in self._balance_completer.get_completions(document, complete_event): - yield c - + yield from self._balance_completer.get_completions(document, complete_event) elif self._complete_history_arguments(document): - for c in self._history_completer.get_completions(document, complete_event): - yield c - + yield from self._history_completer.get_completions(document, complete_event) elif self._complete_gateway_arguments(document): - for c in self._gateway_completer.get_completions(document, complete_event): - yield c - + yield from self._gateway_completer.get_completions(document, complete_event) elif self._complete_derivatives(document): if "(Exchange/AMM)" in self.prompt_text: - for c in self._derivative_completer.get_completions(document, complete_event): - yield c + yield from self._derivative_completer.get_completions(document, complete_event) else: - for c in self._derivative_exchange_completer.get_completions(document, complete_event): - yield c + yield from self._derivative_exchange_completer.get_completions( + document, complete_event + ) elif self._complete_exchanges(document): - for c in self._exchange_completer.get_completions(document, complete_event): - yield c - + yield from self._exchange_completer.get_completions(document, complete_event) elif self._complete_trading_pairs(document): - for c in self._trading_pair_completer.get_completions(document, complete_event): - yield c + yield from self._trading_pair_completer.get_completions( + document, complete_event + ) elif self._complete_command(document): - for c in self._command_completer.get_completions(document, complete_event): - yield c - + yield from self._command_completer.get_completions(document, complete_event) elif self._complete_configs(document): - for c in self._config_completer.get_completions(document, complete_event): - yield c - + yield from self._config_completer.get_completions(document, complete_event) elif self._complete_options(document): - for c in self._option_completer.get_completions(document, complete_event): - yield c - + yield from self._option_completer.get_completions(document, complete_event) elif self._complete_rate_oracle_source(document): - for c in self._rate_oracle_completer.get_completions(document, complete_event): - yield c + yield from self._rate_oracle_completer.get_completions( + document, complete_event + ) else: text_before_cursor: str = document.text_before_cursor try: - first_word: str = text_before_cursor[0:text_before_cursor.index(' ')] + first_word: str = text_before_cursor[:text_before_cursor.index(' ')] except ValueError: return subcommand_completer: Completer = self.get_subcommand_completer(first_word) if complete_event.completion_requested or self._complete_subcommand(document): - for c in subcommand_completer.get_completions(document, complete_event): - yield c + yield from subcommand_completer.get_completions(document, complete_event) def load_completer(hummingbot_application): diff --git a/hummingbot/client/ui/custom_widgets.py b/hummingbot/client/ui/custom_widgets.py index 72d3e08fbc..5f0d603d3a 100644 --- a/hummingbot/client/ui/custom_widgets.py +++ b/hummingbot/client/ui/custom_widgets.py @@ -31,10 +31,7 @@ class CustomBuffer(Buffer): def validate_and_handle(self): valid = self.validate(set_cursor=True) if valid: - if self.accept_handler: - keep_text = self.accept_handler(self) - else: - keep_text = False + keep_text = self.accept_handler(self) if self.accept_handler else False if not keep_text: self.reset() @@ -45,7 +42,7 @@ class FormattedTextLexer(Lexer): def __init__(self) -> None: super().__init__() - self.html_tag_css_style_map: Dict[str, str] = {style: css for style, css in load_style().style_rules} + self.html_tag_css_style_map: Dict[str, str] = dict(load_style().style_rules) self.html_tag_css_style_map.update({ style: config.value for style, config in color_config_map.items() @@ -74,7 +71,7 @@ def get_line(lineno: int) -> StyleAndTextTuples: for special_word, style in self.text_style_tag_map.items() for match in list(re.finditer(special_word, current_line)) ] - if len(matched_indexes) == 0: + if not matched_indexes: return [("", current_line)] previous_idx = 0 @@ -154,19 +151,13 @@ def __init__(self, text='', multiline=True, password=False, focus_on_click=focus_on_click) if multiline: - if scrollbar: - right_margins = [ScrollbarMargin(display_arrows=True)] - else: - right_margins = [] - if line_numbers: - left_margins = [NumberedMargin()] - else: - left_margins = [] + right_margins = [ScrollbarMargin(display_arrows=True)] if scrollbar else [] + left_margins = [NumberedMargin()] if line_numbers else [] else: left_margins = [] right_margins = [] - style = 'class:text-area ' + style + style = f'class:text-area {style}' self.window = Window( height=height, @@ -238,7 +229,7 @@ def log(self, text: str, save_log: bool = True, silent: bool = False): new_lines = [] for line in new_lines_raw: while len(line) > max_width: - new_lines.append(line[0:max_width]) + new_lines.append(line[:max_width]) line = line[max_width:] new_lines.append(line) diff --git a/hummingbot/client/ui/hummingbot_cli.py b/hummingbot/client/ui/hummingbot_cli.py index f6f5d4a2af..abeb5e45fd 100644 --- a/hummingbot/client/ui/hummingbot_cli.py +++ b/hummingbot/client/ui/hummingbot_cli.py @@ -196,8 +196,7 @@ def tab_navigate_right(self): current_tabs = [t for t in self.command_tabs.values() if t.tab_index > 0] if not current_tabs: return - selected_tab = [t for t in current_tabs if t.is_selected] - if selected_tab: + if selected_tab := [t for t in current_tabs if t.is_selected]: right_tab = [t for t in current_tabs if t.tab_index == selected_tab[0].tab_index + 1] else: right_tab = [t for t in current_tabs if t.tab_index == 1] diff --git a/hummingbot/client/ui/interface_utils.py b/hummingbot/client/ui/interface_utils.py index 7976116c8c..41fbf9b2a2 100644 --- a/hummingbot/client/ui/interface_utils.py +++ b/hummingbot/client/ui/interface_utils.py @@ -61,31 +61,39 @@ async def start_trade_monitor(trade_monitor): while True: try: - if hb.strategy_task is not None and not hb.strategy_task.done(): - if all(market.ready for market in hb.markets.values()): - with hb.trade_fill_db.get_new_session() as session: - trades: List[TradeFill] = hb._get_trades_from_session( - int(hb.init_time * 1e3), - session=session, - config_file_path=hb.strategy_file_name) - if len(trades) > 0: - market_info: Set[Tuple[str, str]] = set((t.market, t.symbol) for t in trades) - for market, symbol in market_info: - cur_trades = [t for t in trades if t.market == market and t.symbol == symbol] - cur_balances = await hb.get_current_balances(market) - perf = await PerformanceMetrics.create(market, symbol, cur_trades, cur_balances) - return_pcts.append(perf.return_pct) - pnls.append(perf.total_pnl) - avg_return = sum(return_pcts) / len(return_pcts) if len(return_pcts) > 0 else s_decimal_0 - quote_assets = set(t.symbol.split("-")[1] for t in trades) - if len(quote_assets) == 1: - total_pnls = f"{PerformanceMetrics.smart_round(sum(pnls))} {list(quote_assets)[0]}" - else: - total_pnls = "N/A" - trade_monitor.log(f"Trades: {len(trades)}, Total P&L: {total_pnls}, " - f"Return %: {avg_return:.2%}") - return_pcts.clear() - pnls.clear() + if ( + hb.strategy_task is not None + and not hb.strategy_task.done() + and all(market.ready for market in hb.markets.values()) + ): + with hb.trade_fill_db.get_new_session() as session: + trades: List[TradeFill] = hb._get_trades_from_session( + int(hb.init_time * 1e3), + session=session, + config_file_path=hb.strategy_file_name) + if trades: + market_info: Set[Tuple[str, str]] = {(t.market, t.symbol) for t in trades} + for market, symbol in market_info: + cur_trades = [t for t in trades if t.market == market and t.symbol == symbol] + cur_balances = await hb.get_current_balances(market) + perf = await PerformanceMetrics.create(market, symbol, cur_trades, cur_balances) + return_pcts.append(perf.return_pct) + pnls.append(perf.total_pnl) + avg_return = ( + sum(return_pcts) / len(return_pcts) + if return_pcts + else s_decimal_0 + ) + + quote_assets = {t.symbol.split("-")[1] for t in trades} + if len(quote_assets) == 1: + total_pnls = f"{PerformanceMetrics.smart_round(sum(pnls))} {list(quote_assets)[0]}" + else: + total_pnls = "N/A" + trade_monitor.log(f"Trades: {len(trades)}, Total P&L: {total_pnls}, " + f"Return %: {avg_return:.2%}") + return_pcts.clear() + pnls.clear() await _sleep(2) # sleeping for longer to manage resources except asyncio.CancelledError: raise diff --git a/hummingbot/client/ui/layout.py b/hummingbot/client/ui/layout.py index 3ae7aa1bc6..b86fb907c4 100644 --- a/hummingbot/client/ui/layout.py +++ b/hummingbot/client/ui/layout.py @@ -236,9 +236,13 @@ def generate_layout(input_field: TextArea, trade_monitor: TextArea, command_tabs: Dict[str, CommandTab], ): - components = {} + components = { + "item_top_version": Window( + FormattedTextControl(get_version), style="class:header" + ) + } + - components["item_top_version"] = Window(FormattedTextControl(get_version), style="class:header") components["item_top_active"] = Window(FormattedTextControl(get_active_strategy), style="class:header") components["item_top_file"] = Window(FormattedTextControl(get_strategy_file), style="class:header") components["item_top_toggle"] = right_pane_toggle @@ -266,8 +270,9 @@ def generate_layout(input_field: TextArea, tab.close_button.window.style = tab.button.window.style tab_buttons.append(VSplit([tab.button, tab.close_button])) pane_right_field = log_field - focused_right_field = [tab.output_field for tab in command_tabs.values() if tab.is_selected] - if focused_right_field: + if focused_right_field := [ + tab.output_field for tab in command_tabs.values() if tab.is_selected + ]: pane_right_field = focused_right_field[0] components["pane_right_top"] = VSplit(tab_buttons, height=1, style="class:log-field", padding_char=" ", padding=2) components["pane_right"] = ConditionalContainer( diff --git a/hummingbot/client/ui/parser.py b/hummingbot/client/ui/parser.py index 24587f0aa0..508a65ac6a 100644 --- a/hummingbot/client/ui/parser.py +++ b/hummingbot/client/ui/parser.py @@ -33,8 +33,7 @@ def subcommands_from(self, top_level_command: str) -> List[str]: if parser is None: return [] subcommands = parser._optionals._option_string_actions.keys() - filtered = list(filter(lambda sub: sub.startswith("--"), subcommands)) - return filtered + return list(filter(lambda sub: sub.startswith("--"), subcommands)) def load_parser(hummingbot, command_tabs) -> [ThrowingArgumentParser, Any]: diff --git a/hummingbot/client/ui/style.py b/hummingbot/client/ui/style.py index c83e7b91fc..18c58bd701 100644 --- a/hummingbot/client/ui/style.py +++ b/hummingbot/client/ui/style.py @@ -48,48 +48,52 @@ def load_style(config_map=global_config_map): color_error_label = hex_to_ansi(color_error_label) # Apply custom configuration - style["output-field"] = "bg:" + color_output_pane + " " + color_terminal_primary - style["input-field"] = "bg:" + color_input_pane + " " + style["input-field"].split(' ')[-1] - style["log-field"] = "bg:" + color_logs_pane + " " + style["log-field"].split(' ')[-1] - style["tab_button.focused"] = "bg:" + color_terminal_primary + " " + color_logs_pane + style["output-field"] = f"bg:{color_output_pane} {color_terminal_primary}" + style["input-field"] = ( + f"bg:{color_input_pane} " + style["input-field"].split(' ')[-1] + ) + + style["log-field"] = ( + f"bg:{color_logs_pane} " + style["log-field"].split(' ')[-1] + ) + + style["tab_button.focused"] = f"bg:{color_terminal_primary} {color_logs_pane}" style["tab_button"] = style["tab_button"].split(' ')[0] + " " + color_logs_pane - style["header"] = "bg:" + color_top_pane + " " + style["header"].split(' ')[-1] - style["footer"] = "bg:" + color_bottom_pane + " " + style["footer"].split(' ')[-1] + style["header"] = f"bg:{color_top_pane} " + style["header"].split(' ')[-1] + style["footer"] = f"bg:{color_bottom_pane} " + style["footer"].split(' ')[-1] style["primary"] = color_terminal_primary style["search"] = color_terminal_primary style["search.current"] = color_terminal_primary - style["primary-label"] = "bg:" + color_primary_label + " " + color_output_pane - style["secondary-label"] = "bg:" + color_secondary_label + " " + color_output_pane - style["success-label"] = "bg:" + color_success_label + " " + color_output_pane - style["warning-label"] = "bg:" + color_warning_label + " " + color_output_pane - style["info-label"] = "bg:" + color_info_label + " " + color_output_pane - style["error-label"] = "bg:" + color_error_label + " " + color_output_pane - - return Style.from_dict(style) - else: # Load default style style = default_ui_style # Apply custom configuration - style["output-field"] = "bg:" + color_output_pane + " " + color_terminal_primary - style["input-field"] = "bg:" + color_input_pane + " " + style["input-field"].split(' ')[-1] - style["log-field"] = "bg:" + color_logs_pane + " " + style["log-field"].split(' ')[-1] - style["header"] = "bg:" + color_top_pane + " " + style["header"].split(' ')[-1] - style["footer"] = "bg:" + color_bottom_pane + " " + style["footer"].split(' ')[-1] + style["output-field"] = f"bg:{color_output_pane} {color_terminal_primary}" + style["input-field"] = ( + f"bg:{color_input_pane} " + style["input-field"].split(' ')[-1] + ) + + style["log-field"] = ( + f"bg:{color_logs_pane} " + style["log-field"].split(' ')[-1] + ) + + style["header"] = f"bg:{color_top_pane} " + style["header"].split(' ')[-1] + style["footer"] = f"bg:{color_bottom_pane} " + style["footer"].split(' ')[-1] style["primary"] = color_terminal_primary - style["tab_button.focused"] = "bg:" + color_terminal_primary + " " + color_logs_pane + style["tab_button.focused"] = f"bg:{color_terminal_primary} {color_logs_pane}" style["tab_button"] = style["tab_button"].split(' ')[0] + " " + color_logs_pane - style["primary-label"] = "bg:" + color_primary_label + " " + color_output_pane - style["secondary-label"] = "bg:" + color_secondary_label + " " + color_output_pane - style["success-label"] = "bg:" + color_success_label + " " + color_output_pane - style["warning-label"] = "bg:" + color_warning_label + " " + color_output_pane - style["info-label"] = "bg:" + color_info_label + " " + color_output_pane - style["error-label"] = "bg:" + color_error_label + " " + color_output_pane - return Style.from_dict(style) + style["primary-label"] = f"bg:{color_primary_label} {color_output_pane}" + style["secondary-label"] = f"bg:{color_secondary_label} {color_output_pane}" + style["success-label"] = f"bg:{color_success_label} {color_output_pane}" + style["warning-label"] = f"bg:{color_warning_label} {color_output_pane}" + style["info-label"] = f"bg:{color_info_label} {color_output_pane}" + style["error-label"] = f"bg:{color_error_label} {color_output_pane}" + + return Style.from_dict(style) def reset_style(config_map=global_config_map, save=True): @@ -132,14 +136,14 @@ def hex_to_ansi(color_hex): color_hex = color_hex.replace('#', '') # Calculate distance, choose the closest ANSI color - hex_r = int(color_hex[0:2], 16) + hex_r = int(color_hex[:2], 16) hex_g = int(color_hex[2:4], 16) hex_b = int(color_hex[4:6], 16) distance_min = None - for ansi_hex in ansi_palette: - ansi_r = int(ansi_hex[0:2], 16) + for ansi_hex, color_ansi in ansi_palette.items(): + ansi_r = int(ansi_hex[:2], 16) ansi_g = int(ansi_hex[2:4], 16) ansi_b = int(ansi_hex[4:6], 16) @@ -147,9 +151,7 @@ def hex_to_ansi(color_hex): if distance_min is None or distance < distance_min: distance_min = distance - color_ansi = ansi_palette[ansi_hex] - - return "#" + color_ansi + return f"#{color_ansi}" text_ui_style = { diff --git a/hummingbot/connector/client_order_tracker.py b/hummingbot/connector/client_order_tracker.py index 2abe3a2dd1..4e9664fa4c 100644 --- a/hummingbot/connector/client_order_tracker.py +++ b/hummingbot/connector/client_order_tracker.py @@ -76,7 +76,7 @@ def cached_orders(self) -> Dict[str, InFlightOrder]: """ Returns orders that are no longer actively tracked. """ - return {client_order_id: order for client_order_id, order in self._cached_orders.items()} + return dict(self._cached_orders.items()) @property def all_orders(self) -> Dict[str, InFlightOrder]: @@ -122,10 +122,14 @@ def fetch_order( if client_order_id in self.all_orders: return self.all_orders[client_order_id] - for order in self.all_orders.values(): - if order.exchange_order_id == exchange_order_id: - return order - return None + return next( + ( + order + for order in self.all_orders.values() + if order.exchange_order_id == exchange_order_id + ), + None, + ) def _trigger_created_event(self, order: InFlightOrder): event_tag = MarketEvent.BuyOrderCreated if order.trade_type is TradeType.BUY else MarketEvent.SellOrderCreated @@ -290,8 +294,11 @@ def process_order_not_found(self, client_order_id: str): self._order_not_found_records[client_order_id] += 1 - if self._order_not_found_records[client_order_id] > self.ORDER_NOT_FOUND_COUNT_LIMIT: - if not tracked_order.is_done: - tracked_order.current_state = OrderState.FAILED - self.stop_tracking_order(client_order_id=client_order_id) - self._trigger_failure_event(tracked_order) + if ( + self._order_not_found_records[client_order_id] + > self.ORDER_NOT_FOUND_COUNT_LIMIT + and not tracked_order.is_done + ): + tracked_order.current_state = OrderState.FAILED + self.stop_tracking_order(client_order_id=client_order_id) + self._trigger_failure_event(tracked_order) diff --git a/hummingbot/connector/connector/balancer/balancer_connector.py b/hummingbot/connector/connector/balancer/balancer_connector.py index cff658f27a..1bf57467b9 100644 --- a/hummingbot/connector/connector/balancer/balancer_connector.py +++ b/hummingbot/connector/connector/balancer/balancer_connector.py @@ -114,8 +114,8 @@ async def initiate_pool(self) -> str: self.logger().info(f"Initializing Balancer connector and caching pools for {self._trading_pairs}.") resp = await self._api_request("get", "eth/balancer/start", {"pairs": json.dumps(self._trading_pairs)}) - status = bool(str(resp["success"])) if bool(str(resp["success"])): + status = bool(str(resp["success"])) self._initiate_pool_status = status except asyncio.CancelledError: raise @@ -163,13 +163,13 @@ async def get_allowances(self) -> Dict[str, Decimal]: Retrieves allowances for token in trading_pairs :return: A dictionary of token and its allowance (how much Balancer can spend). """ - ret_val = {} resp = await self._api_request("post", "eth/allowances", {"tokenList": "[" + (",".join(['"' + t + '"' for t in self._tokens])) + "]", "connector": self.name}) - for token, amount in resp["approvals"].items(): - ret_val[token] = Decimal(str(amount)) - return ret_val + return { + token: Decimal(str(amount)) + for token, amount in resp["approvals"].items() + } @async_ttl_cache(ttl=5, maxsize=10) async def get_quote_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Optional[Decimal]: @@ -377,73 +377,74 @@ async def _update_order_status(self): """ Calls REST API to get status update for each in-flight order. """ - if len(self._in_flight_orders) > 0: - tracked_orders = list(self._in_flight_orders.values()) - - tasks = [] - for tracked_order in tracked_orders: - order_id = await tracked_order.get_exchange_order_id() - tasks.append(self._api_request("post", - "eth/poll", - {"txHash": order_id})) - update_results = await safe_gather(*tasks, return_exceptions=True) - for update_result in update_results: - self.logger().info(f"Polling for order status updates of {len(tasks)} orders.") - if isinstance(update_result, Exception): - raise update_result - if "txHash" not in update_result: - self.logger().info(f"_update_order_status txHash not in resp: {update_result}") - continue - if update_result["confirmed"] is True: - if update_result["receipt"]["status"] == 1: - gas_used = update_result["receipt"]["gasUsed"] - gas_price = tracked_order.gas_price - fee = Decimal(str(gas_used)) * Decimal(str(gas_price)) / Decimal(str(1e9)) - self.trigger_event( - MarketEvent.OrderFilled, - OrderFilledEvent( - self.current_timestamp, - tracked_order.client_order_id, - tracked_order.trading_pair, - tracked_order.trade_type, - tracked_order.order_type, - Decimal(str(tracked_order.price)), - Decimal(str(tracked_order.amount)), - AddedToCostTradeFee( - flat_fees=[TokenAmount(tracked_order.fee_asset, Decimal(str(fee)))] - ), - exchange_trade_id=order_id - ) + if len(self._in_flight_orders) <= 0: + return + tracked_orders = list(self._in_flight_orders.values()) + + tasks = [] + for tracked_order in tracked_orders: + order_id = await tracked_order.get_exchange_order_id() + tasks.append(self._api_request("post", + "eth/poll", + {"txHash": order_id})) + update_results = await safe_gather(*tasks, return_exceptions=True) + for update_result in update_results: + self.logger().info(f"Polling for order status updates of {len(tasks)} orders.") + if isinstance(update_result, Exception): + raise update_result + if "txHash" not in update_result: + self.logger().info(f"_update_order_status txHash not in resp: {update_result}") + continue + if update_result["confirmed"] is True: + if update_result["receipt"]["status"] == 1: + gas_used = update_result["receipt"]["gasUsed"] + gas_price = tracked_order.gas_price + fee = Decimal(str(gas_used)) * Decimal(str(gas_price)) / Decimal(str(1e9)) + self.trigger_event( + MarketEvent.OrderFilled, + OrderFilledEvent( + self.current_timestamp, + tracked_order.client_order_id, + tracked_order.trading_pair, + tracked_order.trade_type, + tracked_order.order_type, + Decimal(str(tracked_order.price)), + Decimal(str(tracked_order.amount)), + AddedToCostTradeFee( + flat_fees=[TokenAmount(tracked_order.fee_asset, Decimal(str(fee)))] + ), + exchange_trade_id=order_id ) - tracked_order.last_state = "FILLED" - self.logger().info(f"The {tracked_order.trade_type.name} order " - f"{tracked_order.client_order_id} has completed " - f"according to order status API.") - event_tag = MarketEvent.BuyOrderCompleted if tracked_order.trade_type is TradeType.BUY \ - else MarketEvent.SellOrderCompleted - event_class = BuyOrderCompletedEvent if tracked_order.trade_type is TradeType.BUY \ - else SellOrderCompletedEvent - self.trigger_event(event_tag, - event_class(self.current_timestamp, - tracked_order.client_order_id, - tracked_order.base_asset, - tracked_order.quote_asset, - tracked_order.fee_asset, - tracked_order.executed_amount_base, - tracked_order.executed_amount_quote, - float(fee), - tracked_order.order_type)) - self.stop_tracking_order(tracked_order.client_order_id) - else: - self.logger().info( - f"The market order {tracked_order.client_order_id} has failed according to order status API. ") - self.trigger_event(MarketEvent.OrderFailure, - MarketOrderFailureEvent( - self.current_timestamp, - tracked_order.client_order_id, - tracked_order.order_type - )) - self.stop_tracking_order(tracked_order.client_order_id) + ) + tracked_order.last_state = "FILLED" + self.logger().info(f"The {tracked_order.trade_type.name} order " + f"{tracked_order.client_order_id} has completed " + f"according to order status API.") + event_tag = MarketEvent.BuyOrderCompleted if tracked_order.trade_type is TradeType.BUY \ + else MarketEvent.SellOrderCompleted + event_class = BuyOrderCompletedEvent if tracked_order.trade_type is TradeType.BUY \ + else SellOrderCompletedEvent + self.trigger_event(event_tag, + event_class(self.current_timestamp, + tracked_order.client_order_id, + tracked_order.base_asset, + tracked_order.quote_asset, + tracked_order.fee_asset, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + float(fee), + tracked_order.order_type)) + else: + self.logger().info( + f"The market order {tracked_order.client_order_id} has failed according to order status API. ") + self.trigger_event(MarketEvent.OrderFailure, + MarketOrderFailureEvent( + self.current_timestamp, + tracked_order.client_order_id, + tracked_order.order_type + )) + + self.stop_tracking_order(tracked_order.client_order_id) def get_taker_order_type(self): return OrderType.LIMIT @@ -505,9 +506,12 @@ def tick(self, timestamp: float): Is called automatically by the clock for each clock's tick (1 second by default). It checks if status polling task is due for execution. """ - if time.time() - self._last_poll_timestamp > self.POLL_INTERVAL: - if self._poll_notifier is not None and not self._poll_notifier.is_set(): - self._poll_notifier.set() + if ( + time.time() - self._last_poll_timestamp > self.POLL_INTERVAL + and self._poll_notifier is not None + and not self._poll_notifier.is_set() + ): + self._poll_notifier.set() async def _status_polling_loop(self): while True: diff --git a/hummingbot/connector/connector/terra/terra_connector.py b/hummingbot/connector/connector/terra/terra_connector.py index c426fa4178..d83f781671 100644 --- a/hummingbot/connector/connector/terra/terra_connector.py +++ b/hummingbot/connector/connector/terra/terra_connector.py @@ -330,9 +330,12 @@ def tick(self, timestamp: float): Is called automatically by the clock for each clock's tick (1 second by default). It checks if status polling task is due for execution. """ - if time.time() - self._last_poll_timestamp > self.POLL_INTERVAL: - if self._poll_notifier is not None and not self._poll_notifier.is_set(): - self._poll_notifier.set() + if ( + time.time() - self._last_poll_timestamp > self.POLL_INTERVAL + and self._poll_notifier is not None + and not self._poll_notifier.is_set() + ): + self._poll_notifier.set() async def _status_polling_loop(self): while True: diff --git a/hummingbot/connector/connector/uniswap/uniswap_connector.py b/hummingbot/connector/connector/uniswap/uniswap_connector.py index c96fe5ce51..0b143b6dec 100644 --- a/hummingbot/connector/connector/uniswap/uniswap_connector.py +++ b/hummingbot/connector/connector/uniswap/uniswap_connector.py @@ -116,8 +116,7 @@ async def initiate_pool(self) -> str: # self.logger().info(f"Initializing Uniswap connector and paths for {self._trading_pairs} pairs.") resp = await self._api_request("get", "eth/uniswap/start", {"pairs": json.dumps(self._trading_pairs)}) - status = bool(str(resp["success"])) - if status: + if status := bool(str(resp["success"])): self._initiate_pool_status = status await asyncio.sleep(60) except asyncio.CancelledError: @@ -166,13 +165,13 @@ async def get_allowances(self) -> Dict[str, Decimal]: Retrieves allowances for token in trading_pairs :return: A dictionary of token and its allowance (how much Uniswap can spend). """ - ret_val = {} resp = await self._api_request("post", "eth/allowances", {"tokenList": "[" + (",".join(['"' + t + '"' for t in self._tokens])) + "]", "connector": self.name}) - for token, amount in resp["approvals"].items(): - ret_val[token] = Decimal(str(amount)) - return ret_val + return { + token: Decimal(str(amount)) + for token, amount in resp["approvals"].items() + } @async_ttl_cache(ttl=5, maxsize=10) async def get_quote_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Optional[Decimal]: @@ -375,73 +374,74 @@ async def _update_order_status(self): """ Calls REST API to get status update for each in-flight order. """ - if len(self._in_flight_orders) > 0: - tracked_orders = list(self._in_flight_orders.values()) - - tasks = [] - for tracked_order in tracked_orders: - order_id = await tracked_order.get_exchange_order_id() - tasks.append(self._api_request("post", - "eth/poll", - {"txHash": order_id})) - update_results = await safe_gather(*tasks, return_exceptions=True) - for update_result in update_results: - self.logger().info(f"Polling for order status updates of {len(tasks)} orders.") - if isinstance(update_result, Exception): - raise update_result - if "txHash" not in update_result: - self.logger().info(f"_update_order_status txHash not in resp: {update_result}") - continue - if update_result["confirmed"] is True: - if update_result["receipt"]["status"] == 1: - gas_used = update_result["receipt"]["gasUsed"] - gas_price = tracked_order.gas_price - fee = Decimal(str(gas_used)) * Decimal(str(gas_price)) / Decimal(str(1e9)) - self.trigger_event( - MarketEvent.OrderFilled, - OrderFilledEvent( - self.current_timestamp, - tracked_order.client_order_id, - tracked_order.trading_pair, - tracked_order.trade_type, - tracked_order.order_type, - Decimal(str(tracked_order.price)), - Decimal(str(tracked_order.amount)), - AddedToCostTradeFee( - flat_fees=[TokenAmount(tracked_order.fee_asset, Decimal(str(fee)))] - ), - exchange_trade_id=order_id - ) + if len(self._in_flight_orders) <= 0: + return + tracked_orders = list(self._in_flight_orders.values()) + + tasks = [] + for tracked_order in tracked_orders: + order_id = await tracked_order.get_exchange_order_id() + tasks.append(self._api_request("post", + "eth/poll", + {"txHash": order_id})) + update_results = await safe_gather(*tasks, return_exceptions=True) + for update_result in update_results: + self.logger().info(f"Polling for order status updates of {len(tasks)} orders.") + if isinstance(update_result, Exception): + raise update_result + if "txHash" not in update_result: + self.logger().info(f"_update_order_status txHash not in resp: {update_result}") + continue + if update_result["confirmed"] is True: + if update_result["receipt"]["status"] == 1: + gas_used = update_result["receipt"]["gasUsed"] + gas_price = tracked_order.gas_price + fee = Decimal(str(gas_used)) * Decimal(str(gas_price)) / Decimal(str(1e9)) + self.trigger_event( + MarketEvent.OrderFilled, + OrderFilledEvent( + self.current_timestamp, + tracked_order.client_order_id, + tracked_order.trading_pair, + tracked_order.trade_type, + tracked_order.order_type, + Decimal(str(tracked_order.price)), + Decimal(str(tracked_order.amount)), + AddedToCostTradeFee( + flat_fees=[TokenAmount(tracked_order.fee_asset, Decimal(str(fee)))] + ), + exchange_trade_id=order_id ) - tracked_order.last_state = "FILLED" - self.logger().info(f"The {tracked_order.trade_type.name} order " - f"{tracked_order.client_order_id} has completed " - f"according to order status API.") - event_tag = MarketEvent.BuyOrderCompleted if tracked_order.trade_type is TradeType.BUY \ - else MarketEvent.SellOrderCompleted - event_class = BuyOrderCompletedEvent if tracked_order.trade_type is TradeType.BUY \ - else SellOrderCompletedEvent - self.trigger_event(event_tag, - event_class(self.current_timestamp, - tracked_order.client_order_id, - tracked_order.base_asset, - tracked_order.quote_asset, - tracked_order.fee_asset, - tracked_order.executed_amount_base, - tracked_order.executed_amount_quote, - float(fee), - tracked_order.order_type)) - self.stop_tracking_order(tracked_order.client_order_id) - else: - self.logger().info( - f"The market order {tracked_order.client_order_id} has failed according to order status API. ") - self.trigger_event(MarketEvent.OrderFailure, - MarketOrderFailureEvent( - self.current_timestamp, - tracked_order.client_order_id, - tracked_order.order_type - )) - self.stop_tracking_order(tracked_order.client_order_id) + ) + tracked_order.last_state = "FILLED" + self.logger().info(f"The {tracked_order.trade_type.name} order " + f"{tracked_order.client_order_id} has completed " + f"according to order status API.") + event_tag = MarketEvent.BuyOrderCompleted if tracked_order.trade_type is TradeType.BUY \ + else MarketEvent.SellOrderCompleted + event_class = BuyOrderCompletedEvent if tracked_order.trade_type is TradeType.BUY \ + else SellOrderCompletedEvent + self.trigger_event(event_tag, + event_class(self.current_timestamp, + tracked_order.client_order_id, + tracked_order.base_asset, + tracked_order.quote_asset, + tracked_order.fee_asset, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + float(fee), + tracked_order.order_type)) + else: + self.logger().info( + f"The market order {tracked_order.client_order_id} has failed according to order status API. ") + self.trigger_event(MarketEvent.OrderFailure, + MarketOrderFailureEvent( + self.current_timestamp, + tracked_order.client_order_id, + tracked_order.order_type + )) + + self.stop_tracking_order(tracked_order.client_order_id) def get_taker_order_type(self): return OrderType.LIMIT @@ -503,9 +503,12 @@ def tick(self, timestamp: float): Is called automatically by the clock for each clock's tick (1 second by default). It checks if status polling task is due for execution. """ - if time.time() - self._last_poll_timestamp > self.POLL_INTERVAL: - if self._poll_notifier is not None and not self._poll_notifier.is_set(): - self._poll_notifier.set() + if ( + time.time() - self._last_poll_timestamp > self.POLL_INTERVAL + and self._poll_notifier is not None + and not self._poll_notifier.is_set() + ): + self._poll_notifier.set() async def _status_polling_loop(self): while True: diff --git a/hummingbot/connector/connector/uniswap_v3/uniswap_v3_connector.py b/hummingbot/connector/connector/uniswap_v3/uniswap_v3_connector.py index d3fd05fb4e..884babbb90 100644 --- a/hummingbot/connector/connector/uniswap_v3/uniswap_v3_connector.py +++ b/hummingbot/connector/connector/uniswap_v3/uniswap_v3_connector.py @@ -67,8 +67,7 @@ async def initiate_pool(self) -> str: # self.logger().info(f"Initializing Uniswap connector and paths for {self._trading_pairs} pairs.") resp = await self._api_request("get", "eth/uniswap/v3/start", {"pairs": json.dumps(self._trading_pairs)}) - status = bool(str(resp["success"])) - if status: + if status := bool(str(resp["success"])): self._initiate_pool_status = status self._trading_pairs = resp["pairs"] await asyncio.sleep(60) @@ -162,7 +161,6 @@ async def update_swap_order(self, update_result: Dict[str, any], tracked_order: tracked_order.executed_amount_quote, float(fee), tracked_order.order_type)) - self.stop_tracking_order(tracked_order.client_order_id) else: self.logger().info( f"The {tracked_order.type} order {tracked_order.client_order_id} has failed according to order status API. ") @@ -172,89 +170,85 @@ async def update_swap_order(self, update_result: Dict[str, any], tracked_order: tracked_order.client_order_id, tracked_order.order_type )) - self.stop_tracking_order(tracked_order.client_order_id) + + self.stop_tracking_order(tracked_order.client_order_id) async def update_lp_order(self, update_result: Dict[str, any], tracked_pos: UniswapV3InFlightPosition): """ Unlike swap orders, lp orders only stop tracking when a remove position is detected. """ - if update_result.get("confirmed", False): - if update_result["receipt"].get("status", 0) == 1: - gas_used = update_result["receipt"]["gasUsed"] - gas_price = tracked_pos.gas_price - fee = Decimal(str(gas_used)) * Decimal(str(gas_price)) / Decimal(str(1e9)) - tracked_pos.tx_fees.append(fee) - transaction_results = await self._api_request("post", - "eth/uniswap/v3/result", - {"logs": json.dumps(update_result["receipt"]["logs"]), - "pair": tracked_pos.trading_pair}) - for result in transaction_results["info"]: - if result["name"] == "IncreaseLiquidity" and tracked_pos.last_status == UniswapV3PositionStatus.PENDING_CREATE: - token_id, amount0, amount1 = self.parse_liquidity_events(result["events"], - transaction_results["baseDecimal"], - transaction_results["quoteDecimal"]) - tracked_pos.token_id = token_id - tracked_pos.base_amount = amount0 - tracked_pos.quote_amount = amount1 - tracked_pos.last_status = UniswapV3PositionStatus.OPEN - self.logger().info(f"Liquidity added for tokenID - {token_id}.") - self.trigger_event(MarketEvent.RangePositionUpdated, - RangePositionUpdatedEvent(self.current_timestamp, - tracked_pos.hb_id, - tracked_pos.last_tx_hash, - tracked_pos.token_id, - tracked_pos.base_amount, - tracked_pos.quote_amount, - tracked_pos.last_status.name - )) - self.trigger_event(MarketEvent.RangePositionCreated, - RangePositionCreatedEvent(self.current_timestamp, - tracked_pos.hb_id, - tracked_pos.last_tx_hash, - tracked_pos.token_id, - tracked_pos.trading_pair, - tracked_pos.fee_tier, - tracked_pos.lower_price, - tracked_pos.upper_price, - tracked_pos.base_amount, - tracked_pos.quote_amount, - tracked_pos.last_status.name, - tracked_pos.gas_price - )) - elif result["name"] == "DecreaseLiquidity" and tracked_pos.last_status == UniswapV3PositionStatus.PENDING_REMOVE: - token_id, amount0, amount1 = self.parse_liquidity_events(result["events"], - transaction_results["baseDecimal"], - transaction_results["quoteDecimal"]) - tracked_pos.token_id = token_id - tracked_pos.last_status = UniswapV3PositionStatus.REMOVED - self.logger().info(f"Liquidity decreased for tokenID - {token_id}.") - self.trigger_event(MarketEvent.RangePositionUpdated, - RangePositionUpdatedEvent(self.current_timestamp, - tracked_pos.hb_id, - tracked_pos.last_tx_hash, - tracked_pos.token_id, - tracked_pos.base_amount, - tracked_pos.quote_amount, - tracked_pos.last_status.name - )) - self.trigger_event(MarketEvent.RangePositionRemoved, - RangePositionRemovedEvent(self.current_timestamp, tracked_pos.hb_id, - tracked_pos.token_id)) - self.stop_tracking_position(tracked_pos.hb_id) - elif result["name"] == "Collect": - pass - # not sure how to handle this at the moment - # token_id, amount0, amount1 = self.parse_liquidity_events(result["events"]) - # tracked_order.update_exchange_order_id(token_id) - # self.logger().info(f"Liquidity removed for tokenID - {token_id}.") - else: - self.logger().info( - f"Error updating range position, token_id: {tracked_pos.token_id}, hb_id: {tracked_pos.hb_id}" - ) - self.trigger_event(MarketEvent.RangePositionFailure, - RangePositionFailureEvent(self.current_timestamp, tracked_pos.hb_id)) - self.stop_tracking_position(tracked_pos.hb_id) - tracked_pos.last_status = UniswapV3PositionStatus.FAILED + if not update_result.get("confirmed", False): + return + if update_result["receipt"].get("status", 0) == 1: + gas_used = update_result["receipt"]["gasUsed"] + gas_price = tracked_pos.gas_price + fee = Decimal(str(gas_used)) * Decimal(str(gas_price)) / Decimal(str(1e9)) + tracked_pos.tx_fees.append(fee) + transaction_results = await self._api_request("post", + "eth/uniswap/v3/result", + {"logs": json.dumps(update_result["receipt"]["logs"]), + "pair": tracked_pos.trading_pair}) + for result in transaction_results["info"]: + if result["name"] == "IncreaseLiquidity" and tracked_pos.last_status == UniswapV3PositionStatus.PENDING_CREATE: + token_id, amount0, amount1 = self.parse_liquidity_events(result["events"], + transaction_results["baseDecimal"], + transaction_results["quoteDecimal"]) + tracked_pos.token_id = token_id + tracked_pos.base_amount = amount0 + tracked_pos.quote_amount = amount1 + tracked_pos.last_status = UniswapV3PositionStatus.OPEN + self.logger().info(f"Liquidity added for tokenID - {token_id}.") + self.trigger_event(MarketEvent.RangePositionUpdated, + RangePositionUpdatedEvent(self.current_timestamp, + tracked_pos.hb_id, + tracked_pos.last_tx_hash, + tracked_pos.token_id, + tracked_pos.base_amount, + tracked_pos.quote_amount, + tracked_pos.last_status.name + )) + self.trigger_event(MarketEvent.RangePositionCreated, + RangePositionCreatedEvent(self.current_timestamp, + tracked_pos.hb_id, + tracked_pos.last_tx_hash, + tracked_pos.token_id, + tracked_pos.trading_pair, + tracked_pos.fee_tier, + tracked_pos.lower_price, + tracked_pos.upper_price, + tracked_pos.base_amount, + tracked_pos.quote_amount, + tracked_pos.last_status.name, + tracked_pos.gas_price + )) + elif result["name"] == "DecreaseLiquidity" and tracked_pos.last_status == UniswapV3PositionStatus.PENDING_REMOVE: + token_id, amount0, amount1 = self.parse_liquidity_events(result["events"], + transaction_results["baseDecimal"], + transaction_results["quoteDecimal"]) + tracked_pos.token_id = token_id + tracked_pos.last_status = UniswapV3PositionStatus.REMOVED + self.logger().info(f"Liquidity decreased for tokenID - {token_id}.") + self.trigger_event(MarketEvent.RangePositionUpdated, + RangePositionUpdatedEvent(self.current_timestamp, + tracked_pos.hb_id, + tracked_pos.last_tx_hash, + tracked_pos.token_id, + tracked_pos.base_amount, + tracked_pos.quote_amount, + tracked_pos.last_status.name + )) + self.trigger_event(MarketEvent.RangePositionRemoved, + RangePositionRemovedEvent(self.current_timestamp, tracked_pos.hb_id, + tracked_pos.token_id)) + self.stop_tracking_position(tracked_pos.hb_id) + else: + self.logger().info( + f"Error updating range position, token_id: {tracked_pos.token_id}, hb_id: {tracked_pos.hb_id}" + ) + self.trigger_event(MarketEvent.RangePositionFailure, + RangePositionFailureEvent(self.current_timestamp, tracked_pos.hb_id)) + self.stop_tracking_position(tracked_pos.hb_id) + tracked_pos.last_status = UniswapV3PositionStatus.FAILED async def _update_order_status(self): """ @@ -292,9 +286,12 @@ async def _update_order_status(self): # update info for each positions as well tasks = [] - if len(open_positions) > 0: - for tracked_pos in open_positions: - tasks.append(self.get_position(tracked_pos.token_id)) + if open_positions: + tasks.extend( + self.get_position(tracked_pos.token_id) + for tracked_pos in open_positions + ) + if tasks: position_results = await safe_gather(*tasks, return_exceptions=True) for update_result, tracked_item in zip(position_results, open_positions): @@ -322,17 +319,16 @@ async def _update_order_status(self): tracked_item.token_id)) self.stop_tracking_position(tracked_item.hb_id) + elif tracked_item.trading_pair.split("-")[0] == update_result["position"]["token0"]: + tracked_item.current_base_amount = amount0 + tracked_item.current_quote_amount = amount1 + tracked_item.unclaimed_base_amount = unclaimedToken0 + tracked_item.unclaimed_quote_amount = unclaimedToken1 else: - if tracked_item.trading_pair.split("-")[0] == update_result["position"]["token0"]: - tracked_item.current_base_amount = amount0 - tracked_item.current_quote_amount = amount1 - tracked_item.unclaimed_base_amount = unclaimedToken0 - tracked_item.unclaimed_quote_amount = unclaimedToken1 - else: - tracked_item.current_base_amount = amount1 - tracked_item.current_quote_amount = amount0 - tracked_item.unclaimed_base_amount = unclaimedToken1 - tracked_item.unclaimed_quote_amount = unclaimedToken0 + tracked_item.current_base_amount = amount1 + tracked_item.current_quote_amount = amount0 + tracked_item.unclaimed_base_amount = unclaimedToken1 + tracked_item.unclaimed_quote_amount = unclaimedToken0 def add_position(self, trading_pair: str, @@ -472,17 +468,16 @@ async def _remove_position(self, hb_id: str, token_id: str, reducePercent: Decim {"tokenId": token_id, "reducePercent": reducePercent, "getFee": str(fee_estimate)}) if fee_estimate: return Decimal(str(result.get("gasFee"))) - else: - hash = result.get("hash") - action = "removal of" if reducePercent == Decimal("100.0") else \ - f"{reducePercent}% reduction of liquidity for" - self.logger().info(f"Initiated {action} of position with ID - {token_id}.") - tracked_pos.update_last_tx_hash(hash) - self.trigger_event(MarketEvent.RangePositionUpdated, - RangePositionUpdatedEvent(self.current_timestamp, tracked_pos.hb_id, - tracked_pos.last_tx_hash, tracked_pos.token_id, - tracked_pos.base_amount, tracked_pos.quote_amount, - tracked_pos.last_status.name)) + hash = result.get("hash") + action = "removal of" if reducePercent == Decimal("100.0") else \ + f"{reducePercent}% reduction of liquidity for" + self.logger().info(f"Initiated {action} of position with ID - {token_id}.") + tracked_pos.update_last_tx_hash(hash) + self.trigger_event(MarketEvent.RangePositionUpdated, + RangePositionUpdatedEvent(self.current_timestamp, tracked_pos.hb_id, + tracked_pos.last_tx_hash, tracked_pos.token_id, + tracked_pos.base_amount, tracked_pos.quote_amount, + tracked_pos.last_status.name)) except Exception as e: # self.stop_tracking_position(hb_id) if not fee_estimate: @@ -606,7 +601,12 @@ async def approve_uniswap_spender(self, token_symbol: str) -> Decimal: Approves Uniswap contract as a spender for a token. :param token_symbol: token to approve. """ - spender = "uniswapV3Router" if token_symbol[:1] == "R" else "uniswapV3NFTManager" + spender = ( + "uniswapV3Router" + if token_symbol.startswith("R") + else "uniswapV3NFTManager" + ) + token_symbol = token_symbol[1:] resp = await self._api_request("post", "eth/approve", @@ -638,9 +638,9 @@ async def get_allowances(self) -> Dict[str, Decimal]: ret_val["R" + token] = s_decimal_0""" for token, amount in nft_allowances["approvals"].items(): try: - ret_val["N" + token] = Decimal(str(amount)) + ret_val[f"N{token}"] = Decimal(str(amount)) except Exception: - ret_val["N" + token] = s_decimal_0 + ret_val[f"N{token}"] = s_decimal_0 return ret_val def has_allowances(self) -> bool: diff --git a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_api_order_book_data_source.py b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_api_order_book_data_source.py index 2a9a03a744..7b5bd52902 100644 --- a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_api_order_book_data_source.py +++ b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_api_order_book_data_source.py @@ -83,7 +83,7 @@ async def get_last_traded_prices(cls, domain: str = CONSTANTS.DOMAIN) -> Dict[str, float]: tasks = [cls.get_last_traded_price(t_pair, domain) for t_pair in trading_pairs] results = await safe_gather(*tasks) - return {t_pair: result for t_pair, result in zip(trading_pairs, results)} + return dict(zip(trading_pairs, results)) @classmethod async def get_last_traded_price(cls, @@ -239,7 +239,7 @@ async def get_snapshot( domain=domain, throttler=throttler)} if limit != 0: - params.update({"limit": str(limit)}) + params["limit"] = str(limit) url = utils.rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain) throttler = throttler or ob_source_cls._get_throttler_instance() async with throttler.execute_task(limit_id=CONSTANTS.SNAPSHOT_REST_URL): diff --git a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_auth.py b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_auth.py index 2fe5bad77f..9d51995385 100644 --- a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_auth.py +++ b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_auth.py @@ -19,8 +19,7 @@ def __init__(self, api_key: str, api_secret: str): def generate_signature_from_payload(self, payload: str) -> str: secret = bytes(self._api_secret.encode("utf-8")) - signature = hmac.new(secret, payload.encode("utf-8"), hashlib.sha256).hexdigest() - return signature + return hmac.new(secret, payload.encode("utf-8"), hashlib.sha256).hexdigest() async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: payload: Optional[str] = None diff --git a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py index ff872d94aa..f26f27c266 100644 --- a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py +++ b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py @@ -247,14 +247,17 @@ def buy( t_pair: str = trading_pair order_id: str = utils.get_client_order_id("buy", t_pair) safe_ensure_future( - self._create_order(TradeType.BUY, - order_id, - trading_pair, - amount, - order_type, - kwargs["position_action"], - price) + self._create_order( + TradeType.BUY, + order_id, + t_pair, + amount, + order_type, + kwargs["position_action"], + price, + ) ) + return order_id def sell( @@ -286,14 +289,17 @@ def sell( t_pair: str = trading_pair order_id: str = utils.get_client_order_id("sell", t_pair) safe_ensure_future( - self._create_order(TradeType.SELL, - order_id, - trading_pair, - amount, - order_type, - kwargs["position_action"], - price) + self._create_order( + TradeType.SELL, + order_id, + t_pair, + amount, + order_type, + kwargs["position_action"], + price, + ) ) + return order_id async def cancel_all(self, timeout_seconds: float): @@ -340,11 +346,10 @@ async def cancel_all_account_orders(self, trading_pair: str): add_timestamp=True, is_auth_required=True, ) - if response.get("code") == 200: - for order_id in list(self._client_order_tracker.active_orders.keys()): - self.stop_tracking_order(order_id) - else: + if response.get("code") != 200: raise IOError(f"Error cancelling all account orders. Server Response: {response}") + for order_id in list(self._client_order_tracker.active_orders.keys()): + self.stop_tracking_order(order_id) except Exception as e: self.logger().error("Could not cancel all account orders.") raise e @@ -475,9 +480,8 @@ def tick(self, timestamp: float): else self.LONG_POLL_INTERVAL) last_tick = int(self._last_timestamp / poll_interval) current_tick = int(timestamp / poll_interval) - if current_tick > last_tick: - if not self._poll_notifier.is_set(): - self._poll_notifier.set() + if current_tick > last_tick and not self._poll_notifier.is_set(): + self._poll_notifier.set() if now >= self._next_funding_fee_timestamp + CONSTANTS.FUNDING_SETTLEMENT_DURATION[1]: self._funding_fee_poll_notifier.set() @@ -537,10 +541,9 @@ def get_funding_info(self, trading_pair: str) -> Optional[FundingInfo]: """ if trading_pair in self._order_book_tracker.data_source.funding_info: return self._order_book_tracker.data_source.funding_info[trading_pair] - else: - self.logger().error(f"Funding Info for {trading_pair} not found. Proceeding to fetch using REST API.") - safe_ensure_future(self._order_book_tracker.data_source.get_funding_info(trading_pair)) - return None + self.logger().error(f"Funding Info for {trading_pair} not found. Proceeding to fetch using REST API.") + safe_ensure_future(self._order_book_tracker.data_source.get_funding_info(trading_pair)) + return None def get_next_funding_timestamp(self): # On Binance Futures, Funding occurs every 8 hours at 00:00 UTC; 08:00 UTC and 16:00 @@ -843,11 +846,13 @@ async def _funding_fee_polling_loop(self): try: await self._funding_fee_poll_notifier.wait() - tasks = [] - for trading_pair in self._trading_pairs: - tasks.append( - asyncio.create_task(self._fetch_funding_payment(trading_pair=trading_pair)) + tasks = [ + asyncio.create_task( + self._fetch_funding_payment(trading_pair=trading_pair) ) + for trading_pair in self._trading_pairs + ] + # Only when all tasks is successful would the event notifier be resetted responses: List[bool] = await safe_gather(*tasks) if all(responses): @@ -940,9 +945,8 @@ async def _update_positions(self): amount=amount, leverage=leverage ) - else: - if pos_key in self._account_positions: - del self._account_positions[pos_key] + elif pos_key in self._account_positions: + del self._account_positions[pos_key] async def _update_order_fills_from_trades(self): last_tick = int(self._last_poll_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL) @@ -1290,16 +1294,16 @@ async def __api_request(self, limit_id: Optional[str] = None): rest_assistant = await self._get_rest_assistant() - async with self._throttler.execute_task(limit_id=limit_id if limit_id else path): + async with self._throttler.execute_task(limit_id=limit_id or path): try: if add_timestamp: if method == RESTMethod.POST: data = data or {} - data["recvWindow"] = f"{20000}" + data["recvWindow"] = '20000' data["timestamp"] = str(int(self._binance_time_synchronizer.time()) * 1000) else: params = params or {} - params["recvWindow"] = f"{20000}" + params["recvWindow"] = '20000' params["timestamp"] = str(int(self._binance_time_synchronizer.time()) * 1000) url = utils.rest_url(path, self._domain, api_version) @@ -1310,8 +1314,9 @@ async def __api_request(self, params=params, data=data, is_auth_required=is_auth_required, - throttler_limit_id=limit_id if limit_id else path + throttler_limit_id=limit_id or path, ) + response = await rest_assistant.call(request=request) if response.status != 200: diff --git a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_user_stream_data_source.py b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_user_stream_data_source.py index 625d35c91b..8dbd3deb58 100644 --- a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_user_stream_data_source.py +++ b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_user_stream_data_source.py @@ -38,9 +38,7 @@ def logger(cls) -> HummingbotLogger: @property def last_recv_time(self) -> float: - if self._ws_assistant: - return self._ws_assistant.last_recv_time - return 0 + return self._ws_assistant.last_recv_time if self._ws_assistant else 0 def __init__( self, diff --git a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_utils.py b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_utils.py index 7eae404b9f..0d7b53efe0 100644 --- a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_utils.py +++ b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_utils.py @@ -58,8 +58,9 @@ def wss_url(endpoint: str, domain: str = "binance_perpetual"): def build_api_factory(auth: Optional[AuthBase] = None) -> WebAssistantsFactory: - api_factory = WebAssistantsFactory(auth=auth, rest_pre_processors=[BinancePerpetualRESTPreProcessor()]) - return api_factory + return WebAssistantsFactory( + auth=auth, rest_pre_processors=[BinancePerpetualRESTPreProcessor()] + ) def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: diff --git a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_api_order_book_data_source.py b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_api_order_book_data_source.py index bae2e89786..41d198049a 100644 --- a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_api_order_book_data_source.py +++ b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_api_order_book_data_source.py @@ -125,14 +125,22 @@ async def trading_pair_symbol_map(cls, domain: Optional[str] = None): async def get_last_traded_prices( cls, trading_pairs: List[str], domain: Optional[str] = None, throttler: Optional[AsyncThrottler] = None ) -> Dict[str, float]: - if (domain in cls._last_traded_prices - and all(trading_pair in cls._last_traded_prices[domain] - for trading_pair - in trading_pairs)): - result = {trading_pair: cls._last_traded_prices[domain][trading_pair] for trading_pair in trading_pairs} - else: - result = await cls._get_last_traded_prices_from_exchange(trading_pairs, domain, throttler) - return result + return ( + { + trading_pair: cls._last_traded_prices[domain][trading_pair] + for trading_pair in trading_pairs + } + if ( + domain in cls._last_traded_prices + and all( + trading_pair in cls._last_traded_prices[domain] + for trading_pair in trading_pairs + ) + ) + else await cls._get_last_traded_prices_from_exchange( + trading_pairs, domain, throttler + ) + ) @classmethod async def _get_last_traded_prices_from_exchange( @@ -168,9 +176,9 @@ async def _get_order_book_data(self, trading_pair: str) -> Dict[str, any]: :return: Parsed API Response as a Json dictionary """ symbol_map = await self.trading_pair_symbol_map(self._domain) - symbols = [symbol for symbol, pair in symbol_map.items() if pair == trading_pair] - - if symbols: + if symbols := [ + symbol for symbol, pair in symbol_map.items() if pair == trading_pair + ]: symbol = symbols[0] else: raise ValueError(f"There is no symbol mapping for trading pair {trading_pair}") @@ -217,9 +225,9 @@ async def get_new_order_book(self, trading_pair: str) -> OrderBook: async def _get_funding_info_from_exchange(self, trading_pair: str) -> FundingInfo: symbol_map = await self.trading_pair_symbol_map(self._domain) - symbols = [symbol for symbol, pair in symbol_map.items() if trading_pair == pair] - - if symbols: + if symbols := [ + symbol for symbol, pair in symbol_map.items() if trading_pair == pair + ]: symbol = symbols[0] else: raise ValueError(f"There is no symbol mapping for trading pair {trading_pair}") @@ -434,11 +442,11 @@ async def listen_for_instruments_info(self): event_type = instrument_info_message["type"] entries = [] - if event_type == "snapshot": - entries.append(instrument_info_message["data"]) if event_type == "delta": entries.extend(instrument_info_message["data"]["update"]) + elif event_type == "snapshot": + entries.append(instrument_info_message["data"]) for entry in entries: if "last_price_e4" in entry: self._last_traded_prices[self._domain][trading_pair] = int(entry["last_price_e4"]) * 1e-4 @@ -479,5 +487,4 @@ async def listen_for_instruments_info(self): @classmethod def _get_throttler_instance(cls, trading_pairs: List[str] = None) -> AsyncThrottler: rate_limits = bybit_utils.build_rate_limits(trading_pairs) - throttler = AsyncThrottler(rate_limits) - return throttler + return AsyncThrottler(rate_limits) diff --git a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_auth.py b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_auth.py index 7c21f4a0f7..5d8e8f1aa5 100644 --- a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_auth.py +++ b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_auth.py @@ -30,11 +30,9 @@ def get_ws_auth_payload(self) -> Dict[str, Any]: :return: a dictionary of authentication info including the request signature """ expires = self.get_expiration_timestamp() - raw_signature = 'GET/realtime' + expires + raw_signature = f'GET/realtime{expires}' signature = hmac.new(self._secret_key.encode('utf-8'), raw_signature.encode('utf-8'), hashlib.sha256).hexdigest() - auth_info = [self._api_key, expires, signature] - - return auth_info + return [self._api_key, expires, signature] def get_headers(self, referer_header_required: Optional[bool] = False) -> Dict[str, Any]: """ @@ -45,9 +43,7 @@ def get_headers(self, referer_header_required: Optional[bool] = False) -> Dict[s "Content-Type": "application/json" } if referer_header_required: - result.update({ - "Referer": CONSTANTS.HBOT_BROKER_ID - }) + result["Referer"] = CONSTANTS.HBOT_BROKER_ID return result def extend_params_with_authentication_info(self, params: Dict[str, Any]): @@ -57,7 +53,7 @@ def extend_params_with_authentication_info(self, params: Dict[str, Any]): for key, value in sorted(params.items()): converted_value = float(value) if type(value) is Decimal else value converted_value = converted_value if type(value) is str else json.dumps(converted_value) - key_value_elements.append(str(key) + "=" + converted_value) + key_value_elements.append(f'{str(key)}={converted_value}') raw_signature = '&'.join(key_value_elements) signature = hmac.new(self._secret_key.encode('utf-8'), raw_signature.encode('utf-8'), hashlib.sha256).hexdigest() params["sign"] = signature diff --git a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_derivative.py b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_derivative.py index 1de4988382..baefe9e485 100644 --- a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_derivative.py +++ b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_derivative.py @@ -446,9 +446,7 @@ async def _create_order(self, "price": price, }) else: - params.update({ - "order_type": "Market" - }) + params["order_type"] = "Market" self.start_tracking_order(order_id, None, @@ -489,10 +487,9 @@ async def _create_order(self, self.trigger_event(MarketEvent.OrderFailure, MarketOrderFailureEvent(self.current_timestamp, order_id, order_type)) self.logger().network( - f"Error submitting {trade_type.name} {order_type.name} order to Bybit Perpetual for " - f"{amount} {trading_pair} {price}. Error: {str(e)} Parameters: {params if params else None}", + f"Error submitting {trade_type.name} {order_type.name} order to Bybit Perpetual for {amount} {trading_pair} {price}. Error: {str(e)} Parameters: {params or None}", exc_info=True, - app_warning_msg="Error submitting order to Bybit Perpetual. " + app_warning_msg="Error submitting order to Bybit Perpetual. ", ) def buy(self, trading_pair: str, amount: Decimal, order_type: OrderType = OrderType.MARKET, @@ -586,15 +583,15 @@ async def _execute_cancel(self, trading_pair: str, order_id: str) -> str: response_code = response["ret_code"] if response_code != 0: - if response_code == CONSTANTS.ORDER_NOT_EXISTS_ERROR_CODE: - self.logger().warning( - f"Failed to cancel order {order_id}:" - f" order not found ({response_code} - {response['ret_msg']})") - self.stop_tracking_order(order_id) - else: - raise IOError(f"Bybit Perpetual encountered a problem cancelling the order" - f" ({response['ret_code']} - {response['ret_msg']})") + if response_code != CONSTANTS.ORDER_NOT_EXISTS_ERROR_CODE: + raise IOError( + f'Bybit Perpetual encountered a problem cancelling the order ({response_code} - {response["ret_msg"]})' + ) + self.logger().warning( + f"Failed to cancel order {order_id}:" + f" order not found ({response_code} - {response['ret_msg']})") + self.stop_tracking_order(order_id) return order_id except asyncio.CancelledError: @@ -767,8 +764,10 @@ async def _update_order_status(self): Calls REST API to get order status """ - active_orders: List[BybitPerpetualInFlightOrder] = [ - o for o in self._in_flight_orders.values()] + active_orders: List[BybitPerpetualInFlightOrder] = list( + self._in_flight_orders.values() + ) + tasks = [] for active_order in active_orders: @@ -935,9 +934,8 @@ def _process_account_position_event(self, position_msg: Dict[str, Any], symbol_t amount=amount * (Decimal("-1.0") if position_side == PositionSide.SHORT else Decimal("1.0")), leverage=leverage, ) - else: - if pos_key in self._account_positions: - del self._account_positions[pos_key] + elif pos_key in self._account_positions: + del self._account_positions[pos_key] def _process_order_event_message(self, order_msg: Dict[str, Any]): """ @@ -963,7 +961,7 @@ def _process_order_event_message(self, order_msg: Dict[str, Any]): client_order_id)) self.stop_tracking_order(client_order_id) elif tracked_order.is_failure: - reason = order_msg["reject_reason"] if "reject_reason" in order_msg else "unknown" + reason = order_msg.get("reject_reason", "unknown") self.logger().info(f"The market order {client_order_id} has failed according to order status event. " f"Reason: {reason}") self.trigger_event(MarketEvent.OrderFailure, @@ -1096,11 +1094,11 @@ async def _user_funding_fee_polling_loop(self): try: await self._funding_fee_poll_notifier.wait() - tasks = [] - for trading_pair in self._trading_pairs: - tasks.append( - asyncio.create_task(self._fetch_funding_fee(trading_pair)) - ) + tasks = [ + asyncio.create_task(self._fetch_funding_fee(trading_pair)) + for trading_pair in self._trading_pairs + ] + # Only when all tasks is successful would the event notifier be resetted responses: List[bool] = await safe_gather(*tasks) if all(responses): @@ -1137,14 +1135,12 @@ async def _update_positions(self): # Initial parsing of responses. Joining all the responses parsed_resps: List[Dict[str, Any]] = [] for resp in raw_responses: - if not isinstance(resp, Exception): - result = resp["result"] - if result: - position_entries = result if isinstance(result, list) else [result] - parsed_resps.extend(position_entries) - else: + if isinstance(resp, Exception): self.logger().error(f"Error fetching trades history. Response: {resp}") + elif result := resp["result"]: + position_entries = result if isinstance(result, list) else [result] + parsed_resps.extend(position_entries) for position in parsed_resps: data = position ex_trading_pair = data.get("symbol") @@ -1165,9 +1161,8 @@ async def _update_positions(self): amount=amount * (Decimal("-1.0") if position_side == PositionSide.SHORT else Decimal("1.0")), leverage=leverage, ) - else: - if pos_key in self._account_positions: - del self._account_positions[pos_key] + elif pos_key in self._account_positions: + del self._account_positions[pos_key] async def _set_leverage(self, trading_pair: str, leverage: int = 1): ex_trading_pair = bybit_utils.convert_to_exchange_trading_pair(trading_pair) @@ -1212,10 +1207,9 @@ def get_funding_info(self, trading_pair: str) -> Optional[FundingInfo]: """ if trading_pair in self._order_book_tracker.data_source.funding_info: return self._order_book_tracker.data_source.funding_info[trading_pair] - else: - self.logger().error(f"Funding Info for {trading_pair} not found. Proceeding to fetch using REST API.") - safe_ensure_future(self._order_book_tracker.data_source.get_funding_info(trading_pair)) - return None + self.logger().error(f"Funding Info for {trading_pair} not found. Proceeding to fetch using REST API.") + safe_ensure_future(self._order_book_tracker.data_source.get_funding_info(trading_pair)) + return None def get_buy_collateral_token(self, trading_pair: str) -> str: trading_rule: TradingRule = self._trading_rules[trading_pair] @@ -1227,7 +1221,7 @@ def get_sell_collateral_token(self, trading_pair: str) -> str: def _get_throttler_instance(self) -> AsyncThrottler: if self._trading_pairs is not None: - trading_pairs = [tp for tp in self._trading_pairs] + trading_pairs = list(self._trading_pairs) else: trading_pairs = [] @@ -1237,5 +1231,4 @@ def _get_throttler_instance(self) -> AsyncThrottler: trading_pairs.append(trading_pair) rate_limits = bybit_utils.build_rate_limits(trading_pairs) - throttler = AsyncThrottler(rate_limits) - return throttler + return AsyncThrottler(rate_limits) diff --git a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_order_book_tracker.py b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_order_book_tracker.py index 9b58f74fd6..17e5154314 100644 --- a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_order_book_tracker.py +++ b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_order_book_tracker.py @@ -35,8 +35,11 @@ def __init__(self, async def trading_pair_symbol(self, trading_pair: str) -> str: trading_pairs_map = await BybitPerpetualAPIOrderBookDataSource.trading_pair_symbol_map(self._domain) - symbols = [symbol for symbol, map_trading_pair in trading_pairs_map.items() if trading_pair == map_trading_pair] - if symbols: + if symbols := [ + symbol + for symbol, map_trading_pair in trading_pairs_map.items() + if trading_pair == map_trading_pair + ]: symbol = symbols[0] else: raise ValueError(f"The symbol representing trading pair {trading_pair} could not be found") diff --git a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_user_stream_data_source.py b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_user_stream_data_source.py index ef93ff1a45..9701f06e27 100644 --- a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_user_stream_data_source.py +++ b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_user_stream_data_source.py @@ -141,10 +141,13 @@ async def listen_for_user_stream(self, output: asyncio.Queue): """ tasks_future = None try: - tasks = [] - tasks.append(self._listen_for_user_stream_on_url( - url=bybit_perpetual_utils.wss_linear_private_url(self._domain), - output=output)) + tasks = [ + self._listen_for_user_stream_on_url( + url=bybit_perpetual_utils.wss_linear_private_url(self._domain), + output=output, + ) + ] + tasks.append(self._listen_for_user_stream_on_url( url=bybit_perpetual_utils.wss_non_linear_private_url(self._domain), output=output)) diff --git a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_utils.py b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_utils.py index c28ea81aba..c307357a1f 100644 --- a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_utils.py +++ b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_utils.py @@ -38,11 +38,11 @@ def is_linear_perpetual(trading_pair: str) -> bool: def get_rest_api_market_for_endpoint(trading_pair: Optional[str] = None) -> str: - if trading_pair and is_linear_perpetual(trading_pair): - market = CONSTANTS.LINEAR_MARKET - else: - market = CONSTANTS.NON_LINEAR_MARKET - return market + return ( + CONSTANTS.LINEAR_MARKET + if trading_pair and is_linear_perpetual(trading_pair) + else CONSTANTS.NON_LINEAR_MARKET + ) def rest_api_path_for_endpoint(endpoint: Dict[str, str], @@ -52,13 +52,12 @@ def rest_api_path_for_endpoint(endpoint: Dict[str, str], def rest_api_url_for_endpoint(endpoint: str, domain: Optional[str] = None) -> str: - variant = domain if domain else "bybit_perpetual_main" + variant = domain or "bybit_perpetual_main" return CONSTANTS.REST_URLS.get(variant) + endpoint def get_pair_specific_limit_id(base_limit_id: str, trading_pair: str) -> str: - limit_id = f"{base_limit_id}-{trading_pair}" - return limit_id + return f"{base_limit_id}-{trading_pair}" def get_rest_api_limit_id_for_endpoint(endpoint: Dict[str, str], @@ -71,7 +70,7 @@ def get_rest_api_limit_id_for_endpoint(endpoint: Dict[str, str], def _wss_url(endpoint: Dict[str, str], connector_variant_label: Optional[str]) -> str: - variant = connector_variant_label if connector_variant_label else "bybit_perpetual_main" + variant = connector_variant_label or "bybit_perpetual_main" return endpoint.get(variant) @@ -149,54 +148,73 @@ def build_rate_limits(trading_pairs: Optional[List[str]] = None) -> List[RateLim def _build_private_general_rate_limits() -> List[RateLimit]: - rate_limits = [ + return [ RateLimit( # same for linear and non-linear - limit_id=CONSTANTS.GET_WALLET_BALANCE_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], + limit_id=CONSTANTS.GET_WALLET_BALANCE_PATH_URL[ + CONSTANTS.NON_LINEAR_MARKET + ], limit=120, time_interval=60, - linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), - LinkedLimitWeightPair(CONSTANTS.NON_LINEAR_PRIVATE_BUCKET_120_B_LIMIT_ID)], + linked_limits=[ + LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair( + CONSTANTS.NON_LINEAR_PRIVATE_BUCKET_120_B_LIMIT_ID + ), + ], ), ] - return rate_limits def _build_global_rate_limits() -> List[RateLimit]: - rate_limits = [ - RateLimit(limit_id=CONSTANTS.GET_LIMIT_ID, limit=CONSTANTS.GET_RATE, time_interval=1), - RateLimit(limit_id=CONSTANTS.POST_LIMIT_ID, limit=CONSTANTS.POST_RATE, time_interval=1), + return [ + RateLimit( + limit_id=CONSTANTS.GET_LIMIT_ID, + limit=CONSTANTS.GET_RATE, + time_interval=1, + ), + RateLimit( + limit_id=CONSTANTS.POST_LIMIT_ID, + limit=CONSTANTS.POST_RATE, + time_interval=1, + ), ] - return rate_limits def _build_public_rate_limits(): - public_rate_limits = [ + return [ RateLimit( # same for linear and non-linear - limit_id=CONSTANTS.LATEST_SYMBOL_INFORMATION_ENDPOINT[CONSTANTS.NON_LINEAR_MARKET], + limit_id=CONSTANTS.LATEST_SYMBOL_INFORMATION_ENDPOINT[ + CONSTANTS.NON_LINEAR_MARKET + ], limit=CONSTANTS.GET_RATE, time_interval=1, linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID)], ), RateLimit( # same for linear and non-linear - limit_id=CONSTANTS.QUERY_SYMBOL_ENDPOINT[CONSTANTS.NON_LINEAR_MARKET], + limit_id=CONSTANTS.QUERY_SYMBOL_ENDPOINT[ + CONSTANTS.NON_LINEAR_MARKET + ], limit=CONSTANTS.GET_RATE, time_interval=1, linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID)], ), RateLimit( # same for linear and non-linear - limit_id=CONSTANTS.ORDER_BOOK_ENDPOINT[CONSTANTS.NON_LINEAR_MARKET], + limit_id=CONSTANTS.ORDER_BOOK_ENDPOINT[ + CONSTANTS.NON_LINEAR_MARKET + ], limit=CONSTANTS.GET_RATE, time_interval=1, linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID)], ), RateLimit( # same for linear and non-linear - limit_id=CONSTANTS.SERVER_TIME_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], + limit_id=CONSTANTS.SERVER_TIME_PATH_URL[ + CONSTANTS.NON_LINEAR_MARKET + ], limit=CONSTANTS.GET_RATE, time_interval=1, linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID)], - ) + ), ] - return public_rate_limits def _build_private_rate_limits(trading_pairs: List[str]) -> List[RateLimit]: @@ -238,73 +256,133 @@ def _build_private_pair_specific_non_linear_rate_limits(trading_pair: str) -> Li base_limit_id=CONSTANTS.NON_LINEAR_PRIVATE_BUCKET_120_C_LIMIT_ID, trading_pair=trading_pair ) - rate_limits = [ - RateLimit(limit_id=pair_specific_non_linear_private_bucket_100_limit_id, limit=100, time_interval=60), - RateLimit(limit_id=pair_specific_non_linear_private_bucket_600_limit_id, limit=600, time_interval=60), - RateLimit(limit_id=pair_specific_non_linear_private_bucket_75_limit_id, limit=75, time_interval=60), - RateLimit(limit_id=pair_specific_non_linear_private_bucket_120_b_limit_id, limit=120, time_interval=60), - RateLimit(limit_id=pair_specific_non_linear_private_bucket_120_c_limit_id, limit=120, time_interval=60), + return [ + RateLimit( + limit_id=pair_specific_non_linear_private_bucket_100_limit_id, + limit=100, + time_interval=60, + ), + RateLimit( + limit_id=pair_specific_non_linear_private_bucket_600_limit_id, + limit=600, + time_interval=60, + ), + RateLimit( + limit_id=pair_specific_non_linear_private_bucket_75_limit_id, + limit=75, + time_interval=60, + ), + RateLimit( + limit_id=pair_specific_non_linear_private_bucket_120_b_limit_id, + limit=120, + time_interval=60, + ), + RateLimit( + limit_id=pair_specific_non_linear_private_bucket_120_c_limit_id, + limit=120, + time_interval=60, + ), RateLimit( limit_id=get_pair_specific_limit_id( - base_limit_id=CONSTANTS.SET_LEVERAGE_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], trading_pair=trading_pair + base_limit_id=CONSTANTS.SET_LEVERAGE_PATH_URL[ + CONSTANTS.NON_LINEAR_MARKET + ], + trading_pair=trading_pair, ), limit=75, time_interval=60, - linked_limits=[LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), - LinkedLimitWeightPair(pair_specific_non_linear_private_bucket_75_limit_id)], + linked_limits=[ + LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), + LinkedLimitWeightPair( + pair_specific_non_linear_private_bucket_75_limit_id + ), + ], ), RateLimit( limit_id=get_pair_specific_limit_id( - base_limit_id=CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], + base_limit_id=CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL[ + CONSTANTS.NON_LINEAR_MARKET + ], trading_pair=trading_pair, ), limit=120, time_interval=60, - linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), - LinkedLimitWeightPair(pair_specific_non_linear_private_bucket_120_c_limit_id)], + linked_limits=[ + LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair( + pair_specific_non_linear_private_bucket_120_c_limit_id + ), + ], ), RateLimit( limit_id=get_pair_specific_limit_id( - base_limit_id=CONSTANTS.GET_POSITIONS_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], trading_pair=trading_pair + base_limit_id=CONSTANTS.GET_POSITIONS_PATH_URL[ + CONSTANTS.NON_LINEAR_MARKET + ], + trading_pair=trading_pair, ), limit=120, time_interval=60, - linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), - LinkedLimitWeightPair(pair_specific_non_linear_private_bucket_120_b_limit_id)], + linked_limits=[ + LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair( + pair_specific_non_linear_private_bucket_120_b_limit_id + ), + ], ), RateLimit( limit_id=get_pair_specific_limit_id( - base_limit_id=CONSTANTS.PLACE_ACTIVE_ORDER_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], + base_limit_id=CONSTANTS.PLACE_ACTIVE_ORDER_PATH_URL[ + CONSTANTS.NON_LINEAR_MARKET + ], trading_pair=trading_pair, ), limit=100, time_interval=60, - linked_limits=[LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), - LinkedLimitWeightPair(pair_specific_non_linear_private_bucket_100_limit_id)], + linked_limits=[ + LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), + LinkedLimitWeightPair( + pair_specific_non_linear_private_bucket_100_limit_id + ), + ], ), RateLimit( limit_id=get_pair_specific_limit_id( - base_limit_id=CONSTANTS.CANCEL_ACTIVE_ORDER_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], + base_limit_id=CONSTANTS.CANCEL_ACTIVE_ORDER_PATH_URL[ + CONSTANTS.NON_LINEAR_MARKET + ], trading_pair=trading_pair, ), limit=100, time_interval=60, - linked_limits=[LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), - LinkedLimitWeightPair(pair_specific_non_linear_private_bucket_100_limit_id)], + linked_limits=[ + LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), + LinkedLimitWeightPair( + pair_specific_non_linear_private_bucket_100_limit_id + ), + ], ), RateLimit( limit_id=get_pair_specific_limit_id( - base_limit_id=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], + base_limit_id=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL[ + CONSTANTS.NON_LINEAR_MARKET + ], trading_pair=trading_pair, ), limit=600, time_interval=60, - linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), - LinkedLimitWeightPair(pair_specific_non_linear_private_bucket_600_limit_id)], + linked_limits=[ + LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair( + pair_specific_non_linear_private_bucket_600_limit_id + ), + ], ), RateLimit( limit_id=get_pair_specific_limit_id( - base_limit_id=CONSTANTS.USER_TRADE_RECORDS_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], + base_limit_id=CONSTANTS.USER_TRADE_RECORDS_PATH_URL[ + CONSTANTS.NON_LINEAR_MARKET + ], trading_pair=trading_pair, ), limit=120, @@ -313,8 +391,6 @@ def _build_private_pair_specific_non_linear_rate_limits(trading_pair: str) -> Li ), ] - return rate_limits - def _build_private_pair_specific_linear_rate_limits(trading_pair: str) -> List[RateLimit]: pair_specific_linear_private_bucket_100_limit_id = get_pair_specific_limit_id( @@ -330,75 +406,137 @@ def _build_private_pair_specific_linear_rate_limits(trading_pair: str) -> List[R base_limit_id=CONSTANTS.LINEAR_PRIVATE_BUCKET_120_A_LIMIT_ID, trading_pair=trading_pair ) - rate_limits = [ - RateLimit(limit_id=pair_specific_linear_private_bucket_100_limit_id, limit=100, time_interval=60), - RateLimit(limit_id=pair_specific_linear_private_bucket_600_limit_id, limit=600, time_interval=60), - RateLimit(limit_id=pair_specific_linear_private_bucket_75_limit_id, limit=75, time_interval=60), - RateLimit(limit_id=pair_specific_linear_private_bucket_120_a_limit_id, limit=120, time_interval=60), + return [ + RateLimit( + limit_id=pair_specific_linear_private_bucket_100_limit_id, + limit=100, + time_interval=60, + ), + RateLimit( + limit_id=pair_specific_linear_private_bucket_600_limit_id, + limit=600, + time_interval=60, + ), + RateLimit( + limit_id=pair_specific_linear_private_bucket_75_limit_id, + limit=75, + time_interval=60, + ), + RateLimit( + limit_id=pair_specific_linear_private_bucket_120_a_limit_id, + limit=120, + time_interval=60, + ), RateLimit( limit_id=get_pair_specific_limit_id( - base_limit_id=CONSTANTS.SET_LEVERAGE_PATH_URL[CONSTANTS.LINEAR_MARKET], trading_pair=trading_pair + base_limit_id=CONSTANTS.SET_LEVERAGE_PATH_URL[ + CONSTANTS.LINEAR_MARKET + ], + trading_pair=trading_pair, ), limit=75, time_interval=60, - linked_limits=[LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), - LinkedLimitWeightPair(pair_specific_linear_private_bucket_75_limit_id)], + linked_limits=[ + LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), + LinkedLimitWeightPair( + pair_specific_linear_private_bucket_75_limit_id + ), + ], ), RateLimit( limit_id=get_pair_specific_limit_id( - base_limit_id=CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL[CONSTANTS.LINEAR_MARKET], + base_limit_id=CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL[ + CONSTANTS.LINEAR_MARKET + ], trading_pair=trading_pair, ), limit=120, time_interval=60, - linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), - LinkedLimitWeightPair(pair_specific_linear_private_bucket_120_a_limit_id)], + linked_limits=[ + LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair( + pair_specific_linear_private_bucket_120_a_limit_id + ), + ], ), RateLimit( limit_id=get_pair_specific_limit_id( - base_limit_id=CONSTANTS.GET_POSITIONS_PATH_URL[CONSTANTS.LINEAR_MARKET], trading_pair=trading_pair + base_limit_id=CONSTANTS.GET_POSITIONS_PATH_URL[ + CONSTANTS.LINEAR_MARKET + ], + trading_pair=trading_pair, ), limit=120, time_interval=60, - linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), - LinkedLimitWeightPair(pair_specific_linear_private_bucket_120_a_limit_id)], + linked_limits=[ + LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair( + pair_specific_linear_private_bucket_120_a_limit_id + ), + ], ), RateLimit( limit_id=get_pair_specific_limit_id( - base_limit_id=CONSTANTS.PLACE_ACTIVE_ORDER_PATH_URL[CONSTANTS.LINEAR_MARKET], trading_pair=trading_pair + base_limit_id=CONSTANTS.PLACE_ACTIVE_ORDER_PATH_URL[ + CONSTANTS.LINEAR_MARKET + ], + trading_pair=trading_pair, ), limit=100, time_interval=60, - linked_limits=[LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), - LinkedLimitWeightPair(pair_specific_linear_private_bucket_100_limit_id)], + linked_limits=[ + LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), + LinkedLimitWeightPair( + pair_specific_linear_private_bucket_100_limit_id + ), + ], ), RateLimit( limit_id=get_pair_specific_limit_id( - base_limit_id=CONSTANTS.CANCEL_ACTIVE_ORDER_PATH_URL[CONSTANTS.LINEAR_MARKET], trading_pair=trading_pair + base_limit_id=CONSTANTS.CANCEL_ACTIVE_ORDER_PATH_URL[ + CONSTANTS.LINEAR_MARKET + ], + trading_pair=trading_pair, ), limit=100, time_interval=60, - linked_limits=[LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), - LinkedLimitWeightPair(pair_specific_linear_private_bucket_100_limit_id)], + linked_limits=[ + LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), + LinkedLimitWeightPair( + pair_specific_linear_private_bucket_100_limit_id + ), + ], ), RateLimit( limit_id=get_pair_specific_limit_id( - base_limit_id=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL[CONSTANTS.LINEAR_MARKET], trading_pair=trading_pair + base_limit_id=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL[ + CONSTANTS.LINEAR_MARKET + ], + trading_pair=trading_pair, ), limit=600, time_interval=60, - linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), - LinkedLimitWeightPair(pair_specific_linear_private_bucket_600_limit_id)], + linked_limits=[ + LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair( + pair_specific_linear_private_bucket_600_limit_id + ), + ], ), RateLimit( limit_id=get_pair_specific_limit_id( - base_limit_id=CONSTANTS.USER_TRADE_RECORDS_PATH_URL[CONSTANTS.LINEAR_MARKET], trading_pair=trading_pair + base_limit_id=CONSTANTS.USER_TRADE_RECORDS_PATH_URL[ + CONSTANTS.LINEAR_MARKET + ], + trading_pair=trading_pair, ), limit=120, time_interval=60, - linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), - LinkedLimitWeightPair(pair_specific_linear_private_bucket_120_a_limit_id)], + linked_limits=[ + LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair( + pair_specific_linear_private_bucket_120_a_limit_id + ), + ], ), ] - - return rate_limits diff --git a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_websocket_adaptor.py b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_websocket_adaptor.py index c48695ea04..1589b76d49 100644 --- a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_websocket_adaptor.py +++ b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_websocket_adaptor.py @@ -28,29 +28,23 @@ def __init__(self, websocket: aiohttp.ClientWebSocketResponse): def endpoint_from_message(cls, message: Dict[str, Any]) -> str: if not isinstance(message, dict): return message - if cls._operation_field_name in message.keys(): + if cls._operation_field_name in message: if message[cls._operation_field_name] is cls._subscription_operation: return message[cls._payload_field_name][0] else: return message[cls._operation_field_name] - if cls._topic_field_name in message.keys(): + if cls._topic_field_name in message: return message[cls._topic_field_name] @classmethod def payload_from_message(cls, message: Dict[str, Any]) -> List[Dict[str, Any]]: - if "data" in message: - return message["data"] - return message + return message.get("data", message) async def send_request(self, payload: Dict[str, Any]): await self._websocket.send_json(payload) def _symbols_filter(self, symbols): - if symbols: - symbol_filter = "|".join(symbols) - else: - symbol_filter = "*" - return symbol_filter + return "|".join(symbols) if symbols else "*" async def authenticate(self, payload: Dict[str, Any]): authentication_message = {self._operation_field_name: self._authentication_operation, @@ -100,8 +94,7 @@ async def iter_messages(self) -> AsyncIterable[Dict[str, Any]]: try: while True: try: - msg = await self.receive_json(timeout=self.MESSAGE_TIMEOUT) - yield msg + yield await self.receive_json(timeout=self.MESSAGE_TIMEOUT) except asyncio.TimeoutError: await asyncio.wait_for( self.send_request(payload={self._operation_field_name: CONSTANTS.WS_PING_REQUEST}), diff --git a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_api_order_book_data_source.py b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_api_order_book_data_source.py index 3e4fa9e768..4448f32f6b 100644 --- a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_api_order_book_data_source.py +++ b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_api_order_book_data_source.py @@ -81,11 +81,12 @@ async def fetch_trading_pairs(cls) -> List[str]: response = await rest_assistant.call(request=request) if response.status == 200: all_trading_pairs: Dict[str, Any] = await response.json() - valid_trading_pairs: list = [] - for key, val in all_trading_pairs["markets"].items(): - if val["status"] == "ONLINE": - valid_trading_pairs.append(key) - return valid_trading_pairs + return [ + key + for key, val in all_trading_pairs["markets"].items() + if val["status"] == "ONLINE" + ] + except Exception: # Do nothing if the request fails -- there will be no autocomplete for dydx trading pairs pass @@ -198,15 +199,17 @@ async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asynci while True: try: msg = await msg_queue.get() - if "contents" in msg: - if "trades" in msg["contents"]: - if msg["type"] == "channel_data": - for data in msg["contents"]["trades"]: - msg["ts"] = time.time() - trade_msg: OrderBookMessage = DydxPerpetualOrderBook.trade_message_from_exchange( - data, msg - ) - output.put_nowait(trade_msg) + if ( + "contents" in msg + and "trades" in msg["contents"] + and msg["type"] == "channel_data" + ): + for data in msg["contents"]["trades"]: + msg["ts"] = time.time() + trade_msg: OrderBookMessage = DydxPerpetualOrderBook.trade_message_from_exchange( + data, msg + ) + output.put_nowait(trade_msg) except asyncio.CancelledError: raise except Exception: diff --git a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_auth.py b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_auth.py index a6b230965a..f248e4b35b 100644 --- a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_auth.py +++ b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_auth.py @@ -13,7 +13,7 @@ def get_ws_auth_params(self): timestamp=ts, data={}, ) - ws_auth_params = { + return { "type": "subscribe", "channel": "v3_accounts", "accountNumber": self._dydx_client.account_number, @@ -22,5 +22,3 @@ def get_ws_auth_params(self): "timestamp": ts, "signature": auth_sig } - - return ws_auth_params diff --git a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_client_wrapper.py b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_client_wrapper.py index 0cfa5c2381..8bd1100836 100644 --- a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_client_wrapper.py +++ b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_client_wrapper.py @@ -109,8 +109,7 @@ async def get_server_time(self): return await f def sign(self, request_path, method, timestamp, data): - sign = self.client.private.sign(request_path=request_path, + return self.client.private.sign(request_path=request_path, method=method, iso_timestamp=timestamp, data=data) - return sign diff --git a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_derivative.py b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_derivative.py index 39de5d8ccf..c31e2845aa 100644 --- a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_derivative.py +++ b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_derivative.py @@ -281,9 +281,7 @@ async def place_order( ) -> Dict[str, Any]: order_side = "BUY" if is_buy else "SELL" - post_only = False - if order_type is OrderType.LIMIT_MAKER: - post_only = True + post_only = order_type is OrderType.LIMIT_MAKER dydx_order_type = "LIMIT" if order_type in [OrderType.LIMIT, OrderType.LIMIT_MAKER] else "MARKET" return await self.dydx_client.place_order( @@ -480,43 +478,42 @@ async def cancel_order(self, client_order_id: str): try: if exchange_order_id is None: - # Note, we have no way of canceling an order or querying for information about the order - # without an exchange_order_id - if in_flight_order.created_at < (int(self.time_now_s()) - UNRECOGNIZED_ORDER_DEBOUCE): - # We'll just have to assume that this order doesn't exist - self.stop_tracking_order(in_flight_order.client_order_id) - self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event) - return False - else: + if ( + in_flight_order.created_at + >= int(self.time_now_s()) - UNRECOGNIZED_ORDER_DEBOUCE + ): raise Exception(f"order {client_order_id} has no exchange id") + # We'll just have to assume that this order doesn't exist + self.stop_tracking_order(in_flight_order.client_order_id) + self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event) + return False await self.dydx_client.cancel_order(exchange_order_id) return True except DydxApiError as e: if f"Order with specified id: {exchange_order_id} could not be found" in str(e): - if in_flight_order.created_at < (int(self.time_now_s()) - UNRECOGNIZED_ORDER_DEBOUCE): - # Order didn't exist on exchange, mark this as canceled - self.stop_tracking_order(in_flight_order.client_order_id) - self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event) - return False - else: + if ( + in_flight_order.created_at + >= int(self.time_now_s()) - UNRECOGNIZED_ORDER_DEBOUCE + ): raise Exception( f"order {client_order_id} does not yet exist on the exchange and could not be cancelled." ) + # Order didn't exist on exchange, mark this as canceled + self.stop_tracking_order(in_flight_order.client_order_id) + self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event) elif "is already canceled" in str(e): self.stop_tracking_order(in_flight_order.client_order_id) self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event) - return False elif "is already filled" in str(e): response = await self.dydx_client.get_order(exchange_order_id) order_status = response["order"] in_flight_order.update(order_status) self._issue_order_events(in_flight_order) self.stop_tracking_order(in_flight_order.client_order_id) - return False else: self.logger().warning(f"Unable to cancel order {exchange_order_id}: {str(e)}") - return False + return False except Exception as e: self.logger().warning(f"Failed to cancel order {client_order_id}") self.logger().info(e) @@ -686,11 +683,14 @@ def get_order_by_exchange_id(self, exchange_order_id: str): if exchange_order_id in self._in_flight_orders_by_exchange_id: return self._in_flight_orders_by_exchange_id[exchange_order_id] - for o in self._in_flight_orders.values(): - if o.exchange_order_id == exchange_order_id: - return o - - return None + return next( + ( + o + for o in self._in_flight_orders.values() + if o.exchange_order_id == exchange_order_id + ), + None, + ) # ---------------------------------------- # updates to orders and balances @@ -862,8 +862,8 @@ async def _user_stream_event_listener(self): if position_key not in self._account_positions and market in self._trading_pairs: self._create_position_from_rest_pos_item(position) if "accounts" in data: + quote = "USD" for account in data["accounts"]: - quote = "USD" self._account_available_balances[quote] = Decimal(account["quoteBalance"]) if "orders" in data: for order in data["orders"]: @@ -902,11 +902,10 @@ async def _user_stream_event_listener(self): tracked_order, amount, price, self.get_available_balance("USD") ) self._issue_order_events(tracked_order) - else: - if len(self._orders_pending_ack) > 0: - self._unclaimed_fills[exchange_order_id].add( - DydxPerpetualFillReport(id, amount, price, fee_paid) - ) + elif len(self._orders_pending_ack) > 0: + self._unclaimed_fills[exchange_order_id].add( + DydxPerpetualFillReport(id, amount, price, fee_paid) + ) if "positions" in data: # this is hit when a position is closed positions = data["positions"] @@ -924,29 +923,28 @@ async def _user_stream_event_listener(self): ) if not self._account_positions[pos_key].is_open: del self._account_positions[pos_key] - if "fundingPayments" in data: - if event["type"] != "subscribed": # Only subsequent funding payments - for funding_payment in data["fundingPayments"]: - if funding_payment["market"] not in self._trading_pairs: - continue - ts = dateparse(funding_payment["effectiveAt"]).timestamp() - funding_rate: Decimal = Decimal(funding_payment["rate"]) - trading_pair: str = funding_payment["market"] - payment: Decimal = Decimal(funding_payment["payment"]) - action: str = "paid" if payment < s_decimal_0 else "received" - - self.logger().info(f"Funding payment of {payment} {action} on {trading_pair} market") - self.trigger_event( - MARKET_FUNDING_PAYMENT_COMPLETED_EVENT_TAG, - FundingPaymentCompletedEvent( - timestamp=ts, - market=self.name, - funding_rate=funding_rate, - trading_pair=trading_pair, - amount=payment, - ), - ) - self._trading_pair_last_funding_payment_ts[trading_pair] = ts + if "fundingPayments" in data and event["type"] != "subscribed": + for funding_payment in data["fundingPayments"]: + if funding_payment["market"] not in self._trading_pairs: + continue + ts = dateparse(funding_payment["effectiveAt"]).timestamp() + funding_rate: Decimal = Decimal(funding_payment["rate"]) + trading_pair: str = funding_payment["market"] + payment: Decimal = Decimal(funding_payment["payment"]) + action: str = "paid" if payment < s_decimal_0 else "received" + + self.logger().info(f"Funding payment of {payment} {action} on {trading_pair} market") + self.trigger_event( + MARKET_FUNDING_PAYMENT_COMPLETED_EVENT_TAG, + FundingPaymentCompletedEvent( + timestamp=ts, + market=self.name, + funding_rate=funding_rate, + trading_pair=trading_pair, + amount=payment, + ), + ) + self._trading_pair_last_funding_payment_ts[trading_pair] = ts except asyncio.CancelledError: raise except Exception: @@ -997,10 +995,12 @@ async def _update_account_positions(self): del self._account_positions[pos_key] elif market in self._trading_pairs: self._create_position_from_rest_pos_item(position) - positions_to_delete = [] - for position_str in self._account_positions: - if position_str not in current_positions["openPositions"]: - positions_to_delete.append(position_str) + positions_to_delete = [ + position_str + for position_str in self._account_positions + if position_str not in current_positions["openPositions"] + ] + for account_position in positions_to_delete: del self._account_positions[account_position] @@ -1077,12 +1077,15 @@ async def _update_order_status(self): # check if this error is because the api cliams to be unaware of this order. If so, and this order # is reasonably old, mark the orde as cancelled - if "could not be found" in str(dydx_order_request["msg"]): - if tracked_order.created_at < (int(self.time_now_s()) - UNRECOGNIZED_ORDER_DEBOUCE): - try: - self.cancel_order(client_order_id) - except Exception: - pass + if "could not be found" in str( + dydx_order_request["msg"] + ) and tracked_order.created_at < ( + int(self.time_now_s()) - UNRECOGNIZED_ORDER_DEBOUCE + ): + try: + self.cancel_order(client_order_id) + except Exception: + pass continue try: @@ -1232,9 +1235,8 @@ def tick(self, timestamp: float): ) last_tick = int(self._last_poll_timestamp / poll_interval) current_tick = int(timestamp / poll_interval) - if current_tick > last_tick: - if not self._poll_notifier.is_set(): - self._poll_notifier.set() + if current_tick > last_tick and not self._poll_notifier.is_set(): + self._poll_notifier.set() self._last_poll_timestamp = timestamp def buy( diff --git a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_order_book.py b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_order_book.py index 67bfe0fe99..c179ab9645 100644 --- a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_order_book.py +++ b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_order_book.py @@ -22,12 +22,8 @@ def snapshot_message_from_exchange(cls, msg: Dict[str, any], timestamp: Optional metadata: Optional[Dict] = None) -> OrderBookMessage: if metadata: msg.update(metadata) - if msg["rest"]: - bids = [{"price": Decimal(bid["price"]), "amount": Decimal(bid["size"])} for bid in msg["bids"]] - asks = [{"price": Decimal(ask["price"]), "amount": Decimal(ask["size"])} for ask in msg["asks"]] - else: - bids = [{"price": Decimal(bid['price']), "amount": Decimal(bid['size'])} for bid in msg["bids"]] - asks = [{"price": Decimal(ask['price']), "amount": Decimal(ask['size'])} for ask in msg["asks"]] + bids = [{"price": Decimal(bid["price"]), "amount": Decimal(bid["size"])} for bid in msg["bids"]] + asks = [{"price": Decimal(ask["price"]), "amount": Decimal(ask["size"])} for ask in msg["asks"]] return DydxPerpetualOrderBookMessage(OrderBookMessageType.SNAPSHOT, { "trading_pair": msg["trading_pair"], "update_id": timestamp, diff --git a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_order_book_message.py b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_order_book_message.py index decdced518..e251223699 100644 --- a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_order_book_message.py +++ b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_order_book_message.py @@ -58,8 +58,7 @@ def __eq__(self, other) -> bool: def __lt__(self, other) -> bool: if self.timestamp != other.timestamp: return self.timestamp < other.timestamp - else: - """ + """ If timestamp is the same, the ordering is snapshot < diff < trade """ - return self.type.value < other.type.value + return self.type.value < other.type.value diff --git a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_user_stream_data_source.py b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_user_stream_data_source.py index 846bf8e27e..1cf904b113 100644 --- a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_user_stream_data_source.py +++ b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_user_stream_data_source.py @@ -40,9 +40,7 @@ def order_book_class(self): @property def last_recv_time(self): - if self._ws_assistant: - return self._ws_assistant.last_recv_time - return -1 + return self._ws_assistant.last_recv_time if self._ws_assistant else -1 async def _get_ws_assistant(self) -> WSAssistant: if self._ws_assistant is None: diff --git a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_utils.py b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_utils.py index f3aa9271a4..43ae90f93e 100644 --- a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_utils.py +++ b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_utils.py @@ -13,8 +13,7 @@ def build_api_factory() -> WebAssistantsFactory: - api_factory = WebAssistantsFactory() - return api_factory + return WebAssistantsFactory() KEYS = { diff --git a/hummingbot/connector/derivative/perpetual_finance/perpetual_finance_derivative.py b/hummingbot/connector/derivative/perpetual_finance/perpetual_finance_derivative.py index 16ee9c23ee..a88954b4e9 100644 --- a/hummingbot/connector/derivative/perpetual_finance/perpetual_finance_derivative.py +++ b/hummingbot/connector/derivative/perpetual_finance/perpetual_finance_derivative.py @@ -154,11 +154,11 @@ async def get_allowances(self) -> Dict[str, Decimal]: Retrieves allowances for token in trading_pairs :return: A dictionary of token and its allowance (how much PerpetualFinance can spend). """ - ret_val = {} resp = await self._api_request("post", "perpfi/allowances") - for asset, amount in resp["approvals"].items(): - ret_val[asset] = Decimal(str(amount)) - return ret_val + return { + asset: Decimal(str(amount)) + for asset, amount in resp["approvals"].items() + } @async_ttl_cache(ttl=5, maxsize=10) async def get_quote_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Optional[Decimal]: @@ -261,7 +261,7 @@ async def _create_order(self, "minBaseAssetAmount": Decimal("0")}) else: # api_params.update({"minimalQuoteAsset": price * amount}) - api_params.update({"minimalQuoteAsset": Decimal("0")}) + api_params["minimalQuoteAsset"] = Decimal("0") self.start_tracking_order(order_id, None, trading_pair, trade_type, price, amount, self._leverage[trading_pair], position_action.name) try: order_result = await self._api_request("post", f"perpfi/{position_action.name.lower()}", api_params) @@ -332,85 +332,86 @@ async def _update_order_status(self): """ Calls REST API to get status update for each in-flight order. """ - if len(self._in_flight_orders) > 0: - tracked_orders = list(self._in_flight_orders.values()) - - tasks = [] - for tracked_order in tracked_orders: - order_id = await tracked_order.get_exchange_order_id() - tasks.append(self._api_request("post", - "perpfi/receipt", - {"txHash": order_id})) - update_results = await safe_gather(*tasks, return_exceptions=True) - for update_result, tracked_order in zip(update_results, tracked_orders): - self.logger().info(f"Polling for order status updates of {len(tasks)} orders.") - if isinstance(update_result, Exception): - raise update_result - if "txHash" not in update_result: - self.logger().info(f"_update_order_status txHash not in resp: {update_result}") - continue - if update_result["confirmed"] is True: - if update_result["receipt"]["status"] == 1: - fee = build_perpetual_trade_fee( - exchange=self.name, - is_maker=True, - position_action=PositionAction[tracked_order.position], - base_currency=tracked_order.base_asset, - quote_currency=tracked_order.quote_asset, - order_type=tracked_order.order_type, - order_side=tracked_order.trade_type, - amount=tracked_order.amount, - price=tracked_order.price, - ) - fee.flat_fees.append(TokenAmount("XDAI", Decimal(str(update_result["receipt"]["gasUsed"])))) - self.trigger_event( - MarketEvent.OrderFilled, - OrderFilledEvent( - self.current_timestamp, - tracked_order.client_order_id, - tracked_order.trading_pair, - tracked_order.trade_type, - tracked_order.order_type, - Decimal(str(tracked_order.price)), - Decimal(str(tracked_order.amount)), - fee, - exchange_trade_id=order_id, - leverage=self._leverage[tracked_order.trading_pair], - position=tracked_order.position - ) + if len(self._in_flight_orders) <= 0: + return + tracked_orders = list(self._in_flight_orders.values()) + + tasks = [] + for tracked_order in tracked_orders: + order_id = await tracked_order.get_exchange_order_id() + tasks.append(self._api_request("post", + "perpfi/receipt", + {"txHash": order_id})) + update_results = await safe_gather(*tasks, return_exceptions=True) + for update_result, tracked_order in zip(update_results, tracked_orders): + self.logger().info(f"Polling for order status updates of {len(tasks)} orders.") + if isinstance(update_result, Exception): + raise update_result + if "txHash" not in update_result: + self.logger().info(f"_update_order_status txHash not in resp: {update_result}") + continue + if update_result["confirmed"] is True: + if update_result["receipt"]["status"] == 1: + fee = build_perpetual_trade_fee( + exchange=self.name, + is_maker=True, + position_action=PositionAction[tracked_order.position], + base_currency=tracked_order.base_asset, + quote_currency=tracked_order.quote_asset, + order_type=tracked_order.order_type, + order_side=tracked_order.trade_type, + amount=tracked_order.amount, + price=tracked_order.price, + ) + fee.flat_fees.append(TokenAmount("XDAI", Decimal(str(update_result["receipt"]["gasUsed"])))) + self.trigger_event( + MarketEvent.OrderFilled, + OrderFilledEvent( + self.current_timestamp, + tracked_order.client_order_id, + tracked_order.trading_pair, + tracked_order.trade_type, + tracked_order.order_type, + Decimal(str(tracked_order.price)), + Decimal(str(tracked_order.amount)), + fee, + exchange_trade_id=order_id, + leverage=self._leverage[tracked_order.trading_pair], + position=tracked_order.position ) - tracked_order.last_state = "FILLED" - self.logger().info(f"The {tracked_order.trade_type.name} order " - f"{tracked_order.client_order_id} has completed " - f"according to order status API.") - event_tag = MarketEvent.BuyOrderCompleted if tracked_order.trade_type is TradeType.BUY \ - else MarketEvent.SellOrderCompleted - event_class = BuyOrderCompletedEvent if tracked_order.trade_type is TradeType.BUY \ - else SellOrderCompletedEvent - self.trigger_event(event_tag, - event_class(self.current_timestamp, - tracked_order.client_order_id, - tracked_order.base_asset, - tracked_order.quote_asset, - tracked_order.fee_asset, - tracked_order.executed_amount_base, - tracked_order.executed_amount_quote, - float(fee.fee_amount_in_quote(tracked_order.trading_pair, - Decimal(str(tracked_order.price)), - Decimal(str(tracked_order.amount)), - self)), # this ignores the gas fee, which is fine for now - tracked_order.order_type)) - self.stop_tracking_order(tracked_order.client_order_id) - else: - self.logger().info( - f"The market order {tracked_order.client_order_id} has failed according to order status API. ") - self.trigger_event(MarketEvent.OrderFailure, - MarketOrderFailureEvent( - self.current_timestamp, - tracked_order.client_order_id, - tracked_order.order_type - )) - self.stop_tracking_order(tracked_order.client_order_id) + ) + tracked_order.last_state = "FILLED" + self.logger().info(f"The {tracked_order.trade_type.name} order " + f"{tracked_order.client_order_id} has completed " + f"according to order status API.") + event_tag = MarketEvent.BuyOrderCompleted if tracked_order.trade_type is TradeType.BUY \ + else MarketEvent.SellOrderCompleted + event_class = BuyOrderCompletedEvent if tracked_order.trade_type is TradeType.BUY \ + else SellOrderCompletedEvent + self.trigger_event(event_tag, + event_class(self.current_timestamp, + tracked_order.client_order_id, + tracked_order.base_asset, + tracked_order.quote_asset, + tracked_order.fee_asset, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + float(fee.fee_amount_in_quote(tracked_order.trading_pair, + Decimal(str(tracked_order.price)), + Decimal(str(tracked_order.amount)), + self)), # this ignores the gas fee, which is fine for now + tracked_order.order_type)) + else: + self.logger().info( + f"The market order {tracked_order.client_order_id} has failed according to order status API. ") + self.trigger_event(MarketEvent.OrderFailure, + MarketOrderFailureEvent( + self.current_timestamp, + tracked_order.client_order_id, + tracked_order.order_type + )) + + self.stop_tracking_order(tracked_order.client_order_id) def get_taker_order_type(self): return OrderType.LIMIT @@ -472,9 +473,12 @@ def tick(self, timestamp: float): Is called automatically by the clock for each clock's tick (1 second by default). It checks if status polling task is due for execution. """ - if time.time() - self._last_poll_timestamp > self.POLL_INTERVAL: - if self._poll_notifier is not None and not self._poll_notifier.is_set(): - self._poll_notifier.set() + if ( + time.time() - self._last_poll_timestamp > self.POLL_INTERVAL + and self._poll_notifier is not None + and not self._poll_notifier.is_set() + ): + self._poll_notifier.set() async def _status_polling_loop(self): while True: @@ -523,11 +527,15 @@ async def _update_balances(self): self._in_flight_orders_snapshot_timestamp = self.current_timestamp async def _update_positions(self): - position_tasks = [] - for pair in self._trading_pairs: - position_tasks.append(self._api_request("post", - "perpfi/position", - {"pair": convert_to_exchange_trading_pair(pair)})) + position_tasks = [ + self._api_request( + "post", + "perpfi/position", + {"pair": convert_to_exchange_trading_pair(pair)}, + ) + for pair in self._trading_pairs + ] + positions = await safe_gather(*position_tasks, return_exceptions=True) for trading_pair, position in zip(self._trading_pairs, positions): position = position.get("position", {}) @@ -568,11 +576,15 @@ async def _update_positions(self): async def _funding_info_polling_loop(self): while True: try: - funding_info_tasks = [] - for pair in self._trading_pairs: - funding_info_tasks.append(self._api_request("post", - "perpfi/funding", - {"pair": convert_to_exchange_trading_pair(pair)})) + funding_info_tasks = [ + self._api_request( + "post", + "perpfi/funding", + {"pair": convert_to_exchange_trading_pair(pair)}, + ) + for pair in self._trading_pairs + ] + funding_infos = await safe_gather(*funding_info_tasks, return_exceptions=True) for trading_pair, funding_info in zip(self._trading_pairs, funding_infos): self._funding_info[trading_pair] = FundingInfo( diff --git a/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_order_book_data_source.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_order_book_data_source.py index df836555fc..efa7ce2dfc 100644 --- a/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_order_book_data_source.py +++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_order_book_data_source.py @@ -51,13 +51,11 @@ def __init__(self, shared_client: Optional[aiohttp.ClientSession] = None, thrott @classmethod def _get_session_instance(cls) -> aiohttp.ClientSession: - session = aiohttp.ClientSession() - return session + return aiohttp.ClientSession() @classmethod def _get_throttler_instance(cls) -> AsyncThrottler: - throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) - return throttler + return AsyncThrottler(CONSTANTS.RATE_LIMITS) @classmethod async def get_last_traded_prices( diff --git a/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_user_stream_data_source.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_user_stream_data_source.py index e2a5acf398..e97aa16a0d 100755 --- a/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_user_stream_data_source.py +++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_user_stream_data_source.py @@ -43,13 +43,11 @@ def __init__( @classmethod def _get_session_instance(cls) -> aiohttp.ClientSession: - session = aiohttp.ClientSession() - return session + return aiohttp.ClientSession() @classmethod def _get_throttler_instance(cls) -> AsyncThrottler: - throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) - return throttler + return AsyncThrottler(CONSTANTS.RATE_LIMITS) @property def last_recv_time(self) -> float: diff --git a/hummingbot/connector/exchange/ascend_ex/ascend_ex_exchange.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_exchange.py index fbed20874b..ab0a16406e 100644 --- a/hummingbot/connector/exchange/ascend_ex/ascend_ex_exchange.py +++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_exchange.py @@ -863,9 +863,8 @@ def tick(self, timestamp: float): ) last_tick = int(self._last_timestamp / poll_interval) current_tick = int(timestamp / poll_interval) - if current_tick > last_tick: - if not self._poll_notifier.is_set(): - self._poll_notifier.set() + if current_tick > last_tick and not self._poll_notifier.is_set(): + self._poll_notifier.set() self._last_timestamp = timestamp def get_fee( @@ -882,10 +881,12 @@ def get_fee( trading_pair = f"{base_currency}-{quote_currency}" trading_rule = self._trading_rules[trading_pair] fee_percent = Decimal("0") - if order_side == TradeType.BUY: - if trading_rule.commission_type == AscendExCommissionType.QUOTE: - fee_percent = trading_rule.commission_reserve_rate - elif trading_rule.commission_type == AscendExCommissionType.BASE: + if ( + order_side == TradeType.BUY + and trading_rule.commission_type == AscendExCommissionType.QUOTE + or order_side != TradeType.BUY + and trading_rule.commission_type == AscendExCommissionType.BASE + ): fee_percent = trading_rule.commission_reserve_rate return AddedToCostTradeFee(percent=fee_percent) @@ -913,7 +914,7 @@ async def _user_stream_event_listener(self): if event_message.get("m") == "order": order_data = event_message.get("data") trading_pair = order_data["s"] - base_asset, quote_asset = tuple(asset for asset in trading_pair.split("/")) + base_asset, quote_asset = tuple(trading_pair.split("/")) self._process_order_message( AscendExOrder( trading_pair, @@ -976,17 +977,20 @@ async def get_open_orders(self) -> List[OpenOrder]: ret_val.append( OpenOrder( client_order_id=client_order_id, - trading_pair=ascend_ex_utils.convert_from_exchange_trading_pair(order["symbol"]), + trading_pair=ascend_ex_utils.convert_from_exchange_trading_pair( + order["symbol"] + ), price=Decimal(str(order["price"])), amount=Decimal(str(order["orderQty"])), executed_amount=Decimal(str(order["cumFilledQty"])), status=order["status"], order_type=OrderType.LIMIT, - is_buy=True if order["side"].lower() == "buy" else False, + is_buy=order["side"].lower() == "buy", time=int(order["lastExecTime"]), exchange_order_id=exchange_order_id, ) ) + return ret_val def _process_order_message(self, order_msg: AscendExOrder): diff --git a/hummingbot/connector/exchange/ascend_ex/ascend_ex_order_book_message.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_order_book_message.py index f6cb189916..4573c8f48e 100644 --- a/hummingbot/connector/exchange/ascend_ex/ascend_ex_order_book_message.py +++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_order_book_message.py @@ -39,9 +39,7 @@ def update_id(self) -> int: @property def trade_id(self) -> int: - if self.type is OrderBookMessageType.TRADE: - return int(self.timestamp) - return -1 + return int(self.timestamp) if self.type is OrderBookMessageType.TRADE else -1 @property def trading_pair(self) -> str: @@ -69,8 +67,7 @@ def __eq__(self, other) -> bool: def __lt__(self, other) -> bool: if self.timestamp != other.timestamp: return self.timestamp < other.timestamp - else: - """ + """ If timestamp is the same, the ordering is snapshot < diff < trade """ - return self.type.value < other.type.value + return self.type.value < other.type.value diff --git a/hummingbot/connector/exchange/beaxy/beaxy_api_order_book_data_source.py b/hummingbot/connector/exchange/beaxy/beaxy_api_order_book_data_source.py index 2aa737a9c4..5302dc6e32 100644 --- a/hummingbot/connector/exchange/beaxy/beaxy_api_order_book_data_source.py +++ b/hummingbot/connector/exchange/beaxy/beaxy_api_order_book_data_source.py @@ -67,9 +67,9 @@ async def trading_pair_symbol_map(cls) -> Dict[str, str]: @staticmethod async def exchange_symbol_associated_to_pair(trading_pair: str) -> str: symbol_map = await BeaxyAPIOrderBookDataSource.trading_pair_symbol_map() - symbols = [symbol for symbol, pair in symbol_map.items() if pair == trading_pair] - - if symbols: + if symbols := [ + symbol for symbol, pair in symbol_map.items() if pair == trading_pair + ]: symbol = symbols[0] else: raise ValueError(f"There is no symbol mapping for trading pair {trading_pair}") @@ -114,7 +114,7 @@ async def last_price_for_pair(trading_pair): @staticmethod async def get_snapshot(client: aiohttp.ClientSession, trading_pair: str, depth: int = 20) -> Dict[str, Any]: - assert depth in [5, 10, 20] + assert depth in {5, 10, 20} symbol = await BeaxyAPIOrderBookDataSource.exchange_symbol_associated_to_pair(trading_pair) @@ -247,7 +247,7 @@ async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asynci try: trading_pairs = [await self.exchange_symbol_associated_to_pair(p) for p in self._trading_pairs] - ws_path: str = '/'.join([trading_pair for trading_pair in trading_pairs]) + ws_path: str = '/'.join(list(trading_pairs)) stream_url: str = f'{BeaxyConstants.PublicApi.WS_BASE_URL}/trades/{ws_path}' async with websockets.connect(stream_url) as ws: diff --git a/hummingbot/connector/exchange/beaxy/beaxy_constants.py b/hummingbot/connector/exchange/beaxy/beaxy_constants.py index 5077903fc9..60029123c9 100644 --- a/hummingbot/connector/exchange/beaxy/beaxy_constants.py +++ b/hummingbot/connector/exchange/beaxy/beaxy_constants.py @@ -2,6 +2,7 @@ class BeaxyConstants: + class UserStream: ORDER_MESSAGE = 'ORDER' BALANCE_MESSAGE = 'BALANCE' @@ -22,11 +23,13 @@ class TradingApi: WS_ORDERS_ENDPOINT = WS_BASE_URL + '/ws/v2/orders?access_token={access_token}' WS_BALANCE_ENDPOINT = WS_BASE_URL + '/ws/v2/wallets?access_token={access_token}' + + class PublicApi: BASE_URL = 'https://services.beaxy.com' - SYMBOLS_URL = BASE_URL + '/api/v2/symbols' + SYMBOLS_URL = f'{BASE_URL}/api/v2/symbols' RATE_URL = BASE_URL + '/api/v2/symbols/{symbol}/rate' - RATES_URL = BASE_URL + '/api/v2/symbols/rates' + RATES_URL = f'{BASE_URL}/api/v2/symbols/rates' ORDER_BOOK_URL = BASE_URL + '/api/v2/symbols/{symbol}/book?depth={depth}' WS_BASE_URL = 'wss://services.beaxy.com/ws/v2' diff --git a/hummingbot/connector/exchange/beaxy/beaxy_order_book_message.py b/hummingbot/connector/exchange/beaxy/beaxy_order_book_message.py index 1839f42ef9..de4eb62098 100644 --- a/hummingbot/connector/exchange/beaxy/beaxy_order_book_message.py +++ b/hummingbot/connector/exchange/beaxy/beaxy_order_book_message.py @@ -14,10 +14,8 @@ def __new__( *args, **kwargs, ): - if timestamp is None: - if message_type is OrderBookMessageType.SNAPSHOT: - raise ValueError('timestamp must not be None when initializing snapshot messages.') - timestamp = int(time.time()) + if timestamp is None and message_type is OrderBookMessageType.SNAPSHOT: + raise ValueError('timestamp must not be None when initializing snapshot messages.') return super(BeaxyOrderBookMessage, cls).__new__( cls, message_type, content, timestamp=timestamp, *args, **kwargs ) @@ -60,8 +58,7 @@ def __eq__(self, other) -> bool: def __lt__(self, other) -> bool: if self.timestamp != other.timestamp: return self.timestamp < other.timestamp - else: - """ + """ If timestamp is the same, the ordering is snapshot < diff < trade """ - return self.type.value < other.type.value + return self.type.value < other.type.value diff --git a/hummingbot/connector/exchange/beaxy/beaxy_order_book_tracker.py b/hummingbot/connector/exchange/beaxy/beaxy_order_book_tracker.py index 3ba8c17580..1b57119e24 100644 --- a/hummingbot/connector/exchange/beaxy/beaxy_order_book_tracker.py +++ b/hummingbot/connector/exchange/beaxy/beaxy_order_book_tracker.py @@ -103,8 +103,12 @@ async def _refresh_tracking_tasks(self): """ Starts tracking for any new trading pairs, and stop tracking for any inactive trading pairs. """ - tracking_trading_pairs: Set[str] = set([key for key in self._tracking_tasks.keys() - if not self._tracking_tasks[key].done()]) + tracking_trading_pairs: Set[str] = { + key + for key in self._tracking_tasks.keys() + if not self._tracking_tasks[key].done() + } + available_pairs: Dict[str, BeaxyOrderBookTrackerEntry] = await self.data_source.get_tracking_pairs() available_trading_pairs: Set[str] = set(available_pairs.keys()) new_trading_pairs: Set[str] = available_trading_pairs - tracking_trading_pairs diff --git a/hummingbot/connector/exchange/binance/binance_api_order_book_data_source.py b/hummingbot/connector/exchange/binance/binance_api_order_book_data_source.py index 8b392a1ab9..2c768a9828 100755 --- a/hummingbot/connector/exchange/binance/binance_api_order_book_data_source.py +++ b/hummingbot/connector/exchange/binance/binance_api_order_book_data_source.py @@ -89,7 +89,7 @@ async def get_last_traded_prices(cls, local_throttler = throttler or cls._get_throttler_instance() tasks = [cls._get_last_traded_price(t_pair, domain, rest_assistant, local_throttler) for t_pair in trading_pairs] results = await safe_gather(*tasks) - return {t_pair: result for t_pair, result in zip(trading_pairs, results)} + return dict(zip(trading_pairs, results)) @staticmethod @async_ttl_cache(ttl=2, maxsize=1) @@ -423,8 +423,7 @@ async def _subscribe_channels(self, ws: WSAssistant): @classmethod def _get_throttler_instance(cls) -> AsyncThrottler: - throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) - return throttler + return AsyncThrottler(CONSTANTS.RATE_LIMITS) @classmethod async def _get_last_traded_price(cls, diff --git a/hummingbot/connector/exchange/binance/binance_api_user_stream_data_source.py b/hummingbot/connector/exchange/binance/binance_api_user_stream_data_source.py index 1478802c7b..8b19974072 100755 --- a/hummingbot/connector/exchange/binance/binance_api_user_stream_data_source.py +++ b/hummingbot/connector/exchange/binance/binance_api_user_stream_data_source.py @@ -63,9 +63,7 @@ def last_recv_time(self) -> float: Returns the time of the last received message :return: the timestamp of the last received message in seconds """ - if self._ws_assistant: - return self._ws_assistant.last_recv_time - return -1 + return self._ws_assistant.last_recv_time if self._ws_assistant else -1 async def listen_for_user_stream(self, ev_loop: asyncio.AbstractEventLoop, output: asyncio.Queue): """ diff --git a/hummingbot/connector/exchange/binance/binance_auth.py b/hummingbot/connector/exchange/binance/binance_auth.py index a9ae8f4789..4f2c1f7f6a 100644 --- a/hummingbot/connector/exchange/binance/binance_auth.py +++ b/hummingbot/connector/exchange/binance/binance_auth.py @@ -64,5 +64,8 @@ def header_for_authentication(self) -> Dict[str, str]: def _generate_signature(self, params: Dict[str, Any]) -> str: encoded_params_str = urlencode(params) - digest = hmac.new(self.secret_key.encode("utf8"), encoded_params_str.encode("utf8"), hashlib.sha256).hexdigest() - return digest + return hmac.new( + self.secret_key.encode("utf8"), + encoded_params_str.encode("utf8"), + hashlib.sha256, + ).hexdigest() diff --git a/hummingbot/connector/exchange/binance/binance_exchange.py b/hummingbot/connector/exchange/binance/binance_exchange.py index 17af8fb72d..3008bd58c5 100755 --- a/hummingbot/connector/exchange/binance/binance_exchange.py +++ b/hummingbot/connector/exchange/binance/binance_exchange.py @@ -109,10 +109,7 @@ def logger(cls) -> HummingbotLogger: @property def name(self) -> str: - if self._domain == "com": - return "binance" - else: - return f"binance_{self._domain}" + return "binance" if self._domain == "com" else f"binance_{self._domain}" @property def order_books(self) -> Dict[str, OrderBook]: @@ -255,9 +252,8 @@ def tick(self, timestamp: float): last_tick = int(self._last_timestamp / poll_interval) current_tick = int(timestamp / poll_interval) - if current_tick > last_tick: - if not self._poll_notifier.is_set(): - self._poll_notifier.set() + if current_tick > last_tick and not self._poll_notifier.is_set(): + self._poll_notifier.set() self._last_timestamp = timestamp def get_order_book(self, trading_pair: str) -> OrderBook: @@ -426,7 +422,7 @@ async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: """ incomplete_orders = [o for o in self.in_flight_orders.values() if not o.is_done] tasks = [self._execute_cancel(o.trading_pair, o.client_order_id) for o in incomplete_orders] - order_id_set = set([o.client_order_id for o in incomplete_orders]) + order_id_set = {o.client_order_id for o in incomplete_orders} successful_cancellations = [] try: @@ -778,9 +774,10 @@ async def _update_order_fills_from_trades(self): or (self.in_flight_orders and small_interval_current_tick > small_interval_last_tick)): query_time = int(self._last_trades_poll_binance_timestamp * 1e3) self._last_trades_poll_binance_timestamp = self._binance_time_synchronizer.time() - order_by_exchange_id_map = {} - for order in self._order_tracker.all_orders.values(): - order_by_exchange_id_map[order.exchange_order_id] = order + order_by_exchange_id_map = { + order.exchange_order_id: order + for order in self._order_tracker.all_orders.values() + } tasks = [] trading_pairs = self._order_book_tracker._trading_pairs @@ -865,7 +862,7 @@ async def _update_order_status(self): current_tick = self.current_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL tracked_orders: List[InFlightOrder] = list(self.in_flight_orders.values()) - if current_tick > last_tick and len(tracked_orders) > 0: + if current_tick > last_tick and tracked_orders: tasks = [self._api_request( method=RESTMethod.GET, diff --git a/hummingbot/connector/exchange/binance/binance_order_book_tracker.py b/hummingbot/connector/exchange/binance/binance_order_book_tracker.py index 4019dfc95e..68e1e6db88 100644 --- a/hummingbot/connector/exchange/binance/binance_order_book_tracker.py +++ b/hummingbot/connector/exchange/binance/binance_order_book_tracker.py @@ -52,10 +52,7 @@ def logger(cls) -> HummingbotLogger: @property def exchange_name(self) -> str: - if self._domain == "com": - return "binance" - else: - return f"binance_{self._domain}" + return "binance" if self._domain == "com" else f"binance_{self._domain}" def start(self): """ diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_api_order_book_data_source.py b/hummingbot/connector/exchange/bitfinex/bitfinex_api_order_book_data_source.py index b6c7016462..8d7e3798eb 100644 --- a/hummingbot/connector/exchange/bitfinex/bitfinex_api_order_book_data_source.py +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_api_order_book_data_source.py @@ -125,7 +125,7 @@ def _convert_volume(raw_prices: Dict[str, Any]) -> BOOK_RET_TYPE: converters[price["baseAsset"]] = price["price"] del raw_prices[raw_symbol] - for raw_symbol, item in raw_prices.items(): + for item in raw_prices.values(): symbol = f"{item['baseAsset']}{item['quoteAsset']}" if item["baseAsset"] in converters: prices.append( @@ -147,8 +147,7 @@ def _convert_volume(raw_prices: Dict[str, Any]) -> BOOK_RET_TYPE: "USDVolume": item["volume"] * item["price"] * converters[item["quoteAsset"]] } ) - if item["baseAsset"] not in converters: - converters[item["baseAsset"]] = item["price"] * converters[item["quoteAsset"]] + converters[item["baseAsset"]] = item["price"] * converters[item["quoteAsset"]] continue prices.append({ @@ -256,7 +255,7 @@ def _parse_raw_update(self, pair: str, raw_response: str) -> OrderBookMessage: async def get_last_traded_prices(cls, trading_pairs: List[str]) -> Dict[str, float]: tasks = [cls.get_last_traded_price(t_pair) for t_pair in trading_pairs] results = await safe_gather(*tasks) - return {t_pair: result for t_pair, result in zip(trading_pairs, results)} + return dict(zip(trading_pairs, results)) @classmethod async def get_last_traded_price(cls, trading_pair: str) -> float: @@ -397,8 +396,7 @@ async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asynci await asyncio.wait_for(ws.recv(), timeout=self.MESSAGE_TIMEOUT) # snapshot async for raw_msg in self._get_response(ws): - msg = self._prepare_trade(raw_msg) - if msg: + if msg := self._prepare_trade(raw_msg): msg_book: OrderBookMessage = BitfinexOrderBook.trade_message_from_exchange( msg, metadata={"symbol": f"{trading_pair}"} diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_auth.py b/hummingbot/connector/exchange/bitfinex/bitfinex_auth.py index 79deb6428a..e18e817e41 100644 --- a/hummingbot/connector/exchange/bitfinex/bitfinex_auth.py +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_auth.py @@ -10,16 +10,15 @@ def __init__(self, api_key: str, secret_key: str): self.last_nonce = 0 def _sign_payload(self, payload) -> str: - sig = hmac.new(self.secret_key.encode('utf8'), + return hmac.new(self.secret_key.encode('utf8'), payload.encode('utf8'), hashlib.sha384).hexdigest() - return sig def get_nonce(self) -> int: nonce = int(round(time.time() * 1_000_000)) if self.last_nonce == nonce: - nonce = nonce + 1 + nonce += 1 elif self.last_nonce > nonce: nonce = self.last_nonce + 1 @@ -49,7 +48,7 @@ def generate_api_headers(self, path, body): Generate headers for a signed payload """ nonce = str(self.get_nonce()) - signature = "/api/" + path + nonce + body + signature = f"/api/{path}{nonce}{body}" sig = self._sign_payload(signature) diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_order_book_tracker.py b/hummingbot/connector/exchange/bitfinex/bitfinex_order_book_tracker.py index d6d90889df..021730f757 100644 --- a/hummingbot/connector/exchange/bitfinex/bitfinex_order_book_tracker.py +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_order_book_tracker.py @@ -61,9 +61,12 @@ async def _refresh_tracking_tasks(self): """ Starts tracking for any new trading pairs, and stop tracking for any inactive trading pairs. """ - tracking_trading_pair: Set[str] = set( - [key for key in self._tracking_tasks.keys() if not self._tracking_tasks[key].done()] - ) + tracking_trading_pair: Set[str] = { + key + for key in self._tracking_tasks.keys() + if not self._tracking_tasks[key].done() + } + available_pairs: Dict[str, BitfinexOrderBookTrackerEntry] = await self.data_source.get_tracking_pairs() available_trading_pair: Set[str] = set(available_pairs.keys()) new_trading_pair: Set[str] = available_trading_pair - tracking_trading_pair diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_utils.py b/hummingbot/connector/exchange/bitfinex/bitfinex_utils.py index dee0706b6f..e5c7639b06 100644 --- a/hummingbot/connector/exchange/bitfinex/bitfinex_utils.py +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_utils.py @@ -120,8 +120,8 @@ def convert_to_exchange_trading_pair(hb_trading_pair: str) -> str: base_asset = convert_to_exchange_token(base_asset) quote_asset = convert_to_exchange_token(quote_asset) - if len(base_asset) > 3: # Adds ':' delimiter if base asset > 3 characters - trading_pair = f"t{base_asset}:{quote_asset}" - else: - trading_pair = f"t{base_asset}{quote_asset}" - return trading_pair + return ( + f"t{base_asset}:{quote_asset}" + if len(base_asset) > 3 + else f"t{base_asset}{quote_asset}" + ) diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_websocket.py b/hummingbot/connector/exchange/bitfinex/bitfinex_websocket.py index b024fc72ed..e350bd138b 100644 --- a/hummingbot/connector/exchange/bitfinex/bitfinex_websocket.py +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_websocket.py @@ -29,7 +29,7 @@ def logger(cls) -> HummingbotLogger: def __init__(self, auth: Optional[BitfinexAuth]): self._client = None self._auth = auth - self._consumers = dict() + self._consumers = {} self._listening = False # connect to exchange @@ -80,8 +80,7 @@ async def _listen_to_ws(self) -> AsyncIterable[Any]: # listen to consumer's queue updates async def _listen_to_queue(self, consumer_id: str) -> AsyncIterable[Any]: try: - msg = self._consumers[consumer_id].get_nowait() - yield msg + yield self._consumers[consumer_id].get_nowait() except asyncio.QueueEmpty: yield None except Exception as e: diff --git a/hummingbot/connector/exchange/bitmart/bitmart_api_order_book_data_source.py b/hummingbot/connector/exchange/bitmart/bitmart_api_order_book_data_source.py index 061f34ffa3..674acaec49 100644 --- a/hummingbot/connector/exchange/bitmart/bitmart_api_order_book_data_source.py +++ b/hummingbot/connector/exchange/bitmart/bitmart_api_order_book_data_source.py @@ -48,8 +48,7 @@ def __init__(self, @classmethod def _get_throttler_instance(cls) -> AsyncThrottler: - throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) - return throttler + return AsyncThrottler(CONSTANTS.RATE_LIMITS) async def _get_rest_assistant(self) -> RESTAssistant: if self._rest_assistant is None: diff --git a/hummingbot/connector/exchange/bitmart/bitmart_api_user_stream_data_source.py b/hummingbot/connector/exchange/bitmart/bitmart_api_user_stream_data_source.py index 6a9b5b7d6c..d8c89cf823 100755 --- a/hummingbot/connector/exchange/bitmart/bitmart_api_user_stream_data_source.py +++ b/hummingbot/connector/exchange/bitmart/bitmart_api_user_stream_data_source.py @@ -31,8 +31,7 @@ def logger(cls) -> HummingbotLogger: @classmethod def _get_throttler_instance(cls) -> AsyncThrottler: - throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) - return throttler + return AsyncThrottler(CONSTANTS.RATE_LIMITS) def __init__( self, @@ -76,7 +75,7 @@ async def _authenticate(self, ws: WSAssistant): auth_resp: Dict[str, Any] = ws_response.data - if "errorCode" in auth_resp.keys(): + if "errorCode" in auth_resp: self.logger().error(f"WebSocket login errored with message: {auth_resp['errorMessage']}", exc_info=True) raise ConnectionError diff --git a/hummingbot/connector/exchange/bitmart/bitmart_auth.py b/hummingbot/connector/exchange/bitmart/bitmart_auth.py index 18c0c79a72..10b3a99404 100755 --- a/hummingbot/connector/exchange/bitmart/bitmart_auth.py +++ b/hummingbot/connector/exchange/bitmart/bitmart_auth.py @@ -25,10 +25,15 @@ def get_headers( :return: a dictionary of auth headers """ - if auth_type == "SIGNED": + if auth_type == "KEYED": + return { + "Content-Type": 'application/json', + "X-BM-KEY": self.api_key, + } + elif auth_type == "SIGNED": params = json.dumps(params) - payload = f'{str(timestamp)}#{self.memo}#{params}' + payload = f'{timestamp}#{self.memo}#{params}' sign = hmac.new( self.secret_key.encode('utf-8'), @@ -43,12 +48,6 @@ def get_headers( "X-BM-TIMESTAMP": str(timestamp), } - elif auth_type == "KEYED": - return { - "Content-Type": 'application/json', - "X-BM-KEY": self.api_key, - } - else: return { "Content-Type": 'application/json', @@ -60,7 +59,7 @@ def get_ws_auth_payload(self, timestamp: int = None): :return: a dictionary of auth headers with api_key, timestamp, signature """ - payload = f'{str(timestamp)}#{self.memo}#bitmart.WebSocket' + payload = f'{timestamp}#{self.memo}#bitmart.WebSocket' sign = hmac.new( self.secret_key.encode('utf-8'), diff --git a/hummingbot/connector/exchange/bitmart/bitmart_exchange.py b/hummingbot/connector/exchange/bitmart/bitmart_exchange.py index 086271a2e7..22001f8bac 100644 --- a/hummingbot/connector/exchange/bitmart/bitmart_exchange.py +++ b/hummingbot/connector/exchange/bitmart/bitmart_exchange.py @@ -841,9 +841,8 @@ def tick(self, timestamp: float): """ last_tick = int(self._last_timestamp / self.POLL_INTERVAL) current_tick = int(timestamp / self.POLL_INTERVAL) - if current_tick > last_tick: - if not self._poll_notifier.is_set(): - self._poll_notifier.set() + if current_tick > last_tick and not self._poll_notifier.is_set(): + self._poll_notifier.set() self._last_timestamp = timestamp def get_fee(self, @@ -933,15 +932,18 @@ async def get_open_orders(self) -> List[OpenOrder]: ret_val.append( OpenOrder( client_order_id=tracked_order.client_order_id, - trading_pair=bitmart_utils.convert_from_exchange_trading_pair(order["symbol"]), + trading_pair=bitmart_utils.convert_from_exchange_trading_pair( + order["symbol"] + ), price=Decimal(str(order["price"])), amount=Decimal(str(order["size"])), executed_amount=Decimal(str(order["filled_size"])), status=CONSTANTS.ORDER_STATUS[int(order["status"])], order_type=OrderType.LIMIT, - is_buy=True if order["side"].lower() == "buy" else False, + is_buy=order["side"].lower() == "buy", time=int(order["create_time"]), - exchange_order_id=str(order["order_id"]) + exchange_order_id=str(order["order_id"]), ) ) + return ret_val diff --git a/hummingbot/connector/exchange/bitmart/bitmart_order_book_message.py b/hummingbot/connector/exchange/bitmart/bitmart_order_book_message.py index 311bfb57aa..8d4cc3d82e 100644 --- a/hummingbot/connector/exchange/bitmart/bitmart_order_book_message.py +++ b/hummingbot/connector/exchange/bitmart/bitmart_order_book_message.py @@ -33,9 +33,7 @@ def update_id(self) -> int: @property def trade_id(self) -> int: - if self.type is OrderBookMessageType.TRADE: - return int(self.timestamp) - return -1 + return int(self.timestamp) if self.type is OrderBookMessageType.TRADE else -1 @property def trading_pair(self) -> str: @@ -76,11 +74,10 @@ def __eq__(self, other) -> bool: def __lt__(self, other) -> bool: if self.timestamp != other.timestamp: return self.timestamp < other.timestamp - else: - """ + """ If timestamp is the same, the ordering is snapshot < diff < trade """ - return self.type.value < other.type.value + return self.type.value < other.type.value def __hash__(self) -> int: return hash((self.type, self.timestamp)) diff --git a/hummingbot/connector/exchange/bitmart/bitmart_utils.py b/hummingbot/connector/exchange/bitmart/bitmart_utils.py index 356a070fe9..1f013b4663 100644 --- a/hummingbot/connector/exchange/bitmart/bitmart_utils.py +++ b/hummingbot/connector/exchange/bitmart/bitmart_utils.py @@ -129,24 +129,22 @@ def get_new_client_order_id(is_buy: bool, trading_pair: str) -> str: # Decompress WebSocket messages def decompress_ws_message(message): - if type(message) == bytes: - decompress = zlib.decompressobj(-zlib.MAX_WBITS) - inflated = decompress.decompress(message) - inflated += decompress.flush() - return inflated.decode('UTF-8') - else: + if type(message) != bytes: return message + decompress = zlib.decompressobj(-zlib.MAX_WBITS) + inflated = decompress.decompress(message) + inflated += decompress.flush() + return inflated.decode('UTF-8') def compress_ws_message(message): - if type(message) == str: - message = message.encode() - compress = zlib.compressobj(wbits=-zlib.MAX_WBITS) - deflated = compress.compress(message) - deflated += compress.flush() - return deflated - else: + if type(message) != str: return message + message = message.encode() + compress = zlib.compressobj(wbits=-zlib.MAX_WBITS) + deflated = compress.compress(message) + deflated += compress.flush() + return deflated KEYS = { @@ -172,5 +170,4 @@ def compress_ws_message(message): def build_api_factory() -> WebAssistantsFactory: - api_factory = WebAssistantsFactory() - return api_factory + return WebAssistantsFactory() diff --git a/hummingbot/connector/exchange/bittrex/bittrex_api_order_book_data_source.py b/hummingbot/connector/exchange/bittrex/bittrex_api_order_book_data_source.py index f66ef7e2f1..03f2b6406e 100644 --- a/hummingbot/connector/exchange/bittrex/bittrex_api_order_book_data_source.py +++ b/hummingbot/connector/exchange/bittrex/bittrex_api_order_book_data_source.py @@ -54,7 +54,7 @@ def __init__(self, trading_pairs: List[str]): @classmethod async def get_last_traded_prices(cls, trading_pairs: List[str]) -> Dict[str, float]: - results = dict() + results = {} async with aiohttp.ClientSession() as client: resp = await client.get(f"{BITTREX_REST_URL}{BITTREX_TICKER_PATH}") resp_json = await resp.json() @@ -175,8 +175,7 @@ async def _checked_socket_stream(self, connection: signalr_aio.Connection) -> As try: while True: async with timeout(MESSAGE_TIMEOUT): # Timeouts if not receiving any messages for 10 seconds(ping) - msg = await connection.msg_queue.get() - yield msg + yield await connection.msg_queue.get() except asyncio.TimeoutError: self.logger().warning("Message queue get() timed out. Going to reconnect...") diff --git a/hummingbot/connector/exchange/bittrex/bittrex_order_book_message.py b/hummingbot/connector/exchange/bittrex/bittrex_order_book_message.py index 52fc881ccb..0370d2b521 100644 --- a/hummingbot/connector/exchange/bittrex/bittrex_order_book_message.py +++ b/hummingbot/connector/exchange/bittrex/bittrex_order_book_message.py @@ -65,8 +65,7 @@ def __eq__(self, other) -> bool: def __lt__(self, other) -> bool: if self.timestamp != other.timestamp: return self.timestamp < other.timestamp - else: - """ + """ If timestamp is the same, the ordering is snapshot < diff < trade """ - return self.type.value < other.type.value + return self.type.value < other.type.value diff --git a/hummingbot/connector/exchange/bittrex/bittrex_order_book_tracker.py b/hummingbot/connector/exchange/bittrex/bittrex_order_book_tracker.py index 92a3aec4ea..4e96e88059 100644 --- a/hummingbot/connector/exchange/bittrex/bittrex_order_book_tracker.py +++ b/hummingbot/connector/exchange/bittrex/bittrex_order_book_tracker.py @@ -58,9 +58,12 @@ async def _refresh_tracking_tasks(self): """ Starts tracking for any new trading pairs, and stop tracking for any inactive trading pairs. """ - tracking_trading_pair: Set[str] = set( - [key for key in self._tracking_tasks.keys() if not self._tracking_tasks[key].done()] - ) + tracking_trading_pair: Set[str] = { + key + for key in self._tracking_tasks.keys() + if not self._tracking_tasks[key].done() + } + available_pairs: Dict[str, BittrexOrderBookTrackerEntry] = await self.data_source.get_tracking_pairs() available_trading_pair: Set[str] = set(available_pairs.keys()) new_trading_pair: Set[str] = available_trading_pair - tracking_trading_pair diff --git a/hummingbot/connector/exchange/blocktane/blocktane_api_order_book_data_source.py b/hummingbot/connector/exchange/blocktane/blocktane_api_order_book_data_source.py index cdf4d55c32..763411946a 100644 --- a/hummingbot/connector/exchange/blocktane/blocktane_api_order_book_data_source.py +++ b/hummingbot/connector/exchange/blocktane/blocktane_api_order_book_data_source.py @@ -118,8 +118,7 @@ async def get_new_order_book(self, trading_pair: str) -> OrderBook: return order_book def get_ws_connection(self, stream_url): - ws = websockets.connect(stream_url) - return ws + return websockets.connect(stream_url) async def _inner_messages(self, ws: websockets.WebSocketClientProtocol) -> AsyncIterable[str]: # Terminate the recv() loop as soon as the next message timed out, so the outer loop can reconnect. diff --git a/hummingbot/connector/exchange/blocktane/blocktane_api_user_stream_data_source.py b/hummingbot/connector/exchange/blocktane/blocktane_api_user_stream_data_source.py index fb907fdc74..908b05bc8d 100644 --- a/hummingbot/connector/exchange/blocktane/blocktane_api_user_stream_data_source.py +++ b/hummingbot/connector/exchange/blocktane/blocktane_api_user_stream_data_source.py @@ -72,5 +72,6 @@ async def _inner_messages(self, ws) -> AsyncIterable[str]: self._last_recv_time = time.time() def get_ws_connection(self): - ws = websockets.connect(WS_BASE_URL, extra_headers=self._blocktane_auth.generate_auth_dict()) - return ws + return websockets.connect( + WS_BASE_URL, extra_headers=self._blocktane_auth.generate_auth_dict() + ) diff --git a/hummingbot/connector/exchange/blocktane/blocktane_auth.py b/hummingbot/connector/exchange/blocktane/blocktane_auth.py index 38b2d44ac0..79c2c105d7 100644 --- a/hummingbot/connector/exchange/blocktane/blocktane_auth.py +++ b/hummingbot/connector/exchange/blocktane/blocktane_auth.py @@ -15,21 +15,19 @@ def generate_auth_dict(self) -> dict: nonce = self.make_nonce() signature = self.auth_sig(nonce) - payload = { + return { "X-Auth-Apikey": self.api_key, "X-Auth-Nonce": nonce, "X-Auth-Signature": signature, "Content-Type": "application/json" } - return payload def make_nonce(self) -> str: return str(round(time.time() * 1000)) def auth_sig(self, nonce: str) -> str: - sig = hmac.new( + return hmac.new( self.secret_key.encode('utf8'), '{}{}'.format(nonce, self.api_key).encode('utf8'), hashlib.sha256 ).hexdigest() - return sig diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_api_order_book_data_source.py b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_api_order_book_data_source.py index 97ab6094e8..d131797dba 100755 --- a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_api_order_book_data_source.py +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_api_order_book_data_source.py @@ -60,7 +60,7 @@ def __init__( async def get_last_traded_prices(cls, trading_pairs: List[str]) -> Dict[str, Decimal]: tasks = [cls.get_last_traded_price(t_pair) for t_pair in trading_pairs] results = await safe_gather(*tasks) - return {t_pair: result for t_pair, result in zip(trading_pairs, results)} + return dict(zip(trading_pairs, results)) @classmethod async def get_last_traded_price(cls, trading_pair: str) -> Decimal: @@ -83,9 +83,7 @@ async def fetch_trading_pairs() -> List[str]: if response.status == 200: markets = await response.json() raw_trading_pairs: List[str] = list(map(lambda details: details.get('id'), markets)) - trading_pair_list: List[str] = [] - for raw_trading_pair in raw_trading_pairs: - trading_pair_list.append(raw_trading_pair) + trading_pair_list: List[str] = list(raw_trading_pairs) except Exception: # Do nothing if the request fails -- there will be no autocomplete for coinbase trading pairs pass @@ -176,8 +174,7 @@ async def _iter_messages(self, ws: WSAssistant) -> AsyncIterable[Dict]: # Terminate the recv() loop as soon as the next message timed out, so the outer loop can reconnect. try: async for response in ws.iter_messages(): - msg = response.data - yield msg + yield response.data except asyncio.TimeoutError: self.logger().warning("WebSocket ping timed out. Going to reconnect...") finally: @@ -212,13 +209,13 @@ async def listen_for_order_book_diffs(self, ev_loop: asyncio.AbstractEventLoop, raise ValueError(f"Coinbase Pro Websocket message does not contain a type - {msg}") elif msg_type == "error": raise ValueError(f"Coinbase Pro Websocket received error message - {msg['message']}") - elif msg_type in ["open", "match", "change", "done"]: + elif msg_type in {"open", "match", "change", "done"}: if msg_type == "done" and "price" not in msg: # done messages with no price are completed market orders which can be ignored continue order_book_message: OrderBookMessage = CoinbaseProOrderBook.diff_message_from_exchange(msg) output.put_nowait(order_book_message) - elif msg_type in ["received", "activate", "subscriptions"]: + elif msg_type in {"received", "activate", "subscriptions"}: # these messages are not needed to track the order book continue else: diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_api_user_stream_data_source.py b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_api_user_stream_data_source.py index 42dad5c308..ca27e28b76 100755 --- a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_api_user_stream_data_source.py +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_api_user_stream_data_source.py @@ -71,12 +71,9 @@ async def listen_for_user_stream(self, ev_loop: asyncio.AbstractEventLoop, outpu raise ValueError(f"Coinbase Pro Websocket message does not contain a type - {msg}") elif msg_type == "error": raise ValueError(f"Coinbase Pro Websocket received error message - {msg['message']}") - elif msg_type in ["open", "match", "change", "done"]: + elif msg_type in {"open", "match", "change", "done"}: output.put_nowait(msg) - elif msg_type in ["received", "activate", "subscriptions"]: - # these messages are not needed to track the order book - pass - else: + elif msg_type not in ["received", "activate", "subscriptions"]: raise ValueError(f"Unrecognized Coinbase Pro Websocket message received - {msg}") except asyncio.CancelledError: self._ws_assistant = None @@ -101,8 +98,7 @@ async def _iter_messages(self, ws: WSAssistant) -> AsyncIterable[Dict]: # Terminate the recv() loop as soon as the next message timed out, so the outer loop can reconnect. try: async for response in ws.iter_messages(): - msg = response.data - yield msg + yield response.data except asyncio.TimeoutError: self.logger().warning("WebSocket ping timed out. Going to reconnect...") finally: diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book_tracker.py b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book_tracker.py index 55d85f5a11..75d7dab423 100644 --- a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book_tracker.py +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book_tracker.py @@ -107,10 +107,11 @@ async def _order_book_diff_router(self): raise except Exception: self.logger().network( - f'{"Unexpected error routing order book messages."}', + 'Unexpected error routing order book messages.', exc_info=True, - app_warning_msg=f'{"Unexpected error routing order book messages. Retrying after 5 seconds."}' + app_warning_msg='Unexpected error routing order book messages. Retrying after 5 seconds.', ) + await asyncio.sleep(5.0) async def _track_single_book(self, trading_pair: str): diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_utils.py b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_utils.py index 70b2d67ea3..4dd42c5cea 100644 --- a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_utils.py +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_utils.py @@ -58,5 +58,4 @@ def build_coinbase_pro_web_assistant_factory( auth: Optional['CoinbaseProAuth'] = None ) -> WebAssistantsFactory: """The web-assistant's composition root.""" - api_factory = WebAssistantsFactory(auth=auth) - return api_factory + return WebAssistantsFactory(auth=auth) diff --git a/hummingbot/connector/exchange/coinzoom/coinzoom_api_order_book_data_source.py b/hummingbot/connector/exchange/coinzoom/coinzoom_api_order_book_data_source.py index 277693a120..ba703011a5 100644 --- a/hummingbot/connector/exchange/coinzoom/coinzoom_api_order_book_data_source.py +++ b/hummingbot/connector/exchange/coinzoom/coinzoom_api_order_book_data_source.py @@ -48,8 +48,7 @@ def _time(self): @classmethod def _get_throttler_instance(cls) -> AsyncThrottler: - throttler = AsyncThrottler(Constants.RATE_LIMITS) - return throttler + return AsyncThrottler(Constants.RATE_LIMITS) @classmethod async def get_last_traded_prices(cls, @@ -62,7 +61,10 @@ async def get_last_traded_prices(cls, throttler=throttler) for trading_pair in trading_pairs: ex_pair: str = convert_to_exchange_trading_pair(trading_pair, True) - ticker: Dict[Any] = list([tic for symbol, tic in tickers.items() if symbol == ex_pair])[0] + ticker: Dict[Any] = [ + tic for symbol, tic in tickers.items() if symbol == ex_pair + ][0] + results[trading_pair]: Decimal = Decimal(str(ticker["last_price"])) return results @@ -74,7 +76,11 @@ async def fetch_trading_pairs(throttler: Optional[AsyncThrottler] = None) -> Lis method="GET", endpoint=Constants.ENDPOINT["SYMBOL"], throttler=throttler) - trading_pairs: List[str] = list([convert_from_exchange_trading_pair(sym["symbol"]) for sym in symbols]) + trading_pairs: List[str] = [ + convert_from_exchange_trading_pair(sym["symbol"]) + for sym in symbols + ] + # Filter out unmatched pairs so nothing breaks return [sym for sym in trading_pairs if sym is not None] except Exception: @@ -131,7 +137,7 @@ async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asynci async for response in ws.on_message(): msg_keys = list(response.keys()) if response is not None else [] - if not Constants.WS_METHODS["TRADES_UPDATE"] in msg_keys: + if Constants.WS_METHODS["TRADES_UPDATE"] not in msg_keys: continue trade: List[Any] = response[Constants.WS_METHODS["TRADES_UPDATE"]] @@ -144,7 +150,6 @@ async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asynci except Exception: self.logger().error("Unexpected error.", exc_info=True) raise - await asyncio.sleep(5.0) finally: await ws.disconnect() diff --git a/hummingbot/connector/exchange/coinzoom/coinzoom_api_user_stream_data_source.py b/hummingbot/connector/exchange/coinzoom/coinzoom_api_user_stream_data_source.py index 9409f33e53..f2a4d661af 100755 --- a/hummingbot/connector/exchange/coinzoom/coinzoom_api_user_stream_data_source.py +++ b/hummingbot/connector/exchange/coinzoom/coinzoom_api_user_stream_data_source.py @@ -67,7 +67,7 @@ async def _listen_to_orders_trades_balances(self) -> AsyncIterable[Any]: msg_keys = list(msg.keys()) if msg is not None else [] - if not any(ws_method in msg_keys for ws_method in event_methods): + if all(ws_method not in msg_keys for ws_method in event_methods): continue yield msg except Exception as e: diff --git a/hummingbot/connector/exchange/coinzoom/coinzoom_auth.py b/hummingbot/connector/exchange/coinzoom/coinzoom_auth.py index 9379f3716b..d29337e2c4 100755 --- a/hummingbot/connector/exchange/coinzoom/coinzoom_auth.py +++ b/hummingbot/connector/exchange/coinzoom/coinzoom_auth.py @@ -22,10 +22,9 @@ def get_headers(self) -> Dict[str, Any]: Generates authentication headers required by CoinZoom :return: a dictionary of auth headers """ - headers = { + return { "Content-Type": "application/json", "Coinzoom-Api-Key": str(self.api_key), "Coinzoom-Api-Secret": str(self.secret_key), "User-Agent": f"hummingbot ZoomMe: {self.username}" } - return headers diff --git a/hummingbot/connector/exchange/coinzoom/coinzoom_exchange.py b/hummingbot/connector/exchange/coinzoom/coinzoom_exchange.py index 0b38ea274d..8711bd5ea7 100644 --- a/hummingbot/connector/exchange/coinzoom/coinzoom_exchange.py +++ b/hummingbot/connector/exchange/coinzoom/coinzoom_exchange.py @@ -603,15 +603,13 @@ async def _update_balances(self): Calls REST API to update total and available balances. """ try: - # Check for in progress balance updates, queue if fetching and none already waiting, otherwise skip. if self._update_balances_fetching: - if not self._update_balances_queued: - self._update_balances_queued = True - await self._update_balances_finished.wait() - self._update_balances_queued = False - self._update_balances_finished = asyncio.Event() - else: + if self._update_balances_queued: return + self._update_balances_queued = True + await self._update_balances_finished.wait() + self._update_balances_queued = False + self._update_balances_finished = asyncio.Event() self._update_balances_fetching = True account_info = await self._api_request("GET", Constants.ENDPOINT["USER_BALANCES"], is_auth_required=True) self._process_balance_message(account_info) @@ -741,8 +739,9 @@ def _process_order_message(self, order_msg: Dict[str, Any]): else: exchange_order_id = str(order_msg["orderId"]) tracked_orders = list(self._in_flight_orders.values()) - track_order = [o for o in tracked_orders if exchange_order_id == o.exchange_order_id] - if track_order: + if track_order := [ + o for o in tracked_orders if exchange_order_id == o.exchange_order_id + ]: tracked_order = track_order[0] # Estimate fee @@ -834,7 +833,7 @@ async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: # if self._trading_pairs is None: # raise Exception("cancel_all can only be used when trading_pairs are specified.") open_orders = [o for o in self._in_flight_orders.values() if not o.is_done] - if len(open_orders) == 0: + if not open_orders: return [] tasks = [self._execute_cancel(o.trading_pair, o.client_order_id) for o in open_orders] cancellation_results = [] @@ -860,9 +859,8 @@ def tick(self, timestamp: float): else Constants.LONG_POLL_INTERVAL) last_tick = int(self._last_timestamp / poll_interval) current_tick = int(timestamp / poll_interval) - if current_tick > last_tick: - if not self._poll_notifier.is_set(): - self._poll_notifier.set() + if current_tick > last_tick and not self._poll_notifier.is_set(): + self._poll_notifier.set() self._last_timestamp = timestamp def get_fee(self, @@ -915,9 +913,10 @@ async def _user_stream_event_listener(self): method: str = method_key[0] - if method == Constants.WS_METHODS["USER_ORDERS"]: - self._process_order_message(event_message[method]) - elif method == Constants.WS_METHODS["USER_ORDERS_CANCEL"]: + if method in [ + Constants.WS_METHODS["USER_ORDERS"], + Constants.WS_METHODS["USER_ORDERS_CANCEL"], + ]: self._process_order_message(event_message[method]) except asyncio.CancelledError: raise @@ -941,7 +940,7 @@ async def get_open_orders(self) -> List[OpenOrder]: exchange_order_id = str(order["id"]) # CoinZoom doesn't support client order ids yet so we must find it from the tracked orders. track_order = [o for o in tracked_orders if exchange_order_id == o.exchange_order_id] - if not track_order or len(track_order) < 1: + if not track_order: # Skip untracked orders continue client_order_id = track_order[0].client_order_id @@ -954,15 +953,19 @@ async def get_open_orders(self) -> List[OpenOrder]: ret_val.append( OpenOrder( client_order_id=client_order_id, - trading_pair=convert_from_exchange_trading_pair(order["symbol"]), + trading_pair=convert_from_exchange_trading_pair( + order["symbol"] + ), price=Decimal(str(order["price"])), amount=Decimal(str(order["quantity"])), executed_amount=Decimal(str(order["cumQuantity"])), status=order["orderStatus"], order_type=OrderType.LIMIT, - is_buy=True if order["orderSide"].lower() == TradeType.BUY.name.lower() else False, + is_buy=order["orderSide"].lower() + == TradeType.BUY.name.lower(), time=str_date_to_ts(order["timestamp"]), - exchange_order_id=order["id"] + exchange_order_id=order["id"], ) ) + return ret_val diff --git a/hummingbot/connector/exchange/coinzoom/coinzoom_order_book_message.py b/hummingbot/connector/exchange/coinzoom/coinzoom_order_book_message.py index d6bc00541d..f15a7872cb 100644 --- a/hummingbot/connector/exchange/coinzoom/coinzoom_order_book_message.py +++ b/hummingbot/connector/exchange/coinzoom/coinzoom_order_book_message.py @@ -39,9 +39,7 @@ def update_id(self) -> int: @property def trade_id(self) -> int: - if self.type is OrderBookMessageType.TRADE: - return self.timestamp - return -1 + return self.timestamp if self.type is OrderBookMessageType.TRADE else -1 @property def trading_pair(self) -> str: @@ -66,8 +64,7 @@ def __eq__(self, other) -> bool: def __lt__(self, other) -> bool: if self.timestamp != other.timestamp: return self.timestamp < other.timestamp - else: - """ + """ If timestamp is the same, the ordering is snapshot < diff < trade """ - return self.type.value < other.type.value + return self.type.value < other.type.value diff --git a/hummingbot/connector/exchange/coinzoom/coinzoom_websocket.py b/hummingbot/connector/exchange/coinzoom/coinzoom_websocket.py index 88283b01d2..0d2aaf552b 100644 --- a/hummingbot/connector/exchange/coinzoom/coinzoom_websocket.py +++ b/hummingbot/connector/exchange/coinzoom/coinzoom_websocket.py @@ -35,7 +35,7 @@ def __init__(self, auth: Optional[CoinzoomAuth] = None): self._throttler = throttler self._auth: Optional[CoinzoomAuth] = auth - self._isPrivate = True if self._auth is not None else False + self._isPrivate = self._auth is not None self._WS_URL = Constants.WS_PRIVATE_URL if self._isPrivate else Constants.WS_PUBLIC_URL self._client: Optional[websockets.WebSocketClientProtocol] = None self._is_subscribed = False @@ -73,9 +73,14 @@ async def _messages(self) -> AsyncIterable[Any]: # Can handle them here if that changes - use `safe_ensure_future`. # Check response for a subscribed/unsubscribed message; - result: List[str] = list([d['result'] - for k, d in msg.items() - if (isinstance(d, dict) and d.get('result') is not None)]) + result: List[str] = [ + d['result'] + for k, d in msg.items() + if ( + isinstance(d, dict) and d.get('result') is not None + ) + ] + if len(result): if result[0] == 'subscribed': diff --git a/hummingbot/connector/exchange/crypto_com/crypto_com_exchange.py b/hummingbot/connector/exchange/crypto_com/crypto_com_exchange.py index 97c3dfa82b..490d262819 100644 --- a/hummingbot/connector/exchange/crypto_com/crypto_com_exchange.py +++ b/hummingbot/connector/exchange/crypto_com/crypto_com_exchange.py @@ -755,9 +755,8 @@ def tick(self, timestamp: float): else self.LONG_POLL_INTERVAL) last_tick = int(self._last_timestamp / poll_interval) current_tick = int(timestamp / poll_interval) - if current_tick > last_tick: - if not self._poll_notifier.is_set(): - self._poll_notifier.set() + if current_tick > last_tick and not self._poll_notifier.is_set(): + self._poll_notifier.set() self._last_timestamp = timestamp def get_fee(self, @@ -834,15 +833,18 @@ async def get_open_orders(self) -> List[OpenOrder]: ret_val.append( OpenOrder( client_order_id=order["client_oid"], - trading_pair=crypto_com_utils.convert_from_exchange_trading_pair(order["instrument_name"]), + trading_pair=crypto_com_utils.convert_from_exchange_trading_pair( + order["instrument_name"] + ), price=Decimal(str(order["price"])), amount=Decimal(str(order["quantity"])), executed_amount=Decimal(str(order["cumulative_quantity"])), status=order["status"], order_type=OrderType.LIMIT, - is_buy=True if order["side"].lower() == "buy" else False, + is_buy=order["side"].lower() == "buy", time=int(order["create_time"]), - exchange_order_id=order["order_id"] + exchange_order_id=order["order_id"], ) ) + return ret_val diff --git a/hummingbot/connector/exchange/crypto_com/crypto_com_order_book_message.py b/hummingbot/connector/exchange/crypto_com/crypto_com_order_book_message.py index 6b050ea920..bcfc6577bd 100644 --- a/hummingbot/connector/exchange/crypto_com/crypto_com_order_book_message.py +++ b/hummingbot/connector/exchange/crypto_com/crypto_com_order_book_message.py @@ -40,9 +40,7 @@ def update_id(self) -> int: @property def trade_id(self) -> int: - if self.type is OrderBookMessageType.TRADE: - return int(self.timestamp) - return -1 + return int(self.timestamp) if self.type is OrderBookMessageType.TRADE else -1 @property def trading_pair(self) -> str: @@ -75,8 +73,7 @@ def __eq__(self, other) -> bool: def __lt__(self, other) -> bool: if self.timestamp != other.timestamp: return self.timestamp < other.timestamp - else: - """ + """ If timestamp is the same, the ordering is snapshot < diff < trade """ - return self.type.value < other.type.value + return self.type.value < other.type.value diff --git a/hummingbot/connector/exchange/crypto_com/crypto_com_websocket.py b/hummingbot/connector/exchange/crypto_com/crypto_com_websocket.py index bb6636e49a..89e2da3536 100644 --- a/hummingbot/connector/exchange/crypto_com/crypto_com_websocket.py +++ b/hummingbot/connector/exchange/crypto_com/crypto_com_websocket.py @@ -53,7 +53,7 @@ def logger(cls) -> HummingbotLogger: def __init__(self, auth: Optional[CryptoComAuth] = None, shared_client: Optional[aiohttp.ClientSession] = None): self._auth: Optional[CryptoComAuth] = auth - self._is_private = True if self._auth is not None else False + self._is_private = self._auth is not None self._WS_URL = CONSTANTS.WSS_PRIVATE_URL if self._is_private else CONSTANTS.WSS_PUBLIC_URL self._shared_client = shared_client self._websocket: Optional[aiohttp.ClientWebSocketResponse] = None diff --git a/hummingbot/connector/exchange/digifinex/digifinex_auth.py b/hummingbot/connector/exchange/digifinex/digifinex_auth.py index d90e2da161..fc3e6b1204 100644 --- a/hummingbot/connector/exchange/digifinex/digifinex_auth.py +++ b/hummingbot/connector/exchange/digifinex/digifinex_auth.py @@ -34,7 +34,7 @@ def __init__(self, api_key: str, secret_key: str): @classmethod async def query_time_func() -> float: async with aiohttp.ClientSession() as session: - async with session.get(Constants.REST_URL + '/time') as resp: + async with session.get(f'{Constants.REST_URL}/time') as resp: resp_data: Dict[str, float] = await resp.json() return float(resp_data["server_time"]) @@ -54,14 +54,12 @@ def get_private_headers( ).hexdigest() nonce = int(self.time_patcher.time()) - header = { + return { 'ACCESS-KEY': self.api_key, 'ACCESS-TIMESTAMP': str(nonce), 'ACCESS-SIGN': sig, } - return header - def generate_ws_signature(self) -> List[Any]: data = [None] * 3 data[0] = self.api_key diff --git a/hummingbot/connector/exchange/digifinex/digifinex_exchange.py b/hummingbot/connector/exchange/digifinex/digifinex_exchange.py index d12e43dbd5..f00b5a98c3 100644 --- a/hummingbot/connector/exchange/digifinex/digifinex_exchange.py +++ b/hummingbot/connector/exchange/digifinex/digifinex_exchange.py @@ -555,10 +555,10 @@ def _process_order_status(self, exchange_order_id: str, status: int): tracked_order = self.find_exchange_order(exchange_order_id) if tracked_order is None: return - client_order_id = tracked_order.client_order_id # Update order execution status tracked_order.last_state = str(status) if tracked_order.is_cancelled: + client_order_id = tracked_order.client_order_id self.logger().info(f"Successfully cancelled order {client_order_id}.") self.trigger_event(MarketEvent.OrderCancelled, OrderCancelledEvent( @@ -731,9 +731,8 @@ def tick(self, timestamp: float): else self.LONG_POLL_INTERVAL) last_tick = int(self._last_timestamp / poll_interval) current_tick = int(timestamp / poll_interval) - if current_tick > last_tick: - if not self._poll_notifier.is_set(): - self._poll_notifier.set() + if current_tick > last_tick and not self._poll_notifier.is_set(): + self._poll_notifier.set() self._last_timestamp = timestamp def get_fee(self, @@ -811,15 +810,18 @@ async def get_open_orders(self) -> List[OpenOrder]: ret_val.append( OpenOrder( client_order_id=None, - trading_pair=digifinex_utils.convert_from_exchange_trading_pair(order["symbol"]), + trading_pair=digifinex_utils.convert_from_exchange_trading_pair( + order["symbol"] + ), price=Decimal(str(order["price"])), amount=Decimal(str(order["amount"])), executed_amount=Decimal(str(order["executed_amount"])), status=order["status"], order_type=OrderType.LIMIT, - is_buy=True if order["type"] == "buy" else False, + is_buy=order["type"] == "buy", time=int(order["created_date"]), - exchange_order_id=order["order_id"] + exchange_order_id=order["order_id"], ) ) + return ret_val diff --git a/hummingbot/connector/exchange/digifinex/digifinex_order_book_message.py b/hummingbot/connector/exchange/digifinex/digifinex_order_book_message.py index 883ea99da7..ab6fe1daaa 100644 --- a/hummingbot/connector/exchange/digifinex/digifinex_order_book_message.py +++ b/hummingbot/connector/exchange/digifinex/digifinex_order_book_message.py @@ -73,8 +73,7 @@ def __eq__(self, other) -> bool: def __lt__(self, other) -> bool: if self.timestamp != other.timestamp: return self.timestamp < other.timestamp - else: - """ + """ If timestamp is the same, the ordering is snapshot < diff < trade """ - return self.type.value < other.type.value + return self.type.value < other.type.value diff --git a/hummingbot/connector/exchange/digifinex/digifinex_websocket.py b/hummingbot/connector/exchange/digifinex/digifinex_websocket.py index c26d056f69..9b7ef7c3c1 100644 --- a/hummingbot/connector/exchange/digifinex/digifinex_websocket.py +++ b/hummingbot/connector/exchange/digifinex/digifinex_websocket.py @@ -36,7 +36,7 @@ def logger(cls) -> HummingbotLogger: def __init__(self, auth: Optional[DigifinexAuth] = None): self._auth: Optional[DigifinexAuth] = auth - self._isPrivate = True if self._auth is not None else False + self._isPrivate = self._auth is not None self._WS_URL = constants.WSS_PRIVATE_URL if self._isPrivate else constants.WSS_PUBLIC_URL self._client: Optional[websockets.WebSocketClientProtocol] = None @@ -149,7 +149,7 @@ async def request(self, method: str, data: Optional[Any] = {}) -> int: # subscribe to a method async def subscribe(self, category: str, channels: List[str]) -> int: - id = await self.request(category + ".subscribe", channels) + id = await self.request(f'{category}.subscribe', channels) msg = await self._messages() if msg.get('error') is not None: raise ConnectionError(f'subscribe {category} {channels} failed: {msg}') diff --git a/hummingbot/connector/exchange/ftx/ftx_api_order_book_data_source.py b/hummingbot/connector/exchange/ftx/ftx_api_order_book_data_source.py index 5eefd4435f..0a377c2251 100644 --- a/hummingbot/connector/exchange/ftx/ftx_api_order_book_data_source.py +++ b/hummingbot/connector/exchange/ftx/ftx_api_order_book_data_source.py @@ -69,7 +69,7 @@ def get_mid_price(trading_pair: str) -> Optional[Decimal]: resp = requests.get(url=f"{FTX_REST_URL}{FTX_EXCHANGE_INFO_PATH}/{convert_to_exchange_trading_pair(trading_pair)}") record = resp.json()["result"] result = (Decimal(record.get("bid", "0")) + Decimal(record.get("ask", "0"))) / Decimal("2") - return result if result else None + return result or None @staticmethod async def fetch_trading_pairs() -> List[str]: @@ -78,10 +78,12 @@ async def fetch_trading_pairs() -> List[str]: async with client.get(f"{FTX_REST_URL}{FTX_EXCHANGE_INFO_PATH}", timeout=API_CALL_TIMEOUT) as response: if response.status == 200: all_trading_pairs: Dict[str, Any] = await response.json() - valid_trading_pairs: list = [] - for item in all_trading_pairs["result"]: - if item["type"] == "spot": - valid_trading_pairs.append(item["name"]) + valid_trading_pairs: list = [ + item["name"] + for item in all_trading_pairs["result"] + if item["type"] == "spot" + ] + trading_pair_list: List[str] = [] for raw_trading_pair in valid_trading_pairs: converted_trading_pair: Optional[str] = \ @@ -100,9 +102,7 @@ async def get_snapshot(self, client: aiohttp.ClientSession, trading_pair: str, l if response.status != 200: raise IOError(f"Error fetching FTX market snapshot for {trading_pair}. " f"HTTP status is {response.status}.") - data: Dict[str, Any] = simplejson.loads(await response.text(), parse_float=Decimal) - - return data + return simplejson.loads(await response.text(), parse_float=Decimal) async def get_new_order_book(self, trading_pair: str) -> OrderBook: async with aiohttp.ClientSession() as client: @@ -150,11 +150,14 @@ async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asynci await ws.send(ujson.dumps(subscribe_request)) async for raw_msg in self._inner_messages(ws): msg = simplejson.loads(raw_msg, parse_float=Decimal) - if "channel" in msg: - if msg["channel"] == "trades" and msg["type"] == "update": - for trade in msg["data"]: - trade_msg: OrderBookMessage = FtxOrderBook.trade_message_from_exchange(msg, trade) - output.put_nowait(trade_msg) + if ( + "channel" in msg + and msg["channel"] == "trades" + and msg["type"] == "update" + ): + for trade in msg["data"]: + trade_msg: OrderBookMessage = FtxOrderBook.trade_message_from_exchange(msg, trade) + output.put_nowait(trade_msg) except asyncio.CancelledError: raise except Exception: @@ -176,10 +179,13 @@ async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, outp await ws.send(ujson.dumps(subscribe_request)) async for raw_msg in self._inner_messages(ws): msg = simplejson.loads(raw_msg, parse_float=Decimal) - if "channel" in msg: - if msg["channel"] == "orderbook" and msg["type"] == "update": - order_book_message: OrderBookMessage = FtxOrderBook.diff_message_from_exchange(msg, msg["data"]["time"]) - output.put_nowait(order_book_message) + if ( + "channel" in msg + and msg["channel"] == "orderbook" + and msg["type"] == "update" + ): + order_book_message: OrderBookMessage = FtxOrderBook.diff_message_from_exchange(msg, msg["data"]["time"]) + output.put_nowait(order_book_message) except asyncio.CancelledError: raise except Exception: @@ -201,10 +207,13 @@ async def listen_for_order_book_snapshots(self, ev_loop: asyncio.BaseEventLoop, await ws.send(ujson.dumps(subscribe_request)) async for raw_msg in self._inner_messages(ws): msg = simplejson.loads(raw_msg, parse_float=Decimal) - if "channel" in msg: - if msg["channel"] == "orderbook" and msg["type"] == "partial": - order_book_message: OrderBookMessage = FtxOrderBook.snapshot_message_from_exchange(msg, msg["data"]["time"]) - output.put_nowait(order_book_message) + if ( + "channel" in msg + and msg["channel"] == "orderbook" + and msg["type"] == "partial" + ): + order_book_message: OrderBookMessage = FtxOrderBook.snapshot_message_from_exchange(msg, msg["data"]["time"]) + output.put_nowait(order_book_message) except asyncio.CancelledError: raise except Exception: diff --git a/hummingbot/connector/exchange/ftx/ftx_order_book_message.py b/hummingbot/connector/exchange/ftx/ftx_order_book_message.py index 81029037c0..de3399fd26 100644 --- a/hummingbot/connector/exchange/ftx/ftx_order_book_message.py +++ b/hummingbot/connector/exchange/ftx/ftx_order_book_message.py @@ -65,8 +65,7 @@ def __eq__(self, other) -> bool: def __lt__(self, other) -> bool: if self.timestamp != other.timestamp: return self.timestamp < other.timestamp - else: - """ + """ If timestamp is the same, the ordering is snapshot < diff < trade """ - return self.type.value < other.type.value + return self.type.value < other.type.value diff --git a/hummingbot/connector/exchange/ftx/ftx_order_book_tracker.py b/hummingbot/connector/exchange/ftx/ftx_order_book_tracker.py index 713dcab906..e3a1bed688 100644 --- a/hummingbot/connector/exchange/ftx/ftx_order_book_tracker.py +++ b/hummingbot/connector/exchange/ftx/ftx_order_book_tracker.py @@ -66,9 +66,12 @@ async def _refresh_tracking_tasks(self): """ Starts tracking for any new trading pairs, and stop tracking for any inactive trading pairs. """ - tracking_trading_pair: Set[str] = set( - [key for key in self._tracking_tasks.keys() if not self._tracking_tasks[key].done()] - ) + tracking_trading_pair: Set[str] = { + key + for key in self._tracking_tasks.keys() + if not self._tracking_tasks[key].done() + } + available_pairs: Dict[str, FtxOrderBookTrackerEntry] = await self.data_source.get_tracking_pairs() available_trading_pair: Set[str] = set(available_pairs.keys()) new_trading_pair: Set[str] = available_trading_pair - tracking_trading_pair diff --git a/hummingbot/connector/exchange/gate_io/gate_io_api_order_book_data_source.py b/hummingbot/connector/exchange/gate_io/gate_io_api_order_book_data_source.py index 14cc1fe235..e3c2774e37 100644 --- a/hummingbot/connector/exchange/gate_io/gate_io_api_order_book_data_source.py +++ b/hummingbot/connector/exchange/gate_io/gate_io_api_order_book_data_source.py @@ -56,8 +56,7 @@ def __init__( @classmethod def _get_throttler_instance(cls) -> AsyncThrottler: - throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) - return throttler + return AsyncThrottler(CONSTANTS.RATE_LIMITS) @classmethod async def get_last_traded_prices(cls, trading_pairs: List[str]) -> Dict[str, Decimal]: @@ -79,7 +78,7 @@ async def get_last_traded_prices(cls, trading_pairs: List[str]) -> Dict[str, Dec tickers = await api_call_with_retries(request, rest_assistant, throttler, logging.getLogger()) for trading_pair in trading_pairs: ex_pair = convert_to_exchange_trading_pair(trading_pair) - ticker = list([tic for tic in tickers if tic['currency_pair'] == ex_pair])[0] + ticker = [tic for tic in tickers if tic['currency_pair'] == ex_pair][0] results[trading_pair] = Decimal(str(ticker["last"])) return results @@ -99,7 +98,10 @@ async def fetch_trading_pairs(cls) -> List[str]: symbols = await api_call_with_retries( request, rest_assistant, throttler, logging.getLogger() ) - trading_pairs = list([convert_from_exchange_trading_pair(sym["id"]) for sym in symbols]) + trading_pairs = [ + convert_from_exchange_trading_pair(sym["id"]) for sym in symbols + ] + # Filter out unmatched pairs so nothing breaks return [sym for sym in trading_pairs if sym is not None] except Exception: diff --git a/hummingbot/connector/exchange/gate_io/gate_io_api_user_stream_data_source.py b/hummingbot/connector/exchange/gate_io/gate_io_api_user_stream_data_source.py index 216d085201..262719929b 100755 --- a/hummingbot/connector/exchange/gate_io/gate_io_api_user_stream_data_source.py +++ b/hummingbot/connector/exchange/gate_io/gate_io_api_user_stream_data_source.py @@ -39,10 +39,7 @@ def __init__( @property def last_recv_time(self) -> float: - recv_time = 0 - if self._ws is not None: - recv_time = self._ws.last_recv_time - return recv_time + return self._ws.last_recv_time if self._ws is not None else 0 async def _listen_to_orders_trades_balances(self) -> AsyncIterable[Any]: """ diff --git a/hummingbot/connector/exchange/gate_io/gate_io_auth.py b/hummingbot/connector/exchange/gate_io/gate_io_auth.py index 66af21d5d2..dd0860b03c 100755 --- a/hummingbot/connector/exchange/gate_io/gate_io_auth.py +++ b/hummingbot/connector/exchange/gate_io/gate_io_auth.py @@ -52,12 +52,11 @@ def generate_auth_dict_ws(self, :return: a dictionary of auth params """ sig = self.generate_payload(payload['channel'], payload['event'], payload['time'], True) - headers = { + return { "method": "api_key", "KEY": f"{self.api_key}", "SIGN": f"{sig}", } - return headers def get_headers(self, method, @@ -68,11 +67,10 @@ def get_headers(self, :return: a dictionary of auth headers """ payload = self.generate_payload(method, url, params) - headers = { + return { "X-Gate-Channel-Id": CONSTANTS.HBOT_BROKER_ID, "KEY": f"{self.api_key}", "Timestamp": f"{self.nonce}", "SIGN": f"{payload}", "Content-Type": "application/json", } - return headers diff --git a/hummingbot/connector/exchange/gate_io/gate_io_exchange.py b/hummingbot/connector/exchange/gate_io/gate_io_exchange.py index fbffaef6a6..64cc598b61 100644 --- a/hummingbot/connector/exchange/gate_io/gate_io_exchange.py +++ b/hummingbot/connector/exchange/gate_io/gate_io_exchange.py @@ -442,27 +442,26 @@ async def _create_order(self, order_result = await self._api_request(request) if order_result.get('status') in {"cancelled", "expired", "failed"}: raise GateIoAPIError({'label': 'ORDER_REJECTED', 'message': 'Order rejected.'}) - else: - exchange_order_id = str(order_result["id"]) - tracked_order = self._in_flight_orders.get(order_id) - if tracked_order is not None: - self.logger().info(f"Created {order_type.name} {trade_type.name} order {order_id} for " - f"{amount} {trading_pair}.") - tracked_order.update_exchange_order_id(exchange_order_id) - if trade_type is TradeType.BUY: - event_tag = MarketEvent.BuyOrderCreated - event_cls = BuyOrderCreatedEvent - else: - event_tag = MarketEvent.SellOrderCreated - event_cls = SellOrderCreatedEvent - self.trigger_event(event_tag, - event_cls(self.current_timestamp, - order_type, - trading_pair, - amount, - price, - order_id, - exchange_order_id)) + exchange_order_id = str(order_result["id"]) + tracked_order = self._in_flight_orders.get(order_id) + if tracked_order is not None: + self.logger().info(f"Created {order_type.name} {trade_type.name} order {order_id} for " + f"{amount} {trading_pair}.") + tracked_order.update_exchange_order_id(exchange_order_id) + if trade_type is TradeType.BUY: + event_tag = MarketEvent.BuyOrderCreated + event_cls = BuyOrderCreatedEvent + else: + event_tag = MarketEvent.SellOrderCreated + event_cls = SellOrderCreatedEvent + self.trigger_event(event_tag, + event_cls(self.current_timestamp, + order_type, + trading_pair, + amount, + price, + order_id, + exchange_order_id)) except asyncio.CancelledError: raise except Exception as e: @@ -596,15 +595,13 @@ async def _update_balances(self): Calls REST API to update total and available balances. """ try: - # Check for in progress balance updates, queue if fetching and none already waiting, otherwise skip. if self._update_balances_fetching: - if not self._update_balances_queued: - self._update_balances_queued = True - await self._update_balances_finished.wait() - self._update_balances_queued = False - self._update_balances_finished = asyncio.Event() - else: + if self._update_balances_queued: return + self._update_balances_queued = True + await self._update_balances_finished.wait() + self._update_balances_queued = False + self._update_balances_finished = asyncio.Event() self._update_balances_fetching = True endpoint = CONSTANTS.USER_BALANCES_PATH_URL request = GateIORESTRequest( @@ -693,16 +690,15 @@ async def _update_order_status(self): # Process order trades first before processing order statuses trade_responses = await safe_gather(*order_trade_tasks, return_exceptions=True) for response, tracked_order in zip(trade_responses, tracked_orders): - if not isinstance(response, GateIoAPIError): - if len(response) > 0: - for trade_fills in response: - self._process_trade_message(trade_fills, tracked_order.client_order_id) - else: + if isinstance(response, GateIoAPIError): self.logger().warning(f"Failed to fetch trade updates for order {tracked_order.client_order_id}. " f"Response: {response}") if response.error_label == 'ORDER_NOT_FOUND': self.stop_tracking_order_exceed_not_found_limit(tracked_order=tracked_order) + elif len(response) > 0: + for trade_fills in response: + self._process_trade_message(trade_fills, tracked_order.client_order_id) status_responses = await safe_gather(*order_status_tasks, return_exceptions=True) for response, tracked_order in zip(status_responses, tracked_orders): if not isinstance(response, GateIoAPIError): @@ -751,9 +747,7 @@ def _process_order_message(self, order_msg: Dict[str, Any]): """ client_order_id = str(order_msg["text"]) - tracked_order = self.in_flight_orders.get(client_order_id, None) - if tracked_order: - + if tracked_order := self.in_flight_orders.get(client_order_id, None): tracked_order.last_state = order_msg.get("status", order_msg.get("event")) if tracked_order.is_cancelled: @@ -793,8 +787,7 @@ def _process_trade_message(self, trade_msg: Dict[str, Any], client_order_id: Opt } """ client_order_id = client_order_id or str(trade_msg["text"]) - tracked_order = self.in_flight_orders.get(client_order_id, None) - if tracked_order: + if tracked_order := self.in_flight_orders.get(client_order_id, None): updated = tracked_order.update_with_trade_update(trade_msg) if updated: self._trigger_order_fill(tracked_order=tracked_order, @@ -877,7 +870,7 @@ async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: if self._trading_pairs is None: raise Exception("cancel_all can only be used when trading_pairs are specified.") open_orders = [o for o in self._in_flight_orders.values() if not o.is_done] - if len(open_orders) == 0: + if not open_orders: return [] tasks = [self._execute_cancel(o.trading_pair, o.client_order_id) for o in open_orders] cancellation_results = [] @@ -905,9 +898,8 @@ def tick(self, timestamp: float): else CONSTANTS.LONG_POLL_INTERVAL) last_tick = int(self._last_timestamp / poll_interval) current_tick = int(timestamp / poll_interval) - if current_tick > last_tick: - if not self._poll_notifier.is_set(): - self._poll_notifier.set() + if current_tick > last_tick and not self._poll_notifier.is_set(): + self._poll_notifier.set() self._last_timestamp = timestamp def get_fee(self, @@ -993,15 +985,18 @@ async def get_open_orders(self) -> List[OpenOrder]: ret_val.append( OpenOrder( client_order_id=order["text"], - trading_pair=convert_from_exchange_trading_pair(order["currency_pair"]), + trading_pair=convert_from_exchange_trading_pair( + order["currency_pair"] + ), price=Decimal(str(order["price"])), amount=Decimal(str(order["amount"])), executed_amount=Decimal(str(order["filled_total"])), status=order["status"], order_type=OrderType.LIMIT, - is_buy=True if order["side"].lower() == TradeType.BUY.name.lower() else False, + is_buy=order["side"].lower() == TradeType.BUY.name.lower(), time=int(order["create_time"]), - exchange_order_id=order["id"] + exchange_order_id=order["id"], ) ) + return ret_val diff --git a/hummingbot/connector/exchange/gate_io/gate_io_utils.py b/hummingbot/connector/exchange/gate_io/gate_io_utils.py index b14e47609c..c608dc0cf5 100644 --- a/hummingbot/connector/exchange/gate_io/gate_io_utils.py +++ b/hummingbot/connector/exchange/gate_io/gate_io_utils.py @@ -33,8 +33,7 @@ def base_url(self) -> str: def auth_url(self) -> str: if self.endpoint is None: raise ValueError("No endpoint specified. Cannot build auth url.") - auth_url = f"{CONSTANTS.REST_URL_AUTH}/{self.endpoint}" - return auth_url + return f"{CONSTANTS.REST_URL_AUTH}/{self.endpoint}" class GateIoAPIError(IOError): @@ -51,8 +50,7 @@ def __init__(self, error_payload: Dict[str, Any]): def build_gate_io_api_factory() -> WebAssistantsFactory: - api_factory = WebAssistantsFactory() - return api_factory + return WebAssistantsFactory() def split_trading_pair(trading_pair: str) -> Optional[Tuple[str, str]]: diff --git a/hummingbot/connector/exchange/gate_io/gate_io_websocket.py b/hummingbot/connector/exchange/gate_io/gate_io_websocket.py index dad91a5d63..1462f71081 100644 --- a/hummingbot/connector/exchange/gate_io/gate_io_websocket.py +++ b/hummingbot/connector/exchange/gate_io/gate_io_websocket.py @@ -26,17 +26,18 @@ def __init__(self, auth: Optional[GateIoAuth] = None, api_factory: Optional[WebAssistantsFactory] = None): self._auth: Optional[GateIoAuth] = auth - self._is_private = True if self._auth is not None else False + self._is_private = self._auth is not None self._api_factory = api_factory or build_gate_io_api_factory() self._ws_assistant: Optional[WSAssistant] = None self._closed = True @property def last_recv_time(self) -> float: - last_recv_time = 0 - if self._ws_assistant is not None: - last_recv_time = self._ws_assistant.last_recv_time - return last_recv_time + return ( + self._ws_assistant.last_recv_time + if self._ws_assistant is not None + else 0 + ) async def connect(self): self._ws_assistant = await self._api_factory.get_ws_assistant() diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_api_order_book_data_source.py b/hummingbot/connector/exchange/hitbtc/hitbtc_api_order_book_data_source.py index d774d7949e..6000c84e1f 100644 --- a/hummingbot/connector/exchange/hitbtc/hitbtc_api_order_book_data_source.py +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_api_order_book_data_source.py @@ -68,7 +68,7 @@ async def get_last_traded_prices(cls, trading_pairs: List[str]) -> Dict[str, Dec for trading_pair in trading_pairs: ex_pair: str = await HitbtcAPIOrderBookDataSource.exchange_symbol_associated_to_pair(trading_pair) if len(trading_pairs) > 1: - ticker: Dict[Any] = list([tic for tic in tickers if tic['symbol'] == ex_pair])[0] + ticker: Dict[Any] = [tic for tic in tickers if tic['symbol'] == ex_pair][0] else: url_endpoint = Constants.ENDPOINT["TICKER_SINGLE"].format(trading_pair=ex_pair) ticker: Dict[Any] = await api_call_with_retries("GET", url_endpoint) @@ -78,9 +78,9 @@ async def get_last_traded_prices(cls, trading_pairs: List[str]) -> Dict[str, Dec @staticmethod async def exchange_symbol_associated_to_pair(trading_pair: str) -> str: symbol_map = await HitbtcAPIOrderBookDataSource.trading_pair_symbol_map() - symbols = [symbol for symbol, pair in symbol_map.items() if pair == trading_pair] - - if symbols: + if symbols := [ + symbol for symbol, pair in symbol_map.items() if pair == trading_pair + ]: symbol = symbols[0] else: raise ValueError(f"There is no symbol mapping for trading pair {trading_pair}") diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_auth.py b/hummingbot/connector/exchange/hitbtc/hitbtc_auth.py index be37f2e149..eb0583173e 100755 --- a/hummingbot/connector/exchange/hitbtc/hitbtc_auth.py +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_auth.py @@ -28,7 +28,7 @@ def generate_payload( nonce = str(int(time.time())) body = "" # Need to build the full URL with query string for HS256 sig - if params is not None and len(params) > 0: + if params is not None and params: query_string = "&".join([f"{k}={v}" for k, v in params.items()]) if method == "GET": url = f"{url}?{query_string}" @@ -65,8 +65,7 @@ def get_headers(self, :return: a dictionary of auth headers """ payload = self.generate_payload(method, url, params) - headers = { + return { "Authorization": f"HS256 {payload}", "Content-Type": "application/x-www-form-urlencoded", } - return headers diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_exchange.py b/hummingbot/connector/exchange/hitbtc/hitbtc_exchange.py index fe56b9fb7f..742dae891d 100644 --- a/hummingbot/connector/exchange/hitbtc/hitbtc_exchange.py +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_exchange.py @@ -602,19 +602,18 @@ async def _update_order_status(self): client_order_id = tracked_order.client_order_id if isinstance(response, HitbtcAPIError): err = response.error_payload.get('error', response.error_payload) - if err.get('code') == 20002: - self._order_not_found_records[client_order_id] = \ - self._order_not_found_records.get(client_order_id, 0) + 1 - if self._order_not_found_records[client_order_id] < self.ORDER_NOT_EXIST_CONFIRMATION_COUNT: - # Wait until the order not found error have repeated a few times before actually treating - # it as failed. See: https://github.com/CoinAlpha/hummingbot/issues/601 - continue - self.trigger_event(MarketEvent.OrderFailure, - MarketOrderFailureEvent( - self.current_timestamp, client_order_id, tracked_order.order_type)) - self.stop_tracking_order(client_order_id) - else: + if err.get('code') != 20002: continue + self._order_not_found_records[client_order_id] = \ + self._order_not_found_records.get(client_order_id, 0) + 1 + if self._order_not_found_records[client_order_id] < self.ORDER_NOT_EXIST_CONFIRMATION_COUNT: + # Wait until the order not found error have repeated a few times before actually treating + # it as failed. See: https://github.com/CoinAlpha/hummingbot/issues/601 + continue + self.trigger_event(MarketEvent.OrderFailure, + MarketOrderFailureEvent( + self.current_timestamp, client_order_id, tracked_order.order_type)) + self.stop_tracking_order(client_order_id) elif "clientOrderId" not in response: self.logger().info(f"_update_order_status clientOrderId not in resp: {response}") continue @@ -766,7 +765,7 @@ async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: if self._trading_pairs is None: raise Exception("cancel_all can only be used when trading_pairs are specified.") open_orders = [o for o in self._in_flight_orders.values() if not o.is_done] - if len(open_orders) == 0: + if not open_orders: return [] tasks = [self._execute_cancel(o.trading_pair, o.client_order_id) for o in open_orders] cancellation_results = [] @@ -792,9 +791,8 @@ def tick(self, timestamp: float): else Constants.LONG_POLL_INTERVAL) last_tick = int(self._last_timestamp / poll_interval) current_tick = int(timestamp / poll_interval) - if current_tick > last_tick: - if not self._poll_notifier.is_set(): - self._poll_notifier.set() + if current_tick > last_tick and not self._poll_notifier.is_set(): + self._poll_notifier.set() self._last_timestamp = timestamp def get_fee(self, @@ -878,9 +876,10 @@ async def get_open_orders(self) -> List[OpenOrder]: executed_amount=Decimal(str(order["cumQuantity"])), status=order["status"], order_type=OrderType.LIMIT, - is_buy=True if order["side"].lower() == TradeType.BUY.name.lower() else False, + is_buy=order["side"].lower() == TradeType.BUY.name.lower(), time=str_date_to_ts(order["createdAt"]), - exchange_order_id=order["id"] + exchange_order_id=order["id"], ) ) + return ret_val diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_order_book_message.py b/hummingbot/connector/exchange/hitbtc/hitbtc_order_book_message.py index 1e0c38ff97..163d7a5c77 100644 --- a/hummingbot/connector/exchange/hitbtc/hitbtc_order_book_message.py +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_order_book_message.py @@ -66,8 +66,7 @@ def __eq__(self, other) -> bool: def __lt__(self, other) -> bool: if self.timestamp != other.timestamp: return self.timestamp < other.timestamp - else: - """ + """ If timestamp is the same, the ordering is snapshot < diff < trade """ - return self.type.value < other.type.value + return self.type.value < other.type.value diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_websocket.py b/hummingbot/connector/exchange/hitbtc/hitbtc_websocket.py index da65b869a2..6695991b11 100644 --- a/hummingbot/connector/exchange/hitbtc/hitbtc_websocket.py +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_websocket.py @@ -37,7 +37,7 @@ def logger(cls) -> HummingbotLogger: def __init__(self, auth: Optional[HitbtcAuth] = None): self._auth: Optional[HitbtcAuth] = auth - self._isPrivate = True if self._auth is not None else False + self._isPrivate = self._auth is not None self._WS_URL = Constants.WS_PRIVATE_URL if self._isPrivate else Constants.WS_PUBLIC_URL self._client: Optional[websockets.WebSocketClientProtocol] = None @@ -72,10 +72,7 @@ async def _messages(self) -> AsyncIterable[Any]: try: raw_msg_str: str = await asyncio.wait_for(self._client.recv(), timeout=Constants.MESSAGE_TIMEOUT) try: - msg = json.loads(raw_msg_str) - # HitBTC doesn't support ping or heartbeat messages. - # Can handle them here if that changes - use `safe_ensure_future`. - yield msg + yield json.loads(raw_msg_str) except ValueError: continue except asyncio.TimeoutError: diff --git a/hummingbot/connector/exchange/huobi/huobi_api_order_book_data_source.py b/hummingbot/connector/exchange/huobi/huobi_api_order_book_data_source.py index dae25b64ff..2b46fd55b5 100644 --- a/hummingbot/connector/exchange/huobi/huobi_api_order_book_data_source.py +++ b/hummingbot/connector/exchange/huobi/huobi_api_order_book_data_source.py @@ -79,7 +79,7 @@ async def get_last_traded_prices(cls, trading_pairs: List[str]) -> Dict[str, flo url=url) response: RESTResponse = await rest_assistant.call(request=request) - results = dict() + results = {} resp_json = await response.json() for trading_pair in trading_pairs: resp_record = [o for o in resp_json["data"] if o["symbol"] == convert_to_exchange_trading_pair(trading_pair)][0] diff --git a/hummingbot/connector/exchange/huobi/huobi_api_user_stream_data_source.py b/hummingbot/connector/exchange/huobi/huobi_api_user_stream_data_source.py index a7f704262f..7808c2da2f 100644 --- a/hummingbot/connector/exchange/huobi/huobi_api_user_stream_data_source.py +++ b/hummingbot/connector/exchange/huobi/huobi_api_user_stream_data_source.py @@ -36,9 +36,7 @@ def __init__(self, huobi_auth: HuobiAuth, api_factory: Optional[WebAssistantsFac @property def last_recv_time(self) -> float: - if self._ws_assistant: - return self._ws_assistant.last_recv_time - return -1 + return self._ws_assistant.last_recv_time if self._ws_assistant else -1 async def _get_ws_assistant(self) -> WSAssistant: if self._ws_assistant is None: diff --git a/hummingbot/connector/exchange/huobi/huobi_auth.py b/hummingbot/connector/exchange/huobi/huobi_auth.py index 996351a752..73fd4a70b9 100644 --- a/hummingbot/connector/exchange/huobi/huobi_auth.py +++ b/hummingbot/connector/exchange/huobi/huobi_auth.py @@ -69,6 +69,4 @@ def generate_signature(self, encoded_params_str = urlencode(params) payload = "\n".join([method.upper(), self.hostname, query_endpoint, encoded_params_str]) digest = hmac.new(self.secret_key.encode("utf8"), payload.encode("utf8"), hashlib.sha256).digest() - signature_b64 = base64.b64encode(digest).decode() - - return signature_b64 + return base64.b64encode(digest).decode() diff --git a/hummingbot/connector/exchange/huobi/huobi_utils.py b/hummingbot/connector/exchange/huobi/huobi_utils.py index 764fea0115..c137c99493 100644 --- a/hummingbot/connector/exchange/huobi/huobi_utils.py +++ b/hummingbot/connector/exchange/huobi/huobi_utils.py @@ -31,10 +31,9 @@ def split_trading_pair(trading_pair: str) -> Optional[Tuple[str, str]]: m = RE_4_LETTERS_QUOTE.match(trading_pair) if m is None: m = RE_3_LETTERS_QUOTE.match(trading_pair) - if m is None: - m = RE_2_LETTERS_QUOTE.match(trading_pair) + if m is None: + m = RE_2_LETTERS_QUOTE.match(trading_pair) return m.group(1), m.group(2) - # Exceptions are now logged as warnings in trading pair fetcher except Exception: return None @@ -63,8 +62,7 @@ def get_new_client_order_id(trade_type: TradeType, trading_pair: str) -> str: def build_api_factory() -> WebAssistantsFactory: - api_factory = WebAssistantsFactory(ws_post_processors=[HuobiWSPostProcessor()]) - return api_factory + return WebAssistantsFactory(ws_post_processors=[HuobiWSPostProcessor()]) KEYS = { diff --git a/hummingbot/connector/markets_recorder.py b/hummingbot/connector/markets_recorder.py index 4ba99dad02..a38fc950b9 100644 --- a/hummingbot/connector/markets_recorder.py +++ b/hummingbot/connector/markets_recorder.py @@ -177,8 +177,7 @@ def get_market_states(self, .query(MarketState) .filter(MarketState.config_file_path == config_file_path, MarketState.market == market.display_name)) - market_states: Optional[MarketState] = query.one_or_none() - return market_states + return query.one_or_none() def _did_create_order(self, event_tag: int, @@ -194,22 +193,27 @@ def _did_create_order(self, with self._sql_manager.get_new_session() as session: with session.begin(): - order_record: Order = Order(id=evt.order_id, - config_file_path=self._config_file_path, - strategy=self._strategy_name, - market=market.display_name, - symbol=evt.trading_pair, - base_asset=base_asset, - quote_asset=quote_asset, - creation_timestamp=timestamp, - order_type=evt.type.name, - amount=Decimal(evt.amount), - leverage=evt.leverage if evt.leverage else 1, - price=Decimal(evt.price) if evt.price == evt.price else Decimal(0), - position=evt.position if evt.position else PositionAction.NIL.value, - last_status=event_type.name, - last_update_timestamp=timestamp, - exchange_order_id=evt.exchange_order_id) + order_record: Order = Order( + id=evt.order_id, + config_file_path=self._config_file_path, + strategy=self._strategy_name, + market=market.display_name, + symbol=evt.trading_pair, + base_asset=base_asset, + quote_asset=quote_asset, + creation_timestamp=timestamp, + order_type=evt.type.name, + amount=Decimal(evt.amount), + leverage=evt.leverage or 1, + price=Decimal(evt.price) + if evt.price == evt.price + else Decimal(0), + position=evt.position or PositionAction.NIL.value, + last_status=event_type.name, + last_update_timestamp=timestamp, + exchange_order_id=evt.exchange_order_id, + ) + order_status: OrderStatus = OrderStatus(order=order_record, timestamp=timestamp, status=event_type.name) @@ -256,14 +260,16 @@ def _did_fill_order(self, order_id=order_id, trade_type=evt.trade_type.name, order_type=evt.order_type.name, - price=Decimal( - evt.price) if evt.price == evt.price else Decimal(0), + price=Decimal(evt.price) + if evt.price == evt.price + else Decimal(0), amount=Decimal(evt.amount), - leverage=evt.leverage if evt.leverage else 1, + leverage=evt.leverage or 1, trade_fee=evt.trade_fee.to_json(), exchange_trade_id=evt.exchange_trade_id, - position=evt.position if evt.position else PositionAction.NIL.value, + position=evt.position or PositionAction.NIL.value, ) + session.add(order_status) session.add(trade_fill_record) self.save_market_states(self._config_file_path, market, session=session) @@ -312,7 +318,7 @@ def _csv_matches_header(file_path: str, header: tuple) -> bool: return tuple(df.iloc[0].values) == header def append_to_csv(self, trade: TradeFill): - csv_filename = "trades_" + trade.config_file_path[:-4] + ".csv" + csv_filename = f"trades_{trade.config_file_path[:-4]}.csv" csv_path = os.path.join(data_path(), csv_filename) field_names = ("exchange_trade_id",) # id field should be first @@ -329,7 +335,13 @@ def append_to_csv(self, trade: TradeFill): field_data += (age,) if (os.path.exists(csv_path) and (not self._csv_matches_header(csv_path, field_names))): - move(csv_path, csv_path[:-4] + '_old_' + pd.Timestamp.utcnow().strftime("%Y%m%d-%H%M%S") + ".csv") + move( + csv_path, + f'{csv_path[:-4]}_old_' + + pd.Timestamp.utcnow().strftime("%Y%m%d-%H%M%S") + + ".csv", + ) + if not os.path.exists(csv_path): df_header = pd.DataFrame([field_names]) diff --git a/hummingbot/connector/utils.py b/hummingbot/connector/utils.py index 0b15d61171..c2835d058e 100644 --- a/hummingbot/connector/utils.py +++ b/hummingbot/connector/utils.py @@ -39,8 +39,7 @@ def json_to_zrx_order(data: Optional[Dict[str, any]]) -> Optional[ZeroExOrder]: def build_api_factory() -> WebAssistantsFactory: - api_factory = WebAssistantsFactory() - return api_factory + return WebAssistantsFactory() def split_hb_trading_pair(trading_pair: str) -> Tuple[str, str]: @@ -49,5 +48,4 @@ def split_hb_trading_pair(trading_pair: str) -> Tuple[str, str]: def combine_to_hb_trading_pair(base: str, quote: str) -> str: - trading_pair = f"{base}-{quote}" - return trading_pair + return f"{base}-{quote}"