feat: integrate chatbox research with datasets and backtests
This commit is contained in:
@@ -38,6 +38,8 @@ import type {
|
||||
import { AlphaDetail } from "../components/AlphaDetail";
|
||||
import { AlphaSyncDialog } from "../components/AlphaSyncDialog";
|
||||
import type { PageContext, AlphaUIAction as UIAction } from "../ai/types";
|
||||
import type { UIAction as WorkspaceAction } from "../ai/types";
|
||||
import { sourceLabel } from "../research/SourceDetails";
|
||||
|
||||
const metricLabels = {
|
||||
sharpe: "Sharpe",
|
||||
@@ -57,8 +59,10 @@ const initialColumns = [
|
||||
"turnover",
|
||||
"correlation",
|
||||
"research",
|
||||
"source",
|
||||
];
|
||||
const columnLabels: Record<string, string> = {
|
||||
source: "研究来源",
|
||||
name: "Alpha",
|
||||
expression: "表达式",
|
||||
region: "地区 / Universe",
|
||||
@@ -89,6 +93,7 @@ export function AlphaPage({
|
||||
onContext,
|
||||
action,
|
||||
onOverlay,
|
||||
onAction,
|
||||
}: {
|
||||
account: Account | null;
|
||||
taskPanelOpen: boolean;
|
||||
@@ -101,6 +106,7 @@ export function AlphaPage({
|
||||
onContext: (context: PageContext) => void;
|
||||
action: UIAction | null;
|
||||
onOverlay: () => void;
|
||||
onAction: (action: WorkspaceAction) => void;
|
||||
}) {
|
||||
const [data, setData] = useState<Page>({
|
||||
items: [],
|
||||
@@ -225,6 +231,7 @@ export function AlphaPage({
|
||||
}, [action, onOverlay]);
|
||||
const refresh = useCallback(() => setLocalVersion((n) => n + 1), []);
|
||||
useEffect(() => {
|
||||
if (!active) return;
|
||||
const controller = new AbortController();
|
||||
setLoading(true);
|
||||
setError("");
|
||||
@@ -245,7 +252,7 @@ export function AlphaPage({
|
||||
if (!controller.signal.aborted) setLoading(false);
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [query, version, localVersion]);
|
||||
}, [active, query, version, localVersion]);
|
||||
useEffect(() => {
|
||||
setSelected([]);
|
||||
tablePanel.current?.querySelector(".semi-table-body")?.scrollTo({ top: 0 });
|
||||
@@ -348,6 +355,13 @@ export function AlphaPage({
|
||||
</code>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "source",
|
||||
title: "研究来源",
|
||||
width: 150,
|
||||
render: (_, row) =>
|
||||
row!.source_kinds?.map(sourceLabel).join("、") || "暂无本地来源",
|
||||
},
|
||||
{
|
||||
key: "region",
|
||||
title: "地区 / Universe",
|
||||
@@ -594,6 +608,28 @@ export function AlphaPage({
|
||||
{activeFilterCount ? `· ${activeFilterCount} 项已应用` : ""}
|
||||
</summary>
|
||||
<div className="advanced-grid">
|
||||
<label>
|
||||
研究来源
|
||||
<Select
|
||||
aria-label="研究来源筛选"
|
||||
showClear
|
||||
value={draft.source || undefined}
|
||||
optionList={options("source").map((option) => ({
|
||||
...option,
|
||||
label: sourceLabel(option.value),
|
||||
}))}
|
||||
placeholder="全部来源"
|
||||
onChange={(v) => updateDraft("source", v)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
研究编号
|
||||
<Input
|
||||
aria-label="研究编号筛选"
|
||||
value={draft.research_id || ""}
|
||||
onChange={(v) => updateDraft("research_id", v)}
|
||||
/>
|
||||
</label>
|
||||
{[
|
||||
["universe", "Universe"],
|
||||
["alpha_type", "Alpha 类型"],
|
||||
@@ -938,6 +974,7 @@ export function AlphaPage({
|
||||
/>
|
||||
)}
|
||||
<AlphaDetail
|
||||
onAction={onAction}
|
||||
suspended={overlaySuspended}
|
||||
chatOffset={chatOffset}
|
||||
taskPanelOpen={taskPanelOpen}
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
queryString,
|
||||
} from "../api";
|
||||
import type { Account, Job } from "../types";
|
||||
import type { PageContext, UIAction } from "../ai/types";
|
||||
import "./dataset.css";
|
||||
|
||||
type Scope = {
|
||||
@@ -66,6 +67,7 @@ type Draft = {
|
||||
dataset_id: string;
|
||||
scope: Scope;
|
||||
field_ids: string[];
|
||||
field_types: Record<string, string | null>;
|
||||
collection_version: string;
|
||||
selection: string;
|
||||
created_at: string;
|
||||
@@ -114,6 +116,8 @@ export function DatasetPage({
|
||||
onTask,
|
||||
onModal,
|
||||
onChat,
|
||||
onContext,
|
||||
action,
|
||||
}: {
|
||||
account: Account | null;
|
||||
version: string;
|
||||
@@ -123,6 +127,8 @@ export function DatasetPage({
|
||||
onTask: () => void;
|
||||
onModal: (value: boolean) => void;
|
||||
onChat: () => void;
|
||||
onContext: (context: PageContext) => void;
|
||||
action: UIAction | null;
|
||||
}) {
|
||||
const [scope, setScope] = useState<Scope>({
|
||||
instrument_type: "EQUITY",
|
||||
@@ -185,6 +191,61 @@ export function DatasetPage({
|
||||
);
|
||||
const total = selected?.complete_count ?? 0;
|
||||
const chosen = Math.max(0, total - excluded.size);
|
||||
const contextDraft = inputOpen ? draft : null;
|
||||
useEffect(() => {
|
||||
if (!active) return;
|
||||
onContext({
|
||||
page: "datasets",
|
||||
catalog_scope: contextDraft?.scope ?? scope,
|
||||
dataset_id: contextDraft?.dataset_id ?? selected?.id,
|
||||
field_id: contextDraft ? undefined : detail?.field,
|
||||
collection_version:
|
||||
contextDraft?.collection_version ??
|
||||
selected?.collection_version ??
|
||||
undefined,
|
||||
template_input_id: contextDraft?.id,
|
||||
unsaved_field_selection: !contextDraft && excluded.size > 0,
|
||||
});
|
||||
}, [
|
||||
active,
|
||||
scopeQuery,
|
||||
contextDraft,
|
||||
selected?.id,
|
||||
selected?.collection_version,
|
||||
detail?.field,
|
||||
excluded.size,
|
||||
onContext,
|
||||
]);
|
||||
useEffect(() => {
|
||||
if (action?.type !== "open_research_input") return;
|
||||
let live = true;
|
||||
void api<Draft>(`/catalog/inputs/${encode(action.input_id)}`)
|
||||
.then((saved) => {
|
||||
if (!live) return;
|
||||
changeScope(saved.scope);
|
||||
setDraft(saved);
|
||||
setInputOpen(true);
|
||||
})
|
||||
.catch((e) => {
|
||||
if (live) setError(e.message);
|
||||
});
|
||||
return () => {
|
||||
live = false;
|
||||
};
|
||||
}, [action]);
|
||||
const researchInput = (saved: Draft) => {
|
||||
setDraft(saved);
|
||||
setHistoryOpen(false);
|
||||
setInputOpen(true);
|
||||
onContext({
|
||||
page: "datasets",
|
||||
catalog_scope: saved.scope,
|
||||
dataset_id: saved.dataset_id,
|
||||
collection_version: saved.collection_version,
|
||||
template_input_id: saved.id,
|
||||
});
|
||||
onChat();
|
||||
};
|
||||
const top = historyOpen
|
||||
? "history"
|
||||
: inputOpen
|
||||
@@ -1131,6 +1192,11 @@ export function DatasetPage({
|
||||
保存输入草稿
|
||||
</Button>
|
||||
)}
|
||||
{draft && (
|
||||
<Button theme="solid" onClick={() => researchInput(draft)}>
|
||||
用此输入研究
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="catalog-details-body">
|
||||
<h2>{draft ? "输入草稿已保存" : selected?.name}</h2>
|
||||
@@ -1140,10 +1206,24 @@ export function DatasetPage({
|
||||
: `${preparation.current?.count} 个字段`}
|
||||
</p>
|
||||
<p>
|
||||
{scope.region} · {scope.universe} · Delay {scope.delay}
|
||||
{(draft?.scope ?? scope).region} ·{" "}
|
||||
{(draft?.scope ?? scope).universe} · Delay{" "}
|
||||
{(draft?.scope ?? scope).delay}
|
||||
</p>
|
||||
<p>模板消费方尚未接入</p>
|
||||
<p>保存后可交给 Chatbox 研究,构建候选并预览回测。</p>
|
||||
{draft && <p>草稿 ID:{draft.id}</p>}
|
||||
{draft && (
|
||||
<details>
|
||||
<summary>查看固定字段集合</summary>
|
||||
<p>
|
||||
{draft.field_ids
|
||||
.map(
|
||||
(id) => `${id} (${draft.field_types[id] ?? "类型未知"})`,
|
||||
)
|
||||
.join(", ")}
|
||||
</p>
|
||||
</details>
|
||||
)}
|
||||
<p>
|
||||
集合版本:
|
||||
{draft?.collection_version ?? preparation.current?.version}
|
||||
@@ -1177,6 +1257,7 @@ export function DatasetPage({
|
||||
{d.scope.delay}
|
||||
</p>
|
||||
<p>{formatTime(d.created_at, account?.timezone)}</p>
|
||||
<Button onClick={() => researchInput(d)}>用此输入研究</Button>
|
||||
<details>
|
||||
<summary>查看固定字段集合</summary>
|
||||
<p>{d.field_ids.join(", ")}</p>
|
||||
|
||||
Reference in New Issue
Block a user