feat: add Super Alpha research, management and MCP workflows
Deploy production / deploy (push) Successful in 56s

This commit is contained in:
yuxuanhui
2026-09-13 12:32:16 +08:00
parent 7c8188df9c
commit e256d6fef1
59 changed files with 3766 additions and 125 deletions
+28 -5
View File
@@ -5,7 +5,7 @@ from typing import Annotated, Literal
from pydantic import Field, model_validator
from ..backtests.contracts import Candidate, SimulationSettings
from ..backtests.contracts import Candidate, SimulationSettings, SuperSimulationSettings
from ..catalog.contracts import CatalogFilters, Scope
from ..preparations.contracts import PreparationReference
from ..research.workspace_contracts import TemplateSpec
@@ -50,11 +50,26 @@ class CompleteSettings(SimulationSettings):
model_config = {"json_schema_extra": {"required": list(SimulationSettings.model_fields)}}
class CompleteSuperSettings(SuperSimulationSettings):
@model_validator(mode="before")
@classmethod
def complete(cls, value):
if isinstance(value, dict) and set(cls.model_fields) - value.keys():
raise ValueError("必须提供每项完整 SUPER 设置;先读取 get_research_capabilities")
return value
model_config = {"json_schema_extra": {"required": list(SuperSimulationSettings.model_fields)}}
class DirectCandidate(Candidate):
settings: CompleteSettings
settings: CompleteSuperSettings | CompleteSettings
class Provenance(Contract):
research_id: RunId | None = None
superalpha_plan_id: RunId | None = None
superalpha_plan_version: int | None = Field(default=None, ge=1)
selection_snapshot_ids: list[RunId] = Field(default_factory=list, max_length=100)
reference: str | None = Field(default=None, max_length=200)
batch_id: str | None = Field(default=None, max_length=200)
hypothesis: str | None = Field(default=None, max_length=2000)
@@ -129,12 +144,18 @@ class Scopes(Contract):
class SettingOptions(Page):
kind: Literal["settings"]
alpha_type: Literal["REGULAR", "SUPER"] = "REGULAR"
class Operators(Page):
kind: Literal["operators"]
q: str = Field(default="", max_length=300)
category: str | None = None
stage: Literal["REGULAR", "SELECTION", "COMBO"] | None = None
class SuperMetadata(Contract):
kind: Literal["superalpha"]
class Availability(Contract):
@@ -144,7 +165,7 @@ class Availability(Contract):
class Metadata(Contract):
query: Annotated[Scopes | SettingOptions | Operators | Availability, Field(discriminator="kind")]
query: Annotated[Scopes | SettingOptions | Operators | Availability | SuperMetadata, Field(discriminator="kind")]
class CatalogRefresh(Contract):
@@ -192,6 +213,8 @@ class SubmissionCheck(Contract):
class History(Page):
research_id: str | None = Field(default=None, max_length=36)
alpha_type: Literal["REGULAR", "SUPER"] | None = None
source: str | None = Field(default=None, max_length=100)
reference: str | None = Field(default=None, max_length=200)
status: str | None = Field(default=None, max_length=30)
@@ -224,13 +247,13 @@ class Results(Page):
class Artifact(Page):
item_id: RunId
kind: Literal["snapshot", "pnl"]
kind: Literal["snapshot", "pnl", "components"]
date_from: date | None = None
date_to: date | None = None
@model_validator(mode="after")
def dates(self):
if self.kind == "snapshot" and (self.date_from or self.date_to):
if self.kind != "pnl" and (self.date_from or self.date_to):
raise ValueError("日期筛选仅用于 PnL")
if self.date_from and self.date_to and self.date_from > self.date_to:
raise ValueError("起始日期不能晚于结束日期")
+9 -4
View File
@@ -2,7 +2,7 @@
from collections import Counter
from sqlalchemy import func, select
from sqlalchemy import func, or_, select
from ..alphas import number, sanitize
from ..backtests.contracts import fingerprint
@@ -54,7 +54,7 @@ def item_summary(item, result):
("sharpe", "fitness", "returns", "turnover", "margin", "drawdown")}
return encode_snapshot({
**{k: getattr(item, k) for k in (
"id", "run_id", "client_item_id", "expression", "settings", "attempt_id",
"id", "run_id", "client_item_id", "expression", "selection", "combo", "alpha_type", "settings", "attempt_id",
"platform_status", "collection_status", "persistence_status", "simulation_id", "alpha_id",
)},
"error": sanitize(item.error), "metrics": metrics,
@@ -73,10 +73,12 @@ class EvidenceQueries:
query = select(BacktestItem, BacktestResult, BacktestRun).join(
BacktestRun, BacktestRun.id == BacktestItem.run_id
).outerjoin(BacktestResult, BacktestResult.item_id == BacktestItem.id)
for key in ("source", "reference"):
for key in ("source", "reference", "research_id"):
value = getattr(args, key)
if value is not None:
query = query.where(BacktestRun.source["kind" if key == "source" else key].as_string() == value)
if args.alpha_type:
query = query.where(BacktestItem.alpha_type == args.alpha_type)
if args.status:
query = query.where(BacktestRun.status == args.status)
if args.created_from:
@@ -89,7 +91,7 @@ class EvidenceQueries:
query = query.where(BacktestItem.settings["delay"].as_integer() == args.scope.delay)
if args.q:
escaped = args.q.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
query = query.where(BacktestItem.expression.ilike(f"%{escaped}%", escape="\\"))
query = query.where(or_(*(getattr(BacktestItem, key).ilike(f"%{escaped}%", escape="\\") for key in ("expression", "selection", "combo"))))
matches = {}
if args.candidates:
for c in args.candidates:
@@ -120,6 +122,9 @@ class EvidenceQueries:
if not item:
raise ResearchError("NOT_FOUND", "候选不存在")
result = await self.db.get(BacktestResult, item.id)
if args.kind == "components":
from ..superalpha.evidence import actual_components
return await actual_components(self.db, item.id, args.limit, args.offset)
if args.kind == "snapshot":
# Top-level entries retain complete nested values; no hidden string/list truncation.
entries = [{"key": k, "value": v} for k, v in sanitize(result.snapshot).items()] if result else []
+24 -5
View File
@@ -25,6 +25,7 @@ from ..research.workspace_contracts import FieldAvailabilityInput
from ..schemas import JobInput
from ..submission import CheckInput, correlation_allows_check, create_check_job, local_alpha, source
from ..submission import fingerprint as submission_fingerprint
from ..superalpha.access import SuperResearchAccess
from ..worldquant import WqError
from .contracts import DirectCandidate, History
from .queries import EvidenceQueries, page
@@ -37,7 +38,7 @@ class ResearchError(Exception):
"retry_after": retry_after, "affected_items": affected_items or []}
class ResearchAccess:
class ResearchAccess(SuperResearchAccess):
def __init__(self, db, principal, client, public_origin):
self.db, self.principal, self.client = db, principal, client
self.public_origin = public_origin.rstrip("/")
@@ -71,6 +72,10 @@ class ResearchAccess:
async def capabilities(self, args):
return {**await self.backtests.capabilities(), "max_candidates": 100,
"settings_schema": DirectCandidate.model_json_schema(),
"superalpha": {"plan_with": "save_superalpha_plan", "build_with": "build_superalpha_candidates",
"preview_with": "preview_superalpha_selection", "read_selection_with": "get_superalpha_selection",
"job_with": "get_refresh_job", "backtest_with": "submit_backtests", "platform_batch_size": 1,
"component_evidence": "预览与实际组件分别记录,未知不能认定为同池"},
"confirmation": "调用者须已获本批执行授权;直接提交后返回稳定运行 ID",
"duplicate_policies": ["reject", "rerun"], "permissions": sorted(self.principal.scopes),
"metadata_only": True, "actual_platform_allowance": None,
@@ -159,15 +164,23 @@ class ResearchAccess:
async def metadata(self, args):
q = args.query
metadata = ResearchMetadata(self.db)
if q.kind == "superalpha":
from ..superalpha.metadata import metadata as super_metadata
return await super_metadata(self.db)
if q.kind == "scopes":
return {"source": "worldquant_platform", **await platform_options(self.client)}
if q.kind == "operators":
data = await metadata.operators(q.q, q.category, limit=q.limit, offset=q.offset)
data = await metadata.operators(q.q, q.category, limit=q.limit, offset=q.offset, stage=q.stage)
return {**data, "status": "available" if data["fetched_at"] else "not_cached",
"has_more": q.offset + len(data["items"]) < data["total"]}
if q.kind == "settings":
data = await metadata.get("settings")
items = data["content"].get("items", [])
special = {"selectionHandling", "selectionLimit", "componentActivation"}
if q.alpha_type == "REGULAR":
items = [{**r, "fields": {k: v for k, v in r.get("fields", {}).items() if k not in special}} for r in items]
else:
items = [{**r, "super_settings_completeness": "cached" if special <= r.get("fields", {}).keys() else "unknown"} for r in items]
return {"status": "available" if data["fetched_at"] else "not_cached",
"fetched_at": data["fetched_at"], **page(items[q.offset:q.offset+q.limit], len(items), q.limit, q.offset)}
data = await metadata.get(availability_key(q.field_id, q.scope))
@@ -201,14 +214,16 @@ class ResearchAccess:
async def refresh_job(self, args):
job = await self.db.get(Job, args.job_id)
if not job or job.kind not in {"catalog_sync", "field_sync", "pnl_refresh", "self_correlation", "submission_check"}:
if not job or job.kind not in {"catalog_sync", "field_sync", "pnl_refresh", "self_correlation", "submission_check", "super_selection_preview"}:
raise ResearchError("NOT_FOUND", "研究刷新任务不存在")
result = await self.business.get_job_status(args.job_id)
query = select(JobItem).where(JobItem.job_id == job.id, JobItem.error.is_not(None))
total = await self.db.scalar(select(func.count()).select_from(query.subquery()))
errors = list(await self.db.scalars(query.order_by(JobItem.alpha_id).limit(args.limit).offset(args.offset)))
result.pop("errors", None)
return {**result, "job_id": job.id, "artifact_reference": job.payload,
artifact = ({"job_id": job.id, "snapshot_id": job.checkpoint.get("snapshot_id"), "read_with": "get_superalpha_selection"}
if job.kind == "super_selection_preview" else job.payload)
return {**result, "job_id": job.id, "artifact_reference": artifact,
"errors": page([{"alpha_id": e.alpha_id, "error": e.error} for e in errors], total, args.limit, args.offset)}
async def check_self_correlation(self, args):
@@ -306,7 +321,11 @@ class ResearchAccess:
invalid.append(c.client_item_id)
if invalid:
raise ResearchError("UNSUPPORTED_SETTINGS", "已缓存平台设置不支持这些组合;可显式刷新后重试", affected_items=invalid)
return {"settings_validation": "cached", "fetched_at": snapshot["fetched_at"], "field_validation": "unknown"}
result = {"settings_validation": "cached", "fetched_at": snapshot["fetched_at"], "field_validation": "unknown"}
if any(c.alpha_type == "SUPER" for c in candidates):
from ..superalpha.settings import validate_settings
result["super_settings_validation"] = await validate_settings(self.db, [c.settings for c in candidates if c.alpha_type == "SUPER"])
return result
async def submit(self, args):
previous, digest = await self.previous("submit_backtests", args)