xiaowei-system/scripts/stock_review.py

168 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
小唯股票自动复盘 — 每日检查多账户成交,生成复盘报告
==================================================
有平仓交易才推送(静默模式),无交易不打扰。
功能:
- 检查各行业账户今日平仓记录
- 汇总账户盈亏/胜率/持仓
- 生成复盘报告推飞书
- 周度复盘:周五包含本周总结
用法python3 stock_review.py [--push]
"""
import json, sys, urllib.request
from datetime import datetime, timedelta
from pathlib import Path
OUTPUT = Path.home() / ".hermes" / "stock_backtest"
MULTI_DIR = OUTPUT / "multi_account"
FEISHU_WEBHOOK = "https://open.feishu.cn/open-apis/bot/v2/hook/446db983-e392-4d2c-bfb8-f9060e5df3ad"
STATE_FILE = OUTPUT / "review_state.json"
ACCOUNT_INDUSTRIES = ["新能源", "科技", "煤炭", "半导体", "证券", "有色", "石油",
"白酒", "医药", "地产"]
def load_state():
if STATE_FILE.exists():
try:
return json.load(open(STATE_FILE))
except Exception:
pass
return {"last_review": None, "last_week": None}
def save_state(state):
with open(STATE_FILE, "w") as f:
json.dump(state, f, ensure_ascii=False, indent=2)
def send_feishu(msg):
try:
payload = json.dumps({"msg_type": "text", "content": {"text": msg}}).encode()
req = urllib.request.Request(FEISHU_WEBHOOK, data=payload,
headers={"Content-Type": "application/json"})
urllib.request.urlopen(req, timeout=8)
return True
except Exception:
return False
def load_account(industry):
f = MULTI_DIR / f"account_{industry}.json"
if not f.exists():
return None
try:
return json.load(open(f))
except Exception:
return None
def generate_review():
"""生成复盘报告(有交易才返回内容)"""
today = datetime.now().strftime("%Y-%m-%d")
is_friday = datetime.now().weekday() == 4
state = load_state()
lines = [f"📋 股票复盘 {today}"]
# 1. 今日平仓记录
today_trades = []
total_pnl = 0
for ind in ACCOUNT_INDUSTRIES:
acct = load_account(ind)
if not acct:
continue
for t in acct.get("closed_trades", []):
if t.get("date") == today:
today_trades.append({"industry": ind, "stock": acct.get("stock", ind), **t})
total_pnl += t.get("pnl", 0)
if not today_trades:
# 无交易:检查是否有持仓需要展示
has_positions = False
pos_lines = []
total_assets = 0
for ind in ACCOUNT_INDUSTRIES:
acct = load_account(ind)
if not acct:
continue
total_assets += acct.get("current_capital", 0)
if acct.get("positions"):
has_positions = True
shares = sum(p["shares"] for p in acct["positions"])
cost = sum(p["shares"] * p["avg_cost"] for p in acct["positions"])
pos_lines.append(f" {acct.get('stock', ind)}({ind}): {shares}股 成本{cost:.0f}")
if not has_positions:
return None # 无交易无持仓,静默
lines.append(f"\n💰 总资产: {total_assets:.0f}")
lines.append(f"\n📦 当前持仓:")
lines.extend(pos_lines)
lines.append("\n无今日平仓,继续持有观察。")
else:
lines.append(f"\n📈 今日平仓 {len(today_trades)} 笔:")
wins = 0
for t in today_trades:
pnl_pct = t.get("pnl_pct", 0)
emoji = "🟢" if pnl_pct > 0 else "🔴"
if pnl_pct > 0:
wins += 1
lines.append(f" {emoji} {t['stock']}({t['industry']}): 买{t.get('buy_price',0):.2f}→卖{t.get('sell_price',0):.2f} "
f"{t.get('pnl',0):+.0f} ({pnl_pct:+.1f}%)")
lines.append(f"\n 胜率: {wins}/{len(today_trades)} | 今日盈亏: {total_pnl:+.0f}")
# 2. 账户汇总
lines.append(f"\n📊 账户汇总:")
total_assets = 0
for ind in ACCOUNT_INDUSTRIES:
acct = load_account(ind)
if not acct:
continue
shares = sum(p["shares"] for p in acct["positions"]) if acct.get("positions") else 0
total_cost = sum(p["shares"] * p["avg_cost"] for p in acct["positions"]) if acct.get("positions") else 0
total_assets += acct.get("current_capital", 0) + (total_cost if shares else 0)
pos_str = f"持仓{shares}" if shares else "空仓"
lines.append(f" {acct.get('stock', ind)}({ind}): {pos_str} 资金{acct.get('current_capital',0):.0f}")
lines.append(f"\n💰 总资产: {total_assets:.0f}")
# 3. 周度总结(周五)
if is_friday:
week_key = datetime.now().isocalendar()[1]
if state.get("last_week") != week_key:
week_trades = []
week_pnl = 0
week_start = (datetime.now() - timedelta(days=5)).strftime("%Y-%m-%d")
for ind in ACCOUNT_INDUSTRIES:
acct = load_account(ind)
if not acct:
continue
for t in acct.get("closed_trades", []):
if t.get("date", "") >= week_start:
week_trades.append(t)
week_pnl += t.get("pnl", 0)
lines.append(f"\n📅 本周总结({len(week_trades)} 笔平仓,盈亏 {week_pnl:+.0f}")
lines.append(" - 复盘 MA20 信号有效性,检查行业动量变化")
lines.append(" - 参考周五 17:20 行业扫描 + 17:30 因子快照")
state["last_week"] = week_key
state["last_review"] = today
save_state(state)
return "\n".join(lines)
if __name__ == "__main__":
push = "--push" in sys.argv
report = generate_review()
if report:
print(report)
if push:
ok = send_feishu(report)
print(f"\n{'✅ 已推送飞书' if ok else '⚠️ 推送失败'}")
else:
print("(无交易无持仓,静默)")