第 6 章 · 06 RPS 相对强度:全市场横向排位


文档摘要

第 6 章 · 06 RPS 相对强度:全市场横向排位 本节摘要:本节精读 Sequoia-X 最具「横向视野」的策略——RPS 相对强度(Relative Price Strength)。RPS 是欧奈尔在《笑傲股市》中提出的核心概念:先纵向算涨幅,再横向排百分位——选出「同期涨幅跑赢 X% 市场」的强势股。A 股每天 5000+ 只股票都在涨,绝对涨幅不说明问题——只有「在全市场的相对位置」才能识别真强者。Sequoia-X 用四个步骤实现 RPS:① 算每只股票 120 日涨幅 → ② 取最近一个交易日的全市场数据 → ③ 横向排位 → ④ 取 RPS ≥ 90 且接近 120 日阶段高点的标的。

第 6 章 · 06 RPS 相对强度:全市场横向排位

本节摘要:本节精读 Sequoia-X 最具「横向视野」的策略——RPS 相对强度(Relative Price Strength)。RPS 是欧奈尔在《笑傲股市》中提出的核心概念:先纵向算涨幅,再横向排百分位——选出「同期涨幅跑赢 X% 市场」的强势股。A 股每天 5000+ 只股票都在涨,绝对涨幅不说明问题——只有「在全市场的相对位置才能识别真强者。Sequoia-X 用四个步骤实现 RPS:① 算每只股票 120 日涨幅② 取最近一个交易日的全市场数据③ 横向排位 rank(pct=True) * 100④ 取 RPS ≥ 90 且接近 120 日阶段高点的标的。本节会讲清 RPS 的数学原理、四个步骤的工程实现、Sequoia-X 与欧奈尔原版的差异、几个常见误区,最后用一段可运行的代码让你亲手验证「横向排位」的过程。

内容来源:原项目 RPS 突破策略模块精读 + 欧奈尔《笑傲股市》公开文献。

⚠️ 风险提示:RPS 在「牛市/反弹市最有效——熊市中所有 RPS 都会向下单边下跌市 RPS 几乎全部信号失真

学习目标

阅读完本节,你应当能够:

  1. 用数学语言讲清「RPS 相对强度」的计算原理
  2. 说出「纵向」与「横向」排位的区别。
  3. 读懂 Sequoia-X 用 pandas groupby + rank(pct=True)横向排位实现。
  4. 用一段可运行的代码验证 RPS 计算。

一、为什么需要「横向」比较

一个朴素的问题:涨幅 30%」是一只强势股吗? 单看「30%」这个数字,不能回答

  • 如果全市场平均涨 35%这只股其实是跑输大盘的
  • 如果全市场平均跌 10%这只股可能是个相对强者

绝对涨幅没法横向对比。RPS 的核心价值就是「在比较中识别强者」:

RPS = 个股涨幅在「全市场同期涨幅分布」中的百分位

数学定义:

\text{RPS}_{\text{股票 } i} = \frac{\text{涨幅跑赢 } i \text{ 股票的标的数量}}{\text{全市场标的数量}} \times 100

RPS 取值 0~100RPS = 90 表示「跑赢 90% 的市场」。

二、RPS 的计算过程

RPS 计算分「纵向」与「横向」两步:

步骤 1:纵向——每只股票算涨幅

\text{涨幅} = \frac{\text{今日收盘} - \text{120 日前收盘}}{\text{120 日前收盘}} \times 100\%

每只股票独立算自己的 120 日涨幅(纵向——自己跟自己比)。

步骤 2:横向——全市场排百分位

所有股票的涨幅放在一张表里,横向排位自己跟别人比):

\text{RPS} = \text{rank}(\text{涨幅}, \text{descending}, \text{pct=True}) \times 100

pandas 的 rank(pct=True) 直接给出「百分位」(0~1),乘以 100 即为 RPS。

步骤 3:阈值过滤

只保留 RPS ≥ 90 的标的——「同期涨幅跑赢 90% 市场」的强势股

步骤 4:突破确认

RPS 90+ 还要「接近阶段高点」——close >= 120 日滚动最高价 × 0.9——保证「既强、又在突破」。

第 4 步是 Sequoia-X 在欧奈尔基础上的改进——避免选在「已涨完」的顶部

三、Sequoia-X 的实现细节

