Finlab 量化平台徵稿活動得獎作品 營業利益率選股-安正

營業利益率選股

原理

營收 (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: 營業利益率

如何評估營業利益率

了解營業利益是評估一間公司營運能力的基礎之後,希望找出營業利益穩定成長,若沒有穩定成長,至少要穩定,因此考慮到季與季之間不能下跌過大。

再來,營業利益率若長期都是負值,再怎麼穩定也是枉然。

因此,對以下條件進行量化:

  1. 營業利益率連續 seasons 季,下跌不超過某個數值 drop,認為是穩定。
  2. 營業利益率連續 seasons 季都是正值。

以上的 seasonsdrop 都是變數。

先把要量化的公式寫出來:

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 間的公司報酬率最好,代表著有機會以較低的資金取得較好的報酬。

將績效曲線繪製出來如下:

進一步過濾:好中選好

只是選擇營業利益穩定的中低股價公司就有不錯的報酬,是否可以進一步過濾出更好的股票呢?

嘗試加入以下兩個條件試試:

  1. 這季營益率大於上季 ⇒

經營能力變好。 近三月營收平均大於近十二月營收平均 ⇒

  1. 最近營收成長。

回測績效如下:

結果也進一步地提升績效,看起來是個不錯的策略。

後續探討

  • 試圖加入技術指標或其他濾網
  • 使用其他指標來對營業利益率的分類:
    有些資本密集的公司(晶圓代工),由於股本較大,公司本身對費用控管能力較好,卻屬於中低股價。因此本篇用股價區間來區分難免有些偏頗。
    希望可以改用其他指標(本益比、股價淨值比 …),找出資本較小,卻有優異的營運管理能力的公司,在股價相對低點買入,以期獲得更好的報酬。

Colab 範例程式碼


28