1
This commit is contained in:
parent
cde11656c8
commit
41d19b5a36
@ -20,15 +20,15 @@ const QUICK_QUESTIONS = [
|
|||||||
const CHAT_SCENES = [
|
const CHAT_SCENES = [
|
||||||
{
|
{
|
||||||
title: "问今日打法",
|
title: "问今日打法",
|
||||||
description: "把今日结论翻译成人话,说明现在该进攻、试错还是防守。",
|
description: "进攻 / 试错 / 防守",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "问推荐池",
|
title: "问推荐池",
|
||||||
description: "追问某只推荐股为什么进池、什么条件下能看、什么条件下放弃。",
|
description: "进池原因 / 触发 / 放弃",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "问自选股",
|
title: "问自选股",
|
||||||
description: "围绕你自己的观察池、候选池和持仓池做连续追问。",
|
description: "观察池 / 候选池 / 持仓池",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -111,9 +111,6 @@ export default function ChatPage() {
|
|||||||
Combat Chat
|
Combat Chat
|
||||||
</div>
|
</div>
|
||||||
<h1 className="mt-2 text-xl font-bold tracking-tight">AI 作战问答</h1>
|
<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>
|
||||||
|
|
||||||
<div className="glass-card-static p-5 animate-fade-in-up">
|
<div className="glass-card-static p-5 animate-fade-in-up">
|
||||||
@ -129,17 +126,6 @@ export default function ChatPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</aside>
|
||||||
|
|
||||||
<section className="glass-card-static flex min-h-0 flex-col overflow-hidden">
|
<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" />
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-semibold">先用它来拆解系统已经给出的结论</h3>
|
<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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mx-auto mt-8 grid w-full max-w-3xl gap-3 md:grid-cols-2">
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 text-center text-xs text-text-muted/35">
|
|
||||||
这里只适合围绕系统数据追问,不适合作为无上下文的泛财经问答。
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -214,7 +214,6 @@ export default function DashboardPage() {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-1 text-xs text-text-muted">先看结论,再看动作,再看焦点标的。</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@ -305,13 +304,13 @@ function DecisionHero({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<h2 className="mt-2 text-xl font-bold tracking-tight text-text-primary">{summary.headline}</h2>
|
<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}
|
{summary.detail}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="mt-4 grid grid-cols-1 md:grid-cols-2 gap-3">
|
<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.canDo} tone="positive" />
|
||||||
<DecisionList title="不可以做什么" items={summary.cannotDo} tone="risk" />
|
<DecisionList title="边界" items={summary.cannotDo} tone="risk" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -368,7 +367,6 @@ function FocusPanel({
|
|||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-semibold text-text-primary">焦点标的</h3>
|
<h3 className="text-sm font-semibold text-text-primary">焦点标的</h3>
|
||||||
<p className="mt-1 text-xs text-text-muted">首页只保留最该处理的少量标的。</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-3 gap-2 text-center">
|
<div className="grid grid-cols-3 gap-2 text-center">
|
||||||
<MiniCount label="可操作" value={actionableCount} tone="text-red-400" />
|
<MiniCount label="可操作" value={actionableCount} tone="text-red-400" />
|
||||||
@ -382,7 +380,7 @@ function FocusPanel({
|
|||||||
focusQueue.map((rec) => <FocusStockCard key={rec.ts_code} rec={rec} />)
|
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 className="rounded-2xl border border-border-subtle bg-surface-1/70 p-6 text-center text-sm text-text-muted">
|
||||||
今天没有需要处理的焦点标的。
|
暂无焦点标的。
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -409,7 +407,6 @@ function MarketSnapshot({
|
|||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-semibold text-text-primary">市场证据</h3>
|
<h3 className="text-sm font-semibold text-text-primary">市场证据</h3>
|
||||||
<p className="mt-1 text-xs text-text-muted">只保留对今天决策有用的信息。</p>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="rounded-xl bg-surface-1/70 px-3 py-1.5 text-xs font-mono tabular-nums text-text-secondary">
|
<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)}
|
温度 {Math.round(marketTemperature?.temperature ?? 0)}
|
||||||
@ -714,34 +711,34 @@ function buildMarketSummary(
|
|||||||
const temp = marketTemperature?.temperature ?? board?.metrics.temperature ?? 0;
|
const temp = marketTemperature?.temperature ?? board?.metrics.temperature ?? 0;
|
||||||
const allowTrading = strategyProfile?.allow_trading ?? actionableCount > 0;
|
const allowTrading = strategyProfile?.allow_trading ?? actionableCount > 0;
|
||||||
const headline = !allowTrading
|
const headline = !allowTrading
|
||||||
? "今天不主动出手,先防守观察"
|
? "防守观察"
|
||||||
: board?.market_regime ??
|
: board?.market_regime ??
|
||||||
(temp >= 70 ? "市场偏强,可以围绕主线进攻" :
|
(temp >= 70 ? "主线进攻" :
|
||||||
temp >= 50 ? "市场可做,但只做确认机会" :
|
temp >= 50 ? "确认机会" :
|
||||||
temp >= 30 ? "市场分化,轻仓试错" :
|
temp >= 30 ? "轻仓试错" :
|
||||||
"市场偏弱,以观察为主");
|
"偏弱观察");
|
||||||
|
|
||||||
const detail =
|
const detail =
|
||||||
strategyProfile?.decision_note ??
|
strategyProfile?.decision_note ??
|
||||||
board?.summary ??
|
board?.summary ??
|
||||||
(scanStatus?.is_trading
|
(scanStatus?.is_trading
|
||||||
? "当前使用盘中实时数据判断市场,重点在节奏、仓位和主线强弱,而不是静态分数。"
|
? "盘中实时:看节奏、仓位、主线强弱。"
|
||||||
: "当前以收盘后数据为主,适合复盘主线、更新候选池和准备下一交易日。");
|
: "收盘快照:复盘主线,准备下一交易日。");
|
||||||
|
|
||||||
const canDo = [
|
const canDo = [
|
||||||
!allowTrading
|
!allowTrading
|
||||||
? "今天只做观察和等待,不把普通异动抬升为买入机会。"
|
? "观察等待。"
|
||||||
: actionableCount > 0
|
: actionableCount > 0
|
||||||
? `优先只看 ${actionableCount} 只可操作标的,避免在杂波里分散注意力。`
|
? `只看 ${actionableCount} 只可操作标的。`
|
||||||
: "没有明确可操作标的时,只保留观察,不主动开新仓。",
|
: "无可操作标的,保留观察。",
|
||||||
watchCount > 0 ? `重点关注 ${watchCount} 只等待确认的标的,等放量、回流或分歧转一致。` : "把注意力放在最强板块前排,而不是平均分配给所有候选。",
|
watchCount > 0 ? `关注 ${watchCount} 只确认信号。` : "盯最强板块前排。",
|
||||||
board?.position_suggestion ?? (strategyProfile?.max_position_pct ? `总仓位上限先按 ${strategyProfile.max_position_pct}% 控制。` : temp >= 50 ? "仓位可以试错,但不要脱离主线。" : "控制仓位,等待更强确认。"),
|
board?.position_suggestion ?? (strategyProfile?.max_position_pct ? `仓位上限 ${strategyProfile.max_position_pct}%。` : temp >= 50 ? "围绕主线试错。" : "控制仓位。"),
|
||||||
];
|
];
|
||||||
|
|
||||||
const cannotDo = [
|
const cannotDo = [
|
||||||
board?.avoid_rules?.[0] ?? "不要追后排、跟风和没有板块支撑的个股。",
|
board?.avoid_rules?.[0] ?? "不要追后排、跟风和没有板块支撑的个股。",
|
||||||
board?.avoid_rules?.[1] ?? (temp < 50 ? "不要因为个别异动就误判成全面回暖。" : "不要把盘中脉冲当成全天主线。"),
|
board?.avoid_rules?.[1] ?? (temp < 50 ? "不把个别异动当回暖。" : "不把盘中脉冲当主线。"),
|
||||||
!allowTrading || temp < 40 ? "不要扩大仓位做逆势试错。" : "不要脱离纪律随意切换题材。",
|
!allowTrading || temp < 40 ? "不逆势加仓。" : "不随意切换题材。",
|
||||||
];
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -772,28 +769,28 @@ function buildActionGuides(
|
|||||||
|
|
||||||
const priority = [
|
const priority = [
|
||||||
!allowTrading
|
!allowTrading
|
||||||
? "先等市场重新给出清晰主线和承接,不主动寻找执行票。"
|
? "等待清晰主线和承接。"
|
||||||
: actionable[0]
|
: 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[0]} 前排强度。`
|
||||||
: "先观察龙头强度和市场承接,等待更清晰信号。",
|
: "等待更清晰信号。",
|
||||||
focusSectors[1]
|
focusSectors[1]
|
||||||
? `把 ${focusSectors.slice(0, 2).join("、")} 作为主线池,不要同时追太多方向。`
|
? `主线池:${focusSectors.slice(0, 2).join("、")}。`
|
||||||
: "今天只围绕一条最强主线做决策,避免来回切换。",
|
: "只围绕最强主线。",
|
||||||
!allowTrading ? "今天优先保守观察,不做预判型交易。" : temp >= 50 ? "优先做分歧后的回流和确认,不做无量冲高。" : "优先保守观察,只有最强确认才考虑出手。",
|
!allowTrading ? "保守观察。" : temp >= 50 ? "做回流确认,不做无量冲高。" : "只看最强确认。",
|
||||||
];
|
];
|
||||||
|
|
||||||
const watchItems = [
|
const watchItems = [
|
||||||
watch[0]
|
watch[0]
|
||||||
? `${watch[0].name} 处于等待确认阶段,先看量能、板块回流和前排承接。`
|
? `${watch[0].name}:量能、回流、承接。`
|
||||||
: focusSectors[0]
|
: focusSectors[0]
|
||||||
? `${focusSectors[0]} 仍值得盯,但不满足确认前不追。`
|
? `${focusSectors[0]}:确认前不追。`
|
||||||
: "观察是否会出现新的主线聚焦。",
|
: "观察新主线聚焦。",
|
||||||
watch[1]
|
watch[1]
|
||||||
? `${watch[1].name} 适合放进观察队列,不在首页直接下结论。`
|
? `${watch[1].name}:观察队列。`
|
||||||
: "盘中若出现新热点,先确认是否有板块扩散,再决定是否纳入观察。",
|
: "新热点先看板块扩散。",
|
||||||
observe.length > 0 ? `其余 ${observe.length} 只候选保持后台观察,不占用首页决策空间。` : "没有必要在首页堆更多弱标的。",
|
observe.length > 0 ? `后台观察 ${observe.length} 只。` : "暂无弱候选。",
|
||||||
];
|
];
|
||||||
|
|
||||||
const avoid = [
|
const avoid = [
|
||||||
|
|||||||
@ -325,7 +325,6 @@ export default function DiagnosePage() {
|
|||||||
<div className="flex flex-wrap items-center justify-between gap-3 mb-3">
|
<div className="flex flex-wrap items-center justify-between gap-3 mb-3">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[10px] uppercase tracking-[0.22em] text-cyan-400 font-semibold mb-1">结构化结论</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>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => runDiagnosis(result?.ts_code || codeParam || undefined)}
|
onClick={() => runDiagnosis(result?.ts_code || codeParam || undefined)}
|
||||||
|
|||||||
@ -101,10 +101,10 @@ export default function RecommendationsPage() {
|
|||||||
const closed = allRecommendations.filter((rec) => ["closed_win", "closed_loss", "expired", "invalidated"].includes(rec.lifecycle_status ?? ""));
|
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 }> = [
|
const focusTabs: Array<{ key: FocusTab; label: string; count: number; description: string }> = [
|
||||||
{ key: "actionable", label: "可操作", count: actionable.length, description: "只看今天最接近执行的标的" },
|
{ key: "actionable", label: "可操作", count: actionable.length, description: "执行名单" },
|
||||||
{ key: "watch", label: "重点关注", count: watch.length, description: "等待确认,不提前交易" },
|
{ key: "watch", label: "重点关注", count: watch.length, description: "等待确认" },
|
||||||
{ key: "tracking", label: "跟踪中", count: tracking.length, description: "已经给出结论,继续看兑现" },
|
{ key: "tracking", label: "跟踪中", count: tracking.length, description: "兑现进度" },
|
||||||
{ key: "closed", label: "已结束", count: closed.length, description: "复盘有效与失效,不占首页注意力" },
|
{ key: "closed", label: "已结束", count: closed.length, description: "复盘样本" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const focusItems = useMemo(() => {
|
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="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">
|
<div className="animate-fade-in-up">
|
||||||
<h1 className="text-base sm:text-lg font-bold tracking-tight">今日决策池</h1>
|
<h1 className="text-base sm:text-lg font-bold tracking-tight">今日决策池</h1>
|
||||||
<p className="mt-1 text-xs text-text-muted">
|
|
||||||
先判断今天能不能做,再给极少数执行名单和观察名单,历史记录只用于复盘。
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="glass-card-static p-4 md:p-5 animate-fade-in-up">
|
<div className="glass-card-static p-4 md:p-5 animate-fade-in-up">
|
||||||
@ -152,7 +149,7 @@ export default function RecommendationsPage() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="text-[10px] uppercase tracking-[0.22em] text-amber-400 font-semibold">今日结论</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>
|
<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 ? (
|
{latest?.strategy_profile ? (
|
||||||
<div className="mt-3 flex flex-wrap gap-2">
|
<div className="mt-3 flex flex-wrap gap-2">
|
||||||
@ -235,13 +232,6 @@ export default function RecommendationsPage() {
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div className="glass-card-static p-4 md:p-5">
|
<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">
|
<h2 className="text-base font-bold tracking-tight text-text-primary">
|
||||||
{focusTabs.find((tab) => tab.key === focusTab)?.label ?? "焦点标的"}
|
{focusTabs.find((tab) => tab.key === focusTab)?.label ?? "焦点标的"}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-1 text-xs text-text-muted">
|
|
||||||
只给今天真正要处理的少量标的,剩余候选不占主视图。
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -274,7 +261,6 @@ export default function RecommendationsPage() {
|
|||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-sm font-semibold text-text-primary">后台观察</h2>
|
<h2 className="text-sm font-semibold text-text-primary">后台观察</h2>
|
||||||
<p className="mt-1 text-xs text-text-muted">只保留少量名字方便回看,不参与今天的主决策。</p>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-text-muted">{observe.length} 只</span>
|
<span className="text-xs text-text-muted">{observe.length} 只</span>
|
||||||
</div>
|
</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 className="flex flex-col md:flex-row md:items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-sm font-semibold text-text-primary">历史记录</h2>
|
<h2 className="text-sm font-semibold text-text-primary">历史记录</h2>
|
||||||
<p className="mt-1 text-xs text-text-muted">历史默认折叠,只在需要复盘某一天时展开。</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 overflow-x-auto pb-1">
|
<div className="flex gap-2 overflow-x-auto pb-1">
|
||||||
{SIGNAL_FILTERS.map((item) => (
|
{SIGNAL_FILTERS.map((item) => (
|
||||||
@ -389,45 +374,45 @@ function buildFocusSummary({
|
|||||||
const allowTrading = strategyProfile?.allow_trading ?? actionable.length > 0;
|
const allowTrading = strategyProfile?.allow_trading ?? actionable.length > 0;
|
||||||
const headline =
|
const headline =
|
||||||
!allowTrading
|
!allowTrading
|
||||||
? "今天不主动出手,只保留观察名单"
|
? "防守观察"
|
||||||
: actionable.length > 0
|
: actionable.length > 0
|
||||||
? `今天只处理 ${actionable.length} 只可操作标的`
|
? `处理 ${actionable.length} 只可操作标的`
|
||||||
: watch.length > 0
|
: watch.length > 0
|
||||||
? `今天没有直接执行标的,重点看 ${watch.length} 只等待确认`
|
? `关注 ${watch.length} 只确认信号`
|
||||||
: "今天没有明确优势机会,先观察";
|
: "暂无优势机会";
|
||||||
|
|
||||||
const detail =
|
const detail =
|
||||||
strategyProfile?.decision_note
|
strategyProfile?.decision_note
|
||||||
?? (!allowTrading
|
?? (!allowTrading
|
||||||
? "系统判断今天更适合防守观察,不需要为了参与而强行找票。"
|
? "防守观察,等待确认。"
|
||||||
: actionable.length > 0
|
: actionable.length > 0
|
||||||
? "首页只保留最接近执行的标的,真正长分析进入个股详情。"
|
? "执行名单已收敛。"
|
||||||
: watch.length > 0
|
: watch.length > 0
|
||||||
? "今天偏等待确认,不适合在大量候选里反复横跳。"
|
? "等待确认,不扩池。"
|
||||||
: "当前没有明确优势机会,先观察,不主动扩池。");
|
: "观察为主。");
|
||||||
|
|
||||||
const now = [
|
const now = [
|
||||||
!allowTrading
|
!allowTrading
|
||||||
? "先看最强主线是否重新形成扩散和回流,再决定是否恢复进攻。"
|
? "等待主线扩散和回流。"
|
||||||
: actionable[0]
|
: actionable[0]
|
||||||
? `先看 ${actionable[0].name}${actionable[0].trigger_condition ? ` 的触发条件是否成立` : " 是否进一步确认"}。`
|
? `看 ${actionable[0].name}${actionable[0].trigger_condition ? `:${actionable[0].trigger_condition}` : " 的确认信号"}。`
|
||||||
: watch[0]
|
: watch[0]
|
||||||
? `盯住 ${watch[0].name} 是否从观察转成可操作。`
|
? `盯住 ${watch[0].name} 是否从观察转成可操作。`
|
||||||
: "只保留最强主线的少量候选,不主动扩池。",
|
: "只留主线候选。",
|
||||||
watch.length > 0
|
watch.length > 0
|
||||||
? `${watch.length} 只重点关注标的只做跟踪,不提前下结论。`
|
? `${watch.length} 只重点关注,等待确认。`
|
||||||
: allowTrading
|
: allowTrading
|
||||||
? "没有重点关注时,不要强行从观察池里挑票。"
|
? "不从观察池强挑。"
|
||||||
: "没有确认信号前,不把观察股抬升为执行名单。",
|
: "确认前不提级。",
|
||||||
tracking.length > 0
|
tracking.length > 0
|
||||||
? `${tracking.length} 只跟踪中标的继续看兑现情况,避免只看新增不看结果。`
|
? `${tracking.length} 只跟踪兑现。`
|
||||||
: "没有跟踪中的标的时,就把注意力集中在今天的新结论上。",
|
: "关注今日新结论。",
|
||||||
];
|
];
|
||||||
|
|
||||||
const later = [
|
const later = [
|
||||||
observe.length > 0 ? `${observe.length} 只后台观察标的不应占据首页主注意力。` : "没有必要把弱标的堆在默认视图。",
|
observe.length > 0 ? `${observe.length} 只后台观察。` : "不堆弱标的。",
|
||||||
closed.length > 0 ? `${closed.length} 只已结束样本只在需要回看时再展开,不参与今日执行决策。` : "没有结束样本时,也不需要额外制造解释性信息。",
|
closed.length > 0 ? `${closed.length} 只已结束样本。` : "暂无结束样本。",
|
||||||
"方法说明只作为辅助,不应该压过今天的执行结论。",
|
"不追无触发标的。",
|
||||||
];
|
];
|
||||||
|
|
||||||
return { headline, detail, now, later };
|
return { headline, detail, now, later };
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { useWebSocket } from "@/hooks/use-websocket";
|
|||||||
|
|
||||||
function getThemeAliasLine(sector: SectorData) {
|
function getThemeAliasLine(sector: SectorData) {
|
||||||
const aliases = (sector.theme_aliases ?? []).filter((alias) => alias && alias !== sector.sector_name).slice(0, 4);
|
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(" / ")}`;
|
return `包含:${aliases.join(" / ")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,31 +36,31 @@ function getActionPlan(sector: SectorData) {
|
|||||||
if (pct <= 0) {
|
if (pct <= 0) {
|
||||||
return {
|
return {
|
||||||
label: "抗跌观察",
|
label: "抗跌观察",
|
||||||
description: "当前只看谁先止跌转强,不把仍为负的方向当进攻主线。",
|
description: "等待止跌转强。",
|
||||||
risk: "如果板块继续走弱或前排补跌,直接降级处理,不主动追击。",
|
risk: "继续走弱或前排补跌。",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stage === "early") {
|
if (stage === "early") {
|
||||||
return {
|
return {
|
||||||
label: "优先盯",
|
label: "优先盯",
|
||||||
description: "优先看龙头和首次分歧后的回流。",
|
description: "龙头和分歧回流。",
|
||||||
risk: "如果没有扩散、没有回流、没有承接,就不能当主线。",
|
risk: "无扩散、无回流、无承接。",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stage === "mid" && pct > 0 && mainForce > 20) {
|
if (stage === "mid" && pct > 0 && mainForce > 20) {
|
||||||
return {
|
return {
|
||||||
label: "跟回流",
|
label: "跟回流",
|
||||||
description: "更适合等确认,不适合无脑追高。",
|
description: "等待确认。",
|
||||||
risk: "如果板块内部开始掉队,要立刻降级成观察。",
|
risk: "内部掉队。",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: "只观察",
|
label: "只观察",
|
||||||
description: "保留跟踪,不作为今天的主仓位方向。",
|
description: "保留跟踪。",
|
||||||
risk: "一旦退潮加速,直接移出主视野。",
|
risk: "退潮加速。",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,8 +79,8 @@ function getHeadline(sectors: SectorData[]) {
|
|||||||
return {
|
return {
|
||||||
title: "暂无主线数据",
|
title: "暂无主线数据",
|
||||||
detail: "等待实时主题榜或扫描结果更新后再判断。",
|
detail: "等待实时主题榜或扫描结果更新后再判断。",
|
||||||
canDo: ["当前不适合强行定义主线。"],
|
canDo: ["等待主题榜更新。"],
|
||||||
avoid: ["不要在没有主线主题支撑时做情绪化追涨。"],
|
avoid: ["不做情绪追涨。"],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,51 +91,51 @@ function getHeadline(sectors: SectorData[]) {
|
|||||||
const hasPositiveLeader = primaryPct > 0;
|
const hasPositiveLeader = primaryPct > 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: hasPositiveLeader ? `当前优先盯 ${primary.sector_name}` : `当前没有明确进攻主线,${primary.sector_name} 只是相对抗跌`,
|
title: hasPositiveLeader ? `优先盯 ${primary.sector_name}` : `${primary.sector_name} 相对抗跌`,
|
||||||
detail: hasPositiveLeader
|
detail: hasPositiveLeader
|
||||||
? `这里展示的不是外部行业榜,而是系统归一后的主线主题。当前次主线是 ${secondaryName},观察线优先看 ${watchName}。`
|
? `次主线 ${secondaryName},观察线 ${watchName}。`
|
||||||
: `当前排在前面的主题也仍然下跌,说明系统拿到的是“相对最强”而不是“真正走强”的方向。此时更适合把 ${primary.sector_name}、${secondaryName} 当成抗跌观察线,而不是进攻主线。`,
|
: `${primary.sector_name}、${secondaryName} 暂按抗跌观察。`,
|
||||||
canDo: hasPositiveLeader
|
canDo: hasPositiveLeader
|
||||||
? [
|
? [
|
||||||
`${primary.sector_name} 是当前最值得盯的方向,${primaryPlan.description}`,
|
`${primary.sector_name}:${primaryPlan.description}`,
|
||||||
secondary ? `${secondary.sector_name} 适合放在次主线观察,等是否转强。` : "如果没有次主线,就不要硬做轮动。",
|
secondary ? `${secondary.sector_name}:次主线观察。` : "暂无次主线。",
|
||||||
"盯主题时优先看前排代表股、内部涨跌广度和是否出现回流。",
|
"看前排、广度、回流。",
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
`优先看 ${primary.sector_name} 是否最先止跌转强,而不是因为它排第一就直接当主线做。`,
|
`看 ${primary.sector_name} 是否止跌转强。`,
|
||||||
secondary ? `${secondary.sector_name} 只适合作为备选抗跌方向观察。` : "没有明显次主线时,不要强行找轮动方向。",
|
secondary ? `${secondary.sector_name}:备选抗跌。` : "暂无次主线。",
|
||||||
"当前更重要的是确认谁先翻红、谁先扩散、谁有前排承接。",
|
"确认翻红、扩散、承接。",
|
||||||
],
|
],
|
||||||
avoid: hasPositiveLeader
|
avoid: hasPositiveLeader
|
||||||
? [
|
? [
|
||||||
"不要把后排补涨方向当主线来做。",
|
"不把后排补涨当主线。",
|
||||||
"不要只看主题涨幅,不看阶段、扩散和前排强弱。",
|
"不只看涨幅。",
|
||||||
watch ? `${watch.sector_name} 更偏观察,不适合当今天的主攻方向。` : "阶段偏后的板块只保留观察。",
|
watch ? `${watch.sector_name}:观察线。` : "后期板块只观察。",
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
"不要把仍然为负的主题写成“今日主线”来理解。",
|
"不把负涨幅当主线。",
|
||||||
"不要在全市场偏弱时,因为相对抗跌就重仓进攻。",
|
"不因抗跌重仓进攻。",
|
||||||
"不要忽略整体环境,只盯着排行榜第一名。",
|
"不只盯排名第一。",
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSourceLabel(source?: string) {
|
function getSourceLabel(source?: string) {
|
||||||
if (source === "eastmoney") return "东方财富实时板块榜归一";
|
if (source === "eastmoney") return "东方财富";
|
||||||
if (source === "sina") return "新浪实时行业聚合归一";
|
if (source === "sina") return "新浪";
|
||||||
if (source === "snapshot") return "本地主题快照";
|
if (source === "snapshot") return "本地快照";
|
||||||
if (source === "mixed") return "多源归一主题";
|
if (source === "mixed") return "多源";
|
||||||
return "未知来源";
|
return "未知来源";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSourceRiskHint(source?: string, dataMode?: string) {
|
function getSourceRiskHint(source?: string, dataMode?: string) {
|
||||||
if (source === "snapshot" || dataMode === "daily_snapshot") {
|
if (source === "snapshot" || dataMode === "daily_snapshot") {
|
||||||
return "当前展示的是系统主题快照,不是实时外部行业榜;如果你拿它逐项对比东方财富原始板块名,会存在口径差异。";
|
return "快照口径";
|
||||||
}
|
}
|
||||||
if (source === "sina") {
|
if (source === "sina") {
|
||||||
return "当前主题由新浪行业聚合后再归一,和东方财富原始板块分类不完全一致。";
|
return "新浪口径";
|
||||||
}
|
}
|
||||||
return "当前优先使用实时板块数据,并统一归一成系统主题。";
|
return "实时口径";
|
||||||
}
|
}
|
||||||
|
|
||||||
function LeadingStockPill({ stock }: { stock: LeadingStock }) {
|
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="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">
|
<div className="animate-fade-in-up">
|
||||||
<h1 className="text-lg font-bold tracking-tight">主线主题</h1>
|
<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">
|
<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="font-medium text-text-primary">{getSourceLabel(source)}</span>
|
||||||
<span className="text-text-muted/50">·</span>
|
<span className="text-text-muted/50">·</span>
|
||||||
<span className="text-text-muted">{dataMode}</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>
|
</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" ? (
|
{hasRealtime && dataMode === "realtime_overlay" ? (
|
||||||
<p className="mt-2 text-[11px] text-text-muted/70">
|
<p className="mt-2 text-[11px] text-text-muted/70">
|
||||||
当前是实时覆盖模式,结构字段仍参考 {structureTradeDate || "最近交易日"} 的主题快照。
|
结构日 {structureTradeDate || "最近交易日"}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</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>
|
<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>
|
<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">
|
<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.canDo} tone="positive" />
|
||||||
<DecisionList title="今天不要怎么用" items={summary.avoid} tone="risk" />
|
<DecisionList title="边界" items={summary.avoid} tone="risk" />
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="grid grid-cols-1 xl:grid-cols-3 gap-4 animate-fade-in-up">
|
||||||
<LaneCard
|
<LaneCard
|
||||||
title={hasPositiveLeader ? "今日主线" : "相对抗跌"}
|
title={hasPositiveLeader ? "今日主线" : "相对抗跌"}
|
||||||
description={hasPositiveLeader ? "今天最值得盯、最可能承载仓位的方向。" : "当前排在前面的方向,但还不能定义成进攻主线。"}
|
description={hasPositiveLeader ? "优先方向" : "暂不进攻"}
|
||||||
sectors={mainline}
|
sectors={mainline}
|
||||||
tone="red"
|
tone="red"
|
||||||
/>
|
/>
|
||||||
<LaneCard
|
<LaneCard
|
||||||
title={hasPositiveLeader ? "次主线" : "弱轮动观察"}
|
title={hasPositiveLeader ? "次主线" : "弱轮动观察"}
|
||||||
description={hasPositiveLeader ? "可跟踪轮动,但不应抢过主线优先级。" : "只看谁有转强迹象,不把它们当主攻方向。"}
|
description={hasPositiveLeader ? "轮动跟踪" : "转强观察"}
|
||||||
sectors={secondary}
|
sectors={secondary}
|
||||||
tone="amber"
|
tone="amber"
|
||||||
/>
|
/>
|
||||||
<LaneCard
|
<LaneCard
|
||||||
title="观察线"
|
title="观察线"
|
||||||
description="只记录变化,不作为当前主动出手方向。"
|
description="变化记录"
|
||||||
sectors={watchline}
|
sectors={watchline}
|
||||||
tone="slate"
|
tone="slate"
|
||||||
/>
|
/>
|
||||||
@ -609,9 +600,6 @@ export default function SectorsPage() {
|
|||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-sm font-semibold text-text-primary">完整方向列表</h2>
|
<h2 className="text-sm font-semibold text-text-primary">完整方向列表</h2>
|
||||||
<p className="mt-1 text-xs text-text-muted">
|
|
||||||
这里只做完整查询和比对,不承担主结论。
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<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 className="flex items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-sm font-semibold text-text-primary">方向轮动</h2>
|
<h2 className="text-sm font-semibold text-text-primary">方向轮动</h2>
|
||||||
<p className="mt-1 text-xs text-text-muted">
|
|
||||||
用来确认最近几天资金是否在不同方向之间切换,不是首屏主信息。
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowRotation((value) => !value)}
|
onClick={() => setShowRotation((value) => !value)}
|
||||||
|
|||||||
@ -630,9 +630,9 @@ function buildHeroSummary(thesis: StockThesisResponse | null, quote: QuoteData |
|
|||||||
if (thesis?.has_recommendation && thesis.recommendation) {
|
if (thesis?.has_recommendation && thesis.recommendation) {
|
||||||
const rec = thesis.recommendation;
|
const rec = thesis.recommendation;
|
||||||
const quoteText = quote ? `当前价格 ${quote.price.toFixed(2)},涨跌幅 ${quote.pct_chg > 0 ? "+" : ""}${quote.pct_chg.toFixed(2)}%。` : "";
|
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) {
|
function formatDateTime(value?: string) {
|
||||||
|
|||||||
@ -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="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">
|
<div className="animate-fade-in-up">
|
||||||
<h1 className="text-xl font-bold tracking-tight">系统校准</h1>
|
<h1 className="text-xl font-bold tracking-tight">系统校准</h1>
|
||||||
<p className="mt-1 text-sm text-text-muted">
|
|
||||||
这里不是告诉你今天买什么,而是告诉系统最近什么方法有效、什么方法无效、下一轮该怎么改。
|
|
||||||
</p>
|
|
||||||
</div>
|
</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 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>
|
||||||
<div className="text-sm font-semibold text-text-primary">配置化自我迭代</div>
|
<div className="text-sm font-semibold text-text-primary">配置化自我迭代</div>
|
||||||
<div className="mt-1 text-xs text-text-muted">小幅参数调整可自动生效;大改动保留为 Agent 提示词和人工确认。</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{actionMessage ? <span className="text-xs text-amber-400">{actionMessage}</span> : null}
|
{actionMessage ? <span className="text-xs text-amber-400">{actionMessage}</span> : null}
|
||||||
@ -131,11 +127,11 @@ export default function StrategyPage() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="text-[10px] uppercase tracking-[0.22em] text-amber-400 font-semibold">系统当前判断</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>
|
<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">
|
<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.useFor} tone="positive" />
|
||||||
<DecisionList title="这页不是做什么的" items={diagnosis.notFor} tone="risk" />
|
<DecisionList title="边界" items={diagnosis.notFor} tone="risk" />
|
||||||
</div>
|
</div>
|
||||||
</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_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" />
|
<MetricCard label="平均回撤" value={`${(performance?.avg_max_drawdown ?? 0).toFixed(2)}%`} tone="down" />
|
||||||
<MetricFact label="已跟踪" value={`${performance?.tracked ?? 0} 只`} />
|
<MetricFact label="已跟踪" value={`${performance?.tracked ?? 0} 只`} />
|
||||||
<MetricFact label="本页角色" value="方法迭代,不做盘中执行" />
|
<MetricFact label="页面角色" value="方法迭代" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -171,24 +167,7 @@ export default function StrategyPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
<ReviewWindowsPanel windows={iteration.review_windows ?? []} />
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ConfigCenterPanel
|
<ConfigCenterPanel
|
||||||
@ -196,12 +175,9 @@ export default function StrategyPage() {
|
|||||||
onRollback={rollbackConfig}
|
onRollback={rollbackConfig}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ReviewWindowsPanel windows={iteration.review_windows ?? []} />
|
|
||||||
|
|
||||||
<div className="glass-card-static p-5 animate-fade-in-up">
|
<div className="glass-card-static p-5 animate-fade-in-up">
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<SectionTitle title="下一轮系统指令" />
|
<SectionTitle title="下一轮系统指令" />
|
||||||
<span className="text-xs text-text-muted">这些结论应该影响下一轮推荐方法,而不是只停留在页面上。</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 space-y-3">
|
<div className="mt-4 space-y-3">
|
||||||
{(iteration.adjustment_suggestions.length
|
{(iteration.adjustment_suggestions.length
|
||||||
@ -216,12 +192,12 @@ export default function StrategyPage() {
|
|||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
<StatsPanel
|
<StatsPanel
|
||||||
title="哪些策略更有效"
|
title="哪些策略更有效"
|
||||||
description="看不同策略分组最近表现,判断哪类打法该保留、该削弱。"
|
description="按策略分组"
|
||||||
stats={iteration.strategy_stats}
|
stats={iteration.strategy_stats}
|
||||||
/>
|
/>
|
||||||
<StatsPanel
|
<StatsPanel
|
||||||
title="哪些信号更有效"
|
title="哪些信号更有效"
|
||||||
description="看突破、回踩、启动等信号的兑现质量,而不是只看出现频次。"
|
description="按信号分组"
|
||||||
stats={iteration.signal_stats}
|
stats={iteration.signal_stats}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -234,9 +210,6 @@ export default function StrategyPage() {
|
|||||||
|
|
||||||
<div className="glass-card-static p-5 animate-fade-in-up">
|
<div className="glass-card-static p-5 animate-fade-in-up">
|
||||||
<SectionTitle title="最近的失效模式" />
|
<SectionTitle title="最近的失效模式" />
|
||||||
<div className="mt-2 text-xs text-text-muted">
|
|
||||||
这些不是给你盘中参考的,而是告诉系统哪些错误不该继续重复。
|
|
||||||
</div>
|
|
||||||
<div className="mt-4 space-y-2">
|
<div className="mt-4 space-y-2">
|
||||||
{iteration.failure_patterns.length ? (
|
{iteration.failure_patterns.length ? (
|
||||||
iteration.failure_patterns.map((pattern, index) => (
|
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="glass-card-static p-10 text-center">
|
||||||
<div className="text-text-muted text-sm">暂无系统校准数据</div>
|
<div className="text-text-muted text-sm">暂无系统校准数据</div>
|
||||||
<div className="text-text-muted/50 text-xs mt-1">等待推荐进入跟踪和闭环后,再生成方法迭代结论</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -271,27 +243,27 @@ function buildCalibrationDiagnosis(
|
|||||||
const tracked = performance?.tracked ?? 0;
|
const tracked = performance?.tracked ?? 0;
|
||||||
const headline =
|
const headline =
|
||||||
tracked < 10
|
tracked < 10
|
||||||
? "当前更像早期样本积累阶段"
|
? "样本积累中"
|
||||||
: winRate >= 55 && avgReturn >= 0
|
: winRate >= 55 && avgReturn >= 0
|
||||||
? "当前方法仍然有效,但需要持续校准"
|
? "方法有效"
|
||||||
: "当前方法出现退化,需要收紧与调整";
|
: "方法退化";
|
||||||
|
|
||||||
const detail =
|
const detail =
|
||||||
iteration?.summary ??
|
iteration?.summary ??
|
||||||
(tracked < 10
|
(tracked < 10
|
||||||
? "闭环样本还不够多,这一页更适合看方向性的偏差,而不是做强结论。"
|
? "闭环样本不足,只看方向偏差。"
|
||||||
: "这个页面的目标是判断推荐方法最近有没有偏掉,以及下一轮应该如何调整。");
|
: "检查推荐方法偏差和下一轮调整。");
|
||||||
|
|
||||||
const useFor = [
|
const useFor = [
|
||||||
"验证系统最近推荐出来的东西,长期看是否真的有效。",
|
"验证推荐兑现率。",
|
||||||
"识别哪类策略、哪类信号最近更有效或更容易失效。",
|
"识别有效策略和信号。",
|
||||||
"把复盘结论转成下一轮推荐系统的收紧、加强或降权指令。",
|
"生成下一轮配置调整。",
|
||||||
];
|
];
|
||||||
|
|
||||||
const notFor = [
|
const notFor = [
|
||||||
"不是盘中决策页,不负责告诉你现在立刻买哪只股票。",
|
"不做盘中买卖决策。",
|
||||||
"不是板块行情页,不负责追踪今天最热方向。",
|
"不替代板块行情。",
|
||||||
"不是个股详情页,不负责展开单只股票的全部逻辑。",
|
"不展开单股长逻辑。",
|
||||||
];
|
];
|
||||||
|
|
||||||
return { headline, detail, useFor, 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 }) {
|
function NextInstruction({ item, index }: { item: StrategyAdjustment; index: number }) {
|
||||||
const verb = ACTION_LABELS[item.action] ?? item.action;
|
const verb = ACTION_LABELS[item.action] ?? item.action;
|
||||||
const color =
|
const color =
|
||||||
|
|||||||
@ -369,9 +369,6 @@ function WatchlistOverview({
|
|||||||
<div>
|
<div>
|
||||||
<div className="text-[10px] uppercase tracking-[0.22em] text-cyan-400 font-semibold">Personal Mission Control</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>
|
<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">
|
<div className="grid grid-cols-2 md:grid-cols-5 gap-2 mt-4">
|
||||||
<OverviewMetric label="总数" value={items.length} />
|
<OverviewMetric label="总数" value={items.length} />
|
||||||
<OverviewMetric label="可操作" value={actionable} tone="emerald" />
|
<OverviewMetric label="可操作" value={actionable} tone="emerald" />
|
||||||
@ -384,7 +381,6 @@ function WatchlistOverview({
|
|||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[10px] uppercase tracking-wider text-text-muted font-semibold">统一动作</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>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onBatchAnalyze}
|
onClick={onBatchAnalyze}
|
||||||
|
|||||||
@ -38,7 +38,6 @@ export default function MarketTemp({ data, indices }: MarketTempProps) {
|
|||||||
<div className="flex items-start justify-between gap-3 mb-4">
|
<div className="flex items-start justify-between gap-3 mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-sm font-semibold text-text-muted">市场状态</h2>
|
<h2 className="text-sm font-semibold text-text-muted">市场状态</h2>
|
||||||
<p className="text-[10px] text-text-muted/60 mt-0.5">用于决定仓位、节奏和今天到底该不该出手。</p>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[10px] sm:text-xs text-text-muted font-mono tabular-nums">{data.trade_date}</span>
|
<span className="text-[10px] sm:text-xs text-text-muted font-mono tabular-nums">{data.trade_date}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -20,9 +20,9 @@ function getSectorFocus(sector: SectorData): string {
|
|||||||
return `盯住 ${leaders.slice(0, 2).map((item) => item.name).join(" / ")} 的承接和回流。`;
|
return `盯住 ${leaders.slice(0, 2).map((item) => item.name).join(" / ")} 的承接和回流。`;
|
||||||
}
|
}
|
||||||
if ((sector.realtime_up_count ?? 0) > (sector.realtime_down_count ?? 0)) {
|
if ((sector.realtime_up_count ?? 0) > (sector.realtime_down_count ?? 0)) {
|
||||||
return "板块内上涨家数占优,优先看前排继续强化。";
|
return "上涨占优,看前排强化。";
|
||||||
}
|
}
|
||||||
return "板块内分化较重,只保留观察,不追后排。";
|
return "分化较重,观察不追。";
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SectorHeatmap({ sectors }: { sectors: SectorData[] }) {
|
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 className="flex items-start justify-between gap-3 mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-sm font-semibold text-text-muted">今日主线</h2>
|
<h2 className="text-sm font-semibold text-text-muted">今日主线</h2>
|
||||||
<p className="text-[10px] text-text-muted/60 mt-0.5">
|
|
||||||
不是展示板块分数,而是告诉你今天该盯什么方向、看什么前排。
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
{hasRealtime && (
|
{hasRealtime && (
|
||||||
<span className="text-[10px] px-1.5 py-0.5 rounded bg-emerald-500/10 text-emerald-400/80">
|
<span className="text-[10px] px-1.5 py-0.5 rounded bg-emerald-500/10 text-emerald-400/80">
|
||||||
|
|||||||
@ -52,9 +52,9 @@ export default function StockCard({ rec, compact = false }: { rec: Recommendatio
|
|||||||
invalidated: "已失效",
|
invalidated: "已失效",
|
||||||
};
|
};
|
||||||
const actionPlanCopy: Record<string, string> = {
|
const actionPlanCopy: Record<string, string> = {
|
||||||
"可操作": "触发条件成立时才执行",
|
"可操作": "触发后执行",
|
||||||
"重点关注": "等待确认,不提前交易",
|
"重点关注": "等待确认",
|
||||||
"观察": "只记录,不主动出手",
|
"观察": "仅观察",
|
||||||
};
|
};
|
||||||
const evidence = [
|
const evidence = [
|
||||||
rec.prefilter_reason,
|
rec.prefilter_reason,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user