祖鲁法则 — 完整实施方案规范

基于 Jim Slater 著,《祖鲁法则:普通股票的非凡利润》(1992)


目录

  1. 概述 — 祖鲁法则
  2. 核心哲学 — 成为专家
  3. PEG 比率 — Slater 的主指标
  4. 成长股筛选标准
  5. 七个关键指标
  6. 详细入场规则
  7. 出场规则和卖出纪律
  8. 组合构建与管理
  9. 行业分析框架
  10. 常见错误和陷阱
  11. 完整投资生命周期示例
  12. 实施伪代码
  13. 关键语录

1. 概述 — 祖鲁法则

Jim Slater 的核心洞察看似简单:你不需要了解每只股票的一切。相反,你需要在一个小而明确的细分领域了解得比几乎所有人都多。书名来自一个轶事:如果你的配偶在《读者文摘》上读了一篇关于祖鲁人的文章,她比你知道得更多。如果她再读一本图书馆的书,她成为当地专家。如果她访问南非、阅读学术论文、采访祖鲁长老,她成为全国最权威的人物之一——这一切都从一本杂志文章开始。

应用于投资,祖鲁法则意味着:

该方法本质上是**合理价格成长股(GARP)**策略,但比大多数 GARP 实践者使用的量化筛选标准要严格得多。Slater 的系统旨在寻找盈利增长加速、股价尚未被市场完全重新估值、资产负债表足够强劲以维持该增长的公司。

目标范围:英国上市公司(可轻松适应任何发达市场),主要是小型股和中型股(市值约为上市公司的后 70%)。


2. 核心哲学 — 成为专家

2.1 个人投资者的优势

Slater 认为个人投资者相对于机构有几个结构性优势:

  1. 规模灵活性 — 机构无法在小公司中持有有意义的头寸而不影响价格。个人投资者购买 10,000 股小盘股对市场来说是看不见的。
  2. 没有基准约束 — 基金经理必须接近其基准。个人投资者可以集中于最佳想法。
  3. 时间跨度 — 没有季度绩效评估。你可以等待thesis发挥作用。
  4. 无需委员会决策 — 你可以立即根据信念行动。

2.2 能力圈

在 Buffett 普及这个术语之前,Slater 已经主张严格的能力圈:

2.3 信息层次

Slater 按价值对信息来源进行排名:

  1. 年报和账目 — 主要来源;从头到尾阅读。
  2. 中期业绩 — 跟踪盈利轨迹。
  3. 董事股票交易 — 董事用自有资金购买是最强烈的信号之一。
  4. 券商研究 — 用于共识估计但需持怀疑态度。
  5. 报纸提示 — 到发布时几乎毫无价值。

3. PEG 比率 — Slater 的主指标

3.1 定义

PEG 比率(市盈率转成长率)是 Slater 系统中最重要的单一指标:

PEG = (价格 / 每股收益) / (预期每股收益增长率 %)
    = 前瞻 P/E / 预期每股收益增长率

其中:

3.2 解读

PEG 值 解读
< 0.50 极具吸引力 — 验证数据是否正确
0.50-0.75 非常有吸引力 — 强烈买入候选
0.75-1.00 有吸引力 — 符合 Slater 的主要标准
1.00 合理估值 — 增长已完全定价
1.00-1.50 变得昂贵 — 增长部分定价
> 1.50 昂贵 — 避免或考虑卖出

3.3 PEG 计算规则

Slater 明确说明如何正确计算 PEG:

  1. 使用预期(前瞻)盈利,而非追溯。市场定价未来,而非过去。
  2. 增长率必须是未来 12 个月的预测增长率,最好由历史增长模式证实。
  3. 增长率必须是可持续的 — 从 1p 到 10p 每股收益的一次性跳跃不算"1000% 增长"。
  4. 最低增长率 15% — 低于此值,PEG 比率失去预测能力,因为盈利复合不够快。
  5. 最高 P/E 为 20 — 即使有强劲增长,Slater 对非常高 P/E 股票持谨慎态度,因为任何盈利失望的下行风险都是严重的。

3.4 PEG 比率的局限性

Slater 承认 PEG 比率是一个筛选工具,而非完整估值:


4. 成长股筛选标准

Slater 的完整筛选框架涉及七个强制性标准。股票必须通过全部七项才能合格。

4.1 标准 1:PEG 比率低于 1.0

