From 5b51071aa668056e2bb8abe929ab22b6c0136d52 Mon Sep 17 00:00:00 2001 From: Michael Feng Date: Tue, 22 Aug 2023 12:20:47 -0700 Subject: [PATCH 01/11] (feat) improve status - WIP --- ui_components/bot_performance_card.py | 37 ++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/ui_components/bot_performance_card.py b/ui_components/bot_performance_card.py index 6b936021..92535936 100644 --- a/ui_components/bot_performance_card.py +++ b/ui_components/bot_performance_card.py @@ -4,7 +4,41 @@ import streamlit as st import time from utils.os_utils import get_python_files_from_directory, get_yml_files_from_directory +import pandas as pd +def parse_orders(input_str): + # Split the string by lines + lines = input_str.split("\n") + + # Identify the line where the 'Orders:' section starts + for i, line in enumerate(lines): + if "Orders:" in line: + start_idx = i + 1 + break + + # Extract relevant lines after "Orders:" + order_lines = lines[start_idx:] + + # Parse each order line into a list of dictionaries + orders = [] + for order_line in order_lines: + parts = order_line.split() + + # Construct order dictionary from the split parts + order = { + "Exchange": parts[0], + "Market": parts[1], + "Side": parts[2], + "Price": parts[3], + "Amount": parts[4], + "Age": parts[5] + } + orders.append(order) + + # Convert list of dictionaries to DataFrame + df_orders = pd.DataFrame(orders) + + return df_orders class BotPerformanceCard(Dashboard.Item): @@ -54,7 +88,8 @@ def __call__(self, bot_config: dict): 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"}) + msg = parse_orders(bot_config["status"]) + mui.Typography(msg, sx={"fontSize": "0.75rem"}) with mui.Accordion(sx={"padding": 2, "marginBottom": 2}): with mui.AccordionSummary(expandIcon="▼"): mui.Typography("Trades" + "(" + str(len(bot_config["trades"])) + ")") From 58a01ed2c9e52ea79ca429108d71b6d81333731f Mon Sep 17 00:00:00 2001 From: Michael Feng Date: Tue, 22 Aug 2023 14:35:07 -0700 Subject: [PATCH 02/11] data grid WIP --- ui_components/bot_performance_card.py | 90 +++++++++++++++++++++------ 1 file changed, 70 insertions(+), 20 deletions(-) diff --git a/ui_components/bot_performance_card.py b/ui_components/bot_performance_card.py index 92535936..a444dea3 100644 --- a/ui_components/bot_performance_card.py +++ b/ui_components/bot_performance_card.py @@ -4,12 +4,17 @@ import streamlit as st import time from utils.os_utils import get_python_files_from_directory, get_yml_files_from_directory + import pandas as pd def parse_orders(input_str): + # Check for "No active maker orders" in the string + if "No active maker orders" in input_str: + return "No active maker orders" + # Split the string by lines lines = input_str.split("\n") - + # Identify the line where the 'Orders:' section starts for i, line in enumerate(lines): if "Orders:" in line: @@ -17,28 +22,57 @@ def parse_orders(input_str): break # Extract relevant lines after "Orders:" - order_lines = lines[start_idx:] + order_lines = lines[start_idx:] + + # Determine the table type based on the header and extract order lines + table_type = None + for i, line in enumerate(order_lines): + if all(keyword in line for keyword in ['Exchange', 'Market', 'Side']): + table_type = 'simple_pmm' + start_idx = i + 1 + order_lines_parsed = order_lines[start_idx:] + break + elif all(keyword in line for keyword in ['Level', 'Amount (Orig)']): + table_type = 'pmm' + start_idx = i + 1 + order_lines_parsed = order_lines[start_idx:] + break # Parse each order line into a list of dictionaries orders = [] - for order_line in order_lines: + for order_line in order_lines_parsed: parts = order_line.split() - # Construct order dictionary from the split parts - order = { - "Exchange": parts[0], - "Market": parts[1], - "Side": parts[2], - "Price": parts[3], - "Amount": parts[4], - "Age": parts[5] - } - orders.append(order) - - # Convert list of dictionaries to DataFrame - df_orders = pd.DataFrame(orders) - - return df_orders + # Check table type and extract data accordingly + if table_type == 'simple_pmm': + if len(parts) < 5: + continue + order = { + "id": parts[2] + parts[3] + parts[4] + (parts[5] if len(parts) > 5 else ""), + "Exchange": parts[0], + "Market": parts[1], + "Side": parts[2], + "Price": parts[3], + "Amount": parts[4], + "Age": " ".join(parts[5:]) if len(parts) > 5 else None + } + orders.append(order) + elif table_type == 'pmm': + if len(parts) < 6: + continue + order = { + "id": parts[0] + parts[1] + parts[2] + parts[3] + parts[4] + (parts[6] if len(parts) > 6 else ""), + "Level": parts[0], + "Type": parts[1], + "Price": parts[2], + "Spread": parts[3], + "Amount (Adj)": parts[4], + "Amount (Orig)": parts[5], + "Age": " ".join(parts[6:]) if len(parts) > 6 else None + } + orders.append(order) + + return orders class BotPerformanceCard(Dashboard.Item): @@ -88,8 +122,24 @@ def __call__(self, bot_config: dict): with mui.CardContent(sx={"flex": 1}): with mui.Paper(elevation=2, sx={"padding": 2, "marginBottom": 2}): mui.Typography("Status") - msg = parse_orders(bot_config["status"]) - mui.Typography(msg, sx={"fontSize": "0.75rem"}) + orders = parse_orders(bot_config["status"]) + # mui.Typography(str(orders), sx={"fontSize": "0.75rem"}) + # mui.Divider() + + # Convert list of dictionaries to DataFrame + if orders != "No active maker orders": + df_orders = pd.DataFrame(orders) + rows = df_orders.to_dict(orient='records') + columns = [{'field': col, 'headerName': col} for col in df_orders.columns] + # rows, columns = df_orders.shape + mui.Typography(str(rows), sx={"fontSize": "0.75rem"}) + mui.Divider() + mui.Typography(str(columns), sx={"fontSize": "0.75rem"}) + mui.Divider() + mui.DataGrid(rows=rows, columns=columns) + else: + mui.Typography(str(orders), sx={"fontSize": "0.75rem"}) + with mui.Accordion(sx={"padding": 2, "marginBottom": 2}): with mui.AccordionSummary(expandIcon="▼"): mui.Typography("Trades" + "(" + str(len(bot_config["trades"])) + ")") From b942404ed423ade7199accbc5e5801a77839b221 Mon Sep 17 00:00:00 2001 From: Michael Feng Date: Tue, 22 Aug 2023 15:53:27 -0700 Subject: [PATCH 03/11] set initial state as expanded --- main.py | 2 +- ui_components/bot_performance_card.py | 2 +- utils/st_utils.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 98b2e6fc..40ce5a46 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,7 @@ from utils.st_utils import initialize_st_page -initialize_st_page(title="Hummingbot Dashboard", icon="📊") +initialize_st_page(title="Hummingbot Dashboard", icon="📊", initial_sidebar_state="expanded") show_pages( [ diff --git a/ui_components/bot_performance_card.py b/ui_components/bot_performance_card.py index a444dea3..2d5e2549 100644 --- a/ui_components/bot_performance_card.py +++ b/ui_components/bot_performance_card.py @@ -136,7 +136,7 @@ def __call__(self, bot_config: dict): mui.Divider() mui.Typography(str(columns), sx={"fontSize": "0.75rem"}) mui.Divider() - mui.DataGrid(rows=rows, columns=columns) + mui.DataGrid(rows=rows, columns=columns, sx={"height": "300px"}) else: mui.Typography(str(orders), sx={"fontSize": "0.75rem"}) diff --git a/utils/st_utils.py b/utils/st_utils.py index 5875109c..189f07b8 100644 --- a/utils/st_utils.py +++ b/utils/st_utils.py @@ -6,7 +6,7 @@ from st_pages import add_page_title -def initialize_st_page(title: str, icon: str, layout="wide", initial_sidebar_state="auto"): +def initialize_st_page(title: str, icon: str, layout="wide", initial_sidebar_state="collapsed"): st.set_page_config( page_title=title, page_icon=icon, @@ -15,7 +15,7 @@ def initialize_st_page(title: str, icon: str, layout="wide", initial_sidebar_sta ) caller_frame = inspect.currentframe().f_back - add_page_title(layout="wide") + add_page_title(layout=layout, initial_sidebar_state=initial_sidebar_state) current_directory = Path(os.path.dirname(inspect.getframeinfo(caller_frame).filename)) readme_path = current_directory / "README.md" From e7f1753012152442a6e320623265b23d24bbcf02 Mon Sep 17 00:00:00 2001 From: Michael Feng Date: Tue, 22 Aug 2023 18:42:42 -0700 Subject: [PATCH 04/11] add order parser --- .DS_Store | Bin 0 -> 6148 bytes pages/bot_orchestration/app.py | 4 +- ui_components/bot_performance_card.py | 114 +++++++------------------- utils/status_parser copy.py | 79 ++++++++++++++++++ utils/status_parser.py | 55 +++++++++++++ 5 files changed, 164 insertions(+), 88 deletions(-) create mode 100644 .DS_Store create mode 100644 utils/status_parser copy.py create mode 100644 utils/status_parser.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..26c154e2727ff8c63d16b48d252a6395506fc4d3 GIT binary patch literal 6148 zcmeHKI|>3Z5S{S@f{mqRuHX%V=n3`$7J>+(;IH1wb9pr1e41sk(?WRzlb1~9CFB)5 zJ0haX+jb!`6OjqrP#!k)&GyZEHpqwq;W*=RZ_dZV>A36Vz6%(4EH}BzUJf0;?a-(I z6`%rCfC^B7Pb-iWb~63+!90%&P=TLUz`hR!ZdeoBK>u`L@D>0#Lf8#+?GNs z1|kB}paO%c*+Nm*NakF#1^;2XH*JmF@TI|YN6W1yE~EUX;QJt^{v&9Pq- U+d!uy?sOo3222+k75KISF9lB&>Hq)$ literal 0 HcmV?d00001 diff --git a/pages/bot_orchestration/app.py b/pages/bot_orchestration/app.py index b378f560..6483e6ab 100644 --- a/pages/bot_orchestration/app.py +++ b/pages/bot_orchestration/app.py @@ -100,9 +100,9 @@ def update_containers_info(docker_manager): docker_manager = DockerManager() -CARD_WIDTH = 6 +CARD_WIDTH = 12 CARD_HEIGHT = 3 -NUM_CARD_COLS = 2 +NUM_CARD_COLS = 1 if not docker_manager.is_docker_running(): st.warning("Docker is not running. Please start Docker and refresh the page.") diff --git a/ui_components/bot_performance_card.py b/ui_components/bot_performance_card.py index 2d5e2549..c437134a 100644 --- a/ui_components/bot_performance_card.py +++ b/ui_components/bot_performance_card.py @@ -4,76 +4,9 @@ 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 -def parse_orders(input_str): - # Check for "No active maker orders" in the string - if "No active maker orders" in input_str: - return "No active maker orders" - - # Split the string by lines - lines = input_str.split("\n") - - # Identify the line where the 'Orders:' section starts - for i, line in enumerate(lines): - if "Orders:" in line: - start_idx = i + 1 - break - - # Extract relevant lines after "Orders:" - order_lines = lines[start_idx:] - - # Determine the table type based on the header and extract order lines - table_type = None - for i, line in enumerate(order_lines): - if all(keyword in line for keyword in ['Exchange', 'Market', 'Side']): - table_type = 'simple_pmm' - start_idx = i + 1 - order_lines_parsed = order_lines[start_idx:] - break - elif all(keyword in line for keyword in ['Level', 'Amount (Orig)']): - table_type = 'pmm' - start_idx = i + 1 - order_lines_parsed = order_lines[start_idx:] - break - - # Parse each order line into a list of dictionaries - orders = [] - for order_line in order_lines_parsed: - parts = order_line.split() - - # Check table type and extract data accordingly - if table_type == 'simple_pmm': - if len(parts) < 5: - continue - order = { - "id": parts[2] + parts[3] + parts[4] + (parts[5] if len(parts) > 5 else ""), - "Exchange": parts[0], - "Market": parts[1], - "Side": parts[2], - "Price": parts[3], - "Amount": parts[4], - "Age": " ".join(parts[5:]) if len(parts) > 5 else None - } - orders.append(order) - elif table_type == 'pmm': - if len(parts) < 6: - continue - order = { - "id": parts[0] + parts[1] + parts[2] + parts[3] + parts[4] + (parts[6] if len(parts) > 6 else ""), - "Level": parts[0], - "Type": parts[1], - "Price": parts[2], - "Spread": parts[3], - "Amount (Adj)": parts[4], - "Amount (Orig)": parts[5], - "Age": " ".join(parts[6:]) if len(parts) > 6 else None - } - orders.append(order) - - return orders - class BotPerformanceCard(Dashboard.Item): def __init__(self, board, x, y, w, h, **item_props): @@ -120,25 +53,34 @@ 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") - orders = parse_orders(bot_config["status"]) - # mui.Typography(str(orders), sx={"fontSize": "0.75rem"}) - # mui.Divider() + # with mui.Paper(elevation=2, sx={"padding": 2, "marginBottom": 2}): + mui.Typography("Orders") + parser = StatusParser(bot_config["status"]) + orders = parser.parse() - # Convert list of dictionaries to DataFrame - if orders != "No active maker orders": - df_orders = pd.DataFrame(orders) - rows = df_orders.to_dict(orient='records') - columns = [{'field': col, 'headerName': col} for col in df_orders.columns] - # rows, columns = df_orders.shape - mui.Typography(str(rows), sx={"fontSize": "0.75rem"}) - mui.Divider() - mui.Typography(str(columns), sx={"fontSize": "0.75rem"}) - mui.Divider() - mui.DataGrid(rows=rows, columns=columns, sx={"height": "300px"}) - else: - mui.Typography(str(orders), sx={"fontSize": "0.75rem"}) + # Convert list of dictionaries to DataFrame + if orders != "No active maker orders" or "No matching string": + df_orders = pd.DataFrame(orders) + rows = df_orders.to_dict(orient='records') + columns = [{'field': col, 'headerName': col} for col in df_orders.columns] + for column in columns: + # Hide the 'id' column + if column['field'] == 'id': + column['width'] = 0 + # Expand the 'exchange' column + if column['field'] == 'Exchange': + column['width'] = 200 + # Expand the 'price' column + if column['field'] == 'Price': + column['width'] = 150 + mui.DataGrid(rows=rows, + columns=columns, + autoHeight=True, + density="compact", + hideFooter=True, + initialState={"columns": {"columnVisibilityModel": {"id": False}}}) + else: + mui.Typography(str(orders), sx={"fontSize": "0.75rem"}) with mui.Accordion(sx={"padding": 2, "marginBottom": 2}): with mui.AccordionSummary(expandIcon="▼"): diff --git a/utils/status_parser copy.py b/utils/status_parser copy.py new file mode 100644 index 00000000..a9bd7c37 --- /dev/null +++ b/utils/status_parser copy.py @@ -0,0 +1,79 @@ +class StatusParser: + def __init__(self, input_str): + self.lines = input_str.split("\n") + # Check for the type of parser needed + if "No active maker orders" in input_str: + self.parser = self + elif all(keyword in input_str for keyword in ['Exchange', 'Market', 'Side']): + self.parser = SimplePMMScriptParser(self.lines) + elif all(keyword in input_str for keyword in ['Level', 'Amount (Orig)']): + self.parser = PMMStrategyParser(self.lines) + else: + raise ValueError("No matching string") + + 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 SimplePMMScriptParser: + def __init__(self, lines): + self.lines = lines + + 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: + parts = line.split() + if len(parts) < 5: + continue + order = { + "id": parts[2] + parts[3] + parts[4] + (parts[5] if len(parts) > 5 else ""), + "Exchange": parts[0], + "Market": parts[1], + "Side": parts[2], + "Price": parts[3], + "Amount": parts[4], + "Age": " ".join(parts[5:]) if len(parts) > 5 else None + } + orders.append(order) + return orders + +class PMMStrategyParser: + def __init__(self, lines): + self.lines = lines + + 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: + parts = line.split() + if len(parts) < 6: + continue + order = { + "id": parts[0] + parts[1] + parts[2] + parts[3] + parts[4] + (parts[6] if len(parts) > 6 else ""), + "Level": parts[0], + "Type": parts[1], + "Price": parts[2], + "Spread": parts[3], + "Amount (Adj)": parts[4], + "Amount (Orig)": parts[5], + "Age": " ".join(parts[6:]) if len(parts) > 6 else None + } + orders.append(order) + return orders diff --git a/utils/status_parser.py b/utils/status_parser.py new file mode 100644 index 00000000..5337ff6b --- /dev/null +++ b/utils/status_parser.py @@ -0,0 +1,55 @@ +class StatusParser: + def __init__(self, input_str): + self.lines = input_str.split("\n") + + # Check for the type of parser needed + if "No active maker orders" in input_str: + self.parser = self + elif all(keyword in input_str for keyword in ['Exchange', 'Market', 'Side']): + self.parser = OrderParser(self.lines, ['Exchange', 'Market', 'Side', 'Price', 'Amount', 'Age']) + elif all(keyword in input_str for keyword in ['Level', 'Amount (Orig)']): + self.parser = OrderParser(self.lines, ['Level', 'Type', 'Price', 'Spread', 'Amount (Adj)', 'Amount (Orig)', 'Age']) + else: + raise ValueError("No matching string") + + 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 OrderParser: + 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: + parts = line.split() + if len(parts) < len(self.columns): + continue + + # Create the order 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 From a0d5065aa620e365b76d2a20a48f9a157d7bd08c Mon Sep 17 00:00:00 2001 From: Michael Feng Date: Tue, 22 Aug 2023 22:40:38 -0700 Subject: [PATCH 05/11] added balances and trades table --- ui_components/bot_performance_card.py | 82 ++++++++++++++++++++++----- utils/status_parser.py | 76 +++++++++++++++++++++---- 2 files changed, 133 insertions(+), 25 deletions(-) diff --git a/ui_components/bot_performance_card.py b/ui_components/bot_performance_card.py index c437134a..f3205ca6 100644 --- a/ui_components/bot_performance_card.py +++ b/ui_components/bot_performance_card.py @@ -53,17 +53,47 @@ 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("Orders") - parser = StatusParser(bot_config["status"]) - orders = parser.parse() + + # 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: + # Hide the 'id' column + if column['field'] == 'id': + column['width'] = 0 + else: + column['width'] = 200 + + 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"}) + + mui.Divider(sx={"margin": 4}) + + # Active Orders Table + mui.Typography("Active Orders", variant="h6") # 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) - rows = df_orders.to_dict(orient='records') - columns = [{'field': col, 'headerName': col} for col in df_orders.columns] - for column in columns: + 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: # Hide the 'id' column if column['field'] == 'id': column['width'] = 0 @@ -73,20 +103,44 @@ def __call__(self, bot_config: dict): # Expand the 'price' column if column['field'] == 'Price': column['width'] = 150 - mui.DataGrid(rows=rows, - columns=columns, + + 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"}) - 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"}) + mui.Divider(sx={"margin": 4}) + + # Trades Table + mui.Typography("Trades", variant="h6") + 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['trade_id'].astype(str) + df_trades['trade_timestamp'].astype(str) + + 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: + # Hide the 'id' and 'raw_json' columns + if column['field'] == 'id': + column['width'] = 0 + # Expand the 'exchange' column + # if column['field'] == 'Exchange': + # column['width'] = 200 + + mui.DataGrid(rows=trades_rows, + columns=trades_cols, + autoHeight=True, + density="compact", + disableColumnSelector=True, + hideFooter=True, + initialState={"columns": {"columnVisibilityModel": {"id": False}}}) else: with mui.CardContent(sx={"flex": 1}): with mui.Grid(container=True, spacing=2): diff --git a/utils/status_parser.py b/utils/status_parser.py index 5337ff6b..3558b908 100644 --- a/utils/status_parser.py +++ b/utils/status_parser.py @@ -1,16 +1,21 @@ class StatusParser: - def __init__(self, input_str): + def __init__(self, input_str, type='orders'): self.lines = input_str.split("\n") - # Check for the type of parser needed - if "No active maker orders" in input_str: - self.parser = self - elif all(keyword in input_str for keyword in ['Exchange', 'Market', 'Side']): - self.parser = OrderParser(self.lines, ['Exchange', 'Market', 'Side', 'Price', 'Amount', 'Age']) - elif all(keyword in input_str for keyword in ['Level', 'Amount (Orig)']): - self.parser = OrderParser(self.lines, ['Level', 'Type', 'Price', 'Spread', 'Amount (Adj)', 'Amount (Orig)', 'Age']) + 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("No matching string") + raise ValueError(f"Unsupported type: {type}") def parse(self): return self.parser._parse() @@ -20,7 +25,7 @@ def _parse(self): return "No active maker orders" raise NotImplementedError -class OrderParser: +class OrdersParser: def __init__(self, lines, columns): self.lines = lines self.columns = columns @@ -37,11 +42,20 @@ def _parse(self): 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 order dictionary based on provided columns + # Create the orders dictionary based on provided columns order = {} for idx, col in enumerate(self.columns): order[col] = parts[idx] @@ -53,3 +67,43 @@ def _parse(self): 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 \ No newline at end of file From 79abb261ad5e6f42cbff7b241b386922804341d0 Mon Sep 17 00:00:00 2001 From: Michael Feng Date: Tue, 22 Aug 2023 22:42:58 -0700 Subject: [PATCH 06/11] reverted to 2 cards per page --- pages/bot_orchestration/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/bot_orchestration/app.py b/pages/bot_orchestration/app.py index 6483e6ab..48a7dc70 100644 --- a/pages/bot_orchestration/app.py +++ b/pages/bot_orchestration/app.py @@ -100,9 +100,9 @@ def update_containers_info(docker_manager): docker_manager = DockerManager() -CARD_WIDTH = 12 -CARD_HEIGHT = 3 -NUM_CARD_COLS = 1 +CARD_WIDTH = 6 +CARD_HEIGHT = 6 +NUM_CARD_COLS = 2 if not docker_manager.is_docker_running(): st.warning("Docker is not running. Please start Docker and refresh the page.") From 3fbb60a130caad0a540df8cd8e2222338864cf11 Mon Sep 17 00:00:00 2001 From: Michael Feng Date: Wed, 23 Aug 2023 15:03:15 -0700 Subject: [PATCH 07/11] (feat) recent trades --- ui_components/bot_performance_card.py | 37 ++++++++++++++------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/ui_components/bot_performance_card.py b/ui_components/bot_performance_card.py index 311d4e7d..f8ccf411 100644 --- a/ui_components/bot_performance_card.py +++ b/ui_components/bot_performance_card.py @@ -7,6 +7,8 @@ from utils.status_parser import StatusParser import pandas as pd +TRADES_TO_SHOW = 5 + class BotPerformanceCard(Dashboard.Item): def __init__(self, board, x, y, w, h, **item_props): @@ -65,10 +67,8 @@ def __call__(self, bot_config: dict): balances_cols = [{'field': col, 'headerName': col} for col in df_balances.columns] for column in balances_cols: - # Hide the 'id' column - if column['field'] == 'id': - column['width'] = 0 - else: + # Customize width for 'exchange' column + if column['field'] == 'Exchange': column['width'] = 200 mui.DataGrid(rows=balances_rows, @@ -94,13 +94,10 @@ def __call__(self, bot_config: dict): orders_cols = [{'field': col, 'headerName': col} for col in df_orders.columns] for column in orders_cols: - # Hide the 'id' column - if column['field'] == 'id': - column['width'] = 0 - # Expand the 'exchange' column + # Customize width for 'exchange' column if column['field'] == 'Exchange': column['width'] = 200 - # Expand the 'price' column + # Customize width for column if column['field'] == 'Price': column['width'] = 150 @@ -117,22 +114,26 @@ def __call__(self, bot_config: dict): mui.Divider(sx={"margin": 4}) # Trades Table - mui.Typography("Trades", variant="h6") + mui.Typography("Recent Trades", variant="h6") 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['trade_id'].astype(str) + df_trades['trade_timestamp'].astype(str) + df_trades['id'] = df_trades.get('trade_id', '0').astype(str) + df_trades['trade_timestamp'].astype(str) + + # Show recent trades only + df_trades['trade_timestamp'] = df_trades['trade_timestamp'].astype(int) + df_trades = df_trades.sort_values(by='trade_timestamp', ascending=False) + df_trades = df_trades.head(TRADES_TO_SHOW) 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: - # Hide the 'id' and 'raw_json' columns - if column['field'] == 'id': - column['width'] = 0 - # Expand the 'exchange' column - # if column['field'] == 'Exchange': - # column['width'] = 200 + # Customize width for 'market' column + if column['field'] == 'market': + column['width'] = 200 + if column['field'] == 'trade_timestamp': + column['width'] = 150 mui.DataGrid(rows=trades_rows, columns=trades_cols, @@ -140,7 +141,7 @@ def __call__(self, bot_config: dict): density="compact", disableColumnSelector=True, hideFooter=True, - initialState={"columns": {"columnVisibilityModel": {"id": False}}}) + initialState={"columns": {"columnVisibilityModel": {"id": False, "trade_id": False, "base_asset": False, "quote_asset": False, "raw_json": False}}}) else: with mui.CardContent(sx={"flex": 1}): with mui.Grid(container=True, spacing=2): From 370e6d99da63441675361d7cab8bd66a116d4aee Mon Sep 17 00:00:00 2001 From: Michael Feng Date: Wed, 23 Aug 2023 16:08:54 -0700 Subject: [PATCH 08/11] feat/improve-status --- pages/bot_orchestration/app.py | 8 ++--- ui_components/bot_performance_card.py | 51 +++++++++++++++++++-------- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/pages/bot_orchestration/app.py b/pages/bot_orchestration/app.py index a099a854..99198c81 100644 --- a/pages/bot_orchestration/app.py +++ b/pages/bot_orchestration/app.py @@ -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: @@ -85,10 +89,6 @@ def update_containers_info(docker_manager): docker_manager = DockerManager() -CARD_WIDTH = 6 -CARD_HEIGHT = 6 -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() diff --git a/ui_components/bot_performance_card.py b/ui_components/bot_performance_card.py index f8ccf411..ddfd5a95 100644 --- a/ui_components/bot_performance_card.py +++ b/ui_components/bot_performance_card.py @@ -6,8 +6,33 @@ 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 + + +# Define a function to format the time difference +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" + +def convert_to_datetime(val): + try: + # Try treating it as a UNIX timestamp (seconds since the epoch) + timestamp = int(val) + return pd.to_datetime(timestamp, unit='s') + except ValueError: + # If that fails, try treating it as a string representation of a datetime + return pd.to_datetime(val) class BotPerformanceCard(Dashboard.Item): @@ -69,8 +94,7 @@ def __call__(self, bot_config: dict): for column in balances_cols: # Customize width for 'exchange' column if column['field'] == 'Exchange': - column['width'] = 200 - + column['width'] = WIDE_COL_WIDTH mui.DataGrid(rows=balances_rows, columns=balances_cols, autoHeight=True, @@ -80,11 +104,9 @@ def __call__(self, bot_config: dict): initialState={"columns": {"columnVisibilityModel": {"id": False}}}) else: mui.Typography(str(balances), sx={"fontSize": "0.75rem"}) - - mui.Divider(sx={"margin": 4}) # Active Orders Table - mui.Typography("Active Orders", variant="h6") + mui.Typography("Active Orders", variant="h6", sx={"marginTop": 2}) # Convert list of dictionaries to DataFrame orders = StatusParser(bot_config["status"], type="orders").parse() @@ -96,10 +118,10 @@ def __call__(self, bot_config: dict): for column in orders_cols: # Customize width for 'exchange' column if column['field'] == 'Exchange': - column['width'] = 200 + column['width'] = WIDE_COL_WIDTH # Customize width for column if column['field'] == 'Price': - column['width'] = 150 + column['width'] = MEDIUM_COL_WIDTH mui.DataGrid(rows=orders_rows, columns=orders_cols, @@ -111,19 +133,20 @@ def __call__(self, bot_config: dict): else: mui.Typography(str(orders), sx={"fontSize": "0.75rem"}) - mui.Divider(sx={"margin": 4}) - # Trades Table - mui.Typography("Recent Trades", variant="h6") + 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) - # Show recent trades only + # 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] @@ -131,9 +154,9 @@ def __call__(self, bot_config: dict): for column in trades_cols: # Customize width for 'market' column if column['field'] == 'market': - column['width'] = 200 + column['width'] = WIDE_COL_WIDTH if column['field'] == 'trade_timestamp': - column['width'] = 150 + column['width'] = MEDIUM_COL_WIDTH mui.DataGrid(rows=trades_rows, columns=trades_cols, @@ -141,7 +164,7 @@ def __call__(self, bot_config: dict): density="compact", disableColumnSelector=True, hideFooter=True, - initialState={"columns": {"columnVisibilityModel": {"id": False, "trade_id": False, "base_asset": False, "quote_asset": False, "raw_json": False}}}) + 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): From 56bd0d1e991a6f0a9b8262dd15c5eec603b6f896 Mon Sep 17 00:00:00 2001 From: Michael Feng Date: Wed, 23 Aug 2023 16:11:31 -0700 Subject: [PATCH 09/11] remove used fn --- ui_components/bot_performance_card.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ui_components/bot_performance_card.py b/ui_components/bot_performance_card.py index ddfd5a95..378b6eb3 100644 --- a/ui_components/bot_performance_card.py +++ b/ui_components/bot_performance_card.py @@ -12,8 +12,6 @@ WIDE_COL_WIDTH = 180 MEDIUM_COL_WIDTH = 150 - -# Define a function to format the time difference def time_ago(ts): now_utc = datetime.datetime.now(datetime.timezone.utc) seconds_since_epoch_utc = now_utc.timestamp() @@ -25,15 +23,6 @@ def time_ago(ts): else: return f"{delta // 3600}h ago" -def convert_to_datetime(val): - try: - # Try treating it as a UNIX timestamp (seconds since the epoch) - timestamp = int(val) - return pd.to_datetime(timestamp, unit='s') - except ValueError: - # If that fails, try treating it as a string representation of a datetime - return pd.to_datetime(val) - class BotPerformanceCard(Dashboard.Item): def __init__(self, board, x, y, w, h, **item_props): From dd78305aa498f53fb8c3556d85a241c78e81e064 Mon Sep 17 00:00:00 2001 From: Michael Feng Date: Wed, 23 Aug 2023 16:43:15 -0700 Subject: [PATCH 10/11] (fix) remove incorrect name --- ui_components/bot_performance_card.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ui_components/bot_performance_card.py b/ui_components/bot_performance_card.py index 378b6eb3..2818d833 100644 --- a/ui_components/bot_performance_card.py +++ b/ui_components/bot_performance_card.py @@ -49,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"}, @@ -69,7 +66,6 @@ def __call__(self, bot_config: dict): ) if bot_config["is_running"]: with mui.CardContent(sx={"flex": 1}): - # Balances Table mui.Typography("Balances", variant="h6") From 83734225b1e293b1446a2379861463d7c8c042be Mon Sep 17 00:00:00 2001 From: Michael Feng Date: Thu, 24 Aug 2023 10:13:19 -0700 Subject: [PATCH 11/11] fixes --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 4 +- utils/status_parser copy.py | 79 ------------------------------------ 3 files changed, 3 insertions(+), 80 deletions(-) delete mode 100644 .DS_Store delete mode 100644 utils/status_parser copy.py diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 26c154e2727ff8c63d16b48d252a6395506fc4d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKI|>3Z5S{S@f{mqRuHX%V=n3`$7J>+(;IH1wb9pr1e41sk(?WRzlb1~9CFB)5 zJ0haX+jb!`6OjqrP#!k)&GyZEHpqwq;W*=RZ_dZV>A36Vz6%(4EH}BzUJf0;?a-(I z6`%rCfC^B7Pb-iWb~63+!90%&P=TLUz`hR!ZdeoBK>u`L@D>0#Lf8#+?GNs z1|kB}paO%c*+Nm*NakF#1^;2XH*JmF@TI|YN6W1yE~EUX;QJt^{v&9Pq- U+d!uy?sOo3222+k75KISF9lB&>Hq)$ diff --git a/.gitignore b/.gitignore index 9d7dcf48..55a9e47c 100644 --- a/.gitignore +++ b/.gitignore @@ -137,4 +137,6 @@ quants_lab/optimizations/* quants_lab/strategy/experiments/* # Master bot template user-added configs -hummingbot_files/templates/master_bot_conf/conf/* \ No newline at end of file +hummingbot_files/templates/master_bot_conf/conf/* + +**/.DS_Store \ No newline at end of file diff --git a/utils/status_parser copy.py b/utils/status_parser copy.py deleted file mode 100644 index a9bd7c37..00000000 --- a/utils/status_parser copy.py +++ /dev/null @@ -1,79 +0,0 @@ -class StatusParser: - def __init__(self, input_str): - self.lines = input_str.split("\n") - # Check for the type of parser needed - if "No active maker orders" in input_str: - self.parser = self - elif all(keyword in input_str for keyword in ['Exchange', 'Market', 'Side']): - self.parser = SimplePMMScriptParser(self.lines) - elif all(keyword in input_str for keyword in ['Level', 'Amount (Orig)']): - self.parser = PMMStrategyParser(self.lines) - else: - raise ValueError("No matching string") - - 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 SimplePMMScriptParser: - def __init__(self, lines): - self.lines = lines - - 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: - parts = line.split() - if len(parts) < 5: - continue - order = { - "id": parts[2] + parts[3] + parts[4] + (parts[5] if len(parts) > 5 else ""), - "Exchange": parts[0], - "Market": parts[1], - "Side": parts[2], - "Price": parts[3], - "Amount": parts[4], - "Age": " ".join(parts[5:]) if len(parts) > 5 else None - } - orders.append(order) - return orders - -class PMMStrategyParser: - def __init__(self, lines): - self.lines = lines - - 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: - parts = line.split() - if len(parts) < 6: - continue - order = { - "id": parts[0] + parts[1] + parts[2] + parts[3] + parts[4] + (parts[6] if len(parts) > 6 else ""), - "Level": parts[0], - "Type": parts[1], - "Price": parts[2], - "Spread": parts[3], - "Amount (Adj)": parts[4], - "Amount (Orig)": parts[5], - "Age": " ".join(parts[6:]) if len(parts) > 6 else None - } - orders.append(order) - return orders