コード例 #1
0
    def __init__(self):
        self.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
        self.parser: ThrowingArgumentParser = load_parser(self)
        self.app = HummingbotCLI(
            input_handler=self._handle_command,
            bindings=load_key_bindings(self),
            completer=load_completer(self))

        self.acct: Optional[LocalAccount] = None
        self.markets: Dict[str, MarketBase] = {}
        self.wallet: Optional[Web3Wallet] = None
        self.strategy_task: Optional[asyncio.Task] = None
        self.strategy: Optional[StrategyBase] = None
        self.market_pair: Optional[CrossExchangeMarketPair] = None
        self.market_trading_pair_tuples: List[MarketTradingPairTuple] = []
        self.clock: Optional[Clock] = None

        self.init_time: int = int(time.time() * 1e3)
        self.start_time: Optional[int] = None
        self.assets: Optional[Set[str]] = set()
        self.starting_balances = {}
        self.placeholder_mode = False
        self.log_queue_listener: Optional[logging.handlers.QueueListener] = None
        self.reporting_module: Optional[ReportAggregator] = None
        self.data_feed: Optional[DataFeedBase] = None
        self.notifiers: List[NotifierBase] = []
        self.kill_switch: Optional[KillSwitch] = None
        self.liquidity_bounty: Optional[LiquidityBounty] = None
        self._initialize_liquidity_bounty()
        self._app_warnings: Deque[ApplicationWarning] = deque()
        self._trading_required: bool = True

        self.trade_fill_db: SQLConnectionManager = SQLConnectionManager.get_trade_fills_instance()
        self.markets_recorder: Optional[MarketsRecorder] = None
コード例 #2
0
    def __init__(self):
        self.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
        self.parser: ThrowingArgumentParser = load_parser(self)
        self.app = HummingbotCLI(input_handler=self._handle_command,
                                 bindings=load_key_bindings(self),
                                 completer=load_completer(self))

        self.acct: Optional[LocalAccount] = None
        self.markets: Dict[str, MarketBase] = {}
        self.wallet: Optional[Web3Wallet] = None
        self.strategy_task: Optional[asyncio.Task] = None
        self.strategy: Optional[CrossExchangeMarketMakingStrategy] = None
        self.market_pair: Optional[CrossExchangeMarketPair] = None
        self.clock: Optional[Clock] = None

        self.assets: Optional[Set[str]] = set()
        self.starting_balances = {}
        self.placeholder_mode = False
        self.log_queue_listener: Optional[
            logging.handlers.QueueListener] = None
        self.reporting_module: Optional[ReportAggregator] = None
        self.data_feed: Optional[DataFeedBase] = None
        self.stop_loss_tracker: Optional[StopLossTracker] = None
        self._app_warnings: Deque[ApplicationWarning] = deque()
        self._trading_required: bool = True
コード例 #3
0
    def setUp(self) -> None:
        super().setUp()

        tabs = {self.command_name: CommandTab(self.command_name, None, None, None, MagicMock())}
        self.mock_hb = MagicMock()
        self.app = HummingbotCLI(None, None, None, tabs)
        self.app.app = MagicMock()
コード例 #4
0
    def __init__(self, client_config_map: Optional[ClientConfigAdapter] = None):
        self.client_config_map: Union[ClientConfigMap, ClientConfigAdapter] = (  # type-hint enables IDE auto-complete
            client_config_map or load_client_config_map_from_file()
        )

        # This is to start fetching trading pairs for auto-complete
        TradingPairFetcher.get_instance(self.client_config_map)
        self.ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
        self.markets: Dict[str, ExchangeBase] = {}
        # strategy file name and name get assigned value after import or create command
        self._strategy_file_name: Optional[str] = None
        self.strategy_name: Optional[str] = None
        self._strategy_config_map: Optional[BaseStrategyConfigMap] = None
        self.strategy_task: Optional[asyncio.Task] = None
        self.strategy: Optional[StrategyBase] = None
        self.market_pair: Optional[CrossExchangeMarketPair] = None
        self.market_trading_pair_tuples: List[MarketTradingPairTuple] = []
        self.clock: Optional[Clock] = None
        self.market_trading_pairs_map = {}
        self.token_list = {}

        self.init_time: float = time.time()
        self.start_time: Optional[int] = None
        self.placeholder_mode = False
        self.log_queue_listener: Optional[logging.handlers.QueueListener] = None
        self.data_feed: Optional[DataFeedBase] = None
        self.notifiers: List[NotifierBase] = []
        self.kill_switch: Optional[KillSwitch] = None
        self._app_warnings: Deque[ApplicationWarning] = deque()
        self._trading_required: bool = True
        self._last_started_strategy_file: Optional[str] = None

        self.trade_fill_db: Optional[SQLConnectionManager] = None
        self.markets_recorder: Optional[MarketsRecorder] = None
        self._pmm_script_iterator = None
        self._binance_connector = None
        self._shared_client = None

        # gateway variables and monitor
        self._gateway_monitor = GatewayStatusMonitor(self)

        command_tabs = self.init_command_tabs()
        self.parser: ThrowingArgumentParser = load_parser(self, command_tabs)
        self.app = HummingbotCLI(
            self.client_config_map,
            input_handler=self._handle_command,
            bindings=load_key_bindings(self),
            completer=load_completer(self),
            command_tabs=command_tabs
        )

        self._init_gateway_monitor()
コード例 #5
0
    def setUp(self) -> None:
        super().setUp()

        self.client_config_map = ClientConfigAdapter(ClientConfigMap())
        tabs = {
            self.command_name:
            CommandTab(self.command_name, None, None, None, MagicMock())
        }
        self.mock_hb = MagicMock()
        self.app = HummingbotCLI(client_config_map=self.client_config_map,
                                 input_handler=None,
                                 bindings=None,
                                 completer=None,
                                 command_tabs=tabs)
        self.app.app = MagicMock()
        self.hb = HummingbotApplication()
コード例 #6
0
    def __init__(self):
        # This is to start fetching trading pairs for auto-complete
        TradingPairFetcher.get_instance()
        self.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
        # # Enable main thread debug mode to make sure Callbacks taking longer than 50ms are logged.
        # self.ev_loop.set_debug(True)
        # self.ev_loop.slow_callback_duration = 0.001  # 1ms
        self.parser: ThrowingArgumentParser = load_parser(self)
        self.app = HummingbotCLI(input_handler=self._handle_command,
                                 bindings=load_key_bindings(self),
                                 completer=load_completer(self))

        self.markets: Dict[str, ExchangeBase] = {}
        self.wallet: Optional[Web3Wallet] = None
        # strategy file name and name get assigned value after import or create command
        self._strategy_file_name: str = None
        self.strategy_name: str = None
        self.strategy_task: Optional[asyncio.Task] = None
        self.strategy: Optional[StrategyBase] = None
        self.market_pair: Optional[CrossExchangeMarketPair] = None
        self.market_trading_pair_tuples: List[MarketTradingPairTuple] = []
        self.clock: Optional[Clock] = None
        self.market_trading_pairs_map = {}
        self.token_list = {}

        self.init_time: float = time.time()
        self.start_time: Optional[int] = None
        self.assets: Optional[Set[str]] = set()
        self.placeholder_mode = False
        self.log_queue_listener: Optional[
            logging.handlers.QueueListener] = None
        self.data_feed: Optional[DataFeedBase] = None
        self.notifiers: List[NotifierBase] = []
        self.kill_switch: Optional[KillSwitch] = None
        self._app_warnings: Deque[ApplicationWarning] = deque()
        self._trading_required: bool = True
        self._last_started_strategy_file: Optional[str] = None

        self.trade_fill_db: Optional[SQLConnectionManager] = None
        self.markets_recorder: Optional[MarketsRecorder] = None
        self._script_iterator = None
        self._binance_connector = None
コード例 #7
0
    def __init__(self):
        self.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
        self.parser: ThrowingArgumentParser = load_parser(self)
        self.app = HummingbotCLI(input_handler=self._handle_command,
                                 bindings=load_key_bindings(self),
                                 completer=load_completer(self))

        self.markets: Dict[str, ExchangeBase] = {}
        self.wallet: Optional[Web3Wallet] = None
        # strategy file name and name get assigned value after import or create command
        self.strategy_file_name: str = None
        self.strategy_name: str = None
        self.strategy_task: Optional[asyncio.Task] = None
        self.strategy: Optional[StrategyBase] = None
        self.market_pair: Optional[CrossExchangeMarketPair] = None
        self.market_trading_pair_tuples: List[MarketTradingPairTuple] = []
        self.clock: Optional[Clock] = None

        self.init_time: int = int(time.time() * 1e3)
        self.start_time: Optional[int] = None
        self.assets: Optional[Set[str]] = set()
        self.starting_balances = {}
        self.placeholder_mode = False
        self.log_queue_listener: Optional[
            logging.handlers.QueueListener] = None
        self.data_feed: Optional[DataFeedBase] = None
        self.notifiers: List[NotifierBase] = []
        self.kill_switch: Optional[KillSwitch] = None
        self._app_warnings: Deque[ApplicationWarning] = deque()
        self._trading_required: bool = True

        self.trade_fill_db: SQLConnectionManager = SQLConnectionManager.get_trade_fills_instance(
        )
        self.markets_recorder: Optional[MarketsRecorder] = None
        self._script_iterator = None
        # This is to start fetching trading pairs for auto-complete
        TradingPairFetcher.get_instance()
