股票第四波: 多账户按行业分配(5强可交易/5弱拦截), 组合扫描--multi自动路由, 多账户日报cron, 周学习接入行业动量

This commit is contained in:
小唯 A06 2026-08-01 23:41:44 +08:00
parent e86e28fe86
commit fe58e9d6a3
5 changed files with 265 additions and 0 deletions

View File

@ -145,6 +145,17 @@ PHASES = {
}
def load_industry_momentum():
"""读取行业扫描结果stock_industry_scan.py 生成)"""
f = HERMES + "/stock_backtest/industry_scan.json"
if not os.path.exists(f):
return None
try:
return json.load(open(f))
except Exception:
return None
def generate_report(progress, topic):
phase = progress["current_phase"]
item_idx = progress["current_item"]
@ -154,6 +165,23 @@ def generate_report(progress, topic):
report += f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n"
report += f"当前阶段: Phase {phase} - {phase_data.get('title', '完成')}\n\n"
# 市场实时情报2026-08-01 新增:行业动量 + 关注池机会)
scan = load_industry_momentum()
if scan and scan.get("industries"):
report += "**🔭 市场行业动量(学术因子)**\n"
try:
mom_map = scan["industries"].get("avg_mom", {})
ranked = sorted(mom_map.items(), key=lambda kv: kv[1], reverse=True)
strong = [f"{ind}({mom:+.0%})" for ind, mom in ranked[:3] if mom > 0]
weak = [f"{ind}({mom:+.0%})" for ind, mom in ranked[-3:]]
if strong:
report += f"🟢 强势行业: {' '.join(strong)}\n"
if weak:
report += f"🔴 弱势行业: {' '.join(weak)} (金叉硬拦截)\n"
except Exception:
pass
report += "\n"
if phase == 5:
report += "**✅ 理论学习完成,开始模拟交易阶段**\n\n"
report += "下一步:选择模拟平台(小唯推荐聚宽/掘金),搭建回测环境\n"

View File

