利用機器學習預測漲跌-優化方式

第二堂課程最近也終於走向穩定了,剛開始有一些的bug、RAM的優化上的問題,還請同學們多多擔待,我們也會持許推出新的介紹,讓大家有更多可以加強機器學習的方式,這一篇文章中,我們要針對以往的labeling方式進行優化,讓訓練出來的模型,可以有更準確的預測。

沒有參加課程的同學,也可以跟我們一起學習,下方的程式碼都是完全公開的!請大家自行拿取玩玩看喔!

thumbnail 11

機器學習 「features 和 labels」

假如您不知道Labeling是什麼,那我們這邊還是先簡單的介紹一下supervise機器學習(監督式機器學習):

這一類的機器學習模型,可以想像是一個函式 function f,我們想要計算 y = f(x),其中 x 就是 features,另外 y 就是 labels,假如我們想要預測天氣,則 x 就是 溫度 濕度 等等,而 y 就是 0 或 1 代表是否會下雨,假如我們想要預測股價,則 x 就是 技術指標、財報等等,而 y 就是股價。
機器學習就是產生這麼一個 function f,可以藉由 x 來推估 y,也就是 y = f(x),下方就是機器學習用來訓練的資料(x, y)的示意圖,其中 x 就是一些技術指標,而y就是買賣訊號:

fl

機器學習模型(f)大部分是一個黑盒子,也就是我們不需要知道 f 的公式是什麼
(也不太可能顯示出來,因為太複雜了)

而我們就會先給一些 x 跟 y,並且讓演算法去產生 f,
這個就是 supervise 機器學習模型的基本概念

假如我們要用機器學習來預測股票,就要先有 x 跟 y,也就是 features 跟 labels
假如我們想要預測股價,要怎麼樣來製作 feature 跟 labels 呢?

製作 Features

製作 feature 的資料,可以是一些財報、技術指標、我們課程中也有很多製作這些指標的方式,相信網路上也有很多的介紹,由於不是此篇的重點,所以我就不多說明了XD

製作 Labels

製作 label 其實是非常至關重要的,假如 label 太難預測的話,模型會無法有效的訓練,以往最基本的 label 製作方式是使用 fixed time horizon 來預測 w 個時間單位後的漲跌,下圖就是 fixed time horizon 的 Labeling 方式:

fixedtimehorizon

上圖中在 p(t) 的時候,我們希望預測 p(t+w) 的股價,是比較高還是比較低
我們可以使用分類的方式,將股價的漲跌分成三個部分,也就是─1(跌)、0(不漲不跌)、1(漲),這樣子我們就可以讓機器學習來預測,上面圖中,我們可以發現股價比之前的高,所以把它歸類在 1 ,也就是之後會漲。

缺點

然而這種方式有一個缺點,就是當今天模型叫我們買入的時候,一買就是持有 w 個時間單位,不論股價大漲或大跌都必須繼續持有,不能停損停利,這樣就會導致風險無法控制。

當然我們可以在事後回測時加上停損停利,但是這樣就跟模型的初衷有違背,模型產生的label明明就是持有 w 個時間單位,沒有停損停利。

為了解決上述問題,[Prado 2018] Advances in Financial Machine Learning 提出了以下的新方法:

Triple Barrier

這個方法的示意圖如下:

triplebarrier 1

乍看之下跟 fixed time horizon 有點類似,不過這個方法將分類的方式做了一些改進,上圖中,用了三種不同顏色的「柵欄」,當股價從 p(t) 開始出發,隨時間不斷延伸,一定會碰到三個柵欄中的其中一個,而這三個柵欄分別代表了不同的意義:

  • 1(停利)
  • 0(持有 w 個時間單位)
  • ─1(停損)

如此一來,我們就可以讓機器學習來預測「包含停損停利獲利狀況」,訓練出來的模型就可以跟回測的設定相符,增加機器學習的可預測性!
雖然概念很好理解,但要寫成程式碼還是需要一點功力,尤其是執行速度快一點的程式碼,下面就分享如何產生此類的label。

