像冠军一样思考与交易 (Think & Trade Like a Champion) — 完整实施规范

基于 Mark Minervini,《像冠军一样思考与交易》(2017)


目录

  1. 概述 — SEPA方法延续
  2. 冠军心态
  3. 交易选择标准(SEPA精化)
  4. 波动收缩模式(VCP)
  5. 买入规则 — 可买入的向上跳空、支点买入、强势突破
  6. 风险管理 与 仓位规模
  7. 卖出规则
  8. 投资组合管理
  9. 交易回顾与记录
  10. 市场敞口模型
  11. 常见错误
  12. 完整交易生命周期示例
  13. 实施伪代码
  14. 关键语录

1. 概述 — SEPA方法延续

SEPA(特定入场点分析)是Minervini用于识别成长股高概率入场点的专有框架。第一本书(《像股票市场巫师一样交易》,2013)介绍了核心机制,而第二卷深化了方法论,强调:

Minervini的业绩记录——在美国投资锦标赛(1997)期间经验证的220%年度回报以及此后多次顶级成绩——支撑了这一系统的可信度。SEPA的关键洞察是,最佳交易在走出最大行情之前都具备可识别的特征。交易者的工作不是预测未来,而是识别当前设置何时与超级业绩股票的历史蓝图相匹配。

核心哲学:你不需要大多数时候都正确。你需要在错误时保持损失小,在正确时让赢家奔跑。结果的非对称性,而非仅仅是击球率,推动复利。


2. 冠军心态

2.1 过程思维 vs 结果思维

Minervini在关注过程(你能控制的)和结果(你不能控制的)之间划了硬线。一笔完全按计划执行但结果亏损的交易仍然是好的交易。一笔偶然盈利的草率交易是教你错误教训的坏交易。

冠军交易者通过坚持过程来评估表现,而非任何单笔交易的盈亏。在大样本下,正确的过程产生正确的结果。

2.2 通过准备建立信心

信心不是天生的——它是通过准备制造的:

  1. 研究历史赢家:如此彻底地了解过去世纪50、100、500个最大股票走势,你可以在实时中识别设置。
  2. 模拟交易或小规模交易:在投入大资金之前建立你的方法有效的证据。
  3. 盘前 routine:在市场开盘前,准备好观察名单,知道你的买入点,知道你的止损水平,知道你的仓位规模。在开盘期间不应做出任何未预先计划的决定。
  4. 盘后 routine:每天回顾每笔交易。什么做对了?什么做错了?教训是什么?

2.3 绩效周期

Minervini描述了冠军反复导航的四阶段周期:

  1. 无意识的无能:你不知道你不知道什么。大多数初学者在这里,认为交易是关于股票推荐或热门消息。
  2. 有意识的无能:你意识到你的方法坏了。这很痛苦但必要。
  3. 有意识的能力:你知道该做什么,但执行需要持续的努力和警惕。大多数进步中的交易者在这里。
  4. 无意识的能力:规则已内化。执行感觉很自然。这是冠军的运营状态——但需要通过持续练习和回顾来维护。

2.4 持续自我改进框架


3. 交易选择标准(SEPA精化)

3.1 趋势模板 — 8个强制标准

在股票可以被考虑购买之前,它必须同时通过所有八个标准。没有部分通过。每一项标准在评估日必须满足:

# 标准 理由
1 当前价格高于150日(30周)移动平均线 确认中期上升趋势
2 当前价格高于200日(40周)移动平均线 确认长期上升趋势
3 150日均线在200日均线上方 中期趋势引领长期趋势走高
4 200日均线至少上涨1个月(理想4-5个月以上) 长期趋势已经转变并确立
5 50日(10周)均线高于150日和200日均线 短期趋势引领中期和长期
6 当前价格比52周低点高至少25%(许多龙头股高于100%) 股票已展示显著的上行 momentum
7 当前价格在52周高点的25%以内(理想0-15%以内) 股票处于相对强势位置,接近新高
8 IBD相对强度评级为70或更高(理想80-90+) 股票表现优于大多数其他股票

实施注:标准8可以通过计算股票12个月价格表现相对于宇宙中所有其他股票的百分位等级来近似。RS评级80意味着该股票在过去12个月中表现优于80%的其他股票。

3.2 200日均线作为沙线

200日移动平均线是最重要的单一技术参考:

永远不要购买交易于下降中的200日均线下方的股票。这一规则消除了大多数灾难性损失。

3.3 50日均线相对于200日均线的位置

