def test_create_command_restores_config_map_after_config_stop(self): base_strategy = "pure_market_making" strategy_config = get_strategy_config_map(base_strategy) original_exchange = "bybit" strategy_config["exchange"].value = original_exchange self.cli_mock_assistant.queue_prompt_reply(base_strategy) # strategy self.cli_mock_assistant.queue_prompt_reply("binance") # spot connector self.cli_mock_assistant.queue_prompt_to_stop_config() # cancel on trading pair prompt self.async_run_with_timeout(self.app.prompt_for_configuration(None)) strategy_config = get_strategy_config_map(base_strategy) self.assertEqual(original_exchange, strategy_config["exchange"].value)
def test_strategy_config_template_complete(self): folder = realpath(join(__file__, "../../../../../hummingbot/strategy")) # Only include valid directories strategies = [ d for d in listdir(folder) if isdir(join(folder, d)) and not d.startswith("__") ] strategies.sort() for strategy in strategies: strategy_template_path: str = get_strategy_template_path(strategy) strategy_config_map = get_strategy_config_map(strategy) with open(strategy_template_path, "r") as template_fd: template_data = yaml_parser.load(template_fd) template_version = template_data.get("template_version", 0) self.assertGreaterEqual( template_version, 1, f"Template version too low at {strategy_template_path}") for key in template_data: if key == "template_version": continue self.assertTrue(key in strategy_config_map, f"{key} not in {strategy}_config_map") for key in strategy_config_map: self.assertTrue(key in template_data, f"{key} not in {strategy_template_path}")
def list_configs( self, # type: HummingbotApplication ): columns: List[str] = ["Key", "Current Value"] global_cvs: List[ConfigVar] = list( in_memory_config_map.values()) + list(global_config_map.values()) global_data: List[List[Any]] = [[ cv.key, len(str(cv.value)) * "*" if cv.is_secure else str(cv.value) ] for cv in global_cvs] global_df: pd.DataFrame = pd.DataFrame(data=global_data, columns=columns) self._notify("\nglobal configs:") self._notify(str(global_df)) strategy = in_memory_config_map.get("strategy").value if strategy: strategy_cvs: List[ConfigVar] = get_strategy_config_map( strategy).values() strategy_data: List[List[Any]] = [[ cv.key, len(str(cv.value)) * "*" if cv.is_secure else str(cv.value) ] for cv in strategy_cvs] strategy_df: pd.DataFrame = pd.DataFrame(data=strategy_data, columns=columns) self._notify(f"\n{strategy} strategy configs:") self._notify(str(strategy_df)) self._notify("\n")
async def reset_config_loop(self, # type: HummingbotApplication key: str = None): strategy = in_memory_config_map.get("strategy").value strategy_cm = get_strategy_config_map(strategy) self.placeholder_mode = True self.app.toggle_hide_input() if self.strategy: choice = await self.app.prompt(prompt=f"Would you like to stop running the {strategy} strategy " f"and reconfigure the bot? (y/n) >>> ") else: choice = await self.app.prompt(prompt=f"Would you like to reconfigure the bot? (y/n) >>> ") self.app.change_prompt(prompt=">>> ") self.app.toggle_hide_input() self.placeholder_mode = False if choice.lower() in {"y", "yes"}: if self.strategy: await self.stop_loop() if key is None: # Clear original strategy config map if strategy_cm: for k in strategy_cm: strategy_cm[k].value = None in_memory_config_map.get("strategy").value = None in_memory_config_map.get("strategy_file_path").value = None self.clear_application_warning() self.config(key) else: self._notify("Aborted.")
async def prompt_for_configuration(self, # type: HummingbotApplication file_name): self.app.clear_input() self.placeholder_mode = True self.app.hide_input = True required_exchanges.clear() strategy_config = ConfigVar(key="strategy", prompt="What is your market making strategy? >>> ", validator=validate_strategy) await self.prompt_a_config(strategy_config) if self.app.to_stop_config: self.app.to_stop_config = False return strategy = strategy_config.value config_map = get_strategy_config_map(strategy) self._notify(f"Please see https://docs.hummingbot.io/strategies/{strategy.replace('_', '-')}/ " 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 for config in config_map.values(): if config.prompt_on_new and config.required: if not self.app.to_stop_config: await self.prompt_a_config(config) else: self.app.to_stop_config = False return else: config.value = config.default # catch a last key binding to stop config, if any if self.app.to_stop_config: self.app.to_stop_config = False return if file_name is None: file_name = await self.prompt_new_file_name(strategy) if self.app.to_stop_config: self.app.to_stop_config = False self.app.set_text("") return self.app.change_prompt(prompt=">>> ") strategy_path = os.path.join(CONF_FILE_PATH, file_name) template = get_strategy_template_path(strategy) shutil.copy(template, strategy_path) save_to_yml(strategy_path, config_map) self.strategy_file_name = file_name self.strategy_name = strategy # Reload completer here otherwise the new file will not appear self.app.input_field.completer = load_completer(self) self._notify(f"A new config file {self.strategy_file_name} created.") self.placeholder_mode = False self.app.hide_input = False if await self.status_check_all(): self._notify("\nEnter \"start\" to start market making.")
def get_all_available_config_keys() -> List[str]: all_available_config_keys = list(in_memory_config_map.keys()) + list( global_config_map.keys()) current_strategy: str = in_memory_config_map.get("strategy").value strategy_cm: Optional[Dict[str, ConfigVar]] = get_strategy_config_map( current_strategy) if strategy_cm: all_available_config_keys += list(strategy_cm.keys()) return all_available_config_keys
def get_all_available_config_keys() -> List[str]: """ Returns a list of config keys that are currently relevant, including the ones that are not required. """ all_available_config_keys = list(in_memory_config_map.keys()) + list(global_config_map.keys()) current_strategy: str = in_memory_config_map.get("strategy").value strategy_cm: Optional[Dict[str, ConfigVar]] = get_strategy_config_map(current_strategy) if strategy_cm: all_available_config_keys += list(strategy_cm.keys()) return all_available_config_keys
def test_create_command_restores_config_map_after_config_stop_on_new_file_prompt(self): base_strategy = "pure_market_making" strategy_config = get_strategy_config_map(base_strategy) original_exchange = "bybit" strategy_config["exchange"].value = original_exchange self.cli_mock_assistant.queue_prompt_reply(base_strategy) # strategy self.cli_mock_assistant.queue_prompt_reply("binance") # spot connector self.cli_mock_assistant.queue_prompt_reply("BTC-USDT") # trading pair self.cli_mock_assistant.queue_prompt_reply("1") # bid spread self.cli_mock_assistant.queue_prompt_reply("1") # ask spread self.cli_mock_assistant.queue_prompt_reply("30") # order refresh time self.cli_mock_assistant.queue_prompt_reply("1") # order amount self.cli_mock_assistant.queue_prompt_reply("No") # ping pong feature self.cli_mock_assistant.queue_prompt_to_stop_config() # cancel on new file prompt self.async_run_with_timeout(self.app.prompt_for_configuration(None)) strategy_config = get_strategy_config_map(base_strategy) self.assertEqual(original_exchange, strategy_config["exchange"].value)
def _get_config_var_with_key(key: str) -> ConfigVar: current_strategy: str = in_memory_config_map.get("strategy").value strategy_cm: Optional[Dict[str, ConfigVar]] = get_strategy_config_map( current_strategy) if key in in_memory_config_map: cv: ConfigVar = in_memory_config_map.get(key) elif key in global_config_map: cv: ConfigVar = global_config_map.get(key) elif strategy_cm is not None and key in strategy_cm: cv: ConfigVar = strategy_cm.get(key) else: raise ValueError( f"No config variable associated with key name {key}") return cv
def _get_config_var_with_key(key: str) -> ConfigVar: """ Check if key exists in `in_memory_config-map`, `global_config_map`, and `strategy_config_map`. If so, return the corresponding ConfigVar for that key """ current_strategy: str = in_memory_config_map.get("strategy").value strategy_cm: Optional[Dict[str, ConfigVar]] = get_strategy_config_map(current_strategy) if key in in_memory_config_map: cv: ConfigVar = in_memory_config_map.get(key) elif key in global_config_map: cv: ConfigVar = global_config_map.get(key) elif strategy_cm is not None and key in strategy_cm: cv: ConfigVar = strategy_cm.get(key) else: raise ValueError(f"No config variable associated with key name {key}") return cv
async def _inner_config_loop(self, _keys: List[str], single_key: bool): keys = self.key_filter(_keys) for key in keys: current_strategy: str = in_memory_config_map.get("strategy").value strategy_cm: Dict[str, ConfigVar] = get_strategy_config_map(current_strategy) if key in in_memory_config_map: cv: ConfigVar = in_memory_config_map.get(key) elif key in global_config_map: cv: ConfigVar = global_config_map.get(key) else: cv: ConfigVar = strategy_cm.get(key) value = await self.config_single_variable(cv, is_single_key=single_key) cv.value = parse_cvar_value(cv, value) if single_key: self._notify(f"\nNew config saved:\n{key}: {str(value)}") if not self.config_complete: await self._inner_config_loop(self._get_empty_configs(), single_key)
async def inner_loop(_keys: List[str]): for key in _keys: current_strategy: str = in_memory_config_map.get( "strategy").value strategy_cm: Dict[str, ConfigVar] = get_strategy_config_map( current_strategy) if key in in_memory_config_map: cv: ConfigVar = in_memory_config_map.get(key) elif key in global_config_map: cv: ConfigVar = global_config_map.get(key) else: cv: ConfigVar = strategy_cm.get(key) value = await single_prompt(cv) cv.value = parse_cvar_value(cv, value) if single_key: self.app.log(f"\nNew config saved:\n{key}: {str(value)}") if not self.config_complete: await inner_loop(self._get_empty_configs())
async def prompt_for_configuration( self, # type: HummingbotApplication file_name, ): self.app.clear_input() self.placeholder_mode = True self.app.hide_input = True required_exchanges.clear() strategy = await self.get_strategy_name() if self.app.to_stop_config: return config_map = get_strategy_config_map(strategy) self.notify(f"Please see https://docs.hummingbot.io/strategies/{strategy.replace('_', '-')}/ " f"while setting up these below configuration.") if isinstance(config_map, ClientConfigAdapter): await self.prompt_for_model_config(config_map) if not self.app.to_stop_config: file_name = await self.save_config_to_file(file_name, config_map) elif config_map is not None: file_name = await self.prompt_for_configuration_legacy(file_name, strategy, config_map) else: self.app.to_stop_config = True if self.app.to_stop_config: return save_previous_strategy_value(file_name, self.client_config_map) self.strategy_file_name = file_name self.strategy_name = strategy self.strategy_config_map = config_map # Reload completer here otherwise the new file will not appear self.app.input_field.completer = load_completer(self) self.notify(f"A new config file has been created: {self.strategy_file_name}") self.placeholder_mode = False self.app.hide_input = False await self.verify_status()
def strategy_config_map(self): if self.strategy_name is not None: return get_strategy_config_map(self.strategy_name) return None
def test_get_strategy_config_map(self): cm = get_strategy_config_map(strategy="avellaneda_market_making") self.assertIsInstance(cm.hb_config, AvellanedaMarketMakingConfigMap) self.assertFalse(hasattr(cm, "market")) # uninitialized instance
def missing_configurations(self) -> List[str]: missing_globals = missing_required_configs(global_config_map) missing_configs = missing_required_configs(get_strategy_config_map(self.strategy_name)) return missing_globals + missing_configs
async def start_market_making(self, strategy_name: str): strategy_cm = get_strategy_config_map(strategy_name) if strategy_name == "cross_exchange_market_making": maker_market = strategy_cm.get("maker_market").value.lower() taker_market = strategy_cm.get("taker_market").value.lower() raw_maker_symbol = strategy_cm.get( "maker_market_symbol").value.upper() raw_taker_symbol = strategy_cm.get( "taker_market_symbol").value.upper() min_profitability = strategy_cm.get("min_profitability").value trade_size_override = strategy_cm.get("trade_size_override").value strategy_report_interval = global_config_map.get( "strategy_report_interval").value limit_order_min_expiration = strategy_cm.get( "limit_order_min_expiration").value cancel_order_threshold = strategy_cm.get( "cancel_order_threshold").value active_order_canceling = strategy_cm.get( "active_order_canceling").value top_depth_tolerance_rules = [(re.compile(re_str), value) for re_str, value in strategy_cm.get( "top_depth_tolerance").value] top_depth_tolerance = 0.0 for regex, tolerance_value in top_depth_tolerance_rules: if regex.match(raw_maker_symbol) is not None: top_depth_tolerance = tolerance_value try: maker_assets: Tuple[str, str] = SymbolSplitter.split( maker_market, raw_maker_symbol) taker_assets: Tuple[str, str] = SymbolSplitter.split( taker_market, raw_taker_symbol) except ValueError as e: self.app.log(str(e)) return market_names: List[Tuple[str, List[str]]] = [ (maker_market, [raw_maker_symbol]), (taker_market, [raw_taker_symbol]) ] self._initialize_wallet( token_symbols=list(set(maker_assets + taker_assets))) self._initialize_markets(market_names) self.assets = set(maker_assets + taker_assets) self.market_pair = CrossExchangeMarketPair( *([self.markets[maker_market], raw_maker_symbol] + list(maker_assets) + [self.markets[taker_market], raw_taker_symbol] + list(taker_assets) + [top_depth_tolerance])) strategy_logging_options = ( CrossExchangeMarketMakingStrategy.OPTION_LOG_CREATE_ORDER | CrossExchangeMarketMakingStrategy.OPTION_LOG_ADJUST_ORDER | CrossExchangeMarketMakingStrategy.OPTION_LOG_MAKER_ORDER_FILLED | CrossExchangeMarketMakingStrategy.OPTION_LOG_REMOVING_ORDER | CrossExchangeMarketMakingStrategy.OPTION_LOG_STATUS_REPORT | CrossExchangeMarketMakingStrategy.OPTION_LOG_MAKER_ORDER_HEDGED ) self.strategy = CrossExchangeMarketMakingStrategy( market_pairs=[self.market_pair], min_profitability=min_profitability, status_report_interval=strategy_report_interval, logging_options=strategy_logging_options, trade_size_override=trade_size_override, limit_order_min_expiration=limit_order_min_expiration, cancel_order_threshold=cancel_order_threshold, active_order_canceling=active_order_canceling) elif strategy_name == "arbitrage": primary_market = strategy_cm.get("primary_market").value.lower() secondary_market = strategy_cm.get( "secondary_market").value.lower() raw_primary_symbol = strategy_cm.get( "primary_market_symbol").value.upper() raw_secondary_symbol = strategy_cm.get( "secondary_market_symbol").value.upper() min_profitability = strategy_cm.get("min_profitability").value try: primary_assets: Tuple[str, str] = SymbolSplitter.split( primary_market, raw_primary_symbol) secondary_assets: Tuple[str, str] = SymbolSplitter.split( secondary_market, raw_secondary_symbol) except ValueError as e: self.app.log(str(e)) return market_names: List[Tuple[str, List[str]]] = [ (primary_market, [raw_primary_symbol]), (secondary_market, [raw_secondary_symbol]) ] self._initialize_wallet( token_symbols=list(set(primary_assets + secondary_assets))) self._initialize_markets(market_names) self.assets = set(primary_assets + secondary_assets) self.market_pair = ArbitrageMarketPair( *([self.markets[primary_market], raw_primary_symbol] + list(primary_assets) + [self.markets[secondary_market], raw_secondary_symbol] + list(secondary_assets))) strategy_logging_options = ArbitrageStrategy.OPTION_LOG_ALL self.strategy = ArbitrageStrategy( market_pairs=[self.market_pair], min_profitability=min_profitability, logging_options=strategy_logging_options) elif strategy_name == "pure_market_making": order_size = strategy_cm.get("order_amount").value cancel_order_wait_time = strategy_cm.get( "cancel_order_wait_time").value bid_place_threshold = strategy_cm.get("bid_place_threshold").value ask_place_threshold = strategy_cm.get("ask_place_threshold").value maker_market = strategy_cm.get("maker_market").value.lower() raw_maker_symbol = strategy_cm.get( "maker_market_symbol").value.upper() try: primary_assets: Tuple[str, str] = SymbolSplitter.split( maker_market, raw_maker_symbol) except ValueError as e: self.app.log(str(e)) return market_names: List[Tuple[str, List[str]]] = [(maker_market, [raw_maker_symbol])] self._initialize_wallet(token_symbols=list(set(primary_assets))) self._initialize_markets(market_names) self.assets = set(primary_assets) self.market_pair = PureMarketPair( *([self.markets[maker_market], raw_maker_symbol] + list(primary_assets))) strategy_logging_options = PureMarketMakingStrategy.OPTION_LOG_ALL self.strategy = PureMarketMakingStrategy( market_pairs=[self.market_pair], order_size=order_size, bid_place_threshold=bid_place_threshold, ask_place_threshold=ask_place_threshold, cancel_order_wait_time=cancel_order_wait_time, logging_options=strategy_logging_options) elif strategy_name == "discovery": try: market_1 = strategy_cm.get("primary_market").value.lower() market_2 = strategy_cm.get("secondary_market").value.lower() target_symbol_1 = list( strategy_cm.get("target_symbol_1").value) target_symbol_2 = list( strategy_cm.get("target_symbol_2").value) target_profitability = float( strategy_cm.get("target_profitability").value) target_amount = float(strategy_cm.get("target_amount").value) equivalent_token: List[List[str]] = list( strategy_cm.get("equivalent_tokens").value) if not target_symbol_2: target_symbol_2 = SymbolFetcher.get_instance().symbols.get( market_2, []) if not target_symbol_1: target_symbol_1 = SymbolFetcher.get_instance().symbols.get( market_1, []) market_names: List[Tuple[str, List[str]]] = [ (market_1, target_symbol_1), (market_2, target_symbol_2) ] target_base_quote_1: List[Tuple[str, str]] = [ SymbolSplitter.split(market_1, symbol) for symbol in target_symbol_1 ] target_base_quote_2: List[Tuple[str, str]] = [ SymbolSplitter.split(market_2, symbol) for symbol in target_symbol_2 ] self._trading_required = False self._initialize_wallet( token_symbols=[] ) # wallet required only for dex hard dependency self._initialize_markets(market_names) self.market_pair = DiscoveryMarketPair(*([ self.markets[market_1], self.markets[market_1].get_active_exchange_markets ] + [ self.markets[market_2], self.markets[market_2].get_active_exchange_markets ])) self.strategy = DiscoveryStrategy( market_pairs=[self.market_pair], target_symbols=target_base_quote_1 + target_base_quote_2, equivalent_token=equivalent_token, target_profitability=target_profitability, target_amount=target_amount) except Exception as e: self.app.log(str(e)) self.logger().error("Error initializing strategy.", exc_info=True) else: raise NotImplementedError try: self.clock = Clock(ClockMode.REALTIME) if self.wallet is not None: self.clock.add_iterator(self.wallet) for market in self.markets.values(): if market is not None: self.clock.add_iterator(market) if self.strategy: self.clock.add_iterator(self.strategy) self.strategy_task: asyncio.Task = asyncio.ensure_future( self._run_clock()) self.app.log( f"\n '{strategy_name}' strategy started.\n" f" You can use the `status` command to query the progress.") self.starting_balances = await self.wait_till_ready( self.balance_snapshot) self.stop_loss_tracker = StopLossTracker( self.data_feed, list(self.assets), list(self.markets.values()), lambda *args, **kwargs: asyncio.ensure_future( self.stop(*args, **kwargs))) await self.wait_till_ready(self.stop_loss_tracker.start) except Exception as e: self.logger().error(str(e), exc_info=True)
def list(self, obj: str): if obj == "wallets": wallets = list_wallets() if len(wallets) == 0: self.app.log( 'Wallet not available. Please configure your wallet (Enter "config wallet")' ) else: self.app.log('\n'.join(wallets)) elif obj == "exchanges": if len(EXCHANGES) == 0: self.app.log("No exchanges available") else: self.app.log('\n'.join(EXCHANGES)) elif obj == "configs": columns: List[str] = ["Key", "Current Value"] global_cvs: List[ConfigVar] = list( in_memory_config_map.values()) + list( global_config_map.values()) global_data: List[List[str, Any]] = [[ cv.key, len(str(cv.value)) * "*" if cv.is_secure else str(cv.value) ] for cv in global_cvs] global_df: pd.DataFrame = pd.DataFrame(data=global_data, columns=columns) self.app.log("\nglobal configs:") self.app.log(str(global_df)) strategy = in_memory_config_map.get("strategy").value if strategy: strategy_cvs: List[ConfigVar] = get_strategy_config_map( strategy).values() strategy_data: List[List[str, Any]] = [[ cv.key, len(str(cv.value)) * "*" if cv.is_secure else str(cv.value) ] for cv in strategy_cvs] strategy_df: pd.DataFrame = pd.DataFrame(data=strategy_data, columns=columns) self.app.log(f"\n{strategy} strategy configs:") self.app.log(str(strategy_df)) self.app.log("\n") elif obj == "trades": lines = [] if self.strategy is None: self.app.log("No strategy available, cannot show past trades.") else: if len(self.strategy.trades) > 0: df = Trade.to_pandas(self.strategy.trades) df_lines = str(df).split("\n") lines.extend(["", " Past trades:"] + [" " + line for line in df_lines]) else: lines.extend([" No past trades."]) self.app.log("\n".join(lines)) else: self.help("list")
async def prompt_for_configuration( self, # type: HummingbotApplication file_name): self.app.clear_input() self.placeholder_mode = True self.app.hide_input = True required_exchanges.clear() strategy_config = ConfigVar( key="strategy", prompt="What is your market making strategy? >>> ", validator=validate_strategy) await self.prompt_a_config(strategy_config) if self.app.to_stop_config: self.stop_config() return strategy = strategy_config.value config_map = get_strategy_config_map(strategy) config_map_backup = copy.deepcopy(config_map) self._notify( f"Please see https://docs.hummingbot.io/strategies/{strategy.replace('_', '-')}/ " 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 for config in config_map.values(): if config.prompt_on_new and config.required: if not self.app.to_stop_config: await self.prompt_a_config(config) else: break else: config.value = config.default if self.app.to_stop_config: self.stop_config(config_map, config_map_backup) return if file_name is None: file_name = await self.prompt_new_file_name(strategy) if self.app.to_stop_config: self.stop_config(config_map, config_map_backup) self.app.set_text("") return self.app.change_prompt(prompt=">>> ") strategy_path = os.path.join(CONF_FILE_PATH, file_name) template = get_strategy_template_path(strategy) shutil.copy(template, strategy_path) save_to_yml(strategy_path, config_map) self.strategy_file_name = file_name self.strategy_name = strategy # Reload completer here otherwise the new file will not appear self.app.input_field.completer = load_completer(self) self._notify(f"A new config file {self.strategy_file_name} created.") self.placeholder_mode = False self.app.hide_input = False try: timeout = float(global_config_map["create_command_timeout"].value) all_status_go = await asyncio.wait_for(self.status_check_all(), timeout) except asyncio.TimeoutError: self._notify( "\nA network error prevented the connection check to complete. See logs for more details." ) self.strategy_file_name = None self.strategy_name = None raise if all_status_go: self._notify("\nEnter \"start\" to start market making.")