使用Grok製作一份台灣ETF的bar chart race

使用Grok製作一份台灣ETF的bar chart race
20250403-image3

最近想透過Grok來製作這份台灣ETF的bar chart race,我把我的想法告訴給AI,他回給我了最陽春的程式碼:

https://www.instagram.com/reel/DHoFx0bTD5f

import yfinance as yf
import pandas as pd
from datetime import datetime
import numpy as np

# 步驟1:收集使用者輸入
def get_user_input():
    stock_codes = []
    while True:
        code = input("請輸入股票代碼(輸入 'done' 結束):")
        if code.lower() == 'done':
            break
        stock_codes.append(code + '.TW')  # 自動加上 .TW 後綴,適用於台股

    invest_day = int(input("請輸入每月定期定額的日期(1-31):"))
    
    while True:
        start_date_input = input("請輸入開始定期定額的日期(格式:YYYY-MM-DD,例如 2020-01-01):")
        try:
            start_date = pd.to_datetime(start_date_input).tz_localize('UTC')
            break
        except ValueError:
            print("日期格式錯誤,請重新輸入(例如 2020-01-01)。")
    
    while True:
        try:
            monthly_investment = float(input("請輸入每次定期定額的金額(例如 1000):"))
            if monthly_investment <= 0:
                print("金額必須大於 0,請重新輸入。")
                continue
            break
        except ValueError:
            print("輸入無效,請輸入一個數字。")

    print(f"\n你選擇的股票代碼:{stock_codes}")
    print(f"每月定期定額日期:{invest_day}號")
    print(f"開始日期:{start_date.strftime('%Y-%m-%d')}")
    print(f"每次定期定額金額:{monthly_investment} 元")
    return stock_codes, invest_day, start_date, monthly_investment

# 步驟2 & 3:收集數據並計算定期定額(含股息再投入)
def process_data(stock_codes, invest_day, start_date, monthly_investment):
    end_date = pd.to_datetime(datetime.today().strftime('%Y-%m-%d')).tz_localize('UTC')

    # 獲取所有股票的歷史數據(包括股息)並提取名稱
    data = {}
    dividends_data = {}
    stock_names = {}  # 儲存股票名稱
    for code in stock_codes:
        stock = yf.Ticker(code)
        df = stock.history(start=start_date.strftime('%Y-%m-%d'), end=end_date.strftime('%Y-%m-%d'))
        if df.empty:
            print(f"警告:無法獲取 {code} 的數據,請檢查股票代碼或網路連線。")
            continue
        df.index = df.index.tz_convert('UTC')  # 統一轉換為 UTC 時區
        dataundefined = df['Close']
    dividends_data<figure><pre class=""><code>undefined</code></pre></figure> = df['Dividends']
    # 提取股票名稱
    try:
        stock_info = stock.info
        stock_names[code.split('.')[0]] = stock_info.get('longName', stock_info.get('shortName', '未知名稱'))
    except Exception as e:
        print(f"警告:無法獲取 {code} 的名稱,錯誤:{e}")
        stock_names[code.split('.')[0]] = '未知名稱'

if not data:
    raise ValueError("所有股票數據均無法獲取,請檢查輸入的股票代碼或網路連線。")

# 獲取所有唯一的交易日
all_trading_days = pd.concat([df.index.to_series() for df in data.values()]).unique()
all_trading_days = pd.DatetimeIndex(all_trading_days).sort_values()
print(f"交易日數量:{len(all_trading_days)}")

# 生成每月的定期定額日期
invest_dates = []
current_date = start_date.replace(day=1)  # 從開始月份的第一天開始
while current_date &lt;= end_date:
    try:
        invest_date = pd.Timestamp(year=current_date.year, month=current_date.month, day=invest_day, tz='UTC')
    except ValueError:
        current_date += pd.DateOffset(months=1)
        continue

    if invest_date in all_trading_days:
        invest_dates.append(invest_date)
    else:
        next_trading_day_idx = np.searchsorted(all_trading_days, invest_date, side='right')
        if next_trading_day_idx &lt; len(all_trading_days):
            next_trading_day = all_trading_days[next_trading_day_idx]
            if next_trading_day &lt;= end_date:
                invest_dates.append(next_trading_day)

    current_date += pd.DateOffset(months=1)

invest_dates = pd.to_datetime(invest_dates).unique()
print(f"生成的定期定額日期數量:{len(invest_dates)}")
if len(invest_dates) == 0:
    raise ValueError("無法生成有效的定期定額日期,請檢查日期範圍或交易日數據。")

