Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124

建立NBA投注模型需要四個核心步驟:數據收集、特徵工程、演算法建模與盤口校正。透過使用 Python 的 nba_api 抓取官方數據,並納入主客場、背靠背出賽與進階數據(如真實命中率、進階防守數據)訓練模型,即可量化評估勝率與讓分盤。
1. 數據收集 (Data Collection)
nba_api 套件或 Basketball Reference 取得歷史與即時的賽事數據。2. 特徵工程 (Feature Engineering)
將原始數據轉換為具備預測力的指標:
3. 演算法建模 (Modeling)
根據預測目標選擇適合的機器學習模型 Scikit Learn:
4. 盤口校正與資金管理 (Odds & Bankroll Management)
這是一份使用 Python 的 nba_api 套件抓取歷史數據,並透過 scikit-learn 建立邏輯迴歸勝負預測模型的完整程式碼框架:
1. 安裝必要套件
請先在終端機安裝所需的 Python 函式庫:
pip install nba_api pandas scikit-learn
2. Python 實作程式碼
import time
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, classification_report
from nba_api.stats.endpoints import leaguegamefinder
# ==========================================
# 步驟一:使用 nba_api 抓取歷史賽事數據
# ==========================================
print("正在從 NBA API 抓取數據...")
# 抓取最近幾個賽季的常規賽數據
game_finder = leaguegamefinder.LeagueGameFinder(
player_or_team_abbreviation='T', # T 代表 Team (球隊)
season_type_playoffs='Regular Season'
)
games = game_finder.get_data_frames()[0]
# 篩選出 NBA 的比賽 (排除 G-League 或夏季聯賽)
games = games[games['リーグ_ID'] == '00'] if 'リーグ_ID' in games.columns else games[games['TEAM_ID'].astype(str).str.startswith('161')]
# 確保欄位名稱正確,一般為 LEAGUE_ID
games = games[games['LEAGUE_ID'] == '00']
# 轉換日期格式並排序
games['GAME_DATE'] = pd.to_datetime(games['GAME_DATE'])
games = games.sort_values('GAME_DATE')
print(f"成功抓取 {len(games)} 筆球隊賽事記錄。")
# ==========================================
# 步驟二:特徵工程 (計算滾動平均數據)
# ==========================================
# 由於投注時不能使用當場比賽的結果,必須使用「賽前的近況數據」
print("正在計算滾動平均特徵 (Rolling Averages)...")
# 定義我們關心的基礎統計量
features_to_roll = ['PTS', 'FGM', 'FGA', 'FG_PCT', 'FG3M', 'TOV', 'REB']
# 計算每隊過去 5 場比賽的平均表現 (不包含當場比賽,所以要 shift)
rolled_games = []
for team_id, team_df in games.groupby('TEAM_ID'):
team_df = team_df.sort_values('GAME_DATE')
rolling = team_df[features_to_roll].shift(1).rolling(window=5).mean()
rolling.columns = [f'{col}_ROLL5' for col in features_to_roll]
# 合併滾動特徵與原始基礎資訊
combined = pd.concat([team_df[['GAME_ID', 'GAME_DATE', 'TEAM_ID', 'WL', 'MATCHUP']], rolling], axis=1)
rolled_games.append(combined)
df_features = pd.concat(rolled_games).dropna()
# ==========================================
# 步驟三:合併主客場數據,建構單場對決矩陣
# ==========================================
# NBA API 中,一場比賽會有兩筆記錄(主隊一筆、客隊一筆)
# 我們透過 GAME_ID 將兩者合併為一列
# 主隊記錄 (MATCHUP 包含 'vs.')
home_games = df_features[df_features['MATCHUP'].str.contains('vs.')].copy()
# 客隊記錄 (MATCHUP 包含 '@')
away_games = df_features[df_features['MATCHUP'].str.contains('@')].copy()
# 合併主客場
model_df = pd.merge(
home_games,
away_games,
on='GAME_ID',
suffixes=('_HOME', '_AWAY')
)
# 建立目標標籤:主隊贏 = 1,主隊輸 = 0
model_df['HOME_WIN'] = model_df['WL_HOME'].apply(lambda x: 1 if x == 'W' else 0)
# ==========================================
# 步驟四:模型訓練與評估
# ==========================================
# 特徵清單:主隊近況 vs 客隊近況
feature_cols = (
[f'{col}_ROLL5_HOME' for col in features_to_roll] +
[f'{col}_ROLL5_AWAY' for col in features_to_roll]
)
X = model_df[feature_cols]
y = model_df['HOME_WIN']
# 切分訓練集與測試集 (拿最後 20% 的比賽當測試)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, shuffle=False)
# 數據標準化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 初始化並訓練邏輯迴歸模型
model = LogisticRegression()
model.fit(X_train_scaled, y_train)
# 預測與評估
y_pred = model.predict(X_test_scaled)
y_pred_proba = model.predict_proba(X_test_scaled)[:, 1] # 取得主隊獲勝的精確機率
print("\n=== 模型評估報告 ===")
print(f"預測準確率 (Accuracy): {accuracy_score(y_test, y_pred):.2%}")
print("\n詳細分類報告:")
print(classification_report(y_test, y_pred))
# ==========================================
# 步驟五:應用範例 (查看最新比賽的預測機率)
# ==========================================
print("\n=== 預測範例展示 ===")
sample_results = pd.DataFrame({
'GAME_ID': model_df.loc[X_test.index, 'GAME_ID'],
'MATCHUP': model_df.loc[X_test.index, 'MATCHUP_HOME'],
'HOME_WIN_PROBABILITY': y_pred_proba,
'ACTUAL_RESULT': y_test
}).tail(5)
print(sample_results.to_string(index=False))
3. 如何使用這個機率來找「價值投注 (Value Bet)」?
當模型跑出 HOME_WIN_PROBABILITY(主隊勝率)後,您可以將其轉換為模型隱含賠率,公式為:
\(\text{模型賠率}=\frac{1}{\text{主隊勝率}}\)
將目標改為預測讓分盤 (Spread) 或總分大小 (Over/Under)
預測讓分盤 (Spread) 或總分大小 (Over/Under) 需要將模型從「分類」轉為「迴歸 (Regression)」,核心邏輯是直接預測兩隊的精確得分,再拿預測總分與分差去對標莊家盤口。
以下是將上述 Python 架構升級為得分預測模型(使用 Ridge 脊迴歸或 XGBoost 概念)的完整程式碼:
1. 預測邏輯
2. Python 升級程式碼
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import Ridge
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_absolute_error, mean_squared_error
from nba_api.stats.endpoints import leaguegamefinder
# ==========================================
# 步驟一:數據抓取與基本滾動特徵(同前,聚焦進攻與節奏)
# ==========================================
print("正在抓取與處理數據...")
game_finder = leaguegamefinder.LeagueGameFinder(player_or_team_abbreviation='T', season_type_playoffs='Regular Season')
games = game_finder.get_data_frames()[0]
games = games[games['LEAGUE_ID'] == '00']
games['GAME_DATE'] = pd.to_datetime(games['GAME_DATE'])
games = games.sort_values('GAME_DATE')
# 預測得分的關鍵滾動指標:得分、投籃命中率、三分命中率、失誤
features_to_roll = ['PTS', 'FG_PCT', 'FG3_PCT', 'TOV']
rolled_games = []
for team_id, team_df in games.groupby('TEAM_ID'):
team_df = team_df.sort_values('GAME_DATE')
# 計算過去 5 場平均
rolling = team_df[features_to_roll].shift(1).rolling(window=5).mean()
rolling.columns = [f'{col}_ROLL5' for col in features_to_roll]
# 同時加入過去 5 場的「失分平均」(對手得分),衡量防守
opp_pts_rolling = team_df['PTS'].shift(1).rolling(window=5).mean()
rolling['OPP_PTS_ROLL5'] = opp_pts_rolling
combined = pd.concat([team_df[['GAME_ID', 'GAME_DATE', 'TEAM_ID', 'MATCHUP', 'PTS']], rolling], axis=1)
rolled_games.append(combined)
df_features = pd.concat(rolled_games).dropna()
# 合併主客場
home_games = df_features[df_features['MATCHUP'].str.contains('vs.')].copy()
away_games = df_features[df_features['MATCHUP'].str.contains('@')].copy()
model_df = pd.merge(home_games, away_games, on='GAME_ID', suffixes=('_HOME', '_AWAY'))
# ==========================================
# 步驟二:定義迴歸目標 (實際得分)
# ==========================================
# 實際總分與實際分差(主減客)
model_df['ACTUAL_TOTAL'] = model_df['PTS_HOME'] + model_df['PTS_AWAY']
model_df['ACTUAL_SPREAD'] = model_df['PTS_HOME'] - model_df['PTS_AWAY']
# 特徵欄位
feature_cols = (
[f'{col}_ROLL5_HOME' for col in features_to_roll] + ['OPP_PTS_ROLL5_HOME'] +
[f'{col}_ROLL5_AWAY' for col in features_to_roll] + ['OPP_PTS_ROLL5_AWAY']
)
X = model_df[feature_cols]
y_home = model_df['PTS_HOME'] # 目標一:主隊得分
y_away = model_df['PTS_AWAY'] # 目標二:客隊得分
# 切分訓練與測試集
X_train, X_test, y_train_home, y_test_home = train_test_split(X, y_home, test_size=0.2, random_state=42, shuffle=False)
_, _, y_train_away, y_test_away = train_test_split(X, y_away, test_size=0.2, random_state=42, shuffle=False)
# 標準化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# ==========================================
# 步驟三:訓練兩個獨立的得分預測模型
# ==========================================
model_home = Ridge(alpha=1.0)
model_away = Ridge(alpha=1.0)
model_home.fit(X_train_scaled, y_train_home)
model_away.fit(X_train_scaled, y_train_away)
# 預測測試集得分
pred_home = model_home.predict(X_test_scaled)
pred_away = model_away.predict(X_test_scaled)
# 計算模型預測的總分與讓分
pred_total = pred_home + pred_away
pred_spread = pred_home - pred_away
# 實際結果
actual_total = model_df.loc[X_test.index, 'ACTUAL_TOTAL']
actual_spread = model_df.loc[X_test.index, 'ACTUAL_SPREAD']
print("\n=== 模型精確度評估 (平均絕對誤差 MAE) ===")
print(f"總分預測平均誤差: {mean_absolute_error(actual_total, pred_total):.2f} 分")
print(f"分差預測平均誤差: {mean_absolute_error(actual_spread, pred_spread):.2f} 分")
3. 如何比對盤口進行投注?
當您獲得 pred_spread(預測分差)與 pred_total(預測總分)後,直接對標運彩盤口:
💡 讓分盤 (Spread) 應用
+5.5(預測湖人贏 5.5 分)。-2.5(湖人讓 2.5 分)。3 分 的優勢價值(5.5 – 2.5),此時推薦投注 湖人讓分勝。💡 總分大小 (Over/Under) 應用
232.5 分。226.5 分。4. 進階優化提示(如何讓模型更準?)
目前模型僅使用基礎的滾動平均,若要提高實戰準確率,必須引入:
接下來,您想了解如何抓取並加入球隊節奏 (Pace) 和進攻/防守效率 (ORTG/DRTG) 指標,還是如何建立自動比對即時盤口的警報機制?
抓取並加入球隊節奏 (Pace) 和進攻/防守效率 (ORTG/DRTG) 指標並建立自動比對即時盤口的警報機制
1. 抓取進階指標與模型訓練
進階指標的好處在於它們獨立於比賽節奏。例如,ORTG 代表「每百回合得分」,這能精準反映球隊的真實進攻火力,而不會受到比賽打得快或慢的干擾。
import time
import pandas as pd
import numpy as np
from sklearn.linear_model import Ridge
from sklearn.preprocessing import StandardScaler
# 導入 nba_api 進階數據端點
from nba_api.stats.endpoints import leaguedashteamstats, leaguegamefinder
# ==========================================
# 步驟一:抓取進階效率指標 (ORTG / DRTG / PACE)
# ==========================================
print("正在抓取 NBA 團隊進階數據...")
# 抓取指定賽季的每隊進階數據(以 2025-26 賽季為例)
advanced_stats = leaguedashteamstats.LeagueDashTeamStats(
measure_type_detailed_defense='Advanced',
season='2025-26',
season_type_all_star='Regular Season'
).get_data_frames()[0]
# 篩選我們需要的關鍵特徵:進攻效率、防守效率、淨效率、節奏
advanced_df = advanced_stats[['TEAM_ID', 'TEAM_NAME', 'OFF_RATING', 'DEF_RATING', 'NET_RATING', 'PACE']]
print("成功建立進階指標對照表。")
# ==========================================
# 步驟二:整合進常規常規賽程模型中
# ==========================================
# 抓取賽事歷史
game_finder = leaguegamefinder.LeagueGameFinder(player_or_team_abbreviation='T', season_type_playoffs='Regular Season')
games = game_finder.get_data_frames()[0]
games = games[games['LEAGUE_ID'] == '00']
# 將進階指標 Mapping 回每一場比賽的球隊
games_with_adv = pd.merge(games, advanced_df, on='TEAM_ID', how='left')
# 計算包含進階指標的 5 場滾動平均 (Rolling Averages)
features_to_roll = ['OFF_RATING', 'DEF_RATING', 'PACE', 'PTS']
rolled_games = []
for team_id, team_df in games_with_adv.sort_values('GAME_DATE').groupby('TEAM_ID'):
rolling = team_df[features_to_roll].shift(1).rolling(window=5).mean()
rolling.columns = [f'{col}_ROLL5' for col in features_to_roll]
combined = pd.concat([team_df[['GAME_ID', 'GAME_DATE', 'TEAM_ID', 'MATCHUP', 'PTS']], rolling], axis=1)
rolled_games.append(combined)
df_features = pd.concat(rolled_games).dropna()
# 合併主客場對決矩陣
home_games = df_features[df_features['MATCHUP'].str.contains('vs.')].copy()
away_games = df_features[df_features['MATCHUP'].str.contains('@')].copy()
model_df = pd.merge(home_games, away_games, on='GAME_ID', suffixes=('_HOME', '_AWAY'))
# 訓練模型(此處省略標準的分割與 fit 步驟,同前文 Ridge 迴歸模型)
# 假設我們已經訓練好兩個模型:model_home_pts 和 model_away_pts
2. 即時盤口比對與自動警報機制
當模型能夠基於進階數據預測出今日比賽的「精確得分」後,我們需要寫一個腳本:定時抓取即時盤口 → 計算模型與盤口的差距(Edge) → 觸發通報。
下方程式碼展示如何設定這個監控循環,並整合常用的 Telegram Bot 警報(您也可以換成 LINE Notify):
import requests
import json
# 設定您的 Telegram Bot Token 與 Chat ID (用於接收警報)
TELEGRAM_TOKEN = "YOUR_BOT_TOKEN"
CHAT_ID = "YOUR_CHAT_ID"
def send_telegram_alert(message):
"""發送即時訊息到手機 Telegram"""
url = f"https://telegram.org{TELEGRAM_TOKEN}/sendMessage"
payload = {"chat_id": CHAT_ID, "text": message, "parse_mode": "Markdown"}
try:
requests.post(url, json=payload)
except Exception as e:
print(f"警報發送失敗: {e}")
# ==========================================
# 步驟三:模擬抓取今日即時盤口 (Odds API)
# ==========================================
# 備註:實務上可串接 The Odds API, Odds API 或是台灣運彩網頁爬蟲
def fetch_live_odds():
"""
模擬從盤口 API 獲取的即時數據
格式:球隊、莊家開出的讓分 (Spread)、大小分 (Total)
"""
return [
{
"game_id": "99901",
"home_team": "LAL", "away_team": "BOS",
"bookie_spread": -3.5, # 莊家盤口:主隊讓 3.5 分
"bookie_total": 224.5 # 莊家盤口:大小分 224.5 分
},
{
"game_id": "99902",
"home_team": "GSW", "away_team": "PHX",
"bookie_spread": 1.5, # 莊家盤口:主隊受讓 1.5 分
"bookie_total": 231.0
}
]
# ==========================================
# 步驟四:自動比對與核心警報邏輯
# ==========================================
def run_odds_monitor(model_home, model_away, scaler):
print("🚨 啟動即時盤口監控警報系統...")
# 1. 獲取最新即時盤口
live_games = fetch_live_odds()
# 定義觸發警報的最小優勢門檻(例如:模型與盤口相差 3.0 分以上才投注)
SPREAD_THRESHOLD = 3.0
TOTAL_THRESHOLD = 4.5
for game in live_games:
# 2. 丟入今日兩隊的最新進階滾動數據進行預測
# (實務上此處需帶入當天兩隊最新特徵向量 X_today)
# 這裡用假數據模擬模型預測結果:
pred_home_score = 115.2
pred_away_score = 108.4
# 計算模型預測的讓分與總分
pred_spread = pred_home_score - pred_away_score # +6.8 (主贏 6.8 分)
pred_total = pred_home_score + pred_away_score # 223.6 分
# 3. 比對讓分盤 (Spread)
# 莊家開主讓 3.5 (等於 -3.5),模型認為主贏 6.8,相差 3.3 分
spread_edge = pred_spread - (-game['bookie_spread'])
if abs(spread_edge) >= SPREAD_THRESHOLD:
recommendation = f"🔥 【讓分價值投資提示】\n賽事:{game['home_team']} vs {game['away_team']}\n"
recommendation += f"莊家讓分:{game['bookie_spread']}\n模型預測分差:{pred_spread:+.1f}\n"
recommendation += f"💡 建議:投注 【{game['home_team'] if spread_edge > 0 else game['away_team']} 讓分盤】 (優勢值: {abs(spread_edge):.1f} 分)\n"
print(recommendation)
send_telegram_alert(recommendation)
# 4. 比對總分大小盤 (Over/Under)
total_edge = pred_total - game['bookie_total']
if abs(total_edge) >= TOTAL_THRESHOLD:
alert_type = "大分 (OVER)" if total_edge > 0 else "小分 (UNDER)"
total_rec = f"📊 【大小分價值投資提示】\n賽事:{game['home_team']} vs {game['away_team']}\n"
total_rec += f"莊家總分盤:{game['bookie_total']}\n模型預測總分:{pred_total:.1f}\n"
total_rec += f"💡 建議:投注 【{alert_type}】 (優勢值: {abs(total_edge):.1f} 分)\n"
print(total_rec)
send_telegram_alert(total_rec)
# 執行監控
# run_odds_monitor(model_home, model_away, scaler)
3. 實戰營運的最佳工作流 (Workflow)
要讓這個系統自動運作,建議的架構配置如下:
[ 雲端排程 (Cron Job) ]
│
▼ (每日早上 8:00 觸發)
[ Python 腳本 ] ───► 1. 透過 nba_api 更新昨晚最新數據,重訓更新模型參數
│
▼ (每 10 分鐘輪詢一次)
[ 即時盤口 API ] ──► 2. 下載最新國際/台灣運彩即時盤口
│
▼
[ 核心比對模組 ] ──► 3. 模型預測值 vs 即時盤口 > 門檻值?
│
▼ (若滿足條件)
[ 手機警報通報 ] ──► 4. Telegram / LINE 自動推播,提醒您手動或自動下注
使用The Odds API平台盤口作為基準並加入自動化定時執行Windows 工作排程器教學
第一步:獲取 The Odds API 金鑰
API_KEY(免費方案每月提供 500 次點數) [The Odds API]。第二步:撰寫 Python 盤口比對腳本
請將下方完整程式碼儲存為 nba_odds_monitor.py。此腳本會直接調用 The Odds API 的最新 NBA 數據,並與模型預測進行比對,當發現具備價值的場次時,將透過 Telegram 自動發送通知。
import os
import requests
import pandas as pd
from datetime import datetime
# ==========================================
# 核心參數設定
# ==========================================
THE_ODDS_API_KEY = "您的_THE_ODDS_API_金鑰"
TELEGRAM_TOKEN = "您的_TELEGRAM_BOT_TOKEN"
CHAT_ID = "您的_TELEGRAM_CHAT_ID"
# 警報觸發門檻(模型預測與莊家盤口相差幾分以上才發通知)
SPREAD_THRESHOLD = 3.0 # 讓分盤差距大於 3 分
TOTAL_THRESHOLD = 4.5 # 總分盤差距大於 4.5 分
def send_telegram_alert(message):
"""發送即時訊息至手機 Telegram"""
url = f"https://telegram.org{TELEGRAM_TOKEN}/sendMessage"
payload = {"chat_id": CHAT_ID, "text": message, "parse_mode": "Markdown"}
try:
requests.post(url, json=payload, timeout=10)
except Exception as e:
print(f"發送警報失敗: {e}")
def fetch_live_odds():
"""從 The Odds API 抓取美國/歐洲主流莊家的 NBA 即時讓分與總分盤口"""
url = "https://the-odds-api.com"
params = {
'apiKey': THE_ODDS_API_KEY,
'regions': 'us', # 'us' 包含 Bet365, DraftKings 等;亦可改為 'eu'
'markets': 'spreads,totals',
'oddsFormat': 'decimal'
}
try:
response = requests.get(url, params=params, timeout=15)
if response.status_code != 200:
print(f"API 請求失敗,錯誤碼: {response.status_code}")
return []
return response.json()
except Exception as e:
print(f"網絡請求異常: {e}")
return []
def run_analysis():
print(f"[{datetime.now()}] 啟動 NBA 盤口價值分析...")
games_data = fetch_live_odds()
for game in games_data:
home_team = game['home_team']
away_team = game['away_team']
# 尋找目標莊家(此處以 bet365 為例,亦可更換為 lowvig 或 pinnacle)
bookmaker = next((b for b in game['bookmakers'] if b['key'].lower() == 'bet365'), None)
if not bookmaker:
continue
bookie_spread = None
bookie_total = None
# 解析該莊家的讓分盤與總分盤
for market in bookmaker['markets']:
if market['key'] == 'spreads':
home_outcome = next((o for o in market['outcomes'] if o['name'] == home_team), None)
if home_outcome:
bookie_spread = home_outcome['point']
elif market['key'] == 'totals':
over_outcome = next((o for o in market['outcomes'] if o['name'].lower() == 'over'), None)
if over_outcome:
bookie_total = over_outcome['point']
# ----------------------------------------------------
# 模擬模型預測(實務上請在此引入您訓練好的模型與今日最新滾動數據)
# ----------------------------------------------------
pred_home_score = 114.5
pred_away_score = 108.0
pred_spread = pred_home_score - pred_away_score # 模型預測分差(主隊贏 6.5 分)
pred_total = pred_home_score + pred_away_score # 模型預測總分(222.5 分)
# 比對讓分盤
if bookie_spread is not None:
# API 的讓分為負數代表主隊讓分。此處轉換為實際分數差距進行比較
actual_bookie_spread = -bookie_spread
spread_edge = pred_spread - actual_bookie_spread
if abs(spread_edge) >= SPREAD_THRESHOLD:
target_side = home_team if spread_edge > 0 else away_team
msg = f"🔥 *【讓分盤價值提示】*\n賽事:{away_team} @ {home_team}\n"
msg += f"莊家盤口:主隊 {bookie_spread:+}\n模型預測分差:{pred_spread:+.1f}\n"
msg += f"🎯 推薦投注:【{target_side} 讓分】(優勢值: {abs(spread_edge):.1f} 分)"
print(msg)
send_telegram_alert(msg)
# 比對總分大小盤
if bookie_total is not None:
total_edge = pred_total - bookie_total
if abs(total_edge) >= TOTAL_THRESHOLD:
ou_type = "大分 (OVER)" if total_edge > 0 else "小分 (UNDER)"
msg = f"📊 *【大小分價值提示】*\n賽事:{away_team} @ {home_team}\n"
msg += f"莊家總分盤:{bookie_total}\n模型預測總分:{pred_total:.1f}\n"
msg += f"🎯 推薦投注:【{ou_type}】(優勢值: {abs(total_edge):.1f} 分)"
print(msg)
send_telegram_alert(msg)
if __name__ == "__main__":
run_analysis()
第三步:建立 Windows 批次檔 (.bat)
Windows 工作排程器直接呼叫 .py 檔案容易因環境變數配置錯誤而失效,因此建議透過一個 .bat 檔來執行導向:
run_nba_monitor.bat。@echo off
cd /d "C:\您的專案路徑\nba_model"
"C:\Users\您的用戶名\AppData\Local\Programs\Python\Python310\python.exe" nba_odds_monitor.py
exit
(提示:若不確定 python.exe 的精確路徑,可在 Windows 命令提示字元中輸入 where python 查詢)
第四步:設定 Windows 工作排程器 (Task Scheduler)
為了配合 NBA 盤口隨時間劇烈波動的特性,最理想的配置是每天早上 8 點觸發,且每隔 30 分鐘自動重跑一次:
Win + R 鍵,輸入 taskschd.msc 並按下回車,打開「工作排程器」。NBA_Live_Odds_Monitor,點擊下一步。run_nba_monitor.bat。C:\您的專案路徑\nba_model\,結尾請補上斜線),點擊下一步。30 分鐘。1 天。只要您的電腦保持開機並連接網路,系統就會在每天球賽開打前至進行中,每半小時自動抓取盤口、比對模型,並在第一時間將具有正期望值的投注選項推播至您的手機。
透過 API 抓取每日最新傷病名單 (Injury Report) 並與模型得分自動增減連動並加入凱利公式 (Kelly Criterion) 來讓警報訊息自動算出該場比賽推薦下注的資金比例
1. 核心邏輯設計

