Shopping Cart

購物車內沒有任何商品。

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
timestampopenhighlowclosevolume
2020/1/1 00:007170.257170.571577163.254.65E+05
2020/1/1 00:017163.257163.757161.257161.751.50E+04
2020/1/1 00:027159.57161.57155.57157.753.87E+05
2020/1/1 00:037157.757162.57157.257162.55.35E+05
2020/1/1 00:047160.757160.757157.257158.252.20E+05
2022/3/19 23:56422364224242225422421.06E+06
2022/3/19 23:57422424224542228422366.86E+05
2022/3/19 23:58422364226342232422327.39E+05
2022/3/19 23:59422324224942231422385.12E+05
2022/3/20 00:00422384223842209422095.25E+05

Py 101209161710
Py 101209161711

三、迴圈設計:初學者版本

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


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社群量化交易討論群」無壓力討論與分享!

加入Discord 「量化交易討論群」即時獲取實用的資源!

Anti Fraud 10140382164
Anti Fraud 10140382163
Anti Fraud 10140382165
QP66
QP66

具備逾十年交易經驗,研究交易資產橫跨股票、債券、外匯、原物料,以及加密貨幣。現為量化避險基金交易員,亦曾任職於資金規模逾百億的避險基金,以及在區塊鏈企業擔任顧問一職。

擅長從宏觀至微觀,由淺入深挖掘交易機會,並運用Python實現全自動化的投資組合管理。

文章: 24

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

立即訂閱電子報,掌握最新資訊!

    稱呼

    電子郵件

    以下非必填,但若您願意分享,我們將能推送更精準的內容給您

    投資經驗

    是否為理工科背景、工程師或有寫程式的經驗?

    有興趣的主題
    量化交易台股期貨海外期貨虛擬貨幣美股

    有興趣的量化交易軟體/平台
    不清楚MultiChartsTradingViewPythonXQ

    想透過量化交易達成甚麼目的?
    不確定自動交易選股回測投資績效量化自己的投資方法想找現成的策略套用

    還有什麼想詢問的?