#!/usr/bin/env python3 """ 每日美股持仓盈亏报告 """ import requests import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from datetime import datetime # Notion API 配置 NOTION_API_KEY = "ntn_c43902219395mirQBetIfYoww1qKCAF14GBRUQeDee29o2" DATABASE_ID = "2fb105ad-7873-8175-bbbd-e5b87cf101d9" # 邮件配置 SMTP_SERVER = "smtp.163.com" SMTP_PORT = 465 EMAIL_USER = "work_fyx02@163.com" EMAIL_PASSWORD = "QLrTpw7SDxrMuAzh" RECIPIENT_EMAIL = "Yaxing_feng@dgmaorui.com" # 指数代码 INDICES = { "^GSPC": "标普500", "^IXIC": "纳斯达克", "^DJI": "道琼斯" } def get_current_price(symbol): """获取股票当前价格(从Yahoo Finance)""" try: url = f"https://query1.finance.yahoo.com/v8/finance/chart/{symbol}" params = {"range": "1d", "interval": "1m"} response = requests.get(url, params=params, timeout=10) if response.status_code == 200: data = response.json() chart_result = data.get("chart", {}).get("result", [{}]) if chart_result: meta = chart_result[0].get("meta", {}) return meta.get("regularMarketPrice") except Exception as e: print(f"获取价格失败 {symbol}: {e}") return None def get_index_prices(): """获取指数价格""" prices = {} for symbol, name in INDICES.items(): price = get_current_price(symbol) if price: prices[symbol] = {"name": name, "price": price} return prices def get_all_positions(): """从Notion获取所有持仓""" url = "https://api.notion.com/v1/databases/" + DATABASE_ID + "/query" headers = { "Authorization": f"Bearer {NOTION_API_KEY}", "Content-Type": "application/json", "Notion-Version": "2022-06-28" } response = requests.post(url, headers=headers) if response.status_code == 200: results = response.json().get("results", []) positions = [] for page in results: props = page["properties"] symbol = props.get("股票代码", {}).get("title", [{}])[0].get("text", {}).get("content", "") shares = props.get("持仓数量", {}).get("number", 0) cost = props.get("买入成本", {}).get("number", 0) current_price = props.get("当前价格", {}).get("number", 0) market_value = props.get("当前市值", {}).get("number", 0) pnl_amount = props.get("盈亏金额", {}).get("number", 0) pnl_percent = props.get("盈亏百分比", {}).get("number", 0) if symbol: positions.append({ "symbol": symbol, "shares": shares, "cost": cost, "current_price": current_price, "market_value": market_value, "pnl_amount": pnl_amount, "pnl_percent": pnl_percent }) return positions else: print(f"获取持仓失败: {response.status_code}") return [] def calculate_portfolio_summary(positions): """计算投资组合汇总""" total_cost = sum(p.get("cost", 0) or 0 for p in positions) total_market_value = sum(p.get("market_value", 0) or 0 for p in positions) total_pnl = total_market_value - total_cost total_pnl_percent = (total_pnl / total_cost * 100) if total_cost > 0 else 0 return { "total_cost": total_cost, "total_market_value": total_market_value, "total_pnl": total_pnl, "total_pnl_percent": total_pnl_percent } def generate_html_report(positions, summary, indices): """生成HTML报告""" # 获取当前时间(UTC+8) now = datetime.utcnow() date_str = now.strftime("%Y-%m-%d %H:%M") # 生成持仓明细HTML positions_html = "" for p in positions: symbol = p.get("symbol", "") shares = p.get("shares", 0) or 0 cost = p.get("cost", 0) or 0 current_price = p.get("current_price", 0) or 0 market_value = p.get("market_value", 0) or 0 pnl_amount = p.get("pnl_amount", 0) or 0 pnl_percent = p.get("pnl_percent", 0) or 0 pnl_color = "green" if pnl_amount >= 0 else "red" positions_html += f""" {symbol} {shares} ${cost:.2f} ${current_price:.2f} ${market_value:.2f} ${pnl_amount:.2f} {pnl_percent:.2f}% """ # 生成指数HTML indices_html = "" for symbol, data in indices.items(): indices_html += f""" {data['name']} {data['price']:.2f} """ # 汇总颜色 summary_pnl_color = "green" if summary["total_pnl"] >= 0 else "red" html = f"""

📊 美股持仓盈亏日报

报告时间:{date_str} (北京时间)

📈 投资组合汇总

总成本:${summary['total_cost']:.2f}
当前市值:${summary['total_market_value']:.2f}
总盈亏: ${summary['total_pnl']:.2f} ({summary['total_pnl_percent']:.2f}%)

📋 持仓明细

{positions_html}
股票代码 持仓数量 买入成本 当前价格 当前市值 盈亏金额 盈亏百分比

📊 主要指数

{indices_html}
指数名称 当前点位

此报告由 OpenClaw 自动生成
数据来源:Yahoo Finance API
更新时间:每日早上 6:30 (北京时间)

""" return html def send_email(subject, html_content): """发送邮件""" try: # 创建邮件 msg = MIMEMultipart('alternative') msg['Subject'] = subject msg['From'] = EMAIL_USER msg['To'] = RECIPIENT_EMAIL # 添加HTML内容 html_part = MIMEText(html_content, 'html', 'utf-8') msg.attach(html_part) # 发送邮件 with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT) as server: server.login(EMAIL_USER, EMAIL_PASSWORD) server.send_message(msg) print(f"✅ 邮件发送成功: {subject}") return True except Exception as e: print(f"❌ 邮件发送失败: {e}") return False def main(): print("=" * 60) print("开始生成每日美股持仓盈亏报告...") print("=" * 60) # 获取持仓 print("\n1. 获取持仓数据...") positions = get_all_positions() if not positions: print("❌ 未找到持仓数据") return print(f"✅ 找到 {len(positions)} 个持仓") # 获取指数价格 print("\n2. 获取指数价格...") indices = get_index_prices() print(f"✅ 获取到 {len(indices)} 个指数价格") # 计算汇总 print("\n3. 计算投资组合汇总...") summary = calculate_portfolio_summary(positions) print(f" 总成本: ${summary['total_cost']:.2f}") print(f" 当前市值: ${summary['total_market_value']:.2f}") print(f" 总盈亏: ${summary['total_pnl']:.2f} ({summary['total_pnl_percent']:.2f}%)") # 生成报告 print("\n4. 生成HTML报告...") html_report = generate_html_report(positions, summary, indices) print("✅ 报告生成完成") # 发送邮件 print("\n5. 发送邮件...") subject = f"📊 美股持仓盈亏日报 - {datetime.now().strftime('%Y-%m-%d')}" send_email(subject, html_report) print("\n" + "=" * 60) print("报告生成和发送完成!") print("=" * 60) if __name__ == "__main__": main()