程式碼

首先我們可以藉由股價爬蟲這篇裡面介紹的程式碼,來獲得股價,

df = crawl_price("2354.TW")
df.Close.plot()
price

針對以上的程式碼,
假如想知道 crawl_price 的實做,可以點入股價爬蟲這篇

接下來就可以來製作 labels 了!
在我們的機器學習影音課程中,使用的是 fixed time horizon 的方式,先來看看效果如何:

date = '2017'
df.Close[date].plot()
(df.Close.shift(-20) / df.Close > 1).astype(int)[date].plot(secondary_y=True)
signal1

上圖中藍色的為股價,對應到左邊的y軸,黃色的是訊號,對應到右邊的y軸,也就是機器學習要學的 label,其中 1 代表買入,0 代表賣出,看起來效果有些不OK,因為訊號會在短時間內上下跳動,讓我們不知道到底該買還是賣,所以接下來我們就來試試看剛剛所介紹的 triple barrier 產生出來的 label。

下面這段是非常珍貴的程式碼,就連 Advances in Financial Machine Learning 書中附帶的程式碼,都沒有以下的程式碼更簡潔、更完整、更有效率,假如你的 pandas 技巧不夠好的話,請直接拿去使用就可以了XD,不用太深究此 function 中的程式碼!

import numpy as np
import math
def triple_barrier(price, ub, lb, max_period):

    def end_price(s):
        return np.append(s[(s / s[0] > ub) | (s / s[0] < lb)], s[-1])[0]/s[0]
    
    r = np.array(range(max_period))
    
    def end_time(s):
        return np.append(r[(s / s[0] > ub) | (s / s[0] < lb)], max_period-1)[0]

    p = price.rolling(max_period).apply(end_price, raw=True).shift(-max_period+1)
    t = price.rolling(max_period).apply(end_time, raw=True).shift(-max_period+1)
    t = pd.Series([t.index[int(k+i)] if not math.isnan(k+i) else np.datetime64('NaT') 
                   for i, k in enumerate(t)], index=t.index).dropna()

    signal = pd.Series(0, p.index)
    signal.loc[p > ub] = 1
    signal.loc[p < lb] = -1
    ret = pd.DataFrame({'triple_barrier_profit':p, 'triple_barrier_sell_time':t, 'triple_barrier_signal':signal})

    return ret

ret = triple_barrier(df.Close, 1.07, 0.97, 20)

這個 function 的使用方法,就是將

  • 時間序列(程式碼中的df.Close
  • 停利(程式碼中1.07是指7%停利)
  • 停損(程式碼中0.97是指3%停損)
  • 最大持有時間(20天)

分別丟入 triple_barrier 函式當中,就可以計算出以下的 Dataframe

ret.head()
result 3

上圖中有三條序列,其中 index 是日期,另外三條時間序列分別是:

  • triple_barrier_profit 當天買入,直到停損停利後,未來的獲利狀況
  • triple_barrier_sell_time 當天買入,未來會持有的時間
  • triple_barrier_signal 當天買入,未來觸發的是停損停利

我們可以將 triple_barrier_signal 訊號跟股價一起畫出來比較一下:

date = '2017'
df.Close[date].plot()
ret.triple_barrier_signal[date].plot(secondary_y=True)
signal2

label 的訊號變得比較乾淨,試試看訓練出更好的模型吧!
可以使用 google colab 線上編輯此代碼

FinLab - 韓承佑

嗨大家好,我是韓承佑,FinLab創辦人,畢業於巴黎薩克雷大學資工博士,目前擔任臺灣量化交易協會 學術顧問、台北商業大學 創新育成中心 創業技術顧問與上市科技公司 量化交易顧問。當初,我喜歡寫程式、無意間因為軟體比賽接觸Fintech,從此開始了財經跟程式的學習之路。我們成立 FinLab 量化投資部落格,用自己研發的軟體,對台灣股市做大量快速的實驗。希望可以在量化投資的路上,當大家的「武器製造商」!