规则:PEG < 1.0(最好 < 0.75)
      其中 PEG = 前瞻 P/E / 预期每股收益增长率 %

这是主要过滤器。它确保你不会为所提供的发展付出过高的代价。

4.2 标准 2:预期每股收益增长 > 15%

规则:预测每股收益增长 >= 每年 15%
      证实:至少 3 年历史每股收益增长 >= 15%

增长必须是:

4.3 标准 3:强劲现金流

规则:每股经营现金流 >= 每股收益
      (现金流与盈利比率 >= 1.0)

此过滤器消除了报告会计盈利但未产生真实现金的公司。Slater 称现金流为"公司的命脉",并认为此检查是不可协商的。

额外现金流检查:

4.4 标准 4:低负债率(低债务)

规则:负债率(净负债 / 股东权益)< 50%
      理想:净现金状况(负债率 < 0%)

Slater 强烈偏好低债务公司,因为:

例外:物业公司和某些资本密集型企业可能在结构上携带更高债务;应用行业特定规范。

4.5 标准 5:高相对强度

规则:12 个月相对强度 > 0
      (股票在过去一年跑赢市场)
      理想:在所有股票的上四分位

Slater 明确表示价格动能确认基本面动能。盈利增长强劲但股价疲软的股票通常有隐藏问题。相反,强劲的相对强度意味着机构资金开始流入——你想顺风而非逆水。

测量:

4.6 标准 6:竞争优势 / 业务质量

这是唯一的定性标准,需要判断:

Slater 的启发法:"如果你不能用两句话解释为什么这家公司的增长会持续,你还不够了解它。"

4.7 标准 7:小盘股或中盘股偏好

规则:市值在上市公司的后 70%
      (排除超大盘股和大多数大盘股)

理由:


5. 七个关键指标

除了筛选标准,Slater 确定了七个信号,当组合使用时,会显著提高投资成功的概率:

指标 1:加速盈利增长

信号:第 3 年每股收益增长 > 第 2 年 > 第 1 年

加速增长比稳定增长强大得多。一家每股收益增长 15%、然后 20%、然后 25% 的公司将看到其 P/E 倍数扩张 AND 盈利增长——股价上涨的双重引擎。

指标 2:正面盈利惊喜

信号:实际报告每股收益 > 共识预测每股收益
      (在最近 3 个报告期中的至少 2 个)

持续超出预测的公司有一个管理团队,承诺少于实际交付。这是一个强大的文化信号。每次正面惊喜都迫使分析师提高估计,推动进一步购买。

指标 3:董事买入

信号:董事用个人资金购买股票
      (特别是多位董事在短时间内集中购买)

董事比任何分析师都更了解他们的业务。当他们用自有资金购买时(而非期权行权),这是可用的最强烈的看涨信号之一。Slater 特别关注:

指标 4:股息增长

信号:每股股息逐年增加
      股息覆盖率(每股收益 / 每股股息)>= 2.0 倍

上升的股息表明管理层对未来现金流充满信心。除非他们期望维持,否则不会提高股息。2 倍或更高的股息覆盖率意味着有充足的空间在奖励股东的同时为增长再投资。

指标 5:相对于销售的低市值

信号:市销率 < 1.0(对于非科技公司最好 < 0.5)

低市销率提供了安全边际。即使利润率暂时下降,以收入以下价格交易的公司如果利润率正常化,则具有显著的重估潜力。

指标 6:新事物

信号:新产品、新市场、新管理层或新技术
      创造了盈利加速的催化剂

Slater 从 William O'Neil 那里借鉴了这一点(CANSLIM 中的"N")。需要催化剂来推动重估。没有新事物,便宜的股票可能无限期地保持便宜。

指标 7:超越负债率的强劲资产负债表

信号:流动比率 > 1.5
      利息覆盖率 > 4 倍
      无重大表外负债
      每股净资产值提供下行支撑

资产负债表是安全网。即使盈利暂时停滞,强劲的资产负债表也给了公司时间恢复,而无需进行稀释性融资。


6. 详细入场规则

6.1 入场前清单

购买任何仓位前,验证每个项目:

