async def show_balances(self): total_col_name = f'Total ({RateOracle.global_token_symbol})' self._notify("Updating balances, please wait...") network_timeout = float(global_config_map["other_commands_timeout"].value) try: all_ex_bals = await asyncio.wait_for( UserBalances.instance().all_balances_all_exchanges(), network_timeout ) except asyncio.TimeoutError: self._notify("\nA network error prevented the balances to update. See logs for more details.") raise all_ex_avai_bals = UserBalances.instance().all_avai_balances_all_exchanges() all_ex_limits: Optional[Dict[str, Dict[str, str]]] = global_config_map["balance_asset_limit"].value if all_ex_limits is None: all_ex_limits = {} exchanges_total = 0 for exchange, bals in all_ex_bals.items(): self._notify(f"\n{exchange}:") df, allocated_total = await self.exchange_balances_extra_df(bals, all_ex_avai_bals.get(exchange, {})) 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")] 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%}") exchanges_total += df[total_col_name].sum() self._notify(f"\n\nExchanges Total: {RateOracle.global_token_symbol} {exchanges_total:.0f} ") celo_address = global_config_map["celo_address"].value if celo_address is not None: try: 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")] self._notify("\ncelo:") self._notify("\n".join(lines)) except Exception as e: self._notify(f"\ncelo CLI Error: {str(e)}") 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")] 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")] self._notify("\nxdai:") self._notify("\n".join(lines))
async def connection_df(self # type: HummingbotApplication ): columns = [ "Exchange", " Keys Added", " Keys Confirmed", " Connector Status" ] data = [] failed_msgs = {} network_timeout = float( global_config_map["other_commands_timeout"].value) try: err_msgs = await asyncio.wait_for( UserBalances.instance().update_exchanges(reconnect=True), network_timeout) except asyncio.TimeoutError: self._notify( "\nA network error prevented the connection table to populate. See logs for more details." ) raise for option in sorted(OPTIONS): keys_added = "No" keys_confirmed = 'No' status = get_connector_status(option) if option == "ethereum": eth_address = global_config_map["ethereum_wallet"].value if eth_address is not None and eth_address in Security.private_keys( ): keys_added = "Yes" err_msg = UserBalances.validate_ethereum_wallet() if err_msg is not None: failed_msgs[option] = err_msg else: keys_confirmed = 'Yes' elif option == "celo": celo_address = global_config_map["celo_address"].value if celo_address is not None and Security.encrypted_file_exists( "celo_password"): keys_added = "Yes" err_msg = await self.validate_n_connect_celo(True) if err_msg is not None: failed_msgs[option] = err_msg else: keys_confirmed = 'Yes' else: api_keys = (await Security.api_keys(option)).values() if len(api_keys) > 0: keys_added = "Yes" err_msg = err_msgs.get(option) if err_msg is not None: failed_msgs[option] = err_msg else: keys_confirmed = 'Yes' data.append([option, keys_added, keys_confirmed, status]) return pd.DataFrame(data=data, columns=columns), failed_msgs
async def asset_ratio_maintenance_prompt( self, # type: HummingbotApplication config_map): exchange = config_map['exchange'].value market = config_map["market"].value base, quote = market.split("-") balances = await UserBalances.instance().balances( exchange, base, quote) if balances is None: return base_ratio = UserBalances.base_amount_ratio(market, balances) base_ratio = round(base_ratio, 3) quote_ratio = 1 - base_ratio base, quote = config_map["market"].value.split("-") cvar = ConfigVar( key="temp_config", prompt= f"On {exchange}, you have {balances.get(base, 0):.4f} {base} and " f"{balances.get(quote, 0):.4f} {quote}. By market value, " f"your current inventory split is {base_ratio:.1%} {base} " f"and {quote_ratio:.1%} {quote}." f" Would you like to keep this ratio? (Yes/No) >>> ", required_if=lambda: True, type_str="bool", validator=validate_bool) await self.prompt_a_config(cvar) if cvar.value: config_map['inventory_target_base_pct'].value = round( base_ratio * Decimal('100'), 1) else: await self.prompt_a_config(config_map["inventory_target_base_pct"]) config_map['inventory_skew_enabled'].value = True
async def connection_df(self # type: HummingbotApplication ): columns = ["Exchange", " Keys Added", " Keys Confirmed"] data = [] failed_msgs = {} err_msgs = await UserBalances.instance().update_exchanges( reconnect=True) for option in sorted(OPTIONS): keys_added = "No" keys_confirmed = 'No' if option == "ethereum": eth_address = global_config_map["ethereum_wallet"].value if eth_address is not None and eth_address in Security.private_keys( ): keys_added = "Yes" err_msg = UserBalances.validate_ethereum_wallet() if err_msg is not None: failed_msgs[option] = err_msg else: keys_confirmed = 'Yes' else: api_keys = (await Security.api_keys(option)).values() if len(api_keys) > 0: keys_added = "Yes" err_msg = err_msgs.get(option) if err_msg is not None: failed_msgs[option] = err_msg else: keys_confirmed = 'Yes' data.append([option, keys_added, keys_confirmed]) return pd.DataFrame(data=data, columns=columns), failed_msgs
async def get_current_balances( self, # type: HummingbotApplication market: str): if market in self.markets and self.markets[market].ready: return self.markets[market].get_all_balances() elif "Paper" in market: paper_balances = global_config_map[ "paper_trade_account_balance"].value if paper_balances is None: return {} return { token: Decimal(str(bal)) for token, bal in paper_balances.items() } elif "perpetual_finance" == market: return await UserBalances.xdai_balances() else: gateway_eth_connectors = [ cs.name for cs in 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)
async def connect_ethereum(self, # type: HummingbotApplication ): self.placeholder_mode = True self.app.hide_input = True ether_wallet = global_config_map["ethereum_wallet"].value to_connect = True if ether_wallet is not None: answer = await self.app.prompt(prompt=f"Would you like to replace your existing Ethereum wallet " f"{ether_wallet} (Yes/No)? >>> ") if self.app.to_stop_config: self.app.to_stop_config = False return if answer.lower() not in ("yes", "y"): to_connect = False if to_connect: private_key = await self.app.prompt(prompt="Enter your wallet private key >>> ", is_password=True) public_address = Security.add_private_key(private_key) global_config_map["ethereum_wallet"].value = public_address if global_config_map["ethereum_rpc_url"].value is None: await self.prompt_a_config(global_config_map["ethereum_rpc_url"]) if global_config_map["ethereum_rpc_ws_url"].value is None: await self.prompt_a_config(global_config_map["ethereum_rpc_ws_url"]) if self.app.to_stop_config: self.app.to_stop_config = False return save_to_yml(GLOBAL_CONFIG_PATH, global_config_map) err_msg = UserBalances.validate_ethereum_wallet() if err_msg is None: self._notify(f"Wallet {public_address} connected to hummingbot.") else: self._notify(f"\nError: {err_msg}") self.placeholder_mode = False self.app.hide_input = False self.app.change_prompt(prompt=">>> ")
async def show_balances(self): self._notify("Updating balances, please wait...") df = await self.balances_df() lines = [ " " + line for line in df.to_string(index=False).split("\n") ] self._notify("\n".join(lines)) eth_address = global_config_map["ethereum_wallet"].value if eth_address is not None: bal = UserBalances.ethereum_balance() bal = round(bal, 4) self._notify( f"Ethereum balance in ...{eth_address[-4:]} wallet: {bal} ETH") self._notify( f"Note: You may have other ERC 20 tokens in this same address (not shown here)." ) celo_address = global_config_map["celo_address"].value if celo_address is not None: try: if not CeloCLI.unlocked: await self.validate_n_connect_celo() bals = CeloCLI.balances() self._notify("Celo balances:") for token, bal in bals.items(): self._notify( f" {token} - total: {bal.total} locked: {bal.locked}") except Exception as e: self._notify(f"Celo CLI Error: {str(e)}")
async def get_binance_connector(self): if self._binance_connector is not None: return self._binance_connector api_keys = await Security.api_keys("binance") if not api_keys: return None self._binance_connector = UserBalances.connect_market("binance", **api_keys) return self._binance_connector
async def ethereum_balances_df( self, # type: HummingbotApplication ): rows = [] bal = UserBalances.ethereum_balance() rows.append({"asset": "ETH", "amount": round(bal, 4)}) df = pd.DataFrame(data=rows, columns=["asset", "amount"]) df.sort_values(by=["asset"], inplace=True) return df
async def show_balances(self): self._notify("Updating balances, please wait...") all_ex_bals = await UserBalances.instance().all_balances_all_exchanges( ) all_ex_avai_bals = UserBalances.instance( ).all_avai_balances_all_exchanges() all_ex_limits: Optional[Dict[str, Dict[ str, str]]] = global_config_map["balance_asset_limit"].value if all_ex_limits is None: all_ex_limits = {} for exchange, bals in all_ex_bals.items(): self._notify(f"\n{exchange}:") # df = await self.exchange_balances_df(bals, all_ex_limits.get(exchange, {})) df, allocated_total = await self.exchange_balances_usd_df( bals, all_ex_avai_bals.get(exchange, {})) 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") ] self._notify("\n".join(lines)) self._notify( f"\n Total: $ {df['Total ($)'].sum():.0f} " f"Allocated: {allocated_total / df['Total ($)'].sum():.2%}" ) celo_address = global_config_map["celo_address"].value if celo_address is not None: try: 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") ] self._notify("\ncelo:") self._notify("\n".join(lines)) except Exception as e: self._notify(f"\ncelo CLI Error: {str(e)}") eth_address = global_config_map["ethereum_wallet"].value if eth_address is not None: df = await self.ethereum_balances_df() lines = [ " " + line for line in df.to_string(index=False).split("\n") ] self._notify("\nethereum:") self._notify("\n".join(lines))
async def get_current_balances(self, # type: HummingbotApplication market: str): if market in self.markets and self.markets[market].ready: return self.markets[market].get_all_balances() elif "Paper" in market: paper_balances = self.client_config_map.paper_trade.paper_trade_account_balance if paper_balances is None: return {} return {token: Decimal(str(bal)) for token, bal in paper_balances.items()} else: await UserBalances.instance().update_exchange_balance(market, self.client_config_map) return UserBalances.instance().all_balances(market)
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)}) else: eth_bal = UserBalances.ethereum_balance() rows.append({"Asset": "ETH", "Amount": round(eth_bal, 4)}) df = pd.DataFrame(data=rows, columns=["Asset", "Amount"]) df.sort_values(by=["Asset"], inplace=True) return df
async def connect_exchange(self, # type: HummingbotApplication exchange): self.app.clear_input() self.placeholder_mode = True self.app.hide_input = True if exchange == "kraken": self._notify("Reminder: Please ensure your Kraken API Key Nonce Window is at least 10.") exchange_configs = [c for c in global_config_map.values() if c.key in settings.CONNECTOR_SETTINGS[exchange].config_keys and c.is_connect_key] 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: 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)? >>> " else: prompt = f"Would you like to replace your existing {exchange_configs[0].key} (Yes/No)? >>> " answer = await self.app.prompt(prompt=prompt) if self.app.to_stop_config: self.app.to_stop_config = False return if answer.lower() not in ("yes", "y"): to_connect = False if to_connect: for config in exchange_configs: await self.prompt_a_config(config) if self.app.to_stop_config: self.app.to_stop_config = False return Security.update_secure_config(config.key, config.value) api_keys = await Security.api_keys(exchange) network_timeout = float(global_config_map["other_commands_timeout"].value) try: err_msg = await asyncio.wait_for( UserBalances.instance().add_exchange(exchange, **api_keys), network_timeout ) except asyncio.TimeoutError: self._notify("\nA network error prevented the connection to complete. See logs for more details.") self.placeholder_mode = False self.app.hide_input = False self.app.change_prompt(prompt=">>> ") raise if err_msg is None: self._notify(f"\nYou are now connected to {exchange}.") else: self._notify(f"\nError: {err_msg}") self.placeholder_mode = False self.app.hide_input = False self.app.change_prompt(prompt=">>> ")
async def show_balances(self): self._notify("Updating balances, please wait...") df = await self.balances_df() lines = [ " " + line for line in df.to_string(index=False).split("\n") ] self._notify("\n".join(lines)) eth_address = global_config_map["ethereum_wallet"].value if eth_address is not None: bal = UserBalances.ethereum_balance() bal = round(bal, 4) self._notify( f"Ethereum balance in ...{eth_address[-4:]} wallet: {bal} ETH") self._notify( f"Note: You may have other ERC 20 tokens in this same address (not shown here)." )
async def validate_required_connections(self) -> Dict[str, str]: invalid_conns = {} if self.strategy_name == "celo_arb": err_msg = await self.validate_n_connect_celo(True) if err_msg is not None: invalid_conns["celo"] = err_msg if not global_config_map.get("paper_trade_enabled").value: 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() if ex in required_exchanges and err_msg is not None}) if any(ex in DEXES for ex in required_exchanges): err_msg = UserBalances.validate_ethereum_wallet() if err_msg is not None: invalid_conns["ethereum"] = err_msg return invalid_conns
async def get_current_balances(self, # type: HummingbotApplication market: str): paper_trade_suffix = '_PaperTrade' if market in self.markets and self.markets[market].ready: return self.markets[market].get_all_balances() elif paper_trade_suffix in market: return self.markets[market[:-len(paper_trade_suffix)]].get_all_balances() elif "perpetual_finance" == market: return await UserBalances.xdai_balances() else: gateway_eth_connectors = [cs.name for cs in 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)
async def validate_required_connections(self) -> Dict[str, str]: invalid_conns = {} if self.strategy_name == "celo_arb": 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 ]): 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() if ex in required_exchanges and err_msg is not None }) if ethereum_wallet_required(): err_msg = UserBalances.validate_ethereum_wallet() if err_msg is not None: invalid_conns["ethereum"] = err_msg return invalid_conns
async def show_balances(self # type: HummingbotApplication ): total_col_name = f'Total ({RateOracle.global_token_symbol})' sum_not_for_show_name = "sum_not_for_show" self.notify("Updating balances, please wait...") network_timeout = float( self.client_config_map.commands_timeout.other_commands_timeout) try: all_ex_bals = await asyncio.wait_for( UserBalances.instance().all_balances_all_exchanges( self.client_config_map), network_timeout) except asyncio.TimeoutError: self.notify( "\nA network error prevented the balances to update. See logs for more details." ) raise all_ex_avai_bals = UserBalances.instance( ).all_available_balances_all_exchanges() exchanges_total = 0 for exchange, bals in all_ex_bals.items(): self.notify(f"\n{exchange}:") df, allocated_total = await self.exchange_balances_extra_df( exchange, bals, all_ex_avai_bals.get(exchange, {})) if df.empty: self.notify("You have no balance on this exchange.") else: lines = [ " " + line for line in df.drop(sum_not_for_show_name, axis=1). to_string(index=False).split("\n") ] self.notify("\n".join(lines)) self.notify( f"\n Total: {RateOracle.global_token_symbol} " f"{PerformanceMetrics.smart_round(df[total_col_name].sum())}" ) allocated_percentage = 0 if df[sum_not_for_show_name].sum() != Decimal("0"): allocated_percentage = allocated_total / df[ sum_not_for_show_name].sum() self.notify(f"Allocated: {allocated_percentage:.2%}") exchanges_total += df[total_col_name].sum() self.notify( f"\n\nExchanges Total: {RateOracle.global_token_symbol} {exchanges_total:.0f} " ) celo_address = CELO_KEYS.celo_address if hasattr( CELO_KEYS, "celo_address") else None if celo_address is not None: try: 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") ] self.notify("\ncelo:") self.notify("\n".join(lines)) except Exception as e: self.notify(f"\ncelo CLI Error: {str(e)}")
async def start_check(self, # type: HummingbotApplication log_level: Optional[str] = None, restore: Optional[bool] = False, strategy_file_name: Optional[str] = None): if self.strategy_task is not None and not self.strategy_task.done(): self.notify('The bot is already running - please run "stop" first') return if settings.required_rate_oracle: # If the strategy to run requires using the rate oracle to find FX rates, validate there is a rate for # each configured token pair if not (await self.confirm_oracle_conversion_rate()): self.notify("The strategy failed to start.") return if strategy_file_name: file_name = strategy_file_name.split(".")[0] self.strategy_file_name = file_name self.strategy_name = file_name elif not await self.status_check_all(notify_success=False): self.notify("Status checks failed. Start aborted.") return if self._last_started_strategy_file != self.strategy_file_name: init_logging("hummingbot_logs.yml", override_log_level=log_level.upper() if log_level else None, strategy_file_path=self.strategy_file_name) self._last_started_strategy_file = self.strategy_file_name # If macOS, disable App Nap. if platform.system() == "Darwin": import appnope appnope.nope() self._initialize_notifiers() try: self._initialize_strategy(self.strategy_name) except NotImplementedError: self.strategy_name = None self.strategy_file_name = None self.notify("Invalid strategy. Start aborted.") raise 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: connector: str = str(exchange) status: str = get_connector_status(connector) warning_msg: Optional[str] = warning_messages.get(connector, None) # confirm gateway connection conn_setting: settings.ConnectorSetting = settings.AllConnectorSettings.get_connector_settings()[connector] if conn_setting.uses_gateway_generic_connector(): connector_details: Dict[str, Any] = conn_setting.conn_init_parameters() if connector_details: data: List[List[str]] = [ ["chain", connector_details['chain']], ["network", connector_details['network']], ["wallet_address", connector_details['wallet_address']] ] await UserBalances.instance().update_exchange_balance(connector) balances: List[str] = [ f"{str(PerformanceMetrics.smart_round(v, 8))} {k}" for k, v in UserBalances.instance().all_balances(connector).items() ] data.append(["balances", ""]) for bal in balances: data.append(["", bal]) wallet_df: pd.DataFrame = pd.DataFrame(data=data, columns=["", f"{connector} configuration"]) self.notify(wallet_df.to_string(index=False)) self.app.clear_input() self.placeholder_mode = True use_configuration = await self.app.prompt(prompt="Do you want to continue? (Yes/No) >>> ") self.placeholder_mode = False self.app.change_prompt(prompt=">>> ") if use_configuration in ["N", "n", "No", "no"]: return if use_configuration not in ["Y", "y", "Yes", "yes"]: self.notify("Invalid input. Please execute the `start` command again.") return # Display custom warning message for specific connectors elif warning_msg is not None: self.notify(f"\nConnector status: {status}\n" f"{warning_msg}") # Display warning message if the exchange connector has outstanding issues or not working elif not status.endswith("GREEN"): self.notify(f"\nConnector status: {status}. This connector has one or more issues.\n" "Refer to our Github page for more info: https://github.com/coinalpha/hummingbot") self.notify(f"\nStatus check complete. Starting '{self.strategy_name}' strategy...") await self.start_market_making(restore) # We always start the RateOracle. It is required for PNL calculation. RateOracle.get_instance().start()