查看全课大纲(第 32 / 52 节)
  1. 1.量化分析基本概念:从咖啡馆到华尔街
  2. 2.量化交易员学习路线:从零基础到策略开发
  3. 3.量化策略生命周期:数据分析的五大流程
  4. 4.量化数据类型:数值型与类别型数据
  5. 5.量化沙盒搭建(1):Python与Anaconda安装
  6. 6.量化沙盒搭建(2):Jupyter Notebook与库配置
  7. 7.本地多源数据:量化历史回测的基石
  8. 8.TXT数据流操作:处理金融交易日志
  9. 9.JSON数据流操作:解析股票行情API响应
  10. 10.CSV行情文件操作:读取与存储历史K线
  11. 11.Excel报表操作:清洗财报与财务指标
  12. 12.量化数据库:SQL常用查询语法
  13. 13.量化数据库:Python连接与写入
  14. 14.量化多表连接:关联股票信息与行情
  15. 15.实战:小象咖啡店营业数据与个股财务指标的多源数据分析
  16. 16.量化数据采集:爬虫工作原理与合规
  17. 17.量化爬虫核心:URL管理与去重策略
  18. 18.量化网页下载:防反爬与请求头伪装
  19. 19.网页解析(1):使用XPath定位财经新闻
  20. 20.网页解析(2):使用CSS选择器与ItemLoader
  21. 21.Scrapy框架:构建工业级量化爬虫
  22. 22.Scrapy实战(1):新建项目与数据模型定义
  23. 23.Scrapy实战(2):编写财经爬虫逻辑
  24. 24.Scrapy实战(3):启用数据管道清洗行情
  25. 25.Scrapy实战(4):防反爬中间件配置
  26. 26.实战:构建每日热门个股行情自动采集系统
  27. 27.NumPy与SciPy:加速量化矩阵计算
  28. 28.NumPy数组:构建股票价格矩阵
  29. 29.数组切片:提取特定股票与交易日数据
  30. 30.NumPy常用方法:计算均值与波动率
  31. 31.向量化计算:摆脱缓慢的For循环
  32. 32.通用函数ufunc:计算对数收益率与累计收益
  33. 33.实战:股票组合资产收益率与协方差矩阵计算
  34. 34.Pandas Series:管理单只股票收盘价
  35. 35.Pandas DataFrame:构建多只股票行情面板
  36. 36.Pandas Index:时间序列索引与对齐
  37. 37.DataFrame索引操作:loc与iloc定位行情
  38. 38.索引总结:量化多字段筛选与定位
  39. 39.运算与对齐:合并不同交易日的数据
  40. 40.Pandas map:对股票代码与状态做映射
  41. 41.Pandas apply:滚动计算技术指标
  42. 42.排序操作:按市盈率与收益率对股票排名
  43. 43.数据清洗:填充与剔除停牌股票缺失值
  44. 44.数据清洗:剔除重复报价与异常交易数据
  45. 45.常用统计:量化最大回撤与分位数风险评估
  46. 46.极值定位:寻找历史最高价与最低价的发生日
  47. 47.风险与累计收益:计算年化波动率与累计回报
  48. 48.实战:小象咖啡店营业数据与热门个股时间序列清洗分析
  49. 49.Series索引操作:按位置与标签访问行情数据
  50. 50.文件读写操作:CSV行情数据导入导出
  51. 51.数据清洗:替换异常报价数据
  52. 52.常用统计:sum/mean/median/count聚合指标

通用函数ufunc:计算对数收益率与累计收益

约 10 分钟

小象实战讲义 · 量化交易Python入门:从咖啡馆到华尔街

欢迎进入量化矩阵计算的世界!NumPy 是所有 Python 金融库的底层基石。本章你将掌握 ndarray 多维数组的操作,学习如何用高效的向量化计算和广播机制代替缓慢的 Python 循环,计算股票组合的协方差与收益率。

💡 核心导读

本节将深入探讨NumPy最核心的向量化计算能力和通用函数(ufunc)机制。通过学习,您将掌握:1) 如何用向量化操作替代低效的循环计算;2) 理解ufunc的广播机制和性能优势;3) 熟练使用数学、统计、逻辑等各类通用函数。这些技能将帮助您将Python代码性能提升1-2个数量级,同时保持代码简洁可读。


向量化编程范式

为什么需要向量化

传统Python循环在数值计算时存在显著性能瓶颈:

import numpy as np

def slow_dot(a, b):
    result = 0
    for x, y in zip(a, b):
        result += x * y
    return result

def fast_dot(a, b):
    return np.sum(a * b)

a = np.array([1.0, 2.0, 3.0, 4.0])
b = np.array([5.0, 6.0, 7.0, 8.0])

slow_result = slow_dot(a, b)
fast_result = fast_dot(a, b)

