這是課程"做你自己的套利機器人"的輔助課。第 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 步,只不過用的是匯率表。