Skip to content

用Python计算技术指标

技术指标是通过对价格、成交量等市场数据进行数学计算得到的衍生数据,用于辅助判断市场趋势、动量和超买超卖状态。用 Python 自动计算技术指标,是量化分析的基础能力。

本文将介绍两类方法:一是使用专业库 TA-Lib 快速计算,二是自己手写计算逻辑以深入理解指标原理。

环境准备

安装基础库

bash
pip install pandas numpy akshare

安装 TA-Lib

TA-Lib(Technical Analysis Library)是最经典的技术分析计算库,用 C 语言编写,支持 150 多种技术指标。Python 通过封装层调用它。

Windows 安装(推荐用 whl 文件):

bash
# 先到 https://github.com/cgohlke/talib-build/releases 下载对应版本的 .whl 文件
# 然后离线安装
pip install TA_Lib-0.4.28-cp312-cp312-win_amd64.whl

macOS 安装

bash
brew install ta-lib
pip install ta-lib

Linux 安装

bash
wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz
tar -xzf ta-lib-0.4.0-src.tar.gz
cd ta-lib/
./configure
make
make install
pip install ta-lib

安装完成后验证:

python
import talib
print(talib.__version__)  # 应输出类似 "0.4.28"

如果安装遇到问题,可以使用纯 Python 实现的替代库:pip install pandas-ta,它不需要编译 C 代码,但计算速度比 TA-Lib 慢。

获取测试数据

我们先用 akshare 获取一组测试数据,后续所有指标都基于这份数据计算:

python
import akshare as ak
import pandas as pd
import numpy as np

# 获取平安银行(000001)近250个交易日的日K数据
df = ak.stock_zh_a_hist(
    symbol="000001",
    period="daily",
    start_date="20240101",
    end_date="20241231",
    adjust="qfq"
)

df = df.rename(columns={
    "日期": "Date",
    "开盘": "Open",
    "收盘": "Close",
    "最高": "High",
    "最低": "Low",
    "成交量": "Volume"
})

df["Date"] = pd.to_datetime(df["Date"])
df = df.set_index("Date")
df = df[["Open", "High", "Low", "Close", "Volume"]]

print(f"数据范围: {df.index[0].date()} ~ {df.index[-1].date()}")
print(f"共 {len(df)} 条记录")
print(df.tail())

MACD 指标

MACD(Moving Average Convergence Divergence,指数平滑异同移动平均线) 是最常用的趋势跟踪指标,由 Gerald Appel 于 1970 年代发明。

MACD 包含三个组成部分:

组成部分说明
DIF(快线)12日EMA 减去 26日EMA,反映短期与长期趋势的差异
DEA(慢线)DIF 的 9日 EMA,是 DIF 的平滑版本
MACD柱(DIF - DEA) × 2,反映多空力量的强弱变化

EMA(Exponential Moving Average,指数移动平均):一种加权移动平均,近期数据权重更大,对价格变化更敏感。相比简单移动平均(SMA),EMA 反应更快。

用 TA-Lib 计算

python
import talib

close = df["Close"].values  # TA-Lib 接受 numpy 数组

# 计算 MACD
# 参数:fastperiod=12, slowperiod=26, signalperiod=9
dif, dea, macd_bar = talib.MACD(
    close,
    fastperiod=12,
    slowperiod=26,
    signalperiod=9
)

# 将结果添加到 DataFrame
df["DIF"] = dif
df["DEA"] = dea
df["MACD"] = macd_bar * 2  # talib 返回的是 (DIF-DEA),乘2才是国内习惯

print(df[["Close", "DIF", "DEA", "MACD"]].tail(10))

手动计算 MACD

理解手动计算过程,有助于深入理解指标的含义:

python
# 第一步:计算 EMA12 和 EMA26
ema12 = df["Close"].ewm(span=12, adjust=False).mean()
ema26 = df["Close"].ewm(span=26, adjust=False).mean()

# 第二步:DIF = EMA12 - EMA26
dif_manual = ema12 - ema26

# 第三步:DEA = DIF 的 9日 EMA
dea_manual = dif_manual.ewm(span=9, adjust=False).mean()

# 第四步:MACD 柱 = (DIF - DEA) × 2
macd_manual = (dif_manual - dea_manual) * 2

# 验证与 talib 结果一致
print("DIF 差异:", abs(dif_manual.dropna() - pd.Series(dif).dropna()).max())
print("MACD 差异:", abs(macd_manual.dropna() - pd.Series(macd_bar * 2).dropna()).max())

