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
+84 -21
View File
@@ -1,3 +1,4 @@
import { WorkspaceTable } from "../components/WorkspaceTable";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
Banner,
@@ -10,7 +11,6 @@ import {
Popover,
Pagination,
Select,
Table,
Tag,
TextArea,
Toast,
@@ -118,6 +118,9 @@ const initialColumns = [
"source",
];
const columnLabels: Record<string, string> = {
selection: "Selection",
combo: "Combo",
component_count: "组件数(已核实)",
source: "研究来源",
name: "Alpha",
expression: "表达式",
@@ -147,6 +150,7 @@ const columnLabels: Record<string, string> = {
};
export function AlphaPage({
managementScope = "non_super",
account,
taskPanelOpen,
version,
@@ -160,6 +164,7 @@ export function AlphaPage({
onOverlay,
onAction,
}: {
managementScope?: "super" | "non_super";
account: Account | null;
taskPanelOpen: boolean;
version: string;
@@ -173,6 +178,21 @@ export function AlphaPage({
onOverlay: () => void;
onAction: (action: WorkspaceAction) => void;
}) {
const columnKey =
managementScope === "super" ? "superalpha-columns" : "alpha-columns";
const defaultColumns =
managementScope === "super"
? initialColumns.flatMap((c) =>
c === "expression" ? ["selection", "combo", "component_count"] : [c],
)
: initialColumns;
const menuColumns = Object.fromEntries(
Object.entries(columnLabels).filter(([k]) =>
managementScope === "super"
? k !== "expression"
: !["selection", "combo", "component_count"].includes(k),
),
);
const [data, setData] = useState<Page>({
items: [],
total: 0,
@@ -197,7 +217,7 @@ export function AlphaPage({
const [visibleColumns, setVisibleColumns] = useState<string[]>(() => {
try {
const stored: unknown = JSON.parse(
localStorage.getItem("alpha-columns") || "null",
localStorage.getItem(columnKey) || "null",
);
return Array.isArray(stored)
? [
@@ -208,9 +228,9 @@ export function AlphaPage({
),
]),
]
: initialColumns;
: defaultColumns;
} catch {
return initialColumns;
return defaultColumns;
}
});
const [detailId, setDetailId] = useState<string | null>(null);
@@ -223,6 +243,7 @@ export function AlphaPage({
version: number;
text: string;
} | null>(null);
const selectionVersions = useRef<Record<string, number>>({});
const [bulkVersions, setBulkVersions] = useState<Record<string, number>>({});
const actionNonce = useRef(0);
const [bulk, setBulk] = useState({
@@ -250,6 +271,7 @@ export function AlphaPage({
const params = useMemo(
() => ({
...filters,
management_scope: managementScope,
submission,
submission_blocked: submissionBlocked || undefined,
sort,
@@ -257,15 +279,24 @@ export function AlphaPage({
limit: pageSize,
offset: (page - 1) * pageSize,
}),
[filters, submission, submissionBlocked, sort, direction, pageSize, page],
[
filters,
submission,
submissionBlocked,
sort,
direction,
pageSize,
page,
managementScope,
],
);
const query = queryString(params);
useEffect(() => {
if (!active) return;
onContext({
page: "alphas",
page: managementScope === "super" ? "superalphas" : "alphas",
alpha_id: detailId,
selected_ids: selected,
selected_ids: selected.slice(0, 100),
filters: Object.fromEntries(
Object.entries(params).filter(
([, value]) => value !== "" && value !== undefined && value !== null,
@@ -274,7 +305,7 @@ export function AlphaPage({
});
}, [active, detailId, selected, params, onContext]);
useEffect(() => {
if (!action || actionNonce.current === action.nonce) return;
if (!active || !action || actionNonce.current === action.nonce) return;
actionNonce.current = action.nonce;
if (action.type === "open_alpha") {
setDetailId(action.alpha_id);
@@ -306,7 +337,7 @@ export function AlphaPage({
if (nextSort) setSort(String(nextSort));
if (nextDirection) setDirection(String(nextDirection));
}
}, [action, onOverlay]);
}, [action, onOverlay, active]);
const refresh = useCallback(() => setLocalVersion((n) => n + 1), []);
useEffect(() => {
if (!active) return;
@@ -315,7 +346,9 @@ export function AlphaPage({
setError("");
Promise.all([
api<Page>(`/alphas?${query}`, { signal: controller.signal }),
api<Facets>("/alphas/facets", { signal: controller.signal }),
api<Facets>(`/alphas/facets?management_scope=${managementScope}`, {
signal: controller.signal,
}),
])
.then(([results, nextFacets]) => {
if (!controller.signal.aborted) {
@@ -333,6 +366,9 @@ export function AlphaPage({
}, [active, query, version, localVersion]);
useEffect(() => {
setSelected([]);
selectionVersions.current = {};
}, [filters, submission, submissionBlocked, managementScope]);
useEffect(() => {
tablePanel.current?.querySelector(".semi-table-body")?.scrollTo({ top: 0 });
}, [query]);
function updateDraft(key: string, value: unknown) {
@@ -464,6 +500,24 @@ export function AlphaPage({
</code>
),
},
...(["selection", "combo"] as const).map(
(key): ColumnProps<Alpha> => ({
key,
title: columnLabels[key],
width: 260,
render: (_, row) => (
<code title={row![`${key}_preview`]}>
{row![`${key}_preview`] || "未提供"}
</code>
),
}),
),
{
key: "component_count",
title: "组件数(已核实)",
width: 140,
render: (_, row) => row!.component_count ?? "未核实",
},
{
key: "source",
title: "研究来源",
@@ -953,6 +1007,7 @@ export function AlphaPage({
{error && <Banner type="danger" description={error} />}
<section className="library-panel" ref={tablePanel}>
<SavedViews
managementScope={managementScope}
submission={submission}
submissionBlocked={submissionBlocked}
selectedId={selectedViewId}
@@ -1005,7 +1060,7 @@ export function AlphaPage({
]),
];
setVisibleColumns(next);
localStorage.setItem("alpha-columns", JSON.stringify(next));
localStorage.setItem(columnKey, JSON.stringify(next));
}}
/>
<div
@@ -1058,7 +1113,7 @@ export function AlphaPage({
<CheckboxGroup
direction="vertical"
value={displayedColumns}
options={Object.entries(columnLabels).map(
options={Object.entries(menuColumns).map(
([value, label]) => ({
value,
label,
@@ -1071,10 +1126,7 @@ export function AlphaPage({
onChange={(values) => {
const next = values.map(String);
setVisibleColumns(next);
localStorage.setItem(
"alpha-columns",
JSON.stringify(next),
);
localStorage.setItem(columnKey, JSON.stringify(next));
}}
/>
</div>
@@ -1221,9 +1273,7 @@ export function AlphaPage({
onClick={() => {
setBulkVersions(
Object.fromEntries(
data.items
.filter((row) => selected.includes(row.id))
.map((row) => [row.id, row.research.version]),
selected.map((id) => [id, selectionVersions.current[id]]),
),
);
setBulkOpen(true);
@@ -1252,7 +1302,8 @@ export function AlphaPage({
</Button>
</div>
)}
<Table<Alpha>
<WorkspaceTable<Alpha>
fill
columns={columns}
dataSource={data.items}
rowKey="id"
@@ -1265,7 +1316,19 @@ export function AlphaPage({
}}
rowSelection={{
selectedRowKeys: selected,
onChange: (keys) => setSelected((keys ?? []).map(String)),
onChange: (keys) => {
const pageIds = new Set(data.items.map((r) => r.id));
const nextIds = (keys ?? [])
.map(String)
.filter((id) => pageIds.has(id));
for (const row of data.items)
if (nextIds.includes(row.id))
selectionVersions.current[row.id] = row.research.version;
setSelected((old) => [
...old.filter((id) => !pageIds.has(id)),
...nextIds,
]);
},
fixed: !compact,
}}
pagination={false}
+3 -3
View File
@@ -25,12 +25,12 @@ const scopes = [
[
"research:refresh",
"刷新研究数据",
"更新缓存、检查自相关及恢复 WorldQuant 认证",
"更新缓存、预览 SUPER 组件、检查自相关及恢复 WorldQuant 认证",
],
[
"research:write",
"保存研究模板",
"保存大模型总结的模板及来源,供后续批量回测",
"保存研究模板与方案",
"保存模板、Super Alpha 方案及构造候选,不执行回测",
],
["backtests:execute", "执行回测", "提交新的回测批次"],
["backtests:control", "控制回测", "暂停、继续、停止与恢复采集"],