此文章為VIP限定
雖然 FinLab 支援 Qlib 很久了,但一直沒有好好的跟大家介紹如何使用,於是乎,最近中小股票真的表現令人窒息,所以就好好做研究,相信總會時來運轉的。台股權值股與中小型股差異,來到20年來最大,台積電創新高之際,已經有很多股票跌破年線了,老實說,虧很多嗎?是還好,但就是很悶,心情不受影響是不可能的,然而人生在世,就是來體驗這些喜怒哀樂,就讓我們繼續努力,看結果如何,雖然很煩,但是我還是對於未來非常樂觀!
Qlib
Qlib 是由微軟開源的量化投資工具庫,專注於基於機器學習的投資研究。它提供了資料處理、策略建模、回測及評估的一站式解決方案。
Qlib 在 FinLab 中的應用
Qlib 的演算法有非常多前人研究的心血,不是我們能夠短時間比擬的,所以要站在巨人的肩膀上,才能事半功倍!Qlib 的最大特點,就是在於
1. 特徵的建構
2. 模型的多樣選擇
基於以上兩點,你都可以透過 FinLab package 將它們給接入進來使用。然而我覺得 Qlib 架構比較難與其它的生態系或 Package 整合,因為整個框架以程式系統來說,是比較封閉,沒辦法與傳統的 sklearn,或是 lgb、xgb 整合,所以才需要使用 FinLab 將其解放。
Qlib vs FinLab
Qlib 是通用型的 package,所以特徵都是使用價格來製作,而 FinLab 本身支援台股多樣的資料與指標,所以延伸的部分非常多!歡迎將以下的功能當成基礎版本,自己加入更多特徵來增進模型的運測能力!
安裝
接下來,將會使用 Colab 來進行設定,但以下範例需要 Colab Pro 版本,才能順利運行,因為 RAM 的需求太大了。假如各位沒有買 Colab 付費版,也可以在自己的主機上試試看,初次安裝,需要有以下的套件:
!pip install finlab > log.txt
!pip install ta-lib-bin > log.txt
!pip install catboost > log.txt
上面的程式中,安裝 talib 的方法,是使用 ta-lib-bin,是 colab 上特別的安裝方法,假如你是在桌機上使用,要用
conda install conda-forge::ta-lib
來取代。
安裝 Qlib
安裝 Qlib 的方式,有很多種,一種是直接將 github 上的最新版本下載下來安裝:
!git clone https://github.com/microsoft/qlib.git
%cd qlib
!make install
!pip install .
%cd ..
要注意,以上的程式碼是在 terminal 中使用,不是 python 語法喔!
製作特徵
我們可以使用 qlib 內建的功能,產生出158種,或是360種不同的價格訊號,只需要三行即可完成:
from finlab.ml import qlib as q
q.init()
q.dump()
f158 = q.alpha("Alpha158")
然而這樣的資料量,實在是太大了,所以我們可以將資料的頻率改成「週」,大約可以減少80%的資料量,這樣的訓練方式,勉強能夠在 Colab Pro 上執行(50GB RAM)。假如是 alpha360 資料集,基本上會需要上百GB,就請有這樣硬體能力的玩家自行上手了!
from finlab.ml import label as mll
from finlab.ml import feature as mlf
features = mlf.combine({
'qlib': f158,
}, resample='W')
labels = mll.return_percentage(features.index, period=2)
is_train = features.index.get_level_values('datetime') < '2020-01-01'
接下來,我們發現,f158之中,有一個特徵都是 NaN(也就是空值),我們必須要將它刪除:
features = features.drop('VWAP0', axis=1)
目前原因不明,應該還有一些 BUG 需要釐清,也可以利用 `mlf.combine` 自行計算補上 VWAP 的資料。
訓練模型
訓練模型的環節,FinLab 提供一個超級好用的功能
model_templates = q.get_models()
就可以將 qlib 所有的模型導入,並且直接用來訓練:
import gc
import numpy as np
import pickle
from finlab.ml import qlib as q
model_templates = q.get_models()
is_train = features.index.get_level_values('datetime') < '2020-01-01'
model_path = './models.pkl'
if os.path.isfile(model_path):
with open(model_path, 'rb') as f:
models = pickle.load(f)
else:
models = {}
for name, Model in model_templates.items():
print(name)
if name in models:
continue
if name == 'DNNModel':
continue
try:
X_train = features.loc[is_train]
y_train = labels.loc[is_train]
model = Model()
model.fit(X_train, y_train)
except:
notna = X_train.replace([np.inf, -np.inf], np.nan).notna().all(axis=1) & y_train.replace([np.inf, -np.inf], np.nan).notna()
X_train = X_train.loc[notna]
y_train = y_train.loc[notna]
model = Model()
model.fit(X_train, y_train)
with open(f'models.pkl', 'wb') as f:
pickle.dump(models, f)
models[name] = model
gc.collect()
訓練的過程中,可能訓練到一半 RAM 會掛掉,所以每次訓練好新的模型,程式會將所有模型都儲存起來,方便下一次直接調用。訓練時,會將 training data 自動切出 validation data,來確保訓練時避免 overfitting。
測試
最後我們可以回測看看模型得效果如何:
import pickle
import numpy as np
model_path = './models.pkl'
if os.path.isfile(model_path):
with open(model_path, 'rb') as f:
models = pickle.load(f)
ys = {}
for name, model in models.items():
try:
y = model.predict(features[~is_train])
ys[name] = y
except:
y = model.predict(features[~is_train].replace([np.inf, -np.inf], np.nan).fillna(0))
ys[name] = y
from finlab.backtest import sim
import matplotlib.pyplot as plt
from finlab import data
reports = {}
for name, y in ys.items():
print(name)
with data.universe('TSE_OTC'):
close = data.get('price:收盤價')
report = sim(y[close.notna()].is_largest(20), resample='M', upload=False)
reports[name] = report
report.creturn.plot(label=name)
plt.legend()
plt.show()
其實效果算是滿不錯的,實單上,經過更多的調教,我的經驗,會比當前的成果更好,有興趣的玩家可以多多嘗試!其中一些策略回測甚至可以有 40%到50%的平均年報酬率。
話說最近 AI 模型,這幾個月的表現可能都不太好,然而換一種想法,就是當前可能不太適合使用 AI 模型吧~不代表它以後都沒效果,而是時運不佳的關係,反而讓我的心裡有點釋懷。
由上表可知,我們可以選擇 LightGBM 模型來選股,效果最好。
以下示範一個年平均報酬 +50%的策略,這絕對是價值數萬元的程式碼,就這麼隨便的放在這邊讓大家取用,請你跟我說,在世界上的哪一個角落,可以找到這麼神乎其技的AI模型選股績效?找不到吧!只好請大家繼續支持 FinLab,讓我們可以活下去,謝謝:
y = ys['LGBModel']
with data.universe('TSE_OTC'):
close = data.get('price:收盤價')
vol = data.get('price:成交股數')
pos = y[((vol.average(20) > 200_000)) & (close >= close.rolling(10).max())].is_largest(10)
report = sim(pos, resample='M', upload=False)
report.display()