4. 技术指标计算与实现:均线系统(MA/EMA)、MACD指标、RSI指标、布林带、KDJ指标
技术指标这东西,说白了就是给价格数据「化妆」。裸K线看起来太素,加上几条线、几个柱状图,趋势和买卖点就清晰多了。我个人习惯把指标分成两类:趋势跟踪型和震荡型。均线、MACD属于前者,RSI、KDJ属于后者。布林带嘛,它两边都沾点。
今天咱们就把这五个最常用的指标,从公式到代码,一个一个啃下来。
4.1 均线系统:MA 与 EMA
均线是最基础的指标,没有之一。它就是把过去N天的收盘价做个平均,然后连成一条线。
4.1.1 简单移动平均线(MA)
公式很简单:
MA(N) = (Close₁ + Close₂ + ... + Closeₙ) / N
代码实现也不复杂:
def calculate_ma(close_prices, window=20):
"""
计算简单移动平均线
:param close_prices: 收盘价序列,list或numpy数组
:param window: 窗口大小,默认20
:return: MA序列,前window-1个值为NaN
"""
import numpy as np
prices = np.array(close_prices, dtype=float)
ma = np.full_like(prices, np.nan)
for i in range(window - 1, len(prices)):
ma[i] = np.mean(prices[i - window + 1 : i + 1])
return ma
小提示:实际项目中别用循环算MA,太慢了。直接用 pandas.Series.rolling(window).mean(),一行搞定。我在处理百万级K线数据时,循环版本跑了3秒,pandas版本0.03秒——差了100倍。
4.1.2 指数移动平均线(EMA)
EMA和MA最大的区别是:MA对每一天的数据一视同仁,EMA则更看重最近的数据。你想想看,昨天的价格和30天前的价格,哪个对今天更有参考价值?显然是昨天。
EMA的公式稍微绕一点:
EMA(t) = α × Close(t) + (1 - α) × EMA(t-1)
其中 α = 2 / (N + 1)
注意,第一个EMA值通常用第一个收盘价或者前N天的MA来初始化。
def calculate_ema(close_prices, window=20):
"""
计算指数移动平均线
"""
import numpy as np
prices = np.array(close_prices, dtype=float)
ema = np.full_like(prices, np.nan)
alpha = 2 / (window + 1)
# 初始化:用第一个价格作为EMA起点
ema[0] = prices[0]
for i in range(1, len(prices)):
ema[i] = alpha * prices[i] + (1 - alpha) * ema[i - 1]
return ema
注意:EMA的初始值选择会影响前几十个数据点的结果。我曾经在回测中发现,用第一个价格初始化会导致前20个EMA值偏低,后来改用前N天的MA做初始化,效果好了很多。如果你做高频交易,这个细节尤其重要。
4.2 MACD指标
MACD是均线系统的升级版。它由三部分组成:DIF线(快线)、DEA线(慢线)、柱状图。
计算步骤:
- 计算快线:EMA(12) - EMA(26)
- 计算慢线:对DIF再做一次EMA(9)
- 柱状图:DIF - DEA
说白了,MACD就是在看「短期均线和长期均线的差距」的变化趋势。
def calculate_macd(close_prices, fast=12, slow=26, signal=9):
"""
计算MACD指标
返回: (dif, dea, histogram)
"""
ema_fast = calculate_ema(close_prices, fast)
ema_slow = calculate_ema(close_prices, slow)
dif = ema_fast - ema_slow
dea = calculate_ema(dif, signal)
histogram = 2 * (dif - dea) # 乘以2是为了让柱状图更明显
return dif, dea, histogram
实战经验:MACD的金叉死叉信号在震荡行情中特别容易失效。我一般会加一个过滤条件——只有当DIF线突破零轴时,才认为金叉有效。这样能过滤掉大概30%的假信号。
4.3 RSI指标
RSI衡量的是「上涨的力度」和「下跌的力度」谁更强。取值范围0-100,一般认为超过70是超买,低于30是超卖。
公式:
RSI(N) = 100 - 100 / (1 + RS)
RS = 平均上涨幅度 / 平均下跌幅度
这里的「平均」通常用SMA或者 Wilder's Smoothing 来计算。
def calculate_rsi(close_prices, window=14):
"""
计算RSI指标
"""
import numpy as np
prices = np.array(close_prices, dtype=float)
deltas = np.diff(prices)
gains = np.where(deltas > 0, deltas, 0)
losses = np.where(deltas < 0, -deltas, 0)
avg_gain = np.full_like(prices, np.nan)
avg_loss = np.full_like(prices, np.nan)
# 初始平均值用SMA
avg_gain[window] = np.mean(gains[:window])
avg_loss[window] = np.mean(losses[:window])
# 后续用Wilder's Smoothing
for i in range(window + 1, len(prices)):
avg_gain[i] = (avg_gain[i - 1] * (window - 1) + gains[i - 1]) / window
avg_loss[i] = (avg_loss[i - 1] * (window - 1) + losses[i - 1]) / window
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
return rsi
避坑指南:我曾经在回测中发现RSI在单边行情中会长时间停留在超买/超卖区域,这时候反向做单会亏得很惨。后来我加了一条规则:RSI超买/超卖信号必须配合价格趋势的背离才执行交易。效果立竿见影。
4.4 布林带
布林带由三条线组成:中轨(MA)、上轨(中轨 + k倍标准差)、下轨(中轨 - k倍标准差)。k通常取2。
它的核心思想是:价格大概率在上下轨之间波动,突破上下轨意味着趋势可能发生变化。
def calculate_bollinger_bands(close_prices, window=20, k=2):
"""
计算布林带
返回: (upper, middle, lower)
"""
import numpy as np
prices = np.array(close_prices, dtype=float)
middle = calculate_ma(prices, window)
std = np.full_like(prices, np.nan)
for i in range(window - 1, len(prices)):
std[i] = np.std(prices[i - window + 1 : i + 1])
upper = middle + k * std
lower = middle - k * std
return upper, middle, lower
个人习惯:我一般把布林带和RSI配合使用。当价格突破上轨且RSI超过80时,我倾向于做空;当价格跌破下轨且RSI低于20时,我倾向于做多。两个指标互相验证,胜率会高很多。
4.5 KDJ指标
KDJ也叫随机指标,它比RSI更灵敏。它计算的是当前收盘价在过去N天价格区间中的位置。
计算步骤:
- 计算RSV(未成熟随机值):(Close - Lowest) / (Highest - Lowest) × 100
- K值 = 2/3 × 前一日K值 + 1/3 × RSV
- D值 = 2/3 × 前一日D值 + 1/3 × K值
- J值 = 3 × K值 - 2 × D值
def calculate_kdj(close_prices, high_prices, low_prices, window=9):
"""
计算KDJ指标
返回: (k, d, j)
"""
import numpy as np
close = np.array(close_prices, dtype=float)
high = np.array(high_prices, dtype=float)
low = np.array(low_prices, dtype=float)
rsv = np.full_like(close, np.nan)
k = np.full_like(close, np.nan)
d = np.full_like(close, np.nan)
j = np.full_like(close, np.nan)
for i in range(window - 1, len(close)):
highest = np.max(high[i - window + 1 : i + 1])
lowest = np.min(low[i - window + 1 : i + 1])
if highest != lowest:
rsv[i] = (close[i] - lowest) / (highest - lowest) * 100
else:
rsv[i] = 50 # 价格没波动时取中间值
# 初始化K、D
k[window - 1] = 50
d[window - 1] = 50
for i in range(window, len(close)):
k[i] = 2/3 * k[i - 1] + 1/3 * rsv[i]
d[i] = 2/3 * d[i - 1] + 1/3 * k[i]
j = 3 * k - 2 * d
return k, d, j
注意:KDJ的J值经常超出0-100的范围,这是正常的。J值超过100或低于0时,往往意味着极端行情。我在实盘中遇到过J值冲到120的情况,那波行情确实很猛,但随后就是剧烈回调。
4.6 指标组合使用建议
单个指标容易出错,组合使用才是王道。我常用的组合方式:
| 交易场景 | 指标组合 | 信号确认方式 |
|---|---|---|
| 趋势跟踪 | MA + MACD | MA多头排列 + MACD零轴上方金叉 |
| 震荡交易 | 布林带 + RSI | 价格触及布林带上下轨 + RSI超买超卖 |
| 短线交易 | KDJ + EMA | KDJ金叉 + 价格站上EMA20 |
嗯,这些指标的实现其实都不难。难的是怎么用、什么时候用。我建议你先拿历史数据跑一遍,看看每个指标在不同行情下的表现。慢慢你就会发现,指标只是工具,真正赚钱的是你对市场的理解。
最后说一句:代码写完了,记得用真实数据测试一下。我刚开始写指标库的时候,有个EMA的初始值算错了,回测结果看起来特别漂亮——后来才发现是未来函数在作怪。嗯,这种事情,经历过一次就长记性了。