print(f"循环点积结果: {slow_result:.1f}")
print(f"向量化点积结果: {fast_result:.1f}")
print(f"结果一致: {np.isclose(slow_result, fast_result)}")

典型输出结果:

循环点积结果: 70.0
向量化点积结果: 70.0
结果一致: True

向量化核心原理

NumPy的向量化操作通过以下机制实现加速:

  1. 连续内存布局:数组元素在内存中连续存储
  2. 单指令多数据(SIMD):CPU并行处理多个数据
  3. 消除解释器开销:操作在C层面执行

用 ufunc 计算对数收益率

对数收益率是 ufunc 的经典应用:np.log 对价格数组逐元素取对数,一次调用算出全部日对数收益率;再利用对数收益的时间可加性,把相邻两天的对数收益直接相加就是两日累计收益——下面的代码同时验证这两点,与随后概念小测的正确答案相互印证:

import numpy as np

prices = np.array([100.0, 102.0, 101.0, 103.5, 105.0])
log_returns = np.log(prices[1:] / prices[:-1])

print("收盘价序列:", prices)
print("日对数收益率:", np.round(log_returns, 6))
print("前两日累计对数收益(直接相加):", round(log_returns[0] + log_returns[1], 6))
print("校验: ln(101.0/100.0) =", round(np.log(101.0 / 100.0), 6))

典型输出结果:

收盘价序列: [100.  102.  101.  103.5 105. ]
日对数收益率: [ 0.019803 -0.009852  0.024451  0.014389]
前两日累计对数收益(直接相加): 0.00995
校验: ln(101.0/100.0) = 0.00995

通用函数(ufunc)详解

ufunc基本特性

通用函数是对ndarray进行元素级运算的函数,主要特点:

  • 支持数组广播
  • 提供多种返回方式(out参数)
  • 包含丰富的数学运算方法
import numpy as np

arr = np.array([1, 4, 9, 16])

sqrt_arr = np.sqrt(arr)
print(f"平方根: {sqrt_arr}")

added = np.add(arr, [2, 3, 4, 5])
print(f"加法: {added}")

result = np.empty(4)
np.multiply(arr, 2, out=result)
print(f"乘法 (out参数): {result}")

广播机制实战

广播规则遵循”从右向左”维度匹配原则:

平方根: [1. 2. 3. 4.]
加法: [ 3  7 13 21]
乘法 (out参数): [ 2.  8. 18. 32.]
import numpy as np

matrix = np.arange(12).reshape(3, 4)
vector = np.array([10, 20, 30, 40])

broadcasted = matrix + vector
print("广播加法结果矩阵:")
print(broadcasted)
广播加法结果矩阵:
[[10 21 32 43]
 [14 25 36 47]
 [18 29 40 51]]

常用ufunc分类应用

数学运算函数

import numpy as np

angles = np.linspace(0, np.pi, 5)
sines = np.sin(angles)
print(f"正弦值: {sines}")

exps = np.exp([1, 2, 3])
print(f"指数值: {exps}")

arr = np.array([3.1415, 2.7182, 1.6180])
rounded = np.round(arr, 2)
print(f"四舍五入: {rounded}")
正弦值: [0.00000000e+00 7.07106781e-01 1.00000000e+00 7.07106781e-01
 1.22464680e-16]
指数值: [ 2.71828183  7.3890561  20.08553692]
四舍五入: [3.14 2.72 1.62]

统计函数应用

import numpy as np

np.random.seed(42)
data = np.random.normal(5, 2, (4, 5))
print("模拟数据矩阵:")
print(data)
print()

print(f"全局均值: {np.mean(data):.2f} 全局标准差: {np.std(data):.2f}")
print()

row_max = np.max(data, axis=1)
print(f"每行最大值 (axis=1): {row_max}")

col_sum = np.sum(data, axis=0)
print(f"每列求和 (axis=0): {col_sum}")
模拟数据矩阵:
[[5.99342831 4.7234714  6.29537708 8.04605971 4.53169325]
 [4.53172609 8.15842563 6.53486946 4.06105123 6.08512009]
 [4.07316461 4.06854049 5.48392454 1.17343951 1.55016433]
 [3.87542494 2.97433776 5.62849467 3.18395185 2.1753926 ]]

全局均值: 4.66 全局标准差: 1.87

每行最大值 (axis=1): [8.04605971 8.15842563 5.48392454 5.62849467]
每列求和 (axis=0): [18.47374395 19.92477528 23.94266574 16.4645023  14.34237027]

📝 动手练一练

第 1 题 · 概念应用

