跳轉到

通用積木(自訂比較)

所屬章節:積木 Block 以 2026-09-29 實際執行結果為準(資料日 2026-09-24,季報 2026-08-14)。 台股 416 個積木(402 原生 + 3 通用 + 11 以通用積木寫成的具名積木)、美股 260 個

Overview

402 顆原生積木每一顆都把窗格、聚合方式、位移、倍數寫死在 class 裡, 任一項不合用就無解——參數合法值清單裡沒有你要的數字、要比的是前一根而積木只比當根、 要連續 N 天而積木寫死 3 天。

通用積木把這些全部參數化,三顆涵蓋絕大多數「原生積木表達不了」的條件:

積木名稱 id 式子
dataA<>dataB+C a1 左 <> 右 + 常數
(dataA-dataB)/abs(dataB)<>C a2 (左 − 右) ÷ |右| <> 常數(變化率)
dataA/abs(dataB)<>C a3 左 ÷ |右| <> 常數(比值)
  • a2 / a3 的分母取絕對值;分母為 0 時視為不成立。
  • a2 / a3 的常數是比值:0.2 = 20%、0.05 = 5%。
  • 美股同名,用 con_us() / screener_us(),但沒有 pool 參數(美股沒有股池)。

不要自己拿 MSMP 算。 自己算出來的布林矩陣不是積木, screener 的 r['code'] 產不出來,也無法與其他積木 AND。

2026-09-29 異動:a2 由「左 − 右 <> 常數」改為變化率——舊式移項後就是 a1, 兩者等價。a3 由「分母需為正」改為分母取絕對值。三顆的名稱也改了, 舊名稱(通用:兩側比較…、通用:兩側相減…、通用:兩側相除…)已不能用, 用 id(con_tw(id='a1', ...))的寫法不受影響。


left / right 能填什麼

格式一律 '表.欄' 單一字串。常用的:

要什麼 填什麼
價量 日_K.Open .High .Low .Close .Volume .漲跌幅 .成交額_百萬 .市值_百萬 .adj_Close
技術指標(現成的,不用自己算) 日_技術指標.K .D .rsi_5 .rsi_6 .rsi_10 .macd_dif .macd_dea .macd_hist .bulin_upper .bulin_lower .bulin_sma .plus_di .minus_di .ADX
三大法人 日_三大法人.外資買賣超張數 .投信買賣超張數 .自營買賣超張數 .三大法人買賣超_張數 .主力買賣超張數
估值 日_指標.本益比 .股價淨值比 .市值 .peg
融資券 日_融資券.融資餘額_張 .融券餘額_張 .券資比
籌碼集中 其他_籌碼集中.籌碼集中度_20日 .主力買賣超張數_5日 .家數差_20日
月營收 月_營收.YoY .MoM .金額_千 .累積YoY
季財報 季財報_財務指標.營業毛利率 .營業利益率 .稅後淨利率 .ROE_稅後權益報酬率 .每股稅後淨利 .負債比率 .營收成長率、季財報_損益表_千.eps(單季)
股利 季股利資訊.年殖利率 .現金股利 .填權息天數(年頻)、.現金股利_季(季頻,依盈餘所屬季;年配息記在 Q4)
週/月頻 週_K.* 月_K.* 週_技術指標.* 月_技術指標.*

美股(con_us):日_K.Open/High/Low/Close/Volume、季財報_財務指標.*、 季營收.YoY、季股利資訊.*。美股沒有 漲跌幅、adj_*、日_技術指標、日_三大法人。

完整清單見 MSMP 資料表與欄位(台股 25 張表、美股 10 張)。


參數

左右兩側對稱,各有一組。left 與 operator 必填,其餘都有預設值。