コード例 #8
0
class HummingbotApplication(*commands):
    KILL_TIMEOUT = 10.0
    IDEX_KILL_TIMEOUT = 30.0
    APP_WARNING_EXPIRY_DURATION = 3600.0
    APP_WARNING_STATUS_LIMIT = 6

    _main_app: Optional["HummingbotApplication"] = None

    @classmethod
    def logger(cls) -> HummingbotLogger:
        global s_logger
        if s_logger is None:
            s_logger = logging.getLogger(__name__)
        return s_logger

    @classmethod
    def main_application(cls) -> "HummingbotApplication":
        if cls._main_app is None:
            cls._main_app = HummingbotApplication()
        return cls._main_app

    def __init__(self):
        self.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
        self.parser: ThrowingArgumentParser = load_parser(self)
        self.app = HummingbotCLI(
            input_handler=self._handle_command,
            bindings=load_key_bindings(self),
            completer=load_completer(self))

        self.acct: Optional[LocalAccount] = None
        self.markets: Dict[str, MarketBase] = {}
        self.wallet: Optional[Web3Wallet] = None
        self.strategy_task: Optional[asyncio.Task] = None
        self.strategy: Optional[StrategyBase] = None
        self.market_pair: Optional[CrossExchangeMarketPair] = None
        self.market_trading_pair_tuples: List[MarketTradingPairTuple] = []
        self.clock: Optional[Clock] = None

        self.init_time: int = int(time.time() * 1e3)
        self.start_time: Optional[int] = None
        self.assets: Optional[Set[str]] = set()
        self.starting_balances = {}
        self.placeholder_mode = False
        self.log_queue_listener: Optional[logging.handlers.QueueListener] = None
        self.reporting_module: Optional[ReportAggregator] = None
        self.data_feed: Optional[DataFeedBase] = None
        self.notifiers: List[NotifierBase] = []
        self.kill_switch: Optional[KillSwitch] = None
        self.liquidity_bounty: Optional[LiquidityBounty] = None
        self._initialize_liquidity_bounty()
        self._app_warnings: Deque[ApplicationWarning] = deque()
        self._trading_required: bool = True

        self.trade_fill_db: SQLConnectionManager = SQLConnectionManager.get_trade_fills_instance()
        self.markets_recorder: Optional[MarketsRecorder] = None

    def init_reporting_module(self):
        if not self.reporting_module:
            self.reporting_module = ReportAggregator(
                self,
                report_aggregation_interval=global_config_map["reporting_aggregation_interval"].value,
                log_report_interval=global_config_map["reporting_log_interval"].value)
        self.reporting_module.start()

    def _notify(self, msg: str):
        self.app.log(msg)
        for notifier in self.notifiers:
            notifier.add_msg_to_queue(msg)

    def _handle_command(self, raw_command: str):
        raw_command = raw_command.lower().strip()
        try:
            if self.placeholder_mode:
                pass
            else:
                logging.getLogger("hummingbot.command_history").info(raw_command)
                args = self.parser.parse_args(args=raw_command.split())
                kwargs = vars(args)
                if not hasattr(args, "func"):
                    return
                f = args.func
                del kwargs['func']
                f(**kwargs)
        except InvalidCommandError as e:
            self._notify("Invalid command: %s" % (str(e),))
        except ArgumentParserError as e:
            self._notify(str(e))
        except NotImplementedError:
            self._notify("Command not yet implemented. This feature is currently under development.")
        except Exception as e:
            self.logger().error(e, exc_info=True)

    async def _cancel_outstanding_orders(self) -> bool:
        success = True
        try:
            on_chain_cancel_on_exit = global_config_map.get("on_chain_cancel_on_exit").value
            bamboo_relay_use_coordinator = global_config_map.get("bamboo_relay_use_coordinator").value
            kill_timeout: float = self.KILL_TIMEOUT
            self._notify("Cancelling outstanding orders...")

            for market_name, market in self.markets.items():
                if market_name == "idex":
                    self._notify(f"IDEX cancellations may take up to {int(self.IDEX_KILL_TIMEOUT)} seconds...")
                    kill_timeout = self.IDEX_KILL_TIMEOUT
                # By default, the bot does not cancel orders on exit on Radar Relay or Bamboo Relay,
                # since all open orders will expire in a short window
                if not on_chain_cancel_on_exit and (market_name == "radar_relay" or (market_name == "bamboo_relay" and not bamboo_relay_use_coordinator)):
                    continue
                cancellation_results = await market.cancel_all(kill_timeout)
                uncancelled = list(filter(lambda cr: cr.success is False, cancellation_results))
                if len(uncancelled) > 0:
                    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" % (
                        market_name,
                        '\n'.join(uncancelled_order_ids)
                    ))
        except Exception:
            self.logger().error(f"Error canceling outstanding orders.", exc_info=True)
            success = False

        if success:
            self._notify("All outstanding orders cancelled.")
        return success

    async def run(self):
        await self.app.run()

    def add_application_warning(self, app_warning: ApplicationWarning):
        self._expire_old_application_warnings()
        self._app_warnings.append(app_warning)

    def clear_application_warning(self):
        self._app_warnings.clear()

    @staticmethod
    def _initialize_market_assets(market_name: str, symbols: List[str]) -> List[Tuple[str, str]]:
        market: MarketBase = MARKET_CLASSES.get(market_name, MarketBase)
        market_symbols: List[Tuple[str, str]] = [market.split_symbol(symbol) for symbol in symbols]
        return market_symbols

    def _initialize_wallet(self, token_symbols: List[str]):
        ethereum_rpc_url = global_config_map.get("ethereum_rpc_url").value
        erc20_token_addresses = get_erc20_token_addresses(token_symbols)

        if self.acct is not None:
            self.wallet: Web3Wallet = Web3Wallet(private_key=self.acct.privateKey,
                                                 backend_urls=[ethereum_rpc_url],
                                                 erc20_token_addresses=erc20_token_addresses,
                                                 chain=EthereumChain.MAIN_NET)

    def _initialize_markets(self, market_names: List[Tuple[str, List[str]]]):
        ethereum_rpc_url = global_config_map.get("ethereum_rpc_url").value

        # aggregate symbols if there are duplicate markets
        market_symbols_map = {}
        for market_name, symbols in market_names:
            if market_name not in market_symbols_map:
                market_symbols_map[market_name] = []
            market_symbols_map[market_name] += symbols

        for market_name, symbols in market_symbols_map.items():
            if global_config_map.get("paper_trade_enabled").value:
                self._notify(f"\nPaper trade is enabled for market {market_name}")
                try:
                    market = create_paper_trade_market(market_name, symbols)
                except Exception:
                    raise
                paper_trade_account_balance = global_config_map.get("paper_trade_account_balance").value
                for asset, balance in paper_trade_account_balance:
                    market.set_balance(asset, balance)

            elif market_name == "ddex" and self.wallet:
                market = DDEXMarket(wallet=self.wallet,
                                    ethereum_rpc_url=ethereum_rpc_url,
                                    order_book_tracker_data_source_type=OrderBookTrackerDataSourceType.EXCHANGE_API,
                                    symbols=symbols,
                                    trading_required=self._trading_required)

            elif market_name == "idex" and self.wallet:
                idex_api_key: str = global_config_map.get("idex_api_key").value
                try:
                    market = IDEXMarket(idex_api_key=idex_api_key,
                                        wallet=self.wallet,
                                        ethereum_rpc_url=ethereum_rpc_url,
                                        order_book_tracker_data_source_type=OrderBookTrackerDataSourceType.EXCHANGE_API,
                                        symbols=symbols,
                                        trading_required=self._trading_required)
                except Exception as e:
                    self.logger().error(str(e))

            elif market_name == "binance":
                binance_api_key = global_config_map.get("binance_api_key").value
                binance_api_secret = global_config_map.get("binance_api_secret").value
                market = BinanceMarket(binance_api_key,
                                       binance_api_secret,
                                       order_book_tracker_data_source_type=OrderBookTrackerDataSourceType.EXCHANGE_API,
                                       symbols=symbols,
                                       trading_required=self._trading_required)

            elif market_name == "radar_relay" and self.wallet:
                market = RadarRelayMarket(wallet=self.wallet,
                                          ethereum_rpc_url=ethereum_rpc_url,
                                          symbols=symbols,
                                          trading_required=self._trading_required)

            elif market_name == "bamboo_relay" and self.wallet:
                use_coordinator = global_config_map.get("bamboo_relay_use_coordinator").value
                pre_emptive_soft_cancels = global_config_map.get("bamboo_relay_pre_emptive_soft_cancels").value
                market = BambooRelayMarket(wallet=self.wallet,
                                           ethereum_rpc_url=ethereum_rpc_url,
                                           symbols=symbols,
                                           use_coordinator=use_coordinator,
                                           pre_emptive_soft_cancels=pre_emptive_soft_cancels,
                                           trading_required=self._trading_required)

            elif market_name == "coinbase_pro":
                coinbase_pro_api_key = global_config_map.get("coinbase_pro_api_key").value
                coinbase_pro_secret_key = global_config_map.get("coinbase_pro_secret_key").value
                coinbase_pro_passphrase = global_config_map.get("coinbase_pro_passphrase").value

                market = CoinbaseProMarket(coinbase_pro_api_key,
                                           coinbase_pro_secret_key,
                                           coinbase_pro_passphrase,
                                           symbols=symbols,
                                           trading_required=self._trading_required)
            elif market_name == "huobi":
                huobi_api_key = global_config_map.get("huobi_api_key").value
                huobi_secret_key = global_config_map.get("huobi_secret_key").value
                market = HuobiMarket(huobi_api_key,
                                     huobi_secret_key,
                                     order_book_tracker_data_source_type=OrderBookTrackerDataSourceType.EXCHANGE_API,
                                     symbols=symbols,
                                     trading_required=self._trading_required)
            else:
                raise ValueError(f"Market name {market_name} is invalid.")

            self.markets[market_name]: MarketBase = market

        self.markets_recorder = MarketsRecorder(
            self.trade_fill_db,
            list(self.markets.values()),
            in_memory_config_map.get("strategy_file_path").value,
            in_memory_config_map.get("strategy").value
        )
        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))
        for notifier in self.notifiers:
            notifier.start()

    def _initialize_liquidity_bounty(self):
        if liquidity_bounty_config_map.get("liquidity_bounty_enabled").value is not None and \
           liquidity_bounty_config_map.get("liquidity_bounty_client_id").value is not None:
            self.liquidity_bounty = LiquidityBounty.get_instance()
            self.liquidity_bounty.start()
