Research Lab研究实验室 · Valuation · Out-of-sample study估值 · 样本外研究
A forward multiple needs a forward history前瞻倍数,需要前瞻的历史
Our DCA valuation pages score a company by asking where today’s multiples sit in its own history — a percentile. That only works if today’s number and the history are the same kind of number. Rank a forward P/E against a history of trailing P/Es and every growing company looks cheap, and nothing on the page looks wrong: the output is still a tidy number between 0 and 100.
我们的定投估值页用百分位给公司的估值打分:看今天的各项倍数处在这家公司自身历史的什么位置。前提是,今天的数字和历史必须是同一种数字。把前瞻市盈率放进一段滚动市盈率的历史里排名,每一家成长型公司都会显得便宜——而页面上看不出任何异常:结果依然是一个介于 0 到 100 之间、整整齐齐的数字。
TL;DR要点
On 20 September 2026 we measured, across 22 tickers, what ranking today’s forward P/E against a company’s trailing history does to its percentile: the median shift was −11.3 points, and 11 of the 22 landed at exactly 0.0 — below every week of that history. So we built a forward history, dividing each past week’s price by the earnings that fiscal year turned out to deliver. Ranked against it, the median shift shrinks to −3.5 points, and names move in both directions. It still leans slightly cheap, and every rank built this way is labelled.
2026 年 9 月 20 日,我们在 22 只股票上测量了:把今天的前瞻市盈率放进公司的滚动市盈率历史里排名,百分位会怎样变化。结果是中位数变化 −11.3 个百分位点,22 只里有 11 只正好落在 0.0——低于那段历史里的每一周。于是我们重建了一段前瞻历史:用每个历史周的股价,除以那个财年事后实际交出的盈利。放进这段历史里排名,中位数变化缩小到 −3.5 个百分位点,而且有升有降。它仍然略微偏向便宜,所有这样得出的排名都有标注。
Two P/E ratios, one company同一家公司,两个市盈率
A trailing P/E divides the price by the last twelve months of reported earnings. A forward P/E divides it by analysts’ consensus estimate for the coming year. They are different numbers about the same company, and for anything growing the forward one is lower.
Our DCA engine ranks each multiple against the company’s own history — a percentile, where 0 is the cheapest it has been and 100 the dearest. The score built from those percentiles sets the pace of a recurring contribution (it never answers whether to buy), so a bias here does not stay on the page: it changes how much a plan puts in.
The history the engine has is trailing: about five years of weekly prices, each divided by the statements public that week. Nothing sells back yesterday’s consensus, so the forward series we now bank daily started empty and is months from useful. The shortcut is to rank today’s forward P/E in the trailing history anyway — and for a growing company it reads cheap every single time.
滚动市盈率用股价除以过去十二个月已公布的盈利;前瞻市盈率则除以分析师对未来一年盈利的一致预期。两者是同一家公司的两个不同数字,而只要公司在增长,前瞻市盈率就更低。
我们的定投估值引擎,把每一项倍数放进这家公司自身的历史里排名,得到一个百分位:0 是历史上最便宜的时候,100 是最贵的时候。由这些百分位构成的分数决定定期投入的节奏(它从不回答“该不该买”),所以这里的偏差不会只停留在页面上:它会改变一份定投计划实际投入的金额。
引擎手里的历史是滚动口径的:约五年的周度股价,每一周都除以当周已经公开的财报数据。没有人出售昨天的一致预期,所以我们如今每天存下的前瞻序列从零开始,距离可用还要好几个月。捷径是干脆把今天的前瞻市盈率放进滚动历史里排名——而对成长型公司来说,结果每一次都偏便宜。
Building a forward history重建一段前瞻历史
First, a trailing history that does not cheat
The forward history is built from the same weekly prices and the same annual reports as the trailing one, so the trailing one has to be right first.
- Point-in-time. A fiscal year ending 31 December is not knowable on 1 January; the 10-K lands 60–90 days later. Pricing January on it is look-ahead bias — using information nobody had at the time — so each annual report is held back 90 days and each quarterly one 45.
- Each week is ranked against the weeks up to it, never the ones after. Ranking against the whole sample is what makes a backtest look prescient: a 2022 trough only registers as a trough because 2024 is in the denominator.
- At least 60 observations before any percentile is reported. A rank over eleven points can only return eleven answers, and will happily say 100.0.
- Quarterly earnings are summed into a trailing twelve months. A quarterly statement reports that quarter’s EPS, and dividing by it reports a P/E about four times too high — for a mega-cap, 90x instead of 23x — while the history grows a sawtooth that reads as multiple expansion.
- Growth for PEG is measured against the report about a year back, found in a 300–430 day window, not the adjacent one. The neighbour is often a quarter back, which read ~3.7% where the truth was ~10% and inflated PEG about 2.7x.
The last two traps were caught by rendering the numbers while the engine was being built; it shipped on 11 September 2026.
Then, the year ahead
The forward history divides each past week’s price by the earnings that fiscal year turned out to deliver — the same quantity today’s consensus forward EPS is an estimate of — using the same prices and annual reports, with nothing extra to fetch. The year ahead at any week is the earliest fiscal year whose annual report was not yet public: keyed on when the report landed rather than when the year ended, because until its 10-K arrives the forward figure still refers to that year. For price to free cash flow the share count is the one in force at the time; a forward multiple has a forward denominator, not a forward share count.
This deliberately uses hindsight: the denominator is knowledge nobody had that week. It is not a backtest. The question a forward multiple asks is “is 9x next year’s earnings cheap for this company?”, and the only honest denominator for the historical half of that comparison is what next year’s earnings actually were. It lives in a separate function, reconstruct_forward(), so the look-ahead cannot leak into the trailing series, whose whole value is that it has none.
先要有一段不作弊的滚动历史
前瞻历史和滚动历史用的是同一批周度股价、同一批年报,所以首先得把滚动历史做对。
- 时点一致。12 月 31 日结束的财年,1 月 1 日还无从知晓;10-K 年报要在 60–90 天之后才发布。拿它给 1 月定价就是前视偏差——用了当时没人拥有的信息——所以每份年报推迟 90 天、每份季报推迟 45 天才计入。
- 每一周只和截至当周的历史比较,绝不和之后的周比较。拿整段样本来排名,正是回测显得“未卜先知”的原因:2022 年的低谷之所以算低谷,只因为分母里有 2024 年。
- 至少 60 个观测值才报告百分位。在 11 个点上排名,只可能给出 11 种答案,而且会毫不犹豫地报出 100.0。
- 季度盈利要加总成滚动十二个月。季报给出的是单季每股收益,直接拿来做分母,市盈率会高出约四倍——对一家超大市值公司来说,是 90 倍而不是 23 倍——历史曲线上还会长出一排锯齿,看上去像真实的估值扩张。
- PEG 里的增长率,要和大约一年前的那份财报比,在 300–430 天的窗口里寻找,而不是取相邻的那一份。相邻的往往只隔一个季度,曾把增长读成约 3.7%,而真实值约为 10%,使 PEG 虚高约 2.7 倍。
最后这两个陷阱,都是在搭建估值引擎时把数字实际渲染出来才发现的;引擎于 2026 年 9 月 11 日上线。
然后,是“未来一年”
前瞻历史用每个历史周的股价,除以那个财年事后实际交出的盈利——这正是今天一致预期的前瞻 EPS 所要估计的那个量——用的是同样的股价和年报,不需要额外抓取任何数据。任意一周的“未来一年”,是那一周尚未公布年报的最早一个财年:以年报实际发布的日期为准,而不是以财年结束日为准,因为在 10-K 发布之前,前瞻数字指的仍是这个财年。计算市价/自由现金流时,股本取当时有效的数值;前瞻倍数“前瞻”的是分母,而不是股本。
这里刻意用了后见之明:分母是那一周没人知道的信息。但这不是回测。前瞻倍数要回答的问题是“以明年盈利 9 倍的价格,对这家公司来说算不算便宜”,而这个比较的历史那一半,唯一诚实的分母就是明年盈利的实际值。它放在一个单独的函数 reconstruct_forward() 里,这样后见之明就不会渗进滚动序列——那条序列的全部价值,就在于毫无前视。
What we found我们发现了什么
Today’s forward P/E, ranked in its trailing history and in its rebuilt forward history:
今天的前瞻市盈率,分别放进滚动历史与重建的前瞻历史里排名:
Shift in percentile points from the like-for-like trailing ranking; 22 tickers, 20 September 2026. The medians understate the difference: the naive version put 11 of the 22 at exactly 0.0, where the forward multiple sat below every trailing observation and the percentile saturated, discriminating nothing. That is not a bias that could be calibrated out. It is the ranking thrown away for half the sample.
Against the reconstructed forward history the ranking stays informative, and it moves both ways:
相对同口径滚动排名的百分位点变化;22 只股票,2026 年 9 月 20 日。中位数低估了两者的差距:直接排名时 22 只里有 11 只正好落在 0.0,前瞻倍数低于每一个滚动观测值,百分位触底饱和,什么也区分不了。这不是一种可以校准掉的偏差,而是半数样本的排名被整个丢掉了。
放进重建的前瞻历史里排名,结果依然有信息量,而且有升有降:
| Ticker股票 | Shift against the trailing ranking相对滚动排名的变化 |
|---|---|
| CEG | +10.9 · dearer+10.9 · 更贵 |
| MEDP | +35.6 · dearer+35.6 · 更贵 |
| T | +43.7 · dearer+43.7 · 更贵 |
| ADBE, ORCL, NVDA | cheaper更便宜 |
That is a re-ranking — which is what was wanted — rather than one discount applied to everything that grows.
这是一次重新排序——这正是我们想要的——而不是给所有成长股统一打一个折扣。
What the page does now页面现在怎么做
Each factor takes the first forward history that can rank it, in a fixed order, and a factor with neither stays exactly where it was:
- Banked — one row per ticker per day of what the consensus actually was. Point-in-time and the most honest; months from usable.
- Reconstructed — the hindsight denominator above. Available on first page load.
- Trailing — today’s trailing multiple in its own trailing history, the fallback the whole design rests on.
The page never hides which one produced a rank. A factor ranked on the reconstruction is marked “(hindsight)” beside its multiple, and the line under the score reads “from reconstructed forward history (hindsight denominator)”. No series is spliced from two histories: each rank comes from exactly one.
The same day, every ticker moved onto this basis at once, because the forward history needs only the prices and annual reports each ticker’s reconstruction already holds. Checked for ADBE against a series rebuilt from scratch, the derived one matched exactly: 209 weekly P/E points and 156 P/FCF points.
现在,每个因子按固定顺序,采用第一段能为它排名的前瞻历史;两段都没有的因子,保持原样不动:
- 逐日累积的历史——每只股票每天一行,记录当时真实的一致预期。时点一致、最诚实,但还要几个月才能用。
- 重建的历史——即上文分母用事后实际值的那一段。页面第一次打开就能用。
- 滚动历史——今天的滚动倍数在自身滚动历史中的排名,是整个设计赖以兜底的后备。
页面从不隐瞒某个排名出自哪一段历史。按重建历史排名的因子,倍数旁会标注「(事后口径)」,分数下方的说明写着「基于重建的预测口径历史(分母用事后实际值)」。不同的历史永远不会拼接成一条序列:每个排名只出自其中一段。
同一天,所有股票一起切换到了这一口径,因为前瞻历史只需要每只股票的重建数据里已有的股价和年报。我们用 ADBE 与从头重建的序列做了核对,推导结果完全一致:209 个周度市盈率数据点,156 个市价/自由现金流数据点。
What it still gets wrong它仍然存在的偏差
- It still leans cheap. The median −3.5 and mean −7.3 are not zero. One cause is structural: consensus runs more optimistic than outturn, so today’s denominator is a little larger than the historical ones — the gap between an estimate and a result, not between two measures of earnings.
- The most recent year is missing from the distribution. The last ~1 year of weeks drops out, because no fiscal year after them has reported yet, so today’s value is ranked against a history that lacks the current regime.
- It is hindsight. The history records what buyers paid for the earnings that actually arrived, not what they thought they were paying. The banked series takes over factor by factor once it holds the 60 observations a rank needs.
- One day, 22 names. Enough to show the direction and rough size of the bias, not a precise estimate of it.
- A percentile compares a company with its own past. A low one means cheap against its own history on a like-for-like basis. It is not a ranking between companies, and not a recommendation to buy or sell anything.
- 它仍然偏向便宜。中位数 −3.5、均值 −7.3 都不是零。其中一个原因是结构性的:一致预期通常比实际结果乐观,所以今天的分母略大于历史上的分母——这是“预期与结果”之间的差距,而不是“两种盈利口径”之间的差距。
- 最近一年不在分布里。最后大约一年的周数据会被剔除,因为其后的财年还没有公布年报,所以今天的数值是放在一段缺少当前阶段的历史里排名的。
- 它是后见之明。这段历史记录的是买家为最终兑现的盈利付出的价格,而不是他们当时以为自己付出的价格。逐日累积的序列一旦某个因子攒够排名所需的 60 个观测值,就会逐个因子接替它。
- 一天、22 只股票。足以说明偏差的方向和大致规模,但不是对它的精确估计。
- 百分位比较的是公司与它自己的过去。百分位低,意味着在同口径下相对自身历史便宜;它不是公司之间的排名,也不构成买入或卖出任何资产的建议。
Check it yourself亲自验证
Open any ticker’s valuation page — NVDA’s, say. In the factor table, a multiple ranked on the reconstructed forward history reads “forward (hindsight)”, with its trailing (TTM) value beside it; one with a forward value but no forward history to rank it against says “ranked on TTM”. Under the headline score, a “Ranked on” line states the basis and its source. The ranked overview scores every tracked name the same way. The data is today’s, not 20 September’s.
What pins it: tests/test_dca_history.py holds 111 tests. Thirteen cover the forward reconstruction — the year-ahead denominator, keying on publication rather than period end, the recent weeks dropping out, quarterly reports excluded, the point-in-time share count, banked winning over reconstructed — and the look-ahead guards have their own: 6 on the publication lags, 4 on ranking only against the past, 8 on the four-quarter sums and 4 on the year-ago growth window.
打开任意一只股票的估值页,比如 NVDA。在因子表里,按重建前瞻历史排名的倍数会标为「预测(事后口径)」,旁边列出它的滚动(TTM)数值;有前瞻数值、但还没有前瞻历史可供排名的因子,则标为「按滚动值排名」。主分数下方的「排名口径」一行,说明分数的口径及其来源。排名总览页用同样的方式为每只跟踪的股票打分。页面上是最新数据,而不是 9 月 20 日的数据。
测试锁定:tests/test_dca_history.py 共 111 个测试。其中 13 个针对前瞻重建——“未来一年”的分母、以年报发布日而非财年结束日为准、最近几周因尚无实际值而剔除、排除季报、时点一致的股本、逐日累积优先于重建——前视偏差的各道防线也各有测试:发布滞后 6 个,只与过去比较 4 个,四季度加总 8 个,一年期增长窗口 4 个。
Sources: the forward reconstruction and its 22-ticker measurement (fec1338) and its extension to every ticker (a6ae58d), both 20 September 2026; the point-in-time rules and the two reconstruction traps, from the engine’s release (399f8fd, 11 September 2026); method notes in ystocker/dca_history.py and ystocker/dca.py.
资料来源:前瞻重建及其 22 只股票的测量(fec1338)与推广到全部股票(a6ae58d),均为 2026 年 9 月 20 日;时点一致规则与两个重建陷阱,来自估值引擎的上线(399f8fd,2026 年 9 月 11 日);方法说明见 ystocker/dca_history.py 与 ystocker/dca.py。