[ ] PEG < 1.0(使用前瞻盈利计算,增长率 >= 15%)
[ ] 至少 3 年历史每股收益增长 >= 15%
[ ] 共识预测确认 >= 15% 前瞻增长
[ ] 每股现金流 >= 每股收益(现金转换 >= 100%)
[ ] 负债率 < 50%(净债务 / 权益)
[ ] 12 个月相对强度为正(股票跑赢市场)
[ ] 可用两句话或更少描述竞争优势
[ ] 市值在小盘股/中盘股范围内
[ ] 过去 6 个月没有盈利警告
[ ] 董事未出售重大持股
[ ] 股息维持或增加(如适用)
[ ] 未发现重大诉讼或监管风险

6.2 入场时机

Slater 不主张精确的技术时机,但提供指导:

  1. 业绩后 — 在强劲的临时或全年业绩后不久买入,一旦市场消化了新闻。最初的激增通常会持续数周。
  2. 上升趋势中的回调后 — 如果股票因无基本面消息下跌 10-15%,这通常是买入机会(假设所有标准仍然满足)。
  3. 避免业绩前买入 — 风险/收益是不对称的。如果业绩好,股票适度上涨。如果业绩令人失望,股票可能下跌 20-30%。
  4. 避免交易的第一小时 — 价格波动最大,价差最宽。

6.3 入场时的仓位确定

初始仓位:组合价值的 3-5%
最大仓位:通过升值而非额外购买达到组合价值的 10%(通过升值)
最小组合:10 只股票
最大组合:20 只股票

6.4 金字塔加仓

Slater 更倾向于一次性买入全部仓位而非金字塔加仓,因为:


7. 出场规则和卖出纪律

Slater 认为卖出是投资中最难的部分。他的规则:

7.1 强制卖出信号(立即卖出)

如果:每股收益增长低于 15%(增长故事破裂)则卖出
如果:公司发布盈利警告则卖出
如果:PEG 升至 1.5 以上(股票已变得昂贵)则卖出
如果:负债率升至 75% 以上(资产负债表恶化)则卖出
如果:每股现金流连续 2+ 期显著低于每股收益则卖出
如果:CEO 或财务总监出售大量持股则卖出
如果:竞争优势明显侵蚀(新竞争对手、监管变化)则卖出

7.2 裁量卖出信号(审查并可能卖出)

如果:12 个月相对强度变为负则审查
如果:即使 PEG 仍然可以接受,市盈率也超过 20 则审查
如果:市值已增长至大盘股领域("发现"阶段结束)则审查
如果:券商覆盖显著增加(信息优势正在减弱)则审查
如果:你不能再用两句话解释投资论点则审查

7.3 "减半"规则

如果股票从你的购买价格下跌 25%:

7.4 获利了结

Slater 建议不要机械地获利了结:


8. 组合构建与管理

8.1 多元化规则

持股数量:     12-20 只股票
单一股票最大: 组合成本的 10%
单一行业最大: 组合的 25%
现金储备:     最低 5-10%(用于机会的干粉)
市值偏好:     70%+ 在小盘股/中盘股,最多 30% 在大盘股

8.2 组合审查周期

频率 操作
每日 检查股价和任何监管新闻服务公告
每周 审查所有持股的相对强度
每月 用更新的共识估计重新计算 PEG 比率
每季度 根据所有 7 个筛选标准对组合进行全面审查
每半年 审查行业分配并在必要时重新平衡
每年 完整审计:将每个仓位视为重新买入进行审查

8.3 现金管理

8.4 何时全仓与防御

Slater 在其自下而上的股票选择之上使用自上而下的叠加:


9. 行业分析框架

9.1 首选行业

Slater 倾向于增长更可预测的行业:

9.2 应避免的行业

9.3 行业特定 PEG 调整

并非所有行业都应使用相同的 PEG 阈值:

技术服务:   PEG < 1.0(标准)
医疗保健:    PEG < 1.2(盈利可见性溢价)
消费品:      PEG < 0.8(较低增长天花板折扣)
周期性(如使用):PEG < 0.6(盈利波动性折扣)

10. 常见错误和陷阱

错误 1:爱上股票

"投资中最昂贵的词是'这次不同了'。"一旦任何强制性标准失败,无论你对它的情感依恋如何,也无论你已经盈利或亏损了多少,你都必须卖出。

错误 2:忽视现金流

许多投资者只关注每股收益增长而忽视现金流。Slater 警告说,盈利可以通过会计政策(收入确认、折旧、成本资本化)操纵,但现金流更难伪造。始终验证现金流支持报告的盈利。

错误 3:购买提示