50日和200日均线之间的关系提供趋势结构:

3.4 价格相对于两条均线

理想设置是价格交易于50日和200日均线上方,股票在VCP收缩期间回调至或"停留在"50日。50日作为第二阶段上升趋势中领先股票的动态支撑位。

3.5 连续更高低点

在筑底模式内,每次连续的收缩应形成比前一次收缩更高的低点。这表明卖压正在减弱,需求在更高水平吸收供应。

这种在底部内的"楼梯式"更高低点机构积累的标志。

3.6 成交量特征


4. 波动收缩模式(VCP)

4.1 定义和识别规则

VCP是Minervini的标志性模式。它描述了一个底部形成,其中价格波动通过一系列连续的收紧而收缩,每次的价格区间都比前一次小。这种收缩代表机构买家逐渐吸收上方供应,直到卖家基本耗尽,股票准备好上涨。

正式定义:VCP由两个或更多的价格区间收缩组成,其中每次连续的收缩都比前一次小,成交量在模式最紧密的部分下降,创造了股票突破的"支点"。

4.2 底部计数 — 第1、第2和第3阶段底部

并非所有VCP都相同。底部的阶段决定成功概率:

如何计数底部:当股票从第二阶段上涨期间的回调超过其平均回调的2.5倍时,或者当它跌破前一个底部低点时,底部计数重置。

4.3 收缩特征(T1 > T2 > T3 > T4)

VCP的标志是每次收紧(T)都比前一次窄:

例子:
T1(第一次收缩):从左侧高点回调30%
T2(第二次收缩):回调15%
T3(第三次收缩):回调7%
T4(第四次收缩):回调3%  <-- 支点区域

关键规则

4.4 支点处的成交量枯竭

支点处的成交量行为至关重要:

红旗:如果成交量在最后一次收缩期间保持 elevated 或增加,表明持续分发(卖出)。模式尚未成熟。

4.5 正确支点 vs 过早支点

正确支点存在时:

过早支点发生于:

4.6 失败的VCP及如何识别

VCP正在失败或可能失败的迹象:

  1. 宽且松散的行为:收缩没有越来越紧。T2几乎与T1一样宽,或T3比T2宽。这表明供应未被吸收。
  2. 下跌日成交量增加:机构在卖出,而非积累。
  3. 跌破最近一次收缩的低点:如果股票在仍在模式内时跌破T3低点,VCP被打破。不要买入。
  4. 时间过长:如果底部超过65周(约15个月),它失去相关性。理想VCP底部为3-26周。
  5. 在底部期间违反200日均线:股票在筑底过程中应保持在200日上方。违反表明趋势正在恶化。
  6. 晚期底部:如上所述,第四阶段及以上VCP失败率高。
  7. 市场环境:即使最好的VCP在熊市中也会失败。始终先检查市场敞口模型(第10节)。

5. 买入规则 — 可买入的向上跳空、支点买入、强势突破

5.1 支点识别

支点是下买入订单的具体价格:

5.2 成交量确认 — 至少比平均高40-50%

在突破日,成交量必须确认走势:

如果突破日成交量低于40%门槛,突破令人怀疑。如果价格保持在支点上方,你仍可持有仓位,但准备好突破停滞或失败。

5.3 可买入的向上跳空(BGU)标准

可买入的向上跳空是最强大的入场信号之一。当股票在 extraordinary 成交量上跳空上涨,通常在业绩或重大积极催化剂之后:

关键区别:并非所有跳空上涨都可买入。来自 extended、晚期位置的跳空是潜在卖出信号(高潮跳空),而非买入信号。

5.4 低位作弊支点入场

低位作弊支点允许在正式突破之前入场:

优势:止损更紧密(2-4% vs 5-8%),因此你可以用相同的美元风险采取更大的仓位。 劣势:更多错误开始。你可能被止损出局,然后不得不在正式支点重新入场。

5.5 强势突破 — 业绩后超紧密突破

强势突破是Minervini用来描述在强劲业绩跳空上涨后形成的极紧密区间突破的术语:

  1. 股票在业绩时以 massive 成交量跳空上涨(BGU)。
  2. 在接下来的1-3周,股票在极紧密的区间内交易(整个整合期间从高点到低点不到3%)。
  3. 成交量在此紧密区间内显著收缩。
  4. 股票在增加成交量的情况下突破紧密区间的高点。

这是最高概率设置之一,因为:


6. 风险管理 与 仓位规模

6.1 每笔交易最大损失

Minervini的系统建立在资本保护的铁律上:

