# Finlab 投稿 ## 營業利益率選股 ### 原理 **營收 (Revenue)** 是一間公司「做多少生意」的指標,然而做多少生意不表示賺多少錢,營收再高也不代表賺更多的錢,中間還要扣除銷貨成本得到的**營業毛利 (Gross profit)**,營業毛利扣掉費用後得到的**營業利益 (Operating profit)**,而後還有營業外支出,利息與所得稅等等,東扣西扣最後才是代表轉多少錢的指標**稅後淨利 (Net income)**。 先不論一間公司在外面做多少生意,繳多少利息與所得稅等等這些支出,衡量公司賺錢真本事的剩下營業毛利與營業利益這兩個指標。而營業毛利取決於行業類別,例如航運與鋼鐵業,毛利平均 10% 左右;晶圓代工與光學鏡頭,毛利平均在 40% 以上,難以用一個數字去衡量所有公司的好壞。而營業利益表是一間公司控管費用的能力,更能代表這間公司營運能力。 ### 計算營業利益率 :::success 營業利益率 = 營業利益 / 營業收入合計 ::: 使用 **Finlab API**: ```python 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```: 營業利益率 ### 如何評估營業利益率 了解營業利益是評估一間公司營運能力的基礎之後,希望找出營業利益穩定成長,若沒有穩定成長,至少要穩定,因此考慮到季與季之間不能下跌過大。 再來,營業利益率若長期都是負值,再怎麼穩定也是枉然。 因此,對以下條件進行量化: :::info 1. 營業利益率連續 `seasons` 季,下跌不超過某個數值 `drop`,認為是穩定。 2. 營業利益率連續 `seasons` 季都是正值。 以上的 `seasons` 與 `drop` 都是變數。 ::: 先把要量化的公式寫出來: ```python 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`。 ```python 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** 模組,繪製如下: ```python import plotly.express as px fig = px.line(return_season_df, facet_row='seasons', height=1000, title='operating profit ratio backtest') fig.show() ``` ![](https://i.imgur.com/NMSWGv8.png) 觀察到,`season = 4` 的圖表,較能看出一字排開的現象,表示近 4 季(3 次變化),可以明顯地區分出各下跌百分比。且報酬率以季與季變化大於 0% 最好。 放大顯示: ![](https://i.imgur.com/MSzf1kE.png) ### 營業利益率 v.s. 股價 有了上述的基礎之後,再來想要知道可否運用較少的資金,也就是選擇股價較低但滿足上述條件的公司買入,取得較高的報酬。 因此進一步將股價區分,對不同股價區間的公司作回測,得到最終到報酬率的柱狀圖如下: ```python 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]]) ``` ![](https://i.imgur.com/IWxIwZB.png) 看起來符合預期,在營業利益率季與季之間變化大於 5% 的族群中(倒數第二欄柱狀圖表),股價介於 30~40 間的公司報酬率最好,代表著有機會以較低的資金取得較好的報酬。 將績效曲線繪製出來如下: ![](https://i.imgur.com/H3z9V5V.png) ### 進一步過濾:好中選好 只是選擇營業利益穩定的中低股價公司就有不錯的報酬,是否可以進一步過濾出更好的股票呢? 嘗試加入以下兩個條件試試: :::info 1. 這季營益率大於上季 $\Rightarrow$ 經營能力變好。 2. 近三月營收平均大於近十二月營收平均 $\Rightarrow$ 最近營收成長。 ::: 回測績效如下: ![](https://i.imgur.com/dSUTkGg.png) 結果也進一步地提升績效,看起來是個不錯的策略。 ## 後續探討 * 試圖加入技術指標或其他濾網 * 使用其他指標來對營業利益率的分類: 有些資本密集的公司(晶圓代工),由於股本較大,公司本身對費用控管能力較好,卻屬於中低股價。因此本篇用股價區間來區分難免有些偏頗。 希望可以改用其他指標(本益比、股價淨值比 …),找出資本較小,卻有優異的營運管理能力的公司,在股價相對低點買入,以期獲得更好的報酬。 ## Colab 範例程式碼 https://colab.research.google.com/drive/1Fx7NoJHHICHSXMJ2_Ny6x6ien1UpPeLs?usp=sharing