參數 說明 預設
left / right 資料來源,單一字串 '表.欄',例如 '日_K.Volume'、'日_三大法人.投信買賣超張數'、'季財報_財務指標.營業毛利率' left 必填
operator >、<、>=、<=、==、crossup、crossdown 必填
left_rolling / right_rolling 窗格長度。1 = 不聚合,直接取原值(1 日均量即成交量)。資料不足 N 根時用現有的根數算(min_periods=1) 1
left_agg / right_agg 聚合方式:mean、max、min、sum、std、last mean
left_shift / right_shift 往前位移幾根。1 = 取前一根的值(不含當根) 0
left_pct / right_pct 該側增減百分比。35 = ×1.35、-20 = ×0.8 0
const 常數。a1 加在右側;a2 / a3 是比較門檻,以比值表示 0(a3 為 1.0)
continue_days 窗格 K:連續成立幾根才算數 1
continue_times 近 K 根中至少 M 根成立。不給 = K(全部成立);M > K 時條件恆不成立 None
rank 取排名前 N 名。operator='>' 取大的、'<' 取小的 None(不取)
pool rank 的母體股池,如 ['tse','otc','~etf']。~ 為排除語法 ['all']

單側的完整算式:

該側 = agg( data , rolling ).shift(shift) * (1 + pct/100)

從 get_table_tw() 取回來填

get_table_tw()[名稱] 回傳的是「每個參數的選項清單」,通用積木的自由值參數是 []。 直接拿來填幾格再丟給 con_tw() 是可以的——沒填的格子會自動處理:

  • 值為 [] 的參數視為沒指定,用預設值;
  • left_agg / right_agg 還是整份清單(['mean','max',...])時,一律當 'mean'。
p = api.Block.get_table_tw()['dataA<>dataB+C']
p['left'] = '日_三大法人.投信買賣超張數'
p['operator'] = '>'
p['const'] = 200
api.Block.con_tw(p)
# {'id': 'a1', 'left': '日_三大法人.投信買賣超張數', 'left_rolling': 1, 'left_agg': 'mean',
#  ..., 'const': 200, 'continue_days': 1, 'continue_times': None, 'rank': None, 'pool': None}

Manual Sample Input/Output

Example 1:門檻型——right 不用寫

right 不給時右側視為 0,整條退化成「左 <operator> const」。

input

N = 'dataA<>dataB+C'
b = api.Block.con_tw(N, left='日_K.Volume', operator='>', const=3000)
b

output

{'id': 'a1', 'left': '日_K.Volume', 'left_rolling': 1, 'left_agg': 'mean',
 'left_shift': 0, 'left_pct': 0, 'operator': '>', 'right': None,
 'right_rolling': 1, 'right_agg': 'mean', 'right_shift': 0, 'right_pct': 0,
 'const': 3000, 'continue_days': 1, 'continue_times': None, 'rank': None, 'pool': None}
api.Block.screener_tw([b], pool=['tse','otc','~etf','~KY','~DR'])['dataframe'].shape[0]
# 222

更多門檻型:

# 近 5 日均量 > 2000 張
con_tw(N, left='日_K.Volume', left_rolling=5, left_agg='mean',
       operator='>', const=2000)                                    # 370 檔

# 月營收 YoY > 20%(月頻,自動轉日頻)
con_tw(N, left='月_營收.YoY', operator='>', const=20)                # 793 檔

Example 2:雙側比較——明寫 right

「成交量 > 前一根的 5 日均量 × 1.35」。right_shift=1 讓均量不含當根。

input

b = api.Block.con_tw(N, left='日_K.Volume', operator='>',
                     right='日_K.Volume', right_rolling=5, right_agg='mean',
                     right_shift=1, right_pct=35)
api.Block.screener_tw([b], pool=['tse','otc','~etf','~KY','~DR'])['dataframe'].shape[0]

output

263

同一件事也可以用 a3 寫成比值,結果相同(263 檔):

con_tw('dataA/abs(dataB)<>C',
       left='日_K.Volume', right='日_K.Volume',
       right_rolling=5, right_agg='mean', right_shift=1,
       operator='>', const=1.35)