6.2 投资组合热度 — 总风险敞口

投资组合热度是所有持仓中处于风险中的总资本:

如果投资组合热度接近20-25%,不要新增持仓,直到现有持仓上涨到足以将止损移至盈亏平衡,降低总热度。

6.3 仓位规模公式

仓位规模($)= 每笔交易的美元风险 / 距止损距离(%)

其中:
  每笔交易的美元风险 = 投资组合价值 x 每笔交易最大风险(%)
  距止损距离 = (入场价 - 止损价)/ 入场价

例子:
  投资组合:    $100,000
  每笔交易最大风险:1.0% = $1,000
  入场价:  $50.00
  止损价:   $47.00
  距离:     ($50 - $47) / $50 = 6%

  仓位规模 = $1,000 / 0.06 = $16,667
  股数 = $16,667 / $50 = 333股

关键原则:仓位规模是止损距离的函数。更紧密的止损允许更大的持仓。这就是低位作弊支点强大的原因——更紧密的止损允许用相同的美元风险更大的持仓。

6.4 击球率概念

Minervini区分击球率(胜率)和长打率(平均盈利 vs 平均亏损):

期望值公式

期望值 = (胜率 x 平均盈利)-(失败率 x 平均亏损)

例子:
  胜率:50%,平均盈利:$2,000
  失败率:50%,平均亏损:$800
  期望值 = (0.50 x $2,000) - (0.50 x $800) = $1,000 - $400 = $600 每笔交易

冠军交易者优化期望值,而非胜率。如果赢家足够大,在大多数交易上亏损是完全可接受的。

6.5 R倍数跟踪

每笔交易都应该用其R倍数来衡量,其中R = 初始风险(每股从入场到止损的美元距离):

目标:所有交易的平均R倍数应为+1.5R到+3R,才能成为稳健的系统。严格跟踪这个指标。如果平均R倍数随时间下降,过程中的某些东西在退化。


7. 卖出规则

7.1 进攻性卖出 — 强势卖出

进攻性卖出是主动的、计划的卖出以锁定利润:

这种方法在保持对股票潜在继续上涨的敞口的同时锁定利润。

7.2 防御性卖出 — 弱势卖出

防御性卖出是保护资本的反应性卖出:

7.3 20-25%利润规则

基于他对历史超级业绩股票的研究,Minervini观察到许多股票在适当支点后上涨20-25%然后盘整:

7.4 高潮顶部信号

高潮顶部标志股票上涨的结束。识别这些让你在接近顶部卖出,而非回吐利润:

  1. 衰竭跳空(高潮跳空):在 extended 上涨后,股票在 huge 成交量上跳空上涨。这是最后一个跳空,而非第一个。背景很重要——早期跳空是 bullish;晚期跳空是 bearish。
  2. 最宽价差日:股票整个上涨期间的最高价格区间(高点减低点)。这表示极端波动和潜在衰竭。
  3. 最高成交量日:整个上涨期间的最高单日成交量。这代表了"爆裂"——最后一波买家涌入,聪明钱卖给他们。
  4. 铁轨模式:两连大区间日——一涨一跌——在 high 成交量上。反转表示分发。
  5. 抛物线走势:股票上涨速度急剧加快(价格曲线变垂直)。抛物线上涨总是结束——唯一的问题是何时。
  6. 均线延伸:如果股票高于200日均线50-100%+,它已经非常延伸,容易均值回归。

7.5 均线违反作为卖出信号

7.6 时间止损

如果股票从VCP突破然后在 extended 期间没有进展:


8. 投资组合管理

8.1 集中 vs 分散

Minervini主张集中投资组合:

理由:集中强制纪律。如果你只能持有5只股票,你必须选择绝对最佳设置。对于 momentum/成长交易者来说,分散化是对无知的对冲——而Minervini的方法旨在通过严格分析消除无知。

8.2 权益曲线管理

你的权益曲线(随时间累积的盈亏)本身就是一个应该被管理的趋势:

8.3 何时激进 vs 防御

激进时

防御时

8.4 渐进敞口 — 从小额开始,盈利时加仓

Minervini随时间建立持仓的方法:

  1. 初始敞口:当市场首次从调整中转向上涨时,以25-30%的投资组合开始。使用较小的仓位规模。
  2. 试探交易:以正常规模的一半投入1-2个持仓。让市场证明自己。
  3. 随着赢家出现增加敞口:如果初始交易有效(从入场上涨),添加新持仓。增加到50%,然后70%,然后90%+。
  4. 完全敞口:只有当你有多个已经越过盈亏平衡止损且市场明显趋势上涨的赢家时,才投入最大资本。
  5. 在下行中反转过程:随着止损触及和赢家停滞,减少敞口——就像建立时的镜像。

