營業利益率選股
原理
營收 (Revenue) 是一間公司「做多少生意」的指標,然而做多少生意不表示賺多少錢,營收再高也不代表賺更多的錢,中間還要扣除銷貨成本得到的營業毛利 (Gross profit),營業毛利扣掉費用後得到的營業利益 (Operating profit),而後還有營業外支出,利息與所得稅等等,東扣西扣最後才是代表轉多少錢的指標稅後淨利 (Net income)。
先不論一間公司在外面做多少生意,繳多少利息與所得稅等等這些支出,衡量公司賺錢真本事的剩下營業毛利與營業利益這兩個指標。而營業毛利取決於行業類別,例如航運與鋼鐵業,毛利平均 10% 左右;晶圓代工與光學鏡頭,毛利平均在 40% 以上,難以用一個數字去衡量所有公司的好壞。而營業利益表是一間公司控管費用的能力,更能代表這間公司營運能力。
計算營業利益率
營業利益率 = 營業利益 / 營業收入合計
使用 Finlab API:
from finlab import data
operating_income = data.get('financial_statement:營業收入淨額')
operating_profit = data.get('financial_statement:營業利益')
operating_profit_ratio = (operating_profit / operating_income) * 100
其中:operating_income
: 營業收入合計operating_profit
: 營業利益operating_profit_ratio
: 營業利益率
如何評估營業利益率
了解營業利益是評估一間公司營運能力的基礎之後,希望找出營業利益穩定成長,若沒有穩定成長,至少要穩定,因此考慮到季與季之間不能下跌過大。
再來,營業利益率若長期都是負值,再怎麼穩定也是枉然。
因此,對以下條件進行量化:
- 營業利益率連續
seasons
季,下跌不超過某個數值drop
,認為是穩定。 - 營業利益率連續
seasons
季都是正值。
以上的 seasons
與 drop
都是變數。
先把要量化的公式寫出來:
condition1 = (((operating_profit_ratio - operating_profit_ratio.shift(1)) /
abs(operating_profit_ratio.shift(1))).rolling(seasons-1).min()) * 100 > drop
condition2 = operating_profit_ratio.rolling(seasons).min() > 0
position = condition1 & condition2
上式計算出連續 seasons
個季度,季與季之間的變化百分比(例如近 4 季就有 3 次變化)至少高於某個數值 drop
。
def get_return_by_seasons(seasons):
ret = {}
return_dataset = {}
for drop in range(-50, 10, 10):
condition1 = (((operating_profit_ratio - operating_profit_ratio.shift(1)) /
abs(operating_profit_ratio.shift(1))).rolling(seasons-1).min()) * 100 > drop
condition2 = operating_profit_ratio.rolling(seasons).min() > 0
position = condition1 & condition2
report = backtest.sim(position.loc['2014':], resample="M", upload=False)
return_series = report.creturn
return_dataset['seasons'] = seasons
return_dataset[f"{drop}"] = report.creturn
return_dataset = pd.DataFrame(return_dataset)
return return_dataset
return_season_df = pd.concat([get_return_by_seasons(s) for s in [8, 7, 6, 5, 4, 3, 2]])
上面程式碼分別回測連續 2 到連續 8 個季度,季與季之間變化幅度大於 -50%, -40%, -30%, -20%, -10%, 0%
的情形,使用 plotly express 模組,繪製如下:
import plotly.express as px
fig = px.line(return_season_df, facet_row='seasons', height=1000, title='operating profit ratio backtest')
fig.show()
觀察到,season = 4
的圖表,較能看出一字排開的現象,表示近 4 季(3 次變化),可以明顯地區分出各下跌百分比。且報酬率以季與季變化大於 0% 最好。
放大顯示:
營業利益率 v.s. 股價
有了上述的基礎之後,再來想要知道可否運用較少的資金,也就是選擇股價較低但滿足上述條件的公司買入,取得較高的報酬。
因此進一步將股價區分,對不同股價區間的公司作回測,得到最終到報酬率的柱狀圖如下:
close = data.get('price:收盤價')
def get_return_by_percentage(drop):
condition1 = (((operating_profit_ratio - operating_profit_ratio.shift(1)) / abs(operating_profit_ratio.shift(1))).rolling(3).min()) * 100 > drop
condition2 = operating_profit_ratio.rolling(4).min() > 0
price_range = np.arange(0, 120, 10)
ret = []
for s,e in zip(price_range, price_range[1:]):
condition3 = (close > s) & (close < e)
position = condition1 & condition2 & condition3
r = backtest.sim(position.loc['2014':], resample="M", upload=False)
return_series = r.creturn
ret.append({'%':drop, 'price_range':f"{round(s,1)}-{round(e,1)}", 'return':return_series[-1]})
df = pd.DataFrame(ret)
return df
return_percentage_df = pd.concat([get_return_by_percentage(percentage) for percentage in [-50, -40, -30, -20, -10, -5, 0, 5, 10]])
看起來符合預期,在營業利益率季與季之間變化大於 5% 的族群中(倒數第二欄柱狀圖表),股價介於 30~40 間的公司報酬率最好,代表著有機會以較低的資金取得較好的報酬。
將績效曲線繪製出來如下:
進一步過濾:好中選好
只是選擇營業利益穩定的中低股價公司就有不錯的報酬,是否可以進一步過濾出更好的股票呢?
嘗試加入以下兩個條件試試:
- 這季營益率大於上季 ⇒
經營能力變好。 近三月營收平均大於近十二月營收平均 ⇒
- 最近營收成長。
回測績效如下:
結果也進一步地提升績效,看起來是個不錯的策略。
後續探討
- 試圖加入技術指標或其他濾網
- 使用其他指標來對營業利益率的分類:
有些資本密集的公司(晶圓代工),由於股本較大,公司本身對費用控管能力較好,卻屬於中低股價。因此本篇用股價區間來區分難免有些偏頗。
希望可以改用其他指標(本益比、股價淨值比 …),找出資本較小,卻有優異的營運管理能力的公司,在股價相對低點買入,以期獲得更好的報酬。