技术指标到底是什么
打开任何图表,蜡烛图上下都铺着若干条线,那就是技术指标 —— 由价格与成交量再计算一层得到的二阶数值。
原始数据只有一份。每根K线包含开盘、最高、最低、收盘和成交量,合称 OHLCV。所有指标都源自它。它们并不增加信息,只是把肉眼从蜡烛图上读不出的性质变成数字。
这类性质大致有四种:是否涨过头或跌过头(过热)、方向是否正在改变(趋势反转)、波动有多剧烈(波动率)、大方向朝哪边(长期趋势)。指标有数百种,但多数只是用不同方式测量这四者之一。
所以一开始不需要看几十个指标,把这四类各覆盖一个就够了。
智能体直接调用的恰好是五个
取一个具体参照。香港大学数据智能实验室(HKUDS)开源的交易智能体 Vibe-Trading,可以用自然语言下指令,自动取数据并跑回测。打开这个仓库会发现,智能体通过单个工具即时调用的指标正好五个。
它们以常量写在 agent/src/tools/technical_indicator_tool.py 里:RSI 为 14,MACD 为 12/26/9,布林带为 20 日加减两倍标准差,SMA 为 20/50/200,EMA 为 20。括号里的数字是计算窗口长度,也就是回看多少根K线。
这五个正好对上前面四类性质。RSI 管过热,MACD 管反转,布林带管波动率,SMA 与 EMA 管趋势。图表界面另外多了一个 KDJ(9),但面向智能体的工具里没有。
为什么只有五个?因为都出自同一份 OHLCV,指标之间高度重叠。随机指标与 RSI 测的其实是同一件事,叠在一起只会增加信心而不增加信息。停在五个应当读作取舍,而不是功能缺失。
| 指标 | 默认参数 | 测量对象 | 取值范围 |
|---|---|---|---|
| RSI | 14 | 超买 / 超卖 | 0 – 100 |
| MACD | 12 / 26 / 9 | 趋势反转时点 | 价格单位(无上下限) |
| 布林带 | 20, 2σ | 波动率通道 | 价格单位(上/中/下轨) |
| SMA | 20 / 50 / 200 | 趋势线 | 价格单位 |
| EMA | 20 | 近期加权趋势线 | 价格单位 |
RSI(14) — 把过热压缩到 0 到 100 之间
RSI 即相对强弱指数。它比较最近 14 根K线中上涨日与下跌日的力量,压缩成一个数字。
计算分四步。第一,取相对前一日的变化量。第二,把变化量拆成上涨日的涨幅与下跌日的跌幅两支。第三,各自按 14 日取平均。第四,用平均涨幅除以平均跌幅得到 RS,代入 RSI = 100 - 100 / (1 + RS)。
公式的形状把取值锁在 0 与 100 之间。若 14 日连涨,平均跌幅为 0,RSI 就是 100;反之为 0。实际大多在 30 到 70 之间摆动。
读法是惯例。70 以上称超买,30 以下称超卖。这两个数字并非从公式推出,而是创立者 J. Welles Wilder 定下的,因此通常按标的与市场调整。
有一个陷阱。强势上涨中 RSI 会在 70 上方停留数周。一旦越过 70 就卖出的规则,会整段错过行情的起步阶段。RSI 报告的是强度而非方向,必须与趋势类指标配合看。
MACD(12/26/9) — 用两条均线的距离捕捉反转
MACD 即指数平滑异同移动平均。名字长,做的事很简单:测量快均线与慢均线之间的距离。
它产出三个值。MACD 线是 12 日 EMA 减 26 日 EMA,短均线在长均线之上为正,反之为负。信号线是把 MACD 线再用 9 日 EMA 平滑一次。柱状图是 MACD 线减信号线,画成柱子让两线间距可见。
读的是交叉。MACD 线自下向上穿过信号线是金叉,视为偏多信号;自上向下穿过是死叉。柱状图会比交叉略早趋近于零,因此可用作提前提示。
MACD 线穿越零轴要单独看。上穿零轴意味着 12 日均线已超越 26 日均线,趋势本身发生了翻转。
陷阱是滞后。移动平均是已发生价格的平均,因此总在真正的转折之后才反应。在剧烈波动的标的上信号来得晚,在横盘区间里交叉会无意义地反复。缩短窗口会更快,噪音也同样增加。
布林带(20, 2σ) — 用波动率画出通道
布林带由三条线组成。中轨是 20 日简单移动平均,上轨在其上加两倍 20 日标准差,下轨则减去。价格看起来像在这条通道中穿行。
关键在于通道宽度不是固定的。标准差就是波动率的大小,价格剧烈震荡时带口张开,安静时收窄。也就是说这个指标真正展示的是波动率,而不是价格位置。
因此实战中最常盯的是收口。带宽相对近期异常收窄,说明波动率已死,之后通常朝某一方向大幅移动。它预告幅度,不预告方向。
两倍标准差这个数字也有依据。若数值服从正态分布,均值 ± 2 倍标准差内约覆盖 95%。但真实价格变动并非正态,尾部更厚,因此突破带口的情况远比理论频繁。
所以触碰带口本身不是信号。碰到上轨就卖出的规则,在强势上涨中会持续出错,沿着上轨爬升的现象甚至有专门的名字叫带口行走。布林带更适合用来判断波动率状态,而非价格位置。
SMA(20/50/200) 与 EMA(20) — 最古老也最常用的趋势线
SMA 是简单移动平均:把最近 N 日收盘价相加再除以 N。20 日线约为一个月,50 日线约为一个季度,200 日线大致相当于一年的交易日。
200 日线受到特殊对待,原因是惯例而非数学。足够多的机构把它当作长期趋势基准,因此价格在 200 日线之上还是之下,本身就会影响市场参与者的行为。
EMA 是指数移动平均。SMA 把 20 天等权对待,EMA 则给近期价格更大权重,久远价格的影响按指数衰减。它转向比 SMA 快,对噪音的反应也同样快。
把两条线叠加就得到金叉与死叉。短均线上穿长均线为金叉,下穿为死叉。常用的组合是 50 日线与 200 日线。
陷阱与 MACD 相同。均线回看过去,必然滞后。金叉常常出现在涨势已推进相当一段之后,而在横盘区间两线纠缠,信号会接连反转。
用 pandas 亲手算出全部五个
不必额外安装库,单靠 pandas 就能算出全部五个。输入只有收盘价一项,五个函数每个都在十行以内。
把下面的代码保存为 indicators.py,传入收盘价的 pandas Series 即可。收盘价可以用 yfinance、pykrx 取得,也可以用 pandas 读取券商导出的 CSV。
ewm 的 adjust=False 很重要。默认的 True 会用另一套公式修正初始区间,漏掉这个参数会让前面几十根K线的 EMA 与其他工具对不上。
函数写好后建议校验一次。把同一标的在券商 App 或 TradingView 上打开,比较同一日期的 RSI。数字可能对不上 —— 原因就是下一节的内容。
import pandas as pd
def rsi(close: pd.Series, period: int = 14) -> pd.Series:
delta = close.diff()
gain = delta.clip(lower=0)
loss = (-delta).clip(lower=0)
avg_gain = gain.ewm(alpha=1 / period, adjust=False).mean()
avg_loss = loss.ewm(alpha=1 / period, adjust=False).mean()
return 100 - 100 / (1 + avg_gain / avg_loss)
def macd(close, fast=12, slow=26, signal=9):
line = (close.ewm(span=fast, adjust=False).mean()
- close.ewm(span=slow, adjust=False).mean())
sig = line.ewm(span=signal, adjust=False).mean()
return pd.DataFrame({"macd": line, "signal": sig, "hist": line - sig})
def bollinger(close, period=20, num_std=2.0, ddof=0):
mid = close.rolling(period).mean()
sd = close.rolling(period).std(ddof=ddof)
return pd.DataFrame({"upper": mid + num_std * sd,
"middle": mid,
"lower": mid - num_std * sd})
def sma(close, period):
return close.rolling(period).mean()
def ema(close, period):
return close.ewm(span=period, adjust=False).mean()import yfinance as yf
from indicators import rsi, macd, bollinger, sma, ema
close = yf.download("AAPL", period="1y")["Close"].squeeze()
print("RSI(14) :", round(rsi(close).iloc[-1], 2))
print("MACD :", macd(close).iloc[-1].round(4).to_dict())
print("BB(20,2):", bollinger(close).iloc[-1].round(2).to_dict())
print("SMA200 :", round(sma(close, 200).iloc[-1], 2))
print("EMA20 :", round(ema(close, 20).iloc[-1], 2))同名不同数 — RSI 的两种平滑方式
这是本文最要紧的一段。同样写着 RSI 14,不同工具算出的值可能不一样,因为第三步——对涨幅与跌幅取平均——有两种做法。
一种是 Wilder 法,创立者最初提出的方法,用指数加权移动平均并取 alpha = 1 / 14。过去的值不会彻底消失,会持续留下一点影响。TA-Lib 就是这一种 —— 其源码 ta_RSI.c 的注释写着 "Wilder's approach",把前一个均值乘以 period-1,加上今日值,再除以 period(2026-08-07 查阅源码)。
另一种是简单移动平均:只把最近 14 日等权平均,第 15 日之前的值直接丢弃。它被当作独立指标,甚至有自己的名字叫 Cutler's RSI。数值比 Wilder 法更敏感,且当大值滑出窗口时可能突然跳变。
这不是假设。在前面提到的 Vibe-Trading 仓库中,technical_indicator_tool.py 里的 RSI 函数用 rolling(14).mean() 计算。而同一仓库的策略技能文档 technical-basic/SKILL.md 明确写着 RSI 与 ADX 使用 Wilder EWM,即 ewm(alpha=1/period)。两种做法共存于同一个仓库(2026-08-07 查阅源码)。
这不是谁对谁错,两者都是实际在用的定义。问题在于不知情地在一边回测、在另一边实盘:规则相同,进场点却不同。若条件是 RSI 突破 70,那么 69.4 与 71.2 就是两笔不同的交易。
wilder = gain.ewm(alpha=1 / 14, adjust=False).mean() # Wilder 원본 · TA-Lib ta_RSI.c
cutler = gain.rolling(14).mean() # Cutler's RSI · 简单移动平均
# 用同一份收盘价构造两个 RSI 并比较最后一个值
print(round(rsi_wilder(close).iloc[-1], 2),
round(rsi_cutler(close).iloc[-1], 2))布林带同样如此 — 标准差的自由度 ddof
计算标准差时,离差平方和除以什么也有两种做法。除以样本数 n 得到总体标准差(ddof=0),除以 n - 1 得到样本标准差(ddof=1)。统计学中用样本推断总体时惯例使用 n - 1。
pandas 的 rolling(20).std() 默认 ddof=1。不传任何参数得到的就是样本标准差。
TA-Lib 不同。查看源码 ta_BBANDS.c,带宽计算最终落到 ta_VAR.c 的方差公式,而该公式是 variance = sum(x²)/n - mean²。除以 n 即总体方差,因此 ddof=0(2026-08-07 查阅源码)。
差距可以算出来。样本标准差等于总体标准差乘以 sqrt(n / (n - 1))。n 为 20 时 sqrt(20 / 19) = 1.0260,因此用 pandas 默认值画出的带口,半宽比 TA-Lib 宽 2.60%。
2.60% 看着不大,但一旦把触碰带口作为交易条件就不同了。价格贴近带口的那些日子,一边会触发而另一边不会。这正是回测与实盘对不上时,先看这里再怀疑策略的理由。
对齐方法很简单。用哪一种都可以,但回测与实盘必须一致,因此在代码里显式写出 ddof。上一节 bollinger 函数把 ddof=0 设为默认值就是这个意思。
sd_sample = close.rolling(20).std() # pandas 默认值 · ddof=1
sd_pop = close.rolling(20).std(ddof=0) # 与 TA-Lib 一致的总体标准差
print(round(sd_sample.iloc[-1] / sd_pop.iloc[-1], 4)) # 1.026 = sqrt(20 / 19)接入指标之前的检查清单
第一,写明窗口长度。RSI 14、MACD 12/26/9、布林带 20 都只是默认值,各工具可能不同。在代码里写成参数,日后数字对不上时能迅速缩小排查范围。
第二,确认平滑方式。RSI、ADX 这类 Wilder 创立的指标,原始定义是 ewm(alpha=1/period)。不论库文档怎么写,直接看源码是 rolling 还是 ewm,一分钟就够。
第三,写明 ddof。所有基于标准差的指标都适用,不只是布林带,把波动率本身当信号时同样如此。
第四,确认 EMA 的 adjust。pandas ewm 默认 adjust=True 会用不同方式计算初始区间。要与其他工具对齐就用 adjust=False。
第五,不要只凭单个指标建立交易规则。前面提到的 Vibe-Trading 的 technical-basic 技能,会分别计算趋势(EMA 与 ADX)、均值回归(布林带与 RSI)、量价(OBV 与量比)三个维度,再投票合成一个信号:三者同向才进场,出现分歧就观望。
总结一句:学技术指标,一半是知道每个指标测什么,另一半是知道自己用的工具怎么算它。跳过后一半,你会得到数字,却不知道数字是什么意思。
