本篇階段:Prj#5 股票分析
使用介面:Claude Code(via VS Code)
前兩天把總經那一層做完了:二十四條序列、兩層評分、一堆「算不出來就說算不出來」的處置。今天換一個層級,看大盤跟追蹤大盤的 ETF。
這一天的主軸從一個數字開始,雖然我沒有賺到,近一年的報酬:
^TWII +89.8%
0050.TW +103.4%
006208.TW +98.9%
第一組差 13.6 個百分點,第二組差 4.5 個百分點;而美股那邊,^GSPC +17.8%、SPY +18.4%、VOO +18.4%,差 0.6 個百分點。
同樣是「指數與追蹤它的 ETF」,兩個市場的差距仍相差超過 20 倍(22.7 倍)。今天會花篇幅解釋,並且把五個技術面維度的算式展開來說明與討論、然後是籌碼面的相關問題。
重要說明:本文所有數字與敘述僅為個人技術實作紀錄,不構成投資建議。
今天這張表上有三種東西,性質完全不同,先分清楚:
| 是什麼 | 能不能買賣 | |
|---|---|---|
^TWII 加權指數 |
一個計算出來的數字,涵蓋整個上市市場八百多檔 | 不能 |
| 臺灣 50 指數 | 另一個計算出來的數字,只涵蓋五十檔 | 不能 |
0050、006208 |
追蹤臺灣 50 指數的 ETF,是真正在市場上換手的標的 | 能 |
0050 追蹤的是臺灣 50 指數,不是加權指數。
而 yfinance 抓不到臺灣 50 指數,^TAIEX、^TW50 都試過,所以拿 ^TWII 當對照本身就是一個近似。0050 追的那個籃子,跟加權指數不是同一個籃子。
加權指數涵蓋整個上市市場八百多檔,臺灣 50 指數只有五十檔,而且按市值加權,前幾大權值股的權重極高。所以 0050 重壓在少數幾檔上,加權指數則被幾百檔中小型股稀釋。13.6 個百分點就是這件事的量化版本。
美股那邊 SPY、VOO 追的就是 ^GSPC 本身,同一個籃子,所以差距只剩追蹤誤差與費用,0.6 個百分點。這兩組不是同一種比較。 台股那組比的是「兩個不同的籃子」,美股那組比的是「同一個籃子的兩種包裝」。
順帶把「類群」這件事講掉:0050 是市值加權的籃子,不是產業籃子。它重壓半導體是市值造成的結果,不是選股邏輯。這套東西沒有做產業別分類,理由是這條管線的來源裡沒有一份穩定的產業別欄位:證交所的產業分類與 yfinance 的 sector 對不起來。
這一組才是真正的同類比較:0050 與 006208 追蹤同一個臺灣 50 指數,一年差 4.5 個百分點。
追蹤誤差是那五項 ETF 特有指標裡唯一算得出來的,而它的實作只有三行,重點在那個 guard:
def tracking_error(etf_closes, index_closes) -> float | None:
"""追蹤誤差:同一段期間 ETF 與指數的報酬差(百分點)。
兩條序列長度不同就回 None,不猜怎麼對齊!
錯開一天的對齊會安靜地產生一個看起來合理的錯數字,比沒有數字糟。
"""
e = _clean(etf_closes)
i = _clean(index_closes)
if len(e) != len(i) or len(e) < 2 or e[0] == 0 or i[0] == 0:
return None
return ((e[-1] / e[0]) - (i[-1] / i[0])) * 100.0
差在哪,只列得出候選,給不了比例:內扣費用、追蹤方式與再平衡時點、配息與除息的處理、折溢價、規模與流動性。
這五項裡,這條管線只算得出追蹤誤差。其餘四項需要淨值、公開說明書的費用率、成分股異動公告、除息公告,都不在這條管線的來源裡。所以程式在那四格回的不是估計值:
_ETF_GAPS = ("內扣費用", "折溢價", "成分股調整", "配息與除息")
def etf_data_gaps(symbol: str) -> dict[str, str]:
"""這個標的有哪些 ETF 特有指標算不出來。指數沒有這些項目,回空 dict。"""
if symbol.startswith("^"):
return {}
return {name: INSUFFICIENT for name in _ETF_GAPS}
這跟 SPCX 中長期評分的處置一致:算不出來就說算不出來。
變的是「近一年」這個視窗,它每天往前滾一天,掉出一天舊的、補進一天新的。掉出去的那天如果漲幅特別大,整條的年報酬就會位移。
技術面這套只用五個維度,而且刻意都是位置與幅度類的:
| 維度 | 看什麼 | 警示條件 |
|---|---|---|
| MA 排列 | MA5 / MA20 / MA60 的相對位置 | 空頭排列(5 < 20 < 60) |
| RSI(14) | 相對強弱 | ≥ 70 或 ≤ 30,兩端都算 |
| 布林通道 | 收盤在通道裡的位置 | 收在帶外(> 1 或 < 0) |
| 年化波動度 | 近 20 日相對長期的倍數 | 超過 1.5 倍 |
| 距 52 週高點回撤 | 離高點多遠 | 低於 -10% |
表格是結論,底下這幾節是算式。指標模組是純函式、零 I/O、零 UI 相依,所以每一個都可以拿一段手算過的資料直接對答案。
這一條是整個模組的地基,比任何一個指標都先決定。
def _window(series, end: int, size: int) -> list[float] | None:
"""取 series[end-size+1 : end+1],任何一格不可用就回 None。"""
start = end + 1 - size
if start < 0:
return None
out: list[float] = []
for v in series[start : end + 1]:
if v is None:
return None
f = float(v)
if math.isnan(f):
return None
out.append(f)
return out
窗口裡只要有一格是 None 或 NaN,整格回 None。
差別在下游。NaN 會安靜地傳染:它參與任何算術的結果還是 NaN,一路傳到平均、傳到分數,最後在頁面上變成一個 nan 字樣,或者更糟,被某一層 fillna(0) 接住,變成一個看起來正常的 0。None 不會,它在顯示層明確變成「—」,在評分層則會被算成「這個維度算不出來」。
RSI 常被講成「N 日內漲幅平均除以跌幅平均」,但 Wilder 的原始定義不是單純的移動平均,是遞迴平滑:第一格用前 14 根變動的簡單平均起手,之後每一格都是舊值權重 13、新值權重 1。
avg_gain = sum(gains[:period]) / period
avg_loss = sum(losses[:period]) / period
out[period] = _rsi_from(avg_gain, avg_loss)
for i in range(period, len(gains)):
avg_gain = (avg_gain * (period - 1) + gains[i]) / period
avg_loss = (avg_loss * (period - 1) + losses[i]) / period
out[i + 1] = _rsi_from(avg_gain, avg_loss)
這個差別是會被看見的:用簡單移動平均算出來的「RSI」跟 Wilder 版本在同一段資料上可以差好幾點,而兩者都會被叫做 RSI(14)。要嘛照原始定義寫,要嘛就得在文件上講清楚自己算的是別的東西。
另外那個邊界:跌幅平均為零的時候不能直接除。
def _rsi_from(avg_gain: float, avg_loss: float) -> float:
if avg_loss == 0:
return 100.0 if avg_gain > 0 else 50.0
if avg_gain == 0:
return 0.0
return 100.0 - 100.0 / (1.0 + avg_gain / avg_loss)
連續十四天沒有一天下跌,RSI 是 100。而十四天完全沒動(漲跌都是零),回的是 50 不是 100,因為沒有動靜的東西,不該被讀成極度強勢。
跟昨天景氣對策信號那格同一個處理。
if r >= RSI_HIGH:
return True, f"RSI(14) {r:.1f} ≥ {RSI_HIGH:.0f}"
if r <= RSI_LOW:
return True, f"RSI(14) {r:.1f} ≤ {RSI_LOW:.0f}"
return False, f"RSI(14) {r:.1f}"
把 RSI 30 記成「好」、70 記成「壞」(或反過來),等於偷偷塞進一個方向判斷,而這套東西的定位是量測不是指示,所以兩端都只記「落在不尋常的地方」,不說那是好是壞。
代價是分數會變鈍:一檔剛從超賣反彈的標的,跟一檔衝到超買的標的,可能拿到一樣的分數。
布林通道是中線加減 N 個標準差,N 慣例取 2。實作上有一個容易被略過的決定,標準差要用母體(除以 n)還是樣本(除以 n−1):
mean = mid[i]
var = sum((x - mean) ** 2 for x in w) / period
sd = math.sqrt(var)
upper.append(mean + num_std * sd)
lower.append(mean - num_std * sd)
除的是 period,也就是 ddof=0,母體標準差,理由是這裡的 20 根不是從某個母體抽出來的樣本,它就是要描述的那 20 根本身,這也跟多數看盤軟體的畫法一致;改用樣本標準差的話通道會稍微寬一點,帶外的判定就會變鬆。
然後把「收盤在通道裡的位置」正規化成 0 到 1:
width = upper[-1] - lower[-1]
if width == 0:
return 0.5
return (float(last) - lower[-1]) / width
貼下軌是 0、貼上軌是 1,所以 > 1 才叫收在帶外。用比例而不是絕對價差的好處是:這個數字在加權指數(四萬多點)跟 0050(一百多元)上是同一種判斷方式。
模組裡還算了帶寬(上下軌距離除以中線),但它沒有進評分。帶寬量的是「最近波動的收放」,而那件事已經被波動度那個維度量過一次了,兩個放進同一組等權的分數會讓波動被記兩次,留著它是因為圖上用得到。
rets = [math.log(clean[i] / clean[i - 1]) for i in range(1, len(clean))]
mean = sum(rets) / len(rets)
var = sum((r - mean) ** 2 for r in rets) / len(rets)
return math.sqrt(var) * math.sqrt(periods_per_year)
三個決定各講一句。用對數報酬不用簡單報酬,因為對數報酬可加:兩天的對數報酬相加就是兩天的總對數報酬,簡單報酬不行,而年化本質上就是在把日報酬加起來。乘 sqrt(252) 是波動隨時間以根號成長的標準假設,252 是美股一年的交易日數。缺值那一段報酬直接跳過不補值:把缺的那天補成前一天會製造一個假的零報酬,而零報酬會壓低波動度。
那個警示門檻刻意是倍數不是絕對值:近 20 日相對長期水準超過 1.5 倍才警示。台股與美股的常態波動度本來就不同,寫死一個「年化 30% 以上算高」等於偷偷假設兩個市場一樣。用標的自己的長期水準當分母,這個標準對兩邊才會都成立。
技術指標還有一大票,這裡一次交代為什麼停在五個(這邊是 Claude 給出的理由):
True / False。把一連串主觀判斷壓縮成一個布林值,下游就完全看不出那個 True 有多勉強。五個位置與幅度類的維度至少可以直接印出「RSI 62.7」讓人自己判斷。前三個不是因為沒用被刪掉,是因為跟已經收進來的維度共線。五個等權的維度裡混進兩個量同一件事的,等於偷偷加權而沒有說。
分數的算法跟總經那層一樣簡單:未警示維度的比例 × 100。
verdicts = {k: fn(closes) for k, fn in TECHNICAL.items()}
usable = {k: v for k, v in verdicts.items() if v[1] != INSUFFICIENT}
if not usable:
return IndexScore(symbol=symbol, score=None, valid=0)
hit = [k for k, (alert, _) in usable.items() if alert]
return IndexScore(
symbol=symbol,
score=100.0 * (1.0 - len(hit) / len(usable)),
valid=len(usable),
...
)
兩個地方值得指出來。第一,算不出來的維度不計入分母,所以回傳裡有一個 valid 欄位記著這次是幾個維度算出來的:80 分在五個維度跟在三個維度底下是兩件事,不把 valid 一起帶出去就分不出來。第二,每個維度的 subscore 只有 0.0 或 1.0,不看警示得多嚴重。RSI 71 跟 RSI 95 同樣記一次警示。
^TWII 最近五個交易日(2026-09-09 跑):
| 日期 | 分數 | 警示 |
|---|---|---|
| 2026-09-03 | 100.0 | 無 |
| 2026-09-04 | 100.0 | 無 |
| 2026-09-07 | 80.0 | 布林通道 |
| 2026-09-08 | 100.0 | 無 |
| 2026-09-09 | 100.0 | 無 |
簡單平均 96.0、加權平均 96.0(權重 D-4 10%、D-3 15%、D-2 20%、D-1 25%、D-0 30%)。
最新一日的分項長這樣:
ma_stack 多頭排列 MA5 46,804.84 > MA20 45,960.65 > MA60 45,196.84
rsi RSI(14) 61.3
bollinger 通道內位置 0.90
volatility 近 20 日年化波動 16.4%,長期 27.1%(以 243 個交易日代替 52 週,已降級),0.60 倍
drawdown 距 52 週高點 -1.2%(以 243 個交易日代替 52 週,已降級)
每個維度回的不只是布林值,是一個 (是否警示, 理由字串) 的組合,而理由字串裡帶著算出來的實際數值。
每一個門檻都是 Claude 選的,沒有一個是最佳化出來的。 我看過並同意,但那不改變它們的來源:70/30 是 Wilder 的慣例值、1.5 倍是「明顯比平常吵」的直覺、−10% 是常見的「修正」門檻、五個維度等權是暫時的方式。
跟「拿歷史資料回歸、按係數配權重」比,這個做法的誠實度差在哪?差在它沒有假裝自己有依據。回歸出來的權重看起來科學,但它是對過去那段特定期間最佳化的結果,換一段期間就變(其實兩種都不可靠)。
分項裡那句「以 242 個交易日代替 52 週,已降級」是實作時撞到的。
台股一年只有 243 到 244 根日線,美股 252 根。回看視窗原本寫死成 250 根,結果台股的波動度與回撤這兩個維度永遠算不出來。分數照樣算得出來(分母從 5 變成 3),看起來毫無異狀,但台股的分數與美股的分數量的根本不是同一組東西。(之後可能會再調整這部分的實作)
def _lookback(v: list[float]) -> tuple[list[float], str] | None:
"""取回看視窗,並回一段說明它是不是降級的。不足底線回 None。"""
if len(v) < LOOKBACK_MIN:
return None
window = v[-LOOKBACK_FULL:]
if len(window) < LOOKBACK_FULL:
return window, f"(以 {len(window)} 個交易日代替 52 週,{DEGRADED})"
return window, ""
三種結果:足額就是 52 週;不足額降級,而且理由字串自己說出降級了;低於底線回 None,也就是資料不足,不硬算。底線設 200 根,因為它擋得住只有 59 根的 SPCX,又放得過整年 243 根的台股。
降級有底線,不是無限退讓。 一條只有二十根日線的序列硬算「52 週高點」,算得出數字,但那個數字沒有意義。
| 日期 | 融資餘額(張) | 融券(張) | 外資台指期淨未平倉(口) | P/C 未平倉比(%) | 三大法人買賣超(股) |
|---|---|---|---|---|---|
| 2026-09-03 | 8,902,986 | 222,843 | -81,575 | 77.2 | -658,913,818 |
| 2026-09-04 | 8,919,251 | 219,160 | -82,389 | 99.8 | +577,637,662 |
| 2026-09-07 | 9,073,251 | 211,213 | -79,399 | 111.7 | +795,049,420 |
| 2026-09-08 | 9,048,662 | 208,614 | -82,262 | 105.2 | -305,444,935 |
| 2026-09-09 | 9,070,714 | 203,610 | -81,066 | 96.3 | -37,449,937 |
單位在這張表裡有三種:融資融券是「張」、台指期未平倉是「口」、三大法人買賣超是「股」。三者互不換算,一口台指期不是一千股的任何東西。所以程式裡每個欄位名都自己帶單位字尾(_lots / _contracts / _shares),不給任何人「順手乘 1000」的機會。
三大法人買賣超(T86)是盤後資料,當天下午之前抓會拿到空 CSV。而證交所的「空」有兩種長相:真的空字串,或者一句中文「沒有符合條件的資料」。期交所的「空」又是第三種:只有表頭、沒有任何資料列。
三種長相,同一個處置:
lines = text.splitlines()
start = next((i for i, ln in enumerate(lines) if "證券代號" in ln), None)
if start is None:
raise NotPublishedYet(
f"TWSE T86 {date}: 空 CSV — 這天的盤後資料還沒公布"
f"(或不是交易日)。這不等於買賣超是 0。"
)
NotPublishedYet 是自己定義的例外型別,而不是回 None 或回 0。理由是這個區別必須逼呼叫端處理:「還沒公布」跟「這天成交 0」在下游是兩件完全不同的事,前者要保留前一天的值並標 stale,後者是一個真的讀數。回 0 的話兩者長得一模一樣,然後平均值就會被一堆假的零拉下去。
順帶一提,非交易日與「盤後還沒公布」在回應上長得也一樣,程式分不出來,也刻意不分。兩者的處置相同,而要分就得自己維護一份交易日曆,那是另一個會過期的東西。
還有兩個實作細節。證交所的 CSV 前面有幾行標題列,真正的表頭是含「證券代號」的那一列,所以不能直接從第一行開始讀。編碼要用 big5hkscs,不是 UTF-8 也不是 big5,後者遇到某些字會直接炸掉。
這一節是今天最意外的一個發現,而且它不是我的判斷,是證交所自己在報表備註裡寫的:
請以「前日餘額」為準,而以「今日餘額」為輔助參考資料。
原因是各授信機構在成交次日仍繼續調帳。所以今天抓到的「今日餘額」,明天會再變;要拿到某一天的定稿數字,得等隔天回來抓那份報表上的「前日餘額」。
問題是這件事寫在網頁備註裡,程式不會自己知道。三個月後看到 margin_today_lots 這個欄位名,沒有人會記得它是暫定值。所以處置不是加一行註解,是把它寫進型別:
date: dt.date
margin_prev_lots: float | None # 融資・前日餘額(定稿)
margin_today_lots: float | None # 融資・今日餘額(暫定)
@property
def provisional_for(self) -> dt.date:
"""「今日餘額」屬於的日期 —— 就是當天,但值是暫定的。"""
return self.date
@property
def final_for(self) -> dt.date | None:
"""「前日餘額」屬於的日期 —— **前一個交易日**,不是當天。"""
return self.date - dt.timedelta(days=1) if self.date else None
關鍵是這兩個 property 把兩個數字掛在不同的日期上,而不是擺成兩個平行欄位讓呼叫端自己記得誰是誰。平行欄位的版本一樣跑得動,但它把「哪個才算數」這個判斷推給每一個使用它的人,而那種判斷遲早會有一次做錯。
所以上面那張表最新一天(9/9)的融資融券兩格,此刻仍是「今日餘額」的暫定值:融資融券約 21:30 才公布,18:05 那班排程根本不去問它,22:30 有另一班補;而就算補到了,那個數字也還要等隔天才定稿。
表上那兩欄是期交所來的,各講一下它們是什麼。
外資台指期淨未平倉:外資在台指期上「未平倉的多單減未平倉的空單」,單位是口。它跟買賣超不同:買賣超是當天的流量,未平倉是累積的部位。負數代表淨空方。
P/C 未平倉比:選擇權的賣權(Put)未平倉量除以買權(Call)未平倉量,用百分比表示。這個比值常被當成情緒指標,但方向要小心:高的 P/C 比可以讀成避險需求增加,也可以讀成反向指標,兩種講法都有人用,所以這套東西只記數值與兩端門檻,不記方向。
期交所同時提供成交量比與未平倉比兩個版本,這裡取的是未平倉比:
out.append({
"date": d,
"put_volume": _num(r[1]),
"call_volume": _num(r[2]),
"pc_volume_ratio_pct": _num(r[3]),
"put_oi": _num(r[4]),
"call_oi": _num(r[5]),
"pc_oi_ratio_pct": _num(r[6]),
})
成交量比每天都在跳,反映的是當天的交易活動;未平倉比是存量,變化比較慢,比較接近「現在市場的部位長什麼樣」。兩個都解析出來存著,評分只用後者。
實作上的三個坑:期交所要 POST 不是 GET;編碼一樣是 big5hkscs;而三大法人的身份別用字跟證交所不一樣:期交所是「自營商、投信、外資及陸資」,證交所 T86 那邊的欄位名則是「外陸資買賣超股數(不含外資自營商)」這一類。兩邊都叫三大法人,欄位名對不起來,用同一組字串去比對會默默抓到空值。
還有一件小事:期交所給的資料是新到舊排序,解析後照原樣回傳不重排。要最新一筆就取 [0],要按日期排的自己排。在 adapter 層替呼叫端決定排序,是另一種會被忘記的隱含假設。
外資台指期一路是淨空(−79,399 到 −82,389 口)。程式對這個狀態的判定是不警示。
原因跟昨天央行那格一樣:拿「值」去比會永遠亮燈。一直淨空是狀態不是事件,天天記一次警示,燈就沒有意義了。所以判定看的是方向翻轉:由淨多翻淨空,或反過來,那才是事件。
融資也一樣,看的是五日變化率不是餘額水位。餘額多少張是水位,墊高多快才是訊號。
這一節是我覺得整天最值得記的一條。
同一個「三大法人買賣超」,套在三種標的上要讀成三件事:加權指數上是整個市場的資金流向;個股上是法人對這家公司的態度;而ETF 上多半是套利與申贖,不是「看好這個籃子」。
第三種最容易被誤讀。看到「投信大買 0050」就解讀成「法人看好」,很可能只是在做現貨與期貨之間的價差,或是應付申贖需求。ETF 的申購買回機制會讓造市商被動地買賣成分股,那跟看法無關。
所以但書不是寫在文件裡,是型別強制帶著走的:
@dataclass(frozen=True, slots=True)
class ChipReading:
applies: bool
caveat: str
def reading(symbol: str) -> ChipReading:
"""這個標的能不能套市場級籌碼面,以及要附上什麼但書。"""
if not _is_tw(symbol):
return ChipReading(applies=False, caveat=(
f"{INSUFFICIENT} — 三大法人、融資融券、台指期都是台股的資料,"
f"套到美股標的上沒有意義"))
if _is_index(symbol):
return ChipReading(applies=True, caveat=_INDEX_CAVEAT)
return ChipReading(applies=True, caveat=_ETF_CAVEAT)
caveat 是必填欄位,沒有預設值,所以拿得到籌碼面讀數就一定同時拿到那句但書。美股那三檔則 applies=False:三大法人、融資融券、台指期都是台股的資料,套到美股標的上沒有意義。
原本的規劃裡有「融資融券餘額與維持率」。餘額有,維持率沒有。
證交所的 MI_MARGN 只給餘額,市場級的維持率找不到公開來源。所以那一格現在記的是「無公開來源」,不是從餘額硬推一個。
推是推得出來的:拿融資金額除以某個市值基準就有一個數字。但那個數字沒有任何來源背書。
0050 跟 SPY 是具名的可交易標的。提供它們的買賣建議、短中長線建議,落在投顧業務的範圍。頁面加了鎖也不改變這件事。
所以這條線畫在內容本身,不畫在鎖上:可以有評分數字、分項數值、指標圖、機制說明與侷限;不可以有買進/賣出/加碼/減碼、短中長線建議欄位、任何把分數翻譯成動作的句子。
而且這條線不靠自律。輸出層有一支 lint,掃描產出的 HTML,命中行動字眼就讓發布失敗。評分那個 dataclass 也刻意沒有 advice 欄位,想加都沒有地方放。
五個交易日只有五個點。這一節的每個數字都是定性觀察,不是統計證據。
加權用的是 D-4 10%、D-3 15%、D-2 20%、D-1 25%、D-0 30%,三個層級(總經、大盤、個股)共用同一組。
現在的視窗是 9/3 到 9/9,簡單平均與加權平均都是 96.0,因為分數只有 80 和 100 兩種值,而且分布剛好讓兩種平均撞在一起。這不是巧合也不是設計,是點太少的必然結果。
今天最想留下的是第 2 節那個對比:台股那組差 13.6 個百分點、美股那組差 0.6,而它們在表格上長得一模一樣。差距大不代表誰比較好,它代表那兩個東西根本不是同一個籃子。
技術面那五個維度,寫完之後最有感的反而不是算式,是每一個算式旁邊那個「不算」的分支:窗口有缺值就回 None、序列不夠長就降級並說出來、降級到底線以下就承認資料不足、兩條序列長度對不上就不猜對齊。這些分支加起來的行數,比指標本身還多。
籌碼面那一段則是另一種教訓。三個來源、三種單位、三種「空」的長相、兩套對不起來的三大法人欄位名,還有一個寫在網頁備註裡、程式永遠不會自己知道的「今日餘額是暫定值」。這些沒有一項是演算法問題,全部都是「資料到底在講什麼」的問題。
它是一支氣壓計,不是一支羅盤。 它說現在幾百帕,不說要不要帶傘。
資料來源
其他參考
註一:文中所有價格、報酬與評分皆為 2026-09-09 單次執行的結果,屬於定性觀察,不是統計證據。近一年報酬使用滾動視窗,每天都會變動,撰稿當下的數字與本文可能不同。
註二:
0050追蹤的是臺灣 50 指數,不是加權指數。以^TWII作為對照是一個明確的近似,兩者成分不同。
註三:ETF 的內扣費用、折溢價、成分股調整、配息與除息四項,本文的資料來源算不出來,程式回「資料不足」而非估計值。四項各自對 4.5 個百分點差距的貢獻比例待補。
註四:融資融券的「今日餘額」為暫定值、「前日餘額」才是定稿,此為證交所報表備註所載。本文表格中的數值取自各日抓取當下的結果,未逐日回頭以次日的「前日餘額」校正,屬於已知的近似。
註五:融資融券的市場級「維持率」找不到公開來源,本文未提供,待確認。
註六:評分的五個維度、各自門檻與等權設計,都是作者主觀選定的未經最佳化的起點;本文已列出每一項的理由與它量不到的東西。KD、MACD、ATR 與型態辨識未納入,理由是共線或無法寫出可辯護的判定規則,不代表這些指標無效。
註七:五個交易日只有五個點,平均、加權平均與平滑化在這個尺度上不具統計意義。
重要說明:本文所有數字與敘述僅為個人技術實作紀錄,不構成投資建議。