这种"反马丁格尔"方法——赢时增加赌注,输时减少——与大多数交易者做的相反(加倍输家)。这是复利的关键。


9. 交易回顾与记录

9.1 交易后分析框架

每笔交易,无论盈亏,都要进行事后分析:

  1. 设置质量:这是适当的VCP吗?有多少次收缩?成交量确认吗?将设置评为A、B或C。
  2. 入场质量:你在支点买入了吗?成交量足够吗?你追逐了还是过早入场了?将入场评为A、B或C。
  3. 风险管理:止损放置正确吗?你遵守了吗?仓位规模适当吗?
  4. 出场质量:你按计划卖出了吗?你卖得太早(留下显著收益)了吗?你卖得太晚(回吐利润)了吗?
  5. 教训:一句话。这笔交易最重要的收获是什么?

9.2 跟踪的指标

维护记录交易的交易日志(电子表格或专用软件):

指标 描述 目标
胜率 盈利交易百分比 40-60%
平均盈利% 盈利交易的平均百分比收益 15-25%+
平均亏损% 亏损交易的平均百分比损失 3-7%
回报比 平均盈利/平均亏损 3:1或更高
期望值 (胜率 x 平均盈利)-(败率 x 平均亏损) 正值
平均持仓期(W) 赢家平均持仓天数 20-60天
平均持仓期(L) 输家平均持仓天数 5-15天
单笔最大亏损 任何单笔交易的最大损失 <投资组合的2%
最大回撤 峰值到谷底的权益下降 <15-20%
利润因子 总利润/总亏损 >2.0
平均R倍数 所有交易的平均R >+1.5R
连续亏损 连续亏损交易的最大连续次数 注意和管理

9.3 如何识别和修复错误

常见错误在指标中显示:

9.4 年度审计

在每年年底,进行全面回顾:

  1. 总回报 vs 基准:你跑赢标普500了吗?如果没有,为什么?
  2. 最佳5笔交易:它们有什么共同点?它们是第一阶段VCP吗?它们有强劲业绩吗?它们在正确的行业吗?
  3. 最差5笔交易:哪里出了问题?你违反规则了吗?设置实际上有缺陷吗?
  4. 错过的机会:你研究过但没有买入的股票有哪些?为什么?如果买入它们会盈利吗?这有助于识别过度保守。
  5. 规则遵守评分:有多少百分比的交易完全按照计划执行的?目标是90%以上。
  6. 修订计划:根据审计,为下一年更新交易计划。你会做什么不同?

10. 市场敞口模型

10.1 衡量整体市场健康

Minervini不试图预测市场方向。相反,他通过可观察的信号回应市场告诉他的:

10.2 分发日

分发日的定义:

分发日积累

注意:并非所有分发日都相等。成交量略高的轻微0.2%下跌是温和的。成交量高50%的-2.0%下跌是严重的。相应地加权它们。

超过25个交易日的分发日"过期"并从计数中删除。

10.3 何时增加/减少敞口

市场信号 行动 目标敞口
上升趋势确认,突破有效 增加 80-100%已投资
上升趋势完好,信号混合 维持 50-80%已投资
分发积累,突破混合 减少 30-50%已投资
下降趋势确认,突破失败 最小化 0-25%已投资
熊市,瀑布式下跌 退出 0%已投资(100%现金)

10.4 现金作为持仓

现金不是"非持仓"。现金是主动的、有意的配置:

持有现金的意愿——有时是数周或数月——是专业人士与业余人士的区别。大多数交易者感到被迫始终"做点什么"。冠军理解有时最好的行动是不行动。


