Skip to content

Commit

Permalink
Merge pull request #63 from hummingbot/feat/improve-status
Browse files Browse the repository at this point in the history
  • Loading branch information
cardosofede authored Aug 29, 2023
2 parents 478c407 + 8373422 commit 56e5d9c
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 17 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,6 @@ quants_lab/optimizations/*
quants_lab/strategy/experiments/*

# Master bot template user-added configs
hummingbot_files/templates/master_bot_conf/conf/*
hummingbot_files/templates/master_bot_conf/conf/*

**/.DS_Store
8 changes: 4 additions & 4 deletions pages/bot_orchestration/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
from ui_components.launch_broker_card import LaunchBrokerCard
from utils.st_utils import initialize_st_page

CARD_WIDTH = 6
CARD_HEIGHT = 3
NUM_CARD_COLS = 2

initialize_st_page(title="Instances", icon="🦅", initial_sidebar_state="collapsed")

if "is_broker_running" not in st.session_state:
Expand Down Expand Up @@ -85,10 +89,6 @@ def update_containers_info(docker_manager):


docker_manager = DockerManager()
CARD_WIDTH = 6
CARD_HEIGHT = 3
NUM_CARD_COLS = 2

if not docker_manager.is_docker_running():
st.warning("Docker is not running. Please start Docker and refresh the page.")
st.stop()
Expand Down
114 changes: 102 additions & 12 deletions ui_components/bot_performance_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,24 @@
import streamlit as st
import time
from utils.os_utils import get_python_files_from_directory, get_yml_files_from_directory
from utils.status_parser import StatusParser
import pandas as pd
import datetime

TRADES_TO_SHOW = 5
WIDE_COL_WIDTH = 180
MEDIUM_COL_WIDTH = 150

def time_ago(ts):
now_utc = datetime.datetime.now(datetime.timezone.utc)
seconds_since_epoch_utc = now_utc.timestamp()
delta = round(seconds_since_epoch_utc - ts / 1000)
if delta < 60:
return f"{delta}s ago"
if delta < 3600:
return f"{delta // 60}m ago"
else:
return f"{delta // 3600}h ago"

class BotPerformanceCard(Dashboard.Item):

Expand Down Expand Up @@ -32,10 +49,7 @@ def __call__(self, bot_config: dict):
scripts = [file.split("/")[-1] for file in get_python_files_from_directory(scripts_directory)]
strategies = [file.split("/")[-1] for file in get_yml_files_from_directory(strategies_directory)]
if bot_config["selected_strategy"] is None:
if len(scripts):
st.session_state.active_bots[bot_name]["selected_strategy"] = scripts[0]
elif len(strategies):
st.session_state.active_bots[bot_name]["selected_strategy"] = strategies[0]
st.session_state.active_bots[bot_name]["selected_strategy"] = ""

with mui.Card(key=self._key,
sx={"display": "flex", "flexDirection": "column", "borderRadius": 2, "overflow": "auto"},
Expand All @@ -52,14 +66,90 @@ def __call__(self, bot_config: dict):
)
if bot_config["is_running"]:
with mui.CardContent(sx={"flex": 1}):
with mui.Paper(elevation=2, sx={"padding": 2, "marginBottom": 2}):
mui.Typography("Status")
mui.Typography(bot_config["status"], sx={"fontSize": "0.75rem"})
with mui.Accordion(sx={"padding": 2, "marginBottom": 2}):
with mui.AccordionSummary(expandIcon="▼"):
mui.Typography("Trades" + "(" + str(len(bot_config["trades"])) + ")")
with mui.AccordionDetails():
mui.Typography(str(bot_config["trades"]), sx={"fontSize": "0.75rem"})
# Balances Table
mui.Typography("Balances", variant="h6")

# # Convert list of dictionaries to DataFrame
balances = StatusParser(bot_config["status"], type="balances").parse()
if balances != "No balances":
df_balances = pd.DataFrame(balances)
balances_rows = df_balances.to_dict(orient='records')
balances_cols = [{'field': col, 'headerName': col} for col in df_balances.columns]

for column in balances_cols:
# Customize width for 'exchange' column
if column['field'] == 'Exchange':
column['width'] = WIDE_COL_WIDTH
mui.DataGrid(rows=balances_rows,
columns=balances_cols,
autoHeight=True,
density="compact",
disableColumnSelector=True,
hideFooter=True,
initialState={"columns": {"columnVisibilityModel": {"id": False}}})
else:
mui.Typography(str(balances), sx={"fontSize": "0.75rem"})

# Active Orders Table
mui.Typography("Active Orders", variant="h6", sx={"marginTop": 2})

# Convert list of dictionaries to DataFrame
orders = StatusParser(bot_config["status"], type="orders").parse()
if orders != "No active maker orders" or "No matching string":
df_orders = pd.DataFrame(orders)
orders_rows = df_orders.to_dict(orient='records')
orders_cols = [{'field': col, 'headerName': col} for col in df_orders.columns]