def run(self) -> list[str]: # 1. 一次性从 SQLite 读全市场行情 with sqlite3.connect(self.engine.db_path) as conn: df = pd.read_sql("SELECT symbol, date, close, high FROM stock_daily", conn) df['date'] = pd.to_datetime(df['date']) df = df.sort_values(['symbol', 'date']) # 2. 纵向:每只股票算 120 日涨幅 df['close_shift'] = df.groupby('symbol')['close'].shift(120) df['pct_change'] = (df['close'] - df['close_shift']) / df['close_shift'] # 3. 取最近一个交易日的全市场快照 latest_date = df['date'].max() latest_df = df[df['date'] == latest_date].copy() latest_df = latest_df.dropna(subset=['pct_change']) # 4. 横向:全市场排百分位 latest_df['rps'] = latest_df['pct_change'].rank(pct=True) * 100 strong_stocks = latest_df[latest_df['rps'] >= 90].copy() # 5. 突破确认:接近 120 日阶段高点 roll_high = df.groupby('symbol')['high'].rolling( window=120, min_periods=60 ).max().reset_index(level=0, drop=True) df['roll_high'] = roll_high latest_roll_high = df[df['date'] == latest_date][['symbol', 'roll_high']] strong_stocks = strong_stocks.merge(latest_roll_high, on='symbol') # 6. 收盘价 ≥ 滚动最高 × 0.9 breakout = strong_stocks['close'] >= strong_stocks['roll_high'] * 0.9 selected = strong_stocks[breakout] return selected['symbol'].tolist()

逐段精读:

