此文章為VIP限定
月營收是台股獨特的基本面因子,操作台股的玩家,都知道月營收的影響性,常可先預判季財報,究竟月營收選股條件要怎麼寫?這篇就用超簡單的語法,結合「突破策略豆知識」技巧寫出15年來年化報酬37%的高報酬率策略,讓你迅速上手月營收突破這個台股經典的策略。
月營收選股策略 – 創高基礎型態
程式範例
from finlab import data
from finlab.backtest import sim
rev = data.get('monthly_revenue:當月營收')
rev_yoy_growth = data.get('monthly_revenue:去年同月增減(%)')
# 近2月平均營收
rev_ma = rev.average(2)
# 近2月平均營收創12個月來新高
condition = rev_ma == rev_ma.rolling(12, min_periods=6).max()
# 單月營收年增率與訊號相乘,若訊號為False,相乘結果為0
position= rev_yoy_growth*condition
# 符合選股條件的名單中,再選出單月營收年增率前10強,並在營收公告截止日換股。
position=position[position>0].is_largest(10)
report=sim(position ,name="營收動能",upload=False)
report.display()
程式非常簡單,只有10來行。說明幾個關鍵處。
月營收有個關鍵在要注意過年效應,有時候年假在1月、有時在2月中旬前,由於台股多數企業的營運區域都在華人地區,年假通常會停工或減班,如果是在1月過年,訂單常遞延到2月。因此在比較營收時,為了平滑過年效應,我會習慣取近2月平均營收來取代單月營收,不然1、2月若碰到年假,單月營收要創近12月新高的可能性實在太低。
如果策略要實戰,縮減檔數是必須的,因為多數人沒那麼多資金全買,所以範例簡單用單月月營收來選最後10強。你可能會疑惑為何要寫position[position>0].is_largest(10)
,而不是position.is_largest(10)
就好?那是因為is_largest(10)
會選數值前10高的,若沒有排除不符合營收創高的標的,前10高的數值可能會選到不合條件的,會從股號小的標的開始補到10檔為止。
此策略的換股週期是用月營收索引,也就是月營收公告截止日,好處是貼近營收因子發酵期。
來看看策略的表現,非常不錯,15年來,年化高達 26% !
你以為這樣就結束了嗎?其實還可以優化,繼續看下去。
營收股價雙創高的渦輪效應
股價創新高動能
營收利多,最怕利多不漲,那很可能是市場早就反應,因此我們的目標是股價與營收連動,一同創高,代表股價的動能和營收創新高的關聯較大,如何寫出好棒棒的股價創新高動能策略,可以看先前的文章「突破策略豆知識」,運用FinlabDataframe好用的sustiain方法確認突破強度。
程式與回測結果如下:
from finlab.backtest import sim
from finlab import data
with data.universe(market='TSE_OTC'):
close = data.get("price:收盤價")
# 近5日內有3日以上的股價創前200日新高
position = (close == close.rolling(200).max()).sustain(5,2)
report = sim(position, resample="M", name="創新高延續動能策略", upload=False)
report.display()
雙渦輪
如果把營收創新高和股價創新高的程式做結合,會發生什麼事?
from finlab import data
from finlab.backtest import sim
close = data.get('price:收盤價')
vol = data.get('price:成交股數')
rev = data.get('monthly_revenue:當月營收')
rev_yoy_growth = data.get('monthly_revenue:去年同月增減(%)')
# 近2月平均營收
rev_ma = rev.average(2)
# 近2月平均營收創12個月來新高
condition1 = rev_ma == rev_ma.rolling(12, min_periods=6).max()
# 近5日內有2日股價創新高
condition2 = (close == close.rolling(200).max()).sustain(5,2)
# 近五日成交均量大於500張
condition3 = vol.average(5) > 500*1000
conditions = condition1 & condition2 & condition3
# 符合選股條件的名單中,再選出單月營收年增率前10強,並在營收公告截止日換股。
position= rev_yoy_growth*conditions
position=position[position>0].is_largest(10).reindex(rev.index_str_to_date().index, method='ffill')
# 設定position_limit避免重壓
report = sim(position=position,stop_loss=0.2,take_profit=0.8, position_limit=0.25, fee_ratio=1.425/1000*0.3,name="營收股價雙渦輪",upload=False)
report.display()
上面程式要注意的小地方是有加上流動性條件,只買近五日成交均量大於500張的標的,避免太冷門的標的很難入手。由於月營收創高和股價創高的條件交集後,會轉成日週期索引,若要使用月營收截止日換股,必須要做reindex的動作,rev.index_str_to_date
會將月營收文字格式日期轉成月營收公告截止日,例如 2022-M10 變成 2022-11-11。
最後為避免重壓標地,設定position_limit=0.25,代表單擋持股上限為25%,避免只選到1-2檔全壓的大波動狀況。如果偏好降低波動,也可調成0.1,如此一來若只選到4檔標的,總持股比例只會有40%,在熊市時由於創新高標的本來就少,因此持股比例自然降低,具自然避險躲空頭的效果。
停損不建議設定小,此策略波動較大,設太小容易被洗掉。最後來看看加乘的效果吧!
15年來年化報酬37%!夏普率1.3,幾乎每一年都獲利,比前述單因子選股的表現優異許多,熊市年 2022 竟然還有16%正報酬,以條件這麼簡單的策略來說,算是很神的成績!
結論
colab 範例
量化平台部署範例
簡單卻很有實戰性,這就是FinLab與其他地方不同之處,不走太學術的路線,考慮實戰面,除了完整公開程式碼,還跟你講策略背後的思維。
此策略當然還有優化的空間~但這就是大家各自的功課了,Ben 把最核心的條件交給你了,一起去找股海的大秘寶吧!