def get_taker_to_maker_conversion_rate(
        self, market_pair: CrossExchangeMarketPair
    ) -> Tuple[str, str, Decimal, str, str, Decimal]:
        """
        Find conversion rates from taker market to maker market
        :param market_pair: maker and taker trading pairs for which to do conversion
        :return: A tuple of quote pair symbol, quote conversion rate source, quote conversion rate,
        base pair symbol, base conversion rate source, base conversion rate
        """
        quote_pair = f"{market_pair.taker.quote_asset}-{market_pair.maker.quote_asset}"
        if market_pair.taker.quote_asset != market_pair.maker.quote_asset:
            quote_rate_source = RateOracle.source.name
            quote_rate = RateOracle.get_instance().rate(quote_pair)
        else:
            quote_rate_source = "fixed"
            quote_rate = Decimal("1")

        base_pair = f"{market_pair.taker.base_asset}-{market_pair.maker.base_asset}"
        if market_pair.taker.base_asset != market_pair.maker.base_asset:
            base_rate_source = RateOracle.source.name
            base_rate = RateOracle.get_instance().rate(base_pair)
        else:
            base_rate_source = "fixed"
            base_rate = Decimal("1")

        return quote_pair, quote_rate_source, quote_rate, base_pair, base_rate_source, base_rate
Exemplo n.º 2
0
    async def start_check(
            self,  # type: HummingbotApplication
            log_level: Optional[str] = None,
            restore: Optional[bool] = False):
        if self.strategy_task is not None and not self.strategy_task.done():
            self._notify(
                'The bot is already running - please run "stop" first')
            return

        if settings.required_rate_oracle:
            if not (await self.confirm_oracle_conversion_rate()):
                self._notify("The strategy failed to start.")
                return
            else:
                RateOracle.get_instance().start()
        is_valid = await self.status_check_all(notify_success=False)
        if not is_valid:
            self._notify("Status checks failed. Start aborted.")
            return
        if self._last_started_strategy_file != self.strategy_file_name:
            init_logging(
                "hummingbot_logs.yml",
                override_log_level=log_level.upper() if log_level else None,
                strategy_file_path=self.strategy_file_name)
            self._last_started_strategy_file = self.strategy_file_name

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

        self._initialize_notifiers()

        self._notify(
            f"\nStatus check complete. Starting '{self.strategy_name}' strategy..."
        )
        if global_config_map.get("paper_trade_enabled").value:
            self._notify(
                "\nPaper Trading ON: All orders are simulated, and no real orders are placed."
            )

        for exchange in settings.required_exchanges:
            connector = str(exchange)
            status = get_connector_status(connector)

            # Display custom warning message for specific connectors
            warning_msg = warning_messages.get(connector, None)
            if warning_msg is not None:
                self._notify(f"\nConnector status: {status}\n"
                             f"{warning_msg}")

            # Display warning message if the exchange connector has outstanding issues or not working
            elif status != "GREEN":
                self._notify(
                    f"\nConnector status: {status}. This connector has one or more issues.\n"
                    "Refer to our Github page for more info: https://github.com/coinalpha/hummingbot"
                )

        await self.start_market_making(self.strategy_name, restore)
Exemplo n.º 3
0
 def calculate_profitability(self, position: UniswapV3InFlightPosition):
     """
     Does simple computation and returns a dictionary containing data required by other functions.
     :param position: an instance of UniswapV3InFlightPosition
     :return {}: dictionaty containing "base_change", "quote_change", "base_fee", "quote_fee"
                 "tx_fee", "profitability".
     """
     base_tkn, quote_tkn = position.trading_pair.split("-")
     init_base = Decimal(str(position.base_amount))
     init_quote = Decimal(str(position.quote_amount))
     base_change = Decimal(str(position.current_base_amount)) - Decimal(
         str(position.base_amount))
     quote_change = Decimal(str(position.current_quote_amount)) - Decimal(
         str(position.quote_amount))
     base_fee = Decimal(str(position.unclaimed_base_amount))
     quote_fee = Decimal(str(position.unclaimed_quote_amount))
     if base_tkn != "WETH":
         fee_rate = RateOracle.get_instance().rate(f"ETH-{quote_tkn}")
         if fee_rate:
             tx_fee = Decimal(str(position.gas_price)) * 2 * fee_rate
         else:  # cases like this would be rare
             tx_fee = Decimal(str(position.gas_price)) * 2
     else:
         tx_fee = Decimal(str(position.gas_price)) * 2
     init_value = (init_base * self._last_price) + init_quote
     profitability = s_decimal_0 if init_value == s_decimal_0 else \
         ((((base_change + base_fee) * self._last_price) + quote_change + quote_fee - tx_fee) / init_value)
     return {
         "base_change": base_change,
         "quote_change": quote_change,
         "base_fee": base_fee,
         "quote_fee": quote_fee,
         "tx_fee": tx_fee,
         "profitability": profitability
     }