# 計算每檔股票的累積價值(含股息再投入)
cumulative_values = pd.DataFrame(index=invest_dates)
for code in stock_codes:
    if code not in data:
        continue
    prices = data<figure><pre class=""><code>undefined</code></pre></figure>[data<figure><pre class=""><code>undefined</code></pre></figure>.index.isin(invest_dates)]
    dividends = dividends_data<figure><pre class=""><code>undefined</code></pre></figure>[dividends_data<figure><pre class=""><code>undefined</code></pre></figure>.index.isin(invest_dates)]
    
    units = 0  # 持有單位數
    values = []
    for date in invest_dates:
        price = prices.get(date)
        dividend = dividends.get(date, 0)  # 若無股息,預設為 0
        if pd.notna(price):
            # 計算當天股息金額(持有單位數 × 每單位股息)
            dividend_amount = units * dividend if dividend &gt; 0 else 0
            # 總投入金額 = 使用者輸入的固定金額 + 股息再投入
            total_investment = monthly_investment + dividend_amount
            # 購買單位數
            units += total_investment / price
            # 當前總價值
            total_value = units * price
            values.append(total_value)
        else:
            values.append(None)
    cumulative_values[code.split('.')[0]] = values

# 將累積價值取整到個位數
cumulative_values = cumulative_values.round(0).astype(int)

# 添加總投入成本(使用者輸入的金額 × 投資次數)
total_investments = len(invest_dates)  # 總投資次數
total_cost = total_investments * monthly_investment
cumulative_values.loc['總投入成本'] = total_cost

# 格式化日期索引
cumulative_values.index = cumulative_values.index.map(lambda x: x.strftime('%Y-%m-%d') if isinstance(x, pd.Timestamp) else x)

return cumulative_values, stock_names

主程式
def main():
stock_codes, invest_day, start_date, monthly_investment = get_user_input()
cumulative_values, stock_names = process_data(stock_codes, invest_day, start_date, monthly_investment)
output_file = 'etf_cumulative_values.csv'
cumulative_values.to_csv(output_file)
print(f"\n數據已儲存為 {output_file}")
print("請將此檔案手動上傳至 Flourish,選擇 'Bar Chart Race' 模板進行調整。")
print("\n數據預覽(含總投入成本):")
print(cumulative_values)
print("\n股票代號與名稱:")
print("股票代號:", ", ".join(stock_names.keys()))
print("股票名稱:", ", ".join(stock_names.values()))

if name == "main":
main()

這個程式主要功能有:

1.可以輸入很多ETF代碼

2.每個月幾號做定期定額

3.定期定額開始日

4.每次投入的金額

然後,會自動股息再投入(當天)

最終結果會產生CSV檔,如下:

我只需要把這個CSV檔,匯入到專門做這種bar chart race 的網站Flourish去做就可以了。

在Flourish簡單的設定行列代表的名稱,例如Lable標籤是代號、Value是月份對應的現在的資產;Categories分類就填中文名稱。

簡單的設定好之後,就會有競賽圖出現囉。

做這可以幹嘛?

我想可以給投資新手看如果簡單的定期定額,例如我做的是每個月無腦投10000,從2020年8月投資到現在,如果不投資放銀行就只有56萬,但是丟到0050卻可以有85萬的價值,直接多出51%的價值!更何況時下跌時加碼投資後,績效會更佳。

簡單來說就是圖示化投資績效,讓投資者更了解定期定額的魅力。

不過這個程式碼有個小問題,就是沒辦法做到配股,我嘗試試了幾回AI,到最後股數會莫名其妙翻倍,所以,如果拿來做銀行股,或其他有配股的公司計算定期定額,可能會稍微不準。

供給讀者們參考~

Read more

讀書心得-Read Write Own:開啟WEB3新局的區塊鏈網路

讀書心得-Read Write Own:開啟WEB3新局的區塊鏈網路

本書清楚的說明網路及區塊鏈的歷史以及區塊鏈的優點,以及抨擊那些獨佔網路生態的大型企業,對於不熟悉區塊鏈的新手,是個很好的入門書,理解到區塊鏈的迷人之處。 協定網路與區塊鏈網路 協定網路是一個大家講好的「規則」,是由中心化機構維持運作,例如「網際網路協定 TCP/IP」、「網站瀏覽協定HTTP」多半是由領少量的捐款或是志工來維持經營,所以要維持運作的話通常比較困難。 作者說明有個強化協定網路運作的方法,「區塊鏈網路」 區塊鏈網路採取「分散式帳本及共識帳本」、「代幣獎勵」、「不可竄改」、「去中心化」等方式來維持運作,例如:想要做交易,將交易需求發送各個節點,讓他們簽屬通過,好處是,發起交易者的手續費是直接交由共同簽屬的節點(礦工)。 這種運作方式聽起來好像沒甚麼特別的,但卻是非常「去中心化」的方式,也就是說,是經由多數人的檢查及驗證同意,意即要竄改內容是無法完成交易的,也因此區塊鏈網路是本質上透明、不可以修改的運作方式。 企業網路 網路初期,還沒有任何一家公司把網路社群一把抓住。 在我有印象的早期是使用撥接網路,接著可以上到Yahoo搜尋一些網路資料,接著開始出現了聊天