Example 3:continue_days、continue_times 與 rank

# 前 2~21 天投信買賣超皆為 0
#   left_shift=1 把區間推到前一天起算(不含當日)
#   continue_days=20 要求連續 20 根 -> 正好是「前 2~21 天」
con_tw(N, left='日_三大法人.投信買賣超張數', left_shift=1,
       operator='==', const=0, continue_days=20)

# 近 10 日中至少 7 日投信買超
con_tw(N, left='日_三大法人.投信買賣超張數', operator='>', const=0,
       continue_days=10, continue_times=7)                         # 88 檔

# 三大法人買賣超 > 300 張,取最大的前 30 名
con_tw(N, left='日_三大法人.三大法人買賣超_張數',
       operator='>', const=300, rank=30,
       pool=['tse','otc','~etf','~KY','~DR'])                       # 30 檔

# 收盤價最低的前 20 名(operator='<' 時 rank 取小)
con_tw(N, left='日_K.Close', operator='<', const=9999, rank=20,
       pool=['tse','otc','~etf','~KY','~DR'])                       # 20 檔

continue_times 讓「N 期中至少 M 期」這類計數條件也能表達, 例如「前 8 季至少 6 季 EPS > 0」:

con_tw(N, left='季財報_損益表_千.eps', operator='>', const=0,
       continue_days=8, continue_times=6)

Example 4:a2 變化率——季財報季增

a2 的左右同欄位、右側 shift=1,就是「(本季 − 上季) ÷ |上季|」。

input

b = api.Block.con_tw('(dataA-dataB)/abs(dataB)<>C',
                     left='季財報_財務指標.營業毛利率',
                     right='季財報_財務指標.營業毛利率',
                     right_shift=1, operator='>', const=0)
api.Block.screener_tw([b], pool=['tse','otc','~etf','~KY','~DR'])['dataframe'].shape[0]

output

925

這等價於原生積木 f7 毛利率較上一季成長(衰退),但換成任何一個財報欄位都能用—— 營業利益率、稅後淨利率、ROE_稅後權益報酬率、每股淨值…… 不必等有人刻對應積木。

要求成長幅度時,常數寫比值:

# 單季 EPS 較上季成長 20% 以上
con_tw('(dataA-dataB)/abs(dataB)<>C',
       left='季財報_損益表_千.eps', right='季財報_損益表_千.eps',
       right_shift=1, operator='>', const=0.2)                      # 896 檔

分母取絕對值,所以上季虧損也能正確判斷方向:上季 −0.37、本季 −0.18, 變化率是 +51%(虧損縮小),不會因為分母為負被算成 −51%。


Example 5:a3 相除——佔比

# 投信買賣超 / 成交量 > 5%
con_tw('dataA/abs(dataB)<>C',
       left='日_三大法人.投信買賣超張數', right='日_K.Volume',
       operator='>', const=0.05)                                    # 49 檔

分母取絕對值,分母為 0 的格子一律不成立。


33 條實測:通用積木能表達什麼

下列全部以通用積木組成並實跑通過(資料日 2026-09-24,季報 2026-08-14, pool 為上市櫃排除 ETF ['tse','otc','~etf'])。 過去以為只能用原生積木的技術指標類,因為 日_技術指標 表早有現成欄位,直接比即可。