ewm(span=N) 是 pandas 提供的指数加权计算方法,span 参数对应 EMA 的周期。adjust=False 表示使用递归公式计算,与常用行情软件的结果一致。

MACD 的常见用法

  • 金叉买入:DIF 从下方穿越 DEA(MACD柱由负转正)
  • 死叉卖出:DIF 从上方穿越 DEA(MACD柱由正转负)
  • 顶背离:股价创新高但 MACD 没有创新高,可能预示趋势反转
  • 底背离:股价创新低但 MACD 没有创新低,可能预示反弹

RSI 指标

RSI(Relative Strength Index,相对强弱指标) 由 Welles Wilder 于 1978 年提出,用于衡量价格变动的速度和幅度,判断市场的超买超卖状态。

RSI 的取值范围为 0~100,一般使用 14 日作为默认周期:

  • RSI > 70:超买区,价格可能过高,有回调风险
  • RSI < 30:超卖区,价格可能过低,有反弹机会
  • RSI 在 40~60 之间:中性区域,趋势不明朗

用 TA-Lib 计算

python
# 计算 RSI,默认周期14
rsi = talib.RSI(close, timeperiod=14)
df["RSI14"] = rsi

# 也可以计算不同周期
df["RSI6"] = talib.RSI(close, timeperiod=6)   # 短期RSI,更灵敏
df["RSI24"] = talib.RSI(close, timeperiod=24)  # 长期RSI,更平滑

print(df[["Close", "RSI14"]].tail(10))

手动计算 RSI

手写 RSI 是理解其计算原理的最好方式。RSI 的核心公式为:

$$RSI = 100 - \frac{100}{1 + RS}$$

其中 $RS = \frac{平均涨幅}{平均跌幅}$

python
def calc_rsi(series, period=14):
    """
    手动计算 RSI 指标

    参数:
        series: pd.Series,价格序列(通常是收盘价)
        period: int,计算周期,默认14

    返回:
        pd.Series,RSI 值序列
    """
    # 第一步:计算每日价格变动
    delta = series.diff(1)  # 今天收盘价 - 昨天收盘价

    # 第二步:分离涨跌
    gain = delta.where(delta > 0, 0.0)   # 上涨日:取正值;下跌日:取0
    loss = (-delta).where(delta < 0, 0.0) # 下跌日:取绝对值;上涨日:取0

    # 第三步:计算平均涨跌幅
    # 第一个平均用简单平均,之后用指数平滑(Wilder 平滑法)
    avg_gain = gain.rolling(window=period, min_periods=period).mean()
    avg_loss = loss.rolling(window=period, min_periods=period).mean()

    # Wilder 平滑:从第二个值开始,用递推公式
    for i in range(period, len(gain)):
        if pd.notna(avg_gain.iloc[i]) and avg_gain.iloc[i] != 0:
            avg_gain.iloc[i] = (
                avg_gain.iloc[i - 1] * (period - 1) + gain.iloc[i]
            ) / period
            avg_loss.iloc[i] = (
                avg_loss.iloc[i - 1] * (period - 1) + loss.iloc[i]
            ) / period

    # 第四步:计算 RS 和 RSI
    rs = avg_gain / avg_loss
    rsi = 100 - (100 / (1 + rs))

    return rsi

# 使用手写函数
df["RSI14_manual"] = calc_rsi(df["Close"], period=14)

# 对比与 talib 结果的差异
diff = abs(df["RSI14"].dropna() - df["RSI14_manual"].dropna())
print(f"与 talib 结果最大差异: {diff.max():.6f}")

Wilder 平滑法:与普通 EMA 不同,Wilder 使用的平滑系数是 1/period(而不是 2/(period+1))。这是 RSI、ATR 等指标的标准计算方法。

KDJ 指标

KDJ(随机指标) 由 George Lane 提出,通过比较收盘价在一定周期内价格区间中的位置来判断超买超卖。它是 A 股投资者最常用的短线指标之一。

KDJ 由三条线组成:

  • K 值(快线):当日 RSV 的指数平滑
  • D 值(慢线):K 值的指数平滑
  • J 值(方向线):3K - 2D,对 K、D 的放大

RSV(Raw Stochastic Value,未成熟随机值)(收盘价 - N日最低价) / (N日最高价 - N日最低价) × 100,反映当日收盘价在近 N 日价格区间中的相对位置。

用 TA-Lib 计算