报纸提示、券商升级和在线论坛推荐是最低质量的信息来源。当信息到达这些渠道时,它已经充分定价。使用年报和筛选标准进行自己的研究。

错误 4:过度多元化

持有 50 只股票不是多元化——这是带有更高成本的 closet indexing。如果你无法根据所有七个标准监控每个仓位,你持有的股票太多了。十二到二十个仓位是最佳选择:足够多元化以分散公司特定风险,足够少以深入了解每个持股。

错误 5:因为"故事很棒"而购买高 PEG 股票

最危险的股票是那些有真正令人兴奋的叙事但 PEG 比率高于 1.5 的股票。故事可能是真实的,但价格已经反映了它。当现实不可避免地甚至轻微地令人失望时,P/E 压缩是毁灭性的。

错误 6:在不检查标准的情况下补仓

当股票下跌时,本能是购买更多。Slater 的规则很明确:只有在每个标准都通过的情况下,才能增加亏损仓位。否则,卖小亏损比持有大亏损更好。

错误 7:忽视市场环境

即使最好的成长股在严重熊市中也会挣扎。虽然 Slater 的方法是自下而上的,但他承认市场条件的重要性。当利率急剧上升且经济周期转向时,减少敞口并增加现金。

错误 8:将周期性复苏与结构性增长混淆

从衰退中复苏的周期性公司可能显示 50%+ 每股收益增长和 0.3 的 PEG。这是一个陷阱。增长是均值回归,而非结构性的。始终检查公司的增长是有机的和可持续的,还是仅仅是从抑郁基础的反弹。


11. 完整投资生命周期示例

阶段 1:筛选

你运行 PEG 筛选器并识别出"TechServ plc",一家小型股 IT 服务公司:

TechServ plc — 筛选结果
---------------------------------
市值:              8500 万英镑(小盘股)
股价:             250 便士
前瞻 P/E:          14 倍
预期每股收益增长:   22%
PEG 比率:          0.64(14 / 22)
历史每股收益增长:   18%、20%、22%(3 年 — 加速)
现金流 / 每股收益:  1.15(强劲现金生成)
负债率:            12%(最低债务)
相对强度(12 个月):+28% vs 市场 +8%(RS = +20%)
股息收益率:         1.8%,覆盖率 3.2 倍

全部七个强制性标准通过。TechServ 进入详细分析阶段。

阶段 2:深入研究

你阅读年报并发现:

竞争优势总结:"TechServ 是英国律师事务所托管 IT 的主导提供商,拥有 78% 的经常性收入和 94% 的客户保留率,创造高转换成本。"

阶段 3:入场

阶段 4:监控(月 1-6)

第 2 个月:临时业绩显示每股收益增长 24% — 超出 22% 的预测。分析师提高估计。PEG 降至 0.56。股价升至 285 便士。不需要操作。

第 4 个月:一个竞争对手赢得了 TechServ 追求的小合同。股价因消息跌至 270 便士。你重新检查所有标准 — 全部仍然通过。仓位现在价值 21,600 英镑。你持有。

第 6 个月:全年业绩确认 25% 每股收益增长。分析师现在预测明年增长 24%。PEG = 0.58(基于更高盈利的前瞻 P/E 现在为 14)。股价达到 320 便士。仓位价值 25,600 英镑(28% 收益)。你持有。

阶段 5:重估(月 7-18)

第 10 个月:一只中盘股基金持有 3% 的股份。券商开始覆盖,评级为"买入"。股价达到 420 便士。PEG 现在为 0.71。仓位价值 33,600 英镑。你持有 — PEG 仍低于 1.0。

第 14 个月:股价达到 550 便士。前瞻 P/E 在 23% 增长上为 18 倍。PEG = 0.78。仓位现在为组合的 6.4%。所有标准仍然通过。你持有,但注意到仓位正在接近 10% 的组合限制。

第 18 个月:股价达到 680 便士。前瞻 P/E 在 20% 增长(放缓)上为 21 倍。PEG = 1.05。PEG 已突破 1.0,增长正在减速。

阶段 6:出场

你审查仓位:

你在 680 便士卖出全部仓位。

结果:买入 8,000 股 @ 250 便士 = 20,000 英镑
      卖出 8,000 股 @ 680 便士 = 54,400 英镑
      利润:34,400 英镑(18 个月内 172% 收益)

阶段 7:事后分析

你记录:


12. 实施伪代码

