import { useEffect, useRef, useState } from "react"; import { Banner, Button, Checkbox, Descriptions, Empty, Input, Select, SideSheet, Spin, TabPane, Tabs, Tag, TextArea, Toast, } from "@douyinfe/semi-ui-19"; import { api, ApiError, displayValue, formatTime, patch, post, stateOptions, } from "../api"; import type { AlphaDetail as Detail, Pnl, Research, ResearchState, } from "../types"; import { PnlChart } from "./PnlChart"; export function AlphaDetail({ id, version, timezone, taskPanelOpen, onClose, onSaved, onTask, suspended = false, chatOffset = 0, }: { id: string | null; version: string; timezone?: string; taskPanelOpen: boolean; onClose: () => void; onSaved: () => void; onTask: () => void; suspended?: boolean; chatOffset?: number; }) { const [detail, setDetail] = useState(null); const [pnl, setPnl] = useState(null); const [research, setResearch] = useState(null); const [tagText, setTagText] = useState(""); const [error, setError] = useState(""); const [saving, setSaving] = useState(false); const [conflict, setConflict] = useState(false); const [tab, setTab] = useState("overview"); const dirty = useRef(false); useEffect(() => { dirty.current = false; setConflict(false); setDetail(null); setResearch(null); setPnl(null); setError(""); setTab("overview"); }, [id]); useEffect(() => { let active = true; if (id) api(`/alphas/${id}`) .then((value) => { if (active) { setDetail(value); setError(""); if (!dirty.current) { setResearch(value.research); setTagText(value.research.tags.join(", ")); } else if ( research && value.research.version !== research.version ) { setConflict(true); } } }) .catch((e) => { if (active) setError(e.message); }); return () => { active = false; }; }, [id, version]); useEffect(() => { let active = true; if (id && tab === "pnl") api(`/alphas/${id}/pnl`) .then((p) => { if (active) setPnl(p); }) .catch((e) => { if (active) Toast.error(e.message); }); return () => { active = false; }; }, [id, tab, version]); async function fetchPnl() { try { await post("/sync-jobs", { kind: "pnl_refresh", alpha_ids: [id] }); onTask(); } catch (e) { Toast.error((e as Error).message); } } async function save() { if (!research) return; setSaving(true); try { const { updated_at: _, ...editable } = research; await patch(`/alphas/${id}/research`, { ...editable, tags: tagText .split(/[,,]/) .map((s) => s.trim()) .filter(Boolean), }); dirty.current = false; setConflict(false); Toast.success("研究记录已保存"); onSaved(); } catch (e) { Toast.error((e as Error).message); if (e instanceof ApiError && e.status === 409) { setConflict(true); try { setDetail(await api(`/alphas/${id}`)); } catch { /* Preserve the draft if refresh fails. */ } } } finally { setSaving(false); } } return ( Alpha 详情 {id} } onCancel={onClose} > {error ? ( ) : !detail || !research ? ( ) : ( <> {conflict && ( 研究记录已被其他操作更新,你的草稿已保留。请先复制需要保留的内容,再 。 } /> )}

{detail.name || detail.id}

{detail.alpha_type ?? "类型未提供"} {detail.language ?? "语言未提供"} {detail.status ?? "状态未提供"}
({ key, value: displayValue(detail[key as keyof Detail]), }))} />

平台创建:{formatTime(detail.date_created, timezone)} · 最近同步:{formatTime(detail.synced_at, timezone)}

{detail.expression !== null && ( )} {detail.selection !== null && ( )} {detail.combo !== null && ( )}

回测设置

({ key, value: displayValue(value), }))} />

平台已有检查结果

展示已同步结果,查看此页不会触发新的平台检查。

{detail.checks.length ? ( detail.checks.map((check, i) => (
{displayValue(check.name)} {displayValue(check.value)} / {displayValue(check.limit)} {displayValue(check.result)}
)) ) : ( )}

累计 PnL

{pnl?.fetched_at ? `缓存更新于 ${formatTime(pnl.fetched_at, timezone)}` : "尚未获取 PnL"}

{pnl?.cached ? ( <>

{pnl.points.length} 条记录 · 缺失数据保留为断点

) : ( )}

这些记录只保存在你的工作空间,平台同步不会覆盖。