Skip to content

Instantly share code, notes, and snippets.

@Ray901
Last active September 18, 2025 16:15
Show Gist options
  • Select an option

  • Save Ray901/2f8afc162373ad05416822db2da9cf83 to your computer and use it in GitHub Desktop.

Select an option

Save Ray901/2f8afc162373ad05416822db2da9cf83 to your computer and use it in GitHub Desktop.
Python FinMind Stock kline
import requests
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from datetime import datetime
# ====== 設定區 ======
token = "" # ←請填入你的 FinMind API token
start_date = "2025-01-01"
end_date = datetime.today().strftime('%Y-%m-%d')
stock_ids = [
"2330", "2317", "2454", "2344",
"3036", "3260", "2408", "3231",
"3293", "2337", "6531", "4973"
]
stock_names = {
"2330": "台積電",
"2317": "鴻海",
"2454": "聯發科",
"2344": "華邦電",
"3036": "文曄",
"3260": "威剛",
"2408": "南亞科",
"3231": "緯創",
"3293": "鈊象",
"2337": "旺宏",
"6531": "AP Memory",
"4973": "廣穎電通"
}
# ====== 建立 3x4 子圖 ======
rows, cols = 3, 4
fig = make_subplots(
rows=rows, cols=cols,
shared_xaxes=False,
shared_yaxes=False,
subplot_titles=[stock_names.get(sid, sid) for sid in stock_ids],
column_widths=[1/cols]*cols,
row_heights=[1/rows]*rows,
vertical_spacing=0.07,
horizontal_spacing=0.05
)
# ====== 抓資料並畫圖 ======
valid_stocks = []
url = "https://api.finmindtrade.com/api/v4/data"
for idx, stock_id in enumerate(stock_ids):
params = {
"dataset": "TaiwanStockPrice",
"data_id": stock_id,
"start_date": start_date,
"end_date": end_date,
"token": token
}
resp = requests.get(url, params=params, verify=False)
data = resp.json()
df = pd.DataFrame(data.get("data", []))
df.dropna(subset=["open", "max", "min", "close"], inplace=True)
df = df[(df["open"] > 0) & (df["close"] > 0)]
if df.empty or "date" not in df.columns:
print(f"[!] {stock_id} 無資料或格式錯誤")
continue
# 計算均線
df["MA5"] = df["close"].rolling(window=5).mean()
df["MA20"] = df["close"].rolling(window=20).mean()
row = idx // cols + 1
col = idx % cols + 1
# K 線
fig.add_trace(
go.Candlestick(
x=df['date'],
open=df['open'],
high=df['max'],
low=df['min'],
close=df['close'],
name=stock_names.get(stock_id, stock_id),
showlegend=False,
increasing_line_color="red", # 收漲 → 紅色
decreasing_line_color="green" # 收跌 → 綠色
),
row=row,
col=col
)
# 5日均線
fig.add_trace(
go.Scatter(
x=df['date'],
y=df['MA5'],
mode="lines",
line=dict(color="orange", width=1),
name="MA5",
showlegend=False
),
row=row,
col=col
)
# 20日均線
fig.add_trace(
go.Scatter(
x=df['date'],
y=df['MA20'],
mode="lines",
line=dict(color="blue", width=1),
name="MA20",
showlegend=False
),
row=row,
col=col
)
valid_stocks.append(stock_id)
# ====== 關閉所有有效子圖的 Range Slider ======
for i in range(1, len(valid_stocks) + 1):
fig.layout[f'xaxis{i}'].rangeslider.visible = False
# ====== 圖表美化 ======
fig.update_layout(
height=900,
width=1600,
title_text="台股 12 檔股票 K 線圖 + MA5/MA20(資料來源:FinMind)",
showlegend=False,
margin=dict(t=100, b=50)
)
fig.write_html("stock_kline_3x4.html", auto_open=True)
# ====== 抓資料並計算週漲幅 ======
weekly_changes = pd.DataFrame()
for stock_id in stock_ids:
params = {
"dataset": "TaiwanStockPrice",
"data_id": stock_id,
"start_date": start_date,
"end_date": end_date,
"token": token
}
resp = requests.get(url, params=params, verify=False)
data = resp.json()
df = pd.DataFrame(data.get("data", []))
if df.empty or "date" not in df.columns:
print(f"[!] {stock_id} 無資料或格式錯誤")
continue
df["date"] = pd.to_datetime(df["date"])
df = df.set_index("date")
# 取每週最後一個收盤價
weekly_close = df["close"].resample("W").last()
# 計算週漲幅 (%)
weekly_return = weekly_close.pct_change() * 100
weekly_return.name = stock_names.get(stock_id, stock_id)
# 合併到表格
weekly_changes = pd.concat([weekly_changes, weekly_return], axis=1)
# ====== 美化輸出 ======
weekly_changes = weekly_changes.round(2)
weekly_changes.index.name = "date" # 🔹 指定 index 名稱,避免 KeyError
# 彩色 HTML 表格函式
def highlight_table(val):
try:
num = float(val)
num_fmt = f"{num:.2f}"
if num > 0:
return f'<td style="color:red;font-weight:bold;">{num_fmt}</td>'
elif num < 0:
return f'<td style="color:green;font-weight:bold;">{num_fmt}</td>'
else:
return f'<td style="color:black;">{num_fmt}</td>'
except:
return f"<td>{val}</td>"
# 建立 HTML 表格
html_rows = []
# 加上表頭
header = ["日期"] + list(weekly_changes.columns)
html_header = "<tr>" + "".join([f"<th>{col}</th>" for col in header]) + "</tr>"
html_rows.append(html_header)
# 內容
for idx, row in weekly_changes.reset_index().iterrows():
html_cells = [f"<td>{row['date'].strftime('%Y-%m-%d')}</td>"]
for val in row[1:]:
html_cells.append(highlight_table(val))
html_rows.append("<tr>" + "".join(html_cells) + "</tr>")
# 組合 HTML
html_custom = (
"<table border='1' style='border-collapse:collapse; text-align:center;'>"
"<caption style='caption-side:top; font-weight:bold; font-size:16px;'>每週漲幅 (%)</caption>"
+ "".join(html_rows) +
"</table>"
)
# 輸出 HTML 檔
html_path = "weekly_changes.html"
with open(html_path, "w", encoding="utf-8") as f:
f.write(html_custom)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment