コード例 #1
0
    def fee_amount_in_token(self,
                            trading_pair: str,
                            price: Decimal,
                            order_amount: Decimal,
                            token: str,
                            exchange: Optional['ExchangeBase'] = None,
                            rate_source: Optional[Any] = None) -> Decimal:

        base, quote = split_hb_trading_pair(trading_pair)
        fee_amount = Decimal("0")
        if self.percent != 0:
            amount_from_percentage = (price * order_amount) * self.percent
            if self._are_tokens_interchangeable(quote, token):
                fee_amount += amount_from_percentage
            else:
                conversion_pair = combine_to_hb_trading_pair(base=quote,
                                                             quote=token)
                conversion_rate = self._get_exchange_rate(
                    conversion_pair, exchange, rate_source)
                fee_amount += amount_from_percentage * conversion_rate
        for flat_fee in self.flat_fees:
            if self._are_tokens_interchangeable(flat_fee.token, token):
                # No need to convert the value
                fee_amount += flat_fee.amount
            elif (self._are_tokens_interchangeable(flat_fee.token, base)
                  and (self._are_tokens_interchangeable(quote, token))):
                # In this case instead of looking for the rate we use directly the price in the parameters
                fee_amount += flat_fee.amount * price
            else:
                conversion_pair = combine_to_hb_trading_pair(
                    base=flat_fee.token, quote=token)
                conversion_rate = self._get_exchange_rate(
                    conversion_pair, exchange, rate_source)
                fee_amount += (flat_fee.amount * conversion_rate)
        return fee_amount
コード例 #2
0
    async def collect_metrics(self, events: List[OrderFilledEvent]):
        try:
            total_volume = Decimal("0")

            for fill_event in events:
                trade_base, trade_quote = split_hb_trading_pair(
                    fill_event.trading_pair)
                from_quote_conversion_pair = combine_to_hb_trading_pair(
                    base=trade_quote, quote=self._valuation_token)
                rate = await self._rate_provider.stored_or_live_rate(
                    from_quote_conversion_pair)

                if rate is not None:
                    total_volume += fill_event.amount * fill_event.price * rate
                else:
                    from_base_conversion_pair = combine_to_hb_trading_pair(
                        base=trade_base, quote=self._valuation_token)
                    rate = await self._rate_provider.stored_or_live_rate(
                        from_base_conversion_pair)
                    if rate is not None:
                        total_volume += fill_event.amount * rate
                    else:
                        self.logger().debug(
                            f"Could not find a conversion rate rate using Rate Oracle for any of "
                            f"the pairs {from_quote_conversion_pair} or {from_base_conversion_pair}"
                        )

            if total_volume > Decimal("0"):
                self._dispatch_trade_volume(total_volume)
        except asyncio.CancelledError:
            raise
        except Exception:
            self._collected_events.extend(events)
コード例 #3
0
 def _get_order_collateral_token(self, exchange: 'ExchangeBase') -> Optional[str]:
     trading_pair = self.trading_pair
     base, quote = split_hb_trading_pair(trading_pair)
     if self.order_side == TradeType.BUY:
         oc_token = quote
     else:
         oc_token = base
     return oc_token
コード例 #4
0
 def get_size_token_and_order_size(self) -> TokenAmount:
     trading_pair = self.trading_pair
     base, quote = split_hb_trading_pair(trading_pair)
     if self.order_side == TradeType.BUY:
         order_size = self.amount * self.price
         size_token = quote
     else:
         order_size = self.amount
         size_token = base
     return TokenAmount(size_token, order_size)
コード例 #5
0
    def get_assets(self, connector_name: str) -> List[str]:
        """
        Returns a unique list of unique of token names sorted alphabetically

        :param connector_name: The name of the connector

        :return: A list of token names
        """
        result: Set = set()
        for trading_pair in self.markets[connector_name]:
            result.update(split_hb_trading_pair(trading_pair))
        return sorted(result)
コード例 #6
0
    def _market_trading_pair_tuple(
            self, connector_name: str,
            trading_pair: str) -> MarketTradingPairTuple:
        """
        Creates and returns a new MarketTradingPairTuple

        :param connector_name: The name of the connector
        :param trading_pair: The trading pair
        :return: A new MarketTradingPairTuple object.
        """
        base, quote = split_hb_trading_pair(trading_pair)
        return MarketTradingPairTuple(self.connectors[connector_name],
                                      trading_pair, base, quote)
コード例 #7
0
    def _get_fee(self, exchange: 'ExchangeBase') -> TradeFeeBase:
        base, quote = split_hb_trading_pair(self.trading_pair)
        position_action = PositionAction.CLOSE if self.position_close else PositionAction.OPEN
        fee = build_perpetual_trade_fee(
            exchange.name,
            self.is_maker,
            position_action,
            base,
            quote,
            self.order_type,
            self.order_side,
            self.amount,
            self.price,
        )

        return fee
