From 9dfa3e3c7825f85e0dc018d1b8c55002df58bf27 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Tue, 4 Jun 2024 20:08:36 +0200 Subject: [PATCH 01/53] (feat) rename deploy page --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 218827f..d037b1a 100644 --- a/main.py +++ b/main.py @@ -12,7 +12,7 @@ def main_page(): Page("main.py", "Hummingbot Dashboard", "📊"), Section("Bot Orchestration", "🐙"), Page("frontend/pages/orchestration/instances/app.py", "Instances", "🦅"), - Page("frontend/pages/orchestration/launch_bot_v2/app.py", "Deploy", "🚀"), + Page("frontend/pages/orchestration/launch_bot_v2/app.py", "Deploy V2", "🚀"), Page("frontend/pages/orchestration/credentials/app.py", "Credentials", "🔑"), Page("frontend/pages/orchestration/portfolio/app.py", "Portfolio", "💰"), # Page("frontend/pages/orchestration/launch_bot_v2_st/app.py", "Deploy ST", "🙌"), From 7db22e8c05c263c2ef192fb24c40e2170f7d2e76 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Tue, 4 Jun 2024 20:08:55 +0200 Subject: [PATCH 02/53] (feat) add delete controllers feature and simplify box --- frontend/components/launch_strategy_v2.py | 63 ++++++++++++++--------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/frontend/components/launch_strategy_v2.py b/frontend/components/launch_strategy_v2.py index 810957a..2d265e5 100644 --- a/frontend/components/launch_strategy_v2.py +++ b/frontend/components/launch_strategy_v2.py @@ -11,7 +11,7 @@ from .dashboard import Dashboard class LaunchStrategyV2(Dashboard.Item): DEFAULT_ROWS = [] DEFAULT_COLUMNS = [ - {"field": 'id', "headerName": 'ID', "width": 230}, + {"field": 'id', "headerName": 'ID', "minWidth": 230, "editable": False, }, {"field": 'controller_name', "headerName": 'Controller Name', "width": 150, "editable": False, }, {"field": 'controller_type', "headerName": 'Controller Type', "width": 150, "editable": False, }, {"field": 'connector_name', "headerName": 'Connector', "width": 150, "editable": False, }, @@ -79,26 +79,23 @@ class LaunchStrategyV2(Dashboard.Item): st.warning("You need to define the bot name and select the controllers configs " "that you want to deploy.") + def delete_selected_configs(self): + if self._controller_config_selected: + for config in self._controller_config_selected: + response = self._backend_api_client.delete_controller_config(config) + st.success(response) + self._controller_configs_available = self._backend_api_client.get_all_controllers_config() + else: + st.warning("You need to select the controllers configs that you want to delete.") + def __call__(self): with mui.Paper(key=self._key, sx={"display": "flex", "flexDirection": "column", "borderRadius": 3, "overflow": "hidden"}, elevation=1): with self.title_bar(padding="10px 15px 10px 15px", dark_switcher=False): - mui.Typography("🚀 Select the controller configs to launch", variant="h5") + mui.Typography("🎛️ Bot Configuration", variant="h5") with mui.Grid(container=True, spacing=2, sx={"padding": "10px 15px 10px 15px"}): - with mui.Grid(item=True, xs=8): - mui.Alert( - "The new instance will contain the credentials configured in the following base instance:", - severity="info") - with mui.Grid(item=True, xs=4): - available_credentials = self._backend_api_client.get_accounts() - with mui.FormControl(variant="standard", sx={"width": "100%"}): - mui.FormHelperText("Credentials") - with mui.Select(label="Credentials", defaultValue="master_account", - variant="standard", onChange=lazy(self._set_credentials)): - for master_config in available_credentials: - mui.MenuItem(master_config, value=master_config) with mui.Grid(item=True, xs=4): mui.TextField(label="Instance Name", variant="outlined", onChange=lazy(self._set_bot_name), sx={"width": "100%"}) @@ -111,12 +108,13 @@ class LaunchStrategyV2(Dashboard.Item): for image in available_images: mui.MenuItem(image, value=image) with mui.Grid(item=True, xs=4): - with mui.Button(onClick=self.launch_new_bot, - variant="outlined", - color="success", - sx={"width": "100%", "height": "100%"}): - mui.icon.AddCircleOutline() - mui.Typography("Create") + available_credentials = self._backend_api_client.get_accounts() + with mui.FormControl(variant="standard", sx={"width": "100%"}): + mui.FormHelperText("Credentials") + with mui.Select(label="Credentials", defaultValue="master_account", + variant="standard", onChange=lazy(self._set_credentials)): + for master_config in available_credentials: + mui.MenuItem(master_config, value=master_config) all_controllers_config = self._backend_api_client.get_all_controllers_config() data = [] for config in all_controllers_config: @@ -140,15 +138,29 @@ class LaunchStrategyV2(Dashboard.Item): "time_limit": time_limit}) with mui.Grid(item=True, xs=12): - mui.Alert("Select the controller configs to deploy", severity="info") with mui.Paper(key=self._key, sx={"display": "flex", "flexDirection": "column", "borderRadius": 3, "overflow": "hidden", "height": 1000}, - elevation=1): + elevation=2): with self.title_bar(padding="10px 15px 10px 15px", dark_switcher=False): - mui.icon.ViewCompact() - mui.Typography("Controllers Config") - with mui.Box(sx={"flex": 1, "minHeight": 3}): + with mui.Grid(container=True, spacing=2): + with mui.Grid(item=True, xs=8): + mui.Typography("🗄️ Available Configurations", variant="h6") + with mui.Grid(item=True, xs=2): + with mui.Button(onClick=self.delete_selected_configs, + variant="outlined", + color="error", + sx={"width": "100%", "height": "100%"}): + mui.icon.Delete() + mui.Typography("Delete") + with mui.Grid(item=True, xs=2): + with mui.Button(onClick=self.launch_new_bot, + variant="outlined", + color="success", + sx={"width": "100%", "height": "100%"}): + mui.icon.AddCircleOutline() + mui.Typography("Launch Bot") + with mui.Box(sx={"flex": 1, "minHeight": 3, "width": "100%"}): mui.DataGrid( columns=self.DEFAULT_COLUMNS, rows=data, @@ -156,5 +168,6 @@ class LaunchStrategyV2(Dashboard.Item): rowsPerPageOptions=[15], checkboxSelection=True, disableSelectionOnClick=True, + disableColumnResize=False, onSelectionModelChange=self._handle_row_selection, ) From 530db31958302f74809de59ac914ca233171f938 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Tue, 4 Jun 2024 20:09:05 +0200 Subject: [PATCH 03/53] (feat) add route to simplify backend api --- backend/services/backend_api_client.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/services/backend_api_client.py b/backend/services/backend_api_client.py index 8be28af..d46cf8f 100644 --- a/backend/services/backend_api_client.py +++ b/backend/services/backend_api_client.py @@ -159,6 +159,12 @@ class BackendAPIClient: response = requests.post(url, json=config) return response.json() + def delete_controller_config(self, controller_name: str): + """Delete a controller configuration.""" + url = f"{self.base_url}/delete-controller-config" + response = requests.post(url, params={"config_name": controller_name}) + return response.json() + def get_real_time_candles(self, connector: str, trading_pair: str, interval: str, max_records: int): """Get candles data.""" url = f"{self.base_url}/real-time-candles" From 4add30deb9a639b39dee1f1eaa3ccf0e4c85a4fe Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 5 Jun 2024 00:19:25 +0200 Subject: [PATCH 04/53] (feat) add random name generator --- frontend/utils.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 frontend/utils.py diff --git a/frontend/utils.py b/frontend/utils.py new file mode 100644 index 0000000..f84d8d8 --- /dev/null +++ b/frontend/utils.py @@ -0,0 +1,33 @@ +import random + + +def generate_random_name(existing_names_list): + # Convert the list to a set for efficient lookup + existing_names = set(existing_names_list) + + money_related = ["Dollar", "Coin", "Credit", "Wealth", "Fortune", "Cash", "Gold", "Profit", "Rich", "Value"] + trading_related = ["Market", "Trade", "Exchange", "Broker", "Stock", "Bond", "Option", "Margin", "Future", "Index"] + algorithm_related = ["Algo", "Bot", "Code", "Script", "Logic", "Matrix", "Compute", "Sequence", "Data", "Binary"] + science_related = ["Quantum", "Neuron", "Atom", "Fusion", "Gravity", "Particle", "Genome", "Spectrum", "Theory", + "Experiment"] + space_related = ["Galaxy", "Nebula", "Star", "Planet", "Orbit", "Cosmos", "Asteroid", "Comet", "Blackhole", + "Eclipse"] + bird_related = ["Falcon", "Eagle", "Hawk", "Sparrow", "Robin", "Swallow", "Owl", "Raven", "Dove", "Phoenix"] + + categories = [money_related, trading_related, algorithm_related, science_related, space_related, bird_related] + + while True: + # Select two different categories + first_category = random.choice(categories) + second_category = random.choice([category for category in categories if category != first_category]) + + # Select one word from each category + first_word = random.choice(first_category) + second_word = random.choice(second_category) + + name = f"{first_word}-{second_word}" + + if name not in existing_names: + existing_names.add(name) + existing_names_list.append(name) # Update the list to keep track of used names + return name \ No newline at end of file From 1ca8d9968da97ff6c7b5c013a920da6f832a778d Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 5 Jun 2024 00:19:37 +0200 Subject: [PATCH 05/53] (feat) adapt bollinger v1 to random names --- frontend/pages/config/bollinger_v1/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/pages/config/bollinger_v1/app.py b/frontend/pages/config/bollinger_v1/app.py index 9f6ec6d..64cdfe7 100644 --- a/frontend/pages/config/bollinger_v1/app.py +++ b/frontend/pages/config/bollinger_v1/app.py @@ -33,7 +33,7 @@ st.text("This tool will let you create a config for Bollinger V1 and visualize t get_default_config_loader("bollinger_v1") inputs = user_inputs() -st.session_state["default_config"] = inputs +st.session_state["default_config"].update(inputs) st.write("### Visualizing Bollinger Bands and Trading Signals") days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=3) @@ -68,4 +68,4 @@ if bt_results: st.write("---") render_close_types(bt_results["results"]) st.write("---") -render_save_config("bollinger_v1", inputs) +render_save_config(st.session_state["default_config"]["id"], st.session_state["default_config"]) From 5c2c0cc34b85afbdd9bae20012874b81f5c61464 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 5 Jun 2024 00:19:54 +0200 Subject: [PATCH 06/53] (feat) adapt macd bb to random names --- frontend/pages/config/macd_bb_v1/app.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/pages/config/macd_bb_v1/app.py b/frontend/pages/config/macd_bb_v1/app.py index f061fcc..a9f0e3e 100644 --- a/frontend/pages/config/macd_bb_v1/app.py +++ b/frontend/pages/config/macd_bb_v1/app.py @@ -28,7 +28,8 @@ backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=B get_default_config_loader("macd_bb_v1") # User inputs inputs = user_inputs() -st.session_state["default_config"] = inputs +st.session_state["default_config"].update(inputs) + st.write("### Visualizing MACD Bollinger Trading Signals") days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=3) @@ -64,4 +65,5 @@ if bt_results: st.write("---") render_close_types(bt_results["results"]) st.write("---") -render_save_config("bollinger_v1", inputs) +render_save_config(st.session_state["default_config"]["id"], st.session_state["default_config"]) + From 4e156f47624cc4c50256e5dec4488c069382487d Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 5 Jun 2024 00:20:00 +0200 Subject: [PATCH 07/53] (feat) adapt supertrend to random names --- frontend/pages/config/supertrend_v1/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/pages/config/supertrend_v1/app.py b/frontend/pages/config/supertrend_v1/app.py index af195d3..2438f66 100644 --- a/frontend/pages/config/supertrend_v1/app.py +++ b/frontend/pages/config/supertrend_v1/app.py @@ -25,7 +25,7 @@ backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=B get_default_config_loader("supertrend_v1") # User inputs inputs = user_inputs() -st.session_state["default_config"] = inputs +st.session_state["default_config"].update(inputs) st.write("### Visualizing Supertrend Trading Signals") days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=3) @@ -61,4 +61,4 @@ if bt_results: st.write("---") render_close_types(bt_results["results"]) st.write("---") -render_save_config("bollinger_v1", inputs) +render_save_config(st.session_state["default_config"]["id"], st.session_state["default_config"]) From 00b452e39d09e7dab0934e05d04192967404b58a Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 5 Jun 2024 00:20:10 +0200 Subject: [PATCH 08/53] (feat) improve loaders and save config --- frontend/components/config_loader.py | 25 ++++++++++++++++++------- frontend/components/save_config.py | 20 ++++++++++++++------ 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/frontend/components/config_loader.py b/frontend/components/config_loader.py index 3f5ff1f..812c983 100644 --- a/frontend/components/config_loader.py +++ b/frontend/components/config_loader.py @@ -2,16 +2,27 @@ import streamlit as st from CONFIG import BACKEND_API_HOST, BACKEND_API_PORT from backend.services.backend_api_client import BackendAPIClient +from frontend.utils import generate_random_name backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) def get_default_config_loader(controller_name: str): - use_default_config = st.checkbox("Use default config", value=True) all_configs = backend_api_client.get_all_controllers_config() - if use_default_config: - st.session_state["default_config"] = {} - else: - configs = [config for config in all_configs if config["controller_name"] == controller_name] - default_config = st.selectbox("Select a config", [config["id"] for config in configs]) - st.session_state["default_config"] = next((config for config in all_configs if config["id"] == default_config), {}) + existing_configs = [config["id"].split("-")[0] for config in all_configs] + default_dict = {"id": generate_random_name(existing_configs)} + with st.expander("Configurations", expanded=True): + c1, c2 = st.columns(2) + with c1: + use_default_config = st.checkbox("Use default config", value=True) + if use_default_config: + st.session_state["default_config"] = default_dict + else: + with c2: + configs = [config for config in all_configs if config["controller_name"] == controller_name] + default_config = st.selectbox("Select a config", [config["id"] for config in configs]) + st.session_state["default_config"] = next((config for config in all_configs if config["id"] == default_config), None) + if st.session_state["default_config"] is None: + st.session_state["default_config"] = default_dict + else: + st.session_state["default_config"]["id"] = st.session_state["default_config"]["id"].split("_")[0] diff --git a/frontend/components/save_config.py b/frontend/components/save_config.py index 89896d7..2afedfa 100644 --- a/frontend/components/save_config.py +++ b/frontend/components/save_config.py @@ -4,19 +4,27 @@ from CONFIG import BACKEND_API_HOST, BACKEND_API_PORT from backend.services.backend_api_client import BackendAPIClient -def render_save_config(controller_name: str, config_data: dict): +def render_save_config(config_base: str, config_data: dict): st.write("### Upload Config to BackendAPI") + backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) + all_configs = backend_api_client.get_all_controllers_config() + config_bases = set(config_name["id"].split("_")[0] for config_name in all_configs) + config_base = config_base.split("_")[0] + if config_base in config_bases: + config_tag = max(float(config["id"].split("_")[-1]) for config in all_configs if config_base in config["id"]) + version, tag = str(config_tag).split(".") + config_tag = f"{version}.{int(tag) + 1}" + else: + config_tag = "0.1" c1, c2, c3 = st.columns([1, 1, 0.5]) - connector = config_data.get("connector_name", "") - trading_pair = config_data.get("trading_pair", "") with c1: - config_base = st.text_input("Config Base", value=f"{controller_name}-{connector}-{trading_pair.split('-')[0]}") + config_base = st.text_input("Config Base", value=config_base) with c2: - config_tag = st.text_input("Config Tag", value="1.1") + config_tag = st.text_input("Config Tag", value=config_tag) with c3: upload_config_to_backend = st.button("Upload") if upload_config_to_backend: - config_data["id"] = f"{config_base}-{config_tag}" + config_data["id"] = f"{config_base}_{config_tag}" backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) backend_api_client.add_controller_config(config_data) st.success("Config uploaded successfully!") From fdacbe995ae477d1f52eec6adee1bcdc01fa7ad3 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Thu, 6 Jun 2024 02:29:20 +0200 Subject: [PATCH 09/53] (feat) improve units for custom spreads --- frontend/components/executors_distribution.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/components/executors_distribution.py b/frontend/components/executors_distribution.py index 0b77495..5cdae99 100644 --- a/frontend/components/executors_distribution.py +++ b/frontend/components/executors_distribution.py @@ -2,10 +2,16 @@ import streamlit as st from frontend.components.st_inputs import get_distribution, normalize, distribution_inputs -def get_executors_distribution_inputs(default_spreads=[0.01, 0.02], default_amounts=[0.2, 0.8]): +def get_executors_distribution_inputs(use_custom_spread_units=False): + default_amounts = [0.2, 0.8] default_config = st.session_state.get("default_config", {}) - buy_spreads = default_config.get("buy_spreads", default_spreads) - sell_spreads = default_config.get("sell_spreads", default_spreads) + if use_custom_spread_units: + buy_spreads = [spread / 100 for spread in default_config.get("buy_spreads", [1, 2])] + sell_spreads = [spread / 100 for spread in default_config.get("sell_spreads", [1, 2])] + else: + buy_spreads = default_config.get("buy_spreads", [0.01, 0.02]) + sell_spreads = default_config.get("sell_spreads", [0.01, 0.02]) + buy_amounts_pct = default_config.get("buy_amounts_pct", default_amounts) sell_amounts_pct = default_config.get("sell_amounts_pct", default_amounts) buy_order_levels_def = len(buy_spreads) From e9085437e99161494513546608c7aa5dc00ce434 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Thu, 6 Jun 2024 02:29:29 +0200 Subject: [PATCH 10/53] (feat) add random generator of names for configs --- frontend/utils.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/utils.py b/frontend/utils.py index f84d8d8..4059d11 100644 --- a/frontend/utils.py +++ b/frontend/utils.py @@ -5,14 +5,14 @@ def generate_random_name(existing_names_list): # Convert the list to a set for efficient lookup existing_names = set(existing_names_list) - money_related = ["Dollar", "Coin", "Credit", "Wealth", "Fortune", "Cash", "Gold", "Profit", "Rich", "Value"] - trading_related = ["Market", "Trade", "Exchange", "Broker", "Stock", "Bond", "Option", "Margin", "Future", "Index"] - algorithm_related = ["Algo", "Bot", "Code", "Script", "Logic", "Matrix", "Compute", "Sequence", "Data", "Binary"] - science_related = ["Quantum", "Neuron", "Atom", "Fusion", "Gravity", "Particle", "Genome", "Spectrum", "Theory", - "Experiment"] - space_related = ["Galaxy", "Nebula", "Star", "Planet", "Orbit", "Cosmos", "Asteroid", "Comet", "Blackhole", - "Eclipse"] - bird_related = ["Falcon", "Eagle", "Hawk", "Sparrow", "Robin", "Swallow", "Owl", "Raven", "Dove", "Phoenix"] + money_related = ["dollar", "coin", "credit", "wealth", "fortune", "cash", "gold", "profit", "rich", "value"] + trading_related = ["market", "trade", "exchange", "broker", "stock", "bond", "option", "margin", "future", "index"] + algorithm_related = ["algo", "bot", "code", "script", "logic", "matrix", "compute", "sequence", "data", "binary"] + science_related = ["quantum", "neuron", "atom", "fusion", "gravity", "particle", "genome", "spectrum", "theory", + "experiment"] + space_related = ["galaxy", "nebula", "star", "planet", "orbit", "cosmos", "asteroid", "comet", "blackhole", + "eclipse"] + bird_related = ["falcon", "eagle", "hawk", "sparrow", "robin", "swallow", "owl", "raven", "dove", "phoenix"] categories = [money_related, trading_related, algorithm_related, science_related, space_related, bird_related] @@ -30,4 +30,4 @@ def generate_random_name(existing_names_list): if name not in existing_names: existing_names.add(name) existing_names_list.append(name) # Update the list to keep track of used names - return name \ No newline at end of file + return name From aa9e137281a513a2cb2da7cee40fe8d47773bdcb Mon Sep 17 00:00:00 2001 From: cardosofede Date: Thu, 6 Jun 2024 02:29:41 +0200 Subject: [PATCH 11/53] (feat) update default configs in pages --- frontend/pages/config/dman_maker_v2/app.py | 4 ++-- frontend/pages/config/pmm_dynamic/app.py | 6 +++--- frontend/pages/config/pmm_simple/app.py | 5 ++++- frontend/pages/config/pmm_simple/user_inputs.py | 1 - 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/frontend/pages/config/dman_maker_v2/app.py b/frontend/pages/config/dman_maker_v2/app.py index 7fbd2b0..1505c38 100644 --- a/frontend/pages/config/dman_maker_v2/app.py +++ b/frontend/pages/config/dman_maker_v2/app.py @@ -52,7 +52,7 @@ st.plotly_chart(fig, use_container_width=True) # Combine inputs and dca_inputs into final config config = {**inputs, **dca_inputs} -st.session_state["default_config"] = config +st.session_state["default_config"].update(config) bt_results = backtesting_section(config, backend_api_client) if bt_results: fig = create_backtesting_figure( @@ -68,4 +68,4 @@ if bt_results: st.write("---") render_close_types(bt_results["results"]) st.write("---") -render_save_config("dman_maker_v2", config) \ No newline at end of file +render_save_config(st.session_state["default_config"]["id"], st.session_state["default_config"]) diff --git a/frontend/pages/config/pmm_dynamic/app.py b/frontend/pages/config/pmm_dynamic/app.py index 418512d..e43a8a2 100644 --- a/frontend/pages/config/pmm_dynamic/app.py +++ b/frontend/pages/config/pmm_dynamic/app.py @@ -55,12 +55,12 @@ with st.expander("Visualizing PMM Dynamic Indicators", expanded=True): st.write("### Executors Distribution") st.write("The order distributions are affected by the average NATR. This means that if the first order has a spread of " "1 and the NATR is 0.005, the first order will have a spread of 0.5% of the mid price.") -buy_spread_distributions, sell_spread_distributions, buy_order_amounts_pct, sell_order_amounts_pct = get_executors_distribution_inputs() +buy_spread_distributions, sell_spread_distributions, buy_order_amounts_pct, sell_order_amounts_pct = get_executors_distribution_inputs(use_custom_spread_units=True) inputs["buy_spreads"] = [spread * 100 for spread in buy_spread_distributions] inputs["sell_spreads"] = [spread * 100 for spread in sell_spread_distributions] inputs["buy_amounts_pct"] = buy_order_amounts_pct inputs["sell_amounts_pct"] = sell_order_amounts_pct -st.session_state["default_config"] = inputs +st.session_state["default_config"].update(inputs) with st.expander("Executor Distribution:", expanded=True): natr_avarage = spreads_multiplier.mean() buy_spreads = [spread * natr_avarage for spread in inputs["buy_spreads"]] @@ -84,4 +84,4 @@ if bt_results: st.write("---") render_close_types(bt_results["results"]) st.write("---") -render_save_config("pmm_dynamic", inputs) +render_save_config(st.session_state["default_config"]["id"], st.session_state["default_config"]) diff --git a/frontend/pages/config/pmm_simple/app.py b/frontend/pages/config/pmm_simple/app.py index 28b9c75..69d60b5 100644 --- a/frontend/pages/config/pmm_simple/app.py +++ b/frontend/pages/config/pmm_simple/app.py @@ -17,11 +17,14 @@ from frontend.visualization.backtesting_metrics import render_backtesting_metric initialize_st_page(title="PMM Simple", icon="👨‍🏫") backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) + # Page content st.text("This tool will let you create a config for PMM Simple, backtest and upload it to the Backend API.") get_default_config_loader("pmm_simple") + inputs = user_inputs() +st.session_state["default_config"].update(inputs) with st.expander("Executor Distribution:", expanded=True): fig = create_executors_distribution_traces(inputs["buy_spreads"], inputs["sell_spreads"], inputs["buy_amounts_pct"], inputs["sell_amounts_pct"], inputs["total_amount_quote"]) st.plotly_chart(fig, use_container_width=True) @@ -41,4 +44,4 @@ if bt_results: st.write("---") render_close_types(bt_results["results"]) st.write("---") -render_save_config("pmm_simple", inputs) +render_save_config(st.session_state["default_config"]["id"], st.session_state["default_config"]) diff --git a/frontend/pages/config/pmm_simple/user_inputs.py b/frontend/pages/config/pmm_simple/user_inputs.py index 69ff319..b160481 100644 --- a/frontend/pages/config/pmm_simple/user_inputs.py +++ b/frontend/pages/config/pmm_simple/user_inputs.py @@ -35,5 +35,4 @@ def user_inputs(): "trailing_delta": ts_delta } } - st.session_state["default_config"] = config return config From 15f6ab0bfbcfcb879cd0d55be9107582745ac949 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Thu, 6 Jun 2024 23:33:40 +0200 Subject: [PATCH 12/53] (feat) add version --- frontend/components/launch_strategy_v2.py | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/components/launch_strategy_v2.py b/frontend/components/launch_strategy_v2.py index 2d265e5..136661c 100644 --- a/frontend/components/launch_strategy_v2.py +++ b/frontend/components/launch_strategy_v2.py @@ -11,7 +11,8 @@ from .dashboard import Dashboard class LaunchStrategyV2(Dashboard.Item): DEFAULT_ROWS = [] DEFAULT_COLUMNS = [ - {"field": 'id', "headerName": 'ID', "minWidth": 230, "editable": False, }, + {"field": 'config_base', "headerName": 'Config Base', "minWidth": 160, "editable": False, }, + {"field": 'version', "headerName": 'Version', "minWidth": 100, "editable": False, }, {"field": 'controller_name', "headerName": 'Controller Name', "width": 150, "editable": False, }, {"field": 'controller_type', "headerName": 'Controller Type', "width": 150, "editable": False, }, {"field": 'connector_name', "headerName": 'Connector', "width": 150, "editable": False, }, @@ -125,17 +126,16 @@ class LaunchStrategyV2(Dashboard.Item): take_profit = config.get("take_profit", 0) trailing_stop = config.get("trailing_stop", {"activation_price": 0, "trailing_delta": 0}) time_limit = config.get("time_limit", 0) - data.append({"id": config["id"], "controller_name": config["controller_name"], - "controller_type": config["controller_type"], - "connector_name": connector_name, - "trading_pair": trading_pair, - "total_amount_quote": total_amount_quote, - "max_loss_quote": total_amount_quote * stop_loss / 2, - "stop_loss": stop_loss, - "take_profit": take_profit, - "trailing_stop": str(trailing_stop["activation_price"]) + " / " + - str(trailing_stop["trailing_delta"]), - "time_limit": time_limit}) + config_base, version = config["id"].split("_") + data.append({ + "id": config["id"], "config_base": config_base, "version": version, + "controller_name": config["controller_name"], "controller_type": config["controller_type"], + "connector_name": connector_name, "trading_pair": trading_pair, + "total_amount_quote": total_amount_quote, "max_loss_quote": total_amount_quote * stop_loss / 2, + "stop_loss": stop_loss, "take_profit": take_profit, + "trailing_stop": str(trailing_stop["activation_price"]) + " / " + + str(trailing_stop["trailing_delta"]), + "time_limit": time_limit}) with mui.Grid(item=True, xs=12): with mui.Paper(key=self._key, From 62e0f292d2bc35a7ed6db3a644fd0b4d9c6bd9d0 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Thu, 6 Jun 2024 23:34:00 +0200 Subject: [PATCH 13/53] (feat) improve loaders and config savers --- frontend/components/config_loader.py | 16 ++++++++-------- frontend/components/save_config.py | 5 +++-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/frontend/components/config_loader.py b/frontend/components/config_loader.py index 812c983..8cb1542 100644 --- a/frontend/components/config_loader.py +++ b/frontend/components/config_loader.py @@ -11,18 +11,18 @@ def get_default_config_loader(controller_name: str): all_configs = backend_api_client.get_all_controllers_config() existing_configs = [config["id"].split("-")[0] for config in all_configs] default_dict = {"id": generate_random_name(existing_configs)} + default_config = st.session_state.get("default_config") + config_controller_name = st.session_state.get("controller_name", "nan") + if default_config is None or controller_name != config_controller_name: + st.session_state["default_config"] = default_dict with st.expander("Configurations", expanded=True): c1, c2 = st.columns(2) with c1: use_default_config = st.checkbox("Use default config", value=True) - if use_default_config: - st.session_state["default_config"] = default_dict - else: - with c2: + with c2: + if not use_default_config: configs = [config for config in all_configs if config["controller_name"] == controller_name] default_config = st.selectbox("Select a config", [config["id"] for config in configs]) st.session_state["default_config"] = next((config for config in all_configs if config["id"] == default_config), None) - if st.session_state["default_config"] is None: - st.session_state["default_config"] = default_dict - else: - st.session_state["default_config"]["id"] = st.session_state["default_config"]["id"].split("_")[0] + st.session_state["default_config"]["id"] = st.session_state["default_config"]["id"].split("_")[0] + diff --git a/frontend/components/save_config.py b/frontend/components/save_config.py index 2afedfa..2c6fae6 100644 --- a/frontend/components/save_config.py +++ b/frontend/components/save_config.py @@ -4,12 +4,12 @@ from CONFIG import BACKEND_API_HOST, BACKEND_API_PORT from backend.services.backend_api_client import BackendAPIClient -def render_save_config(config_base: str, config_data: dict): +def render_save_config(config_base_default: str, config_data: dict): st.write("### Upload Config to BackendAPI") backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) all_configs = backend_api_client.get_all_controllers_config() config_bases = set(config_name["id"].split("_")[0] for config_name in all_configs) - config_base = config_base.split("_")[0] + config_base = config_base_default.split("_")[0] if config_base in config_bases: config_tag = max(float(config["id"].split("_")[-1]) for config in all_configs if config_base in config["id"]) version, tag = str(config_tag).split(".") @@ -27,4 +27,5 @@ def render_save_config(config_base: str, config_data: dict): config_data["id"] = f"{config_base}_{config_tag}" backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) backend_api_client.add_controller_config(config_data) + st.session_state["default_config"] = None st.success("Config uploaded successfully!") From 9cb1a9cf064f4cd69ed1cf77f2a317683b40d8d5 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Thu, 6 Jun 2024 23:49:30 +0200 Subject: [PATCH 14/53] (feat) restore it if is a different controller name --- frontend/components/config_loader.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/components/config_loader.py b/frontend/components/config_loader.py index 8cb1542..9dc9276 100644 --- a/frontend/components/config_loader.py +++ b/frontend/components/config_loader.py @@ -12,7 +12,8 @@ def get_default_config_loader(controller_name: str): existing_configs = [config["id"].split("-")[0] for config in all_configs] default_dict = {"id": generate_random_name(existing_configs)} default_config = st.session_state.get("default_config") - config_controller_name = st.session_state.get("controller_name", "nan") + config_controller_name = st.session_state.get("controller_name", controller_name) + st.write(f"controller_name: {controller_name} | config_controller_name: {config_controller_name}") if default_config is None or controller_name != config_controller_name: st.session_state["default_config"] = default_dict with st.expander("Configurations", expanded=True): From 8ec3098b269ddd0b47a3177d9c7ff6fe0ff9eb51 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Fri, 7 Jun 2024 00:01:54 +0200 Subject: [PATCH 15/53] (feat) improve response of is docker running --- backend/services/backend_api_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/services/backend_api_client.py b/backend/services/backend_api_client.py index d46cf8f..f90d931 100644 --- a/backend/services/backend_api_client.py +++ b/backend/services/backend_api_client.py @@ -26,7 +26,7 @@ class BackendAPIClient: """Check if Docker is running.""" url = f"{self.base_url}/is-docker-running" response = requests.get(url) - return response.json() + return response.json()["is_docker_running"] def pull_image(self, image_name: str): """Pull a Docker image.""" From 5adf7df3ed68ba5a6b21e3a7409b309858b7339b Mon Sep 17 00:00:00 2001 From: cardosofede Date: Fri, 7 Jun 2024 00:02:15 +0200 Subject: [PATCH 16/53] (feat) add method to get the instance of hte backend api with error handling --- frontend/st_utils.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/frontend/st_utils.py b/frontend/st_utils.py index 82575ca..6336e33 100644 --- a/frontend/st_utils.py +++ b/frontend/st_utils.py @@ -66,3 +66,18 @@ def style_metric_cards( unsafe_allow_html=True, ) + +def get_backend_api_client(): + from CONFIG import BACKEND_API_HOST, BACKEND_API_PORT + from backend.services.backend_api_client import BackendAPIClient + backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) + is_docker_running = False + try: + is_docker_running = backend_api_client.is_docker_running() + except Exception as e: + st.error(f"There was an error trying to connect to the Backend API: \n\n{str(e)} \n\nPlease make sure the Backend API is running.") + st.stop() + if not is_docker_running: + st.error("Docker is not running. Please make sure Docker is running.") + st.stop() + return backend_api_client From 275b87ddc4321a7882e0b29984ec3f5a2958c764 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Fri, 7 Jun 2024 00:04:12 +0200 Subject: [PATCH 17/53] (feat) adapt components to get backend api client --- frontend/components/bot_performance_card.py | 5 +++-- frontend/components/config_loader.py | 5 ++--- frontend/components/deploy_v2_with_controllers.py | 3 ++- frontend/components/launch_strategy_v2.py | 3 ++- frontend/components/save_config.py | 4 ++-- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/frontend/components/bot_performance_card.py b/frontend/components/bot_performance_card.py index c045bc9..fcddf5d 100644 --- a/frontend/components/bot_performance_card.py +++ b/frontend/components/bot_performance_card.py @@ -6,12 +6,13 @@ from CONFIG import BACKEND_API_HOST, BACKEND_API_PORT from frontend.components.dashboard import Dashboard from backend.services.backend_api_client import BackendAPIClient +from frontend.st_utils import get_backend_api_client TRADES_TO_SHOW = 5 WIDE_COL_WIDTH = 250 MEDIUM_COL_WIDTH = 170 SMALL_COL_WIDTH = 100 -backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) +backend_api_client = get_backend_api_client() def stop_bot(bot_name): @@ -38,7 +39,7 @@ class BotPerformanceCardV2(Dashboard.Item): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self._backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) + self._backend_api_client = get_backend_api_client() def _handle_stopped_row_selection(self, params, _): self._stopped_controller_config_selected = params diff --git a/frontend/components/config_loader.py b/frontend/components/config_loader.py index 9dc9276..052594a 100644 --- a/frontend/components/config_loader.py +++ b/frontend/components/config_loader.py @@ -1,10 +1,9 @@ import streamlit as st -from CONFIG import BACKEND_API_HOST, BACKEND_API_PORT -from backend.services.backend_api_client import BackendAPIClient +from frontend.st_utils import get_backend_api_client from frontend.utils import generate_random_name -backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) +backend_api_client = get_backend_api_client() def get_default_config_loader(controller_name: str): diff --git a/frontend/components/deploy_v2_with_controllers.py b/frontend/components/deploy_v2_with_controllers.py index 9faae93..d62eaaf 100644 --- a/frontend/components/deploy_v2_with_controllers.py +++ b/frontend/components/deploy_v2_with_controllers.py @@ -4,6 +4,7 @@ import pandas as pd from CONFIG import BACKEND_API_HOST, BACKEND_API_PORT from backend.services.backend_api_client import BackendAPIClient +from frontend.st_utils import get_backend_api_client class LaunchV2WithControllers: @@ -14,7 +15,7 @@ class LaunchV2WithControllers: ] def __init__(self): - self._backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) + self._backend_api_client = get_backend_api_client() self._controller_configs_available = self._backend_api_client.get_all_controllers_config() self._controller_config_selected = [] self._bot_name = None diff --git a/frontend/components/launch_strategy_v2.py b/frontend/components/launch_strategy_v2.py index 136661c..f0e1aee 100644 --- a/frontend/components/launch_strategy_v2.py +++ b/frontend/components/launch_strategy_v2.py @@ -6,6 +6,7 @@ from streamlit_elements import mui, lazy from CONFIG import BACKEND_API_HOST, BACKEND_API_PORT from backend.services.backend_api_client import BackendAPIClient from .dashboard import Dashboard +from ..st_utils import get_backend_api_client class LaunchStrategyV2(Dashboard.Item): @@ -27,7 +28,7 @@ class LaunchStrategyV2(Dashboard.Item): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self._backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) + self._backend_api_client = get_backend_api_client() self._controller_configs_available = self._backend_api_client.get_all_controllers_config() self._controller_config_selected = None self._bot_name = None diff --git a/frontend/components/save_config.py b/frontend/components/save_config.py index 2c6fae6..d116dd1 100644 --- a/frontend/components/save_config.py +++ b/frontend/components/save_config.py @@ -2,11 +2,12 @@ import streamlit as st from CONFIG import BACKEND_API_HOST, BACKEND_API_PORT from backend.services.backend_api_client import BackendAPIClient +from frontend.st_utils import get_backend_api_client def render_save_config(config_base_default: str, config_data: dict): st.write("### Upload Config to BackendAPI") - backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) + backend_api_client = get_backend_api_client() all_configs = backend_api_client.get_all_controllers_config() config_bases = set(config_name["id"].split("_")[0] for config_name in all_configs) config_base = config_base_default.split("_")[0] @@ -25,7 +26,6 @@ def render_save_config(config_base_default: str, config_data: dict): upload_config_to_backend = st.button("Upload") if upload_config_to_backend: config_data["id"] = f"{config_base}_{config_tag}" - backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) backend_api_client.add_controller_config(config_data) st.session_state["default_config"] = None st.success("Config uploaded successfully!") From 04dc8a7d64aac9b0cd4e779c122658fc8fba3ed3 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Fri, 7 Jun 2024 00:04:20 +0200 Subject: [PATCH 18/53] (feat) adapt pages to get the backend api client --- frontend/pages/config/bollinger_v1/app.py | 4 ++-- frontend/pages/config/dman_maker_v2/app.py | 4 ++-- frontend/pages/config/dman_v5/app.py | 4 ++-- frontend/pages/config/kalman_filter_v1/app.py | 4 ++-- frontend/pages/config/macd_bb_v1/app.py | 4 ++-- frontend/pages/config/pmm_dynamic/app.py | 4 ++-- frontend/pages/config/pmm_simple/app.py | 4 ++-- frontend/pages/config/supertrend_v1/app.py | 4 ++-- frontend/pages/config/xemm_controller/app.py | 4 ++-- frontend/pages/data/download_candles/app.py | 5 ++--- frontend/pages/orchestration/credentials/app.py | 4 ++-- frontend/pages/orchestration/instances/app.py | 4 ++-- frontend/pages/orchestration/portfolio/app.py | 4 ++-- 13 files changed, 26 insertions(+), 27 deletions(-) diff --git a/frontend/pages/config/bollinger_v1/app.py b/frontend/pages/config/bollinger_v1/app.py index 64cdfe7..a14a4fe 100644 --- a/frontend/pages/config/bollinger_v1/app.py +++ b/frontend/pages/config/bollinger_v1/app.py @@ -11,7 +11,7 @@ from frontend.components.backtesting import backtesting_section from frontend.components.config_loader import get_default_config_loader from frontend.components.save_config import render_save_config from frontend.pages.config.utils import get_max_records, get_candles -from frontend.st_utils import initialize_st_page +from frontend.st_utils import initialize_st_page, get_backend_api_client from frontend.pages.config.bollinger_v1.user_inputs import user_inputs from plotly.subplots import make_subplots @@ -26,7 +26,7 @@ from frontend.visualization.utils import add_traces_to_fig # Initialize the Streamlit page initialize_st_page(title="Bollinger V1", icon="📈", initial_sidebar_state="expanded") -backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) +backend_api_client = get_backend_api_client() st.text("This tool will let you create a config for Bollinger V1 and visualize the strategy.") diff --git a/frontend/pages/config/dman_maker_v2/app.py b/frontend/pages/config/dman_maker_v2/app.py index 1505c38..830fe99 100644 --- a/frontend/pages/config/dman_maker_v2/app.py +++ b/frontend/pages/config/dman_maker_v2/app.py @@ -7,7 +7,7 @@ from frontend.components.config_loader import get_default_config_loader from frontend.components.dca_distribution import get_dca_distribution_inputs from frontend.components.save_config import render_save_config from frontend.pages.config.dman_maker_v2.user_inputs import user_inputs -from frontend.st_utils import initialize_st_page +from frontend.st_utils import initialize_st_page, get_backend_api_client from frontend.visualization.backtesting import create_backtesting_figure from frontend.visualization.backtesting_metrics import render_backtesting_metrics, render_accuracy_metrics, \ render_close_types @@ -16,7 +16,7 @@ from frontend.visualization.executors_distribution import create_executors_distr # Initialize the Streamlit page initialize_st_page(title="D-Man Maker V2", icon="🧙‍♂️") -backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) +backend_api_client = get_backend_api_client() # Page content diff --git a/frontend/pages/config/dman_v5/app.py b/frontend/pages/config/dman_v5/app.py index ac39d9f..9522cb8 100644 --- a/frontend/pages/config/dman_v5/app.py +++ b/frontend/pages/config/dman_v5/app.py @@ -6,7 +6,7 @@ from plotly.subplots import make_subplots 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 +from frontend.st_utils import initialize_st_page, get_backend_api_client # Initialize the Streamlit page initialize_st_page(title="D-Man V5", icon="📊", initial_sidebar_state="expanded") @@ -142,6 +142,6 @@ with c3: if upload_config_to_backend: - backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) + backend_api_client = get_backend_api_client() backend_api_client.add_controller_config(config) st.success("Config uploaded successfully!") diff --git a/frontend/pages/config/kalman_filter_v1/app.py b/frontend/pages/config/kalman_filter_v1/app.py index 5f7e358..2479d18 100644 --- a/frontend/pages/config/kalman_filter_v1/app.py +++ b/frontend/pages/config/kalman_filter_v1/app.py @@ -7,7 +7,7 @@ from pykalman import KalmanFilter 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 +from frontend.st_utils import initialize_st_page, get_backend_api_client # Initialize the Streamlit page initialize_st_page(title="Kalman Filter V1", icon="📈", initial_sidebar_state="expanded") @@ -220,6 +220,6 @@ with c3: if upload_config_to_backend: - backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) + backend_api_client = get_backend_api_client() backend_api_client.add_controller_config(config) st.success("Config uploaded successfully!") \ No newline at end of file diff --git a/frontend/pages/config/macd_bb_v1/app.py b/frontend/pages/config/macd_bb_v1/app.py index a9f0e3e..75383e5 100644 --- a/frontend/pages/config/macd_bb_v1/app.py +++ b/frontend/pages/config/macd_bb_v1/app.py @@ -11,7 +11,7 @@ from frontend.components.config_loader import get_default_config_loader from frontend.components.save_config import render_save_config from frontend.pages.config.macd_bb_v1.user_inputs import user_inputs from frontend.pages.config.utils import get_candles, get_max_records -from frontend.st_utils import initialize_st_page +from frontend.st_utils import initialize_st_page, get_backend_api_client from frontend.visualization import theme from frontend.visualization.backtesting import create_backtesting_figure from frontend.visualization.backtesting_metrics import render_backtesting_metrics, render_accuracy_metrics, \ @@ -23,7 +23,7 @@ from frontend.visualization.utils import add_traces_to_fig # Initialize the Streamlit page initialize_st_page(title="MACD_BB V1", icon="📊", initial_sidebar_state="expanded") -backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) +backend_api_client = get_backend_api_client() get_default_config_loader("macd_bb_v1") # User inputs diff --git a/frontend/pages/config/pmm_dynamic/app.py b/frontend/pages/config/pmm_dynamic/app.py index e43a8a2..8bbbbc8 100644 --- a/frontend/pages/config/pmm_dynamic/app.py +++ b/frontend/pages/config/pmm_dynamic/app.py @@ -13,7 +13,7 @@ from frontend.components.backtesting import backtesting_section from frontend.pages.config.pmm_dynamic.spread_and_price_multipliers import get_pmm_dynamic_multipliers from frontend.pages.config.pmm_dynamic.user_inputs import user_inputs from frontend.pages.config.utils import get_max_records, get_candles -from frontend.st_utils import initialize_st_page +from frontend.st_utils import initialize_st_page, get_backend_api_client from frontend.visualization import theme from frontend.visualization.backtesting import create_backtesting_figure from frontend.visualization.candles import get_candlestick_trace @@ -25,7 +25,7 @@ from frontend.visualization.utils import add_traces_to_fig # Initialize the Streamlit page initialize_st_page(title="PMM Dynamic", icon="👩‍🏫") -backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) +backend_api_client = get_backend_api_client() # Page content st.text("This tool will let you create a config for PMM Dynamic, backtest and upload it to the Backend API.") diff --git a/frontend/pages/config/pmm_simple/app.py b/frontend/pages/config/pmm_simple/app.py index 69d60b5..445c362 100644 --- a/frontend/pages/config/pmm_simple/app.py +++ b/frontend/pages/config/pmm_simple/app.py @@ -7,7 +7,7 @@ from frontend.components.save_config import render_save_config # Import submodules from frontend.pages.config.pmm_simple.user_inputs import user_inputs from frontend.components.backtesting import backtesting_section -from frontend.st_utils import initialize_st_page +from frontend.st_utils import initialize_st_page, get_backend_api_client from frontend.visualization.backtesting import create_backtesting_figure from frontend.visualization.executors_distribution import create_executors_distribution_traces from frontend.visualization.backtesting_metrics import render_backtesting_metrics, render_close_types, \ @@ -15,7 +15,7 @@ from frontend.visualization.backtesting_metrics import render_backtesting_metric # Initialize the Streamlit page initialize_st_page(title="PMM Simple", icon="👨‍🏫") -backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) +backend_api_client = get_backend_api_client() # Page content diff --git a/frontend/pages/config/supertrend_v1/app.py b/frontend/pages/config/supertrend_v1/app.py index 2438f66..3f00f5f 100644 --- a/frontend/pages/config/supertrend_v1/app.py +++ b/frontend/pages/config/supertrend_v1/app.py @@ -8,7 +8,7 @@ from frontend.components.config_loader import get_default_config_loader from frontend.components.save_config import render_save_config from frontend.pages.config.supertrend_v1.user_inputs import user_inputs from frontend.pages.config.utils import get_candles, get_max_records -from frontend.st_utils import initialize_st_page +from frontend.st_utils import initialize_st_page, get_backend_api_client from frontend.visualization import theme from frontend.visualization.backtesting import create_backtesting_figure from frontend.visualization.backtesting_metrics import render_backtesting_metrics, render_accuracy_metrics, \ @@ -20,7 +20,7 @@ from frontend.visualization.utils import add_traces_to_fig # Initialize the Streamlit page initialize_st_page(title="SuperTrend V1", icon="📊", initial_sidebar_state="expanded") -backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) +backend_api_client = get_backend_api_client() get_default_config_loader("supertrend_v1") # User inputs diff --git a/frontend/pages/config/xemm_controller/app.py b/frontend/pages/config/xemm_controller/app.py index 00bd029..f78194d 100644 --- a/frontend/pages/config/xemm_controller/app.py +++ b/frontend/pages/config/xemm_controller/app.py @@ -4,7 +4,7 @@ import yaml 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 +from frontend.st_utils import initialize_st_page, get_backend_api_client # Initialize the Streamlit page initialize_st_page(title="XEMM Multiple Levels", icon="⚡️") @@ -135,6 +135,6 @@ with c3: if upload_config_to_backend: - backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) + backend_api_client = get_backend_api_client() backend_api_client.add_controller_config(config) st.success("Config uploaded successfully!") \ No newline at end of file diff --git a/frontend/pages/data/download_candles/app.py b/frontend/pages/data/download_candles/app.py index ee1a7cc..d03aca0 100644 --- a/frontend/pages/data/download_candles/app.py +++ b/frontend/pages/data/download_candles/app.py @@ -3,12 +3,11 @@ from datetime import datetime, time import pandas as pd import plotly.graph_objects as go -from backend.services.backend_api_client import BackendAPIClient -from frontend.st_utils import initialize_st_page +from frontend.st_utils import initialize_st_page, get_backend_api_client # Initialize Streamlit page initialize_st_page(title="Download Candles", icon="💾") -backend_api_client = BackendAPIClient.get_instance() +backend_api_client = get_backend_api_client() c1, c2, c3, c4 = st.columns([2, 2, 2, 0.5]) with c1: diff --git a/frontend/pages/orchestration/credentials/app.py b/frontend/pages/orchestration/credentials/app.py index c719fe3..d5a9883 100644 --- a/frontend/pages/orchestration/credentials/app.py +++ b/frontend/pages/orchestration/credentials/app.py @@ -1,13 +1,13 @@ 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 +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 = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) +client = get_backend_api_client() NUM_COLUMNS = 4 diff --git a/frontend/pages/orchestration/instances/app.py b/frontend/pages/orchestration/instances/app.py index 512c65c..a9a81c1 100644 --- a/frontend/pages/orchestration/instances/app.py +++ b/frontend/pages/orchestration/instances/app.py @@ -8,7 +8,7 @@ from CONFIG import BACKEND_API_HOST, BACKEND_API_PORT from frontend.components.bot_performance_card import BotPerformanceCardV2 from frontend.components.dashboard import Dashboard from backend.services.backend_api_client import BackendAPIClient -from frontend.st_utils import initialize_st_page +from frontend.st_utils import initialize_st_page, get_backend_api_client # Constants for UI layout CARD_WIDTH = 12 @@ -38,7 +38,7 @@ def update_active_bots(api_client): initialize_st_page(title="Instances", icon="🦅") -api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) +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.") diff --git a/frontend/pages/orchestration/portfolio/app.py b/frontend/pages/orchestration/portfolio/app.py index 286bc1b..f2c7e45 100644 --- a/frontend/pages/orchestration/portfolio/app.py +++ b/frontend/pages/orchestration/portfolio/app.py @@ -1,13 +1,13 @@ 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 +from frontend.st_utils import initialize_st_page, get_backend_api_client import streamlit as st import pandas as pd initialize_st_page(title="Portfolio", icon="💰") # Page content -client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT) +client = get_backend_api_client() NUM_COLUMNS = 4 @st.cache_data From 39f23a5d85cff53ad7965c349d1fdaa15ede0d14 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Fri, 7 Jun 2024 00:29:02 +0200 Subject: [PATCH 19/53] (feat) make the keys password type --- .../pages/orchestration/credentials/app.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/frontend/pages/orchestration/credentials/app.py b/frontend/pages/orchestration/credentials/app.py index d5a9883..de59239 100644 --- a/frontend/pages/orchestration/credentials/app.py +++ b/frontend/pages/orchestration/credentials/app.py @@ -70,15 +70,16 @@ 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) -if account_name and account_name != "No accounts available" and connector_name: - st.write(f"Configuration Map for {connector_name}:") config_map = all_connector_config_map[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) - with cols[NUM_COLUMNS - 1]: - if st.button("Submit Credentials"): - response = client.add_connector_keys(account_name, connector_name, config_inputs) - st.write(response) + +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}") + +if st.button("Submit Credentials"): + response = client.add_connector_keys(account_name, connector_name, config_inputs) + st.rerun() + From 23e83d8ba13cc82f78beb759fc2f2df9ec3245ab Mon Sep 17 00:00:00 2001 From: cardosofede Date: Fri, 7 Jun 2024 16:12:30 +0200 Subject: [PATCH 20/53] (feat) handle credentials connection error --- backend/services/backend_api_client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/services/backend_api_client.py b/backend/services/backend_api_client.py index f90d931..935c387 100644 --- a/backend/services/backend_api_client.py +++ b/backend/services/backend_api_client.py @@ -1,5 +1,6 @@ import pandas as pd import requests +import streamlit as st from hummingbot.strategy_v2.models.executors_info import ExecutorInfo @@ -268,6 +269,9 @@ class BackendAPIClient: """Add connector keys.""" url = f"{self.base_url}/add-connector-keys/{account_name}/{connector_name}" response = requests.post(url, json=connector_config) + if response.status_code == 400: + st.error(response.json()["detail"]) + return return response.json() def get_accounts(self): From 7ac69bb7adc06679e2a78d91f59b0ea9c4d06679 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Fri, 7 Jun 2024 16:12:39 +0200 Subject: [PATCH 21/53] (feat) improve performance card and add logs --- frontend/components/bot_performance_card.py | 76 ++++++++++++++++----- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/frontend/components/bot_performance_card.py b/frontend/components/bot_performance_card.py index fcddf5d..fb3be1f 100644 --- a/frontend/components/bot_performance_card.py +++ b/frontend/components/bot_performance_card.py @@ -1,17 +1,17 @@ -import time - +import pandas as pd +import streamlit as st +from hummingbot.strategy_v2.models.executors import CloseType from streamlit_elements import mui -from CONFIG import BACKEND_API_HOST, BACKEND_API_PORT from frontend.components.dashboard import Dashboard -from backend.services.backend_api_client import BackendAPIClient from frontend.st_utils import get_backend_api_client TRADES_TO_SHOW = 5 -WIDE_COL_WIDTH = 250 -MEDIUM_COL_WIDTH = 170 -SMALL_COL_WIDTH = 100 +ULTRA_WIDE_COL_WIDTH = 300 +WIDE_COL_WIDTH = 160 +MEDIUM_COL_WIDTH = 140 +SMALL_COL_WIDTH = 110 backend_api_client = get_backend_api_client() @@ -27,12 +27,16 @@ def archive_bot(bot_name): class BotPerformanceCardV2(Dashboard.Item): DEFAULT_COLUMNS = [ {"field": 'id', "headerName": 'ID', "width": WIDE_COL_WIDTH}, + {"field": 'controller', "headerName": 'Controller', "width": SMALL_COL_WIDTH, "editable": False}, + {"field": 'connector', "headerName": 'Connector', "width": SMALL_COL_WIDTH, "editable": False}, + {"field": 'trading_pair', "headerName": 'Trading Pair', "width": SMALL_COL_WIDTH, "editable": False}, {"field": 'realized_pnl_quote', "headerName": 'Realized PNL ($)', "width": MEDIUM_COL_WIDTH, "editable": False}, {"field": 'unrealized_pnl_quote', "headerName": 'Unrealized PNL ($)', "width": MEDIUM_COL_WIDTH, "editable": False}, {"field": 'global_pnl_quote', "headerName": 'NET PNL ($)', "width": MEDIUM_COL_WIDTH, "editable": False}, - {"field": 'volume_traded', "headerName": 'Volume ($)', "width": MEDIUM_COL_WIDTH, "editable": False}, - {"field": 'open_order_volume', "headerName": 'Open Order Volume ($)', "width": MEDIUM_COL_WIDTH, "editable": False}, - {"field": 'imbalance', "headerName": 'Imbalance ($)', "width": MEDIUM_COL_WIDTH, "editable": False}, + {"field": 'volume_traded', "headerName": 'Volume ($)', "width": SMALL_COL_WIDTH, "editable": False}, + {"field": 'open_order_volume', "headerName": 'Liquidity Placed ($)', "width": MEDIUM_COL_WIDTH, "editable": False}, + {"field": 'imbalance', "headerName": 'Imbalance ($)', "width": SMALL_COL_WIDTH, "editable": False}, + {"field": 'close_types', "headerName": 'Close Types', "width": ULTRA_WIDE_COL_WIDTH, "editable": False} ] _active_controller_config_selected = [] _stopped_controller_config_selected = [] @@ -89,6 +93,8 @@ class BotPerformanceCardV2(Dashboard.Item): bot_data = bot_status.get("data") is_running = bot_data.get("status") == "running" performance = bot_data.get("performance") + error_logs = bot_data.get("error_logs") + general_logs = bot_data.get("general_logs") if is_running: for controller, inner_dict in performance.items(): controller_status = inner_dict.get("status") @@ -98,6 +104,9 @@ class BotPerformanceCardV2(Dashboard.Item): continue controller_performance = inner_dict.get("performance") controller_config = next((config for config in controller_configs if config.get("id") == controller), {}) + controller_name = controller_config.get("controller_name", controller) + connector_name = controller_config.get("connector_name", "NaN") + trading_pair = controller_config.get("trading_pair", "NaN") kill_switch_status = True if controller_config.get("manual_kill_switch") is True else False realized_pnl_quote = controller_performance.get("realized_pnl_quote", 0) unrealized_pnl_quote = controller_performance.get("unrealized_pnl_quote", 0) @@ -105,14 +114,25 @@ class BotPerformanceCardV2(Dashboard.Item): volume_traded = controller_performance.get("volume_traded", 0) open_order_volume = controller_performance.get("open_order_volume", 0) imbalance = controller_performance.get("imbalance", 0) + close_types = controller_performance.get("close_type_counts", {}) + tp = close_types.get("CloseType.TAKE_PROFIT", 0) + sl = close_types.get("CloseType.STOP_LOSS", 0) + time_limit = close_types.get("CloseType.TIME_LIMIT", 0) + ts = close_types.get("CloseType.TRAILING_STOP", 0) + refreshed = close_types.get("CloseType.EARLY_STOP", 0) + close_types_str = f"TP: {tp} | SL: {sl} | TS: {ts} | TL: {time_limit} | RS: {refreshed}" controller_info = { "id": controller, - "realized_pnl_quote": realized_pnl_quote, - "unrealized_pnl_quote": unrealized_pnl_quote, - "global_pnl_quote": global_pnl_quote, - "volume_traded": volume_traded, - "open_order_volume": open_order_volume, - "imbalance": imbalance, + "controller": controller_name, + "connector": connector_name, + "trading_pair": trading_pair, + "realized_pnl_quote": round(realized_pnl_quote, 2), + "unrealized_pnl_quote": round(unrealized_pnl_quote, 2), + "global_pnl_quote": round(global_pnl_quote, 2), + "volume_traded": round(volume_traded, 2), + "open_order_volume": round(open_order_volume, 2), + "imbalance": round(imbalance, 2), + "close_types": close_types_str, } if kill_switch_status: stopped_controllers_list.append(controller_info) @@ -270,6 +290,30 @@ class BotPerformanceCardV2(Dashboard.Item): sx={"width": "100%", "height": "100%"}): mui.icon.AddCircleOutline() mui.Typography("Stop") + with mui.Accordion(sx={"padding": "10px 15px 10px 15px"}): + with mui.AccordionSummary(expandIcon=mui.icon.ExpandMoreIcon()): + mui.Typography("Error Logs") + with mui.AccordionDetails(sx={"display": "flex", "flexDirection": "column"}): + if len(error_logs) > 0: + for log in error_logs: + timestamp = log.get("timestamp") + message = log.get("msg") + logger_name = log.get("logger_name") + mui.Typography(f"{timestamp} - {logger_name}: {message}") + else: + mui.Typography("No error logs available.") + with mui.Accordion(sx={"padding": "10px 15px 10px 15px"}): + with mui.AccordionSummary(expandIcon=mui.icon.ExpandMoreIcon()): + mui.Typography("General Logs") + with mui.AccordionDetails(sx={"display": "flex", "flexDirection": "column"}): + if len(general_logs) > 0: + for log in general_logs: + timestamp = pd.to_datetime(int(log.get("timestamp")), unit="s") + message = log.get("msg") + logger_name = log.get("logger_name") + mui.Typography(f"{timestamp} - {logger_name}: {message}") + else: + mui.Typography("No general logs available.") except Exception as e: print(e) with mui.Card(key=self._key, From 0ac8eddde7c1a111fd70ada042eeb52b95211fd9 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Fri, 7 Jun 2024 16:12:52 +0200 Subject: [PATCH 22/53] (feat) remove rerun from submit credentials --- frontend/pages/orchestration/credentials/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/pages/orchestration/credentials/app.py b/frontend/pages/orchestration/credentials/app.py index de59239..d8a7602 100644 --- a/frontend/pages/orchestration/credentials/app.py +++ b/frontend/pages/orchestration/credentials/app.py @@ -81,5 +81,4 @@ for i, config in enumerate(config_map): if st.button("Submit Credentials"): response = client.add_connector_keys(account_name, connector_name, config_inputs) - st.rerun() From 8a67b442aa2022c4c966912e0ea2974970ef530b Mon Sep 17 00:00:00 2001 From: cardosofede Date: Mon, 10 Jun 2024 11:51:58 +0200 Subject: [PATCH 23/53] (feat) adapt changes to config version --- frontend/components/launch_strategy_v2.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/components/launch_strategy_v2.py b/frontend/components/launch_strategy_v2.py index f0e1aee..5e09203 100644 --- a/frontend/components/launch_strategy_v2.py +++ b/frontend/components/launch_strategy_v2.py @@ -127,7 +127,13 @@ class LaunchStrategyV2(Dashboard.Item): take_profit = config.get("take_profit", 0) trailing_stop = config.get("trailing_stop", {"activation_price": 0, "trailing_delta": 0}) time_limit = config.get("time_limit", 0) - config_base, version = config["id"].split("_") + config_version = config["id"].split("_") + if len(config_version) > 1: + config_base = config_version[0] + version = config_version[1] + else: + config_base = config["id"] + version = "NaN" data.append({ "id": config["id"], "config_base": config_base, "version": version, "controller_name": config["controller_name"], "controller_type": config["controller_type"], From 8c07835f19edd3517258e2fe99f42d6ec0da3a0d Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 12 Jun 2024 11:35:09 +0200 Subject: [PATCH 24/53] (feat) abstract api calls from backend-api --- backend/services/backend_api_client.py | 116 ++++++++++++++----------- 1 file changed, 64 insertions(+), 52 deletions(-) diff --git a/backend/services/backend_api_client.py b/backend/services/backend_api_client.py index 935c387..df3ca61 100644 --- a/backend/services/backend_api_client.py +++ b/backend/services/backend_api_client.py @@ -1,3 +1,5 @@ +from typing import Optional, Dict + import pandas as pd import requests import streamlit as st @@ -13,7 +15,7 @@ class BackendAPIClient: _shared_instance = None @classmethod - def get_instance(cls, *args, **kwargs) -> "MarketsRecorder": + def get_instance(cls, *args, **kwargs) -> "BackendAPIClient": if cls._shared_instance is None: cls._shared_instance = BackendAPIClient(*args, **kwargs) return cls._shared_instance @@ -23,102 +25,112 @@ class BackendAPIClient: self.port = port self.base_url = f"http://{self.host}:{self.port}" + def post(self, endpoint: str, payload: Optional[Dict] = None, params: Optional[Dict] = None): + """ + Post request to the backend API. + :param params: + :param endpoint: + :param payload: + :return: + """ + url = f"{self.base_url}/{endpoint}" + response = requests.post(url, json=payload, params=params) + return self._process_response(response) + + def get(self, endpoint: str): + """ + Get request to the backend API. + :param endpoint: + :return: + """ + url = f"{self.base_url}/{endpoint}" + response = requests.get(url) + return self._process_response(response) + + @staticmethod + def _process_response(response): + if response.status_code == 400: + st.error(response.json()["detail"]) + return + return response.json() + def is_docker_running(self): """Check if Docker is running.""" - url = f"{self.base_url}/is-docker-running" - response = requests.get(url) - return response.json()["is_docker_running"] + endpoint = "is-docker-running" + return self.get(endpoint)["is_docker_running"] def pull_image(self, image_name: str): """Pull a Docker image.""" - url = f"{self.base_url}/pull-image/" - payload = {"image_name": image_name} - response = requests.post(url, json=payload) - return response.json() + endpoint = "pull-image" + return self.post(endpoint, payload={"image_name": image_name}) def list_available_images(self, image_name: str): """List available images by name.""" - url = f"{self.base_url}/available-images/{image_name}" - response = requests.get(url) - return response.json() + endpoint = f"available-images/{image_name}" + return self.get(endpoint) def list_active_containers(self): """List all active containers.""" - url = f"{self.base_url}/active-containers" - response = requests.get(url) - return response.json() + endpoint = "active-containers" + return self.get(endpoint) def list_exited_containers(self): """List all exited containers.""" - url = f"{self.base_url}/exited-containers" - response = requests.get(url) - return response.json() + endpoint = "exited-containers" + return self.get(endpoint) def clean_exited_containers(self): """Clean up exited containers.""" - url = f"{self.base_url}/clean-exited-containers" - response = requests.post(url) - return response.json() + endpoint = "clean-exited-containers" + return self.post(endpoint, payload=None) def remove_container(self, container_name: str, archive_locally: bool = True, s3_bucket: str = None): """Remove a specific container.""" - url = f"{self.base_url}/remove-container/{container_name}" + endpoint = f"remove-container/{container_name}" params = {"archive_locally": archive_locally} if s3_bucket: params["s3_bucket"] = s3_bucket - response = requests.post(url, params=params) - return response.json() + return self.post(endpoint, params=params) def stop_container(self, container_name: str): """Stop a specific container.""" - url = f"{self.base_url}/stop-container/{container_name}" - response = requests.post(url) - return response.json() + endpoint = f"stop-container/{container_name}" + return self.post(endpoint) def start_container(self, container_name: str): """Start a specific container.""" - url = f"{self.base_url}/start-container/{container_name}" - response = requests.post(url) - return response.json() + endpoint = f"start-container/{container_name}" + return self.post(endpoint) def create_hummingbot_instance(self, instance_config: dict): """Create a new Hummingbot instance.""" - url = f"{self.base_url}/create-hummingbot-instance" - response = requests.post(url, json=instance_config) - return response.json() + endpoint = "create-hummingbot-instance" + return self.post(endpoint, payload=instance_config) def start_bot(self, start_bot_config: dict): """Start a Hummingbot bot.""" - url = f"{self.base_url}/start-bot" - response = requests.post(url, json=start_bot_config) - return response.json() + endpoint = "start-bot" + return self.post(endpoint, payload=start_bot_config) def stop_bot(self, bot_name: str, skip_order_cancellation: bool = False, async_backend: bool = True): """Stop a Hummingbot bot.""" - url = f"{self.base_url}/stop-bot" - response = requests.post(url, json={"bot_name": bot_name, "skip_order_cancellation": skip_order_cancellation, "async_backend": async_backend}) - return response.json() + endpoint = "stop-bot" + return self.post(endpoint, payload={"bot_name": bot_name, "skip_order_cancellation": skip_order_cancellation, "async_backend": async_backend}) def import_strategy(self, strategy_config: dict): """Import a trading strategy to a bot.""" - url = f"{self.base_url}/import-strategy" - response = requests.post(url, json=strategy_config) - return response.json() + endpoint = "import-strategy" + return self.post(endpoint, payload=strategy_config) def get_bot_status(self, bot_name: str): """Get the status of a bot.""" - url = f"{self.base_url}/get-bot-status/{bot_name}" - response = requests.get(url) - if response.status_code == 200: - return response.json() - else: - return {"status": "error", "data": "Bot not found"} + endpoint = f"get-bot-status/{bot_name}" + self.get(endpoint) def get_bot_history(self, bot_name: str): """Get the historical data of a bot.""" - url = f"{self.base_url}/get-bot-history/{bot_name}" - response = requests.get(url) - return response.json() + endpoint = f"get-bot-history/{bot_name}" + return self.get(endpoint) def get_active_bots_status(self): """ @@ -286,8 +298,8 @@ class BackendAPIClient: response = requests.get(url) return response.json() - def get_all_balances(self): + def get_accounts_state(self): """Get all balances.""" - url = f"{self.base_url}/get-all-balances" + url = f"{self.base_url}/accounts-state" response = requests.get(url) return response.json() From 400e14ebe05c3962c96dfb5ac16222da43a8df42 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 12 Jun 2024 11:35:21 +0200 Subject: [PATCH 25/53] (feat) improve credentials page and add delete credential functionality --- .../pages/orchestration/credentials/app.py | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/frontend/pages/orchestration/credentials/app.py b/frontend/pages/orchestration/credentials/app.py index d8a7602..e3cbbe2 100644 --- a/frontend/pages/orchestration/credentials/app.py +++ b/frontend/pages/orchestration/credentials/app.py @@ -36,7 +36,7 @@ else: st.markdown("---") -c1, c2 = st.columns([1, 1]) +c1, c2, c3 = st.columns([1, 1, 1]) with c1: # Section to create a new account st.header("Create a New Account") @@ -55,7 +55,20 @@ with c2: if st.button("Delete Account"): if delete_account_name and delete_account_name != "No accounts available": response = client.delete_account(delete_account_name) - st.write(response) + 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.") @@ -79,6 +92,7 @@ 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}") -if st.button("Submit Credentials"): - response = client.add_connector_keys(account_name, connector_name, config_inputs) +with cols[-1]: + if st.button("Submit Credentials"): + response = client.add_connector_keys(account_name, connector_name, config_inputs) From 36165440b80b2cceba9da2e73604d21d32e2dca7 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 12 Jun 2024 11:35:29 +0200 Subject: [PATCH 26/53] (feat) improve formatting --- frontend/pages/orchestration/portfolio/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/pages/orchestration/portfolio/app.py b/frontend/pages/orchestration/portfolio/app.py index f2c7e45..f7ac9aa 100644 --- a/frontend/pages/orchestration/portfolio/app.py +++ b/frontend/pages/orchestration/portfolio/app.py @@ -10,10 +10,12 @@ initialize_st_page(title="Portfolio", icon="💰") client = get_backend_api_client() NUM_COLUMNS = 4 + @st.cache_data def get_all_balances(): return client.get_all_balances() + # Fetch all balances balances = get_all_balances() @@ -26,6 +28,7 @@ def balances_to_df(balances): data.append({"Account": account, "Exchange": exchange, "Token": token, "Amount": amount}) return pd.DataFrame(data) + df_balances = balances_to_df(balances) c1, c2 = st.columns([1, 1]) with c1: From 17329b215fd99268011b1a6ef1cd49cdc640118c Mon Sep 17 00:00:00 2001 From: cardosofede Date: Fri, 21 Jun 2024 12:11:25 +0100 Subject: [PATCH 27/53] (feat) update backend api --- backend/services/backend_api_client.py | 122 +++++++++++-------------- 1 file changed, 52 insertions(+), 70 deletions(-) diff --git a/backend/services/backend_api_client.py b/backend/services/backend_api_client.py index df3ca61..494ee99 100644 --- a/backend/services/backend_api_client.py +++ b/backend/services/backend_api_client.py @@ -137,62 +137,52 @@ class BackendAPIClient: Retrieve the cached status of all active bots. Returns a JSON response with the status and data of active bots. """ - url = f"{self.base_url}/get-active-bots-status" - response = requests.get(url) - if response.status_code == 200: - return response.json() # Successful request - else: - return {"status": "error", "data": "No active bots found"} + endpoint = "get-active-bots-status" + return self.get(endpoint) def get_all_controllers_config(self): """Get all controller configurations.""" - url = f"{self.base_url}/all-controller-configs" - response = requests.get(url) - return response.json() + endpoint = "all-controller-configs" + return self.get(endpoint) def get_available_images(self, image_name: str = "hummingbot"): """Get available images.""" - url = f"{self.base_url}/available-images/{image_name}" - response = requests.get(url) - return response.json()["available_images"] + endpoint = f"available-images/{image_name}" + return self.get(endpoint)["available_images"] def add_script_config(self, script_config: dict): """Add a new script configuration.""" - url = f"{self.base_url}/add-script-config" - response = requests.post(url, json=script_config) - return response.json() + endpoint = "add-script-config" + return self.post(endpoint, payload=script_config) def add_controller_config(self, controller_config: dict): """Add a new controller configuration.""" - url = f"{self.base_url}/add-controller-config" + endpoint = "add-controller-config" config = { "name": controller_config["id"], "content": controller_config } - response = requests.post(url, json=config) - return response.json() + return self.post(endpoint, payload=config) def delete_controller_config(self, controller_name: str): """Delete a controller configuration.""" - url = f"{self.base_url}/delete-controller-config" - response = requests.post(url, params={"config_name": controller_name}) - return response.json() + url = "delete-controller-config" + return self.post(url, params={"config_name": controller_name}) def get_real_time_candles(self, connector: str, trading_pair: str, interval: str, max_records: int): """Get candles data.""" - url = f"{self.base_url}/real-time-candles" + endpoint = "real-time-candles" payload = { "connector": connector, "trading_pair": trading_pair, "interval": interval, "max_records": max_records } - response = requests.post(url, json=payload) - return response.json() + return self.post(endpoint, payload=payload) def get_historical_candles(self, connector: str, trading_pair: str, interval: str, start_time: int, end_time: int): """Get historical candles data.""" - url = f"{self.base_url}/historical-candles" + endpoint = "historical-candles" payload = { "connector": connector, "trading_pair": trading_pair, @@ -200,12 +190,11 @@ class BackendAPIClient: "start_time": start_time, "end_time": end_time } - response = requests.post(url, json=payload) - return response.json() + return self.post(endpoint, payload=payload) def run_backtesting(self, start_time: int, end_time: int, backtesting_resolution: str, trade_cost: float, config: dict): """Run backtesting.""" - url = f"{self.base_url}/run-backtesting" + endpoint = "run-backtesting" payload = { "start_time": start_time, "end_time": end_time, @@ -213,93 +202,86 @@ class BackendAPIClient: "trade_cost": trade_cost, "config": config } - response = requests.post(url, json=payload) - backtesting_results = response.json() + backtesting_results = self.post(endpoint, payload=payload) if "error" in backtesting_results: raise Exception(backtesting_results["error"]) if "processed_data" not in backtesting_results: data = None else: data = pd.DataFrame(backtesting_results["processed_data"]) + if "executors" not in backtesting_results: + executors = [] + else: + executors = [ExecutorInfo(**executor) for executor in backtesting_results["executors"]] return { "processed_data": data, - "executors": [ExecutorInfo(**executor) for executor in backtesting_results["executors"]], + "executors": executors, "results": backtesting_results["results"] } def get_all_configs_from_bot(self, bot_name: str): """Get all configurations from a bot.""" - url = f"{self.base_url}/all-controller-configs/bot/{bot_name}" - response = requests.get(url) - return response.json() + endpoint = f"all-controller-configs/bot/{bot_name}" + return self.get(endpoint) def stop_controller_from_bot(self, bot_name: str, controller_id: str): """Stop a controller from a bot.""" + endpoint = f"update-controller-config/bot/{bot_name}/{controller_id}" config = {"manual_kill_switch": True} - url = f"{self.base_url}/update-controller-config/bot/{bot_name}/{controller_id}" - response = requests.post(url, json=config) - return response.json() + return self.post(endpoint, payload=config) def start_controller_from_bot(self, bot_name: str, controller_id: str): """Start a controller from a bot.""" + endpoint = f"update-controller-config/bot/{bot_name}/{controller_id}" config = {"manual_kill_switch": False} - url = f"{self.base_url}/update-controller-config/bot/{bot_name}/{controller_id}" - response = requests.post(url, json=config) - return response.json() + return self.post(endpoint, payload=config) def get_connector_config_map(self, connector_name: str): """Get connector configuration map.""" - url = f"{self.base_url}/connector-config-map/{connector_name}" - response = requests.get(url) - return response.json() + endpoint = f"connector-config-map/{connector_name}" + return self.get(endpoint) def get_all_connectors_config_map(self): """Get all connector configuration maps.""" - url = f"{self.base_url}/all-connectors-config-map" - response = requests.get(url) - return response.json() + endpoint = "all-connectors-config-map" + return self.get(endpoint) def add_account(self, account_name: str): """Add a new account.""" - url = f"{self.base_url}/add-account" - response = requests.post(url, params={"account_name": account_name}) - return response.json() + endpoint = "add-account" + return self.post(endpoint, params={"account_name": account_name}) def delete_account(self, account_name: str): """Delete an account.""" - url = f"{self.base_url}/delete-account/" - response = requests.post(url, params={"account_name": account_name}) - return response.json() + endpoint = "delete-account/" + return self.post(endpoint, params={"account_name": account_name}) def delete_credential(self, account_name: str, connector_name: str): """Delete credentials.""" - url = f"{self.base_url}/delete-credential/{account_name}/{connector_name}" - response = requests.post(url) - return response.json() + endpoint = f"delete-credential/{account_name}/{connector_name}" + return self.post(endpoint) def add_connector_keys(self, account_name: str, connector_name: str, connector_config: dict): """Add connector keys.""" - url = f"{self.base_url}/add-connector-keys/{account_name}/{connector_name}" - response = requests.post(url, json=connector_config) - if response.status_code == 400: - st.error(response.json()["detail"]) - return - return response.json() + endpoint = f"{self.base_url}/add-connector-keys/{account_name}/{connector_name}" + return self.post(endpoint, payload=connector_config) def get_accounts(self): """Get available credentials.""" - url = f"{self.base_url}/list-accounts" - response = requests.get(url) - return response.json() + endpoint = "list-accounts" + return self.get(endpoint) def get_credentials(self, account_name: str): """Get available credentials.""" - url = f"{self.base_url}/list-credentials/{account_name}" - response = requests.get(url) - return response.json() + endpoint = f"list-credentials/{account_name}" + return self.get(endpoint) def get_accounts_state(self): """Get all balances.""" - url = f"{self.base_url}/accounts-state" - response = requests.get(url) - return response.json() + endpoint = "accounts-state" + return self.get(endpoint) + + def get_account_state_history(self): + """Get account state history.""" + endpoint = "account-state-history" + return self.get(endpoint) From 1b4b22141c3e6a500c1c3ebffe85341bec2c7e64 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 12 Jun 2024 19:28:29 +0200 Subject: [PATCH 28/53] (feat) improve bot performance card --- frontend/components/bot_performance_card.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/components/bot_performance_card.py b/frontend/components/bot_performance_card.py index fb3be1f..e567ae3 100644 --- a/frontend/components/bot_performance_card.py +++ b/frontend/components/bot_performance_card.py @@ -295,7 +295,7 @@ class BotPerformanceCardV2(Dashboard.Item): mui.Typography("Error Logs") with mui.AccordionDetails(sx={"display": "flex", "flexDirection": "column"}): if len(error_logs) > 0: - for log in error_logs: + for log in error_logs[:50]: timestamp = log.get("timestamp") message = log.get("msg") logger_name = log.get("logger_name") @@ -307,7 +307,7 @@ class BotPerformanceCardV2(Dashboard.Item): mui.Typography("General Logs") with mui.AccordionDetails(sx={"display": "flex", "flexDirection": "column"}): if len(general_logs) > 0: - for log in general_logs: + for log in general_logs[:50]: timestamp = pd.to_datetime(int(log.get("timestamp")), unit="s") message = log.get("msg") logger_name = log.get("logger_name") From 00f9163c1560814364cbc063187be60d83de27a0 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 12 Jun 2024 19:28:44 +0200 Subject: [PATCH 29/53] (feat) remove version only --- frontend/components/config_loader.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/frontend/components/config_loader.py b/frontend/components/config_loader.py index 052594a..a586a7e 100644 --- a/frontend/components/config_loader.py +++ b/frontend/components/config_loader.py @@ -8,7 +8,7 @@ backend_api_client = get_backend_api_client() def get_default_config_loader(controller_name: str): all_configs = backend_api_client.get_all_controllers_config() - existing_configs = [config["id"].split("-")[0] for config in all_configs] + existing_configs = [config["id"].split("_")[0] for config in all_configs] default_dict = {"id": generate_random_name(existing_configs)} default_config = st.session_state.get("default_config") config_controller_name = st.session_state.get("controller_name", controller_name) @@ -22,7 +22,11 @@ def get_default_config_loader(controller_name: str): with c2: if not use_default_config: configs = [config for config in all_configs if config["controller_name"] == controller_name] - default_config = st.selectbox("Select a config", [config["id"] for config in configs]) - st.session_state["default_config"] = next((config for config in all_configs if config["id"] == default_config), None) - st.session_state["default_config"]["id"] = st.session_state["default_config"]["id"].split("_")[0] + if len(configs) > 0: + default_config = st.selectbox("Select a config", [config["id"] for config in configs]) + st.session_state["default_config"] = next((config for config in all_configs if config["id"] == default_config), None) + st.session_state["default_config"]["id"] = st.session_state["default_config"]["id"].split("_")[0] + else: + st.warning("No existing configs found for this controller.") + From f797057f5ff1fcc350efdb461a5abf9e49c56777 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 12 Jun 2024 19:28:59 +0200 Subject: [PATCH 30/53] (feat) increase the sleep time --- frontend/pages/orchestration/instances/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/pages/orchestration/instances/app.py b/frontend/pages/orchestration/instances/app.py index a9a81c1..e854da4 100644 --- a/frontend/pages/orchestration/instances/app.py +++ b/frontend/pages/orchestration/instances/app.py @@ -71,5 +71,5 @@ with elements("active_instances_board"): card(bot) while True: - time.sleep(5) + time.sleep(10) st.rerun() \ No newline at end of file From 4aa09a92914ad46e9bc9e4b752f8f1fb84296518 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 12 Jun 2024 19:29:08 +0200 Subject: [PATCH 31/53] (feat) mvp portfolio page --- frontend/pages/orchestration/portfolio/app.py | 170 ++++++++++++++---- 1 file changed, 133 insertions(+), 37 deletions(-) diff --git a/frontend/pages/orchestration/portfolio/app.py b/frontend/pages/orchestration/portfolio/app.py index f7ac9aa..2082c79 100644 --- a/frontend/pages/orchestration/portfolio/app.py +++ b/frontend/pages/orchestration/portfolio/app.py @@ -1,8 +1,8 @@ -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 import pandas as pd +import plotly.graph_objects as go +import plotly.express as px initialize_st_page(title="Portfolio", icon="💰") @@ -12,50 +12,146 @@ NUM_COLUMNS = 4 @st.cache_data -def get_all_balances(): - return client.get_all_balances() +def get_accounts_state(): + return client.get_accounts_state() -# Fetch all balances -balances = get_all_balances() +@st.cache_data +def get_account_state_history(): + return client.get_account_state_history() + # Convert balances to a DataFrame for easier manipulation -def balances_to_df(balances): +def account_state_to_df(account_state): data = [] - for account, exchanges in balances.items(): - for exchange, tokens in exchanges.items(): - for token, amount in tokens.items(): - data.append({"Account": account, "Exchange": exchange, "Token": token, "Amount": amount}) + 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) -df_balances = balances_to_df(balances) -c1, c2 = st.columns([1, 1]) -with c1: - st.header("Current Balances") -with c2: - st.header("Aggregated Balances") +# 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) -c1, c2, c3, c4 = st.columns([2.5, 1.5, 1.5, 1.1]) -with c1: - # Display balances - st.subheader("All Balances") - st.dataframe(df_balances) -with c2: - # Aggregation at the account level - account_agg = df_balances.groupby(["Account", "Token"])["Amount"].sum().reset_index() - st.subheader("Account Level") - st.dataframe(account_agg) +# Fetch account state from the backend +account_state = get_accounts_state() +account_history = get_account_state_history() -with c3: - # Aggregation at the exchange level - exchange_agg = df_balances.groupby(["Exchange", "Token"])["Amount"].sum().reset_index() - st.subheader("Exchange Level") - st.dataframe(exchange_agg) -with c4: - # Overall holdings - overall_agg = df_balances.groupby("Token")["Amount"].sum().reset_index() - st.subheader("Token Level") - st.write(overall_agg) +# 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() +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]] + +tokens_available = st.multiselect("Select Tokens", set(tokens_available), set(tokens_available)) + + +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) From 1d0059b0b4f94ae080e0c7940c4a6b5010313631 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Thu, 13 Jun 2024 11:40:58 +0200 Subject: [PATCH 32/53] (feat) fix xemm id --- frontend/pages/config/xemm_controller/app.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/frontend/pages/config/xemm_controller/app.py b/frontend/pages/config/xemm_controller/app.py index f78194d..f8431e8 100644 --- a/frontend/pages/config/xemm_controller/app.py +++ b/frontend/pages/config/xemm_controller/app.py @@ -104,11 +104,11 @@ st.plotly_chart(sell_order_fig, use_container_width=True) # Display in Streamlit c1, c2, c3 = st.columns([2, 2, 1]) with c1: - config_base = st.text_input("Config Base", value=f"xemm_{maker_connector}_{taker_connector}-{maker_trading_pair.split('-')[0]}") + config_base = st.text_input("Config Base", value=f"xemm-{maker_connector}-{taker_connector}-{maker_trading_pair.split('-')[0]}") with c2: config_tag = st.text_input("Config Tag", value="1.1") -id = f"{config_base}-{config_tag}" +id = f"{config_base}_{config_tag}" config = { "id": id.lower(), "controller_name": "xemm_multiple_levels", @@ -125,12 +125,6 @@ config = { yaml_config = yaml.dump(config, default_flow_style=False) with c3: - download_config = st.download_button( - label="Download YAML", - data=yaml_config, - file_name=f'{id.lower()}.yml', - mime='text/yaml' - ) upload_config_to_backend = st.button("Upload Config to BackendAPI") From b3c258ec174def559cf69d6925848372201365b6 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Thu, 13 Jun 2024 13:17:31 +0200 Subject: [PATCH 33/53] (feat) fix credentials endpoint --- backend/services/backend_api_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/services/backend_api_client.py b/backend/services/backend_api_client.py index 494ee99..c5de5d0 100644 --- a/backend/services/backend_api_client.py +++ b/backend/services/backend_api_client.py @@ -263,7 +263,7 @@ class BackendAPIClient: def add_connector_keys(self, account_name: str, connector_name: str, connector_config: dict): """Add connector keys.""" - endpoint = f"{self.base_url}/add-connector-keys/{account_name}/{connector_name}" + endpoint = f"add-connector-keys/{account_name}/{connector_name}" return self.post(endpoint, payload=connector_config) def get_accounts(self): From 2a9b28b8815d11d24f3ff57bab4f54d68a414765 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Thu, 13 Jun 2024 16:36:57 +0200 Subject: [PATCH 34/53] (feat) return bot status --- backend/services/backend_api_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/services/backend_api_client.py b/backend/services/backend_api_client.py index c5de5d0..af6f785 100644 --- a/backend/services/backend_api_client.py +++ b/backend/services/backend_api_client.py @@ -125,7 +125,7 @@ class BackendAPIClient: def get_bot_status(self, bot_name: str): """Get the status of a bot.""" endpoint = f"get-bot-status/{bot_name}" - self.get(endpoint) + return self.get(endpoint) def get_bot_history(self, bot_name: str): """Get the historical data of a bot.""" From 0759580d4a455e70b350efd3d627f54a1840b16b Mon Sep 17 00:00:00 2001 From: cardosofede Date: Thu, 13 Jun 2024 18:03:54 +0200 Subject: [PATCH 35/53] (feat) remove cache from accounts --- frontend/pages/orchestration/portfolio/app.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/frontend/pages/orchestration/portfolio/app.py b/frontend/pages/orchestration/portfolio/app.py index 2082c79..8bac432 100644 --- a/frontend/pages/orchestration/portfolio/app.py +++ b/frontend/pages/orchestration/portfolio/app.py @@ -11,16 +11,6 @@ client = get_backend_api_client() NUM_COLUMNS = 4 -@st.cache_data -def get_accounts_state(): - return client.get_accounts_state() - - -@st.cache_data -def get_account_state_history(): - return client.get_account_state_history() - - # Convert balances to a DataFrame for easier manipulation def account_state_to_df(account_state): data = [] @@ -61,8 +51,8 @@ def account_history_to_df(history): # Fetch account state from the backend -account_state = get_accounts_state() -account_history = get_account_state_history() +account_state = client.get_accounts_state() +account_history = client.get_account_state_history() # Display the accounts available @@ -84,7 +74,8 @@ for account in accounts: if exchange in account_state[account]: tokens_available += [info["token"] for info in account_state[account][exchange]] -tokens_available = st.multiselect("Select Tokens", set(tokens_available), set(tokens_available)) +token_options = set(tokens_available) +tokens_available = st.multiselect("Select Tokens", token_options, token_options) st.write("---") From 7f5a58c04cfd355c58da61b30e658cbbca31166c Mon Sep 17 00:00:00 2001 From: cardosofede Date: Thu, 20 Jun 2024 10:41:44 +0100 Subject: [PATCH 36/53] (feat) freeze numpy version --- environment_conda.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment_conda.yml b/environment_conda.yml index c903dd2..41f9baf 100644 --- a/environment_conda.yml +++ b/environment_conda.yml @@ -8,6 +8,7 @@ dependencies: - pip - pip: - hummingbot + - numpy==1.26.4 - streamlit==1.33.0 - watchdog - python-dotenv From 7e37f1e335d51ee222591a1b557d422f34d08008 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Tue, 25 Jun 2024 09:13:19 +0200 Subject: [PATCH 37/53] (feat) use official hummingbot image as default --- frontend/components/launch_strategy_v2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/components/launch_strategy_v2.py b/frontend/components/launch_strategy_v2.py index 5e09203..268faf4 100644 --- a/frontend/components/launch_strategy_v2.py +++ b/frontend/components/launch_strategy_v2.py @@ -32,7 +32,7 @@ class LaunchStrategyV2(Dashboard.Item): self._controller_configs_available = self._backend_api_client.get_all_controllers_config() self._controller_config_selected = None self._bot_name = None - self._image_name = "dardonacci/hummingbot:latest" + self._image_name = "hummingbot/hummingbot:latest" self._credentials = "master_account" def _set_bot_name(self, event): @@ -105,7 +105,7 @@ class LaunchStrategyV2(Dashboard.Item): available_images = self._backend_api_client.get_available_images("hummingbot") with mui.FormControl(variant="standard", sx={"width": "100%"}): mui.FormHelperText("Available Images") - with mui.Select(label="Hummingbot Image", defaultValue="dardonacci/hummingbot:latest", + with mui.Select(label="Hummingbot Image", defaultValue="hummingbot/hummingbot:latest", variant="standard", onChange=lazy(self._set_image_name)): for image in available_images: mui.MenuItem(image, value=image) From f18415531a0fc8e4b15d25d3dc3f0e1770049e7f Mon Sep 17 00:00:00 2001 From: cardosofede Date: Tue, 25 Jun 2024 09:13:26 +0200 Subject: [PATCH 38/53] (feat) use official hummingbot image as default --- frontend/components/deploy_v2_with_controllers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/deploy_v2_with_controllers.py b/frontend/components/deploy_v2_with_controllers.py index d62eaaf..ef39165 100644 --- a/frontend/components/deploy_v2_with_controllers.py +++ b/frontend/components/deploy_v2_with_controllers.py @@ -19,7 +19,7 @@ class LaunchV2WithControllers: self._controller_configs_available = self._backend_api_client.get_all_controllers_config() self._controller_config_selected = [] self._bot_name = None - self._image_name = "dardonacci/hummingbot:latest" + self._image_name = "hummingbot/hummingbot:latest" self._credentials = "master_account" def _set_bot_name(self, bot_name): From 4adb0a77721303c8b2c481d1230b9a106a2cf38f Mon Sep 17 00:00:00 2001 From: cardosofede Date: Tue, 25 Jun 2024 17:24:32 +0200 Subject: [PATCH 39/53] (feat) update bollinger v1 --- frontend/pages/config/bollinger_v1/app.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/frontend/pages/config/bollinger_v1/app.py b/frontend/pages/config/bollinger_v1/app.py index a14a4fe..f77353c 100644 --- a/frontend/pages/config/bollinger_v1/app.py +++ b/frontend/pages/config/bollinger_v1/app.py @@ -1,16 +1,10 @@ -from datetime import datetime - import streamlit as st -import pandas as pd -import yaml import pandas_ta as ta # noqa: F401 -from CONFIG import BACKEND_API_HOST, BACKEND_API_PORT -from backend.services.backend_api_client import BackendAPIClient from frontend.components.backtesting import backtesting_section from frontend.components.config_loader import get_default_config_loader from frontend.components.save_config import render_save_config -from frontend.pages.config.utils import get_max_records, get_candles +from frontend.pages.config.utils import get_candles from frontend.st_utils import initialize_st_page, get_backend_api_client from frontend.pages.config.bollinger_v1.user_inputs import user_inputs from plotly.subplots import make_subplots @@ -36,7 +30,7 @@ inputs = user_inputs() st.session_state["default_config"].update(inputs) st.write("### Visualizing Bollinger Bands and Trading Signals") -days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=3) +days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=30) # Load candle data candles = get_candles(connector_name=inputs["candles_connector"], trading_pair=inputs["candles_trading_pair"], interval=inputs["interval"], days=days_to_visualize) From 40864bb5002f192e7143b723ae141c00d90b0bf2 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Tue, 25 Jun 2024 17:24:36 +0200 Subject: [PATCH 40/53] (feat) update macd v1 --- frontend/pages/config/macd_bb_v1/app.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/frontend/pages/config/macd_bb_v1/app.py b/frontend/pages/config/macd_bb_v1/app.py index 75383e5..26fa8a5 100644 --- a/frontend/pages/config/macd_bb_v1/app.py +++ b/frontend/pages/config/macd_bb_v1/app.py @@ -1,23 +1,18 @@ import streamlit as st -import pandas as pd -import plotly.graph_objects as go -import yaml from plotly.subplots import make_subplots -from CONFIG import BACKEND_API_HOST, BACKEND_API_PORT -from backend.services.backend_api_client import BackendAPIClient from frontend.components.backtesting import backtesting_section from frontend.components.config_loader import get_default_config_loader from frontend.components.save_config import render_save_config from frontend.pages.config.macd_bb_v1.user_inputs import user_inputs -from frontend.pages.config.utils import get_candles, get_max_records +from frontend.pages.config.utils import get_candles from frontend.st_utils import initialize_st_page, get_backend_api_client from frontend.visualization import theme from frontend.visualization.backtesting import create_backtesting_figure from frontend.visualization.backtesting_metrics import render_backtesting_metrics, render_accuracy_metrics, \ render_close_types from frontend.visualization.candles import get_candlestick_trace -from frontend.visualization.indicators import get_bbands_traces, get_volume_trace, get_macd_traces +from frontend.visualization.indicators import get_bbands_traces, get_macd_traces from frontend.visualization.signals import get_macdbb_v1_signal_traces from frontend.visualization.utils import add_traces_to_fig @@ -32,7 +27,7 @@ st.session_state["default_config"].update(inputs) st.write("### Visualizing MACD Bollinger Trading Signals") -days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=3) +days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=30) # Load candle data candles = get_candles(connector_name=inputs["candles_connector"], trading_pair=inputs["candles_trading_pair"], interval=inputs["interval"], days=days_to_visualize) From 2cc52c28fd193b1b6f7c16d2ec88d7761e8cac48 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Tue, 25 Jun 2024 17:24:41 +0200 Subject: [PATCH 41/53] (feat) update pmm dynamic --- frontend/pages/config/pmm_dynamic/app.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/pages/config/pmm_dynamic/app.py b/frontend/pages/config/pmm_dynamic/app.py index 8bbbbc8..28e9509 100644 --- a/frontend/pages/config/pmm_dynamic/app.py +++ b/frontend/pages/config/pmm_dynamic/app.py @@ -2,8 +2,6 @@ import streamlit as st import plotly.graph_objects as go from plotly.subplots import make_subplots -from backend.services.backend_api_client import BackendAPIClient -from CONFIG import BACKEND_API_HOST, BACKEND_API_PORT from frontend.components.config_loader import get_default_config_loader from frontend.components.executors_distribution import get_executors_distribution_inputs from frontend.components.save_config import render_save_config @@ -12,7 +10,7 @@ from frontend.components.save_config import render_save_config from frontend.components.backtesting import backtesting_section from frontend.pages.config.pmm_dynamic.spread_and_price_multipliers import get_pmm_dynamic_multipliers from frontend.pages.config.pmm_dynamic.user_inputs import user_inputs -from frontend.pages.config.utils import get_max_records, get_candles +from frontend.pages.config.utils import get_candles from frontend.st_utils import initialize_st_page, get_backend_api_client from frontend.visualization import theme from frontend.visualization.backtesting import create_backtesting_figure @@ -35,7 +33,7 @@ inputs = user_inputs() st.write("### Visualizing MACD and NATR indicators for PMM Dynamic") st.text("The MACD is used to shift the mid price and the NATR to make the spreads dynamic. " "In the order distributions graph, we are going to see the values of the orders affected by the average NATR") -days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=3) +days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=30) # Load candle data candles = get_candles(connector_name=inputs["candles_connector"], trading_pair=inputs["candles_trading_pair"], interval=inputs["interval"], days=days_to_visualize) with st.expander("Visualizing PMM Dynamic Indicators", expanded=True): From 4abdd0ddb4b291698e890b02623db7dd29f6933c Mon Sep 17 00:00:00 2001 From: cardosofede Date: Tue, 25 Jun 2024 17:24:52 +0200 Subject: [PATCH 42/53] (feat) update supertrend --- frontend/pages/config/supertrend_v1/app.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/pages/config/supertrend_v1/app.py b/frontend/pages/config/supertrend_v1/app.py index 3f00f5f..8cc8c8e 100644 --- a/frontend/pages/config/supertrend_v1/app.py +++ b/frontend/pages/config/supertrend_v1/app.py @@ -1,13 +1,11 @@ import streamlit as st from plotly.subplots import make_subplots -from CONFIG import BACKEND_API_HOST, BACKEND_API_PORT -from backend.services.backend_api_client import BackendAPIClient from frontend.components.backtesting import backtesting_section from frontend.components.config_loader import get_default_config_loader from frontend.components.save_config import render_save_config from frontend.pages.config.supertrend_v1.user_inputs import user_inputs -from frontend.pages.config.utils import get_candles, get_max_records +from frontend.pages.config.utils import get_candles from frontend.st_utils import initialize_st_page, get_backend_api_client from frontend.visualization import theme from frontend.visualization.backtesting import create_backtesting_figure @@ -28,7 +26,7 @@ inputs = user_inputs() st.session_state["default_config"].update(inputs) st.write("### Visualizing Supertrend Trading Signals") -days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=3) +days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=30) # Load candle data candles = get_candles(connector_name=inputs["candles_connector"], trading_pair=inputs["candles_trading_pair"], interval=inputs["interval"], days=days_to_visualize) From 4d38e0ee976fdc6e55fd29de24a30e7845d18bab Mon Sep 17 00:00:00 2001 From: cardosofede Date: Tue, 25 Jun 2024 17:43:41 +0200 Subject: [PATCH 43/53] (feat) move to 7 days --- frontend/pages/config/bollinger_v1/app.py | 2 +- frontend/pages/config/macd_bb_v1/app.py | 2 +- frontend/pages/config/pmm_dynamic/app.py | 2 +- frontend/pages/config/supertrend_v1/app.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/pages/config/bollinger_v1/app.py b/frontend/pages/config/bollinger_v1/app.py index f77353c..b0bbdd0 100644 --- a/frontend/pages/config/bollinger_v1/app.py +++ b/frontend/pages/config/bollinger_v1/app.py @@ -30,7 +30,7 @@ inputs = user_inputs() st.session_state["default_config"].update(inputs) st.write("### Visualizing Bollinger Bands and Trading Signals") -days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=30) +days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=7) # Load candle data candles = get_candles(connector_name=inputs["candles_connector"], trading_pair=inputs["candles_trading_pair"], interval=inputs["interval"], days=days_to_visualize) diff --git a/frontend/pages/config/macd_bb_v1/app.py b/frontend/pages/config/macd_bb_v1/app.py index 26fa8a5..3857587 100644 --- a/frontend/pages/config/macd_bb_v1/app.py +++ b/frontend/pages/config/macd_bb_v1/app.py @@ -27,7 +27,7 @@ st.session_state["default_config"].update(inputs) st.write("### Visualizing MACD Bollinger Trading Signals") -days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=30) +days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=7) # Load candle data candles = get_candles(connector_name=inputs["candles_connector"], trading_pair=inputs["candles_trading_pair"], interval=inputs["interval"], days=days_to_visualize) diff --git a/frontend/pages/config/pmm_dynamic/app.py b/frontend/pages/config/pmm_dynamic/app.py index 28e9509..f154444 100644 --- a/frontend/pages/config/pmm_dynamic/app.py +++ b/frontend/pages/config/pmm_dynamic/app.py @@ -33,7 +33,7 @@ inputs = user_inputs() st.write("### Visualizing MACD and NATR indicators for PMM Dynamic") st.text("The MACD is used to shift the mid price and the NATR to make the spreads dynamic. " "In the order distributions graph, we are going to see the values of the orders affected by the average NATR") -days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=30) +days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=7) # Load candle data candles = get_candles(connector_name=inputs["candles_connector"], trading_pair=inputs["candles_trading_pair"], interval=inputs["interval"], days=days_to_visualize) with st.expander("Visualizing PMM Dynamic Indicators", expanded=True): diff --git a/frontend/pages/config/supertrend_v1/app.py b/frontend/pages/config/supertrend_v1/app.py index 8cc8c8e..83e6633 100644 --- a/frontend/pages/config/supertrend_v1/app.py +++ b/frontend/pages/config/supertrend_v1/app.py @@ -26,7 +26,7 @@ inputs = user_inputs() st.session_state["default_config"].update(inputs) st.write("### Visualizing Supertrend Trading Signals") -days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=30) +days_to_visualize = st.number_input("Days to Visualize", min_value=1, max_value=365, value=7) # Load candle data candles = get_candles(connector_name=inputs["candles_connector"], trading_pair=inputs["candles_trading_pair"], interval=inputs["interval"], days=days_to_visualize) From a344594e5d7e47c2ce4465747eaed260e03b75e0 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Tue, 25 Jun 2024 19:26:52 +0200 Subject: [PATCH 44/53] (fix) delay on controller name change --- frontend/components/config_loader.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/components/config_loader.py b/frontend/components/config_loader.py index a586a7e..89976cd 100644 --- a/frontend/components/config_loader.py +++ b/frontend/components/config_loader.py @@ -11,8 +11,7 @@ def get_default_config_loader(controller_name: str): existing_configs = [config["id"].split("_")[0] for config in all_configs] default_dict = {"id": generate_random_name(existing_configs)} default_config = st.session_state.get("default_config") - config_controller_name = st.session_state.get("controller_name", controller_name) - st.write(f"controller_name: {controller_name} | config_controller_name: {config_controller_name}") + config_controller_name = default_config.get("controller_name", controller_name) if default_config is None or controller_name != config_controller_name: st.session_state["default_config"] = default_dict with st.expander("Configurations", expanded=True): From 37cf4de8c334c8b0ee1a773faf8b3be9126ec052 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Tue, 25 Jun 2024 19:42:50 +0200 Subject: [PATCH 45/53] (fix) add check on accounts length --- frontend/pages/orchestration/portfolio/app.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/pages/orchestration/portfolio/app.py b/frontend/pages/orchestration/portfolio/app.py index 8bac432..263bd6a 100644 --- a/frontend/pages/orchestration/portfolio/app.py +++ b/frontend/pages/orchestration/portfolio/app.py @@ -53,7 +53,9 @@ def account_history_to_df(history): # 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())) From d07bf9233e2cbd7b801d561ab559d13e5521a479 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Tue, 25 Jun 2024 19:44:58 +0200 Subject: [PATCH 46/53] (fix) avoid whitespaces --- frontend/pages/orchestration/credentials/app.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/pages/orchestration/credentials/app.py b/frontend/pages/orchestration/credentials/app.py index e3cbbe2..7c98da0 100644 --- a/frontend/pages/orchestration/credentials/app.py +++ b/frontend/pages/orchestration/credentials/app.py @@ -43,7 +43,13 @@ with c1: new_account_name = st.text_input("New Account Name") if st.button("Create Account"): if new_account_name: - response = client.add_account(new_account_name) + if new_account_name in accounts: + st.warning(f"Account {new_account_name} already exists.") + st.stop() + elif new_account_name == "": + st.warning("Please enter a valid account name.") + st.stop() + response = client.add_account(new_account_name.strip()) st.write(response) else: st.write("Please enter an account name.") From ffd3cd93422fb688700bd5d0af27cb07b6f1b7ff Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 26 Jun 2024 00:29:24 +0200 Subject: [PATCH 47/53] (feat) improve accounts name --- backend/services/backend_api_client.py | 2 +- frontend/pages/orchestration/credentials/app.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/services/backend_api_client.py b/backend/services/backend_api_client.py index af6f785..9bc128b 100644 --- a/backend/services/backend_api_client.py +++ b/backend/services/backend_api_client.py @@ -253,7 +253,7 @@ class BackendAPIClient: def delete_account(self, account_name: str): """Delete an account.""" - endpoint = "delete-account/" + endpoint = "delete-account" return self.post(endpoint, params={"account_name": account_name}) def delete_credential(self, account_name: str, connector_name: str): diff --git a/frontend/pages/orchestration/credentials/app.py b/frontend/pages/orchestration/credentials/app.py index 7c98da0..8ff1eac 100644 --- a/frontend/pages/orchestration/credentials/app.py +++ b/frontend/pages/orchestration/credentials/app.py @@ -42,6 +42,7 @@ with c1: 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.strip() if new_account_name: if new_account_name in accounts: st.warning(f"Account {new_account_name} already exists.") @@ -49,7 +50,7 @@ with c1: elif new_account_name == "": st.warning("Please enter a valid account name.") st.stop() - response = client.add_account(new_account_name.strip()) + response = client.add_account(new_account_name) st.write(response) else: st.write("Please enter an account name.") From 485045323fe35fd0011a9c8ede348625711c1dd2 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 26 Jun 2024 00:29:34 +0200 Subject: [PATCH 48/53] (feat) manage missing config data --- frontend/components/config_loader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/components/config_loader.py b/frontend/components/config_loader.py index 89976cd..03a74d9 100644 --- a/frontend/components/config_loader.py +++ b/frontend/components/config_loader.py @@ -10,8 +10,8 @@ def get_default_config_loader(controller_name: str): all_configs = backend_api_client.get_all_controllers_config() existing_configs = [config["id"].split("_")[0] for config in all_configs] default_dict = {"id": generate_random_name(existing_configs)} - default_config = st.session_state.get("default_config") - config_controller_name = default_config.get("controller_name", controller_name) + default_config = st.session_state.get("default_config", default_dict) + config_controller_name = default_config.get("controller_name") if default_config is None or controller_name != config_controller_name: st.session_state["default_config"] = default_dict with st.expander("Configurations", expanded=True): From da498f7696680fcc22c8c8ee90efec603ee90281 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 26 Jun 2024 00:33:01 +0200 Subject: [PATCH 49/53] (feat) replace whitespaces with underscore --- frontend/pages/orchestration/credentials/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/pages/orchestration/credentials/app.py b/frontend/pages/orchestration/credentials/app.py index 8ff1eac..5c3328c 100644 --- a/frontend/pages/orchestration/credentials/app.py +++ b/frontend/pages/orchestration/credentials/app.py @@ -42,7 +42,7 @@ with c1: 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.strip() + 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.") From 5eef21d11ac85d916ffb707fdc7edeccea8428ac Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 26 Jun 2024 00:36:08 +0200 Subject: [PATCH 50/53] (feat) avoid empty strs --- frontend/pages/orchestration/credentials/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/pages/orchestration/credentials/app.py b/frontend/pages/orchestration/credentials/app.py index 5c3328c..2806d54 100644 --- a/frontend/pages/orchestration/credentials/app.py +++ b/frontend/pages/orchestration/credentials/app.py @@ -47,7 +47,7 @@ with c1: if new_account_name in accounts: st.warning(f"Account {new_account_name} already exists.") st.stop() - elif new_account_name == "": + 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) From fd6720012a56f288035053908df855ff6fccf101 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 26 Jun 2024 13:29:30 +0200 Subject: [PATCH 51/53] (feat) remove default config after saving --- frontend/components/save_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/save_config.py b/frontend/components/save_config.py index d116dd1..a23dc80 100644 --- a/frontend/components/save_config.py +++ b/frontend/components/save_config.py @@ -27,5 +27,5 @@ def render_save_config(config_base_default: str, config_data: dict): if upload_config_to_backend: config_data["id"] = f"{config_base}_{config_tag}" backend_api_client.add_controller_config(config_data) - st.session_state["default_config"] = None + st.session_state.pop("default_config") st.success("Config uploaded successfully!") From db0e640693ff344d97f05f646e133e172fa71b26 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 26 Jun 2024 14:47:15 +0200 Subject: [PATCH 52/53] (feat) add condition to check available exchanges --- frontend/pages/orchestration/portfolio/app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/pages/orchestration/portfolio/app.py b/frontend/pages/orchestration/portfolio/app.py index 263bd6a..27d8607 100644 --- a/frontend/pages/orchestration/portfolio/app.py +++ b/frontend/pages/orchestration/portfolio/app.py @@ -67,6 +67,10 @@ if len(accounts) == 0: 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 From ce337ca4160849cab37e48c419e86cee4551a1b0 Mon Sep 17 00:00:00 2001 From: cardosofede Date: Wed, 26 Jun 2024 18:51:09 +0200 Subject: [PATCH 53/53] (feat) add confirmation of successful keys added --- frontend/pages/orchestration/credentials/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/pages/orchestration/credentials/app.py b/frontend/pages/orchestration/credentials/app.py index 2806d54..666f054 100644 --- a/frontend/pages/orchestration/credentials/app.py +++ b/frontend/pages/orchestration/credentials/app.py @@ -102,4 +102,5 @@ for i, config in enumerate(config_map): 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