← Back to list

我用 30 行 Python 寫了一個台股篩選器 — — 而且每個數字都能溯源

找「營收在成長、股價剛站上季線、又買得進」的股票,不該是一件要先跟資料奮鬥三小時的事。

Anthonyiaaan · 2026-08-25 03:48 · 0 claps · 9.7 min read
#台股 #python #量化交易 #api #資料分析
Open on Medium ↗

我用 30 行 Python 寫了一個台股篩選器 — — 而且每個數字都能溯源

找「營收在成長、股價剛站上季線、又買得進」的股票,不該是一件要先跟資料奮鬥三小時的事。

先講一個我自己卡很久的問題。

我想做的事很單純:每個月花十分鐘,掃一遍我關注的股票,挑出「月營收還在成長、股價剛站上季線、成交量又夠大到我進得去出得來」的那幾檔,剩下的先不看。這不是什麼高深策略,是很多人用 Excel 手動在做的事。

真正麻煩的從來不是邏輯,是資料。免費來源東缺一塊西缺一塊;有的把缺漏用前一筆硬補上去,你根本不知道哪些是真的;最陰險的是,你抓月營收的時候很容易「偷看到未來」 — — 用了一個當時市場還沒公布的數字,回測跑出來漂亮得不像話,上線就打回原形。

後來我改用 TW Market Data(下面簡稱 TWMD)的 API 重寫,篩選器本體大概 30 行。這篇就把整個過程走一遍 — — 你不用先註冊也能跟著打第一筆,程式碼可以直接複製貼上跑。

如果你不寫程式,前三段看完你就知道這東西在幹嘛、以及為什麼它給的數字可以信;真的想自己跑再往下看程式。

我們要做什麼

一個小篩選器。輸入:一串你關注的股票代碼。輸出:通過下面三關的清單。

  • 成長:最近一個月的營收年增率(YoY)大於 20%。
  • 動能:目前收盤價站上 60 日均線(俗稱季線)。
  • 流動性:近 20 個交易日的日均成交金額夠大,別挑到一天成交幾十萬、你一買就漲停的冷門股。

三個條件都很土,但合在一起意外地能過濾掉一大堆雜訊。重點是,做完這件事你需要兩種資料:日線價格月營收。TWMD 這兩個資料集都在免費方案裡,所以整篇你不用付錢也能跑完。

三分鐘暖身:先打一筆,不用註冊

TWMD 有五檔股票可以完全不帶金鑰直接打 — — 台積電(2330)、鴻海(2317)、聯發科(2454)、0050、長榮(2603)。打開終端機貼這行:

curl "https://api.twmarketdata.com/v2/datasets/twse-daily-price?symbol=2330&limit=1"

你會拿到類似這樣的東西(我把它縮短了):

{
  "dataset": "twse_daily_price",
  "rows": [
    {
      "symbol": "2330",
      "date": "2026-08-05",
      "open": 80,
      "high": 81,
      "low": 79.1,
      "close": 80.1,
      "volume_shares": 549452,
      "turnover_value": 43901948,
      "price_method": "official",
      "price_confidence": null
    }
  ],
  "count": 1,
  "source_role": "official_twse",
  "lineage": {
    "provider": "TWSE",
    "table": "normalized_twse_daily_prices"
  }
}

注意三件事,這是我後來決定用它的原因:

  1. 資料放在 rows 陣列裡,每一列就是一個交易日,欄位名很直白(closeturnover_value)。
  2. 每筆回應都帶 lineage——這個數字來自哪個官方來源、哪張表,寫得清清楚楚。你要的話可以一路追回交易所的原始發布。
  3. price_confidencenull,不是隨便塞個數字。TWMD 的原則是資料有缺就如實留著,不拿推估值把洞補起來。 對做量化的人來說,這一條比多幾個欄位重要得多——你至少知道哪裡有洞。

不想開終端機的話,官網有個 Playground,在瀏覽器裡就能打,一樣免金鑰。

拿一把金鑰

要查這五檔以外的股票,就得註冊拿金鑰。到 儀表板 開一個免費帳號、建一把 API key(長得像 sk_live_...)。金鑰放在請求標頭 X-API-Key,不要放進網址