コード例 #8
0
    def _get_fee(self, exchange: 'ExchangeBase') -> TradeFeeBase:
        trading_pair = self.trading_pair
        price = self.price
        base, quote = split_hb_trading_pair(trading_pair)
        fee = build_trade_fee(
            exchange.name,
            self.is_maker,
            base,
            quote,
            self.order_type,
            self.order_side,
            self.amount,
            price,
        )

        return fee
コード例 #9
0
    def _get_size_collateral_price(
        self, exchange: 'ExchangeBase', order_collateral_token: str
    ) -> Decimal:
        size_token, _ = self.get_size_token_and_order_size()
        base, quote = split_hb_trading_pair(self.trading_pair)

        if order_collateral_token == size_token:
            price = Decimal("1")
        elif order_collateral_token == base:  # size_token == quote
            price = Decimal("1") / self.price
        elif order_collateral_token == quote:  # size_token == base
            price = self.price
        else:
            size_collateral_pair = combine_to_hb_trading_pair(size_token, order_collateral_token)
            price = exchange.get_price(size_collateral_pair, is_buy=True)  # we are buying

        return price
コード例 #10
0
 def fee_amount_in_quote(
     self, trading_pair: str, price: Decimal, order_amount: Decimal, exchange: Optional['ExchangeBase'] = None
 ) -> Decimal:
     fee_amount = Decimal("0")
     if self.percent > 0:
         fee_amount = (price * order_amount) * self.percent
     base, quote = split_hb_trading_pair(trading_pair)
     for flat_fee in self.flat_fees:
         if interchangeable(flat_fee.token, base):
             fee_amount += (flat_fee.amount * price)
         elif interchangeable(flat_fee.token, quote):
             fee_amount += flat_fee.amount
         else:
             conversion_pair = combine_to_hb_trading_pair(base=flat_fee.token, quote=quote)
             conversion_rate = self._get_exchange_rate(conversion_pair, exchange)
             fee_amount = (flat_fee.amount * conversion_rate)
     return fee_amount
コード例 #11
0
    async def _initialize_metrics(self, exchange: str, trading_pair: str,
                                  trades: List[Any],
                                  current_balances: Dict[str, Decimal]):
        """
        Calculates PnL, fees, Return % and etc...
        :param exchange: the exchange or connector name
        :param trading_pair: the trading market to get performance metrics
        :param trades: the list of TradeFill or Trade object
        :param current_balances: current user account balance
        """

        base, quote = split_hb_trading_pair(trading_pair)
        buys, sells = self._preprocess_trades_and_group_by_type(trades)

        self.num_buys = len(buys)
        self.num_sells = len(sells)
        self.num_trades = self.num_buys + self.num_sells

        self.cur_base_bal = current_balances.get(base, 0)
        self.cur_quote_bal = current_balances.get(quote, 0)
        self.start_base_bal = self.cur_base_bal - self.tot_vol_base
        self.start_quote_bal = self.cur_quote_bal - self.tot_vol_quote

        self.start_price = Decimal(str(trades[0].price))
        self.cur_price = await get_last_price(
            exchange.replace("_paper_trade", ""), trading_pair)
        if self.cur_price is None:
            self.cur_price = Decimal(str(trades[-1].price))
        self.start_base_ratio_pct = self.divide(
            self.start_base_bal * self.start_price,
            (self.start_base_bal * self.start_price) + self.start_quote_bal)
        self.cur_base_ratio_pct = self.divide(
            self.cur_base_bal * self.cur_price,
            (self.cur_base_bal * self.cur_price) + self.cur_quote_bal)

        self.hold_value = (self.start_base_bal *
                           self.cur_price) + self.start_quote_bal
        self.cur_value = (self.cur_base_bal *
                          self.cur_price) + self.cur_quote_bal
        self._calculate_trade_pnl(buys, sells)

        await self._calculate_fees(exchange, quote, trades)

        self.total_pnl = self.trade_pnl - self.fee_in_quote
        self.return_pct = self.divide(self.total_pnl, self.hold_value)
コード例 #12
0
    async def asset_ratio_maintenance_prompt(
        self,  # type: HummingbotApplication
        config_map: BaseTradingStrategyConfigMap,
        input_value: Any = None,
    ):  # pragma: no cover
        if input_value:
            config_map.inventory_target_base_pct = input_value
        else:
            exchange = config_map.exchange
            market = config_map.market
            base, quote = split_hb_trading_pair(market)
            balances = await UserBalances.instance().balances(
                exchange, base, quote)
            if balances is None:
                return
            base_ratio = await UserBalances.base_amount_ratio(
                exchange, market, balances)
            if base_ratio is None:
                return
            base_ratio = round(base_ratio, 3)
            quote_ratio = 1 - base_ratio

            cvar = ConfigVar(
                key="temp_config",
                prompt=
                f"On {exchange}, you have {balances.get(base, 0):.4f} {base} and "
                f"{balances.get(quote, 0):.4f} {quote}. By market value, "
                f"your current inventory split is {base_ratio:.1%} {base} "
                f"and {quote_ratio:.1%} {quote}."
                f" Would you like to keep this ratio? (Yes/No) >>> ",
                required_if=lambda: True,
                type_str="bool",
                validator=validate_bool)
            await self.prompt_a_config_legacy(cvar)
            if cvar.value:
                config_map.inventory_target_base_pct = round(
                    base_ratio * Decimal('100'), 1)
            elif self.app.to_stop_config:
                self.app.to_stop_config = False
            else:
                await self.prompt_a_config(config_map,
                                           config="inventory_target_base_pct")
