隱馬爾科夫鏈模型對於滬深300指數建模的進一步研究

  • 1 研究概述

    在Force Avatar"HMM在股票上的簡單應用"一文中,利用隱馬爾科夫鏈模型(Hidden Markov Model, HMM)對滬深300指數預測建模,取得了較爲理想的效果。本文基於該文章中的源代碼,對模型所使用的可觀測序列的正態性進行分析和修正,並對隱藏狀態數目的選擇作了簡單的探討。分析結果表明,對於可觀測序列正態性的修正,及合理的隱藏狀態數目選擇,能夠顯著提升模型的預測能力。

    2 問題分析

    2.1 可觀測序列的正態性分析和處理

    在Force Avatar的文章中,選取了三個可觀測序列(每日最高最低價格的對數差值, 每5日的指數對數收益差和每5日的指數成交量的對數差)進行可觀測序列進行建模,並假設這三個指標服從正態分佈。

    在基於正態分佈可觀測序列的HMM模型中,對於參數的估計(概率轉移矩陣,概率發射矩陣)和隱藏狀態的標註是通過極大似然估計實現的,其最大似然函數表達式中包含了多元正態分佈的概率密度函數。如果所選擇的可觀測序列偏離正態分佈,將導致參數估計有偏,和對於隱藏狀態的標註出現較高的錯誤率。

    通過直方圖觀察所選的三個可觀測序列的分佈,發現第一個可觀測序列(每日最高最低價格的對數差值)較爲顯著地偏離正態分佈。因此對其進行Box-Cox Transformation數據轉換的方法,使其更接近正態分佈。

    此外,在經驗協方差矩陣計算過程中,具有較大方差的可觀測序列將具有較大的權重。如果可觀測序列的單位不同,其方差的差距可能達到數個數量級。這裏爲了保證所選的可觀測序列在建模中具有接近的權重,對其分佈進行標準化(均值調整爲0,標準差調整爲1個單位)。

    2.2 隱藏狀態數量的選擇

    在廣發證券的一篇HMM模型研究報告中,隱藏狀態的數目設定爲3個。
    探尋西蒙斯投資之道:基於 HMM 模型的周擇時策略研究

    文中並未提及選擇三個隱藏狀態的原因。在Force Avatar所提到的HMM模型所關心的三個問題中,隱藏狀態均假設爲已知。由於目前尚未查到關於隱藏狀態數目選擇的相關研究文獻,目前對於隱藏狀態對於建模的影響爲個人推測:

    以Force Avatar一文中用擲骰子作爲例子解釋HMM模型,其中骰子的數目爲隱藏狀態數目,拋擲結果爲可觀測序列。如果我們用3顆不同的骰子拋擲獲得一個觀測序列,但在建模中我們假設隱藏狀態數目爲5個,因爲HMM無法識別隱藏狀態的合理數目,因此對於任意給定的隱藏狀態數目和初始分佈,模型總能通過反覆迭代對參數進行估計,和對隱藏狀態進行標註。因此我們最終得到的5個隱藏狀態中,有3顆將會對應真骰子,而另外2顆則是這3顆真骰子線性組合得到的假骰子。

    由於這2顆假骰子是真骰子的線性組合,因此它們可能和真骰子以大致相等的概率導致同一觀測序列,從而導致隱藏狀態的標準錯誤,和概率轉移矩陣和概率發射矩陣參數估計的不穩定。

    對於證券市場來說,隱藏狀態有無窮多個。在建模時,我們希望每個隱藏狀態有相互獨立的特徵,保證我們能對市場狀態有清楚的判斷,作出投資決策。合理隱藏狀態的選取是一個較爲複雜的問題,HMM模型對於隱藏狀態標註的準確度取決於可觀測序列的數目,數據的數量和質量,迭代次數,數據分佈的穩定性等多種因素。

    大體上,我們可以認爲市場有三種顯著不同的特徵:快速上升,快速下跌和震盪。直覺上,震盪這種狀態最爲複雜,可能有不同的亞隱藏狀態,例如震盪上升,震盪下跌等等複雜的的市場狀態。而在Force Avatar得到的結果中,震盪行情中出現多種狀態的標註,導致我們無法準確判斷隱藏狀態對應的含義。因此,在目前的建模中,基於以下理由把隱藏狀態設定爲3個:

    1. 希望模型能夠正確標註快速上升,快速下跌和震盪三種市場狀態,並不期望目前的簡單模型能夠對震盪行情中的複雜狀態準確標註。

    2. 震盪行情所包含的隱藏狀態的標註較爲複雜,標註的準確率較低,考慮到交易中實際產生的交易費用,我們希望模型標準的隱藏狀態的含義是清晰和準確的,而不希望基於不清晰的隱藏狀態含義進行交易。

    3 結果討論

    基於以上討論,在以下的源代碼中,我們分別對未修正的可觀測序列和修正後的可觀測序列進行建模。我們先對3隱藏狀態HMM模型和6隱藏狀態HMM模型進行對比。根據3個隱藏狀態得到的模型進行交易,在2013年初左右收益率即達到100%,2014年初達到150%,根據6個隱藏狀態得到的模型進行交易,在2015年初左右收益率達到100%,2015年中達到150%;此外,在2015年下半年的股災中,根據3隱藏狀態模型進行交易,收益率仍然維持在200%左右,而根據3隱藏狀態模型進行交易,收益率仍然跌至170%左右

    下載 (1).png

    下載 (2).png

    然後,我們對基於未修正可觀測序列的3隱藏狀態HMM模型和基於修正可觀測序列的3隱藏狀態HMM模型進行對比。這兩個HMM模型中均能對快速上升,快速下跌和震盪行情三種市場狀態進行較爲準確的標準。基於修正可觀測序列的HMM模型在2015年上半年的牛市中收益率達到300%,遠高於基於未修正可觀測序列的HMM模型的收益率;且在2015年下半年的股災中,基於修正可觀測序列的模型表現出色,在稍微回調以後重新上升,在2015年年底和其峯值大致持平。

    下載 (2).png

    下載.png

    3 研究展望

    在本文中,我們對於HMM模型的隱藏狀態數目選擇進行了討論,並對所選的可觀測序列正態性進行了分析和處理,調整以後的HMM對於滬深300的測試結果顯著改善。

    在下一步的研究中,可以對模型的以下方面作進一步改善:

    1. 對建模時選擇的2000步迭代合理性進行檢驗。理想的迭代數目應該使參數估計和隱藏狀態標註的準確性收斂。

    2. 選擇更多的儘可能正交的,符合正態分佈可觀測序列序列向量作爲指標進行建模。理論上,相互正交的可觀測序列向量比存在線性相關的可觀測序列向量包含更多的市場信息,且和HMM模型中的觀測獨立性假設一致,同時能夠降低經驗協方差矩陣非對角元素估計的誤差。

    3. 對符合正態分佈的可觀測序列進行方差分析,判斷其可合理使用的時間範圍,並通過檢驗結果進一步瞭解市場結構的變化。

    4. 通過交叉驗證(Cross-Validation)進一步確定合理的隱藏狀態數目。

    最後,在此對於Force Avatar 分享自己的研究策略提出致謝。

  • HMM_Model_Modified_3_Hidden_States.ipynb
