2043 字
10 分钟
《量化交易入门》第 7 章:因子选股——用排名代替押注

《量化交易入门》第 7 章:因子选股——用排名代替押注#

上一章我们造出了因子这把”尺子”,但光量出数字还不能赚钱。这一章把尺子组装成一台真正的选股机器:不预测、不押宝、不听消息,只做一件事——给一篮子股票打分、排序、买排名靠前的。这就是量化最经典的玩法:用排名代替押注。

7.1 为什么是”排名”而不是”预测”#

普通人选股的思路是预测式的:我觉得这只股会涨,所以买它。问题是,预测对不对,要等未来才知道,而且你全部的判断都压在一两个”我觉得”上。

量化换了个思路:我不猜谁会涨,我只问”谁现在的特征最好”。

这个转换很关键。因子选股的逻辑链条是:

  1. 相信一个规律,比如”近期强势的股票,短期更容易继续强”(动量);
  2. 用因子给所有候选股票各打一个分;
  3. 按分数从高到低排名;
  4. 只买前 N 名,每只分配同样多的钱。

它不保证第一名一定涨,但它保证你系统性地持有”当前最符合规律”的那一篮子,把单次预测的运气成分,摊薄到很多只股票上。错一两只没关系,靠的是整体概率。

7.2 单因子选股:动量排名#

下面用第 6 章的动量因子,给 6 只模拟股票打分、排名、选前 3 名等权买入。

# 代码 -> (20日前价格, 今天价格, 日波动率%) 均为模拟数据
stocks = {
"A": (10.0, 12.1, 1.5),
"B": (20.0, 19.0, 2.8),
"C": (5.0, 5.9, 1.1),
"D": (8.0, 8.4, 3.5),
"E": (15.0, 17.4, 1.8),
"F": (30.0, 30.6, 0.9),
}
def momentum(p_old, p_now):
return p_now / p_old - 1
# 1) 打分
scored = []
for code, (p_old, p_now, vol) in stocks.items():
scored.append((code, momentum(p_old, p_now), vol))
# 2) 按动量从高到低排名
scored.sort(key=lambda x: x[1], reverse=True)
print("动量排名:")
for rank, (code, mom, vol) in enumerate(scored, 1):
print(f" 第{rank}名 {code} 动量 {mom*100:+.1f}% 波动 {vol}%")
# 3) 选前 3 名,等权配置(10 万资金)
top3 = scored[:3]
cash = 100000
weight = 1 / len(top3)
print("\n选股结果(等权配置):")
for code, mom, vol in top3:
print(f" {code} 分配资金 {cash*weight:.0f} 元(权重 {weight*100:.0f}%)")

输出:

动量排名:
第1名 A 动量 +21.0% 波动 1.5%
第2名 C 动量 +18.0% 波动 1.1%
第3名 E 动量 +16.0% 波动 1.8%
第4名 D 动量 +5.0% 波动 3.5%
第5名 F 动量 +2.0% 波动 0.9%
第6名 B 动量 -5.0% 波动 2.8%
选股结果(等权配置):
A 分配资金 33333 元(权重 33%)
C 分配资金 33333 元(权重 33%)
E 分配资金 33333 元(权重 33%)

这里有三个动作值得记住:打分、排序、等权。其中”等权”是新手最容易忽略的——既然模型只告诉你谁排前面、没告诉你谁更该重仓,那就一视同仁,每只买一样多。这避免了把赌注又偷偷押回首富的那一只。

7.3 多因子选股:让几把尺子一起投票#

只用动量有个问题:你可能买到一只涨得猛、但抖得也凶的股票(比如 D,动量为正,波动却高达 3.5%)。更稳妥的做法是让多个因子一起投票:既要强势,又别太颠簸。

但动量是百分比(十几、二十几),波动也是百分比(一、二),量纲接近只是巧合;真实世界里因子的单位千差万别,不能直接相加。要先做一步标准化,把每个因子变成”它比平均水平好几个标准差”,这个数叫 z 分数。

