diff --git a/pages/orchestration/__init__.py b/pages/orchestration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pages/orchestration/credentials/README.md b/pages/orchestration/credentials/README.md new file mode 100644 index 0000000..18f4d94 --- /dev/null +++ b/pages/orchestration/credentials/README.md @@ -0,0 +1,19 @@ +### Description + +This page helps you deploy and manage Hummingbot instances: + +- Starting and stopping Hummingbot Broker +- Creating, starting and stopping bot instances +- Managing strategy and script files that instances run +- Fetching status of running instances + +### Maintainers + +This page is maintained by Hummingbot Foundation as a template other pages: + +* [cardosfede](https://github.com/cardosfede) +* [fengtality](https://github.com/fengtality) + +### Wiki + +See the [wiki](https://github.com/hummingbot/dashboard/wiki/%F0%9F%90%99-Bot-Orchestration) for more information. \ No newline at end of file diff --git a/pages/orchestration/credentials/__init__.py b/pages/orchestration/credentials/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pages/orchestration/credentials/app.py b/pages/orchestration/credentials/app.py new file mode 100644 index 0000000..666f054 --- /dev/null +++ b/pages/orchestration/credentials/app.py @@ -0,0 +1,106 @@ +from CONFIG import BACKEND_API_HOST, BACKEND_API_PORT +from backend.services.backend_api_client import BackendAPIClient +from frontend.st_utils import initialize_st_page, get_backend_api_client +import streamlit as st + + +initialize_st_page(title="Credentials", icon="🔑") + +# Page content +client = get_backend_api_client() +NUM_COLUMNS = 4 + + +@st.cache_data +def get_all_connectors_config_map(): + return client.get_all_connectors_config_map() + +# Section to display available accounts and credentials +accounts = client.get_accounts() +all_connector_config_map = get_all_connectors_config_map() +st.header("Available Accounts and Credentials") + +if accounts: + n_accounts = len(accounts) + accounts.remove("master_account") + accounts.insert(0, "master_account") + for i in range(0, n_accounts, NUM_COLUMNS): + cols = st.columns(NUM_COLUMNS) + for j, account in enumerate(accounts[i:i + NUM_COLUMNS]): + with cols[j]: + st.subheader(f"🏦 {account}") + credentials = client.get_credentials(account) + st.json(credentials) +else: + st.write("No accounts available.") + +st.markdown("---") + +c1, c2, c3 = st.columns([1, 1, 1]) +with c1: + # Section to create a new account + st.header("Create a New Account") + new_account_name = st.text_input("New Account Name") + if st.button("Create Account"): + new_account_name = new_account_name.replace(" ", "_") + if new_account_name: + if new_account_name in accounts: + st.warning(f"Account {new_account_name} already exists.") + st.stop() + elif new_account_name == "" or all(char == "_" for char in new_account_name): + st.warning("Please enter a valid account name.") + st.stop() + response = client.add_account(new_account_name) + st.write(response) + else: + st.write("Please enter an account name.") + +with c2: + # Section to delete an existing account + st.header("Delete an Account") + delete_account_name = st.selectbox("Select Account to Delete", options=accounts if accounts else ["No accounts available"], ) + if st.button("Delete Account"): + if delete_account_name and delete_account_name != "No accounts available": + response = client.delete_account(delete_account_name) + st.warning(response) + else: + st.write("Please select a valid account.") + +with c3: + # Section to delete a credential from an existing account + st.header("Delete Credential") + delete_account_cred_name = st.selectbox("Select the credentials account", options=accounts if accounts else ["No accounts available"],) + creds_for_account = [credential.split(".")[0] for credential in client.get_credentials(delete_account_cred_name)] + delete_cred_name = st.selectbox("Select a Credential to Delete", options=creds_for_account if creds_for_account else ["No credentials available"]) + if st.button("Delete Credential"): + if (delete_account_cred_name and delete_account_cred_name != "No accounts available") and (delete_cred_name and delete_cred_name != "No credentials available"): + response = client.delete_credential(delete_account_cred_name, delete_cred_name) + st.warning(response) + else: + st.write("Please select a valid account.") + +st.markdown("---") + +# Section to add credentials +st.header("Add Credentials") +c1, c2 = st.columns([1, 1]) +with c1: + account_name = st.selectbox("Select Account", options=accounts if accounts else ["No accounts available"]) +with c2: + all_connectors = list(all_connector_config_map.keys()) + binance_perpetual_index = all_connectors.index("binance_perpetual") if "binance_perpetual" in all_connectors else None + connector_name = st.selectbox("Select Connector", options=all_connectors, index=binance_perpetual_index) + config_map = all_connector_config_map[connector_name] + +st.write(f"Configuration Map for {connector_name}:") +config_inputs = {} +cols = st.columns(NUM_COLUMNS) +for i, config in enumerate(config_map): + with cols[i % (NUM_COLUMNS - 1)]: + config_inputs[config] = st.text_input(config, type="password", key=f"{connector_name}_{config}") + +with cols[-1]: + if st.button("Submit Credentials"): + response = client.add_connector_keys(account_name, connector_name, config_inputs) + if response: + st.success(response) \ No newline at end of file diff --git a/pages/orchestration/file_manager/README.md b/pages/orchestration/file_manager/README.md new file mode 100644 index 0000000..d82ed9e --- /dev/null +++ b/pages/orchestration/file_manager/README.md @@ -0,0 +1,13 @@ +### Description + +This page helps you manage and edit script files to run with Hummingbot instances: + +- Selecting files +- Editing and saving files + +### Maintainers + +This page is maintained by Hummingbot Foundation: + +* [cardosfede](https://github.com/cardosfede) +* [fengtality](https://github.com/fengtality) diff --git a/pages/orchestration/file_manager/__init__.py b/pages/orchestration/file_manager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pages/orchestration/file_manager/app.py b/pages/orchestration/file_manager/app.py new file mode 100644 index 0000000..8f20839 --- /dev/null +++ b/pages/orchestration/file_manager/app.py @@ -0,0 +1,39 @@ +from types import SimpleNamespace +import streamlit as st +from streamlit_elements import elements, mui + +from frontend.components.bots_file_explorer import BotsFileExplorer +from frontend.components.dashboard import Dashboard +from frontend.components.editor import Editor +from frontend.st_utils import initialize_st_page + +initialize_st_page(title="Strategy Configs", icon="🗂️") + + +if "fe_board" not in st.session_state: + board = Dashboard() + fe_board = SimpleNamespace( + dashboard=board, + file_explorer=BotsFileExplorer(board, 0, 0, 3, 7), + editor=Editor(board, 4, 0, 9, 7), + ) + st.session_state.fe_board = fe_board + +else: + fe_board = st.session_state.fe_board + +# Add new tabs +for tab_name, content in fe_board.file_explorer.tabs.items(): + if tab_name not in fe_board.editor.tabs: + fe_board.editor.add_tab(tab_name, content["content"], content["language"]) + +# Remove deleted tabs +for tab_name in list(fe_board.editor.tabs.keys()): + if tab_name not in fe_board.file_explorer.tabs: + fe_board.editor.remove_tab(tab_name) + +with elements("file_manager"): + with mui.Paper(elevation=3, style={"padding": "2rem"}, spacing=[2, 2], container=True): + with fe_board.dashboard(): + fe_board.file_explorer() + fe_board.editor() diff --git a/pages/orchestration/instances/README.md b/pages/orchestration/instances/README.md new file mode 100644 index 0000000..18f4d94 --- /dev/null +++ b/pages/orchestration/instances/README.md @@ -0,0 +1,19 @@ +### Description + +This page helps you deploy and manage Hummingbot instances: + +- Starting and stopping Hummingbot Broker +- Creating, starting and stopping bot instances +- Managing strategy and script files that instances run +- Fetching status of running instances + +### Maintainers + +This page is maintained by Hummingbot Foundation as a template other pages: + +* [cardosfede](https://github.com/cardosfede) +* [fengtality](https://github.com/fengtality) + +### Wiki + +See the [wiki](https://github.com/hummingbot/dashboard/wiki/%F0%9F%90%99-Bot-Orchestration) for more information. \ No newline at end of file diff --git a/pages/orchestration/instances/__init__.py b/pages/orchestration/instances/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pages/orchestration/instances/app.py b/pages/orchestration/instances/app.py new file mode 100644 index 0000000..d506e95 --- /dev/null +++ b/pages/orchestration/instances/app.py @@ -0,0 +1,73 @@ +import time + +import streamlit as st +from streamlit_elements import elements, mui +from types import SimpleNamespace + +from frontend.components.bot_performance_card import BotPerformanceCardV2 +from frontend.components.dashboard import Dashboard +from frontend.st_utils import initialize_st_page, get_backend_api_client + +# Constants for UI layout +CARD_WIDTH = 12 +CARD_HEIGHT = 4 +NUM_CARD_COLS = 1 + +def get_grid_positions(n_cards: int, cols: int = NUM_CARD_COLS, card_width: int = CARD_WIDTH, card_height: int = CARD_HEIGHT): + rows = n_cards // cols + 1 + x_y = [(x * card_width, y * card_height) for x in range(cols) for y in range(rows)] + return sorted(x_y, key=lambda x: (x[1], x[0])) + + +def update_active_bots(api_client): + active_bots_response = api_client.get_active_bots_status() + if active_bots_response.get("status") == "success": + current_active_bots = active_bots_response.get("data") + stored_bots = {card[1]: card for card in st.session_state.active_instances_board.bot_cards} + + new_bots = set(current_active_bots.keys()) - set(stored_bots.keys()) + removed_bots = set(stored_bots.keys()) - set(current_active_bots.keys()) + for bot in removed_bots: + st.session_state.active_instances_board.bot_cards = [card for card in st.session_state.active_instances_board.bot_cards if card[1] != bot] + positions = get_grid_positions(len(current_active_bots), NUM_CARD_COLS, CARD_WIDTH, CARD_HEIGHT) + for bot, (x, y) in zip(new_bots, positions[:len(new_bots)]): + card = BotPerformanceCardV2(st.session_state.active_instances_board.dashboard, x, y, CARD_WIDTH, CARD_HEIGHT) + st.session_state.active_instances_board.bot_cards.append((card, bot)) + + +initialize_st_page(title="Instances", icon="🦅") +api_client = get_backend_api_client() + +if not api_client.is_docker_running(): + st.warning("Docker is not running. Please start Docker and refresh the page.") + st.stop() + +if "active_instances_board" not in st.session_state: + active_bots_response = api_client.get_active_bots_status() + bot_cards = [] + board = Dashboard() + st.session_state.active_instances_board = SimpleNamespace( + dashboard=board, + bot_cards=bot_cards, + ) + active_bots = active_bots_response.get("data") + number_of_bots = len(active_bots) + if number_of_bots > 0: + positions = get_grid_positions(number_of_bots, NUM_CARD_COLS, CARD_WIDTH, CARD_HEIGHT) + for (bot, bot_info), (x, y) in zip(active_bots.items(), positions): + bot_status = api_client.get_bot_status(bot) + card = BotPerformanceCardV2(board, x, y, CARD_WIDTH, CARD_HEIGHT) + st.session_state.active_instances_board.bot_cards.append((card, bot)) +else: + update_active_bots(api_client) + +with elements("active_instances_board"): + with mui.Paper(sx={"padding": "2rem"}, variant="outlined"): + mui.Typography("🏠 Local Instances", variant="h5") + for card, bot in st.session_state.active_instances_board.bot_cards: + with st.session_state.active_instances_board.dashboard(): + card(bot) + +while True: + time.sleep(10) + st.rerun() \ No newline at end of file diff --git a/pages/orchestration/launch_bot_v2/README.md b/pages/orchestration/launch_bot_v2/README.md new file mode 100644 index 0000000..18f4d94 --- /dev/null +++ b/pages/orchestration/launch_bot_v2/README.md @@ -0,0 +1,19 @@ +### Description + +This page helps you deploy and manage Hummingbot instances: + +- Starting and stopping Hummingbot Broker +- Creating, starting and stopping bot instances +- Managing strategy and script files that instances run +- Fetching status of running instances + +### Maintainers + +This page is maintained by Hummingbot Foundation as a template other pages: + +* [cardosfede](https://github.com/cardosfede) +* [fengtality](https://github.com/fengtality) + +### Wiki + +See the [wiki](https://github.com/hummingbot/dashboard/wiki/%F0%9F%90%99-Bot-Orchestration) for more information. \ No newline at end of file diff --git a/pages/orchestration/launch_bot_v2/__init__.py b/pages/orchestration/launch_bot_v2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pages/orchestration/launch_bot_v2/app.py b/pages/orchestration/launch_bot_v2/app.py new file mode 100644 index 0000000..bf1e15b --- /dev/null +++ b/pages/orchestration/launch_bot_v2/app.py @@ -0,0 +1,31 @@ +from types import SimpleNamespace + +import streamlit as st +from streamlit_elements import elements, mui + +from frontend.components.dashboard import Dashboard +from frontend.components.launch_strategy_v2 import LaunchStrategyV2 +from frontend.st_utils import initialize_st_page + +CARD_WIDTH = 6 +CARD_HEIGHT = 3 +NUM_CARD_COLS = 2 + +initialize_st_page(title="Launch Bot", icon="🙌") + +if "launch_bots_board" not in st.session_state: + board = Dashboard() + launch_bots_board = SimpleNamespace( + dashboard=board, + launch_bot=LaunchStrategyV2(board, 0, 0, 12, 10), + ) + st.session_state.launch_bots_board = launch_bots_board + +else: + launch_bots_board = st.session_state.launch_bots_board + + +with elements("create_bot"): + with mui.Paper(elevation=3, style={"padding": "2rem"}, spacing=[2, 2], container=True): + with launch_bots_board.dashboard(): + launch_bots_board.launch_bot() diff --git a/pages/orchestration/launch_bot_v2_st/README.md b/pages/orchestration/launch_bot_v2_st/README.md new file mode 100644 index 0000000..18f4d94 --- /dev/null +++ b/pages/orchestration/launch_bot_v2_st/README.md @@ -0,0 +1,19 @@ +### Description + +This page helps you deploy and manage Hummingbot instances: + +- Starting and stopping Hummingbot Broker +- Creating, starting and stopping bot instances +- Managing strategy and script files that instances run +- Fetching status of running instances + +### Maintainers + +This page is maintained by Hummingbot Foundation as a template other pages: + +* [cardosfede](https://github.com/cardosfede) +* [fengtality](https://github.com/fengtality) + +### Wiki + +See the [wiki](https://github.com/hummingbot/dashboard/wiki/%F0%9F%90%99-Bot-Orchestration) for more information. \ No newline at end of file diff --git a/pages/orchestration/launch_bot_v2_st/__init__.py b/pages/orchestration/launch_bot_v2_st/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pages/orchestration/launch_bot_v2_st/app.py b/pages/orchestration/launch_bot_v2_st/app.py new file mode 100644 index 0000000..50c2c08 --- /dev/null +++ b/pages/orchestration/launch_bot_v2_st/app.py @@ -0,0 +1,9 @@ +from frontend.components.deploy_v2_with_controllers import LaunchV2WithControllers +from frontend.st_utils import initialize_st_page + + +initialize_st_page(title="Launch Bot ST", icon="🙌") + + +launcher = LaunchV2WithControllers() +launcher() diff --git a/pages/orchestration/portfolio/README.md b/pages/orchestration/portfolio/README.md new file mode 100644 index 0000000..18f4d94 --- /dev/null +++ b/pages/orchestration/portfolio/README.md @@ -0,0 +1,19 @@ +### Description + +This page helps you deploy and manage Hummingbot instances: + +- Starting and stopping Hummingbot Broker +- Creating, starting and stopping bot instances +- Managing strategy and script files that instances run +- Fetching status of running instances + +### Maintainers + +This page is maintained by Hummingbot Foundation as a template other pages: + +* [cardosfede](https://github.com/cardosfede) +* [fengtality](https://github.com/fengtality) + +### Wiki + +See the [wiki](https://github.com/hummingbot/dashboard/wiki/%F0%9F%90%99-Bot-Orchestration) for more information. \ No newline at end of file diff --git a/pages/orchestration/portfolio/__init__.py b/pages/orchestration/portfolio/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pages/orchestration/portfolio/app.py b/pages/orchestration/portfolio/app.py new file mode 100644 index 0000000..27d8607 --- /dev/null +++ b/pages/orchestration/portfolio/app.py @@ -0,0 +1,154 @@ +from frontend.st_utils import initialize_st_page, get_backend_api_client +import streamlit as st +import pandas as pd +import plotly.graph_objects as go +import plotly.express as px + +initialize_st_page(title="Portfolio", icon="💰") + +# Page content +client = get_backend_api_client() +NUM_COLUMNS = 4 + + +# Convert balances to a DataFrame for easier manipulation +def account_state_to_df(account_state): + data = [] + for account, exchanges in account_state.items(): + for exchange, tokens_info in exchanges.items(): + for info in tokens_info: + data.append({ + "account": account, + "exchange": exchange, + "token": info["token"], + "price": info["price"], + "units": info["units"], + "value": info["value"], + "available_units": info["available_units"], + }) + return pd.DataFrame(data) + + +# Convert historical account states to a DataFrame +def account_history_to_df(history): + data = [] + for record in history: + timestamp = record["timestamp"] + for account, exchanges in record["state"].items(): + for exchange, tokens_info in exchanges.items(): + for info in tokens_info: + data.append({ + "timestamp": timestamp, + "account": account, + "exchange": exchange, + "token": info["token"], + "price": info["price"], + "units": info["units"], + "value": info["value"], + "available_units": info["available_units"], + }) + return pd.DataFrame(data) + + +# Fetch account state from the backend +account_state = client.get_accounts_state() +account_history = client.get_account_state_history() +if len(account_state) == 0: + st.warning("No accounts found.") + st.stop() + +# Display the accounts available +accounts = st.multiselect("Select Accounts", list(account_state.keys()), list(account_state.keys())) +if len(accounts) == 0: + st.warning("Please select an account.") + st.stop() + +# Display the exchanges available +exchanges_available = [] +for account in accounts: + exchanges_available += account_state[account].keys() + +if len(exchanges_available) == 0: + st.warning("No exchanges found.") + st.stop() +exchanges = st.multiselect("Select Exchanges", exchanges_available, exchanges_available) + +# Display the tokens available +tokens_available = [] +for account in accounts: + for exchange in exchanges: + if exchange in account_state[account]: + tokens_available += [info["token"] for info in account_state[account][exchange]] + +token_options = set(tokens_available) +tokens_available = st.multiselect("Select Tokens", token_options, token_options) + + +st.write("---") + +filtered_account_state = {} +for account in accounts: + filtered_account_state[account] = {} + for exchange in exchanges: + if exchange in account_state[account]: + filtered_account_state[account][exchange] = [token_info for token_info in account_state[account][exchange] if token_info["token"] in tokens_available] + +filtered_account_history = [] +for record in account_history: + filtered_record = {"timestamp": record["timestamp"], "state": {}} + for account in accounts: + if account in record["state"]: + filtered_record["state"][account] = {} + for exchange in exchanges: + if exchange in record["state"][account]: + filtered_record["state"][account][exchange] = [token_info for token_info in record["state"][account][exchange] if token_info["token"] in tokens_available] + filtered_account_history.append(filtered_record) + +if len(filtered_account_state) > 0: + account_state_df = account_state_to_df(filtered_account_state) + total_balance_usd = round(account_state_df["value"].sum(), 2) + c1, c2 = st.columns([1, 5]) + with c1: + st.metric("Total Balance (USD)", total_balance_usd) + with c2: + account_state_df['% Allocation'] = (account_state_df['value'] / total_balance_usd) * 100 + account_state_df['label'] = account_state_df['token'] + ' ($' + account_state_df['value'].apply( + lambda x: f'{x:,.2f}') + ')' + + # Create a sunburst chart with Plotly Express + fig = px.sunburst(account_state_df, + path=['account', 'exchange', 'label'], + values='value', + hover_data={'% Allocation': ':.2f'}, + title='% Allocation by Account, Exchange, and Token', + color='account', + color_discrete_sequence=px.colors.qualitative.Vivid) + + fig.update_traces(textinfo='label+percent entry') + + fig.update_layout(margin=dict(t=0, l=0, r=0, b=0), height=800, title_x=0.01, title_y=1,) + + st.plotly_chart(fig, use_container_width=True) + + st.dataframe(account_state_df[['exchange', 'token', 'units', 'price', 'value', 'available_units']], width=1800, + height=600) + +# Plot the evolution of the portfolio over time +if len(filtered_account_history) > 0: + account_history_df = account_history_to_df(filtered_account_history) + account_history_df['timestamp'] = pd.to_datetime(account_history_df['timestamp']) + + # Aggregate the value of the portfolio over time + portfolio_evolution_df = account_history_df.groupby('timestamp')['value'].sum().reset_index() + + fig = px.line(portfolio_evolution_df, x='timestamp', y='value', title='Portfolio Evolution Over Time') + fig.update_layout(xaxis_title='Time', yaxis_title='Total Value (USD)', height=600) + st.plotly_chart(fig, use_container_width=True) + + # Plot the evolution of each token's value over time + token_evolution_df = account_history_df.groupby(['timestamp', 'token'])['value'].sum().reset_index() + + fig = px.area(token_evolution_df, x='timestamp', y='value', color='token', title='Token Value Evolution Over Time', + color_discrete_sequence=px.colors.qualitative.Vivid) + fig.update_layout(xaxis_title='Time', yaxis_title='Value (USD)', height=600) + st.plotly_chart(fig, use_container_width=True)