From dacb3316fa5bdc03ca3fc14d39171b9d190a5607 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 21:39:23 +0800 Subject: [PATCH] fix(orchestrator): code quality fixes in config and signals - config: remove hardcoded absolute path for quant_backtest_path (now empty string) - config: add llm_solo_penalty (0.7) and quant_solo_penalty (0.8) fields - signals: SignalMerger now accepts OrchestratorConfig in __init__ - signals: use config.llm_solo_penalty / quant_solo_penalty instead of magic numbers - signals: apply quant_weight_cap / llm_weight_cap as confidence upper bounds - signals: both-None branch raises ValueError instead of returning ticker="" - signals: replace assert with explicit ValueError for llm-None-when-quant-None - signals: replace datetime.utcnow() with datetime.now(timezone.utc) --- orchestrator/config.py | 5 ++++- orchestrator/signals.py | 32 +++++++++++++++++--------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/orchestrator/config.py b/orchestrator/config.py index 0beb6c5e..9d3eaea5 100644 --- a/orchestrator/config.py +++ b/orchestrator/config.py @@ -3,9 +3,12 @@ from dataclasses import dataclass, field @dataclass class OrchestratorConfig: - quant_backtest_path: str = "/Users/chenshaojie/Downloads/quant_backtest" + # Must be set to the local quant backtest output directory before use + quant_backtest_path: str = "" trading_agents_config: dict = field(default_factory=dict) quant_weight_cap: float = 0.8 # quant 置信度上限 llm_weight_cap: float = 0.9 # llm 置信度上限 llm_batch_days: int = 7 # LLM 每隔几天运行一次(节省 API) cache_dir: str = "orchestrator/cache" # LLM 信号缓存目录 + llm_solo_penalty: float = 0.7 # LLM 单轨时的置信度折扣 + quant_solo_penalty: float = 0.8 # Quant 单轨时的置信度折扣 diff --git a/orchestrator/signals.py b/orchestrator/signals.py index f9549dcc..1ccecaa3 100644 --- a/orchestrator/signals.py +++ b/orchestrator/signals.py @@ -1,8 +1,10 @@ import logging from dataclasses import dataclass, field -from datetime import datetime +from datetime import datetime, timezone from typing import Optional +from orchestrator.config import OrchestratorConfig + logger = logging.getLogger(__name__) @@ -36,30 +38,27 @@ def _sign(x: float) -> int: class SignalMerger: + def __init__(self, config: OrchestratorConfig) -> None: + self._config = config + def merge(self, quant: Optional[Signal], llm: Optional[Signal]) -> FinalSignal: - now = datetime.utcnow() + now = datetime.now(timezone.utc) # 两者均失败 if quant is None and llm is None: - ticker = "" - return FinalSignal( - ticker=ticker, - direction=0, - confidence=0.0, - quant_signal=None, - llm_signal=None, - timestamp=now, - ) + raise ValueError("both quant and llm signals are None") ticker = (quant or llm).ticker # type: ignore[union-attr] # 只有 LLM(quant 失败) if quant is None: - assert llm is not None + if llm is None: + raise ValueError("llm signal is None when quant is None") return FinalSignal( ticker=ticker, direction=llm.direction, - confidence=llm.confidence * 0.7, + confidence=min(llm.confidence * self._config.llm_solo_penalty, + self._config.llm_weight_cap), quant_signal=None, llm_signal=llm, timestamp=now, @@ -70,7 +69,8 @@ class SignalMerger: return FinalSignal( ticker=ticker, direction=quant.direction, - confidence=quant.confidence * 0.8, + confidence=min(quant.confidence * self._config.quant_solo_penalty, + self._config.quant_weight_cap), quant_signal=quant, llm_signal=None, timestamp=now, @@ -88,7 +88,9 @@ class SignalMerger: ticker, ) total_conf = quant.confidence + llm.confidence - final_confidence = abs(weighted_sum) / total_conf if total_conf > 0 else 0.0 + raw_confidence = abs(weighted_sum) / total_conf if total_conf > 0 else 0.0 + final_confidence = min(raw_confidence, self._config.quant_weight_cap, + self._config.llm_weight_cap) return FinalSignal( ticker=ticker,