11. 常见错误

  1. 在底部过早买入:股票尚未完成其VCP收缩。上方供应尚未被吸收。结果:股票反转回底部。

  2. 买入延伸的股票:股票已经比适当支点高10-15%。风险回报反转。结果:正常回调会止损出局导致损失。

  3. 逢低摊平:添加到亏损持仓。这违反了动量交易的每一条原则。结果:小损失变成灾难性损失。

  4. 不遵守止损:最具破坏性的错误。一笔大损失可以抹去数月的纪律性收益。结果:账户损害和心理伤害。

  5. 过度交易:因为感到需要 active 而接受平庸的设置。结果:被千刀万剐。

  6. ** Winners仓位不足**:即使设置完美且股票正在运作,持仓太小,无法对投资组合产生有意义的影响。结果:大量小赢无法抵消不可避免的损失。

  7. 过早卖出赢家:因为害怕回吐利润导致过早离场。股票继续翻倍或翻三倍。结果:截断的收益破坏回报比。

  8. 忽视市场环境:在熊市中买入突破。即使是最好的设置在退潮时也会失败。结果:在其他方面有效的模式上高失败率。

  9. 报复交易:亏损后,立即进入新交易来"弥补"。结果:导致进一步损失的情绪化、不纪律的交易。

  10. 不一致:有时遵循规则,有时忽略它们。结果:没有优势。你无法评估你不一致执行的系统。

  11. 混淆牛市与技能:在强劲牛市中,几乎所有东西都上涨。这滋生过度自信和自满。结果:当市场转向时灾难性损失。

  12. 分析麻痹:研究如此多的指标、模式和变量,以至于你无法对任何交易扣动扳机。结果:错过的机会和沮丧。


12. 完整交易生命周期示例

场景:假设股票"ABCD"交易价格为$48

第一阶段:识别(趋势模板筛选)

日期:1月15日
ABCD当前价格:     $48.00
200日均线:             $38.00(上涨5个月)    -- 通过
150日均线:             $41.00(在200日上方)          -- 通过
50日均线:              $45.50(在150日和200日上方) -- 通过
价格 vs 200日均线:    上方                            -- 通过
价格 vs 150日均线:    上方                            -- 通过
52周低点:            $25.00;当前高于92%     -- 通过(>25%)
52周高点:           $52.00;当前低于8%      -- 通过(<25%)
RS评级:              89                               -- 通过(>70)
所有8项标准:通过 -- 股票进入观察名单

第二阶段:VCP形成(模式监控)

1月15日 - 1月25日:T1收缩
  高点:$52.00(52周高点)
  低点: $43.00
  深度:17.3%(第一次收缩可接受)
  成交量:下跌时平均至略高于平均

1月26日 - 2月8日:T2收缩
  高点:$49.50
  低点: $45.50
  深度:8.1%(不到T1的一半 -- 好)
  成交量:下跌时低于平均

2月9日 - 2月15日:T3收缩
  高点:$49.00
  低点: $47.50
  深度:3.1%(不到T2的一半 -- 优秀)
  成交量:低于50日平均50% -- 成交量枯竭确认

支点确定:$49.00(T3紧密区间的高点)

第三阶段:入场

日期:2月16日
买入止损单放置在$49.10(支点上方10美分)
订单在成交量比50日平均高120%时触发 -- 确认

止损:$46.50(在T3低点下方,低于入场5.3%)
每股风险:$49.10 - $46.50 = $2.60

投资组合:$200,000
每笔交易风险:1.0% = $2,000
仓位规模:$2,000 / $2.60 = 769股
美元价值:769 x $49.10 = $37,758(投资组合的18.9%)

第四阶段:管理

第1周(2月16-20日):ABCD收于$51.25(从入场+4.4%)
  行动:持有。股票表现正常。

第2周(2月23-27日):ABCD收于$54.00(从入场+10.0%)
  行动:将止损提高到盈亏平衡($49.10)。这笔交易的风险已消除。

第3周(3月2-6日):ABCD收于$56.50(从入场+15.1%)
  行动:在$56.50卖出250股。锁定部分利润。
  剩余:519股。

第5周(3月16-20日):ABCD收于$61.00(从入场+24.2%)
  行动:在$61.00卖出250股。接近25%利润目标。
  剩余:269股。沿21日EMA($57.50)追踪止损。

第8周(4月6-10日):ABCD收于$58.00,低于21日EMA
  达到高点$67.00后。
  行动:以$58.00卖出剩余269股。

第五阶段:交易后回顾

总股数:769
  第一批:250股以$56.50卖出,收益 = $7.40/股 = $1,850
  第二批:250股以$61.00卖出,收益 = $11.90/股 = $2,975
  第三批:269股以$58.00卖出,收益 = $8.90/股 = $2,394

总利润:$7,219
持仓回报:$7,219 / $37,758 = 19.1%
投资组合回报:$7,219 / $200,000 = 3.6%
R倍数:$7,219 / $2,000 = +3.6R

设置质量:A(教科书式VCP,3次收缩,成交量枯竭)
入场质量:A(成交量确认的支点买入)
管理质量:B+(本可以持有最后一批更久一点)