Exemplo n.º 4
0
    async def stop_loop(
            self,  # type: HummingbotApplication
            skip_order_cancellation: bool = False):
        self.logger().info("stop command initiated.")
        self._notify("\nWinding down...")

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

        if self._script_iterator is not None:
            self._script_iterator.stop(self.clock)

        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.
            if self.clock:
                self.clock.remove_iterator(self.strategy)
            success = await self._cancel_outstanding_orders()
            # Give some time for cancellation events to trigger
            await asyncio.sleep(0.5)
            if success:
                # Only erase markets when cancellation has been successful
                self.markets = {}

        if self.strategy_task is not None and not self.strategy_task.cancelled(
        ):
            self.strategy_task.cancel()

        if RateOracle.get_instance().started:
            RateOracle.get_instance().stop()

        if self.markets_recorder is not None:
            self.markets_recorder.stop()

        if self.kill_switch is not None:
            self.kill_switch.stop()

        self.strategy_task = None
        self.strategy = None
        self.market_pair = None
        self.clock = None
        self.markets_recorder = None
        self.market_trading_pairs_map.clear()
Exemplo n.º 5
0
    def _get_exchange_rate(trading_pair: str, exchange: Optional["ExchangeBase"] = None) -> Decimal:
        from hummingbot.core.rate_oracle.rate_oracle import RateOracle

        if exchange is not None:
            rate = exchange.get_price(trading_pair, is_buy=True)
        else:
            rate = RateOracle.get_instance().rate(trading_pair)
        return rate
Exemplo n.º 6
0
    def init_params(
        self,
        market_info_1: MarketTradingPairTuple,
        market_info_2: MarketTradingPairTuple,
        min_profitability: Decimal,
        order_amount: Decimal,
        market_1_slippage_buffer: Decimal = Decimal("0"),
        market_2_slippage_buffer: Decimal = Decimal("0"),
        concurrent_orders_submission: bool = True,
        status_report_interval: float = 900,
        gateway_transaction_cancel_interval: int = 600,
    ):
        """
        Assigns strategy parameters, this function must be called directly after init.
        The reason for this is to make the parameters discoverable on introspect (it is not possible on init of
        a Cython class).
        :param market_info_1: The first market
        :param market_info_2: The second market
        :param min_profitability: The minimum profitability for execute trades (e.g. 0.0003 for 0.3%)
        :param order_amount: The order amount
        :param market_1_slippage_buffer: The buffer for which to adjust order price for higher chance of
        the order getting filled. This is quite important for AMM which transaction takes a long time where a slippage
        is acceptable rather having the transaction get rejected. The submitted order price will be adjust higher
        for buy order and lower for sell order.
        :param market_2_slippage_buffer: The slipper buffer for market_2
        :param concurrent_orders_submission: whether to submit both arbitrage taker orders (buy and sell) simultaneously
        If false, the bot will wait for first exchange order filled before submitting the other order.
        :param status_report_interval: Amount of seconds to wait to refresh the status report
        :param gateway_transaction_cancel_interval: Amount of seconds to wait before trying to cancel orders that are
        blockchain transactions that have not been included in a block (they are still in the mempool).
        """
        self._market_info_1 = market_info_1
        self._market_info_2 = market_info_2
        self._min_profitability = min_profitability
        self._order_amount = order_amount
        self._market_1_slippage_buffer = market_1_slippage_buffer
        self._market_2_slippage_buffer = market_2_slippage_buffer
        self._concurrent_orders_submission = concurrent_orders_submission
        self._last_no_arb_reported = 0
        self._all_arb_proposals = None
        self._all_markets_ready = False

        self._ev_loop = asyncio.get_event_loop()
        self._main_task = None

        self._last_timestamp = 0
        self._status_report_interval = status_report_interval
        self.add_markets([market_info_1.market, market_info_2.market])
        self._quote_eth_rate_fetch_loop_task = None

        self._rate_source = RateOracle.get_instance()

        self._cancel_outdated_orders_task = None
        self._gateway_transaction_cancel_interval = gateway_transaction_cancel_interval

        self._order_id_side_map: Dict[str, ArbProposalSide] = {}
