feat(orchestrator): add signals.py and config.py

- Signal / FinalSignal dataclasses
- SignalMerger with weighted merge, single-track fallbacks, and cancel-out HOLD
- OrchestratorConfig with all required fields
This commit is contained in:
陈少杰 2026-04-09 21:35:31 +08:00
parent 73fa75d9fb
commit 56dc76d44a
3 changed files with 111 additions and 0 deletions

0
orchestrator/__init__.py Normal file
View File

11
orchestrator/config.py Normal file
View File

@ -0,0 +1,11 @@
from dataclasses import dataclass, field
@dataclass
class OrchestratorConfig:
quant_backtest_path: str = "/Users/chenshaojie/Downloads/quant_backtest"
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 信号缓存目录

100
orchestrator/signals.py Normal file
View File

@ -0,0 +1,100 @@
import logging
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
logger = logging.getLogger(__name__)
@dataclass
class Signal:
ticker: str
direction: int # +1 买入, -1 卖出, 0 持有
confidence: float # 0.0 ~ 1.0
source: str # "quant" | "llm"
timestamp: datetime
metadata: dict = field(default_factory=dict) # 原始输出,用于调试
@dataclass
class FinalSignal:
ticker: str
direction: int # sign(quant_dir×quant_conf + llm_dir×llm_conf)sign(0)→0(HOLD)
confidence: float # abs(weighted_sum) / total_conf
quant_signal: Optional[Signal]
llm_signal: Optional[Signal]
timestamp: datetime
def _sign(x: float) -> int:
"""Return +1, -1, or 0."""
if x > 0:
return 1
elif x < 0:
return -1
return 0
class SignalMerger:
def merge(self, quant: Optional[Signal], llm: Optional[Signal]) -> FinalSignal:
now = datetime.utcnow()
# 两者均失败
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,
)
ticker = (quant or llm).ticker # type: ignore[union-attr]
# 只有 LLMquant 失败)
if quant is None:
assert llm is not None
return FinalSignal(
ticker=ticker,
direction=llm.direction,
confidence=llm.confidence * 0.7,
quant_signal=None,
llm_signal=llm,
timestamp=now,
)
# 只有 Quantllm 失败)
if llm is None:
return FinalSignal(
ticker=ticker,
direction=quant.direction,
confidence=quant.confidence * 0.8,
quant_signal=quant,
llm_signal=None,
timestamp=now,
)
# 两者都有:加权合并
weighted_sum = (
quant.direction * quant.confidence
+ llm.direction * llm.confidence
)
final_direction = _sign(weighted_sum)
if final_direction == 0:
logger.info(
"SignalMerger: weighted_sum=0 for %s — signals cancel out, HOLD",
ticker,
)
total_conf = quant.confidence + llm.confidence
final_confidence = abs(weighted_sum) / total_conf if total_conf > 0 else 0.0
return FinalSignal(
ticker=ticker,
direction=final_direction,
confidence=final_confidence,
quant_signal=quant,
llm_signal=llm,
timestamp=now,
)