mirror of
https://github.com/aljazceru/hummingbot-dashboard.git
synced 2026-01-23 07:04:21 +01:00
Merge branch 'main' into feat/trailing_stop_backtest
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -8,6 +8,7 @@ dependencies:
|
||||
- pip
|
||||
- pip:
|
||||
- hummingbot
|
||||
- numpy==1.26.4
|
||||
- streamlit==1.33.0
|
||||
- watchdog
|
||||
- python-dotenv
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.")
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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!")
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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!")
|
||||
|
||||
@@ -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!")
|
||||
@@ -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"])
|
||||
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -35,5 +35,4 @@ def user_inputs():
|
||||
"trailing_delta": ts_delta
|
||||
}
|
||||
}
|
||||
st.session_state["default_config"] = config
|
||||
return config
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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!")
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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
33
frontend/utils.py
Normal 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
|
||||
2
main.py
2
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", "🙌"),
|
||||
|
||||
Reference in New Issue
Block a user