Skip to content

Commit 22ce5ac

Browse files
lanfuliclaude
andcommitted
analytics v2 Stage 3: fundamentals & liquidity panel
fundGrid() in the detail panel surfaces row.fundamentals with honest per-field degradation (null -> "—"): market cap, ADV($), short %float, days-to-cover, trailing/forward P/E, P/S, beta, profit margin, next earnings (live countdown), + data-source label. Foreign-listing pill in the focus head + detail header (derived from currency!=USD, independent of the fundamentals fetch). Verified: foreign ticker (SIVE) shows the 10-cell grid + pill; US ticker without fundamentals shows no grid; no console errors. Real values fill in after the fundamentals backfill (Yahoo rate-limited at commit time). Fix: patch sub() now uses replacement-function form to avoid JS String.replace $'/$&/$` special-pattern interpretation (the literal '$' in the ADV label had injected post-match content). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0296d2e commit 22ce5ac

2 files changed

Lines changed: 83 additions & 3 deletions

File tree

reports/aleabito-60d-dashboard.html

Lines changed: 42 additions & 2 deletions
Large diffs are not rendered by default.

scripts/build-aleabito-dashboard.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1778,6 +1778,11 @@ function buildHtmlV2(data) {
17781778
.combo-hdr-px { font-size: 14px; font-weight: 800; font-variant-numeric: tabular-nums; }
17791779
.combo-ftr { display: flex; justify-content: space-between; margin: 6px 2px 0; font-family: var(--mono); font-size: 11px; color: var(--subtle); }
17801780
1781+
.fund-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: 2px 0 14px; }
1782+
.fund-cell { background: rgba(13,18,33,0.5); border: 1px solid var(--line); border-radius: 10px; padding: 8px 10px; }
1783+
.fc-l { font-size: 10px; color: var(--subtle); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1784+
.fc-v { font-family: var(--mono); font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; margin-top: 2px; }
1785+
.foreign-pill { display: inline-block; font-size: 10px; font-weight: 700; padding: 1px 7px; border-radius: 999px; background: rgba(246,200,95,0.16); color: var(--amber); }
17811786
.track-card { padding: 16px 22px 18px; margin: 0 0 18px; }
17821787
.track-empty { display: flex; flex-direction: column; gap: 4px; }
17831788
.track-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; }
@@ -3581,6 +3586,40 @@ function buildHtmlV2(data) {
35813586
'<div class="stats-note">样本 n=' + model.n + ' 个交易日 · 小样本 + 多重比较;格兰杰为"预测性先后"而非真正因果 · 仅描述,不构成投资建议</div>' +
35823587
'</div>';
35833588
}
3589+
function compactNum(v) {
3590+
if (v == null || !isFinite(v)) return '—';
3591+
var a = Math.abs(v);
3592+
if (a >= 1e12) return (v / 1e12).toFixed(2) + 'T';
3593+
if (a >= 1e9) return (v / 1e9).toFixed(2) + 'B';
3594+
if (a >= 1e6) return (v / 1e6).toFixed(1) + 'M';
3595+
if (a >= 1e3) return (v / 1e3).toFixed(1) + 'K';
3596+
return String(Math.round(v));
3597+
}
3598+
function fundCell(label, value, cls) { return '<div class="fund-cell"><div class="fc-l">' + label + '</div><div class="fc-v ' + (cls || '') + '">' + value + '</div></div>'; }
3599+
function fundGrid(f) {
3600+
if (!f) return '';
3601+
var num2 = function (v) { return v == null || !isFinite(v) ? '—' : Number(v).toFixed(2); };
3602+
var pct = function (v) { return v == null || !isFinite(v) ? '—' : (v * 100).toFixed(1) + '%'; };
3603+
var earn = '—';
3604+
if (f.nextEarnings) {
3605+
var t = new Date(f.nextEarnings + 'T00:00:00Z').getTime();
3606+
var dd = isFinite(t) ? Math.round((t - Date.now()) / 86400000) : null;
3607+
earn = f.nextEarnings + (dd == null ? '' : ' (' + (dd >= 0 ? dd + ' 天后' : (-dd) + ' 天前') + ')');
3608+
}
3609+
var srcLabel = f.source === 'quoteSummary' ? 'Yahoo 完整' : (f.source === 'quote' ? 'Yahoo 简表' : '部分数据');
3610+
var cells = '';
3611+
cells += fundCell('市值 Mkt Cap', f.marketCap != null ? compactNum(f.marketCap) : '—');
3612+
cells += fundCell('日均成交额 ADV', f.avgDollarVol != null ? '$' + compactNum(f.avgDollarVol) : '—');
3613+
cells += fundCell('做空 / 流通 Short%', pct(f.shortPercentFloat));
3614+
cells += fundCell('回补天数 D2C', f.shortRatio != null ? Number(f.shortRatio).toFixed(1) + 'd' : '—');
3615+
cells += fundCell('市盈率 P/E', num2(f.trailingPE));
3616+
cells += fundCell('预期 Fwd P/E', num2(f.forwardPE));
3617+
cells += fundCell('市销率 P/S', num2(f.priceToSales));
3618+
cells += fundCell('Beta', num2(f.beta));
3619+
cells += fundCell('净利率 Margin', pct(f.profitMargin), deltaClass(f.profitMargin));
3620+
cells += fundCell('下次财报 Earnings', earn);
3621+
return '<div class="section-label"><span>基本面与流动性 · Fundamentals</span><span class="muted">' + srcLabel + (f.foreignListed ? ' · 海外上市' : '') + '</span></div><div class="fund-grid">' + cells + '</div>';
3622+
}
35843623
function renderTrackRecord() {
35853624
var el = $("trackBody");
35863625
if (!el) return;
@@ -3615,7 +3654,7 @@ function buildHtmlV2(data) {
36153654
? '<div class="focus-px ' + deltaClass(chg) + '">' + formatNumber(px.last_close, 2) + ' ' + html(px.currency || '') + ' <span class="focus-chg">' + formatPct(chg) + '</span></div>'
36163655
: '<div class="focus-px muted">暂无价格</div>';
36173656
el.innerHTML =
3618-
'<div class="focus-head"><div class="focus-id"><span class="focus-ticker">' + row.ticker + '</span><span class="focus-name muted">' + html(px.symbol || row.ticker) + ' · ' + html(row.primary_theme) + '</span></div>' +
3657+
'<div class="focus-head"><div class="focus-id"><span class="focus-ticker">' + row.ticker + '</span><span class="focus-name muted">' + html(px.symbol || row.ticker) + ' · ' + html(row.primary_theme) + (row.fundamentals && row.fundamentals.foreignListed ? ' <span class="foreign-pill">海外</span>' : '') + '</span></div>' +
36193658
pxHtml + '</div>' +
36203659
trackBadge(row) +
36213660
combinedChart(row, true) +
@@ -3695,6 +3734,7 @@ function buildHtmlV2(data) {
36953734
'<div class="mini-metric"><div class="mini-label">7D Momentum</div><div class="mini-value ' + deltaClass(row.velocity) + '">' + (row.velocity > 0 ? "+" : "") + formatNumber(row.velocity) + '</div></div>' +
36963735
'<div class="mini-metric"><div class="mini-label">' + (isFullWindow() ? "3M Price" : windowDays() + "D Price") + '</div><div class="mini-value ' + deltaClass(row.price.change_pct) + '">' + formatPct(row.price.change_pct) + '</div></div>' +
36973736
'</div>' +
3737+
fundGrid(row.fundamentals) +
36983738
'<div class="section-label"><span>最新来源样本</span><span class="muted">中文摘要 + 英文原文</span></div>' +
36993739
'<div class="sample-list">' + samples + '</div>';
37003740
renderFocus(row);

0 commit comments

Comments
 (0)