我習慣塞進環境變數,程式碼裡就不會出現金鑰:

export TWMD_API_KEY="sk_live_你的金鑰"

一個小 helper

所有端點長得一樣:GET /v2/datasets/{資料集},帶 symbol 之類的參數,金鑰放標頭,資料回在 rows。先包一個函式,後面就不用重複:

import os
import requests
BASE = "https://api.twmarketdata.com/v2/datasets"
KEY = os.environ["TWMD_API_KEY"]
def get_rows(dataset: str, **params) -> list[dict]:
    """打一個 TWMD 資料端點,回傳 rows 陣列。"""
    r = requests.get(
        f"{BASE}/{dataset}",
        params=params,
        headers={"X-API-Key": KEY},
        timeout=15,
    )
    r.raise_for_status()
    return r.json().get("rows", [])

symbol 是必填的——這幾個資料集沒有「一次給我全市場」的用法,你要自己準備一串候選股、一檔一檔查。這其實是好事:你會很清楚自己在掃哪些標的,額度也花在刀口上。

條件一:動能(站上季線)

季線就是 60 個交易日的收盤均價。抓夠長的一段日線,算最後 60 天的平均,再看最新收盤價有沒有站上去:

def passes_momentum(symbol: str) -> bool:
    rows = get_rows(
        "twse-daily-price",
        symbol=symbol,
        start_date="2026-04-01",   # 抓大約五個月,確保有 60 個交易日
        end_date="2026-08-25",
        limit=200,
    )
    closes = [r["close"] for r in rows if r.get("close") is not None]
    if len(closes) < 60:
        return False                # 資料不夠就別猜,直接淘汰
    ma60 = sum(closes[-60:]) / 60
    latest_close = closes[-1]
    return latest_close > ma60

closes[-1] 是不是真的「最新」,取決於 API 回來的排序。第一次跑務必自己 print(rows[0]["date"], rows[-1]["date"]) 確認一下順序,別預設。這是我踩過的雷,講一次省你半小時。

條件二:成長(月營收年增)

台股散戶最愛看的數字,大概就是月營收年增率了。TWMD 的 monthly-revenue 直接給你 revenue_yoy,而且是小數——0.18 就是 18%:

def passes_growth(symbol: str, threshold: float = 0.20) -> bool:
    rows = get_rows("monthly-revenue", symbol=symbol, limit=1)
    if not rows:
        return False
    yoy = rows[0].get("revenue_yoy")
    return yoy is not None and yoy >= threshold

revenue_yoy 可能是 null(這檔那個月沒有年增資料),所以先擋掉 None 再比。不要看到 null 就自己補個 0——那會把「不知道」偷偷變成「沒成長」,是兩件不同的事。

條件三:流動性(買得進賣得出)

用近 20 個交易日的日均成交金額(turnover_value,單位是元)當門檻。這裡設一億,你可以照自己的胃口調:

def passes_liquidity(symbol: str, min_avg_turnover: float = 1e8) -> bool:
    rows = get_rows(
        "twse-daily-price",
        symbol=symbol,
        start_date="2026-07-20",
        end_date="2026-08-25",
        limit=40,
    )
    turnovers = [r["turnover_value"] for r in rows if r.get("turnover_value")]
    if len(turnovers) < 20:
        return False
    avg = sum(turnovers[-20:]) / 20
    return avg >= min_avg_turnover

串起來

import time
watchlist = ["2330", "2317", "2454", "2603", "3008",
             "2412", "1301", "2882", "3034", "2379"]
passed = []
for symbol in watchlist:
    try:
        if passes_growth(symbol) and passes_momentum(symbol) and passes_liquidity(symbol):
            passed.append(symbol)
    except requests.HTTPError as e:
        print(f"{symbol} 查詢失敗: {e}")
    time.sleep(0.3)   # 對 API 客氣一點,別狂打
print("通過篩選:", passed)

我把成長條件放第一個判斷,是因為它只花一次呼叫;and 短路後,不成長的股票根本不會去打那兩個比較貴的價格查詢,額度省下來。各方案的額度和速率限制在 定價頁,量大的話記得看一下。

就這樣。換掉 watchlist、調一下三個門檻,你就有一個自己的篩選器了。