條件 積木與參數 檔數
K > 80 a1, left='日_技術指標.K', operator='>', const=80 145
K < 20 同上 operator='<', const=20 181
RSI6 > 80 left='日_技術指標.rsi_6', operator='>', const=80 95
MACD 柱 > 0 left='日_技術指標.macd_hist', operator='>', const=0 1228
MACD 黃金交叉 left='日_技術指標.macd_dif', operator='crossup', right='日_技術指標.macd_dea' 66
KD 黃金交叉 left='日_技術指標.K', operator='crossup', right='日_技術指標.D' 105
ADX > 25 left='日_技術指標.ADX', operator='>', const=25 613
穿越布林上軌 left='日_K.Close', operator='crossup', right='日_技術指標.bulin_upper' 54
MACD 紅柱連 3 日遞增 a2, left/right='日_技術指標.macd_hist', right_shift=1, const=0, continue_days=3 704
ROE > 15% left='季財報_財務指標.ROE_稅後權益報酬率', operator='>', const=15 51
毛利率 > 30% left='季財報_財務指標.營業毛利率', operator='>', const=30 786
營收 YoY > 20% left='月_營收.YoY', operator='>', const=20 853
營收連 3 月 YoY > 0 同上 const=0, continue_days=3 1057
營收 MoM > 10% left='月_營收.MoM', operator='>', const=10 412
本益比 < 15 left='日_指標.本益比', operator='<', const=15 1015
股價淨值比 < 1 left='日_指標.股價淨值比', operator='<', const=1 371
殖利率 > 5% left='季股利資訊.年殖利率', operator='>', const=5 2(見注意事項 7)
EPS 季增 a2, left/right='季財報_財務指標.每股稅後淨利', right_shift=1, const=0 1231
近四季 EPS > 0 left='季財報_損益表_千.eps', left_rolling=4, left_agg='sum', operator='>', const=0 1537
前 8 季至少 6 季 EPS > 0 left='季財報_損益表_千.eps', operator='>', const=0, continue_days=8, continue_times=6 1293
現金股利發放率 > 30% 且近四季 EPS > 0 a3, left='季股利資訊.現金股利_季', right='季財報_損益表_千.eps',兩側 rolling=4, agg='sum'、const=0.3,再 AND 上一列 1154
營收創 12 月新高 left/right='月_營收.金額_千', right_rolling=12, right_agg='max', right_shift=1 326
漲幅 > 5% left='日_K.漲跌幅', operator='>', const=5 56
漲停(>9.9%) 同上 const=9.9 15
連 3 日上漲 同上 const=0, continue_days=3 86
收紅 K left='日_K.Close', operator='>', right='日_K.Open' 903
創 20 日新高 left/right='日_K.Close', right_rolling=20, right_agg='max', right_shift=1 166
破 20 日低 同上 right_agg='min', operator='<' 121
5 日線 > 20 日線 left_rolling=5, left_agg='mean', right_rolling=20, right_agg='mean' 996
黃金交叉 5/20 同上 operator='crossup' 106
振幅 > 3% a3, left='日_K.High', right='日_K.Low', const=1.03 504
成交額前 100 大 left='日_K.成交額_百萬', const=0, rank=100, pool=[...] 100

什麼時候才回頭找原生積木

MSMP 沒有現成欄位、而且 rolling / shift / continue 組不出來的語意: 「連續 N 年配息」「葛林布雷排名」「分點籌碼」「產業分類」, 以及需要「年度對年度」對齊的比值。

「近四季累計」(rolling=4, agg='sum')、「最近 N 期至少有 M 期…」(continue_times)、 「填權息天數」(季股利資訊.填權息天數,年頻)現在都能用通用積木寫。


這三顆解掉的實際問題

需求 原生積木卡在哪 通用積木怎麼寫
成交量 > 前一根 5 日均量 ×1.35 v5x 有倍數但無 shift(5ma 含當根)、v11 有 shift 但倍數固定 1.0、v12 兩者都有但 agg 固定 max right_rolling=5, right_agg='mean', right_shift=1, right_pct=35
前 2~21 天投信買賣超皆為 0,且當日 >50 s6x 的 rolling(D).sum()==D 含當日,與「當日 >50」互斥,AND 必為 0 檔;days 合法值也沒有 21 left_shift=1, operator='==', const=0, continue_days=20
三大法人買賣超 >300 張取前 30 排行榜積木無法表達「先篩門檻再排序取 N」;s1 的 n 合法值也沒有 300 operator='>', const=300, rank=30, pool=[...]
近 5 日均量 > 2000 張 v37 的 threshold 最低 20000 left_rolling=5, left_agg='mean', const=2000
任一財報欄位的季增 只有毛利率/營益率/稅後淨利率有 f7/f11/f15,其餘欄位沒有 a2 左右同欄位、right_shift=1、const=0
近 N 日中至少 M 日投信買超 s13 只有「超過 N 日」且選項固定 continue_days=N, continue_times=M

