串接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]