diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dc82c98b..2afdc300 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,33 +1,33 @@ repos: -- repo: https://github.com/pre-commit/pre-commit-hooks + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.3.0 hooks: - - id: detect-private-key + - id: detect-private-key -- repo: https://github.com/pre-commit/mirrors-eslint + - repo: https://github.com/pre-commit/mirrors-eslint rev: v8.10.0 hooks: - - id: eslint - files: \.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx + - id: eslint + files: \.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx types: [file] -- repo: https://github.com/CoinAlpha/git-hooks + - repo: https://github.com/CoinAlpha/git-hooks rev: 78f0683233a09c68a072fd52740d32c0376d4f0f hooks: - - id: detect-wallet-private-key + - id: detect-wallet-private-key types: [file] exclude: .json -- repo: https://github.com/pycqa/isort + - repo: https://github.com/pycqa/isort rev: 5.12.0 hooks: - id: isort files: "\\.(py)$" args: [--settings-path=pyproject.toml] -- repo: https://github.com/pycqa/flake8 - rev: 3.9.2 + - repo: https://github.com/pycqa/flake8 + rev: 7.0.0 hooks: - id: flake8 - additional_dependencies: ['flake8'] + additional_dependencies: ["flake8"] args: [--max-line-length=130] diff --git a/backend/services/backend_api_client.py b/backend/services/backend_api_client.py index ba9bdba4..9f38e1e9 100644 --- a/backend/services/backend_api_client.py +++ b/backend/services/backend_api_client.py @@ -39,7 +39,7 @@ def post(self, endpoint: str, payload: Optional[Dict] = None, params: Optional[D response = requests.post(url, json=payload, params=params, auth=self.auth) return self._process_response(response) - def get(self, endpoint: str): + def get(self, endpoint: str) -> Any: """ Get request to the backend API. :param endpoint: @@ -146,7 +146,7 @@ def get_active_bots_status(self): endpoint = "get-active-bots-status" return self.get(endpoint) - def get_all_controllers_config(self): + def get_all_controllers_config(self) -> List[dict]: """Get all controller configurations.""" endpoint = "all-controller-configs" return self.get(endpoint) diff --git a/environment_conda.yml b/environment_conda.yml index a2b078a4..e62c4dd0 100644 --- a/environment_conda.yml +++ b/environment_conda.yml @@ -9,7 +9,7 @@ dependencies: - pip: - hummingbot - pydantic - - streamlit==1.33.0 + - streamlit==1.40.0 - watchdog - python-dotenv - plotly==5.24.1 diff --git a/frontend/pages/landing.py b/frontend/pages/landing.py new file mode 100644 index 00000000..e63fa3a5 --- /dev/null +++ b/frontend/pages/landing.py @@ -0,0 +1,17 @@ +import streamlit as st + +# readme section +st.markdown("# ๐Ÿ“Š Hummingbot Dashboard") +st.markdown("Hummingbot Dashboard is an open source application that helps you create, backtest, and optimize " + "various types of algo trading strategies. Afterwards, you can deploy them as " + "[Hummingbot](http://hummingbot.org)") +st.write("---") +st.header("Watch the Hummingbot Dashboard Tutorial!") +st.video("https://youtu.be/7eHiMPRBQLQ?si=PAvCq0D5QDZz1h1D") +st.header("Feedback and issues") +st.write( + "Please give us feedback in the **#dashboard** channel of the " + "[hummingbot discord](https://discord.gg/hummingbot)! ๐Ÿ™") +st.write( + "If you encounter any bugs or have suggestions for improvement, please create an issue in the " + "[hummingbot dashboard github](https://github.com/hummingbot/dashboard).") diff --git a/frontend/pages/permissions.py b/frontend/pages/permissions.py index 41fcc43b..5fbdba75 100644 --- a/frontend/pages/permissions.py +++ b/frontend/pages/permissions.py @@ -1,35 +1,39 @@ -from st_pages import Page, Section +import streamlit as st def main_page(): - return [Page("main.py", "Hummingbot Dashboard", "๐Ÿ“Š")] + return [st.Page("frontend/pages/landing.py", title="Hummingbot Dashboard", icon="๐Ÿ“Š", url_path="landing")] def public_pages(): - return [ - Section("Config Generator", "๐ŸŽ›๏ธ"), - Page("frontend/pages/config/grid_strike/app.py", "Grid Strike", "๐ŸŽณ"), - Page("frontend/pages/config/pmm_simple/app.py", "PMM Simple", "๐Ÿ‘จโ€๐Ÿซ"), - Page("frontend/pages/config/pmm_dynamic/app.py", "PMM Dynamic", "๐Ÿ‘ฉโ€๐Ÿซ"), - Page("frontend/pages/config/dman_maker_v2/app.py", "D-Man Maker V2", "๐Ÿค–"), - Page("frontend/pages/config/bollinger_v1/app.py", "Bollinger V1", "๐Ÿ“ˆ"), - Page("frontend/pages/config/macd_bb_v1/app.py", "MACD_BB V1", "๐Ÿ“Š"), - Page("frontend/pages/config/supertrend_v1/app.py", "SuperTrend V1", "๐Ÿ‘จโ€๐Ÿ”ฌ"), - Page("frontend/pages/config/xemm_controller/app.py", "XEMM Controller", "โšก๏ธ"), - Section("Data", "๐Ÿ’พ"), - Page("frontend/pages/data/download_candles/app.py", "Download Candles", "๐Ÿ’น"), - Section("Community Pages", "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ"), - Page("frontend/pages/data/token_spreads/app.py", "Token Spreads", "๐Ÿง™"), - Page("frontend/pages/data/tvl_vs_mcap/app.py", "TVL vs Market Cap", "๐Ÿฆ‰"), - Page("frontend/pages/performance/bot_performance/app.py", "Strategy Performance", "๐Ÿ“ˆ"), - ] + return { + "Config Generator": [ + st.Page("frontend/pages/config/grid_strike/app.py", title="Grid Strike", icon="๐ŸŽณ", url_path="grid_strike"), + st.Page("frontend/pages/config/pmm_simple/app.py", title="PMM Simple", icon="๐Ÿ‘จโ€๐Ÿซ", url_path="pmm_simple"), + st.Page("frontend/pages/config/pmm_dynamic/app.py", title="PMM Dynamic", icon="๐Ÿ‘ฉโ€๐Ÿซ", url_path="pmm_dynamic"), + st.Page("frontend/pages/config/dman_maker_v2/app.py", title="D-Man Maker V2", icon="๐Ÿค–", url_path="dman_maker_v2"), + st.Page("frontend/pages/config/bollinger_v1/app.py", title="Bollinger V1", icon="๐Ÿ“ˆ", url_path="bollinger_v1"), + st.Page("frontend/pages/config/macd_bb_v1/app.py", title="MACD_BB V1", icon="๐Ÿ“Š", url_path="macd_bb_v1"), + st.Page("frontend/pages/config/supertrend_v1/app.py", title="SuperTrend V1", icon="๐Ÿ‘จโ€๐Ÿ”ฌ", url_path="supertrend_v1"), + st.Page("frontend/pages/config/xemm_controller/app.py", title="XEMM Controller", icon="โšก๏ธ", url_path="xemm_controller"), + ], + "Data": [ + st.Page("frontend/pages/data/download_candles/app.py", title="Download Candles", icon="๐Ÿ’น", url_path="download_candles"), + ], + "Community Pages": [ + st.Page("frontend/pages/data/token_spreads/app.py", title="Token Spreads", icon="๐Ÿง™", url_path="token_spreads"), + st.Page("frontend/pages/data/tvl_vs_mcap/app.py", title="TVL vs Market Cap", icon="๐Ÿฆ‰", url_path="tvl_vs_mcap"), + st.Page("frontend/pages/performance/bot_performance/app.py", title="Strategy Performance", icon="๐Ÿ“ˆ", url_path="bot_performance"), + ] + } def private_pages(): - return [ - Section("Bot Orchestration", "๐Ÿ™"), - Page("frontend/pages/orchestration/instances/app.py", "Instances", "๐Ÿฆ…"), - 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", "๐Ÿ’ฐ"), - ] + return { + "Bot Orchestration": [ + st.Page("frontend/pages/orchestration/instances/app.py", title="Instances", icon="๐Ÿฆ…", url_path="instances"), + st.Page("frontend/pages/orchestration/launch_bot_v2/app.py", title="Deploy V2", icon="๐Ÿš€", url_path="launch_bot_v2"), + st.Page("frontend/pages/orchestration/credentials/app.py", title="Credentials", icon="๐Ÿ”‘", url_path="credentials"), + st.Page("frontend/pages/orchestration/portfolio/app.py", title="Portfolio", icon="๐Ÿ’ฐ", url_path="portfolio"), + ] + } diff --git a/frontend/st_utils.py b/frontend/st_utils.py index 9b667ed9..4a16b410 100644 --- a/frontend/st_utils.py +++ b/frontend/st_utils.py @@ -1,33 +1,46 @@ import inspect import os.path from pathlib import Path +from typing import Optional, Union import pandas as pd import streamlit as st import streamlit_authenticator as stauth import yaml -from st_pages import add_page_title, show_pages +from streamlit.commands.page_config import InitialSideBarState, Layout from yaml import SafeLoader from CONFIG import AUTH_SYSTEM_ENABLED from frontend.pages.permissions import main_page, private_pages, public_pages -def initialize_st_page(title: str, icon: str, layout="wide", initial_sidebar_state="expanded"): +def initialize_st_page(title: str, icon: str, layout: Layout = 'wide', initial_sidebar_state: InitialSideBarState = "expanded"): st.set_page_config( page_title=title, page_icon=icon, layout=layout, initial_sidebar_state=initial_sidebar_state ) - caller_frame = inspect.currentframe().f_back - - add_page_title(layout=layout, initial_sidebar_state=initial_sidebar_state) + + # Add page title + st.title(title) + + # Get caller frame info safely + frame: Optional[Union[inspect.FrameInfo, inspect.Traceback]] = None + try: + caller_frame = inspect.currentframe() + if caller_frame is not None: + caller_frame = caller_frame.f_back + if caller_frame is not None: + frame = inspect.getframeinfo(caller_frame) + except Exception: + pass - current_directory = Path(os.path.dirname(inspect.getframeinfo(caller_frame).filename)) - readme_path = current_directory / "README.md" - with st.expander("About This Page"): - st.write(readme_path.read_text()) + if frame is not None: + current_directory = Path(os.path.dirname(frame.filename)) + readme_path = current_directory / "README.md" + with st.expander("About This Page"): + st.write(readme_path.read_text()) def download_csv_button(df: pd.DataFrame, filename: str, key: str): @@ -87,7 +100,11 @@ def get_backend_api_client(): def auth_system(): if not AUTH_SYSTEM_ENABLED: - show_pages(main_page() + private_pages() + public_pages()) + return { + "Main": main_page(), + **private_pages(), + **public_pages(), + } else: with open('credentials.yml') as file: config = yaml.load(file, Loader=SafeLoader) @@ -99,13 +116,22 @@ def auth_system(): config['cookie']['key'], config['cookie']['expiry_days'], ) - show_pages(main_page() + public_pages()) + # Show only public pages for non-authenticated users st.session_state.authenticator.login() if st.session_state["authentication_status"] is False: st.error('Username/password is incorrect') elif st.session_state["authentication_status"] is None: st.warning('Please enter your username and password') + return { + "Main": main_page(), + **public_pages() + } else: st.session_state.authenticator.logout(location="sidebar") st.sidebar.write(f'Welcome *{st.session_state["name"]}*') - show_pages(main_page() + private_pages() + public_pages()) + # Show all pages for authenticated users + return { + "Main": main_page(), + **private_pages(), + **public_pages(), + } diff --git a/main.py b/main.py index 98d20c98..298ce07f 100644 --- a/main.py +++ b/main.py @@ -3,23 +3,76 @@ from frontend.st_utils import auth_system +def patch_modules_streamlit_elements(file_path: str, old_line: str, new_line: str): + import os + + import streamlit_elements + + relative_file_path = "core/callback.py" + library_root = list(streamlit_elements.__path__)[0] + file_path = os.path.join(library_root, relative_file_path) + + with open(file_path, "r") as file: + lines = file.readlines() + + is_changed = False + for index, line in enumerate(lines): + if old_line in line: + print(f"Replacing line {index + 1} in {file_path}") + lines[index] = line.replace(old_line, new_line) + is_changed = True + + if is_changed: + with open(file_path, "w") as file: + file.writelines(lines) + import importlib + importlib.reload(streamlit_elements) + + return True + +def patch_streamlit_elements(): + # # fix 1.34.0 + # patch_modules_streamlit_elements( + # "core/callback.py", + # "from streamlit.components.v1 import components", + # "from streamlit.components.v1 import custom_component as components\n", + # ) + + + #fix 1.40.0 + patch_modules_streamlit_elements( + "core/callback.py", + ' user_key = kwargs.get("user_key", None)\n', + """ + try: + user_key = None + new_callback_data = kwargs[ + "ctx" + ].session_state._state._new_session_state.get( + "streamlit_elements.core.frame.elements_frame", None + ) + if new_callback_data is not None: + user_key = new_callback_data._key + except: + user_key = None + """.rstrip() + + "\n", + ) + + +if __name__ == "__main__": + patch_streamlit_elements() + def main(): - # readme section - st.markdown("# ๐Ÿ“Š Hummingbot Dashboard") - st.markdown("Hummingbot Dashboard is an open source application that helps you create, backtest, and optimize " - "various types of algo trading strategies. Afterwards, you can deploy them as " - "[Hummingbot](http://hummingbot.org)") - st.write("---") - st.header("Watch the Hummingbot Dashboard Tutorial!") - st.video("https://youtu.be/7eHiMPRBQLQ?si=PAvCq0D5QDZz1h1D") - st.header("Feedback and issues") - st.write( - "Please give us feedback in the **#dashboard** channel of the " - "[hummingbot discord](https://discord.gg/hummingbot)! ๐Ÿ™") - st.write( - "If you encounter any bugs or have suggestions for improvement, please create an issue in the " - "[hummingbot dashboard github](https://github.com/hummingbot/dashboard).") - - -auth_system() -main() + # Get the navigation structure based on auth state + pages = auth_system() + + # Set up navigation once + pg = st.navigation(pages) + + # Run the selected page + pg.run() + + +if __name__ == "__main__": + main()