Merge branch 'main' into feat/trailing_stop_backtest

This commit is contained in:
Peter Willemsen
2024-07-01 20:14:43 +02:00
25 changed files with 571 additions and 323 deletions

View File

@@ -1,5 +1,8 @@
from typing import Optional, Dict
import pandas as pd
import requests
import streamlit as st
from hummingbot.strategy_v2.models.executors_info import ExecutorInfo
@@ -12,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
@@ -22,158 +25,164 @@ 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()
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}"
return 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):
"""
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 = "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,
@@ -181,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,
@@ -194,90 +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)
return response.json()
endpoint = f"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_all_balances(self):
def get_accounts_state(self):
"""Get all balances."""
url = f"{self.base_url}/get-all-balances"
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)

View File

@@ -8,6 +8,7 @@ dependencies:
- pip
- pip:
- hummingbot
- numpy==1.26.4
- streamlit==1.33.0
- watchdog
- python-dotenv

View File

@@ -1,17 +1,18 @@
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
backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT)
ULTRA_WIDE_COL_WIDTH = 300
WIDE_COL_WIDTH = 160
MEDIUM_COL_WIDTH = 140
SMALL_COL_WIDTH = 110
backend_api_client = get_backend_api_client()
def stop_bot(bot_name):
@@ -26,19 +27,23 @@ 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 = []
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
@@ -88,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")
@@ -97,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)
@@ -104,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)
@@ -269,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[:50]:
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[:50]:
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,

View File

@@ -1,17 +1,31 @@
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):
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)}
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):
c1, c2 = st.columns(2)
with c1:
use_default_config = st.checkbox("Use default config", value=True)
with c2:
if not use_default_config:
configs = [config for config in all_configs if config["controller_name"] == controller_name]
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.")

View File

@@ -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,11 +15,11 @@ 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
self._image_name = "dardonacci/hummingbot:latest"
self._image_name = "hummingbot/hummingbot:latest"
self._credentials = "master_account"
def _set_bot_name(self, bot_name):

View File

@@ -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)

View File

@@ -6,12 +6,14 @@ 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):
DEFAULT_ROWS = []
DEFAULT_COLUMNS = [
{"field": 'id', "headerName": 'ID', "width": 230},
{"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, },
@@ -26,11 +28,11 @@ 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
self._image_name = "dardonacci/hummingbot:latest"
self._image_name = "hummingbot/hummingbot:latest"
self._credentials = "master_account"
def _set_bot_name(self, event):
@@ -79,18 +81,34 @@ 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):
mui.TextField(label="Instance Name", variant="outlined", onChange=lazy(self._set_bot_name),
sx={"width": "100%"})
with mui.Grid(item=True, xs=4):
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="hummingbot/hummingbot:latest",
variant="standard", onChange=lazy(self._set_image_name)):
for image in available_images:
mui.MenuItem(image, value=image)
with mui.Grid(item=True, xs=4):
available_credentials = self._backend_api_client.get_accounts()
with mui.FormControl(variant="standard", sx={"width": "100%"}):
@@ -99,24 +117,6 @@ class LaunchStrategyV2(Dashboard.Item):
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%"})
with mui.Grid(item=True, xs=4):
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",
variant="standard", onChange=lazy(self._set_image_name)):
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")
all_controllers_config = self._backend_api_client.get_all_controllers_config()
data = []
for config in all_controllers_config:
@@ -127,28 +127,47 @@ 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_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"],
"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):
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 +175,6 @@ class LaunchStrategyV2(Dashboard.Item):
rowsPerPageOptions=[15],
checkboxSelection=True,
disableSelectionOnClick=True,
disableColumnResize=False,
onSelectionModelChange=self._handle_row_selection,
)

View File

@@ -2,21 +2,30 @@ 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(controller_name: str, config_data: dict):
def render_save_config(config_base_default: str, config_data: dict):
st.write("### Upload Config to BackendAPI")
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]
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}"
backend_api_client = BackendAPIClient.get_instance(host=BACKEND_API_HOST, port=BACKEND_API_PORT)
config_data["id"] = f"{config_base}_{config_tag}"
backend_api_client.add_controller_config(config_data)
st.session_state.pop("default_config")
st.success("Config uploaded successfully!")

View File

@@ -1,17 +1,11 @@
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.st_utils import initialize_st_page
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
@@ -26,17 +20,17 @@ 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.")
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)
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)
@@ -68,4 +62,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"])

View File

@@ -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
@@ -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)
render_save_config(st.session_state["default_config"]["id"], st.session_state["default_config"])

View File

@@ -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!")

View File

@@ -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!")

View File

@@ -1,37 +1,33 @@
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.st_utils import initialize_st_page
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
# 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
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)
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)
@@ -64,4 +60,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"])

View File

@@ -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,8 +10,8 @@ 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.st_utils import initialize_st_page
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.candles import get_candlestick_trace
@@ -25,7 +23,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.")
@@ -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=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):
@@ -55,12 +53,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 +82,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"])

View File

@@ -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,13 +15,16 @@ 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
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"])

View File

@@ -35,5 +35,4 @@ def user_inputs():
"trailing_delta": ts_delta
}
}
st.session_state["default_config"] = config
return config

View File

@@ -1,14 +1,12 @@
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.st_utils import initialize_st_page
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, \
@@ -20,15 +18,15 @@ 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
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)
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)
@@ -61,4 +59,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"])

View File

@@ -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="⚡️")
@@ -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,16 +125,10 @@ 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")
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!")

View File

@@ -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:

View File

@@ -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
@@ -36,13 +36,20 @@ 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")
new_account_name = st.text_input("New Account Name")
if st.button("Create Account"):
new_account_name = new_account_name.replace(" ", "_")
if new_account_name:
if new_account_name in accounts:
st.warning(f"Account {new_account_name} already exists.")
st.stop()
elif new_account_name == "" or all(char == "_" for char in new_account_name):
st.warning("Please enter a valid account name.")
st.stop()
response = client.add_account(new_account_name)
st.write(response)
else:
@@ -55,7 +62,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.")
@@ -70,15 +90,17 @@ 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}")
with cols[-1]:
if st.button("Submit Credentials"):
response = client.add_connector_keys(account_name, connector_name, config_inputs)
if response:
st.success(response)

View File

@@ -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.")
@@ -71,5 +71,5 @@ with elements("active_instances_board"):
card(bot)
while True:
time.sleep(5)
time.sleep(10)
st.rerun()

View File

@@ -1,58 +1,154 @@
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
import plotly.graph_objects as go
import plotly.express as px
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
def get_all_balances():
return client.get_all_balances()
# Fetch all balances
balances = get_all_balances()
# 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")
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)
# 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)
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)
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)
# 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()
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()
if len(exchanges_available) == 0:
st.warning("No exchanges found.")
st.stop()
exchanges = st.multiselect("Select Exchanges", exchanges_available, exchanges_available)
# Display the tokens available
tokens_available = []
for account in accounts:
for exchange in exchanges:
if exchange in account_state[account]:
tokens_available += [info["token"] for info in account_state[account][exchange]]
token_options = set(tokens_available)
tokens_available = st.multiselect("Select Tokens", token_options, token_options)
st.write("---")
filtered_account_state = {}
for account in accounts:
filtered_account_state[account] = {}
for exchange in exchanges:
if exchange in account_state[account]:
filtered_account_state[account][exchange] = [token_info for token_info in account_state[account][exchange] if token_info["token"] in tokens_available]
filtered_account_history = []
for record in account_history:
filtered_record = {"timestamp": record["timestamp"], "state": {}}
for account in accounts:
if account in record["state"]:
filtered_record["state"][account] = {}
for exchange in exchanges:
if exchange in record["state"][account]:
filtered_record["state"][account][exchange] = [token_info for token_info in record["state"][account][exchange] if token_info["token"] in tokens_available]
filtered_account_history.append(filtered_record)
if len(filtered_account_state) > 0:
account_state_df = account_state_to_df(filtered_account_state)
total_balance_usd = round(account_state_df["value"].sum(), 2)
c1, c2 = st.columns([1, 5])
with c1:
st.metric("Total Balance (USD)", total_balance_usd)
with c2:
account_state_df['% Allocation'] = (account_state_df['value'] / total_balance_usd) * 100
account_state_df['label'] = account_state_df['token'] + ' ($' + account_state_df['value'].apply(
lambda x: f'{x:,.2f}') + ')'
# Create a sunburst chart with Plotly Express
fig = px.sunburst(account_state_df,
path=['account', 'exchange', 'label'],
values='value',
hover_data={'% Allocation': ':.2f'},
title='% Allocation by Account, Exchange, and Token',
color='account',
color_discrete_sequence=px.colors.qualitative.Vivid)
fig.update_traces(textinfo='label+percent entry')
fig.update_layout(margin=dict(t=0, l=0, r=0, b=0), height=800, title_x=0.01, title_y=1,)
st.plotly_chart(fig, use_container_width=True)
st.dataframe(account_state_df[['exchange', 'token', 'units', 'price', 'value', 'available_units']], width=1800,
height=600)
# Plot the evolution of the portfolio over time
if len(filtered_account_history) > 0:
account_history_df = account_history_to_df(filtered_account_history)
account_history_df['timestamp'] = pd.to_datetime(account_history_df['timestamp'])
# Aggregate the value of the portfolio over time
portfolio_evolution_df = account_history_df.groupby('timestamp')['value'].sum().reset_index()
fig = px.line(portfolio_evolution_df, x='timestamp', y='value', title='Portfolio Evolution Over Time')
fig.update_layout(xaxis_title='Time', yaxis_title='Total Value (USD)', height=600)
st.plotly_chart(fig, use_container_width=True)
# Plot the evolution of each token's value over time
token_evolution_df = account_history_df.groupby(['timestamp', 'token'])['value'].sum().reset_index()
fig = px.area(token_evolution_df, x='timestamp', y='value', color='token', title='Token Value Evolution Over Time',
color_discrete_sequence=px.colors.qualitative.Vivid)
fig.update_layout(xaxis_title='Time', yaxis_title='Value (USD)', height=600)
st.plotly_chart(fig, use_container_width=True)

View File

@@ -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

33
frontend/utils.py Normal file
View File

@@ -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

View File

@@ -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", "🙌"),