第 2 章 · 02 ATR 波动率与 RSI 超买超卖(对应 QS009/010)


文档摘要

第 2 章 · 02 ATR 波动率与 RSI 超买超卖(对应 QS009/010) 速查摘要:本节用两个脚本学两个经典指标。ATR(Average True Range,真实波动幅度) 衡量"一天内价格能波动多少",是风控和仓位管理的核心——J. Welles Wilder 在《New Concepts in Technical Trading Systems》提出;RSI(Relative Strength Index,相对强弱指数) 衡量"近期涨势相对跌势有多强",>70 超买、 70 超买(可能回调),RSI 0)也可能向下(Low - 前收 70 超买、<30 超卖。 跨列取最大( 按行),ATR 里三列取 max 必须写对方向。

第 2 章 · 02 ATR 波动率与 RSI 超买超卖(对应 QS009/010)

速查摘要:本节用两个脚本学两个经典指标。ATR(Average True Range,真实波动幅度) 衡量"一天内价格能波动多少",是风控和仓位管理的核心——J. Welles Wilder 在《New Concepts in Technical Trading Systems》提出;RSI(Relative Strength Index,相对强弱指数) 衡量"近期涨势相对跌势有多强",>70 超买、<30 超卖,是震荡策略的标配。两个指标都从 OHLC 收盘价衍生,核心 pandas 技巧分别是 shift(1) 取前收(配 TR 三量取 max)和 clip 分离涨跌。

涉及脚本:原项目 QS009-average-true-range/01_atr.py(约 77 行 Python);QS010-relative-strength-index/01_rsi.py(约 91 行 Python)

⚠️ 注意:两个脚本都依赖 OpenBB 3(from openbb_terminal.sdk import openbb),v4 不兼容(脚本里有注释掉的 v4 兼容写法可参考)。ATR 这版用 SMA 简化了 Wilder 原版的 EMA 平滑,结果会与 ta-lib 等严格实现略有差异——做研究够用,做实盘建议用 Wilder 原版 EMA。RSI 同样用 SMA 而非 Wilder EMA,思路一致。

工具与原理

ATR:True Range 三量取 max

ATR 的核心是 True Range(TR)——衡量"一根 K 线的真实波动幅度"。为什么不是简单的 High - Low?因为存在跳空缺口:如果今天开盘就跳空高开,今天的 High/Low 体现不出昨天到今天的全部波动。所以 Wilder 定义 TR 为下面三个量取最大:

TRₜ = max( Hₜ - Lₜ, # 当日振幅 |Hₜ - Cₜ₋₁|, # 当日最高与前收的跳空 |Lₜ - Cₜ₋₁| # 当日最低与前收的跳空 )

其中 Cₜ₋₁ 是前一日收盘价。然后 ATR 是 TR 的滚动平均(本脚本用 SMA,Wilder 原版用 EMA):

ATRₜ = SMA(TR, 14)

金融用途:ATR 直接反映资产"当天的典型波动",用来定止损位(比如止损 = 入场价 - 2×ATR)、定仓位大小(波动大的品种少买,把单笔风险控制在固定金额)。这是海龟交易法的核心。

RSI:涨跌强度比

RSI 由 J. Welles Wilder 同期提出,衡量一段时间内"涨势强度相对跌势强度"。思路:

  1. 算每日价格变化 delta = Close.diff()
  2. 分离上涨和下跌:
    • gain = max(delta, 0)(只保留正变化)
    • loss = -min(delta, 0)(只保留负变化,取绝对值)。
  3. 算 window(通常 14)期内的平均 gain 和平均 loss。
  4. RS(Relative Strength)= avg_gain / avg_loss
  5. RSI = 100 - 100/(1+RS)

RSI 把比值压到 0-100 区间:全涨时 avg_loss=0 → RS→∞ → RSI→100;全跌时 avg_gain=0 → RS=0 → RSI=0;涨跌各半 RS=1 → RSI=50。经验阈值:**RSI > 70 超买**(可能回调),**RSI < 30 超卖**(可能反弹)。但强趋势里 RSI 可长期停在超买区,不能机械反向交易,需结合趋势判断。

SMA vs Wilder EMA 的差别

Wilder 原版用一种特殊 EMA(等价于 alpha=1/period,即 span=2*period-1)。脚本简化用 rolling(period).mean()(SMA)。两者数值接近但略有差异——研究阶段无所谓,与第三方库(ta-lib)对数时记得检查平滑方式。

脚本精读

QS009:ATR 实现

核心函数 calculate_atr(40-47 行):

def calculate_atr(df, period=14): df['H-L'] = df['High'] - df['Low'] df['H-PC'] = abs(df['High'] - df['Close'].shift(1)) df['L-PC'] = abs(df['Low'] - df['Close'].shift(1)) df['TR'] = df[['H-L', 'H-PC', 'L-PC']].max(axis=1) df['ATR'] = df['TR'].rolling(window=period).mean() df.drop(['H-L', 'H-PC', 'L-PC', 'TR'], axis=1, inplace=True) return df

逐行解读:

  • df['H-L']:当日振幅。
  • df['H-PC']/df['L-PC']:PC = Previous Close,关键操作是 df['Close'].shift(1) 把收盘价往下挪一行,实现"当日行对齐前日收盘"。这是时序计算里最核心的技巧:shift(1) = 取昨日值,shift(-1) = 取明日值。没有 shift,OHLC 在同一行无法体现"跨日跳空"。
  • abs() 取绝对值,因为跳空可能向上(High - 前收 > 0)也可能向下(Low - 前收 < 0),TR 看幅度不看方向。
  • df[['H-L','H-PC','L-PC']].max(axis=1) 三列按行取最大。axis=1 是"跨列取 max",得到每行的 TR;默认 axis=0 是"跨行取 max",会得到三个标量,不是我们要的。
  • rolling(window=14).mean() SMA 平滑得 ATR(Wilder 原版用 EMA,这里简化)。
  • 最后 drop 删掉中间列,只留 ATR——保持 df 整洁。

💡 核心心法:ATR 的关键是 shift(1) 取前收。时序里"今天的某量"经常要参考"昨天的某量",shift 是这个对齐操作的通用工具。记住:shift(1) = 昨天,shift(-1) = 明天。

画图(53-74 行)——双子图:

plt.figure(figsize=(10, 8)) plt.subplot(2, 1, 1) plt.plot(df.index, df['High'], label='High', color='green') plt.plot(df.index, df['Low'], label='Low', color='red') plt.plot(df.index, df['Close'], label='Close', color='blue') plt.title('Stock Prices (High, Low, Close)') plt.subplot(2, 1, 2) plt.plot(df_with_atr.index, df_with_atr['ATR'], label='ATR', color='orange') plt.title('Average True Range (ATR)') plt.tight_layout() plt.show()

plt.subplot(2,1,k) 表示"2 行 1 列网格的第 k 个",上下两图:上是价格,下是 ATR。tight_layout() 自动调整间距防重叠。

QS010:RSI 实现

核心计算(46-64 行):

delta = df['Close'].diff() gain = delta.clip(lower=0) loss = -delta.clip(upper=0) window_length = 14 avg_gain = gain.rolling(window=window_length, min_periods=window_length).mean() avg_loss = loss.rolling(window=window_length, min_periods=window_length).mean() RS = avg_gain / avg_loss RSI = 100 - (100 / (1 + RS)) df['RSI'] = RSI

逐段解读:

  • delta = df['Close'].diff() 算逐日价格变化(今日 - 昨日)。diff() 等价于 Close - Close.shift(1)
  • gain = delta.clip(lower=0) 把所有负值截成 0,只留上涨部分。clip(lower=0) 是 pandas 里"截断到下界"的优雅写法,比 delta.where(delta>0, 0) 更直观。
  • loss = -delta.clip(upper=0) 这行很巧妙:先 clip(upper=0) 把正值截成 0(只留下跌部分,都是负数),再取负变正。这样 gain 和 loss 都是非负数,可直接比较。这种"clip 双向分离"是分离涨跌的标准技巧,比写循环快得多。
  • rolling(window=14, min_periods=14) 算 14 期平均 gain/loss;min_periods=14 表示前 13 行不出值(保证窗口满),与 Wilder 定义一致。
  • RS = avg_gain / avg_loss 相对强弱。
  • RSI = 100 - 100/(1+RS) 把 RS 压到 0-100。当 avg_loss=0 时除零会得 inf,100/(1+inf)=0,RSI=100,符合"全涨"预期(pandas 会自动处理 inf)。

💡 核心心法:clip(lower=0) 取上涨、-delta.clip(upper=0) 取下跌,这两行是分离 gain/loss 最地道的写法,一行替代一个 if-else 循环。背下来,所有涉及"涨跌分离"的指标都能用。

画图(72-85 行)——gridspec_kw 控子图比例:

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), gridspec_kw={'height_ratios': [3, 1]}) ax1.plot(df['Close'], color='blue') ax1.set_title(f'{SYMBOL} Closing Price') ax2.plot(df['RSI'], color='green') ax2.set_ylim([0, 100]) ax2.axhline(70, color='red', linestyle='--') ax2.axhline(30, color='red', linestyle='--')

