feat: add Super Alpha research, management and MCP workflows
Deploy production / deploy (push) Successful in 56s
Deploy production / deploy (push) Successful in 56s
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -25,12 +25,12 @@ const scopes = [
|
||||
[
|
||||
"research:refresh",
|
||||
"刷新研究数据",
|
||||
"更新缓存、检查自相关及恢复 WorldQuant 认证",
|
||||
"更新缓存、预览 SUPER 组件、检查自相关及恢复 WorldQuant 认证",
|
||||
],
|
||||
[
|
||||
"research:write",
|
||||
"保存研究模板",
|
||||
"保存大模型总结的模板及来源,供后续批量回测",
|
||||
"保存研究模板与方案",
|
||||
"保存模板、Super Alpha 方案及构造候选,不执行回测",
|
||||
],
|
||||
["backtests:execute", "执行回测", "提交新的回测批次"],
|
||||
["backtests:control", "控制回测", "暂停、继续、停止与恢复采集"],
|
||||
|
||||
Reference in New Issue
Block a user