以通用積木寫成的具名積木

Excel 規格上有一批積木是用 a1 / a3 寫成的:python參數把資料來源、agg、shift 等釘死, 只開放少數參數。用法與原生積木相同,con_tw(名稱, ...) 會自動補上釘死的值。

積木名稱 id 開放的參數
投信買賣超(張數)連續P日<>N張 a1 continue_days、operator、const
近N個交易日投信買賣超(千股)<>V a1 left_rolling、operator、const
近N個交易日三大法人買賣超合計(千股)>0 a1 left_rolling
連續N年(不含今年)填權息天數皆<M天 a1 continue_days、const
營業利益率(%)>上一季 a1 (無)
稅後純益率(%)>上一季 a1 (無)
券資比<>N%的Top20清單 a1 operator、const
前N季EPS至少有M季<>V元 a1 continue_days、continue_times、operator、const
近N個交易日投信買超天數>=M天 a1 continue_days、continue_times
現金股利發放率>N% a3 const(比值,0.05 = 5%)
近四季EPS>0 a1 (無)
api.Block.con_tw('前N季EPS至少有M季<>V元', continue_days=8, continue_times=6,
                 operator='>', const=0)
# {'id': 'a1', 'continue_days': 8, 'continue_times': 6, 'operator': '>', 'const': 0,
#  'left': '季財報_損益表_千.eps'}

現金股利發放率>N% 的分母取絕對值,近四季 EPS 為負、仍有配息的公司也會通過 (實測發放率 > 5% 的 1611 檔中有 91 檔)。要排除就 AND 上 近四季EPS>0。


七個會咬人的地方

1. rank 的 pool 要和 screener_tw(pool=...) 填一樣

rank 是橫斷面運算,母體不同結果就不同。與 ranking.py 一致, 通用積木在排名之前先套 pool。

只在 screener_tw 給 pool、而 con_tw 沒給時,會先在全市場排完前 30, 再被 screener 切掉不在池內的,最後拿到的不足 30 檔:

con_tw 不給 pool(預設全市場)-> 30 檔,落在 tse+otc-etf/KY/DR 內只剩 13 檔
con_tw 給 pool=['tse','otc','~etf','~KY','~DR'] -> 完整 30 檔

2. 「排除 X < N」要寫成 > N

積木是「留下符合的」,不是「排除符合的」。 共同定義寫「排除近 5 日均量 < 2000 張」,積木要寫 operator='>', const=2000。 寫成 < 會變成只留下低量股(實測 1487 檔 vs 正確的 370 檔)。

3. 「為 0」是 ==,不是 <

< 是賣超、> 是買超、== 才是沒有買賣。 「連續 20 天投信買賣超為 0」寫成 operator='<', const=0, continue_days=20 會變成「連續 20 天都賣超」——現實中幾乎不存在,實測 0 檔, 而正確寫法有 1345 檔。

4. 月/季/年頻資料自動轉日頻

季財報_財務指標.*、月_營收.*、季股利資訊.* 的 index 是字串 ('2026-Q2'、'2026-M08'),不轉的話 screener 會拋:

TypeError: '<=' not supported between instances of 'str' and 'Timestamp'

通用積木內部已用 index_str_to_date().ffill() 轉成公告日 (月頻→次月 10 日、季頻→財報公告日)並自動遮蔽下市後的資料, sig 與所有中間值都會轉,直接餵給 screener 不會炸,也不需要自己處理頻率。 最新一季沒有資料的股票(未公告、下市)不會沿用舊值,條件不成立。

