Table of Contents
TargetPercentPipeAlgo
回測 F-score 策略每季再平衡的績效表現。在當今的股市中,投資人面臨著選擇眾多股票的挑戰,許多投資人希望能夠找到被低估但基本面良好的股票。然而,市場的波動和信息的不對稱性往往使得這個目標變得困難重重。在這種情境下,Piotroski F-score,亦稱為皮爾托斯基分數或 F-score,成為了投資者的重要工具。
由前芝加哥大學教授 Joseph Piotroski 提出的 F-score 策略,透過 9 項財報條件的評估,幫助投資人深入了解公司的財務健康狀況,F-score 涵蓋公司的獲利性、安全性及成長性,提供了多維度的分析框架,讓投資人能夠更全面地評估公司的潛在價值。為了探討 F-score 在台股市場的表現,本文使用 TQuant Lab 建構 F-score 策略,幫助投資人深入了解 F-score 所能帶來的投資效益。
根據 Joseph Piotroski 教授所著的論文,為了找出被低估且在獲利性、安全性及成長性都相對穩健的股票,建構 F-score 策略時,我們需要透過以下兩個步驟來生成交易訊號:
F-score 策略的 9 項基本面條件如下:
以下,我們將透過 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 所有上市櫃普通股的股票代碼。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 所需的財務條件,我們使用 Tej Tool API 抓取以下 9 項財務資料:
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
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
在計算 F-score 前,我們先使用 CustomFactor
函式自訂以下兩個因子:
Pipeline()
提供使用者快速處理多檔標的的量化指標與價量資料的功能,於本次案例我們用以處理:
擷取部分 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
使用 TEJ 自製的簡化版 Zipline 回測引擎:TargetPercentPipeAlgo
,輕鬆在一行內設定所有回測參數,最少只需輸入策略 pipeline 即可進行回測。
p.s. TargetPercentPipeAlgo
的詳細使用說明可參考 TQuant Lab GitHub: Simple Algorithm-TargetPercentPipeAlgo
本次策略修改的相關參數:
VolumeShareSlippage
Custom_TW_Commission
from 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()
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 的相關方案,用高品質的資料庫,建構出適合自己的交易策略。
電子報訂閱