第 2 章 · 03 Parabolic SAR:Welles Wilder 的递归止盈止损


文档摘要

第 2 章 · 03 Parabolic SAR:Welles Wilder 的递归止盈止损 本节摘要:Parabolic SAR(Stop and Reverse)是技术分析大师 J. Welles Wilder Jr. 发明的趋势跟踪指标。和 MACD/AO 的"均线之差"完全不同,SAR 在图上是一条点状线:多头时它贴在价格下方,空头时贴在价格上方,代表"趋势还在时的支撑/阻力位"。SAR 与价格交叉就反手——所以叫"Stop and Reverse(停损并反手)"。它的算法极其痛苦(连维基百科都讲不清),核心是一个递归公式加一个不断加大的加速因子。本节带你把这个"传说中难算"的指标拆到底,你会看到作者用一行 把整个递推讲完,再用 防止瞬时穿破。

第 2 章 · 03 Parabolic SAR:Welles Wilder 的递归止盈止损

本节摘要:Parabolic SAR(Stop and Reverse)是技术分析大师 J. Welles Wilder Jr. 发明的趋势跟踪指标。和 MACD/AO 的"均线之差"完全不同,SAR 在图上是一条点状线:多头时它贴在价格下方,空头时贴在价格上方,代表"趋势还在时的支撑/阻力位"。SAR 与价格交叉就反手——所以叫"Stop and Reverse(停损并反手)"。它的算法极其痛苦(连维基百科都讲不清),核心是一个递归公式加一个不断加大的加速因子。本节带你把这个"传说中难算"的指标拆到底,你会看到作者用一行 temp=sar[i-1]+af*(ep-sar[i-1]) 把整个递推讲完,再用 max(temp, High[i-1], High[i-2]) 防止瞬时穿破。

涉及源码:原项目 Parabolic SAR backtest.py(169 行),README 第 8 节。

⚠️ 注意:脚本头部 import fix_yahoo_finance as yf 需替换为 import yfinance as yf。SAR 的递归用整数索引,所以脚本里先 reset_index() 再算,画图时再 set_index 回时间轴——这个技巧上一节 AO 也用过。

金融/数学原理

一、SAR 是什么:点状的"动态止盈止损线"

先理解它的视觉形态,再理解数学。打开任何行情软件,叠加 Parabolic SAR,你会看到价格上方或下方有一串小圆点——

  • 上升趋势中,圆点贴在 K 线下方,随着价格上涨缓缓上移。每个圆点就是"当前的动态止损位":只要价格不跌破这个点,趋势就算没破。
  • 下降趋势中,圆点贴在 K 线上方,随价格下跌缓缓下移,是空头的动态止盈位。
  • 当价格穿越圆点(多头时跌破、空头时涨破),就触发"Stop and Reverse"——平掉原仓位、反手开新仓,圆点也跳到价格的另一侧。

经济直觉:SAR 把" trailing stop(移动止损)"这个交易员的本能操作,固化成了一个数学公式。它的精妙之处在于——止损位会随着趋势走远而加速靠近价格,把利润越锁越紧,一旦反转立刻出局。

二、Welles Wilder:传奇的"机械工程师转技术分析师"

README 里特意提到,SAR 和后面会讲的 RSI 共享同一个发明人 J. Welles Wilder Jr.。这人是个传奇——他当过机械工程师房地产开发商,直到 40 多岁才转行做技术分析师。1978 年他一口气出版了《New Concepts in Technical Trading Systems》,献出了 SAR、RSI、ATR、DMI 等一整套路指标,几乎奠定了现代技术分析的半壁江山。

这个背景很重要:Wilder 的指标带着浓厚的工程思维——喜欢用递归、加速因子、机械规则,把交易完全系统化。SAR 的"加速因子"就是一种典型的工程装置:起初止损位慢慢挪,趋势确认后越挪越快,像抛物线一样(hence "Parabolic")收紧。这也是为什么 SAR 的计算那么"递归式"——它的设计本身就嵌入了"上一根的状态决定这一根"的逻辑。

三、核心递推公式与加速因子

SAR 的递推(以上升趋势为例):