2. 升級版 Python 實作程式碼
請將以下程式碼儲存並取代原本的 nba_odds_monitor.py 內容:
import requests
import numpy as np
import pandas as pd
from datetime import datetime
# ==========================================
# 1. 核心參數與 API 設定
# ==========================================
THE_ODDS_API_KEY = "您的_THE_ODDS_API_金鑰"
TELEGRAM_TOKEN = "您的_TELEGRAM_BOT_TOKEN"
CHAT_ID = "您的_TELEGRAM_CHAT_ID"
# 資金管理設定
TOTAL_BANKROLL = 100000 # 您的總下注資金總額 (例如 10 萬台幣)
KELLY_FRACTION = 0.25 # 1/4 凱利公式,降低波動風險
# 2026 賽季核心球員傷病價值對照表 (可根據 PER 或 WARP 指標滾動調整)
PLAYER_VALUE_MAP = {
'Nikola Jokic': 7.5,
'Luka Doncic': 7.0,
'Giannis Antetokounmpo': 6.5,
'Shai Gilgeous-Alexander': 6.0,
'Jayson Tatum': 5.5,
'Joel Embiid': 7.0,
'Stephen Curry': 5.0
}
def send_telegram_alert(message):
url = f"https://telegram.org{TELEGRAM_TOKEN}/sendMessage"
payload = {"chat_id": CHAT_ID, "text": message, "parse_mode": "Markdown"}
try: requests.post(url, json=payload, timeout=10)
except Exception as e: print(f"發送警報失敗: {e}")
# ==========================================
# 2. 抓取今日最新傷病名單 (模擬即時 API)
# ==========================================
def fetch_today_injuries():
"""
實務上可串接彈性的體育新聞 API 或使用 nba_api 監控球員狀態。
此處模擬今日官方公佈的缺陣名單 (Status: Out)。
"""
return [
{'player_name': 'Nikola Jokic', 'team': 'DEN', 'status': 'Out'},
{'player_name': 'Stephen Curry', 'team': 'GSW', 'status': 'Questionable'} # 僅計算確定的 Out
]
# ==========================================
# 3. 核心數學工具:分差轉勝率 & 凱利公式
# ==========================================
def spread_to_win_probability(predicted_spread):
"""
根據歷史數據,將預測分差(主隊分數 - 客隊分數)轉換為勝率。
使用 Logistic 函數逼近:分差每多 1 分,勝率隨之提高。
"""
# 歷史統計公式:勝率 = 1 / (1 + e^(-0.15 * 分差))
# 若 pred_spread = +5 (主贏5分),主隊勝率約為 68%
home_win_prob = 1 / (1 + np.exp(-0.15 * predicted_spread))
return home_win_prob
def calculate_kelly_size(win_prob, bookie_odds):
"""
標準凱利公式計算
win_prob: 模型評估勝率 (0~1)
bookie_odds: 莊家歐洲盤賠率 (如 1.95)
"""
b = bookie_odds - 1 # 淨賠率
q = 1 - win_prob # 輸球機率
if b <= 0: return 0.0
# 凱利公式: f* = (bp - q) / b
f_star = (b * win_prob - q) / b
# 僅在期望值為正時推薦下注
if f_star > 0:
return f_star * KELLY_FRACTION # 乘上分注控制係數
return 0.0
# ==========================================
# 4. 自動化比對與風控主程式
# ==========================================
def run_advanced_analysis():
print(f"[{datetime.now()}] 啟動包含傷病修正與凱利風控的監控系統...")
# 抓取即時盤口
odds_url = "https://the-odds-api.com"
params = {'apiKey': THE_ODDS_API_KEY, 'regions': 'us', 'markets': 'spreads', 'oddsFormat': 'decimal'}
try:
response = requests.get(odds_url, params=params, timeout=15)
games_data = response.json()
except Exception as e:
print(f"盤口抓取異常: {e}")
return
# 載入今日傷病
injury_list = fetch_today_injuries()
out_players = [i['player_name'] for i in injury_list if i['status'] == 'Out']
for game in games_data:
home_team = game['home_team']
away_team = game['away_team']
# 尋找目標莊家 Bet365 獨贏與讓分盤
bookmaker = next((b for b in game['bookmakers'] if b['key'].lower() == 'bet365'), None)
if not bookmaker: continue
bookie_spread = None
home_odds = 1.95 # 模擬莊家即時賠率,實務上可從 h2h 或 spreads 盤口中解析對應賠率
for market in bookmaker['markets']:
if market['key'] == 'spreads':
home_outcome = next((o for o in market['outcomes'] if o['name'] == home_team), None)
if home_outcome:
bookie_spread = home_outcome['point']
home_odds = home_outcome['price'] # 取得莊家開出的精確賠率
if bookie_spread is None: continue
# ----------------------------------------------------
# 模型原始得分預測 (基準分)
# ----------------------------------------------------
base_home_score = 116.0
base_away_score = 110.0
# ----------------------------------------------------
# 自動化傷病調整連動
# ----------------------------------------------------
# 檢查是否有對照表中的明星球員今晚缺陣
# 這裡模擬:假設 Nikola Jokic 屬於客隊且今晚缺陣
for player in out_players:
if player in PLAYER_VALUE_MAP:
# 實務上需判斷球員屬於哪一隊,此處以模擬邏輯示範
if player == 'Nikola Jokic': # 假設 Jokic 效力客隊
base_away_score -= PLAYER_VALUE_MAP[player]
print(f"⚠️ 傷病警報: {player} 缺陣,客隊得分下修 {PLAYER_VALUE_MAP[player]} 分")
# 計算修正後的預測數據
pred_spread = base_home_score - base_away_score # 調整後的預測分差
# ----------------------------------------------------
# 勝率轉換與凱利公式資金計算
# ----------------------------------------------------
# 計算相對於莊家讓分盤口,模型預估的「贏盤勝率」
# 例如:莊家開主隊讓 3.5 分,模型認為主隊能贏 13.5 分,則優勢分差為 +10 分
advantage_margin = pred_spread - (-bookie_spread)
win_probability = spread_to_win_probability(advantage_margin)
# 帶入凱利公式計算下注比例
bet_ratio = calculate_kelly_size(win_probability, home_odds)
# ----------------------------------------------------
# 滿足價值門檻,發送含資金建議的警報
# ----------------------------------------------------
if bet_ratio > 0.02: # 下注比例大於 2% 總資金才觸發警報,避免微小機率噪音
suggested_amount = TOTAL_BANKROLL * bet_ratio
msg = f"🚨 *【高價值投注+自動風控提示】*\n"
msg += f"賽事:{away_team} @ {home_team}\n"
msg += f" Bet365 讓分盤口:主隊 {bookie_spread:+}\n"
msg += f" 傷病修正預測分差:{pred_spread:+.1f} 分\n"
msg += f" 評估赢盤勝率:{win_probability:.1%}\n\n"
msg += f"💰 *【資金分配建議】*\n"
msg += f" 建議下注比例:*{bet_ratio:.2%}* (1/4 凱利風控)\n"
msg += f" 建議下注金額:*${suggested_amount:,.0f}* TWD / 單位"
print(msg)
send_telegram_alert(msg)
if __name__ == "__main__":
run_advanced_analysis()
3. 實戰營運與優化建議
當您成功在 Windows 工作排程器中執行這套系統後,請注意以下優化維護重點:
price 欄位會隨莊家調整而變動,程式中 calculate_kelly_size 會自動讀取最新賠率。如果莊家因傷病迅速更改盤口(例如盤口從 -3.5 變成 -8.5),advantage_margin 會縮小,凱利公式算出的下注金額也會自動減少或不推薦下注,實現自動風控迴路。建立一個本地的SQLite資料庫紀錄歷史投注用來檢驗模型長期的實際勝率與回測並將模型擴展到季後賽
第一步:建立 SQLite 資料庫與自動紀錄模組
SQLite 是 Python 內建的輕量級資料庫,不需額外安裝即可使用。我們建立一個名為 nba_betting.db 的檔案,用來儲存每筆投注建議,並在賽後自動核對真實賽果。
請將此資料庫模組整合進您先前的盤口監控腳本中:
import sqlite3
import pandas as pd
from datetime import datetime
DB_NAME = "nba_betting.db"
def init_db():
"""初始化 SQLite 資料庫,建立歷史投注紀錄表"""
conn = sqlite3.connect(DB_NAME)
cursor = conn.cursor()
# 建立投注紀錄表
cursor.execute('''
CREATE TABLE IF NOT EXISTS bet_history (
game_id TEXT PRIMARY KEY,
game_date TEXT,
home_team TEXT,
away_team TEXT,
season_type TEXT, -- 'Regular' 或 'Playoffs'
bookie_spread REAL, -- 莊家讓分 (主隊)
pred_spread REAL, -- 模型預測分差
win_prob REAL, -- 模型評估勝率
bet_side TEXT, -- 投注方 (Home_Spread / Away_Spread)
bet_ratio REAL, -- 凱利計算下注比例
actual_home_score INTEGER,-- 賽後更新:主隊實際得分
actual_away_score INTEGER,-- 賽後更新:客隊實際得分
bet_result TEXT -- 賽後更新:'Win' / 'Lose' / 'Push' (走水)
)
''')
conn.commit()
conn.close()
def log_bet_suggestion(game_id, home, away, is_playoffs, bookie_line, pred_line, prob, side, ratio):
"""當系統觸發警報時,自動將投注建議寫入資料庫"""
conn = sqlite3.connect(DB_NAME)
cursor = conn.cursor()
season_type = 'Playoffs' if is_playoffs else 'Regular'
date_str = datetime.now().strftime('%Y-%m-%d')
try:
cursor.execute('''
INSERT INTO bet_history
(game_id, game_date, home_team, away_team, season_type, bookie_spread, pred_spread, win_prob, bet_side, bet_ratio)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (game_id, date_str, home, away, season_type, bookie_line, pred_line, prob, side, ratio))
conn.commit()
except sqlite3.IntegrityError:
# 若該場比賽已存在,則不重複寫入
pass
finally:
conn.close()
第二步:自動化賽後核對與回測分析
您需要建立另一個獨立腳本(例如設定在每日下午 2 點執行),透過 nba_api 抓取昨晚的最終比分,自動更新資料庫並計算長期勝率與淨利潤。
from nba_api.stats.endpoints import leaguegamefinder
def update_past_game_results():
"""抓取最新賽果,自動核對資料庫中尚未結算的投注"""
conn = sqlite3.connect(DB_NAME)
df_bets = pd.read_sql_query("SELECT * FROM bet_history WHERE bet_result IS NULL", conn)
if df_bets.empty:
print("所有投注皆已結算完畢。")
conn.close()
return
print(f"正在結算 {len(df_bets)} 筆未完賽賽事...")
# 抓取最新 NBA 賽果
game_finder = leaguegamefinder.LeagueGameFinder(player_or_team_abbreviation='T', league_id_nullable='00')
games = game_finder.get_data_frames()[0]
cursor = conn.cursor()
for _, row in df_bets.iterrows():
g_id = row['game_id']
# 尋找該場比賽主隊的記錄
match_game = games[(games['GAME_ID'] == g_id) & (games['MATCHUP'].str.contains('vs.'))]
if not match_game.empty:
actual_home = int(match_game['PTS'].values[0])
# 透過隨後的客隊數據獲取客隊得分
opp_game = games[(games['GAME_ID'] == g_id) & (games['MATCHUP'].str.contains('@'))]
actual_away = int(opp_game['PTS'].values[0])
# 計算實際讓分盤結果
actual_diff = actual_home - actual_away
bookie_line = row['bookie_spread'] # 負數代表主讓
# 判定輸贏邏輯
result = 'Push'
if row['bet_side'] == 'Home_Spread':
# 主隊加上讓分後若大於客隊分數則贏盤
if actual_home + bookie_line > actual_away: result = 'Win'
elif actual_home + bookie_line < actual_away: result = 'Lose'
else:
# 客隊若受讓
if actual_home + bookie_line < actual_away: result = 'Win'
elif actual_home + bookie_line > actual_away: result = 'Lose'
# 更新資料庫
cursor.execute('''
UPDATE bet_history
SET actual_home_score = ?, actual_away_score = ?, bet_result = ?
WHERE game_id = ?
''', (actual_home, actual_away, result, g_id))
conn.commit()
conn.close()
print("賽果結算更新成功。")
def calculate_roi_report():
"""回測統計功能:一鍵生成勝率與資金損益報告"""
conn = sqlite3.connect(DB_NAME)
df = pd.read_sql_query("SELECT * FROM bet_history WHERE bet_result IS NOT NULL", conn)
conn.close()
if df.empty:
print("尚無結算數據可供回測。")
return
total_bets = len(df)
wins = len(df[df['bet_result'] == 'Win'])
losses = len(df[df['bet_result'] == 'Lose'])
pushes = len(df[df['bet_result'] == 'Push'])
win_rate = wins / (wins + losses) if (wins + losses) > 0 else 0
print("\n" + "="*30)
print(f"📊 模型長期歷史回測報告 ({datetime.now().strftime('%Y-%m-%d')})")
print("="*30)
print(f"總下注場次: {total_bets} 場 (勝: {wins} / 敗: {losses} / 走水: {pushes})")
print(f"真實盤口勝率: {win_rate:.2%}")
print(f"常規賽勝率: {len(df[(df['season_type']=='Regular') & (df['bet_result']=='Win')]) / max(1, len(df[df['season_type']=='Regular'])):.2%}")
print(f"季後賽勝率: {len(df[(df['season_type']=='Playoffs') & (df['bet_result']=='Win')]) / max(1, len(df[df['season_type']=='Playoffs'])):.2%}")
print("="*30)
第三步:將模型擴展至季後賽(Playoffs)參數修正
常規賽模型若直接套用在季後賽,通常會遭遇毀滅性的失準。這是因為常規賽看重深度與體能,而季後賽看重巨星上限與戰術方針。擴展至季後賽時,您必須在程式碼中加入以下三個核心微調參數:
1. 節奏下修參數(Pace Reduction Factor)
# 在季後賽期間,將模型預測的雙方 Pace 滾動平均值自動扣減 3% 到 5%
if is_playoffs:
predicted_pace = predicted_pace * 0.96
3. 縮減輪替權重與巨星上場時間(Rotation Tightening)
💡 實戰下一步
init_db() 來在本地建立您的資料庫檔案。season_type 轉為季後賽,或日期已進入 4-6 月,請在主腳本中將 is_playoffs 變數設為 True,全面啟動防守下修與主場加成。