跳轉到

串接yfinance抓股票資料,寄送固定財經信件

import os
from pathlib import Path
from dotenv import load_dotenv
import smtplib
import requests
import yfinance as yf

from datetime import datetime
from email.mime.text import MIMEText
from email.header import Header


### 環境變數
current_file = Path(__file__).resolve()  # 取得目前這個 Python 檔案的絕對路徑
project_root = current_file.parent.parent  # 往上返回兩層,取得專案根目錄
env_path = project_root / ".env"  # 指定 .env 檔案路徑

load_dotenv(dotenv_path=env_path)  # 載入 .env 環境變數


### 讀取環境變數
FromAddress = os.getenv("FromAddress")  # 寄件者 Email
AppPassword = os.getenv("AppPassword")  # Gmail 應用程式密碼


### 檢查環境變數
if not FromAddress or not AppPassword:
    print("錯誤:請確認 .env 裡面有 FromAddress 與 AppPassword")
    raise SystemExit


### 清理數字格式
def clean_number(value):
    return float(value.replace(",", "").replace("--", "0"))  # 移除逗號與缺值符號後轉成 float


### 取得台股資料
def get_tw_stock_data(stock_code):
    today = datetime.today()  # 取得今天日期
    date_text = today.strftime("%Y%m%d")  # 轉成 TWSE API 需要的日期格式

    url = "https://www.twse.com.tw/exchangeReport/STOCK_DAY"  # 台灣證交所每日成交資訊 API

    params = {
        "response": "json",
        "date": date_text,
        "stockNo": stock_code
    }

    response = requests.get(url, params=params)  # 向台灣證交所送出查詢請求
    result = response.json()  # 將回傳內容轉成 JSON 格式

    if "data" not in result or len(result["data"]) < 2:
        return None  # 若資料不足,回傳 None

    data = result["data"]  # 取得每日交易資料

    latest = data[-1]  # 最新一筆交易資料
    previous = data[-2]  # 前一個交易日資料

    latest_date = latest[0]  # 最新交易日期
    volume = clean_number(latest[1])  # 成交股數
    open_price = clean_number(latest[3])  # 開盤價
    high_price = clean_number(latest[4])  # 最高價
    low_price = clean_number(latest[5])  # 最低價
    close_price = clean_number(latest[6])  # 收盤價

    previous_close = clean_number(previous[6])  # 前一個交易日收盤價

    return {
        "ticker": stock_code,
        "latest_date": latest_date,
        "open": open_price,
        "high": high_price,
        "low": low_price,
        "close": close_price,
        "volume": volume,
        "previous_close": previous_close
    }


### 取得美股資料
def get_us_stock_data(ticker_symbol):
    try:
        data = yf.download(
            ticker_symbol,
            period="10d",
            interval="1d",
            progress=False,
            auto_adjust=False,
            threads=False
        )  # 使用 yfinance 下載近 10 天每日交易資料

    except Exception as error:
        print("錯誤:yfinance 抓取失敗")
        print(error)
        return None

    # yfinance 有時會回傳多層欄位,這裡統一轉成單層欄位
    if hasattr(data.columns, "nlevels") and data.columns.nlevels > 1:
        data.columns = data.columns.get_level_values(0)

    data = data.dropna()  # 移除空值資料

    if len(data) < 2:
        return None  # 若資料不足,回傳 None

    latest = data.iloc[-1]  # 最新一筆交易資料
    previous = data.iloc[-2]  # 前一個交易日資料

    return {
        "ticker": ticker_symbol,
        "latest_date": data.index[-1].strftime("%Y-%m-%d"),
        "open": float(latest["Open"]),
        "high": float(latest["High"]),
        "low": float(latest["Low"]),
        "close": float(latest["Close"]),
        "volume": float(latest["Volume"]),
        "previous_close": float(previous["Close"])
    }


### 使用者輸入
to_address = input("請輸入收件人 Email:").strip()  # 輸入收件者 Email
ticker_symbol = input("請輸入股票或 ETF 代號,例如 2330、006208、0050、NVDA、AAPL:").strip()  # 輸入股票代號


### 檢查使用者輸入
if not to_address:
    print("錯誤:收件人 Email 不可以空白")
    raise SystemExit

if not ticker_symbol:
    print("錯誤:股票或 ETF 代號不可以空白")
    raise SystemExit


### 判斷台股或美股
if ticker_symbol.isdigit():
    stock_data = get_tw_stock_data(ticker_symbol)  # 純數字代號視為台股
