【譯】Python 金融:演算法交易 (2)常見的金融分析方法
本文翻譯自2018年最熱門的Python金融教程 ofollow,noindex">Python For Finance: Algorithmic Trading 。
本教程由以下五部分內容構成:
- Python金融入門
- 常見的金融分析方法
- 簡單的動量策略開發
- 回溯測試策略
- 評估交易策略
這是該教程的第二部分,介紹常見的金融分析方法,包括以下內容:
現在,你已經瞭解了所使用的股票資料,什麼是時間序列資料,以及如何使用 Pandas 快速探索資料,是時候深入到常見的金融分析中了,以便可以真正開始交易策略的開發。
在本節中,你將瞭解更多關於收益率、移動視窗、波動率計算和普通最小二乘迴歸(OLS)的知識。
收益率
簡單的 每日百分比變化 不考慮紅利和其他因素,而是指在一天的交易中股票價值變化的百分比。每日百分比變化的計算很簡單,只需使用 Pandas 包中的 pct_change()
函式。
# 匯入apple公司股票資料 import pandas_datareader as pdr import datetime aapl = pdr.get_data_yahoo('AAPL', start=datetime.datetime(2006, 10, 1), end=datetime.datetime(2012, 1, 1))
# 匯入numpy和pandas import numpy as np import pandas as pd # 將aapl資料框中`Adj Close`列資料賦值給變數`daily_close` daily_close = aapl[['Adj Close']] # 計算每日收益率 daily_pct_change = daily_close.pct_change() # 用0填補缺失值NA daily_pct_change.fillna(0, inplace=True) # 檢視每日收益率的前幾行 print(daily_pct_change.head())
Adj Close Date 2006-10-020.000000 2006-10-03-0.010419 2006-10-040.017548 2006-10-05-0.007296 2006-10-06-0.008152
# 計算每日對數收益率 daily_log_returns = np.log(daily_close.pct_change()+1) # 檢視每日對數收益率的前幾行 print(daily_log_returns.head())
Adj Close Date 2006-10-02NaN 2006-10-03-0.010474 2006-10-040.017396 2006-10-05-0.007323 2006-10-06-0.008186
注意,計算對數收益率可以使你更好地瞭解回報隨時間的增長。
很好,你知道了如何計算每日百分比變化,但如果想要知道月度或季度的收益率,該怎麼辦呢?在這種情況下,讓我們回顧本教程第一部分中介紹的 resample()
函式。
# 按營業月對 `aapl` 資料進行重取樣,取每月最後一項 monthly = aapl.resample('BM').apply(lambda x: x[-1]) # 計算每月的百分比變化,並輸出前幾行 print(monthly.pct_change().head())
HighLowOpenCloseVolumeAdj Close Date 2006-10-31NaNNaNNaNNaNNaNNaN 2006-11-300.1346720.1349870.1321060.1304880.7358540.130489 2006-12-29 -0.078550 -0.084560 -0.089578 -0.0744050.236584-0.074406 2007-01-310.0070260.0118760.0108400.010490 -0.2047140.010490 2007-02-28 -0.004651 -0.016005 -0.021918 -0.0130640.074066-0.013064
# 按季度對`aapl`資料進行重取樣,將均值最為每季度的數值 quarter = aapl.resample("3M").mean() # 計算每季度的百分比變化,並輸出前幾行 print(quarter.pct_change().head())
HighLowOpenCloseVolumeAdj Close Date 2006-10-31NaNNaNNaNNaNNaNNaN 2007-01-310.1305790.1244450.1295910.1263890.4522390.126389 2007-04-300.0340030.0399930.0354530.037156 -0.2560560.037156 2007-07-310.3577060.3475720.3549850.3539290.4299550.353929 2007-10-310.2153160.2110050.2139420.2154160.0441010.215416
使用 pct_change()
相當方便,但也讓人困惑到底每日的百分比是如何計算的。這也是為什麼人們會使用 Pandas 的 shift()
函式來代替 pct_change()
。用 daily_close
除以 daily_close.shift(1)
,然後再減 1
,就得到了每日百分比變化。然而,使用這一函式會使計算得到的資料框的開頭存在缺失值 NA
。
提示:將以下程式碼的結果和之前計算的每日百分比變化相比較,檢視這兩種方法的差異。
# 每日收益率 daily_pct_change = daily_close / daily_close.shift(1) - 1 # 輸出 `daily_pct_change`的前幾行 print(daily_pct_change.head())
Adj Close Date 2006-10-02NaN 2006-10-03-0.010419 2006-10-040.017548 2006-10-05-0.007296 2006-10-06-0.008152
提示:在IPython控制檯中嘗試使用 Pandas 的 shift()
函式計算每日對數收益率。(如果你找不到答案,試一下這行程式碼: daily_log_returns_shift = np.log(daily_close / daily_close.shift(1))
)。
做為參考,每日百分比變化的計算公式為: ,其中
是價格,
是時間(這裡是天),
是收益率。
此外,讓我們來繪製每日百分比變化 daily_pct_change
的分佈。
# 匯入 matplotlib import matplotlib.pyplot as plt # 繪製直方圖 daily_pct_change.hist(bins=50) # 顯示圖 plt.show() # 輸出daily_pct_change的統計摘要 print(daily_pct_change.describe())