SAR_t = SAR_{t-1} + AF_{t-1} \cdot (EP_{t-1} - SAR_{t-1})

三个量:

  • SAR_t:第 t 根 K 线的 SAR 值(动态止损位)。
  • EP_{t-1}(Extreme Point):截至上一根 K 线的极值价——上升趋势里是迄今最高价,下降趋势里是迄今最低价。
  • AF(Acceleration Factor,加速因子):初始 0.02,每当前创新极值(即 EP 更新)就 +0.02,封顶 0.2。也就是说,最多加 9 次步长。

这个公式的物理含义:SAR 朝极值价 EP 的方向,挪动 (EP - SAR) 的一段,挪多少比例由 AF 决定。趋势刚启动时 AF=0.02,SAR 挪得慢(远离价格,给趋势呼吸空间);趋势持续、不断创新高,AF 累加到 0.2,SAR 挪得飞快(贴近价格,锁住利润)。这就是"抛物线"加速的名字由来。

参数选择(脚本注释):initial_af=0.02step_af=0.02end_af=0.2 是外汇和商品的常用值;股票可以改成 0.01(更慢、更宽松),因为股票波动比外汇大,太紧的 SAR 会被频繁打止损。

四、为什么 SAR 这么难讲

作者在脚本开头就吐槽:"it is very painful to calculate sar, though, and many explanations online including wiki cannot clearly explain the process"。原因有三:

  1. 递归依赖:SAR_t 依赖 SAR_{t-1}EP_{t-1}AF_{t-1},而 EPAF 又依赖"是否创新极值"的判断,层层耦合,无法用闭式公式一次算完,必须 for 循环逐步推进。
  2. 趋势翻转时的重置:一旦价格穿越 SAR,趋势方向翻转,AF 重置回 0.02,EP 重置为新方向的极值,SAR 跳到旧方向的初始极值——一堆状态同时切换。
  3. 防瞬时穿破的"取 max":下降趋势里,SAR 不能比"前两根的最高价"还高(否则可能被瞬间穿透),所以要 max(temp, High[i-1], High[i-2])。这种"工程加固"让公式读起来更绕。

理解了这三点,再看代码就不会迷路。

算法与代码精读

一、parabolic_sar():递归主体

def parabolic_sar(new): # 外汇/商品的常用加速因子;股票可设 0.01 initial_af = 0.02 step_af = 0.02 end_af = 0.2 new['trend'] = 0 new['sar'] = 0.0 new['real sar'] = 0.0 new['ep'] = 0.0 new['af'] = 0.0 # 初始值 new['trend'][1] = 1 if new['Close'][1] > new['Close'][0] else -1 new['sar'][1] = new['High'][0] if new['trend'][1] > 0 else new['Low'][0] new.at[1, 'real sar'] = new['sar'][1] new['ep'][1] = new['High'][1] if new['trend'][1] > 0 else new['Low'][1] new['af'][1] = initial_af for i in range(2, len(new)): # 1) 递推 SAR temp = new['sar'][i-1] + new['af'][i-1] * (new['ep'][i-1] - new['sar'][i-1]) if new['trend'][i-1] < 0: new.at[i, 'sar'] = max(temp, new['High'][i-1], new['High'][i-2]) # 防瞬时穿破 temp = 1 if new['sar'][i] < new['High'][i] else new['trend'][i-1] - 1 else: new.at[i, 'sar'] = min(temp, new['Low'][i-1], new['Low'][i-2]) # 防瞬时穿破 temp = -1 if new['sar'][i] > new['Low'][i] else new['trend'][i-1] + 1 new.at[i, 'trend'] = temp # 2) 更新极值 EP if new['trend'][i] < 0: temp = min(new['Low'][i], new['ep'][i-1]) if new['trend'][i] != -1 else new['Low'][i] else: temp = max(new['High'][i], new['ep'][i-1]) if new['trend'][i] != 1 else new['High'][i] new.at[i, 'ep'] = temp # 3) 更新加速因子 AF + 记录 real sar if np.abs(new['trend'][i]) == 1: # 刚翻转,重置 temp = new['ep'][i-1] new.at[i, 'af'] = initial_af else: temp = new['sar'][i] if new['ep'][i] == new['ep'][i-1]: # 没创新极值,AF 不变 new.at[i, 'af'] = new['af'][i-1] else: # 创新极值,AF +step,封顶 end_af new.at[i, 'af'] = min(end_af, new['af'][i-1] + step_af) new.at[i, 'real sar'] = temp return new