コード例 #9
0
class HummingbotApplication(*commands):
    KILL_TIMEOUT = 10.0
    APP_WARNING_EXPIRY_DURATION = 3600.0
    APP_WARNING_STATUS_LIMIT = 6

    _main_app: Optional["HummingbotApplication"] = None

    @classmethod
    def logger(cls) -> HummingbotLogger:
        global s_logger
        if s_logger is None:
            s_logger = logging.getLogger(__name__)
        return s_logger

    @classmethod
    def main_application(cls) -> "HummingbotApplication":
        if cls._main_app is None:
            cls._main_app = HummingbotApplication()
        return cls._main_app

    def __init__(self):
        self.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
        self.parser: ThrowingArgumentParser = load_parser(self)
        self.app = HummingbotCLI(input_handler=self._handle_command,
                                 bindings=load_key_bindings(self),
                                 completer=load_completer(self))

        self.markets: Dict[str, MarketBase] = {}
        self.wallet: Optional[Web3Wallet] = None
        # strategy file name and name get assigned value after import or create command
        self.strategy_file_name: str = None
        self.strategy_name: str = None
        self.strategy_task: Optional[asyncio.Task] = None
        self.strategy: Optional[StrategyBase] = None
        self.market_pair: Optional[CrossExchangeMarketPair] = None
        self.market_trading_pair_tuples: List[MarketTradingPairTuple] = []
        self.clock: Optional[Clock] = None

        self.init_time: int = int(time.time() * 1e3)
        self.start_time: Optional[int] = None
        self.assets: Optional[Set[str]] = set()
        self.starting_balances = {}
        self.placeholder_mode = False
        self.log_queue_listener: Optional[
            logging.handlers.QueueListener] = None
        self.data_feed: Optional[DataFeedBase] = None
        self.notifiers: List[NotifierBase] = []
        self.kill_switch: Optional[KillSwitch] = None
        self._app_warnings: Deque[ApplicationWarning] = deque()
        self._trading_required: bool = True

        self.trade_fill_db: SQLConnectionManager = SQLConnectionManager.get_trade_fills_instance(
        )
        self.markets_recorder: Optional[MarketsRecorder] = None
        self._script_iterator = None

    @property
    def strategy_config_map(self):
        if self.strategy_name is not None:
            return get_strategy_config_map(self.strategy_name)
        return None

    def _notify(self, msg: str):
        self.app.log(msg)
        for notifier in self.notifiers:
            notifier.add_msg_to_queue(msg)

    def _handle_command(self, raw_command: str):
        # unset to_stop_config flag it triggered before loading any command
        if self.app.to_stop_config:
            self.app.to_stop_config = False

        raw_command = raw_command.lower().strip()
        try:
            if self.placeholder_mode:
                pass
            else:
                args = self.parser.parse_args(args=raw_command.split())
                kwargs = vars(args)
                if not hasattr(args, "func"):
                    return
                f = args.func
                del kwargs["func"]
                f(**kwargs)
        except InvalidCommandError as e:
            self._notify("Invalid command: %s" % (str(e), ))
        except ArgumentParserError as e:
            if not self.be_silly(raw_command):
                self._notify(str(e))
        except NotImplementedError:
            self._notify(
                "Command not yet implemented. This feature is currently under development."
            )
        except Exception as e:
            self.logger().error(e, exc_info=True)

    async def _cancel_outstanding_orders(self) -> bool:
        success = True
        try:
            on_chain_cancel_on_exit = global_config_map.get(
                "on_chain_cancel_on_exit").value
            bamboo_relay_use_coordinator = global_config_map.get(
                "bamboo_relay_use_coordinator").value
            kill_timeout: float = self.KILL_TIMEOUT
            self._notify("Cancelling outstanding orders...")

            for market_name, market in self.markets.items():
                # By default, the bot does not cancel orders on exit on Radar Relay or Bamboo Relay,
                # since all open orders will expire in a short window
                if not on_chain_cancel_on_exit and (
                        market_name == "radar_relay" or
                    (market_name == "bamboo_relay"
                     and not bamboo_relay_use_coordinator)):
                    continue
                cancellation_results = await market.cancel_all(kill_timeout)
                uncancelled = list(
                    filter(lambda cr: cr.success is False,
                           cancellation_results))
                if len(uncancelled) > 0:
                    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" %
                        (market_name, '\n'.join(uncancelled_order_ids)))
        except Exception:
            self.logger().error("Error canceling outstanding orders.",
                                exc_info=True)
            success = False

        if success:
            self._notify("All outstanding orders cancelled.")
        return success

    async def run(self):
        await self.app.run()

    def add_application_warning(self, app_warning: ApplicationWarning):
        self._expire_old_application_warnings()
        self._app_warnings.append(app_warning)

    def clear_application_warning(self):
        self._app_warnings.clear()

    @staticmethod
    def _initialize_market_assets(
            market_name: str,
            trading_pairs: List[str]) -> List[Tuple[str, str]]:
        market_class: MarketBase = MARKET_CLASSES.get(market_name, MarketBase)
        market_trading_pairs: List[Tuple[str, str]] = [
            market_class.split_trading_pair(trading_pair)
            for trading_pair in trading_pairs
        ]
        return market_trading_pairs

    @staticmethod
    def _convert_to_exchange_trading_pair(
            market_name: str, hb_trading_pair: List[str]) -> List[str]:
        market_class: MarketBase = MARKET_CLASSES.get(market_name, MarketBase)
        return [
            market_class.convert_to_exchange_trading_pair(trading_pair)
            for trading_pair in hb_trading_pair
        ]

    def _initialize_wallet(self, token_trading_pairs: List[str]):
        if not using_wallet():
            return

        ethereum_wallet = global_config_map.get("ethereum_wallet").value
        private_key = Security._private_keys[ethereum_wallet]
        ethereum_rpc_url = global_config_map.get("ethereum_rpc_url").value
        erc20_token_addresses = get_erc20_token_addresses(token_trading_pairs)

        chain_name: str = global_config_map.get("ethereum_chain_name").value
        self.wallet: Web3Wallet = Web3Wallet(
            private_key=private_key,
            backend_urls=[ethereum_rpc_url],
            erc20_token_addresses=erc20_token_addresses,
            chain=getattr(EthereumChain, chain_name),
        )

    def _initialize_markets(self, market_names: List[Tuple[str, List[str]]]):
        ethereum_rpc_url = global_config_map.get("ethereum_rpc_url").value

        # aggregate trading_pairs if there are duplicate markets
        market_trading_pairs_map = {}
        for market_name, trading_pairs in market_names:
            if market_name not in market_trading_pairs_map:
                market_trading_pairs_map[market_name] = []
            market_class: MarketBase = MARKET_CLASSES.get(
                market_name, MarketBase)
            for trading_pair in trading_pairs:
                exchange_trading_pair: str = market_class.convert_to_exchange_trading_pair(
                    trading_pair)
                market_trading_pairs_map[market_name].append(
                    exchange_trading_pair)

        for market_name, trading_pairs in market_trading_pairs_map.items():
            if global_config_map.get("paper_trade_enabled").value:
                try:
                    market = create_paper_trade_market(market_name,
                                                       trading_pairs)
                except Exception:
                    raise
                paper_trade_account_balance = global_config_map.get(
                    "paper_trade_account_balance").value
                for asset, balance in paper_trade_account_balance.items():
                    market.set_balance(asset, balance)

            elif market_name == "binance":
                binance_api_key = global_config_map.get(
                    "binance_api_key").value
                binance_api_secret = global_config_map.get(
                    "binance_api_secret").value
                market = BinanceMarket(
                    binance_api_key,
                    binance_api_secret,
                    order_book_tracker_data_source_type=
                    OrderBookTrackerDataSourceType.EXCHANGE_API,
                    trading_pairs=trading_pairs,
                    trading_required=self._trading_required,
                )

            elif market_name == "radar_relay":
                assert self.wallet is not None
                market = RadarRelayMarket(
                    wallet=self.wallet,
                    ethereum_rpc_url=ethereum_rpc_url,
                    trading_pairs=trading_pairs,
                    trading_required=self._trading_required,
                )

            elif market_name == "bamboo_relay":
                assert self.wallet is not None
                use_coordinator = global_config_map.get(
                    "bamboo_relay_use_coordinator").value
                pre_emptive_soft_cancels = global_config_map.get(
                    "bamboo_relay_pre_emptive_soft_cancels").value
                market = BambooRelayMarket(
                    wallet=self.wallet,
                    ethereum_rpc_url=ethereum_rpc_url,
                    trading_pairs=trading_pairs,
                    use_coordinator=use_coordinator,
                    pre_emptive_soft_cancels=pre_emptive_soft_cancels,
                    trading_required=self._trading_required,
                )

            elif market_name == "coinbase_pro":
                coinbase_pro_api_key = global_config_map.get(
                    "coinbase_pro_api_key").value
                coinbase_pro_secret_key = global_config_map.get(
                    "coinbase_pro_secret_key").value
                coinbase_pro_passphrase = global_config_map.get(
                    "coinbase_pro_passphrase").value

                market = CoinbaseProMarket(
                    coinbase_pro_api_key,
                    coinbase_pro_secret_key,
                    coinbase_pro_passphrase,
                    trading_pairs=trading_pairs,
                    trading_required=self._trading_required)
            elif market_name == "huobi":
                huobi_api_key = global_config_map.get("huobi_api_key").value
                huobi_secret_key = global_config_map.get(
                    "huobi_secret_key").value
                market = HuobiMarket(
                    huobi_api_key,
                    huobi_secret_key,
                    order_book_tracker_data_source_type=
                    OrderBookTrackerDataSourceType.EXCHANGE_API,
                    trading_pairs=trading_pairs,
                    trading_required=self._trading_required)
            elif market_name == "liquid":
                liquid_api_key = global_config_map.get("liquid_api_key").value
                liquid_secret_key = global_config_map.get(
                    "liquid_secret_key").value

                market = LiquidMarket(
                    liquid_api_key,
                    liquid_secret_key,
                    order_book_tracker_data_source_type=
                    OrderBookTrackerDataSourceType.EXCHANGE_API,
                    user_stream_tracker_data_source_type=
                    UserStreamTrackerDataSourceType.EXCHANGE_API,
                    trading_pairs=trading_pairs,
                    trading_required=self._trading_required)
            elif market_name == "dolomite":
                assert self.wallet is not None
                is_test_net: bool = global_config_map.get(
                    "ethereum_chain_name").value == "DOLOMITE_TEST"
                market = DolomiteMarket(
                    wallet=self.wallet,
                    ethereum_rpc_url=ethereum_rpc_url,
                    order_book_tracker_data_source_type=
                    OrderBookTrackerDataSourceType.EXCHANGE_API,
                    trading_pairs=trading_pairs,
                    isTestNet=is_test_net,
                    trading_required=self._trading_required,
                )
            elif market_name == "bittrex":
                bittrex_api_key = global_config_map.get(
                    "bittrex_api_key").value
                bittrex_secret_key = global_config_map.get(
                    "bittrex_secret_key").value
                market = BittrexMarket(
                    bittrex_api_key,
                    bittrex_secret_key,
                    order_book_tracker_data_source_type=
                    OrderBookTrackerDataSourceType.EXCHANGE_API,
                    trading_pairs=trading_pairs,
                    trading_required=self._trading_required)
            elif market_name == "kucoin":
                kucoin_api_key = global_config_map.get("kucoin_api_key").value
                kucoin_secret_key = global_config_map.get(
                    "kucoin_secret_key").value
                kucoin_passphrase = global_config_map.get(
                    "kucoin_passphrase").value
                market = KucoinMarket(
                    kucoin_api_key,
                    kucoin_passphrase,
                    kucoin_secret_key,
                    order_book_tracker_data_source_type=
                    OrderBookTrackerDataSourceType.EXCHANGE_API,
                    trading_pairs=trading_pairs,
                    trading_required=self._trading_required)
            elif market_name == "eterbase":
                eterbase_api_key = global_config_map.get(
                    "eterbase_api_key").value
                eterbase_secret_key = global_config_map.get(
                    "eterbase_secret_key").value
                eterbase_account = global_config_map.get(
                    "eterbase_account").value
                market = EterbaseMarket(
                    eterbase_api_key,
                    eterbase_secret_key,
                    trading_pairs=trading_pairs,
                    trading_required=self._trading_required,
                    eterbase_account=eterbase_account)
            elif market_name == "kraken":
                kraken_api_key = global_config_map.get("kraken_api_key").value
                kraken_secret_key = global_config_map.get(
                    "kraken_secret_key").value
                market = KrakenMarket(
                    kraken_api_key,
                    kraken_secret_key,
                    order_book_tracker_data_source_type=
                    OrderBookTrackerDataSourceType.EXCHANGE_API,
                    trading_pairs=trading_pairs,
                    trading_required=self._trading_required)
            else:
                raise ValueError(f"Market name {market_name} is invalid.")

            self.markets[market_name]: MarketBase = market

        self.markets_recorder = MarketsRecorder(
            self.trade_fill_db,
            list(self.markets.values()),
            self.strategy_file_name,
            self.strategy_name,
        )
        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,
                    ))
        for notifier in self.notifiers:
            notifier.start()
コード例 #10
0
class HummingbotCLITest(unittest.TestCase):
    command_name = "command_1"

    @classmethod
    def setUpClass(cls) -> None:
        super().setUpClass()
        cls.ev_loop = asyncio.get_event_loop()
        cls.ev_loop.run_until_complete(read_system_configs_from_yml())

    def setUp(self) -> None:
        super().setUp()

        tabs = {self.command_name: CommandTab(self.command_name, None, None, None, MagicMock())}
        self.mock_hb = MagicMock()
        self.app = HummingbotCLI(None, None, None, tabs)
        self.app.app = MagicMock()

    def test_handle_tab_command_on_close_argument(self):
        tab = self.app.command_tabs[self.command_name]
        tab.close_button = MagicMock()
        tab.button = MagicMock()
        tab.output_field = MagicMock()
        self.app.handle_tab_command(self.mock_hb, self.command_name, {"close": True})
        self.assertIsNone(tab.button)
        self.assertIsNone(tab.close_button)
        self.assertIsNone(tab.output_field)
        self.assertFalse(tab.is_selected)
        self.assertEqual(tab.tab_index, 0)

    def test_handle_tab_command_create_new_tab_and_display(self):
        tab = self.app.command_tabs[self.command_name]
        self.app.handle_tab_command(self.mock_hb, self.command_name, {"close": False})
        self.assertIsInstance(tab.button, Button)
        self.assertIsInstance(tab.close_button, Button)
        self.assertIsInstance(tab.output_field, CustomTextArea)
        self.assertEqual(tab.tab_index, 1)
        self.assertTrue(tab.is_selected)
        self.assertTrue(tab.tab_class.display.called)

    @patch("hummingbot.client.ui.layout.Layout")
    @patch("hummingbot.client.ui.layout.FloatContainer")
    @patch("hummingbot.client.ui.layout.ConditionalContainer")
    @patch("hummingbot.client.ui.layout.Box")
    @patch("hummingbot.client.ui.layout.HSplit")
    @patch("hummingbot.client.ui.layout.VSplit")
    def test_handle_tab_command_on_existing_tab(self, mock_vsplit, mock_hsplit, mock_box, moc_cc, moc_fc, mock_layout):
        tab = self.app.command_tabs[self.command_name]
        tab.button = MagicMock()
        tab.output_field = MagicMock()
        tab.close_button = MagicMock()
        tab.is_selected = False
        self.app.handle_tab_command(self.mock_hb, self.command_name, {"close": False})
        self.assertTrue(tab.is_selected)
        self.assertTrue(tab.tab_class.display.call_count == 1)

        # Test display not called if there is a running task
        tab.is_selected = False
        tab.task = MagicMock()
        tab.task.done.return_value = False
        self.app.handle_tab_command(self.mock_hb, self.command_name, {"close": False})
        self.assertTrue(tab.is_selected)
        self.assertTrue(tab.tab_class.display.call_count == 1)

    @patch("hummingbot.client.ui.layout.Layout")
    @patch("hummingbot.client.ui.layout.FloatContainer")
    @patch("hummingbot.client.ui.layout.ConditionalContainer")
    @patch("hummingbot.client.ui.layout.Box")
    @patch("hummingbot.client.ui.layout.HSplit")
    @patch("hummingbot.client.ui.layout.VSplit")
    def test_tab_navigation(self, mock_vsplit, mock_hsplit, mock_box, moc_cc, moc_fc, mock_layout):
        tab2 = CommandTab("command_2", None, None, None, MagicMock(), False)

        self.app.command_tabs["command_2"] = tab2
        tab1 = self.app.command_tabs[self.command_name]

        self.app.handle_tab_command(self.mock_hb, self.command_name, {"close": False})
        self.app.handle_tab_command(self.mock_hb, "command_2", {"close": False})
        self.assertTrue(tab2.is_selected)

        self.app.tab_navigate_left()
        self.assertTrue(tab1.is_selected)
        self.assertFalse(tab2.is_selected)
        self.app.tab_navigate_left()
        self.assertTrue(all(not t.is_selected for t in self.app.command_tabs.values()))
        self.app.tab_navigate_left()
        self.assertTrue(all(not t.is_selected for t in self.app.command_tabs.values()))

        self.app.tab_navigate_right()
        self.assertTrue(tab1.is_selected)

        self.app.tab_navigate_right()
        self.assertFalse(tab1.is_selected)
        self.assertTrue(tab2.is_selected)

        self.app.tab_navigate_right()
        self.assertFalse(tab1.is_selected)
        self.assertTrue(tab2.is_selected)

    @patch("hummingbot.client.ui.hummingbot_cli.global_config_map")
    @patch("hummingbot.client.ui.hummingbot_cli.init_logging")
    def test_did_start_ui(self, mock_init_logging: MagicMock, mock_config_map: MagicMock):
        class UIStartHandler(EventListener):
            def __init__(self):
                super().__init__()
                self.mock = MagicMock()

            def __call__(self, _):
                self.mock()

        handler: UIStartHandler = UIStartHandler()
        self.app.add_listener(HummingbotUIEvent.Start, handler)
        self.app.did_start_ui()

        mock_config_map.get.assert_called()
        mock_init_logging.assert_called()
        handler.mock.assert_called()