Adj Close count1322.000000 mean0.001566 std0.023992 min-0.179195 25%-0.010672 50%0.001677 75%0.014306 max0.139050
以上分佈看起來非常對稱且像中心在0.00附近的正態分佈。儘管如此,你還是需要對 daily_pct_change
使用 describe()
函式,以確保正確解讀了直方圖。從統計摘要的結果中知道,其均值非常接近0.00,標準差是0.02。同時,檢視百分位數,瞭解有多少資料點落在-0.010672, 0.001677 和 0.014306之間。
累積日收益率有助於定期確定投資價值。可以使用每日百分比變化的數值來計算累積日收益率,只需將其加上 1
並計算累積的乘積。
# 計算累積日收益率 cum_daily_return = (1 + daily_pct_change).cumprod() # 輸出 `cum_daily_return` 的前幾行 print(cum_daily_return.head())
Adj Close Date 2006-10-02NaN 2006-10-030.989581 2006-10-041.006946 2006-10-050.999600 2006-10-060.991451
注意仍舊可以使用 Matplotlib 快速繪製 cum_daily_return
的曲線;只需對其加上 plot()
函式即可,可選擇使用引數 figsize
設定圖片大小。
# 繪製累積日收益率曲線 cum_daily_return.plot(figsize=(12,8)) # 顯示繪圖 plt.show()

非常簡單,不是嗎?現在,如果你不想使用日回報率,而是用月回報率,那麼對 cum_daily_return
使用 resample()
函式就可輕鬆實現月度水平的統計:
# 將累積日回報率轉換成累積月回報率 cum_monthly_return = cum_daily_return.resample("M").mean() # 輸出 `cum_monthly_return` 的前幾行 print(cum_monthly_return.head())
Adj Close Date 2006-10-311.031710 2006-11-301.140058 2006-12-311.155110 2007-01-311.187303 2007-02-281.145176
知道如何計算回報率是一項非常有用的技能,但是如果沒有將其與其他股票進行比較,就沒有太大的意義。這就是為什麼案例中經常會比較多隻股票。在本節接下來的內容中,我們將從雅虎財經中獲取更多的資料以便能比較不同股票的日收益率。
注意,接下來的工作需要你對Pandas有更深入的理解以及知道如何使用Pandas操作資料。
讓我們開始吧!首先從雅虎財經中獲取更多的資料。通過建立一個 get()
函式可以輕鬆地實現這一點。該函式將股票程式碼列表 tickers
以及開始和結束日期作為輸入引數。第二個函式 data()
將 ticker
作為輸入,用於獲取 startdate
和 enddate
日期之間的股票資料並將其返回。將 tickers
列表中的元素通過 map()
函式對映,獲取所有股票的資料並將它們合併在一個數據框中。
以下程式碼獲取了 Apple、Microsoft、IBM 和 Google 的股票資料,並將它們合併在一個大的資料框中。
import pandas_datareader as pdr import datetime def get(tickers, startdate, enddate): def data(ticker): return (pdr.get_data_yahoo(ticker, start=startdate, end=enddate)) datas = map (data, tickers) return(pd.concat(datas, keys=tickers, names=['Ticker', 'Date'])) tickers = ['AAPL', 'MSFT', 'IBM', 'GOOG'] all_data = get(tickers, datetime.datetime(2006, 10, 1), datetime.datetime(2012, 1, 1))
注意這一程式碼源自 “Mastering Pandas for Finance” 一書,並且在本教程中根據新的標準進行了升級。還是要注意,因為開發人員仍在研究從雅虎財經API中獲取資料的更持久的修復方案,你可能還是需要匯入 fix_yahoo_finance
包。可以從 此處 找到安裝說明或者檢視這篇教程的 Jupyter notebook 。
現在,檢視以上獲取的資料:
print(all_data.head())
HighLowOpenCloseVolume\ Ticker Date AAPL2006-10-0210.83857210.61428510.72857210.694285178159800.0 2006-10-0310.70714310.45571410.63571510.582857197677200.0 2006-10-0410.78000010.45142810.58571410.768572207270700.0 2006-10-0510.88000010.59000010.64714210.690000170970800.0 2006-10-0610.72000010.54428610.63142910.602858116739700.0 Adj Close Ticker Date AAPL2006-10-027.161565 2006-10-037.086947 2006-10-047.211311 2006-10-057.158698 2006-10-067.100338
接下來讓我們使用這個大的資料框做一些有趣的圖表:
# 選取 `Adj Close` 這一列並變換資料框 daily_close_px = all_data[['Adj Close']].reset_index().pivot('Date', 'Ticker', 'Adj Close') # 對`daily_close_px` 計算每日百分比變化 daily_pct_change = daily_close_px.pct_change() # 繪製分佈直方圖 daily_pct_change.hist(bins=50, sharex=True, figsize=(12,8)) # 顯示繪圖結果 plt.show()

