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