From 480f0299b050f078283bd13abd697afbb7a3a76b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=B0=91=E6=9D=B0?= Date: Thu, 9 Apr 2026 22:10:15 +0800 Subject: [PATCH] feat(orchestrator): LiveMode + /ws/orchestrator WebSocket endpoint --- orchestrator/live_mode.py | 47 +++++++++++++++++++++++++++++++++++ web_dashboard/backend/main.py | 34 +++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 orchestrator/live_mode.py diff --git a/orchestrator/live_mode.py b/orchestrator/live_mode.py new file mode 100644 index 00000000..b96b5e04 --- /dev/null +++ b/orchestrator/live_mode.py @@ -0,0 +1,47 @@ +import asyncio +import json +import logging +from datetime import datetime, timezone +from typing import List, Optional + +logger = logging.getLogger(__name__) + + +class LiveMode: + """ + Triggers signal computation for a list of tickers and broadcasts + results via a callback (e.g., WebSocket send). + """ + + def __init__(self, orchestrator): + self._orchestrator = orchestrator + + async def run_once(self, tickers: List[str], date: Optional[str] = None) -> List[dict]: + """ + Compute combined signals for all tickers on the given date (default: today). + Returns list of signal dicts. + """ + if date is None: + date = datetime.now(timezone.utc).strftime("%Y-%m-%d") + + results = [] + for ticker in tickers: + try: + sig = self._orchestrator.get_combined_signal(ticker, date) + results.append({ + "ticker": ticker, + "date": date, + "direction": sig.direction, + "confidence": sig.confidence, + "quant_direction": sig.quant_signal.direction if sig.quant_signal else None, + "llm_direction": sig.llm_signal.direction if sig.llm_signal else None, + "timestamp": sig.timestamp.isoformat(), + }) + except Exception as e: + logger.error("LiveMode: failed for %s %s: %s", ticker, date, e) + results.append({ + "ticker": ticker, + "date": date, + "error": str(e), + }) + return results diff --git a/web_dashboard/backend/main.py b/web_dashboard/backend/main.py index 05c70daa..229b2852 100644 --- a/web_dashboard/backend/main.py +++ b/web_dashboard/backend/main.py @@ -1100,6 +1100,40 @@ async def root(): return {"message": "TradingAgents Web Dashboard API", "version": "0.1.0"} +@app.websocket("/ws/orchestrator") +async def ws_orchestrator(websocket: WebSocket): + """WebSocket endpoint for orchestrator live signals.""" + await websocket.accept() + try: + while True: + data = await websocket.receive_text() + payload = json.loads(data) + tickers = payload.get("tickers", []) + date = payload.get("date") + + # Lazy import to avoid loading heavy deps at startup + import sys + sys.path.insert(0, str(REPO_ROOT)) + from orchestrator.config import OrchestratorConfig + from orchestrator.orchestrator import TradingOrchestrator + from orchestrator.live_mode import LiveMode + + config = OrchestratorConfig( + quant_backtest_path=os.environ.get("QUANT_BACKTEST_PATH", ""), + ) + orchestrator = TradingOrchestrator(config) + live = LiveMode(orchestrator) + results = await live.run_once(tickers, date) + await websocket.send_text(json.dumps({"signals": results})) + except WebSocketDisconnect: + pass + except Exception as e: + try: + await websocket.send_text(json.dumps({"error": str(e)})) + except Exception: + pass + + if __name__ == "__main__": import uvicorn # Run with: cd web_dashboard && ../env312/bin/python -m uvicorn main:app --reload