else:
    stock_data = get_us_stock_data(ticker_symbol)  # 非純數字代號視為美股


### 檢查股票資料
if stock_data is None:
    print(f"錯誤:查無 {ticker_symbol} 的資料,請確認股票或 ETF 代號是否正確")
    raise SystemExit


### 取出股票資料
latest_date = stock_data["latest_date"]  # 最新交易日期
open_price = stock_data["open"]  # 開盤價
high_price = stock_data["high"]  # 最高價
low_price = stock_data["low"]  # 最低價
close_price = stock_data["close"]  # 收盤價
volume = stock_data["volume"]  # 成交量
previous_close = stock_data["previous_close"]  # 前一個交易日收盤價

change_amount = close_price - previous_close  # 計算漲跌金額
change_percent = (change_amount / previous_close) * 100  # 計算漲跌幅百分比


### 簡單解讀
if change_amount > 0:
    market_comment = "今日收盤價高於前一個交易日,短線表現偏強。"

elif change_amount < 0:
    market_comment = "今日收盤價低於前一個交易日,短線表現偏弱。"

else:
    market_comment = "今日收盤價與前一個交易日相同,股價變動不大。"


if close_price > open_price:
    intraday_comment = "盤中走勢來看,收盤價高於開盤價,代表買盤相對積極。"

elif close_price < open_price:
    intraday_comment = "盤中走勢來看,收盤價低於開盤價,代表賣壓相對明顯。"

else:
    intraday_comment = "盤中走勢來看,開盤價與收盤價相同,整體波動有限。"


### 組合信件
subject = f"【財經觀察報告】{ticker_symbol}"  # 郵件主旨

body = f"""
您好:

以下是您查詢的股票 / ETF 財經觀察報告。

股票或 ETF 代號:{ticker_symbol}
最新交易日期:{latest_date}

開盤價:{open_price:.2f}
最高價:{high_price:.2f}
最低價:{low_price:.2f}
最新收盤價:{close_price:.2f}
成交量:{volume:,.0f}

與前一個交易日相比:
漲跌金額:{change_amount:.2f}
漲跌幅:{change_percent:.2f}%

簡單解讀:
{market_comment}
{intraday_comment}

提醒:
本信件僅供教學練習,不構成投資建議。
"""


### 建立郵件內容
msg = MIMEText(body, "plain", "utf-8")  # 建立純文字郵件內容
msg["Subject"] = Header(subject, "utf-8")  # 設定郵件主旨
msg["From"] = FromAddress  # 設定寄件者
msg["To"] = to_address  # 設定收件者


### 寄送郵件
try:
    with smtplib.SMTP("smtp.gmail.com", 587) as server:

        server.ehlo()  # 啟動與 SMTP 伺服器的對話
        server.starttls()  # 建立 TLS 加密連線
        server.login(FromAddress, AppPassword)  # 登入 Gmail SMTP 伺服器

        server.sendmail(
            FromAddress,
            [to_address],
            msg.as_string()
        )  # 寄送財經報告郵件

except Exception as error:
    print("錯誤:Gmail 寄送失敗")
    print(error)
    raise SystemExit


print(f"財經報告已寄出至:{to_address}")

提示詞

請幫我寫一個 Python 程式,請給我完整可執行程式碼:

## 功能需求:
使用者執行程式後,可以輸入:
1. 收件人 Email
2. 要查詢的股票或 ETF 代號,例如 2330.TW、0050.TW、NVDA、AAPL

程式需要使用 yfinance 抓取該股票或 ETF 近 5 日的股價資料,並整理成一份純文字財經報告。

## 報告內容需要包含:
- 股票或 ETF 代號
- 最新交易日期
- 最新收盤價
- 開盤價
- 最高價
- 最低價
- 成交量
- 與前一個交易日相比的漲跌金額
- 與前一個交易日相比的漲跌幅
- 一段簡單的文字解讀

接著,程式要使用 Gmail SMTP 將這份財經報告寄到使用者輸入的 Email。

## 技術要求:
- 使用 python-dotenv 讀取 .env
- .env 內包含 FromAddress 與 AppPassword
- 使用 smtplib 寄信
- 使用 MIMEText 建立純文字信件
- 使用 yfinance 抓股票資料
- 如果股票代號錯誤或查無資料,要顯示錯誤訊息並停止寄信
- 信件最後要加上免責聲明:本信件僅供教學練習,不構成投資建議

## 下面是老師成功的程式碼,請你參考看看:
[貼上串接yfinance抓股票資料,寄送固定財經信件.py]