コード例 #11
0
class HummingbotApplication(*commands):
    KILL_TIMEOUT = 10.0
    APP_WARNING_EXPIRY_DURATION = 3600.0
    APP_WARNING_STATUS_LIMIT = 6

    _main_app: Optional["HummingbotApplication"] = None

    @classmethod
    def logger(cls) -> HummingbotLogger:
        global s_logger
        if s_logger is None:
            s_logger = logging.getLogger(__name__)
        return s_logger

    @classmethod
    def main_application(cls) -> "HummingbotApplication":
        if cls._main_app is None:
            cls._main_app = HummingbotApplication()
        return cls._main_app

    def __init__(self):
        # This is to start fetching trading pairs for auto-complete
        TradingPairFetcher.get_instance()
        self.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
        self.parser: ThrowingArgumentParser = load_parser(self)
        self.app = HummingbotCLI(
            input_handler=self._handle_command, bindings=load_key_bindings(self), completer=load_completer(self)
        )

        self.markets: Dict[str, ExchangeBase] = {}
        # strategy file name and name get assigned value after import or create command
        self._strategy_file_name: str = None
        self.strategy_name: str = None
        self.strategy_task: Optional[asyncio.Task] = None
        self.strategy: Optional[StrategyBase] = None
        self.market_pair: Optional[CrossExchangeMarketPair] = None
        self.market_trading_pair_tuples: List[MarketTradingPairTuple] = []
        self.clock: Optional[Clock] = None
        self.market_trading_pairs_map = {}
        self.token_list = {}

        self.init_time: float = time.time()
        self.start_time: Optional[int] = None
        self.placeholder_mode = False
        self.log_queue_listener: Optional[logging.handlers.QueueListener] = None
        self.data_feed: Optional[DataFeedBase] = None
        self.notifiers: List[NotifierBase] = []
        self.kill_switch: Optional[KillSwitch] = None
        self._app_warnings: Deque[ApplicationWarning] = deque()
        self._trading_required: bool = True
        self._last_started_strategy_file: Optional[str] = None

        self.trade_fill_db: Optional[SQLConnectionManager] = None
        self.markets_recorder: Optional[MarketsRecorder] = None
        self._script_iterator = None
        self._binance_connector = None

        # gateway variables
        self._shared_client = None

    @property
    def strategy_file_name(self) -> str:
        return self._strategy_file_name

    @strategy_file_name.setter
    def strategy_file_name(self, value: Optional[str]):
        self._strategy_file_name = value
        if value is not None:
            db_name = value.split(".")[0]
            self.trade_fill_db = SQLConnectionManager.get_trade_fills_instance(db_name)
        else:
            self.trade_fill_db = None

    @property
    def strategy_config_map(self):
        if self.strategy_name is not None:
            return get_strategy_config_map(self.strategy_name)
        return None

    def _notify(self, msg: str):
        self.app.log(msg)
        for notifier in self.notifiers:
            notifier.add_msg_to_queue(msg)

    def _handle_command(self, raw_command: str):
        # unset to_stop_config flag it triggered before loading any command
        if self.app.to_stop_config:
            self.app.to_stop_config = False

        raw_command = raw_command.lower().strip()
        command_split = raw_command.split()
        try:
            if self.placeholder_mode:
                pass
            else:
                shortcuts = global_config_map.get("command_shortcuts").value
                shortcut = None
                # see if we match against shortcut command
                if shortcuts is not None:
                    for s in shortcuts:
                        if command_split[0] == s['command']:
                            shortcut = s
                            break

                # perform shortcut expansion
                if shortcut is not None:
                    # check number of arguments
                    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
                        # 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:
                                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)
                    if not hasattr(args, "func"):
                        return
                    f = args.func
                    del kwargs["func"]
                    f(**kwargs)
        except ArgumentParserError as e:
            if not self.be_silly(raw_command):
                self._notify(str(e))
        except NotImplementedError:
            self._notify("Command not yet implemented. This feature is currently under development.")
        except Exception as e:
            self.logger().error(e, exc_info=True)

    async def _cancel_outstanding_orders(self) -> bool:
        success = True
        try:
            kill_timeout: float = self.KILL_TIMEOUT
            self._notify("Cancelling outstanding orders...")

            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:
                    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" % (
                        market_name,
                        '\n'.join(uncancelled_order_ids)
                    ))
        except Exception:
            self.logger().error("Error canceling outstanding orders.", exc_info=True)
            success = False

        if success:
            self._notify("All outstanding orders cancelled.")
        return success

    async def run(self):
        await self.app.run()

    def add_application_warning(self, app_warning: ApplicationWarning):
        self._expire_old_application_warnings()
        self._app_warnings.append(app_warning)

    def clear_application_warning(self):
        self._app_warnings.clear()

    @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

    def _initialize_markets(self, market_names: List[Tuple[str, List[str]]]):
        # aggregate trading_pairs if there are duplicate markets

        for market_name, trading_pairs in market_names:
            if market_name not in self.market_trading_pairs_map:
                self.market_trading_pairs_map[market_name] = []
            for hb_trading_pair in trading_pairs:
                self.market_trading_pairs_map[market_name].append(hb_trading_pair)

        for connector_name, trading_pairs in self.market_trading_pairs_map.items():
            conn_setting = CONNECTOR_SETTINGS[connector_name]
            if global_config_map.get("paper_trade_enabled").value and conn_setting.type == ConnectorType.Exchange:
                connector = create_paper_trade_market(connector_name, trading_pairs)
                paper_trade_account_balance = global_config_map.get("paper_trade_account_balance").value
                for asset, balance in paper_trade_account_balance.items():
                    connector.set_balance(asset, balance)
            else:
                Security.update_config_map(global_config_map)
                keys = {key: config.value for key, config in global_config_map.items()
                        if key in conn_setting.config_keys}
                init_params = conn_setting.conn_init_parameters(keys)
                init_params.update(trading_pairs=trading_pairs, trading_required=self._trading_required)
                if conn_setting.use_ethereum_wallet:
                    ethereum_rpc_url = global_config_map.get("ethereum_rpc_url").value
                    # Todo: Hard coded this execption for now until we figure out how to handle all ethereum connectors.
                    if connector_name in ["balancer", "uniswap", "uniswap_v3", "perpetual_finance"]:
                        private_key = get_eth_wallet_private_key()
                        init_params.update(wallet_private_key=private_key, ethereum_rpc_url=ethereum_rpc_url)
                connector_class = get_connector_class(connector_name)
                connector = connector_class(**init_params)
            self.markets[connector_name] = connector

        self.markets_recorder = MarketsRecorder(
            self.trade_fill_db,
            list(self.markets.values()),
            self.strategy_file_name,
            self.strategy_name,
        )
        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,
                    )
                )
        for notifier in self.notifiers:
            notifier.start()
コード例 #12
0
class HummingbotApplication(*commands):
    KILL_TIMEOUT = 10.0
    APP_WARNING_EXPIRY_DURATION = 3600.0
    APP_WARNING_STATUS_LIMIT = 6

    _main_app: Optional["HummingbotApplication"] = None

    @classmethod
    def logger(cls) -> HummingbotLogger:
        global s_logger
        if s_logger is None:
            s_logger = logging.getLogger(__name__)
        return s_logger

    @classmethod
    def main_application(cls) -> "HummingbotApplication":
        if cls._main_app is None:
            cls._main_app = HummingbotApplication()
        return cls._main_app

    def __init__(self):
        self.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
        self.parser: ThrowingArgumentParser = load_parser(self)
        self.app = HummingbotCLI(
            input_handler=self._handle_command, bindings=load_key_bindings(self), completer=load_completer(self)
        )

        self.markets: Dict[str, ExchangeBase] = {}
        self.wallet: Optional[Web3Wallet] = None
        # strategy file name and name get assigned value after import or create command
        self._strategy_file_name: str = None
        self.strategy_name: str = None
        self.strategy_task: Optional[asyncio.Task] = None
        self.strategy: Optional[StrategyBase] = None
        self.market_pair: Optional[CrossExchangeMarketPair] = None
        self.market_trading_pair_tuples: List[MarketTradingPairTuple] = []
        self.clock: Optional[Clock] = None
        self.market_trading_pairs_map = {}
        self.token_list = {}

        self.init_time: float = time.time()
        self.start_time: Optional[int] = None
        self.assets: Optional[Set[str]] = set()
        self.placeholder_mode = False
        self.log_queue_listener: Optional[logging.handlers.QueueListener] = None
        self.data_feed: Optional[DataFeedBase] = None
        self.notifiers: List[NotifierBase] = []
        self.kill_switch: Optional[KillSwitch] = None
        self._app_warnings: Deque[ApplicationWarning] = deque()
        self._trading_required: bool = True
        self._last_started_strategy_file: Optional[str] = None

        self.trade_fill_db: Optional[SQLConnectionManager] = None
        self.markets_recorder: Optional[MarketsRecorder] = None
        self._script_iterator = None
        # This is to start fetching trading pairs for auto-complete
        TradingPairFetcher.get_instance()

        self._binance_connector = None

    @property
    def strategy_file_name(self) -> str:
        return self._strategy_file_name

    @strategy_file_name.setter
    def strategy_file_name(self, value: str):
        self._strategy_file_name = value
        db_name = value.split(".")[0]
        self.trade_fill_db = SQLConnectionManager.get_trade_fills_instance(db_name=db_name)

    @property
    def strategy_config_map(self):
        if self.strategy_name is not None:
            return get_strategy_config_map(self.strategy_name)
        return None

    def _notify(self, msg: str):
        self.app.log(msg)
        for notifier in self.notifiers:
            notifier.add_msg_to_queue(msg)

    def _handle_command(self, raw_command: str):
        # unset to_stop_config flag it triggered before loading any command
        if self.app.to_stop_config:
            self.app.to_stop_config = False

        raw_command = raw_command.lower().strip()
        try:
            if self.placeholder_mode:
                pass
            else:
                args = self.parser.parse_args(args=raw_command.split())
                kwargs = vars(args)
                if not hasattr(args, "func"):
                    return
                f = args.func
                del kwargs["func"]
                f(**kwargs)
        except InvalidCommandError as e:
            self._notify("Invalid command: %s" % (str(e),))
        except ArgumentParserError as e:
            if not self.be_silly(raw_command):
                self._notify(str(e))
        except NotImplementedError:
            self._notify("Command not yet implemented. This feature is currently under development.")
        except Exception as e:
            self.logger().error(e, exc_info=True)

    async def _cancel_outstanding_orders(self) -> bool:
        success = True
        try:
            on_chain_cancel_on_exit = global_config_map.get("on_chain_cancel_on_exit").value
            bamboo_relay_use_coordinator = global_config_map.get("bamboo_relay_use_coordinator").value
            kill_timeout: float = self.KILL_TIMEOUT
            self._notify("Cancelling outstanding orders...")

            for market_name, market in self.markets.items():
                # By default, the bot does not cancel orders on exit on Radar Relay or Bamboo Relay,
                # since all open orders will expire in a short window
                if not on_chain_cancel_on_exit and (market_name == "radar_relay" or (market_name == "bamboo_relay" and not bamboo_relay_use_coordinator)):
                    continue
                cancellation_results = await market.cancel_all(kill_timeout)
                uncancelled = list(filter(lambda cr: cr.success is False, cancellation_results))
                if len(uncancelled) > 0:
                    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" % (
                        market_name,
                        '\n'.join(uncancelled_order_ids)
                    ))
        except Exception:
            self.logger().error("Error canceling outstanding orders.", exc_info=True)
            success = False

        if success:
            self._notify("All outstanding orders cancelled.")
        return success

    async def run(self):
        await self.app.run()

    def add_application_warning(self, app_warning: ApplicationWarning):
        self._expire_old_application_warnings()
        self._app_warnings.append(app_warning)

    def clear_application_warning(self):
        self._app_warnings.clear()

    @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

    def _initialize_wallet(self, token_trading_pairs: List[str]):
        if not using_wallet():
            return
        if not self.token_list:
            self.token_list = get_erc20_token_addresses()

        ethereum_wallet = global_config_map.get("ethereum_wallet").value
        private_key = Security._private_keys[ethereum_wallet]
        ethereum_rpc_url = global_config_map.get("ethereum_rpc_url").value
        erc20_token_addresses = {t: l[0] for t, l in self.token_list.items() if t in token_trading_pairs}

        chain_name: str = global_config_map.get("ethereum_chain_name").value
        self.wallet: Web3Wallet = Web3Wallet(
            private_key=private_key,
            backend_urls=[ethereum_rpc_url],
            erc20_token_addresses=erc20_token_addresses,
            chain=getattr(EthereumChain, chain_name),
        )

    def _initialize_markets(self, market_names: List[Tuple[str, List[str]]]):
        # aggregate trading_pairs if there are duplicate markets

        for market_name, trading_pairs in market_names:
            if market_name not in self.market_trading_pairs_map:
                self.market_trading_pairs_map[market_name] = []
            for hb_trading_pair in trading_pairs:
                self.market_trading_pairs_map[market_name].append(hb_trading_pair)

        for connector_name, trading_pairs in self.market_trading_pairs_map.items():
            conn_setting = CONNECTOR_SETTINGS[connector_name]
            if global_config_map.get("paper_trade_enabled").value and conn_setting.type == ConnectorType.Exchange:
                try:
                    connector = create_paper_trade_market(connector_name, trading_pairs)
                except Exception:
                    raise
                paper_trade_account_balance = global_config_map.get("paper_trade_account_balance").value
                for asset, balance in paper_trade_account_balance.items():
                    connector.set_balance(asset, balance)
            else:
                Security.update_config_map(global_config_map)
                keys = {key: config.value for key, config in global_config_map.items()
                        if key in conn_setting.config_keys}
                init_params = conn_setting.conn_init_parameters(keys)
                init_params.update(trading_pairs=trading_pairs, trading_required=self._trading_required)
                if conn_setting.use_ethereum_wallet:
                    ethereum_rpc_url = global_config_map.get("ethereum_rpc_url").value
                    # Todo: Hard coded this execption for now until we figure out how to handle all ethereum connectors.
                    if connector_name in ["balancer", "uniswap", "perpetual_finance"]:
                        private_key = get_eth_wallet_private_key()
                        init_params.update(wallet_private_key=private_key, ethereum_rpc_url=ethereum_rpc_url)
                    else:
                        assert self.wallet is not None
                        init_params.update(wallet=self.wallet, ethereum_rpc_url=ethereum_rpc_url)
                connector_class = get_connector_class(connector_name)
                connector = connector_class(**init_params)
            self.markets[connector_name] = connector

        self.markets_recorder = MarketsRecorder(
            self.trade_fill_db,
            list(self.markets.values()),
            self.strategy_file_name,
            self.strategy_name,
        )
        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,
                    )
                )
        for notifier in self.notifiers:
            notifier.start()
