Oracle3
Open-source trading engine and MCP server for prediction markets. Fee-aware no-arbitrage trading across related contracts, live on Kalshi, Polymarket and Solana or on paper.
2026 基隆市長選舉 Polymarket 賭盤即時監控:賠率、成交明細、錢包彙總
Translation pending
FinanceJavaScript · PythonGitHub @Kcpb-chesOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
追蹤 Polymarket 上台灣各縣市首長選舉賭盤的即時賠率與每一筆成交明細, 包含下單錢包地址、顯示名稱、買賣方向、金額與時間。頂端頁簽切換縣市。 兩者都接同一個 GitHub repo 自動部署,Actions 每次 commit 快照後兩邊都會更新。 網址帶錨點可直接開到指定縣市,例如。 Polymarket 封鎖台灣 IP。 這是整套架構的前提。 直接用瀏覽器打 polymarket.com 的 API,在台灣會連不上;掛 VPN 才通。 所以資料來源做成兩條腿,網頁會自己選能走的那條。 而且要用到的兩個 API 穩定度差很多,所以它們是分開判斷的: Gamma API 的請求送得到伺服器,但 Cloudflare 有時回傳的內容不帶 access-control-allow-origin 標頭,瀏覽器就擋下來了。 (用 curl 測反而會成功,因為拿到的是快取版本——所以不要用 curl 判斷瀏覽器能不能連。) 關鍵是成交明細抓得到就算即時——賠率抓不到只是換個來源,不該讓整頁降級。 中間那種混合狀態一定會在畫面上講清楚哪個是舊的、哪個是新的, 不會讓人誤把舊資料當成當下行情。Read the full README ↗
追蹤 Polymarket 上台灣各縣市首長選舉賭盤的即時賠率與每一筆成交明細, 包含下單錢包地址、顯示名稱、買賣方向、金額與時間。頂端頁簽切換縣市。
目前涵蓋 19 個縣市,全部 2026-11-28 到期:
| 縣市 | Event ID | 網址錨點 |
|---|---|---|
| 基隆市 | 848410 | #keelung |
| 臺北市 | 848341 | #taipei |
| 新北市 | 848347 | #new-taipei |
| 桃園市 | 848370 | #taoyuan |
| 高雄市 | 848409 | #kaohsiung |
| 新竹市 | 848435 | #hsinchu-city |
| 新竹縣 | 848418 | #hsinchu-county |
| 臺中市 | 848407 | #taichung |
| 臺南市 | 848408 | #tainan |
| 彰化縣 | 848438 | #changhua |
| 南投縣 | 848447 | #nantou |
| 雲林縣 | 848448 | #yunlin |
| 嘉義縣 | 848449 | #chiayi-county |
| 屏東縣 | 848479 | #pingtung |
| 花蓮縣 | 848480 | #hualien |
| 臺東縣 | 848481 | #taitung |
| 宜蘭縣 | 848417 | #yilan |
| 嘉義市 | 848453 | #chiayi-city |
| 苗栗縣 | 848436 | #miaoli |
| 平台 | 網址 | 備註 |
|---|---|---|
| Vercel | https://keelung-polymarket-tracker.vercel.app/ | 主要,Ches 的內網擋 github.io |
| GitHub Pages | https://kcpb-ches.github.io/keelung-polymarket-tracker/ | 備用 |
兩者都接同一個 GitHub repo 自動部署,Actions 每次 commit 快照後兩邊都會更新。
網址帶錨點可直接開到指定縣市,例如 https://keelung-polymarket-tracker.vercel.app/#kaohsiung。
Polymarket 封鎖台灣 IP。 這是整套架構的前提。
直接用瀏覽器打 polymarket.com 的 API,在台灣會連不上;掛 VPN 才通。
所以資料來源做成兩條腿,網頁會自己選能走的那條。
而且要用到的兩個 API 穩定度差很多,所以它們是分開判斷的:
| API | 用途 | 從瀏覽器直連 |
|---|---|---|
| Data API | 每一筆成交明細(核心資料) | 穩定 |
| Gamma API | 盤口賠率、成交量、流動性 | 時好時壞 |
Gamma API 的請求送得到伺服器,但 Cloudflare 有時回傳的內容不帶
access-control-allow-origin 標頭,瀏覽器就擋下來了。
(用 curl 測反而會成功,因為拿到的是快取版本——所以不要用 curl 判斷瀏覽器能不能連。)
因此實際行為有三種:
開啟網頁 → 同時試「成交明細」和「盤口賠率」(各自重試 3 次)
│
├─ 兩個都成功 ─────────→ 🟢 即時直連,無提示
│
├─ 成交成功、盤口失敗 ─→ 🟢 即時直連 + 一行說明:
│ 「賠率是 X 時間的快照值,成交紀錄是當下最新」
│
└─ 成交也失敗 ─────────→ 🟡 快照資料,提示連 VPN
關鍵是成交明細抓得到就算即時——賠率抓不到只是換個來源,不該讓整頁降級。 中間那種混合狀態一定會在畫面上講清楚哪個是舊的、哪個是新的, 不會讓人誤把舊資料當成當下行情。
快照由 scripts/snapshot.py 產生。它可以在你自己電腦跑(要開 VPN),
也可以掛在 GitHub Actions 上跑——GitHub 的機器在美國,不受台灣封鎖影響,
等於免費幫你顧一台永遠連得到 Polymarket 的機器。
keelung_polymarket/
├── index.html 前端入口
├── app.js 全部前端邏輯(抓取、篩選、三種視圖、CSV)
├── style.css 樣式(深色為主,可切淺色)
├── data-<eventId>.json ← 各縣市最新快照,前端連不到 API 時讀這個
│
├── scripts/
│ ├── snapshot.py 抓取腳本(純標準庫,不用 pip install)
│ ├── should_commit.py 判斷這一輪值不值得留一個 commit
│ └── backfill_new_wallets.py 一次性:回填 new-wallets.json 的歷史資料
│
├── cron-worker/ Cloudflare Worker:每 5 分鐘觸發 Actions(見該資料夾 README)
│
├── seen_wallets.json ← 已知錢包名冊,新錢包偵測的依據
├── new-wallets.json ← 新進錢包完整明細,「新進錢包通知」頁籤讀這個
│
├── snapshots/
│ └── YYYY-MM-DD.json 每天一份賠率與統計存檔(不含逐筆明細)
│
└── .github/workflows/snapshot.yml GitHub Actions 排程(每小時)+ 寄信
cd keelung_polymarket
python3 -m http.server 8765
然後開 http://localhost:8765/index.html。
不能直接用滑鼠雙擊
index.html開啟。用file://開的話瀏覽器會擋掉讀取data.json的請求,一定要透過上面的小伺服器。
python3 scripts/snapshot.py
會同時更新 data.json 和 snapshots/當天日期.json。
.github/workflows/snapshot.yml 每小時抓一次快照並 commit| 模式 | 用途 |
|---|---|
| 📋 表格 | 每筆成交一列,可依任一欄位排序、分頁,適合逐筆核對 |
| 📰 動態 | 卡片式時間軸,適合快速掃過最近有誰在下注 |
| 👛 錢包 | 依錢包彙總:累計筆數、買進/賣出金額、淨投入、押注標的、首次進場時間 |
錢包視圖會把開盤 24 小時內就進場的錢包標記出來——早期交易者可能有資訊優勢,
是值得優先看的對象。這個時間窗可以在 app.js 的 EARLY_WINDOW_H 調整。
所有錢包地址都可以點擊,三個入口:Polygonscan(鏈上紀錄)、 Polymarket(個人頁)、Relay(跨鏈轉帳,回溯 USDC.e 的來源鏈)。 滑鼠移到地址上會出現複製按鈕。表格視圖最右欄也有 Relay 連結。
頁籤列上跟各縣市同一層,有一個 🔔 新進錢包通知。它是所有縣市的彙整, 列出每個錢包第一次在某個縣市下注的那一筆交易——跟寄出去的通知信同一份資料, 差別只在信會過期、這一頁可以隨時回頭查。
資料來自 new-wallets.json,由 Actions 每輪產生,所以沒有 VPN 也看得到。
可以依縣市、日期、交易者名稱或錢包地址篩選,也能只看跨縣市錢包,並匯出 CSV。
頂端三個數字分別是:符合目前篩選條件的筆數、最近 24 小時的新進錢包、 以及其中跨縣市的錢包數。
同一個錢包在不同縣市各算一筆(它在每個縣市都有自己的「首航」), 所以列數會比錢包數多。
new-wallets.json 採合併寫入:只補新的,不動既有紀錄。
首筆交易明細是從 data-*.json 撈的,而那些檔有 MAX_OFFSET 上限。
等成交筆數成長到截斷,早期錢包的首筆交易會從 data-*.json 消失;
若每輪重建,這份名單會跟著愈縮愈短。合併寫入則是記過就不會掉。
歷史資料由 scripts/backfill_new_wallets.py 一次性補齊(2026-09-21 執行,回填 1,447 筆),
之後日常維護交給 snapshot.py,不需要再跑。
每次抓完資料後,比對 seen_wallets.json 名冊,出現從未見過的錢包就寄信。
十個縣市合併成一封信,依縣市分段,不會一次收到八封。
信裡列出每個新錢包的首筆動作(押哪位候選人、買賣、Yes/No、股數、成交價、金額), 以及 Polygonscan、Polymarket、Relay 三個直達連結。
主旨會標註筆數與涉及幾個縣市,有跨縣市錢包時加註「N 個跨縣市」, 否則標註金額逾 $50 的筆數,讓人從手機通知欄就能判斷輕重。
同一個錢包在多個縣市都下注時,信裡會用黃色標籤標出「也出現在 ○○、○○」。 這是單一縣市看不出來的模式,也是最值得優先檢視的訊號。
目前約 380 個錢包裡有 136 個跨縣市,其中有錢包同時押了 9 個縣市。
| 名稱 | 內容 | 必填 |
|---|---|---|
MAIL_USERNAME | 寄件的 Gmail 地址 | 是 |
MAIL_PASSWORD | Google 應用程式密碼(16 碼,不是登入密碼) | 是 |
MAIL_TO | 額外收件地址,多個用逗號分隔 | 否,不填就只寄給自己 |
MAIL_TO 是「加上去」而不是「取代掉」——MAIL_USERNAME 那個信箱一定收得到,
填了 MAIL_TO 就是再多寄給那些人。收件地址放 secret 而不是寫死在 workflow 裡,
是因為這個 repo 是公開的,寫死會被爬蟲撈去發垃圾信。
應用程式密碼在 https://myaccount.google.com/apppasswords 產生(需先開兩步驟驗證), 只能用來寄信,隨時可撤銷。設定方式:
gh secret set MAIL_USERNAME --body "你的信箱@gmail.com"
gh secret set MAIL_PASSWORD # 執行後貼上 16 碼
到 Actions → snapshot → Run workflow,勾選「寄一封測試信」。
它會拿最近進場的錢包組一封標示為測試的信寄出,內容格式與真實通知完全相同—— 測試信長什麼樣,真信就長什麼樣。不會動到名冊,可以放心重複測試。 換信箱、或懷疑通知壞掉時都用這個驗證。
seen_wallets.json 依 event id 分組,每個縣市各有自己的名冊:
{ "updated_at": "...", "count": 533,
"events": { "848410": { "0xabc…": {"first_seen": "...", "name": "..."} }, ... } }
不從 data-*.json 反推「看過哪些錢包」,是因為那些檔有 MAX_OFFSET(10000 筆)上限。
將來筆數成長到會截斷時,早期的錢包會從資料裡消失,被誤判成新錢包而爆寄一整批信。
三種情況會靜默建檔、不寄信,避免一次收到上百封:
要把某個縣市重設為「全部已知」,把它在 events 底下那一段刪掉再跑一次即可。
所有縣市與候選人中文名都集中在兩個地方,要新增縣市就各加一筆,其餘程式碼完全不用動:
app.js 最上面的 EVENTS 陣列(前端顯示用,含政黨配色)scripts/snapshot.py 最上面的 EVENTS 清單(抓取與通知信用){
id: 848410, slug: 'keelung', city: '基隆市', office: '市長',
candidates: {
'Hsieh Kuo-liang': { zh: '謝國樑', party: 'kmt', partyZh: '國民黨' },
'Tung Tzu-wei': { zh: '童子瑋', party: 'dpp', partyZh: '民進黨' },
},
},
Polymarket 的 zh-hant 介面是機器翻譯,而且有錯:
Chang San-cheng(張善政),
中文介面卻顯示「鄭文燦」——兩者是不同人。
目前依 Ches 判斷採用「鄭文燦」,程式碼裡有註記,若日後確認有誤改一行即可。所以中文名一律以設定表為準,人工對照 API 回傳的英文拼音(groupItemTitle)。
party 只填確定的,沒把握的一律留空(顯示為中性灰)。
寧可不標也不要標錯——標錯會影響判讀。
同理,中譯沒把握的候選人乾脆不放進對照表,前端會顯示英文拼音原文, 不會把人漏掉。以下幾位待確認,確認後補進兩張設定表即可:
| 縣市 | 英文拼音 | 備註 |
|---|---|---|
| 彰化縣 | Wei Ping-cheng、Hung Jung-chang | |
| 南投縣 | Wen Shih-cheng | |
| 嘉義縣 | Wu Pin-jui、Tsai Sung-yi | |
| 花蓮縣 | Yu Shu-chen、Yeh Yao-hui、Lo Pei-chin | Yu Shu-chen 目前領先 |
| 臺東縣 | Wu Hsiu-hua、Li Wu Ying-chih | Wu Hsiu-hua 目前領先 |
每個 event 底下有 29~33 個 market,但真候選人只有 2~6 位,
其餘是 Candidate A ~ Candidate Z 的佔位盤(成交量 0、賠率固定 0.5)。
前端預設隱藏沒人下注的佔位盤,只在下方標註還有幾個。
之後若有新人參選、佔位盤被改成真名,把名字加進設定表就會自動顯示;
沒加的話會顯示英文原文,不會被吃掉。
| API | 用途 | 認證 |
|---|---|---|
gamma-api.polymarket.com/events?id=<eventId> | 盤口賠率、成交量、流動性 | 不需要 |
data-api.polymarket.com/trades?eventId=<eventId>&takerOnly=true | 每一筆成交明細 | 不需要 |
takerOnly=true 很重要:鏈上每筆撮合都有 maker、taker 兩方,
不加這個參數會把同一筆交易算成兩筆,成交數直接膨脹一倍。
資料判讀要注意的兩件事:
name / pseudonym 是使用者在 Polymarket 上的公開暱稱,不是實名。
部分帳號的 name 是系統自動填的錢包地址字串(例如 0xfBd8...-1779347618060),
程式會辨識出來並改用 pseudonym 或標示為「未具名」。offset 上限是 10000。目前最大的高雄盤約 482 筆,離上限還很遠;
真的成長到那個量級時,snapshot.py 要改用 start/end 時間窗分段抓。:30 以避開整點前後的排隊高峰。
要真正即時就用直連模式(開 VPN),網頁會自己去抓最新的。app.js 或 style.css 要記得把 index.html 裡的 ?v= 數字加一,
否則瀏覽器會一直用快取的舊版本。
網頁載入時 console 會印出 [選舉賭盤監控] app.js v8,
版號跟你剛改的對不上就是中了快取,按 Cmd+Shift+R 強制重新載入。| 什麼 | 多久一次 | 改哪裡 |
|---|---|---|
| 網頁自動重抓 | 5 分鐘 | app.js 的 REFRESH_INTERVAL |
| Actions 抓快照+檢查新錢包 | 5 分鐘(由 Cloudflare Worker 觸發) | cron-worker/wrangler.toml 的 crons |
2026-09-22 實測:把 cron 從每 3 小時改成每小時,平均間隔幾乎沒變。
| 預期間隔 | 實際平均間隔 | |
|---|---|---|
每 3 小時(30 */3 * * *) | 3 小時 | 4.6 小時 |
每 1 小時(30 * * * *) | 1 小時 | 4.3 小時 |
改成每小時後的 23 小時內只跑了 4 班,執行率 17%。
結論是 GitHub 給這個 repo 的排程執行大約就是一天 5~6 次,跟 cron 寫多密無關。 這是 GitHub 對免費/公開 repo 排程的節流,官方文件只寫「schedule 事件可能因負載而延遲」, 沒說會直接砍掉這麼多班。
還是維持每小時而不調回 3 小時,是因為反正配額一樣, 提出的請求多一點、被分配到的機會也多一點,沒有壞處。
節流只限制排程,不限制手動觸發——按 Run workflow 都是秒級啟動。
所以真正的解法是讓 GitHub 以外的服務定時呼叫 workflow_dispatch API。
cron-worker/ 就是這件事:一支 Cloudflare Worker,每 5 分鐘打一次那支 API。
設定方式見 cron-worker/README.md。
snapshot.yml 裡的排程保留不動,當作 Worker 掛掉時的備援。
一天跑 288 次,如果每次都 commit,兩個月就是一萬七千個 commit,git pull 會愈來愈慢。
而這些 commit 絕大多數沒有意義——這個盤一天約 50 筆成交,288 個快照裡大概有 250 個
除了時間戳之外內容一模一樣。
所以 scripts/should_commit.py 會先判斷「這個盤真的有新動作嗎」:
| 會觸發 commit | |
|---|---|
| 任何縣市有新成交 | ✅ |
| 錢包名冊出現新錢包 | ✅ |
| 新進錢包名單有新項目 | ✅ |
時間戳(fetched_at/updated_at) | ❌ |
| 盤口賠率、流動性、總成交量 | ❌ |
錢包的全站預測次數(traded) | ❌ |
下面三類為什麼不算:它們會自己一直跳動,跟「有沒有人在這個盤下注」無關——
賠率會因做市商調單而變,traded 會因為那個錢包在 Polymarket 其他市場
(跟台灣選舉無關)交易而變。讓它們觸發 commit 等於回到每輪都 commit。
代價:沒有新成交的那段時間,快照裡的賠率會停在最後一次 commit 的數字。 但沒有成交時賠率本來就不太會動,而且網頁連得到 API 時是直接讀即時賠率, 快照只是備援。
想看當下最新不必等排程:到 Actions → snapshot → Run workflow 手動觸發一次,
約一分半後網頁就是新的(按右上角 ↻ 重載)。終端機也可以直接下
gh workflow run snapshot.yml。
概念參考自 tw-election-2026-tracker (全國地方選舉政黨盤),沿用了它的深色介面風格與雙視圖設計, 但抓取邏輯、分組維度(政黨 → 候選人)、資料來源策略均為重寫。
主要差異:原專案靠一台 VPS 常駐 Python 程式抓資料再 git push, 單點故障且需要自行維運(其線上資料已停更);這裡改為前端直連優先、 GitHub Actions 快照備援,不需要任何伺服器。