作者:Joel Greenblatt 首次出版:2005年 核心策略:魔法公式——一个简单的、机械的量化价值投资策略,结合高 earnings yield 与高 return on capital 来识别以便宜价格购买的优秀公司。
Joel Greenblatt 的 genius 在于蒸馏。他提取了价值投资的本质——以低于平均价格购买高于平均的业务——并将其简化为两个可机械应用的可衡量变量。魔法公式设计得足够简单,让个人投资者能够实施,但足够严格,足以长期跑赢专业基金经理。
价值投资有效,因为市场在短期内经常是非理性的。价格因情绪反应、机构约束和行为偏见而偏离内在价值。魔法公式通过系统识别以下公司来利用这一点:
组合至关重要。便宜但糟糕业务的公司(value traps)被 return on capital 要求过滤掉。贵但优秀的公司(growth traps)被 earnings yield 要求过滤掉。只有在两个维度都得分良好的公司才能通过筛选。
| 因素 | 解释 |
|---|---|
| 短期低效 | 市场因对坏消息的反应、忽视、机构抛售压力在1-3年内错误定价股票 |
| 长期有效 | 在3-5年内,价格随着基本面变得不可否认而趋向内在价值 |
| 行为障碍 | 公式有效恰恰是因为它令人不舒服——它迫使你购买不受喜欢的股票并持有 through underperformance |
| 简单性障碍 | 大多数专业人士无法向董事会和客户 justification 一个"简单"的策略——复杂性偏见创造机会 |
| 因素 | 指标 | 它衡量什么 | 为什么重要 |
|---|---|---|---|
| Earnings Yield | EBIT / 企业价值 | 相对于盈利力量股票有多便宜 | 识别 bargain 价格 |
| Return on Capital | EBIT / (净营运资本 + 净固定资产) | 业务将投入资本转化为盈利的效率 | 识别优秀业务 |
Greenblatt 故意避免传统 P/E 比率因为:
P/E 问题:
EBIT/EV 优势:
传统 ROE 被杠杆扭曲——公司可以通过承担债务来拥有高 ROE。Greenblatt 的资本回报使用有形运营资产:
Earnings Yield = EBIT / 企业价值
其中:
EBIT:
企业价值:
| Earnings Yield | 解读 |
|---|---|
| > 15% | 非常便宜 — strong bargain candidate |
| 10-15% | 便宜 — good value territory |
| 7-10% | 中等 — fair value range |
| 4-7% | 贵 — growth expectations priced in |
| < 4% | 非常贵 — requires high growth to justify |
将 earnings yield 与无风险利率(10年期国债收益率)比较:
Return on Capital = EBIT / (净营运资本 + 净固定资产)
其中:
分母代表业务运营所需的有形资本:
| Return on Capital | 解读 |
|---|---|
| > 50% | 卓越 — likely has a strong moat (brand, network effects, or asset-light model) |
| 25-50% | 优秀 — strong competitive position |
| 15-25% | 良好 — above-average business quality |
| 10-15% | 平均 — typical business |
| < 10% | 低于平均 — commodity business or competitive challenges |
业务持续高回报有形资本告诉你:
这是魔法公式的机械核心:
第1步:从符合条件的可投资 universe 开始(所有满足最低标准的股票)
第2步:按 Earnings Yield 排名所有公司(最高 = 排名1,最低 = 排名N)
第3步:按 Return on Capital 排名所有公司(最高 = 排名1,最低 = 排名N)
第4步:计算综合排名 = Earnings Yield 排名 + Return on Capital 排名
第5步:按综合排名排序(最低综合排名 = 最佳总分)
第6步:选择 top 20-30 家公司
在排名前排除:
| 排除类别 | 原因 |
|---|---|
| 金融公司(银行、保险、REITs) | 它们的资本结构使 EBIT/EV 和 ROC 毫无意义 |
| 公用事业 | 受监管回报使 ROC 分析不可靠 |
| 外国 ADR | 会计差异和数据质量问题 |
| 市值 < $5000万的公司(或 < $2亿 for conservative approach) | 流动性和数据可靠性问题 |
| EBIT 为负的公司 | 根据定义无法按 earnings yield 排名 |
| 公司 | EY 排名 | ROC 排名 | 综合排名 | 选中? |
|---|---|---|---|---|
| 公司 A | 5 | 12 | 17 | 是(Top 30) |
| 公司 B | 150 | 2 | 152 | 否(好但贵) |
| 公司 C | 3 | 200 | 203 | 否(便宜但糟糕) |
| 公司 D | 8 | 8 | 16 | 是(Top 30) |
| 公司 E | 25 | 5 | 30 | 是(Top 30) |
公式自然避免:
时机 — 分批入场:
Greenblatt 建议随着时间构建投资组合以避免单点时间风险:
替代 — 一次性入场:
出场纪律完全是机械的:
对于 tax-loss positions(亏损股票):
对于盈利仓位:
关键规则:不要仅仅因为你喜欢就持有股票 beyond the rebalancing date。系统是机械的。如果股票一年后仍然是 top-ranked,它会自然被重新购买。
| 方法 | 频率 | 优点 | 缺点 |
|---|---|---|---|
| 年度(推荐) | 每年一次 | 税收效率,低 turnover,少 transaction costs | 全年持有;一些持仓可能恶化 |
| 半年度 | 每年两次 | 更快适应变化的基本面 | 更高 transaction costs,更多 short-term gains |
| 季度 | 每季度四次 | 最响应 | 最高 costs,大多数 taxable events,noise trading |
Greenblatt 的建议:年度重新平衡 with staggered entry(所以你大致每月重新平衡一次,但每个持仓持有约1年)。
Equal weight:向每个持仓分配相等美元金额。
| 投资组合规模 | 股票数 | 每只权重 |
|---|---|---|
| 最小可行 | 20 | 5.0% each |
| 推荐 | 30 | 3.3% each |
| 最大 | 30 | 3.3% each |
为什么 equal weight(而不是基于信念):
公式可能在某些市场条件下产生行业集中投资组合(例如,当油价便宜时许多能源股)。Greenblatt 不建议行业上限,因为集中是发现最佳机会的特点。但是,保守投资者可以选择:
关键理解:魔法公式将在 extended periods 内跑输市场。
这是最大的实际风险——不是亏钱,而是跑输市场:
| 跑输期间 | 预期频率 | 行为挑战 |
|---|---|---|
| 1个季度 | 很常见 | 轻度不适 |
| 1年 | ~25%的年份 | 严重怀疑 |
| 连续2年 | 偶尔发生 | 强烈诱惑放弃 |
| 连续3年 | 稀有但可能 | 大多数投资者在此 capitulate |
成功的投资者是那些在跑输期间保持纪律的人。
Greenblatt 强调公式的优势恰恰来自它创造的不适。它选择的股票不受欢迎、不受欢迎,往往有负面新闻流。购买它们 FEELS wrong。这就是公式有效的原因——大多数人们无法做到。
在开始之前,写下并签署:
"我承诺遵循魔法公式至少3年。我将购买排名最高的股票。我将年度重新平衡。我不会 override the system。我理解公式大约每4年中有1年会跑输。我将仅在3年最低期限后判断结果。"
资本:$100,000 allocated to the Magic Formula strategy。 计划:在3个月内 staggered entry,~每月10只股票,30只股票 total。
| 排名 | 公司 | EY 排名 | ROC 排名 | 综合 | 分配 |
|---|---|---|---|---|---|
| 1 | AutoParts Co | 8 | 3 | 11 | $3,333 |
| 2 | Software Inc | 15 | 1 | 16 | $3,333 |
| 3 | Retail Chain | 4 | 14 | 18 | $3,333 |
| 4 | Pharma Corp | 6 | 15 | 21 | $3,333 |
| 5 | Media Group | 2 | 22 | 24 | $3,333 |
| 6 | Tech Services | 20 | 5 | 25 | $3,333 |
| 7 | Consumer Brand | 12 | 16 | 28 | $3,333 |
| 8 | Industrial Co | 7 | 23 | 30 | $3,333 |
| 9 | Health Devices | 25 | 7 | 32 | $3,333 |
| 10 | Energy Equip | 3 | 30 | 33 | $3,333 |
总投入:$33,330。剩余:$66,670 在货币市场。
重复排名过程。选择接下来的10只股票(可能与第1月重叠如果仍排名靠前)。再投资 $33,330。
重复。投资组合现在持有30只股票,每只约 $3,333。完全投资。
1月 batch(10只股票)的税收aware 重新平衡:
| 股票 | 回报 | 行动 | 税收处理 |
|---|---|---|---|
| AutoParts Co | +22% | 在1月1日之后卖出(长期收益) | 再等2周 |
| Software Inc | +45% | 在1月1日之后卖出(长期收益) | 再等2周 |
| Retail Chain | -30% | 现在卖出(短期损失 for tax benefit) | 立即卖出 |
| Pharma Corp | +8% | 在1月1日之后卖出(长期收益) | 再等2周 |
| Media Group | -15% | 现在卖出(短期损失 for tax benefit) | 立即卖出 |
| Tech Services | +35% | 在1月1日之后卖出(长期收益) | 再等2周 |
| Consumer Brand | +12% | 在1月1日之后卖出(长期收益) | 再等2周 |
| Industrial Co | -5% | 现在卖出(短期损失 for tax benefit) | 立即卖出 |
| Health Devices | +55% | 在1月1日之后卖出(长期收益) | 再等2周 |
| Energy Equip | -20% | 现在卖出(短期损失 for tax benefit) | 立即卖出 |
立即卖出4个输家。等2周卖出6个赢家。用新筛选的当前排名最高的股票替换全部10只。
class MagicFormulaInvestor:
def __init__(self, config):
self.capital = config['initial_capital']
self.target_positions = config.get('target_positions', 30)
self.min_market_cap = config.get('min_market_cap', 200_000_000)
self.rebalance_frequency = 'annual'
self.portfolio = {}
self.purchase_dates = {}
self.excluded_sectors = ['Financials', 'Utilities']
def screen_universe(self, market_data):
universe = []
for stock in market_data.all_stocks():
if stock.market_cap < self.min_market_cap:
continue
if stock.sector in self.excluded_sectors:
continue
if stock.ebit <= 0:
continue
if stock.is_adr:
continue
universe.append(stock)
return universe
def calculate_earnings_yield(self, stock):
excess_cash = max(stock.cash - stock.revenue * 0.03, 0)
enterprise_value = (stock.market_cap + stock.total_debt - excess_cash)
if enterprise_value <= 0:
return 0
return stock.ebit / enterprise_value
def calculate_return_on_capital(self, stock):
net_working_capital = stock.current_assets - stock.current_liabilities
net_fixed_assets = stock.ppe_net
tangible_capital = net_working_capital + net_fixed_assets
if tangible_capital <= 0:
return float('inf')
return stock.ebit / tangible_capital
def rank_stocks(self, universe):
for stock in universe:
stock.earnings_yield = self.calculate_earnings_yield(stock)
stock.return_on_capital = self.calculate_return_on_capital(stock)
ey_sorted = sorted(universe, key=lambda s: s.earnings_yield, reverse=True)
for rank, stock in enumerate(ey_sorted, 1):
stock.ey_rank = rank
roc_sorted = sorted(universe, key=lambda s: s.return_on_capital, reverse=True)
for rank, stock in enumerate(roc_sorted, 1):
stock.roc_rank = rank
for stock in universe:
stock.combined_rank = stock.ey_rank + stock.roc_rank
return sorted(universe, key=lambda s: s.combined_rank)
def select_top_stocks(self, ranked_universe, count):
return ranked_universe[:count]
def execute_staggered_entry(self, market_data, months=3):
stocks_per_batch = self.target_positions // months
capital_per_batch = self.capital / months
for month in range(months):
universe = self.screen_universe(market_data)
ranked = self.rank_stocks(universe)
available = [s for s in ranked if s.ticker not in self.portfolio]
selections = self.select_top_stocks(available, stocks_per_batch)
amount_per_stock = capital_per_batch / len(selections)
for stock in selections:
self.buy(stock, amount_per_stock)
self.purchase_dates[stock.ticker] = market_data.current_date
def annual_rebalance(self, current_date, market_data):
sells_now = []
sells_later = []
for ticker, position in self.portfolio.items():
purchase_date = self.purchase_dates[ticker]
holding_period = (current_date - purchase_date).days
if holding_period >= 350:
if position.unrealized_return < 0:
sells_now.append(ticker)
else:
sells_later.append(ticker)
for ticker in sells_now:
self.sell(ticker)
for ticker in sells_later:
self.schedule_sell(ticker, days_until=14)
universe = self.screen_universe(market_data)
ranked = self.rank_stocks(universe)
replacements_needed = len(sells_now)
available = [s for s in ranked if s.ticker not in self.portfolio]
new_stocks = self.select_top_stocks(available, replacements_needed)
for stock in new_stocks:
amount = self.capital / self.target_positions
self.buy(stock, amount)
"魔法公式有效因为它是一种系统性方式购买高于平均的业务 at below-average prices。"
"公式不会每年有效。这是 guarantee。而这正是它长期有效的原因。"
"在没有任何想法的情况下选择个别股票就像在炸药工厂里挥舞燃烧的火柴。你可能活下来,但你还是个白痴。"
"如果公式每年都有效,每个人都会使用它,它就会停止有效。跑输期间是保持其有效性的原因。"
"投资的秘密是找出一些东西的价值——然后少付很多。"
"大多数人排名事物:他们想要最好的。魔法公式只是使用两个简单的排名并结合它们。就这样。它优雅是因为它省略了什么。"
"公式在三年或更长的每个可衡量期间都跑赢了市场平均。但很少有人有纪律坚持它。"
"Ben Graham 发现廉价股票 as a group 跑赢市场平均。魔法公式只是添加了洞察:廉价的好业务做得更好。"
"你可以机械地遵循公式,或者将公式的输出作为你自己研究的起点。无论哪种方式,它都给你一个巨大的 head start。"
"在短期内,Market 先生是一台投票机。长期来看,他是一台称重机。魔法公式旨在购买称重机最终将奖励的东西。"
基于 Joel Greenblatt 的"The Little Book That Beats the Market"实施。本规范捕获了完整的魔法公式系统——一种机械的、量化价值投资策略,专为寻求通过纪律性、基于规则的投资跑赢市场的个人投资者设计。