雖然現在我已使用 AI 協作方式產出程式碼與 debug, 主控權與選擇權仍然在我. 但是最近跟明賢表弟在 LINE 上交流軟體經驗後, 發現我在這部分似乎定格在石器時代了, 我原先對要花錢的 Cursor, GitHub Copilot, 或 Claude Code 等 Vibe coding 工具抱持觀望態度, 認為能透過 ChatGPT 交互問答得到答案又何必要花錢? 直到看過他展示目前的作業模式後, 我的態度與觀點就開始動搖了, 於是決定從 GitHub Copilot 著手來了解 Vibe coding.
我上周末從母校圖書館借到下面這本書, 依照書中說明啟用 GitHub Copilot 功能 :
登入 GitHub 後, 按右上角人頭, 點選 "Copilot settings" :
在 GitHub Copilot 頁面中按 "Start using Copilot Free" :
出現交談頁面, 輸入提示詞按 Enter 或右方三角形按鈕提交詢問 :
預設模型是 GPT-5 mini, 點下拉式選單可選擇模型, 免費版只能選 GPT-5 mini, GPT 4.1, GPT 4o, 以及 Claude Haiku 4.1 四種模型 :
我用經典的九九乘法表來測試 :
生成如下程式碼 :
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
multiplication_table.py
印出傳統的九九乘法表(1 到 9)
"""
def multiplication_table(n: int = 9) -> None:
"""印出 1..n 的乘法表,每列只顯示到當前被乘數(傳統九九表格式)。"""
for i in range(1, n + 1):
for j in range(1, i + 1):
# i*j 以 2 個字元寬度對齊,方便看齊
print(f"{j}×{i}={i*j:2}", end=" ")
print()
if __name__ == "__main__":
multiplication_table()
我把程式丟到 Thonny 去執行, 結果如下 :
>>> %Run multiplication_table.py
1×1= 1
1×2= 2 2×2= 4
1×3= 3 2×3= 6 3×3= 9
1×4= 4 2×4= 8 3×4=12 4×4=16
1×5= 5 2×5=10 3×5=15 4×5=20 5×5=25
1×6= 6 2×6=12 3×6=18 4×6=24 5×6=30 6×6=36
1×7= 7 2×7=14 3×7=21 4×7=28 5×7=35 6×7=42 7×7=49
1×8= 8 2×8=16 3×8=24 4×8=32 5×8=40 6×8=48 7×8=56 8×8=64
1×9= 9 2×9=18 3×9=27 4×9=36 5×9=45 6×9=54 7×9=63 8×9=72 9×9=81
看起來還不錯, 接下來改用難一點的, 叫它幫我寫一個用 pandas-ta 套件計算 DMI 指標並繪製 K 線圖的程式, 提示詞如下 :
"請用 pandas-ta 套件的函式 ta.adx() 寫一個計算趨向指標 DMI (Directional Movement Index), 並使用 kbar 套件繪製 K 線圖 (顯示成交量) 的 Python 程式"
生成的程式碼看起來有點複雜 :
# dmi_with_pandas_ta_and_kbar.py
from __future__ import annotations
import sys
import warnings
import pandas as pd
import pandas_ta as ta
import yfinance as yf
# plotting imports will be attempted lazily
import matplotlib.pyplot as plt
warnings.filterwarnings("ignore", category=FutureWarning)
def download_ohlcv(ticker: str = "AAPL", period: str = "6mo", interval: str = "1d") -> pd.DataFrame:
"""
從 yfinance 下載 OHLCV 資料並回傳 DataFrame,index 為 DatetimeIndex。
"""
df = yf.download(ticker, period=period, interval=interval, progress=False)
if df.empty:
raise RuntimeError(f"無法從 yfinance 取得資料: {ticker} {period} {interval}")
# 確保欄位小寫且名稱為 open, high, low, close, volume
df = df.rename(columns={c: c.lower() for c in df.columns})
# 有些 yfinance 回傳 'adj close',確保 'close' 存在
if "close" not in df.columns and "adj close" in df.columns:
df["close"] = df["adj close"]
return df[["open", "high", "low", "close", "volume"]]
def compute_adx_dmi(df: pd.DataFrame, length: int = 14) -> pd.DataFrame:
"""
使用 pandas-ta 的 DataFrame accessor .ta.adx() 計算 ADX 與 DMP/DMN(+DI, -DI)。
會 append 欄位到原始 DataFrame:
- ADX_{length}
- DMP_{length} (通常視為 +DI)
- DMN_{length} (通常視為 -DI)
另外建立便於使用的標準欄位: '+DI'、'-DI'、'ADX'
"""
# pandas_ta 的 DataFrame accessor
df = df.copy()
df.ta.adx(length=length, append=True) # 會生成 ADX_{len}, DMP_{len}, DMN_{len}
adx_col = f"ADX_{length}"
dmp_col = f"DMP_{length}"
dmn_col = f"DMN_{length}"
if adx_col not in df.columns or dmp_col not in df.columns or dmn_col not in df.columns:
raise RuntimeError("pandas-ta 未生成預期欄位,請確認 pandas-ta 版本與使用方法。")
# 方便使用的別名
df["+DI"] = df[dmp_col]
df["-DI"] = df[dmn_col]
df["ADX"] = df[adx_col]
return df
def plot_with_kbar_if_available(df: pd.DataFrame, ticker: str = "AAPL") -> None:
"""
嘗試使用 kbar 繪圖;若失敗則退回到 mplfinance。
kbar API 在不同版本可能有差異,所以這邊採用保守的嘗試方式:
- 嘗試呼叫常見的 kbar.plot 或 kbar.kline.plot 等函式
- 若都不存在或呼叫失敗,使用 mplfinance 繪圖
"""
try:
import kbar
except Exception:
kbar = None
# 準備要在副圖上畫的線
add_indicator_lines = {
"+DI": df["+DI"],
"-DI": df["-DI"],
"ADX": df["ADX"],
}
# 嘗試 kbar 的常見畫法(多數 kbar 版本會接受 DataFrame)
if kbar is not None:
try:
# 嘗試幾種常見的 kbar API 風格
# 1) kbar.plot(df, ...) 或 kbar.kline.plot(...)
if hasattr(kbar, "plot"):
try:
# 這種呼叫是最簡短直觀的嘗試
kbar.plot(df, type="candle", volume=True, title=f"{ticker} - K 線 (kbar)")
# kbar.plot 不一定支援 overlay 指標,若需要可另外畫
# 畫 +DI, -DI, ADX 在新視窗
fig, ax = plt.subplots(1, 1, figsize=(10, 3))
ax.plot(df.index, df["+DI"], label="+DI", color="green")
ax.plot(df.index, df["-DI"], label="-DI", color="red")
ax.plot(df.index, df["ADX"], label="ADX", color="blue")
ax.set_title(f"{ticker} - +DI / -DI / ADX")
ax.legend()
plt.show()
return
except Exception:
# 如果 kbar.plot 存在但執行失敗,嘗試其他 API
pass
# 2) kbar.kline.plot
if hasattr(kbar, "kline") and hasattr(kbar.kline, "plot"):
kbar.kline.plot(df, volume=True, title=f"{ticker} - K 線 (kbar.kline)")
fig, ax = plt.subplots(1, 1, figsize=(10, 3))
ax.plot(df.index, df["+DI"], label="+DI", color="green")
ax.plot(df.index, df["-DI"], label="-DI", color="red")
ax.plot(df.index, df["ADX"], label="ADX", color="blue")
ax.set_title(f"{ticker} - +DI / -DI / ADX")
ax.legend()
plt.show()
return
# 3) kbar.KLine 或其他類別介面
if hasattr(kbar, "KLine"):
try:
k = kbar.KLine(df)
if hasattr(k, "plot"):
k.plot(volume=True, title=f"{ticker} - K 線 (kbar.KLine)")
fig, ax = plt.subplots(1, 1, figsize=(10, 3))
ax.plot(df.index, df["+DI"], label="+DI", color="green")
ax.plot(df.index, df["-DI"], label="-DI", color="red")
ax.plot(df.index, df["ADX"], label="ADX", color="blue")
ax.set_title(f"{ticker} - +DI / -DI / ADX")
ax.legend()
plt.show()
return
except Exception:
pass
# 如果 kbar 存在但上述方法都失敗,拋出例外讓程式使用 mplfinance
raise RuntimeError("kbar 已安裝但常見 API 呼叫失敗或不相容,改回使用 mplfinance。")
except Exception as e:
print("kbar 繪圖失敗,原因:", e, file=sys.stderr)
print("將使用 mplfinance 作為備援繪圖。", file=sys.stderr)
# --- mplfinance 備援繪圖 ---
try:
import mplfinance as mpf
except Exception as e:
raise RuntimeError("未安裝 kbar,也未安裝 mplfinance。請安裝至少一個繪圖套件 (pip install kbar mplfinance)。") from e
# 準備 mplfinance 的 addplot
ap = [
mpf.make_addplot(df["+DI"], color="g", width=1.0, panel=1, ylabel="+DI / -DI"),
mpf.make_addplot(df["-DI"], color="r", width=1.0, panel=1),
mpf.make_addplot(df["ADX"], color="b", width=1.0, panel=2, ylabel="ADX"),
]
# 長條圖顏色根據漲跌
mc = mpf.make_marketcolors(up="tab:red", down="tab:green", volume="gray")
s = mpf.make_mpf_style(marketcolors=mc)
mpf.plot(
df,
type="candle",
volume=True,
style=s,
addplot=ap,
title=f"{ticker} - K 線 (mplfinance 備援)",
figsize=(12, 8),
datetime_format="%Y-%m-%d",
tight_layout=True,
)
def main():
# 這裡可以改成從 CLI 參數讀入
TICKER = "AAPL"
PERIOD = "6mo"
INTERVAL = "1d"
ADX_LENGTH = 14
print(f"下載 {TICKER} 的 OHLCV 資料 ({PERIOD}, {INTERVAL})...")
df = download_ohlcv(TICKER, PERIOD, INTERVAL)
print(f"資料筆數: {len(df)},樣本時間區間: {df.index[0]} ~ {df.index[-1]}")
print(f"計算 ADX / DMI (length={ADX_LENGTH})...")
df = compute_adx_dmi(df, length=ADX_LENGTH)
# 顯示最後幾列結果以供檢視
print(df[["open", "high", "low", "close", "volume", "+DI", "-DI", "ADX"]].tail(8))
print("嘗試以 kbar 繪圖(若 kbar 不可用或 API 不相容,將退回到 mplfinance)...")
plot_with_kbar_if_available(df, ticker=TICKER)
if __name__ == "__main__":
main()
執行結果出現錯誤 :
>>> %Run dmi_with_pandas_ta_and_kbar.py
下載 AAPL 的 OHLCV 資料 (6mo, 1d)...
Traceback (most recent call last):
File "D:\python\test\dmi_with_pandas_ta_and_kbar.py", line 177, in <module>
main()
File "D:\python\test\dmi_with_pandas_ta_and_kbar.py", line 163, in main
df = download_ohlcv(TICKER, PERIOD, INTERVAL)
File "D:\python\test\dmi_with_pandas_ta_and_kbar.py", line 23, in download_ohlcv
df = df.rename(columns={c: c.lower() for c in df.columns})
File "D:\python\test\dmi_with_pandas_ta_and_kbar.py", line 23, in <dictcomp>
df = df.rename(columns={c: c.lower() for c in df.columns})
AttributeError: 'tuple' object has no attribute 'lower'
顯然它並不知道 yfinance 已經改版了, 新版的傳回值是多層 columns 結構, 必須先轉成傳統的單層 DataFrame, 我將此錯誤訊息回饋給它 :
"上面的程式執行出現如下錯誤, 請 debug 並修正"
但是仍然出現錯誤, 乾脆提供我之前寫的 AOBV 指標測試程式給它 :
"請參考下面 AOBV 指標的寫法 :
# pandas_ta_aobv.py
import yfinance as yf
import pandas_ta as ta
from kbar import KBar
df=yf.download('0050.TW', start='2024-11-06', end='2025-01-09', auto_adjust=False)
df.columns=df.columns.map(lambda x: x[0])
df.ta.aobv(close='Close', append=True)
kb=KBar(df)
kb.addplot(df['OBV'], panel=2, color='red', width=1.2, ylabel='OBV')
kb.addplot(df['AOBV_LR_2'], panel=3, color='red', width=1.2, ylabel='AOBV')
kb.addplot(df['AOBV_SR_2'], panel=3, color='blue', width=1.2)
kb.plot(volume=True, mav=(5, 10), title='K 線圖與 OBV, AOBV 指標')"
終於給出可以正確執行的程式碼 :
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
dmi_with_pandas_ta_and_kbar.py
說明:
- 參考你提供的 AOBV 範例寫法,使用 kbar.KBar 來繪製 K 線(顯示成交量)並把 +DI / -DI / ADX 加到副圖。
- 使用 pandas-ta 的 .ta.adx() 計算 ADX / +DI / -DI。
- 若欄位為 MultiIndex(yfinance 在某些情況會回傳),會取每個 tuple 的第一個元素,如同你的 AOBV 範例。
- 若 kbar 不可用,會退回到 mplfinance 繪圖(與前一版行為一致)。
用法:
python dmi_with_pandas_ta_and_kbar.py --ticker AAPL --period 6mo --interval 1d --length 14
或用 start/end:
python dmi_with_pandas_ta_and_kbar.py --ticker 0050.TW --start 2024-11-06 --end 2025-01-09 --interval 1d --length 14
"""
from __future__ import annotations
import argparse
import sys
import warnings
import pandas as pd
import pandas_ta as ta
import yfinance as yf
warnings.filterwarnings("ignore", category=FutureWarning)
def download_ohlcv(ticker: str, period: str | None, start: str | None, end: str | None, interval: str) -> pd.DataFrame:
"""
下載 OHLCV,並針對 MultiIndex 欄位做簡單 normalize(取 tuple[0])。
最後確保至少有 Open/High/Low/Close/Volume (TitleCase 或 Close/close) 可用。
"""
# yfinance: 若有 start/end 則通常不用 period
if start or end:
df = yf.download(ticker, start=start, end=end, interval=interval, auto_adjust=False, progress=False)
else:
df = yf.download(ticker, period=period or "6mo", interval=interval, auto_adjust=False, progress=False)
if df.empty:
raise RuntimeError(f"無法從 yfinance 取得資料: {ticker} (period={period}, start={start}, end={end}, interval={interval})")
# 如果是 MultiIndex,像 (('Open',''),('High','')) 等情況,取 tuple 的第 0 個元素 (與你的 AOBV 範例相同)
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.map(lambda x: x[0])
# 有時候欄位是小寫、有時是 Title Case,統一處理:
cols = {c: str(c) for c in df.columns}
# 若只有 'Adj Close' 而沒有 'Close',建立 Close 欄位(跟 AOBV 範例做法一致)
if "Close" not in df.columns and "Adj Close" in df.columns:
df["Close"] = df["Adj Close"]
if "close" not in df.columns and "adj close" in [c.lower() for c in df.columns]:
# handle lowercase adj close
# find actual column name that lower() == 'adj close'
for c in df.columns:
if str(c).lower() == "adj close":
df["close"] = df[c]
break
# 最後檢查至少能找到 High/Low/Close (或 high/low/close)
found_high = any(c.lower() == "high" for c in df.columns)
found_low = any(c.lower() == "low" for c in df.columns)
found_close = any(c.lower() == "close" for c in df.columns)
found_open = any(c.lower() == "open" for c in df.columns)
found_volume = any(c.lower() == "volume" for c in df.columns)
if not (found_high and found_low and found_close):
raise RuntimeError(f"yfinance 回傳欄位缺少 high/low/close (available: {list(df.columns)})")
# 為了方便 pandas-ta 與 kbar,建立常見 TitleCase 欄位 (Open/High/Low/Close/Volume) 如果尚未有
def ensure_title(col_lower, title):
if title not in df.columns:
for c in df.columns:
if str(c).lower() == col_lower:
df[title] = df[c]
return
ensure_title("open", "Open")
ensure_title("high", "High")
ensure_title("low", "Low")
ensure_title("close", "Close")
ensure_title("volume", "Volume")
return df
def compute_adx_dmi(df: pd.DataFrame, length: int = 14) -> pd.DataFrame:
"""
使用 pandas-ta 計算 ADX/DMP/DMN,並增加 '+DI','-DI','ADX' 欄位 (以 length 為 suffix)。
需要欄位 High/Low/Close(不論大小寫)。
"""
# pandas-ta 在 DataFrame accessor 上運作,接受 high/low/close 欄位名稱參數
# 我們先嘗試用 TitleCase 欄位 (High/Low/Close)(符合 yfinance 常態)
df2 = df.copy()
# 指定欄位名稱(若沒有 TitleCase,改用小寫)
high_col = "High" if "High" in df2.columns else next((c for c in df2.columns if c.lower() == "high"), None)
low_col = "Low" if "Low" in df2.columns else next((c for c in df2.columns if c.lower() == "low"), None)
close_col = "Close" if "Close" in df2.columns else next((c for c in df2.columns if c.lower() == "close"), None)
if not (high_col and low_col and close_col):
raise RuntimeError("需要 high/low/close 欄位才能計算 ADX/DMI")
# pandas-ta 的 ta.adx 可以接受參數 high/low/close
df2.ta.adx(length=length, high=high_col, low=low_col, close=close_col, append=True)
# pandas-ta 會建立 ADX_{length}, DMP_{length}, DMN_{length}
adx_col = f"ADX_{length}"
dmp_col = f"DMP_{length}"
dmn_col = f"DMN_{length}"
if adx_col not in df2.columns or dmp_col not in df2.columns or dmn_col not in df2.columns:
raise RuntimeError("pandas-ta 未生成預期欄位,請確認 pandas-ta 版本 (生成 ADX_{len}, DMP_{len}, DMN_{len})")
# 建立更方便的欄位名稱(與你先前程式一致)
df2["+DI"] = df2[dmp_col]
df2["-DI"] = df2[dmn_col]
df2["ADX"] = df2[adx_col]
return df2
def plot_with_kbar(df: pd.DataFrame, ticker: str = "AAPL", mav: tuple | None = (5, 10)):
"""
依照 AOBV 的寫法使用 kbar.KBar:
- kb = KBar(df)
- kb.addplot(series, panel=..., color=..., width=..., ylabel=...)
- kb.plot(volume=True, mav=(...), title=...)
若 kbar 不可用,退回到 mplfinance。
"""
try:
from kbar import KBar
except Exception as e:
KBar = None
print("kbar 無法匯入,將使用 mplfinance 作為備援繪圖。", file=sys.stderr)
# kbar 範例通常以 TitleCase 欄位 (Open/High/Low/Close/Volume)
# 確保 DataFrame 中有 TitleCase 欄位,否則建立對應欄位
df_plot = df.copy()
for lower, title in (("open", "Open"), ("high", "High"), ("low", "Low"), ("close", "Close"), ("volume", "Volume")):
if title not in df_plot.columns:
for c in df_plot.columns:
if str(c).lower() == lower:
df_plot[title] = df_plot[c]
break
if KBar is not None:
try:
kb = KBar(df_plot)
# +DI / -DI 放 panel=2,ADX 放 panel=3(參照你給的 AOBV 範例 panel 用法)
kb.addplot(df_plot["+DI"], panel=2, color="green", width=1.2, ylabel="+DI")
kb.addplot(df_plot["-DI"], panel=2, color="red", width=1.2)
kb.addplot(df_plot["ADX"], panel=3, color="blue", width=1.2, ylabel="ADX")
kb.plot(volume=True, mav=mav, title=f"{ticker} - K 線圖與 DMI (+DI/-DI/ADX)")
return
except Exception as e:
print("使用 kbar 繪圖時發生錯誤,將退回到 mplfinance。錯誤:", e, file=sys.stderr)
# mplfinance 備援繪圖
try:
import mplfinance as mpf
except Exception as e:
raise RuntimeError("未安裝 kbar,也未安裝 mplfinance。請安裝至少一個繪圖套件 (pip install kbar mplfinance)。") from e
# mplfinance 需要 TitleCase 欄位
df_mpf = df_plot.copy()
ap = [
mpf.make_addplot(df_mpf["+DI"], color="g", width=1.0, panel=1, ylabel="+DI / -DI"),
mpf.make_addplot(df_mpf["-DI"], color="r", width=1.0, panel=1),
mpf.make_addplot(df_mpf["ADX"], color="b", width=1.0, panel=2, ylabel="ADX"),
]
mc = mpf.make_marketcolors(up="tab:red", down="tab:green", volume="gray")
s = mpf.make_mpf_style(marketcolors=mc)
mpf.plot(
df_mpf.rename(columns={c: (c if isinstance(c, str) else str(c)) for c in df_mpf.columns}),
type="candle",
volume=True,
style=s,
addplot=ap,
title=f"{ticker} - K 線圖與 DMI (+DI/-DI/ADX) (mplfinance)",
figsize=(12, 8),
mav=mav,
datetime_format="%Y-%m-%d",
tight_layout=True,
)
def parse_args():
p = argparse.ArgumentParser(description="計算 DMI (ADX/+DI/-DI) 並繪製 K 線 (kbar 或 mplfinance)")
p.add_argument("--ticker", "-t", default="AAPL", help="股票代號,例如 AAPL 或 0050.TW")
p.add_argument("--period", "-p", default="6mo", help="若未指定 start/end,使用 yfinance period (ex: 6mo, 1y)")
p.add_argument("--start", help="開始日期 (YYYY-MM-DD),若指定則使用 start/end 而非 period")
p.add_argument("--end", help="結束日期 (YYYY-MM-DD)")
p.add_argument("--interval", "-i", default="1d", help="yfinance interval,例如 1d, 1h")
p.add_argument("--length", "-l", type=int, default=14, help="ADX 計算長度 (預設 14)")
p.add_argument("--mav", nargs="*", type=int, default=(5, 10), help="移動平均線 mav,例如 --mav 5 10")
return p.parse_args()
def main():
args = parse_args()
try:
print(f"下載 {args.ticker} 的 OHLCV 資料 (period={args.period}, start={args.start}, end={args.end}, interval={args.interval})...")
df = download_ohlcv(args.ticker, args.period, args.start, args.end, args.interval)
print(f"資料筆數: {len(df)},時間區間: {df.index[0]} ~ {df.index[-1]}")
print(f"計算 ADX / DMI (length={args.length})...")
df2 = compute_adx_dmi(df, length=args.length)
# 顯示最後幾列確認
display_cols = [c for c in ["Open", "High", "Low", "Close", "Volume", "+DI", "-DI", "ADX"] if c in df2.columns]
print(df2[display_cols].tail(8))
print("繪圖(優先使用 kbar,如不可用則使用 mplfinance)...")
plot_with_kbar(df2, ticker=args.ticker, mav=tuple(args.mav) if args.mav else None)
except Exception as e:
print("執行發生錯誤:", e, file=sys.stderr)
raise
if __name__ == "__main__":
main()
執行結果 :
唉, 明明 11 行程式碼就能搞定, 為何要寫得這麼複雜呢?







沒有留言 :
張貼留言