12.1 PEG 筛选器

# =============================================================================
# 祖鲁法则 — PEG 成长股筛选器
# 基于 Jim Slater《祖鲁法则》(1992)的标准
# =============================================================================

class ZuluScreener:
    """
    根据 Slater 的七个强制性标准筛选股票范围。
    返回按 PEG 比率排序的候选列表(升序)。
    """

    # --- 配置 ---
    MAX_PEG = 1.0                   # 最大 PEG 比率
    MIN_EPS_GROWTH = 15.0           # 最小前瞻每股收益增长(%)
    MIN_HISTORICAL_YEARS = 3        # 验证历史增长年数
    MIN_CASH_FLOW_RATIO = 1.0       # 经营现金流 / 每股收益最小值
    MAX_GEARING = 50.0              # 最大净债务 / 权益(%)
    MAX_PE = 20.0                   # 最大前瞻 P/E 比率
    MIN_RELATIVE_STRENGTH = 0.0     # 相对于指数的最小 12m 相对强度

    def __init__(self, data_provider):
        self.data = data_provider    # API 或数据库连接

    def screen(self, universe):
        """
        对股票范围运行所有七个过滤器。
        返回按 PEG 比率排序的 (ticker, score_dict) 元组列表。
        """
        candidates = []

        for ticker in universe:
            result = self.evaluate(ticker)
            if result['passes_all']:
                candidates.append((ticker, result))

        # 按 PEG 比率排序 — 最低(相对于增长最便宜)优先
        candidates.sort(key=lambda x: x[1]['peg_ratio'])
        return candidates

    def evaluate(self, ticker):
        """根据所有七个标准评估单个股票。"""
        stock = self.data.get_fundamentals(ticker)
        price_data = self.data.get_price_data(ticker)

        result = {
            'ticker': ticker,
            'company_name': stock.name,
            'market_cap': stock.market_cap,
        }

        # --- 标准 1:PEG 比率 ---
        forward_pe = stock.forward_pe
        eps_growth = stock.consensus_eps_growth_pct  # 例如 22 为 22%

        if eps_growth > 0 and forward_pe > 0:
            peg = forward_pe / eps_growth
        else:
            peg = float('inf')  # 无法计算 — 取消资格

        result['peg_ratio'] = round(peg, 2)
        result['forward_pe'] = forward_pe
        result['eps_growth_fwd'] = eps_growth
        result['peg_pass'] = (peg <= self.MAX_PEG and forward_pe <= self.MAX_PE)

        # --- 标准 2:历史 >= 15% 增长 3+ 年的每股收益增长 ---
        historical_growth = stock.historical_eps_growth  # 年度 % 列表
        recent_growth = historical_growth[-self.MIN_HISTORICAL_YEARS:]

        growth_consistent = (
            len(recent_growth) >= self.MIN_HISTORICAL_YEARS
            and all(g >= self.MIN_EPS_GROWTH for g in recent_growth)
        )
        growth_accelerating = (
            len(recent_growth) >= 2
            and recent_growth[-1] > recent_growth[-2]
        )

        result['historical_growth'] = recent_growth
        result['growth_pass'] = (
            growth_consistent
            and eps_growth >= self.MIN_EPS_GROWTH
        )
        result['growth_accelerating'] = growth_accelerating

        # --- 标准 3:现金流强度 ---
        cash_flow_ratio = stock.operating_cash_flow_ps / stock.eps if stock.eps > 0 else 0
        free_cash_flow_positive = stock.free_cash_flow > 0

        result['cash_flow_ratio'] = round(cash_flow_ratio, 2)
        result['cash_flow_pass'] = (
            cash_flow_ratio >= self.MIN_CASH_FLOW_RATIO
            and free_cash_flow_positive
        )

        # --- 标准 4:低负债率 ---
        gearing = stock.net_debt / stock.shareholders_equity * 100 if stock.shareholders_equity > 0 else float('inf')

        result['gearing_pct'] = round(gearing, 1)
        result['gearing_pass'] = gearing <= self.MAX_GEARING

        # --- 标准 5:相对强度 ---
        stock_return_12m = price_data.total_return_12m
        index_return_12m = self.data.get_index_return_12m()
        relative_strength = stock_return_12m - index_return_12m

        result['relative_strength'] = round(relative_strength, 2)
        result['rs_pass'] = relative_strength > self.MIN_RELATIVE_STRENGTH

        # --- 标准 6:竞争优势(定性标志)---
        # 这必须手动评估;我们检查代理信号
        result['director_buying'] = stock.recent_director_purchases > 0
        result['high_margins'] = stock.operating_margin > 15.0
        result['recurring_revenue_pct'] = stock.recurring_revenue_pct

        # 定性通过需要手动审查 — 标记给分析师
        result['quality_flag'] = 'REQUIRES_MANUAL_REVIEW'

        # --- 标准 7:小/中盘股 ---
        market_cap_percentile = self.data.get_market_cap_percentile(ticker)
        result['market_cap_percentile'] = market_cap_percentile
        result['size_pass'] = market_cap_percentile <= 70  # 后 70%

        # --- 总通过 ---
        result['passes_all'] = (
            result['peg_pass']
            and result['growth_pass']
            and result['cash_flow_pass']
            and result['gearing_pass']
            and result['rs_pass']
            and result['size_pass']
            # quality_flag 留待手动审查 — 不阻止筛选
        )

        return result

    def rank_candidates(self, candidates):
        """
        按综合评分对通过候选进行排名。
        分数越低 = 越有吸引力。
        """
        scored = []
        for ticker, data in candidates:
            # 主排序:PEG 比率(越低越好)
            peg_score = data['peg_ratio']

            # 加速增长奖励
            accel_bonus = -0.1 if data['growth_accelerating'] else 0

            # 董事买入奖励
            director_bonus = -0.05 if data['director_buying'] else 0

            # 净现金奖励(负负债率)
            cash_bonus = -0.05 if data['gearing_pct'] < 0 else 0

            composite = peg_score + accel_bonus + director_bonus + cash_bonus
            scored.append((ticker, data, round(composite, 3)))

        scored.sort(key=lambda x: x[2])
        return scored

