这是课程"做你自己的套利机器人"的辅助课。第 13 课用一行 numpy 检查数百万个货币回路。本页用四个价格解释这些词和这个技巧。
直接的办法:循环
有一种币的四个价格,你想知道每一步变化了多少:当前价格除以上一个价格。大家写的第一个版本是循环:
prices = [100.0, 101.5, 99.8, 102.0]
ret = [prices[i+1] / prices[i] for i in range(len(prices) - 1)]
print([round(r, 4) for r in ret])
[1.015, 0.9833, 1.022]
这能用。但扫描器变慢正是因为这个:Python 一次只走列表里的一项。四个价格没人会察觉,一百万个就会察觉。
向量:当作一个整体来用的列表
向量就是一列数。numpy 库把它存放在一块内存里,让你用一个表达式对整个列表做运算:
import numpy as np
p = np.array(prices)
print(np.round(p[1:] / p[:-1], 4))
print(p.mean(), p.max(), p.argmax())
[1.015 0.9833 1.022 ]
100.825 102.0 3
p[1:] 表示"除第一个以外的所有价格",p[:-1] 表示"除最后一个以外的所有价格"。两者相除,是把相同位置的一对数相除:101.5/100、99.8/101.5、102/99.8。结果和循环一样,但没有循环。mean 是平均值,max 是最大值,argmax 是最大值的位置(下标 3,价格 102)。
矩阵:数字表格
矩阵是有行有列的表格。货币之间的汇率表就是一个矩阵:第 i 行第 j 列表示一个单位的 i 能换到多少 j。
M = np.array([[1, 2, 3],
[4, 5, 6]])
print(M.shape, M[1, 2], M[:, 0], M[0, :])
print(M * 10)
(2, 3) 6 [1 4] [1 2 3]
[[10 20 30]
[40 50 60]]
来读一读。shape (2, 3) 表示 2 行 3 列。M[1, 2] 是第 1 行第 2 列(从 0 开始数),即 6。M[:, 0] 是整个第一列,M[0, :] 是整个第一行。M * 10 把每个格子都乘以 10。
广播:小表格被拉伸成大表格
把两个形状不同的东西相加会怎样?numpy 会把较小的那个复制拉伸,直到形状一致。这就叫广播。
print(M + np.array([100, 200, 300]))
print(M + np.array([[1], [2]]))
[[101 202 303]
[104 205 306]]
[[2 3 4]
[6 7 8]]
第一行里,三个数的一行被向下复制到 M 的每一行。第二行里,一列 [[1], [2]] 被向右复制:第一行加 1,第二行加 2。第 13 课用同样的拉伸,把同一个表格的三种视角相乘,一步得到所有货币三元组构成的立方体。
快多少
import time
x = np.arange(1_000_000, dtype=float)
t = time.perf_counter(); s = 0.0
for v in x: s += v * 2
t1 = time.perf_counter() - t
t = time.perf_counter(); s2 = (x * 2).sum(); t2 = time.perf_counter() - t
print(s == s2, "loop %.3f s vs numpy %.4f s" % (t1, t2))
True loop 0.111 s vs numpy 0.0011 s
答案相同,在这台机器上大约快 100 倍(你的数字会不同)。循环把时间花在对每一项的记账上,而 numpy 只把整块数据一次交给编译好的代码。
你的练习
- 运行所有代码,把输出和课程对比。
- 自己做一个 5 个价格的向量,用一行代码算出每一步的比值。手算检查第一个。
- 建一个 3 乘 3 的数字表。打印它的第二列和最后一行。
- 把
np.array([[10], [20], [30]])加到你的表上。先在纸上预测结果,再检查。 - 把
x的大小改成 1000 万。两个耗时怎么变?
下一步
现在可以读第 13 课了。它的技巧正是上面的第 4 步,只不过用的是汇率表。