Exemplo n.º 7
0
 def test_rate_oracle_network(self):
     oracle = RateOracle.get_instance()
     oracle.start()
     asyncio.get_event_loop().run_until_complete(oracle.get_ready())
     self.assertGreater(len(oracle.prices), 0)
     rate = oracle.rate("SCRT-USDT")
     self.assertGreater(rate, 0)
     rate1 = oracle.rate("BTC-USDT")
     self.assertGreater(rate1, 100)
     oracle.stop()
Exemplo n.º 8
0
    def _get_exchange_rate(trading_pair: str,
                           exchange: Optional["ExchangeBase"] = None,
                           rate_source: Optional[Any] = None) -> Decimal:
        from hummingbot.core.rate_oracle.rate_oracle import RateOracle

        if exchange is not None and trading_pair in exchange.order_books:
            rate = exchange.get_price_by_type(trading_pair, PriceType.MidPrice)
        else:
            local_rate_source = rate_source or RateOracle.get_instance()
            rate = local_rate_source.rate(trading_pair)
            if rate is None:
                raise ValueError(
                    f"Could not find the exchange rate for {trading_pair} using the rate source "
                    f"{local_rate_source} (please verify it has been correctly configured)"
                )
        return rate
Exemplo n.º 9
0
 def create_proposal(self) -> List[OrderCandidate]:
     """
     Creates and returns a proposal (a list of order candidate), in this strategy the list has 1 element at most.
     """
     daily_closes = self._get_daily_close_list(self.trading_pair)
     start_index = (-1 * self.moving_avg_period) - 1
     # Calculate the average of the 50 element prior to the last element
     avg_close = mean(daily_closes[start_index:-1])
     proposal = []
     # If the current price (the last close) is below the dip, add a new order candidate to the proposal
     if daily_closes[-1] < avg_close * (Decimal("1") - self.dip_percentage):
         order_price = self.connector.get_price(self.trading_pair, False) * Decimal("0.9")
         usd_conversion_rate = RateOracle.get_instance().rate(self.conversion_pair)
         amount = (self.buy_usd_amount / usd_conversion_rate) / order_price
         proposal.append(OrderCandidate(self.trading_pair, False, OrderType.LIMIT, TradeType.BUY, amount,
                                        order_price))
     return proposal
Exemplo n.º 10
0
 def test_rate_oracle_network(self):
     oracle = RateOracle.get_instance()
     oracle.start()
     asyncio.get_event_loop().run_until_complete(oracle.get_ready())
     print(oracle.prices)
     self.assertGreater(len(oracle.prices), 0)
     rate = oracle.rate("SCRT-USDT")
     print(f"rate SCRT-USDT: {rate}")
     self.assertGreater(rate, 0)
     rate1 = oracle.rate("BTC-USDT")
     print(f"rate BTC-USDT: {rate1}")
     self.assertGreater(rate1, 100)
     # wait for 5 s to check rate again
     asyncio.get_event_loop().run_until_complete(asyncio.sleep(5))
     rate2 = oracle.rate("BTC-USDT")
     print(f"rate BTC-USDT: {rate2}")
     self.assertNotEqual(0, rate2)
     oracle.stop()