python
# TA-Lib 中 KDJ 对应的函数是 STOCH(随机指标)
# fastk_period=9, slowk_period=3, slowd_period=3 是国内常用参数
k, d = talib.STOCH(
    df["High"].values,
    df["Low"].values,
    close,
    fastk_period=9,
    slowk_period=3,
    slowk_matype=0,  # 0 = SMA
    slowd_period=3,
    slowd_matype=0
)

df["K"] = k
df["D"] = d
df["J"] = 3 * k - 2 * d  # J 值需要手动计算

print(df[["Close", "K", "D", "J"]].tail(10))

手动计算 KDJ

python
def calc_kdj(df, n=9, m1=3, m2=3):
    """
    手动计算 KDJ 指标

    参数:
        df: DataFrame,需要包含 High, Low, Close 列
        n: int,RSV 计算周期,默认9
        m1: int,K 值平滑周期,默认3
        m2: int,D 值平滑周期,默认3

    返回:
        DataFrame,包含 K, D, J 三列
    """
    # 计算 N 日内最低价和最高价
    low_n = df["Low"].rolling(window=n).min()
    high_n = df["High"].rolling(window=n).max()

    # 计算 RSV
    rsv = (df["Close"] - low_n) / (high_n - low_n) * 100

    # 计算 K 值:K = (2/3) × 前日K + (1/3) × 当日RSV
    k = pd.Series(np.nan, index=df.index)
    k.iloc[n - 1] = 50  # 初始值取50

    for i in range(n, len(df)):
        k.iloc[i] = (2 / 3) * k.iloc[i - 1] + (1 / 3) * rsv.iloc[i]

    # 计算 D 值:D = (2/3) × 前日D + (1/3) × 当日K
    d = pd.Series(np.nan, index=df.index)
    d.iloc[n - 1] = 50  # 初始值取50

    for i in range(n, len(df)):
        d.iloc[i] = (2 / 3) * d.iloc[i - 1] + (1 / 3) * k.iloc[i]

    # 计算 J 值
    j = 3 * k - 2 * d

    result = pd.DataFrame({"K": k, "D": d, "J": j}, index=df.index)
    return result

kdj_df = calc_kdj(df)
print(kdj_df.tail(10))

KDJ 的常见用法

  • K 线上穿 D 线(金叉),且 J 值从负值区域回升:买入信号
  • K 线下穿 D 线(死叉),且 J 值从正值区域回落:卖出信号
  • K > 80 且 D > 80:超买区,短期可能回调
  • K < 20 且 D < 20:超卖区,短期可能反弹

布林带

布林带(Bollinger Bands,简称 BOLL) 由 John Bollinger 在 1980 年代提出,由中轨(20日SMA)、上轨(中轨+2倍标准差)、下轨(中轨-2倍标准差)三条线组成。

布林带的核心思想:价格在大多数时候会在上下轨之间波动。当价格触及上轨时可能偏高,触及下轨时可能偏低。

用 TA-Lib 计算

python
# 计算布林带
# 参数:timeperiod=20, nbdevup=2, nbdevdn=2, matype=0(SMA)
upper, middle, lower = talib.BBANDS(
    close,
    timeperiod=20,
    nbdevup=2,     # 上轨:中轨 + 2倍标准差
    nbdevdn=2,     # 下轨:中轨 - 2倍标准差
    matype=0       # 0 = 简单移动平均
)

df["BOLL_upper"] = upper
df["BOLL_middle"] = middle
df["BOLL_lower"] = lower

print(df[["Close", "BOLL_upper", "BOLL_middle", "BOLL_lower"]].tail(10))

手动计算布林带

python
def calc_boll(series, period=20, num_std=2):
    """
    手动计算布林带

    参数:
        series: pd.Series,价格序列
        period: int,中轨周期,默认20
        num_std: float,标准差倍数,默认2

    返回:
        DataFrame,包含 upper, middle, lower 三列
    """
    middle = series.rolling(window=period).mean()
    std = series.rolling(window=period).std()
    upper = middle + num_std * std
    lower = middle - num_std * std

    return pd.DataFrame({
        "upper": upper,
        "middle": middle,
        "lower": lower
    })

boll = calc_boll(df["Close"])
print(boll.tail(10))

布林带的常见用法

  • 布林带收窄:上下轨间距缩小,说明波动率降低,可能即将出现大幅突破
  • 价格突破上轨:短期强势,但要注意是否过热
  • 价格跌破下轨:短期弱势,但可能存在超卖反弹机会
  • 价格沿上轨/下轨运行:趋势行情中的典型特征

批量计算多只股票指标