for column in orders_cols:
# Customize width for 'exchange' column
if column['field'] == 'Exchange':
column['width'] = WIDE_COL_WIDTH
# Customize width for column
if column['field'] == 'Price':
column['width'] = MEDIUM_COL_WIDTH

mui.DataGrid(rows=orders_rows,
columns=orders_cols,
autoHeight=True,
density="compact",
disableColumnSelector=True,
hideFooter=True,
initialState={"columns": {"columnVisibilityModel": {"id": False}}})
else:
mui.Typography(str(orders), sx={"fontSize": "0.75rem"})

# Trades Table
mui.Typography("Recent Trades", variant="h6", sx={"marginTop": 2})
df_trades = pd.DataFrame(bot_config["trades"])

# Add 'id' column to the dataframe by concatenating 'trade_id' and 'trade_timestamp'
df_trades['id'] = df_trades.get('trade_id', '0').astype(str) + df_trades['trade_timestamp'].astype(str)

# Convert timestamp col to datetime
df_trades['trade_timestamp'] = df_trades['trade_timestamp'].astype(int)

# Show last X trades
df_trades = df_trades.sort_values(by='trade_timestamp', ascending=False)
df_trades = df_trades.head(TRADES_TO_SHOW)
df_trades['time_ago'] = df_trades['trade_timestamp'].apply(time_ago)

trades_rows = df_trades.to_dict(orient='records')
trades_cols = [{'field': col, 'headerName': col} for col in df_trades.columns]

for column in trades_cols:
# Customize width for 'market' column
if column['field'] == 'market':
column['width'] = WIDE_COL_WIDTH
if column['field'] == 'trade_timestamp':
column['width'] = MEDIUM_COL_WIDTH

mui.DataGrid(rows=trades_rows,
columns=trades_cols,
autoHeight=True,
density="compact",
disableColumnSelector=True,
hideFooter=True,
initialState={"columns": {"columnVisibilityModel": {"id": False, "trade_id": False, "trade_timestamp": False, "base_asset": False, "quote_asset": False, "raw_json": False}}})
else:
with mui.CardContent(sx={"flex": 1}):
with mui.Grid(container=True, spacing=2):
Expand Down
109 changes: 109 additions & 0 deletions utils/status_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
class StatusParser:
def __init__(self, input_str, type='orders'):
self.lines = input_str.split("\n")

if type == 'orders':
if "No active maker orders" in input_str:
self.parser = self
elif all(keyword in input_str for keyword in ['Orders:','Exchange', 'Market', 'Side', 'Price', 'Amount', 'Age']):
self.parser = OrdersParser(self.lines, ['Exchange', 'Market', 'Side', 'Price', 'Amount', 'Age'])
elif all(keyword in input_str for keyword in ['Orders:','Level', 'Amount (Orig)', 'Amount (Adj)']):
self.parser = OrdersParser(self.lines, ['Level', 'Type', 'Price', 'Spread', 'Amount (Orig)', 'Amount (Adj)', 'Age'])
else:
raise ValueError("No matching string for type 'order'")
elif type == 'balances':
self.parser = BalancesParser(self.lines)
# if all(keyword in input_str for keyword in ['Balances:']):
else:
raise ValueError(f"Unsupported type: {type}")

def parse(self):
return self.parser._parse()

def _parse(self):
if "No active maker orders" in self.lines:
return "No active maker orders"
raise NotImplementedError

class OrdersParser:
def __init__(self, lines, columns):
self.lines = lines
self.columns = columns

def _parse(self):
if "No active maker orders" in "\n".join(self.lines):
return "No active maker orders"

orders = []
for i, line in enumerate(self.lines):
if "Orders:" in line:
start_idx = i + 1
break

lines = self.lines[start_idx + 1:]
for line in lines:

# Ignore warning lines
# if line.startswith("***"):
# break

# Break when there's a blank line
if not line.strip():
break

parts = line.split()
if len(parts) < len(self.columns):
continue

# Create the orders dictionary based on provided columns
order = {}
for idx, col in enumerate(self.columns):
order[col] = parts[idx]

# Special handling for 'id' column (concatenating several parts)
if 'id' not in order:
order['id'] = ''.join(parts[:len(self.columns)-1])

orders.append(order)

return orders

class BalancesParser:
def __init__(self, lines):
self.lines = lines
self.columns = ['Exchange', 'Asset', 'Total Balance', 'Available Balance']

def _parse(self):
# Check if "Balances:" exists in the lines
if not any("Balances:" in line for line in self.lines):
return "No balances"

balances = []
for i, line in enumerate(self.lines):
if "Balances:" in line:
start_idx = i + 1
break

lines = self.lines[start_idx + 1:]
for line in lines:

# Break when there's a blank line
if not line.strip():
break

parts = line.split()
if len(parts) < len(self.columns):
continue

# Create the balances dictionary based on provided columns
balance = {}
for idx, col in enumerate(self.columns):
balance[col] = parts[idx]

# Special handling for 'id' column (concatenating several parts)
if 'id' not in balance:
balance['id'] = ''.join(parts[:len(self.columns)-1])

balances.append(balance)

return balances

0 comments on commit 56e5d9c

Please sign in to comment.