Exemplo n.º 11
0
 async def calculate_profitability(self,
                                   position: UniswapV3InFlightPosition):
     """
     Does simple computation and returns a dictionary containing data required by other functions.
     :param position: an instance of UniswapV3InFlightPosition
     :return {}: dictionaty containing "base_change", "quote_change", "base_fee", "quote_fee"
                 "tx_fee", "profitability".
     """
     base_tkn, quote_tkn = position.trading_pair.split("-")
     init_base = position.base_amount
     init_quote = position.quote_amount
     base_change = position.current_base_amount - position.base_amount
     quote_change = position.current_quote_amount - position.quote_amount
     base_fee = position.unclaimed_base_amount
     quote_fee = position.unclaimed_quote_amount
     if len(position.tx_fees) < 2 or position.tx_fees[-1] == s_decimal_0:
         remove_lp_fee = await self._market_info.market._remove_position(
             position.hb_id, position.token_id, Decimal("100.0"), True)
         remove_lp_fee = remove_lp_fee if remove_lp_fee is not None else s_decimal_0
         position.tx_fees.append(remove_lp_fee)
     if quote_tkn != "WETH":
         fee_rate = RateOracle.get_instance().rate(f"ETH-{quote_tkn}")
         if fee_rate:
             tx_fee = sum(position.tx_fees) * fee_rate
         else:  # cases like this would be rare
             tx_fee = sum(position.tx_fees)
     else:
         tx_fee = sum(position.tx_fees)
     init_value = (init_base * self._last_price) + init_quote
     profitability = s_decimal_0 if init_value == s_decimal_0 else \
         ((((base_change + base_fee) * self._last_price) + quote_change + quote_fee - tx_fee) / init_value)
     return {
         "base_change": base_change,
         "quote_change": quote_change,
         "base_fee": base_fee,
         "quote_fee": quote_fee,
         "tx_fee": tx_fee,
         "profitability": profitability
     }
Exemplo n.º 12
0
    async def start_check(self,  # type: HummingbotApplication
                          log_level: Optional[str] = None,
                          restore: Optional[bool] = False,
                          strategy_file_name: Optional[str] = None):
        if self.strategy_task is not None and not self.strategy_task.done():
            self.notify('The bot is already running - please run "stop" first')
            return

        if settings.required_rate_oracle:
            # If the strategy to run requires using the rate oracle to find FX rates, validate there is a rate for
            # each configured token pair
            if not (await self.confirm_oracle_conversion_rate()):
                self.notify("The strategy failed to start.")
                return

        if strategy_file_name:
            file_name = strategy_file_name.split(".")[0]
            self.strategy_file_name = file_name
            self.strategy_name = file_name
        elif not await self.status_check_all(notify_success=False):
            self.notify("Status checks failed. Start aborted.")
            return
        if self._last_started_strategy_file != self.strategy_file_name:
            init_logging("hummingbot_logs.yml",
                         override_log_level=log_level.upper() if log_level else None,
                         strategy_file_path=self.strategy_file_name)
            self._last_started_strategy_file = self.strategy_file_name

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

        self._initialize_notifiers()
        try:
            self._initialize_strategy(self.strategy_name)
        except NotImplementedError:
            self.strategy_name = None
            self.strategy_file_name = None
            self.notify("Invalid strategy. Start aborted.")
            raise

        if any([str(exchange).endswith("paper_trade") for exchange in settings.required_exchanges]):
            self.notify("\nPaper Trading Active: All orders are simulated and no real orders are placed.")

        for exchange in settings.required_exchanges:
            connector: str = str(exchange)
            status: str = get_connector_status(connector)
            warning_msg: Optional[str] = warning_messages.get(connector, None)

            # confirm gateway connection
            conn_setting: settings.ConnectorSetting = settings.AllConnectorSettings.get_connector_settings()[connector]
            if conn_setting.uses_gateway_generic_connector():
                connector_details: Dict[str, Any] = conn_setting.conn_init_parameters()
                if connector_details:
                    data: List[List[str]] = [
                        ["chain", connector_details['chain']],
                        ["network", connector_details['network']],
                        ["wallet_address", connector_details['wallet_address']]
                    ]
                    await UserBalances.instance().update_exchange_balance(connector)
                    balances: List[str] = [
                        f"{str(PerformanceMetrics.smart_round(v, 8))} {k}"
                        for k, v in UserBalances.instance().all_balances(connector).items()
                    ]
                    data.append(["balances", ""])
                    for bal in balances:
                        data.append(["", bal])
                    wallet_df: pd.DataFrame = pd.DataFrame(data=data, columns=["", f"{connector} configuration"])
                    self.notify(wallet_df.to_string(index=False))

                    self.app.clear_input()
                    self.placeholder_mode = True
                    use_configuration = await self.app.prompt(prompt="Do you want to continue? (Yes/No) >>> ")
                    self.placeholder_mode = False
                    self.app.change_prompt(prompt=">>> ")

                    if use_configuration in ["N", "n", "No", "no"]:
                        return

                    if use_configuration not in ["Y", "y", "Yes", "yes"]:
                        self.notify("Invalid input. Please execute the `start` command again.")
                        return

            # Display custom warning message for specific connectors
            elif warning_msg is not None:
                self.notify(f"\nConnector status: {status}\n"
                            f"{warning_msg}")

            # Display warning message if the exchange connector has outstanding issues or not working
            elif not status.endswith("GREEN"):
                self.notify(f"\nConnector status: {status}. This connector has one or more issues.\n"
                            "Refer to our Github page for more info: https://github.com/coinalpha/hummingbot")

        self.notify(f"\nStatus check complete. Starting '{self.strategy_name}' strategy...")
        await self.start_market_making(restore)
        # We always start the RateOracle. It is required for PNL calculation.
        RateOracle.get_instance().start()