コード例 #13
0
class HummingbotApplication:
    KILL_TIMEOUT = 5.0
    APP_WARNING_EXPIRY_DURATION = 3600.0
    APP_WARNING_STATUS_LIMIT = 6

    _main_app: Optional["HummingbotApplication"] = None

    @classmethod
    def logger(cls) -> HummingbotLogger:
        global s_logger
        if s_logger is None:
            s_logger = logging.getLogger(__name__)
        return s_logger

    @classmethod
    def main_application(cls) -> "HummingbotApplication":
        if cls._main_app is None:
            cls._main_app = HummingbotApplication()
        return cls._main_app

    def __init__(self):
        self.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
        self.parser: ThrowingArgumentParser = load_parser(self)
        self.app = HummingbotCLI(input_handler=self._handle_command,
                                 bindings=load_key_bindings(self),
                                 completer=load_completer(self))

        self.acct: Optional[LocalAccount] = None
        self.markets: Dict[str, MarketBase] = {}
        self.wallet: Optional[Web3Wallet] = None
        self.strategy_task: Optional[asyncio.Task] = None
        self.strategy: Optional[CrossExchangeMarketMakingStrategy] = None
        self.market_pair: Optional[CrossExchangeMarketPair] = None
        self.clock: Optional[Clock] = None

        self.assets: Optional[Set[str]] = set()
        self.starting_balances = {}
        self.placeholder_mode = False
        self.log_queue_listener: Optional[
            logging.handlers.QueueListener] = None
        self.reporting_module: Optional[ReportAggregator] = None
        self.data_feed: Optional[DataFeedBase] = None
        self.stop_loss_tracker: Optional[StopLossTracker] = None
        self._app_warnings: Deque[ApplicationWarning] = deque()
        self._trading_required: bool = True

    def init_reporting_module(self):
        if not self.reporting_module:
            self.reporting_module = ReportAggregator(
                self,
                report_aggregation_interval=global_config_map[
                    "reporting_aggregation_interval"].value,
                log_report_interval=global_config_map["reporting_log_interval"]
                .value)
        self.reporting_module.start()

    def _handle_command(self, raw_command: str):
        raw_command = raw_command.lower().strip()
        try:
            if self.placeholder_mode:
                pass
            else:
                logging.getLogger("hummingbot.command_history").info(
                    raw_command)
                args = self.parser.parse_args(args=raw_command.split())
                kwargs = vars(args)
                if not hasattr(args, "func"):
                    return
                f = args.func
                del kwargs['func']
                f(**kwargs)
        except InvalidCommandError as e:
            self.app.log("Invalid command: %s" % (str(e), ))
        except ArgumentParserError as e:
            self.app.log(str(e))
        except NotImplementedError:
            self.app.log(
                "Command not yet implemented. This feature is currently under development."
            )
        except Exception as e:
            self.logger().error(e, exc_info=True)

    async def _cancel_outstanding_orders(self) -> bool:
        on_chain_cancel_on_exit = global_config_map.get(
            "on_chain_cancel_on_exit").value
        success = True
        self.app.log("Cancelling outstanding orders...")
        for market_name, market in self.markets.items():
            # By default, the bot does not cancel orders on exit on Radar Relay or Bamboo Relay, since all open orders will
            # expire in a short window
            if not on_chain_cancel_on_exit and (market_name == "radar_relay" or
                                                market_name == "bamboo_relay"):
                continue
            cancellation_results = await market.cancel_all(self.KILL_TIMEOUT)
            uncancelled = list(
                filter(lambda cr: cr.success is False, cancellation_results))
            if len(uncancelled) > 0:
                success = False
                uncancelled_order_ids = list(
                    map(lambda cr: cr.order_id, uncancelled))
                self.app.log(
                    "\nFailed to cancel the following orders on %s:\n%s" %
                    (market_name, '\n'.join(uncancelled_order_ids)))
        if success:
            self.app.log("All outstanding orders cancelled.")
        return success

    async def run(self):
        await self.app.run()

    @property
    def config_complete(self):
        config_map = load_required_configs()
        for key in self._get_empty_configs():
            cvar = config_map.get(key)
            if cvar.value is None and cvar.required:
                return False
        return True

    @staticmethod
    def _get_empty_configs() -> List[str]:
        config_map = load_required_configs()
        return [
            key for key, config in config_map.items() if config.value is None
        ]

    def get_wallet_balance(self) -> pd.DataFrame:
        return pd.DataFrame(data=list(self.wallet.get_all_balances().items()),
                            columns=["currency",
                                     "balance"]).set_index("currency")

    def get_exchange_balance(self, exchange_name: str) -> pd.DataFrame:
        market: MarketBase = self.markets[exchange_name]
        raw_balance: pd.DataFrame = pd.DataFrame(
            data=list(market.get_all_balances().items()),
            columns=["currency", "balance"]).set_index("currency")
        return raw_balance[raw_balance.balance > 0]

    def config(self, key: str = None):
        self.app.clear_input()
        if key is not None and key not in load_required_configs().keys():
            self.app.log("Invalid config variable %s" % (key, ))
            return
        if key is not None:
            keys = [key]
        else:
            keys = self._get_empty_configs()
        asyncio.ensure_future(self._config_loop(keys))

    def _expire_old_application_warnings(self):
        now: float = time.time()
        expiry_threshold: float = now - self.APP_WARNING_EXPIRY_DURATION
        while len(self._app_warnings
                  ) > 0 and self._app_warnings[0].timestamp < expiry_threshold:
            self._app_warnings.popleft()

    def add_application_warning(self, app_warning: ApplicationWarning):
        self._expire_old_application_warnings()
        self._app_warnings.append(app_warning)

    async def _create_or_import_wallet(self):
        choice = await self.app.prompt(
            prompt=global_config_map.get("wallet").prompt)
        if choice == "import":
            private_key = await self.app.prompt(
                prompt="Your wallet private key >>> ", is_password=True)
            password = await self.app.prompt(
                prompt="A password to protect your wallet key >>> ",
                is_password=True)

            try:
                self.acct = import_and_save_wallet(password, private_key)
                self.app.log("Wallet %s imported into hummingbot" %
                             (self.acct.address, ))
            except Exception as e:
                self.app.log(f"Failed to import wallet key: {e}")
                result = await self._create_or_import_wallet()
                return result
        elif choice == "create":
            password = await self.app.prompt(
                prompt="A password to protect your wallet key >>> ",
                is_password=True)
            self.acct = create_and_save_wallet(password)
            self.app.log("New wallet %s created" % (self.acct.address, ))
        else:
            self.app.log('Invalid choice. Please enter "create" or "import".')
            result = await self._create_or_import_wallet()
            return result
        return self.acct.address

    async def _unlock_wallet(self):
        choice = await self.app.prompt(
            prompt=
            "Would you like to unlock your previously saved wallet? (y/n) >>> "
        )
        if choice.lower() in {"y", "yes"}:
            wallets = list_wallets()
            self.app.log("Existing wallets:")
            self.list(obj="wallets")
            if len(wallets) == 1:
                public_key = wallets[0]
            else:
                public_key = await self.app.prompt(
                    prompt="Which wallet would you like to import ? >>> ")
            password = await self.app.prompt(prompt="Enter your password >>> ",
                                             is_password=True)
            try:
                acct = unlock_wallet(public_key=public_key, password=password)
                self.app.log("Wallet %s unlocked" % (acct.address, ))
                self.acct = acct
                return self.acct.address
            except Exception as e:
                self.app.log("Cannot unlock wallet. Please try again.")
                result = await self._unlock_wallet()
                return result
        else:
            value = await self._create_or_import_wallet()
            return value

    async def _import_or_create_strategy_config(self):
        current_strategy: str = in_memory_config_map.get("strategy").value
        strategy_file_path_cv: ConfigVar = in_memory_config_map.get(
            "strategy_file_path")
        choice = await self.app.prompt(
            prompt="Import previous configs or create a new config file? "
            "(import/create) >>> ")
        if choice == "import":
            strategy_path = await self.app.prompt(strategy_file_path_cv.prompt)
            strategy_path = strategy_path
            self.app.log(
                f"Loading previously saved config file from {strategy_path}..."
            )
        elif choice == "create":
            strategy_path = await copy_strategy_template(current_strategy)
            self.app.log(f"new config file at {strategy_path} created.")
        else:
            self.app.log('Invalid choice. Please enter "create" or "import".')
            strategy_path = await self._import_or_create_strategy_config()

        # Validate response
        if not strategy_file_path_cv.validate(strategy_path):
            self.app.log(
                f"Invalid path {strategy_path}. Please enter \"create\" or \"import\"."
            )
            strategy_path = await self._import_or_create_strategy_config()
        return strategy_path

    async def _config_loop(self, keys: List[str] = []):
        self.app.log("Please follow the prompt to complete configurations: ")
        self.placeholder_mode = True
        self.app.toggle_hide_input()

        single_key = len(keys) == 1

        async def single_prompt(cvar: ConfigVar):
            if cvar.required or single_key:
                if cvar.key == "strategy_file_path":
                    val = await self._import_or_create_strategy_config()
                elif cvar.key == "wallet":
                    wallets = list_wallets()
                    if len(wallets) > 0:
                        val = await self._unlock_wallet()
                    else:
                        val = await self._create_or_import_wallet()
                    logging.getLogger("hummingbot.public_eth_address").info(
                        val)
                else:
                    val = await self.app.prompt(prompt=cvar.prompt,
                                                is_password=cvar.is_secure)
                if not cvar.validate(val):
                    self.app.log("%s is not a valid %s value" %
                                 (val, cvar.key))
                    val = await single_prompt(cvar)
            else:
                val = cvar.value
            if val is None or (isinstance(val, string_types)
                               and len(val) == 0):
                val = cvar.default
            return val

        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())

        try:
            await inner_loop(keys)
            await write_config_to_yml()
            if not single_key:
                self.app.log(
                    "\nConfig process complete. Enter \"start\" to start market making."
                )
                self.app.set_text("start")
        except asyncio.TimeoutError:
            self.logger().error("Prompt timeout")
        except Exception as err:
            self.logger().error("Unknown error while writing config. %s" %
                                (err, ),
                                exc_info=True)
        finally:
            self.app.toggle_hide_input()
            self.placeholder_mode = False
            self.app.change_prompt(prompt=">>> ")

    def _initialize_wallet(self, token_symbols: List[str]):
        ethereum_rpc_url = global_config_map.get("ethereum_rpc_url").value
        erc20_token_addresses = get_erc20_token_addresses(token_symbols)

        if self.acct is not None:
            self.wallet: Web3Wallet = Web3Wallet(
                private_key=self.acct.privateKey,
                backend_urls=[ethereum_rpc_url],
                erc20_token_addresses=erc20_token_addresses,
                chain=EthereumChain.MAIN_NET)

    def _initialize_markets(self, market_names: List[Tuple[str, List[str]]]):
        ethereum_rpc_url = global_config_map.get("ethereum_rpc_url").value
        for market_name, symbols in market_names:
            if market_name == "ddex" and self.wallet:
                market = DDEXMarket(
                    wallet=self.wallet,
                    ethereum_rpc_url=ethereum_rpc_url,
                    order_book_tracker_data_source_type=
                    OrderBookTrackerDataSourceType.EXCHANGE_API,
                    symbols=symbols,
                    trading_required=self._trading_required)

            elif market_name == "binance":
                binance_api_key = global_config_map.get(
                    "binance_api_key").value
                binance_api_secret = global_config_map.get(
                    "binance_api_secret").value
                market = BinanceMarket(
                    ethereum_rpc_url=ethereum_rpc_url,
                    binance_api_key=binance_api_key,
                    binance_api_secret=binance_api_secret,
                    order_book_tracker_data_source_type=
                    OrderBookTrackerDataSourceType.EXCHANGE_API,
                    symbols=symbols,
                    trading_required=self._trading_required)

            elif market_name == "radar_relay" and self.wallet:
                market = RadarRelayMarket(
                    wallet=self.wallet,
                    ethereum_rpc_url=ethereum_rpc_url,
                    symbols=symbols,
                    trading_required=self._trading_required)

            elif market_name == "bamboo_relay" and self.wallet:
                market = BambooRelayMarket(wallet=self.wallet,
                                           web3_url=ethereum_rpc_url,
                                           symbols=symbols)

            elif market_name == "coinbase_pro":
                coinbase_pro_api_key = global_config_map.get(
                    "coinbase_pro_api_key").value
                coinbase_pro_secret_key = global_config_map.get(
                    "coinbase_pro_secret_key").value
                coinbase_pro_passphrase = global_config_map.get(
                    "coinbase_pro_passphrase").value

                market = CoinbaseProMarket(
                    ethereum_rpc_url=ethereum_rpc_url,
                    coinbase_pro_api_key=coinbase_pro_api_key,
                    coinbase_pro_secret_key=coinbase_pro_secret_key,
                    coinbase_pro_passphrase=coinbase_pro_passphrase,
                    symbols=symbols,
                    trading_required=self._trading_required)

            else:
                raise ValueError(f"Market name {market_name} is invalid.")

            self.markets[market_name]: MarketBase = market

    def _format_application_warnings(self) -> str:
        lines: List[str] = []
        if len(self._app_warnings) < 1:
            return ""

        lines.append("\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}")
        else:
            module_based_warnings: OrderedDict = OrderedDict()
            for app_warning in reversed(self._app_warnings):
                logger_name: str = app_warning.logger_name
                if logger_name not in module_based_warnings:
                    module_based_warnings[logger_name] = deque([app_warning])
                else:
                    module_based_warnings[logger_name].append(app_warning)

            warning_lines: List[str] = []
            while len(warning_lines) < self.APP_WARNING_STATUS_LIMIT:
                logger_keys: List[str] = list(module_based_warnings.keys())
                for key in logger_keys:
                    warning_item: ApplicationWarning = module_based_warnings[
                        key].popleft()
                    if len(module_based_warnings[key]) < 1:
                        del module_based_warnings[key]
                    warning_lines.append(
                        f"    * {pd.Timestamp(warning_item.timestamp, unit='s')} - "
                        f"({key}) - {warning_item.warning_msg}")
            lines.extend(warning_lines[:self.APP_WARNING_STATUS_LIMIT])

        return "\n".join(lines)

    def status(self) -> bool:
        # Preliminary checks.
        self.app.log("\n  Preliminary checks:")
        if self.config_complete:
            self.app.log("   - Config check: Config complete")
        else:
            self.app.log(
                '   x Config check: Pending config. Please enter "config" before starting the bot.'
            )
            return False

        eth_node_valid = check_web3(
            global_config_map.get("ethereum_rpc_url").value)
        if eth_node_valid:
            self.app.log("   - Node check: Ethereum node running and current")
        else:
            self.app.log(
                '   x Node check: Bad ethereum rpc url. Your node may be syncing. '
                'Please re-configure by entering "config ethereum_rpc_url"')
            return False

        if self.wallet is not None:
            if self.wallet.network_status is NetworkStatus.CONNECTED:
                if self._trading_required:
                    has_minimum_eth = self.wallet.get_balance("ETH") > 0.01
                    if has_minimum_eth:
                        self.app.log(
                            "   - ETH wallet check: Minimum ETH requirement satisfied"
                        )
                    else:
                        self.app.log(
                            "   x ETH wallet check: Not enough ETH in wallet. "
                            "A small amount of Ether is required for sending transactions on "
                            "Decentralized Exchanges")
            else:
                self.app.log(
                    "   x ETH wallet check: ETH wallet is not connected.")

        loading_markets: List[MarketBase] = []
        for market in self.markets.values():
            if not market.ready:
                loading_markets.append(market)

        if len(loading_markets) > 0:
            self.app.log(
                f"   x Market check:  Waiting for markets " +
                ",".join([m.name.capitalize() for m in loading_markets]) +
                f" to get ready for trading. \n"
                f"                    Please keep the bot running and try to start again in a few minutes. \n"
            )

            for market in loading_markets:
                market_status_df = pd.DataFrame(
                    data=market.status_dict.items(),
                    columns=["description", "status"])
                self.app.log(
                    f"   x {market.name.capitalize()} market status:\n" +
                    "\n".join([
                        "     " + 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()
        ]):
            offline_markets: List[str] = [
                market_name for market_name, market in self.markets.items()
                if market.network_status is not NetworkStatus.CONNECTED
            ]
            for offline_market in offline_markets:
                self.app.log(
                    f"   x Market check:  {offline_market} is currently offline."
                )

        # See if we can print out the strategy status.
        self.app.log("   - Market check: All markets ready")
        if self.strategy is None:
            self.app.log("   x initializing strategy.")
        else:
            self.app.log(self.strategy.format_status() + "\n")

        # Application warnings.
        self._expire_old_application_warnings()
        if len(self._app_warnings) > 0:
            self.app.log(self._format_application_warnings())

        return True

    def help(self, command):
        if command == 'all':
            self.app.log(self.parser.format_help())
        else:
            subparsers_actions = [
                action for action in self.parser._actions
                if isinstance(action, argparse._SubParsersAction)
            ]

            for subparsers_action in subparsers_actions:
                subparser = subparsers_action.choices.get(command)
                self.app.log(subparser.format_help())

    def get_balance(self,
                    currency: str = "WETH",
                    wallet: bool = False,
                    exchange: str = None):
        if wallet:
            if self.wallet is None:
                self.app.log(
                    'Wallet not available. Please configure your wallet (Enter "config wallet")'
                )
            elif currency is None:
                self.app.log(f"{self.get_wallet_balance()}")
            else:
                self.app.log(self.wallet.get_balance(currency.upper()))
        elif exchange:
            if exchange in self.markets:
                if currency is None:
                    self.app.log(f"{self.get_exchange_balance(exchange)}")
                else:
                    self.app.log(self.markets[exchange].get_balance(
                        currency.upper()))
            else:
                self.app.log(
                    'The exchange you entered has not been initialized. '
                    'You may check your exchange balance after entering the "start" command.'
                )
        else:
            self.help("get_balance")

    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")

    def describe(self, wallet: bool = False, exchange: str = None):
        if wallet:
            if self.wallet is None:
                self.app.log(
                    'None available. Your wallet may not have been initialized. Enter "start" to initialize '
                    'your wallet.')
            else:
                self.app.log(self.wallet.address)
                self.app.log(f"{self.get_wallet_balance()}")
        elif exchange is not None:
            if exchange in self.markets:
                self.app.log(f"{self.get_exchange_balance(exchange)}")
            else:
                raise InvalidCommandError(
                    "The exchange you specified has not been initialized")
        else:
            self.help("describe")

    def start(self, log_level: Optional[str] = None):
        is_valid = self.status()
        if not is_valid:
            return

        if log_level is not None:
            init_logging("hummingbot_logs.yml",
                         override_log_level=log_level.upper())

        # If macOS, disable App Nap.
        if platform.system() == "Darwin":
            import appnope
            appnope.nope()

        # TODO add option to select data feed
        self.data_feed: DataFeedBase = CoinCapDataFeed.get_instance()

        ExchangeRateConversion.get_instance().start()
        strategy_name = in_memory_config_map.get("strategy").value
        self.init_reporting_module()
        self.app.log(
            f"\n  Status check complete. Starting '{strategy_name}' strategy..."
        )
        asyncio.ensure_future(self.start_market_making(strategy_name))

    async def _run_clock(self):
        with self.clock as clock:
            await clock.run()

    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)

    async def stop(self, skip_order_cancellation: bool = False):
        self.app.log("\nWinding down...")

        # Restore App Nap on macOS.
        if platform.system() == "Darwin":
            import appnope
            appnope.nap()

        if self._trading_required and not skip_order_cancellation:
            # Remove the strategy from clock before cancelling orders, to
            # prevent race condition where the strategy tries to create more
            # orders during cancellation.
            self.clock.remove_iterator(self.strategy)
            success = await self._cancel_outstanding_orders()
            if success:
                # Only erase markets when cancellation has been successful
                self.markets = {}
        if self.reporting_module:
            self.reporting_module.stop()
        if self.strategy_task is not None and not self.strategy_task.cancelled(
        ):
            self.strategy_task.cancel()
        if self.strategy:
            self.strategy.stop()
        ExchangeRateConversion.get_instance().stop()
        self.stop_loss_tracker.stop()
        self.wallet = None
        self.strategy_task = None
        self.strategy = None
        self.market_pair = None
        self.clock = None

    async def exit(self, force: bool = False):
        if self.strategy_task is not None and not self.strategy_task.cancelled(
        ):
            self.strategy_task.cancel()
        if self.strategy:
            self.strategy.stop()
        if force is False and self._trading_required:
            success = await self._cancel_outstanding_orders()
            if not success:
                self.app.log(
                    'Wind down process terminated: Failed to cancel all outstanding orders. '
                    '\nYou may need to manually cancel remaining orders by logging into your chosen exchanges'
                    '\n\nTo force exit the app, enter "exit -f"')
                return
            # Freeze screen 1 second for better UI
            await asyncio.sleep(1)
        ExchangeRateConversion.get_instance().stop()
        self.app.exit()

    async def export_private_key(self):
        if self.acct is None:
            self.app.log(
                "Your wallet is currently locked. Please enter \"config\""
                " to unlock your wallet first")
        else:
            self.placeholder_mode = True
            self.app.toggle_hide_input()

            ans = await self.app.prompt(
                "Are you sure you want to print your private key in plain text? (y/n) >>> "
            )

            if ans.lower() in {"y", "yes"}:
                self.app.log(
                    "\nWarning: Never disclose this key. Anyone with your private keys can steal any assets "
                    "held in your account.\n")
                self.app.log("Your private key:")
                self.app.log(self.acct.privateKey.hex())

            self.app.change_prompt(prompt=">>> ")
            self.app.toggle_hide_input()
            self.placeholder_mode = False

    def export_trades(self, path: str = ""):
        if not path:
            fname = f"trades_{pd.Timestamp.now().strftime('%Y-%m-%d-%H-%M-%S')}.csv"
            path = join(dirname(__file__), f"../../logs/{fname}")

        if self.strategy is None:
            self.app.log("No strategy available, cannot export past trades.")

        else:
            if len(self.strategy.trades) > 0:
                try:
                    df: pd.DataFrame = Trade.to_pandas(self.strategy.trades)
                    df.to_csv(path, header=True)
                    self.app.log(f"Successfully saved trades to {path}")
                except Exception as e:
                    self.app.log(f"Error saving trades to {path}: {e}")

    def history(self):
        self.list("trades")
        self.compare_balance_snapshots()

    async def wait_till_ready(self, func: Callable, *args, **kwargs):
        while True:
            all_ready = all([market.ready for market in self.markets.values()])
            if not all_ready:
                await asyncio.sleep(0.5)
            else:
                return func(*args, **kwargs)

    def balance_snapshot(self) -> Dict[str, Dict[str, float]]:
        snapshot: Dict[str, Any] = {}
        for market_name in self.markets:
            balance_dict = self.markets[market_name].get_all_balances()
            for c in self.assets:
                if c not in snapshot:
                    snapshot[c] = {}
                if c in balance_dict:
                    snapshot[c][market_name] = balance_dict[c]
                else:
                    snapshot[c][market_name] = 0.0
        return snapshot

    def compare_balance_snapshots(self):
        if len(self.starting_balances) == 0:
            self.app.log(
                "  Balance snapshots are not available before bot starts")
            return

        rows = []
        for market_name in self.markets:
            for asset in self.assets:
                starting_balance = self.starting_balances.get(asset).get(
                    market_name)
                current_balance = self.balance_snapshot().get(asset).get(
                    market_name)
                rows.append([
                    market_name, asset, starting_balance, current_balance,
                    current_balance - starting_balance
                ])

        df = pd.DataFrame(
            rows,
            index=None,
            columns=["Market", "Asset", "Starting", "Current", "Delta"])
        lines = ["", "  Performance:"
                 ] + ["    " + line for line in str(df).split("\n")]
        self.app.log("\n".join(lines))
