# web_ui.py (v3.7.0 – added /arbitrage command and Pseudo‑settle by column) # FastAPI application serving health endpoint, Telegram webhook, JSON API for dashboard, # and a live dark‑mode dashboard with auto‑refresh (every 60 seconds, single API call). # Includes: # - World Clock with waving flag emojis # - Economic Calendar card (today & tomorrow events) # - /note saving, /task commands, /flag commands, /arbitrage status # - Virtual account, dry‑run positions, LLM errors (today only) # - Mobile‑friendly responsive tables (horizontal scroll) # - Backup history, last restore, last factory rebuild, last restart # - Dry‑Run Open Positions table now shows "Pseudo‑settle by" (entry_time + 3 days) # All comments in English. import os, glob, json from datetime import datetime, timezone, timedelta from fastapi import FastAPI, Request from fastapi.responses import JSONResponse, HTMLResponse os.makedirs("/app/wiki", exist_ok=True) app = FastAPI() TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID") TELEGRAM_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN") DEFAULT_FETCH_INTERVAL = 120 FETCH_INTERVAL_FILE = "/app/fetch_interval.txt" TRADE_MODE_FILE = "/app/trade_mode.txt" ADAPTIVE_HISTORY_FILE = "/app/adaptive_history.json" def read_file(path, default=""): try: if os.path.exists(path): with open(path, "r") as f: return f.read().strip() except: pass return default def read_json(path, default=None): try: if os.path.exists(path): with open(path, "r") as f: return json.load(f) except: pass return default def write_file(path, content): try: with open(path, "w") as f: f.write(str(content)) except: pass # ── Health endpoint ────────────────────────────── @app.api_route("/health", methods=["GET", "HEAD"]) def health(): return {"status": "ok"} # ── Telegram webhook ───────────────────────────── @app.api_route("/telegram-webhook", methods=["GET", "POST"]) async def telegram_webhook(request: Request): if request.method == "GET": return JSONResponse({"status": "Webhook endpoint is active"}) data = await request.json() if "message" not in data: return JSONResponse({"ok": True}) chat_id = data["message"]["chat"]["id"] text = data["message"].get("text", "") if text == "/status": reply_text = "System is running. Risk pool: $5" elif text == "/arbitrage": # Check the state file written by arbitrage_launcher.py state = read_file("/app/arbitrage_clob_state.txt", "unknown") if state == "up": reply_text = "✅ Arbitrage bot is running (CLOB API reachable)." elif state == "down": reply_text = "⚠️ Arbitrage bot is disabled – CLOB API unreachable." else: reply_text = "ℹ️ Arbitrage bot status unknown (still initialising)." elif text == "/balance": try: if os.path.exists("/app/balance.txt"): with open("/app/balance.txt") as f: bal = float(f.read().strip()) reply_text = f"Current balance: ${bal:.2f}" else: reply_text = "Balance not yet available." except: reply_text = "Error reading balance." elif text == "/pnl": capital_state = read_json("/app/capital_state.json", {}) total_pnl = round(capital_state.get("total_capital", 20) - 20, 2) reply_text = f"📊 P&L Summary\nTotal: ${total_pnl:.2f}" elif text == "/wikicount": wiki_dir = "/app/wiki" if os.path.exists(wiki_dir): count = len(glob.glob(os.path.join(wiki_dir, "*.md"))) reply_text = f"Wiki entries: {count}" else: reply_text = "Wiki directory not found." elif text == "/lastsignal": try: if os.path.exists("/app/last_processed_signal.json"): with open("/app/last_processed_signal.json") as f: s = json.load(f) ts = s.get("processed_at", "unknown") reply_text = ( f"Last signal (processed):\n" f"Time: {ts}\n" f"Market: {s.get('market_slug')}\n" f"Action: {s.get('action')}\n" f"Confidence: {s.get('confidence')}" ) elif os.path.exists("/app/latest_signals.json"): with open("/app/latest_signals.json") as f: signals = json.load(f) if signals: s = signals[-1] reply_text = ( f"Pending signal:\n" f"Market: {s.get('market_slug')}\n" f"Action: {s.get('action')}\n" f"Confidence: {s.get('confidence')}" ) else: reply_text = "No qualified signals found." else: reply_text = "No qualified signals found." except: reply_text = "Error reading signal file." elif text == "/clusters": cluster_data = read_json("/app/cluster_results.json") if not cluster_data or not cluster_data.get("clusters"): reply_text = "No cluster data yet. First analysis may take a few minutes." else: lines = [] for cid, profile in cluster_data["clusters"].items(): decision = profile.get("decision", "pending") freq_tag = "" if profile.get("avg_high_freq_score", 0) > 50 or profile.get("avg_small_trade_ratio", 0) > 0.8: freq_tag = " [HIGH-FREQ]" lines.append( f"Cluster {cid}{freq_tag}: {profile['size']} wallets, " f"win rate {profile['avg_win_rate']:.1%}, " f"${profile['avg_trade_size']:.0f}/trade, " f"{profile['avg_daily_trades']:.1f} trades/day → {decision}" ) first_cluster = list(cluster_data["clusters"].values())[0] auto_adj = first_cluster.get("auto_adjust") if auto_adj: lines.append( f"Auto K: tried K={auto_adj['search_range']}, " f"selected K={auto_adj['selected_k']} (silhouette={auto_adj['silhouette_score']})" ) lines.append( f"Adaptive: MIN_TRADES={auto_adj.get('min_trades_for_analysis', 'N/A')}, " f"MIN_SIZE=${auto_adj.get('min_trade_size_usd', 'N/A')}" ) lines.append(f"Followed wallets: {len(cluster_data.get('followed_wallets', []))}") reply_text = "📊 Wallet Clusters:\n" + "\n".join(lines) elif text.startswith("/note"): note_content = text[5:].strip() if not note_content: reply_text = "Usage: /note " else: timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") entry = f"[{timestamp}] {note_content}\n" try: with open("/app/notes.txt", "a") as f: f.write(entry) safe_topic = note_content[:30].replace('/', '_').replace(' ', '_').replace('%', '_') wiki_filename = f"{datetime.now(timezone.utc).strftime('%Y-%m-%d_%H-%M-%S')}_Note-{safe_topic}.md" with open(f"/app/wiki/{wiki_filename}", "w") as wf: wf.write(f"# Note: {note_content}\n\n**Time:** {timestamp}\n**Content:** {note_content}\n") reply_text = f"Note saved and added to Wiki: {note_content}" except Exception as e: reply_text = f"Failed to save note: {e}" elif text.startswith("/deletenote"): search_text = text[11:].strip() if not search_text: reply_text = "Usage: /deletenote " else: notes_path = "/app/notes.txt" wiki_dir = "/app/wiki" deleted_notes = 0 deleted_wiki = 0 if os.path.exists(notes_path): with open(notes_path, "r") as f: lines = f.readlines() new_lines = [l for l in lines if search_text.lower() not in l.lower()] if len(new_lines) < len(lines): with open(notes_path, "w") as f: f.writelines(new_lines) deleted_notes = len(lines) - len(new_lines) safe_topic = search_text[:30].replace('/', '_').replace(' ', '_').replace('%', '_') pattern = os.path.join(wiki_dir, f"*_Note-{safe_topic}.md") for fp in glob.glob(pattern): try: os.remove(fp) deleted_wiki += 1 except: pass if deleted_notes > 0 or deleted_wiki > 0: reply_text = f"Deleted {deleted_notes} line(s) from notes.txt and {deleted_wiki} wiki file(s) matching that text." else: reply_text = "No matching note found." elif text.startswith("/flag"): parts = text[5:].strip().split(maxsplit=2) if len(parts) < 3: reply_text = "Usage: /flag open % YES/NO OR /flag close [correct|incorrect]" else: sub = parts[0].lower() if sub == "open": prob_str = parts[1] desc = parts[2] flag = { "probability": prob_str, "description": desc, "opened_at": datetime.now(timezone.utc).isoformat(), "status": "open", "result": None, } flags = read_json("/app/flags.json", []) flags.append(flag) with open("/app/flags.json", "w") as f: json.dump(flags, f) reply_text = f"🏴 Flag opened: {desc}" elif sub == "close": remaining = text[10:].strip() tokens = remaining.split() result = None if tokens and tokens[-1].lower() in ("correct", "incorrect"): result = tokens[-1].lower() desc_to_close = " ".join(tokens[:-1]) else: desc_to_close = remaining if not desc_to_close: reply_text = "Please specify a description to close." else: flags = read_json("/app/flags.json", []) updated = False for flag in flags: if flag.get("status") == "open" and desc_to_close.lower() in flag.get("description", "").lower(): flag["status"] = "closed" flag["closed_at"] = datetime.now(timezone.utc).isoformat() flag["result"] = result updated = True if updated: with open("/app/flags.json", "w") as f: json.dump(flags, f) reply_text = f"🏁 Flag closed: {desc_to_close}" else: reply_text = "No matching open flag found." else: reply_text = "Unknown flag action." elif text.startswith("/task"): task_parts = text[5:].strip().split() if not task_parts: reply_text = ( "Usage: /task . Options: simulate on|off|status, insane, conservative, " "report now, threshold , analyze , wallet , pause, resume, interval [], " "backup_interval [], wiki_retention []" ) else: sub = task_parts[0].lower() if sub == "simulate": if len(task_parts) >= 2: action = task_parts[1].lower() if action == "on": with open("/app/dry_run_active.txt", "w") as f: f.write("1") reply_text = "Dry‑run simulation enabled." elif action == "off": if os.path.exists("/app/dry_run_active.txt"): os.remove("/app/dry_run_active.txt") reply_text = "Dry‑run simulation disabled." elif action == "status": stats = {} try: if os.path.exists("/app/virtual_positions.json"): with open("/app/virtual_positions.json") as f: positions = json.load(f) open_pos = [p for p in positions if p.get("status") == "open"] closed_pos = [p for p in positions if p.get("status") == "closed"] wins = [p for p in closed_pos if p.get("result") == "win"] losses = [p for p in closed_pos if p.get("result") == "loss"] total_pnl = sum(p.get("pnl", 0) or 0 for p in closed_pos) stats = { "active": os.path.exists("/app/dry_run_active.txt"), "total": len(positions), "open": len(open_pos), "closed": len(closed_pos), "wins": len(wins), "losses": len(losses), "win_rate": f"{(len(wins) / len(closed_pos) * 100):.1f}%" if closed_pos else "N/A", "total_pnl": round(total_pnl, 2), } else: stats = {"active": os.path.exists("/app/dry_run_active.txt"), "total": 0} except Exception as e: stats = {"error": str(e)} reply_text = ( f"🧪 Dry‑Run Simulation Status\n" f"Active: {'Enabled' if stats.get('active') else 'Disabled'}\n" f"Total positions: {stats.get('total', 0)}\n" f"Open: {stats.get('open', 0)} | Closed: {stats.get('closed', 0)}\n" f"Wins: {stats.get('wins', 0)} | Losses: {stats.get('losses', 0)}\n" f"Win rate: {stats.get('win_rate', 'N/A')}\n" f"Total P&L: ${stats.get('total_pnl', 0)}" ) else: reply_text = "Unknown simulate action." else: reply_text = "Usage: /task simulate on|off|status" elif sub == "insane": write_file(TRADE_MODE_FILE, "insane") write_file("/app/dry_run_active.txt", "1") write_file(FETCH_INTERVAL_FILE, "15") reply_text = "🔥 INSANE MODE activated. Virtual aggressive trading. Real money PROTECTED." elif sub == "conservative": write_file(TRADE_MODE_FILE, "conservative") write_file(FETCH_INTERVAL_FILE, "120") reply_text = "🛡️ Conservative mode restored." elif sub == "report" and len(task_parts) >= 2 and task_parts[1] == "now": with open("/app/force_report.txt", "w") as f: f.write("1") reply_text = "Report triggered." elif sub == "threshold" and len(task_parts) >= 2: try: val = float(task_parts[1]) with open("/app/follow_threshold.txt", "w") as f: f.write(str(val)) reply_text = f"Follow threshold set to {val}" except: reply_text = "Invalid threshold value." elif sub == "interval": if len(task_parts) >= 2: try: new_val = int(task_parts[1]) if new_val < 10: reply_text = "Interval must be at least 10 seconds." else: write_file(FETCH_INTERVAL_FILE, new_val) reply_text = f"Fetch interval set to {new_val} seconds." except: reply_text = "Invalid interval value." else: cur = read_file(FETCH_INTERVAL_FILE, str(DEFAULT_FETCH_INTERVAL)) reply_text = f"Current fetch interval: {cur} seconds." elif sub == "backup_interval": if len(task_parts) >= 2: try: hours = float(task_parts[1]) if hours < 1: reply_text = "Backup interval must be at least 1 hour." else: write_file("/app/backup_interval_hours.txt", str(hours)) reply_text = f"Backup interval set to {hours} hours." except: reply_text = "Invalid number of hours." else: cur = read_file("/app/backup_interval_hours.txt", str(6)) reply_text = f"Current backup interval: {cur} hours." elif sub == "wiki_retention": if len(task_parts) >= 2: try: days = int(task_parts[1]) if days < 1: reply_text = "Retention must be at least 1 day." else: write_file("/app/wiki_retention_days.txt", str(days)) reply_text = f"Wiki retention set to {days} days. Old entries will be archived daily." except ValueError: reply_text = "Invalid number of days." else: cur = read_file("/app/wiki_retention_days.txt", "45") reply_text = f"Current wiki retention: {cur} days." else: reply_text = "Unknown task." elif text == "/report": reply_text = "Use /task report now for a fresh summary." elif text == "/errors": errors = read_file("/app/llm_error_count.txt", "0") details = [] try: with open("/app/llm_error_details.txt") as f: details = [line.strip() for line in f.readlines() if line.strip()][-5:] except: pass reply_text = f"LLM errors today: {errors}" if details: reply_text += "\nRecent details:\n" + "\n".join(details) elif "hello" in text.lower(): reply_text = "Hello! I'm alive and running on Hugging Face." else: reply_text = ( "Unknown command. Try /status, /balance, /pnl, /wikicount, /lastsignal, /clusters, " "/note, /flag, /task, /report, /errors, /deletenote, /arbitrage" ) return JSONResponse({"method": "sendMessage", "chat_id": chat_id, "text": reply_text}) # ── Unified API endpoint ──────────────────────── @app.get("/api/all") def api_all(): balance = read_file("/app/balance.txt", "N/A") signal = {"market": "None", "action": "", "confidence": "", "processed_at": ""} try: if os.path.exists("/app/last_processed_signal.json"): with open("/app/last_processed_signal.json") as f: s = json.load(f) signal = { "market": s.get("market_slug"), "action": s.get("action"), "confidence": s.get("confidence"), "processed_at": s.get("processed_at", ""), } elif os.path.exists("/app/latest_signals.json"): with open("/app/latest_signals.json") as f: signals = json.load(f) if signals: s = signals[-1] signal = { "market": s.get("market_slug"), "action": s.get("action"), "confidence": s.get("confidence"), "processed_at": "", } except: pass hermes_decision = read_file("/app/hermes_status.txt", "No recent decision") wiki_entries = [] wiki_dir = "/app/wiki" if os.path.exists(wiki_dir): files = sorted(glob.glob(os.path.join(wiki_dir, "*.md")), reverse=True)[:5] for fp in files: name = os.path.basename(fp) parts = name.split("_", 2) ts = parts[0] + " " + parts[1].replace("-", ":") if len(parts) >= 2 else name wiki_entries.append({"time": ts, "filename": name}) polyhub = read_file("/app/polyhub_status.txt", "unknown") hermes = read_file("/app/hermes_status.txt", "unknown") balance_updater = read_file("/app/balance_status.txt", "unknown") report_text = "No report yet." try: if os.path.exists("/app/dashboard_report.json"): with open("/app/dashboard_report.json") as f: report_text = json.load(f).get("report", "No report yet.") except: pass # ── LLM errors – today only ────────────────────── llm_errors_today = read_file("/app/llm_error_count.txt", "0") today_prefix = datetime.now(timezone.utc).strftime("%Y-%m-%d") error_counts_today = {} try: with open("/app/llm_error_types.txt") as f: for line in f: line = line.strip() if not line: continue if line.startswith(f"[{today_prefix}"): etype = line.split("] ", 1)[1].strip() error_counts_today[etype] = error_counts_today.get(etype, 0) + 1 except: pass error_details_today = [] try: with open("/app/llm_error_details.txt") as f: for line in f: line = line.strip() if line.startswith(f"[{today_prefix}"): error_details_today.append(line) except: pass error_details_today = error_details_today[-5:] recent_notes = [] total_notes = 0 try: with open("/app/notes.txt") as f: lines = [line.strip() for line in f.readlines() if line.strip()] total_notes = len(lines) recent_notes = lines[-5:] except: pass cluster_summary = [] auto_adjust_info = None cluster_data = read_json("/app/cluster_results.json") if cluster_data and cluster_data.get("clusters"): for cid, profile in cluster_data["clusters"].items(): is_high_freq = ( profile.get("avg_high_freq_score", 0) > 50 or profile.get("avg_small_trade_ratio", 0) > 0.8 ) cluster_summary.append( { "id": cid, "size": profile["size"], "avg_win_rate": round(profile["avg_win_rate"], 3), "avg_trade_size": round(profile["avg_trade_size"], 0), "avg_daily_trades": round(profile["avg_daily_trades"], 1), "decision": profile.get("decision", "pending"), "is_high_freq": is_high_freq, } ) if auto_adjust_info is None: auto_adjust_info = profile.get("auto_adjust") followed_count = len(cluster_data.get("followed_wallets", [])) else: followed_count = 0 if auto_adjust_info is None: adaptive_params = read_json("/app/adaptive_params.json", {}) if adaptive_params: auto_adjust_info = { "search_range": "N/A", "selected_k": "N/A", "silhouette_score": "N/A", "min_trades_for_analysis": adaptive_params.get("min_trades_for_analysis", 5), "min_trade_size_usd": adaptive_params.get("min_trade_size_usd", 1.0), } adaptive_history = read_json(ADAPTIVE_HISTORY_FILE, [])[-5:] capital_state = read_json("/app/capital_state.json", {}) total_capital = capital_state.get("total_capital", 20.0) comp_summary = { "tier": capital_state.get("tier", "L1"), "consecutive_wins": capital_state.get("consecutive_wins", 0), "consecutive_losses": capital_state.get("consecutive_losses", 0), "risk_pool": capital_state.get("risk_pool", 5.0), "max_bet": capital_state.get("max_bet", 1.0), "halted": capital_state.get("halted", False), "original_deposit": capital_state.get("original_deposit"), "deposit_date": capital_state.get("deposit_date"), "total_capital": total_capital, } # ── Phase definitions ─────────────────────────── phase_thresholds = [50, 200, 1000, 5000] phase_names = [ "Phase 1: Cold Start", "Phase 2: Acceleration", "Phase 3: Breakthrough", "Phase 4: Expansion", "Phase 5: Stability", ] phase_details = [ "Build risk pool, upgrade to L2", "Grow risk pool, upgrade to L3", "Monthly net profit $500‑1K", "Platform migration & paid models", "Monthly net profit $5K+", ] current_phase = 1 for i, thr in enumerate(phase_thresholds): if total_capital >= thr: current_phase = i + 2 phases = [] for i in range(5): start_cap = 0 if i == 0 else phase_thresholds[i - 1] end_cap = phase_thresholds[i] if i < 4 else None if i == current_phase - 1: progress = min(1.0, (total_capital - start_cap) / (end_cap - start_cap)) if end_cap else 1.0 elif i < current_phase - 1: progress = 1.0 else: progress = 0.0 phases.append( { "name": phase_names[i], "detail": phase_details[i], "start": start_cap, "end": end_cap, "progress": round(progress, 3), "is_current": (i == current_phase - 1), } ) # ── KPI tracker ───────────────────────────────── monthly_profit_target = float(os.getenv("KPI_MONTHLY_TARGET", "500")) monthly_profit = 0.0 try: pnl_data = read_json("/app/balance_pivot.json") if pnl_data and "daily" in pnl_data and balance: bal = float(balance) daily_base = pnl_data.get("daily", bal) daily_pnl = bal - daily_base monthly_profit = round(daily_pnl * 30, 2) except: pass kpi = { "monthly_target": monthly_profit_target, "current_monthly_profit": monthly_profit, "progress_pct": round(min(1.0, monthly_profit / monthly_profit_target) * 100, 1), "break_even_days": "N/A", } if monthly_profit > 0: daily_profit = monthly_profit / 30 if daily_profit > 0: kpi["break_even_days"] = int(monthly_profit_target / daily_profit) # ── Virtual capital state ─────────────────────── virtual_capital = read_json("/app/virtual_capital_state.json", {}) tier_idx = virtual_capital.get("tier_index", 0) tier_labels = ["L1", "L2", "L3", "L4"] virtual_summary = { "original_deposit": virtual_capital.get("original_deposit", 20.0), "total_capital": round(virtual_capital.get("total_capital", 20.0), 2), "risk_pool": round(virtual_capital.get("risk_pool", 5.0), 2), "tier": tier_labels[tier_idx] if isinstance(tier_idx, int) and 0 <= tier_idx < 4 else "L1", "max_bet": round(virtual_capital.get("max_bet", 1.0), 2), "halted": virtual_capital.get("halted", False), } # ── Virtual positions detail ──────────────────── virtual_positions_data = [] try: if os.path.exists("/app/virtual_positions.json"): with open("/app/virtual_positions.json") as f: virtual_positions_data = json.load(f) except: pass open_positions = [p for p in virtual_positions_data if p.get("status") == "open"] closed_positions = [p for p in virtual_positions_data if p.get("status") == "closed"] wins = [p for p in closed_positions if p.get("result") == "win"] losses = [p for p in closed_positions if p.get("result") == "loss"] total_pnl = sum(p.get("pnl", 0) or 0 for p in closed_positions) dry_run_stats = { "active": os.path.exists("/app/dry_run_active.txt"), "total": len(virtual_positions_data), "open": len(open_positions), "closed": len(closed_positions), "wins": len(wins), "losses": len(losses), "win_rate": f"{(len(wins) / len(closed_positions) * 100):.1f}%" if closed_positions else "N/A", "total_pnl": round(total_pnl, 2), "open_positions": [ { "market": p["market"], "action": p["action"], "confidence": p.get("confidence", 0), "bet_amount": p.get("bet_amount", 0), "entry_time": p["entry_time"], "tier": p.get("tier_at_entry", "L1"), "strategy": p.get("strategy", "copy-trade"), # ── Pseudo‑settle deadline = entry_time + 3 days ── "pseudo_settle_by": (datetime.fromisoformat(p["entry_time"]) + timedelta(days=3)).strftime("%Y-%m-%d %H:%M") } for p in open_positions[-5:] ], "closed_positions": [ { "market": p["market"], "action": p["action"], "result": p.get("result", "unknown"), "pnl": p.get("pnl", 0), "entry_time": p["entry_time"], "exit_time": p.get("exit_time", ""), "strategy": p.get("strategy", "copy-trade"), } for p in closed_positions[-5:] ], } flags = read_json("/app/flags.json", []) open_flags = [f for f in flags if f.get("status") == "open"] closed_flags = [f for f in flags if f.get("status") != "open"] correct = len([f for f in closed_flags if f.get("result") == "correct"]) incorrect = len([f for f in closed_flags if f.get("result") == "incorrect"]) flag_summary = { "total": len(flags), "open": len(open_flags), "closed": len(closed_flags), "correct": correct, "incorrect": incorrect, "accuracy": f"{(correct / (correct + incorrect) * 100):.1f}%" if (correct + incorrect) > 0 else "N/A", } backup_status = "disabled" backup_time = "never" backup_raw = read_file("/app/backup_status.txt", "") if backup_raw and "|" in backup_raw: parts = backup_raw.split("|", 1) backup_status = parts[0].strip() backup_time = parts[1].strip() # ── Timestamps ───────────────────────────── last_restore = read_file("/app/last_restore.txt", "Never") last_startup = read_file("/app/last_startup.txt", "Unknown") last_factory_rebuild = read_file("/app/last_factory_rebuild.txt", "Unknown") # ── Backup history (last 10 entries) ─────── backup_history = [] try: if os.path.exists("/app/backup_history.txt"): with open("/app/backup_history.txt", "r") as f: lines = [line.strip() for line in f.readlines() if line.strip()] backup_history = lines[-10:] except: pass # ── Economic Calendar (today & tomorrow) ────────── economic_events = read_json("/app/economic_calendar_data.json", []) health_score = 100 if "running" not in polyhub: health_score -= 30 if "running" not in hermes: health_score -= 30 if "running" not in balance_updater: health_score -= 20 if comp_summary.get("halted"): health_score -= 20 if backup_status == "failed": health_score -= 10 return { "balance": balance, "signal": signal, "hermes_decision": hermes_decision, "wiki_entries": wiki_entries, "polyhub": polyhub, "hermes": hermes, "balance_updater": balance_updater, "backup_status": backup_status, "backup_time": backup_time, "report": report_text, "llm_errors_today": llm_errors_today, "llm_error_counts": error_counts_today, "llm_error_details": error_details_today, "recent_notes": recent_notes, "total_notes": total_notes, "clusters": cluster_summary, "followed_wallets": followed_count, "auto_adjust": auto_adjust_info, "compounding": comp_summary, "virtual_capital": virtual_summary, "dry_run": dry_run_stats, "flags": flag_summary, "health_score": health_score, "phases": phases, "current_phase": current_phase, "kpi": kpi, "adaptive_history": adaptive_history, "last_restore": last_restore, "last_startup": last_startup, "last_factory_rebuild": last_factory_rebuild, "backup_history": backup_history, "economic_events": economic_events, } # ── Dashboard (HTML) with waving flag animation ── @app.get("/dashboard", response_class=HTMLResponse) def dashboard(): html = """ Polymarket Bot Dashboard

