feat(mcp): add quarterly pyramid distribution lookup
This commit is contained in:
@@ -20,6 +20,12 @@ class Empty(Contract):
|
||||
pass
|
||||
|
||||
|
||||
class PyramidQuery(Contract):
|
||||
current_date: date = Field(description="用于确定季度的日期,格式 YYYY-MM-DD;自动查询该季度完整起止范围")
|
||||
region: str = Field(min_length=3, max_length=10, pattern=r"^[A-Z]+$")
|
||||
delay: int = Field(ge=0, le=1, strict=True)
|
||||
|
||||
|
||||
class Authentication(Contract):
|
||||
action: Literal["connect", "verify"] = "connect"
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"""Partition platform category counts without treating missing evidence as zero."""
|
||||
|
||||
from calendar import monthrange
|
||||
|
||||
|
||||
def quarter_period(current_date):
|
||||
"""Return the full calendar quarter containing the supplied date, inclusive."""
|
||||
quarter = (current_date.month - 1) // 3 + 1
|
||||
end_month = quarter * 3
|
||||
start = current_date.replace(month=end_month - 2, day=1)
|
||||
end = current_date.replace(month=end_month, day=monthrange(current_date.year, end_month)[1])
|
||||
return {"quarter": f"{current_date.year}-Q{quarter}",
|
||||
"start_date": start.isoformat(), "end_date": end.isoformat()}
|
||||
|
||||
|
||||
def distribution(raw, region, delay):
|
||||
"""Return three category lists; raise ValueError on incomplete or duplicate data."""
|
||||
if not isinstance(raw, dict) or not isinstance(raw.get("pyramids"), list):
|
||||
raise ValueError("平台未提供 Pyramid 分布")
|
||||
groups = {"lit": [], "in_progress": [], "unlit": []}
|
||||
seen = set()
|
||||
for row in raw["pyramids"]:
|
||||
if not isinstance(row, dict):
|
||||
raise ValueError("平台 Pyramid 数据格式异常")
|
||||
if row.get("region") != region or row.get("delay") != delay:
|
||||
continue
|
||||
category, count = row.get("category"), row.get("alphaCount")
|
||||
if (not isinstance(category, dict)
|
||||
or not isinstance(category.get("id"), str) or not category["id"]
|
||||
or not isinstance(category.get("name"), str) or not category["name"]
|
||||
or type(count) is not int or count < 0 or category["id"] in seen):
|
||||
raise ValueError("平台分类或计数缺失、非法或重复,不能判定点塔状态")
|
||||
seen.add(category["id"])
|
||||
key = "lit" if count >= 3 else "in_progress" if count > 0 else "unlit"
|
||||
groups[key].append({"category": {"id": category["id"], "name": category["name"]},
|
||||
"alpha_count": count, "remaining": max(0, 3 - count)})
|
||||
if not seen:
|
||||
raise ValueError("平台未返回此 region/delay 的分类,不能认定全部未点亮")
|
||||
for items in groups.values():
|
||||
items.sort(key=lambda item: item["category"]["id"])
|
||||
return groups
|
||||
@@ -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 ..worldquant import WqError
|
||||
from .contracts import DirectCandidate, History
|
||||
from .queries import EvidenceQueries, page
|
||||
|
||||
@@ -48,6 +49,25 @@ class ResearchAccess:
|
||||
def run_url(self, run_id):
|
||||
return f"{self.public_origin}/#backtests?run_id={run_id}"
|
||||
|
||||
async def pyramid_distribution(self, args):
|
||||
"""Read the supplied date's full quarter using the three-Alpha completion rule."""
|
||||
from .pyramids import distribution, quarter_period
|
||||
|
||||
account = await self.db.get(Account, self.principal.account_id)
|
||||
if not account or account.wq_user_id != self.principal.wq_user_id:
|
||||
raise ResearchError("ACCOUNT_MISMATCH", "平台账户绑定已变化")
|
||||
period = quarter_period(args.current_date)
|
||||
try:
|
||||
raw = await self.client.get_pyramid_alphas(period["start_date"], period["end_date"])
|
||||
groups = distribution(raw, args.region, args.delay)
|
||||
except WqError as exc:
|
||||
raise ResearchError(exc.code.upper(), str(exc), retryable=True) from None
|
||||
except ValueError as exc:
|
||||
raise ResearchError("INVALID_PLATFORM_DATA", str(exc)) from None
|
||||
return {"region": args.region, "delay": args.delay, "threshold": 3,
|
||||
"current_date": args.current_date.isoformat(), "period": period,
|
||||
"source": "worldquant_platform", **groups}
|
||||
|
||||
async def capabilities(self, args):
|
||||
return {**await self.backtests.capabilities(), "max_candidates": 100,
|
||||
"settings_schema": DirectCandidate.model_json_schema(),
|
||||
|
||||
Reference in New Issue
Block a user