@ -0,0 +1,210 @@
#!/usr/bin/env python3
"""
小唯多账户模拟交易 按行业分配独立 paper 账户
============================================
2026-08-01 行业扫描后新增强势行业单独账户弱势行业拦截
账户结构
强势行业账户: 新能源 / 科技 / 煤炭 / 半导体 / 证券每个独立资金池
弱势行业账户: 白酒 / 医药 / 通信 / 汽车 / 地产硬拦截只记录不交易
用法:
python3 stock_multi_account.py status # 所有账户状态
python3 stock_multi_account.py status <行业> # 指定行业账户
python3 stock_multi_account.py execute <sig_json> # 信号执行(自动路由)
python3 stock_multi_account.py report # 汇总报告
"""
import json, os, sys
from datetime import datetime
from pathlib import Path
OUTPUT = Path.home() / ".hermes" / "stock_backtest" / "multi_account"
OUTPUT.mkdir(parents=True, exist_ok=True)
# 行业 → 账户配置
# 强势行业: 独立资金池,可交易
# 弱势行业: 资金池 0硬拦截只记录
ACCOUNTS = {
"新能源": {"code": "300750", "name": "宁德时代", "capital": 100000, "blocked": False},
"科技": {"code": "002415", "name": "海康威视", "capital": 100000, "blocked": False},
"煤炭": {"code": "601088", "name": "中国神华", "capital": 100000, "blocked": False},
"半导体": {"code": "688981", "name": "中芯国际", "capital": 100000, "blocked": False},
"证券": {"code": "600030", "name": "中信证券", "capital": 100000, "blocked": False},
"白酒": {"code": "000858", "name": "五粮液", "capital": 0, "blocked": True},
"医药": {"code": "600276", "name": "恒瑞医药", "capital": 0, "blocked": True},
"地产": {"code": "000002", "name": "万科A", "capital": 0, "blocked": True},
}
WEAK_INDUSTRIES = {"白酒", "医药", "通信", "汽车", "地产"}
def account_file(industry):
return OUTPUT / f"account_{industry}.json"
def load_account(industry):
f = account_file(industry)
if f.exists():
with open(f) as fp:
return json.load(fp)
cfg = ACCOUNTS.get(industry, {})
return {
"industry": industry,
"stock": cfg.get("name", ""),
"code": cfg.get("code", ""),
"blocked": cfg.get("blocked", industry in WEAK_INDUSTRIES),
"initial_capital": cfg.get("capital", 0),
"current_capital": cfg.get("capital", 0),
"positions": [],
"closed_trades": [],
"last_signal": "空仓",
"last_signal_date": "",
"stats": {"total_trades": 0, "winning_trades": 0, "losing_trades": 0},
"created": datetime.now().strftime("%Y-%m-%d"),
}
def save_account(acct):
with open(account_file(acct["industry"]), "w") as f:
json.dump(acct, f, ensure_ascii=False, indent=2)
def execute_signal(sig, dry_run=False):
"""
信号执行多账户路由版
sig: {"industry": "新能源", "signal": "BUY"/"SELL"/..., "close": 价格, ...}
"""
if not sig:
return ("SKIP", "无信号")
industry = sig.get("industry", "")
signal = sig.get("signal", "")
price = sig.get("close") or sig.get("price") or 0
# 弱势行业硬拦截
if signal == "BUY" and industry in WEAK_INDUSTRIES:
return ("BLOCK", f"弱势行业({industry}动量负)金叉,硬规则不自动买入")
if industry not in ACCOUNTS:
return ("SKIP", f"未配置行业账户: {industry}")
acct = load_account(industry)
has_position = bool(acct["positions"])
if signal == "BUY" and not has_position:
if acct.get("blocked"):
return ("BLOCK", f"行业{industry}账户已锁定")
shares = int(acct["current_capital"] // price) if price > 0 else 0
if shares <= 0:
return ("SKIP", "资金不足")
cost = shares * price
if dry_run:
return ("BUY", f"{industry}金叉买入@{price:.2f} {shares}")
acct["positions"].append({"shares": shares, "avg_cost": price})
acct["current_capital"] -= cost
acct["last_signal"] = "买入"
acct["last_signal_date"] = datetime.now().strftime("%Y-%m-%d")
save_account(acct)
return ("BUY", f"{industry}买入{shares}股@{price:.2f} 剩余{acct['current_capital']:.0f}")
elif signal == "SELL" and has_position:
shares = sum(p["shares"] for p in acct["positions"])
if dry_run:
return ("SELL", f"{industry}死叉卖出@{price:.2f} {shares}")
avg_cost = sum(p["shares"] * p["avg_cost"] for p in acct["positions"]) / shares
pnl = (price - avg_cost) * shares
acct["current_capital"] += price * shares
acct["closed_trades"].append({
"date": datetime.now().strftime("%Y-%m-%d"),
"buy_price": avg_cost, "sell_price": price,
"shares": shares, "pnl": pnl,
"pnl_pct": pnl / (avg_cost * shares) * 100 if avg_cost * shares else 0,
})
acct["stats"]["total_trades"] += 1
acct["stats"]["winning_trades"] += 1 if pnl > 0 else 0
acct["stats"]["losing_trades"] += 1 if pnl < 0 else 0
acct["positions"] = []
acct["last_signal"] = "卖出"
acct["last_signal_date"] = datetime.now().strftime("%Y-%m-%d")
save_account(acct)
return ("SELL", f"{industry}卖出{shares}股@{price:.2f} 盈亏{pnl:+.0f}")
elif signal == "BUY" and has_position:
return ("HOLD", f"{industry}已持仓,忽略重复买入")
elif signal == "SELL" and not has_position:
return ("HOLD", f"{industry}已空仓,忽略重复卖出")
elif signal == "HOLD_LONG":
return ("HOLD", f"{industry}持仓中" if has_position else f"{industry}空仓(非金叉不买)")
return ("HOLD", f"{industry}空仓观望")
def cmd_status(industry=None):
print("=" * 60)
print(f"小唯多账户模拟 — {datetime.now().strftime('%Y-%m-%d %H:%M')}")
print("=" * 60)
industries = [industry] if industry else list(ACCOUNTS.keys())
total_assets = 0
for ind in industries:
acct = load_account(ind)
shares = sum(p["shares"] for p in acct["positions"])
total_cost = sum(p["shares"] * p["avg_cost"] for p in acct["positions"])
market_val = total_cost # 简化:成本计市值(真实市值需行情)
total = acct["current_capital"] + (market_val if shares else 0)
total_assets += total
tag = "⛔拦截" if acct.get("blocked") else "🟢可交易"
pos = f"持仓{shares}" if shares else "空仓"
pnl = total - acct["initial_capital"]
print(f"\n{ind}{tag} 标的:{acct['stock']}({acct['code']})")
print(f" 资金: {acct['current_capital']:.0f} | {pos} | 盈亏: {pnl:+.0f}")
if acct["closed_trades"]:
wins = acct["stats"]["winning_trades"]
tot = acct["stats"]["total_trades"]
print(f" 已平仓: {tot}笔 | 胜率: {wins}/{tot}")
print(f"\n{'='*60}")
print(f"总资产: {total_assets:.0f}")
def cmd_report():
"""汇总报告(飞书推送用)"""
lines = [f"📊 多账户日报 {datetime.now().strftime('%Y-%m-%d')}"]
total = 0
for ind, cfg in ACCOUNTS.items():
acct = load_account(ind)
shares = sum(p["shares"] for p in acct["positions"])
total_cost = sum(p["shares"] * p["avg_cost"] for p in acct["positions"])
market_val = total_cost if shares else 0
t = acct["current_capital"] + market_val
total += t
pnl = t - acct["initial_capital"]
emoji = "" if acct.get("blocked") else "🟢"
lines.append(f"{emoji} {ind}: {acct['stock']} 资产{t:.0f} ({pnl:+.0f})")
lines.append(f"💰 总资产: {total:.0f}")
return "\n".join(lines)
if __name__ == "__main__":
cmd = sys.argv[1] if len(sys.argv) > 1 else "status"
if cmd == "status":
ind = sys.argv[2] if len(sys.argv) > 2 else None
cmd_status(ind)
elif cmd == "execute":
# 从 stdin 或文件读取信号 JSON
if len(sys.argv) > 2 and os.path.exists(sys.argv[2]):
sig = json.load(open(sys.argv[2]))
elif len(sys.argv) > 2:
sig = json.loads(sys.argv[2])
else:
sig = json.load(sys.stdin)
action, detail = execute_signal(sig)
print(f"[{action}] {detail}")
elif cmd == "report":
print(cmd_report())
else:
print(__doc__)

5
scripts/stock_multi_daily.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
# 每日多账户日报:按行业 paper 账户汇总(强势可交易/弱势拦截)
# 被 cron 调用(工作日 18:00
cd ~/.hermes/scripts || exit 1
exec python3 stock_multi_account.py report

View File

@ -319,8 +319,25 @@ def build_portfolio_report(push=False):
if stats:
stat_line = f"\n 📊 MA20策略: 历史胜率{stats.get('win_rate',0):.0f}% | α{stats.get('alpha',0):+.1f}% | 最大回撤{stats.get('max_drawdown',0):.0f}%"
msg += f"🟡 {name}({code}) 金叉! 价格{r['price']:.2f} 偏离MA20 {diff:+.1f}%{stat_line}\n"
# 多账户自动路由2026-08-01 新增)
if "--multi" in sys.argv:
try:
import stock_multi_account as sma
action, detail = sma.execute_signal(
{"industry": industry, "signal": "BUY", "close": r["price"]})
msg += f" 📝 多账户: [{action}] {detail}\n"
except Exception as e:
msg += f" ⚠️ 多账户执行失败: {e}\n"
elif r["dead_cross"]:
msg += f"🔴 {name}({code}) 死叉! 平仓\n"
if "--multi" in sys.argv:
try:
import stock_multi_account as sma
action, detail = sma.execute_signal(
{"industry": industry, "signal": "SELL", "close": r["price"]})
msg += f" 📝 多账户: [{action}] {detail}\n"
except Exception as e:
msg += f" ⚠️ 多账户执行失败: {e}\n"
if not signals:
msg += "\n暂无金叉,继续空仓等待。\n"
msg += f"\n生成: {datetime.now().strftime('%H:%M')}"

View File

@ -0,0 +1,5 @@
#!/bin/bash
# 每日组合信号 + 多账户自动路由
# 被 cron c293eead6688 调用(工作日 09:00
cd ~/.hermes/scripts || exit 1
exec python3 stock_portfolio.py --push --multi