通用積木(自訂比較)
所屬章節:積木 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'] |
單側的完整算式:
從 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
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}
更多門檻型:
# 近 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
同一件事也可以用 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」:
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
這等價於原生積木 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 會拋:
通用積木內部已用 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逐日決定進出場。
相關文件
- 積木 Block 總覽
- 組積木 con
- 執行選股 screener
- 股池 pool
- 已知落差與注意事項
- MSMP 資料表與欄位完整清單:
left/right能填哪些表.欄 - 台股積木完整參考
- 美股積木完整參考