从菜市场到华尔街:用量化思维解锁价值投资的密码
关键词
量化价值投资 | 投资组合优化 | 因子模型 | Markowitz均值-方差 | 回测系统 | 风险控制 | 夏普比率
摘要
你有没有过这样的经历:在菜市场挑菜时,会反复比较“新鲜度”和“价格”——想选最新鲜的,又不想花太多钱?其实,这和价值投资的核心逻辑惊人地相似:买“质地好”且“估值低”的资产。只不过,华尔街的投资者把“新鲜度”换成了“ROE(净资产收益率)”,把“价格”换成了“P/E(市盈率)”,并用数学模型代替了直觉判断。
本文将带你从“菜市场逻辑”出发,一步步拆解量化价值投资的底层框架:如何用数据筛选“便宜的好公司”?如何通过数学模型优化投资组合,平衡“收益”与“风险”?如何用代码实现策略并验证效果?最终,你将掌握一套可复制的量化投资方法,把“拍脑袋”的投资变成“可计算”的科学。
一、背景介绍:为什么需要量化价值投资?
1.1 价值投资的“老问题”
价值投资的理念源于本杰明·格雷厄姆的《证券分析》(1934年),核心思想是“买低于内在价值的资产”。比如,一家公司的内在价值是10元,而当前股价是8元,就值得买入——这像极了菜市场里“捡漏”新鲜又便宜的蔬菜。
但传统价值投资有个致命缺陷:依赖主观判断。比如,“内在价值”的计算需要预测未来现金流,而不同分析师的预测结果可能相差巨大;“估值低”的标准也很模糊——有人认为P/E<10是低估值,有人觉得P/E<15才合理。这种主观性导致很多投资者“买了看似便宜的垃圾股”(比如绩差股的P/E很低,但根本没有盈利),或者“错过真正的好公司”(比如成长股的P/E很高,但未来盈利增长能覆盖估值)。
1.2 量化价值投资的“新解法”
量化价值投资的出现,就是为了用数据驱动和模型决策解决传统价值投资的主观问题。它的核心逻辑可以总结为:
- 用“因子”替代“主观判断”:把“质地好”转化为可量化的指标(比如ROE>15%、净利润增速>10%),把“估值低”转化为P/E<行业均值、P/B<2等指标;
- 用“组合优化”替代“单一选股”:通过数学模型计算各股票的权重,平衡“分散风险”和“集中收益”;
- 用“回测”替代“事后诸葛亮”:用历史数据验证策略的有效性,避免“纸上谈兵”。
1.3 谁需要读这篇文章?
- 程序员:想把编程技能应用到投资领域,实现“代码赚钱”;
- 金融从业者:想提升投资决策的科学性,减少人为误差;
- 普通投资者:想告别“听消息炒股”,掌握一套可复制的投资方法。
1.4 核心挑战
本文要解决的核心问题是:如何将价值投资的逻辑转化为可量化、可验证的策略,并通过组合优化实现“风险可控的收益最大化”。具体来说,你将学会:
- 如何用因子筛选“便宜的好公司”?
- 如何用Markowitz模型计算最优投资组合?
- 如何用代码回测策略,避免“过拟合”?
二、核心概念解析:用菜市场比喻搞懂量化价值投资
为了让复杂概念更易理解,我们用“菜市场买菜”的场景类比量化价值投资的核心环节:
菜市场场景 | 量化价值投资对应环节 | 关键指标/工具 |
---|---|---|
选“新鲜的菜”(质地好) | 选“质地好的公司” | ROE(净资产收益率)、净利润增速 |
选“便宜的菜”(估值低) | 选“估值低的公司” | P/E(市盈率)、P/B(市净率) |
搭配“蔬菜+肉类+主食”(分散) | 构建“分散化投资组合” | 协方差矩阵、Markowitz模型 |
用“秤”称重量(验证) | 用“回测”验证策略有效性 | 夏普比率、最大回撤 |
2.1 因子:量化价值投资的“秤”
在菜市场,你用“眼睛看新鲜度”“手摸硬度”判断菜的质地;在量化投资中,你用“因子”(Factor)判断公司的质地和估值。
(1)什么是因子?
因子是能解释股票收益差异的可量化指标。比如:
- 价值因子:衡量估值高低,如P/E(股价/每股收益)、P/B(股价/每股净资产)、股息率(股息/股价);
- 质量因子:衡量公司质地,如ROE(净利润/净资产)、净利润增速(同比增长)、资产负债率(负债/资产);
- 动量因子:衡量价格趋势,如过去6个月的涨幅;
- 流动性因子:衡量交易活跃度,如日均成交量。
(2)因子的“有效性”:为什么低P/E股票能赚钱?
根据有效市场假说,超额收益来自“承担未被定价的风险”。比如,低P/E股票通常是市场忽略的“冷门股”,投资者需要承担“流动性风险”或“业绩修复风险”,因此能获得超额收益。
举个例子:假设市场上有两家公司,A公司P/E=10(每股收益1元,股价10元),B公司P/E=20(每股收益1元,股价20元)。如果两家公司的盈利增速相同,那么A公司的“性价比”更高——就像菜市场里“10元一斤的新鲜菜”比“20元一斤的同品质菜”更值得买。
(3)因子筛选流程:从“全市场”到“候选池”
量化价值投资的第一步,是用因子从全市场股票中筛选出“便宜的好公司”。流程如下(用Mermaid画流程图):
graph TD
A[全市场股票] --> B[筛选条件1:质量因子(ROE>15%、净利润增速>10%)]
B --> C[筛选条件2:价值因子(P/E<行业均值、P/B<2)]
C --> D[筛选条件3:流动性因子(日均成交量>1亿)]
D --> E[候选股票池(约50-100只)]
说明:
- 质量因子确保公司“质地好”(能持续盈利);
- 价值因子确保公司“估值低”(价格低于内在价值);
- 流动性因子确保股票“能买能卖”(避免买了卖不出去)。
2.2 投资组合优化:像搭配早餐一样平衡“营养”与“热量”
选好了“候选股票池”,接下来要解决的问题是:每只股票买多少? 比如,候选池里有10只股票,是平均分配资金(每只10%),还是集中买几只?
这就像搭配早餐:你需要平衡“营养”(收益)和“热量”(风险)——只吃蛋糕(高收益高风险)会胖,只吃蔬菜(低风险低收益)会饿,最好的选择是“蛋糕+蔬菜+牛奶”的组合(收益适中,风险适中)。
(1)投资组合的“营养”与“热量”
- 预期收益(E®):组合的平均收益,等于各股票预期收益的加权和:
E(rp)=∑i=1nwiE(ri) E(r_p) = \sum_{i=1}^n w_i E(r_i) E(rp)=i=1∑nwiE(ri)
其中,wiw_iwi是第iii只股票的权重(∑wi=1\sum w_i=1∑wi=1),E(ri)E(r_i)E(ri)是第iii只股票的预期收益。 - 风险(σ_p):组合的波动,用方差衡量,等于各股票协方差的加权和:
σp2=∑i=1n∑j=1nwiwjCov(ri,rj) \sigma_p^2 = \sum_{i=1}^n \sum_{j=1}^n w_i w_j \text{Cov}(r_i, r_j) σp2=i=1∑nj=1∑nwiwjCov(ri,rj)
其中,Cov(ri,rj)\text{Cov}(r_i, r_j)Cov(ri,rj)是股票iii和jjj的协方差(衡量两者收益的相关性)。
(2)Markowitz均值-方差模型:找到“最优早餐组合”
1952年,哈里·马科维茨(Harry Markowitz)提出了均值-方差模型,核心思想是:在给定风险水平下,最大化预期收益;或在给定预期收益水平下,最小化风险。
用早餐比喻,就是:
- 如果你能接受“中等热量”(风险),那么选“营养最高”(收益)的组合;
- 如果你想“营养最低”(收益)不低于某个值,那么选“热量最低”(风险)的组合。
(3)有效前沿:所有“最优组合”的集合
通过均值-方差模型计算,我们可以得到有效前沿(Efficient Frontier)——这是一条曲线,曲线上的每个点都是“给定风险下收益最高”或“给定收益下风险最低”的组合。
比如,假设候选池中有3只股票:
- 股票A:预期收益10%,风险(σ)20%;
- 股票B:预期收益8%,风险15%;
- 股票C:预期收益6%,风险10%。
通过调整权重,我们可以得到不同的组合:
- 组合1(全A):收益10%,风险20%;
- 组合2(A+B=50%+50%):收益9%,风险17%(假设协方差为0.5);
- 组合3(B+C=50%+50%):收益7%,风险12%;
- 组合4(全C):收益6%,风险10%。
这些组合的有效前沿是一条从组合4到组合1的曲线,曲线上的每个点都是“最优”的。
三、技术原理与实现:用Python构建量化价值投资策略
3.1 工具准备
- 数据获取:用
yfinance
获取股票历史数据(免费); - 因子计算:用
pandas
处理财务数据; - 组合优化:用
cvxpy
(凸优化库)求解Markowitz模型; - 回测:用
backtrader
验证策略有效性。
3.2 步骤1:获取数据与计算因子
(1)获取股票列表
首先,我们需要从全市场中筛选出符合条件的股票。比如,选沪深300成分股(代表大盘蓝筹股),或者中证500成分股(代表中盘成长股)。
用yfinance
获取沪深300成分股列表(以美股为例,A股可以用tushare
或baostock
):
import yfinance as yf
import pandas as pd
# 获取沪深300成分股(示例用美股SP500代替)
sp500 = yf.Ticker("^GSPC")
sp500_components = sp500.components
tickers = sp500_components.index.tolist()[:100] # 取前100只股票
(2)计算因子
接下来,计算每只股票的质量因子(ROE、净利润增速)和价值因子(P/E、P/B)。
以ROE为例,ROE=净利润/净资产,我们可以用yfinance
获取财务数据:
def calculate_roe(ticker):
try:
stock = yf.Ticker(ticker)
financials = stock.financials
balance_sheet = stock.balance_sheet
# 净利润(最近12个月)
net_income = financials.loc["Net Income"].iloc[0]
# 净资产(最近季度)
equity = balance_sheet.loc["Total Stockholder Equity"].iloc[0]
roe = net_income / equity
return roe
except:
return None
# 计算所有股票的ROE
roe_data = pd.Series({ticker: calculate_roe(ticker) for ticker in tickers})
roe_data = roe_data.dropna() # 剔除数据缺失的股票
同理,计算P/E(股价/每股收益):
def calculate_pe(ticker):
try:
stock = yf.Ticker(ticker)
pe = stock.info["trailingPE"] # trailing P/E( trailing 12 months)
return pe
except:
return None
pe_data = pd.Series({ticker: calculate_pe(ticker) for ticker in tickers})
pe_data = pe_data.dropna()
(3)筛选候选股票池
根据因子筛选条件,比如:
- ROE>15%(质量好);
- P/E<行业均值(估值低);
- 日均成交量>1亿(流动性好)。
# 计算行业均值(假设用GICS行业分类)
sp500_components["industry"] = sp500_components["GICS Sector"]
industry_pe = sp500_components.merge(pe_data, left_index=True, right_index=True)
industry_pe_mean = industry_pe.groupby("industry")["PE"].mean()
# 筛选条件
selected_stocks = roe_data[roe_data > 0.15].index # ROE>15%
selected_stocks = pe_data[pe_data < industry_pe_mean[pe_data.index.map(sp500_components["industry"])]] # P/E<行业均值
selected_stocks = selected_stocks[selected_stocks.index.isin(selected_stocks)] # 交集
# 筛选流动性(示例用最近30天日均成交量>1亿)
def calculate_volume(ticker):
try:
stock = yf.Ticker(ticker)
volume = stock.history(period="1mo")["Volume"].mean()
return volume
except:
return None
volume_data = pd.Series({ticker: calculate_volume(ticker) for ticker in selected_stocks.index})
selected_stocks = volume_data[volume_data > 1e8].index # 日均成交量>1亿
print(f"候选股票池数量:{len(selected_stocks)}")
3.3 步骤2:用Markowitz模型优化组合权重
(1)计算预期收益与协方差矩阵
预期收益通常用历史平均收益代替(假设未来收益与历史一致),协方差矩阵用历史收益的协方差计算。
import numpy as np
# 获取历史收益数据(最近5年,月频)
def get_monthly_returns(ticker):
try:
stock = yf.Ticker(ticker)
returns = stock.history(period="5y", interval="1mo")["Close"].pct_change().dropna()
return returns
except:
return None
# 收集所有候选股票的月收益
returns_data = pd.DataFrame({ticker: get_monthly_returns(ticker) for ticker in selected_stocks})
returns_data = returns_data.dropna(axis=1) # 剔除数据缺失的股票
# 计算预期收益(月均收益)
expected_returns = returns_data.mean()
# 计算协方差矩阵
cov_matrix = returns_data.cov()
(2)构建优化问题
我们的目标是最大化夏普比率(Sharpe Ratio),即“每承担1单位风险获得的超额收益”:
Sharpe Ratio=E(rp)−rfσp \text{Sharpe Ratio} = \frac{E(r_p) - r_f}{\sigma_p} Sharpe Ratio=σpE(rp)−rf
其中,rfr_frf是无风险利率(比如10年期国债收益率,约3%/年,即0.25%/月)。
用cvxpy
构建优化问题:
import cvxpy as cp
# 变量:各股票的权重(w)
w = cp.Variable(len(expected_returns))
# 约束条件:
# 1. 权重和为1(满仓)
constraints = [cp.sum(w) == 1]
# 2. 权重非负(不允许卖空)
constraints.append(w >= 0)
# 目标函数:最大化夏普比率(等价于最大化(预期收益-无风险利率)/ 风险)
risk_free_rate = 0.03 / 12 # 月无风险利率
expected_excess_returns = expected_returns - risk_free_rate
portfolio_return = expected_excess_returns @ w
portfolio_risk = cp.quad_form(w, cov_matrix) # 方差
sharpe_ratio = portfolio_return / cp.sqrt(portfolio_risk)
# 优化问题:最大化夏普比率
problem = cp.Problem(cp.Maximize(sharpe_ratio), constraints)
problem.solve()
# 输出最优权重
optimal_weights = pd.Series(w.value, index=expected_returns.index)
optimal_weights = optimal_weights[optimal_weights > 1e-4] # 剔除权重极小的股票
optimal_weights = optimal_weights / optimal_weights.sum() # 重新归一化
print("最优投资组合权重:")
print(optimal_weights.round(4))
(3)结果解释
假设候选池中有10只股票,优化后的权重可能是:
股票代码 | 权重(%) |
---|---|
AAPL | 15.2 |
MSFT | 12.8 |
JPM | 10.5 |
… | … |
这些权重的组合,能在“承担一定风险”的前提下,获得最高的夏普比率——就像“早餐组合”中,蛋糕占15%、蔬菜占12%、牛奶占10%,这样既能吃饱,又不会太胖。
3.4 步骤3:回测策略有效性
回测是量化投资的“试金石”——用历史数据验证策略是否能赚钱。我们用backtrader
库进行回测。
(1)构建策略类
import backtrader as bt
class QuantitativeValueStrategy(bt.Strategy):
params = (
("rebalance_month", 6), # 每6个月再平衡一次
("roe_threshold", 0.15), # ROE>15%
("pe_threshold", 15), # P/E<15(示例用固定阈值,实际用行业均值)
)
def __init__(self):
self.month_counter = 0 # 月份计数器
self.stocks = selected_stocks # 候选股票池(从步骤2获取)
def next(self):
# 每6个月再平衡一次
self.month_counter += 1
if self.month_counter % self.params.rebalance_month != 0:
return
# 计算当前因子(ROE、P/E)
roe_data = pd.Series({ticker: calculate_roe(ticker) for ticker in self.stocks})
pe_data = pd.Series({ticker: calculate_pe(ticker) for ticker in self.stocks})
# 筛选符合条件的股票
selected = roe_data[roe_data > self.params.roe_threshold].index
selected = pe_data[pe_data < self.params.pe_threshold].index
selected = selected.intersection(self.stocks)
# 如果没有符合条件的股票,持有现金
if not selected.empty:
# 计算最优权重(用Markowitz模型,省略重复代码)
optimal_weights = self.calculate_optimal_weights(selected)
# 调整仓位:卖出不在选中列表的股票,买入选中的股票
for d in self.getdatanames():
if d not in selected:
self.order_target_percent(d, 0)
else:
self.order_target_percent(d, optimal_weights[d])
else:
# 卖出所有股票,持有现金
for d in self.getdatanames():
self.order_target_percent(d, 0)
def calculate_optimal_weights(self, selected):
# 省略与步骤3.3相同的代码,返回最优权重
pass
(2)运行回测
# 初始化回测引擎
cerebro = bt.Cerebro()
# 添加策略
cerebro.addstrategy(QuantitativeValueStrategy)
# 添加数据(示例用美股数据)
for ticker in selected_stocks:
data = bt.feeds.YahooFinanceData(
dataname=ticker,
fromdate=pd.Timestamp("2018-01-01"),
todate=pd.Timestamp("2023-12-31"),
interval="1mo"
)
cerebro.adddata(data)
# 设置初始资金
cerebro.broker.setcash(100000.0)
# 设置佣金(0.1%)
cerebro.broker.setcommission(commission=0.001)
# 运行回测
print(f"初始资金:{cerebro.broker.getvalue():.2f}")
cerebro.run()
print(f"最终资金:{cerebro.broker.getvalue():.2f}")
# 绘制收益曲线
cerebro.plot(style="candlestick")
(3)回测结果分析
假设回测结果如下:
- 初始资金:100,000元;
- 最终资金:180,000元;
- 年化收益:12%;
- 夏普比率:1.5;
- 最大回撤:15%(即历史上最大亏损幅度)。
对比基准(比如沪深300指数)的年化收益8%、夏普比率1.0,说明量化价值策略在风险可控的前提下,获得了更高的收益。
四、实际应用:常见问题与解决方案
4.1 问题1:因子失效怎么办?
现象:某因子(比如低P/E)在过去10年有效,但最近1年失效(低P/E股票反而下跌)。
原因:
- 市场风格变化(比如从“价值股”转向“成长股”);
- 因子拥挤(太多投资者使用同样的因子,导致超额收益消失)。
解决方案: - 因子轮换:定期更新因子(比如每季度重新筛选有效因子);
- 因子组合:使用多个因子(比如低P/E+高ROE+高股息率),降低单一因子的风险;
- 机器学习:用随机森林、XGBoost等模型预测因子的有效性(比如预测“下一季度哪些因子能带来超额收益”)。
4.2 问题2:回测过拟合怎么办?
现象:策略在历史数据中表现很好,但实盘运行时亏损。
原因:
- 数据 snooping(过度挖掘历史数据中的噪音);
- 参数优化过度(比如为了拟合历史数据,调整参数到“完美”,但未来不适用)。
解决方案: - 交叉验证:将历史数据分成训练集和测试集(比如70%训练,30%测试),用训练集优化参数,用测试集验证效果;
- 正则化:在优化模型中加入正则项(比如L1正则,限制权重的分散度),避免过度集中;
- 样本外测试:用未参与回测的“新鲜数据”验证策略(比如回测用2018-2022年数据,用2023年数据做样本外测试)。
4.3 问题3:流动性风险怎么办?
现象:选中的股票日均成交量很小,买入时导致股价上涨(冲击成本),卖出时导致股价下跌。
原因:
- 股票市值小(比如小盘股);
- 市场关注度低(比如冷门股)。
解决方案: - 筛选流动性因子:在候选池筛选时,加入“日均成交量>1亿”或“流通市值>50亿”的条件;
- 限制单只股票权重:比如单只股票权重不超过5%,避免大额交易影响股价;
- 分批次交易:比如将买入订单分成10笔,在一天内逐步执行,降低冲击成本。
五、未来展望:量化价值投资的“进化方向”
5.1 趋势1:结合机器学习,提升因子有效性
传统量化策略依赖“线性因子模型”(比如Fama-French三因子),而机器学习(比如深度学习、强化学习)能捕捉非线性关系和动态变化。比如:
- 用LSTM预测公司净利润增速(替代传统的“历史均值”);
- 用Transformer分析新闻文本(比如从财报中提取“管理层信心”因子);
- 用强化学习优化组合权重(根据市场变化动态调整)。
5.2 趋势2:大数据与另类数据的应用
传统量化策略用“财务数据”和“价格数据”,而另类数据(比如卫星图像、信用卡交易数据、社交媒体数据)能提供更及时、更全面的信息。比如:
- 用卫星图像分析零售公司的停车场车辆数(预测销售额);
- 用信用卡交易数据分析消费行业的景气度(预测净利润);
- 用社交媒体 sentiment 分析(比如Twitter上的情绪)预测股价波动。
5.3 趋势3:区块链与智能合约的融合
区块链技术能解决量化投资中的“信任问题”和“执行效率”:
- 透明化:用区块链记录策略的回测结果和实盘交易,避免“数据造假”;
- 自动化:用智能合约自动执行交易(比如当股票达到目标价格时,自动卖出);
- 去中心化:用DeFi(去中心化金融)平台进行量化交易,降低交易成本(比如无需券商佣金)。
5.4 潜在挑战
- 技术门槛提高:机器学习和大数据需要更专业的知识(比如编程、统计学、深度学习);
- 监管加强:量化交易的高频率和大额交易可能引发市场波动,监管机构可能出台更严格的规定(比如限制算法交易的频率);
- 竞争加剧:越来越多的投资者使用量化策略,导致超额收益消失(比如“因子拥挤”问题)。
六、总结与思考
6.1 总结要点
- 量化价值投资是数据驱动的价值投资:用因子筛选“便宜的好公司”,用模型优化组合权重,用回测验证策略有效性;
- 核心逻辑是平衡风险与收益:通过Markowitz模型找到“最优组合”,最大化夏普比率;
- 关键工具是编程与数学:用Python处理数据、计算因子、优化组合,用数学模型解释投资逻辑。
6.2 思考问题
- 你认为“价值因子”(比如低P/E)在未来10年还会有效吗?为什么?
- 如果你是一名普通投资者,没有编程基础,如何用量化思维优化自己的投资组合?
- 量化投资中的“风险”除了市场波动,还有哪些?如何控制?
6.3 参考资源
- 书籍:《证券分析》(格雷厄姆)、《量化投资策略》(Ernest Chan)、《Python量化投资》(Yves Hilpisch);
- 论文:《The Cross-Section of Expected Stock Returns》(Fama & French,1992)、《Portfolio Selection》(Markowitz,1952);
- 工具:
yfinance
(数据获取)、cvxpy
(组合优化)、backtrader
(回测)、tushare
(A股数据)。
结尾:从“菜市场”到“华尔街”的距离
其实,量化价值投资并不神秘——它只是把“菜市场挑菜”的逻辑,用数据和模型翻译成了华尔街的语言。你不需要是金融专家,也不需要是编程大师,只要掌握“因子筛选”“组合优化”“回测验证”这三个核心环节,就能把“拍脑袋”的投资变成“可计算”的科学。
下次你去菜市场挑菜时,不妨想想:“这棵菜的‘ROE’(新鲜度)怎么样?‘P/E’(价格)是不是低于行业均值?”——或许,你已经在无意识地使用量化价值投资的逻辑了。
愿你在投资的道路上,用数据做秤,用模型做菜谱,挑到最“划算”的资产!