13. 实施伪代码

13.1 VCP检测算法

def detect_vcp(price_data, volume_data, lookback=130):
    """
    在历史价格数据中检测波动收缩模式。

    参数:
        price_data: 包含列[date, open, high, low, close]的DataFrame
        volume_data: 每日成交量序列
        lookback: 最大底部长度,以交易日计(默认约26周)

    返回:
        带有收缩详情和支点水平的VCP对象列表
    """
    vcps = []
    highs = price_data['high']
    lows = price_data['low']
    closes = price_data['close']
    avg_volume_50 = volume_data.rolling(50).mean()

    # 第1步:找到左侧高点(底部开始)
    left_high = highs.rolling(lookback).max()

    # 第2步:识别收缩
    for i in range(lookback, len(price_data)):
        base_start = i - lookback
        base_high = highs[base_start:i].max()
        contractions = find_contractions(highs, lows, base_start, i)

        if len(contractions) < 2:
            continue  # 需要至少2次收缩

        # 第3步:验证收缩正在收紧
        depths = [c['depth_pct'] for c in contractions]
        if not is_decreasing(depths):
            continue  # 收缩必须变窄

        # 第4步:检查收缩比率(每次大约是前一次的一半或更少)
        ratios_valid = all(
            depths[j+1] <= depths[j] * 0.75
            for j in range(len(depths) - 1)
        )
        if not ratios_valid:
            continue

        # 第5步:检查最后一次收缩的成交量枯竭
        final_contraction = contractions[-1]
        final_volume = volume_data[
            final_contraction['start']:final_contraction['end']
        ].mean()
        volume_ratio = final_volume / avg_volume_50.iloc[i]

        if volume_ratio > 0.70:
            continue  # 成交量不够枯竭(想要<平均的70%)

        # 第6步:检查最终区域的紧密性
        final_range = (
            highs[final_contraction['start']:final_contraction['end']].max() -
            lows[final_contraction['start']:final_contraction['end']].min()
        )
        final_tightness = final_range / closes.iloc[i]
        if final_tightness > 0.05:
            continue  # 最终区域太宽(想要<5%,理想<3%)

        # 第7步:定义支点
        pivot = highs[final_contraction['start']:final_contraction['end']].max()

        vcps.append({
            'date': price_data['date'].iloc[i],
            'pivot': pivot,
            'num_contractions': len(contractions),
            'contraction_depths': depths,
            'volume_ratio': volume_ratio,
            'final_tightness': final_tightness,
            'quality_score': score_vcp(depths, volume_ratio, final_tightness)
        })

    return vcps


def find_contractions(highs, lows, start, end):
    """识别日期范围内的摆动高点到低点收缩。"""
    contractions = []
    swing_highs = find_swing_highs(highs, start, end, window=5)
    swing_lows = find_swing_lows(lows, start, end, window=5)

    for j in range(len(swing_highs)):
        sh = swing_highs[j]
        # 找到这个摆动高点之后的下一个摆动低点
        subsequent_lows = [sl for sl in swing_lows if sl['index'] > sh['index']]
        if not subsequent_lows:
            continue
        sl = subsequent_lows[0]
        depth = (sh['price'] - sl['price']) / sh['price'] * 100
        contractions.append({
            'start': sh['index'],
            'end': sl['index'],
            'high': sh['price'],
            'low': sl['price'],
            'depth_pct': depth
        })

    return contractions


def score_vcp(depths, volume_ratio, tightness):
    """VCP质量评分0-100。"""
    score = 0
    # 更多收缩 = 更好(最多4次)
    score += min(len(depths), 4) * 10  # 最多40

    # 更紧的收缩比率 = 更好
    avg_ratio = sum(depths[i+1]/depths[i] for i in range(len(depths)-1)) / (len(depths)-1)
    if avg_ratio < 0.4:
        score += 25
    elif avg_ratio < 0.55:
        score += 15
    else:
        score += 5

    # 更低成交量 = 更好
    if volume_ratio < 0.4:
        score += 20
    elif volume_ratio < 0.6:
        score += 10
    else:
        score += 5

    # 更紧的最终区域 = 更好
    if tightness < 0.02:
        score += 15
    elif tightness < 0.03:
        score += 10
    else:
        score += 5

    return score

13.2 趋势模板筛选

