mirror of
https://github.com/d0zingcat/deploy.git
synced 2026-05-13 15:09:33 +00:00
385 lines
17 KiB
Python
385 lines
17 KiB
Python
import time
|
|
|
|
import pandas as pd
|
|
import streamlit as st
|
|
|
|
from frontend.st_utils import get_backend_api_client, initialize_st_page
|
|
|
|
initialize_st_page(icon="🦅", show_readme=False)
|
|
|
|
# Initialize backend client
|
|
backend_api_client = get_backend_api_client()
|
|
|
|
# Initialize session state for auto-refresh
|
|
if "auto_refresh_enabled" not in st.session_state:
|
|
st.session_state.auto_refresh_enabled = True
|
|
|
|
# Set refresh interval
|
|
REFRESH_INTERVAL = 10 # seconds
|
|
|
|
|
|
def stop_bot(bot_name):
|
|
"""Stop a running bot."""
|
|
try:
|
|
backend_api_client.bot_orchestration.stop_and_archive_bot(bot_name)
|
|
st.success(f"Bot {bot_name} stopped and archived successfully")
|
|
time.sleep(2) # Give time for the backend to process
|
|
except Exception as e:
|
|
st.error(f"Failed to stop bot {bot_name}: {e}")
|
|
|
|
|
|
def archive_bot(bot_name):
|
|
"""Archive a stopped bot."""
|
|
try:
|
|
backend_api_client.docker.stop_container(bot_name)
|
|
backend_api_client.docker.remove_container(bot_name)
|
|
st.success(f"Bot {bot_name} archived successfully")
|
|
time.sleep(1)
|
|
except Exception as e:
|
|
st.error(f"Failed to archive bot {bot_name}: {e}")
|
|
|
|
|
|
def stop_controllers(bot_name, controllers):
|
|
"""Stop selected controllers."""
|
|
success_count = 0
|
|
for controller in controllers:
|
|
try:
|
|
backend_api_client.controllers.update_bot_controller_config(
|
|
bot_name,
|
|
controller,
|
|
{"manual_kill_switch": True}
|
|
)
|
|
success_count += 1
|
|
except Exception as e:
|
|
st.error(f"Failed to stop controller {controller}: {e}")
|
|
|
|
if success_count > 0:
|
|
st.success(f"Successfully stopped {success_count} controller(s)")
|
|
# Temporarily disable auto-refresh to prevent immediate state reset
|
|
st.session_state.auto_refresh_enabled = False
|
|
|
|
return success_count > 0
|
|
|
|
|
|
def start_controllers(bot_name, controllers):
|
|
"""Start selected controllers."""
|
|
success_count = 0
|
|
for controller in controllers:
|
|
try:
|
|
backend_api_client.controllers.update_bot_controller_config(
|
|
bot_name,
|
|
controller,
|
|
{"manual_kill_switch": False}
|
|
)
|
|
success_count += 1
|
|
except Exception as e:
|
|
st.error(f"Failed to start controller {controller}: {e}")
|
|
|
|
if success_count > 0:
|
|
st.success(f"Successfully started {success_count} controller(s)")
|
|
# Temporarily disable auto-refresh to prevent immediate state reset
|
|
st.session_state.auto_refresh_enabled = False
|
|
|
|
return success_count > 0
|
|
|
|
|
|
def render_bot_card(bot_name):
|
|
"""Render a bot performance card using native Streamlit components."""
|
|
try:
|
|
# Get bot status first
|
|
bot_status = backend_api_client.bot_orchestration.get_bot_status(bot_name)
|
|
|
|
# Only try to get controller configs if bot exists and is running
|
|
controller_configs = []
|
|
if bot_status.get("status") == "success":
|
|
bot_data = bot_status.get("data", {})
|
|
is_running = bot_data.get("status") == "running"
|
|
if is_running:
|
|
try:
|
|
controller_configs = backend_api_client.controllers.get_bot_controller_configs(bot_name)
|
|
controller_configs = controller_configs if controller_configs else []
|
|
except Exception as e:
|
|
# If controller configs fail, continue without them
|
|
st.warning(f"Could not fetch controller configs for {bot_name}: {e}")
|
|
controller_configs = []
|
|
|
|
with st.container(border=True):
|
|
|
|
if bot_status.get("status") == "error":
|
|
# Error state
|
|
col1, col2 = st.columns([3, 1])
|
|
with col1:
|
|
st.error(f"🤖 **{bot_name}** - Not Available")
|
|
st.error(f"An error occurred while fetching bot status of {bot_name}. Please check the bot client.")
|
|
else:
|
|
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", [])
|
|
|
|
# Bot header
|
|
col1, col2, col3 = st.columns([2, 1, 1])
|
|
with col1:
|
|
if is_running:
|
|
st.success(f"🤖 **{bot_name}** - Running")
|
|
else:
|
|
st.warning(f"🤖 **{bot_name}** - Stopped")
|
|
|
|
with col3:
|
|
if is_running:
|
|
if st.button("⏹️ Stop", key=f"stop_{bot_name}", use_container_width=True):
|
|
stop_bot(bot_name)
|
|
else:
|
|
if st.button("📦 Archive", key=f"archive_{bot_name}", use_container_width=True):
|
|
archive_bot(bot_name)
|
|
|
|
if is_running:
|
|
# Calculate totals
|
|
active_controllers = []
|
|
stopped_controllers = []
|
|
error_controllers = []
|
|
total_global_pnl_quote = 0
|
|
total_volume_traded = 0
|
|
total_unrealized_pnl_quote = 0
|
|
|
|
for controller, inner_dict in performance.items():
|
|
controller_status = inner_dict.get("status")
|
|
if controller_status == "error":
|
|
error_controllers.append({
|
|
"Controller": controller,
|
|
"Error": inner_dict.get("error", "Unknown error")
|
|
})
|
|
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", "N/A")
|
|
trading_pair = controller_config.get("trading_pair", "N/A")
|
|
kill_switch_status = controller_config.get("manual_kill_switch", False)
|
|
|
|
realized_pnl_quote = controller_performance.get("realized_pnl_quote", 0)
|
|
unrealized_pnl_quote = controller_performance.get("unrealized_pnl_quote", 0)
|
|
global_pnl_quote = controller_performance.get("global_pnl_quote", 0)
|
|
volume_traded = controller_performance.get("volume_traded", 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)
|
|
failed = close_types.get("CloseType.FAILED", 0)
|
|
close_types_str = f"TP: {tp} | SL: {sl} | TS: {ts} | TL: {time_limit} | ES: {refreshed} | F: {failed}"
|
|
|
|
controller_info = {
|
|
"Select": False,
|
|
"ID": controller_config.get("id"),
|
|
"Controller": controller_name,
|
|
"Connector": connector_name,
|
|
"Trading Pair": trading_pair,
|
|
"Realized PNL ($)": round(realized_pnl_quote, 2),
|
|
"Unrealized PNL ($)": round(unrealized_pnl_quote, 2),
|
|
"NET PNL ($)": round(global_pnl_quote, 2),
|
|
"Volume ($)": round(volume_traded, 2),
|
|
"Close Types": close_types_str,
|
|
"_controller_id": controller
|
|
}
|
|
|
|
if kill_switch_status:
|
|
stopped_controllers.append(controller_info)
|
|
else:
|
|
active_controllers.append(controller_info)
|
|
|
|
total_global_pnl_quote += global_pnl_quote
|
|
total_volume_traded += volume_traded
|
|
total_unrealized_pnl_quote += unrealized_pnl_quote
|
|
|
|
total_global_pnl_pct = total_global_pnl_quote / total_volume_traded if total_volume_traded > 0 else 0
|
|
|
|
# Display metrics
|
|
col1, col2, col3, col4 = st.columns(4)
|
|
|
|
with col1:
|
|
st.metric("🏦 NET PNL", f"${total_global_pnl_quote:.2f}")
|
|
with col2:
|
|
st.metric("💹 Unrealized PNL", f"${total_unrealized_pnl_quote:.2f}")
|
|
with col3:
|
|
st.metric("📊 NET PNL (%)", f"{total_global_pnl_pct:.2%}")
|
|
with col4:
|
|
st.metric("💸 Volume Traded", f"${total_volume_traded:.2f}")
|
|
|
|
# Active Controllers
|
|
if active_controllers:
|
|
st.success("🚀 **Active Controllers:** Controllers currently running and trading")
|
|
active_df = pd.DataFrame(active_controllers)
|
|
|
|
edited_active_df = st.data_editor(
|
|
active_df,
|
|
column_config={
|
|
"Select": st.column_config.CheckboxColumn(
|
|
"Select",
|
|
help="Select controllers to stop",
|
|
default=False,
|
|
),
|
|
"_controller_id": None, # Hide this column
|
|
},
|
|
disabled=[col for col in active_df.columns if col != "Select"],
|
|
hide_index=True,
|
|
use_container_width=True,
|
|
key=f"active_table_{bot_name}"
|
|
)
|
|
|
|
selected_active = [
|
|
row["_controller_id"]
|
|
for _, row in edited_active_df.iterrows()
|
|
if row["Select"]
|
|
]
|
|
|
|
if selected_active:
|
|
if st.button(f"⏹️ Stop Selected ({len(selected_active)})",
|
|
key=f"stop_active_{bot_name}",
|
|
type="secondary"):
|
|
with st.spinner(f"Stopping {len(selected_active)} controller(s)..."):
|
|
stop_controllers(bot_name, selected_active)
|
|
time.sleep(1)
|
|
|
|
# Stopped Controllers
|
|
if stopped_controllers:
|
|
st.warning("💤 **Stopped Controllers:** Controllers that are paused or stopped")
|
|
stopped_df = pd.DataFrame(stopped_controllers)
|
|
|
|
edited_stopped_df = st.data_editor(
|
|
stopped_df,
|
|
column_config={
|
|
"Select": st.column_config.CheckboxColumn(
|
|
"Select",
|
|
help="Select controllers to start",
|
|
default=False,
|
|
),
|
|
"_controller_id": None, # Hide this column
|
|
},
|
|
disabled=[col for col in stopped_df.columns if col != "Select"],
|
|
hide_index=True,
|
|
use_container_width=True,
|
|
key=f"stopped_table_{bot_name}"
|
|
)
|
|
|
|
selected_stopped = [
|
|
row["_controller_id"]
|
|
for _, row in edited_stopped_df.iterrows()
|
|
if row["Select"]
|
|
]
|
|
|
|
if selected_stopped:
|
|
if st.button(f"▶️ Start Selected ({len(selected_stopped)})",
|
|
key=f"start_stopped_{bot_name}",
|
|
type="primary"):
|
|
with st.spinner(f"Starting {len(selected_stopped)} controller(s)..."):
|
|
start_controllers(bot_name, selected_stopped)
|
|
time.sleep(1)
|
|
|
|
# Error Controllers
|
|
if error_controllers:
|
|
st.error("💀 **Controllers with Errors:** Controllers that encountered errors")
|
|
error_df = pd.DataFrame(error_controllers)
|
|
st.dataframe(error_df, use_container_width=True, hide_index=True)
|
|
|
|
# Logs sections (available for both running and stopped bots)
|
|
with st.expander("📋 Error Logs"):
|
|
if error_logs:
|
|
for log in error_logs[:50]:
|
|
timestamp = log.get("timestamp", "")
|
|
message = log.get("msg", "")
|
|
logger_name = log.get("logger_name", "")
|
|
st.text(f"{timestamp} - {logger_name}: {message}")
|
|
else:
|
|
st.info("No error logs available.")
|
|
|
|
with st.expander("📝 General Logs"):
|
|
if general_logs:
|
|
for log in general_logs[:50]:
|
|
timestamp = pd.to_datetime(int(log.get("timestamp", 0)), unit="s")
|
|
message = log.get("msg", "")
|
|
logger_name = log.get("logger_name", "")
|
|
st.text(f"{timestamp} - {logger_name}: {message}")
|
|
else:
|
|
st.info("No general logs available.")
|
|
|
|
except Exception as e:
|
|
with st.container(border=True):
|
|
st.error(f"🤖 **{bot_name}** - Error")
|
|
st.error(f"An error occurred while fetching bot status: {str(e)}")
|
|
|
|
|
|
# Page Header
|
|
st.title("🦅 Hummingbot Instances")
|
|
|
|
# Auto-refresh controls
|
|
col1, col2, col3 = st.columns([3, 1, 1])
|
|
|
|
# Create placeholder for status message
|
|
status_placeholder = col1.empty()
|
|
|
|
with col2:
|
|
if st.button("▶️ Start Auto-refresh" if not st.session_state.auto_refresh_enabled else "⏸️ Stop Auto-refresh",
|
|
use_container_width=True):
|
|
st.session_state.auto_refresh_enabled = not st.session_state.auto_refresh_enabled
|
|
|
|
with col3:
|
|
if st.button("🔄 Refresh Now", use_container_width=True):
|
|
# Re-enable auto-refresh if it was temporarily disabled
|
|
if not st.session_state.auto_refresh_enabled:
|
|
st.session_state.auto_refresh_enabled = True
|
|
pass
|
|
|
|
|
|
@st.fragment(run_every=REFRESH_INTERVAL if st.session_state.auto_refresh_enabled else None)
|
|
def show_bot_instances():
|
|
"""Fragment to display bot instances with auto-refresh."""
|
|
try:
|
|
active_bots_response = backend_api_client.bot_orchestration.get_active_bots_status()
|
|
|
|
if active_bots_response.get("status") == "success":
|
|
active_bots = active_bots_response.get("data", {})
|
|
|
|
# Filter out any bots that might be in transitional state
|
|
truly_active_bots = {}
|
|
for bot_name, bot_info in active_bots.items():
|
|
try:
|
|
bot_status = backend_api_client.bot_orchestration.get_bot_status(bot_name)
|
|
if bot_status.get("status") == "success":
|
|
bot_data = bot_status.get("data", {})
|
|
if bot_data.get("status") in ["running", "stopped"]:
|
|
truly_active_bots[bot_name] = bot_info
|
|
except Exception:
|
|
continue
|
|
|
|
if truly_active_bots:
|
|
# Show refresh status
|
|
if st.session_state.auto_refresh_enabled:
|
|
status_placeholder.info(f"🔄 Auto-refreshing every {REFRESH_INTERVAL} seconds")
|
|
else:
|
|
status_placeholder.warning("⏸️ Auto-refresh paused. Click 'Refresh Now' to resume.")
|
|
|
|
# Render each bot
|
|
for bot_name in truly_active_bots.keys():
|
|
render_bot_card(bot_name)
|
|
else:
|
|
status_placeholder.info("No active bot instances found. Deploy a bot to see it here.")
|
|
else:
|
|
st.error("Failed to fetch active bots status.")
|
|
|
|
except Exception as e:
|
|
st.error(f"Failed to connect to backend: {e}")
|
|
st.info("Please make sure the backend is running and accessible.")
|
|
|
|
|
|
# Call the fragment
|
|
show_bot_instances()
|