小象在分析两只股票(A和B)的收益序列时,分别计算了它们的日对数收益率。他发现,要计算过去一周(5个交易日)的总收益率,可以直接将股票A的5个日对数收益率相加,而股票B则需要将5个日简单收益率通过 (1+r1)*(1+r2)*...*(1+r5)-1 的公式来计算。以下哪种说法最准确地解释了他采用不同计算方法的原因?

  • A. 股票A的价格数据质量更高,因此可以使用更简单的加法。
  • B. 股票A的日收益率波动更小,近似满足可加性,而股票B的波动大,所以不行。
  • C. 股票A计算的是对数收益率,具有时间可加性;股票B计算的是简单收益率,多期累计需用乘法。
  • D. 这是一个计算错误,无论哪种收益率,多期累计都应该使用乘法公式。
👉 点击查看参考答案

答案:C

对数收益率 rlog,t=ln⁡(Pt/Pt−1)r_{log, t} = \ln(P_t / P_{t-1}) 具有关键的时间可加性:从第0天到第T天的累计对数收益率等于期间各日对数收益率之和,即 ln⁡(PT/P0)=∑t=1Trlog,t\ln(P_T / P_0) = \sum_{t=1}^{T} r_{log, t}。因此,对于股票A,计算一周总收益只需将5个日对数收益率相加。

简单收益率 rsimple,t=(Pt−Pt−1)/Pt−1r_{simple, t} = (P_t - P_{t-1}) / P_{t-1} 不具有可加性。从第0天到第T天的累计简单收益率需要通过乘法(复利)计算:Rtotal=(1+r1)(1+r2)...(1+rT)−1R_{total} = (1+r_1)(1+r_2)...(1+r_T)-1。因此,对于股票B,必须使用题目中描述的乘法公式。

选项A和B的描述没有理论依据。选项D的说法是错误的,因为对不同性质的收益率,累计计算的方法本就不同。


第 2 题 · 代码填空

小象需要计算一个由三只股票(Apple, Banana, Cherry)过去4天收盘价构成的投资组合的日对数收益率矩阵。他已经有了价格数据矩阵 prices(每一行代表一只股票,每一列代表一个交易日),并尝试使用向量化操作一次性计算出所有股票在所有时间点上的对数收益率。请补全下面的代码。

import numpy as np

# 三只股票,过去4个交易日的收盘价 (行:股票, 列:时间)
prices = np.array([
    [100.0, 102.0, 101.5, 103.0],  # Apple
    [50.0,  48.0,  49.0,  51.0],   # Banana
    [200.0, 205.0, 204.0, 208.0]   # Cherry
])

# 使用向量化操作计算日对数收益率
# 提示:对数收益率 = ln(今日价格 / 昨日价格);空位为"昨日价格"对应的切片
log_returns = np.log(prices[:, 1:] / ______)

print("价格矩阵 (3 stocks x 4 days):")
print(prices)
print("\n日对数收益率矩阵 (3 stocks x 3 days):")
print(np.round(log_returns, 4))
👉 点击查看参考答案

空位应填:prices[:, :-1]

完整代码与解释:

import numpy as np

# 三只股票,过去4个交易日的收盘价 (行:股票, 列:时间)
prices = np.array([
    [100.0, 102.0, 101.5, 103.0],  # Apple
    [50.0,  48.0,  49.0,  51.0],   # Banana
    [200.0, 205.0, 204.0, 208.0]   # Cherry
])

# 使用向量化操作计算日对数收益率
# 关键步骤:
# 1. prices[:, 1:] 获取每只股票从第2天到第4天的价格子矩阵。
# 2. prices[:, :-1] 获取每只股票从第1天到第3天的价格子矩阵。
# 3. 两个子矩阵进行逐元素的除法,得到每日价格比 (P_t / P_{t-1})。
# 4. np.log 作为ufunc,对整个结果矩阵逐元素取自然对数。
log_returns = np.log(prices[:, 1:] / prices[:, :-1])

print("价格矩阵 (3 stocks x 4 days):")
print(prices)
print("\n日对数收益率矩阵 (3 stocks x 3 days):")
print(np.round(log_returns, 4))

运行结果示例:

价格矩阵 (3 stocks x 4 days):
[[100.  102.  101.5 103. ]
 [ 50.   48.   49.   51. ]
 [200.  205.  204.  208. ]]

日对数收益率矩阵 (3 stocks x 3 days):
[[ 0.0198 -0.0049  0.0147]
 [-0.0408  0.0206  0.04  ]
 [ 0.0247 -0.0049  0.0194]]

该代码利用NumPy的切片和ufunc np.log,无需任何循环,一次性完成了整个投资组合的收益率计算,是向量化编程的典型应用。

本章小结

📋 行动清单

  • 使用向量化操作重写一个现有的Python循环计算代码,比较性能差异
  • 创建一个3x4矩阵和1x4向量,手动推导并验证广播规则的实际效果
  • 对随机生成的100万数据点同时计算sin、cos和tan值,统计总耗时

—— 小象教研组