Files
server-configs/send-pnl-report.py

305 lines
8.7 KiB
Python
Raw Normal View History

2026-02-13 22:24:27 +08:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
发送持仓盈亏报告邮件
"""
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
import requests
import json
from datetime import datetime
# SMTP 配置
SMTP_SERVER = "smtp.163.com"
SMTP_PORT = 465
EMAIL = "work_fyx02@163.com"
AUTH_CODE = "QLrTpw7SDxrMuAzh"
# 收件人
TO_EMAIL = "Yaxing_feng@dgmaorui.com"
# Yahoo Finance API 配置
YAHOO_API_URL = "https://query1.finance.yahoo.com/v8/finance/chart/"
# 指数配置
INDEXES = {
"^GSPC": "标普500",
"^IXIC": "纳斯达克",
"^DJI": "道琼斯"
}
# 股票配置从Notion数据库读取
STOCKS = [
{"symbol": "MSFT", "name": "Microsoft Corporation", "quantity": 1, "cost_basis": 439.00}
]
def get_stock_price(symbol):
"""获取股票实时价格"""
try:
url = f"{YAHOO_API_URL}{symbol}"
params = {
"interval": "1d",
"range": "1d"
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
response = requests.get(url, params=params, headers=headers, timeout=10)
if response.status_code == 200:
data = response.json()
if "chart" in data and "result" in data["chart"]:
result = data["chart"]["result"]
if result and len(result) > 0:
meta = result[0].get("meta", {})
current_price = meta.get("regularMarketPrice")
previous_close = meta.get("chartPreviousClose")
if current_price and previous_close:
change = current_price - previous_close
change_percent = (change / previous_close) * 100
return {
"symbol": symbol,
"price": current_price,
"previous_close": previous_close,
"change": change,
"change_percent": change_percent,
"currency": meta.get("currency", "USD")
}
return None
except Exception as e:
print(f"获取 {symbol} 价格失败: {e}")
return None
def get_index_prices():
"""获取指数价格"""
indexes = {}
for symbol, name in INDEXES.items():
price_data = get_stock_price(symbol)
if price_data:
indexes[symbol] = {
"name": name,
"price": price_data["price"],
"change": price_data["change"],
"change_percent": price_data["change_percent"]
}
return indexes
def calculate_pnl():
"""计算持仓盈亏"""
total_cost = 0
total_market_value = 0
total_pnl = 0
stock_details = []
for stock in STOCKS:
symbol = stock["symbol"]
name = stock["name"]
quantity = stock["quantity"]
cost_basis = stock["cost_basis"]
# 计算总成本
cost = quantity * cost_basis
total_cost += cost
# 获取当前价格
price_data = get_stock_price(symbol)
if price_data:
current_price = price_data["price"]
change = price_data["change"]
change_percent = price_data["change_percent"]
# 计算当前市值
market_value = quantity * current_price
total_market_value += market_value
# 计算盈亏
pnl = market_value - cost
total_pnl += pnl
# 计算盈亏百分比
pnl_percent = (pnl / cost) * 100
stock_details.append({
"symbol": symbol,
"name": name,
"quantity": quantity,
"cost_basis": cost_basis,
"current_price": current_price,
"cost": cost,
"market_value": market_value,
"pnl": pnl,
"pnl_percent": pnl_percent,
"change": change,
"change_percent": change_percent
})
# 计算总盈亏百分比
total_pnl_percent = 0
if total_cost > 0:
total_pnl_percent = (total_pnl / total_cost) * 100
return {
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"total_cost": total_cost,
"total_market_value": total_market_value,
"total_pnl": total_pnl,
"total_pnl_percent": total_pnl_percent,
"stocks": stock_details
}
def send_pnl_report():
"""发送盈亏报告邮件"""
print("=" * 60)
print("发送持仓盈亏报告")
print("=" * 60)
print(f"发件人: {EMAIL}")
print(f"收件人: {TO_EMAIL}")
print(f"SMTP 服务器: {SMTP_SERVER}:{SMTP_PORT}")
print("=" * 60)
try:
# 获取盈亏数据
print("\n正在获取盈亏数据...")
pnl_data = calculate_pnl()
# 获取指数数据
print("正在获取指数数据...")
indexes = get_index_prices()
# 格式化邮件内容
date_str = datetime.now().strftime("%Y年%m月%d")
body = f"""📧 **美股持仓盈亏日报**
**日期** {date_str}
---
## 📊 持仓概览
**总持仓市值** ${pnl_data['total_market_value']:.2f}
**总成本** ${pnl_data['total_cost']:.2f}
**总盈亏** ${pnl_data['total_pnl']:+.2f} ({pnl_data['total_pnl_percent']:+.2f}%)
---
## 📈 个股表现
"""
# 添加个股详情
for stock in pnl_data["stocks"]:
body += f"""
### {stock['symbol']} - {stock['name']}
- **持仓** {stock['quantity']} @ ${stock['cost_basis']}
- **当前价** ${stock['current_price']:.2f}
- **总成本** ${stock['cost']:.2f}
- **当前市值** ${stock['market_value']:.2f}
- **盈亏** ${stock['pnl']:+.2f} ({stock['pnl_percent']:+.2f}%)
- **日涨跌** ${stock['change']:+.2f} ({stock['change_percent']:+.2f}%)
"""
# 添加指数数据
body += """
---
## 📊 大盘指数
"""
for symbol, data in indexes.items():
body += f"""
- **{data['name']}** {data['price']:.2f} ({data['change_percent']:+.2f}%)
"""
# 添加总结
body += f"""
---
## 📝 总结
**今日盈亏** ${pnl_data['total_pnl']:+.2f}
**盈亏百分比** ${pnl_data['total_pnl_percent']:+.2f}%
**持仓股票数量** {len(pnl_data['stocks'])}
---
**此报告由 OpenClaw 自动发送**
**每日定时任务美东时间收盘后北京时间早上6点后**
"""
# 创建邮件
msg = MIMEMultipart()
msg["From"] = Header(f"OpenClaw <{EMAIL}>", "utf-8")
msg["To"] = Header(TO_EMAIL, "utf-8")
msg["Subject"] = Header(f"美股持仓盈亏日报 - {date_str}", "utf-8")
msg.attach(MIMEText(body, "plain", "utf-8"))
# 连接 SMTP 服务器并发送邮件
print("\n正在连接 SMTP 服务器...")
with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT) as server:
print(f"连接成功: {SMTP_SERVER}:{SMTP_PORT}")
print("正在登录...")
server.login(EMAIL, AUTH_CODE)
print(f"登录成功: {EMAIL}")
print("正在发送邮件...")
server.send_message(msg)
print("邮件发送成功!")
print("\n" + "=" * 60)
print("✅ 盈亏报告发送完成")
print("=" * 60)
print(f"收件人: {TO_EMAIL}")
print(f"总盈亏: ${pnl_data['total_pnl']:+.2f} ({pnl_data['total_pnl_percent']:+.2f}%)")
print("=" * 60)
return True
except Exception as e:
print("\n" + "=" * 60)
print("❌ 发送失败")
print("=" * 60)
print(f"错误信息: {e}")
print("=" * 60)
return False
def main():
"""主函数"""
print("开始发送持仓盈亏报告...")
print()
success = send_pnl_report()
if success:
print("\n💡 提示:")
print("1. 邮件已发送到目标邮箱")
print("2. 请检查收件箱(包括垃圾邮件)")
print("3. 每天定时发送盈亏报告")
else:
print("\n🔧 解决方案:")
print("1. 检查 SMTP 配置")
print("2. 检查网络连接")
print("3. 检查股票数据获取")
if __name__ == "__main__":
main()