FinLab 致力於推廣我們在量化投資上的知識與 Python 實作經驗,並且分享如何運用 Python 創造屬於自己的投資生態系,靠自己的能力為自己創造投資收入,不用在外花錢聽信無根據的明牌,讓大家用很低的成本複製我們在財經一路上的所學與經驗。除了過去在 Hahow 開設課程,現在為了打造更豐富的 Python 量化投資生態系,建置了 SaaS 與延伸教學內容平台,不斷研發,提供給大家更多武器應對複雜的金融市場。
進入首頁後,點選右上角登入按鈕,即可使用 Google 帳號登錄 FinLab 會員。若無 Google 帳號,請先 註冊 Google 帳號 方可接續進行。
提供3種選項,每個人都可以選擇適合自己的環境:
Python:
使用 pip install finlab
並且在一般 Python 環境下執行回測。
注意:若在finlab hahow課程資料夾內操作,可能與本地finlab模組有命名衝突問題,請重開新的python開發環境或在colab操作。
FinLab IDE:
免安裝,直接在網頁上執行 Python 程式語法,並搭配 FinLab 函式庫使用。
注意:免費權限有每日運行時間限制600秒,建議先在一般 Python 環境下執行回測(無限制時間),確認策略運作正常後,再將程式碼貼到FinLab IDE執行部屬。
FinLab GUI:
免安裝,直接在網頁上執行,用圖形化介面編輯策略。
一般 Python 環境安裝:
pip install finlab
註:假如是在 Colab或Jupyter 環境下,可以使用 !pip install finlab
來安裝。
左側導覽列點選選股策略Icon。
點選“建立新策略”。
輸入策略名稱,點選“確定”。
點選“程式碼編輯器”,進入IDE開發畫面。
一行程式快速抓取豐富的股市資料庫!
利用 finlab.data 來得到「所有」歷史資料。
至資料庫目錄複製想獲取的資料的使用方法。
舉例:
from finlab import data
close = data.get('price:收盤價')
close
至資料庫目錄複製想獲取的資料的使用方法貼到IDE編寫。
舉例:
from finlab import data
close = data.get('price:收盤價')
close
至IDE右側財務指標或技術指標點選,即可生成data.get(所選指標)的程式碼於IDE。
舉例:
至IDE右側財務指標或技術指標點選,即可生成所選的指標拼圖於GUI。
舉例:
簡單開發策略,執行強大的回測系統!
利用python pandas及finlab語法撰寫條件程式碼。
Colab範例檔連結。
程式範例如下:
from finlab import data
from finlab.backtest import sim
# 下載資料
pe = data.get('price_earning_ratio:本益比')
close = data.get('price:收盤價')
# 收盤價位於20日均線支撐上
cond1=close > close.rolling(20).mean()
# 使用indicator計算rsi指標
rsi=data.indicator('RSI', timeperiod=5)
# rsi高檔鈍化
cond2=(rsi>80).rolling(3).sum()==3
# 排除股價過高與過低的股票
cond3=(close > 5)&(close < 500)
# 選出買進訊號中益本比前10大標地
ind=(1/pe)*(cond1&cond2&cond3)
position=ind.is_largest(10)
sim(position,resample='M',name="demo_strategy")
利用python pandas及finlab語法撰寫條件程式碼,範例如下:
from finlab import data
from finlab.backtest import sim
# 下載資料
pe = data.get('price_earning_ratio:本益比')
close = data.get('price:收盤價')
# 收盤價位於20日均線支撐上
cond1=close > close.rolling(20).mean()
# 使用indicator計算rsi指標
rsi=data.indicator('RSI', timeperiod=5)
# rsi高檔鈍化
cond2=(rsi>80).rolling(3).sum()==3
# 排除股價過高與過低的股票
cond3=(close > 5)&(close < 500)
# 選出買進訊號中益本比前10大標地
ind=(1/pe)*(cond1&cond2&cond3)
position=ind.is_largest(10)
sim(position,resample='M',name="demo_strategy")
GUI操作原則:
選股範例條件如下:
GUI操作範例如下:
以下為使用 finlab
package 更細部的介紹,針對每一個函式撰寫範例。
finlab.login(api_token:str)
api_token查詢頁面 獲取api_token,執行finlab.login(api_token)
登入來設置相關環境變數。
若不自行輸入,在使用Finlab模組功能時,欲執行會員功能時,系統會自動會跳出
GUI頁面 請求輸入api_token,驗證完方可使用對應權限的功能。
finlab.data.get(dataset)
利用 finlab.data 來得到「所有」歷史資料,可以至資料庫目錄複製資料的使用方法。一行程式快速抓取豐富的股市資料庫!
目前針對 FREE 跟 VIP 用戶,所得到的資料時間段不同:
資料格式為分為兩種:「時間序列資料」和「非時間序列資料」
資料獲取方法,就是使用 data.get
函式,傳入:主資料:子資料
的格式,例如price:收盤價
。
獲得的資料,其縱軸為日期,橫軸為股票代號,製作選股策略非常方便。
from finlab import data
data.get('price:收盤價')
資料獲取方法,就是使用 data.get
函式,傳入:主資料
的格式,例如 company_main_business
。可以發現拿到的資料,不是按照時間序列排列。
from finlab import data
data.get('company_main_business')
finlab.data.indicator(indname:str, adjust_price:bool=False, resample:str='D', **kwargs):
支援 Talib 上百種技術指標!只需要一行,幫你算出2000檔股票、10年的所有資訊!
在使用這個函式前,必需要先安裝 talib 套件,可以參考 官方安裝教學 來安裝,支援 Windows、MacOS、Linux。
indname
: talib指標名稱,例如 SMA
, STOCH
, RSI
等,可以參考 talib 教學。adjust_price
: 是否使用還原股價計算。resample
: 技術指標價格週期,ex: D
代表日線, W
代表週線, M
代表月線。**kwargs
: 所選talib指標的其他參數設定,以 RSI
為例,參考 talib 文件中 RSI 範例,調整項為計算週期 timeperiod
。建議使用者可以先參考以下範例,並且搭配 talib官方文件,就可以掌握製作技術指標的方法了。
計算所有股票的 RSI 數值
from finlab import data
data.indicator('RSI', timeperiod=14)
上圖中,可以看到資料包含 NaN
,代表資料筆數不足,無法計算出數值,每一檔股票除了剛開始的 14 天外,幾乎都會有數值。
計算所有股票的 KD 值,由於 KD 數值是由 K 和 D 所組成,每一檔股票每天會有兩個數值,所以 data.indicator
會自動回傳兩個 pd.DataFrame
,可以用 k, d = data.indicator('STOCH')
將 K 與 D 數值紀錄,之後用於比大小,例如 k > d
。
from finlab import data
k, d = data.indicator('STOCH')
k
延續上述的範例二,假如我們希望將最近一天 K > D 值的股票清單列出來,可以用以下語法
(k > d).iloc[-1]
0015 False
0050 True
0051 False
0052 True
0053 True
...
9951 True
9955 False
9958 False
9960 True
9962 True
Name: 2021-07-13 00:00:00, Length: 2269, dtype: bool
finlab.data.set_universe(market='ALL', category='ALL')
使用者可以使用此函式設定市場與類股的回測標的
設定完畢後,再使用 data.get
或是 data.indicator
時,就只會獲得所選範圍之標的,所得到的股票會被過濾。
market (str)
: 市場名稱。'ALL', 'TSE', 'OTC', 'TSE_OTC', 'ETF'
category (str or list)
: 類股名稱,可以使用模糊比對,例如 '電子'
,會選到'電子工業'
, '電子通路業'
… 等。'光電業', '其他', '其他電子業',
'化學工業', '半導體', '塑膠工業', '存託憑證', '建材營造', '文化創意業', '橡膠工業', '水泥工業',
'汽車工業', '油電燃氣業', '玻璃陶瓷', '生技醫療', '生技醫療業', '紡織纖維', '航運業', '觀光事業', '貿易百貨',
'資訊服務業', '農業科技', '通信網路業', '造紙工業', '金融', '鋼鐵工業', '電器電纜', '電子商務',
'電子通路業', '電子零組件', '電機機械', '電腦及週邊', '食品工業'
限定只抓取上市櫃普通股(排除ETF、ETN、特別股、存託憑證)
from finlab import data
data.set_universe(market='TSE_OTC', category='水泥') # 水泥工業
data.get('price:收盤價')
finlab.backtest.sim(position, resample=None, ...)
一行執行強大的回測系統,產生 position
的每日訊號,帶入 finlab.backtest.sim
進行回測,快速得到風險報酬分析、選股清單!
position (pd.Dataframe)
:
買賣訊號紀錄。 True 為持有, False 為空手。
若選擇做空-position,只要將 sim(position) 改成負的 sim(-position.astype(float))
即可做空。
必填。
resample (str)
:
交易週期。將 position
的訊號以週期性的方式論動股票,預設為每天換股。其他常用數值為 W
、 M
、 Q
(每週、每月、每季換股一次),也可以使用 W-Fri
在週五的時候產生新的股票清單,並且於下週交易日下單。更多freq的選擇請見pandas文件。
trade_at_price (str)
:
選擇回測之還原股價以收盤價或開盤價計算。可選'close'或'open'。
fee_ratio (float)
:
交易手續費率,預設為台灣無打折手續費 0.001425
。
tax_ratio (float)
:
交易稅率,預設為台灣交易稅率 0.003
name (str)
:
策略名稱,預設為 未指名
。策略名稱。相同名稱之策略上傳會覆寫。
命名規則:全英文或開頭中文,不接受開頭英文接中文。
stop_loss (float)
:
停損基準。範例:0.1,代表成本價下跌 10% 時出場。
take_profit (float)
:
停利基準。範例:0.1,代表成本價上漲 10% 時出場。
position_limit(float)
:
單檔標的持股比例上限,控制倉位風險。範例:0.2,代表單檔標的最多持有20 % 部位。
day_trade(bool)
:
使用當沖模式。範例:預設為False,不使用;
True為使用當沖模式回測。
upload(bool)
:
上傳策略至finlab網站。範例:預設為True,上傳策略;
False,不上傳,可用
finlab.backtest.sim(position, upload=False, ...).display()
快速檢視策略績效。
finlab.report.Report()
你相信年報酬 +27% 的策略,程式碼三行就搞定嗎?讓 FinLab 打開你的眼界,接下來我們可以建構一個策略,策略每個月會檢查股票清單,持有價格小於 6 元的股票,剔除價格大於 6 元的股票,每個月底執行,周而復始。來回測看看這樣的績效到底如何:
from finlab import data
from finlab import backtest
# 只買入價格小於 6 元的股票
close = data.get('price:收盤價')
position = close < 6
# 回測,每月(M)調整一次
backtest.sim(position, resample='M', name="價格小於6的股票")
class finlab.report.Report()
Report.position
: 得到每日歷史部位明細 (pd.Dataframe)。
Report.creturn
: 得到策略報酬率變化 (pd.Series)。
Report.display()
:
顯示策略績效圖。
Report.get_trade_record(...)
: 取策略每筆交易紀錄與優勢比率資料。
time_scale(int)
: 設定進場第n日出場的交易數據,預設為原先出場日。用途為計算不同出場日的數據變化。edge_ratio_series(bool)
: 是否計算edge_ratio。edge_ratio_time_range(range())
: edge_ratio時段週期。default為range(2, 40, 3)。atr_freq(int)
: edge_ratio使用的ATR 時段週期參數。trade_record_df: pd.Dataframe, edge_ratio_dataset: pd.Series
from finlab import data
from finlab.backtest import sim
pb = data.get('price_earning_ratio:股價淨值比')
close = data.get('price:收盤價')
report = (1/(pb * close) * (close > close.average(60)) * (close > 5)).is_largest(20)
sim(buy, resample='Q')
report.position
report.creturn
report.benchmark = data.get('benchmark_return:發行量加權股價報酬指數').squeeze()
report.display()
report.get_trade_record()
class finlab.dataframe.FinlabDataFrame()
除了使用熟悉的 Pandas 語法外,我們也提供很多語法糖,讓大家開發程式時,可以用簡易的語法完成複雜的功能,讓開發策略更簡潔!我們將所有的語法糖包裹在 FinlabDataFrame
中,用啟來跟 pd.DataFrame
一樣,但是多了很多功能!只要使用 finlab.data.get()
所獲得的資料,皆為 FinlabDataFrame
格式,接下來我們就來看看, FinlabDataFrame
有哪些好用的語法糖吧!
當資料日期沒有對齊(例如: 財報 vs 收盤價 vs 月報)時,在使用以下運算符號:+
, -
, *
, /
, >
, >=
, ==
, <
, <=
, &
, |
, ~
,不需要先將資料對齊,因為 FinlabDataFrame
會自動幫你處理,以下是示意圖。
以下是範例:cond1
與 cond2
分別為「每天」,和「每季」的資料,假如要取交集的時間,可以用以下語法:
from finlab import data
# 取得 FinlabDataFrame
close = data.get('price:收盤價')
roa = data.get('fundamental_features:ROA稅後息前')
# 運算兩個選股條件交集
cond1 = close > 37
cond2 = roa > 0
cond_1_2 = cond1 & cond2
FinlabDataFrame.average(n)
取 n 筆移動平均,若股票在時間窗格內,有 N/2 筆 NaN,則會產生 NaN。
from finlab import data
close = data.get('price:收盤價')
sma = close.average(10)
cond = close > sma
只需要簡單的語法,就可以將其中一部分的訊號繪製出來檢查:
import matplotlib.pyplot as plt
close.loc['2021', '2330'].plot()
sma.loc['2021', '2330'].plot()
cond.loc['2021', '2330'].mul(20).add(500).plot()
plt.legend(['close', 'sma', 'cond'])
FinlabDataFrame.is_largest(n)
from finlab import data
roa = data.get('fundamental_features:ROA稅後息前')
good_stocks = roa.is_largest(10)
FinlabDataFrame.is_smallest(n)
from finlab import data
pb = data.get('price_earning_ratio:股價淨值比')
cheap_stocks = pb.is_smallest(10)
entries.hold_until(exits, ...)
這大概是所有策略撰寫中,最重要的語法糖,上述語法中 entries
為進場訊號,而 exits
是出場訊號。所以 entries.hold_until(exits)
,就是進場訊號為 True
時,買入並持有該檔股票,直到出場訊號為 True
則賣出。
此函式有很多細部設定,可以讓你最多選擇 N 檔股票做輪動。另外,當超過 N 檔進場訊號發生,也可以按照客制化的排序,選擇優先選入的股票。最後,可以持外設定停損停利,來增加出場的時機點。以下是 hold_until
的參數說明:
exit (pd.Dataframe)
: 出場訊號。nstocks_limit (int)
: 持有檔數上限。stoploss (float)
: 停損基準。範例:0.1,代表成本價下跌 10% 時出場。takeprofit (float)
: 停利基準。範例:0.1,代表成本價上漲 10% 時出場。trade_at (str)
: 停損停利參考價。可選 close
或 open
。rank (pd.Dataframe)
: 當天進場訊號超過 nstocks_limit 時,以 rank 數字越大的股票優先進場。價格 > 20 日均線入場, 價格 < 60 日均線出場,最多持有10檔,超過 10 個進場訊號,則以股價淨值比小的股票優先選入。
from finlab import data
from finlab.backtest import sim
close = data.get('price:收盤價')
pb = data.get('price_earning_ratio:股價淨值比')
sma20 = close.average(20)
sma60 = close.average(60)
entries = close > sma20
exits = close < sma60
position = entries.hold_until(exits, nstocks_limit=20, rank=-pb)
sim(position)
rise(n=1)
取是否比前第n筆高,若符合條件的值則為True,反之為False。
from finlab import data
data.get('price:收盤價').rise(10)
fall(n=1)
取是否比前第n筆低,若符合條件的值則為True,反之為False。
from finlab import data
data.get('price:收盤價').fall(10)
FinlabDataFrame.sustain(nwindow, nsatisfy=None)
from finlab import data
data.get('price:收盤價').rise().sustain(2)
FinlabDataFrame.quantile_row(n=1)
取得每列n定分位數的值。
from finlab import data
#
data.get('price:收盤價').quantile_row(0.9)
FinlabDataFrame.groupby_category()
資料按產業分群,類似 pd.DataFrame.groupby()
。
from finlab import data
pe = data.get('price_earning_ratio:股價淨值比')
pe.groupby_category().mean()['半導體'].plot()
全球 2020 量化寬鬆加上晶片短缺,使得半導體股價淨值比衝高。
FinlabDataFrame.is_entry()
from finlab import data
data.get('price:收盤價').is_largest(10).is_entry()
FinlabDataFrame.is_exit()
from finlab import data
data.get('price:收盤價').is_largest(10).is_entry()
股票最忌諱就是「買高賣低」,汲汲營營,但是你有沒有想過,大家都怕買高,反而要買更高?
創新高的股票,由於受到市場的關注,上漲動能非常強,所以選擇這些股票,就能帶來不錯的報酬率。
下方的條件,使用了 pd.DataFrame
裡的功能 rolling(250).max()
,代表 250 天的最高價,
假如今天的收盤,等於 250 天的最高價,就代表創新高了!
from finlab import data
from finlab.backtest import sim
# 創 250 天新高的股票
close = data.get('price:收盤價')
position = (close == close.rolling(250).max())
# 回測,每月(M)調整一次,選出當天創新高股票
sim(position, resample='M', name="創年新高策略")
上述策略會選出很多檔標的,要是我們只想要選出 20 檔要怎麼寫呢?這個策略我們可以將 RSI 最大的 20 檔股票納入組合,並且持有一週,來試試看效果如何。我們可以用 data.indicator
來計算所有股票的 RSI,也可以用 rsi.is_largest
來計算此股票的 RSI 是否是最大的 20 檔。
from finlab import data
from finlab.backtest import sim
# 選出 RSI 最大的 20 檔股票
rsi = data.indicator('RSI')
position = rsi.is_largest(20)
# 回測,每月(M)調整一次
report = sim(position, resample='W', name="高RSI策略")
可以一次使用兩種不同的指標來選股嗎?答案是肯定的,而且這兩個指標,還可以是每季的財報跟每天的價格,混和條件選股,由於下方的 roe
和 close
並不是 pd.DataFrame
而是 finlab.dataframe.FinlabDataFrame
,當在做條件運算,例如交集、聯集(and or)時,不同頻率的 index 會被取連集並且整合,所以使用者不需要思考,究竟資料是「季」還是「月」還是「日」,可以直接當作 index 和 columns 是對齊的,直接運算(程式會自動想辦法)。
下面的策略,利用 close / close.shift(60)
計算乖離率,並取 30 檔股票出來,再用 ROE 當濾網,選擇 ROE 為正的股票。
from finlab import data
from finlab.backtest import sim
# 下載 ROE 跟收盤價
roe = data.get("fundamental_features:ROE稅後")
close = data.get('price:收盤價')
position = (
(close / close.shift(60)).is_largest(30) # 選出乖離率前 30 名的股票
& (roe > 0) # 選出 ROE 大於 0 的股票
)
# 回測,每月(M)調整一次
report = sim(position, resample='M', name="乖離率和ROE濾網策略")
點選策略右側齒輪符號(下圖紅圈處)刪除策略。
點選右上“設定自動更新策略”按鈕,設定需要每日自動更新清單與回測的策略,選取策略名稱與更新時間,目前每個帳號限定3項策略可自動更新。
Note:限定使用Finlab IDE介面上傳程式後,方可設定“自動更新策略”。使用colab或其他編輯器者,可在撰寫完策略後,將程式貼於Finlab IDE介面執行。
Step1: 點選策略名稱進入策略,之後點選在策略名稱旁的齒輪符號。
finlab.plot.plot_tw_stock_candles(stock_id, recent_days=400, ...)
stock_id (str)
: 標地代碼 ex:2330。recent_days (int)
: 取近n日內的資料。adjust_price (bool)
: 是否使用還原股價。resample (str)
: 技術指標價格週期,範例:D
代表日線, W
代表週線, M
代表月線。overlay_func (dict)
: 主圖輔助線,範例可以參考下方教學{
'ema_5':lambda df:abstract.EMA(df['close'],timeperiod=5),
'ema_10':lambda df:abstract.EMA(df['close'],timeperiod=10),
'ema_20':lambda df:abstract.EMA(df['close'],timeperiod=20),
'ema_60':lambda df:abstract.EMA(df['close'],timeperiod=60),
}
{
'rsi_10':lambda df:abstract.RSI(df['close'],timeperiod=10),
'rsi_20':lambda df:abstract.RSI(df['close'],timeperiod=20),
}
最簡單的範例如下,可以直接呼叫並且繪製 2330
台積電的股價
from finlab.plot import plot_tw_stock_candles
plot_tw_stock_candles("2330")
from finlab.plot import plot_tw_stock_candles
plot_tw_stock_candles("2330", resample="M")
我們可以額外使用 talib
來繪製想要看的技術指標,例如 5日、10日、20日的移動幾何平均,由於這些技術指標,是跟股價繪製在一起,所以可以用 overlay_function
這個參數。
from finlab.plot import plot_tw_stock_candles
import talib
overlay_func = {
'ema_05': lambda df: talib.EMA(df.close, timeperiod=5),
'ema_10': lambda df: talib.EMA(df.close, timeperiod=10),
'ema_20': lambda df: talib.EMA(df.close, timeperiod=20),
}
plot_tw_stock_candles("2330", overlay_func=overlay_func)
與上述的範例不同,技術指標如 KD、RSI,不能跟股價繪製在同一張圖表,需要額外在不同的圖表上繪製,就可以使用 technical_func
來繪製:
from finlab.plot import plot_tw_stock_candles
import talib
technical_func = {
'rsi_10': lambda df: talib.RSI(df['close'], timeperiod=10),
'rsi_20': lambda df: talib.RSI(df['close'], timeperiod=20),
}
plot_tw_stock_candles("2330", technical_func=technical_func)
finlab.plot.plot_tw_stock_treemap(start=None, end=None, ...)
start (str)
: 資料起始日,ex:2021-01-02。
end (str)
: 資料結束日,ex:2021-01-05。
adjust_price (bool)
: 是否使用還原股價。
area_ind (str)
: 決定板塊面積大小的參數,可選範圍為["market_value", "turnover", "turnover_ratio"]
(分別為:市值、交易力量、交易佔比)。
item (str)
: 決定板塊面積大小的參數,預設可選範圍為["return_ratio", "turnover_ratio"]
或是帶入finlab database的使用方法,ex:'price_earning_ratio:本益比'
。
color_scales (str)
: 色溫條樣式,預設為'Temps'。
樣式選擇詳細資訊。
clip (tuple)
: 數值限定範圍,預設為None。避免數值範圍過大讓色溫顯示模糊,ex:(0,100)。
以下範例為使用此函式,繪製出 2021-7-1
~ 2021-7-2
的成交量的比例。其面積是成交量的大小,而顏色則是上漲或下跌的幅度。
from finlab.plot import plot_tw_stock_treemap
plot_tw_stock_treemap(
start='2021-7-1',
end='2021-7-2',
area_ind='turnover_ratio',
item='turnover_ratio')
finlab.plot_tw_stock_river(start=None, end=None, stock_id='2330', mode='pe', split_range=8)
start (str)
: 資料起始日,ex:2021-01-02。end (str)
: 資料結束日,ex:2021-01-05。stock_id (str)
: 股號,ex:'2330'。mode (str)
: 資料模式,可選範圍為["pe", "pb"]
,分別為本益比、股價淨值比。split_range (int)
:河流階層數 。以下範例為使用此函式繪製出台積電的歷史本益比河流圖。
from finlab.plot import plot_tw_stock_river
plot_tw_stock_river(stock_id='2330',mode='pb',split_range=8)
finlab.plot.plot_tw_stock_radar(df=None, feats=None, ...)
df(dataframe)
: 客製化指標dataframe,數值使用pandas的rank與cut做群組分級。如果不填,則使用下一個feats參數帶入現有資料庫指標
格式範例:
營業毛利率 營業利益率 稅後淨利率
stock_id
1101 6 9 9
1102 6 9 9
1103 1 1 1
1104 4 7 8
1108 3 6 5
feats (list)
: 指標清單,填入資料庫使用方法至list。預設為20項常用財務指標。
格式範例:
['fundamental_features:營業毛利率','fundamental_features:營業利益率']
select_targets(list)
: 台股標的清單。
格式範例:['1101','1102']
mode(str)
: 雷達圖模式選擇,可用模式為'line_polar','bar_polar','scatter_polar'
,
詳細資訊。
line_polar_fill(str)
: line_polar模式下的圖型填滿選擇,可用模式為:None,'toself','tonext'
,
詳細資訊。
cut_bins(int)
: 設定指標分級級距。
title(str)
: 設定圖表標題。
適合呈現整體投資組合指標累積評分結果。
from finlab.plot import plot_tw_stock_radar
plot_tw_stock_radar(
select_targets=["1101", "2330", "8942", "6263"],
mode="bar_polar" ,
line_polar_fill=line_polar_fill)
line_polar 適合比較單一標地指標評分。
from finlab.plot import plot_tw_stock_radar
plot_tw_stock_radar(
select_targets=["1101", "2330", "8942", "6263"],
mode="bar_polar" ,
line_polar_fill=None)
FinLab 策略程式的開發建立在 Pandas 的基礎,藉由 Pandas 強大的資料處理功能,我們可以省下許多學習成本,更方便地操控資料。以下整理出經常使用在策略撰寫函數,並以 Finlab 財經資料為範例,示範策略轉寫。若你已經有 Python 基礎,此份資料方便你在處理資料遇到困難時得以檢索、複習、套用,無論在使用 Finlab 模組功能或開發其他延伸應用,都能得心應手。若你想操考更多語法功能細節,請參考文章底處的參考資源連結。
import pandas as pd
import numpy as np
from finlab import data
pd.Dataframe
建立二維資料表,有索引與欄位。可想成是多欄資料,由多個Series組成。
資料格式範例,我們這個章節,將分析三檔股票
2317
2330
3008
2018
年的股票資料取出,接下來的資料分析,就會著重在這三檔股票。df = data.get('price:收盤價')
df = df.loc['2018', ['2317', '2330', '3008']]
df
2317 2330 3008
date
2018-01-02 95.0 232.5 4255.0
2018-01-03 94.0 237.0 4145.0
2018-01-04 92.6 239.5 4135.0
2018-01-05 93.0 240.0 4100.0
2018-01-08 91.8 242.0 3930.0
... ... ... ...
2018-12-24 71.4 220.0 3270.0
2018-12-25 70.7 217.5 3165.0
2018-12-26 70.2 216.5 3125.0
2018-12-27 70.8 223.0 3135.0
2018-12-28 70.8 225.5 3215.0
可以將上述的 df
中,用時間和股票來選取其中的一小部分,以下是各種的用法:
我們可以選取歷史資料中的一條時間序列,例如我們希望將 2330
台積電從上述 df
中取出來:
df['2330']
date
2018-01-02 232.5
2018-01-03 237.0
2018-01-04 239.5
2018-01-05 240.0
2018-01-08 242.0
...
2018-12-24 220.0
2018-12-25 217.5
2018-12-26 216.5
2018-12-27 223.0
2018-12-28 225.5
Name: 2330, Length: 247, dtype: float64
head & tail 會選取前 n 筆,和後 n 筆資料,預設 n = 5
df.head()
2317 2330 3008
date
2018-01-02 95.0 232.5 4255.0
2018-01-03 94.0 237.0 4145.0
2018-01-04 92.6 239.5 4135.0
2018-01-05 93.0 240.0 4100.0
2018-01-08 91.8 242.0 3930.0
df.tail(3)
2317 2330 3008
date
2018-12-26 70.2 216.5 3125.0
2018-12-27 70.8 223.0 3135.0
2018-12-28 70.8 225.5 3215.0
df.index
DatetimeIndex(['2018-01-02', '2018-01-03', '2018-01-04', '2018-01-05',
'2018-01-08', '2018-01-09', '2018-01-10', '2018-01-11',
'2018-01-12', '2018-01-15',
...
'2018-12-18', '2018-12-19', '2018-12-20', '2018-12-21',
'2018-12-22', '2018-12-24', '2018-12-25', '2018-12-26',
'2018-12-27', '2018-12-28'],
dtype='datetime64[ns]', name='date', length=247, freq=None)
df.columns
Index(['2317', '2330', '3008'], dtype='object')
values:取值。
demo_values=df.values
array([[ 9.54, 57.85, 32.83, ..., nan, 46. , 49.6 ],
[ 9.54, 58.1 , 32.99, ..., nan, 45.9 , 50.4 ],
[ 9.52, 57.6 , 32.8 , ..., nan, 49.1 , 49.1 ],
...,
[ nan, 137.6 , 57.8 , ..., 131.5 , 27.2 , 14.6 ],
[ nan, 138.95, 58.2 , ..., 134. , 27.35, 16.05],
[ nan, 138.3 , 57.9 , ..., 129.5 , 27.25, 17.65]])
索引布林邏輯篩選。
ex:取得索引大於2020年份的資料。
demo_boolean_indexing=df[df.index>'2020']
0015 0050 0051 0052 0053
date
2020-01-02 NaN 97.65 35.45 73.30 42.70
2020-01-03 NaN 97.65 35.30 72.85 42.58
2020-01-06 NaN 96.40 35.10 72.05 42.04
2020-01-07 NaN 96.10 34.72 71.80 41.70
2020-01-08 NaN 95.65 34.69 71.25 41.61
... ... ... ... ... ...
2021-06-25 NaN 136.95 57.85 124.30 66.30
2021-06-28 NaN 137.20 57.90 124.20 66.15
2021-06-29 NaN 137.60 57.80 124.65 66.45
2021-06-30 NaN 138.95 58.20 125.00 66.80
2021-07-01 NaN 138.30 57.90 124.35 66.20
loc:使用標籤篩選欄列。
ex:選擇2020-01-03~2020-01-08中所有標地的股價。
demo_loc1=df.loc['2020-01-03':'2020-01-08']
0015 0050 0051 0052 0053
date
2020-01-03 NaN 97.65 35.30 72.85 42.58
2020-01-06 NaN 96.40 35.10 72.05 42.04
2020-01-07 NaN 96.10 34.72 71.80 41.70
2020-01-08 NaN 95.65 34.69 71.25 41.61
ex:選擇0050&1101&2330的股價,以下兩種方法等價。
demo_loc2=df.loc[:,['0050','1101','2330']]
demo_column_select=df[['0050','1101','2330']]
0050 1101 2330
date
2007-04-23 57.85 29.60 68.6
2007-04-24 58.10 30.25 69.8
2007-04-25 57.60 29.65 69.3
2007-04-26 57.70 29.65 69.9
2007-04-27 57.50 30.35 69.0
... ... ... ...
2021-06-25 136.95 51.00 591.0
2021-06-28 137.20 51.10 590.0
2021-06-29 137.60 51.20 595.0
2021-06-30 138.95 51.00 595.0
2021-07-01 138.30 50.60 593.0
ex:選擇2020-01-03~2020-01-08中0050&1101&2330的股價。
demo_loc3=df.loc['2020-01-03':'2020-01-08',['0050','1101','2330']]
0050 1101 2330
date
2020-01-03 97.65 43.95 339.5
2020-01-06 96.40 43.45 332.0
2020-01-07 96.10 43.60 329.5
2020-01-08 95.65 43.40 329.5
iloc:使用標籤位置篩選欄列。
ex:選擇第6列到第10列的資料。
demo_iloc1=df.iloc[5:10]
0015 0050 0051 0052 0053
date
2007-04-30 9.37 56.90 32.07 38.10 NaN
2007-05-02 9.50 57.55 32.02 38.49 NaN
2007-05-03 9.41 58.20 31.75 38.80 NaN
2007-05-04 9.59 59.20 32.41 39.23 NaN
2007-05-07 9.60 59.55 32.60 39.44 NaN
ex:選擇df表中第6檔到第10檔標地資料。
demo_iloc2=df.iloc[:,5:10]
0054 0055 0056 0057 0058
date
2007-04-23 NaN NaN NaN NaN NaN
2007-04-24 NaN NaN NaN NaN NaN
2007-04-25 NaN NaN NaN NaN NaN
2007-04-26 NaN NaN NaN NaN NaN
2007-04-27 NaN NaN NaN NaN NaN
... ... ... ... ... ...
2021-06-25 31.84 21.25 34.74 94.05 NaN
2021-06-28 31.84 21.29 35.10 94.05 NaN
2021-06-29 31.80 21.20 34.80 94.70 NaN
2021-06-30 31.90 21.26 35.00 95.45 NaN
2021-07-01 31.56 21.24 35.00 94.95 NaN
ex:選擇第6列到第10列的資料&選擇df表中第6檔到第10檔標地資料。
demo_iloc3=df.iloc[5:10,5:10]
0054 0055 0056 0057 0058
date
2007-04-30 NaN NaN NaN NaN NaN
2007-05-02 NaN NaN NaN NaN NaN
2007-05-03 NaN NaN NaN NaN NaN
2007-05-04 NaN NaN NaN NaN NaN
2007-05-07 NaN NaN NaN NaN NaN
nlargest:取前n大,ex:取近一日股價前n高的標地。
demo_nlargest=df.iloc[-1].nlargest(10)
6415 3710.0
3008 3105.0
5274 2115.0
8454 1900.0
4966 1380.0
5269 1375.0
6409 1340.0
3529 1310.0
1590 1100.0
6669 976.0
Name: 2021-07-01 00:00:00, dtype: float64
nsmallest:取前n大,ex:取近一日股價前n低的標地。
demo_nsmallest=df.iloc[-1].nsmallest(10)
8101 2.61
5701 2.85
02001R 2.88
3095 2.96
2443 2.99
6289 2.99
9188 3.09
9110 3.12
2364 3.22
6225 3.40
Name: 2021-07-01 00:00:00, dtype: float64
pandas預防資源消耗,預設顯示列數為10(頭尾各五列),中間資料以‘…’帶過,若想要調整顯示數量來檢視資料,可作以下控制。
Hint: 要注意的是此影響為全域,接下來的顯示數都會被影響。
列數全展開:
pd.set_option("display.max_rows", None)
欄數全展開:
pd.set_option("display.max_columns", None)
最多顯示20列:
pd.set_option("display.max_rows", 20)
最多顯示20欄:
pd.set_option("display.max_columns", 20)
還原顯示數預設初始值:
pd.reset_option("^display")
sum:
加總,axis=0為整欄加總,axis=1為整列加總。
ex:計算每日股價大於100的標地數量
產生股價大於100元的布林dataframe,若大於100則顯示True(計算時視為1),若不大於100則顯示False(計算時視為0)
price_up_10=df>100
demo_sum=price_up_10.sum(axis=1)
date
2007-04-23 70
2007-04-24 72
2007-04-25 70
2007-04-26 71
2007-04-27 71
...
2021-06-25 282
2021-06-28 285
2021-06-29 283
2021-06-30 283
2021-07-01 280
Length: 3503, dtype: int64
mean:平均數,常用於計算均線。
demo_mean=df.mean()
0015 7.697360
0050 67.783269
0051 29.989126
0052 45.283546
0053 29.846568
...
9951 84.396078
9955 30.770911
9958 29.483578
9960 24.562452
9962 12.956349
Length: 2269, dtype: float64
median:中位數。
demo_median=df.median()
0015 7.49
0050 62.90
0051 29.44
0052 38.77
0053 27.10
...
9951 79.00
9955 20.90
9958 11.50
9960 22.30
9962 11.50
Length: 2269, dtype: float64
std:標準差,常用於計算乖離率。
demo_std=df.std()
0015 1.482317
0050 19.781026
0051 6.076147
0052 20.926838
0053 9.881821
...
9951 54.171955
9955 21.267716
9958 33.751259
9960 8.755944
9962 6.390186
Length: 2269, dtype: float64
max:最大值。
demo_max=df.max()
0015 12.40
0050 143.00
0051 58.20
0052 136.45
0053 68.80
...
9951 220.00
9955 140.50
9958 141.50
9960 61.10
9962 50.40
Length: 2269, dtype: float64
min:最小值。
demo_min=df.min()
0015 4.15
0050 29.50
0051 13.40
0052 17.02
0053 11.85
...
9951 0.00
9955 10.90
9958 5.10
9960 0.00
9962 4.95
Length: 2269, dtype: float64
cumsum:累加。
ex:累積漲多少元。
demo_diff=df.diff()
demo_cumsum=demo_diff.cumsum()
0015 0050 0051 0052 0053
date
2007-04-23 NaN NaN NaN NaN NaN
2007-04-24 0.00 0.25 0.16 0.25 NaN
2007-04-25 -0.02 -0.25 -0.03 0.19 NaN
2007-04-26 0.05 -0.15 -0.03 0.20 NaN
2007-04-27 0.01 -0.35 -0.11 0.00 NaN
... ... ... ... ... ...
2021-06-25 NaN 79.10 25.57 86.56 37.18
2021-06-28 NaN 79.35 25.62 86.46 37.03
2021-06-29 NaN 79.75 25.52 86.91 37.33
2021-06-30 NaN 81.10 25.92 87.26 37.68
2021-07-01 NaN 80.45 25.62 86.61 37.08
cumprod:累乘。
ex:常用於計算累積報酬率。
demo_pct_change=df.pct_change()+1
demo_cumprod=demo_pct_change.cumprod()
0015 0050 0051 0052 0053
date
2007-04-23 NaN NaN NaN NaN NaN
2007-04-24 1.000000 1.004322 1.004874 1.006510 NaN
2007-04-25 0.997904 0.995678 0.999086 1.004948 NaN
2007-04-26 1.005241 0.997407 0.999086 1.005208 NaN
2007-04-27 1.001048 0.993950 0.996649 1.000000 NaN
... ... ... ... ... ...
2021-06-25 0.966457 2.367329 1.762108 3.236979 2.207057
2021-06-28 0.966457 2.371651 1.763631 3.234375 2.202064
2021-06-29 0.966457 2.378565 1.760585 3.246094 2.212051
2021-06-30 0.966457 2.401901 1.772769 3.255208 2.223702
2021-07-01 0.966457 2.390666 1.763631 3.238281 2.203728
cummax:累積最大值,常用於計算drawdown、創新高。
demo_cummax=df.cummax()
0015 0050 0051 0052 0053
date
2007-04-23 9.54 57.85 32.83 38.40 NaN
2007-04-24 9.54 58.10 32.99 38.65 NaN
2007-04-25 9.54 58.10 32.99 38.65 NaN
2007-04-26 9.59 58.10 32.99 38.65 NaN
2007-04-27 9.59 58.10 32.99 38.65 NaN
... ... ... ... ... ...
2021-06-25 NaN 143.00 57.85 136.45 68.8
2021-06-28 NaN 143.00 57.90 136.45 68.8
2021-06-29 NaN 143.00 57.90 136.45 68.8
2021-06-30 NaN 143.00 58.20 136.45 68.8
2021-07-01 NaN 143.00 58.20 136.45 68.8
cummin:累積最小值,常用於計算drawdown、創新低。
demo_cummin=df.cummin()
0015 0050 0051 0052 0053
date
2007-04-23 9.54 57.85 32.83 38.40 NaN
2007-04-24 9.54 57.85 32.83 38.40 NaN
2007-04-25 9.52 57.60 32.80 38.40 NaN
2007-04-26 9.52 57.60 32.80 38.40 NaN
2007-04-27 9.52 57.50 32.72 38.40 NaN
... ... ... ... ... ...
2021-06-25 NaN 29.50 13.40 17.02 11.85
2021-06-28 NaN 29.50 13.40 17.02 11.85
2021-06-29 NaN 29.50 13.40 17.02 11.85
2021-06-30 NaN 29.50 13.40 17.02 11.85
2021-07-01 NaN 29.50 13.40 17.02 11.85
計算第c百分位數數值,常用於取標的數前c%強標地。
demo_quantile=df.iloc[-1].quantile(0.9)
126.0
corr:
計算相關性。
demo_corr=df.iloc[:,:5].corr()
0015 0050 0051 0052 0053
0015 1.000000 0.892513 0.922749 0.926133 0.823550
0050 0.892513 1.000000 0.852582 0.968580 0.989484
0051 0.922749 0.852582 1.000000 0.851140 0.870181
0052 0.926133 0.968580 0.851140 1.000000 0.983993
0053 0.823550 0.989484 0.870181 0.983993 1.000000
describe:
計算統計資料。
demo_describe=df.describe()
0015 0050 0051 0052 0053
count 1640.000000 3503.000000 3500.000000 3395.000000 3435.000000
mean 7.697360 67.783269 29.989126 45.283546 29.846568
std 1.482317 19.781026 6.076147 20.926838 9.881821
min 4.150000 29.500000 13.400000 17.020000 11.850000
25% 7.090000 54.850000 26.497500 33.440000 23.880000
50% 7.490000 62.900000 29.440000 38.770000 27.100000
75% 8.300000 78.250000 32.252500 50.100000 33.825000
max 12.400000 143.000000 58.200000 136.450000 68.800000
shift:
資料移動,常用於前後期數值增減比較、年增率計算。
ex:收盤價向下平移一列,第一列會變NaN。
demo_shift1=df.shift()
0015 0050 0051 0052 0053
date
2007-04-23 NaN NaN NaN NaN NaN
2007-04-24 9.54 57.85 32.83 38.40 NaN
2007-04-25 9.54 58.10 32.99 38.65 NaN
2007-04-26 9.52 57.60 32.80 38.59 NaN
2007-04-27 9.59 57.70 32.80 38.60 NaN
... ... ... ... ... ...
2021-06-25 NaN 136.70 57.60 124.70 65.75
2021-06-28 NaN 136.95 57.85 124.30 66.30
2021-06-29 NaN 137.20 57.90 124.20 66.15
2021-06-30 NaN 137.60 57.80 124.65 66.45
2021-07-01 NaN 138.95 58.20 125.00 66.80
ex:收盤價向上平移一列,最後一列會變NaN。。
demo_shift2=df.shift(-1)
0015 0050 0051 0052 0053
date
2007-04-23 9.54 58.10 32.99 38.65 NaN
2007-04-24 9.52 57.60 32.80 38.59 NaN
2007-04-25 9.59 57.70 32.80 38.60 NaN
2007-04-26 9.55 57.50 32.72 38.40 NaN
2007-04-27 9.37 56.90 32.07 38.10 NaN
... ... ... ... ... ...
2021-06-25 NaN 137.20 57.90 124.20 66.15
2021-06-28 NaN 137.60 57.80 124.65 66.45
2021-06-29 NaN 138.95 58.20 125.00 66.80
2021-06-30 NaN 138.30 57.90 124.35 66.20
2021-07-01 NaN NaN NaN NaN NaN
rolling:
移動窗格作業,常結合資料運算公式做滾動式計算。
ex:計算20日移動平均線(前n筆數資料未滿10日取na,未滿20日以n日計算)。
demo_rolling=df.rolling(20,min_periods=10).mean()
0015 0050 0051 0052 0053
date
2007-04-23 NaN NaN NaN NaN NaN
2007-04-24 NaN NaN NaN NaN NaN
2007-04-25 NaN NaN NaN NaN NaN
2007-04-26 NaN NaN NaN NaN NaN
2007-04-27 NaN NaN NaN NaN NaN
... ... ... ... ... ...
2021-06-25 NaN 137.0975 55.6150 124.5325 65.8150
2021-06-28 NaN 137.1675 55.8250 124.5675 65.8725
2021-06-29 NaN 137.1925 56.0050 124.5475 65.8950
2021-06-30 NaN 137.2550 56.1575 124.5425 65.9225
2021-07-01 NaN 137.2850 56.2975 124.5550 65.9500
diff:
列數相減。
ex:取每日漲跌價。
demo_diff=df.diff()
0015 0050 0051 0052 0053
date
2007-04-23 NaN NaN NaN NaN NaN
2007-04-24 0.00 0.25 0.16 0.25 NaN
2007-04-25 -0.02 -0.50 -0.19 -0.06 NaN
2007-04-26 0.07 0.10 0.00 0.01 NaN
2007-04-27 -0.04 -0.20 -0.08 -0.20 NaN
... ... ... ... ... ...
2021-06-25 NaN 0.25 0.25 -0.40 0.55
2021-06-28 NaN 0.25 0.05 -0.10 -0.15
2021-06-29 NaN 0.40 -0.10 0.45 0.30
2021-06-30 NaN 1.35 0.40 0.35 0.35
2021-07-01 NaN -0.65 -0.30 -0.65 -0.60
pct_change:
列數相除。
ex:取每日漲跌幅。
demo_pct_change=df.pct_change()
0015 0050 0051 0052 0053
date
2007-04-23 NaN NaN NaN NaN NaN
2007-04-24 0.000000 0.004322 0.004874 0.006510 NaN
2007-04-25 -0.002096 -0.008606 -0.005759 -0.001552 NaN
2007-04-26 0.007353 0.001736 0.000000 0.000259 NaN
2007-04-27 -0.004171 -0.003466 -0.002439 -0.005181 NaN
... ... ... ... ... ...
2021-06-25 0.000000 0.001829 0.004340 -0.003208 0.008365
2021-06-28 0.000000 0.001825 0.000864 -0.000805 -0.002262
2021-06-29 0.000000 0.002915 -0.001727 0.003623 0.004535
2021-06-30 0.000000 0.009811 0.006920 0.002808 0.005267
2021-07-01 0.000000 -0.004678 -0.005155 -0.005200 -0.008982
乖離率線圖:
df=data.get('price:收盤價').iloc[-400:]['2330']
mean_20=df.rolling(20).mean()
std_value=df.rolling(20).std()
up=mean_20+std_value*2
down=mean_20-std_value*2
up.plot(label='up',legend=True)
down.plot(label='down',legend=True)
mean_20.plot(label='ma_20',legend=True)
df.plot(title='bias plot',label='close',legend=True,grid=True,figsize=(20, 8))
相關性熱力圖:
df=data.get('price:收盤價')
check_list=['1101','1102','2330','2454','6263','9939']
demo_corr=df.iloc[-600:][check_list].corr()
demo_corr.style.background_gradient(cmap ='viridis')\
.set_properties(**{'font-size': '20px'})
了解pandas語法後,就可以著手實戰應用於開發策略。
均線多頭
from finlab import data
df = data.get('price:收盤價')
# 限定範圍
df = df[(df.index > '2015') & (df.index < '2019')]
cond1 = df > df.rolling(5).mean()
cond2 = df > df.rolling(10,min_periods=5).mean()
cond3 = df > df.rolling(20,min_periods=10).mean()
cond4 = df > df.rolling(60,min_periods=40).mean()
position = (df * (cond1 & cond2 & cond3 & cond4))
position = position[position > 0]
position = position.is_largest(10)
突破布林通道上緣
from finlab import data
df = data.get('price:收盤價')
mean_20 = df.rolling(20).mean()
std_value = df.rolling(20).std()
up = mean_20 + std_value*2
# 收盤價剛站上布林通道上緣
cond1 = (df > up) & (df.shift() < up)
cond2 = (df > 5) & (df < 200)
position = (cond1 & cond2)
position = position[position > 0]
position = position.is_smallest(20)