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:
parent
73fa75d9fb
commit
56dc76d44a
|
|
@ -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 信号缓存目录
|
||||
|
|
@ -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]
|
||||
|
||||
# 只有 LLM(quant 失败)
|
||||
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,
|
||||
)
|
||||
|
||||
# 只有 Quant(llm 失败)
|
||||
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,
|
||||
)
|
||||
Loading…
Reference in New Issue