在实际量化分析中,我们通常需要对多只股票同时计算技术指标。以下是一个完整的批量计算框架:

python
def batch_calc_indicators(stock_list, start_date, end_date):
    """
    批量计算多只股票的技术指标

    参数:
        stock_list: list,股票代码列表,如 ["000001", "600519", "000858"]
        start_date: str,起始日期,如 "20240101"
        end_date: str,结束日期,如 "20241231"

    返回:
        dict,key 为股票代码,value 为包含指标的 DataFrame
    """
    result = {}

    for symbol in stock_list:
        try:
            # 获取数据
            df = ak.stock_zh_a_hist(
                symbol=symbol,
                period="daily",
                start_date=start_date,
                end_date=end_date,
                adjust="qfq"
            )

            df = df.rename(columns={
                "日期": "Date", "开盘": "Open", "收盘": "Close",
                "最高": "High", "最低": "Low", "成交量": "Volume"
            })
            df["Date"] = pd.to_datetime(df["Date"])
            df = df.set_index("Date")
            df = df[["Open", "High", "Low", "Close", "Volume"]]

            close = df["Close"].values

            # 批量计算指标
            df["MA5"] = talib.SMA(close, timeperiod=5)
            df["MA20"] = talib.SMA(close, timeperiod=20)
            df["MA60"] = talib.SMA(close, timeperiod=60)

            dif, dea, macd_bar = talib.MACD(close)
            df["DIF"] = dif
            df["DEA"] = dea
            df["MACD"] = macd_bar * 2

            df["RSI14"] = talib.RSI(close, timeperiod=14)

            upper, middle, lower = talib.BBANDS(close)
            df["BOLL_upper"] = upper
            df["BOLL_middle"] = middle
            df["BOLL_lower"] = lower

            result[symbol] = df
            print(f"✓ {symbol} 指标计算完成,共 {len(df)} 条记录")

        except Exception as e:
            print(f"✗ {symbol} 处理失败: {e}")

    return result

# 批量计算5只股票
stocks = ["000001", "600519", "000858", "601318", "000333"]
all_data = batch_calc_indicators(stocks, "20240101", "20241231")

# 查看某只股票的最新指标
if "600519" in all_data:
    print(all_data["600519"].tail(5))

批量筛选:RSI 超卖股票

python
# 从计算结果中筛选 RSI < 30 的股票(超卖状态)
print("=== RSI 超卖股票 ===")
for symbol, data in all_data.items():
    latest = data.iloc[-1]
    if pd.notna(latest["RSI14"]) and latest["RSI14"] < 30:
        print(
            f"{symbol}: 收盘价 {latest['Close']:.2f}, "
            f"RSI {latest['RSI14']:.1f}"
        )

指标计算注意事项

1. 前置 NaN 值

所有基于滚动窗口计算的指标,在初始阶段都会产生 NaN(缺失值)。例如 MA60 需要至少 60 个交易日才能算出第一个值。在策略中要注意过滤掉这些 NaN:

python
# 方法一:用 dropna 移除 NaN 行
df_clean = df.dropna(subset=["MA20", "RSI14"])

# 方法二:用 fillna 填充默认值
df["RSI14"] = df["RSI14"].fillna(50)  # RSI 中性值

2. 复权对指标的影响

计算技术指标必须使用复权数据(前复权或后复权均可)。不复权的数据在除权除息日会产生价格跳空,导致均线和指标失真。

3. 指标的滞后性

所有基于历史数据计算的指标都具有滞后性。均线、MACD、布林带都是跟随价格的"滞后指标",它们反映的是已经发生的趋势,而不是预测未来。RSI 和 KDJ 虽然属于"震荡指标",反应更灵敏,但同样存在滞后和假信号的问题。

小结

本文介绍了四种常用技术指标的 Python 计算方法:

指标类型核心参数主要用途
MACD趋势跟踪12, 26, 9判断趋势方向和强弱
RSI震荡指标14判断超买超卖
KDJ震荡指标9, 3, 3短线买卖时机
布林带波动率20, 2判断波动区间和突破

掌握了技术指标的计算方法后,下一步就是将这些指标组合起来,构建完整的交易策略。在下一篇《策略开发流程》中,我们将系统学习如何从零开始开发一个量化策略。

免责声明:本文所有代码仅用于技术教学目的,文中涉及的股票代码仅为数据示例,不构成任何投资建议。技术指标是辅助分析工具,不能作为投资决策的唯一依据。投资有风险,入市需谨慎。

仅供学习交流,不构成任何投资建议