一個一定要知道的雷:別偷看到未來

上面這個篩選器是拿來看「現在」的,沒問題。但只要你哪天想把它變成回測 — — 「如果我 2022 年就這樣選股,績效如何?」 — — 就會踩到量化裡最經典的陷阱:前視偏誤(lookahead bias)

月營收不是月底就公布的,公司有法定申報期限,通常隔月才揭露。所以 2022 年 3 月的營收,市場可能到 4 月中才看得到。如果你回測時用「3 月」這個月份去對齊 3 月的股價,等於讓你的策略提前知道了還沒公布的數字 — — 回測會漂亮得離譜,實盤直接破功。

TWMD 在這件事上處理得很老實:每一列揭露類資料都帶一個 knowledge_date(知識日期),而且保守地設成法定申報截止日——寧可偏晚也不偏早。回測時你用 knowledge_date 過濾,而不是用營收月份,數字就會在它「真正進入市場的那天」才進入你的策略。官方在 資料集規格書 裡把這個取捨寫得很白,值得一讀。

順帶一提,日線價格資料集含已下市股票(從 2004 年起、約 525 萬列)。這聽起來不起眼,但如果你回測時只用「今天還活著」的股票,你的績效會被系統性高估 — — 那些下市、倒掉的公司被你悄悄地從歷史裡刪掉了,這叫倖存者偏差。想避開它,前提就是你的資料裡留著那些已經消失的名字。

這兩點 — — 知識日期、含下市股 — — 就是我願意在這上面花錢的原因。它們不會讓你的 demo 更炫,但會讓你的回測不騙自己。

想更進一步

  • 換更長的候選清單:與其手打,可以用 指數成分股 把某個指數的成分股一次拉出來當 universe。
  • 加更多條件:估值(本益比)、三大法人買超、財報三表 — — 資料集總覽 有全部清單,每個都同一套 rows 結構,helper 不用改。
  • 讓 AI 幫你查:TWMD 有 MCP server,接上 Claude 或 ChatGPT 之後,你可以直接用中文問「幫我看台積電近 12 個月營收年增」,它會去打同一個 API、把帶來源的數字拿回來。不想寫 Python 的話,這是最短的路。

最後

一個能用的台股篩選器,核心就是這 30 行。難的部分從來不是邏輯,是背後那份「不缺、不亂補、不偷看未來」的資料 — — 這也是我寫這篇的原因:把資料選對,後面的程式碼會簡單到不可思議。

先去打那筆免金鑰的 2330 看看回應長怎樣,你大概十分鐘就有感覺了。

免費開始:twmarketdata.com · 文件:快速開始

本文的資料與統計僅供研究與教學,非投資建議;投資決策與風險請自行判斷。文中程式碼與欄位以 TWMD 官方文件為準,API 若有更新請以官網為主。


메타데이터
post_id
e8fc84d169ee
slug
我用-30-行-python-寫了一個台股篩選器-而且每個數字都能溯源-e8fc84d169ee
url
https://medium.com/@anthonyiaaan/%E6%88%91%E7%94%A8-30-%E8%A1%8C-python-%E5%AF%AB%E4%BA%86%E4%B8%80%E5%80%8B%E5%8F%B0%E8%82%A1%E7%AF%A9%E9%81%B8%E5%99%A8-%E8%80%8C%E4%B8%94%E6%AF%8F%E5%80%8B%E6%95%B8%E5%AD%97%E9%83%BD%E8%83%BD%E6%BA%AF%E6%BA%90-e8fc84d169ee
canonical_url
https://medium.com/@anthonyiaaan/%E6%88%91%E7%94%A8-30-%E8%A1%8C-python-%E5%AF%AB%E4%BA%86%E4%B8%80%E5%80%8B%E5%8F%B0%E8%82%A1%E7%AF%A9%E9%81%B8%E5%99%A8-%E8%80%8C%E4%B8%94%E6%AF%8F%E5%80%8B%E6%95%B8%E5%AD%97%E9%83%BD%E8%83%BD%E6%BA%AF%E6%BA%90-e8fc84d169ee
author_url
https://medium.com/@anthonyiaaan
status
ok
fetched_at
2026-09-08 15:49:05