544 lines
36 KiB
HTML
544 lines
36 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>因子 tears 与对比设计稿</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}
|
||
|
||
/* ===== 顶栏 ===== */
|
||
.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:14px;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}
|
||
.fcard{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-lg);padding:14px 16px;margin-bottom:12px}
|
||
.fcard .hd{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
|
||
.fname{font-family:var(--mono);font-size:17px;font-weight:700;color:var(--brand);letter-spacing:.03em}
|
||
.expr{font-family:var(--mono);font-size:11.5px;line-height:1.7;background:var(--panel);border:1px solid var(--border-2);border-left:2px solid #00b8cc;border-radius:var(--r-sm);padding:10px 14px;color:var(--text-2);overflow-x:auto;white-space:pre;margin-top:8px}
|
||
.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)}
|
||
.st{display:inline-flex;align-items:center;gap:5px;font-family:var(--mono);font-size:10.5px;letter-spacing:.08em}
|
||
.lamp{width:7px;height:7px;border-radius:50%;display:inline-block;flex-shrink:0}
|
||
.st-ok .lamp{box-shadow:0 0 6px rgba(46,230,138,.7)}
|
||
.st-watch .lamp{box-shadow:0 0 6px rgba(255,176,0,.7)}
|
||
.pos{color:var(--up)}
|
||
.neg{color:var(--down)}
|
||
|
||
/* ===== 指标条 ===== */
|
||
.metric-strip{display:grid;grid-template-columns:repeat(7,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(4,1fr)}}
|
||
|
||
/* ===== 周期开关 / 图表卡 ===== */
|
||
.period-switch{display:flex;border:1px solid var(--border);border-radius:var(--r-sm);overflow:hidden}
|
||
.period-switch button{font-family:var(--mono);font-size:11px;padding:4px 12px;color:var(--text-2);background:transparent;border:none;cursor:pointer}
|
||
.period-switch button.on{background:rgba(0,229,255,.12);color:var(--brand)}
|
||
.charts{display:grid;grid-template-columns:3fr 2fr;gap:12px;margin-bottom:12px}
|
||
.two-half{grid-template-columns:1fr 1fr}
|
||
@media(max-width:980px){.charts{grid-template-columns:1fr}}
|
||
.chart-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-lg);padding:12px 14px;min-width:0}
|
||
.chart-card h3{font-size:12.5px;font-weight:600;letter-spacing:.04em;margin:0}
|
||
.chart-card .cs{color:var(--text-3);font-family:var(--mono);font-size:10.5px;margin:3px 0 8px}
|
||
.cbody{width:100%}
|
||
svg{display:block}
|
||
svg text{font-family:var(--mono)}
|
||
.actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
|
||
.btn{font-size:12.5px;font-weight:600;border-radius:var(--r-md);padding:7px 16px;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:var(--text);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)}
|
||
.hint-inline{font-family:var(--mono);font-size:10.5px;color:var(--text-3)}
|
||
.hint{color:var(--text-3);font-size:11.5px;margin:10px 2px 0}
|
||
|
||
/* ===== 表格 ===== */
|
||
.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:left;padding:8px 10px;border-bottom:1px solid var(--border);background:var(--bg-hover);white-space:nowrap}
|
||
.tbl td{padding:8px 10px;border-bottom:1px solid var(--border-2);white-space:nowrap}
|
||
.tbl tr:hover td{background:var(--bg-hover)}
|
||
.dim{color:var(--text-3)}
|
||
.cy{color:var(--brand)}
|
||
.fname-s{color:var(--brand)}
|
||
.addbtn{font-family:var(--mono);font-size:10.5px;padding:2px 10px;border-radius:var(--r-sm);border:1px solid var(--border);background:transparent;color:var(--text-2);cursor:pointer;transition:all .15s var(--ease)}
|
||
.addbtn:hover{color:var(--text);border-color:var(--text-3)}
|
||
.addbtn.on{color:var(--brand);border-color:rgba(0,229,255,.4);background:var(--cyan-soft)}
|
||
|
||
/* ===== 对比托盘 ===== */
|
||
.tray{display:flex;align-items:center;gap:10px;margin-top:12px;border:1px dashed rgba(0,229,255,.35);background:rgba(0,229,255,.05);border-radius:var(--r-md);padding:10px 12px;flex-wrap:wrap}
|
||
.tray .tk{font-size:10.5px;color:var(--brand);letter-spacing:.1em}
|
||
.tray .chips{display:flex;gap:6px;flex-wrap:wrap;flex:1}
|
||
.tchip{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)}
|
||
.tchip .x{cursor:pointer;color:var(--text-3)}
|
||
.tchip .x:hover{color:var(--danger)}
|
||
|
||
/* ===== 对比页 ===== */
|
||
.chipsrow{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px}
|
||
.cmp-tbl{width:100%;border-collapse:collapse;font-size:12.5px}
|
||
.cmp-tbl th,.cmp-tbl td{padding:9px 12px;border-bottom:1px solid var(--border-2);text-align:right;font-variant-numeric:tabular-nums}
|
||
.cmp-tbl th:first-child,.cmp-tbl td:first-child{text-align:left}
|
||
.cmp-tbl thead th{font-family:var(--mono);font-size:11.5px;letter-spacing:.03em;background:var(--bg-hover);border-bottom:1px solid var(--border)}
|
||
.cmp-tbl td.rk{color:var(--text-3);font-family:var(--mono);font-size:11px}
|
||
.cmp-tbl td.best{color:var(--brand);font-weight:700}
|
||
.minis{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:12px}
|
||
.chart-card.mini h3{font-family:var(--mono);font-size:12px}
|
||
.h3c{font-size:10px;margin-left:6px}
|
||
.cmx{display:grid;gap:3px;margin-top:6px}
|
||
.cmx .cl{font-family:var(--mono);font-size:10.5px;text-align:center;padding:4px 2px;color:var(--text-2)}
|
||
.cmx .cc{font-family:var(--mono);font-size:11px;text-align:center;padding:9px 2px;border-radius:var(--r-sm);border:1px solid var(--border-2);font-variant-numeric:tabular-nums}
|
||
|
||
/* ===== 说明页 ===== */
|
||
.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}
|
||
.foot{color:var(--text-3);font-family:var(--mono);font-size:10.5px;text-align:center;margin-top:26px}
|
||
</style>
|
||
<div class="wrap">
|
||
<div class="topbar">
|
||
<div class="t">三国量化研究台 <span class="dim">· 因子工作台 设计稿 v1 · 2026-08-28</span></div>
|
||
<nav class="tabs">
|
||
<button class="on" data-tab="tears">① tears 报告页</button>
|
||
<button data-tab="compare">② 加入对比</button>
|
||
<button data-tab="notes">③ 实现说明</button>
|
||
</nav>
|
||
</div>
|
||
|
||
<!-- ============ 设计① tears 报告页 ============ -->
|
||
<section id="tab-tears" class="tab on">
|
||
<div class="dcap"><span class="no">设计 ①</span><span class="txt"><b>完整 tears 报告页</b> · 终端风原生图表重构,替代当前 iframe 里的浅色 alphalens 报告 · 入口:因子详情「查看完整 tears 报告」/ 分析任务完成页直达 · 右上 1D/5D/10D 全页联动(可点)</span></div>
|
||
<div class="mock">
|
||
<span class="backlink">← 返回分析结果</span>
|
||
<div class="page-head">
|
||
<div><h1 class="page-title">因子 tears · alpha16</h1><p class="page-sub">task_8f3a2c · 全A抽样 800 标的 · 2022-01-01 ~ 2024-06-30 · 日频 RankIC</p></div>
|
||
<div class="period-switch"><button data-tp="1">1D</button><button class="on" data-tp="5">5D</button><button data-tp="10">10D</button></div>
|
||
</div>
|
||
<div class="fcard">
|
||
<div class="hd"><span class="fname">alpha16</span><span class="cat-chip cat-alpha101">alpha101</span><span class="st st-ok"><span class="lamp" style="background:var(--lamp-ok)"></span>有效</span></div>
|
||
<div class="expr">(-1 * ts_rank(rank(correlation(rank(high), rank(volume), 3)), 3))</div>
|
||
</div>
|
||
<div class="metric-strip" id="t-strip"></div>
|
||
<div class="charts">
|
||
<div class="chart-card"><h3>月度 IC 与累计 IC</h3><div class="cs">柱=月度 RankIC(红正绿负)· 线=累计 IC(右轴)</div><div class="cbody" id="t-ic"></div></div>
|
||
<div class="chart-card"><h3>月度 IC 热力图<span class="new-tag">新增</span></h3><div class="cs">红=正预测力 · 绿=负 · 看月度稳定性与失效期</div><div class="cbody" id="t-heat"></div></div>
|
||
</div>
|
||
<div class="charts">
|
||
<div class="chart-card"><h3>分组累计净值 Q1 / Q5 / Q10</h3><div class="cs">按因子值十分组回测净值 · 单调分层=因子有效</div><div class="cbody" id="t-ql"></div></div>
|
||
<div class="chart-card"><h3>多空净值 Q10 − Q1<span class="new-tag">新增</span></h3><div class="cs">做多最高组做空最低组 · ○=最大回撤点</div><div class="cbody" id="t-ls"></div></div>
|
||
</div>
|
||
<div class="charts two-half">
|
||
<div class="chart-card"><h3 id="t-dec-title">十分组年化收益 (5D)</h3><div class="cs">Q1=因子值最低组 · 单调性=区分度</div><div class="cbody" id="t-dec"></div></div>
|
||
<div class="chart-card"><h3>IC 衰减<span class="new-tag">新增</span></h3><div class="cs">持有期拉长后的 IC 均值 · 衰减快=适合短持有</div><div class="cbody" id="t-decay"></div></div>
|
||
</div>
|
||
<div class="actions"><button class="btn">返回分析任务</button><button class="btn">导出报告 PNG</button><span class="hint-inline">指标条与十分组图随周期开关联动</span></div>
|
||
</div>
|
||
</section>
|
||
<!-- ============ 设计② 加入对比 ============ -->
|
||
<section id="tab-compare" class="tab">
|
||
<div class="dcap"><span class="no">设计 ②</span><span class="txt"><b>「加入对比」做实</b> · 排行榜行内多选 + 底部托盘 + 对比页 · 本段可交互:点行内「+对比 / ✓ 已选」试试,托盘随动,「对比 N 因子 →」跳到对比页</span></div>
|
||
<div class="mock">
|
||
<div class="page-head">
|
||
<div><h1 class="page-title">IC 排行榜 · 行内多选</h1><p class="page-sub">run ev_20260825_211248 · 全A 5,214 标的 · 2018-01 ~ 2026-06 · 5D</p></div>
|
||
</div>
|
||
<div class="tblwrap"><table class="tbl">
|
||
<thead><tr><th>#</th><th>因子</th><th>类别</th><th>IC 均值</th><th>ICIR</th><th>结论</th><th>对比</th></tr></thead>
|
||
<tbody id="lb-body"></tbody>
|
||
</table></div>
|
||
<div class="tray">
|
||
<span class="mono tk">对比托盘</span>
|
||
<div class="chips" id="tray-chips"></div>
|
||
<button class="btn primary" id="tray-go">对比 3 因子 →</button>
|
||
</div>
|
||
<p class="hint">详情页入口:按钮「加入对比」点击后变「已加入 ✓」;托盘全局悬浮于页面底部,2~6 个因子随时进对比页。</p>
|
||
</div>
|
||
|
||
<div class="mock" id="cmp-page" style="margin-top:14px">
|
||
<div class="page-head">
|
||
<div><h1 class="page-title">因子对比</h1><p class="page-sub">同批次同口径并排 · 批次 ev_20260825_211248 · 2018-01 ~ 2026-06</p></div>
|
||
<div class="period-switch"><button data-cp="1">1D</button><button class="on" data-cp="5">5D</button><button data-cp="10">10D</button></div>
|
||
</div>
|
||
<div class="chipsrow" id="cmp-chips"></div>
|
||
<div class="chart-card" style="margin-bottom:12px"><h3>指标并排<span class="new-tag">行最优青色高亮</span></h3><div class="tblwrap" id="cmp-tbl" style="margin-top:8px"></div></div>
|
||
<div class="charts">
|
||
<div class="chart-card"><h3>累计 IC 叠加</h3><div class="cs">各因子累计 RankIC 曲线 · 斜率=预测力</div><div class="cbody" id="cmp-overlay"></div></div>
|
||
<div class="chart-card"><h3>IC 序列相关性<span class="new-tag">新增</span></h3><div class="cs">月度 IC 序列 Pearson · 高相关=同质因子,为去重铺路</div><div class="cbody" id="cmp-cmx"></div></div>
|
||
</div>
|
||
<div class="minis" id="cmp-minis"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ 实现说明 ============ -->
|
||
<section id="tab-notes" class="tab">
|
||
<div class="dcap"><span class="no">说明</span><span class="txt">两处设计的数据来源、实现路径与工作量 · <b>两项待拍板</b></span></div>
|
||
<div class="mock">
|
||
<h3 class="nh">tears 报告页 · 三种实现路径</h3>
|
||
<div class="tblwrap"><table class="tbl">
|
||
<thead><tr><th>方案</th><th>做法</th><th>视觉</th><th>交互</th><th>工作量</th></tr></thead>
|
||
<tbody>
|
||
<tr><td class="mono cy">A 原生重构(本稿①)</td><td>服务端把 alphalens 已算的分层序列(日度IC / 分组净值 / 多空净值 / 换手)序列化成 JSON 端点,前端 ECharts 暗色渲染</td><td>与全站完全统一</td><td>周期切换全页联动、tooltip、导出 PNG</td><td class="mono">后端端点 + 前端页 ≈ 2~3 天</td></tr>
|
||
<tr><td class="mono">B matplotlib 暗化</td><td>tears 生成端统一套暗色 rcParams,仍走 iframe 内嵌</td><td>暗底但仍是 matplotlib 风,字体/气质仍割裂</td><td>维持现状(无联动)</td><td class="mono">≈ 半天</td></tr>
|
||
<tr><td class="mono">C 终端壳包 iframe</td><td>iframe 外面包终端风卡片框,报告本体不动</td><td>壳统一、内里割裂</td><td>无</td><td class="mono">≈ 0.5 天</td></tr>
|
||
</tbody>
|
||
</table></div>
|
||
<p class="hint">推荐 <b class="cy">A</b>:要的是终态一致;B 只是把白底换黑底,图表气质仍是 matplotlib。B 也可作为先行止血项,随后 A 替换。</p>
|
||
<h3 class="nh">加入对比(设计②)</h3>
|
||
<div class="tblwrap"><table class="tbl">
|
||
<thead><tr><th style="width:110px">项</th><th>说明</th></tr></thead>
|
||
<tbody>
|
||
<tr><td class="mono">数据来源</td><td>纯前端:现有 <span class="mono">GET /factor/eval/detail</span> × N 个因子(指标 / 月度IC / 十分组全在返回里);相关性 = 月度 IC 序列 Pearson,前端可算,零后端改动</td></tr>
|
||
<tr><td class="mono">入口</td><td>排行榜行内「+对比」/ 详情页「加入对比」按钮(加入后变「已加入 ✓」)/ 底部全局托盘(2~6 个)</td></tr>
|
||
<tr><td class="mono">工作量</td><td class="mono">≈ 1 天</td></tr>
|
||
<tr><td class="mono">衔接</td><td>相关性格子即后续「62 个 effective 因子相关性去重分析」的页面雏形,做了不白做</td></tr>
|
||
</tbody>
|
||
</table></div>
|
||
<h3 class="nh">待拍板</h3>
|
||
<ol class="ask">
|
||
<li>tears 报告走 <b class="cy">A(原生重构,2~3 天)</b>、B(半天暗化先行)还是 C?我的建议:A,一步到位。</li>
|
||
<li>对比页按本稿②范围确认即可开工(指标并排 + 累计 IC + 相关性 + 十分组小倍数)。</li>
|
||
</ol>
|
||
</div>
|
||
</section>
|
||
|
||
<p class="foot">图表数据为种子化生成的示意值(形态对齐 08-25 批次真实结果)· 线上以接口真实数据为准</p>
|
||
</div>
|
||
<script>
|
||
'use strict'
|
||
/* ---------- 工具 ---------- */
|
||
function mulberry32(a){return function(){a|=0;a=a+0x6D2B79F5|0;var t=Math.imul(a^a>>>15,1|a);t=t+Math.imul(t^t>>>7,61|t)^t;return((t^t>>>14)>>>0)/4294967296}}
|
||
function makeGauss(rnd){var sp=null;return function(){if(sp!==null){var v=sp;sp=null;return v}var u=0,v=0;while(u===0)u=rnd();while(v===0)v=rnd();var m=Math.sqrt(-2*Math.log(u)),a=2*Math.PI*v;sp=m*Math.sin(a);return m*Math.cos(a)}}
|
||
function $(s){return document.querySelector(s)}
|
||
var AX='#4a5868',GRID='#16202e',UP='#ff4d5e',DN='#2ee68a',CY='#00e5ff'
|
||
function pct(v,d){if(d===undefined)d=1;return(v>=0?'+':'')+(v*100).toFixed(d)+'%'}
|
||
function num(v,d){return v.toFixed(d)}
|
||
function sgn(v){return v>=0?'pos':'neg'}
|
||
|
||
/* ---------- 示意数据(种子化生成,形态对齐真实批次) ---------- */
|
||
function buildMonths(){var o=[],y,m;for(y=2022;y<=2024;y++)for(m=1;m<=12;m++){if(y===2024&&m>6)break;o.push(y+'-'+(m<10?'0':'')+m)}return o}
|
||
var M=buildMonths()
|
||
var G1=makeGauss(mulberry32(20260828))
|
||
var icT=M.map(function(_,i){var v=0.055+0.085*G1();if(i>=8&&i<=11)v-=0.07;if(i>=26)v+=0.035;return Math.round(v*1000)/1000})
|
||
var cc=0,cumT=icT.map(function(v){return cc+=v})
|
||
var G2=makeGauss(mulberry32(777))
|
||
var c10=1,c5=1,c1=1,q10=[],q5=[],q1=[]
|
||
M.forEach(function(_,i){
|
||
var r10=0.020+0.013*G2(),r5=0.005+0.009*G2(),r1=-0.0065+0.011*G2()
|
||
if(i>=8&&i<=11){r10-=0.035;r1+=0.02}
|
||
c10*=1+r10;c5*=1+r5;c1*=1+r1;q10.push(c10);q5.push(c5);q1.push(c1)
|
||
})
|
||
var lsR=[],i2
|
||
for(i2=0;i2<M.length;i2++){var a2=i2===0?q10[0]:q10[i2]/q10[i2-1],b2=i2===0?q1[0]:q1[i2]/q1[i2-1];lsR.push(a2-b2+0.001)}
|
||
var cl=1,lsCum=lsR.map(function(r){return cl*=1+r})
|
||
|
||
var TEARS={metrics:{
|
||
'1':{ic:.048,icir:.58,t:21.3,win:.662,ls:.315,dd:-.087,ac:.94},
|
||
'5':{ic:.065,icir:.66,t:29.9,win:.684,ls:.423,dd:-.121,ac:.94},
|
||
'10':{ic:.071,icir:.61,t:25.6,win:.670,ls:.489,dd:-.149,ac:.94}},
|
||
dec:{'1':[-.078,-.059,-.041,-.024,-.009,.005,.019,.036,.054,.079],
|
||
'5':[-.096,-.072,-.050,-.031,-.014,.007,.028,.049,.070,.098],
|
||
'10':[-.104,-.080,-.058,-.038,-.019,.006,.031,.054,.078,.109]}}
|
||
var FCOL={alpha16:'#00e5ff',resi_5:'#b48cff',roc_20:'#ffb000',std_20:'#5f8fd9',KLEN:'#d98fd9'}
|
||
function genCum(seed,drift,sd){var g=makeGauss(mulberry32(seed)),c=0;return M.map(function(){return c+=drift+sd*g()})}
|
||
var CUMS={alpha16:cumT,resi_5:genCum(31,.05,.10),roc_20:genCum(32,.03,.11),std_20:genCum(33,-.04,.07),KLEN:genCum(34,-.01,.08)}
|
||
var CORR={'alpha16|resi_5':.42,'alpha16|roc_20':.18,'alpha16|std_20':.35,'alpha16|KLEN':.06,'resi_5|roc_20':.27,'resi_5|std_20':.31,'resi_5|KLEN':.11,'roc_20|std_20':.22,'roc_20|KLEN':.09,'std_20|KLEN':.13}
|
||
function corrOf(a,b){if(a===b)return 1;var v=CORR[a+'|'+b];if(v===undefined)v=CORR[b+'|'+a];return v===undefined?0:v}
|
||
|
||
var CMP=[
|
||
{name:'alpha16',cat:'alpha101',concl:'effective',dec5:[-.096,-.072,-.050,-.031,-.014,.007,.028,.049,.070,.098],
|
||
m:{'1':{ic:.048,icir:.58,t:21.3,win:.662,ls:.315,to:.081},'5':{ic:.065,icir:.66,t:29.9,win:.684,ls:.423,to:.081},'10':{ic:.071,icir:.61,t:25.6,win:.670,ls:.489,to:.081}}},
|
||
{name:'resi_5',cat:'alpha158',concl:'effective',dec5:[-.118,-.089,-.060,-.034,-.011,.012,.036,.064,.097,.129],
|
||
m:{'1':{ic:.041,icir:.47,t:18.9,win:.638,ls:.612,to:.124},'5':{ic:.058,icir:.52,t:23.1,win:.649,ls:.991,to:.124},'10':{ic:.062,icir:.48,t:20.3,win:.641,ls:.884,to:.124}}},
|
||
{name:'roc_20',cat:'alpha158',concl:'watch',dec5:[-.032,-.019,-.008,.001,.007,.010,.012,.014,.019,.030],
|
||
m:{'1':{ic:.022,icir:.19,t:8.4,win:.557,ls:.118,to:.158},'5':{ic:.036,icir:.31,t:13.8,win:.602,ls:.225,to:.158},'10':{ic:.039,icir:.28,t:11.9,win:.593,ls:.261,to:.158}}},
|
||
{name:'std_20',cat:'alpha158',concl:'effective',dec5:[.021,.016,.011,.007,.003,-.002,-.006,-.011,-.016,-.023],
|
||
m:{'1':{ic:-.031,icir:-.36,t:-15.7,win:.598,ls:.187,to:.062},'5':{ic:-.041,icir:-.38,t:-16.4,win:.601,ls:.243,to:.062},'10':{ic:-.044,icir:-.35,t:-14.8,win:.594,ls:.271,to:.062}}},
|
||
{name:'KLEN',cat:'builtin',concl:'watch',dec5:[-.006,.004,-.002,.007,.001,-.003,.005,-.001,.003,-.008],
|
||
m:{'1':{ic:-.008,icir:-.07,t:-3.1,win:.517,ls:.041,to:.141},'5':{ic:-.012,icir:-.09,t:-3.8,win:.521,ls:.064,to:.141},'10':{ic:-.014,icir:-.08,t:-3.4,win:.524,ls:.078,to:.141}}}
|
||
]
|
||
var LB_ROWS=[CMP[0],CMP[1],CMP[3],CMP[2],CMP[4]]
|
||
var STATE={tp:'5',cp:'5',sel:['alpha16','resi_5','roc_20']}
|
||
|
||
/* ---------- SVG 图表 ---------- */
|
||
function svgo(W,H){return '<svg viewBox="0 0 '+W+' '+H+'" style="width:100%;height:auto" role="img">'}
|
||
function tx(x,y,s,fill,fs,an){return '<text x="'+x+'" y="'+y+'" fill="'+(fill||AX)+'" font-size="'+(fs||9)+'" text-anchor="'+(an||'middle')+'">'+s+'</text>'}
|
||
|
||
function chIC(ics){
|
||
var W=660,H=226,pl=36,pr=48,pt=18,pb=24,pw=W-pl-pr,ph=H-pt-pb,n=ics.length,i
|
||
var mx=Math.max(.12,Math.max.apply(null,ics.map(Math.abs)))*1.15
|
||
var cum=[],c=0;ics.forEach(function(v){c+=v;cum.push(c)})
|
||
var cmax=Math.max(.5,Math.max.apply(null,cum))*1.06
|
||
function y1(v){return pt+ph/2-v/mx*(ph/2)}
|
||
function y2(v){return pt+ph-Math.max(0,v)/cmax*ph}
|
||
function x(i){return pl+(i+.5)*pw/n}
|
||
var s=svgo(W,H)
|
||
;[-.08,0,.08].forEach(function(t){s+='<line x1="'+pl+'" x2="'+(W-pr)+'" y1="'+y1(t).toFixed(1)+'" y2="'+y1(t).toFixed(1)+'" stroke="'+GRID+'"'+(t===0?'':' stroke-dasharray="3 4"')+'/>'})
|
||
s+=tx(pl-5,y1(.08)+3,'.08',AX,9,'end')+tx(pl-5,y1(0)+3,'0',AX,9,'end')+tx(pl-5,y1(-.08)+3,'-.08',AX,9,'end')
|
||
s+=tx(W-pr+5,y2(cmax)+3,num(cmax,1),CY,9,'start')+tx(W-pr+5,y2(0)+3,'0',CY,9,'start')
|
||
var bw=Math.max(3,pw/n-1.5)
|
||
ics.forEach(function(v,i){var y0=y1(0),y=y1(v)
|
||
s+='<rect x="'+(x(i)-bw/2).toFixed(1)+'" y="'+Math.min(y,y0).toFixed(1)+'" width="'+bw.toFixed(1)+'" height="'+Math.abs(y-y0).toFixed(1)+'" fill="'+(v>=0?UP:DN)+'" opacity=".85"/>'})
|
||
s+='<polyline points="'+cum.map(function(v,i){return x(i).toFixed(1)+','+y2(v).toFixed(1)}).join(' ')+'" fill="none" stroke="'+CY+'" stroke-width="1.8"/>'
|
||
M.forEach(function(m,i){if(i%4===0)s+=tx(x(i),H-8,m.slice(2),AX,9)})
|
||
s+='<rect x="'+pl+'" y="4" width="8" height="8" fill="'+UP+'"/>'+tx(pl+12,11,'月度IC',AX,9,'start')
|
||
s+='<line x1="'+(pl+58)+'" x2="'+(pl+70)+'" y1="8" y2="8" stroke="'+CY+'" stroke-width="1.8"/>'+tx(pl+74,11,'累计IC·右轴',AX,9,'start')
|
||
return s+'</svg>'
|
||
}
|
||
|
||
function chHeat(){
|
||
var yMap={'2022':icT.slice(0,12),'2023':icT.slice(12,24),'2024':icT.slice(24).concat([null,null,null,null,null,null])}
|
||
var cw=40,chh=24,g=2,lw=36,th=16,j
|
||
var W=lw+12*(cw+g)+g,H=th+3*(chh+g)+4
|
||
var s=svgo(W,H)
|
||
for(j=0;j<12;j++)s+=tx(lw+j*(cw+g)+cw/2,11,String(j+1),AX,8.5)
|
||
Object.keys(yMap).forEach(function(y,i){
|
||
s+=tx(lw-8,th+i*(chh+g)+chh/2+3,y,AX,9,'end')
|
||
yMap[y].forEach(function(v,j){
|
||
var X=lw+j*(cw+g),Y=th+i*(chh+g)
|
||
if(v===null){s+='<rect x="'+X+'" y="'+Y+'" width="'+cw+'" height="'+chh+'" rx="1.5" fill="#070b12" stroke="#0f1822"/>';return}
|
||
var a=Math.min(1,Math.abs(v)/.16)
|
||
s+='<rect x="'+X+'" y="'+Y+'" width="'+cw+'" height="'+chh+'" rx="1.5" fill="'+(v>=0?'rgba(255,77,94,':'rgba(46,230,138,')+(0.08+a*.72).toFixed(2)+')"/>'
|
||
var t2=(Math.round(v*100)/100).toFixed(2).replace('0.','.')
|
||
s+=tx(X+cw/2,Y+chh/2+3,v<0?'-'+t2.replace('-',''):t2,a>.55?'#05080d':'#7a8a9a',8.5)
|
||
})})
|
||
return s+'</svg>'
|
||
}
|
||
|
||
function chLines(series,yFmt){
|
||
var W=660,H=230,pl=44,pr=88,pt=14,pb=22,pw=W-pl-pr,ph=H-pt-pb,k,i
|
||
var all=[];series.forEach(function(sr){all=all.concat(sr.vals)})
|
||
var mn=Math.min.apply(null,all),mx=Math.max.apply(null,all),pd=(mx-mn)*.1||1;mn-=pd;mx+=pd
|
||
function X(i){return pl+i/(M.length-1)*pw}
|
||
function Y(v){return pt+(mx-v)/(mx-mn)*ph}
|
||
var s=svgo(W,H)
|
||
for(k=0;k<=3;k++){var v=mn+(mx-mn)*k/3,yy=Y(v).toFixed(1)
|
||
s+='<line x1="'+pl+'" x2="'+(W-pr)+'" y1="'+yy+'" y2="'+yy+'" stroke="'+GRID+'"/>'+tx(pl-6,+yy+3,yFmt(v),AX,9,'end')}
|
||
M.forEach(function(m,i){if(i%4===0)s+=tx(X(i),H-7,m.slice(2),AX,9)})
|
||
series.forEach(function(sr){
|
||
s+='<polyline points="'+sr.vals.map(function(v,i){return X(i).toFixed(1)+','+Y(v).toFixed(1)}).join(' ')+'" fill="none" stroke="'+sr.color+'" stroke-width="1.8"/>'
|
||
var lv=sr.vals[sr.vals.length-1]
|
||
s+=tx(W-pr+6,Y(lv)+3,sr.name+' '+yFmt(lv),sr.color,9.5,'start')})
|
||
return s+'</svg>'
|
||
}
|
||
|
||
function chLS(){
|
||
var W=660,H=230,pl=44,pr=16,pt=14,pb=22,pw=W-pl-pr,ph=H-pt-pb,k
|
||
var mn=Math.min.apply(null,lsCum),mx=Math.max.apply(null,lsCum),pd=(mx-mn)*.12||1
|
||
mn=Math.min(mn-pd,0.985);mx+=pd
|
||
function X(i){return pl+i/(M.length-1)*pw}
|
||
function Y(v){return pt+(mx-v)/(mx-mn)*ph}
|
||
var peak=-1,pi=0,ti=0,ddi=0,tdd=0
|
||
lsCum.forEach(function(v,i){if(v>peak){peak=v;pi=i}var dd=v/peak-1;if(dd<tdd){tdd=dd;ddi=i;ti=pi}})
|
||
var s=svgo(W,H)
|
||
for(k=0;k<=3;k++){var v=mn+(mx-mn)*k/3,yy=Y(v).toFixed(1)
|
||
s+='<line x1="'+pl+'" x2="'+(W-pr)+'" y1="'+yy+'" y2="'+yy+'" stroke="'+GRID+'"/>'+tx(pl-6,+yy+3,(v*100).toFixed(0)+'%',AX,9,'end')}
|
||
M.forEach(function(m,i){if(i%4===0)s+=tx(X(i),H-7,m.slice(2),AX,9)})
|
||
var pts=lsCum.map(function(v,i){return X(i).toFixed(1)+','+Y(v).toFixed(1)})
|
||
s+='<polygon points="'+pl+','+Y(mn).toFixed(1)+' '+pts.join(' ')+' '+(W-pr)+','+Y(mn).toFixed(1)+'" fill="rgba(255,176,0,0.10)"/>'
|
||
s+='<polyline points="'+pts.join(' ')+'" fill="none" stroke="#ffb000" stroke-width="1.8"/>'
|
||
s+='<circle cx="'+X(ddi).toFixed(1)+'" cy="'+Y(lsCum[ddi]).toFixed(1)+'" r="3" fill="none" stroke="'+DN+'" stroke-width="1.2"/>'
|
||
s+=tx(X(ddi),Y(lsCum[ddi])-8,'最大回撤 '+pct(tdd)+' · '+M[ti].slice(2)+'~'+M[ddi].slice(2),DN,9.5)
|
||
return s+'</svg>'
|
||
}
|
||
|
||
function chDec(vals,small){
|
||
var W=small?300:660,H=small?150:212,pl=small?26:40,pr=8,pt=small?12:16,pb=20,pw=W-pl-pr,ph=H-pt-pb,i
|
||
var mx=Math.max(.02,Math.max.apply(null,vals.map(Math.abs)))*1.18
|
||
var y0=pt+ph/2
|
||
function Y(v){return pt+ph/2-v/mx*(ph/2)}
|
||
var s=svgo(W,H)
|
||
s+='<line x1="'+pl+'" x2="'+(W-pr)+'" y1="'+y0+'" y2="'+y0+'" stroke="'+GRID+'"/>'
|
||
if(!small){s+='<line x1="'+pl+'" x2="'+(W-pr)+'" y1="'+Y(.05).toFixed(1)+'" y2="'+Y(.05).toFixed(1)+'" stroke="'+GRID+'" stroke-dasharray="3 4"/>'
|
||
s+='<line x1="'+pl+'" x2="'+(W-pr)+'" y1="'+Y(-.05).toFixed(1)+'" y2="'+Y(-.05).toFixed(1)+'" stroke="'+GRID+'" stroke-dasharray="3 4"/>'
|
||
s+=tx(pl-5,Y(.05)+3,'+5%',AX,9,'end')+tx(pl-5,Y(-.05)+3,'-5%',AX,9,'end')}
|
||
var bw=pw/10*.55
|
||
vals.forEach(function(v,i){var cx=pl+(i+.5)*pw/10,y=Y(v)
|
||
s+='<rect x="'+(cx-bw/2).toFixed(1)+'" y="'+Math.min(y,y0).toFixed(1)+'" width="'+bw.toFixed(1)+'" height="'+Math.abs(y-y0).toFixed(1)+'" fill="'+(v>=0?UP:DN)+'"/>'
|
||
s+=tx(cx,H-7,'D'+(i+1),AX,small?8:9)
|
||
if(!small)s+=tx(cx,v>=0?y-4:y+11,pct(v),v>=0?UP:DN,8.5)})
|
||
return s+'</svg>'
|
||
}
|
||
|
||
function chDecay(){
|
||
var W=300,H=150,pl=34,pr=8,pt=16,pb=20,pw=W-pl-pr,ph=H-pt-pb
|
||
var items=[['1D',TEARS.metrics['1'].ic],['5D',TEARS.metrics['5'].ic],['10D',TEARS.metrics['10'].ic]]
|
||
var mx=.085,y0=pt+ph/2
|
||
function Y(v){return pt+ph/2-v/mx*(ph/2)}
|
||
var s=svgo(W,H)
|
||
s+='<line x1="'+pl+'" x2="'+(W-pr)+'" y1="'+y0+'" y2="'+y0+'" stroke="'+GRID+'"/>'
|
||
s+=tx(pl-5,Y(.06)+3,'.06',AX,9,'end')+tx(pl-5,y0+3,'0',AX,9,'end')
|
||
var bw=pw/3*.4
|
||
items.forEach(function(it,i){var cx=pl+(i+.5)*pw/3,y=Y(it[1])
|
||
s+='<rect x="'+(cx-bw/2).toFixed(1)+'" y="'+y.toFixed(1)+'" width="'+bw.toFixed(1)+'" height="'+(y0-y).toFixed(1)+'" fill="rgba(0,229,255,0.7)"/>'
|
||
s+=tx(cx,y-5,num(it[1],3),CY,9)+tx(cx,H-7,it[0],AX,9)})
|
||
return s+'</svg>'
|
||
}
|
||
|
||
/* ---------- 渲染 ---------- */
|
||
function chip(cat){return '<span class="cat-chip cat-'+cat+'">'+cat+'</span>'}
|
||
function lamp(c){var m={effective:['有效','st-ok','var(--lamp-ok)'],watch:['观察','st-watch','var(--lamp-warn)'],eliminated:['淘汰','st-elim','var(--lamp-idle)']},e=m[c]
|
||
return '<span class="st '+e[1]+'"><span class="lamp" style="background:'+e[2]+'"></span>'+e[0]+'</span>'}
|
||
|
||
function renderTears(){
|
||
var p=STATE.tp,m=TEARS.metrics[p]
|
||
$('#t-strip').innerHTML=[
|
||
['IC 均值 ('+p+'D)',num(m.ic,3),sgn(m.ic)],
|
||
['ICIR',num(m.icir,2),sgn(m.icir)],
|
||
['t 值',num(m.t,1),sgn(m.t)],
|
||
['IC 胜率',num(m.win*100,1)+'%',''],
|
||
['多空年化',pct(m.ls),sgn(m.ls)],
|
||
['多空最大回撤',pct(m.dd),'neg'],
|
||
['因子自相关',num(m.ac,2),'']
|
||
].map(function(x){return '<div class="metric"><div class="k">'+x[0]+'</div><div class="v '+x[2]+'">'+x[1]+'</div></div>'}).join('')
|
||
$('#t-ic').innerHTML=chIC(icT)
|
||
$('#t-heat').innerHTML=chHeat()
|
||
$('#t-ql').innerHTML=chLines([
|
||
{name:'Q10 最高组',color:UP,vals:q10},
|
||
{name:'Q5',color:'#7a8a9a',vals:q5},
|
||
{name:'Q1 最低组',color:DN,vals:q1}],function(v){return(v*100).toFixed(0)+'%'})
|
||
$('#t-ls').innerHTML=chLS()
|
||
$('#t-dec-title').textContent='十分组年化收益 ('+p+'D)'
|
||
$('#t-dec').innerHTML=chDec(TEARS.dec[p],false)
|
||
$('#t-decay').innerHTML=chDecay()
|
||
}
|
||
|
||
function renderLB(){
|
||
$('#lb-body').innerHTML=LB_ROWS.map(function(f,i){
|
||
var sel=STATE.sel.indexOf(f.name)>=0,m=f.m['5']
|
||
return '<tr><td class="mono dim">'+(i+1)+'</td><td class="mono fname-s">'+f.name+'</td><td>'+chip(f.cat)+
|
||
'</td><td class="mono '+sgn(m.ic)+'">'+num(m.ic,3)+'</td><td class="mono '+sgn(m.icir)+'">'+num(m.icir,2)+
|
||
'</td><td>'+lamp(f.concl)+'</td><td><button class="addbtn'+(sel?' on':'')+'" data-add="'+f.name+'">'+(sel?'✓ 已选':'+对比')+'</button></td></tr>'}).join('')
|
||
}
|
||
function renderTray(){
|
||
$('#tray-chips').innerHTML=STATE.sel.map(function(f){
|
||
return '<span class="tchip" style="border-color:'+FCOL[f]+'66"><span style="color:'+FCOL[f]+'">●</span>'+f.name+
|
||
(STATE.sel.length>2?'<span class="x" data-f="'+f+'" title="移除">✕</span>':'')+'</span>'}).join('')+
|
||
'<span class="hint-inline">2~6 个</span>'
|
||
$('#tray-go').textContent='对比 '+STATE.sel.length+' 因子 →'
|
||
}
|
||
|
||
function cmxHTML(names){
|
||
var h='<div class="cmx" style="grid-template-columns:64px repeat('+names.length+',1fr)">'
|
||
h+='<div></div>'+names.map(function(n){return '<div class="cl" style="color:'+FCOL[n]+'">'+n+'</div>'}).join('')
|
||
names.forEach(function(a){
|
||
h+='<div class="cl" style="color:'+FCOL[a]+'">'+a+'</div>'
|
||
names.forEach(function(b){var c=corrOf(a,b),al=Math.min(1,Math.abs(c))
|
||
h+='<div class="cc" style="background:rgba(0,229,255,'+(0.04+al*.4).toFixed(2)+')">'+(a===b?'—':num(c,2))+'</div>'})})
|
||
return h+'</div>'
|
||
}
|
||
|
||
function renderCmp(){
|
||
var p=STATE.cp
|
||
var fs=CMP.filter(function(f){return STATE.sel.indexOf(f.name)>=0})
|
||
$('#cmp-chips').innerHTML=fs.map(function(f){
|
||
return '<span class="tchip" style="border-color:'+FCOL[f.name]+'66"><span style="color:'+FCOL[f.name]+'">●</span>'+f.name+' '+chip(f.cat)+'</span>'}).join('')+
|
||
'<span class="hint-inline">在排行榜行内 + 或详情页「加入对比」增删</span>'
|
||
var rows=[
|
||
{k:'结论',h:function(f){return lamp(f.concl)}},
|
||
{k:'IC 均值',h:function(f){return '<span class="'+sgn(f.m[p].ic)+'">'+num(f.m[p].ic,3)+'</span>'},g:function(f){return f.m[p].ic},b:'max'},
|
||
{k:'ICIR',h:function(f){return '<span class="'+sgn(f.m[p].icir)+'">'+num(f.m[p].icir,2)+'</span>'},g:function(f){return f.m[p].icir},b:'max'},
|
||
{k:'t 值',h:function(f){return '<span class="'+sgn(f.m[p].t)+'">'+num(f.m[p].t,1)+'</span>'},g:function(f){return Math.abs(f.m[p].t)},b:'max'},
|
||
{k:'IC 胜率',h:function(f){return num(f.m[p].win*100,1)+'%'},g:function(f){return f.m[p].win},b:'max'},
|
||
{k:'多空年化',h:function(f){return '<span class="'+sgn(f.m[p].ls)+'">'+pct(f.m[p].ls)+'</span>'},g:function(f){return f.m[p].ls},b:'max'},
|
||
{k:'日均换手',h:function(f){return num(f.m[p].to*100,1)+'%'},g:function(f){return f.m[p].to},b:'min'}]
|
||
var h='<table class="cmp-tbl"><thead><tr><th>指标('+p+'D)</th>'+
|
||
fs.map(function(f){return '<th class="mcol" style="color:'+FCOL[f.name]+'">'+f.name+'</th>'}).join('')+'</tr></thead><tbody>'
|
||
rows.forEach(function(r){
|
||
var bi=-1,bv=null
|
||
if(r.g){bv=r.b==='min'?Infinity:-Infinity
|
||
fs.forEach(function(f,i){var v=r.g(f);if(r.b==='min'?v<bv:v>bv){bv=v;bi=i}})}
|
||
h+='<tr><td class="rk">'+r.k+'</td>'+fs.map(function(f,i){
|
||
return '<td class="'+(i===bi?'best':'')+'">'+r.h(f)+'</td>'}).join('')+'</tr>'})
|
||
h+='</tbody></table>'
|
||
$('#cmp-tbl').innerHTML=h
|
||
$('#cmp-overlay').innerHTML=chLines(fs.map(function(f){
|
||
return {name:f.name,color:FCOL[f.name],vals:CUMS[f.name]}}),function(v){return v.toFixed(1)})
|
||
$('#cmp-cmx').innerHTML=cmxHTML(fs.map(function(f){return f.name}))
|
||
var k=p==='1'?.78:p==='10'?1.12:1
|
||
$('#cmp-minis').innerHTML=fs.map(function(f){
|
||
return '<div class="chart-card mini"><h3>'+f.name+'<span class="h3c" style="color:'+FCOL[f.name]+'">'+p+'D</span></h3>'+
|
||
'<div class="cs">十分组年化 · 越单调区分度越好</div><div class="cbody">'+chDec(f.dec5.map(function(v){return v*k}),true)+'</div></div>'}).join('')
|
||
}
|
||
|
||
/* ---------- 交互 ---------- */
|
||
document.querySelectorAll('.tabs button').forEach(function(b){
|
||
b.addEventListener('click',function(){
|
||
document.querySelectorAll('.tabs button').forEach(function(x){x.classList.toggle('on',x===b)})
|
||
document.querySelectorAll('.tab').forEach(function(t){t.classList.toggle('on',t.id==='tab-'+b.dataset.tab)})
|
||
})})
|
||
document.querySelectorAll('[data-tp]').forEach(function(b){
|
||
b.addEventListener('click',function(){
|
||
STATE.tp=b.dataset.tp
|
||
document.querySelectorAll('[data-tp]').forEach(function(x){x.classList.toggle('on',x===b)})
|
||
renderTears()})})
|
||
document.querySelectorAll('[data-cp]').forEach(function(b){
|
||
b.addEventListener('click',function(){
|
||
STATE.cp=b.dataset.cp
|
||
document.querySelectorAll('[data-cp]').forEach(function(x){x.classList.toggle('on',x===b)})
|
||
renderCmp()})})
|
||
$('#lb-body').addEventListener('click',function(e){
|
||
var b=e.target.closest('button[data-add]');if(!b)return
|
||
var f=b.dataset.add,i=STATE.sel.indexOf(f)
|
||
if(i>=0)STATE.sel.splice(i,1)
|
||
else if(STATE.sel.length<6)STATE.sel.push(f)
|
||
renderLB();renderTray();renderCmp()})
|
||
$('#tray-chips').addEventListener('click',function(e){
|
||
var x=e.target.closest('.x');if(!x||STATE.sel.length<=2)return
|
||
STATE.sel=STATE.sel.filter(function(v){return v!==x.dataset.f})
|
||
renderLB();renderTray();renderCmp()})
|
||
$('#tray-go').addEventListener('click',function(){
|
||
var el=$('#cmp-page')
|
||
el.scrollIntoView({behavior:matchMedia('(prefers-reduced-motion: reduce)').matches?'auto':'smooth'})
|
||
el.classList.add('flash');setTimeout(function(){el.classList.remove('flash')},1200)})
|
||
|
||
renderTears();renderLB();renderTray();renderCmp()
|
||
</script>
|
||
</body>
|
||
</html>
|