12.2 组合构建与监控

# =============================================================================
# 组合管理 — 祖鲁法则实施
# =============================================================================

class ZuluPortfolio:
    """
    管理通过祖鲁法则选择的成长股组合。
    处理仓位确定、监控和出场信号。
    """

    MAX_POSITIONS = 20
    MIN_POSITIONS = 10
    MAX_SINGLE_POSITION_PCT = 10.0   # 组合成本的 % 
    TARGET_POSITION_PCT = 4.0        # 新仓位的组合 %
    MAX_SECTOR_PCT = 25.0            # 每个行业的组合 %
    MIN_CASH_PCT = 5.0               # 最低现金储备
    STOP_LOSS_PCT = 30.0             # 每个仓位的绝对最大亏损
    REVIEW_LOSS_PCT = 25.0           # 在此亏损时触发深入审查

    # 出场阈值
    EXIT_PEG_MAX = 1.5
    EXIT_GEARING_MAX = 75.0
    EXIT_GROWTH_MIN = 15.0

    def __init__(self, screener, initial_capital):
        self.screener = screener
        self.capital = initial_capital
        self.positions = {}           # ticker -> Position
        self.cash = initial_capital
        self.trade_log = []

    def add_position(self, ticker, price, criteria_snapshot):
        """如果组合规则允许,开新仓位。"""

        # 检查组合容量
        if len(self.positions) >= self.MAX_POSITIONS:
            print(f"阻止:组合已满({self.MAX_POSITIONS} 个仓位)")
            return False

        # 计算仓位规模
        portfolio_value = self.get_portfolio_value()
        position_value = portfolio_value * (self.TARGET_POSITION_PCT / 100)

        # 确保最低现金储备
        if (self.cash - position_value) < (portfolio_value * self.MIN_CASH_PCT / 100):
            print("阻止:将违反最低现金储备")
            return False

        # 检查行业集中度
        sector = criteria_snapshot.get('sector', 'Unknown')
        sector_exposure = self.get_sector_exposure(sector)
        if (sector_exposure + position_value) / portfolio_value * 100 > self.MAX_SECTOR_PCT:
            print(f"阻止:行业 {sector} 将超过 {self.MAX_SECTOR_PCT}%")
            return False

        # 执行
        shares = int(position_value / price)
        cost = shares * price

        self.positions[ticker] = Position(
            ticker=ticker,
            shares=shares,
            entry_price=price,
            entry_date=today(),
            entry_criteria=criteria_snapshot,
            sector=sector,
        )
        self.cash -= cost

        self.trade_log.append({
            'action': 'BUY',
            'ticker': ticker,
            'shares': shares,
            'price': price,
            'date': today(),
            'peg_at_entry': criteria_snapshot['peg_ratio'],
        })

        return True

    def monitor_all_positions(self):
        """
        每月监控周期。重新评估每个仓位
        相对于 Slater 的标准。生成操作信号。
        """
        signals = []

        for ticker, position in self.positions.items():
            current = self.screener.evaluate(ticker)
            current_price = self.screener.data.get_current_price(ticker)

            # 计算收益
            pct_return = (current_price - position.entry_price) / position.entry_price * 100

            signal = {
                'ticker': ticker,
                'return_pct': round(pct_return, 1),
                'current_peg': current['peg_ratio'],
                'current_growth': current['eps_growth_fwd'],
                'actions': [],
            }

            # --- 强制卖出检查 ---

            # PEG 太高
            if current['peg_ratio'] > self.EXIT_PEG_MAX:
                signal['actions'].append('卖出:PEG > 1.5')

            # 增长低于最低
            if current['eps_growth_fwd'] < self.EXIT_GROWTH_MIN:
                signal['actions'].append('卖出:前瞻增长 < 15%')

            # 负债率恶化
            if current['gearing_pct'] > self.EXIT_GEARING_MAX:
                signal['actions'].append('卖出:负债率 > 75%')

            # 现金流崩溃
            if current['cash_flow_ratio'] < 0.7:
                signal['actions'].append('卖出:现金流比率崩溃')

            # 止损
            if pct_return < -self.STOP_LOSS_PCT:
                signal['actions'].append(f'卖出:止损触发({pct_return:.1f}%)')

            # --- 审查信号 ---

            # 超过审查阈值的亏损
            if -self.REVIEW_LOSS_PCT < pct_return < -self.STOP_LOSS_PCT + 5:
                signal['actions'].append('审查:亏损超过 25% — 重新检查所有标准')

            # PEG 进入昂贵领域
            if 1.0 < current['peg_ratio'] <= self.EXIT_PEG_MAX:
                signal['actions'].append('审查:PEG 在 1.0 和 1.5 之间')

            # 相对强度变为负
            if current['relative_strength'] < 0:
                signal['actions'].append('审查:负相对强度')

            # 仓位变得太大
            position_value = position.shares * current_price
            portfolio_value = self.get_portfolio_value()
            position_pct = position_value / portfolio_value * 100
            if position_pct > self.MAX_SINGLE_POSITION_PCT:
                signal['actions'].append(
                    f'审查:仓位为组合的 {position_pct:.1f}% — 考虑减仓'
                )

            # 增长减速
            if (current.get('growth_accelerating') is False
                    and position.entry_criteria.get('growth_accelerating') is True):
                signal['actions'].append('审查:增长自入场以来减速')

            # 没有问题
            if not signal['actions']:
                signal['actions'].append('持有:所有标准完整')

            signals.append(signal)

        return signals

    def execute_exit(self, ticker, price, reason):
        """平仓并记录交易。"""
        position = self.positions.pop(ticker)
        proceeds = position.shares * price

        self.cash += proceeds
        pct_return = (price - position.entry_price) / position.entry_price * 100

        self.trade_log.append({
            'action': 'SELL',
            'ticker': ticker,
            'shares': position.shares,
            'price': price,
            'date': today(),
            'reason': reason,
            'return_pct': round(pct_return, 1),
            'holding_period_days': (today() - position.entry_date).days,
        })

        return pct_return

    def generate_monthly_report(self):
        """为每月审查生成摘要报告。"""
        portfolio_value = self.get_portfolio_value()
        cash_pct = self.cash / portfolio_value * 100

        report = {
            'total_value': portfolio_value,
            'cash': self.cash,
            'cash_pct': round(cash_pct, 1),
            'num_positions': len(self.positions),
            'positions': [],
            'signals': self.monitor_all_positions(),
            'sector_breakdown': self.get_sector_breakdown(),
        }

        # 识别重新平衡需求
        if cash_pct < self.MIN_CASH_PCT:
            report['alert'] = f'现金低于最低({cash_pct:.1f}% < {self.MIN_CASH_PCT}%)'

        if len(self.positions) < self.MIN_POSITIONS:
            report['alert'] = f'投资不足:仅有 {len(self.positions)} 个仓位'

        return report

    def get_portfolio_value(self):
        """包括现金的组合总价值。"""
        invested = sum(
            pos.shares * self.screener.data.get_current_price(ticker)
            for ticker, pos in self.positions.items()
        )
        return invested + self.cash

    def get_sector_exposure(self, sector):
        """暴露于给定行业的总价值。"""
        return sum(
            pos.shares * self.screener.data.get_current_price(ticker)
            for ticker, pos in self.positions.items()
            if pos.sector == sector
        )

    def get_sector_breakdown(self):
        """按行业分列的百分比明细。"""
        total = self.get_portfolio_value()
        breakdown = {}
        for ticker, pos in self.positions.items():
            value = pos.shares * self.screener.data.get_current_price(ticker)
            sector = pos.sector
            breakdown[sector] = breakdown.get(sector, 0) + value
        return {k: round(v / total * 100, 1) for k, v in breakdown.items()}