关键技巧 gridspec_kw={'height_ratios': [3, 1]}:上下两个子图高度比 3:1——价格图大、RSI 图小。这是因为 RSI 只是个 0-100 的辅助指标,不需要和价格同等篇幅;等高会让价格图被压扁。axhline(70/30) 画水平虚线标超买超卖阈值,直观看出突破时机。set_ylim([0,100]) 固定 RSI 纵轴范围,便于横向对比。

关键技巧

技巧一:shift(1) 取前收(ATR 核心)。时序里"今日量参考昨日量"是家常便饭,shift(1) 把序列整体下挪一行实现这个对齐。shift(-1) 是反方向(取明日),做前瞻收益会用到(见 MACD 节)。

技巧二:clip(lower=0) / -clip(upper=0) 分离涨跌(RSI 核心)。一行替代 if-else,优雅且向量化快。这是分离 gain/loss 的地道写法,背下来终身受用。

技巧三:df[['列1','列2','列3']].max(axis=1) 跨列取最大axis=1 表示"按行"操作(跨列聚合),得到每行的最大值;axis=0 是"按列"操作。ATR 里 TR 要三列按行取 max,务必写 axis=1

技巧四:gridspec_kw={'height_ratios': [3,1]} 控制子图比例。主指标图大、辅助指标图小,比默认等高更专业。配合 axhline 画阈值线,标准的技术分析图布局。

