1. 这节究竟解决什么实际问题?¶
3.1 趋势类指标 的具体任务是:从均线和趋势强度构造规则,并检查规则进入交易前是否已知。 本节将明确输入数据、计算过程、输出及其在后续研究中的用途。
2. 前置知识¶
理解收益率、滚动窗口和基础 DataFrame 操作。 对公式的理解应包括变量定义、单位、时间索引和适用条件。
3. 数据说明¶
使用固定日频价格样本;高低开收和成交量如为教学代理,会在代码中明确说明。 运行前先检查 shape、日期范围、列名与缺失值。数据看起来能画图,不代表它适合回答研究问题。
4. 学习目标与完成标准¶
完成本节后,你应能用自己的话解释 3.1 趋势类指标 的输入、计算步骤、输出和局限;尤其能说明:从均线和趋势强度构造规则,并检查规则进入交易前是否已知。 你还应能独立改动一个参数并解释结果为什么变化,指出至少一个会让结论失真的假设。
5. 核心概念与公式¶
技术指标将历史价格或成交量压缩为统计量。指标本身不包含交易方向、执行时点和风险约束。
滚动指标只使用窗口结束日及以前的数据;交易仓位必须至少滞后一个可交易周期。 看到公式时依次确认:每个符号的单位是什么?它属于哪个时点?分母会不会为零?它是描述性统计量、预测量,还是交易规则?
6. 从零实现¶
原始代码按照“准备输入 → 计算中间量 → 得到结果 → 解释输出”的顺序组织。每个单元应检查变量类型、索引、维度和代表性数值。中间变量用于核对公式实现和定位计算错误。
7. 成熟库实现¶
公共指标函数减少重复代码;首次学习仍应读懂每个滚动、平滑和标准化步骤。 使用库函数前先阅读参数含义、默认值和返回对象;库输出一个数字,并不说明这个数字适合你的交易假设。
8. 两种实现的差异¶
从零实现便于核对公式、调试和识别隐含假设;成熟库适合在计算逻辑已验证后复用。两种实现依赖相同的数据口径与时点假设,使用库函数不会改变输入数据的质量。
9. 预期结果与检查范围¶
你应看到维度正确的 Series/DataFrame、日期单调递增的索引,以及能支持“从均线和趋势强度构造规则,并检查规则进入交易前是否已知。”这一任务的表格或图形。金融日收益率均值通常接近 0;若出现极大的连续收益、空表、全 NaN 或索引错位,应先排查数据和对齐逻辑,而不是立即相信结果。
10. 常见错误与诊断¶
窗口不足时误读 NaN、同日用收盘信号按收盘成交、忽略除零、把指标阈值当普适规律。
诊断顺序:先打印类型与 shape,再看 head()/tail(),接着检查日期和 NaN,最后才比较数值。这样能避免在错误的对象上反复改参数。
11. 研究偏差检查¶
测试大量窗口后只保留样本内表现最高的参数会产生数据窥探偏差;任何信号都应和简单基线比较。 结果至少应与一个简单基线比较,并明确哪些部分只是教学演示。
12. 三层练习与答案¶
- 基础练习:验证一个中间变量、补全一段小计算,并用一句话解释输出。
- 研究练习:改变窗口、样本区间、约束或标的,记录哪些结论仍成立。
- 开放项目:把本节方法嵌入模块项目,报告基线、失败结果和限制。
题目在 ../exercises/03_indicators/01_trend_indicators_exercises.ipynb;完整推导、可运行参考实现与结果检查在 ../solutions/03_indicators/01_trend_indicators_solutions.ipynb。请先独立作答,再打开答案。
13. 延伸阅读¶
延伸材料:Technical Analysis of the Financial Markets(Murphy)。阅读记录应包括研究假设、数据频率、样本范围和结论适用条件。
14. 下一节¶
下一节是 02_momentum_indicators。本节的概念会成为理解它的输入,而不是孤立的名词。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import yfinance as yf
plt.rcParams['figure.figsize'] = (13, 5)
data = yf.download('AAPL', start='2022-01-01', end='2024-01-01', progress=False)
close = data['Close'].squeeze()
print('数据加载完成 ')
数据加载完成
# 默认使用固定样本,保证课程与 CI 不依赖实时接口。
USE_OFFLINE_DATA = True
if USE_OFFLINE_DATA:
from pathlib import Path
import sys
docs_root = next(parent for parent in [Path.cwd(), *Path.cwd().parents]
if (parent / 'utils' / 'offline_data.py').exists())
sys.path.insert(0, str(docs_root))
from utils.offline_data import configure_yfinance_offline
configure_yfinance_offline(yf)
else:
print('在线下载为扩展步骤;请核对数据字段、时区和下载日期。')
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
1. SMA — 简单移动均线¶
为什么需要它?¶
你每天看股票,会发现价格像锯齿一样上上下下,今天涨了明天跌,很难看出到底是在上涨还是下跌。
移动均线的思路很简单:把过去 N 天的价格做个平均,得到一条平滑的曲线。
移动平均使用最近一段窗口内的价格均值平滑短期波动。窗口越长,曲线通常越平滑,但对价格变化的响应也越慢。
公式¶
$$SMA_n(t) = \frac{1}{n} \sum_{i=0}^{n-1} P_{t-i}$$
就是把最近 $n$ 天的收盘价加起来除以 $n$,就这么简单。
局限性¶
- 滞后:移动平均只使用窗口内历史价格,因此对最新价格变化的响应存在延迟
- 权重均等:窗口内每个价格在 SMA 中权重相同;EMA 则给予近期观测更高权重
金叉 / 死叉¶
一个经典用法是同时画两条均线,短期(如20日)和长期(如60日):
- 金叉(买入信号):短期均线从下穿越长期均线 → 说明近期价格开始强于长期趋势,趋势可能向上
- 死叉(卖出信号):短期均线从上穿越长期均线 → 近期价格开始弱于长期趋势,趋势可能向下
sma20 = close.rolling(20).mean()
sma60 = close.rolling(60).mean()
# 金叉/死叉信号
cross_up = (sma20 > sma60) & (sma20.shift(1) <= sma60.shift(1)) # 金叉
cross_dn = (sma20 < sma60) & (sma20.shift(1) >= sma60.shift(1)) # 死叉
fig, ax = plt.subplots()
ax.plot(close.index, close.values, label='收盘价', linewidth=1, alpha=0.7)
ax.plot(sma20.index, sma20.values, label='SMA20(短期均线)', linewidth=1.5, color='orange')
ax.plot(sma60.index, sma60.values, label='SMA60(长期均线)', linewidth=1.5, color='red')
ax.scatter(close.index[cross_up], close.values[cross_up], marker='^', color='green', s=120, zorder=5, label='金叉(买入)')
ax.scatter(close.index[cross_dn], close.values[cross_dn], marker='v', color='red', s=120, zorder=5, label='死叉(卖出)')
ax.set_title('SMA20 / SMA60 金叉死叉策略', fontsize=13)
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.show()
2. EMA — 指数移动均线¶
为什么在 SMA 之外还需要 EMA?¶
SMA 有一个直觉上的问题:它把 30 天前的价格和昨天的价格一视同仁。但常识告诉我们,昨天发生的事比一个月前的事更有参考价值。
EMA 的改进:给最近的价格更高的权重。 时间越近,权重越高;时间越远,权重呈指数衰减(exponential decay,这就是名字里「指数」的来源)。
效果:EMA 线比 SMA 线更贴近最新价格,对趋势变化反应更快。
公式¶
$$EMA_t = \alpha \cdot P_t + (1-\alpha) \cdot EMA_{t-1}, \quad \alpha = \frac{2}{n+1}$$
直觉理解:今天的 EMA = 今天价格的一点点 + 昨天 EMA 的大部分。 ($\alpha$ 越大,越重视今天的变化,EMA 越灵敏)
ema12 = close.ewm(span=12, adjust=False).mean()
ema26 = close.ewm(span=26, adjust=False).mean()
fig, ax = plt.subplots()
ax.plot(close.index, close.values, label='收盘价', linewidth=1, alpha=0.5)
ax.plot(sma20.index, sma20.values, label='SMA20(慢,滞后)', linewidth=1.5, linestyle='--')
ax.plot(ema12.index, ema12.values, label='EMA12(快,更贴近价格)', linewidth=1.5)
ax.set_title('SMA vs EMA 对比 — EMA 对价格变化反应更快', fontsize=13)
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.show()
3. MACD — 指数平滑异同移动平均线¶
为什么需要 MACD?¶
单条均线只能告诉你「现在的趋势大方向」,但不能告诉你趋势的强度在加速还是减弱。
MACD 的思路很精妙:用两条 EMA 之间的差距来衡量趋势的动力。
MACD 柱状图等于 MACD 线与信号线之差,用于描述两条指数移动平均差值的变化:
- 短期 EMA(12日)追得快,代表近期市场力量
- 长期 EMA(26日)走得慢,代表长期背景力量
- MACD = 短期 - 长期:差值为正,说明近期比长期强,市场在加速向上;差值为负,说明近期弱,向下的力量在加大
三条线是什么¶
$$MACD = EMA_{12} - EMA_{26} \quad \text{(主线,衡量趋势差距)}$$ $$Signal = EMA_9(MACD) \quad \text{(信号线,主线的「均线」,更平滑)}$$ $$Histogram = MACD - Signal \quad \text{(柱状图,两线距离,衡量动力是否在增强)}$$
信号计算示例¶
- MACD 线上穿 Signal 线:买入信号(上涨动力增强)
- MACD 线下穿 Signal 线:卖出信号(下跌动力增强)
- Histogram 柱子越来越长:趋势动力加速;越来越短:动力在减弱,可能要转头
- 背离(Divergence):价格创新低,但 MACD 未创新低(底背离,可能反转向上);价格创新高,但 MACD 未创新高(顶背离,可能反转向下)。
macd_line = ema12 - ema26
signal_line = macd_line.ewm(span=9, adjust=False).mean()
histogram = macd_line - signal_line
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(13, 8), sharex=True)
# 价格图
ax1.plot(close.index, close.values, linewidth=1.2, color='steelblue')
ax1.set_title('AAPL 收盘价', fontsize=12)
ax1.set_ylabel('价格')
ax1.grid(alpha=0.3)
# MACD 图
ax2.plot(macd_line.index, macd_line.values, label='MACD(主线)', linewidth=1.5, color='blue')
ax2.plot(signal_line.index, signal_line.values, label='Signal(信号线)', linewidth=1.5, color='orange')
colors = ['green' if v >= 0 else 'red' for v in histogram.values]
ax2.bar(histogram.index, histogram.values, color=colors, alpha=0.4, label='Histogram(动力柱)')
ax2.axhline(0, color='black', linewidth=0.8)
ax2.set_title('MACD 指标', fontsize=12)
ax2.legend()
ax2.grid(alpha=0.3)
plt.tight_layout()
plt.show()
print(' 交易信号:MACD 线从下穿越 Signal 线 → 买入信号(近期上涨动力超过长期平均)')
交易信号:MACD 线从下穿越 Signal 线 → 买入信号(近期上涨动力超过长期平均)
练习¶
- 将 SMA 参数从 (20, 60) 改为 (5, 20),观察金叉/死叉频率变化。(短均线参数越小,信号越多,但假信号也越多)
- MACD 的直方图(Histogram)在什么情况下达到峰值?这意味着什么?
- 实现一个简单规则:MACD 线上穿 Signal 线买入,下穿卖出,计算年化收益。
下一节 → 02_momentum_indicators.ipynb