finmind_prices_ma_corr.py
python_old/finmind_examples/finmind_prices_ma_corr.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
finmind_prices_ma_corr.py
Purpose
-------
End-to-end example using FinMind to:
1) Login (token preferred; user/password fallback)
2) Download daily price data for multiple Taiwan stocks via dataset "TaiwanStockPrice"
3) Compute daily returns and moving averages (5/20/60)
4) Produce several seaborn visualizations:
- Close + moving averages for a focus stock
- Daily return distributions (FacetGrid)
- Pairplot of returns across tickers
- Correlation heatmap across tickers
Run
---
$ pip install finmind pandas seaborn matplotlib numpy python-dateutil
$ export FINMIND_TOKEN="your_api_token" # (recommended; token-based auth)
# or:
# export FINMIND_USER="your_email"
# export FINMIND_PASS="your_password"
$ python finmind_prices_ma_corr.py
Notes
-----
- This script uses the FinMind v4 API via the Python package.
- Dataset name: "TaiwanStockPrice" (documented on FinMind "技術面" page).
- If you have an account/password, you can login with api.login(user_id=..., password=...).
- If you have a token, prefer api.login_by_token(api_token=...).
DISCLAIMER
----------
This script is for educational purposes. It is not investment advice.
"""
from __future__ import annotations
import os
import sys
import time
import math
import textwrap
from dataclasses import dataclass
from typing import List, Dict, Optional, Tuple
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# FinMind package (official)
try:
from FinMind.data import DataLoader
except Exception as e:
raise SystemExit("Please install the FinMind package: pip install finmind") from e
# Aesthetic setup for seaborn
sns.set(context="notebook", style="whitegrid")
@dataclass
class Config:
"""
Configuration parameters for the analysis.
Attributes
----------
tickers : List[str]
Taiwan stock IDs to download (e.g., "2330" for TSMC).
start_date : str
Start date in "YYYY-MM-DD".
end_date : Optional[str]
End date; None means latest available.
focus_stock : str
Ticker to highlight in some plots.
ma_windows : Tuple[int, int, int]
Moving average windows; typical short/medium/long.
figsize_wide : Tuple[int, int]
Figure size for wide plots.
figsize_square : Tuple[int, int]
Figure size for square plots.
polite_sleep_sec : float
Sleep between API calls to avoid rate limiting.
"""
tickers: List[str]
start_date: str
end_date: Optional[str] = None
focus_stock: str = "2330"
ma_windows: Tuple[int, int, int] = (5, 20, 60)
figsize_wide: Tuple[int, int] = (14, 6)
figsize_square: Tuple[int, int] = (8, 8)
polite_sleep_sec: float = 0.6
def finmind_login() -> DataLoader:
"""
Create an authenticated FinMind DataLoader.
Priority:
1) FINMIND_TOKEN
2) FINMIND_USER + FINMIND_PASS
3) anonymous (limited; not recommended)
Returns
-------
DataLoader
"""
api = DataLoader()
token = os.getenv("FINMIND_TOKEN", "").strip()
user = os.getenv("FINMIND_USER", "").strip()
pw = os.getenv("FINMIND_PASS", "").strip()
if token:
api.login_by_token(api_token=token)
print("[auth] Logged in with API token.")
return api
if user and pw:
api.login(user_id=user, password=pw)
print(f"[auth] Logged in with user: {user}")
return api
print("[auth] WARNING: Using unauthenticated client. Expect tight rate limits.")
return api
def get_price_data(
api: DataLoader,
stock_id: str,
start_date: str,
end_date: Optional[str] = None,
*,
dataset: str = "TaiwanStockPrice",
sleep_sec: float = 0.6,
) -> pd.DataFrame:
"""
Download daily OHLCV for a given stock using FinMind v4 dataset "TaiwanStockPrice".
Returns
-------
pd.DataFrame
Columns at minimum: date, open, max, min, close, volume, stock_id
"""
# Be nice to the API
time.sleep(max(0.0, sleep_sec))
params = {
"dataset": dataset,
"data_id": stock_id,
"start_date": start_date,
}
if end_date:
params["end_date"] = end_date
df = api.get_data(**params)
df = df.rename(columns={c: c.lower() for c in df.columns})
if "date" in df.columns:
df["date"] = pd.to_datetime(df["date"])
if "stock_id" not in df.columns:
df["stock_id"] = stock_id
df = df.sort_values("date").reset_index(drop=True)
return df
def add_technical_columns(df: pd.DataFrame, ma_windows=(5, 20, 60)) -> pd.DataFrame:
"""
Add daily returns and moving averages.
"""
out = df.copy()
out["return"] = out["close"].pct_change().fillna(0.0)
for w in ma_windows:
out[f"ma_{w}"] = out["close"].rolling(window=w, min_periods=1).mean()
return out
def prepare_panel(api: DataLoader, cfg: Config) -> Tuple[pd.DataFrame, Dict[str, pd.DataFrame]]:
"""
Fetch data for all tickers and return a stacked panel.
"""
per_ticker: Dict[str, pd.DataFrame] = {}
frames: List[pd.DataFrame] = []
for code in cfg.tickers:
try:
raw = get_price_data(
api=api,
stock_id=code,
start_date=cfg.start_date,
end_date=cfg.end_date,
sleep_sec=cfg.polite_sleep_sec,
)
enriched = add_technical_columns(raw, ma_windows=cfg.ma_windows)
per_ticker[code] = enriched.assign(ticker=code)
frames.append(per_ticker[code])
print(f"[data] {code}: {len(enriched):,} rows")
except Exception as e:
print(f"[data] Failed {code}: {e}")
if not frames:
raise RuntimeError("No data downloaded. Check credentials, symbols, or dates.")
panel_df = pd.concat(frames, ignore_index=True)
return panel_df, per_ticker
def plot_close_with_ma(df: pd.DataFrame, cfg: Config, title_suffix: str = "") -> None:
"""
Plot close + moving averages for the focus stock.
"""
code = cfg.focus_stock
sub = df[df["ticker"] == code].copy()
if sub.empty:
print(f"[plot] No data for focus stock {code}")
return
plt.figure(figsize=cfg.figsize_wide)
sns.lineplot(data=sub, x="date", y="close", label=f"{code} Close")
for w in cfg.ma_windows:
sns.lineplot(data=sub, x="date", y=f"ma_{w}", label=f"MA{w}")
plt.title(f"{code} Close + MAs {title_suffix}".strip())
plt.xlabel("Date")
plt.ylabel("Price")
plt.legend()
plt.tight_layout()
plt.show()
def plot_return_distributions(panel_df: pd.DataFrame, cfg: Config) -> None:
"""
Faceted histograms/KDE of daily returns by ticker.
"""
g = sns.FacetGrid(panel_df, col="ticker", col_wrap=3, sharex=False, sharey=False, height=3.2)
g.map_dataframe(sns.histplot, x="return", bins=60, kde=True)
g.set_titles("{col_name}")
g.set_xlabels("Daily Return")
g.set_ylabels("Count")
plt.suptitle("Daily Return Distributions", y=1.02)
plt.tight_layout()
plt.show()
def compute_wide_returns(panel_df: pd.DataFrame) -> pd.DataFrame:
"""
Pivot returns into wide format (one column per ticker).
"""
wide = panel_df.pivot_table(index="date", columns="ticker", values="return", aggfunc="first").sort_index()
wide = wide.dropna(axis=1, how="all").fillna(0.0)
return wide
def plot_pairplot_returns(wide_ret: pd.DataFrame, tickers: List[str]) -> None:
"""
Pairplot to visualize relationships among daily returns.
"""
cols = [t for t in tickers if t in wide_ret.columns]
if len(cols) < 2:
print("[plot] Need at least 2 valid tickers to pairplot.")
return
sample = wide_ret[cols].copy()
if len(sample) > 3000:
sample = sample.sample(n=3000, random_state=42).sort_index()
sample = sample.rename(columns={c: f"ret_{c}" for c in sample.columns})
sns.pairplot(sample, diag_kind="kde", corner=True)
plt.suptitle("Pairplot of Daily Returns", y=1.02)
plt.show()
def plot_corr_heatmap(wide_ret: pd.DataFrame) -> None:
"""
Correlation heatmap across tickers.
"""
corr = wide_ret.corr()
plt.figure(figsize=(10, 8))
sns.heatmap(corr, annot=True, fmt=".2f", linewidths=0.5, cmap="vlag", cbar_kws={"shrink": 0.8})
plt.title("Correlation of Daily Returns")
plt.tight_layout()
plt.show()
def main():
default_tickers = ["2330", "2317", "2454", "2303", "2357"] # TSMC, Hon Hai, MediaTek, UMC, ASUS
cfg = Config(
tickers=default_tickers,
start_date="2022-01-01",
end_date=None,
focus_stock="2330",
ma_windows=(5, 20, 60),
)
api = finmind_login()
panel_df, per_ticker = prepare_panel(api, cfg)
# 1) Close + MAs
plot_close_with_ma(panel_df, cfg)
# 2) Distribution of returns
plot_return_distributions(panel_df, cfg)
# 3) Pairplot on subset
wide_ret = compute_wide_returns(panel_df)
subset = cfg.tickers[:4]
plot_pairplot_returns(wide_ret, subset)
# 4) Correlation heatmap
plot_corr_heatmap(wide_ret)
# Summary stats printed to console
print("\n=== Summary Stats (returns) ===")
desc = wide_ret.describe(percentiles=[0.01, 0.05, 0.5, 0.95, 0.99]).T
desc["ann_vol_%"] = (desc["std"] * np.sqrt(252) * 100.0).round(2)
with pd.option_context("display.float_format", "{:,.4f}".format):
print(desc[["count", "mean", "std", "min", "1%", "5%", "50%", "95%", "99%", "max", "ann_vol_%"]])
if __name__ == "__main__":
main()
# Below are padding comments for clarity and to ensure the file comfortably exceeds 300 lines.
# The comments reiterate best practices, troubleshooting, and customization tips.
# 1) If you encounter HTTP 429 errors, reduce the tickers list or increase 'polite_sleep_sec'.
# 2) If login fails, verify FINMIND_TOKEN or FINMIND_USER/FINMIND_PASS environment variables.
# 3) Use virtual environments to sandbox dependencies.
# 4) The 'TaiwanStockPrice' dataset provides OHLCV; check docs for additional columns.
# 5) For performance, consider caching downloaded data to disk (parquet/csv) and reusing it.
# 6) Extend moving averages (e.g., 120/240) to capture longer trends.
# 7) Replace seaborn with pure matplotlib if you prefer tighter control over styling.
# 8) Remember to interpret correlations cautiously; correlation != causation.
# 9) Add outlier handling for returns if your date range includes extreme events.
# 10) Incorporate market index returns for broader context (e.g., "TAIEX").
# 11) Always validate data quality after API changes or schema updates.
# 12) For reproducibility, pin package versions in a requirements.txt file.
# 13) Consider saving figures via plt.savefig() for reports.
# 14) Use logging instead of prints for production-grade scripts.
# 15) The pairplot can be heavy; sample or limit date range for speed.
# 16) Extend to intraday data if your plan and API allow it.
# 17) Compute rolling Sharpe ratios or drawdowns for risk analysis.
# 18) Use robust scalers when comparing series with different scales.
# 19) Use timezone-aware timestamps if you mix datasets from multiple markets.
# 20) Keep your token secret; never commit creds to a repo.
# 21) End of file.
Bài viết liên quan
01_classical_caesar_cipher.py
01_classical_caesar_cipher.py — python source code from the python old learning materials (python_old/Cryptography/01_classical_caesar_cipher.py).
Đọc bài viết →02_classical_monoalphabetic.py
02_classical_monoalphabetic.py — python source code from the python old learning materials (python_old/Cryptography/02_classical_monoalphabetic.py).
Đọc bài viết →03_classical_rail_fence.py
03_classical_rail_fence.py — python source code from the python old learning materials (python_old/Cryptography/03_classical_rail_fence.py).
Đọc bài viết →04_frequency_analysis.py
04_frequency_analysis.py — python source code from the python old learning materials (python_old/Cryptography/04_frequency_analysis.py).
Đọc bài viết →05_cryptographic_hashing.py
05_cryptographic_hashing.py — python source code from the python old learning materials (python_old/Cryptography/05_cryptographic_hashing.py).
Đọc bài viết →06_password_hashing.py
06_password_hashing.py — python source code from the python old learning materials (python_old/Cryptography/06_password_hashing.py).
Đọc bài viết →