逐段拆解:

初始化(第 1 根):trend 由 Close 涨跌判定(1 多/-1 空);sar 取相反方向的极值(多头起步用前一根 High 作 SAR,空头用 Low);ep 取当前方向极值;af=initial_af。这一组初始值是整个递归的"种子"。

步骤 1——递推 SAR(核心一行):temp = sar[i-1] + af[i-1]*(ep[i-1]-sar[i-1]) 就是上一节那个公式。然后分趋势方向做"防瞬时穿破"处理:

  • 下降趋势(trend<0):sar = max(temp, High[i-1], High[i-2])。意思是 SAR 不能低于前两根的最高价,否则一根长上影就可能把止损打穿。这是 Wilder 原始规则里的工程加固。
  • 上升趋势(trend>0):sar = min(temp, Low[i-1], Low[i-2]),镜像处理。

接着判断是否翻转:trend[i-1]<0 时,如果 sar[i] < High[i](价格涨破 SAR),则 trend=1(翻多),否则 trend = trend[i-1]-1(继续空头,且趋势持续天数 -1,绝对值表示已持续几根)。这里 trend 的绝对值 1 表示刚翻转,绝对值 >1 表示趋势已持续。这个"用趋势持续天数编码翻转"的技巧,让后面 if np.abs(trend)==1 一眼判断"刚翻转,要重置 AF"。

步骤 2——更新 EP:trend != -1(不是刚翻空)时,EP 取"当前 Low 和历史 EP 的较小者"——因为空头持续时 EP 应该是迄今最低价,只有创新低才更新。刚翻空(trend==-1)时 EP 直接重置为当前 Low。多头镜像。

步骤 3——更新 AF + 记录 real sar:刚翻转(|trend|==1)时 AF 重置为 initial_af;否则若 EP 没变(没创新极值)AF 保持,若 EP 变了 AF += step(封顶 0.2)。

real sar 是什么? 注意脚本维护了 sarreal sar 两列。real sar 是"画图用的、去掉翻转瞬间跳变毛刺后的 SAR",它的取值规则是:翻转时取翻转前的 EP(跳到对侧),非翻转时取当根 sar。画图和信号生成都用 real sar,因为它更平滑、更适合肉眼识别趋势。

二、signal_generation():real sar 与 Close 交叉

def signal_generation(df, method): new = method(df) new['positions'], new['signals'] = 0, 0 new['positions'] = np.where(new['real sar'] < new['Close'], 1, 0) new['signals'] = new['positions'].diff() return new

一行话:real sar < Close 就满仓,否则空仓。这等价于"价格在 SAR 上方 = 多头,在下方 = 空头"。positions/signals 双列约定和 MACD、AO 完全一致——读到这里你已经第三次看到这套骨架了,应该完全内化了。

注意:这里没有 saucer 那种"抢信号"的复杂逻辑,SAR 本身就是反手指标(Stop and Reverse),信号源唯一,所以不需要 cumsum 仲裁。

三、main():整数索引递归 + 时间轴画图

def main(): stdate = '2016-01-01' eddate = '2018-01-01' ticker = 'EA' slicer = 450 df = yf.download(ticker, start=stdate, end=eddate) del df['Adj Close'] del df['Volume'] df.reset_index(inplace=True) # 关键:日期索引 → 整数索引 new = signal_generation(df, parabolic_sar) new.set_index(new['date'], inplace=True) # 算完再换回时间轴 new = new[slicer:] plot(new, ticker)

reset_index() 是 SAR 的必需操作——递归循环里大量用 High[i-1]High[i-2] 这种位置访问,如果索引还是 Timestamp 就会报 KeyError。算完后再 set_index(date) 换回去,画图才有日期 x 轴。这种"算之前 reset,画之前 set"的技巧,上一节 AO 也用过,是处理时间序列递归的通用模式。