class Position:
    """代表单个组合仓位。"""
    def __init__(self, ticker, shares, entry_price, entry_date,
                 entry_criteria, sector):
        self.ticker = ticker
        self.shares = shares
        self.entry_price = entry_price
        self.entry_date = entry_date
        self.entry_criteria = entry_criteria
        self.sector = sector

12.3 筛选管道 — 主执行

def run_zulu_screen():
    """
    完整筛选管道:
    1. 提取小/中盘股范围
    2. 应用 Slater 的七个过滤器
    3. 按综合 PEG 评分对幸存者排名
    4. 输出可操作列表供手动深入审查
    """
    data = DataProvider(api_key="...")
    screener = ZuluScreener(data)

    # 步骤 1:定义范围(市值的后 70%,排除金融/矿业)
    universe = data.get_universe(
        market='LSE',
        max_market_cap_percentile=70,
        exclude_sectors=['Banks', 'Mining', 'Oil & Gas', 'Utilities']
    )
    print(f"范围:{len(universe)} 只股票")

    # 步骤 2:筛选
    candidates = screener.screen(universe)
    print(f"通过所有过滤器:{len(candidates)} 只股票")

    # 步骤 3:排名
    ranked = screener.rank_candidates(candidates)

    # 步骤 4:输出前 30 名供手动审查
    print("\n=== 祖鲁法则最佳候选 ===\n")
    print(f"{'排名':<5} {'代码':<8} {'PEG':<6} {'前瞻 PE':<7} "
          f"{'增长%':<8} {'CF 比率':<9} {'负债率%':<9} {'RS%':<6} {'评分':<6}")
    print("-" * 75)

    for i, (ticker, data_dict, score) in enumerate(ranked[:30], 1):
        print(f"{i:<5} {ticker:<8} {data_dict['peg_ratio']:<6.2f} "
              f"{data_dict['forward_pe']:<7.1f} "
              f"{data_dict['eps_growth_fwd']:<8.1f} "
              f"{data_dict['cash_flow_ratio']:<9.2f} "
              f"{data_dict['gearing_pct']:<9.1f} "
              f"{data_dict['relative_strength']:<6.1f} "
              f"{score:<6.3f}")

    return ranked

13. 关键语录

"PEG 比率是我所知的选择成长股最可靠的指标。 小于 1.0 的 PEG 因子是第一个必要标准。"

"现金流是公司的命脉。每股收益可以是意见问题; 现金流是事实问题。"

"理想的成长股具有低 PEG 因子、强劲现金流、低负债率、 加速每股收益、竞争优势,并且董事正在购买股票。"

"个人投资者相对于机构有一个巨大优势——他们可以投资于 对大部队来说太小的公司。"

"当一只股票具有非常低的 PEG 因子且董事正在购买时, 胜算对你非常有利。"

"投资者最常犯的错误是过早卖出盈利股,持有亏损股太久。 换句话说,他们剪掉花朵,给杂草浇水。"

"如果你不能用几句话解释为什么你持有某只股票, 你可能不应该拥有它。"

"盈利警告始终是卖出信号。令人失望一次的公司 几乎总是会再次令人失望。"

"成为一个小领域专家的美妙之处在于,你可以在其他投资者身上建立巨大优势。 大多数投资者铺得太开——他们知道很多事情的很少, 而不是知道很多事情的一点。"

"相对强度是一个重要的确认指标。具有优异基本面 但价格正在下跌的股票正在向你发出警告信号。"


实施规范结束。