首頁 > Python 理財程式小技巧 - 優化程式執行速度,從減少重...

Python 理財程式小技巧 – 優化程式執行速度,從減少重複索引龐大數據和向量化做起

Python 理財程式小技巧 - 優化程式執行速度,從減少重複索引龐大數據和向量化做起
Python 理財程式小技巧 – 優化程式執行速度,從減少重複索引龐大數據和向量化做起

Python 理財程式小技巧系列閱讀:

隨著 Python 越來越普及,許多讀者在求學階段就對 Python 有些著墨,甚至是使用 Python 來完成學校作業。但其實,除了資訊科系以外,大部分 Python 初學者的程式只能說是「跑得出結果」,但是效率是有明顯改善空間的。

確實,Python 在眾多程式語言當中,本身速度就沒有優勢,但我們可以透過一些小技巧,大幅縮短程式運行時間。

本篇主要講述的是 for 迴圈的設計,以及龐大數據量的 pandas 資料索引的優化。

情境解說與資料讀取

首先,這裡介紹一個很常見的情境,比較一下不同做法的速度:

假設我們要對一個資料長度超過 100 萬的一分線價量資料 DataFrame,比對每筆收盤價有沒有比當根 K 棒開盤價高。(這通常會出現在我們要計算策略訊號的時候)

首先,先讀取資料:


import pandas as pd
price = pd.read_csv(“BTCUSDT.csv”, index_col=”timestamp”)

讀取進來的資料長相為:

print(price) # DataFrame長寬: 1164743 rows x 5 columns
timestamp open high low close volume
2020/1/1 00:00 7170.25 7170.5 7157 7163.25 4.65E+05
2020/1/1 00:01 7163.25 7163.75 7161.25 7161.75 1.50E+04
2020/1/1 00:02 7159.5 7161.5 7155.5 7157.75 3.87E+05
2020/1/1 00:03 7157.75 7162.5 7157.75 7162.5 5.35E+05
2020/1/1 00:04 7160.75 7160.75 7157.25 7158.25 2.20E+05
2022/3/19 23:56 42236 42242 42225 42242 1.06E+06
2022/3/19 23:57 42242 42245 42228 42236 6.86E+05
2022/3/19 23:58 42236 42263 42232 42232 7.39E+05
2022/3/19 23:59 42232 42249 42231 42238 5.12E+05
2022/3/20 00:00 42238 42238 42209 42229 5.25E+05

迴圈設計:初學者版本

要比對每筆資料,一般初學者往往會很直覺地寫出這個解法:


for i in range(len(price)):
    if price.iloc[i]["close"] > price.iloc[i]["open"]:
        movement = "rise"
    elif price.iloc[i]["close"] < price.iloc[i]["open"]:
        movement = "drop"
    else:
        movement = "flat"

這個解法在學校應該沒什麼問題,放著一個晚上總是跑得完。實際上,我們實測這段程式碼花了 277.03 秒。這速度都超過一碗泡麵的時間了!

那這段程式碼的問題在哪?

首先,for 迴圈我們用 range 每圈對應到「第 i 行」,這個迴圈的寫法注定讓後面取用資料的時候,運行異常緩慢。再來,在迴圈內,我們調用了四次 price 整個 DataFrame 來找到該行的收盤價和開盤價的值。每一次這種調用不會多花多少時間,但是每一圈都有四次調用,而我們有 1,164,743 圈,累積下來的時間花費就變得非常可觀!

所以,我們接著來修正看看,如果每圈只調用兩次 price 一整份的 DataFrame,可以節省多少時間?


for i in range(len(price)):
    close_price = price.iloc[i]["close"]
    open_price = price.iloc[i]["open"]
    if close_price > open_price:
        movement = "rise"
    elif close_price < open_price:
        movement = "drop"
    else:
        movement = "flat"

結果總花費時間為 179.66 秒。相比節省了將近 100 秒,也就是 36% 的時間!

迴圈設計:使用 index

如果我們再優化,不要用 iloc 的方式,改用 index 做 for 迴圈,效率有改善嗎?


for index in price.index:
    close_price = price.loc[index, "close"]
    open_price = price.loc[index, "open"]
    if close_price > open_price:
        movement = "rise"
    elif close_price < open_price:
        movement = "drop"
    else:
        movement = "flat"

程式碼優化如上方區塊紅色字體的部分。實測結果,我們在 20.19 秒就運行完成了!儘管每一圈迴圈都要調用兩次 price,但顯然透過 loc,比 iloc 查找資料的運行速度快得多了!

迴圈設計:使用 iterrows

如果是透過許多初學者愛用的 iterrows 來遍歷每一行呢?


for index, row in price.iterrows():
    if row["close"] > row["open"]:
        movement = "rise"
    elif row["close"] < row["open"]:
        movement = "drop"
    else:
        movement = "flat"

實測結果為 65.86 秒,比 iloc 快,但沒有 loc 快。

效率最強王牌:向量化運算

但其實,Python 最強王牌在這裡:向量化(Vectorize)運算!


price["movement"] = pd.Series(
    np.where(price["close"] > price["open"], "rise", "drop"),
    index = price.index
)
price.loc[price["close"] == price["open"], "movement"] = "flat"

🏆 這段程式碼僅花費 1.28 秒就完成任務!

向量化(Vectorize)運算

這裡講解一下程式碼:我們直接在 price 這個 DataFrame 新增一個 column 叫做“movement”,定義這欄的值是用 np.where 判斷收盤價是否大於開盤價的結果,若收盤價大於開盤價,則存為“rise”。若否,則存為“drop”。把這個 np.where 輸出的 array 轉成 pd.Series 格式,就能準確對齊 price 的 index 存入了!

不過要注意,收盤價與開盤價比較,不只有上升、下跌,還有平盤。因此,要另外寫一行,把收盤價大於開盤價的 movement 的值都要改為“flat”,這才會是正確答案。

結論

Python 有很多小撇步可以改善運行效率,向量化是一個非常強大的武器!雖然有時候無法避免使用迴圈,但光是迴圈設計的不同,就有明顯速度差異了。如果有其他更有效率的運行方式,歡迎私訊或留言讓我們知道哦!讓我們一起成長下去吧!

延伸閱讀:


量化通粉絲社群,定期分享實用資源
✅加入LINE匿名群組量化通QuantPass」無壓力討論與分享!
✅追蹤量化通的粉絲專頁量化通QuantPass」即時獲取實用的資源!

程式交易課程推薦
📣 Python 程式交易系列線上課程,手把手開始用程式交易打造自己的被動收入!

發表迴響

相關文章