def passes_trend_template(stock_data):
    """
    检查股票是否通过Minervini趋势模板的全部8项标准。

    参数:
        stock_data: 包含列[date, close, volume]的DataFrame
                    至少需要250行数据。

    返回:
        (bool, dict): (全部通过, 单项结果)的元组
    """
    close = stock_data['close']
    current_price = close.iloc[-1]

    ma_50 = close.rolling(50).mean().iloc[-1]
    ma_150 = close.rolling(150).mean().iloc[-1]
    ma_200 = close.rolling(200).mean().iloc[-1]

    # 检查200日均线至少上涨1个月(22个交易日)
    ma_200_series = close.rolling(200).mean()
    ma_200_one_month_ago = ma_200_series.iloc[-22]
    ma_200_trending_up = ma_200_series.iloc[-1] > ma_200_one_month_ago

    # 52周高点和低点
    high_52w = stock_data['high'].iloc[-252:].max()
    low_52w = stock_data['low'].iloc[-252:].min()

    pct_above_low = ((current_price - low_52w) / low_52w) * 100
    pct_below_high = ((high_52w - current_price) / high_52w) * 100

    # 相对强度(简化:12个月百分位等级)
    twelve_month_return = (current_price / close.iloc[-252] - 1) * 100
    # 生产中,与宇宙中所有股票比较
    rs_rating = compute_rs_rating(twelve_month_return)

    results = {
        '1_price_above_150ma': current_price > ma_150,
        '2_price_above_200ma': current_price > ma_200,
        '3_150ma_above_200ma': ma_150 > ma_200,
        '4_200ma_trending_up': ma_200_trending_up,
        '5_50ma_above_150_and_200': ma_50 > ma_150 and ma_50 > ma_200,
        '6_price_25pct_above_52w_low': pct_above_low >= 25,
        '7_price_within_25pct_of_52w_high': pct_below_high <= 25,
        '8_rs_rating_above_70': rs_rating >= 70
    }

    passes_all = all(results.values())
    return passes_all, results

13.3 持仓管理系统

