Files
MoFin/venv/lib/python3.12/site-packages/alphasift/dsa_adapter.py
T
知微 fa45d8aa5f fix: 小果地址统一node122(兼容LAN+EasyTier)
- health_checklist.json: 192.168.1.122→node122
- ocr_client.py: docstring IP→node122
- docs/market-data-requirements.md: IP→node122
- 所有API调用通过ProxyHandler({})绕过系统代理
  Privoxy对node122:18003返回500,直连正常
2026-06-30 02:56:35 +08:00

179 lines
6.3 KiB
Python

# -*- coding: utf-8 -*-
"""Stable DSA integration adapter.
DSA should depend on this module instead of AlphaSift internal pipeline,
strategy, or model details. Keep this contract backward-compatible.
"""
from __future__ import annotations
from dataclasses import asdict, is_dataclass
from typing import Any, Dict, List
from alphasift import __version__
from alphasift.pipeline import screen as run_screen
from alphasift.strategy import list_strategies as load_strategies
CONTRACT_VERSION = "1"
def get_contract_version() -> str:
"""Return the stable adapter contract version."""
return CONTRACT_VERSION
def get_status(context: Dict[str, Any] | None = None) -> Dict[str, Any]:
"""Return non-sensitive adapter status for DSA health checks."""
try:
strategies = list_strategies(context=context)
strategy_count = len(strategies)
available = True
error = ""
except Exception as exc: # pragma: no cover - defensive integration boundary
strategy_count = 0
available = False
error = str(exc)
payload: Dict[str, Any] = {
"available": available,
"version": __version__,
"contract_version": CONTRACT_VERSION,
"strategy_count": strategy_count,
}
if error:
payload["error"] = error
return payload
def list_strategies(context: Dict[str, Any] | None = None) -> List[Dict[str, Any]]:
"""List enabled AlphaSift strategies using a DSA-stable shape."""
return [
{
"id": item.name,
"name": item.display_name,
"description": item.description,
"version": item.version,
"category": item.category,
"tags": list(item.tags),
"market_scope": list(item.market_scope),
}
for item in load_strategies()
]
def screen(
strategy: str,
*,
market: str = "cn",
max_results: int = 20,
use_llm: bool = True,
context: Dict[str, Any] | None = None,
) -> Dict[str, Any]:
"""Run one screening request and return a stable DSA response."""
result = run_screen(
strategy,
market=market,
max_output=max_results,
use_llm=use_llm,
context=context,
)
picks = [_normalize_pick(item, index + 1) for index, item in enumerate(result.picks)]
return {
"source": "alphasift",
"contract_version": CONTRACT_VERSION,
"run_id": result.run_id,
"strategy": result.strategy,
"strategy_version": result.strategy_version,
"strategy_category": result.strategy_category,
"market": result.market,
"snapshot_count": result.snapshot_count,
"after_filter_count": result.after_filter_count,
"llm_ranked": result.llm_ranked,
"llm_market_view": result.llm_market_view,
"llm_selection_logic": result.llm_selection_logic,
"llm_portfolio_risk": result.llm_portfolio_risk,
"llm_coverage": result.llm_coverage,
"llm_parse_errors": list(result.llm_parse_errors),
"candidate_count": len(picks),
"candidates": picks,
"warnings": list(result.degradation),
"source_errors": list(result.source_errors),
}
def _normalize_pick(raw: Any, fallback_rank: int) -> Dict[str, Any]:
item = _to_plain(raw)
if not isinstance(item, dict):
item = {"code": str(item)}
score = item.get("final_score")
if score is None:
score = item.get("screen_score")
reason = item.get("llm_thesis") or item.get("ranking_reason") or item.get("risk_summary") or _build_reason(item)
return {
"rank": item.get("rank") or fallback_rank,
"code": item.get("code") or item.get("symbol") or item.get("stock_code") or "",
"name": item.get("name") or item.get("stock_name") or "",
"score": score,
"screen_score": item.get("screen_score"),
"reason": reason,
"risk_level": item.get("risk_level") or "",
"risk_flags": list(item.get("risk_flags") or []),
"llm_score": item.get("llm_score"),
"llm_confidence": item.get("llm_confidence"),
"llm_sector": item.get("llm_sector") or "",
"llm_theme": item.get("llm_theme") or "",
"llm_tags": list(item.get("llm_tags") or []),
"llm_thesis": item.get("llm_thesis") or "",
"llm_catalysts": list(item.get("llm_catalysts") or []),
"llm_risks": list(item.get("llm_risks") or []),
"llm_watch_items": list(item.get("llm_watch_items") or []),
"llm_invalidators": list(item.get("llm_invalidators") or []),
"llm_style_fit": item.get("llm_style_fit") or "",
"price": item.get("price"),
"change_pct": item.get("change_pct"),
"amount": item.get("amount"),
"industry": item.get("industry") or "",
"factor_scores": dict(item.get("factor_scores") or {}),
"dsa_context": dict(item.get("dsa_context") or {}),
"dsa_news": list(item.get("dsa_news") or []),
"dsa_analysis_summary": item.get("dsa_analysis_summary") or "",
"post_analysis_summaries": dict(item.get("post_analysis_summaries") or {}),
"post_analysis_tags": list(item.get("post_analysis_tags") or []),
"raw": item,
}
def _build_reason(item: Dict[str, Any]) -> str:
summaries = item.get("post_analysis_summaries") or {}
if isinstance(summaries, dict):
summary = next((str(value) for value in summaries.values() if value), "")
if summary:
return summary
parts: List[str] = []
factors = item.get("factor_scores") or {}
if isinstance(factors, dict):
top_factors = sorted(
((key, value) for key, value in factors.items() if isinstance(value, (int, float))),
key=lambda pair: pair[1],
reverse=True,
)[:3]
if top_factors:
factor_text = ", ".join(f"{key} {value:.1f}" for key, value in top_factors)
parts.append(f"Top factors: {factor_text}")
if item.get("industry"):
parts.append(f"Industry: {item['industry']}")
if item.get("risk_level"):
parts.append(f"Risk level: {item['risk_level']}")
return "; ".join(parts)
def _to_plain(value: Any) -> Any:
if is_dataclass(value):
return asdict(value)
if hasattr(value, "model_dump"):
return value.model_dump()
if hasattr(value, "dict") and callable(value.dict):
return value.dict()
return value