コード例 #14
0
class HummingbotApplication(*commands):
    KILL_TIMEOUT = 10.0
    APP_WARNING_EXPIRY_DURATION = 3600.0
    APP_WARNING_STATUS_LIMIT = 6

    _main_app: Optional["HummingbotApplication"] = None

    @classmethod
    def logger(cls) -> HummingbotLogger:
        global s_logger
        if s_logger is None:
            s_logger = logging.getLogger(__name__)
        return s_logger

    @classmethod
    def main_application(cls) -> "HummingbotApplication":
        if cls._main_app is None:
            cls._main_app = HummingbotApplication()
        return cls._main_app

    def __init__(self):
        self.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
        shortcuts = global_config_map.get("command_shortcuts").value
        self.parser: ThrowingArgumentParser = load_parser(self, shortcuts)
        self.app = HummingbotCLI(
            input_handler=self._handle_command, bindings=load_key_bindings(self), completer=load_completer(self)
        )

        self.markets: Dict[str, ExchangeBase] = {}
        self.wallet: Optional[Web3Wallet] = None
        # strategy file name and name get assigned value after import or create command
        self.strategy_file_name: str = None
        self.strategy_name: str = None
        self.strategy_task: Optional[asyncio.Task] = None
        self.strategy: Optional[StrategyBase] = None
        self.market_pair: Optional[CrossExchangeMarketPair] = None
        self.market_trading_pair_tuples: List[MarketTradingPairTuple] = []
        self.clock: Optional[Clock] = None

        self.init_time: int = int(time.time() * 1e3)
        self.start_time: Optional[int] = None
        self.assets: Optional[Set[str]] = set()
        self.starting_balances = {}
        self.placeholder_mode = False
        self.log_queue_listener: Optional[logging.handlers.QueueListener] = None
        self.data_feed: Optional[DataFeedBase] = None
        self.notifiers: List[NotifierBase] = []
        self.kill_switch: Optional[KillSwitch] = None
        self._app_warnings: Deque[ApplicationWarning] = deque()
        self._trading_required: bool = True

        self.trade_fill_db: SQLConnectionManager = SQLConnectionManager.get_trade_fills_instance()
        self.markets_recorder: Optional[MarketsRecorder] = None
        self._script_iterator = None
        # This is to start fetching trading pairs for auto-complete
        TradingPairFetcher.get_instance()

    @property
    def strategy_config_map(self):
        if self.strategy_name is not None:
            return get_strategy_config_map(self.strategy_name)
        return None

    def _notify(self, msg: str):
        self.app.log(msg)
        for notifier in self.notifiers:
            notifier.add_msg_to_queue(msg)

    def _send_image(self, image: str):
        try:
            f = open(image, 'rb')
        except:
            self._notify(f'Could not open {image}')
            return

        for notifier in self.notifiers:
            notifier.add_file_to_queue(f)

    def _handle_command(self, raw_command: str):
        # unset to_stop_config flag it triggered before loading any command
        if self.app.to_stop_config:
            self.app.to_stop_config = False

        raw_command = raw_command.lower().strip()
        command_split = raw_command.split()
        try:
            if self.placeholder_mode:
                pass
            else:
                shortcuts = global_config_map.get("command_shortcuts").value
                shortcut = None
                # see if we match against shortcut command
                for s in shortcuts:
                    if command_split[0] == s['command']:
                        shortcut = s
                        break

                # perform shortcut expansion
                if shortcut is not None:
                    # check number of arguments
                    num_shortcut_args = shortcut['arguments'][0]
                    if len(command_split) == num_shortcut_args + 1:
                        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])
                            self._notify(f'  >>> {final_cmd}')
                            self._handle_command(final_cmd)
                    else:
                        self._notify(f'Invalid number of arguments for shortcut')
                # regular command
                else:
                    args = self.parser.parse_args(args=command_split)
                    kwargs = vars(args)
                    if not hasattr(args, "func"):
                        return
                    f = args.func
                    del kwargs["func"]
                    f(**kwargs)
        except InvalidCommandError as e:
            self._notify("Invalid command: %s" % (str(e),))
        except ArgumentParserError as e:
            if not self.be_silly(raw_command):
                self._notify(str(e))
        except NotImplementedError:
            self._notify("Command not yet implemented. This feature is currently under development.")
        except Exception as e:
            self.logger().error(e, exc_info=True)

    async def _cancel_outstanding_orders(self) -> bool:
        success = True
        try:
            on_chain_cancel_on_exit = global_config_map.get("on_chain_cancel_on_exit").value
            bamboo_relay_use_coordinator = global_config_map.get("bamboo_relay_use_coordinator").value
            kill_timeout: float = self.KILL_TIMEOUT
            self._notify("Cancelling outstanding orders...")

            for market_name, market in self.markets.items():
                # By default, the bot does not cancel orders on exit on Radar Relay or Bamboo Relay,
                # since all open orders will expire in a short window
                if not on_chain_cancel_on_exit and (market_name == "radar_relay" or (market_name == "bamboo_relay" and not bamboo_relay_use_coordinator)):
                    continue
                cancellation_results = await market.cancel_all(kill_timeout)
                uncancelled = list(filter(lambda cr: cr.success is False, cancellation_results))
                if len(uncancelled) > 0:
                    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" % (
                        market_name,
                        '\n'.join(uncancelled_order_ids)
                    ))
        except Exception:
            self.logger().error("Error canceling outstanding orders.", exc_info=True)
            success = False

        if success:
            self._notify("All outstanding orders cancelled.")
        return success

    async def run(self):
        await self.app.run()

    def add_application_warning(self, app_warning: ApplicationWarning):
        self._expire_old_application_warnings()
        self._app_warnings.append(app_warning)

    def clear_application_warning(self):
        self._app_warnings.clear()

    @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

    def _initialize_wallet(self, token_trading_pairs: List[str]):
        if not using_wallet():
            return

        ethereum_wallet = global_config_map.get("ethereum_wallet").value
        private_key = Security._private_keys[ethereum_wallet]
        ethereum_rpc_url = global_config_map.get("ethereum_rpc_url").value
        erc20_token_addresses = get_erc20_token_addresses(token_trading_pairs)

        chain_name: str = global_config_map.get("ethereum_chain_name").value
        self.wallet: Web3Wallet = Web3Wallet(
            private_key=private_key,
            backend_urls=[ethereum_rpc_url],
            erc20_token_addresses=erc20_token_addresses,
            chain=getattr(EthereumChain, chain_name),
        )

    def _initialize_markets(self, market_names: List[Tuple[str, List[str]]]):
        ethereum_rpc_url = global_config_map.get("ethereum_rpc_url").value

        # aggregate trading_pairs if there are duplicate markets
        market_trading_pairs_map = {}
        for market_name, trading_pairs in market_names:
            if market_name not in market_trading_pairs_map:
                market_trading_pairs_map[market_name] = []
            for hb_trading_pair in trading_pairs:
                market_trading_pairs_map[market_name].append(hb_trading_pair)

        for connector_name, trading_pairs in market_trading_pairs_map.items():
            if global_config_map.get("paper_trade_enabled").value:
                try:
                    connector = create_paper_trade_market(market_name, trading_pairs)
                except Exception:
                    raise
                paper_trade_account_balance = global_config_map.get("paper_trade_account_balance").value
                for asset, balance in paper_trade_account_balance.items():
                    connector.set_balance(asset, balance)

            elif connector_name in CEXES or connector_name in DERIVATIVES:
                keys = dict((key, value.value) for key, value in dict(filter(lambda item: connector_name in item[0], global_config_map.items())).items())
                connector_class = get_connector_class(connector_name)
                connector = connector_class(**keys, trading_pairs=trading_pairs, trading_required=self._trading_required)

            elif connector_name in DEXES:
                assert self.wallet is not None
                keys = dict((key, value.value) for key, value in dict(filter(lambda item: connector_name in item[0], global_config_map.items())).items())
                connector_class = get_connector_class(connector_name)
                connector = connector_class(**keys, wallet=self.wallet, ethereum_rpc_url=ethereum_rpc_url, trading_pairs=trading_pairs, trading_required=self._trading_required)
                # TO-DO for DEXes: rename all extra argument to match key in global_config_map

            else:
                raise ValueError(f"Connector name {connector_name} is invalid.")

            self.markets[connector_name] = connector

        self.markets_recorder = MarketsRecorder(
            self.trade_fill_db,
            list(self.markets.values()),
            self.strategy_file_name,
            self.strategy_name,
        )
        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,
                    )
                )
        for notifier in self.notifiers:
            notifier.start()
