async def exchange_balances_extra_df( self, # type: HummingbotApplication ex_balances: Dict[str, Decimal], ex_avai_balances: Dict[str, Decimal]): total_col_name = f"Total ({RateOracle.global_token_symbol})" allocated_total = Decimal("0") rows = [] for token, bal in ex_balances.items(): if bal == Decimal(0): continue avai = Decimal(ex_avai_balances.get( token.upper(), 0)) if ex_avai_balances is not None else Decimal(0) allocated = f"{(bal - avai) / bal:.0%}" rate = await RateOracle.global_rate(token) rate = Decimal("0") if rate is None else rate global_value = rate * bal allocated_total += rate * (bal - avai) rows.append({ "Asset": token.upper(), "Total": round(bal, 4), total_col_name: PerformanceMetrics.smart_round(global_value), "Allocated": allocated, }) df = pd.DataFrame( data=rows, columns=["Asset", "Total", total_col_name, "Allocated"]) df.sort_values(by=["Asset"], inplace=True) return df, allocated_total
async def market_trades_report( self, # type: HummingbotApplication connector, days: float, market: str): trades: List[Trade] = await connector.get_my_trades(market, days) g_sym = RateOracle.global_token_symbol if not trades: self._notify(f"There is no trade on {market}.") return data = [] amount_g_col_name = f" Amount ({g_sym})" columns = ["Time", " Side", " Price", "Amount", amount_g_col_name] trades = sorted(trades, key=lambda x: (x.trading_pair, x.timestamp)) fees = {} # a dict of token and total fee amount fee_usd = 0 for trade in trades: time = f"{datetime.fromtimestamp(trade.timestamp / 1e3).strftime('%Y-%m-%d %H:%M:%S')} " side = "buy" if trade.side is TradeType.BUY else "sell" usd = await RateOracle.global_value( trade.trading_pair.split("-")[0], trade.amount) data.append([ time, side, PerformanceMetrics.smart_round(trade.price), PerformanceMetrics.smart_round(trade.amount), round(usd) ]) for fee in trade.trade_fee.flat_fees: if fee[0] not in fees: fees[fee[0]] = fee[1] else: fees[fee[0]] += fee[1] fee_usd += await RateOracle.global_value(fee[0], fee[1]) lines = [] df: pd.DataFrame = pd.DataFrame(data=data, columns=columns) lines.extend([f" {market.upper()}"]) lines.extend( [" " + line for line in df.to_string(index=False).split("\n")]) self._notify("\n" + "\n".join(lines)) fee_text = ",".join(k + ": " + f"{v:.4f}" for k, v in fees.items()) self._notify( f"\n Total traded: {g_sym} {df[amount_g_col_name].sum():.0f} " f"Fees: {fee_text} ({g_sym} {fee_usd:.2f})")
def profitability_df(self): data = [] columns = [ "Id", f"{self.base_asset} Change", f"{self.quote_asset} Change", f"Unclaimed {self.base_asset}", f"Unclaimed {self.quote_asset}", "Total Profit/loss (%)" ] for position in self.active_positions: profit_data = self.calculate_profitability(position) data.append([ position.token_id, PerformanceMetrics.smart_round(profit_data['base_change'], 8), PerformanceMetrics.smart_round(profit_data['quote_change'], 8), PerformanceMetrics.smart_round(profit_data['base_fee'], 8), PerformanceMetrics.smart_round(profit_data['quote_fee'], 8), PerformanceMetrics.smart_round(profit_data['profitability'], 8) ]) return pd.DataFrame(data=data, columns=columns)
def quotes_rate_df(self): columns = ["Quotes pair", "Rate"] quotes_pair: str = f"{self._market_info_2.quote_asset}-{self._market_info_1.quote_asset}" data = [[ quotes_pair, PerformanceMetrics.smart_round(self._rate_source.rate(quotes_pair)) ]] return pd.DataFrame(data=data, columns=columns)
def active_positions_df(self) -> pd.DataFrame: columns = [ "Id", "Symbol", "Fee Tier", "Range", "Current Base", "Current Quote", "" ] data = [] if len(self.active_positions) > 0: for position in self.active_positions: data.append([ position.token_id, position.trading_pair, position.fee_tier, f"{PerformanceMetrics.smart_round(Decimal(str(position.lower_price)), 8)} - " f"{PerformanceMetrics.smart_round(Decimal(str(position.upper_price)), 8)}", PerformanceMetrics.smart_round( Decimal(str(position.current_base_amount)), 8), PerformanceMetrics.smart_round( Decimal(str(position.current_quote_amount)), 8), "[In range]" if self._last_price >= position.lower_price and self._last_price <= position.upper_price else "[Out of range]" ]) return pd.DataFrame(data=data, columns=columns)
def test_smart_round(self): value = PerformanceMetrics.smart_round(None) self.assertIsNone(value) value = PerformanceMetrics.smart_round(Decimal("NaN")) self.assertTrue(value.is_nan()) value = PerformanceMetrics.smart_round(Decimal("10000.123456789")) self.assertEqual(value, Decimal("10000")) value = PerformanceMetrics.smart_round(Decimal("100.123456789")) self.assertEqual(value, Decimal("100.1")) value = PerformanceMetrics.smart_round(Decimal("1.123456789")) self.assertEqual(value, Decimal("1.12")) value = PerformanceMetrics.smart_round(Decimal("0.123456789")) self.assertEqual(value, Decimal("0.1234")) value = PerformanceMetrics.smart_round(Decimal("0.000456789")) self.assertEqual(value, Decimal("0.00045")) value = PerformanceMetrics.smart_round(Decimal("0.000056789")) self.assertEqual(value, Decimal("0.00005678")) value = PerformanceMetrics.smart_round(Decimal("0")) self.assertEqual(value, Decimal("0")) value = PerformanceMetrics.smart_round(Decimal("0.123456"), 2) self.assertEqual(value, Decimal("0.12"))
def active_positions_df(self) -> pd.DataFrame: columns = [ "Symbol", "Type", "Fee Tier", "Amount", "Upper Price", "Lower Price", "" ] data = [] if len(self.active_positions) > 0: for position in self.active_positions: amount = self._base_token_amount if position in self.active_buys else self._quote_token_amount data.append([ position.trading_pair, "Buy" if position in self.active_buys else "Sell", position.fee_tier, f"{PerformanceMetrics.smart_round(Decimal(str(amount)), 8)}", PerformanceMetrics.smart_round( Decimal(str(position.upper_price)), 8), PerformanceMetrics.smart_round( Decimal(str(position.lower_price)), 8), "[In range]" if self._last_price >= position.lower_price and self._last_price <= position.upper_price else "[Out of range]" ]) return pd.DataFrame(data=data, columns=columns)
async def exchange_balances_extra_df( self, # type: HummingbotApplication exchange: str, ex_balances: Dict[str, Decimal], ex_avai_balances: Dict[str, Decimal]): conn_setting = AllConnectorSettings.get_connector_settings()[exchange] total_col_name = f"Total ({RateOracle.global_token_symbol})" allocated_total = Decimal("0") rows = [] for token, bal in ex_balances.items(): avai = Decimal(ex_avai_balances.get( token.upper(), 0)) if ex_avai_balances is not None else Decimal(0) # show zero balances if it is a gateway connector (the user manually # chose to show those values with 'gateway connector-tokens') if conn_setting.uses_gateway_generic_connector(): if bal == Decimal(0): allocated = "0%" else: allocated = f"{(bal - avai) / bal:.0%}" else: # the exchange is CEX. Only show balance if non-zero. if bal == Decimal(0): continue allocated = f"{(bal - avai) / bal:.0%}" rate = await RateOracle.global_rate(token) rate = Decimal("0") if rate is None else rate global_value = rate * bal allocated_total += rate * (bal - avai) rows.append({ "Asset": token.upper(), "Total": round(bal, 4), total_col_name: PerformanceMetrics.smart_round(global_value), "sum_not_for_show": global_value, "Allocated": allocated, }) df = pd.DataFrame(data=rows, columns=[ "Asset", "Total", total_col_name, "sum_not_for_show", "Allocated" ]) df.sort_values(by=["Asset"], inplace=True) return df, allocated_total
async def format_status(self) -> str: """ Returns a status string formatted to display nicely on terminal. The strings composes of 4 parts: market, assets, spread and warnings(if any). """ if not self._connector_ready: return "UniswapV3 connector not ready." columns = ["Exchange", "Market", "Current Price"] data = [] market, trading_pair, base_asset, quote_asset = self._market_info data.append([ market.display_name, trading_pair, PerformanceMetrics.smart_round(Decimal(str(self._last_price)), 8) ]) markets_df = pd.DataFrame(data=data, columns=columns) lines = [] lines.extend(["", " Markets:"] + [ " " + line for line in markets_df.to_string(index=False).split("\n") ]) # See if there're any active positions. if len(self.active_positions) > 0: pos_info_df = self.active_positions_df() lines.extend(["", " Positions:"] + [ " " + line for line in pos_info_df.to_string(index=False).split("\n") ]) pos_profitability_df = self.profitability_df() lines.extend(["", " Positions Performance:"] + [ " " + line for line in pos_profitability_df.to_string( index=False).split("\n") ]) else: lines.extend(["", " No active positions."]) assets_df = self.wallet_balance_data_frame([self._market_info]) lines.extend(["", " Assets:"] + [" " + line for line in str(assets_df).split("\n")]) warning_lines = self.network_warning([self._market_info]) warning_lines.extend(self.balance_warning([self._market_info])) if len(warning_lines) > 0: lines.extend(["", "*** WARNINGS ***"] + warning_lines) return "\n".join(lines)
def report_performance_by_market( self, # type: HummingbotApplication market: str, trading_pair: str, perf: PerformanceMetrics, precision: int): lines = [] base, quote = trading_pair.split("-") lines.extend([f"\n{market} / {trading_pair}"]) trades_columns = ["", "buy", "sell", "total"] trades_data = [ [ f"{'Number of trades':<27}", perf.num_buys, perf.num_sells, perf.num_trades ], [ f"{f'Total trade volume ({base})':<27}", PerformanceMetrics.smart_round(perf.b_vol_base, precision), PerformanceMetrics.smart_round(perf.s_vol_base, precision), PerformanceMetrics.smart_round(perf.tot_vol_base, precision) ], [ f"{f'Total trade volume ({quote})':<27}", PerformanceMetrics.smart_round(perf.b_vol_quote, precision), PerformanceMetrics.smart_round(perf.s_vol_quote, precision), PerformanceMetrics.smart_round(perf.tot_vol_quote, precision) ], [ f"{'Avg price':<27}", PerformanceMetrics.smart_round(perf.avg_b_price, precision), PerformanceMetrics.smart_round(perf.avg_s_price, precision), PerformanceMetrics.smart_round(perf.avg_tot_price, precision) ], ] trades_df: pd.DataFrame = pd.DataFrame(data=trades_data, columns=trades_columns) lines.extend(["", " Trades:"] + [ " " + line for line in trades_df.to_string(index=False).split("\n") ]) assets_columns = ["", "start", "current", "change"] assets_data = [ [f"{base:<17}", "-", "-", "-"] if market in DERIVATIVES else # No base asset for derivatives because they are margined [ f"{base:<17}", PerformanceMetrics.smart_round(perf.start_base_bal, precision), PerformanceMetrics.smart_round(perf.cur_base_bal, precision), PerformanceMetrics.smart_round(perf.tot_vol_base, precision) ], [ f"{quote:<17}", PerformanceMetrics.smart_round(perf.start_quote_bal, precision), PerformanceMetrics.smart_round(perf.cur_quote_bal, precision), PerformanceMetrics.smart_round(perf.tot_vol_quote, precision) ], [ f"{trading_pair + ' price':<17}", PerformanceMetrics.smart_round(perf.start_price), PerformanceMetrics.smart_round(perf.cur_price), PerformanceMetrics.smart_round(perf.cur_price - perf.start_price) ], [f"{'Base asset %':<17}", "-", "-", "-"] if market in DERIVATIVES else # No base asset for derivatives because they are margined [ f"{'Base asset %':<17}", f"{perf.start_base_ratio_pct:.2%}", f"{perf.cur_base_ratio_pct:.2%}", f"{perf.cur_base_ratio_pct - perf.start_base_ratio_pct:.2%}" ], ] assets_df: pd.DataFrame = pd.DataFrame(data=assets_data, columns=assets_columns) lines.extend(["", " Assets:"] + [ " " + line for line in assets_df.to_string(index=False).split("\n") ]) perf_data = [ [ "Hold portfolio value ", f"{PerformanceMetrics.smart_round(perf.hold_value, precision)} {quote}" ], [ "Current portfolio value ", f"{PerformanceMetrics.smart_round(perf.cur_value, precision)} {quote}" ], [ "Trade P&L ", f"{PerformanceMetrics.smart_round(perf.trade_pnl, precision)} {quote}" ] ] perf_data.extend([ "Fees paid ", f"{PerformanceMetrics.smart_round(fee_amount, precision)} {fee_token}" ] for fee_token, fee_amount in perf.fees.items()) perf_data.extend([[ "Total P&L ", f"{PerformanceMetrics.smart_round(perf.total_pnl, precision)} {quote}" ], ["Return % ", f"{perf.return_pct:.2%}"]]) perf_df: pd.DataFrame = pd.DataFrame(data=perf_data) lines.extend(["", " Performance:"] + [ " " + line for line in perf_df.to_string( index=False, header=False).split("\n") ]) self._notify("\n".join(lines))
async def format_status(self) -> str: """ Returns a status string formatted to display nicely on terminal. The strings composes of 4 parts: markets, assets, profitability and warnings(if any). """ if self._all_arb_proposals is None: return " The strategy is not ready, please try again later." columns = ["Exchange", "Market", "Sell Price", "Buy Price", "Mid Price"] data = [] for market_info in [self._market_info_1, self._market_info_2]: market, trading_pair, base_asset, quote_asset = market_info buy_price = await market.get_quote_price(trading_pair, True, self._order_amount) sell_price = await market.get_quote_price(trading_pair, False, self._order_amount) # check for unavailable price data buy_price = PerformanceMetrics.smart_round(Decimal(str(buy_price)), 8) if buy_price is not None else '-' sell_price = PerformanceMetrics.smart_round(Decimal(str(sell_price)), 8) if sell_price is not None else '-' mid_price = PerformanceMetrics.smart_round(((buy_price + sell_price) / 2), 8) if '-' not in [buy_price, sell_price] else '-' data.append([ market.display_name, trading_pair, sell_price, buy_price, mid_price ]) markets_df = pd.DataFrame(data=data, columns=columns) lines = [] lines.extend(["", " Markets:"] + [" " + line for line in markets_df.to_string(index=False).split("\n")]) columns = ["Exchange", "Gas Fees"] data = [] for market_info in [self._market_info_1, self._market_info_2]: if hasattr(market_info.market, "network_transaction_fee"): transaction_fee: TokenAmount = getattr(market_info.market, "network_transaction_fee") data.append([market_info.market.display_name, f"{transaction_fee.amount} {transaction_fee.token}"]) network_fees_df = pd.DataFrame(data=data, columns=columns) if len(data) > 0: lines.extend( ["", " Network Fees:"] + [" " + line for line in network_fees_df.to_string(index=False).split("\n")] ) assets_df = self.wallet_balance_data_frame([self._market_info_1, self._market_info_2]) lines.extend(["", " Assets:"] + [" " + line for line in str(assets_df).split("\n")]) lines.extend(["", " Profitability:"] + self.short_proposal_msg(self._all_arb_proposals)) quotes_rates_df = self.quotes_rate_df() lines.extend(["", f" Quotes Rates ({str(self._rate_source)})"] + [" " + line for line in str(quotes_rates_df).split("\n")]) warning_lines = self.network_warning([self._market_info_1]) warning_lines.extend(self.network_warning([self._market_info_2])) warning_lines.extend(self.balance_warning([self._market_info_1])) warning_lines.extend(self.balance_warning([self._market_info_2])) if len(warning_lines) > 0: lines.extend(["", "*** WARNINGS ***"] + warning_lines) return "\n".join(lines)