import math
data = {
"A": (21.0, 1.5), "B": (-5.0, 2.8), "C": (18.0, 1.1),
"D": (5.0, 3.5), "E": (16.0, 1.8), "F": (2.0, 0.9),
}
codes = list(data.keys())
mom = {c: data[c][0] for c in codes}
vol = {c: data[c][1] for c in codes}
def zscore(values):
vals = list(values.values())
mean = sum(vals) / len(vals)
std = math.sqrt(sum((v-mean)**2 for v in vals)/len(vals))
return {k: (v-mean)/std for k, v in values.items()}
z_mom = zscore(mom) # 动量越高越好
z_vol = zscore(vol) # 波动越低越好,用时取负
# 综合分 = 0.6*动量 + 0.4*低波动
total = {c: 0.6*z_mom[c] - 0.4*z_vol[c] for c in codes}
ranking = sorted(codes, key=lambda c: total[c], reverse=True)
print("多因子综合排名(动量60% + 低波动40%):")
for i, c in enumerate(ranking, 1):
print(f" 第{i}名 {c} 综合分 {total[c]:+.2f}")

输出:

多因子综合排名(动量60% + 低波动40%):
第1名 A 综合分 +0.92
第2名 C 综合分 +0.90
第3名 E 综合分 +0.47
第4名 F 综合分 -0.03
第5名 D 综合分 -0.96
第6名 B 综合分 -1.30

注意第 4、5 名发生了互换:单看动量,D(+5%)排在 F(+2%)前面;可一旦把”低波动”也纳入投票,F 因为最稳(0.9%)升到第 4,D 因为最颠簸(3.5%)跌到第 5。这就是多因子的意义:不让某一个维度独断专行。

公式里的 0.6 和 0.4 是权重,代表你更看重谁。权重怎么定?和所有参数一样,不能拍脑袋,要靠第 5 章的回测去比较——这也再次说明,因子、选股、回测是咬在一起的一环。

7.5 买完不是一劳永逸:要定期调仓#

排名是会变的。今天动量最强的 A,一个月后可能已经涨不动、掉出榜单,而新的股票会挤进来。所以因子选股不是买一次就躺着,而是定一个调仓周期——比如每月一次——到了日子就重新打分、重新排名,把跌出前 N 名的卖掉、把新上榜的买入,让持仓始终对准”当前最好的那一篮子”。

调仓频率有讲究,这是一个权衡:

  • 调得太勤(比如每天):排名紧跟市场,但每次买卖都要付手续费、滑点,交易成本会吃掉利润(第 9 章细算),而且容易被短期噪声牵着跑。
  • 调得太疏(比如一年):成本低、省心,但你手里的组合可能早和最新排名对不上了,策略名存实亡。

月度、季度是实践中常见的折中。具体选多长,没有标准答案,依然要靠回测在”跟得紧”和”花得少”之间找到划算的那个点。记住这个反复出现的主题:量化里凡是旋钮,都交给回测定,而不是交给感觉。

7.6 这套方法的两个”生死线”#

理解了机制,还要知道它什么时候会失灵:

  1. 因子背后的规律必须还成立。 因子选股赚的是”规律延续”的钱。动量策略在趋势行情里好用,一旦市场转入长期横盘或急速反转,“追强势”就会反复买在山顶。所以因子要持续检验(第 6 章提醒过)。
  2. 排名是相对的,不是绝对的。 它只保证你买到”最好的那一篮子”,但如果整个市场都在跌,最好的那一篮子也会跌,只是可能跌得少一点。选股解决的是”买谁”,解决不了”该不该满仓”——后者是下一部分风险管理的内容。

7.7 这一章你该记住的#

  1. 因子选股 = 用”打分、排名、买前 N 名”代替”预测、押注单只”
  2. 三个标准动作:打分、排序、等权配置
  3. 多因子先做 z 分数标准化再加权,方向相反的因子记得取负号
  4. 权重靠回测确定;因子失灵时排名策略照样亏,它不解决仓位问题

选股机器有了,可这台机器什么时候该重仓、什么时候该轻仓、什么时候该干脆停下来?下一章进入全新的部分——风险管理:仓位、止损与爆仓的数学,我们算给你看,为什么”活下来”比”赚得多”更重要。


📚 《量化交易入门》系列:零基础小白向量化交易,每章一个主题 + 可运行 Python 示例。第 7 章已读,下一章《风险管理:仓位、止损与爆仓的数学》明天见。

《量化交易入门》第 7 章:因子选股——用排名代替押注
https://021028.xyz/posts/default/137/
作者
021028
发布于
2026-10-11
许可协议
CC BY-NC-SA 4.0