def test_aggregated_position_with_two_related_trades_from_three(self): trades = [] trades.append(self.mock_trade(id="order1", amount=100, price=10)) trades.append(self.mock_trade(id="order2", amount=200, price=15)) trades.append(self.mock_trade(id="order1", amount=300, price=20)) aggregated_buys, aggregated_sells = PerformanceMetrics.aggregate_position_order( trades, []) self.assertEqual(len(aggregated_buys), 2) trade = aggregated_buys[0] self.assertTrue(trade.order_id == "order1" and trade.amount == 400 and trade.price == 15) self.assertEqual(aggregated_buys[1], trades[1]) self.assertEqual(len(aggregated_sells), 0) trades = [] trades.append(self.mock_trade(id="order1", amount=100, price=10)) trades.append(self.mock_trade(id="order2", amount=200, price=15)) trades.append(self.mock_trade(id="order1", amount=300, price=20)) aggregated_buys, aggregated_sells = PerformanceMetrics.aggregate_position_order( [], trades) self.assertEqual(len(aggregated_buys), 0) self.assertEqual(len(aggregated_sells), 2) trade = aggregated_sells[0] self.assertTrue(trade.order_id == "order1" and trade.amount == 400 and trade.price == 15) self.assertEqual(aggregated_sells[1], trades[1])
def test_calculate_fees_in_quote_for_one_trade_with_fees_different_tokens( self): rate_oracle = RateOracle() rate_oracle._prices["DAI-COINALPHA"] = Decimal("2") rate_oracle._prices["USDT-DAI"] = Decimal("0.9") RateOracle._shared_instance = rate_oracle performance_metric = PerformanceMetrics() flat_fees = [ TokenAmount(token="USDT", amount=Decimal("10")), TokenAmount(token="DAI", amount=Decimal("5")), ] trade = Trade(trading_pair="HBOT-COINALPHA", side=TradeType.BUY, price=Decimal("1000"), amount=Decimal("1"), order_type=OrderType.LIMIT, market="binance", timestamp=1640001112.223, trade_fee=AddedToCostTradeFee(percent=Decimal("0.1"), percent_token="COINALPHA", flat_fees=flat_fees)) self.async_run_with_timeout( performance_metric._calculate_fees(quote="COINALPHA", trades=[trade])) expected_fee_amount = trade.amount * trade.price * trade.trade_fee.percent expected_fee_amount += flat_fees[0].amount * Decimal("0.9") * Decimal( "2") expected_fee_amount += flat_fees[1].amount * Decimal("2") self.assertEqual(expected_fee_amount, performance_metric.fee_in_quote)
def test_position_order_returns_nothing_when_no_open_and_no_close_orders( self): trade_for_open = [ self.mock_trade(id=f"order{i}", amount=100, price=10, position="INVALID") for i in range(3) ] trades_for_close = [ self.mock_trade(id=f"order{i}", amount=100, price=10, position="INVALID") for i in range(2) ] self.assertIsNone( PerformanceMetrics.position_order(trade_for_open, trades_for_close)) trade_for_open[1].position = "OPEN" self.assertIsNone( PerformanceMetrics.position_order(trade_for_open, trades_for_close)) trade_for_open[1].position = "INVALID" trades_for_close[-1].position = "CLOSE" self.assertIsNone( PerformanceMetrics.position_order(trade_for_open, trades_for_close))
def test_aggregated_position_for_unrelated_trades(self): trades = [] trades.append(self.mock_trade(id="order1", amount=100, price=10)) trades.append(self.mock_trade(id="order2", amount=200, price=15)) trades.append(self.mock_trade(id="order3", amount=300, price=20)) aggregated_buys, aggregated_sells = PerformanceMetrics.aggregate_position_order(trades, []) self.assertEqual(aggregated_buys, trades) self.assertEqual(len(aggregated_sells), 0) aggregated_buys, aggregated_sells = PerformanceMetrics.aggregate_position_order([], trades) self.assertEqual(len(aggregated_buys), 0) self.assertEqual(aggregated_sells, trades)
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 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 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 test_performance_metrics(self): trades: List[Trade] = [ Trade(trading_pair, TradeType.BUY, 100, 10, None, trading_pair, 1, TradeFee(0.0, [(quote, 0)])), Trade(trading_pair, TradeType.SELL, 120, 15, None, trading_pair, 1, TradeFee(0.0, [(quote, 0)])) ] cur_bals = {base: 100, quote: 10000} metrics = asyncio.get_event_loop().run_until_complete( PerformanceMetrics.create("hbot_exchange", trading_pair, trades, cur_bals)) self.assertEqual(Decimal("200"), metrics.trade_pnl) print(metrics)
def test_calculate_fees_in_quote_for_one_trade_fill_with_fees_different_tokens( self): rate_oracle = RateOracle() rate_oracle._prices["DAI-COINALPHA"] = Decimal("2") rate_oracle._prices["USDT-DAI"] = Decimal("0.9") RateOracle._shared_instance = rate_oracle performance_metric = PerformanceMetrics() flat_fees = [ TokenAmount(token="USDT", amount=Decimal("10")), TokenAmount(token="DAI", amount=Decimal("5")), ] trade = TradeFill( config_file_path="some-strategy.yml", strategy="pure_market_making", market="binance", symbol="HBOT-COINALPHA", base_asset="HBOT", quote_asset="COINALPHA", timestamp=int(time.time()), order_id="someId0", trade_type="BUY", order_type="LIMIT", price=1000, amount=1, trade_fee=AddedToCostTradeFee(percent=Decimal("0.1"), percent_token="COINALPHA", flat_fees=flat_fees).to_json(), exchange_trade_id="someExchangeId0", position=PositionAction.NIL.value, ) self.async_run_with_timeout( performance_metric._calculate_fees(quote="COINALPHA", trades=[trade])) expected_fee_amount = Decimal(str(trade.amount)) * Decimal( str(trade.price)) * Decimal("0.1") expected_fee_amount += flat_fees[0].amount * Decimal("0.9") * Decimal( "2") expected_fee_amount += flat_fees[1].amount * Decimal("2") self.assertEqual(expected_fee_amount, performance_metric.fee_in_quote)
def test_position_order_returns_open_and_close_pair(self): trades_for_open = [self.mock_trade(id=f"order{i}", amount=100, price=10, position="INVALID") for i in range(3)] trades_for_close = [self.mock_trade(id=f"order{i}", amount=100, price=10, position="INVALID") for i in range(2)] trades_for_open[1].position = "OPEN" trades_for_close[-1].position = "CLOSE" selected_open, selected_close = PerformanceMetrics.position_order(trades_for_open.copy(), trades_for_close.copy()) self.assertEqual(selected_open, trades_for_open[1]) self.assertEqual(selected_close, trades_for_close[-1])
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_performance_metrics_for_derivatives(self, is_trade_fill_mock): is_trade_fill_mock.return_value = True trades = [] trades.append(self.mock_trade(id="order1", amount=100, price=10, position="OPEN", type="BUY", fee=TradeFee(0.0, [(quote, 0)]))) trades.append(self.mock_trade(id="order2", amount=100, price=15, position="CLOSE", type="SELL", fee=TradeFee(0.0, [(quote, 0)]))) trades.append(self.mock_trade(id="order3", amount=100, price=20, position="OPEN", type="SELL", fee=TradeFee(0.1, [("USD", 0)]))) trades.append(self.mock_trade(id="order4", amount=100, price=15, position="CLOSE", type="BUY", fee=TradeFee(0.1, [("USD", 0)]))) cur_bals = {base: 100, quote: 10000} metrics = asyncio.get_event_loop().run_until_complete( PerformanceMetrics.create("hbot_exchange", trading_pair, trades, cur_bals)) self.assertEqual(metrics.num_buys, 2) self.assertEqual(metrics.num_sells, 2) self.assertEqual(metrics.num_trades, 4) self.assertEqual(metrics.b_vol_base, Decimal("200")) self.assertEqual(metrics.s_vol_base, Decimal("-200")) self.assertEqual(metrics.tot_vol_base, Decimal("0")) self.assertEqual(metrics.b_vol_quote, Decimal("-2500")) self.assertEqual(metrics.s_vol_quote, Decimal("3500")) self.assertEqual(metrics.tot_vol_quote, Decimal("1000")) self.assertEqual(metrics.avg_b_price, Decimal("12.5")) self.assertEqual(metrics.avg_s_price, Decimal("17.5")) self.assertEqual(metrics.avg_tot_price, Decimal("15")) self.assertEqual(metrics.start_base_bal, Decimal("100")) self.assertEqual(metrics.start_quote_bal, Decimal("9000")) self.assertEqual(metrics.cur_base_bal, 100) self.assertEqual(metrics.cur_quote_bal, 10000), self.assertEqual(metrics.start_price, Decimal("10")), self.assertEqual(metrics.cur_price, Decimal("15")) self.assertEqual(metrics.trade_pnl, Decimal("1000")) self.assertEqual(metrics.total_pnl, Decimal("650"))
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
def test_performance_metrics(self): rate_oracle = RateOracle() rate_oracle._prices["USDT-HBOT"] = Decimal("5") RateOracle._shared_instance = rate_oracle trade_fee = AddedToCostTradeFee( flat_fees=[TokenAmount(quote, Decimal("0"))]) trades = [ TradeFill( config_file_path="some-strategy.yml", strategy="pure_market_making", market="binance", symbol=trading_pair, base_asset=base, quote_asset=quote, timestamp=int(time.time()), order_id="someId0", trade_type="BUY", order_type="LIMIT", price=100, amount=10, trade_fee=trade_fee.to_json(), exchange_trade_id="someExchangeId0", position=PositionAction.NIL.value, ), TradeFill( config_file_path="some-strategy.yml", strategy="pure_market_making", market="binance", symbol=trading_pair, base_asset=base, quote_asset=quote, timestamp=int(time.time()), order_id="someId1", trade_type="SELL", order_type="LIMIT", price=120, amount=15, trade_fee=trade_fee.to_json(), exchange_trade_id="someExchangeId1", position=PositionAction.NIL.value, ) ] cur_bals = {base: 100, quote: 10000} metrics = asyncio.get_event_loop().run_until_complete( PerformanceMetrics.create(trading_pair, trades, cur_bals)) self.assertEqual(Decimal("799"), metrics.trade_pnl) print(metrics)
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 test_performance_metrics_for_derivatives(self, is_trade_fill_mock): rate_oracle = RateOracle() rate_oracle._prices["USDT-HBOT"] = Decimal("5") RateOracle._shared_instance = rate_oracle is_trade_fill_mock.return_value = True trades = [] trades.append( self.mock_trade(id="order1", amount=Decimal("100"), price=Decimal("10"), position="OPEN", type="BUY", fee=AddedToCostTradeFee( flat_fees=[TokenAmount(quote, Decimal("0"))]))) trades.append( self.mock_trade(id="order2", amount=Decimal("100"), price=Decimal("15"), position="CLOSE", type="SELL", fee=AddedToCostTradeFee( flat_fees=[TokenAmount(quote, Decimal("0"))]))) trades.append( self.mock_trade(id="order3", amount=Decimal("100"), price=Decimal("20"), position="OPEN", type="SELL", fee=AddedToCostTradeFee( Decimal("0.1"), flat_fees=[TokenAmount("USD", Decimal("0"))]))) trades.append( self.mock_trade(id="order4", amount=Decimal("100"), price=Decimal("15"), position="CLOSE", type="BUY", fee=AddedToCostTradeFee( Decimal("0.1"), flat_fees=[TokenAmount("USD", Decimal("0"))]))) cur_bals = {base: 100, quote: 10000} metrics = asyncio.get_event_loop().run_until_complete( PerformanceMetrics.create(trading_pair, trades, cur_bals)) self.assertEqual(metrics.num_buys, 2) self.assertEqual(metrics.num_sells, 2) self.assertEqual(metrics.num_trades, 4) self.assertEqual(metrics.b_vol_base, Decimal("200")) self.assertEqual(metrics.s_vol_base, Decimal("-200")) self.assertEqual(metrics.tot_vol_base, Decimal("0")) self.assertEqual(metrics.b_vol_quote, Decimal("-2500")) self.assertEqual(metrics.s_vol_quote, Decimal("3500")) self.assertEqual(metrics.tot_vol_quote, Decimal("1000")) self.assertEqual(metrics.avg_b_price, Decimal("12.5")) self.assertEqual(metrics.avg_s_price, Decimal("17.5")) self.assertEqual(metrics.avg_tot_price, Decimal("15")) self.assertEqual(metrics.start_base_bal, Decimal("100")) self.assertEqual(metrics.start_quote_bal, Decimal("9000")) self.assertEqual(metrics.cur_base_bal, 100) self.assertEqual(metrics.cur_quote_bal, 10000), self.assertEqual(metrics.start_price, Decimal("10")), self.assertEqual(metrics.cur_price, Decimal("0.2")) self.assertEqual(metrics.trade_pnl, Decimal("1000")) self.assertEqual(metrics.total_pnl, Decimal("650"))
def test_aggregated_position_with_no_trades(self): aggregated_buys, aggregated_sells = PerformanceMetrics.aggregate_position_order( [], []) self.assertEqual(len(aggregated_buys), 0) self.assertEqual(len(aggregated_sells), 0)
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)