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 { SuperAlphaResearchPage } from "./superalpha/SuperAlphaResearchPage";
|
||||
import { FieldDirectory } from "./preparations/FieldDirectory";
|
||||
import { DataPreparationPage } from "./preparations/DataPreparationPage";
|
||||
import { SnapshotDialog } from "./preparations/SnapshotDialog";
|
||||
@@ -66,6 +67,7 @@ export default function App() {
|
||||
useEffect(() => {
|
||||
if (
|
||||
[
|
||||
"superalpha-research",
|
||||
"operators",
|
||||
"templates",
|
||||
"variants",
|
||||
@@ -101,6 +103,9 @@ export default function App() {
|
||||
const [alphaContext, setAlphaContext] = useState<PageContext>({
|
||||
page: "alphas",
|
||||
});
|
||||
const [superAlphaContext, setSuperAlphaContext] = useState<PageContext>({
|
||||
page: "superalphas",
|
||||
});
|
||||
const [backtestContext, setBacktestContext] = useState<PageContext>({
|
||||
page: "backtests",
|
||||
});
|
||||
@@ -252,7 +257,23 @@ export default function App() {
|
||||
location.hash = next;
|
||||
setPage(next);
|
||||
};
|
||||
const navigationRequest = useRef(0);
|
||||
const handleAction = (action: UIAction) => {
|
||||
const request = ++navigationRequest.current;
|
||||
if (action.type === "open_alpha" && !action.alpha_type) {
|
||||
void api<{ alpha_type: string }>(
|
||||
`/alphas/${encodeURIComponent(action.alpha_id)}`,
|
||||
)
|
||||
.then((alpha) => {
|
||||
if (navigationRequest.current === request)
|
||||
handleAction({
|
||||
...action,
|
||||
alpha_type: alpha.alpha_type || "REGULAR",
|
||||
});
|
||||
})
|
||||
.catch((error: Error) => Toast.error(error.message));
|
||||
return;
|
||||
}
|
||||
const destination = actionDestination(action);
|
||||
if (!destination) return;
|
||||
if (destination.chat === "open") setChatOpen(true);
|
||||
@@ -261,6 +282,21 @@ export default function App() {
|
||||
if (destination.page) changePage(destination.page);
|
||||
setAIAction(action);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (!authenticated) return;
|
||||
const locate = () => {
|
||||
const target = pageFromHash(location.hash);
|
||||
if (!["alphas", "superalphas"].includes(target)) return;
|
||||
const id = new URLSearchParams(location.hash.split("?")[1]).get(
|
||||
"alpha_id",
|
||||
);
|
||||
if (id)
|
||||
handleAction({ type: "open_alpha", alpha_id: id, nonce: Date.now() });
|
||||
};
|
||||
locate();
|
||||
window.addEventListener("hashchange", locate);
|
||||
return () => window.removeEventListener("hashchange", locate);
|
||||
}, [authenticated]);
|
||||
const logout = async () => {
|
||||
try {
|
||||
await post("/auth/logout");
|
||||
@@ -439,6 +475,44 @@ export default function App() {
|
||||
onOverlay={focusBusiness}
|
||||
/>
|
||||
</div>
|
||||
<div className="alpha-page-view" hidden={page !== "superalphas"}>
|
||||
<AlphaPage
|
||||
managementScope="super"
|
||||
onAction={handleAction}
|
||||
taskPanelOpen={showJobs}
|
||||
account={account}
|
||||
version={`${refreshKey}:${resourceVersions.alphas}:${completedVersion}`}
|
||||
onTask={taskCreated}
|
||||
onAccount={() => changePage("account")}
|
||||
active={page === "superalphas"}
|
||||
overlaySuspended={
|
||||
page !== "superalphas" || (viewport < 1440 && chatOpen)
|
||||
}
|
||||
chatOffset={chatOffset}
|
||||
onContext={setSuperAlphaContext}
|
||||
action={
|
||||
aiAction?.type === "open_alpha" ||
|
||||
aiAction?.type === "apply_filters"
|
||||
? aiAction
|
||||
: null
|
||||
}
|
||||
onOverlay={focusBusiness}
|
||||
/>
|
||||
</div>
|
||||
{visitedResearch.includes("superalpha-research") && (
|
||||
<div
|
||||
className="alpha-page-view"
|
||||
hidden={page !== "superalpha-research"}
|
||||
>
|
||||
<SuperAlphaResearchPage
|
||||
active={page === "superalpha-research"}
|
||||
action={aiAction}
|
||||
onAction={handleAction}
|
||||
onContext={setResearchContext}
|
||||
timezone={account?.timezone}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="backtest-page-view" hidden={page !== "backtests"}>
|
||||
{visitedBacktests && (
|
||||
<BacktestPage
|
||||
@@ -572,6 +646,11 @@ export default function App() {
|
||||
? researchContext
|
||||
: { page: "variants" as const },
|
||||
alphas: alphaContext,
|
||||
superalphas: superAlphaContext,
|
||||
"superalpha-research":
|
||||
researchContext.page === "superalpha-research"
|
||||
? researchContext
|
||||
: { page: "superalpha-research" as const },
|
||||
datasets: datasetContext,
|
||||
fields: { page: "fields" as const },
|
||||
preparations: { page: "preparations" as const },
|
||||
|
||||
@@ -15,6 +15,8 @@ export type PageContext = {
|
||||
page:
|
||||
| "home"
|
||||
| "alphas"
|
||||
| "superalphas"
|
||||
| "superalpha-research"
|
||||
| "account"
|
||||
| "datasets"
|
||||
| "fields"
|
||||
@@ -48,9 +50,17 @@ export type PageContext = {
|
||||
filters?: Record<string, unknown>;
|
||||
};
|
||||
export type AlphaUIAction =
|
||||
| { type: "open_alpha"; alpha_id: string; nonce: number }
|
||||
| { type: "open_alpha"; alpha_id: string; alpha_type?: string; nonce: number }
|
||||
| { type: "apply_filters"; filters: Record<string, unknown>; nonce: number };
|
||||
export type UIAction =
|
||||
| {
|
||||
type: "open_superalpha_research";
|
||||
plan_id?: string;
|
||||
version?: number;
|
||||
experiment_id?: string;
|
||||
alpha_id?: string;
|
||||
nonce: number;
|
||||
}
|
||||
| { type: "open_feature"; asset_id: string; version?: number; nonce: number }
|
||||
| { type: "open_template"; asset_id: string; version?: number; nonce: number }
|
||||
| { type: "open_experiment"; experiment_id: string; nonce: number }
|
||||
@@ -59,7 +69,7 @@ export type UIAction =
|
||||
| { type: "open_research_input"; input_id: string; nonce: number }
|
||||
| { type: "open_backtest"; run_id: string; nonce: number }
|
||||
| { type: "open_backtest_preview"; preview_id: string; nonce: number }
|
||||
| { type: "open_alpha"; alpha_id: string; nonce: number }
|
||||
| { type: "open_alpha"; alpha_id: string; alpha_type?: string; nonce: number }
|
||||
| { type: "apply_filters"; filters: Record<string, unknown>; nonce: number };
|
||||
export type ToolCard = {
|
||||
id: string;
|
||||
|
||||
@@ -68,6 +68,10 @@ const contextLabels: Record<
|
||||
home: () => "上下文:首页看板",
|
||||
alphas: (context) =>
|
||||
`上下文:${context.alpha_id ? `Alpha ${context.alpha_id}` : "Alpha 列表"}${context.selected_ids?.length ? ` · 已选 ${context.selected_ids.length} 条` : ""}`,
|
||||
superalphas: (c) =>
|
||||
`上下文:Super Alpha 管理${c.alpha_id ? ` · ${c.alpha_id}` : ""}`,
|
||||
"superalpha-research": (c) =>
|
||||
`上下文:Super Alpha 研究${c.research_asset_id ? ` · 方案 ${c.research_asset_id}` : ""}${c.research_experiment_id ? ` · 候选 ${c.research_experiment_id}` : ""}`,
|
||||
operators: () => "上下文:算子库",
|
||||
templates: (context) =>
|
||||
`上下文:模板工坊${context.research_asset_id ? ` · ${context.research_asset_id}` : ""}${context.research_experiment_id ? ` · 实验 ${context.research_experiment_id}` : ""}`,
|
||||
@@ -100,6 +104,7 @@ type Destination = {
|
||||
};
|
||||
// Exhaustive action destinations prevent a new action silently falling into Alpha.
|
||||
const destinations: Record<UIAction["type"], Destination> = {
|
||||
open_superalpha_research: { page: "superalpha-research", chat: "responsive" },
|
||||
open_feature: { page: "features", chat: "responsive" },
|
||||
open_template: { page: "templates", chat: "responsive" },
|
||||
open_experiment: { page: "templates", chat: "responsive" },
|
||||
@@ -113,6 +118,14 @@ const destinations: Record<UIAction["type"], Destination> = {
|
||||
};
|
||||
|
||||
export function actionDestination(action: UIAction): Destination | undefined {
|
||||
if (action.type === "open_alpha" && action.alpha_type === "SUPER")
|
||||
return { page: "superalphas", chat: "responsive" };
|
||||
if (
|
||||
action.type === "apply_filters" &&
|
||||
(action.filters.management_scope === "super" ||
|
||||
action.filters.alpha_type === "SUPER")
|
||||
)
|
||||
return { page: "superalphas", chat: "responsive" };
|
||||
return Object.hasOwn(destinations, action.type)
|
||||
? destinations[action.type]
|
||||
: undefined;
|
||||
|
||||
@@ -85,6 +85,7 @@ export const jobLabels: Record<string, string> = {
|
||||
catalog_full_sync: "全量同步数据目录与字段",
|
||||
catalog_sync: "同步数据集目录",
|
||||
field_sync: "同步数据字段",
|
||||
super_selection_preview: "Super Alpha 组件预览",
|
||||
full_sync: "全量同步 Alpha",
|
||||
daily_sync: "按天同步 Alpha",
|
||||
self_correlation: "本地自相关检测",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ComponentsPanel } from "../superalpha/ComponentsPanel";
|
||||
import {
|
||||
CatalogTableToolbar,
|
||||
CatalogIconAction,
|
||||
@@ -90,6 +91,7 @@ export function BacktestPage({
|
||||
}) {
|
||||
const [view, setView] = useState("runs");
|
||||
const [sourceFilter, setSourceFilter] = useState("");
|
||||
const [alphaType, setAlphaType] = useState("");
|
||||
const [q, setQ] = useState("");
|
||||
const [filterDraft, setFilterDraft] = useState({ q: "", source: "" });
|
||||
const [pageSize, setPageSize] = useState(25);
|
||||
@@ -180,7 +182,9 @@ export function BacktestPage({
|
||||
offset: (page - 1) * pageSize,
|
||||
sort: sort.value,
|
||||
direction: sort.direction,
|
||||
...(view === "runs" ? { source: sourceFilter } : {}),
|
||||
...(view === "runs"
|
||||
? { source: sourceFilter, alpha_type: alphaType }
|
||||
: {}),
|
||||
});
|
||||
const [data, c, kinds] = await Promise.all([
|
||||
api<Page<Run> | Page<DraftSummary>>(`/backtests/${view}?${params}`),
|
||||
@@ -199,7 +203,16 @@ export function BacktestPage({
|
||||
} finally {
|
||||
if (sequence === requestSequence.current) setLoading(false);
|
||||
}
|
||||
}, [page, pageSize, sourceFilter, q, view, sort.value, sort.direction]);
|
||||
}, [
|
||||
page,
|
||||
pageSize,
|
||||
sourceFilter,
|
||||
alphaType,
|
||||
q,
|
||||
view,
|
||||
sort.value,
|
||||
sort.direction,
|
||||
]);
|
||||
useEffect(() => {
|
||||
if (!active) return;
|
||||
let alive = true;
|
||||
@@ -472,6 +485,24 @@ export function BacktestPage({
|
||||
const drawerVisible = active && !suspended;
|
||||
return (
|
||||
<section className="backtest-page">
|
||||
{view === "runs" && (
|
||||
<div className="backtest-toolbar">
|
||||
<span>候选类型</span>
|
||||
<Select
|
||||
aria-label="回测候选类型"
|
||||
value={alphaType}
|
||||
optionList={[
|
||||
{ value: "", label: "全部类型" },
|
||||
{ value: "REGULAR", label: "REGULAR" },
|
||||
{ value: "SUPER", label: "SUPER" },
|
||||
]}
|
||||
onChange={(v) => {
|
||||
setAlphaType(String(v));
|
||||
setPage(1);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<CatalogTableToolbar
|
||||
label="回测研究"
|
||||
active={active && !suspended && !editor && !runId && !settingsDraft}
|
||||
@@ -723,8 +754,21 @@ export function BacktestPage({
|
||||
{
|
||||
title: "表达式",
|
||||
dataIndex: "expression",
|
||||
width: 260,
|
||||
width: 320,
|
||||
ellipsis: true,
|
||||
render: (_, c) => (
|
||||
<span
|
||||
title={
|
||||
c!.alpha_type === "SUPER"
|
||||
? `Selection: ${c!.selection}\nCombo: ${c!.combo}`
|
||||
: c!.expression
|
||||
}
|
||||
>
|
||||
{c!.alpha_type === "SUPER"
|
||||
? `SUPER · Selection: ${c!.selection} · Combo: ${c!.combo}`
|
||||
: c!.expression}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "最终参数",
|
||||
@@ -1002,12 +1046,14 @@ export function BacktestPage({
|
||||
dataIndex: "expression",
|
||||
width: 260,
|
||||
ellipsis: true,
|
||||
render: (v, i) => (
|
||||
render: (_, i) => (
|
||||
<Button
|
||||
theme="borderless"
|
||||
onClick={() => setItemDetail(i!)}
|
||||
>
|
||||
{String(v)}
|
||||
{i!.alpha_type === "SUPER"
|
||||
? `SUPER · ${i!.selection} · ${i!.combo}`
|
||||
: i!.expression}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
@@ -1055,6 +1101,7 @@ export function BacktestPage({
|
||||
onAction({
|
||||
type: "open_alpha",
|
||||
alpha_id: i!.alpha_id!,
|
||||
alpha_type: i!.alpha_type,
|
||||
nonce: Date.now(),
|
||||
})
|
||||
}
|
||||
@@ -1081,7 +1128,21 @@ export function BacktestPage({
|
||||
收起候选详情
|
||||
</Button>
|
||||
</div>
|
||||
<p>{itemDetail.expression}</p>
|
||||
{itemDetail.alpha_type === "SUPER" ? (
|
||||
<>
|
||||
<h4>Selection</h4>
|
||||
<pre>{itemDetail.selection}</pre>
|
||||
<h4>Combo</h4>
|
||||
<pre>{itemDetail.combo}</pre>
|
||||
<ComponentsPanel
|
||||
key={itemDetail.id}
|
||||
url={`/backtests/items/${itemDetail.id}/artifact?kind=components`}
|
||||
timezone={timezone}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<p>{itemDetail.expression}</p>
|
||||
)}
|
||||
{itemDetail.error && (
|
||||
<Banner type="warning" description={itemDetail.error} />
|
||||
)}
|
||||
|
||||
@@ -12,6 +12,9 @@ export type SimulationSettings = {
|
||||
language: "FASTEXPR";
|
||||
visualization: boolean;
|
||||
maxTrade: "ON" | "OFF";
|
||||
selectionHandling?: "POSITIVE" | "NON_ZERO" | "NON_NAN";
|
||||
selectionLimit?: number;
|
||||
componentActivation?: "IS" | "OS";
|
||||
maxPosition?: "ON" | "OFF";
|
||||
};
|
||||
export const initialSettings: SimulationSettings = {
|
||||
@@ -34,9 +37,15 @@ export type Candidate = {
|
||||
client_item_id: string;
|
||||
expression: string;
|
||||
settings: SimulationSettings;
|
||||
alpha_type?: "REGULAR";
|
||||
alpha_type?: "REGULAR" | "SUPER";
|
||||
selection?: string;
|
||||
combo?: string;
|
||||
};
|
||||
export type Source = {
|
||||
research_kind?: string | null;
|
||||
superalpha_plan_id?: string | null;
|
||||
superalpha_plan_version?: number | null;
|
||||
selection_snapshot_ids?: string[];
|
||||
kind: string;
|
||||
reference?: string | null;
|
||||
batch_id?: string | null;
|
||||
@@ -102,6 +111,9 @@ export type Run = {
|
||||
scheduler: Scheduler;
|
||||
};
|
||||
export type Item = {
|
||||
alpha_type: "REGULAR" | "SUPER";
|
||||
selection?: string;
|
||||
combo?: string;
|
||||
id: string;
|
||||
client_item_id: string;
|
||||
expression: string;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ComponentsPanel } from "../superalpha/ComponentsPanel";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { formatAlphaMetric } from "../alphaMetrics";
|
||||
import {
|
||||
@@ -65,6 +66,7 @@ export function AlphaDetail({
|
||||
chatOffset?: number;
|
||||
}) {
|
||||
const [detail, setDetail] = useState<Detail | null>(null);
|
||||
const [descriptions, setDescriptions] = useState<Record<string, string>>({});
|
||||
const [pnl, setPnl] = useState<Pnl | null>(null);
|
||||
const [research, setResearch] = useState<Research | null>(null);
|
||||
const [tagText, setTagText] = useState("");
|
||||
@@ -77,6 +79,7 @@ export function AlphaDetail({
|
||||
dirty.current = false;
|
||||
setConflict(false);
|
||||
setDetail(null);
|
||||
setDescriptions({});
|
||||
setResearch(null);
|
||||
setPnl(null);
|
||||
setError("");
|
||||
@@ -122,6 +125,21 @@ export function AlphaDetail({
|
||||
active = false;
|
||||
};
|
||||
}, [id, tab, version]);
|
||||
useEffect(() => {
|
||||
if (!id || detail?.alpha_type !== "SUPER") return;
|
||||
const c = new AbortController();
|
||||
void api<{ descriptions: Record<string, string> }>(
|
||||
`/superalpha/alphas/${id}`,
|
||||
{ signal: c.signal },
|
||||
)
|
||||
.then((value) => {
|
||||
if (!c.signal.aborted) setDescriptions(value.descriptions);
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
if (!c.signal.aborted) setError(e.message);
|
||||
});
|
||||
return () => c.abort();
|
||||
}, [id, detail?.alpha_type, version]);
|
||||
async function fetchPnl() {
|
||||
try {
|
||||
await post("/sync-jobs", { kind: "pnl_refresh", alpha_ids: [id] });
|
||||
@@ -222,6 +240,19 @@ export function AlphaDetail({
|
||||
<Button>在 BRAIN 中打开 ↗</Button>
|
||||
</a>
|
||||
</div>
|
||||
{detail.alpha_type === "SUPER" && (
|
||||
<Button
|
||||
onClick={() =>
|
||||
onAction({
|
||||
type: "open_superalpha_research",
|
||||
alpha_id: detail.id,
|
||||
nonce: Date.now(),
|
||||
})
|
||||
}
|
||||
>
|
||||
复制为研究方案
|
||||
</Button>
|
||||
)}
|
||||
<Tabs
|
||||
className="alpha-detail-tabs"
|
||||
activeKey={tab}
|
||||
@@ -256,6 +287,18 @@ export function AlphaDetail({
|
||||
{detail.combo !== null && (
|
||||
<CodeBlock title="Combo" value={detail.combo} />
|
||||
)}
|
||||
{detail.alpha_type === "SUPER" && (
|
||||
<>
|
||||
<CodeBlock
|
||||
title="Selection Description"
|
||||
value={descriptions.selection || "未提供"}
|
||||
/>
|
||||
<CodeBlock
|
||||
title="Combo Description"
|
||||
value={descriptions.combo || "未提供"}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Divider />
|
||||
<h3>回测设置</h3>
|
||||
<DetailFieldGrid
|
||||
@@ -266,6 +309,17 @@ export function AlphaDetail({
|
||||
/>
|
||||
</div>
|
||||
</TabPane>
|
||||
{detail.alpha_type === "SUPER" && (
|
||||
<TabPane tab="组件证据" itemKey="components">
|
||||
{tab === "components" && (
|
||||
<ComponentsPanel
|
||||
key={detail.id}
|
||||
url={`/superalpha/alphas/${detail.id}/components`}
|
||||
timezone={timezone}
|
||||
/>
|
||||
)}
|
||||
</TabPane>
|
||||
)}
|
||||
<TabPane tab="指标" itemKey="metrics">
|
||||
<MetricTable title="IS 指标" values={detail.is_metrics} />
|
||||
<Divider />
|
||||
|
||||
@@ -34,10 +34,22 @@ const navigation = [
|
||||
},
|
||||
{ id: "features", label: "特征工程", icon: IconBeaker, group: "研究实验" },
|
||||
{ id: "variants", label: "Alpha 变体", icon: IconBeaker, group: "研究实验" },
|
||||
{
|
||||
id: "superalpha-research",
|
||||
label: "Super Alpha 研究",
|
||||
icon: IconBeaker,
|
||||
group: "研究实验",
|
||||
},
|
||||
{ id: "backtests", label: "回测研究", icon: IconBeaker, group: "研究实验" },
|
||||
{ id: "pipeline", label: "研究流水线", icon: IconBeaker, group: "研究编排" },
|
||||
{ id: "quantflow", label: "QuantFlow", icon: IconBeaker, group: "研究编排" },
|
||||
{ id: "alphas", label: "Alpha 管理", icon: IconGridView, group: "研究成果" },
|
||||
{
|
||||
id: "superalphas",
|
||||
label: "Super Alpha 管理",
|
||||
icon: IconGridView,
|
||||
group: "研究成果",
|
||||
},
|
||||
{ id: "mcp-keys", label: "MCP Key", icon: IconCommand, group: "系统管理" },
|
||||
{ id: "account", label: "个人信息", icon: IconUser, group: "" },
|
||||
] as const;
|
||||
@@ -285,7 +297,7 @@ export function AppSidebar({
|
||||
>
|
||||
<div className="command-items">
|
||||
{navigation.map(({ id, label, icon: Icon }) => (
|
||||
<button key={id} onClick={() => navigate(id)}>
|
||||
<button key={id} aria-label={label} onClick={() => navigate(id)}>
|
||||
<Icon />
|
||||
{label}
|
||||
</button>
|
||||
|
||||
@@ -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", "控制回测", "暂停、继续、停止与恢复采集"],
|
||||
|
||||
@@ -44,6 +44,7 @@ function filterSignature(filters: Record<string, unknown>) {
|
||||
}
|
||||
|
||||
export function SavedViews({
|
||||
managementScope = "non_super",
|
||||
filters,
|
||||
columns,
|
||||
submission,
|
||||
@@ -55,6 +56,7 @@ export function SavedViews({
|
||||
suspended,
|
||||
onOverlay,
|
||||
}: {
|
||||
managementScope?: "super" | "non_super";
|
||||
filters: Record<string, unknown>;
|
||||
columns: string[];
|
||||
submission: Submission;
|
||||
@@ -90,13 +92,22 @@ export function SavedViews({
|
||||
views.push(...result.items);
|
||||
if (views.length >= result.total || result.items.length === 0) break;
|
||||
}
|
||||
if (!controller.signal.aborted) setItems(views);
|
||||
if (!controller.signal.aborted)
|
||||
setItems(
|
||||
views.filter(
|
||||
(v) =>
|
||||
(v.content.filters.management_scope ??
|
||||
(v.content.filters.alpha_type === "SUPER"
|
||||
? "super"
|
||||
: "non_super")) === managementScope,
|
||||
),
|
||||
);
|
||||
}
|
||||
void load().catch((error: Error) => {
|
||||
if (!controller.signal.aborted) Toast.error(error.message);
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [revision]);
|
||||
}, [revision, managementScope]);
|
||||
|
||||
function openEditor(mode: Editor["mode"], view?: View) {
|
||||
const content = view?.content ?? { name: "", filters, columns };
|
||||
|
||||
@@ -5,6 +5,7 @@ import "./style.css";
|
||||
|
||||
export const sourceLabel = (kind: string) =>
|
||||
({
|
||||
superalpha: "Super Alpha 研究",
|
||||
chatbox: "Chatbox 研究",
|
||||
manual: "手工研究",
|
||||
mcp: "MCP 研究",
|
||||
@@ -33,6 +34,24 @@ export function SourceDetails({
|
||||
<p>来源引用:{source.reference}</p>
|
||||
)}
|
||||
<div className="inline-actions">
|
||||
{(source.superalpha_plan_id ||
|
||||
((source.kind === "superalpha" ||
|
||||
source.research_kind === "superalpha") &&
|
||||
source.research_id)) && (
|
||||
<Button
|
||||
onClick={() =>
|
||||
onAction({
|
||||
type: "open_superalpha_research",
|
||||
plan_id: source.superalpha_plan_id ?? undefined,
|
||||
version: source.superalpha_plan_version ?? undefined,
|
||||
experiment_id: source.research_id ?? undefined,
|
||||
nonce: Date.now(),
|
||||
})
|
||||
}
|
||||
>
|
||||
查看 Super Alpha 研究
|
||||
</Button>
|
||||
)}
|
||||
{source.research_id &&
|
||||
["template", "variant", "feature", "pipeline", "quantflow"].includes(
|
||||
source.kind,
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
|
||||
/** One typed settings value for all producers; fixed inputs constrain the scope, never the numeric parameters. */
|
||||
export function SimulationSettingsEditor({
|
||||
alphaType = "REGULAR",
|
||||
value,
|
||||
onChange,
|
||||
rows,
|
||||
@@ -28,6 +29,7 @@ export function SimulationSettingsEditor({
|
||||
disabled = false,
|
||||
onValidityChange,
|
||||
}: {
|
||||
alphaType?: "REGULAR" | "SUPER";
|
||||
value: SimulationSettings;
|
||||
onChange: (value: SimulationSettings) => void;
|
||||
rows: SettingsRow[];
|
||||
@@ -98,8 +100,10 @@ export function SimulationSettingsEditor({
|
||||
disabled={disabled || loading}
|
||||
min={options.minimum}
|
||||
max={options.maximum}
|
||||
precision={key === "decay" ? 0 : undefined}
|
||||
step={key === "decay" ? 1 : 0.01}
|
||||
precision={
|
||||
["decay", "selectionLimit"].includes(key) ? 0 : undefined
|
||||
}
|
||||
step={["decay", "selectionLimit"].includes(key) ? 1 : 0.01}
|
||||
validateStatus={message ? "error" : "default"}
|
||||
onChange={(next) =>
|
||||
onChange({
|
||||
@@ -161,6 +165,23 @@ export function SimulationSettingsEditor({
|
||||
<div className="settings-grid">
|
||||
{["neutralization", "decay", "truncation"].map(field)}
|
||||
</div>
|
||||
{alphaType === "SUPER" && (
|
||||
<>
|
||||
<div className="settings-grid">
|
||||
{["selectionHandling", "selectionLimit", "componentActivation"].map(
|
||||
field,
|
||||
)}
|
||||
</div>
|
||||
{["selectionHandling", "selectionLimit", "componentActivation"].some(
|
||||
(k) => !row?.fields?.[k],
|
||||
) && (
|
||||
<small className="settings-hint">
|
||||
平台元数据未提供全部 SUPER
|
||||
设置范围;当前显示本地契约,账户适用性未核实。
|
||||
</small>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<details>
|
||||
<summary>更多参数</summary>
|
||||
<div className="settings-grid">
|
||||
|
||||
@@ -86,6 +86,9 @@ export function asScopeOptions(rows: SettingsRow[]): ScopeOption[] {
|
||||
return rows.map((row) => ({ ...row, universes: [row.universe] }));
|
||||
}
|
||||
export const parameterLabels: Record<string, string> = {
|
||||
selectionHandling: "Selection Handling",
|
||||
selectionLimit: "Selection Limit",
|
||||
componentActivation: "Component Activation",
|
||||
neutralization: "Neutralization",
|
||||
decay: "Decay",
|
||||
truncation: "Truncation",
|
||||
@@ -99,6 +102,9 @@ export const parameterLabels: Record<string, string> = {
|
||||
};
|
||||
// These are application contract limits, not a substitute for account-specific market choices.
|
||||
const contractFields: Record<string, FieldOption> = {
|
||||
selectionHandling: { choices: ["POSITIVE", "NON_ZERO", "NON_NAN"] },
|
||||
componentActivation: { choices: ["IS", "OS"] },
|
||||
selectionLimit: { minimum: 1, maximum: 100000 },
|
||||
decay: { minimum: 0, maximum: 10000 },
|
||||
truncation: { minimum: 0, maximum: 1 },
|
||||
pasteurization: { choices: ["ON", "OFF"] },
|
||||
@@ -143,7 +149,10 @@ export function fieldOptions(
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
if (result.choices && (key === "decay" || key === "truncation")) {
|
||||
if (
|
||||
result.choices &&
|
||||
["decay", "truncation", "selectionLimit"].includes(key)
|
||||
) {
|
||||
result.choices = result.choices.filter(
|
||||
(v) =>
|
||||
typeof v === "number" &&
|
||||
@@ -171,6 +180,13 @@ export function settingsErrors(
|
||||
)
|
||||
errors.push("所选数据准备必须具有相同组合,并与回测组合一致");
|
||||
for (const key of Object.keys(parameterLabels)) {
|
||||
if (
|
||||
["selectionHandling", "selectionLimit", "componentActivation"].includes(
|
||||
key,
|
||||
) &&
|
||||
value.selectionHandling === undefined
|
||||
)
|
||||
continue;
|
||||
const current =
|
||||
value[key as keyof SimulationSettings] ??
|
||||
(key === "maxPosition" ? "OFF" : undefined);
|
||||
@@ -183,12 +199,13 @@ export function settingsErrors(
|
||||
`${parameterLabels[key]} 当前值 ${String(current ?? "未提供")} 不可用,请重新选择`,
|
||||
);
|
||||
if (
|
||||
(key === "decay" || key === "truncation") &&
|
||||
["decay", "truncation", "selectionLimit"].includes(key) &&
|
||||
(typeof current !== "number" ||
|
||||
!Number.isFinite(current) ||
|
||||
current < options.minimum! ||
|
||||
current > options.maximum! ||
|
||||
(key === "decay" && !Number.isInteger(current)))
|
||||
(["decay", "selectionLimit"].includes(key) &&
|
||||
!Number.isInteger(current)))
|
||||
)
|
||||
errors.push(
|
||||
`${parameterLabels[key]} 必须为 ${options.minimum}–${options.maximum} 的${key === "decay" ? "整数" : "数值"}`,
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Banner, Pagination, Spin } from "@douyinfe/semi-ui-19";
|
||||
import { api, displayValue, formatTime } from "../api";
|
||||
import { WorkspaceTable } from "../components/WorkspaceTable";
|
||||
import type { Components } from "./types";
|
||||
|
||||
/** Component evidence is always read from its own immutable observation. */
|
||||
export function ComponentsPanel({
|
||||
url,
|
||||
timezone,
|
||||
}: {
|
||||
url: string;
|
||||
timezone?: string;
|
||||
}) {
|
||||
const [page, setPage] = useState(1);
|
||||
const [data, setData] = useState<Components>();
|
||||
const [error, setError] = useState("");
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
setData(undefined);
|
||||
}, [url]);
|
||||
useEffect(() => {
|
||||
const c = new AbortController();
|
||||
setError("");
|
||||
void api<Components>(
|
||||
`${url}${url.includes("?") ? "&" : "?"}limit=25&offset=${(page - 1) * 25}`,
|
||||
{ signal: c.signal },
|
||||
)
|
||||
.then((value) => {
|
||||
if (!c.signal.aborted) setData(value);
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
if (!c.signal.aborted) setError(e.message);
|
||||
});
|
||||
return () => c.abort();
|
||||
}, [url, page]);
|
||||
if (error) return <Banner type="danger" description={error} />;
|
||||
if (!data) return <Spin />;
|
||||
return (
|
||||
<section className="super-components">
|
||||
<p>
|
||||
{data.source === "preview" ? "Selection 预览组件" : "回测实际组件"} ·{" "}
|
||||
{data.complete
|
||||
? `已核实 ${data.reported_total ?? data.total} 个`
|
||||
: "完整性未核实"}{" "}
|
||||
· 观察时间 {formatTime(data.observed_at, timezone)}
|
||||
</p>
|
||||
{data.complete && data.total === 0 && (
|
||||
<Banner
|
||||
type="warning"
|
||||
description="组件池为空,回测可能无法产生结果。请调整 Selection。"
|
||||
/>
|
||||
)}
|
||||
{(data.warnings ?? []).map((w, i) => (
|
||||
<Banner key={i} type="warning" description={displayValue(w)} />
|
||||
))}
|
||||
<p className="muted">组件指纹:{data.component_hash ?? "未核实"}</p>
|
||||
<WorkspaceTable
|
||||
rowKey="id"
|
||||
dataSource={data.items}
|
||||
scroll={{ x: 1100, y: 320 }}
|
||||
columns={[
|
||||
{ title: "组件 ID", dataIndex: "id", width: 190 },
|
||||
{
|
||||
title: "平台选择值",
|
||||
width: 180,
|
||||
render: (_, r) =>
|
||||
displayValue(r?.value ?? r?.selectionValue ?? r?.selection),
|
||||
},
|
||||
{
|
||||
title: "可用指标与平台返回字段",
|
||||
width: 730,
|
||||
render: (_, r) => JSON.stringify(r),
|
||||
},
|
||||
]}
|
||||
empty="未提供组件"
|
||||
/>
|
||||
<footer className="workspace-table-footer super-toolbar">
|
||||
<span>已保存 {data.total} 条</span>
|
||||
<Pagination
|
||||
total={data.total}
|
||||
currentPage={page}
|
||||
pageSize={25}
|
||||
onPageChange={setPage}
|
||||
/>
|
||||
</footer>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,82 @@
|
||||
.super-research {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: var(--semi-color-bg-0);
|
||||
}
|
||||
.super-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 12px;
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
min-height: 40px;
|
||||
}
|
||||
.super-toolbar > .semi-input-wrapper {
|
||||
max-width: 320px;
|
||||
}
|
||||
.super-toolbar .super-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
.super-editor {
|
||||
overflow: auto;
|
||||
padding: 16px;
|
||||
min-height: 0;
|
||||
}
|
||||
.super-editor label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.super-editor-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.2fr) minmax(320px, 1fr);
|
||||
gap: 24px;
|
||||
}
|
||||
.super-editor .semi-input-textarea {
|
||||
font-family: ui-monospace, monospace;
|
||||
}
|
||||
.super-pane {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.super-pane > .semi-banner {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.super-scroll {
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 12px;
|
||||
}
|
||||
.super-research pre,
|
||||
.super-evidence pre {
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.super-components {
|
||||
min-width: 0;
|
||||
}
|
||||
.super-evidence {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
.super-evidence > div {
|
||||
min-width: 0;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.super-editor-grid,
|
||||
.super-evidence {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.super-toolbar {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
initialSettings,
|
||||
type Candidate,
|
||||
type SimulationSettings,
|
||||
type Source,
|
||||
} from "../backtests/types";
|
||||
export type PlanSpec = {
|
||||
name: string;
|
||||
hypothesis: string;
|
||||
reference: string;
|
||||
selection: string;
|
||||
combo: string;
|
||||
settings: SimulationSettings;
|
||||
variables: Record<string, { kind: string; values: (string | number)[] }>;
|
||||
setting_variants: Record<string, (string | number | boolean)[]>;
|
||||
include_baseline: boolean;
|
||||
parent_plan_id?: string | null;
|
||||
parent_plan_version?: number | null;
|
||||
parent_alpha_id?: string | null;
|
||||
parent_experiment_id?: string | null;
|
||||
};
|
||||
export const newPlan = (): PlanSpec => ({
|
||||
name: "",
|
||||
hypothesis: "",
|
||||
reference: "",
|
||||
selection: "",
|
||||
combo: "1",
|
||||
variables: {},
|
||||
setting_variants: {},
|
||||
include_baseline: true,
|
||||
settings: {
|
||||
...initialSettings,
|
||||
selectionHandling: "POSITIVE",
|
||||
selectionLimit: 100,
|
||||
componentActivation: "IS",
|
||||
},
|
||||
});
|
||||
export type Plan = {
|
||||
id: string;
|
||||
version: number;
|
||||
latest_version: number;
|
||||
name: string;
|
||||
archived: boolean;
|
||||
content: PlanSpec;
|
||||
created_at: string;
|
||||
};
|
||||
export type Components = {
|
||||
snapshot_id?: string;
|
||||
job_id?: string;
|
||||
item_id?: string;
|
||||
source: string;
|
||||
status: string;
|
||||
complete: boolean;
|
||||
reported_total?: number | null;
|
||||
total: number;
|
||||
observed_at: string | null;
|
||||
component_hash?: string | null;
|
||||
request_hash?: string;
|
||||
warnings?: unknown[];
|
||||
error?: string;
|
||||
request?: { selection: string; settings: SimulationSettings };
|
||||
items: { id: string; [key: string]: unknown }[];
|
||||
};
|
||||
export type Experiment = {
|
||||
id: string;
|
||||
name: string;
|
||||
total: number;
|
||||
candidates: Candidate[];
|
||||
source: Source;
|
||||
created_at: string;
|
||||
evidence: {
|
||||
plan: PlanSpec;
|
||||
plan_reference: { id?: string; version?: number };
|
||||
selection_snapshot_ids: string[];
|
||||
combination_count: string;
|
||||
mode: string;
|
||||
seed: number;
|
||||
annotations: Record<
|
||||
string,
|
||||
{ baseline: boolean; duplicate_of: string | null; request_hash: string }
|
||||
>;
|
||||
};
|
||||
};
|
||||
export type Result = {
|
||||
id: string;
|
||||
run_id: string;
|
||||
client_item_id: string;
|
||||
alpha_id?: string | null;
|
||||
selection: string;
|
||||
combo: string;
|
||||
settings: SimulationSettings;
|
||||
error?: string | null;
|
||||
platform_status: string;
|
||||
collection_status: string;
|
||||
persistence_status: string;
|
||||
metrics: {
|
||||
is: Record<string, number | null>;
|
||||
os: Record<string, number | null>;
|
||||
};
|
||||
checks: {
|
||||
status: string;
|
||||
counts: Record<string, number>;
|
||||
total: number;
|
||||
non_pass: unknown[];
|
||||
};
|
||||
result: { observed_at: string; complete: boolean } | null;
|
||||
components: Components;
|
||||
pnl_fetched_at: string | null;
|
||||
};
|
||||
@@ -39,6 +39,9 @@ export type Alpha = {
|
||||
id: string;
|
||||
name: string | null;
|
||||
expression_preview: string;
|
||||
selection_preview?: string;
|
||||
combo_preview?: string;
|
||||
component_count?: number | null;
|
||||
alpha_type: string | null;
|
||||
language: string | null;
|
||||
stage: string | null;
|
||||
|
||||
Reference in New Issue
Block a user