技巧五:tight_layout() 防重叠。多子图调用前先加 plt.tight_layout(),自动调整间距,避免标题、坐标轴互相遮挡。

💡 速查要点:ATR 三量取 max——shift(1) 取前收是跳空计算的核心;RSI 分离涨跌——clip(lower=0) 取 gain、-clip(upper=0) 取 loss,是最地道的写法。两脚本都依赖 OpenBB 3,用 SMA 简化了 Wilder 原版 EMA(研究够用、对数注意)。

本节要点

  1. ATR = True Range 的滚动均值,TR = max(H-L, |H-前收|, |L-前收|),shift(1) 取前收是跳空计算的关键,用于风控(止损、仓位)。
  2. shift(1) 是时序"取昨日值"的通用技巧,shift(-1) 取明日值(做前瞻收益)。
  3. RSIclip(lower=0) 取 gain、-clip(upper=0) 取 loss,优雅分离涨跌;RSI=100-100/(1+RS),>70 超买、<30 超卖。
  4. df[[...]].max(axis=1) 跨列取最大(axis=1 按行),ATR 里三列取 max 必须写对方向。
  5. gridspec_kw={'height_ratios': [3,1]} 控子图比例,主图大、辅图小,比默认等高更专业;两个脚本都依赖 OpenBB 3,用 SMA 简化了 Wilder 原版 EMA。

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