Exemplo n.º 13
0
    def profit_pct(
            self,
            rate_source: Optional[RateOracle] = None,
            account_for_fee: bool = False,
    ) -> Decimal:
        """
        Returns a profit in percentage value (e.g. 0.01 for 1% profitability)
        Assumes the base token is the same in both arbitrage sides
        """
        if not rate_source:
            rate_source = RateOracle.get_instance()

        buy_side: ArbProposalSide = self.first_side if self.first_side.is_buy else self.second_side
        sell_side: ArbProposalSide = self.first_side if not self.first_side.is_buy else self.second_side
        base_conversion_pair: str = f"{sell_side.market_info.base_asset}-{buy_side.market_info.base_asset}"
        quote_conversion_pair: str = f"{sell_side.market_info.quote_asset}-{buy_side.market_info.quote_asset}"

        sell_base_to_buy_base_rate: Decimal = Decimal(1)
        sell_quote_to_buy_quote_rate: Decimal = rate_source.rate(quote_conversion_pair)

        buy_fee_amount: Decimal = s_decimal_0
        sell_fee_amount: Decimal = s_decimal_0
        result: Decimal = s_decimal_0

        if sell_quote_to_buy_quote_rate and sell_base_to_buy_base_rate:
            if account_for_fee:
                buy_trade_fee: TradeFeeBase = build_trade_fee(
                    exchange=buy_side.market_info.market.name,
                    is_maker=False,
                    base_currency=buy_side.market_info.base_asset,
                    quote_currency=buy_side.market_info.quote_asset,
                    order_type=OrderType.MARKET,
                    order_side=TradeType.BUY,
                    amount=buy_side.amount,
                    price=buy_side.order_price,
                    extra_flat_fees=buy_side.extra_flat_fees
                )
                sell_trade_fee: TradeFeeBase = build_trade_fee(
                    exchange=sell_side.market_info.market.name,
                    is_maker=False,
                    base_currency=sell_side.market_info.base_asset,
                    quote_currency=sell_side.market_info.quote_asset,
                    order_type=OrderType.MARKET,
                    order_side=TradeType.SELL,
                    amount=sell_side.amount,
                    price=sell_side.order_price,
                    extra_flat_fees=sell_side.extra_flat_fees
                )
                buy_fee_amount: Decimal = buy_trade_fee.fee_amount_in_token(
                    trading_pair=buy_side.market_info.trading_pair,
                    price=buy_side.quote_price,
                    order_amount=buy_side.amount,
                    token=buy_side.market_info.quote_asset,
                    rate_source=rate_source
                )
                sell_fee_amount: Decimal = sell_trade_fee.fee_amount_in_token(
                    trading_pair=sell_side.market_info.trading_pair,
                    price=sell_side.quote_price,
                    order_amount=sell_side.amount,
                    token=sell_side.market_info.quote_asset,
                    rate_source=rate_source
                )

            buy_spent_net: Decimal = (buy_side.amount * buy_side.quote_price) + buy_fee_amount
            sell_gained_net: Decimal = (sell_side.amount * sell_side.quote_price) - sell_fee_amount
            sell_gained_net_in_buy_quote_currency: Decimal = (
                sell_gained_net * sell_quote_to_buy_quote_rate / sell_base_to_buy_base_rate
            )

            result: Decimal = (
                ((sell_gained_net_in_buy_quote_currency - buy_spent_net) / buy_spent_net)
                if buy_spent_net != s_decimal_0
                else s_decimal_0
            )
        else:
            self.logger().warning("The arbitrage proposal profitability could not be calculated due to a missing rate"
                                  f" ({base_conversion_pair}={sell_base_to_buy_base_rate},"
                                  f" {quote_conversion_pair}={sell_quote_to_buy_quote_rate})")
        return result