del df['Adj Close']/del df['Volume']:yf.download 默认会返回 Adj Close 和 Volume 列,SAR 用不到,删掉保持 DataFrame 干净。slicer=450 切掉前面太多数据,只画最近一段(否则点状线太密看不清)。

四、plot():点状线 + 反手标记

new['Close'].plot(lw=3, label='%s' % ticker) new['real sar'].plot(linestyle=':', label='Parabolic SAR', color='k') # 黑色点状线 ax.plot(..., marker='^', color='g', label='LONG', ...) # 反手做多 ax.plot(..., marker='v', color='r', label='SHORT', ...) # 反手做空

real sarlinestyle=':' 画成黑色点状线,这就是 SAR 在行情软件里的经典形态。

图: SAR 持仓

图中你能清楚看到:多头段 SAR 贴在价格下方缓缓上移,空头段跳到上方;每次穿越就是一次反手,对应图上的 ^/v 箭头。SAR 的加速特性也很直观——趋势持续越久,圆点离价格越近,反转时越早出局。

🎯 反直觉发现:SAR 最反直觉的地方在于——它的止损位会"主动靠近"价格。这与交易员的直觉相反:我们通常以为"好止损"应该给趋势留足空间(远离价格),但 SAR 偏偏在趋势越走越顺时,把止损越拉越近。这种"反向操作"的代价是——在震荡市里 SAR 会被反复打脸:价格来回穿越点状线,每次都触发反手,结果就是连续小亏。所以 SAR 是强趋势市场的利器,震荡市的绞肉机。这又一次印证上一节的反直觉主题:没有"万能好指标",只有"匹配市场状态"的指标。实战中,常见做法是先用 ATR 或 ADX 判断趋势强度,只在强趋势段启用 SAR。

关键技巧

  1. reset_index() 整数索引做递归:High[i-1]/High[i-2] 位置访问要整数索引,算完 set_index(date) 回时间轴画图。
  2. temp = sar[i-1] + af*(ep - sar[i-1]) 是 SAR 全部精华:一行递推,把"动态止损向极值价靠拢"的物理意义浓缩进去。
  3. max(temp, High[i-1], High[i-2]) 防瞬时穿破:Wilder 原始规则的工程加固,确保下降趋势里 SAR 不被一根上影线轻易穿透。
  4. trend 编码趋势持续天数:|trend|==1 表示刚翻转(需重置 AF),|trend|>1 表示趋势持续,这种编码让"是否重置"的判断一行搞定。
  5. real sar vs sar 双列:real sar 是去毛刺的画图用列,翻转时取旧 EP,非翻转取当根 sar;信号生成和画图都用 real sar。
  6. np.where(real sar < Close, 1, 0):SAR 的反手天然就是 positions,无需 cumsum 仲裁(信号源唯一)。

本节要点

  1. Parabolic SAR 是 Welles Wilder(机械工程师转技术分析师)发明的递归止盈止损指标,图上呈点状线,多头在价下、空头在价上。
  2. 核心递推 SAR_t = SAR_{t-1} + AF*(EP - SAR_{t-1}),加速因子 AF 初始 0.02、每创新极值 +0.02、封顶 0.2(股票可设 0.01 更宽松)。
  3. 下降趋势中 SAR = max(temp, High[i-1], High[i-2]) 防瞬时穿破;趋势翻转时 AF 重置、EP 重置。
  4. 信号生成:np.where(real sar < Close, 1, 0) 给持仓,.diff() 给反手下单,沿用全书 positions/signals 双列约定。
  5. 反直觉:SAR 在趋势越顺时止损越紧(主动靠近价格),强趋势利器但震荡市绞肉机——印证"指标要匹配市场状态"。

本章三个指标讲完了。旅程第一步的结论是:动量/趋势类指标都不复杂,但各有"性格"——MACD 简单可靠、AO 灵敏却未必更好、SAR 强趋势专属。下一章,我们离开均线与点状线,转向日内突破与 K 线形态。


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