By 落葉
讀書心得-耕股

讀書心得-耕股

耕股主要是講基本面取向的投資用書,閱讀搭配作者建立的股票研究網站,比較不沉悶。 週期循環指標 依照作者經驗,當我們可以從以下六點來看出目前股市處在哪個階段: 1.巴菲特指標(台股&美股) 依照作者整理,當台股巴菲特指標超過180%時隨即會發生市場修正,在1990年中東危機、2008年金融海嘯、2020年美中貿易戰也都是超過180%,並且發生了修正。 但聰明的讀者會發現,從2020年過後直到今日,巴菲特指標都超過180%,最高已達到333%(25年10月底),為何持續5年都沒發生過大修正? 我個人解讀是,投資者對於風險容忍度提高了,既使巴菲特指數已嚴重超過標準值180%,但仍毫無畏懼的持續投資。 不過我也認為經濟泡沫或許比以往歷史吹的還大,等到泡沫爆開時,可能會跌得非常慘。 2.台灣百大優質企業累計營收年增率趨勢 是作者自創的指標,觀察台灣百大企業營收年增率、3個月營收成長率的走勢關係,當3個月營收成長率向上穿過營收年增率表示景氣不錯(黃金交叉),當3個月營收成長率向下穿過年增率表示景氣有衰退現象(死亡交叉)。 景氣非常好應該是3個月營收率持續都在往上增加,年收成長

By 落葉
讀書心得-我可能錯了

讀書心得-我可能錯了

本書時作者比約恩是個瑞典人,主要講述他前往泰國當森林僧人的十七年來學到的對人生的體悟。 我有著念頭,可是我的念頭不代表我 這句話非常有魔力:「我有著念頭,可是我的念頭不代表我。」 當我們在低潮時,有時會有些負面想法。 而這些負面想法,如果久了又多了,真的對我們心理造成很大的傷害。 這句話可以讓自己站在客觀的角度觀察想法,觀察這個與我無關的想法。我把他解讀為「雜訊」,這只是腦中的一段雜訊,左耳進、右耳出了。 書中提到:「如何放下拖累你的一連串念頭呢?--你需要將注意力轉移到別處。這些念頭唯一的養分來源,就是你的注意力。」 這句話很棒,因為當處在低潮時,很容易一直陷入在負面想法的循環中,那我們可以做的事情就是「轉移注意力」,停止沒必要的負面想法。 書中又提到:「所有人都有能力放下自己的念頭,以及選擇將注意力擺在哪裡,並決定讓注意力在對自己無助益的事情上停留多長的時間。」 像一個有如「兔子」般的人敞開心扉 這也是體驗當下的方式。 有時聽朋友說些自己以為聽過的事情,就變得漫不經心,或是接話,其實這對演講者很不尊重。 我們應時時保持謙遜、保持好奇心,放下成見的仔細聆聽

By 落葉
讀書心得-隱性潛能

讀書心得-隱性潛能

本書主要是講學習法和自我成長的心法,每一章節會先講故事,接著引導到自述的論點,讀起來非常順,且又不無聊。 有時候,你可能會覺得學習某些技能很不順利,總是卡卡的,或是好像學不怎麼深刻,過一陣子好像又忘個精光。 以下是我讀到比較令我印象深刻的學技能心法。 盡量犯錯(享受不適感) 書中舉學習語言當例子。 比如我們在學習英文,我們很害怕自己講錯話,用錯單字、文法,使得對方覺得我們好像英文很差,然後被嘲笑。 不過其實我們應該要"擁抱尷尬"才對,想想還在嬰兒時期的我們,我們不是也都牙牙學語嗎? 換個角度想,如果有個學中文的外國人,他試著用中文跟你溝通,他不小心說錯字時,是不是已經覺得他很厲害了?我們不會放大他的錯誤,而是為他"因為努力說我們的語言而感到開心",是個很有勇氣的人,對吧! 說錯被糾正,使得我們記住這個字該如何說、這個情況要怎麼回答,這才是學習語言的方式。 我們也應該每天累積犯錯機會,多講就會成長、講錯就改進。 不完美主義 完美主義也是會拖累我們學習的進度。 完美主義者害怕犯錯、給予太多假設、

By 落葉