コード例 #15
0
class HummingbotApplication(*commands):
    KILL_TIMEOUT = 10.0
    APP_WARNING_EXPIRY_DURATION = 3600.0
    APP_WARNING_STATUS_LIMIT = 6

    _main_app: Optional["HummingbotApplication"] = None

    @classmethod
    def logger(cls) -> HummingbotLogger:
        global s_logger
        if s_logger is None:
            s_logger = logging.getLogger(__name__)
        return s_logger

    @classmethod
    def main_application(cls, client_config_map: Optional[ClientConfigAdapter] = None) -> "HummingbotApplication":
        if cls._main_app is None:
            cls._main_app = HummingbotApplication(client_config_map)
        return cls._main_app

    def __init__(self, client_config_map: Optional[ClientConfigAdapter] = None):
        self.client_config_map: Union[ClientConfigMap, ClientConfigAdapter] = (  # type-hint enables IDE auto-complete
            client_config_map or load_client_config_map_from_file()
        )

        # This is to start fetching trading pairs for auto-complete
        TradingPairFetcher.get_instance(self.client_config_map)
        self.ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
        self.markets: Dict[str, ExchangeBase] = {}
        # strategy file name and name get assigned value after import or create command
        self._strategy_file_name: Optional[str] = None
        self.strategy_name: Optional[str] = None
        self._strategy_config_map: Optional[BaseStrategyConfigMap] = None
        self.strategy_task: Optional[asyncio.Task] = None
        self.strategy: Optional[StrategyBase] = None
        self.market_pair: Optional[CrossExchangeMarketPair] = None
        self.market_trading_pair_tuples: List[MarketTradingPairTuple] = []
        self.clock: Optional[Clock] = None
        self.market_trading_pairs_map = {}
        self.token_list = {}

        self.init_time: float = time.time()
        self.start_time: Optional[int] = None
        self.placeholder_mode = False
        self.log_queue_listener: Optional[logging.handlers.QueueListener] = None
        self.data_feed: Optional[DataFeedBase] = None
        self.notifiers: List[NotifierBase] = []
        self.kill_switch: Optional[KillSwitch] = None
        self._app_warnings: Deque[ApplicationWarning] = deque()
        self._trading_required: bool = True
        self._last_started_strategy_file: Optional[str] = None

        self.trade_fill_db: Optional[SQLConnectionManager] = None
        self.markets_recorder: Optional[MarketsRecorder] = None
        self._pmm_script_iterator = None
        self._binance_connector = None
        self._shared_client = None

        # gateway variables and monitor
        self._gateway_monitor = GatewayStatusMonitor(self)

        command_tabs = self.init_command_tabs()
        self.parser: ThrowingArgumentParser = load_parser(self, command_tabs)
        self.app = HummingbotCLI(
            self.client_config_map,
            input_handler=self._handle_command,
            bindings=load_key_bindings(self),
            completer=load_completer(self),
            command_tabs=command_tabs
        )

        self._init_gateway_monitor()

    @property
    def gateway_config_keys(self) -> List[str]:
        return self._gateway_monitor.gateway_config_keys

    @property
    def strategy_file_name(self) -> str:
        return self._strategy_file_name

    @strategy_file_name.setter
    def strategy_file_name(self, value: Optional[str]):
        self._strategy_file_name = value
        if value is not None:
            db_name = value.split(".")[0]
            self.trade_fill_db = SQLConnectionManager.get_trade_fills_instance(
                self.client_config_map, db_name
            )
        else:
            self.trade_fill_db = None

    @property
    def strategy_config_map(self):
        if self._strategy_config_map is not None:
            return self._strategy_config_map
        if self.strategy_name is not None:
            return get_strategy_config_map(self.strategy_name)
        return None

    @strategy_config_map.setter
    def strategy_config_map(self, config_map: BaseStrategyConfigMap):
        self._strategy_config_map = config_map

    def _init_gateway_monitor(self):
        try:
            # Do not start the gateway monitor during unit tests.
            if asyncio.get_running_loop() is not None:
                self._gateway_monitor = GatewayStatusMonitor(self)
                self._gateway_monitor.start()
        except RuntimeError:
            pass

    def notify(self, msg: str):
        self.app.log(msg)
        for notifier in self.notifiers:
            notifier.add_msg_to_queue(msg)

    def _handle_command(self, raw_command: str):
        # unset to_stop_config flag it triggered before loading any command
        if self.app.to_stop_config:
            self.app.to_stop_config = False

        raw_command = raw_command.strip()
        # NOTE: Only done for config command
        if raw_command.startswith("config"):
            command_split = raw_command.split(maxsplit=2)
        else:
            command_split = raw_command.split()
        try:
            if self.placeholder_mode:
                pass
            elif len(command_split) == 0:
                pass
            else:
                # 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(raw_command)
                    return

                shortcuts = self.client_config_map.command_shortcuts
                shortcut = None
                # see if we match against shortcut command
                if shortcuts is not None:
                    for each_shortcut in shortcuts:
                        if command_split[0] == each_shortcut.command:
                            shortcut = each_shortcut
                            break

                # perform shortcut expansion
                if shortcut is not None:
                    # check number of arguments
                    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
                        # 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:
                                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)
                    if not hasattr(args, "func"):
                        self.app.handle_tab_command(self, command_split[0], kwargs)
                    else:
                        f = args.func
                        del kwargs["func"]
                        f(**kwargs)
        except ArgumentParserError as e:
            if not self.be_silly(raw_command):
                self.notify(str(e))
        except NotImplementedError:
            self.notify("Command not yet implemented. This feature is currently under development.")
        except Exception as e:
            self.logger().error(e, exc_info=True)

    async def _cancel_outstanding_orders(self) -> bool:
        success = True
        try:
            kill_timeout: float = self.KILL_TIMEOUT
            self.notify("Canceling outstanding orders...")

            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:
                    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" % (
                        market_name,
                        '\n'.join(uncancelled_order_ids)
                    ))
        except Exception:
            self.logger().error("Error canceling outstanding orders.", exc_info=True)
            success = False

        if success:
            self.notify("All outstanding orders canceled.")
        return success

    async def run(self):
        await self.app.run()

    def add_application_warning(self, app_warning: ApplicationWarning):
        self._expire_old_application_warnings()
        self._app_warnings.append(app_warning)

    def clear_application_warning(self):
        self._app_warnings.clear()

    @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

    def _initialize_markets(self, market_names: List[Tuple[str, List[str]]]):
        # aggregate trading_pairs if there are duplicate markets

        for market_name, trading_pairs in market_names:
            if market_name not in self.market_trading_pairs_map:
                self.market_trading_pairs_map[market_name] = []
            for hb_trading_pair in trading_pairs:
                self.market_trading_pairs_map[market_name].append(hb_trading_pair)

        for connector_name, trading_pairs in self.market_trading_pairs_map.items():
            conn_setting = AllConnectorSettings.get_connector_settings()[connector_name]

            if connector_name.endswith("paper_trade") and conn_setting.type == ConnectorType.Exchange:
                connector = create_paper_trade_market(conn_setting.parent_name, self.client_config_map, trading_pairs)
                paper_trade_account_balance = self.client_config_map.paper_trade.paper_trade_account_balance
                if paper_trade_account_balance is not None:
                    for asset, balance in paper_trade_account_balance.items():
                        connector.set_balance(asset, balance)
            else:
                keys = Security.api_keys(connector_name)
                init_params = conn_setting.conn_init_parameters(keys)
                init_params.update(trading_pairs=trading_pairs, trading_required=self._trading_required)
                connector_class = get_connector_class(connector_name)
                read_only_config = ReadOnlyClientConfigAdapter.lock_config(self.client_config_map)
                connector = connector_class(read_only_config, **init_params)
            self.markets[connector_name] = connector

        self.markets_recorder = MarketsRecorder(
            self.trade_fill_db,
            list(self.markets.values()),
            self.strategy_file_name,
            self.strategy_name,
        )
        self.markets_recorder.start()

    def _initialize_notifiers(self):
        self.notifiers.extend(
            [
                notifier for notifier in self.client_config_map.telegram_mode.get_notifiers(self)
                if notifier not in self.notifiers
            ]
        )
        for notifier in self.notifiers:
            notifier.start()

    def init_command_tabs(self) -> Dict[str, CommandTab]:
        """
        Initiates and returns a CommandTab dictionary with mostly defaults and None values, These values will be
        populated later on by HummingbotCLI
        """
        command_tabs: Dict[str, CommandTab] = {}
        for tab_class in tab_classes:
            name = tab_class.get_command_name()
            command_tabs[name] = CommandTab(name, None, None, None, tab_class)
        return command_tabs

    def save_client_config(self):
        save_to_yml(CLIENT_CONFIG_PATH, self.client_config_map)