class PositionManager:
    """用Minervini的风险规则管理活动持仓。"""

    def __init__(self, portfolio_value, max_risk_per_trade=0.01,
                 max_portfolio_heat=0.20, max_positions=6):
        self.portfolio_value = portfolio_value
        self.max_risk_per_trade = max_risk_per_trade
        self.max_portfolio_heat = max_portfolio_heat
        self.max_positions = max_positions
        self.positions = {}
        self.trade_log = []

    def calculate_position_size(self, entry_price, stop_price):
        """根据风险参数计算买入股数。"""
        risk_per_share = entry_price - stop_price
        if risk_per_share <= 0:
            raise ValueError("止损必须在入场价下方")

        dollar_risk = self.portfolio_value * self.max_risk_per_trade
        shares = int(dollar_risk / risk_per_share)
        position_value = shares * entry_price

        # 限制在每持仓约25%的投资组合
        max_position = self.portfolio_value * 0.25
        if position_value > max_position:
            shares = int(max_position / entry_price)

        return shares, dollar_risk

    def current_portfolio_heat(self):
        """计算所有持仓的总风险敞口。"""
        total_heat = 0
        for symbol, pos in self.positions.items():
            risk = (pos['current_price'] - pos['stop_price']) * pos['shares']
            # 只计算负风险(止损在当前价下方的持仓)
            if pos['stop_price'] < pos['current_price']:
                risk = (pos['entry_price'] - pos['stop_price']) * pos['shares']
            else:
                risk = 0  # 止损在入场价或上方(无风险)
            total_heat += max(risk, 0)
        return total_heat / self.portfolio_value

    def can_add_position(self):
        """检查我们是否可以添加新持仓。"""
        if len(self.positions) >= self.max_positions:
            return False, "达到最大持仓数"
        if self.current_portfolio_heat() >= self.max_portfolio_heat:
            return False, "投资组合热度达到最大"
        return True, "正常"

    def open_position(self, symbol, entry_price, stop_price, date):
        """开新持仓。"""
        can_add, reason = self.can_add_position()
        if not can_add:
            raise ValueError(f"无法添加持仓:{reason}")

        shares, dollar_risk = self.calculate_position_size(entry_price, stop_price)

        self.positions[symbol] = {
            'entry_price': entry_price,
            'stop_price': stop_price,
            'current_price': entry_price,
            'shares': shares,
            'initial_shares': shares,
            'entry_date': date,
            'initial_risk': dollar_risk,
            'partial_sells': []
        }
        return shares

    def update_stops(self, symbol, current_price):
        """根据价格进展更新止损。"""
        pos = self.positions[symbol]
        entry = pos['entry_price']
        initial_risk = entry - pos['stop_price']

        gain_from_entry = current_price - entry
        gain_in_r = gain_from_entry / initial_risk if initial_risk > 0 else 0

        # 在+2R时将止损移至盈亏平衡
        if gain_in_r >= 2.0 and pos['stop_price'] < entry:
            pos['stop_price'] = entry
            print(f"{symbol}: 止损提高到盈亏平衡 at {entry:.2f}")

        # 在+3R后沿21日EMA追踪止损
        # (生产中,传入实际的21-EMA值)

        pos['current_price'] = current_price

    def check_sell_signals(self, symbol, current_price, ma_10, ma_21_ema,
                            ma_50, daily_volume, avg_volume_50, days_held):
        """检查所有卖出条件并返回信号。"""
        signals = []
        pos = self.positions[symbol]
        entry = pos['entry_price']
        gain_pct = (current_price - entry) / entry * 100

        # 防御性:硬止损
        if current_price <= pos['stop_price']:
            signals.append(('SELL_ALL', '硬止损触及'))

        # 进攻性:20-25%利润目标
        if gain_pct >= 20:
            signals.append(('SELL_PARTIAL', '20%+利润目标达到'))

        # 均线违反
        if current_price < ma_21_ema and daily_volume > avg_volume_50:
            signals.append(('SELL_PARTIAL', '21-EMA违反且成交量高于平均'))
        if current_price < ma_50 and daily_volume > avg_volume_50:
            signals.append(('SELL_ALL', '50日均线违反且成交量高于平均'))

        # 时间止损
        if days_held >= 25 and gain_pct < 3:
            signals.append(('SELL_ALL', '时间止损:5周,无进展'))
        elif days_held >= 15 and gain_pct < 2:
            signals.append(('SELL_PARTIAL', '时间警告:3周,最小进展'))

        # 高潮信号
        # (需要额外数据:最宽区间、上涨期间最高成交量等)

        return signals

    def close_position(self, symbol, exit_price, date, reason):
        """平仓并记录交易。"""
        pos = self.positions[symbol]
        pnl = (exit_price - pos['entry_price']) * pos['shares']

        # 添加部分卖出利润
        for ps in pos['partial_sells']:
            pnl += (ps['price'] - pos['entry_price']) * ps['shares']

        r_multiple = pnl / pos['initial_risk'] if pos['initial_risk'] > 0 else 0

        self.trade_log.append({
            'symbol': symbol,
            'entry_price': pos['entry_price'],
            'exit_price': exit_price,
            'shares': pos['shares'],
            'pnl': pnl,
            'r_multiple': r_multiple,
            'entry_date': pos['entry_date'],
            'exit_date': date,
            'reason': reason
        })

        del self.positions[symbol]
        return pnl, r_multiple

14. 关键语录

"交易不是预测未来。交易是识别——识别与历史上最大行情开始时相同的设置。当设置正确时,你不需要知道股票明天会做什么。你只需要买入并让市场验证你的判断。"

"每笔交易都是概率游戏。不是关于对错——而是关于当对时赚很多,当错时损失少。接受这个现实,你会成为更好的交易者。"

"最赚钱的交易通常感觉最不舒服。在别人害怕时买入,在别人贪婪时卖出——说起来容易,做起来难。这就是为什么成功只属于少数人。"

"止损是交易的生命保险。不带止损交易就像不带 parachute 跳伞。一次大亏损可以抹去你花了数月或数年建立的账户。"

"股票不会关心你买了多少或者你花了多少时间研究它。它们不在乎你的感受。尊重价格行为——它告诉你真相,而不是你的意见。"

"表现最好的交易通常是最容易持有的交易——设置清晰,股票行为正常,止损合理。你不需要紧紧抓住它;你只需要让它运行。"

"最大的敌人不是市场。它是你自己的情绪。恐惧、贪婪、希望——这些是屠杀交易者的凶手。学会控制它们,或者它们会控制你。"

"规则是为了保护你。你的规则越严格,你越自由。没有结构,你就是情绪的玩物,被每一次波动摇摆。"

"记住,每笔交易都是独立的。不要让过去的亏损影响你的下一步决策。也不要让过去的盈利让你过度自信。每一刻都是新的。"


本规范综合了Mark Minervini在《像冠军一样思考与交易》中的核心方法论,构建为高概率成长股交易的完整实施指南,强调心理纪律、模式识别、风险管理和持续自我改进。

(文件结束)