另一類有用的圖是散點矩陣圖。使用 pandas
庫能夠輕易實現它。在程式碼中加入 scatter_matrix()
函式就可以繪製散點矩陣圖。將 daily_pct_change
作為引數傳遞給該函式,在對角線上使用核密度估計(KDE)做圖。另外,使用 alpha
引數設定透明度, figsize
引數設定圖片大小。
# 對 `daily_pct_change` 資料繪製散點矩陣圖 pd.plotting.scatter_matrix(daily_pct_change, diagonal='kde', alpha=0.1,figsize=(12,12)) # 顯示繪圖結果 plt.show()

注意如果你在本地執行程式碼,可能需要使用 plotting
模組來繪製散點矩陣圖(例如 pd.plotting.scatter_matrix()
)。而且,最好要知道核密度估計圖估算了隨機變數的概率密度函式。
恭喜你成功地完成了第一項常見的金融分析:收益率探索。現在讓我們進入下一個主題:移動視窗。
移動視窗
移動視窗指的是在一特定的時間視窗內計算資料的統計量,並在資料中按特定的間隔滑動視窗。這樣,只要視窗在時間序列的日期內不斷滑動,統計量就被連續的計算。
在Pandas中有許多函式都可以計算移動視窗,比如 rolling_mean()
、 rolling_std()
…… 在 這裡 檢視這些函式。
然而,注意這些函式大多數將被棄用,所以最好將 rolling()
函式和 mean()
、 std()
…… 結合使用,當然也依賴於實際要計算的移動視窗的種類。
但是,移動視窗到底意味著什麼呢?
當然確切的含義取決於對資料使用的統計量。例如,移動平均值平滑了資料中的短期波動並突出了長期趨勢。
# 選取調整的收盤價 adj_close_px = aapl['Adj Close'] # 計算移動均值 moving_avg = adj_close_px.rolling(window=40).mean() # 檢視後十項結果 print(moving_avg[-10:])
Date 2011-12-1637.186161 2011-12-1937.160666 2011-12-2037.137180 2011-12-2137.134022 2011-12-2237.129119 2011-12-2337.125867 2011-12-2737.129645 2011-12-2837.124527 2011-12-2937.145119 2011-12-3037.163272 Name: Adj Close, dtype: float64
提示:在IPython控制檯中嘗試Pandas包中其他標準的移動視窗函式,比如 rolling_max()
、 rolling_var()
或者 rolling_median()
。注意也可以結合使用 rolling()
和 max()
、 var()
或 median()
來得到相同的結果。
當然,你可能沒能真正理解這一切。也許使用 Matplotlib 做一幅簡單的圖,可以幫助你理解移動平均值及其實際的含義:
# 短期的移動視窗 aapl['42'] = adj_close_px.rolling(window=40).mean() # 長期的移動視窗 aapl['252'] = adj_close_px.rolling(window=252).mean() # 繪製調整的收盤價,同時包含短期和長期的移動視窗均值 aapl[['Adj Close', '42', '252']].plot() # 顯示繪圖結果 plt.show()

