From 14191abc297db05ff6c36a3ce6f0031d99d406e5 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:05:03 +0800 Subject: [PATCH] feat(orchestrator): TradingOrchestrator main class with get_combined_signal --- orchestrator/orchestrator.py | 63 ++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 orchestrator/orchestrator.py diff --git a/orchestrator/orchestrator.py b/orchestrator/orchestrator.py new file mode 100644 index 00000000..baf042eb --- /dev/null +++ b/orchestrator/orchestrator.py @@ -0,0 +1,63 @@ +import logging +from datetime import datetime, timezone +from typing import Optional + +from orchestrator.config import OrchestratorConfig +from orchestrator.signals import Signal, FinalSignal, SignalMerger +from orchestrator.quant_runner import QuantRunner +from orchestrator.llm_runner import LLMRunner + +logger = logging.getLogger(__name__) + + +class TradingOrchestrator: + def __init__(self, config: OrchestratorConfig): + self._config = config + self._merger = SignalMerger(config) + self._quant: Optional[QuantRunner] = None + self._llm: Optional[LLMRunner] = None + + # Initialize runners (quant requires quant_backtest_path) + if config.quant_backtest_path: + try: + self._quant = QuantRunner(config) + except Exception as e: + logger.warning("TradingOrchestrator: QuantRunner init failed: %s", e) + + self._llm = LLMRunner(config) + + def get_combined_signal(self, ticker: str, date: str) -> FinalSignal: + """ + Get merged signal for ticker on date. + Degradation: + - quant fails (error signal): use llm only with llm_solo_penalty + - llm fails (error signal): use quant only with quant_solo_penalty + - both fail: raises ValueError + """ + quant_sig: Optional[Signal] = None + llm_sig: Optional[Signal] = None + + # Get quant signal + if self._quant is not None: + try: + quant_sig = self._quant.get_signal(ticker, date) + # Treat error signals (confidence=0, direction=0 with error metadata) as None + if quant_sig.metadata.get("error") or quant_sig.metadata.get("reason") == "no_data": + logger.warning("TradingOrchestrator: quant signal degraded for %s %s", ticker, date) + quant_sig = None + except Exception as e: + logger.error("TradingOrchestrator: quant get_signal failed: %s", e) + quant_sig = None + + # Get llm signal + try: + llm_sig = self._llm.get_signal(ticker, date) + if llm_sig.metadata.get("error"): + logger.warning("TradingOrchestrator: llm signal degraded for %s %s", ticker, date) + llm_sig = None + except Exception as e: + logger.error("TradingOrchestrator: llm get_signal failed: %s", e) + llm_sig = None + + # merge raises ValueError if both None + return self._merger.merge(quant_sig, llm_sig)