🤖 Polymarket Trading Bot Dashboard

Health Score: --

Loading...

🕒 World Clock

📅 Economic Calendar (Today & Tomorrow)

Loading...

🎯 5‑Phase Roadmap

📈 KPI Tracker (Monthly Net Profit)

Target-
Current Month Profit-
Progress-
Break‑even (days)-

📊 System Status

Original Deposit-
Current Balance-
PolyHub Listener-
Hermes Agent-
Balance Updater-
Git Backup-
Last Restore-
Last Factory Rebuild-
Last Space Restart-

🕒 Backup History (last 10)

Timestamp (UTC)Status
No entries yet.

📡 Latest Signal

-

🧠 Hermes Decision

-

⚠️ LLM Errors Today

-

-
-

📝 Recent Notes (0)

-

💰 Balance & Compounding

Original Deposit-
Current Balance-
Tier-
Consecutive Wins/Losses-
Risk Pool-
MAX BET-
Halted-

💰 Virtual Account

Original Deposit-
Current Balance-
Risk Pool-
Tier-
MAX BET-

🧪 Dry Run Simulation

Active-
Open Positions-
Closed-
Wins / Losses-
Win Rate-
Total P&L-

📋 Dry Run Open Positions

-

📋 Dry Run Closed Positions

-

🏴 Flags

Open-
Closed-
Accuracy-

📊 Wallet Clusters (LLM reasoning)

-

📚 Recent Wiki Entries (0)

-

📊 Daily Detailed Report

Loading...

Dashboard auto‑refreshes every 60 seconds. Last update:

""" return HTMLResponse(content=html)