波動率計算
股票的波動率衡量了股票在特定時間內收益率的變化。常常將一隻股票的波動率和另一隻股票比較,以尋找風險較小的股票;或是將之與市場指數比較,來檢查股票在整個市場上的波動。一般來說,波動率越高,該股票的投資風險更大,導致人們選擇投資其他股票。
對數收益率的歷史移動標準差,也就是歷史移動波動率,可能更令人感興趣。也可以使用 pd.rolling_std(data, window=x) * math.sqrt(window)
來計算。
# 定義最小週期 min_periods = 75 # 計算波動率 vol = daily_pct_change.rolling(min_periods).std() * np.sqrt(min_periods) # 繪製波動率曲線 vol.plot(figsize=(10, 8)) # 顯示繪圖結果 plt.show()

通過計算股票百分比變化的移動視窗標準差得到波動率。從上述程式碼中可以清楚地看到這一點。
注意視窗的大小能夠改變整體的結果:如果擴大視窗(也就是讓 min_periods
變大),結果將變得不那麼有代表性。如果縮小視窗,結果將更接近於標準差。
考慮到所有這些,你會發現基於資料取樣頻率得到合適的視窗大小絕對是一項技能。
普通最小二乘迴歸
完成了上述所有計算後,你還可以使用更傳統的迴歸分析(比如普通最小二乘迴歸(OLS)),對金融資料進行更多的統計分析。
要做到這一點,你必須使用 statsmodels
庫,它不僅提供了用於估算多種統計模型的類和函式,還能讓你進行統計檢驗以及統計的資料探索。
注意你確實可以使用Pandas實現OLS迴歸,但是在將來的版本中其 ols
模組將被棄用。所以最明智的做法是使用 statsmodels
包。
# 匯入`statsmodels` 包的 `api` 模組,設定別名 `sm` import statsmodels.api as sm # 獲取調整的收盤價資料 all_adj_close = all_data[['Adj Close']] # 計算對數收益率 all_returns = np.log(all_adj_close / all_adj_close.shift(1)) # 提取蘋果公司資料 aapl_returns = all_returns.iloc[all_returns.index.get_level_values('Ticker') == 'AAPL'] aapl_returns.index = aapl_returns.index.droplevel('Ticker') # 提取微軟公司資料 msft_returns = all_returns.iloc[all_returns.index.get_level_values('Ticker') == 'MSFT'] msft_returns.index = msft_returns.index.droplevel('Ticker') # 使用 aapl_returns 和 msft_returns 建立新的資料框 return_data = pd.concat([aapl_returns, msft_returns], axis=1)[1:] return_data.columns = ['AAPL', 'MSFT'] # 增加常數項 X = sm.add_constant(return_data['AAPL']) # 建立模型 model = sm.OLS(return_data['MSFT'],X).fit() # 輸出模型的摘要資訊 print(model.summary())
OLS Regression Results ============================================================================== Dep. Variable:MSFTR-squared:0.281 Model:OLSAdj. R-squared:0.280 Method:Least SquaresF-statistic:515.5 Date:Mon, 19 Nov 2018Prob (F-statistic):1.32e-96 Time:09:30:16Log-Likelihood:3514.0 No. Observations:1322AIC:-7024. Df Residuals:1320BIC:-7014. Df Model:1 Covariance Type:nonrobust ============================================================================== coefstd errtP>|t|[0.0250.975] ------------------------------------------------------------------------------ const-0.00050.000-1.1190.263-0.0010.000 AAPL0.44070.01922.7040.0000.4030.479 ============================================================================== Omnibus:268.593Durbin-Watson:2.074 Prob(Omnibus):0.000Jarque-Bera (JB):7029.415 Skew:-0.211Prob(JB):0.00 Kurtosis:14.289Cond. No.41.6 ============================================================================== Warnings: [1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
注意在合併 AAPL 和 MSFT 收益率資料時,使用了 [1:]
切片,這樣就沒有缺失值來擾亂你的模型了。
當你研究模型摘要結果時,請注意以下內容:
-
Dep. Variable
指出哪個變數是模型的響應。 -
Model
是擬閤中使用的模型,在本案例中是OLS
。 -
另外,
Method
指出模型引數是如何被計算的。在本案例中被設定為Least Squares
。
目前為止,還沒有出現任何新的資訊,這些都已經在上述程式碼中設定了。然而,也有另一些令人感興趣的項,比如:
-
觀測量的數目(
No. Observations
)。注意你也可以使用Pandas包中的info()
函式得到它,只要在 IPython 控制檯中執行return_data.info()
。 -
殘差的自由度(
DF Residuals
) -
DF Model
指模型引數的數目,注意它並不包括程式碼中定義的X
的常數項。
這基本上是左邊欄的內容。右邊欄給出了更多關於擬合的資訊。例如你可以看到:
-
R-squared
是決定係數。這個分數表明迴歸線接近真實資料點的程度。在本例中,結果是 0.281,用百分比表示該分數是 28.1% 。當決定係數是 0% 時,表明模型完全不能解釋響應資料在其均值附近的變異性。當然,當分數為 100% 時情況恰恰相反。 -
Adj. R-squared
分數乍看上去和R-squared
的數值差不多。然而,該度量背後的計算是基於觀測量的數目和殘差的自由度調整了R-squared
的值。在本例中這一調整沒有起到多少作用,致使兩者的結果相近。 -
F-statistic
衡量該擬合的顯著性。通過將模型的均方誤差除以殘差的均方誤差來計算。 -
Prob (F-statistic)
指得到上述F-statistic
結果的概率,假設零假設認為它們是無關的。 -
Log-Likelihood
指的是似然函式的對數。 -
AIC
是赤池資訊量準則,這一指標根據觀測量的數目和模型的複雜性,對對數似然度進行了調整。 -
最後,
BIC
或者是貝葉斯資訊準則,類似於上述AIC
,但是它用更多的引數更嚴格地懲罰模型。
在模型摘要的第一部分下方,彙報了模型的每一項係數:
-
coef
表示係數的估計值。 -
std err
是係數估計的標準誤差。 -
t
代表t統計量。該度量用於測量係數的統計顯著性。 -
P>|t|
表示係數等於0為真的零假設。如果該值小於置信水平(通常是0.05),則表明該係數對應的項和響應之間存在統計學上的顯著關係。
最後,在模型摘要的最後部分,你將看到用於評估殘差分佈的其他統計檢驗:
-
Omnibus
是 Omnibus D’Angostino 檢驗,它為偏斜和峰度的存在提供了組合的統計檢驗。 -
Prob(Omnibus)
將Omnibus
度量轉變成了概率。 -
其次,
Skew
或偏斜,測量資料關於均值的對稱性。 -
Kurtosis
給出了分佈形狀的指示,因為它比較了接近均值的資料量和遠離均值(在尾部)的資料量。 -
Durbin-Watson
是對自相關的存在的檢驗,Jarque-Bera (JB)
是另一項對偏斜和峰度(kurtosis)的檢驗。也可以將其結果轉換成概率,即為Prob (JB)
。 -
最後,
Cond. No
是對多重共線性的檢驗。
使用 Matplotlib 繪製普通最小二乘迴歸擬合的直線。
# 繪製 AAPL 和 MSFT 收益率的散點圖 plt.plot(return_data['AAPL'], return_data['MSFT'], 'r.') # 增加座標軸 ax = plt.axis() # 初始化 `x` x = np.linspace(ax[0], ax[1] + 0.01) # 繪製迴歸線 plt.plot(x, model.params[0] + model.params[1] * x, 'b', lw=2) # 定製此圖 plt.grid(True) plt.axis('tight') plt.xlabel('Apple Returns') plt.ylabel('Microsoft returns') # 輸出此圖 plt.show()

注意也可以使用收益率的移動相關性對結果進行核查。只需對滾動相關性的結果呼叫 plot()
函式:
# 繪製滾動相關性 return_data['MSFT'].rolling(window=252).corr(return_data['AAPL']).plot() # 顯示該圖 plt.show()
