This commit is contained in:
aaron 2026-04-30 22:52:35 +08:00
parent cde11656c8
commit 41d19b5a36
11 changed files with 127 additions and 226 deletions

View File

@ -20,15 +20,15 @@ const QUICK_QUESTIONS = [
const CHAT_SCENES = [
{
title: "问今日打法",
description: "把今日结论翻译成人话,说明现在该进攻、试错还是防守。",
description: "进攻 / 试错 / 防守",
},
{
title: "问推荐池",
description: "追问某只推荐股为什么进池、什么条件下能看、什么条件下放弃。",
description: "进池原因 / 触发 / 放弃",
},
{
title: "问自选股",
description: "围绕你自己的观察池、候选池和持仓池做连续追问。",
description: "观察池 / 候选池 / 持仓池",
},
];
@ -111,9 +111,6 @@ export default function ChatPage() {
Combat Chat
</div>
<h1 className="mt-2 text-xl font-bold tracking-tight">AI </h1>
<p className="mt-2 text-sm leading-7 text-text-secondary">
</p>
</div>
<div className="glass-card-static p-5 animate-fade-in-up">
@ -129,17 +126,6 @@ export default function ChatPage() {
))}
</div>
</div>
<div className="glass-card-static p-5 animate-fade-in-up">
<div className="text-[10px] font-semibold uppercase tracking-[0.22em] text-text-muted">
使
</div>
<div className="mt-3 space-y-2 text-xs leading-6 text-text-secondary">
<p></p>
<p>线</p>
<p></p>
</div>
</div>
</aside>
<section className="glass-card-static flex min-h-0 flex-col overflow-hidden">
@ -177,10 +163,7 @@ export default function ChatPage() {
<path d="M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z" />
</svg>
</div>
<h3 className="text-lg font-semibold"></h3>
<p className="mx-auto mt-2 max-w-xl text-sm leading-7 text-text-secondary">
AI
</p>
<h3 className="text-lg font-semibold"></h3>
</div>
<div className="mx-auto mt-8 grid w-full max-w-3xl gap-3 md:grid-cols-2">
@ -260,9 +243,6 @@ export default function ChatPage() {
</button>
</div>
<div className="mt-2 text-center text-xs text-text-muted/35">
</div>
</div>
</section>
</div>

View File

@ -214,7 +214,6 @@ export default function DashboardPage() {
</span>
)}
</div>
<p className="mt-1 text-xs text-text-muted"></p>
</div>
<div className="flex items-center gap-2">
@ -305,13 +304,13 @@ function DecisionHero({
) : null}
</div>
<h2 className="mt-2 text-xl font-bold tracking-tight text-text-primary">{summary.headline}</h2>
<p className="mt-2 max-w-3xl text-sm leading-6 text-text-secondary">
<p className="mt-2 max-w-3xl text-sm leading-6 text-text-secondary line-clamp-2">
{summary.detail}
</p>
<div className="mt-4 grid grid-cols-1 md:grid-cols-2 gap-3">
<DecisionList title="可以做什么" items={summary.canDo} tone="positive" />
<DecisionList title="不可以做什么" items={summary.cannotDo} tone="risk" />
<DecisionList title="动作" items={summary.canDo} tone="positive" />
<DecisionList title="边界" items={summary.cannotDo} tone="risk" />
</div>
</div>
@ -368,7 +367,6 @@ function FocusPanel({
<div className="flex items-start justify-between gap-3">
<div>
<h3 className="text-sm font-semibold text-text-primary"></h3>
<p className="mt-1 text-xs text-text-muted"></p>
</div>
<div className="grid grid-cols-3 gap-2 text-center">
<MiniCount label="可操作" value={actionableCount} tone="text-red-400" />
@ -382,7 +380,7 @@ function FocusPanel({
focusQueue.map((rec) => <FocusStockCard key={rec.ts_code} rec={rec} />)
) : (
<div className="rounded-2xl border border-border-subtle bg-surface-1/70 p-6 text-center text-sm text-text-muted">
</div>
)}
</div>
@ -409,7 +407,6 @@ function MarketSnapshot({
<div className="flex items-center justify-between gap-3">
<div>
<h3 className="text-sm font-semibold text-text-primary"></h3>
<p className="mt-1 text-xs text-text-muted"></p>
</div>
<span className="rounded-xl bg-surface-1/70 px-3 py-1.5 text-xs font-mono tabular-nums text-text-secondary">
{Math.round(marketTemperature?.temperature ?? 0)}
@ -714,34 +711,34 @@ function buildMarketSummary(
const temp = marketTemperature?.temperature ?? board?.metrics.temperature ?? 0;
const allowTrading = strategyProfile?.allow_trading ?? actionableCount > 0;
const headline = !allowTrading
? "今天不主动出手,先防守观察"
? "防守观察"
: board?.market_regime ??
(temp >= 70 ? "市场偏强,可以围绕主线进攻" :
temp >= 50 ? "市场可做,但只做确认机会" :
temp >= 30 ? "市场分化,轻仓试错" :
"市场偏弱,以观察为主");
(temp >= 70 ? "主线进攻" :
temp >= 50 ? "确认机会" :
temp >= 30 ? "轻仓试错" :
"偏弱观察");
const detail =
strategyProfile?.decision_note ??
board?.summary ??
(scanStatus?.is_trading
? "当前使用盘中实时数据判断市场,重点在节奏、仓位和主线强弱,而不是静态分数。"
: "当前以收盘后数据为主,适合复盘主线、更新候选池和准备下一交易日。");
? "盘中实时:看节奏、仓位、主线强弱。"
: "收盘快照:复盘主线,准备下一交易日。");
const canDo = [
!allowTrading
? "今天只做观察等待,不把普通异动抬升为买入机会。"
? "观察等待。"
: actionableCount > 0
? `优先只看 ${actionableCount} 只可操作标的,避免在杂波里分散注意力`
: "没有明确可操作标的时,只保留观察,不主动开新仓。",
watchCount > 0 ? `重点关注 ${watchCount}等待确认的标的,等放量、回流或分歧转一致。` : "把注意力放在最强板块前排,而不是平均分配给所有候选。",
board?.position_suggestion ?? (strategyProfile?.max_position_pct ? `仓位上限先按 ${strategyProfile.max_position_pct}% 控制` : temp >= 50 ? "仓位可以试错,但不要脱离主线。" : "控制仓位,等待更强确认。"),
? `只看 ${actionableCount} 只可操作标的`
: "无可操作标的,保留观察。",
watchCount > 0 ? `关注 ${watchCount}确认信号。` : "盯最强板块前排。",
board?.position_suggestion ?? (strategyProfile?.max_position_pct ? `仓位上限 ${strategyProfile.max_position_pct}%` : temp >= 50 ? "围绕主线试错。" : "控制仓位。"),
];
const cannotDo = [
board?.avoid_rules?.[0] ?? "不要追后排、跟风和没有板块支撑的个股。",
board?.avoid_rules?.[1] ?? (temp < 50 ? "不要因为个别异动就误判成全面回暖。" : "不要把盘中脉冲当成全天主线。"),
!allowTrading || temp < 40 ? "不要扩大仓位做逆势试错。" : "不要脱离纪律随意切换题材。",
board?.avoid_rules?.[1] ?? (temp < 50 ? "不把个别异动当回暖。" : "不把盘中脉冲当主线。"),
!allowTrading || temp < 40 ? "不逆势加仓。" : "不随意切换题材。",
];
return {
@ -772,28 +769,28 @@ function buildActionGuides(
const priority = [
!allowTrading
? "先等市场重新给出清晰主线和承接,不主动寻找执行票。"
? "等待清晰主线和承接。"
: actionable[0]
? `${actionable[0].name}${actionable[0].trigger_condition ? `,触发条件是 ${actionable[0].trigger_condition}` : " 的确认信号"}`
? `${actionable[0].name}${actionable[0].trigger_condition ? `${actionable[0].trigger_condition}` : " 的确认信号"}`
: focusSectors[0]
? `${focusSectors[0]} 前排是否继续强化,再决定是否参与`
: "先观察龙头强度和市场承接,等待更清晰信号。",
? `${focusSectors[0]} 前排强度`
: "等待更清晰信号。",
focusSectors[1]
? `${focusSectors.slice(0, 2).join("、")} 作为主线池,不要同时追太多方向`
: "今天只围绕一条最强主线做决策,避免来回切换。",
!allowTrading ? "今天优先保守观察,不做预判型交易。" : temp >= 50 ? "优先分歧后的回流确认,不做无量冲高。" : "优先保守观察,只有最强确认才考虑出手。",
? `主线池:${focusSectors.slice(0, 2).join("、")}`
: "只围绕最强主线。",
!allowTrading ? "保守观察。" : temp >= 50 ? "做回流确认,不做无量冲高。" : "只看最强确认。",
];
const watchItems = [
watch[0]
? `${watch[0].name} 处于等待确认阶段,先看量能、板块回流和前排承接。`
? `${watch[0].name}:量能、回流、承接。`
: focusSectors[0]
? `${focusSectors[0]} 仍值得盯,但不满足确认前不追。`
: "观察是否会出现主线聚焦。",
? `${focusSectors[0]}确认前不追。`
: "观察新主线聚焦。",
watch[1]
? `${watch[1].name} 适合放进观察队列,不在首页直接下结论`
: "盘中若出现新热点,先确认是否有板块扩散,再决定是否纳入观察。",
observe.length > 0 ? `其余 ${observe.length} 只候选保持后台观察,不占用首页决策空间。` : "没有必要在首页堆更多弱标的。",
? `${watch[1].name}:观察队列`
: "新热点先看板块扩散。",
observe.length > 0 ? `后台观察 ${observe.length} 只。` : "暂无弱候选。",
];
const avoid = [

View File

@ -325,7 +325,6 @@ export default function DiagnosePage() {
<div className="flex flex-wrap items-center justify-between gap-3 mb-3">
<div>
<div className="text-[10px] uppercase tracking-[0.22em] text-cyan-400 font-semibold mb-1"></div>
<div className="text-sm text-text-secondary"></div>
</div>
<button
onClick={() => runDiagnosis(result?.ts_code || codeParam || undefined)}

View File

@ -101,10 +101,10 @@ export default function RecommendationsPage() {
const closed = allRecommendations.filter((rec) => ["closed_win", "closed_loss", "expired", "invalidated"].includes(rec.lifecycle_status ?? ""));
const focusTabs: Array<{ key: FocusTab; label: string; count: number; description: string }> = [
{ key: "actionable", label: "可操作", count: actionable.length, description: "只看今天最接近执行的标的" },
{ key: "watch", label: "重点关注", count: watch.length, description: "等待确认,不提前交易" },
{ key: "tracking", label: "跟踪中", count: tracking.length, description: "已经给出结论,继续看兑现" },
{ key: "closed", label: "已结束", count: closed.length, description: "复盘有效与失效,不占首页注意力" },
{ key: "actionable", label: "可操作", count: actionable.length, description: "执行名单" },
{ key: "watch", label: "重点关注", count: watch.length, description: "等待确认" },
{ key: "tracking", label: "跟踪中", count: tracking.length, description: "兑现进度" },
{ key: "closed", label: "已结束", count: closed.length, description: "复盘样本" },
];
const focusItems = useMemo(() => {
@ -142,9 +142,6 @@ export default function RecommendationsPage() {
<div className="max-w-7xl mx-auto px-4 md:px-8 pt-6 pb-20 md:pb-10 space-y-5">
<div className="animate-fade-in-up">
<h1 className="text-base sm:text-lg font-bold tracking-tight"></h1>
<p className="mt-1 text-xs text-text-muted">
</p>
</div>
<div className="glass-card-static p-4 md:p-5 animate-fade-in-up">
@ -152,7 +149,7 @@ export default function RecommendationsPage() {
<div>
<div className="text-[10px] uppercase tracking-[0.22em] text-amber-400 font-semibold"></div>
<h2 className="mt-2 text-xl font-bold tracking-tight text-text-primary">{focusSummary.headline}</h2>
<p className="mt-2 text-sm leading-6 text-text-secondary">{focusSummary.detail}</p>
<p className="mt-2 text-sm leading-6 text-text-secondary line-clamp-2">{focusSummary.detail}</p>
{latest?.strategy_profile ? (
<div className="mt-3 flex flex-wrap gap-2">
@ -235,13 +232,6 @@ export default function RecommendationsPage() {
</button>
))}
</div>
<div className="mt-4 rounded-2xl border border-border-subtle bg-surface-1/70 p-3">
<div className="text-[10px] uppercase tracking-wider text-text-muted font-semibold"></div>
<div className="mt-2 text-xs leading-6 text-text-secondary">
</div>
</div>
</div>
<div className="glass-card-static p-4 md:p-5">
@ -250,9 +240,6 @@ export default function RecommendationsPage() {
<h2 className="text-base font-bold tracking-tight text-text-primary">
{focusTabs.find((tab) => tab.key === focusTab)?.label ?? "焦点标的"}
</h2>
<p className="mt-1 text-xs text-text-muted">
</p>
</div>
</div>
@ -274,7 +261,6 @@ export default function RecommendationsPage() {
<div className="flex items-center justify-between gap-3">
<div>
<h2 className="text-sm font-semibold text-text-primary"></h2>
<p className="mt-1 text-xs text-text-muted">便</p>
</div>
<span className="text-xs text-text-muted">{observe.length} </span>
</div>
@ -302,7 +288,6 @@ export default function RecommendationsPage() {
<div className="flex flex-col md:flex-row md:items-center justify-between gap-3">
<div>
<h2 className="text-sm font-semibold text-text-primary"></h2>
<p className="mt-1 text-xs text-text-muted"></p>
</div>
<div className="flex gap-2 overflow-x-auto pb-1">
{SIGNAL_FILTERS.map((item) => (
@ -389,45 +374,45 @@ function buildFocusSummary({
const allowTrading = strategyProfile?.allow_trading ?? actionable.length > 0;
const headline =
!allowTrading
? "今天不主动出手,只保留观察名单"
? "防守观察"
: actionable.length > 0
? `今天只处理 ${actionable.length} 只可操作标的`
? `处理 ${actionable.length} 只可操作标的`
: watch.length > 0
? `今天没有直接执行标的,重点看 ${watch.length}等待确认`
: "今天没有明确优势机会,先观察";
? `关注 ${watch.length}确认信号`
: "暂无优势机会";
const detail =
strategyProfile?.decision_note
?? (!allowTrading
? "系统判断今天更适合防守观察,不需要为了参与而强行找票。"
? "防守观察,等待确认。"
: actionable.length > 0
? "首页只保留最接近执行的标的,真正长分析进入个股详情。"
? "执行名单已收敛。"
: watch.length > 0
? "今天偏等待确认,不适合在大量候选里反复横跳。"
: "当前没有明确优势机会,先观察,不主动扩池。");
? "等待确认,不扩池。"
: "观察为主。");
const now = [
!allowTrading
? "先看最强主线是否重新形成扩散和回流,再决定是否恢复进攻。"
? "等待主线扩散和回流。"
: actionable[0]
? `${actionable[0].name}${actionable[0].trigger_condition ? ` 的触发条件是否成立` : " 是否进一步确认"}`
? `${actionable[0].name}${actionable[0].trigger_condition ? `${actionable[0].trigger_condition}` : " 的确认信号"}`
: watch[0]
? `盯住 ${watch[0].name} 是否从观察转成可操作。`
: "只最强主线的少量候选,不主动扩池。",
: "只留主线候选。",
watch.length > 0
? `${watch.length} 只重点关注标的只做跟踪,不提前下结论`
? `${watch.length} 只重点关注,等待确认`
: allowTrading
? "没有重点关注时,不要强行从观察池里挑票。"
: "没有确认信号前,不把观察股抬升为执行名单。",
? "不从观察池强挑。"
: "确认前不提级。",
tracking.length > 0
? `${tracking.length} 只跟踪中标的继续看兑现情况,避免只看新增不看结果`
: "没有跟踪中的标的时,就把注意力集中在今天的新结论上。",
? `${tracking.length} 只跟踪兑现。`
: "关注今日新结论。",
];
const later = [
observe.length > 0 ? `${observe.length} 只后台观察标的不应占据首页主注意力。` : "没有必要把弱标的堆在默认视图。",
closed.length > 0 ? `${closed.length} 只已结束样本只在需要回看时再展开,不参与今日执行决策。` : "没有结束样本时,也不需要额外制造解释性信息。",
"方法说明只作为辅助,不应该压过今天的执行结论。",
observe.length > 0 ? `${observe.length} 只后台观察` : "不堆弱标的。",
closed.length > 0 ? `${closed.length} 只已结束样本` : "暂无结束样本。",
"不追无触发标的。",
];
return { headline, detail, now, later };

View File

@ -9,7 +9,7 @@ import { useWebSocket } from "@/hooks/use-websocket";
function getThemeAliasLine(sector: SectorData) {
const aliases = (sector.theme_aliases ?? []).filter((alias) => alias && alias !== sector.sector_name).slice(0, 4);
if (!aliases.length) return "系统主题已归一,无需拆分原始板块口径。";
if (!aliases.length) return "系统主题";
return `包含:${aliases.join(" / ")}`;
}
@ -36,31 +36,31 @@ function getActionPlan(sector: SectorData) {
if (pct <= 0) {
return {
label: "抗跌观察",
description: "当前只看谁先止跌转强,不把仍为负的方向当进攻主线。",
risk: "如果板块继续走弱或前排补跌,直接降级处理,不主动追击。",
description: "等待止跌转强。",
risk: "继续走弱或前排补跌。",
};
}
if (stage === "early") {
return {
label: "优先盯",
description: "优先看龙头和首次分歧后的回流。",
risk: "如果没有扩散、没有回流、没有承接,就不能当主线。",
description: "龙头和分歧回流。",
risk: "无扩散、无回流、无承接。",
};
}
if (stage === "mid" && pct > 0 && mainForce > 20) {
return {
label: "跟回流",
description: "更适合等确认,不适合无脑追高。",
risk: "如果板块内部开始掉队,要立刻降级成观察。",
description: "确认。",
risk: "内部掉队。",
};
}
return {
label: "只观察",
description: "保留跟踪,不作为今天的主仓位方向。",
risk: "一旦退潮加速,直接移出主视野。",
description: "保留跟踪。",
risk: "退潮加速。",
};
}
@ -79,8 +79,8 @@ function getHeadline(sectors: SectorData[]) {
return {
title: "暂无主线数据",
detail: "等待实时主题榜或扫描结果更新后再判断。",
canDo: ["当前不适合强行定义主线。"],
avoid: ["不要在没有主线主题支撑时做情绪追涨。"],
canDo: ["等待主题榜更新。"],
avoid: ["不做情绪追涨。"],
};
}
@ -91,51 +91,51 @@ function getHeadline(sectors: SectorData[]) {
const hasPositiveLeader = primaryPct > 0;
return {
title: hasPositiveLeader ? `当前优先盯 ${primary.sector_name}` : `当前没有明确进攻主线,${primary.sector_name} 只是相对抗跌`,
title: hasPositiveLeader ? `优先盯 ${primary.sector_name}` : `${primary.sector_name} 相对抗跌`,
detail: hasPositiveLeader
? `这里展示的不是外部行业榜,而是系统归一后的主线主题。当前次主线 ${secondaryName},观察线优先看 ${watchName}`
: `当前排在前面的主题也仍然下跌,说明系统拿到的是“相对最强”而不是“真正走强”的方向。此时更适合把 ${primary.sector_name}${secondaryName} 当成抗跌观察线,而不是进攻主线`,
? `次主线 ${secondaryName},观察线 ${watchName}`
: `${primary.sector_name}${secondaryName} 暂按抗跌观察`,
canDo: hasPositiveLeader
? [
`${primary.sector_name} 是当前最值得盯的方向,${primaryPlan.description}`,
secondary ? `${secondary.sector_name} 适合放在次主线观察,等是否转强。` : "如果没有次主线,就不要硬做轮动。",
"盯主题时优先看前排代表股、内部涨跌广度和是否出现回流。",
`${primary.sector_name}${primaryPlan.description}`,
secondary ? `${secondary.sector_name}:次主线观察。` : "暂无次主线。",
"看前排、广度、回流。",
]
: [
`优先${primary.sector_name} 是否最先止跌转强,而不是因为它排第一就直接当主线做`,
secondary ? `${secondary.sector_name} 只适合作为备选抗跌方向观察。` : "没有明显次主线时,不要强行找轮动方向。",
"当前更重要的是确认谁先翻红、谁先扩散、谁有前排承接。",
`${primary.sector_name} 是否止跌转强。`,
secondary ? `${secondary.sector_name}:备选抗跌。` : "暂无次主线。",
"确认翻红、扩散、承接。",
],
avoid: hasPositiveLeader
? [
"不把后排补涨方向当主线来做。",
"不只看主题涨幅,不看阶段、扩散和前排强弱。",
watch ? `${watch.sector_name} 更偏观察,不适合当今天的主攻方向。` : "阶段偏后的板块只保留观察。",
"不把后排补涨当主线。",
"不只看涨幅。",
watch ? `${watch.sector_name}:观察线。` : "后期板块只观察。",
]
: [
"不要把仍然为负的主题写成“今日主线”来理解。",
"不要在全市场偏弱时,为相对抗跌重仓进攻。",
"不要忽略整体环境,只盯着排行榜第一名。",
"不把负涨幅当主线。",
"不因抗跌重仓进攻。",
"不只盯排名第一。",
],
};
}
function getSourceLabel(source?: string) {
if (source === "eastmoney") return "东方财富实时板块榜归一";
if (source === "sina") return "新浪实时行业聚合归一";
if (source === "snapshot") return "本地主题快照";
if (source === "mixed") return "多源归一主题";
if (source === "eastmoney") return "东方财富";
if (source === "sina") return "新浪";
if (source === "snapshot") return "本地快照";
if (source === "mixed") return "多源";
return "未知来源";
}
function getSourceRiskHint(source?: string, dataMode?: string) {
if (source === "snapshot" || dataMode === "daily_snapshot") {
return "当前展示的是系统主题快照,不是实时外部行业榜;如果你拿它逐项对比东方财富原始板块名,会存在口径差异。";
return "快照口径";
}
if (source === "sina") {
return "当前主题由新浪行业聚合后再归一,和东方财富原始板块分类不完全一致。";
return "新浪口径";
}
return "当前优先使用实时板块数据,并统一归一成系统主题。";
return "实时口径";
}
function LeadingStockPill({ stock }: { stock: LeadingStock }) {
@ -530,27 +530,18 @@ export default function SectorsPage() {
<div className="max-w-7xl mx-auto px-4 md:px-8 pt-6 pb-20 md:pb-10 space-y-5">
<div className="animate-fade-in-up">
<h1 className="text-lg font-bold tracking-tight">线</h1>
<p className="mt-1 text-xs text-text-muted">
线
{hasRealtime ? <span className="ml-1 text-emerald-400/70">· </span> : null}
</p>
<div className="mt-2 inline-flex items-center gap-2 rounded-xl border border-border-subtle bg-surface-1/70 px-3 py-2 text-[11px] text-text-secondary">
<span className="text-text-muted"></span>
<span className="text-text-muted"></span>
<span className="font-medium text-text-primary">{getSourceLabel(source)}</span>
<span className="text-text-muted/50">·</span>
<span className="text-text-muted">{dataMode}</span>
{hasRealtime ? <span className="text-emerald-400/70"></span> : null}
<span className="text-text-muted/50">·</span>
<span className="text-amber-400/80">{sourceRiskHint}</span>
</div>
<p className="mt-2 text-[11px] text-amber-400/80">
{sourceRiskHint}
</p>
{hasRealtime && dataMode === "realtime_today" ? (
<p className="mt-2 text-[11px] text-text-muted/70">
使广使
</p>
) : null}
{hasRealtime && dataMode === "realtime_overlay" ? (
<p className="mt-2 text-[11px] text-text-muted/70">
{structureTradeDate || "最近交易日"}
{structureTradeDate || "最近交易日"}
</p>
) : null}
</div>
@ -569,8 +560,8 @@ export default function SectorsPage() {
<h2 className="mt-2 text-xl font-bold tracking-tight text-text-primary">{summary.title}</h2>
<p className="mt-2 max-w-3xl text-sm leading-6 text-text-secondary">{summary.detail}</p>
<div className="mt-4 grid grid-cols-1 md:grid-cols-2 gap-3">
<DecisionList title="今天可以怎么用" items={summary.canDo} tone="positive" />
<DecisionList title="今天不要怎么用" items={summary.avoid} tone="risk" />
<DecisionList title="动作" items={summary.canDo} tone="positive" />
<DecisionList title="边界" items={summary.avoid} tone="risk" />
</div>
</div>
@ -586,19 +577,19 @@ export default function SectorsPage() {
<div className="grid grid-cols-1 xl:grid-cols-3 gap-4 animate-fade-in-up">
<LaneCard
title={hasPositiveLeader ? "今日主线" : "相对抗跌"}
description={hasPositiveLeader ? "今天最值得盯、最可能承载仓位的方向。" : "当前排在前面的方向,但还不能定义成进攻主线。"}
description={hasPositiveLeader ? "优先方向" : "暂不进攻"}
sectors={mainline}
tone="red"
/>
<LaneCard
title={hasPositiveLeader ? "次主线" : "弱轮动观察"}
description={hasPositiveLeader ? "可跟踪轮动,但不应抢过主线优先级。" : "只看谁有转强迹象,不把它们当主攻方向。"}
description={hasPositiveLeader ? "轮动跟踪" : "转强观察"}
sectors={secondary}
tone="amber"
/>
<LaneCard
title="观察线"
description="只记录变化,不作为当前主动出手方向。"
description="变化记录"
sectors={watchline}
tone="slate"
/>
@ -609,9 +600,6 @@ export default function SectorsPage() {
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<h2 className="text-sm font-semibold text-text-primary"></h2>
<p className="mt-1 text-xs text-text-muted">
</p>
</div>
<div className="flex flex-wrap items-center gap-2">
{[
@ -654,9 +642,6 @@ export default function SectorsPage() {
<div className="flex items-center justify-between gap-3">
<div>
<h2 className="text-sm font-semibold text-text-primary"></h2>
<p className="mt-1 text-xs text-text-muted">
</p>
</div>
<button
onClick={() => setShowRotation((value) => !value)}

View File

@ -630,9 +630,9 @@ function buildHeroSummary(thesis: StockThesisResponse | null, quote: QuoteData |
if (thesis?.has_recommendation && thesis.recommendation) {
const rec = thesis.recommendation;
const quoteText = quote ? `当前价格 ${quote.price.toFixed(2)},涨跌幅 ${quote.pct_chg > 0 ? "+" : ""}${quote.pct_chg.toFixed(2)}%。` : "";
return `当前处于${rec.action_plan ?? "观察"}阶段。${quoteText} 先看触发与失效条件,再看技术和资金证据,不把参考分当成最终结论`;
return `当前${rec.action_plan ?? "观察"}${quoteText} 触发:${rec.trigger_condition || "暂无"};失效:${rec.invalidation_condition || "暂无"}`;
}
return "暂无推荐归档,这里优先展示该股票的历史诊断与行情证据。后续若进入推荐池,会在此沉淀操作计划和跟踪结果。";
return "暂无推荐归档。";
}
function formatDateTime(value?: string) {

View File

@ -105,15 +105,11 @@ export default function StrategyPage() {
<div className="max-w-7xl mx-auto px-4 md:px-8 pt-6 pb-20 md:pb-10 space-y-5">
<div className="animate-fade-in-up">
<h1 className="text-xl font-bold tracking-tight"></h1>
<p className="mt-1 text-sm text-text-muted">
</p>
</div>
<div className="glass-card-static p-3 animate-fade-in-up flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div>
<div className="text-sm font-semibold text-text-primary"></div>
<div className="mt-1 text-xs text-text-muted"> Agent </div>
</div>
<div className="flex flex-wrap items-center gap-2">
{actionMessage ? <span className="text-xs text-amber-400">{actionMessage}</span> : null}
@ -131,11 +127,11 @@ export default function StrategyPage() {
<div>
<div className="text-[10px] uppercase tracking-[0.22em] text-amber-400 font-semibold"></div>
<h2 className="mt-2 text-xl font-bold tracking-tight text-text-primary">{diagnosis.headline}</h2>
<p className="mt-2 max-w-3xl text-sm leading-6 text-text-secondary">{diagnosis.detail}</p>
<p className="mt-2 max-w-3xl text-sm leading-6 text-text-secondary line-clamp-2">{diagnosis.detail}</p>
<div className="mt-4 grid grid-cols-1 md:grid-cols-2 gap-3">
<DecisionList title="这页是做什么的" items={diagnosis.useFor} tone="positive" />
<DecisionList title="这页不是做什么的" items={diagnosis.notFor} tone="risk" />
<DecisionList title="用途" items={diagnosis.useFor} tone="positive" />
<DecisionList title="边界" items={diagnosis.notFor} tone="risk" />
</div>
</div>
@ -145,7 +141,7 @@ export default function StrategyPage() {
<MetricCard label="平均收益" value={`${(performance?.avg_return ?? 0) > 0 ? "+" : ""}${(performance?.avg_return ?? 0).toFixed(2)}%`} tone={(performance?.avg_return ?? 0) >= 0 ? "up" : "down"} />
<MetricCard label="平均回撤" value={`${(performance?.avg_max_drawdown ?? 0).toFixed(2)}%`} tone="down" />
<MetricFact label="已跟踪" value={`${performance?.tracked ?? 0}`} />
<MetricFact label="页角色" value="方法迭代,不做盘中执行" />
<MetricFact label="角色" value="方法迭代" />
</div>
</div>
</div>
@ -171,24 +167,7 @@ export default function StrategyPage() {
</div>
) : null}
</div>
<div className="glass-card-static p-5 animate-fade-in-up">
<SectionTitle title="应该怎么用" />
<div className="mt-3 space-y-3">
<UsageCard
title="先看系统有没有偏掉"
description="如果整体胜率、平均收益、回撤都在恶化,说明不是个股问题,而是推荐方法本身需要收紧。"
/>
<UsageCard
title="再看哪类方法有效"
description="按策略、按信号拆开看,判断是突破、回踩还是某类策略最近更有效。"
/>
<UsageCard
title="最后把结果回写到下一轮"
description="这里的目标不是描述过去,而是生成下一轮该加强、该降权、该观察的系统指令。"
/>
</div>
</div>
<ReviewWindowsPanel windows={iteration.review_windows ?? []} />
</div>
<ConfigCenterPanel
@ -196,12 +175,9 @@ export default function StrategyPage() {
onRollback={rollbackConfig}
/>
<ReviewWindowsPanel windows={iteration.review_windows ?? []} />
<div className="glass-card-static p-5 animate-fade-in-up">
<div className="flex items-center justify-between gap-3">
<SectionTitle title="下一轮系统指令" />
<span className="text-xs text-text-muted"></span>
</div>
<div className="mt-4 space-y-3">
{(iteration.adjustment_suggestions.length
@ -216,12 +192,12 @@ export default function StrategyPage() {
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<StatsPanel
title="哪些策略更有效"
description="看不同策略分组最近表现,判断哪类打法该保留、该削弱。"
description="按策略分组"
stats={iteration.strategy_stats}
/>
<StatsPanel
title="哪些信号更有效"
description="看突破、回踩、启动等信号的兑现质量,而不是只看出现频次。"
description="按信号分组"
stats={iteration.signal_stats}
/>
</div>
@ -234,9 +210,6 @@ export default function StrategyPage() {
<div className="glass-card-static p-5 animate-fade-in-up">
<SectionTitle title="最近的失效模式" />
<div className="mt-2 text-xs text-text-muted">
</div>
<div className="mt-4 space-y-2">
{iteration.failure_patterns.length ? (
iteration.failure_patterns.map((pattern, index) => (
@ -255,7 +228,6 @@ export default function StrategyPage() {
) : (
<div className="glass-card-static p-10 text-center">
<div className="text-text-muted text-sm"></div>
<div className="text-text-muted/50 text-xs mt-1"></div>
</div>
)}
</div>
@ -271,27 +243,27 @@ function buildCalibrationDiagnosis(
const tracked = performance?.tracked ?? 0;
const headline =
tracked < 10
? "当前更像早期样本积累阶段"
? "样本积累中"
: winRate >= 55 && avgReturn >= 0
? "当前方法仍然有效,但需要持续校准"
: "当前方法出现退化,需要收紧与调整";
? "方法有效"
: "方法退化";
const detail =
iteration?.summary ??
(tracked < 10
? "闭环样本还不够多,这一页更适合看方向性的偏差,而不是做强结论。"
: "这个页面的目标是判断推荐方法最近有没有偏掉,以及下一轮应该如何调整。");
? "闭环样本不足,只看方向偏差。"
: "检查推荐方法偏差和下一轮调整。");
const useFor = [
"验证系统最近推荐出来的东西,长期看是否真的有效。",
"识别哪类策略、哪类信号最近更有效或更容易失效。",
"把复盘结论转成下一轮推荐系统的收紧、加强或降权指令。",
"验证推荐兑现率。",
"识别有效策略和信号。",
"生成下一轮配置调整。",
];
const notFor = [
"不是盘中决策页,不负责告诉你现在立刻买哪只股票。",
"不是板块行情页,不负责追踪今天最热方向。",
"不是个股详情页,不负责展开单只股票的全部逻辑。",
"不做盘中买卖决策。",
"不替代板块行情。",
"不展开单股长逻辑。",
];
return { headline, detail, useFor, notFor };
@ -527,15 +499,6 @@ function formatUnknown(value: unknown): string {
}
}
function UsageCard({ title, description }: { title: string; description: string }) {
return (
<div className="rounded-2xl border border-border-subtle bg-surface-1/70 p-3">
<div className="text-sm font-semibold text-text-primary">{title}</div>
<div className="mt-1 text-xs leading-6 text-text-muted">{description}</div>
</div>
);
}
function NextInstruction({ item, index }: { item: StrategyAdjustment; index: number }) {
const verb = ACTION_LABELS[item.action] ?? item.action;
const color =

View File

@ -369,9 +369,6 @@ function WatchlistOverview({
<div>
<div className="text-[10px] uppercase tracking-[0.22em] text-cyan-400 font-semibold">Personal Mission Control</div>
<h2 className="text-xl font-bold tracking-tight mt-2"></h2>
<p className="text-sm text-text-secondary leading-relaxed mt-2">
线
</p>
<div className="grid grid-cols-2 md:grid-cols-5 gap-2 mt-4">
<OverviewMetric label="总数" value={items.length} />
<OverviewMetric label="可操作" value={actionable} tone="emerald" />
@ -384,7 +381,6 @@ function WatchlistOverview({
<div className="flex items-center justify-between gap-3">
<div>
<div className="text-[10px] uppercase tracking-wider text-text-muted font-semibold"></div>
<div className="text-sm text-text-secondary mt-1"></div>
</div>
<button
onClick={onBatchAnalyze}

View File

@ -38,7 +38,6 @@ export default function MarketTemp({ data, indices }: MarketTempProps) {
<div className="flex items-start justify-between gap-3 mb-4">
<div>
<h2 className="text-sm font-semibold text-text-muted"></h2>
<p className="text-[10px] text-text-muted/60 mt-0.5"></p>
</div>
<span className="text-[10px] sm:text-xs text-text-muted font-mono tabular-nums">{data.trade_date}</span>
</div>

View File

@ -20,9 +20,9 @@ function getSectorFocus(sector: SectorData): string {
return `盯住 ${leaders.slice(0, 2).map((item) => item.name).join(" / ")} 的承接和回流。`;
}
if ((sector.realtime_up_count ?? 0) > (sector.realtime_down_count ?? 0)) {
return "板块内上涨家数占优,优先看前排继续强化。";
return "上涨占优,看前排强化。";
}
return "板块内分化较重,只保留观察不追后排。";
return "分化较重,观察不追。";
}
export default function SectorHeatmap({ sectors }: { sectors: SectorData[] }) {
@ -43,9 +43,6 @@ export default function SectorHeatmap({ sectors }: { sectors: SectorData[] }) {
<div className="flex items-start justify-between gap-3 mb-4">
<div>
<h2 className="text-sm font-semibold text-text-muted">线</h2>
<p className="text-[10px] text-text-muted/60 mt-0.5">
</p>
</div>
{hasRealtime && (
<span className="text-[10px] px-1.5 py-0.5 rounded bg-emerald-500/10 text-emerald-400/80">

View File

@ -52,9 +52,9 @@ export default function StockCard({ rec, compact = false }: { rec: Recommendatio
invalidated: "已失效",
};
const actionPlanCopy: Record<string, string> = {
"可操作": "触发条件成立时才执行",
"重点关注": "等待确认,不提前交易",
"观察": "只记录,不主动出手",
"可操作": "触发执行",
"重点关注": "等待确认",
"观察": "仅观察",
};
const evidence = [
rec.prefilter_reason,