5. a2 / a3 的常數是比值,不是百分比

「成長 20%」寫 const=0.2,不是 20。a1 的 const 則是原始單位 (張、元、%——看欄位本身)。

6. 左右兩側頻率不同時,對齊的是「各自最新公告值」

ProboDataFrame 會把兩側都轉成公告日再對齊,所以跨頻率算得出數字, 但不保證是同一期間。例如 季股利資訊.現金股利(年頻)÷ 季財報 EPS, 2330 在 2026-05-15 會算出 22.00 ÷ 22.08 = 99.6%——2025 全年股利除以 2026Q1 累計 EPS, 數字看起來合理但是錯的。兩側盡量用同頻率的欄位(例如 現金股利_季 配 eps)。

7. 季股利資訊.年殖利率 的今年列目前多為 0

2026-Y 這一列已經出現,但大多數股票的值是 0 或不完整(2412、2884、1101 為 0.0, 2330 為 0.29)。它的公告日一到就蓋過 2025 年的值,所以「殖利率 > 5%」 現在只剩 2 檔(全市場看 2025-Y 那一列,> 5% 的有 390 檔)。這是資料源的狀態,不是積木的問題; 要用去年殖利率就加 left_shift=1。


完整範例:三個策略

N = 'dataA<>dataB+C'
B = api.Block
POOL = ['tse', 'otc', '~etf', '~KY', '~DR']

# ── 量能強 ────────────────────────────────────────────────
# 昨日成交量 > 3000 張,且最新一根量 > 第二根 5 日均量 ×1.35
b1 = B.con_tw(N, left='日_K.Volume', left_shift=1, operator='>', const=3000)
b2 = B.con_tw(N, left='日_K.Volume', operator='>',
              right='日_K.Volume', right_rolling=5, right_agg='mean',
              right_shift=1, right_pct=35)
B.screener_tw([b1, b2], pool=['tse','otc','~etf'])          # 32 檔

# ── 外資關注 ──────────────────────────────────────────────
# 近5日均量 >2000 張,前 2~21 天投信買賣超皆為 0,當日投信買超 >50 張
c1 = B.con_tw(N, left='日_K.Volume', left_rolling=5, left_agg='mean',
              operator='>', const=2000)
c2 = B.con_tw(N, left='日_三大法人.投信買賣超張數', left_shift=1,
              operator='==', const=0, continue_days=20)
c3 = B.con_tw(N, left='日_三大法人.投信買賣超張數', operator='>', const=50)
B.screener_tw([c1, c2, c3], pool=POOL)   # 當日 0 檔,全期 460 次 / 262 個交易日

# ── 三大法人買超 Top30 ────────────────────────────────────
d1 = B.con_tw(N, left='日_三大法人.三大法人買賣超_張數',
              operator='>', const=300, rank=30, pool=POOL)
B.screener_tw([d1], pool=POOL)                              # 30 檔

「外資關注」當日 0 檔是真的沒有符合的股票(②有 1361 檔、③有 60 檔, 交集為空),不是積木壞了——sig 形狀 (1000, 1877) 欄數正常。 全期回看有 460 次訊號,最近一次是 2026-09-08 的大銀微系統(4576)。 判讀方式見 已知落差與注意事項。


餵進回測

通用積木產生的 sig 與原生積木格式完全相同(日期 × 股票代號的 bool 矩陣), 回測配方原樣適用:

r = B.screener_tw([c1, c2, c3], pool=POOL)
df['sig'] = df['DateTime'].map(r['sig'][SYM]).fillna(False)
core.feed_data(df, ['DateTime','開盤價','最高價','最低價','收盤價','成交量'],
               columns=['sig'])          # 額外欄位一定要宣告

完整骨架見 回測總覽 與 配方集 的配方二。

選股結果不要直接當回測標的——用今天選出的股票回測過去是 look-ahead。 要嘛用固定清單,要嘛用 sig 逐日決定進出場。


相關文件