136 lines
4.2 KiB
Python
Executable File
136 lines
4.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
每日投资报告
|
||
- 用户表: 代码 + 持仓数量 + 持仓成本(每股成本价)
|
||
- 自动计算: 总成本 = 持仓成本 × 持仓数量
|
||
"""
|
||
|
||
from datetime import datetime
|
||
|
||
USER_DB_ID = "2fb105ad78738175bbbde5b87cf101d9"
|
||
NOTION_TOKEN = "ntn_c43902219395mirQBetIfYoww1qKCAF14GBRUQeDee29o2"
|
||
|
||
def get_user_holdings():
|
||
import requests
|
||
url = f"https://api.notion.com/v1/databases/{USER_DB_ID}/query"
|
||
headers = {
|
||
"Authorization": f"Bearer {NOTION_TOKEN}",
|
||
"Content-Type": "application/json",
|
||
"Notion-Version": "2022-06-28"
|
||
}
|
||
|
||
response = requests.post(url, headers=headers, json={}, timeout=30)
|
||
if response.status_code == 200:
|
||
data = response.json()
|
||
holdings = []
|
||
for item in data.get("results", []):
|
||
props = item.get("properties", {})
|
||
code = ""
|
||
titles = props.get("股票代码", {}).get("title", [])
|
||
if titles:
|
||
code = titles[0].get("plain_text", "").upper()
|
||
|
||
shares = props.get("持仓数量", {}).get("number", 0) or 0
|
||
cost_per_share = props.get("持仓成本", {}).get("number", 0) or 0
|
||
|
||
if code and shares > 0:
|
||
total_cost = round(shares * cost_per_share, 2)
|
||
holdings.append({
|
||
"code": code,
|
||
"shares": shares,
|
||
"cost_per_share": cost_per_share,
|
||
"total_cost": total_cost
|
||
})
|
||
return holdings
|
||
return []
|
||
|
||
def get_us_price(code):
|
||
import yfinance as yf
|
||
import time
|
||
time.sleep(3) # 避免限流
|
||
try:
|
||
ticker = yf.Ticker(code)
|
||
hist = ticker.history(period="1d")
|
||
if len(hist) > 0:
|
||
return round(hist['Close'].iloc[-1], 2)
|
||
except:
|
||
pass
|
||
return None
|
||
|
||
def main():
|
||
import requests
|
||
import yfinance as yf
|
||
|
||
print(f"\n{'='*85}")
|
||
print(f"📊 每日投资报告")
|
||
print(f"更新时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
|
||
print(f"{'='*85}\n")
|
||
|
||
print("[1] 从用户表读取...")
|
||
holdings = get_user_holdings()
|
||
|
||
if not holdings:
|
||
print("❌ 无法获取数据\n")
|
||
return
|
||
|
||
print(f"\n[2] 获取股价...")
|
||
prices = {}
|
||
for h in holdings:
|
||
code = h["code"]
|
||
price = get_us_price(code)
|
||
if price:
|
||
prices[code] = price
|
||
print(f" {code}: ${price}")
|
||
else:
|
||
print(f" {code}: 获取失败")
|
||
|
||
print(f"\n{'='*85}")
|
||
print("📊 持仓盈亏")
|
||
print(f"{'='*85}\n")
|
||
|
||
print(f"{'代码':<10} {'股数':<10} {'每股成本':<12} {'总成本':<12} {'当前价':<12} {'市值':<12} {'盈亏':<18}")
|
||
print("-" * 100)
|
||
|
||
total_cost = 0
|
||
total_value = 0
|
||
|
||
for h in holdings:
|
||
code = h["code"]
|
||
shares = h["shares"]
|
||
cost_per = h["cost_per_share"]
|
||
cost = h["total_cost"]
|
||
price = prices.get(code)
|
||
|
||
if price:
|
||
value = round(shares * price, 2)
|
||
else:
|
||
value = 0
|
||
|
||
pnl = round(value - cost, 2)
|
||
pnl_pct = round((pnl / cost * 100), 2) if cost > 0 else 0
|
||
|
||
total_cost += cost
|
||
total_value += value
|
||
|
||
price_str = f"${price:.2f}" if price else "❌"
|
||
cost_str = f"${cost:.2f}" if cost else "❌"
|
||
value_str = f"${value:.2f}" if value else "❌"
|
||
emoji = "🟢" if pnl >= 0 else "🔴"
|
||
pnl_str = f"{emoji}${pnl:+.2f} ({pnl_pct:+.2f}%)"
|
||
cost_per_str = f"${cost_per:.2f}" if cost_per else "❌"
|
||
|
||
print(f"{code:<10} {shares:<10.2f} {cost_per_str:<12} {cost_str:<12} {price_str:<12} {value_str:<12} {pnl_str:<18}")
|
||
|
||
print("-" * 100)
|
||
total_pnl = round(total_value - total_cost, 2)
|
||
total_pnl_pct = round((total_pnl / total_cost * 100), 2) if total_cost > 0 else 0
|
||
total_emoji = "🟢" if total_pnl >= 0 else "🔴"
|
||
print(f"{'合计':<10} {'':<10} {'':<12} ${total_cost:<11.2f} {'':<12} ${total_value:<11.2f} {total_emoji}${total_pnl:+.2f} ({total_pnl_pct:+.2f}%)")
|
||
|
||
print(f"\n{'='*85}")
|
||
print("💡 成本规则: 总成本 = 每股成本价(持仓成本) × 持仓数量")
|
||
print(f"{'='*85}")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|