コード例 #13
0
class BuyDipExample(ScriptStrategyBase):
    """
    THis strategy buys ETH (with BTC) when the ETH-BTC drops 5% below 50 days moving average (of a previous candle)
    This example demonstrates:
      - How to call Binance REST API for candle stick data
      - How to incorporate external pricing source (Coingecko) into the strategy
      - How to listen to order filled event
      - How to structure order execution on a more complex strategy
    Before running this example, make sure you run `config rate_oracle_source coingecko`
    """
    connector_name: str = "binance_paper_trade"
    trading_pair: str = "ETH-BTC"
    base_asset, quote_asset = split_hb_trading_pair(trading_pair)
    conversion_pair: str = f"{quote_asset}-USD"
    buy_usd_amount: Decimal = Decimal("100")
    moving_avg_period: int = 50
    dip_percentage: Decimal = Decimal("0.05")
    #: A cool off period before the next buy (in seconds)
    cool_off_interval: float = 10.
    #: The last buy timestamp
    last_ordered_ts: float = 0.

    markets = {connector_name: {trading_pair}}

    @property
    def connector(self) -> ExchangeBase:
        """
        The only connector in this strategy, define it here for easy access
        """
        return self.connectors[self.connector_name]

    def on_tick(self):
        """
        Runs every tick_size seconds, this is the main operation of the strategy.
        - Create proposal (a list of order candidates)
        - Check the account balance and adjust the proposal accordingly (lower order amount if needed)
        - Lastly, execute the proposal on the exchange
        """
        proposal: List[OrderCandidate] = self.create_proposal()
        proposal = self.connector.budget_checker.adjust_candidates(proposal, all_or_none=False)
        if proposal:
            self.execute_proposal(proposal)

    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

    def execute_proposal(self, proposal: List[OrderCandidate]):
        """
        Places the order candidates on the exchange, if it is not within cool off period and order candidate is valid.
        """
        if self.last_ordered_ts > time.time() - self.cool_off_interval:
            return
        for order_candidate in proposal:
            if order_candidate.amount > Decimal("0"):
                self.buy(self.connector_name, self.trading_pair, order_candidate.amount, order_candidate.order_type,
                         order_candidate.price)
                self.last_ordered_ts = time.time()

    def did_fill_order(self, event: OrderFilledEvent):
        """
        Listens to fill order event to log it and notify the hummingbot application.
        If you set up Telegram bot, you will get notification there as well.
        """
        msg = (f"({event.trading_pair}) {event.trade_type.name} order (price: {event.price}) of {event.amount} "
               f"{split_hb_trading_pair(event.trading_pair)[0]} is filled.")
        self.log_with_clock(logging.INFO, msg)
        self.notify_hb_app_with_timestamp(msg)

    def _get_daily_close_list(self, trading_pair: str) -> List[Decimal]:
        """
        Fetches binance candle stick data and returns a list daily close
        This is the API response data structure:
        [
          [
            1499040000000,      // Open time
            "0.01634790",       // Open
            "0.80000000",       // High
            "0.01575800",       // Low
            "0.01577100",       // Close
            "148976.11427815",  // Volume
            1499644799999,      // Close time
            "2434.19055334",    // Quote asset volume
            308,                // Number of trades
            "1756.87402397",    // Taker buy base asset volume
            "28.46694368",      // Taker buy quote asset volume
            "17928899.62484339" // Ignore.
          ]
        ]

        :param trading_pair: A market trading pair to

        :return: A list of daily close
        """

        url = "https://api.binance.com/api/v3/klines"
        params = {"symbol": trading_pair.replace("-", ""),
                  "interval": "1d"}
        records = requests.get(url=url, params=params).json()
        return [Decimal(str(record[4])) for record in records]
コード例 #14
0
def is_linear_perpetual(trading_pair: str) -> bool:
    """
    Returns True if trading_pair is in USDT(Linear) Perpetual
    """
    _, quote_asset = split_hb_trading_pair(trading_pair)
    return quote_asset == "USDT"
コード例 #15
0
 def order_amount_prompt(
         cls, model_instance: 'AvellanedaMarketMakingConfigMap') -> str:
     trading_pair = model_instance.market
     base_asset, quote_asset = split_hb_trading_pair(trading_pair)
     return f"What is the amount of {base_asset} per order?"
コード例 #16
0
 def get_sell_collateral_token(self, trading_pair: str) -> str:
     _, quote = split_hb_trading_pair(trading_pair)
     return quote