聊天視窗

自由數據:用資料科學解鎖個人財務自由 - 第 2 章

第二章:財務數據的收集與清理

發布於 2026-02-24 23:59

# 第二章:財務數據的收集與清理 > 在量化投資與個人財務管理中,**資料的質量往往是決定成功與否的關鍵**。本章將從三個層面帶領讀者完成財務數據的收集、清理與儲存,為後續的特徵工程與模型構建奠定堅實基礎。 ## 2.1 金融市場數據來源 | 類型 | 主要資料 | 代表性 API | 特色說明 | |------|----------|-----------|-----------| | 股票 | 交易價格、成交量、公司公告 | `yfinance`, `Alpha Vantage`, `Polygon.io` | 免費與付費結合,覆蓋全球主要交易所 | | 匯率 | 即時匯率、歷史匯率 | `exchangerate.host`, `OANDA` | 低延遲、精確對應交易日 | | 基金 | NAV、基金淨值、分紅 | `Morningstar`, `ETF.com` | 基金特定資訊、分類資料 | | 債券 | 票息、到期日、信用評級 | `Bloomberg`, `FactSet` | 需付費,資訊深度高 | | 行情指標 | MACD、RSI、布林帶 | 自行計算 | 基於原始價格資料 | > **選擇資料來源時需考慮:** > > - **可獲取性**:免費 vs 付費、API 金鑰限制。 > - **資料完整度**:歷史長度、更新頻率。 > - **資料品質**:精度、缺失值、日誌一致性。 ## 2.2 API 連結實務 ### 2.2.1 使用 `yfinance` 下載股票歷史價格 python import yfinance as yf import pandas as pd # 下載台積電(TSE:2330)過去 2 年的日行情 ticker = "2330.TW" data = yf.download(ticker, period="2y", interval="1d") # 基本資料檢查 print(data.head()) > `yfinance` 直接從 Yahoo Finance 抓取資料,使用方便但需要注意: > - **資料延遲**:Yahoo 的資料大約 5 分鐘延遲。 > - **資料完整度**:周末與節假日缺失;若需完整日曆,可自行補齊。 ### 2.2.2 Alpha Vantage 取得 5 分鐘 K 線 python import requests import json import pandas as pd API_KEY = "YOUR_ALPHA_VANTAGE_KEY" symbol = "AAPL" function = "TIME_SERIES_INTRADAY" interval = "5min" url = f"https://www.alphavantage.co/query?function={function}&symbol={symbol}&interval={interval}&apikey={API_KEY}&outputsize=compact" res = requests.get(url) raw = json.loads(res.text) # 轉成 DataFrame key = f"Time Series ({interval})" df = pd.DataFrame(raw[key]).T # 列名轉為小寫 cols = {c.split(' ')[-1]: c for c in df.columns} df.rename(columns=cols, inplace=True) # 資料型態轉換 for col in df.columns: df[col] = pd.to_numeric(df[col]) print(df.head()) > Alpha Vantage 提供 **免費配額**(每分鐘 5 次請求),對於小型投資者或初學者足夠。 ### 2.2.3 匯率資料抓取(exchangerate.host) python import requests import pandas as pd base = "USD" symbols = "TWD,EUR" url = f"https://api.exchangerate.host/timeseries?start_date=2023-01-01&end_date=2023-12-31&base={base}&symbols={symbols}" res = requests.get(url) raw = res.json() rates = raw["rates"] # 轉成 DataFrame df = pd.DataFrame(rates).T print(df.head()) > `exchangerate.host` 為免費且開源的匯率 API,沒有金鑰限制,適合日常使用。 ## 2.3 資料庫操作 ### 2.3.1 選擇資料庫 | 資料庫 | 優點 | 缺點 | |--------|------|------| | SQLite | 嵌入式、輕量、易於部署 | 不支援併發、容量有限 | | PostgreSQL | 開源、擴展性好、複雜查詢 | 需要伺服器、配置複雜 | | MySQL | 廣泛使用、易於學習 | 某些高階功能需付費版 | | MongoDB | 文件導向、彈性結構 | 不適合大量關聯型資料 | > **建議**:在個人財務分析中,SQLite 足以滿足日常需求;若需要多使用者協作或更高併發,可考慮 PostgreSQL。 ### 2.3.2 建立資料表結構(以 SQLite 為例) sql CREATE TABLE IF NOT EXISTS stock_prices ( symbol TEXT NOT NULL, date TEXT NOT NULL, open REAL, high REAL, low REAL, close REAL, volume INTEGER, PRIMARY KEY (symbol, date) ); CREATE TABLE IF NOT EXISTS forex_rates ( base TEXT NOT NULL, symbol TEXT NOT NULL, date TEXT NOT NULL, rate REAL, PRIMARY KEY (base, symbol, date) ); ### 2.3.3 將 `pandas` DataFrame 直接寫入 SQLite python import sqlite3 conn = sqlite3.connect("financial_data.db") # 假設 df 為股票歷史價格 DataFrame # columns: ['Open','High','Low','Close','Volume'] # 添加 symbol 與 date symbol = "2330.TW" # 日期列必須為字串 df['date'] = df.index.strftime("%Y-%m-%d") # 重新排序列 cols = ['symbol', 'date', 'Open', 'High', 'Low', 'Close', 'Volume'] stock_df = df[cols].rename(columns={ 'Open': 'open', 'High': 'high', 'Low': 'low', 'Close': 'close', 'Volume': 'volume' }) stock_df.insert(0, 'symbol', symbol) # 寫入資料庫 stock_df.to_sql("stock_prices", conn, if_exists="append", index=False) > **備註**:在寫入前可先使用 `df.drop_duplicates(subset=['symbol','date'])` 以避免重複資料。 ## 2.4 資料清理流程 1. **缺失值處理** - `NaN` → 直接丟棄、前向/後向填補或插值(`df.interpolate()`)。 - 對於金融資料,缺失通常代表交易所休市,直接丟棄即可。 2. **重複值處理** python df.drop_duplicates(subset=['symbol','date'], keep='first', inplace=True) 3. **資料型態轉換** - 日期轉為 `datetime`:`pd.to_datetime(df['date'])`。 - 數值轉為 `float`:`df['close'] = df['close'].astype(float)`。 4. **貨幣統一** - 若同時處理多幣種,需先取得匯率,進行兌換。示例:將 TWD 兌換為 USD。 python df['close_usd'] = df['close'] * df['twd_to_usd_rate'] 5. **時間對齊** - **補齊日曆**: python all_dates = pd.date_range(start=df['date'].min(), end=df['date'].max(), freq='D') df = df.set_index('date').reindex(all_dates).reset_index() df.rename(columns={'index':'date'}, inplace=True) - **去除節假日**:可使用 `holidays` 套件判斷是否為交易日。 6. **異常值檢測** - **價格跳變**:`df['pct_change'] = df['close'].pct_change()`。 - 若跳變幅度超過 10%(或自訂門檻),可標記為 **潛在異常**。 ### 2.4.1 清理範例:台積電日行情 python import yfinance as yf import pandas as pd symbol = "2330.TW" raw = yf.download(symbol, period="2y", interval="1d") # 基本清理 raw.dropna(inplace=True) # 台股週末已缺失 raw.drop_duplicates(subset=['Date'], keep='first', inplace=True) raw.reset_index(inplace=True) raw['date'] = pd.to_datetime(raw['Date']).dt.date # 列名統一 raw.rename(columns={ 'Open':'open', 'High':'high', 'Low':'low', 'Close':'close', 'Volume':'volume' }, inplace=True) raw = raw[['symbol', 'date', 'open', 'high', 'low', 'close', 'volume']] raw.insert(0, 'symbol', symbol) # 檢查是否有負數量化 print(raw.describe()) ## 2.5 實戰小結 | 步驟 | 目的 | 常見陷阱 | |------|------|-----------| | 1. 設定 API 限額與金鑰管理 | 避免因超額而被封鎖 | 忘記檢查 `requests` 狀態碼、JSON 結構變動 | | 2. 將資料寫入 SQLite 時保持 **主鍵唯一** | 防止重複 | 沒設定主鍵時會產生多筆相同資料 | | 3. 資料型態統一 | 方便後續計算 | 字串數值混用會導致 `astype` 失敗 | | 4. 缺失值只在 **非交易日** 允許 | 不影響趨勢 | 低頻資料若出現 `NaN` 代表資料錯誤,需進一步檢查 | | 5. 匯率兌換時使用「當日」匯率 | 使不同幣種可比 | 若使用「前一天」匯率,將產生 **時間失真** | > **實務建議**:將清理邏輯打包成 **函式庫**(例如 `clean_stock_prices(df)`),並在 ETL 腳本中重複使用,能顯著提升可維護性與可重現性。 ## 2.6 章節小結 本章介紹了金融市場數據的主要來源、各種 API 的使用範例、資料庫的選型與操作,以及一套完整的資料清理流程。以下是關鍵 Take‑away: 1. **資料來源要先確定**:選擇能滿足頻率、歷史長度與品質的 API。 2. **資料寫入前先做好資料型態與重複值處理**,避免在資料庫層面出現難以調查的錯誤。 3. **清理工作是持續迴圈**,隨著資料源更新、API 版本變動,需要定期重跑 ETL 流程。 4. **把資料庫設計當成「靜態」結構**,配合版本控制 (`sql` 腳本) 能確保分析環境的可追蹤性。 > 接下來的章節將進一步探討如何利用已清理好的財務資料,結合技術指標、基本面數據與外部宏觀訊息,進行 **特徵工程** 與 **機器學習模型** 的開發。祝你在資料收集與清理的道路上一路順風!