コード例 #16
0
class HummingbotCLITest(unittest.TestCase):
    command_name = "command_1"

    def setUp(self) -> None:
        super().setUp()

        tabs = {
            self.command_name:
            CommandTab(self.command_name, None, None, None, MagicMock())
        }
        self.mock_hb = MagicMock()
        self.app = HummingbotCLI(None, None, None, tabs)
        self.app.app = MagicMock()

    def test_handle_tab_command_on_close_argument(self):
        tab = self.app.command_tabs[self.command_name]
        tab.close_button = MagicMock()
        tab.button = MagicMock()
        tab.output_field = MagicMock()
        self.app.handle_tab_command(self.mock_hb, self.command_name,
                                    {"close": True})
        self.assertIsNone(tab.button)
        self.assertIsNone(tab.close_button)
        self.assertIsNone(tab.output_field)
        self.assertFalse(tab.is_selected)
        self.assertEqual(tab.tab_index, 0)

    def test_handle_tab_command_create_new_tab_and_display(self):
        tab = self.app.command_tabs[self.command_name]
        self.app.handle_tab_command(self.mock_hb, self.command_name,
                                    {"close": False})
        self.assertIsInstance(tab.button, Button)
        self.assertIsInstance(tab.close_button, Button)
        self.assertIsInstance(tab.output_field, CustomTextArea)
        self.assertEqual(tab.tab_index, 1)
        self.assertTrue(tab.is_selected)
        self.assertTrue(tab.tab_class.display.called)

    @patch("hummingbot.client.ui.layout.Layout")
    @patch("hummingbot.client.ui.layout.FloatContainer")
    @patch("hummingbot.client.ui.layout.ConditionalContainer")
    @patch("hummingbot.client.ui.layout.Box")
    @patch("hummingbot.client.ui.layout.HSplit")
    @patch("hummingbot.client.ui.layout.VSplit")
    def test_handle_tab_command_on_existing_tab(self, mock_vsplit, mock_hsplit,
                                                mock_box, moc_cc, moc_fc,
                                                mock_layout):
        tab = self.app.command_tabs[self.command_name]
        tab.button = MagicMock()
        tab.output_field = MagicMock()
        tab.close_button = MagicMock()
        tab.is_selected = False
        self.app.handle_tab_command(self.mock_hb, self.command_name,
                                    {"close": False})
        self.assertTrue(tab.is_selected)
        self.assertTrue(tab.tab_class.display.call_count == 1)

        # Test display not called if there is a running task
        tab.is_selected = False
        tab.task = MagicMock()
        tab.task.done.return_value = False
        self.app.handle_tab_command(self.mock_hb, self.command_name,
                                    {"close": False})
        self.assertTrue(tab.is_selected)
        self.assertTrue(tab.tab_class.display.call_count == 1)

    @patch("hummingbot.client.ui.layout.Layout")
    @patch("hummingbot.client.ui.layout.FloatContainer")
    @patch("hummingbot.client.ui.layout.ConditionalContainer")
    @patch("hummingbot.client.ui.layout.Box")
    @patch("hummingbot.client.ui.layout.HSplit")
    @patch("hummingbot.client.ui.layout.VSplit")
    def test_tab_navigation(self, mock_vsplit, mock_hsplit, mock_box, moc_cc,
                            moc_fc, mock_layout):
        tab2 = CommandTab("command_2", None, None, None, MagicMock(), False)

        self.app.command_tabs["command_2"] = tab2
        tab1 = self.app.command_tabs[self.command_name]

        self.app.handle_tab_command(self.mock_hb, self.command_name,
                                    {"close": False})
        self.app.handle_tab_command(self.mock_hb, "command_2",
                                    {"close": False})
        self.assertTrue(tab2.is_selected)

        self.app.tab_navigate_left()
        self.assertTrue(tab1.is_selected)
        self.assertFalse(tab2.is_selected)
        self.app.tab_navigate_left()
        self.assertTrue(
            all(not t.is_selected for t in self.app.command_tabs.values()))
        self.app.tab_navigate_left()
        self.assertTrue(
            all(not t.is_selected for t in self.app.command_tabs.values()))

        self.app.tab_navigate_right()
        self.assertTrue(tab1.is_selected)

        self.app.tab_navigate_right()
        self.assertFalse(tab1.is_selected)
        self.assertTrue(tab2.is_selected)

        self.app.tab_navigate_right()
        self.assertFalse(tab1.is_selected)
        self.assertTrue(tab2.is_selected)