Files
sanguo_vnpy_v2/docs/factor_research/factor-new-result-mockup.html
T
claude_dev d1d48896a9
CI/CD / test (push) Successful in 2s
CI/CD / nas-deploy (push) Successful in 6s
CI/CD / nas-verify (push) Successful in 8s
docs(factor): New/Result两页终端风设计稿v1(可交互,任务#12方案B) [nas]
照 tears-compare-mockup 流程先出稿拍板再实现。三tab:
①提交页New:因子多选从Element下拉改「搜索+分组点选」面板(240因子下拉翻页难)
+三类别色chip(101青/158紫/builtin琥珀)+已选托盘chips可删/清空(同对比托盘交互)
+标的池mono textarea实时计数徽标+跨行业30只预设(与详情页一键直达同样本便于复现)
+区间禁未来+跨度显示+提交校验前置(未满足禁用+原因文案)
②结果页Result外壳:5格metric-strip(最优ICIR红/最优因子青)+IC明细终端表格
(右对齐tabular-nums,IC/ICIR/t正红负绿,|t|≥2加粗,全局最优行青标+左青条)
+tears区块虚线占位示意(新版TearsPanel已上线本次不动)
③实现说明:范围/交互变化/拆分预估(New半天+Result1-2小时,纯前端零后端)
种子合成数据mulberry32;charset声明防file://乱码;本机Chrome实测两tab+交互OK
2026-08-30 09:41:49 +08:00

461 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>因子分析两页终端风设计稿</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap">
<style>
:root{
--bg:#05080d;--bg-card:#0a0f17;--bg-hover:#0e1622;--bg-overlay:#0d1320;--panel:#070b12;
--text:#d4e4f0;--text-2:#7a8a9a;--text-3:#4a5868;
--brand:#00e5ff;--cyan-dim:#00b8cc;--cyan-soft:rgba(0,229,255,0.14);
--purple-dim:#b48cff;--amber:#ffb000;--amber-soft:rgba(255,176,0,0.14);
--up:#ff4d5e;--down:#2ee68a;--danger:#ff4d5e;
--lamp-ok:#2ee68a;--lamp-warn:#ffb000;--lamp-idle:#4a5868;
--border:#16202e;--border-2:#0f1822;
--grid-line:rgba(0,229,255,0.035);--grid-size:32px;
--r-sm:2px;--r-md:3px;--r-lg:4px;
--mono:'JetBrains Mono',ui-monospace,'SFMono-Regular',Consolas,'Liberation Mono',Menlo,monospace;
--sans:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei',Roboto,sans-serif;
--ease:cubic-bezier(0.4,0,0.2,1);
}
*{box-sizing:border-box}
html,body{margin:0;padding:0}
@media (prefers-reduced-motion:no-preference){html{scroll-behavior:smooth}}
body{background:var(--bg);color:var(--text);font-family:var(--sans);font-size:13px;line-height:1.55;-webkit-font-smoothing:antialiased}
body::before{content:'';position:fixed;inset:0;pointer-events:none;z-index:0;
background-image:linear-gradient(var(--grid-line) 1px,transparent 1px),linear-gradient(90deg,var(--grid-line) 1px,transparent 1px);
background-size:var(--grid-size) var(--grid-size)}
.wrap{position:relative;z-index:1;max-width:1240px;margin:0 auto;padding:18px 22px 90px}
.mono{font-family:var(--mono)}
button{font-family:inherit}
button:focus-visible{outline:1px solid var(--brand);outline-offset:2px}
input,textarea{font-family:inherit}
input:focus-visible,textarea:focus-visible{outline:1px solid rgba(0,229,255,.5);outline-offset:0}
/* ===== 顶栏 ===== */
.topbar{display:flex;align-items:center;justify-content:space-between;gap:14px;border:1px solid var(--border);background:var(--bg-card);border-radius:var(--r-lg);padding:10px 14px;margin-bottom:14px;flex-wrap:wrap}
.topbar .t{font-family:var(--mono);font-size:12.5px;letter-spacing:.05em;font-weight:700}
.topbar .t .dim{color:var(--text-3);font-weight:400}
.tabs{display:flex;gap:6px;flex-wrap:wrap}
.tabs button{font-family:var(--mono);font-size:11.5px;padding:5px 14px;border-radius:var(--r-sm);border:1px solid var(--border);background:transparent;color:var(--text-2);cursor:pointer;transition:all .15s var(--ease)}
.tabs button:hover{color:var(--text);border-color:var(--text-3)}
.tabs button.on{color:var(--brand);background:var(--cyan-soft);border-color:rgba(0,229,255,.35)}
.tab{display:none}
.tab.on{display:block}
/* ===== 设计标注 ===== */
.dcap{display:flex;align-items:center;gap:10px;margin:16px 2px 10px;flex-wrap:wrap}
.dcap .no{font-family:var(--mono);font-size:11px;color:var(--amber);border:1px solid rgba(255,176,0,.4);background:var(--amber-soft);border-radius:var(--r-sm);padding:2px 8px;letter-spacing:.08em;white-space:nowrap}
.dcap .txt{color:var(--text-2);font-size:12px}
.dcap .txt b{color:var(--text)}
.mock{border:1px solid var(--border);background:var(--bg);border-radius:var(--r-lg);padding:20px 22px 26px;box-shadow:0 8px 32px rgba(0,0,0,.6)}
.mock.flash{box-shadow:0 0 0 1px rgba(0,229,255,.55),0 0 24px rgba(0,229,255,.15)}
.new-tag{font-family:var(--mono);font-size:9px;color:var(--amber);border:1px dashed rgba(255,176,0,.45);border-radius:var(--r-sm);padding:1px 6px;letter-spacing:.06em;margin-left:8px;vertical-align:1px}
/* ===== 页面骨架 ===== */
.backlink{color:var(--brand);font-size:12px;margin-bottom:12px;display:inline-block;font-family:var(--mono);letter-spacing:.03em}
.page-head{display:flex;justify-content:space-between;align-items:flex-end;gap:14px;margin-bottom:16px;flex-wrap:wrap}
.page-title{font-size:18px;font-weight:700;letter-spacing:.04em;margin:0}
.page-sub{color:var(--text-2);font-size:12px;margin:2px 0 0;font-family:var(--mono);letter-spacing:.02em}
/* ===== 面板 ===== */
.pane{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-lg);margin-bottom:12px;min-width:0}
.pane>.ph{display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid var(--border-2);flex-wrap:wrap}
.pane>.ph .pt{font-size:12.5px;font-weight:700;letter-spacing:.06em}
.pane>.ph .tag{font-family:var(--mono);font-size:9.5px;color:var(--text-3);letter-spacing:.1em}
.pane>.pb{padding:14px}
.formrow{display:flex;align-items:flex-start;gap:14px}
.flab{width:88px;flex-shrink:0;padding-top:7px;text-align:right;color:var(--text-2);font-size:12.5px}
.fbody{flex:1;min-width:0}
/* ===== 因子选择器 ===== */
.fx-search{display:flex;gap:8px;margin-bottom:10px;flex-wrap:wrap;align-items:center}
.fx-search input{flex:1;min-width:200px;background:var(--panel);border:1px solid var(--border);border-radius:var(--r-sm);color:var(--text);font-family:var(--mono);font-size:12px;padding:6px 10px}
.fx-search input::placeholder{color:var(--text-3)}
.fx-count{font-family:var(--mono);font-size:10.5px;color:var(--text-3);white-space:nowrap}
.fx-groups{display:grid;grid-template-columns:1fr 1fr;gap:12px}
@media(max-width:980px){.fx-groups{grid-template-columns:1fr}}
.fx-group>.gh{display:flex;align-items:center;gap:8px;margin-bottom:8px}
.fx-group>.gh .gt{font-family:var(--mono);font-size:11px;letter-spacing:.08em;font-weight:600}
.fx-group>.gh .gc{font-family:var(--mono);font-size:10px;color:var(--text-3)}
.cat-chip{display:inline-block;padding:1px 8px;border-radius:var(--r-sm);font-family:var(--mono);font-size:10.5px;font-weight:600;line-height:17px;border:1px solid transparent}
.cat-alpha101{color:var(--brand);background:rgba(0,229,255,.1);border-color:rgba(0,229,255,.3)}
.cat-alpha158{color:var(--purple-dim);background:rgba(180,140,255,.1);border-color:rgba(180,140,255,.3)}
.cat-builtin{color:var(--amber);background:rgba(255,176,0,.1);border-color:rgba(255,176,0,.3)}
.fx-grid{display:flex;flex-wrap:wrap;gap:5px}
.fxp{font-family:var(--mono);font-size:11px;padding:2px 9px;border-radius:var(--r-sm);border:1px solid var(--border);background:var(--panel);color:var(--text-2);cursor:pointer;transition:all .12s var(--ease);user-select:none}
.fxp:hover{color:var(--text);border-color:var(--text-3)}
.fxp.sel-alpha101{color:var(--brand);border-color:rgba(0,229,255,.45);background:var(--cyan-soft)}
.fxp.sel-alpha158{color:var(--purple-dim);border-color:rgba(180,140,255,.45);background:rgba(180,140,255,.12)}
.fxp.sel-builtin{color:var(--amber);border-color:rgba(255,176,0,.45);background:var(--amber-soft)}
.fxp.hidden{display:none}
.preset{font-family:var(--mono);font-size:10.5px;padding:3px 10px;border-radius:var(--r-sm);border:1px dashed var(--border);background:transparent;color:var(--text-2);cursor:pointer}
.preset:hover{color:var(--brand);border-color:rgba(0,229,255,.4)}
/* ===== 已选托盘 ===== */
.picked{display:flex;align-items:center;gap:8px;margin-top:12px;border:1px dashed rgba(0,229,255,.3);background:rgba(0,229,255,.04);border-radius:var(--r-md);padding:8px 12px;flex-wrap:wrap;min-height:38px}
.picked .pk{font-family:var(--mono);font-size:10.5px;color:var(--brand);letter-spacing:.1em;white-space:nowrap}
.picked .chips{display:flex;gap:5px;flex-wrap:wrap;flex:1}
.pchip{display:inline-flex;align-items:center;gap:6px;font-family:var(--mono);font-size:11px;border:1px solid var(--border);border-radius:var(--r-sm);padding:2px 8px;background:var(--panel)}
.pchip .x{cursor:pointer;color:var(--text-3)}
.pchip .x:hover{color:var(--danger)}
.picked .clear{font-family:var(--mono);font-size:10.5px;color:var(--text-3);cursor:pointer;white-space:nowrap}
.picked .clear:hover{color:var(--danger)}
/* ===== 标的池 / 日期 ===== */
.sym-area{width:100%;min-height:128px;background:var(--panel);border:1px solid var(--border);border-radius:var(--r-sm);color:var(--text);font-family:var(--mono);font-size:12px;line-height:1.8;padding:8px 10px;resize:vertical}
.sym-area::placeholder{color:var(--text-3)}
.sym-meta{display:flex;align-items:center;gap:10px;margin-top:8px;flex-wrap:wrap}
.sym-badge{font-family:var(--mono);font-size:10.5px;color:var(--brand);border:1px solid rgba(0,229,255,.3);background:var(--cyan-soft);border-radius:var(--r-sm);padding:2px 8px}
.hint-inline{font-family:var(--mono);font-size:10.5px;color:var(--text-3)}
.dates{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.dates input[type=date]{background:var(--panel);border:1px solid var(--border);border-radius:var(--r-sm);color:var(--text);font-family:var(--mono);font-size:12px;padding:6px 10px;color-scheme:dark}
.dates .sep{color:var(--text-3);font-family:var(--mono);font-size:11px}
.span-note{font-family:var(--mono);font-size:10.5px;color:var(--text-3)}
/* ===== 提交条 ===== */
.submit-bar{display:flex;align-items:center;gap:14px;margin-top:4px;flex-wrap:wrap}
.btn{font-size:12.5px;font-weight:600;border-radius:var(--r-md);padding:8px 18px;border:1px solid var(--border);color:var(--text-2);background:var(--bg-card);transition:all .15s var(--ease);cursor:pointer}
.btn:hover{color:var(--text);border-color:var(--text-3)}
.btn.primary{color:#032027;background:var(--brand);border-color:var(--brand);box-shadow:0 0 0 1px rgba(0,229,255,.22),0 0 18px rgba(0,229,255,.08);font-weight:700}
.btn.primary:hover{background:#33ecff}
.btn:disabled{opacity:.45;cursor:not-allowed;box-shadow:none}
.btn.primary:disabled:hover{background:var(--brand)}
.valid-note{font-family:var(--mono);font-size:11px;color:var(--amber)}
/* ===== 结果页指标条 ===== */
.metric-strip{display:grid;grid-template-columns:repeat(5,1fr);gap:1px;background:var(--border);border:1px solid var(--border);border-radius:var(--r-lg);overflow:hidden;margin-bottom:12px}
.metric{background:var(--bg-card);padding:10px 12px;min-width:0}
.metric .k{color:var(--text-3);font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;white-space:nowrap}
.metric .v{font-family:var(--mono);font-variant-numeric:tabular-nums;font-size:17px;font-weight:600;margin-top:2px;white-space:nowrap}
@media(max-width:1080px){.metric-strip{grid-template-columns:repeat(3,1fr)}}
/* ===== 表格 ===== */
.tblwrap{overflow-x:auto}
.tbl{width:100%;border-collapse:collapse;font-size:12px}
.tbl th{font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--text-3);text-align:right;padding:8px 10px;border-bottom:1px solid var(--border);background:var(--bg-hover);white-space:nowrap}
.tbl th:first-child{text-align:left}
.tbl td{padding:8px 10px;border-bottom:1px solid var(--border-2);white-space:nowrap;text-align:right;font-variant-numeric:tabular-nums}
.tbl td:first-child{text-align:left}
.tbl tr:hover td{background:var(--bg-hover)}
.dim{color:var(--text-3)}
.cy{color:var(--brand)}
.pos{color:var(--up)}
.neg{color:var(--down)}
.fname-s{color:var(--brand);font-family:var(--mono)}
.sig{font-weight:700}
.best-row td{background:rgba(0,229,255,.05)}
.best-row td:first-child{box-shadow:inset 2px 0 0 var(--brand)}
/* ===== tears 区块示意 ===== */
.tears-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}
.tears-tabs .tt{font-family:var(--mono);font-size:11px;padding:3px 12px;border-radius:var(--r-sm);border:1px solid var(--border);color:var(--text-2);cursor:pointer}
.tears-tabs .tt.on{color:var(--brand);background:var(--cyan-soft);border-color:rgba(0,229,255,.35)}
.tears-ph{border:1px dashed var(--border);border-radius:var(--r-md);background:var(--panel);padding:26px 18px;text-align:center}
.tears-ph .big{font-family:var(--mono);font-size:13px;color:var(--brand);letter-spacing:.04em}
.tears-ph .sm{color:var(--text-3);font-size:11.5px;margin-top:6px}
/* ===== 说明页 ===== */
.nh{font-size:13.5px;font-weight:700;letter-spacing:.03em;margin:20px 0 8px;color:var(--text)}
.nh:first-child{margin-top:0}
.ask{margin:6px 0 0;padding-left:20px;color:var(--text-2)}
.ask li{margin:6px 0}
.ask b{color:var(--text)}
.foot{color:var(--text-3);font-family:var(--mono);font-size:10.5px;text-align:center;margin-top:26px}
</style>
</head>
<body>
<div class="wrap">
<div class="topbar">
<div class="t">因子分析 · 两页终端风重构 <span class="dim">/ factor-new-result-mockup · 2026-08-30</span></div>
<nav class="tabs">
<button class="on" data-tab="new">① 提交页 New</button>
<button data-tab="result">② 结果页 Result 外壳</button>
<button data-tab="notes">③ 实现说明</button>
</nav>
</div>
<!-- ================= ① 提交页 ================= -->
<section id="tab-new" class="tab on">
<div class="dcap"><span class="no">N1</span><span class="txt"><b>提交页</b>:自选因子 × 自选标的池 × 自选区间的自由实验台。终端风替换 Element 表单——因子改为「搜索 + 分组点选」面板(240 个因子下拉翻页不便)。</span></div>
<div class="mock flash">
<span class="backlink">← 投研 / 因子分析</span>
<div class="page-head">
<div>
<h2 class="page-title">因子分析 <span class="new-tag">终端风重构</span></h2>
<p class="page-sub">IC 横截面 · 分层 tears 报告 · 自由实验台(排行榜答不了的换池 / 分年 / 多因子并排归这里)</p>
</div>
</div>
<!-- 因子选择 -->
<div class="pane">
<div class="ph"><span class="pt">因子选择</span><span class="tag">FACTOR PICKER · 支持多选</span></div>
<div class="pb">
<div class="fx-search">
<input id="fxq" type="text" placeholder="搜索因子名 / 类别,如 alpha16、KMID、alpha158…">
<span class="fx-count" id="fxcount"></span>
<button class="preset" id="fxtop">预选:榜单 top 因子</button>
</div>
<div class="fx-groups" id="fxgroups"><!-- JS 渲染 --></div>
<div class="picked" id="picked">
<span class="pk">已选 <span id="pkn">0</span></span>
<div class="chips" id="pkchips"><span class="hint-inline">点击上方因子加入,≥1 个可提交</span></div>
<span class="clear" id="pkclear">清空</span>
</div>
</div>
</div>
<!-- 标的池 + 区间 -->
<div class="pane">
<div class="ph"><span class="pt">标的池与区间</span><span class="tag">UNIVERSE &amp; SPAN</span></div>
<div class="pb">
<div class="formrow">
<div class="flab">标的池</div>
<div class="fbody">
<textarea class="sym-area" id="symarea" spellcheck="false" placeholder="每行一个标的代码(≥2,IC 横截面需多标的);支持空格 / 逗号 / 换行混合分隔">600519
000858
600036
601318
000001
600000
300750
002594</textarea>
<div class="sym-meta">
<span class="sym-badge" id="symbadge">8 只</span>
<span class="hint-inline">IC 横截面统计需要 ≥2 只;截面排名类因子(cs_rank 族)建议 300+ 只才不失真</span>
<button class="preset" id="sympool">载入:跨行业 30 只样本</button>
</div>
</div>
</div>
<div class="formrow" style="margin-top:14px">
<div class="flab">分析区间</div>
<div class="fbody">
<div class="dates">
<input type="date" id="dstart" value="2024-01-02">
<span class="sep"></span>
<input type="date" id="dend" value="2026-06-30">
<span class="span-note" id="spannote"></span>
</div>
<p class="hint-inline" style="margin:8px 0 0">区间前半作因子计算预热,后半(test 段)进入 IC 与分层统计</p>
</div>
</div>
</div>
</div>
<!-- 提交 -->
<div class="submit-bar">
<button class="btn primary" id="submitbtn">提交分析 →</button>
<span class="valid-note" id="validnote"></span>
<span class="hint-inline">提交后进入进度页,完成自动跳转结果页</span>
</div>
</div>
</section>
<!-- ================= ② 结果页 ================= -->
<section id="tab-result" class="tab">
<div class="dcap"><span class="no">R1</span><span class="txt"><b>结果页外壳</b>:指标条 + IC 统计明细终端风替换;分层 tears 区块(TearsPanel)已是新版终端风,<b>本次不动</b>,仅示意位置。</span></div>
<div class="mock">
<span class="backlink">← 投研 / 因子分析</span>
<div class="page-head">
<div>
<h2 class="page-title">因子分析结果 <span class="dim mono" style="font-size:13px">factor_e3a91c07</span></h2>
<p class="page-sub">IC 横截面统计 · 分层 tears 报告 · 3 因子 × 8 标的 × 2024-01-02 → 2026-06-30</p>
</div>
</div>
<div class="metric-strip">
<div class="metric"><div class="k">因子数</div><div class="v">3</div></div>
<div class="metric"><div class="k">最优 ICIR</div><div class="v pos">0.412</div></div>
<div class="metric"><div class="k">最优因子</div><div class="v cy" style="font-size:15px">alpha54</div></div>
<div class="metric"><div class="k">平均 |IC|</div><div class="v">0.0431</div></div>
<div class="metric"><div class="k">显著数 |t|≥2</div><div class="v">4 / 9</div></div>
</div>
<div class="pane">
<div class="ph"><span class="pt">IC 统计明细</span><span class="tag">按因子分组 · 最优 ICIR 行青标 · IC/ICIR/t 正红负绿</span></div>
<div class="pb tblwrap">
<table class="tbl">
<thead><tr><th>因子</th><th>周期</th><th>IC 均值</th><th>IC 标准差</th><th>ICIR</th><th>t 统计</th><th>样本数</th></tr></thead>
<tbody id="icrows"><!-- JS 渲染 --></tbody>
</table>
</div>
</div>
<div class="pane">
<div class="ph"><span class="pt">分层 tears 报告</span><span class="tag">TEARS · 已上线新版,本次重构不动</span></div>
<div class="pb">
<div class="tears-tabs">
<span class="tt on">alpha54</span><span class="tt">alpha16</span><span class="tt">KMID</span>
</div>
<div class="tears-ph">
<div class="big">TearsPanel(新版终端风 · 已上线)</div>
<div class="sm">指标条 + 月度 IC 柱 / 累计线 + 热力图 + 分组净值 + 多空净值 + IC 衰减,1 / 5 / 10D 联动 —— 此区域保持现状,不在本次两页重构范围内</div>
</div>
</div>
</div>
</div>
</section>
<!-- ================= ③ 实现说明 ================= -->
<section id="tab-notes" class="tab">
<div class="mock">
<h3 class="nh">范围</h3>
<ul class="ask">
<li><b>New.vue(提交页)</b> + <b>Result.vue 外壳(指标卡 + IC 明细表)</b> 两处终端风重构;<b>TearsPanel 不动</b>(已是新版终端风)。</li>
<li>要素零变化:因子多选按类分组 / 标的池 textarea 混合分隔 / 起止日期禁选未来 / 提交校验(≥1 因子 + ≥2 标的)/ query.factor 预填。</li>
<li>纯前端改造,零后端改动;沿用 tokens.css,与排行榜 / 详情 / 对比 / tears 同一套视觉语言。</li>
</ul>
<h3 class="nh">提交页交互变化</h3>
<ul class="ask">
<li><b>因子选择从「多选下拉」改为「搜索 + 分组点选」面板</b>:240 个因子在下拉里翻页困难;点选即加入已选托盘,托盘 chips 可单删 / 清空,与排行榜「加入对比」托盘交互一致。</li>
<li>保留类别三色:alpha101 青 / alpha158 紫 / builtin 琥珀(同排行榜类别 chip)。</li>
<li>标的池:mono 字体 textarea + 实时计数徽标 + 快捷预设(跨行业 30 只,与详情页一键直达同一样本,方便复现)。</li>
<li>提交按钮在未满足校验时禁用并给出原因(替代提交后弹 warning)。</li>
</ul>
<h3 class="nh">结果页外壳变化</h3>
<ul class="ask">
<li>五格指标条换 metric-strip 形态(同 tears 页指标条),最优 ICIR 红涨色、最优因子青色。</li>
<li>IC 明细表换终端表格:数值右对齐 tabular-numsIC / ICIR / t 正红负绿,|t|≥2 加粗,全局最优 ICIR 行青色左标。</li>
</ul>
<h3 class="nh">实现拆分(预估)</h3>
<ul class="ask">
<li><b>New.vue</b>:因子面板组件 FactorPicker.vue(搜索 + 分组网格 + 已选托盘)+ 表单区终端化,约半天。</li>
<li><b>Result.vue</b>:指标条 + 表格模板替换(数据逻辑不动),约 1–2 小时。</li>
<li>npm run build + push NAS 验收。</li>
</ul>
<p class="foot">设计稿数据为种子合成(mulberry32),仅供形态确认 · docs/factor_research/factor-new-result-mockup.html</p>
</div>
</section>
</div>
<script>
// —— 种子随机(形态稳定) ——
function mulberry32(a){return function(){a|=0;a=a+0x6D2B79F5|0;let t=Math.imul(a^a>>>15,1|a);t=t+Math.imul(t^t>>>7,61|t)^t;return((t^t>>>14)>>>0)/4294967296}}
const rnd=mulberry32(20260830);
// —— 因子种子数据(真实名抽样) ——
const CATS=[
{key:'alpha101',label:'alpha101',color:'cat-alpha101',names:['alpha1','alpha2','alpha3','alpha6','alpha9','alpha12','alpha16','alpha18','alpha32','alpha42','alpha44','alpha54','alpha65','alpha83','alpha101']},
{key:'alpha158',label:'alpha158',color:'cat-alpha158',names:['KMID','KLEN','KUP','KLOW','KMID2','ROC','ROC20','MA','MA20','STD','STD20','BETA','RSQR','RESI','RESI5','MAX','MIN','QTLU','QTLD','RANK','CORR','CNT','VMA','VSTD','WVMA']},
{key:'builtin',label:'builtin',color:'cat-builtin',names:['bb','cc','dontchg','environment','newhigh','sigma','tq','tq20','vol','year_high']}
];
let picked=new Set(['alpha16']);
const DEFAULT_TOP=['alpha16','alpha54','RESI5','alpha42','alpha32'];
// —— 渲染因子分组 ——
const groupsEl=document.getElementById('fxgroups');
CATS.forEach(cat=>{
const g=document.createElement('div');g.className='fx-group';
g.innerHTML=`<div class="gh"><span class="cat-chip ${cat.color}">${cat.label}</span><span class="gc">${cat.names.length} 个</span></div>`;
const grid=document.createElement('div');grid.className='fx-grid';
cat.names.forEach(n=>{
const b=document.createElement('span');b.className='fxp';b.textContent=n;b.dataset.cat=cat.key;b.dataset.name=n;
if(picked.has(n))b.classList.add('sel-'+cat.key);
b.onclick=()=>{picked.has(n)?picked.delete(n):picked.add(n);renderPicked();refreshCounts();};
grid.appendChild(b);
});
g.appendChild(grid);groupsEl.appendChild(g);
});
// —— 搜索过滤 ——
const q=document.getElementById('fxq');
q.addEventListener('input',()=>{
const v=q.value.trim().toLowerCase();
document.querySelectorAll('.fxp').forEach(el=>{
el.classList.toggle('hidden',v&&!el.dataset.name.toLowerCase().includes(v)&&!el.dataset.cat.includes(v));
});
refreshCounts();
});
document.getElementById('fxtop').onclick=()=>{picked=new Set(DEFAULT_TOP);renderPicked();refreshCounts();};
// —— 已选托盘 ——
function catOf(n){for(const c of CATS)if(c.names.includes(n))return c;return CATS[2];}
function renderPicked(){
const chips=document.getElementById('pkchips');
document.getElementById('pkn').textContent=picked.size;
if(!picked.size){chips.innerHTML='<span class="hint-inline">点击上方因子加入,≥1 个可提交</span>';}
else{
chips.innerHTML='';
[...picked].forEach(n=>{
const c=catOf(n);
const s=document.createElement('span');s.className='pchip';
s.innerHTML=`<span class="cat-chip ${c.color}">${c.label}</span>${n}<span class="x" title="移除">✕</span>`;
s.querySelector('.x').onclick=()=>{picked.delete(n);renderPicked();refreshCounts();};
chips.appendChild(s);
});
}
validate();
}
document.getElementById('pkclear').onclick=()=>{picked.clear();renderPicked();refreshCounts();};
// —— 计数 / 校验 ——
function visibleCount(){return [...document.querySelectorAll('.fxp')].filter(e=>!e.classList.contains('hidden')).length;}
function refreshCounts(){
document.getElementById('fxcount').textContent=`显示 ${visibleCount()} / 51`;
document.querySelectorAll('.fxp').forEach(el=>{
const c=el.dataset.cat,sel=picked.has(el.dataset.name);
el.classList.toggle('sel-alpha101',sel&&c==='alpha101');
el.classList.toggle('sel-alpha158',sel&&c==='alpha158');
el.classList.toggle('sel-builtin',sel&&c==='builtin');
});
}
const symArea=document.getElementById('symarea');
function symCount(){return symArea.value.split(/[\s,]+/).map(s=>s.trim()).filter(Boolean).length;}
function validate(){
const sc=symCount();
document.getElementById('symbadge').textContent=`${sc}`;
const btn=document.getElementById('submitbtn'),note=document.getElementById('validnote');
if(!picked.size){btn.disabled=true;note.textContent='待办:至少选 1 个因子';}
else if(sc<2){btn.disabled=true;note.textContent='待办:标的池 ≥2 只';}
else{btn.disabled=false;note.textContent='';}
}
symArea.addEventListener('input',validate);
document.getElementById('sympool').onclick=()=>{
symArea.value=['600519','000858','600036','601318','000001','600000','300750','002594','600276','000538','600887','000651','000333','600690','000725','002415','600030','601166','600028','601857','600585','000625','601633','002352','600104','601398','600941','300059','002230','601888'].join('\n');
validate();
};
// 区间跨度
function span(){try{const a=new Date(dstart.value),b=new Date(dend.value);const d=Math.round((b-a)/864e5);document.getElementById('spannote').textContent=isFinite(d)?`跨度 ${d} 天 ≈ ${(d/365).toFixed(1)}`:'';}catch(e){}}
const dstart=document.getElementById('dstart'),dend=document.getElementById('dend');
dstart.oninput=span;dend.oninput=span;
// 提交(示意)
document.getElementById('submitbtn').onclick=()=>{alert('示意:提交 → 跳转进度页(/factor/progress/{task_id}),完成自动进结果页。真实实现走现有 submitFactor API。');};
// —— 结果页 IC 表(种子合成) ——
const factors=['alpha54','alpha16','KMID'];
const rows=[];
factors.forEach(f=>{
[1,5,10].forEach(p=>{
const m=(rnd()-0.42)*0.12, sd=0.06+rnd()*0.05;
const n=280+Math.floor(rnd()*24), icir=m/sd, t=icir*Math.sqrt(n);
rows.push({f,p,m,sd,icir,t,n});
});
});
const best=Math.max(...rows.map(r=>r.icir));
document.getElementById('icrows').innerHTML=rows.map(r=>{
const cls=v=>v>=0?'pos':'neg';
const sig=Math.abs(r.t)>=2?' sig':'';
const bestRow=r.icir===best?' class="best-row"':'';
return `<tr${bestRow}><td><span class="fname-s">${r.f}</span>${r.icir===best?' <span class="cy dim" style="font-size:10px">◈ 最优</span>':''}</td><td class="dim mono">${r.p}D</td><td class="${cls(r.m)}">${r.m.toFixed(4)}</td><td>${r.sd.toFixed(4)}</td><td class="${cls(r.icir)}${sig}">${r.icir.toFixed(4)}</td><td class="${cls(r.t)}${sig}">${r.t.toFixed(2)}</td><td class="dim">${r.n}</td></tr>`;
}).join('');
// —— tab 切换 ——
document.querySelectorAll('.tabs button').forEach(b=>{
b.onclick=()=>{
document.querySelectorAll('.tabs button').forEach(x=>x.classList.remove('on'));
document.querySelectorAll('.tab').forEach(x=>x.classList.remove('on'));
b.classList.add('on');document.getElementById('tab-'+b.dataset.tab).classList.add('on');
};
});
document.querySelectorAll('.tears-tabs .tt').forEach(b=>{
b.onclick=()=>{document.querySelectorAll('.tears-tabs .tt').forEach(x=>x.classList.remove('on'));b.classList.add('on');};
});
// init
renderPicked();refreshCounts();validate();span();
</script>
</body>
</html>