from hmmlearn.hmm import GaussianHMM
import numpy as np
from matplotlib import cm, pyplot as plt
import matplotlib.dates as dates
import pandas as pd
import datetime


from scipy import stats # To perfrom box-cox transformation
from sklearn import preprocessing # To center and standardize the data.


# Source Code from previous HMM modeling


# Note that numbers of hidden states are modified to be 3, instead of 6.


beginDate = '2005-01-01'
endDate = '2015-12-31'
n = 3 # Hidden states are set to be 3 instead of 6
data = get_price('CSI300.INDX',start_date=beginDate, end_date=endDate,frequency='1d')
data[0:9]


volume = data['TotalVolumeTraded']
close = data['ClosingPx']


logDel = np.log(np.array(data['HighPx'])) - np.log(np.array(data['LowPx']))
logDel


logRet_1 = np.array(np.diff(np.log(close)))#這個作爲後面計算收益使用
logRet_5 = np.log(np.array(close[5:])) - np.log(np.array(close[:-5]))
logRet_5


logVol_5 = np.log(np.array(volume[5:])) - np.log(np.array(volume[:-5]))
logVol_5


logDel = logDel[5:]
logRet_1 = logRet_1[4:]
close = close[5:]
Date = pd.to_datetime(data.index[5:])

  • 通過直方圖來觀察所選指標(可觀測序列)分佈的正態性。其中第一個指標(每日最高最低價格的對數差值)明顯偏離正態分佈

# the histogram of the raw observation sequences


n, bins, patches = plt.hist(logDel, 50, normed=1, facecolor='green', alpha=0.75)


plt.show()


n, bins, patches = plt.hist(logRet_5, 50, normed=1, facecolor='green', alpha=0.75)


plt.show()


n, bins, patches = plt.hist(logVol_5, 50, normed=1, facecolor='green', alpha=0.75)


plt.show()


通過Box-Cox Transformation來對第一個指標進行調整,使其更接近正態分佈。


