本文重點摘要
- 文章難度:★★☆☆☆
- 簡介 F-score 策略的九項選股條件。
- 利用 Pipeline 篩選符合 F-score 策略九項指標的股票,並形成買進訊號。
- 透過 TQuant Lab 的簡化版回測引擎 TargetPercentPipeAlgo 回測 F-score 策略每季再平衡的績效表現。
前言
在當今的股市中,投資人面臨著選擇眾多股票的挑戰,許多投資人希望能夠找到被低估但基本面良好的股票。然而,市場的波動和信息的不對稱性往往使得這個目標變得困難重重。在這種情境下,Piotroski F-score,亦稱為皮爾托斯基分數或 F-score,成為了投資者的重要工具。
由前芝加哥大學教授 Joseph Piotroski 提出的 F-score 策略,透過 9 項財報條件的評估,幫助投資人深入了解公司的財務健康狀況,F-score 涵蓋公司的獲利性、安全性及成長性,提供了多維度的分析框架,讓投資人能夠更全面地評估公司的潛在價值。為了探討 F-score 在台股市場的表現,本文使用 TQuant Lab 建構 F-score 策略,幫助投資人深入了解 F-score 所能帶來的投資效益。
F-score 策略
根據 Joseph Piotroski 教授所著的論文,為了找出被低估且在獲利性、安全性及成長性都相對穩健的股票,建構 F-score 策略時,我們需要透過以下兩個步驟來生成交易訊號:
- 找出市場中淨值市價比 ( Book-to-Market Ratio, BM Ratio ) 前 20% 的股票形成股票池。
p.s. BM Ratio 越高,代表該股票價值越被低估,因為該股票的交易價格低於其資產的價值。 - 根據 F-score 的 9 項基本面條件計算分數,符合一項得 1 分。我們將買入 8 分和 9 分的標的,並在每季進行一次再平衡。
F-score 策略的 9 項基本面條件如下:
- 獲利性
- 資產報酬率 ( ROA ) > 0
- 今年 ROA > 去年 ROA
- 營業現金流 > 0
- 營業現金流 > 稅後淨利
- 安全性
- 今年度的長期負債金額 < 上一年度
- 今年度的流動比率 > 上一年度
- 上一年度沒有發行新股
- 成長性
- 今年度的毛利率 > 上一年度
- 今年度的資產週轉率 > 上一年度
以下,我們將透過 TQuant Lab 來生成 F-score 策略所需的交易訊號。
編輯環境與模組需求
本文使用 Windows 11 並以 Jupyter Lab 作為編輯器。
載入套件與環境設定
import os import numpy as np import pandas as pd # tej_key tej_key = 'your key' api_base = 'https://api.tej.com.tw' os.environ['TEJAPI_KEY'] = tej_key os.environ['TEJAPI_BASE'] = api_base
取得股票池
我們透過以下兩個步驟取得被低估的股票:
- 利用 get_universe 函式取得 2019-05-06 所有上市櫃普通股的股票代碼。
- 利用 TEJ Tool API 取得上述股票的股價淨值比 ( Price-Book Ratio, PB ratio ),藉此計算 BM Ratio,進而取得 BM Ratio 前 20 % 的股票。
p.s. PB Ratio 和我們需要的 BM Ratio 互為倒數。 from zipline.sources.TEJ_Api_Data import get_universe pool = get_universe(start = '2019-05-06', end = '2019-05-06', mkt = ['TWSE', 'OTC'], # 上市櫃公司 stktp_e = ['Common Stock', 'Common Stock-Foreign'] # 普通股 & KY股 ) import TejToolAPI data = TejToolAPI.get_history_data(start = '2019-05-06', end = '2019-05-06', ticker = pool, columns = ['PBR_TEJ'], transfer_to_chinese = False ) # 計算 BM Ratio = 1/PB Ratio data['BM_ratio'] = 1/data['PBR_TEJ'] # 計算 BM Ratio 的 80 分位數 quantile_80 = data['BM_ratio'].quantile(0.80) # 選取 BM Ratio 前 20% 的資料 top_20_percert_data = data[data['BM_ratio'] >= quantile_80] # 取得 BM Ratio 在市場前 20% 的股票 -> pool pool = top_20_percert_data['coid'].tolist()
取得 F-score 策略需要的財務資料
根據計算 F-score 所需的財務條件,我們使用 Tej Tool API 抓取以下 9 項財務資料:
- ROA: Return_on_Total_Assets_A_percent
- 營業現金流:Cash_Flow_from_Operating_Activities
- 每股稅後淨利:Net_Income_Per_Share
- 在外流通股數_仟股:Outstanding_Shares_1000_Shares
- 長期負債:Total_Non_current_Liabilities
- 流動比率:Current_Ratio
- 現金增資_仟股:Cash_Capital_Increase_Thousand_Shares
- 營業毛利率:Gross_Margin_Rate_percent
- 資產週轉率:Total_Assets_Turnover start = '2019-05-06' end = '2023-12-31' columns = ['Return_on_Total_Assets_A_percent', 'Cash_Flow_from_Operating_Activities', 'Net_Income_Per_Share', 'Outstanding_Shares_1000_Shares', 'Total_Non_current_Liabilities', 'Current_Ratio', 'Cash_Capital_Increase_Thousand_Shares', 'Gross_Margin_Rate_percent', 'Total_Assets_Turnover'] fin_data = TejToolAPI.get_history_data(start = start, end = end, ticker = pool, columns = columns, transfer_to_chinese = False ) fin_data = fin_data.sort_values(['coid','mdate']) fin_data = fin_data.filter(regex='(TTM$|^(?!.*(A$|Q$|TTM$)).*$)') # 僅保留移動四季 (TTM) 的資料 fin_data['Net_Income'] = fin_data['Outstanding_Shares_1000_Shares'] * fin_data['Net_Income_Per_Share_TTM'] fin_data
導入股票池價量資料
資料期間從 2019-05-06 至 2023-12-31,並導入上述 340 檔股票的價量資料與加權股價報酬指數 ( IR0001 ) 作為績效比較基準。
os.environ['mdate'] = start + ' ' + end os.environ['ticker'] = ' '.join(pool) + ' ' + 'IR0001' !zipline ingest -b tquant
將財務資料導入 Pipeline
CustomDataset 可以將資料庫中的內容導入 Pipeline 中,方便後續回測使用。於本範例我們用以將計算 F-score 所需的財務資料導入 Pipeline。擷取部分程式碼如下:
from zipline.pipeline.data.dataset import Column, DataSet from zipline.pipeline.domain import TW_EQUITIES class CustomDataset(DataSet): Return_on_Total_Assets_A_percent_TTM = Column(dtype=float) Cash_Flow_from_Operating_Activities_TTM = Column(dtype=float) Net_Income = Column(dtype=float) Total_Non_current_Liabilities_TTM = Column(dtype=float) Current_Ratio_TTM = Column(dtype=float) Cash_Capital_Increase_Thousand_Shares = Column(dtype=float) Gross_Margin_Rate_percent_TTM = Column(dtype=float) Total_Assets_Turnover_TTM = Column(dtype=float) domain = TW_EQUITIES
建立 CustomFactor 函式
在計算 F-score 前,我們先使用 CustomFactor 函式自訂以下兩個因子:
- 計算財務數據 YOY 變化。
- 將通過 F-score 的資料轉為 1 ,沒通過的轉為 0,用以計算 F-score 分數。
建立 Pipeline 函式
Pipeline() 提供使用者快速處理多檔標的的量化指標與價量資料的功能,於本次案例我們用以處理:
- 導入股價與財務資料。
- 計算財務數據 YOY 變化、年度平均現金增資 ( 用以判斷是否發行新股 ) 。
- 計算 F-score,並在 longs 欄位中生成交易訊號:F-score ≥ 8 分為 True,其餘則為 False。
擷取部分 Pipeline 內容如下:
from zipline.pipeline import Pipeline from zipline.pipeline.factors import Factor, SimpleMovingAverage from zipline.pipeline.filters import StaticAssets benchmark_asset = bundle.asset_finder.lookup_symbol('IR0001',as_of_date = None) def make_pipeline(): return Pipeline( columns = { 'price': price, 'f_score_total': f_score_total, 'longs': f_score_total >= 8 }, screen = ~StaticAssets([benchmark_asset]) # 排除大盤的數據 ) start_dt = pd.Timestamp(start, tz = 'UTC') end_dt = pd.Timestamp(end, tz = 'UTC') pipeline_result = engine.run_pipeline(make_pipeline(), start_dt, end_dt) pipeline_result
回測 F-score 策略
使用 TEJ 自製的簡化版 Zipline 回測引擎:TargetPercentPipeAlgo,輕鬆在一行內設定所有回測參數,最少只需輸入策略 pipeline 即可進行回測。
p.s. TargetPercentPipeAlgo 的詳細使用說明可參考 TQuant Lab GitHub: Simple Algorithm-TargetPercentPipeAlgo
本次策略修改的相關參數:
- 回測起迄日 ( start_session, end_session ):2020 / 05 / 30 ~ 2023 / 12 / 31
- 初始本金 ( capital_base ):1 千萬
- 交易日 ( 再平衡日 ):3, 6, 9, 12 月的十五號
- 最大槓桿:90%
- 策略 pipeline:將讀取上述 Pipeline 中的 longs 欄位判斷再平衡日是否交易
- 滑價模型:VolumeShareSlippage
- 手續費模型:Custom_TW_Commissionfrom zipline.algo.pipeline_algo import * start_dt = pd.Timestamp(start, tz = 'UTC') end_dt = pd.Timestamp(end, tz = 'UTC') algo = TargetPercentPipeAlgo( start_session=start_dt, end_session=end_dt, capital_base=1e7, tradeday=tradeday, max_leverage=0.9, pipeline=make_pipeline, slippage_model=slippage.VolumeShareSlippage(volume_limit=0.15, price_impact=0.01), commission_model = commission.Custom_TW_Commission(min_trade_cost = 20, discount = 1.0, tax = 0.003), custom_loader=custom_loader, analyze=analyze ) results = algo.run()
利用 Pyfolio 進行績效評估
import pyfolio as pf returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(results) benchmark_rets = results['benchmark_return'] pf.tears.create_full_tear_sheet(returns=returns, positions=positions, transactions=transactions, benchmark_rets=benchmark_rets )
藉由上表我們可以看到 F-score 策略的年化報酬率達到 27.25%,年化波動度約為 18.6%,另外夏普比率為 1.39,且 α 值為 0.11,顯示 F-score 策略在相對可控的風險之下,亦能為投資人賺取不俗的超額報酬。觀察 β 值,0.77 代表 F-score 策略的績效與大盤走勢的相關性不算高,表示 F-score 的九項選股條件確實能為投資人規避部分系統風險。在績效比較圖中,可以發現 F-score 策略於 2022 年的回檔下跌幅度較大盤小,且脫離熊市後,回升的績效也較大盤為佳,再次說明本策略優良的獲利能力。
在年化報酬率圖中,可以發現除了 2022 年經歷較大的回檔之外,其餘三年的年化報酬率都能達到超過 30% 的表現。
藉由長短部位曝險圖,我們可以看到因為先前在 TargetPercentPipeAlgo 設定最大槓桿為 90%,所以曝險部位穩定在 0.9 左右,使 F-score 策略保有部分餘裕不受大波動的影響。
結論
本次策略由 Joseph Piotroski ( 2002 ) 的論文作為出發點,欲探討 F-score 策略於台股市場能否為投資人穩定獲利。策略建構上,我們透過淨值市價比找尋被低估的股票,並使用 Pipeline 計算 F-score 及生成交易訊號,最後應用簡化版的回測引擎 TargetPercentPipeAlgo 進行策略回測。
因為 F-score 策略每季再平衡的特性,使投資人在每季有新的財報數據發佈後,能及時更新投資組合,而 Pyfolio 的績效分析,也進一步驗證了 F-score 策略優於大盤的獲利能力,顯示即使此策略已推出逾 20 年,在公司獲利性、安全性及成長性的評估上依然擁有不俗的能力,且在台股市場中也有應用的空間。
溫馨提醒,本次策略與標的僅供參考,不代表任何商品或投資上的建議。之後也會介紹使用 TEJ 資料庫來建構各式指標,並回測指標績效,所以歡迎對各種交易回測有興趣的讀者,選購 TQuant Lab 的相關方案,用高品質的資料庫,建構出適合自己的交易策略。
【TQuant Lab回測系統】解決你的量化金融痛點
全方位提供交易回測所需工具