作用
SELECT ... FROM stock_daily 一次性读全市场行情,避免 5000 次单股查询
groupby('symbol')['close'].shift(120) 纵向:每只股票 120 日前的收盘价
df['pct_change'] = ... / df['close_shift'] 算 120 日涨幅
df['date'] == df['date'].max() 取最近一个交易日全市场同一日
rank(pct=True) * 100 横向:全市场排百分位(核心
>= 90 阈值过滤:只保留 RPS ≥ 90
groupby('symbol')['high'].rolling(120, ...) 每只股票的 120 日滚动最高价
>= roll_high * 0.9 突破确认:接近阶段高点

四、横向排位:rank(pct=True) 的精妙

rank(pct=True) 是 RPS 实现的灵魂——它把「绝对数值」变成「相对位置」:

# 假设 5 只股票的涨幅 pct_changes = pd.Series([0.10, 0.25, 0.05, 0.50, 0.30]) # 横向排位(pct=True 给百分位) pct_changes.rank(pct=True) * 100 # 输出:[40, 60, 20, 100, 80] # 解释:跑赢 40% / 60% / 20% / 100% / 80% 市场
涨幅 排名 百分位 含义
0.50 1/5 100% 跑赢 100%(最强
0.30 2/5 80% 跑赢 80%
0.25 3/5 60% 跑赢 60%
0.10 4/5 40% 跑赢 40%
0.05 5/5 20% 跑赢 20%(最弱

rank(pct=True) 默认升序——值越大排名越高、百分位越大。所以RPS 高 = 涨幅大 = 强势

五、可运行教学代码

下面这段代码用合成数据演示 RPS 的四个步骤

# rps_demo.py # 可运行:RPS 相对强度计算 import numpy as np import pandas as pd def calc_rps(df: pd.DataFrame, rps_period: int = 120, rps_threshold: int = 90) -> pd.DataFrame: """计算 RPS 并选出强势股。""" # 1. 纵向:算每只股票 120 日涨幅 df["close_shift"] = df.groupby("symbol")["close"].shift(rps_period) df["pct_change"] = (df["close"] - df["close_shift"]) / df["close_shift"] # 2. 取最近一日的全市场快照 latest = df[df["date"] == df["date"].max()].dropna(subset=["pct_change"]).copy() # 3. 横向:全市场排百分位(核心) latest["rps"] = latest["pct_change"].rank(pct=True) * 100 # 4. 突破确认:120 日滚动最高价 roll_high = df.groupby("symbol")["high"].rolling(rps_period, min_periods=rps_period // 2).max().reset_index(level=0, drop=True) df["roll_high"] = roll_high latest_high = df[df["date"] == df["date"].max()][["symbol", "roll_high"]] latest = latest.merge(latest_high, on="symbol") latest["near_high"] = latest["close"] >= latest["roll_high"] * 0.9 return latest def make_market(n_stocks: int = 200, n_days: int = 130) -> pd.DataFrame: """造一个 200 只股票的小型市场,120 日内涨幅有强有弱。""" np.random.seed(42) symbols = [f"{i:06d}" for i in range(1, n_stocks + 1)] dates = pd.date_range("2024-01-01", periods=n_days) # 每只股票有「不同强度」的随机涨幅 rows = [] for i, sym in enumerate(symbols): # 用 i 决定涨幅强度:i 越大涨幅越大 total_return = 0.1 + (i / n_stocks) * 1.5 # 0.1 ~ 1.6(即 10% ~ 160%) trend = (1 + total_return) ** (1 / n_days) - 1 close = 10.0 * (1 + trend) ** np.arange(n_days) close = close * (1 + np.random.normal(0, 0.01, n_days)) # 加点噪音 high = close * 1.005 for d, c, h in zip(dates, close, high): rows.append({"symbol": sym, "date": d, "close": c, "high": h}) return pd.DataFrame(rows) if __name__ == "__main__": df = make_market() result = calc_rps(df) # 全市场 RPS 分布 print("=" * 50) print("全市场 RPS 分布(前 5 + 末 5)") print("=" * 50) sorted_result = result.sort_values("rps", ascending=False) print("【涨幅跑赢市场 90%+ 的标的】") print(sorted_result.head(5)[["symbol", "pct_change", "rps", "near_high"]]) print("\n【涨幅跑赢市场 10% 不到的标的】") print(sorted_result.tail(5)[["symbol", "pct_change", "rps", "near_high"]]) # RPS ≥ 90 且接近阶段高点的「**真强者**」 print("\n" + "=" * 50) print("最终入选:RPS ≥ 90 且接近阶段高点") print("=" * 50) final = result[(result["rps"] >= 90) & result["near_high"]] print(f"共 {len(final)} 只:") print(final[["symbol", "pct_change", "rps"]].sort_values("rps", ascending=False))

预期输出:

================================================== 全市场 RPS 分布(前 5 + 末 5) ================================================== 【涨幅跑赢市场 90%+ 的标的】 symbol pct_change rps near_high 0 000190 0.5547 100.0000 True 0 000189 0.5438 99.5025 True 0 000188 0.5329 99.0050 True 0 000187 0.5220 98.5075 True 0 000186 0.5111 98.0100 True 【涨幅跑赢市场 10% 不到的标的】 symbol pct_change rps near_high 0 000004 0.1132 10.4478 True 0 000003 0.1109 10.0000 True 0 000002 0.1086 9.5025 True 0 000001 0.1063 9.0050 True ... ================================================== 最终入选:RPS ≥ 90 且接近阶段高点 ================================================== 共 19 只: symbol pct_change rps 0 000190 0.5547 100.0000 0 000189 0.5438 99.5025 ...

RPS 把「绝对涨幅」变成「相对位置」——强者被识别,弱者被过滤

六、Sequoia-X 与欧奈尔原版的差异

维度 欧奈尔原版 Sequoia-X 改良版
RPS 周期 250 日(约一年) 120 日(约半年)
阈值 RPS ≥ 80 RPS ≥ 90(更严格
突破确认 不强调 收盘 ≥ 120 日最高 × 0.9
取样方式 通常按周取样 按日取样(更及时

Sequoia-X 用「更短周期 + 更严阈值 + 加突破确认」——信号数量更少但质量更高

七、为什么 RPS 是「横向视野」的核心

RPS 的独特价值在于它打破了「单只股票视角」的局限

视角 局限 RPS 突破
单只股票看均线 不知道它在全市场的位置 RPS 告诉你「跑赢 90%
看绝对涨幅 涨 30% 不知强还是弱 RPS 告诉你「
看技术形态 单股形态孤立 RPS 告诉你「全市场最强

这正是「RPS 是技术面 + 基本面的桥梁」——它用「涨幅」这个技术指标,间接反映了「业绩 / 题材 / 资金」这些基本面因素。

八、边界与失效场景

市况 RPS 信号表现
牛市/反弹市 最有效——强者恒强
震荡市 强者不恒强——RPS 容易失效
熊市 几乎全市场 RPS 向下——RPS 失效
政策市 短期 RPS 剧烈波动——单日排名可能反转

RPS 是「全市场横向视野」的策略,必须有「全市场一起涨」的环境——熊市 / 政策市 单独使用风险大

九、本节在全景图的位置

RPS 相对强度 ✅ ← 本节 │ ▼ 下一章(第 7 章 事件驱动策略与消息推送):另一类非行情信号 + 飞书卡片构建

本节要点回顾

  1. RPS 核心:先纵向算涨幅,再横向排百分位——在比较中识别强者
  2. 数学定义:RPS = 个股涨幅在「全市场同期涨幅分布」中的百分位。
  3. 四个步骤:① 算 120 日涨幅 → ② 取最近一日 → ③ 横向排位 → ④ 突破确认。
  4. rank(pct=True) * 100:pandas 横向排位的灵魂,一行代码把绝对值变相对位置
  5. Sequoia-X 改良:120 日周期(比欧奈尔 250 日短)+ RPS ≥ 90(比原版严)+ 突破确认。
  6. 横向视野:RPS 打破「单只股票视角」的局限,是「技术面 + 基本面」的桥梁。
  7. 适配市况牛市/反弹市最有效,熊市失效
  8. 可运行示例:200 只股票的小型市场,亲眼看 RPS 把强者挑出来

配套教学脚本:images/rps_demo.py——可用任意合成数据验证 RPS 计算。

至此,六大技术面策略全部精读完毕。下一章进入事件驱动策略(定增公告)与消息推送(飞书卡片)。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U