同時對三個指標的分佈進行標準化(調整其均值爲0,且標準差調整爲1),保證指標在參數估計中具有大致相等的權重

# Box-Cox Transformation of the observation sequences


boxcox_logDel, _ = stats.boxcox(logDel)


# Standardize the observation sequence distribution


rescaled_boxcox_logDel = preprocessing.scale(boxcox_logDel, axis=0, with_mean=True, with_std=True, copy=False)


rescaled_logRet_5 = preprocessing.scale(logRet_5, axis=0, with_mean=True, with_std=True, copy=False)


rescaled_logVol_5 = preprocessing.scale(logVol_5, axis=0, with_mean=True, with_std=True, copy=False)


# the histogram of the rescaled observation sequences


n, bins, patches = plt.hist(rescaled_boxcox_logDel, 50, normed=1, facecolor='green', alpha=0.75)


plt.show()


n, bins, patches = plt.hist(rescaled_logRet_5, 50, normed=1, facecolor='green', alpha=0.75)


plt.show()


n, bins, patches = plt.hist(rescaled_logVol_5, 50, normed=1, facecolor='green', alpha=0.75)


plt.show()


  • . . .
    In [55]:
    # Observation sequnces matrix 
    # Rescaled observation sequences matrix 
    rescaled_A =np.column_stack([rescaled_boxcox_logDel, rescaled_logRet_5, rescaled_logVol_5]) 

    對於未修正的指標進行隱馬爾科夫鏈建模。

    對於未修正的指標進行隱馬爾科夫鏈建模。

    In [64]:

    x
     
    # HMM modeling based on raw observation sequences
    model = GaussianHMM(n_components= 3, covariance_type="full", n_iter=2000).fit([A])
    hidden_states = model.predict(A)
    hidden_states
    plt.figure(figsize=(25, 18)) 
    for i in range(model.n_components):
        pos = (hidden_states==i)
        plt.plot_date(Date[pos],close[pos],'o',label='hidden state %d'%i,lw=2)
        plt.legend(loc="left")
    . . .
    In [67]:
     
    for i in range(3):
        pos = (hidden_states==i)
        pos = np.append(0,pos[:-1])#第二天進行買入操作
        df = res.logRet_1
        res['state_ret%s'%i] = df.multiply(pos)
        plt.plot_date(Date,np.exp(res['state_ret%s'%i].cumsum()),'-',label='hidden state %d'%i)
        plt.legend(loc="left")
    In [83]:
     
    long = (hidden_states==0)  #做多
    short = (hidden_states == 1)  #做空
    long = np.append(0,long[:-1]) #第二天才能操作
    short = np.append(0,short[:-1]) #第二天才能操作
    res['ret'] =  df.multiply(long) - df.multiply(short)  
    plt.plot_date(Date,np.exp(res['ret'].cumsum()),'r-')
    Out[83]:
    [<matplotlib.lines.Line2D at 0x7fc541b9d1d0>]
    . . .
    In [ ]:
    對修正後的指標進行隱馬爾科夫鏈建模。
    In [71]:
     
    # HMM modeling based on processed observation sequences
    rescaled_model = GaussianHMM(n_components= 3, covariance_type="full", n_iter=2000).fit([rescaled_A])
    rescaled_hidden_states = rescaled_model.predict(rescaled_A)
    rescaled_hidden_states
    plt.figure(figsize=(25, 18)) 
    for i in range(model.n_components):
        pos = (rescaled_hidden_states==i)
        plt.plot_date(Date[pos],close[pos],'o',label='hidden state %d'%i,lw=2)
        plt.legend(loc="left")
    . . .
    In [73]:
     
    for i in range(3):
        pos = (rescaled_hidden_states==i)
        pos = np.append(0,pos[:-1])#第二天進行買入操作
        df = res.logRet_1
        res['state_ret%s'%i] = df.multiply(pos)
        plt.plot_date(Date,np.exp(res['state_ret%s'%i].cumsum()),'-',label='hidden state %d'%i)
        plt.legend(loc="left")
    In [74]:
     
    long = (rescaled_hidden_states==0)  #做多
    short = (rescaled_hidden_states==1) + (rescaled_hidden_states == 2)  #做空
    long = np.append(0,long[:-1]) #第二天才能操作
    short = np.append(0,short[:-1]) #第二天才能操作
    res['ret'] =  df.multiply(long) - df.multiply(short)  
    plt.plot_date(Date,np.exp(res['ret'].cumsum()),'r-')
    Out[74]:
    [<matplotlib.lines.Line2D at 0x7fc541b4fe48>]
  • A = np.column_stack([logDel,logRet_5,logVol_5]) 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章