Claude Skill

stock-analysis

基于5位公众号股票投资大V(猫笔刀、格兰投研、投资明见、财躺平、终身黑白)200+篇文章蒸馏形成的风格画像,通过 AI Agent 实现风格化投资分析,支持单大V复盘、多V并行对比、板块漏斗筛选、短线选股、组合复盘、财报点评、观点跟踪等多模式。当用户请求股票复盘、大V风格分析、多V对比、板块分析、短线选股、组合复盘、财报点评时使用。触发词:股票复盘、大V分析、模拟大V、风格化复盘、多V对比、每日复盘、板块分析、选股、组合复盘、财报点评、大V观点。

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download redfox-data-redfox-community-skills_stock-analysis-5e7b435.zip · 70 KB
Part of redfox-data/redfox-community — 66 skills

Install

skills CLI npx skills add https://github.com/redfox-data/redfox-community/tree/main/skills/stock-analysis
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install redfox-data-redfox-community@llmmart
Git git clone https://github.com/redfox-data/redfox-community.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole redfox-data/redfox-community collection as a plugin from our marketplace. Git is the plain clone.

README

股票分析专家 / stock-analysis


简介

将5位知名公众号股票投资大V(猫笔刀、格兰投研、投资明见、财躺平、终身黑白)200+篇文章的投资理念和操作方法蒸馏为结构化风格画像,用自然语言就能让AI模拟大V风格进行多模式投资分析。

核心价值

  • 多视角交叉验证:同一标的用5位大V风格分别分析,发现共识与分歧,避免单一偏见
  • 风格化深度解读:每位大V有独特的交易体系和表达风格,分析贴合真实思维模式
  • 双重质量保障:生成内容经表层特征(标志性表达/文章结构)+思维层特征(核心思维/论证方式)双重审计后才输出

适用对象

  • 📈 个人投资者 — 获取多风格、多维度的投资分析参考,辅助决策
  • 📝 内容创作者 — 学习大V的分析框架和表达技巧,提升内容质量
  • 🔍 投研新手 — 通过风格化分析快速理解不同投资流派的思维方式

功能特性

核心功能

  • 多V并行对比:5位大V风格化分析同一标的或板块,并排展示共识点与分歧点
  • 每日复盘:按指定大V风格生成当日盘面复盘文章,从独特视角理解市场情绪
  • 板块漏斗:在大V分析框架下四层筛选(赛道→龙头→确定性→操作),穿透市场热点
  • 短线选股:从公众号热文中提取个股被提及的频率,按大V关注度量化筛选
  • 组合复盘:检索大V对各持仓标的的历史观点,按交易框架全面诊断你的持仓
  • 财报点评:按大V风格深度解读公司财报,多维度分析基本面

特色亮点

  • 5位大V风格蒸馏:基于200+篇真实文章提炼六大维度DNA(交易体系/市场判断/表达风格/内容深度/互动特征/热点图谱),非模板化生成
  • 盘面数据实时采集:通过联网搜索获取最新行情数据,每个数值标注来源,杜绝AI编造
  • 内容质量双审计:表层特征+思维层特征双重校验,综合评分达标才输出
  • 画像持续进化:支持增量同步大V最新文章,自动更新风格画像

密钥获取与安全说明

  • 本技能需要使用环境变量:REDFOX_API_KEY。
  • REDFOX_API_KEY 由 红狐 hub (https://redfox.hk)提供。
  • 请前往 红狐 hub 注册账号,获取 REDFOX_API_KEY。
  • 配置设备环境变量 REDFOX_API_KEY 后使用本技能。
  • 在提供密钥前,请先确认密钥来源、可用范围、有效期及是否支持重置/撤销。
  • 禁止在代码、提示词、日志或输出文件中硬编码/明文暴露密钥。

使用指南

直接用自然语言描述需求,无需记忆命令。

常用说法速查

意图 示例话术 效果
单大V分析 "用猫笔刀的风格分析宁德时代适不适合买入" 生成猫笔刀风格的个股分析
多V对比 "用5位大V的风格分别分析茅台" 并排展示5位大V对同一标的的看法
板块分析 "共同分析航天板块" 漏斗筛选板块内标的,评估确定性
短线选股 "用财躺平的风格筛选半导体短线" 从大V热文中提取高关注度个股
组合复盘 "用投资明见的风格复盘我的持仓:中国卫星、航发动力" 按大V交易框架诊断持仓组合
财报分析 "点评贵州茅台最新财报" 风格化解读财报,多维度分析
观点追踪 "查下财躺平对中国卫星的观点" 检索大V历史观点,跟踪判断兑现情况

使用场景

场景 角色 示例问法 收益
选股决策 个人投资者 "用终身黑白的风格分析茅台现在还值不值得买" 从价值投资视角评估安全边际
板块研究 行业研究员 "共同分析半导体板块" 多视角产业链逻辑交叉验证
每日复盘 短线交易者 "用猫笔刀的风格写一篇今日复盘" 风格化理解当日盘面与情绪
持仓诊断 组合管理者 "用5位大V的风格复盘我的持仓:中国卫星、航发动力" 发现持仓中被忽视的风险或机会
学习框架 投资新手 "格兰投研是怎么分析一个行业的" 快速理解不同投资流派的思维模式

重要说明

  • ⚠️ 所有分析内容均为 AI 风格模拟,与各公众号大V本人无关,不构成任何投资建议
  • 📊 盘面数据通过联网搜索获取,每个数据标注来源,如有偏差可提供真实数据重新生成
  • 🔄 每日首次使用会询问是否同步大V最新文章(文章同步按次扣费,调用记录上报不扣费)

Skill manifest

股票分析专家-公众号股票大V蒸馏

⚠️ 免责声明:本工具仅供学习研究使用,所有生成内容均为 AI 风格模拟,不构成任何投资建议。 盘面数据必须实时采集,禁止 LLM 编造涨跌停、资金流向等数据。


📝 简介

将5位公众号股票投资大V的200+篇文章蒸馏为结构化风格画像(六维DNA:交易体系/市场判断/表达风格/内容深度/互动特征/热点图谱),通过 AI Agent 实现多模式风格化分析。盘面数据通过 WebSearch 实时采集并逐数值标注来源,生成内容经质量审计(表层+思维层双重校验)后准出。


✨ 功能特性

功能模块 能力描述 核心价值
多V并行对比 5位大V风格化分析同一标的/板块,输出共识与分歧 多视角交叉验证,避免单一偏见
每日复盘 按指定大V风格生成当日盘面复盘文章 风格化视角理解市场情绪与操作逻辑
板块漏斗 大V分析框架下四层筛选:赛道→龙头→确定性→操作 产业链逻辑穿透市场热点
短线选股 从公众号热文提取个股提及频率,按交易类型筛选 大V关注度量化选股
组合复盘 检索大V对各标的历史观点,按交易框架评估持仓 风格化持仓诊断与风险评估
财报点评 按大V风格解读公司财报,多维度分析基本面 基本面专业解读风格化输出

🔑 鉴权

前往 redfox.hk 注册获取 API Key,通过以下方式配置:

# 环境变量
export REDFOX_API_KEY="ak_你的密钥"

# 或配置文件 ~/.qoder/apis/redfox.json
{"api_key": "ak_你的密钥"}

API Key 用于:

  • queryWorkList(文章同步):按次扣费,每位大V同步扣 1 次积分
  • record/save(调用记录上报):仅用于统计,不扣费

快速开始

自然语言直接描述需求,Agent 自动识别模式:

场景 触发示例
单大V分析 "用猫笔刀的风格分析宁德时代"
多V对比 "用5位大V的风格分别分析茅台"
板块分析 "共同分析航天板块"
短线选股 "用财躺平的风格筛选半导体短线"
组合复盘 "用投资明见的风格复盘我的持仓:中国卫星、航发动力"
财报点评 "点评贵州茅台最新财报"
每日复盘 "用猫笔刀的风格写一篇今日复盘"
观点跟踪 "查下财躺平对中国卫星的观点"

预置大V画像

profiles/ 目录下每位大V一份 JSON 画像,开箱即用:

大V 交易类型 核心风格
财躺平 短线+趋势 宏观趋势+逆向思维,道法自然哲学,"老粉都知道"
猫笔刀 短线 多资产视角+周期推理,幽默随性,"今晚舅酱"
格兰投研 长线 产业链深度研报型,数据驱动,"产业景气决定一切"
投资明见 长线 纯技术面趋势跟踪,指数为核心,"趋势是最好的朋友"
终身黑白 长线 深度价值投资,护城河+安全边际,"慢慢变富"

执行流程

Phase -1:增量文章同步(触发时询问)

当日首次触发时必须先询问用户是否同步大V最新文章(每位扣 1 次积分)。

三种选择:跳过 / 部分同步(指定大V)/ 全部同步(5位,完成后必须进入 Phase 5 补充蒸馏)。

python3 "$SKILL_PATH/scripts/analyze.py" --author "财躺平" --mode sync

Phase 0:环境准备 + AI 偏见自查

python3 "$SKILL_PATH/scripts/analyze.py" --check-env

检查依赖和画像就绪后,按以下标准评估信息丰富度并写入生成内容开头:

等级 特征 应对策略
A 级(信息充裕) 画像高置信、盘面完整 反面检验:大V看多时,聪明人为什么看空?
B 级(信息适中) 画像中置信、部分数据缺失 每个推算数据标注置信度
C 级(信息稀缺) 画像低置信、数据缺失 第一性原理提问,不拼凑"看起来完整"的报告

偏见自查清单:

  • "确定性"感受来自盘面数据还是大V画像语气?
  • 去掉大V画像仅看盘面,结论会变吗?
  • 是否只呈现了看多理由,忽略反面论据?

Phase 1:盘面数据采集

铁律:盘面数据必须实时采集,禁止 LLM 编造。

数据获取:用户手动输入 > WebSearch 搜索 > 禁止 LLM 编造。

关键数据交叉验证(≥2 个来源,差异 > 1% 须标注):

数据项 主源 副源
涨跌停家数 交易所官网 东方财富/同花顺
成交额 交易所官网 万得/同花顺
北向资金 沪深港通官网 东方财富
主力资金流向 东方财富 同花顺

Phase 2:风格画像加载

python3 "$SKILL_PATH/scripts/analyze.py" --list-profiles

Phase 3:内容生成

数据来源披露(所有场景强制):每个盘面数据后括号标注来源域名(如"同花顺 10jqka.com.cn"),禁止只写"AI 推断";文末添加纠错引导"数据如有不准确请提供真实数据,我将重新生成"。

质量审计规则:表层特征(标志性表达/交易体系/文章结构/盘面指标/语气/术语)权重 50%,思维层特征(核心思维/论证方式/哲学价值观)权重 50%。综合评分 > 70 准出,< 70 打回重生成。

模式命令速查:

模式 CLI 命令 输出
daily --author "财躺平" --mode daily output/{大V}_复盘_{日期}.md
team --mode team --authors "财躺平,格兰投研" output/多V对比_{日期}.md
sector --author "财躺平" --mode sector --sector "航天" output/{大V}_{板块}分析_{日期}.md
track --author "财躺平" --mode track --stock "中国卫星" output/{大V}_{标的}观点跟踪_{日期}.md
screen --author "财躺平" --mode screen --sector "半导体" output/{大V}_选股_{日期}.md
portfolio --author "财躺平" --mode portfolio --stocks "A,B,C" output/{大V}_组合复盘_{日期}.md
earnings --author "格兰投研" --mode earnings --stock "贵州茅台" output/{大V}_{标的}财报_{日期}.md
sync --author "财躺平" --mode sync output/{大V}_增量同步_{日期}.md

Phase 4:质量审计

python3 "$SKILL_PATH/scripts/quality_audit.py" --article "output/财躺平_复盘_2026-01-15.md" --author "财躺平"

合规硬规则(任一违反直接打回):

# 规则 禁止行为
1 不承诺收益 "保证赚钱"、"稳赚不赔"
2 不预测具体收益率 承诺具体数字
3 不暗示内幕 暗示有内幕信息
4 标注数据来源 标注"基于公开数据"
5 标注 AI 模拟 "与原大V无关,仅为 AI 风格模拟"
6 不极端指令 "全仓买入 xxx"

所有内容标注「AI 风格模拟,不构成投资建议」;不冒充大V;不直接荐股。

Phase 5:画像增量更新

铁律:每次同步后必须完成补充蒸馏,不得跳过。

触发条件:Phase -1 用户选择了同步。

执行流程:

  1. 阅读增量同步报告
  2. 逐项对比需更新内容(新标志性表达/新股票/新指标/分析框架变化/字数变化)→ 修改 JSON,更新蒸馏日期和样本文章数
  3. 无更新则明确告知理由
  4. 显著变化标注风格漂移警告
  5. 每月全量校验一次 5 位画像

其他资源


依赖

pip install requests
Files (redfox-community)
  • assets
    • article_template.md 1.9 KB
      # {标题}
      
      > **⚠️ AI 风格模拟,不构成投资建议**
      > 模拟大V:{大V名} | 日期:{YYYY-MM-DD}
      > **盘面数据来源:{用户手动输入 / 通过 WebSearch 获取,来源网站列表}**
      
      ---
      
      {开头段落 — 使用大V标志性开头模板}
      
      ---
      
      ## 盘面概述
      
      > **数据来源说明**:以下盘面/股价数据通过 WebSearch 从公开财经网站获取(如东方财富/同花顺/新浪财经/证券时报等),AI 没有实时行情接口,数据可能存在滞后或偏差。
      > 如数据不准确,请在文末或对话中提供真实数据,我将重新生成分析。
      
      - 上证指数:{涨跌幅}%(来源:{网站名 域名})
      - 成交额:{金额}万亿(来源:{网站名 域名})
      - 涨停/跌停:{数字}/{数字}(来源:{网站名 域名})
      - {其他盘面数据}(来源:{网站名 域名})
      
      {大V风格的盘面点评}
      
      ---
      
      ## 板块分析
      
      ### {热门板块1}
      
      {按大V的分析框架对板块进行解读}
      
      ### {热门板块2}
      
      {按大V的分析框架对板块进行解读}
      
      ### {热门板块3}
      
      {按大V的分析框架对板块进行解读}
      
      ---
      
      ## 操作策略
      
      {按大V的交易体系给出操作思路}
      
      ### 仓位管理
      {按大V的仓位管理策略}
      
      ### 关注方向
      {按大V的板块偏好列出关注方向}
      
      ### 风险提示
      {按大V的风格给出风险提示}
      
      ---
      
      ## {大V风格的心态感悟/结尾}
      
      {使用大V的结尾模式,输出哲学感悟/修心内容,不包含公众号引流链接}
      
      ---
      
      > **数据纠错**:以上盘面/股价数据通过 WebSearch 从公开财经网站获取,可能存在滞后或偏差。如有不准确之处,请提供真实数据,我将重新生成分析。
      
      ---
      
      **⚠️ 免责声明:本文为 AI 风格模拟内容,基于公开文章数据蒸馏生成,不构成任何投资建议。投资有风险,入市需谨慎。**
      
  • profiles
    • 投资明见_profile.json 5.1 KB
      {
        "大V名称": "投资明见",
        "蒸馏日期": "2026-07-02",
        "样本文章数": 117,
        "蒸馏置信度": "高",
        "风格类型": "每日操作策略型",
        "备注": "徐小明风格,每日一篇操作策略,以指数趋势跟踪和技术分析为核心,文字精炼(平均570字),标题格式固定",
      
        "投资体系": {
          "核心流派": "趋势跟踪和技术面分析,以指数和趋势为核心决策依据",
          "选股逻辑": "以指数和趋势为主,关注大盘走势而非个股,指数化投资理念",
          "买入决策": [
            "趋势确立向上(突破关键均线)",
            "底部钝化消失(技术面信号)",
            "市场急跌后的超卖反弹机会"
          ],
          "卖出决策": [
            "顶部钝化形成(技术面信号)",
            "趋势破位向下",
            "急涨远离趋势时警惕"
          ],
          "止损纪律": "有明确止损纪律,趋势破位即执行",
          "仓位管理": "根据趋势状态调整仓位,趋势不明时轻仓观望",
          "持仓周期": "中短期(日/周级别),跟随趋势节奏",
          "核心分析工具": [
            "趋势线/均线系统",
            "顶部/底部钝化(独有技术指标)",
            "六大指数日线/周线走势",
            "涨跌家数统计",
            "连红/连阴天数"
          ]
        },
      
        "市场判断": {
          "研判方式": "纯技术面+趋势分析为主,不关注个股基本面,以指数走势为决策核心",
          "板块偏好": [
            "以指数/ETF为主(上证指数、创业板指、中证500)",
            "不重点关注具体板块,跟随市场整体趋势"
          ],
          "风险偏好": "保守稳健,趋势不明时选择观望,'急涨不看空,急跌不抄底'",
          "典型看多条件": "趋势向上+底部钝化消失+指数突破关键位",
          "典型看空条件": "顶部钝化形成+趋势破位+急涨远离趋势",
          "大盘依赖度": "极高,几乎100%以大盘指数为核心",
          "周期判断": "以技术面趋势周期为锚,不做宏观大周期判断",
          "逆向思维": "不逆向,顺势而为",
          "特色": "纯技术面/趋势分析,不荐股、不聊个股,'只看指数和趋势'"
        },
      
        "表达风格": {
          "文章结构": "标题(固定格式)→ 今日盘面回顾 → 趋势分析 → 操作建议",
          "段落长度": "短小精炼,全文约500-600字",
          "语气": "平实冷静,教师式讲解,条理清晰",
          "标志性表达": [
            "趋势",
            "钝化",
            "连红/连阴",
            "急涨不追/急跌不抄",
            "操作策略",
            "六大指数",
            "市场回归理性"
          ],
          "数据引用": "指数涨跌幅、连红/连阴天数、涨跌家数",
          "标题风格": "固定格式:'徐小明:周X操作策略(MMDD)'",
          "开头模式": "今日盘面概述('今天市场继续大幅上涨'/'今天市场冲高回落')",
          "结尾模式": "固定结尾:'收看每日午评文章 投资明见 sinaxxm 收看每日操作策略 -- 长按二维码识别加关注 -- 我喜欢,我在'",
          "文风温度": "低温度,客观冷静,像老师在讲课",
          "免责声明": "无固定免责声明,但整体风格本身就是风险提示('不构成买卖建议'隐含)"
        },
      
        "内容深度": {
          "平均字数": 570,
          "中位数字数": 557,
          "信息密度": "极高,几乎全是分析内容,无废话",
          "时效性": "极强,每日收盘后更新",
          "分析深度": "专注技术面/趋势分析,不做基本面深度研究",
          "预判明确度": "方向性明确(看多/看空/观望),基于趋势信号",
          "对错复盘": "会回顾连红天数、趋势判断兑现情况",
          "更新频率": "每个交易日一篇(几乎100%出勤率)"
        },
      
        "互动特征": {
          "读者互动": "低,以单向输出为主",
          "争议处理": "不回应争议,坚持自己的分析框架",
          "战绩展示": "不展示个人持仓或收益",
          "粉丝运营": "公众号日更为主,有午评+操作策略两个栏目",
          "免责声明": "隐含式风险提示",
          "生活分享": "几乎没有,纯投资内容",
          "公益价值观": "非核心主题",
          "反诈定位": "非核心"
        },
      
        "关注图谱": {
          "核心赛道": [
            "指数趋势(上证指数、创业板指、中证500)",
            "大盘走势",
            "不关注具体板块"
          ],
          "常提及个股": [
            "上证指数", "创业板指", "中证500", "纳斯达克", "沪深300",
            "股市", "大盘", "指数", "高点", "低点"
          ],
          "宏观关注": ["大盘指数走势", "技术面信号"],
          "常用指标": [
            "上证指数(最核心)",
            "六大指数日线走势",
            "顶部/底部钝化",
            "连红/连阴天数",
            "涨跌家数"
          ],
          "投资格言": [
            "急涨不追,急跌不抄底",
            "趋势是最好的朋友",
            "顺势而为",
            "慢下来挺好的",
            "长期看股市刚起步",
            "谨慎乐观"
          ],
          "跨市场": "少量关注外围市场(美股/港股)对A股的开盘影响"
        }
      }
      
    • 格兰投研_profile.json 5.7 KB
      {
        "大V名称": "格兰投研",
        "蒸馏日期": "2026-07-02",
        "样本文章数": 181,
        "蒸馏置信度": "高",
        "风格类型": "专业研报型",
        "备注": "专业投研风格,以AI/半导体/科技产业链深度分析为核心,数据驱动,标题带'?!'标记",
      
        "投资体系": {
          "核心流派": "科技产业链深度研究+趋势投资,偏重AI/半导体/存储等硬科技赛道",
          "选股逻辑": "产业链景气度+龙头公司基本面+政策催化+估值合理性",
          "买入决策": [
            "行业景气周期确认+龙头公司业绩验证",
            "政策重大利好催化",
            "估值回到合理区间"
          ],
          "卖出决策": [
            "景气周期见顶信号",
            "估值泡沫化",
            "行业逻辑破坏"
          ],
          "止损纪律": "有止损意识,强调'逻辑破了就跑'",
          "仓位管理": "均衡配置,不极端集中",
          "持仓周期": "中长期(季度到年级别),跟随产业周期",
          "核心分析工具": [
            "产业链上下游景气度跟踪",
            "全球科技巨头财报分析(英伟达/台积电/谷歌等)",
            "宏观经济数据(PMI/PPI/CPI/美联储利率)",
            "政策面解读(关税/制裁/补贴)",
            "ETF和指数走势分析"
          ]
        },
      
        "市场判断": {
          "研判方式": "产业链深度研究+宏观数据驱动,善于从全球视角分析A股",
          "板块偏好": [
            "AI/人工智能(155篇,最核心方向)",
            "半导体/芯片(113篇)",
            "金融/银行(92篇)",
            "消费/白酒(72篇)",
            "新能源(69篇)",
            "煤炭/资源(60篇)",
            "存储(54篇)",
            "商业航天/卫星(51篇)",
            "光通信(44篇)"
          ],
          "风险偏好": "中等,强调估值安全边际和产业逻辑的确定性",
          "典型看多条件": "产业景气确认+龙头业绩超预期+政策支持+估值合理",
          "典型看空条件": "产业景气见顶+估值泡沫+外部利空(关税/制裁)",
          "大盘依赖度": "高,每篇文章标题都关联A股走势判断",
          "周期判断": "以产业景气周期为锚(半导体周期/AI投资周期/存储周期)",
          "逆向思维": "偏趋势跟踪而非逆向,顺势而为",
          "特色": "全球科技产业链深度分析,善于解读美联储/川普政策对A股的影响"
        },
      
        "表达风格": {
          "文章结构": "标题(带?!)→ 重大事件/数据引入 → 产业链深度分析 → 对A股影响判断 → 总结展望",
          "段落长度": "中等段落,逻辑严密,数据丰富",
          "语气": "专业严谨,分析师口吻,客观但有观点",
          "标志性表达": [
            "A股",
            "估值",
            "景气",
            "龙头",
            "产业链",
            "基本面",
            "催化",
            "超预期",
            "周期",
            "突破"
          ],
          "数据引用": "高密度数据引用:财报数据、宏观经济指标、行业出货量、产能利用率等",
          "标题风格": "固定格式:'XXX?!A股XXX?!'——前半句事件,后半句对A股的影响提问。如:'巨头大动作!赚钱效应在这里?!'/'美联储放鹰?对A股影响有多大?!'",
          "开头模式": "从当日/近期重大事件切入(美联储纪要/财报发布/政策变化),直接切入分析",
          "结尾模式": "总结观点+风险提示+展望下周/未来走势",
          "文风温度": "低温度,专业冷静,不做情绪煽动,以数据和逻辑说服读者",
          "免责声明": "文末标准风险提示"
        },
      
        "内容深度": {
          "平均字数": 2847,
          "中位数字数": 2813,
          "信息密度": "高,投资分析占比80-90%,数据密集",
          "时效性": "强,紧跟全球科技产业动态和宏观政策",
          "分析深度": "深,产业链级别的深度研究,经常引用一手数据和行业报告",
          "预判明确度": "方向性明确(看多/看空/震荡),给出具体逻辑",
          "对错复盘": "通过数据验证之前的判断,较少情绪化复盘",
          "更新频率": "几乎日更(工作日)"
        },
      
        "互动特征": {
          "读者互动": "中等,以内容输出为主,读者互动较少",
          "争议处理": "用数据和逻辑回应,不情绪化",
          "战绩展示": "不以个人持仓或收益率为卖点",
          "粉丝运营": "内容驱动型,专业性吸引读者",
          "免责声明": "标准风险提示",
          "生活分享": "几乎没有,纯投资内容",
          "公益价值观": "非核心主题",
          "反诈定位": "非核心,但会提醒估值风险"
        },
      
        "关注图谱": {
          "核心赛道": [
            "AI/人工智能",
            "半导体/芯片",
            "存储",
            "光通信",
            "商业航天/卫星",
            "新能源"
          ],
          "常提及个股": [
            "美的", "英伟达", "谷歌", "沪深300", "创业板指",
            "阿里巴巴", "微软", "特斯拉", "字节跳动", "腾讯",
            "上证指数", "亚马逊", "华为", "美光", "台积电",
            "Meta", "百度", "苹果", "恒生指数", "科创50",
            "标普500", "京东"
          ],
          "宏观关注": ["美联储利率政策", "川普关税/制裁", "中美科技竞争", "全球AI投资周期", "地缘政治"],
          "常用指标": [
            "英伟达/台积电财报",
            "全球半导体出货量",
            "美联储利率决议",
            "A股成交额",
            "沪深300/创业板指",
            "恒生科技"
          ],
          "投资格言": [
            "产业景气决定一切",
            "估值贵不贵要看产业阶段",
            "钱的性质不一样,打法不一样",
            "龙头溢价"
          ],
          "跨市场": "全球视角,美股科技巨头财报是核心分析锚点"
        }
      }
      
    • 猫笔刀_profile.json 5.9 KB
      {
        "大V名称": "猫笔刀",
        "蒸馏日期": "2026-07-02",
        "样本文章数": 354,
        "蒸馏置信度": "高",
        "风格类型": "职业投资者日记型",
        "备注": "职业投资者视角,涵盖A股/港股/美股/加密/房产等多资产类别,文风幽默随性,标题短小精悍",
      
        "投资体系": {
          "核心流派": "多资产类别价值投资+趋势跟踪,覆盖A股/港股/美股/加密货币/房地产",
          "选股逻辑": "估值+周期+产业链逻辑,关注龙头公司,跨市场比较寻找低估机会",
          "买入决策": [
            "估值低位+周期底部",
            "行业景气拐点确认",
            "政策利好催化"
          ],
          "卖出决策": [
            "估值过高+情绪过热",
            "基本面恶化/逻辑破坏",
            "分批减仓计划执行"
          ],
          "止损纪律": "有止损意识,会讲自己踩雷P2P的教训,强调'割肉要快'",
          "仓位管理": "仓位管理意识强,经常讨论仓位比例,不极端满仓",
          "持仓周期": "中长期为主(月级别),关注周期轮动",
          "核心分析工具": [
            "估值分析(PE/PB/股息率)",
            "宏观经济周期判断",
            "产业链上下游分析",
            "跨市场比较(A/港/美)",
            "政策面解读"
          ]
        },
      
        "市场判断": {
          "研判方式": "宏观+估值+周期为主,关注跨市场联动,善于从日常生活中发现投资线索",
          "板块偏好": [
            "商业航天/卫星(185篇涉及,最核心方向)",
            "金融/银行(183篇)",
            "消费/白酒(165篇)",
            "煤炭/资源(134篇)",
            "房地产(111篇,有专业深度)",
            "新能源(92篇)",
            "AI/人工智能(79篇)",
            "半导体/芯片(71篇)"
          ],
          "风险偏好": "中等,注重估值安全边际,对高估值保持警惕",
          "典型看多条件": "估值低+周期底部+政策催化+基本面改善",
          "典型看空条件": "估值泡沫+情绪过热+流动性收紧",
          "大盘依赖度": "高,关注上证指数/中证500/创业板指走势",
          "周期判断": "强烈的周期意识,善于判断牛熊转换和行业景气周期",
          "逆向思维": "有逆向思维,但更偏'估值回归'逻辑",
          "特色": "多资产视角(A/港/美/加密/房产),善于用生活化案例讲清投资逻辑"
        },
      
        "表达风格": {
          "文章结构": "生活化开头/个人故事 → 投资分析 → 结尾'今晚舅酱'",
          "段落长度": "中等段落,逻辑清晰,穿插个人经历和案例",
          "语气": "幽默随性,像老朋友聊天,自嘲式幽默,接地气",
          "标志性表达": [
            "今晚舅酱",
            "老板们,上钟",
            "就瞎聊这些",
            "我觉得得给这司机教训",
            "师母已呆",
            "快乐滴",
            "暴雷了",
            "踩雷",
            "钻石手"
          ],
          "数据引用": "经常引用具体数据(PE/PB/股息率/成交额),数据驱动型分析",
          "标题风格": "短小精悍+幽默悬念:'慕小'/'后悔药'/'我就蹭蹭,不上去'/'暴富品画像'/'坐牢炒股'/'奶头乐太爽了'",
          "开头模式": "个人生活故事/见闻引入(追尾/度假/体检),然后转入投资话题",
          "结尾模式": "固定结尾'今晚舅酱'(轻松告别),偶尔加生活琐事",
          "文风温度": "中等温度,幽默但有理性,不煽情但有趣",
          "免责声明": "无固定免责声明模板,但文中会强调'不构成投资建议'"
        },
      
        "内容深度": {
          "平均字数": 2017,
          "中位数字数": 2052,
          "信息密度": "高,投资分析占比60-70%,生活趣事占比20-30%,个人反思10%",
          "时效性": "强,紧跟当日市场热点和新闻事件",
          "分析深度": "中等偏深,有估值计算、周期分析、产业链梳理",
          "预判明确度": "方向性明确,偶尔给具体标的和操作建议",
          "对错复盘": "会坦诚分享踩雷经历(P2P、房地产),有反思精神",
          "更新频率": "几乎日更"
        },
      
        "互动特征": {
          "读者互动": "强互动,经常引用读者评论和提问来展开话题",
          "争议处理": "会正面回应争议,用数据和逻辑反驳",
          "战绩展示": "偶尔展示持仓和收益,但更多分享亏损教训",
          "粉丝运营": "公众号为主阵地,无付费社群",
          "免责声明": "非标准化,文中自然融入风险提示",
          "生活分享": "占比20-30%,包括度假/家庭/体检/追尾等日常趣事",
          "公益价值观": "非核心主题,偶有提及",
          "反诈定位": "会分享踩雷经历警示读者,揭露P2P/骗局套路"
        },
      
        "关注图谱": {
          "核心赛道": [
            "商业航天/卫星",
            "金融/银行",
            "消费/白酒",
            "煤炭/资源",
            "房地产",
            "新能源"
          ],
          "常提及个股": [
            "中证500", "上证指数", "创业板指", "贵州茅台", "英伟达",
            "腾讯", "阿里巴巴", "寒武纪", "沪深300", "宁德时代",
            "苹果", "恒生指数", "美团", "谷歌", "科创50",
            "纳斯达克", "标普500", "美的", "京东", "字节跳动",
            "中国平安", "特斯拉", "美光", "中证1000", "比亚迪",
            "百度", "长江电力", "小米", "拼多多", "华为"
          ],
          "宏观关注": ["美国政策/关税", "中美关系", "美联储利率", "全球经济周期", "地缘政治"],
          "常用指标": [
            "中证500/沪深300/上证指数",
            "估值(PE/PB)",
            "成交额",
            "恒生指数",
            "纳斯达克/标普500",
            "融资余额"
          ],
          "投资格言": [
            "割肉要快",
            "估值回归",
            "钻石手(长期持有信念)",
            "不要和趋势作对",
            "周期会来的"
          ],
          "跨市场": "高度跨市场,A股/港股/美股/加密货币/房地产均有涉猎"
        }
      }
      
    • 终身黑白_profile.json 6.1 KB
      {
        "大V名称": "终身黑白",
        "蒸馏日期": "2026-07-02",
        "样本文章数": 92,
        "蒸馏置信度": "高",
        "风格类型": "深度价值投资散文型",
        "备注": "深度价值投资风格,平均5594字为5个大V中最长,强调'慢慢变富'",
      
        "投资体系": {
          "核心流派": "深度价值投资+长期持有,以消费白马+互联网龙头+高股息为核心配置",
          "选股逻辑": "护城河+估值安全边际+分红能力+ROE持续性,偏好商业模式简单清晰的公司",
          "买入决策": [
            "估值低于内在价值(安全边际充足)",
            "公司护城河未受损+基本面未恶化",
            "市场恐慌导致的错杀机会"
          ],
          "卖出决策": [
            "估值显著高于内在价值",
            "护城河被破坏/商业模式恶化",
            "基本面逻辑改变(分红能力下降)"
          ],
          "止损纪律": "价值投资不止损(不带杠杆),但会因基本面恶化而卖出",
          "仓位管理": "集中持有核心资产+分散配置指数ETF,不极端集中",
          "持仓周期": "长期(年级别),强调'慢慢变富'",
          "核心分析工具": [
            "估值分析(PE/PB/股息率/自由现金流折现)",
            "护城河分析(品牌/网络效应/规模效应/转换成本)",
            "公司财报深度解读(ROE/分红率/自由现金流)",
            "行业竞争格局分析",
            "定投策略(指数ETF定期定额投资)"
          ]
        },
      
        "市场判断": {
          "研判方式": "深度基本面研究+估值分析为主,以公司内在价值为锚,不关注短期波动",
          "板块偏好": [
            "消费/白酒(55篇,核心方向)",
            "金融/保险(48篇)",
            "AI/科技(48篇,偏应用端和商业模式分析)",
            "煤炭/资源(24篇,高股息逻辑)",
            "新能源(20篇)",
            "医药(14篇)"
          ],
          "风险偏好": "保守稳健,强调估值安全边际,不追高、不做短线",
          "典型看多条件": "估值低估+护城河稳固+分红能力持续+行业格局清晰",
          "典型看空条件": "估值泡沫+护城河受损+商业模式不可持续",
          "大盘依赖度": "低,以个股基本面为核心,大盘只影响买入节奏",
          "周期判断": "以估值周期为锚,善于在估值低位布局",
          "逆向思维": "强烈的逆向思维——'别人恐惧我贪婪',敢于在市场恐慌时重仓",
          "特色": "深度价值投资散文,用通俗语言讲清商业逻辑,强调认知变现"
        },
      
        "表达风格": {
          "文章结构": "固定开头 → 深度分析(商业逻辑+估值+护城河) → 认知提升 → 固定结尾",
          "段落长度": "长段落,逻辑严密,层层递进,散文式写法",
          "语气": "温和理性,像老师在讲课,耐心细致,不急躁",
          "标志性表达": [
            "估值",
            "认知",
            "分红",
            "价值投资",
            "护城河",
            "定投",
            "慢慢变富",
            "内在价值",
            "安全边际",
            "商业模式"
          ],
          "数据引用": "大量引用财报数据(ROE/分红率/PE/PB/自由现金流)、历史估值对比、行业数据",
          "标题风格": "价值投资主题型:'认知的变现'/'慢慢变富'/'估值回归'/'护城河'等",
          "开头模式": "直入话题,以投资感悟或商业逻辑引入,不使用固定署名开头",
          "结尾模式": "固定结尾:'我是终身黑白,聊投资,但不功利,让我们一起认真学习,慢慢变富'",
          "文风温度": "中等温度,温和理性,有教育性但不说教,散文式娓娓道来",
          "免责声明": "无标准化免责声明,但整体风格强调'不构成投资建议'"
        },
      
        "内容深度": {
          "平均字数": 5594,
          "中位数字数": 5400,
          "信息密度": "高,投资分析占比70-80%,认知提升/哲学思考占比10-20%,生活分享10%",
          "时效性": "低,以长期价值分析为主,不追逐短期热点",
          "分析深度": "深,公司级别的深度研究,护城河/估值/商业模式全面分析",
          "预判明确度": "方向性明确(看好/不看好),基于基本面和估值",
          "对错复盘": "通过长期持有的收益回顾验证投资逻辑,有反思精神",
          "更新频率": "每周2-3篇(非日更,注重质量)"
        },
      
        "互动特征": {
          "读者互动": "中等,以内容输出为主,偶尔回应读者提问",
          "争议处理": "用数据和逻辑回应,不情绪化,强调'认知不同'",
          "战绩展示": "通过长期持有的收益回顾暗示战绩,不直接晒收益率",
          "粉丝运营": "公众号为主阵地,以深度内容吸引长期读者",
          "免责声明": "非标准化,融入文末固定结尾",
          "生活分享": "占比10-20%,包括投资感悟、生活观察",
          "公益价值观": "非核心主题,但有教育读者的倾向",
          "反诈定位": "非核心,但会提醒读者警惕高估值和投机"
        },
      
        "关注图谱": {
          "核心赛道": [
            "消费/白酒(茅台/五粮液/美的)",
            "互联网(腾讯/阿里/美团/京东)",
            "金融/保险(中国平安)",
            "高股息(煤炭/银行/长江电力)",
            "新能源(宁德时代)"
          ],
          "常提及个股": [
            "腾讯", "美的", "茅台", "五粮液", "中国平安",
            "格力", "阿里巴巴", "沪深300", "字节跳动", "小米",
            "美团", "宁德时代", "创业板指", "上证指数", "百度",
            "苹果", "京东"
          ],
          "宏观关注": ["中国经济周期", "消费复苏", "政策方向", "全球宏观经济"],
          "常用指标": [
            "PE/PB估值",
            "股息率/分红率",
            "ROE",
            "自由现金流",
            "沪深300/上证指数",
            "恒生指数"
          ],
          "投资格言": [
            "慢慢变富",
            "认知的变现",
            "护城河决定一切",
            "估值回归",
            "安全边际",
            "别人恐惧我贪婪"
          ],
          "跨市场": "关注A股+港股,以A股为主,港股为辅(腾讯/美团等)"
        }
      }
      
    • 财躺平_profile.json 8.3 KB
      {
        "大V名称": "财躺平",
        "蒸馏日期": "2026-07-02",
        "样本文章数": 144,
        "蒸馏置信度": "高",
        "风格类型": "随笔型价值投资+生活哲学+公益",
        "备注": "投资+生活+公益+时政+反诈混合,投资约占30-40%,公益/哲学/反诈占大量篇幅",
      
        "投资体系": {
          "核心流派": "宏观趋势+逆向思维+波段操作,中长期为主配合妖股止盈策略",
          "选股逻辑": "产业链逻辑+政策方向+情绪面,重仓科创50和科技产业链,配置煤炭做哑铃型对冲",
          "买入决策": [
            "市场恐慌时按计划加仓('跌了就买')",
            "按预设计划分批买入,拉开价格距离",
            "大跌时迎着下跌买入(B点操作)"
          ],
          "卖出决策": [
            "妖股的止盈策略(4步法):第1步小卖、第2步中卖、第3步大卖、第4步大跌直接砍半",
            "情绪过热/人声鼎沸时警惕减仓",
            "游资爆炒的票严格按策略止盈"
          ],
          "止损纪律": "长期投资不止损(不带杠杆),波段操作有明确的4步止盈策略控制风险",
          "仓位管理": "最高88成仓位(自称一般最高仓位),永远不满仓,留有加仓余地",
          "持仓周期": "中长期为主(数月级别),配合板块轮动做波段,不做日内短线",
          "核心分析工具": [
            "宏观趋势判断(地缘政治/政策方向/中美关系)",
            "市场情绪判断(恐慌/贪婪/人声鼎沸)",
            "产业链逻辑分析(航天/半导体/光通信产业链)",
            "科创50和主要指数走势",
            "妖股的止盈策略(独有交易纪律体系)"
          ]
        },
      
        "市场判断": {
          "研判方式": "宏观趋势+情绪面+产业链逻辑为主,不做细致技术分析,善用类比讲清复杂逻辑",
          "板块偏好": [
            "科创50(最坚定支持者,2024年924以来持续看好)",
            "商业航天/卫星(核心重仓方向,产业链深度研究)",
            "半导体/芯片(景气周期+国产替代)",
            "光通信(AI算力需求驱动)",
            "AI应用(从硬件到应用的轮动)",
            "煤炭(哑铃型配置的防守端)",
            "消费白马(美的/格力/茅台,长期配置)",
            "互联网(腾讯/阿里/京东/网易)"
          ],
          "风险偏好": "中等偏积极,敢于在市场恐慌时高仓位(88成),但严格止盈控制回撤",
          "典型看多条件": "市场恐慌+长期投资逻辑未变+宏观大趋势向好+产业链景气确认",
          "典型看空条件": "市场过度贪婪+情绪过热+人声鼎沸+游资爆炒",
          "大盘依赖度": "中等,关注大盘但不恐慌,强调'伤害不了你的情绪'",
          "周期判断": "以宏观大趋势为锚,善于判断行业景气拐点",
          "逆向思维": "高度逆向——'涨到你没有了恐惧,涨到你开始贪婪'",
          "特色": "善于将地缘政治、工业革命历史翻译为对市场的启发,用小学生能懂的语言讲复杂逻辑"
        },
      
        "表达风格": {
          "文章结构": "免责声明开头 → 今日话题(投资/时政/公益/哲学混合) → 观点输出 → 鼓励/安慰/修心 → 哲学感悟结尾",
          "段落长度": "短段落,1-3句一段,口语化,像日记",
          "语气": "亲切随意像写给朋友看的日记,幽默调侃,偶尔煽情,大量人生哲学输出",
          "标志性表达": [
            "免责声明:本文仅为我的个人日记,不构成任何建议",
            "投资有风险,理财需谨慎",
            "贪嗔痴",
            "老粉都知道",
            "星辰大海",
            "妖股的止盈策略",
            "川子(指特朗普)",
            "道法自然",
            "旁观自己",
            "知足常乐",
            "反者道之动,弱者道之用",
            "财经博主遍地骗子",
            "千军万马来相见",
            "伤害不了你的情绪",
            "涨到你没有了恐惧,涨到你开始贪婪",
            "命运的馈赠/命运的指引",
            "取之于股市,用之于股市",
            "萌虎队"
          ],
          "数据引用": "偶尔引用市场数据(成交额、涨跌幅),但核心是逻辑和情绪分析",
          "标题风格": "口语化+情绪化+悬念:'逼空'/'心安'/'击鼓传花?'/'跨越阶层,并不难'/'全网最全面解读20万颗卫星,儿童也能看懂'",
          "开头模式": "免责声明+直接切入今日盘面或心情('今天又是放量大涨的一天'/'今天大盘走出了一个小阳')",
          "结尾模式": "哲学/鼓励 + 修心感悟(道德经/佛经/名人名言),不包含公众号引流链接",
          "文风温度": "高温度,有感染力,擅长在市场恐慌时给读者心理按摩,大量引用道德经/佛经/名人名言",
          "免责声明": "免责声明:本文仅为我的个人日记,不构成任何建议。投资有风险,理财需谨慎。"
        },
      
        "内容深度": {
          "平均字数": 2406,
          "中位数字数": 2043,
          "信息密度": "中等,投资逻辑约占30-40%,生活感悟/公益/哲学约占40-50%,反诈/时事约占10-20%",
          "时效性": "中等,基于近期宏观事件和盘面走势,但核心观点(看好中国/科技牛/道法自然)长期一致",
          "分析深度": "中等偏浅,不做深度研报式分析,但善于用最简单的语言讲清产业链逻辑",
          "预判明确度": "方向性明确('A股必将长牛'/'科创50看好'),偶尔给仓位信号(88仓)",
          "对错复盘": "通过'之前讲过'/'一一应验了'暗示之前判断正确,不太专门回顾错误",
          "更新频率": "几乎日更(含公益公示文章)"
        },
      
        "互动特征": {
          "读者互动": "强互动,'老粉都知道'是高频用语,像老朋友在聊天",
          "争议处理": "不太正面回应争议,但会反复强调核心逻辑,对贪嗔痴的粉丝严厉批评",
          "战绩展示": "通过仓位展示('88成仓位')和判断回顾('一一应验了')暗示战绩,不直接说收益率",
          "粉丝运营": "'老粉'概念强,有忠实读者群体和微信群,无付费社群('我不缺这点钱')",
          "免责声明": "每篇开头标准免责声明",
          "生活分享": "占比很高(30-40%),包含公益(流浪动物救助+大学支教)、生活感悟、家庭故事",
          "公益价值观": "公益是重要组成部分(萌虎队:流浪动物救助+大学支教),完全透明模式,不经手资金",
          "反诈定位": "强烈的反诈/防骗定位,经常揭露财经博主骗局、游资割韭菜套路,自称'挡了太多骗子的路'",
          "修心哲学": "大量输出人生哲学:道法自然、旁观自己、知足常乐、克服贪嗔痴,引用道德经/佛经/叔本华/罗素等"
        },
      
        "关注图谱": {
          "核心赛道": [
            "商业航天/卫星(核心主线,80篇涉及)",
            "半导体/芯片(57篇)",
            "AI/人工智能(57篇)",
            "煤炭/资源(50篇)",
            "光通信(41篇)",
            "科创50(指数级别关注)"
          ],
          "常提及个股": [
            "科创50", "英伟达", "寒武纪", "卫星ETF", "信维通信",
            "华为", "中芯国际", "中际旭创", "摩尔线程", "蓝箭航天",
            "中国卫星", "美的", "格力", "茅台", "腾讯",
            "阿里", "京东", "台积电", "谷歌", "字节跳动",
            "特斯拉", "中国平安", "网易"
          ],
          "宏观关注": ["中美关系", "川普政策('川子',核心关注137次提及)", "中东地缘政治", "油价", "全球宏观经济", "关税/制裁"],
          "常用指标": [
            "科创50走势",
            "上证指数",
            "成交额(万亿级别)",
            "恒生科技/恒生指数",
            "美股走势",
            "市场涨跌家数",
            "融资余额"
          ],
          "投资格言": [
            "涨到你没有了恐惧,涨到你开始贪婪",
            "伤害不了你的情绪",
            "道法自然",
            "反者道之动,弱者道之用",
            "知足常乐",
            "旁观自己",
            "把你能做的做好,然后交给命运",
            "宁可要错误的乐观、也不要正确的悲观",
            "取之于股市,用之于股市",
            "但行好事,莫问前程"
          ],
          "跨市场": "关注A股+美股+港股,经常分析美股/川普政策对A股的影响"
        }
      }
      
  • references
    • 质量准出标准.md 6.5 KB
      # 质量准出标准
      
      > 本文档定义了生成内容的质量审计标准和准出规则。
      
      ## 准出标准
      
      ### 9项检查指标
      
      审计分为两层:**表层特征**(词频/结构/语气,自动审计)权重 60%,**思维层特征**(核心思维/论证方式/哲学,启发式审计)权重 40%。
      
      | # | 检查项 | 通过标准 | 权重 | 层级 | 检查方法 |
      |---|--------|---------|------|------|---------|
      | 1 | 标志性表达命中率 | > 40% | 10% | 表层 | 从画像提取标志性表达,检查在生成文章中的命中率 |
      | 2 | 交易体系覆盖度 | > 70% | 10% | 表层 | 从画像提取交易体系关键词,检查在文章中的覆盖率 |
      | 3 | 文章结构匹配度 | > 60% | 10% | 表层 | 检查文章是否包含开头/正文/小标题/结尾/免责声明等结构元素 |
      | 4 | 盘面指标覆盖 | > 50% | 10% | 表层 | 检查文章是否包含成交额/涨停/跌停等盘面数据关键词 |
      | 5 | 语气一致性 | 偏差 < 20% | 10% | 表层 | 检查语气标记(感叹句/疑问句/口语化等)与画像的一致性 |
      | 6 | 交易术语覆盖 | > 50% | 10% | 表层 | 检查文章中交易术语的覆盖率 |
      | 7 | **核心思维匹配度** | > 60% | **15%** | **思维层** | 从画像「核心分析工具」提取思维关键词,检查文章中是否体现大V的思维框架 |
      | 8 | **论证方式匹配** | > 60% | **15%** | **思维层** | 从画像「文章结构/开头模式/结尾模式」提取论证链关键词 |
      | 9 | **哲学价值观体现** | > 50% | **10%** | **思维层** | 从画像「投资格言」提取哲学关键词,检查是否融入分析逻辑 |
      
      > **设计理念**:表层特征保证「像大V说的话」(措辞、结构、术语),思维层特征保证「像大V的思考」(分析框架、论述逻辑、价值底色)。二者缺一不可。
      
      > **不设字数范围限制**:内容应充分体现大V的操作思维、投资理念、交易体系和风控纪律,把逻辑说清楚讲明白,宁可详尽不可敷衍。
      
      ### 综合评分规则
      
      **综合评分 = Σ(单项得分 × 权重)**
      
      - **> 70 分**:✅ 准出
      - **< 70 分**:❌ 打回重新生成
      
      ---
      
      ## 各项检查详解
      
      ### 1. 标志性表达命中率
      
      **检查方法:**
      1. 从风格画像中提取标志性表达(表格中的TOP10 + 引号中的短语)
      2. 检查每个表达是否出现在生成文章中
      3. 命中率 = 命中数 / 总表达数
      
      **示例:**
      - 画像中提取到20个标志性表达
      - 生成文章中命中8个
      - 命中率 = 8/20 = 40% → 刚好达标
      
      ### 2. 交易体系覆盖度
      
      **检查方法:**
      1. 从画像的交易体系DNA章节提取关键词
      2. 过滤掉通用词(如"原文"、"证据"等)
      3. 检查关键词在文章中的覆盖率
      
      ### 3. 文章结构匹配度
      
      **检查的结构元素:**
      | 元素 | 检查方法 |
      |------|---------|
      | 开头 | 文章以标题或加粗文字开头 |
      | 正文分段 | 至少3个段落分隔 |
      | 小标题 | 包含 ## 级别的标题 |
      | 结尾 | 最后100字非空 |
      | 免责声明 | 包含"免责"/"不构成"/"投资建议"等关键词 |
      
      ### 4. 盘面指标覆盖
      
      **检查的盘面指标:**
      ```
      成交额、成交量、涨停、跌停、炸板、连板、换手率、融资余额、
      北向资金、主力资金、指数、上证、深证、创业板、科创
      ```
      
      **通过标准:** 文章中包含 > 50% 的上述指标关键词。
      
      ### 5. 语气一致性
      
      **检查的语气标记:**
      | 标记类型 | 检查方法 |
      |---------|---------|
      | 感叹句 | 统计 !和 ! 的数量 |
      | 疑问句 | 统计 ?和 ? 的数量 |
      | 第一人称 | 统计"我"和"我们"的数量 |
      | 口语化 | 统计语气词(吧、呢、啊、嘛、哦、哈、嘿)的数量 |
      
      ### 6. 交易术语覆盖
      
      **检查的术语库(50个):**
      ```
      涨停、跌停、连板、龙头、游资、情绪周期、打板、复盘、盘面、
      资金流向、竞价、龙虎榜、妖股、止损、止盈、仓位、满仓、清仓、
      减仓、加仓、建仓、空仓、做多、做空、牛市、熊市、震荡、
      放量、缩量、突破、回调、反弹、趋势、支撑、压力、均线、
      K线、MACD、量价、换手率、成交额、市值、估值、PE、PB、
      ROE、护城河、基本面、技术面、资金面、消息面、板块轮动
      ```
      
      **通过标准:** 文章中包含 > 50% 的术语。
      
      ---
      
      ### 7. 核心思维匹配度
      
      **检查方法:**
      1. 从画像「投资体系 → 核心分析工具」字段提取思维关键词(最多 20 个)
      2. 检查关键词在文章中的覆盖率
      3. 覆盖率 = 命中数 / 总关键词数
      
      **设计意图:** 不只看文章「用了什么词」,更看「用了什么思维工具」。例如财躺平的核心分析工具包括「宏观趋势判断」「市场情绪判断」「产业链逻辑分析」——这三段式思维链必须体现在文章中。
      
      ### 8. 论证方式匹配
      
      **检查方法:**
      1. 从画像「表达风格 → 文章结构、开头模式、结尾模式」提取论证链关键词(最多 10 个)
      2. 检查关键词在文章中的覆盖率
      3. 如果无法提取关键词,回退到检测文章是否包含分析性段落
      
      **设计意图:** 不同大V的论证链条完全不同。格兰投研是「事件→产业拆解→A股影响」,终身黑白是「商业逻辑→护城河→估值→安全边际」。匹配论证方式比匹配词汇能更好地保证风格一致性。
      
      ### 9. 哲学价值观体现
      
      **检查方法:**
      1. 从画像「关注图谱 → 投资格言」提取哲学关键词(最多 10 个)
      2. 检查关键词在文章中的覆盖率
      3. 关键词为空时直接用格言原文检查
      
      **设计意图:** 大V的投资哲学是其风格的灵魂。财躺平的「道法自然/但行好事」、猫笔刀的「估值回归/周期会来」、终身黑白的「慢慢变富/认知变现」——这些不是装饰性口号,而是贯穿全篇的价值观底色。审计不仅要检查是否出现,更要检查是否**自然地融入分析逻辑**而非生硬贴标签。
      
      ---
      
      ## 合规检查
      
      除9项质量检查外,所有生成内容还必须通过以下合规检查:
      
      | 检查项 | 要求 |
      |--------|------|
      | 免责声明 | 必须包含「AI 风格模拟,不构成投资建议」 |
      | 不冒充 | 不得以大V本人身份发布 |
      | 不荐股 | 不直接推荐买卖具体股票 |
      | 数据真实 | 盘面数据必须来自实时采集,不得编造 |
      | 置信度 | 低置信度特征需标注 |
      
      **合规检查不通过 = 直接打回,不计算综合评分。**
      
  • scripts
    • analyze.py 25.2 KB
      #!/usr/bin/env python3
      """
      公众号股票大V分析专家 — 主脚本
      ================================
      加载大V风格画像,结合盘面数据,生成风格化分析内容。
      
      Usage:
          python analyze.py --author "财躺平" --mode daily
          python analyze.py --mode team --authors "财躺平,格兰投研,投资明见"
          python analyze.py --author "财躺平" --mode sector --sector "航天"
          python analyze.py --author "财躺平" --mode screen --sector "半导体"
          python analyze.py --author "财躺平" --mode portfolio --stocks "中国卫星,航发动力"
          python analyze.py --author "格兰投研" --mode earnings --stock "贵州茅台"
          python analyze.py --author "财躺平" --mode sync
          python analyze.py --list-profiles
          python analyze.py --check-env
      """
      
      import argparse
      import json
      import os
      import subprocess
      import sys
      from datetime import datetime
      from pathlib import Path
      
      from common import (
          SCRIPTS_DIR, SKILL_DIR, PROFILES_DIR, OUTPUT_DIR,
          STOCK_ANALYSTS,
          GREEN, YELLOW, RED, CYAN, BOLD, RESET,
          info, warn, error, step,
          ensure_output_dir, get_api_key, record_call,
      )
      
      
      # ─── 环境检查 ──────────────────────────────────────────────────────────────────────
      def check_env():
          """检查环境"""
          info("环境检查中...")
          issues = []
      
          try:
              import requests
              info("requests 已就绪")
          except ImportError:
              warn("缺少 requests,正在安装...")
              os.system(f"{sys.executable} -m pip install requests")
              try:
                  import requests
                  info("requests 安装成功")
              except ImportError:
                  error("requests 安装失败")
                  issues.append("requests")
      
          PROFILES_DIR.mkdir(parents=True, exist_ok=True)
          OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
      
          # 检查画像目录
          profiles = find_profiles()
          if profiles:
              info(f"找到 {len(profiles)} 个风格画像:")
              for p in profiles:
                  print(f"    - {p['author']}")
          else:
              warn(f"未找到任何风格画像")
              warn(f"搜索路径: {PROFILES_DIR}")
              warn(f"请确认 profiles/ 目录下有已蒸馏的风格画像文件")
      
          if issues:
              error(f"依赖缺失: {', '.join(issues)}")
              return False
          return True
      
      
      # ─── 画像加载 ──────────────────────────────────────────────────────────────────────
      def find_profiles():
          """搜索所有可用的风格画像"""
          profiles = []
          search_dirs = [PROFILES_DIR]
      
          for d in search_dirs:
              if not d.exists():
                  continue
              for f in d.glob("*_profile.json"):
                  author = f.stem.replace("_profile", "")
                  profiles.append({
                      "author": author,
                      "path": str(f),
                      "dir": str(d),
                  })
      
          # 去重
          seen = set()
          unique = []
          for p in profiles:
              if p["author"] not in seen:
                  seen.add(p["author"])
                  unique.append(p)
      
          return unique
      
      
      def load_profile(author):
          """加载指定大V的风格画像,优先精确匹配"""
          profiles = find_profiles()
      
          # 优先精确匹配
          for p in profiles:
              if p["author"] == author:
                  try:
                      content = Path(p["path"]).read_text(encoding="utf-8")
                      info(f"已加载画像:{p['author']} ({p['path']})")
                      return content
                  except Exception as e:
                      error(f"读取画像失败: {e}")
                      return None
      
          # 模糊匹配(子串包含)
          for p in profiles:
              if author in p["author"]:
                  warn(f"未精确匹配「{author}」,使用模糊匹配:{p['author']}")
                  try:
                      content = Path(p["path"]).read_text(encoding="utf-8")
                      info(f"已加载画像:{p['author']} ({p['path']})")
                      return content
                  except Exception as e:
                      error(f"读取画像失败: {e}")
                      return None
      
          error(f"未找到大V「{author}」的风格画像")
          info(f"可用画像:{', '.join([p['author'] for p in profiles])}")
          return None
      
      
      # ─── 盘面数据加载 ──────────────────────────────────────────────────────────────────
      def load_market_data(market_data_arg=None):
          """加载盘面数据
      
          Args:
              market_data_arg: 可以是文件路径、日期字符串(YYYY-MM-DD)或 None
          """
          f = None
          if market_data_arg:
              p = Path(market_data_arg)
              if p.is_file():
                  # 用户直接传了文件路径
                  f = p
              else:
                  # 按日期拼接
                  f = OUTPUT_DIR / f"盘面数据_{market_data_arg}.json"
          else:
              # 找最新的盘面数据
              files = sorted(OUTPUT_DIR.glob("盘面数据_*.json"), reverse=True)
              f = files[0] if files else None
      
          if not f or not f.exists():
              warn("未找到盘面数据文件")
              warn("请先运行: python scripts/fetch_market_data.py --days 1")
              return None
      
          try:
              data = json.loads(f.read_text(encoding="utf-8"))
              info(f"已加载盘面数据:{f.name}")
              return data
          except Exception as e:
              error(f"读取盘面数据失败: {e}")
              return None
      
      
      # ─── 生成分析任务 ────────────────────────────────────────────────────────────────────
      def generate_daily_task(author, profile, market_data):
          """生成单大V每日复盘任务"""
          today = datetime.now().strftime("%Y-%m-%d")
      
          lines = []
          lines.append(f"# {author} 风格化复盘生成任务")
          lines.append(f"> 日期:{today}")
          lines.append(f"> ⚠️ AI 风格模拟,不构成投资建议\n")
      
          lines.append("## 1. 风格画像摘要\n")
          lines.append("请基于以下风格画像生成复盘文章:\n")
          lines.append(f"```\n{profile[:3000]}\n```\n")
      
          lines.append("## 2. 盘面数据\n")
          if market_data:
              lines.append(f"```json\n{json.dumps(market_data, ensure_ascii=False, indent=2)[:3000]}\n```\n")
          else:
              lines.append("**未找到盘面数据文件。**\n")
              lines.append("请用户提供以下盘面数据(至少包含):")
              lines.append("- 上证指数收盘点位和涨跌幅")
              lines.append("- 成交额")
              lines.append("- 涨停/跌停家数")
              lines.append("- 热门板块")
              lines.append("- 主要资金流向\n")
      
          lines.append("## 3. 生成要求\n")
          lines.append("请严格按照风格画像生成一篇复盘文章,要求:\n")
      
          lines.append("### 标题")
          lines.append("- 使用大V的标题模式")
          lines.append(f"- 参考画像中的标题模式和示例\n")
      
          lines.append("### 开头")
          lines.append("- 使用大V的标志性开头模板")
          lines.append(f"- 包含至少1个标志性表达\n")
      
          lines.append("### 正文结构")
          lines.append("- 按大V的文章结构组织内容")
          lines.append("- 包含盘面概述 → 板块分析 → 操作策略")
          lines.append("- 使用大V的交易体系框架分析")
          lines.append(f"- 包含至少3个标志性表达\n")
      
          lines.append("### 结尾")
          lines.append("- 使用大V的结尾模式")
          lines.append("- 包含免责声明(如大V有此习惯)\n")
      
          lines.append("### 合规要求")
          lines.append("- 文章开头或结尾必须标注:**「AI 风格模拟,不构成投资建议」**")
          lines.append("- 不得编造未在盘面数据中出现的具体数据")
          lines.append("- 严格区分盘面事实与交易判断\n")
      
          lines.append(f"## 4. 输出")
          lines.append(f"保存到:`output/{author}_复盘_{today}.md`\n")
      
          return "\n".join(lines)
      
      
      def generate_team_task(authors, profiles_data, market_data):
          """生成多V对比任务"""
          today = datetime.now().strftime("%Y-%m-%d")
      
          lines = []
          lines.append(f"# 多V并行对比分析任务")
          lines.append(f"> 日期:{today}")
          lines.append(f"> 参与大V:{', '.join(authors)}")
          lines.append(f"> ⚠️ AI 风格模拟,不构成投资建议\n")
      
          lines.append("## 1. 盘面数据\n")
          if market_data:
              lines.append(f"```json\n{json.dumps(market_data, ensure_ascii=False, indent=2)[:2000]}\n```\n")
          else:
              lines.append("请用户提供当日盘面数据。\n")
      
          lines.append("## 2. 生成要求\n")
          lines.append("请为每位大V分别生成一段复盘观点,然后进行对比分析。\n")
      
          for author in authors:
              profile = profiles_data.get(author, "")
              lines.append(f"### {author} 的复盘观点\n")
              lines.append(f"**风格画像摘要:**\n```\n{profile[:1500]}\n```\n")
              lines.append(f"生成要求:")
              lines.append(f"- 严格按{author}的风格画像生成")
              lines.append(f"- 包含{author}的标志性表达")
              lines.append(f"- 字数控制在500-800字\n")
      
          lines.append("## 3. 对比分析\n")
          lines.append("在各位大V的复盘之后,生成对比分析:\n")
          lines.append("- **共识点**:各位大V都看好的方向")
          lines.append("- **分歧点**:各位大V观点不同的地方")
          lines.append("- **不同视角**:各位大V关注的维度差异\n")
      
          lines.append("## 4. 合规要求\n")
          lines.append("- 标注:「AI 风格模拟,不构成投资建议」")
          lines.append("- 不冒充大V本人")
          lines.append("- 不直接荐股\n")
      
          lines.append(f"## 5. 输出")
          lines.append(f"保存到:`output/多V对比_{today}.md`\n")
      
          return "\n".join(lines)
      
      
      def generate_sector_task(author, profile, sector, market_data):
          """生成板块漏斗分析任务"""
          today = datetime.now().strftime("%Y-%m-%d")
      
          lines = []
          lines.append(f"# {author} 风格化板块分析任务 — {sector}")
          lines.append(f"> 日期:{today}")
          lines.append(f"> ⚠️ AI 风格模拟,不构成投资建议\n")
      
          lines.append("## 1. 风格画像摘要\n")
          lines.append(f"```\n{profile[:2000]}\n```\n")
      
          lines.append(f"## 2. 分析框架\n")
          lines.append(f"请按{author}的分析框架,对「{sector}」板块进行漏斗筛选:\n")
      
          lines.append("### 第一层:赛道识别")
          lines.append(f"- {sector}板块当前的热度")
          lines.append(f"- 催化事件和政策面")
          lines.append(f"- 市场情绪指标\n")
      
          lines.append("### 第二层:龙头筛选")
          lines.append(f"- {sector}板块的核心标的")
          lines.append("- 主营业务占比")
          lines.append("- 业绩确定性")
          lines.append("- 估值水平\n")
      
          lines.append("### 第三层:确定性评估")
          lines.append("- 哪些是确定性机会(绕不开的)")
          lines.append("- 哪些是炒作预期(资产注入等)")
          lines.append("- 哪些是真实业绩驱动\n")
      
          lines.append("### 第四层:操作策略")
          lines.append(f"- 按{author}的仓位管理策略")
          lines.append("- 买入/卖出信号")
          lines.append("- 风险提示\n")
      
          lines.append("## 3. 合规要求\n")
          lines.append("- 标注:「AI 风格模拟,不构成投资建议」")
          lines.append("- 不直接荐股,仅分析板块逻辑\n")
      
          lines.append(f"## 4. 输出")
          lines.append(f"保存到:`output/{author}_{sector}分析_{today}.md`\n")
      
          return "\n".join(lines)
      
      
      def generate_track_task(author, profile, stock):
          """生成观点跟踪任务"""
          today = datetime.now().strftime("%Y-%m-%d")
      
          lines = []
          lines.append(f"# {author} 观点跟踪 — {stock}")
          lines.append(f"> 日期:{today}")
          lines.append(f"> ⚠️ AI 风格模拟,不构成投资建议\n")
      
          lines.append("## 1. 风格画像摘要\n")
          lines.append(f"```\n{profile[:1500]}\n```\n")
      
          lines.append(f"## 2. 跟踪目标:{stock}\n")
          lines.append(f"请从风格画像中检索{author}对「{stock}」的相关观点:\n")
      
          lines.append("### 历史观点")
          lines.append(f"- {author}是否提及过{stock}?")
          lines.append(f"- 如果提及,是什么时候、什么背景下?")
          lines.append(f"- 具体观点是什么?(看好/看空/中性)\n")
      
          lines.append("### 判断逻辑")
          lines.append(f"- {author}对{stock}的判断逻辑是什么?")
          lines.append(f"- 基于什么维度分析的?(基本面/技术面/资金面)\n")
      
          lines.append("### 兑现情况")
          lines.append(f"- {author}的判断是否已兑现?")
          lines.append(f"- 如果未兑现,可能的原因是什么?\n")
      
          lines.append("## 3. 合规要求\n")
          lines.append("- 仅引用画像中的公开观点")
          lines.append("- 不构成投资建议\n")
      
          lines.append(f"## 4. 输出")
          lines.append(f"保存到:`output/{author}_{stock}观点跟踪_{today}.md`\n")
      
          return "\n".join(lines)
      
      
      # ─── 新模式入口 ────────────────────────────────────────────────────────────────────
      def run_screen(author, sector=None, days=7):
          """短线选股"""
          step(f"模式:短线选股 — {author}" + (f" / {sector}" if sector else ""))
          script = SKILL_DIR / "scripts" / "stock_screen.py"
          cmd = [sys.executable, str(script), "--author", author, "--days", str(days)]
          if sector:
              cmd.extend(["--sector", sector])
          print(f"{BOLD}执行:{RESET}{' '.join(cmd)}")
          return subprocess.call(cmd)
      
      
      def run_portfolio(author, stocks, period=30):
          """组合复盘"""
          step(f"模式:组合复盘 — {author} / {', '.join(stocks)}")
          script = SKILL_DIR / "scripts" / "portfolio.py"
          cmd = [sys.executable, str(script), "--author", author, "--stocks", ",".join(stocks), "--period", str(period)]
          print(f"{BOLD}执行:{RESET}{' '.join(cmd)}")
          return subprocess.call(cmd)
      
      
      def run_earnings(author, stock, data_file=None, manual=False):
          """财报点评"""
          step(f"模式:财报点评 — {author} / {stock}")
          script = SKILL_DIR / "scripts" / "earnings.py"
          cmd = [sys.executable, str(script), "--author", author, "--stock", stock]
          if data_file:
              cmd.extend(["--data", data_file])
          elif manual:
              cmd.append("--manual")
          print(f"{BOLD}执行:{RESET}{' '.join(cmd)}")
          return subprocess.call(cmd)
      
      
      def run_sync(author, days=7, api_key=None):
          """增量文章同步 — 拉取近N天新文章用于蒸馏补充"""
          step(f"模式:增量同步 — {author} / 近{days}天")
          script = SKILL_DIR / "scripts" / "sync_articles.py"
          cmd = [sys.executable, str(script), "--author", author, "--days", str(days)]
          if api_key:
              cmd.extend(["--api-key", api_key])
          print(f"{BOLD}执行:{RESET}{' '.join(cmd)}")
          return subprocess.call(cmd)
      
      
      # ─── 主流程 ──────────────────────────────────────────────────────────────────────────
      def run_daily(author, market_data_file=None):
          """单大V每日复盘"""
          step(f"模式:单大V每日复盘 — {author}")
      
          profile = load_profile(author)
          if not profile:
              return False
      
          market_data = load_market_data(market_data_file)
      
          task = generate_daily_task(author, profile, market_data)
      
          # 保存任务
          today = datetime.now().strftime("%Y-%m-%d")
          task_file = OUTPUT_DIR / f"{author}_复盘任务_{today}.md"
          task_file.write_text(task, encoding="utf-8")
          info(f"生成任务已保存:{task_file}")
      
          # 提示AI生成
          print()
          print(f"{BOLD}{'='*60}{RESET}")
          print(f"{BOLD}  复盘生成任务已就绪{RESET}")
          print(f"  画像:{author}")
          print(f"  盘面数据:{'已加载' if market_data else '未找到,需用户输入'}")
          print(f"  任务文件:{task_file}")
          print(f"  模板参考:{SKILL_DIR}/assets/article_template.md")
          print(f"  输出到:{OUTPUT_DIR}/{author}_复盘_{today}.md")
          print(f"{BOLD}{'='*60}{RESET}")
      
          return True
      
      
      def run_team(authors_list, market_data_file=None):
          """多V并行对比"""
          step(f"模式:多V并行对比 — {', '.join(authors_list)}")
      
          profiles_data = {}
          for author in authors_list:
              profile = load_profile(author)
              if not profile:
                  warn(f"跳过:{author}(未找到画像)")
                  continue
              profiles_data[author] = profile
      
          if len(profiles_data) < 2:
              error("至少需要2位大V的画像才能进行对比")
              return False
      
          market_data = load_market_data(market_data_file)
      
          task = generate_team_task(authors_list, profiles_data, market_data)
      
          today = datetime.now().strftime("%Y-%m-%d")
          task_file = OUTPUT_DIR / f"多V对比任务_{today}.md"
          task_file.write_text(task, encoding="utf-8")
          info(f"生成任务已保存:{task_file}")
      
          print()
          print(f"{BOLD}{'='*60}{RESET}")
          print(f"{BOLD}  多V对比任务已就绪{RESET}")
          print(f"  参与大V:{', '.join(profiles_data.keys())}")
          print(f"  盘面数据:{'已加载' if market_data else '未找到,需用户输入'}")
          print(f"  任务文件:{task_file}")
          print(f"  输出到:{OUTPUT_DIR}/多V对比_{today}.md")
          print(f"{BOLD}{'='*60}{RESET}")
      
          return True
      
      
      def run_sector(author, sector, market_data_file=None):
          """板块漏斗分析"""
          step(f"模式:板块漏斗 — {author} / {sector}")
      
          profile = load_profile(author)
          if not profile:
              return False
      
          market_data = load_market_data(market_data_file)
      
          task = generate_sector_task(author, profile, sector, market_data)
      
          today = datetime.now().strftime("%Y-%m-%d")
          task_file = OUTPUT_DIR / f"{author}_{sector}分析任务_{today}.md"
          task_file.write_text(task, encoding="utf-8")
          info(f"生成任务已保存:{task_file}")
      
          print()
          print(f"{BOLD}{'='*60}{RESET}")
          print(f"{BOLD}  板块分析任务已就绪{RESET}")
          print(f"  大V:{author}")
          print(f"  板块:{sector}")
          print(f"  任务文件:{task_file}")
          print(f"  输出到:{OUTPUT_DIR}/{author}_{sector}分析_{today}.md")
          print(f"{BOLD}{'='*60}{RESET}")
      
          return True
      
      
      def run_track(author, stock):
          """观点跟踪"""
          step(f"模式:观点跟踪 — {author} / {stock}")
      
          profile = load_profile(author)
          if not profile:
              return False
      
          task = generate_track_task(author, profile, stock)
      
          today = datetime.now().strftime("%Y-%m-%d")
          task_file = OUTPUT_DIR / f"{author}_{stock}跟踪任务_{today}.md"
          task_file.write_text(task, encoding="utf-8")
          info(f"生成任务已保存:{task_file}")
      
          print()
          print(f"{BOLD}{'='*60}{RESET}")
          print(f"{BOLD}  观点跟踪任务已就绪{RESET}")
          print(f"  大V:{author}")
          print(f"  标的:{stock}")
          print(f"  任务文件:{task_file}")
          print(f"  输出到:{OUTPUT_DIR}/{author}_{stock}观点跟踪_{today}.md")
          print(f"{BOLD}{'='*60}{RESET}")
      
          return True
      
      
      def list_profiles():
          """列出所有可用画像"""
          profiles = find_profiles()
          if not profiles:
              warn("未找到任何风格画像")
              warn(f"搜索路径: {PROFILES_DIR}")
              warn(f"请确认 profiles/ 目录下有已蒸馏的风格画像文件")
              return
      
          print(f"\n{BOLD}可用风格画像:{RESET}")
          for p in profiles:
              print(f"  {GREEN}✓{RESET} {p['author']}")
              print(f"    路径:{p['path']}")
      
      
      def main():
          parser = argparse.ArgumentParser(description="公众号股票大V分析专家")
          parser.add_argument("--author", help="指定大V名称")
          parser.add_argument("--mode", choices=["daily", "team", "sector", "track", "screen", "portfolio", "earnings", "sync"], help="分析模式")
          parser.add_argument("--authors", help="多V对比,逗号分隔(team模式)")
          parser.add_argument("--sector", help="板块名称(sector模式)")
          parser.add_argument("--stock", help="跟踪标的(track模式)/ 财报标的(earnings模式)")
          parser.add_argument("--stocks", help="持仓列表,逗号分隔(portfolio模式)")
          parser.add_argument("--period", type=int, default=30, help="回顾周期天数(portfolio模式,默认30)")
          parser.add_argument("--data", help="财报数据JSON文件(earnings模式)")
          parser.add_argument("--manual-input", action="store_true", help="手动输入数据(earnings模式)")
          parser.add_argument("--days", type=int, default=7, help="近N天数据(screen模式,默认7)")
          parser.add_argument("--market-data", help="盘面数据文件路径")
          parser.add_argument("--api-key", help="指定API Key(sync/validate等需要API的模式)")
          parser.add_argument("--list-profiles", action="store_true", help="列出可用画像")
          parser.add_argument("--check-env", action="store_true", help="检查环境")
      
          args = parser.parse_args()
      
          if args.check_env:
              sys.exit(0 if check_env() else 1)
      
          if args.list_profiles:
              list_profiles()
              sys.exit(0)
      
          if not args.mode:
              parser.print_help()
              print(f"\n{BOLD}已知大V:{RESET} {', '.join(STOCK_ANALYSTS)}")
              print(f"{BOLD}示例:{RESET}")
              print(f"  python analyze.py --author 财躺平 --mode daily")
              print(f"  python analyze.py --mode team --authors 财躺平,格兰投研")
              print(f"  python analyze.py --author 财躺平 --mode sector --sector 航天")
              print(f"  python analyze.py --author 财躺平 --mode screen --sector 半导体")
              print(f"  python analyze.py --author 财躺平 --mode portfolio --stocks 中国卫星,航发动力")
              print(f"  python analyze.py --author 格兰投研 --mode earnings --stock 贵州茅台")
              print(f"  python analyze.py --author 财躺平 --mode sync")
              sys.exit(0)
      
          OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
      
          # 调用记录上报(fire-and-forget,失败不影响主流程)──
          api_key = get_api_key(args.api_key)
          if api_key:
              authors_for_record = []
              if args.mode == "team" and args.authors:
                  authors_for_record = [a.strip() for a in args.authors.split(",")]
              elif args.author:
                  authors_for_record = [args.author]
              record_call(api_key, mode=args.mode, authors=authors_for_record if authors_for_record else None)
      
          if args.mode == "daily":
              if not args.author:
                  error("--mode daily 需要 --author 参数")
                  sys.exit(1)
              run_daily(args.author, args.market_data)
      
          elif args.mode == "team":
              if not args.authors:
                  error("--mode team 需要 --authors 参数(逗号分隔)")
                  sys.exit(1)
              authors_list = [a.strip() for a in args.authors.split(",")]
              run_team(authors_list, args.market_data)
      
          elif args.mode == "sector":
              if not args.author or not args.sector:
                  error("--mode sector 需要 --author 和 --sector 参数")
                  sys.exit(1)
              run_sector(args.author, args.sector, args.market_data)
      
          elif args.mode == "track":
              if not args.author or not args.stock:
                  error("--mode track 需要 --author 和 --stock 参数")
                  sys.exit(1)
              run_track(args.author, args.stock)
      
          elif args.mode == "screen":
              if not args.author:
                  error("--mode screen 需要 --author 参数")
                  sys.exit(1)
              ret = run_screen(args.author, args.sector, args.days)
              if ret != 0:
                  sys.exit(ret)
      
          elif args.mode == "portfolio":
              if not args.author or not args.stocks:
                  error("--mode portfolio 需要 --author 和 --stocks 参数")
                  sys.exit(1)
              stocks_list = [s.strip() for s in args.stocks.split(",") if s.strip()]
              ret = run_portfolio(args.author, stocks_list, args.period)
              if ret != 0:
                  sys.exit(ret)
      
          elif args.mode == "earnings":
              if not args.author or not args.stock:
                  error("--mode earnings 需要 --author 和 --stock 参数")
                  sys.exit(1)
              ret = run_earnings(args.author, args.stock, args.data, args.manual_input)
              if ret != 0:
                  sys.exit(ret)
      
          elif args.mode == "sync":
              if not args.author:
                  error("--mode sync 需要 --author 参数")
                  sys.exit(1)
              ret = run_sync(args.author, args.days, args.api_key)
              if ret != 0:
                  sys.exit(ret)
      
      
      if __name__ == "__main__":
          main()
      
    • common.py 14.4 KB
      #!/usr/bin/env python3
      """
      stock-analysis 公共模块
      ========================
      提取所有脚本共用的配置、工具函数,消除代码重复。
      
      提供:
      - 终端颜色 & 打印工具
      - API 配置(广域库 queryWorkList 接口、Key 管理)
      - 画像加载(JSON 格式)
      - HTTP 请求封装(含状态码检查)
      - STOCK_ANALYSTS 名单 + 账号ID映射
      """
      
      import json
      import os
      import sys
      import time
      from datetime import datetime, timedelta
      from pathlib import Path
      
      # ─── Windows 控制台 UTF-8 ──────────────────────────────────────────────────────────
      if sys.platform == "win32":
          sys.stdout.reconfigure(encoding="utf-8", errors="replace")
          sys.stderr.reconfigure(encoding="utf-8", errors="replace")
      
      try:
          import requests
          HAS_REQUESTS = True
      except ImportError:
          HAS_REQUESTS = False
      
      # ─── 路径常量 ──────────────────────────────────────────────────────────────────────
      SCRIPTS_DIR = Path(__file__).parent           # scripts/
      SKILL_DIR = SCRIPTS_DIR.parent                # stock-analysis/
      PROFILES_DIR = SKILL_DIR / "profiles"
      OUTPUT_DIR = SKILL_DIR / "output"
      
      # ─── API 配置 ──────────────────────────────────────────────────────────────────────
      API_URL = "https://redfox.hk/story/api/gzh/data/queryWorkList"
      RECORD_API_URL = "https://redfox.hk/story/api/skill/record/save"
      RECORD_SOURCE = "公众号股票大V蒸馏-GitHub"
      CONFIG_DIR = Path.home() / ".qoder" / "apis"
      CONFIG_FILE = CONFIG_DIR / "redfox.json"
      ENV_KEY = "REDFOX_API_KEY"
      
      STOCK_ANALYSTS = ["财躺平", "猫笔刀", "格兰投研", "投资明见", "终身黑白"]
      
      # 分析师 → 公众号账号ID映射(广域库接口自动路由:gh_开头→wxId,其余→account)
      ACCOUNT_IDS = {
          "财躺平": "gh_ad228eaec48a",
          "投资明见": "sinaxxm",
          "猫笔刀": "maobidao",
          "格兰投研": "gelantouyan",
          "终身黑白": "zshbtz",
      }
      
      # ─── 终端颜色 ──────────────────────────────────────────────────────────────────────
      GREEN = "\033[92m"
      YELLOW = "\033[93m"
      RED = "\033[91m"
      CYAN = "\033[96m"
      BOLD = "\033[1m"
      RESET = "\033[0m"
      
      
      def info(msg):
          print(f"{GREEN}[✓]{RESET} {msg}")
      
      
      def warn(msg):
          print(f"{YELLOW}[!]{RESET} {msg}")
      
      
      def error(msg):
          print(f"{RED}[✗]{RESET} {msg}")
      
      
      def step(msg):
          print(f"{CYAN}[→]{RESET} {msg}")
      
      
      # ─── API Key 管理 ──────────────────────────────────────────────────────────────────
      def get_api_key(cli_key=None):
          """获取 API Key:命令行 > 环境变量 > 配置文件"""
          if cli_key:
              return cli_key
      
          env_key = os.environ.get(ENV_KEY)
          if env_key:
              return env_key
      
          if CONFIG_FILE.exists():
              try:
                  data = json.loads(CONFIG_FILE.read_text(encoding="utf-8-sig"))
                  key = data.get("api_key")
                  if key:
                      return key
              except Exception:
                  pass
      
          error("未配置 API Key,请通过以下方式之一配置:")
          print(f"  1. 环境变量:export REDFOX_API_KEY=\"ak_你的密钥\"")
          print(f"  2. 配置文件:~/.qoder/apis/redfox.json")
          print(f"  3. 命令行参数:--api-key ak_你的密钥")
          print(f"\n  注册地址:https://www.redfox.hk/login")
          return None
      
      
      # ─── 调用记录 ──────────────────────────────────────────────────────────────────────
      def record_call(api_key, mode=None, authors=None):
          """
          向 redfox.hk 上报 skill 调用记录(fire-and-forget,失败不影响主流程)。
      
          Args:
              api_key: REDFOX_API_KEY
              mode: 分析模式(daily/team/sector/track/screen/portfolio/earnings/sync)
              authors: 参与分析的大V名称列表或字符串
          """
          if not api_key:
              return
          if not HAS_REQUESTS:
              return
      
          payload = {"source": RECORD_SOURCE}
          if mode:
              payload["mode"] = mode
          if authors:
              if isinstance(authors, list):
                  payload["authors"] = ",".join(authors)
              else:
                  payload["authors"] = str(authors)
      
          try:
              resp = requests.post(
                  RECORD_API_URL,
                  json=payload,
                  headers={"Content-Type": "application/json", "REDFOX_API_KEY": api_key},
                  timeout=5,
              )
              # 静默处理,不阻塞主流程
          except Exception:
              pass  # fire-and-forget
      
      
      # ─── 画像加载 ──────────────────────────────────────────────────────────────────────
      def find_profile(author):
          """查找指定分析师的风格画像(JSON 文本),优先精确匹配"""
          # 优先精确匹配
          for d in [PROFILES_DIR]:
              if not d.exists():
                  continue
              for f in d.glob("*_profile.json"):
                  profile_author = f.stem.replace("_profile", "")
                  if author == profile_author:
                      return f.read_text(encoding="utf-8")
          # 模糊匹配
          for d in [PROFILES_DIR]:
              if not d.exists():
                  continue
              for f in d.glob("*_profile.json"):
                  profile_author = f.stem.replace("_profile", "")
                  if author in profile_author:
                      warn(f"未精确匹配「{author}」,使用模糊匹配:{profile_author}")
                      return f.read_text(encoding="utf-8")
          return None
      
      
      def load_profile_json(author):
          """加载画像并解析为 dict,返回 (data, file_path) 或 (None, None)"""
          # 优先精确匹配
          for f in PROFILES_DIR.glob("*_profile.json"):
              profile_author = f.stem.replace("_profile", "")
              if author == profile_author:
                  try:
                      data = json.loads(f.read_text(encoding="utf-8"))
                      return data, f
                  except Exception as e:
                      error(f"读取画像失败: {e}")
                      return None, None
          # 模糊匹配
          for f in PROFILES_DIR.glob("*_profile.json"):
              profile_author = f.stem.replace("_profile", "")
              if author in profile_author:
                  warn(f"未精确匹配「{author}」,使用模糊匹配:{profile_author}")
                  try:
                      data = json.loads(f.read_text(encoding="utf-8"))
                      return data, f
                  except Exception as e:
                      error(f"读取画像失败: {e}")
                      return None, None
          warn(f"未找到大V「{author}」的风格画像,将仅输出文章数据")
          return None, None
      
      
      def detect_trading_type(profile):
          """从画像文本判断长线/短线"""
          if not profile:
              return "unknown"
          long_kw = ["价值投资", "护城河", "ROE", "长期持有", "估值", "自由现金流", "分红"]
          short_kw = ["涨停", "连板", "龙头", "打板", "情绪周期", "游资", "短线", "题材"]
          long_score = sum(profile.count(k) for k in long_kw)
          short_score = sum(profile.count(k) for k in short_kw)
          if short_score > long_score * 1.5:
              return "short"
          elif long_score > short_score * 1.5:
              return "long"
          return "mixed"
      
      
      # ─── HTTP 请求封装 ──────────────────────────────────────────────────────────────────
      def api_post(session, url, payload, timeout=20, context=""):
          """
          带 HTTP 状态码检查的 POST 请求。
          返回 (result_dict, should_continue_bool)
          - result_dict: API 返回的 JSON
          - should_continue_bool: False 表示应终止分页循环
          """
          try:
              if context:
                  step(context)
              resp = session.post(url, json=payload, timeout=timeout)
      
              # HTTP 状态码检查
              if resp.status_code == 429:
                  warn("HTTP 429 限频,等待5秒...")
                  time.sleep(5)
                  return None, True  # 继续重试
              elif resp.status_code == 401:
                  error("HTTP 401 认证失败,请检查 API Key")
                  return None, False
              elif resp.status_code == 403:
                  error("HTTP 403 权限不足")
                  return None, False
              elif resp.status_code >= 500:
                  warn(f"HTTP {resp.status_code} 服务端错误,等待3秒重试...")
                  time.sleep(3)
                  return None, True
              elif resp.status_code != 200:
                  warn(f"HTTP {resp.status_code} 非预期状态码")
                  return None, False
      
              result = resp.json()
              return result, True
      
          except requests.exceptions.Timeout:
              warn(f"请求超时({timeout}s)")
              return None, False
          except requests.exceptions.ConnectionError as e:
              warn(f"连接失败: {e}")
              return None, False
          except Exception as e:
              warn(f"请求异常: {e}")
              return None, False
      
      
      def check_api_code(result):
          """
          检查 API 业务状态码,返回 (should_retry, should_break)
          - (True, False): 限频,应等待后重试
          - (False, True): 致命错误,应终止
          - (False, False): 正常
          """
          code = result.get("code")
          if code in (200, 2000):
              return False, False
      
          msg = result.get("msg", "")
          if code in (4004, 3108):
              warn("限频,等待5秒...")
              time.sleep(5)
              return True, False
          elif code in (3106, 3107):
              error(f"API错误: code={code}, msg={msg}")
              return False, True
          elif code == 3203:
              error(f"账号不在广域库中,暂未收录: {msg}")
              return False, True
          else:
              warn(f"API返回: code={code}, msg={msg}")
              return False, True
      
      
      def extract_articles_from_data(result):
          """从 queryWorkList API 返回中提取文章列表(data.list)"""
          data_raw = result.get("data", {})
          if isinstance(data_raw, dict):
              return data_raw.get("list") or []
          elif isinstance(data_raw, list):
              return data_raw
          return []
      
      
      def build_account_params(account):
          """
          广域库账号ID路由:account/wxId/bizInfo 三者选其一。
          - gh_ 开头 → wxId(公众号原始ID)
          - == 结尾 → bizInfo(账号采集ID)
          - 其余 → account(公众号微信号)
          """
          if account.startswith("gh_"):
              return {"wxId": account}
          if account.endswith("=="):
              return {"bizInfo": account}
          return {"account": account}
      
      
      def fetch_articles_paginated(api_key, account, account_name=None, days=7, count=50,
                                   label=""):
          """
          通用分页拉取文章函数(广域库 queryWorkList 接口,每页固定20条)。
          account: 公众号微信号 / 原始ID(gh_开头) / 采集ID,自动路由到对应参数
          account_name: 分析师名称(仅用于日志显示)
          """
          if not HAS_REQUESTS:
              error("缺少 requests 库,请执行: pip install requests")
              return []
      
          session = requests.Session()
          session.headers.update({"Content-Type": "application/json", "X-API-Key": api_key})
      
          PAGE_SIZE = 20  # 广域库每页固定20条
          end_date = datetime.now()
          start_date = end_date - timedelta(days=days)
          start_str = start_date.strftime("%Y-%m-%d")
          payload = {
              **build_account_params(account),
              "offset": 0,
              "sortType": "2",  # 按最新发布时间排序
              "publishTimeStart": start_date.strftime("%Y-%m-%d 00:00:00"),
              "publishTimeEnd": end_date.strftime("%Y-%m-%d 23:59:59"),
              "source": RECORD_SOURCE,
          }
      
          display_name = account_name or account
          all_articles = []
          fetched = 0
          consecutive_retries = 0
          MAX_CONSECUTIVE_RETRIES = 5
      
          while fetched < count:
              payload["offset"] = fetched
      
              ctx = f"{label}(offset={fetched})..." if label else f"获取{display_name}文章(offset={fetched})..."
              result, should_continue = api_post(session, API_URL, payload, context=ctx)
      
              if result is None:
                  if not should_continue:
                      break
                  consecutive_retries += 1
                  if consecutive_retries >= MAX_CONSECUTIVE_RETRIES:
                      warn(f"连续重试 {MAX_CONSECUTIVE_RETRIES} 次仍失败,终止请求")
                      break
                  continue
      
              consecutive_retries = 0
      
              retry, break_now = check_api_code(result)
              if retry:
                  consecutive_retries += 1
                  if consecutive_retries >= MAX_CONSECUTIVE_RETRIES:
                      warn(f"连续限频重试 {MAX_CONSECUTIVE_RETRIES} 次仍失败,终止请求")
                      break
                  continue
              if break_now:
                  break
      
              consecutive_retries = 0
      
              articles = extract_articles_from_data(result)
              if not articles:
                  break
      
              all_articles.extend(articles)
              fetched += len(articles)
              info(f"已获取 {fetched} 篇")
      
              # 本页不足20条,已是最后一页
              if len(articles) < PAGE_SIZE:
                  break
      
              # 按最新排序,本页末篇早于起始日期,后续页只会更早,停止翻页
              last_pub = str(articles[-1].get("publishTime") or "")[:10]
              if last_pub and last_pub < start_str:
                  break
              time.sleep(1)
      
          # 客户端按发布时间兜底过滤,确保只保留近N天文章
          filtered = [a for a in all_articles if str(a.get("publishTime") or "")[:10] >= start_str]
          return filtered[:count]
      
      
      def ensure_output_dir():
          """确保输出目录存在"""
          OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
          return OUTPUT_DIR
      
    • earnings.py 11.4 KB
      #!/usr/bin/env python3
      """
      财报点评 — 按大V风格解读财报
      ================================
      输入标的名称和财报数据,按大V的分析框架和表达风格生成财报点评任务。
      长线大V侧重:护城河、ROE趋势、自由现金流;
      短线大V侧重:业绩超预期幅度、资金反应、短期催化。
      
      Usage:
          python earnings.py --author "格兰投研" --stock "贵州茅台" --data earnings.json
          python earnings.py --author "财躺平" --stock "宁德时代"
          python earnings.py --author "终身黑白" --stock "招商银行" --manual
      """
      
      import argparse
      import json
      import sys
      from datetime import datetime
      from pathlib import Path
      
      from common import (
          STOCK_ANALYSTS, PROFILES_DIR, OUTPUT_DIR,
          GREEN, YELLOW, RED, CYAN, BOLD, RESET,
          info, warn, error, step,
          find_profile, detect_trading_type, ensure_output_dir,
      )
      
      
      # ─── 财报数据输入 ──────────────────────────────────────────────────────────────────
      def manual_input(stock):
          """手动输入财报数据"""
          print(f"\n{BOLD}请输入{stock}财报核心数据:{RESET}\n")
      
          data = {"stock": stock, "source": "manual"}
      
          data["report_period"] = input("报告期(如:2025年年报 / 2026Q1): ").strip()
          data["revenue"] = input("营业收入(亿元,如:1505.6): ").strip()
          data["revenue_yoy"] = input("营收同比增速%(如:+12.5): ").strip()
          data["net_profit"] = input("归母净利润(亿元,如:747.3): ").strip()
          data["net_profit_yoy"] = input("净利润同比增速%(如:+15.2): ").strip()
          data["roe"] = input("ROE%(如:30.2): ").strip()
          data["gross_margin"] = input("毛利率%(如:91.5): ").strip()
          data["pe"] = input("当前PE(如:28.5): ").strip()
          data["pb"] = input("当前PB(如:8.2): ").strip()
          data["dividend_yield"] = input("股息率%(如:2.1): ").strip()
          data["fcf"] = input("自由现金流(亿元,如:680): ").strip()
          data["highlights"] = input("亮点/超预期点(简述): ").strip()
          data["risks"] = input("风险/低于预期点(简述): ").strip()
          data["guidance"] = input("公司指引/展望(简述): ").strip()
      
          return data
      
      
      def load_earnings_data(data_path, stock):
          """加载财报数据JSON"""
          f = Path(data_path)
          if not f.exists():
              error(f"财报数据文件不存在:{data_path}")
              return None
          try:
              data = json.loads(f.read_text(encoding="utf-8"))
              if "stock" not in data:
                  data["stock"] = stock
              info(f"已加载财报数据:{f.name}")
              return data
          except Exception as e:
              error(f"读取财报数据失败:{e}")
              return None
      
      
      # ─── 长线分析框架 ──────────────────────────────────────────────────────────────────
      LONG_ANALYSIS_FRAMEWORK = """
      ### 长线投资者分析框架
      
      #### 1. 生意本质
      - 商业模式是否简单可理解?
      - 产品/服务的护城河类型(品牌/网络效应/成本优势/转换成本)
      - 竞争格局是否稳定?
      
      #### 2. 财务质量
      - ROE 趋势(连续3年 > 15%?)
      - 毛利率稳定性
      - 自由现金流是否充裕?
      - 资产负债率是否健康?
      
      #### 3. 成长性
      - 营收增速趋势
      - 净利润增速与营收增速是否匹配
      - 增长驱动因素(量增/价增/新业务)
      
      #### 4. 估值判断
      - PE/PB 历史分位
      - 相对增速的PEG
      - 股息率吸引力
      
      #### 5. 风险识别
      - 管理层变动
      - 行业政策变化
      - 竞争加剧信号
      """
      
      # ─── 短线分析框架 ──────────────────────────────────────────────────────────────────
      SHORT_ANALYSIS_FRAMEWORK = """
      ### 短线交易者分析框架
      
      #### 1. 业绩预期差
      - 营收/净利润是否超市场预期?
      - 超预期幅度(> 10% 为显著超预期)
      - 市场对业绩的预期是否已充分定价?
      
      #### 2. 资金反应预判
      - 业绩公布后可能的股价反应
      - 机构资金动向(是否提前布局)
      - 游资可能的炒作方向
      
      #### 3. 短期催化
      - 财报中的催化事件(分红/回购/新订单/产能释放)
      - 行业政策催化
      - 技术面配合度
      
      #### 4. 交易信号
      - 业绩公布前后是否适合介入
      - 支撑位和压力位
      - 止损位设定
      
      #### 5. 情绪判断
      - 当前市场对标的的情绪(过热/冷淡/分歧)
      - 业绩对情绪的边际影响
      """
      
      
      # ─── 生成任务文件 ──────────────────────────────────────────────────────────────────
      def generate_earnings_task(author, profile, trading_type, stock, earnings_data):
          today = datetime.now().strftime("%Y-%m-%d")
      
          lines = []
          lines.append(f"# {author} 风格化财报点评任务 — {stock}")
          lines.append(f"> 日期:{today}")
          lines.append(f"> 标的:{stock}")
          lines.append(f"> 交易类型:{trading_type}")
          lines.append(f"> ⚠️ AI 风格模拟,不构成投资建议\n")
      
          lines.append("## 1. 风格画像摘要\n")
          if profile:
              lines.append(f"```\n{profile[:2000]}\n```\n")
          else:
              lines.append("未找到风格画像,使用默认分析框架。\n")
      
          lines.append("## 2. 财报数据\n")
          if earnings_data:
              lines.append("| 指标 | 数值 |")
              lines.append("|------|------|")
      
              field_labels = {
                  "report_period": "报告期",
                  "revenue": "营业收入(亿元)",
                  "revenue_yoy": "营收同比增速%",
                  "net_profit": "归母净利润(亿元)",
                  "net_profit_yoy": "净利润同比增速%",
                  "roe": "ROE%",
                  "gross_margin": "毛利率%",
                  "pe": "PE",
                  "pb": "PB",
                  "dividend_yield": "股息率%",
                  "fcf": "自由现金流(亿元)",
                  "highlights": "亮点/超预期",
                  "risks": "风险/低于预期",
                  "guidance": "公司指引/展望",
              }
      
              for key, label in field_labels.items():
                  val = earnings_data.get(key)
                  if val:
                      lines.append(f"| {label} | {val} |")
              lines.append("")
      
              # 额外字段
              for key, val in earnings_data.items():
                  if key not in field_labels and key not in ("stock", "source"):
                      lines.append(f"- **{key}**:{val}")
              lines.append("")
          else:
              lines.append("**未提供财报数据。**\n")
              lines.append("请用户提供以下核心数据(至少):")
              lines.append("- 营收及同比增速")
              lines.append("- 净利润及同比增速")
              lines.append("- ROE")
              lines.append("- PE/PB\n")
      
          lines.append("## 3. 分析框架\n")
          if trading_type in ("long", "mixed"):
              lines.append(LONG_ANALYSIS_FRAMEWORK)
          if trading_type in ("short", "mixed"):
              lines.append(SHORT_ANALYSIS_FRAMEWORK)
      
          lines.append("## 4. 生成要求\n")
          lines.append(f"请按{author}的表达风格和分析框架,生成一篇财报点评文章:\n")
      
          if trading_type == "short":
              lines.append("- 标题突出业绩超预期/低于预期的核心信号")
              lines.append("- 开头直接给出结论(超预期还是低于预期)")
              lines.append("- 重点分析资金面可能的反应")
              lines.append("- 给出短期交易信号和操作建议")
              lines.append("- 包含风险提示\n")
          else:
              lines.append("- 标题体现长线视角(如:护城河是否加深)")
              lines.append("- 开头阐述生意本质和核心逻辑")
              lines.append("- 深入分析财务质量和成长持续性")
              lines.append("- 给出估值判断和安全边际")
              lines.append("- 包含长期风险识别\n")
      
          lines.append("### 风格要求")
          lines.append("- 使用大V的标志性表达")
          lines.append("- 按大V的文章结构组织")
          lines.append("- 字数参考大V平均字数\n")
      
          lines.append("## 5. 合规要求\n")
          lines.append("- 标注:「AI 风格模拟,不构成投资建议」")
          lines.append("- 财报数据引用需标注来源")
          lines.append("- 不直接荐股,仅分析逻辑\n")
      
          lines.append(f"## 6. 输出")
          lines.append(f"保存到:`output/{author}_{stock}财报_{today}.md`\n")
      
          return "\n".join(lines)
      
      
      # ─── 主流程 ────────────────────────────────────────────────────────────────────────
      def main():
          parser = argparse.ArgumentParser(description="财报点评 — 按大V风格解读财报")
          parser.add_argument("--author", required=True, help="指定大V名称")
          parser.add_argument("--stock", required=True, help="标的名称(如:贵州茅台)")
          parser.add_argument("--data", help="财报数据JSON文件路径")
          parser.add_argument("--manual", action="store_true", help="手动输入财报数据")
          parser.add_argument("--output-dir", default=str(OUTPUT_DIR), help="输出目录")
      
          args = parser.parse_args()
      
          output_dir = Path(args.output_dir)
          output_dir.mkdir(parents=True, exist_ok=True)
      
          # 加载画像
          step(f"加载画像:{args.author}")
          profile = find_profile(args.author)
          trading_type = detect_trading_type(profile)
          if profile:
              info(f"画像已加载,交易类型:{trading_type}")
          else:
              warn(f"未找到{args.author}的风格画像,使用默认分析")
      
          # 加载财报数据
          earnings_data = None
          if args.manual:
              earnings_data = manual_input(args.stock)
          elif args.data:
              earnings_data = load_earnings_data(args.data, args.stock)
      
          if not earnings_data and not args.manual:
              warn("未提供财报数据,可指定 --data 或使用 --manual 手动输入")
              warn("将生成空模板任务文件")
      
          # 生成任务
          task = generate_earnings_task(args.author, profile, trading_type, args.stock, earnings_data)
          today = datetime.now().strftime("%Y-%m-%d")
          task_file = output_dir / f"{args.author}_{args.stock}财报任务_{today}.md"
          task_file.write_text(task, encoding="utf-8")
          info(f"财报点评任务已保存:{task_file}")
      
          # 显示摘要
          print(f"\n{BOLD}{'='*60}{RESET}")
          print(f"{BOLD}  财报点评摘要{RESET}")
          print(f"{BOLD}{'='*60}{RESET}")
          print(f"  大V:{args.author}({trading_type})")
          print(f"  标的:{args.stock}")
          print(f"  财报数据:{'已提供' if earnings_data else '未提供'}")
      
          if earnings_data:
              print(f"\n{BOLD}核心数据:{RESET}")
              for key in ["revenue", "revenue_yoy", "net_profit", "net_profit_yoy", "roe"]:
                  val = earnings_data.get(key)
                  if val:
                      print(f"  {key}: {val}")
      
          print(f"\n{GREEN}✓{RESET} 任务文件:{task_file}")
      
      
      if __name__ == "__main__":
          main()
      
    • fetch_market_data.py 6.4 KB
      #!/usr/bin/env python3
      """
      盘面数据采集 — 手动输入并结构化存储
      =====================================
      盘面数据需由用户提供或手动输入,脚本负责结构化存储为 JSON。
      
      Usage:
          python fetch_market_data.py --manual
          python fetch_market_data.py --from-json market_data.json
      """
      
      import argparse
      import json
      import sys
      from datetime import datetime
      from pathlib import Path
      
      from common import (
          OUTPUT_DIR,
          GREEN, YELLOW, RED, CYAN, BOLD, RESET,
          info, warn, error, step,
          ensure_output_dir,
      )
      
      
      # ─── 手动输入盘面数据 ──────────────────────────────────────────────────────────────
      def manual_input():
          """手动输入盘面数据"""
          print(f"\n{BOLD}请输入今日盘面数据:{RESET}\n")
      
          data = {
              "date": datetime.now().strftime("%Y-%m-%d"),
              "source": "manual",
              "input_time": datetime.now().isoformat(),
              "data_sources": {},
              "cross_validated": False,
          }
      
          data["sh_index"] = input("上证指数收盘点位(如4000.50): ").strip()
          data["sh_change"] = input("上证涨跌幅%(如+1.5): ").strip()
          data["sz_index"] = input("深证成指收盘点位(可选,回车跳过): ").strip()
          data["sz_change"] = input("深证涨跌幅%(可选): ").strip()
          data["gem_index"] = input("创业板指收盘点位(可选): ").strip()
          data["gem_change"] = input("创业板涨跌幅%(可选): ").strip()
          data["volume"] = input("成交额(万亿,如3.5): ").strip()
          data["limit_up_count"] = input("涨停家数: ").strip()
          data["limit_down_count"] = input("跌停家数: ").strip()
          data["continuous_board"] = input("连板股数量(可选): ").strip()
          data["hot_sectors"] = input("热门板块(逗号分隔): ").strip()
          data["capital_flow"] = input("主力资金流向(如净流入/净流出金额): ").strip()
          data["north_flow"] = input("北向资金流向(可选): ").strip()
          data["market_sentiment"] = input("市场情绪(如:偏强/震荡/偏弱): ").strip()
          data["notes"] = input("其他备注: ").strip()
      
          # 数据交叉验证
          print(f"\n{BOLD}数据交叉验证:{RESET}")
          validate = input("是否已对关键数据进行交叉验证?(y/n): ").strip().lower()
          if validate == 'y':
              data["cross_validated"] = True
              data["data_sources"]["primary"] = input("主数据来源(如:交易所官网/东方财富): ").strip()
              data["data_sources"]["secondary"] = input("副数据来源(如:同花顺/万得): ").strip()
              data["data_sources"]["notes"] = input("数据差异备注(可跳过): ").strip()
          else:
              data["cross_validated"] = False
              print(f"{YELLOW}[!]{RESET} 建议对涨跌停家数、成交额等数据进行交叉验证")
      
          return data
      
      
      def load_from_json(json_path):
          """从JSON文件加载盘面数据"""
          f = Path(json_path)
          if not f.exists():
              error(f"文件不存在:{json_path}")
              return None
          try:
              data = json.loads(f.read_text(encoding="utf-8"))
              if "date" not in data:
                  data["date"] = datetime.now().strftime("%Y-%m-%d")
              data["source"] = "json_file"
              data["input_time"] = datetime.now().isoformat()
              info(f"已加载盘面数据:{f.name}")
              return data
          except Exception as e:
              error(f"读取JSON失败:{e}")
              return None
      
      
      # ─── 主流程 ────────────────────────────────────────────────────────────────────────
      def main():
          parser = argparse.ArgumentParser(description="盘面数据采集 — 手动输入并结构化存储")
          parser.add_argument("--manual", action="store_true", help="手动输入盘面数据(交互式)")
          parser.add_argument("--from-json", help="从JSON文件加载盘面数据")
          parser.add_argument("--output-dir", default=str(OUTPUT_DIR), help="输出目录")
      
          args = parser.parse_args()
      
          output_dir = Path(args.output_dir)
          output_dir.mkdir(parents=True, exist_ok=True)
      
          data = None
      
          if args.from_json:
              data = load_from_json(args.from_json)
          elif args.manual:
              data = manual_input()
          else:
              # 默认进入手动输入模式
              print(f"{BOLD}提示:盘面数据需手动输入{RESET}")
              print(f"使用 --manual 进入交互式输入")
              print(f"使用 --from-json <file> 从JSON文件加载")
              print(f"\n也可直接提供JSON格式的盘面数据文件,脚本会结构化存储。")
              parser.print_help()
              sys.exit(0)
      
          if not data:
              error("未获取到盘面数据")
              sys.exit(1)
      
          # 保存
          today = datetime.now().strftime("%Y-%m-%d")
          f = output_dir / f"盘面数据_{today}.json"
          f.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
          info(f"盘面数据已保存:{f}")
      
          # 显示摘要
          print(f"\n{BOLD}{'='*60}{RESET}")
          print(f"{BOLD}  盘面数据摘要{RESET}")
          print(f"{BOLD}{'='*60}{RESET}")
          print(f"  日期:{data.get('date', today)}")
          if data.get("sh_index"):
              print(f"  上证指数:{data['sh_index']}({data.get('sh_change', '')}%)")
          if data.get("sz_index"):
              print(f"  深证成指:{data['sz_index']}({data.get('sz_change', '')}%)")
          if data.get("gem_index"):
              print(f"  创业板指:{data['gem_index']}({data.get('gem_change', '')}%)")
          if data.get("volume"):
              print(f"  成交额:{data['volume']}万亿")
          if data.get("limit_up_count"):
              print(f"  涨停/跌停:{data['limit_up_count']}/{data.get('limit_down_count', '?')}")
          if data.get("hot_sectors"):
              print(f"  热门板块:{data['hot_sectors']}")
          if data.get("capital_flow"):
              print(f"  资金流向:{data['capital_flow']}")
          if data.get("market_sentiment"):
              print(f"  市场情绪:{data['market_sentiment']}")
          if data.get("notes"):
              print(f"  备注:{data['notes']}")
      
          print(f"\n{GREEN}✓{RESET} 可用于分析专家:python scripts/analyze.py --author 财躺平 --mode daily")
      
      
      if __name__ == "__main__":
          main()
      
    • portfolio.py 10.9 KB
      #!/usr/bin/env python3
      """
      组合复盘 — 按大V风格回顾持仓表现
      ====================================
      输入持仓列表,检索大V近期文章中对各标的的观点,按大V交易框架评估组合。
      
      Usage:
          python portfolio.py --author "财躺平" --stocks "中国卫星,航发动力,中航沈飞"
          python portfolio.py --author "格兰投研" --stocks "贵州茅台,宁德时代" --period 60
      """
      
      import argparse
      import sys
      from collections import defaultdict
      from datetime import datetime
      from pathlib import Path
      
      from common import (
          ACCOUNT_IDS, STOCK_ANALYSTS, PROFILES_DIR, OUTPUT_DIR,
          GREEN, YELLOW, RED, CYAN, BOLD, RESET,
          info, warn, error, step,
          get_api_key, find_profile, detect_trading_type,
          fetch_articles_paginated, ensure_output_dir,
      )
      
      
      # ─── API 调用(使用 common 通用分页函数)───────────────────────────────────────────
      def fetch_analyst_articles(api_key, author, days=30, count=100):
          """获取大V近期文章"""
          account = ACCOUNT_IDS.get(author, "")
          return fetch_articles_paginated(
              api_key, account, account_name=author,
              days=days, count=count, label=f"获取{author}文章",
          )
      
      
      # ─── 组合分析 ──────────────────────────────────────────────────────────────────────
      def analyze_portfolio(articles, stocks, author):
          """检索大V文章中对各持仓标的的观点"""
          portfolio_analysis = {
              "author": author,
              "stocks": stocks,
              "total_articles": len(articles),
              "analysis_time": datetime.now().isoformat(),
              "stock_views": {},
          }
      
          for stock in stocks:
              stock_info = {
                  "stock": stock,
                  "mentions": 0,
                  "related_articles": [],
                  "sentiment_keywords": [],
                  "key_points": [],
              }
      
              # 正面/负面关键词
              positive_kw = ["看好", "加仓", "建仓", "突破", "强势", "龙头", "底部",
                             "低估", "价值", "增长", "超预期", "护城河"]
              negative_kw = ["减仓", "清仓", "止损", "破位", "弱势", "高位",
                             "套牢", "套现", "减持", "低于预期", "风险"]
      
              pos_count = 0
              neg_count = 0
      
              for a in articles:
                  title = str(a.get("title", ""))
                  summary = str(a.get("summary", ""))
                  text = title + " " + summary
      
                  if stock in text:
                      stock_info["mentions"] += 1
                      pub = str(a.get("publishTime") or a.get("publicTime") or "")[:10]
                      stock_info["related_articles"].append({
                          "title": title[:60],
                          "date": pub,
                          "url": a.get("workUrl") or a.get("url") or "",
                      })
      
                      # 情绪分析
                      for kw in positive_kw:
                          if kw in text:
                              pos_count += 1
                              if kw not in stock_info["sentiment_keywords"]:
                                  stock_info["sentiment_keywords"].append(kw)
                      for kw in negative_kw:
                          if kw in text:
                              neg_count += 1
                              if kw not in stock_info["sentiment_keywords"]:
                                  stock_info["sentiment_keywords"].append(kw)
      
              # 情绪判定
              if pos_count > neg_count * 2:
                  stock_info["sentiment"] = "看多"
              elif neg_count > pos_count * 2:
                  stock_info["sentiment"] = "看空"
              elif pos_count > 0 and neg_count > 0:
                  stock_info["sentiment"] = "分歧"
              elif stock_info["mentions"] == 0:
                  stock_info["sentiment"] = "未提及"
              else:
                  stock_info["sentiment"] = "中性"
      
              stock_info["pos_score"] = pos_count
              stock_info["neg_score"] = neg_count
      
              portfolio_analysis["stock_views"][stock] = stock_info
      
          return portfolio_analysis
      
      
      # ─── 生成任务文件 ──────────────────────────────────────────────────────────────────
      def generate_portfolio_task(author, profile, trading_type, stocks, portfolio_analysis, period):
          today = datetime.now().strftime("%Y-%m-%d")
      
          lines = []
          lines.append(f"# {author} 风格化组合复盘任务")
          lines.append(f"> 日期:{today}")
          lines.append(f"> 持仓标的:{', '.join(stocks)}")
          lines.append(f"> 回顾周期:近{period}天")
          lines.append(f"> ⚠️ AI 风格模拟,不构成投资建议\n")
      
          lines.append("## 1. 风格画像摘要\n")
          if profile:
              lines.append(f"```\n{profile[:2000]}\n```\n")
          else:
              lines.append("未找到风格画像,使用默认分析框架。\n")
      
          lines.append("## 2. 持仓组合分析\n")
          lines.append(f"**分析样本**:{portfolio_analysis['total_articles']} 篇{author}近期文章\n")
      
          stock_views = portfolio_analysis.get("stock_views", {})
          if stock_views:
              lines.append("### 各持仓标的观点摘要\n")
              lines.append("| 标的 | 提及次数 | 情绪倾向 | 正面信号 | 负面信号 | 相关文章 |")
              lines.append("|------|---------|---------|---------|---------|---------|")
      
              for stock, view in stock_views.items():
                  pos = view.get("pos_score", 0)
                  neg = view.get("neg_score", 0)
                  articles_count = len(view.get("related_articles", []))
                  sample = view.get("related_articles", [{}])[0].get("title", "无") if articles_count else "无"
                  lines.append(f"| {stock} | {view['mentions']} | {view['sentiment']} | {pos} | {neg} | {sample[:20]} |")
              lines.append("")
      
          # 各标的详情
          for stock, view in stock_views.items():
              if view["mentions"] > 0:
                  lines.append(f"### {stock}\n")
                  lines.append(f"- 提及次数:{view['mentions']}")
                  lines.append(f"- 情绪倾向:{view['sentiment']}")
                  if view.get("sentiment_keywords"):
                      lines.append(f"- 关键信号:{', '.join(view['sentiment_keywords'][:8])}")
                  lines.append("")
                  if view.get("related_articles"):
                      lines.append("**相关文章:**")
                      for ra in view["related_articles"][:5]:
                          lines.append(f"- [{ra['date']}] {ra['title']}")
                      lines.append("")
      
          lines.append("## 3. 生成要求\n")
          lines.append(f"请按{author}的{'短线' if trading_type == 'short' else '长线'}交易框架,生成组合复盘报告:\n")
      
          if trading_type == "short":
              lines.append("- 各持仓标的的短线走势评估")
              lines.append("- 情绪周期对持仓的影响")
              lines.append("- 龙头辨识度和板块地位")
              lines.append("- 止盈/止损信号判断")
              lines.append("- 仓位调整建议\n")
          else:
              lines.append("- 各持仓标的的基本面评估")
              lines.append("- 估值水平和安全边际")
              lines.append("- 长期逻辑是否发生变化")
              lines.append("- 组合集中度和风险分散")
              lines.append("- 调仓建议\n")
      
          lines.append("## 4. 合规要求\n")
          lines.append("- 标注:「AI 风格模拟,不构成投资建议」")
          lines.append("- 不直接荐股,仅分析逻辑")
          lines.append("- 仅引用公开文章中的观点\n")
      
          lines.append(f"## 5. 输出")
          lines.append(f"保存到:`output/{author}_组合复盘_{today}.md`\n")
      
          return "\n".join(lines)
      
      
      # ─── 主流程 ────────────────────────────────────────────────────────────────────────
      def main():
          parser = argparse.ArgumentParser(description="组合复盘 — 按大V风格回顾持仓")
          parser.add_argument("--author", required=True, help="指定大V名称")
          parser.add_argument("--stocks", required=True, help="持仓列表(逗号分隔)")
          parser.add_argument("--period", type=int, default=30, help="回顾周期(天,默认30天)")
          parser.add_argument("--api-key", help="指定API Key")
          parser.add_argument("--output-dir", default=str(OUTPUT_DIR), help="输出目录")
      
          args = parser.parse_args()
      
          output_dir = Path(args.output_dir)
          output_dir.mkdir(parents=True, exist_ok=True)
      
          stocks = [s.strip() for s in args.stocks.split(",") if s.strip()]
          if not stocks:
              error("请提供至少一个持仓标的")
              sys.exit(1)
      
          # 加载画像
          step(f"加载画像:{args.author}")
          profile = find_profile(args.author)
          trading_type = detect_trading_type(profile)
          if profile:
              info(f"画像已加载,交易类型:{trading_type}")
          else:
              warn(f"未找到{args.author}的风格画像,使用默认分析")
      
          # 获取大V近期文章
          api_key = get_api_key(args.api_key)
          if not api_key:
              sys.exit(1)
          step(f"获取{args.author}近{args.period}天文章...")
          articles = fetch_analyst_articles(api_key, args.author, args.period, 100)
      
          if not articles:
              warn(f"未获取到{args.author}的文章数据")
              warn("可能原因:1) API额度不足 2) 该大V近期无文章")
      
          info(f"共获取 {len(articles)} 篇文章")
      
          # 组合分析
          step("分析持仓组合...")
          portfolio_analysis = analyze_portfolio(articles, stocks, args.author)
      
          # 生成任务
          task = generate_portfolio_task(args.author, profile, trading_type, stocks, portfolio_analysis, args.period)
          today = datetime.now().strftime("%Y-%m-%d")
          task_file = output_dir / f"{args.author}_组合复盘任务_{today}.md"
          task_file.write_text(task, encoding="utf-8")
          info(f"组合复盘任务已保存:{task_file}")
      
          # 显示摘要
          print(f"\n{BOLD}{'='*60}{RESET}")
          print(f"{BOLD}  组合复盘摘要{RESET}")
          print(f"{BOLD}{'='*60}{RESET}")
          print(f"  大V:{args.author}({trading_type})")
          print(f"  持仓:{', '.join(stocks)}")
          print(f"  样本:{len(articles)} 篇文章")
      
          stock_views = portfolio_analysis.get("stock_views", {})
          if stock_views:
              print(f"\n{BOLD}各标的观点:{RESET}")
              for stock, view in stock_views.items():
                  sentiment_color = GREEN if view["sentiment"] == "看多" else (RED if view["sentiment"] == "看空" else YELLOW)
                  print(f"  {stock:10s} | 提及{view['mentions']:2d}次 | {sentiment_color}{view['sentiment']}{RESET}")
      
          print(f"\n{GREEN}✓{RESET} 任务文件:{task_file}")
      
      
      if __name__ == "__main__":
          main()
      
    • quality_audit.py 25.3 KB
      #!/usr/bin/env python3
      """
      质量审计脚本 — 校验生成内容是否符合大V风格画像
      =================================================
      对生成的文章进行9维质量检查,综合评分 > 70 分准出。
      
      Usage:
          python quality_audit.py --article "output/财躺平_复盘_2026-01-15.md" --author "财躺平"
          python quality_audit.py --article "output/多V对比_2026-01-15.md"
      """
      
      import argparse
      import json
      import re
      import sys
      from datetime import datetime
      from pathlib import Path
      
      from common import (
          PROFILES_DIR, OUTPUT_DIR,
          GREEN, YELLOW, RED, CYAN, BOLD, RESET,
          info, warn, error, step,
          ensure_output_dir,
      )
      
      # 交易术语库
      TRADING_TERMS = [
          "涨停", "跌停", "连板", "龙头", "游资", "情绪周期", "打板", "复盘", "盘面",
          "资金流向", "竞价", "龙虎榜", "妖股", "止损", "止盈", "仓位", "满仓", "清仓",
          "减仓", "加仓", "建仓", "空仓", "做多", "做空", "牛市", "熊市", "震荡",
          "放量", "缩量", "突破", "回调", "反弹", "趋势", "支撑", "压力", "均线",
          "K线", "MACD", "量价", "换手率", "成交额", "市值", "估值", "PE", "PB",
          "ROE", "护城河", "基本面", "技术面", "资金面", "消息面", "板块轮动",
      ]
      
      # 盘面指标关键词
      MARKET_INDICATORS = [
          "成交额", "成交量", "涨停", "跌停", "炸板", "连板", "换手率", "融资余额",
          "北向资金", "主力资金", "指数", "上证", "深证", "创业板", "科创",
      ]
      
      
      # ─── 画像加载 ──────────────────────────────────────────────────────────────────────
      from common import find_profile
      
      
      def parse_profile_json(profile_text):
          """尝试将画像文本解析为JSON,返回 (profile_dict, is_json)"""
          try:
              data = json.loads(profile_text)
              return data, True
          except (json.JSONDecodeError, TypeError):
              return profile_text, False
      
      
      # ─── 质量检查项 ────────────────────────────────────────────────────────────────────
      def check_signature_phrases(article, profile):
          """检查标志性表达命中率"""
          phrases = []
          profile_data, is_json = parse_profile_json(profile)
      
          if is_json:
              # JSON格式:从 expression_style.标志性表达 中提取
              expr = profile_data.get("表达风格", {})
              sig_phrases = expr.get("标志性表达", [])
              if isinstance(sig_phrases, list):
                  phrases = [p for p in sig_phrases if isinstance(p, str) and len(p) > 2]
              # 也从免责声明中提取
              disclaimer = expr.get("免责声明", "")
              if disclaimer and len(disclaimer) > 5:
                  phrases.append(disclaimer)
          else:
              # 兼容旧版 markdown 格式
              table_matches = re.findall(r'\|\s*\d+\s*\|\s*(.+?)\s*\|\s*(\d+)次', profile)
              for match in table_matches:
                  phrase = match[0].strip()
                  if phrase and len(phrase) > 2:
                      phrases.append(phrase)
              quote_matches = re.findall(r'[「"](.+?)[」"]', profile)
              for match in quote_matches:
                  if len(match) > 2:
                      phrases.append(match)
      
          # 去重
          phrases = list(dict.fromkeys(phrases))[:20]
      
          if not phrases:
              return {
                  "name": "标志性表达命中率",
                  "score": 0,
                  "weight": 0.10,
                  "detail": "画像中未提取到标志性表达",
                  "pass": False,
              }
      
          # 检查命中率
          hits = 0
          hit_list = []
          for phrase in phrases:
              if phrase in article:
                  hits += 1
                  hit_list.append(phrase)
      
          hit_rate = hits / len(phrases) if phrases else 0
          passed = hit_rate > 0.40
      
          return {
              "name": "标志性表达命中率",
              "score": round(hit_rate * 100, 1),
              "weight": 0.10,
              "detail": f"命中 {hits}/{len(phrases)} 个标志性表达({hit_rate:.0%})",
              "hit_phrases": hit_list[:10],
              "pass": passed,
          }
      
      
      def check_trading_system(article, profile):
          """检查交易体系覆盖度"""
          system_keywords = []
          profile_data, is_json = parse_profile_json(profile)
      
          if is_json:
              # JSON格式:从投资体系和市场判断中提取关键词
              inv = profile_data.get("投资体系", {})
              mkt = profile_data.get("市场判断", {})
              # 收集所有文本字段
              text_parts = []
              for key in ["核心流派", "选股逻辑", "止损纪律", "仓位管理", "持仓周期"]:
                  val = inv.get(key, "")
                  if isinstance(val, str):
                      text_parts.append(val)
                  elif isinstance(val, list):
                      text_parts.extend(val)
              for key in ["研判方式", "风险偏好", "逆向思维"]:
                  val = mkt.get(key, "")
                  if isinstance(val, str):
                      text_parts.append(val)
              # 提取中文关键词
              all_text = " ".join(text_parts)
              words = re.findall(r'[\u4e00-\u9fff]{2,6}', all_text)
              common_words = {"原文", "证据", "描述", "大V", "交易", "体系", "模式", "逻辑", "信号", "仓位", "管理"}
              system_keywords = [w for w in words if w not in common_words and len(w) >= 2][:30]
          else:
              # 兼容旧版 markdown 格式
              system_section = re.search(r'## 1\..*?(?=## 2\.|$)', profile, re.DOTALL)
              if system_section:
                  text = system_section.group()
                  words = re.findall(r'[\u4e00-\u9fff]{2,6}', text)
                  common_words = {"原文", "证据", "描述", "大V", "交易", "体系", "模式", "逻辑", "信号", "仓位", "管理"}
                  system_keywords = [w for w in words if w not in common_words and len(w) >= 2][:30]
      
          if not system_keywords:
              system_keywords = ["选股", "买入", "卖出", "止损", "止盈", "仓位", "龙头", "趋势", "突破", "回调"]
      
          # 检查覆盖度
          hits = 0
          for kw in system_keywords:
              if kw in article:
                  hits += 1
      
          coverage = hits / len(system_keywords) if system_keywords else 0
          passed = coverage > 0.70
      
          return {
              "name": "交易体系覆盖度",
              "score": round(coverage * 100, 1),
              "weight": 0.10,
              "detail": f"覆盖 {hits}/{len(system_keywords)} 个交易体系关键词",
              "pass": passed,
          }
      
      
      def check_article_structure(article, profile):
          """检查文章结构匹配度"""
          # 检查文章是否包含基本结构元素
          structure_elements = {
              "开头": bool(re.match(r'^#|^\*\*|^[^\n]{10,}', article.strip())),
              "正文分段": article.count('\n\n') >= 3,
              "小标题": bool(re.search(r'^##\s', article, re.MULTILINE)),
              "结尾": article.strip()[-100:].strip() != "",
              "免责声明": any(kw in article for kw in ["免责", "不构成", "投资建议", "AI风格模拟"]),
          }
      
          matched = sum(1 for v in structure_elements.values() if v)
          total = len(structure_elements)
          match_rate = matched / total
      
          passed = match_rate > 0.60
      
          return {
              "name": "文章结构匹配度",
              "score": round(match_rate * 100, 1),
              "weight": 0.10,
              "detail": f"匹配 {matched}/{total} 个结构元素",
              "elements": {k: "✓" if v else "✗" for k, v in structure_elements.items()},
              "pass": passed,
          }
      
      
      def check_market_indicators(article):
          """检查盘面指标覆盖"""
          hits = []
          for indicator in MARKET_INDICATORS:
              if indicator in article:
                  hits.append(indicator)
      
          coverage = len(hits) / len(MARKET_INDICATORS) if MARKET_INDICATORS else 0
          passed = coverage > 0.50
      
          return {
              "name": "盘面指标覆盖",
              "score": round(coverage * 100, 1),
              "weight": 0.10,
              "detail": f"覆盖 {len(hits)}/{len(MARKET_INDICATORS)} 个盘面指标",
              "hit_indicators": hits,
              "pass": passed,
          }
      
      
      def check_tone_consistency(article, profile):
          """检查语气一致性"""
          # 简化版语气检测
          tone_markers = {
              "感叹句": len(re.findall(r'[!!]', article)),
              "疑问句": len(re.findall(r'[??]', article)),
              "第一人称": len(re.findall(r'[我][们]?', article)),
              "口语化": len(re.findall(r'[吧呢啊嘛哦哈嘿]', article)),
          }
      
          # 从画像中提取语气特征
          profile_data, is_json = parse_profile_json(profile)
          if is_json:
              expr = profile_data.get("表达风格", {})
              tone_text = expr.get("语气", "") + " " + expr.get("文风温度", "")
          else:
              tone_section = re.search(r'语气.*?人称', profile, re.DOTALL)
              tone_text = tone_section.group() if tone_section else ""
      
          # 简化判断:检查文章是否有语气特征
          tone_count = sum(tone_markers.values())
          has_tone = tone_count > 5
      
          # 偏差计算:有语气特征得低偏差(通过),无语气特征得高偏差(不通过)
          if has_tone:
              # 语气标记充足,偏差低;语气越丰富偏差越低
              deviation = max(0.05, 0.15 - (tone_count - 5) * 0.01)
          else:
              # 语气标记不足,偏差高
              deviation = min(0.5, 0.30 + (5 - tone_count) * 0.04)
      
          passed = deviation <= 0.20
      
          return {
              "name": "语气一致性",
              "score": round((1 - deviation) * 100, 1),
              "weight": 0.10,
              "detail": f"语气标记:{tone_markers}",
              "pass": passed,
          }
      
      
      def check_word_count(article, profile):
          """检查字数范围"""
          target_avg = 3000  # 默认
          profile_data, is_json = parse_profile_json(profile)
      
          if is_json:
              depth = profile_data.get("内容深度", {})
              avg_words = depth.get("平均字数", None)
              if avg_words and isinstance(avg_words, (int, float)):
                  target_avg = int(avg_words)
          else:
              avg_match = re.search(r'平均字数[::]\s*(\d+)', profile)
              if avg_match:
                  target_avg = int(avg_match.group(1))
      
          # 计算文章字数(去除markdown标记)
          clean = re.sub(r'[#*`\[\](){}|>]', '', article)
          clean = re.sub(r'\n+', '', clean)
          actual_count = len(clean.strip())
      
          deviation = abs(actual_count - target_avg) / target_avg if target_avg > 0 else 1
          passed = deviation < 0.30
      
          return {
              "name": "字数范围",
              "score": round((1 - min(deviation, 1)) * 100, 1),
              "weight": 0.10,
              "detail": f"实际{actual_count}字,目标{target_avg}字(偏差{deviation:.0%})",
              "pass": passed,
          }
      
      
      def check_trading_terms(article):
          """检查交易术语覆盖"""
          hits = []
          for term in TRADING_TERMS:
              if term in article:
                  hits.append(term)
      
          coverage = len(hits) / len(TRADING_TERMS) if TRADING_TERMS else 0
          passed = coverage > 0.50
      
          return {
              "name": "交易术语覆盖",
              "score": round(coverage * 100, 1),
              "weight": 0.10,
              "detail": f"覆盖 {len(hits)}/{len(TRADING_TERMS)} 个交易术语",
              "hit_terms": hits[:10],
              "pass": passed,
          }
      
      
      # ─── 思维层检查(7-9) ────────────────────────────────────────────────────────────
      
      def check_core_thinking(article, profile):
          """检查核心思维匹配度——文章是否体现了大V的分析思维框架"""
          profile_data, is_json = parse_profile_json(profile)
          if not is_json:
              return {"name": "核心思维匹配度", "score": 50, "weight": 0.15,
                      "detail": "非JSON画像,跳过", "pass": True}
      
          inv = profile_data.get("投资体系", {})
          tools = inv.get("核心分析工具", [])
          if not tools:
              return {"name": "核心思维匹配度", "score": 0, "weight": 0.15,
                      "detail": "画像中未找到核心分析工具", "pass": False}
      
          # 从核心分析工具中提取思维关键词
          thinking_keywords = []
          for tool in tools:
              words = re.findall(r'[\u4e00-\u9fff]{2,6}', tool)
              thinking_keywords.extend([w for w in words if len(w) >= 2])
      
          # 去重后取前20个
          seen = set()
          unique_kw = []
          for w in thinking_keywords:
              if w not in seen:
                  seen.add(w)
                  unique_kw.append(w)
          thinking_keywords = unique_kw[:20]
      
          if not thinking_keywords:
              return {"name": "核心思维匹配度", "score": 0, "weight": 0.15,
                      "detail": "未能提取思维关键词", "pass": False}
      
          hits = [kw for kw in thinking_keywords if kw in article]
          coverage = len(hits) / len(thinking_keywords)
          passed = coverage > 0.60
      
          return {
              "name": "核心思维匹配度",
              "score": round(coverage * 100, 1),
              "weight": 0.15,
              "detail": f"思维关键词覆盖 {len(hits)}/{len(thinking_keywords)}({coverage:.0%})",
              "hit_phrases": hits[:10],
              "pass": passed,
          }
      
      
      def check_argumentation_style(article, profile):
          """检查论证方式匹配——文章是否按大V特有的论述模式展开"""
          profile_data, is_json = parse_profile_json(profile)
          if not is_json:
              return {"name": "论证方式匹配", "score": 50, "weight": 0.15,
                      "detail": "非JSON画像,跳过", "pass": True}
      
          author_name = profile_data.get("大V名称", "")
          expr = profile_data.get("表达风格", {})
      
          # 提取开头和结尾模式关键字作为论证链标记
          opening = expr.get("开头模式", "")
          closing = expr.get("结尾模式", "")
          structure = expr.get("文章结构", "")
      
          # 从结构描述中提取论证步骤关键词
          arg_keywords_text = f"{opening} {closing} {structure}"
          words = re.findall(r'[\u4e00-\u9fff]{2,6}', arg_keywords_text)
          common = {"原文", "证据", "描述", "文章", "固定", "开头", "结尾", "模式", "包含", "使用"}
          arg_keywords = [w for w in words if w not in common and len(w) >= 2][:10]
      
          if not arg_keywords:
              # 无法提取论证关键词时,检查文章是否包含分析性段落
              has_analysis = bool(re.search(r'产业链|估值|基本面|趋势|护城河|周期|逻辑', article))
              return {
                  "name": "论证方式匹配",
                  "score": 70 if has_analysis else 30,
                  "weight": 0.15,
                  "detail": "论证结构检查(启发式)" if has_analysis else "缺少分析性段落",
                  "pass": has_analysis,
              }
      
          hits = [kw for kw in arg_keywords if kw in article]
          coverage = len(hits) / len(arg_keywords)
          passed = coverage > 0.60
      
          return {
              "name": "论证方式匹配",
              "score": round(coverage * 100, 1),
              "weight": 0.15,
              "detail": f"论证关键词覆盖 {len(hits)}/{len(arg_keywords)}({coverage:.0%})",
              "hit_phrases": hits[:10],
              "pass": passed,
          }
      
      
      def check_philosophy_values(article, profile):
          """检查哲学价值观体现——文章是否融入了大V的投资哲学和价值观"""
          profile_data, is_json = parse_profile_json(profile)
          if not is_json:
              return {"name": "哲学价值观体现", "score": 50, "weight": 0.10,
                      "detail": "非JSON画像,跳过", "pass": True}
      
          # 从投资格言中提取哲学关键词
          atlas = profile_data.get("关注图谱", {})
          mottos = atlas.get("投资格言", [])
          if not mottos:
              return {"name": "哲学价值观体现", "score": 0, "weight": 0.10,
                      "detail": "画像中未找到投资格言", "pass": False}
      
          # 同时检查互动特征中的修心哲学/价值观字段
          interaction = profile_data.get("互动特征", {})
          philosophy_text = " ".join(mottos)
      
          # 取前10个格言中的关键词
          words = re.findall(r'[\u4e00-\u9fff]{2,6}', philosophy_text)
          common = {"原文", "证据", "描述", "文章"}
          philosophy_kw = [w for w in words if w not in common and len(w) >= 2][:10]
      
          if not philosophy_kw:
              philosophy_kw = mottos[:5]  # 直接用格言原文
      
          hits = 0
          hit_list = []
          for kw in philosophy_kw:
              if kw in article:
                  hits += 1
                  hit_list.append(kw)
      
          coverage = hits / len(philosophy_kw) if philosophy_kw else 0
          passed = coverage > 0.50
      
          return {
              "name": "哲学价值观体现",
              "score": round(coverage * 100, 1),
              "weight": 0.10,
              "detail": f"哲学格言覆盖 {hits}/{len(philosophy_kw)}({coverage:.0%})",
              "hit_phrases": hit_list[:10],
              "pass": passed,
          }
      
      
      # ─── 合规检查 ──────────────────────────────────────────────────────────────────────
      
      # 6条合规硬规则
      COMPLIANCE_RULES = [
          {
              "id": 1,
              "name": "不承诺收益",
              "check": lambda text: not any(kw in text for kw in ["保证赚钱", "稳赚不赔", "必涨", "翻倍", "包赚"]),
              "desc": "不包含承诺性表述",
          },
          {
              "id": 2,
              "name": "不预测具体收益率",
              "check": lambda text: not re.search(r'(预计|预期|预计).{0,5}(\d+\s*%|翻倍|\d+\s*倍收益)', text),
              "desc": "不承诺具体收益率数字",
          },
          {
              "id": 3,
              "name": "不暗示内幕",
              "check": lambda text: not any(kw in text for kw in ["内幕", "消息灵通", "据我所知", "独家消息", "未公开"]),
              "desc": "不暗示有内幕信息",
          },
          {
              "id": 4,
              "name": "标注数据来源",
              "check": lambda text: any(kw in text for kw in ["公开数据", "公开信息", "市场数据", "盘面数据"]),
              "desc": "标注基于公开数据生成",
          },
          {
              "id": 5,
              "name": "标注AI模拟",
              "check": lambda text: any(kw in text for kw in ["AI风格模拟", "AI 风格模拟", "风格模拟", "不构成投资建议"]),
              "desc": "标注AI风格模拟",
          },
          {
              "id": 6,
              "name": "不极端指令",
              "check": lambda text: not any(kw in text for kw in ["全仓买入", "满仓梭哈", "赶紧全买", "清仓卖出"]),
              "desc": "不给出极端操作指令",
          },
      ]
      
      
      def check_compliance(article):
          """合规检查 — 6条硬规则"""
          violations = []
          passes = []
      
          for rule in COMPLIANCE_RULES:
              if rule["check"](article):
                  passes.append(rule)
              else:
                  violations.append(rule)
      
          all_passed = len(violations) == 0
      
          return {
              "name": "合规检查",
              "passed_rules": [r["name"] for r in passes],
              "violations": [r["name"] for r in violations],
              "all_passed": all_passed,
              "detail": f"通过 {len(passes)}/{len(COMPLIANCE_RULES)} 条规则"
                        + (f",违反:{', '.join(r['name'] for r in violations)}" if violations else ""),
          }
      
      
      # ─── 综合评分 ──────────────────────────────────────────────────────────────────────
      def run_audit(article_path, author=None):
          """执行完整质量审计"""
          article_file = Path(article_path)
          if not article_file.exists():
              error(f"文章文件不存在:{article_path}")
              return None
      
          article = article_file.read_text(encoding="utf-8")
          info(f"已加载文章:{article_file.name}({len(article)} 字符)")
      
          profile = None
          if author:
              profile = find_profile(author)
              if profile:
                  info(f"已加载画像:{author}")
              else:
                  warn(f"未找到{author}的风格画像,部分检查项将使用默认值")
                  profile = ""
      
          if not profile:
              profile = "平均字数:3000\n语气与人称\n免责声明"
      
          # 前置合规检查(不通过直接打回)
          step("前置检查:合规检查(6条硬规则)...")
          compliance = check_compliance(article)
      
          if not compliance["all_passed"]:
              print(f"\n{BOLD}{'='*60}{RESET}")
              print(f"{RED}{BOLD}  ❌ 合规检查不通过 — 直接打回{RESET}")
              print(f"{BOLD}{'='*60}{RESET}\n")
              for rule_name in compliance["passed_rules"]:
                  print(f"  {GREEN}✓{RESET} {rule_name}")
              for rule_name in compliance["violations"]:
                  print(f"  {RED}✗{RESET} {rule_name}")
              print(f"\n  {BOLD}合规检查不通过 = 直接打回,不计算综合评分{RESET}")
              print(f"  请修改文章后重新审计。\n")
      
              # 保存审计报告
              report = {
                  "article": str(article_file),
                  "author": author or "unknown",
                  "audit_time": datetime.now().isoformat(),
                  "compliance": compliance,
                  "total_score": 0,
                  "passed": False,
                  "reason": "合规检查不通过",
              }
              report_file = OUTPUT_DIR / f"质量审计_{article_file.stem}_{datetime.now().strftime('%Y%m%d')}.json"
              report_file.parent.mkdir(parents=True, exist_ok=True)
              report_file.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
              info(f"审计报告已保存:{report_file}")
              return 0
      
          info(f"合规检查通过:{compliance['detail']}")
      
          # 执行9项检查
          checks = []
      
          step("检查1:标志性表达命中率...")
          checks.append(check_signature_phrases(article, profile))
      
          step("检查2:交易体系覆盖度...")
          checks.append(check_trading_system(article, profile))
      
          step("检查3:文章结构匹配度...")
          checks.append(check_article_structure(article, profile))
      
          step("检查4:盘面指标覆盖...")
          checks.append(check_market_indicators(article))
      
          step("检查5:语气一致性...")
          checks.append(check_tone_consistency(article, profile))
      
          step("检查6:交易术语覆盖...")
          checks.append(check_trading_terms(article))
      
          step("检查7:核心思维匹配度...")
          checks.append(check_core_thinking(article, profile))
      
          step("检查8:论证方式匹配...")
          checks.append(check_argumentation_style(article, profile))
      
          step("检查9:哲学价值观体现...")
          checks.append(check_philosophy_values(article, profile))
      
          # 计算综合评分
          total_score = sum(c["score"] * c["weight"] for c in checks)
          passed = total_score > 70
      
          # 输出报告
          print(f"\n{BOLD}{'='*60}{RESET}")
          print(f"{BOLD}  质量审计报告{RESET}")
          print(f"{BOLD}{'='*60}{RESET}\n")
      
          for c in checks:
              status = f"{GREEN}✓ PASS{RESET}" if c["pass"] else f"{RED}✗ FAIL{RESET}"
              print(f"  {status} {c['name']}")
              print(f"    得分:{c['score']}/100(权重{c['weight']:.0%})")
              print(f"    详情:{c['detail']}")
              if "hit_phrases" in c and c["hit_phrases"]:
                  print(f"    命中表达:{', '.join(c['hit_phrases'][:5])}")
              if "hit_terms" in c and c["hit_terms"]:
                  print(f"    命中术语:{', '.join(c['hit_terms'][:5])}")
              if "elements" in c:
                  for elem, status in c["elements"].items():
                      print(f"    {elem}: {status}")
              print()
      
          print(f"{BOLD}{'─'*60}{RESET}")
          score_color = GREEN if passed else RED
          verdict = "✅ 准出" if passed else "❌ 打回重生成"
          print(f"  {BOLD}综合评分:{score_color}{total_score:.1f}{RESET}/100")
          print(f"  {BOLD}审计结论:{score_color}{verdict}{RESET}")
          print(f"{BOLD}{'='*60}{RESET}\n")
      
          # 保存审计报告
          report = {
              "article": str(article_file),
              "author": author or "unknown",
              "audit_time": datetime.now().isoformat(),
              "compliance": compliance,
              "total_score": round(total_score, 1),
              "passed": passed,
              "checks": checks,
          }
      
          report_file = OUTPUT_DIR / f"质量审计_{article_file.stem}_{datetime.now().strftime('%Y%m%d')}.json"
          report_file.parent.mkdir(parents=True, exist_ok=True)
          report_file.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
          info(f"审计报告已保存:{report_file}")
      
          return total_score
      
      
      # ─── 主流程 ────────────────────────────────────────────────────────────────────────
      def main():
          parser = argparse.ArgumentParser(description="质量审计 — 校验生成内容")
          parser.add_argument("--article", required=True, help="待审计的文章路径")
          parser.add_argument("--author", help="大V名称(用于加载画像)")
      
          args = parser.parse_args()
      
          score = run_audit(args.article, args.author)
          if score is not None:
              sys.exit(0 if score > 70 else 1)
          else:
              sys.exit(2)
      
      
      if __name__ == "__main__":
          main()
      
    • stock_screen.py 14.5 KB
      #!/usr/bin/env python3
      """
      短线选股 — 按大V交易体系筛选标的
      ==================================
      从公众号热文中提取个股提及频率,结合大V的交易类型应用不同筛选策略。
      
      Usage:
          python stock_screen.py --author "财躺平"
          python stock_screen.py --author "财躺平" --sector "航天"
          python stock_screen.py --author "格兰投研" --days 14 --count 100
      """
      
      import argparse
      import json
      import re
      import sys
      from collections import Counter, defaultdict
      from datetime import datetime
      from pathlib import Path
      
      from common import (
          ACCOUNT_IDS, STOCK_ANALYSTS, PROFILES_DIR, OUTPUT_DIR,
          GREEN, YELLOW, RED, CYAN, BOLD, RESET,
          info, warn, error, step,
          get_api_key, find_profile, detect_trading_type,
          fetch_articles_paginated, ensure_output_dir,
      )
      
      
      # ─── 赛道关键词库 ──────────────────────────────────────────────────────────────────
      SECTOR_KEYWORDS = {
          "航天": ["航天", "卫星", "火箭", "商业航天", "星链", "太空"],
          "半导体": ["半导体", "芯片", "光刻", "存储", "科创", "中芯", "台积电"],
          "AI": ["AI", "人工智能", "大模型", "GPT", "Agent", "算力", "英伟达"],
          "新能源": ["新能源", "光伏", "风电", "储能", "锂电", "充电桩"],
          "金融": ["券商", "银行", "保险", "金融"],
          "消费": ["消费", "白酒", "食品", "零售", "茅台", "五粮液"],
          "医药": ["医药", "医疗", "创新药", "生物"],
          "军工": ["军工", "国防", "装备", "中航", "航发"],
          "脑机接口": ["脑机", "Neuralink"],
          "核聚变": ["核聚变", "可控核"],
          "房地产": ["房地产", "地产", "楼市"],
          "机器人": ["机器人", "人形机器人", "减速器"],
      }
      
      # 个股识别正则(中文股票名称模式)
      STOCK_NAME_PATTERN = re.compile(
          r'(?:中国|中|国|华|南|北|东|西|新|大|上|深|长|广|江|浙|海|天|金|银|龙|凤|'
          r'安|宝|富|恒|嘉|美|高|万|百|千|红|蓝|绿|紫|星|光|明|达|利|通|信|科|泰|'
          r'航|发|沈|成|西|赣|鲁|闽|川|渝|鄂|湘|皖|豫|冀|辽|吉|黑)[\u4e00-\u9fff]{1,5}'
          r'(?:股份|科技|电子|集团|控股|材料|设备|能源|动力|智能|芯片|生物|药业|'
          r'化工|机械|光电|信息|通信|网络|软件|互联|新材|环保|装备|工业|精密|'
          r'电气|自动化|仪器|仪表|航空|航天|船舶|重工)'
      )
      
      # 短线筛选关注词
      SHORT_SCREEN_KW = ["涨停", "连板", "龙头", "资金流入", "主力买入", "封板",
                         "竞价", "炸板", "反包", "首板", "二板", "三板", "妖股",
                         "情绪", "辨识度", "卡位", "补涨", "加速"]
      
      # 长线筛选关注词
      LONG_SCREEN_KW = ["护城河", "ROE", "净利润", "毛利率", "自由现金流", "估值",
                        "PE", "PB", "分红", "回购", "业绩增长", "核心竞争力",
                        "市场份额", "研发", "壁垒", "长期", "内在价值"]
      
      
      # ─── API 调用(使用 common 通用分页函数)───────────────────────────────────────────
      def fetch_hot_articles(api_key, author, days=7, count=100):
          account = ACCOUNT_IDS.get(author, "")
          return fetch_articles_paginated(
              api_key, account, account_name=author,
              days=days, count=count, label="获取热文",
          )
      
      
      # ─── 选股分析 ──────────────────────────────────────────────────────────────────────
      def extract_stock_mentions(articles, sector_filter=None):
          """从文章中提取个股提及"""
          stock_counter = Counter()
          stock_context = defaultdict(list)
      
          for a in articles:
              text = " ".join([
                  str(a.get("title", "")),
                  str(a.get("summary", "")),
                  str(a.get("workSummary", "")),
              ])
              title = str(a.get("title", ""))
      
              # 提取个股名称
              matches = STOCK_NAME_PATTERN.findall(text)
              for m in matches:
                  if len(m) >= 3:
                      stock_counter[m] += 1
                      stock_context[m].append(title[:60])
      
              # 也检查赛道关键词匹配(用于板块热度)
              if sector_filter:
                  sector_kw = SECTOR_KEYWORDS.get(sector_filter, [])
                  sector_score = sum(text.count(kw) for kw in sector_kw)
                  if sector_score == 0:
                      continue  # 如果指定了板块,跳过不相关的文章
      
          return stock_counter, stock_context
      
      
      def analyze_screening(articles, trading_type, sector_filter=None):
          """按大V交易类型执行选股分析"""
          stock_counter, stock_context = extract_stock_mentions(articles, sector_filter)
      
          # 分析短线/长线特征
          screening_result = {
              "trading_type": trading_type,
              "sector_filter": sector_filter,
              "total_articles": len(articles),
              "analysis_time": datetime.now().isoformat(),
          }
      
          if trading_type in ("short", "mixed"):
              # 短线筛选:高频提及 + 板块热度
              short_scores = {}
              for stock, count in stock_counter.items():
                  contexts = stock_context[stock]
                  context_text = " ".join(contexts)
                  short_score = count * 2  # 提及频率权重
                  short_score += sum(1 for kw in SHORT_SCREEN_KW if kw in context_text)
                  short_scores[stock] = short_score
      
              top_short = sorted(short_scores.items(), key=lambda x: x[1], reverse=True)[:15]
              screening_result["short_screen"] = [
                  {"stock": s, "score": sc, "mentions": stock_counter[s],
                   "sample_titles": stock_context[s][:3]}
                  for s, sc in top_short
              ]
      
          if trading_type in ("long", "mixed"):
              # 长线筛选:基本面关键词
              long_scores = {}
              for stock, count in stock_counter.items():
                  contexts = stock_context[stock]
                  context_text = " ".join(contexts)
                  long_score = count  # 提及频率
                  long_score += sum(2 for kw in LONG_SCREEN_KW if kw in context_text)
                  long_scores[stock] = long_score
      
              top_long = sorted(long_scores.items(), key=lambda x: x[1], reverse=True)[:15]
              screening_result["long_screen"] = [
                  {"stock": s, "score": sc, "mentions": stock_counter[s],
                   "sample_titles": stock_context[s][:3]}
                  for s, sc in top_long
              ]
      
          # 板块热度
          sector_scores = defaultdict(int)
          for a in articles:
              text = str(a.get("title", "")) + " " + str(a.get("summary", ""))
              for sector, kws in SECTOR_KEYWORDS.items():
                  for kw in kws:
                      sector_scores[sector] += text.count(kw)
          screening_result["sector_heat"] = dict(
              sorted(sector_scores.items(), key=lambda x: x[1], reverse=True)[:10]
          )
      
          # 所有提及的个股排行
          screening_result["all_mentions"] = [
              {"stock": s, "count": c, "sample_titles": stock_context[s][:2]}
              for s, c in stock_counter.most_common(30)
          ]
      
          return screening_result
      
      
      # ─── 生成任务文件 ──────────────────────────────────────────────────────────────────
      def generate_screen_task(author, profile, trading_type, screening_result):
          today = datetime.now().strftime("%Y-%m-%d")
      
          lines = []
          lines.append(f"# {author} 风格化选股分析任务")
          lines.append(f"> 日期:{today}")
          lines.append(f"> 交易类型:{trading_type}")
          lines.append(f"> ⚠️ AI 风格模拟,不构成投资建议\n")
      
          lines.append("## 1. 风格画像摘要\n")
          if profile:
              lines.append(f"```\n{profile[:2000]}\n```\n")
          else:
              lines.append("未找到风格画像,使用默认分析框架。\n")
      
          lines.append("## 2. 选股数据\n")
          lines.append(f"**分析样本**:{screening_result['total_articles']} 篇热文")
          lines.append(f"**板块过滤**:{screening_result.get('sector_filter') or '全市场'}\n")
      
          # 板块热度
          sector_heat = screening_result.get("sector_heat", {})
          if sector_heat:
              lines.append("### 板块热度排行\n")
              lines.append("| 板块 | 热度分 |")
              lines.append("|------|--------|")
              for sector, score in list(sector_heat.items())[:8]:
                  lines.append(f"| {sector} | {score} |")
              lines.append("")
      
          # 个股提及排行
          all_mentions = screening_result.get("all_mentions", [])
          if all_mentions:
              lines.append("### 个股提及频率 TOP15\n")
              lines.append("| 个股 | 提及次数 | 相关文章 |")
              lines.append("|------|---------|---------|")
              for m in all_mentions[:15]:
                  titles = "、".join(m["sample_titles"][:2])
                  lines.append(f"| {m['stock']} | {m['count']} | {titles} |")
              lines.append("")
      
          # 短线/长线选股结果
          if trading_type in ("short", "mixed"):
              short_screen = screening_result.get("short_screen", [])
              if short_screen:
                  lines.append("### 短线选股信号 TOP10\n")
                  lines.append("| 个股 | 综合分 | 提及次数 | 相关文章 |")
                  lines.append("|------|--------|---------|---------|")
                  for s in short_screen[:10]:
                      titles = "、".join(s["sample_titles"][:2])
                      lines.append(f"| {s['stock']} | {s['score']} | {s['mentions']} | {titles} |")
                  lines.append("")
      
          if trading_type in ("long", "mixed"):
              long_screen = screening_result.get("long_screen", [])
              if long_screen:
                  lines.append("### 长线选股信号 TOP10\n")
                  lines.append("| 个股 | 综合分 | 提及次数 | 相关文章 |")
                  lines.append("|------|--------|---------|---------|")
                  for s in long_screen[:10]:
                      titles = "、".join(s["sample_titles"][:2])
                      lines.append(f"| {s['stock']} | {s['score']} | {s['mentions']} | {titles} |")
                  lines.append("")
      
          lines.append("## 3. 生成要求\n")
          lines.append(f"请按{author}的{'短线' if trading_type == 'short' else '长线'}选股框架,生成选股分析报告:\n")
      
          if trading_type == "short":
              lines.append("- 聚焦短线龙头和辨识度标的")
              lines.append("- 分析情绪周期位置")
              lines.append("- 评估板块轮动节奏")
              lines.append("- 关注量价关系和资金面")
              lines.append("- 给出仓位建议和操作策略\n")
          else:
              lines.append("- 聚焦基本面质量和估值")
              lines.append("- 分析护城河和竞争优势")
              lines.append("- 评估长期成长空间")
              lines.append("- 关注财务数据和行业趋势")
              lines.append("- 给出买入逻辑和安全边际\n")
      
          lines.append("## 4. 合规要求\n")
          lines.append("- 标注:「AI 风格模拟,不构成投资建议」")
          lines.append("- 不直接荐股,仅分析逻辑")
          lines.append("- 区分事实与判断\n")
      
          lines.append(f"## 5. 输出")
          lines.append(f"保存到:`output/{author}_选股_{today}.md`\n")
      
          return "\n".join(lines)
      
      
      # ─── 主流程 ────────────────────────────────────────────────────────────────────────
      def main():
          parser = argparse.ArgumentParser(description="大V风格化选股")
          parser.add_argument("--author", required=True, help="指定大V名称")
          parser.add_argument("--sector", help="板块过滤(可选)")
          parser.add_argument("--days", type=int, default=7, help="近N天数据(默认7天)")
          parser.add_argument("--count", type=int, default=100, help="获取文章数量(默认100)")
          parser.add_argument("--api-key", help="指定API Key")
          parser.add_argument("--output-dir", default=str(OUTPUT_DIR), help="输出目录")
      
          args = parser.parse_args()
      
          output_dir = Path(args.output_dir) if args.output_dir != str(OUTPUT_DIR) else ensure_output_dir()
          output_dir.mkdir(parents=True, exist_ok=True)
      
          # 加载画像
          step(f"加载画像:{args.author}")
          profile = find_profile(args.author)
          trading_type = detect_trading_type(profile)
          if profile:
              info(f"画像已加载,交易类型:{trading_type}")
          else:
              warn(f"未找到{args.author}的风格画像,使用默认分析")
      
          # 获取数据
          api_key = get_api_key(args.api_key)
          if not api_key:
              sys.exit(1)
          step(f"获取近{args.days}天热文数据...")
          articles = fetch_hot_articles(api_key, args.author, args.days, args.count)
      
          if not articles:
              warn("未获取到文章数据")
              warn("可使用 --sector 过滤板块或增大 --days")
              sys.exit(0)
      
          info(f"共获取 {len(articles)} 篇文章")
      
          # 选股分析
          step("执行选股分析...")
          screening_result = analyze_screening(articles, trading_type, args.sector)
      
          # 生成任务
          task = generate_screen_task(args.author, profile, trading_type, screening_result)
          today = datetime.now().strftime("%Y-%m-%d")
          task_file = output_dir / f"{args.author}_选股任务_{today}.md"
          task_file.write_text(task, encoding="utf-8")
          info(f"选股任务已保存:{task_file}")
      
          # 显示摘要
          print(f"\n{BOLD}{'='*60}{RESET}")
          print(f"{BOLD}  选股分析摘要{RESET}")
          print(f"{BOLD}{'='*60}{RESET}")
          print(f"  大V:{args.author}({trading_type})")
          print(f"  样本:{len(articles)} 篇文章")
      
          mentions = screening_result.get("all_mentions", [])
          if mentions:
              print(f"\n{BOLD}个股提及 TOP10:{RESET}")
              for m in mentions[:10]:
                  print(f"  - {m['stock']}({m['count']}次)")
      
          sector_heat = screening_result.get("sector_heat", {})
          if sector_heat:
              print(f"\n{BOLD}板块热度 TOP5:{RESET}")
              for s, sc in list(sector_heat.items())[:5]:
                  print(f"  - {s}({sc}分)")
      
          print(f"\n{GREEN}✓{RESET} 任务文件:{task_file}")
      
      
      if __name__ == "__main__":
          main()
      
    • sync_articles.py 10.9 KB
      #!/usr/bin/env python3
      """
      增量文章同步 — 拉取大V近7天新文章用于蒸馏补充
      ==============================================
      每次触发 skill 时自动调用,获取最新文章内容,
      为画像增量更新提供原始素材。
      
      接口:广域库 queryWorkList(按公众号微信号/原始ID精确查询,每页固定20条)
      
      Usage:
          python sync_articles.py --author "财躺平"
          python sync_articles.py --author "格兰投研" --days 7 --api-key ak_xxx
      """
      
      import argparse
      import json
      import re
      import sys
      from datetime import datetime
      from pathlib import Path
      
      from common import (
          ACCOUNT_IDS, STOCK_ANALYSTS, PROFILES_DIR, OUTPUT_DIR,
          GREEN, YELLOW, RED, CYAN, BOLD, RESET,
          info, warn, error, step,
          get_api_key, load_profile_json,
          fetch_articles_paginated, ensure_output_dir,
      )
      
      
      # ─── 生成增量摘要 ──────────────────────────────────────────────────────────────────
      def extract_article_info(article):
          """从API返回的文章数据中提取关键字段"""
          # 调试:打印文章所有字段名(仅第一篇时)
          if not getattr(extract_article_info, "_debugged", False):
              if isinstance(article, dict):
                  info(f"API返回字段列表: {list(article.keys())}")
                  extract_article_info._debugged = True
      
          title = str(article.get("title") or article.get("作品标题") or article.get("Title") or "无标题")
          pub_time = str(article.get("publishTime") or article.get("publicTime") or article.get("作品发布时间") or article.get("PublishTime") or "")[:10]
      
          # 尝试多种可能的正文字段名
          content = ""
          for key in ("content", "作品正文", "Content", "workContent", "body", "text", "summary_content", "digest"):
              val = article.get(key)
              if val and str(val).strip():
                  content = str(val)
                  break
      
          # 尝试多种可能的摘要字段名
          summary = ""
          for key in ("summary", "摘要", "Summary", "workSummary", "description", "desc", "abstract"):
              val = article.get(key)
              if val and str(val).strip():
                  summary = str(val)
                  break
      
          # 如果没有摘要,取正文前300字
          if not summary and content:
              summary = content[:300] + ("..." if len(content) > 300 else "")
      
          # 如果没有正文但有摘要,用摘要作为正文
          if not content and summary:
              content = summary
      
          # 如果正文和摘要都为空,用标题作为摘要(queryWorkList 列表接口不返回正文)
          if not content and not summary:
              summary = f"(列表接口未返回正文,请访问 workUrl 获取全文)标题:{title}"
      
          # 提取 workUrl
          work_url = str(article.get("workUrl") or article.get("sourceUrl") or article.get("url") or "")
      
          # 提取正文中的股票/指数关键词
          stock_patterns = [
              r'([\u4e00-\u9fa5]{2,6}(?:股份|集团|科技|电子|银行|保险|证券|汽车|医药|能源|电力|控股|实业))',
              r'((?:上证|深证|沪深|创业板|科创板|北证)[\u4e00-\u9fa5]*)',
              r'((?:茅台|五粮液|比亚迪|宁德时代|腾讯|阿里|美团|京东|小米|华为)[\u4e00-\u9fa5]*)',
              r'(\d{6})\s*(?:SH|SZ|HK|\.SH|\.SZ)',
          ]
          mentioned_stocks = set()
          for pattern in stock_patterns:
              matches = re.findall(pattern, title + " " + summary + " " + content[:1000])
              mentioned_stocks.update(matches)
      
          word_count = len(content) if content else 0
      
          return {
              "title": title,
              "date": pub_time,
              "summary": summary[:500],
              "word_count": word_count,
              "mentioned_stocks": list(mentioned_stocks)[:10],
              "content_length": len(content) if content else 0,
              "work_url": work_url,
              "has_full_content": bool(content and word_count > 0),
          }
      
      
      def generate_sync_report(author, profile, articles):
          """生成增量同步报告"""
          today = datetime.now().strftime("%Y-%m-%d")
          lines = []
      
          lines.append(f"# 增量文章同步 — {author}")
          lines.append(f"> 同步日期:{today}")
          lines.append(f"> 数据来源:queryWorkList({ACCOUNT_IDS.get(author, '未知')})")
          lines.append(f"> 新增文章数:{len(articles)}")
          lines.append("")
      
          if profile:
              lines.append(f"## 当前画像信息")
              lines.append(f"- 蒸馏日期:{profile.get('蒸馏日期', '未知')}")
              lines.append(f"- 样本文章数:{profile.get('样本文章数', '未知')}")
              lines.append(f"- 置信度:{profile.get('蒸馏置信度', '未知')}")
      
              # 获取画像中的常提及个股
              focus = profile.get("关注图谱", {})
              existing_stocks = focus.get("常提及个股", [])
              lines.append(f"- 画像常提及个股:{', '.join(existing_stocks) if existing_stocks else '无'}")
              lines.append("")
      
          if not articles:
              lines.append("## 结果")
              lines.append("近7天无新文章,画像无需更新。")
              return "\n".join(lines)
      
          # 文章详情
          lines.append(f"## 新增文章详情(共{len(articles)}篇)\n")
      
          all_new_stocks = set()
          total_words = 0
      
          for i, art in enumerate(articles, 1):
              info_dict = extract_article_info(art)
              lines.append(f"### 文章 {i}:{info_dict['title']}")
              lines.append(f"- 发布日期:{info_dict['date']}")
              if info_dict.get('has_full_content'):
                  lines.append(f"- 字数:{info_dict['word_count']}")
              else:
                  lines.append(f"- 字数:0(列表接口未返回正文)")
              if info_dict.get('work_url'):
                  lines.append(f"- 文章链接:{info_dict['work_url']}")
              if info_dict['mentioned_stocks']:
                  lines.append(f"- 提及个股:{', '.join(info_dict['mentioned_stocks'])}")
                  all_new_stocks.update(info_dict['mentioned_stocks'])
              lines.append(f"- 摘要:")
              lines.append(f"  {info_dict['summary'][:200]}")
              lines.append("")
              total_words += info_dict['word_count']
      
          # 增量分析
          lines.append("## 增量分析\n")
      
          if profile:
              # 对比新文章中出现的股票 vs 画像中已有的
              focus = profile.get("关注图谱", {})
              existing_stocks = set(focus.get("常提及个股", []))
              new_stocks = all_new_stocks - existing_stocks
      
              if new_stocks:
                  lines.append(f"### 新出现的个股/指数(画像中未收录)")
                  for s in sorted(new_stocks):
                      lines.append(f"- {s}")
                  lines.append("")
                  lines.append(f"**建议**:如新个股在多篇新文章中反复出现,考虑加入画像「常提及个股」列表。\n")
              else:
                  lines.append("### 个股覆盖情况")
                  lines.append("新文章中提及的个股均已在画像中收录,无需更新。\n")
      
              # 字数对比(仅当有正文数据时才比较)
              existing_avg = profile.get("内容深度", {}).get("平均字数", 0)
              if isinstance(existing_avg, str):
                  m = re.search(r'(\d+)', existing_avg)
                  existing_avg = int(m.group(1)) if m else 0
              new_avg = total_words // len(articles) if articles else 0
              if existing_avg > 0 and new_avg > 0:
                  diff_pct = ((new_avg - existing_avg) / existing_avg) * 100
                  if abs(diff_pct) > 20:
                      lines.append(f"### 字数变化")
                      lines.append(f"- 画像平均字数:{existing_avg}字")
                      lines.append(f"- 近期平均字数:{new_avg}字({diff_pct:+.0f}%)")
                      lines.append(f"**建议**:更新画像「平均字数」字段。\n")
              elif new_avg == 0:
                  lines.append("### 字数变化")
                  lines.append("- 列表接口未返回正文,无法对比字数。")
                  lines.append("- **建议**:访问文章链接获取全文后,手动对比字数。\n")
      
          # 蒸馏补充指令
          lines.append("## 蒸馏补充指令\n")
          lines.append("请阅读以上新增文章,检查是否需要更新画像:")
          lines.append("1. 常提及个股是否有新增")
          lines.append("2. 标志性表达是否有新发现")
          lines.append("3. 投资体系描述是否需要调整")
          lines.append("4. 平均字数/更新频率是否需要修正")
          lines.append("5. 如无需更新,跳过;如需更新,修改对应字段")
      
          return "\n".join(lines)
      
      
      # ─── 主流程 ────────────────────────────────────────────────────────────────────────
      def main():
          parser = argparse.ArgumentParser(description="增量文章同步")
          parser.add_argument("--author", required=True, help="大V名称")
          parser.add_argument("--days", type=int, default=7, help="拉取近N天文章(默认7)")
          parser.add_argument("--api-key", help="API Key")
      
          args = parser.parse_args()
      
          # 获取 API Key
          api_key = get_api_key(args.api_key)
          if not api_key:
              sys.exit(1)
      
          # 查找微信号
          account = ACCOUNT_IDS.get(args.author)
          if not account:
              error(f"未知分析师:{args.author},可用:{', '.join(ACCOUNT_IDS.keys())}")
              sys.exit(1)
      
          # 加载画像(可选)
          profile, profile_path = load_profile_json(args.author)
          if profile:
              info(f"已加载画像:{args.author}")
          else:
              warn(f"未找到画像,将仅输出文章数据")
      
          # 拉取文章
          step(f"拉取{args.author}({account})近{args.days}天新文章...")
          articles = fetch_articles_paginated(
              api_key, account, account_name=args.author,
              days=args.days, count=50,
              label=f"获取{args.author}近{args.days}天文章",
          )
      
          if not articles:
              warn(f"近{args.days}天无新文章")
      
          # 生成报告
          OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
          today = datetime.now().strftime("%Y-%m-%d")
          report = generate_sync_report(args.author, profile, articles)
      
          report_file = OUTPUT_DIR / f"{args.author}_增量同步_{today}.md"
          report_file.write_text(report, encoding="utf-8")
          info(f"同步报告已生成:{report_file}")
      
          # 输出摘要
          print(f"\n{BOLD}{'='*50}{RESET}")
          print(f"{BOLD}  增量文章同步完成{RESET}")
          print(f"{'='*50}")
          print(f"  大V:{args.author}")
          print(f"  时间范围:近{args.days}天")
          print(f"  新增文章:{len(articles)} 篇")
          print(f"  报告文件:{report_file}")
          if len(articles) > 0:
              print(f"  {BOLD}→ AI 请阅读报告,判断是否需要更新画像{RESET}")
          else:
              print(f"  {GREEN}→ 无需更新画像{RESET}")
          print(f"{'='*50}\n")
      
      
      if __name__ == "__main__":
          main()
      
    • validate_profile.py 11.6 KB
      #!/usr/bin/env python3
      """
      蒸馏准确性校验 — 验证风格画像是否真实反映大V写作特征
      =====================================================
      从 API 拉取大V近期文章,与画像声明交叉比对,生成校验任务供 AI 评分。
      
      校验逻辑:
      1. 加载画像 JSON,提取可验证声明(关注个股、标志性表达、投资体系等)
      2. 拉取该大V近期 N 篇文章
      3. 生成校验任务文件:每篇文章配对一组校验问题
      4. AI Agent 阅读任务文件,仅凭画像回答,再与文章正文比对
      5. 输出准确率评分,< 80% 打回补充蒸馏
      
      Usage:
          python validate_profile.py --author "财躺平" --sample 10
          python validate_profile.py --author "格兰投研" --sample 15 --api-key ak_xxx
      """
      
      import argparse
      import json
      import random
      import sys
      from datetime import datetime
      from pathlib import Path
      
      from common import (
          ACCOUNT_IDS, STOCK_ANALYSTS, PROFILES_DIR, OUTPUT_DIR,
          GREEN, YELLOW, RED, CYAN, BOLD, RESET,
          info, warn, error, step,
          get_api_key, load_profile_json,
          fetch_articles_paginated, ensure_output_dir,
      )
      
      
      # ─── 提取画像可验证声明 ────────────────────────────────────────────────────────────
      def extract_profile_claims(profile):
          """从画像JSON中提取可验证的声明"""
          claims = {}
      
          # 1. 关注个股
          focus = profile.get("关注图谱", {})
          stocks = focus.get("常提及个股", [])
          claims["常提及个股"] = stocks if isinstance(stocks, list) else []
      
          # 2. 核心赛道
          sectors = focus.get("核心赛道", [])
          claims["核心关注赛道"] = sectors if isinstance(sectors, list) else []
      
          # 3. 标志性表达
          expr = profile.get("表达风格", {})
          sig_phrases = expr.get("标志性表达", [])
          claims["标志性表达"] = sig_phrases if isinstance(sig_phrases, list) else []
      
          # 4. 投资格言
          mottos = focus.get("投资格言", [])
          claims["投资格言"] = mottos if isinstance(mottos, list) else []
      
          # 5. 常用指标
          indicators = focus.get("常用指标", [])
          claims["常用分析指标"] = indicators if isinstance(indicators, list) else []
      
          # 6. 投资体系关键特征
          inv = profile.get("投资体系", {})
          claims["核心投资流派"] = inv.get("核心流派", "")
          claims["持仓周期"] = inv.get("持仓周期", "")
          claims["止损纪律"] = inv.get("止损纪律", "")
      
          # 7. 表达风格关键特征
          claims["标题风格"] = expr.get("标题风格", "")
          claims["开头模式"] = expr.get("开头模式", "")
          claims["结尾模式"] = expr.get("结尾模式", "")
          claims["语气"] = expr.get("语气", "")
      
          # 8. 内容深度
          depth = profile.get("内容深度", {})
          claims["平均字数"] = depth.get("平均字数", 0)
          claims["更新频率"] = depth.get("更新频率", "")
      
          return claims
      
      
      # ─── 生成校验任务 ──────────────────────────────────────────────────────────────────
      def generate_validation_task(author, profile, articles, sample_count=10):
          """生成蒸馏准确性校验任务文件"""
          claims = extract_profile_claims(profile)
          today = datetime.now().strftime("%Y-%m-%d")
      
          # 随机抽样文章
          if len(articles) > sample_count:
              sampled = random.sample(articles, sample_count)
          else:
              sampled = articles
      
          lines = []
          lines.append(f"# 蒸馏准确性校验任务 — {author}")
          lines.append(f"> 日期:{today}")
          lines.append(f"> 抽样文章数:{len(sampled)}/{len(articles)}")
          lines.append(f"> 画像置信度:{profile.get('蒸馏置信度', '未知')}")
          lines.append(f"> 样本文章数:{profile.get('样本文章数', '未知')}")
          lines.append(f"> ⚠️ AI 风格模拟,不构成投资建议\n")
      
          # ── 画像声明摘要 ──
          lines.append("## 一、画像声明摘要(仅凭此回答问题)\n")
          lines.append(f"```json")
          lines.append(json.dumps(claims, ensure_ascii=False, indent=2))
          lines.append(f"```\n")
      
          # ── 校验问题 ──
          lines.append("## 二、校验问题(请仅凭画像回答,然后再看文章验证)\n")
          lines.append("### A. 个股提及校验\n")
          lines.append("对以下每篇文章,请仅凭画像的「常提及个股」列表预测:该文章最可能提到哪些股票/指数?")
          lines.append("然后与文章实际内容比对,标注预测是否正确。\n")
      
          for i, art in enumerate(sampled, 1):
              title = str(art.get("title", art.get("作品标题", "未知标题")))
              pub = str(art.get("publishTime") or art.get("publicTime") or art.get("作品发布时间") or "")[:10]
              content = str(art.get("content") or art.get("作品正文") or art.get("summary") or "")
              # 截取前500字作为正文摘要
              content_preview = content[:500] if len(content) > 500 else content
      
              lines.append(f"#### 文章 {i}:{title}")
              lines.append(f"- 发布日期:{pub}")
              lines.append(f"- 正文摘要(前500字):")
              lines.append(f"```")
              lines.append(content_preview)
              lines.append(f"```")
              lines.append(f"- **画像预测**:(请AI填写)该文章可能提及的个股/指数")
              lines.append(f"- **实际验证**:(请AI阅读正文后填写)文章实际提及的个股/指数")
              lines.append(f"- **判定**:✅ 命中 / ❌ 未命中 / ⚠️ 部分命中\n")
      
          lines.append("### B. 风格特征校验\n")
          lines.append("对以下维度,判断画像描述是否与文章实际风格一致:\n")
      
          style_checks = [
              ("语气一致性", f"画像描述语气为:{claims.get('语气', '未知')}。请判断文章实际语气是否匹配。"),
              ("标志性表达", f"画像标志性表达:{', '.join(claims.get('标志性表达', [])[:5])}。请检查文章中是否出现这些表达或类似表达。"),
              ("标题风格", f"画像标题风格:{claims.get('标题风格', '未知')}。请判断文章标题是否符合该描述。"),
              ("开头模式", f"画像开头模式:{claims.get('开头模式', '未知')}。请判断文章开头是否符合该描述。"),
              ("结尾模式", f"画像结尾模式:{claims.get('结尾模式', '未知')}。请判断文章结尾是否符合该描述。"),
              ("文章长度", f"画像平均字数:{claims.get('平均字数', '未知')}字。请估算文章实际字数并判断是否在±30%范围内。"),
          ]
      
          for name, desc in style_checks:
              lines.append(f"#### {name}")
              lines.append(f"- 校验说明:{desc}")
              lines.append(f"- **判定**:✅ 一致 / ❌ 不一致 / ⚠️ 部分一致\n")
      
          lines.append("### C. 投资体系校验\n")
          lines.append("综合所有抽样文章,判断画像的投资体系描述是否准确:\n")
      
          system_checks = [
              ("核心投资流派", claims.get("核心投资流派", "")),
              ("持仓周期", claims.get("持仓周期", "")),
              ("止损纪律", claims.get("止损纪律", "")),
          ]
      
          for name, desc in system_checks:
              lines.append(f"#### {name}")
              lines.append(f"- 画像描述:{desc}")
              lines.append(f"- **实际表现**:(请AI综合文章判断)")
              lines.append(f"- **判定**:✅ 准确 / ❌ 不准确 / ⚠️ 部分准确\n")
      
          # ── 评分模板 ──
          lines.append("## 三、评分汇总\n")
          lines.append("| 校验维度 | 总题数 | 命中数 | 准确率 |")
          lines.append("|---------|--------|--------|--------|")
          lines.append(f"| A. 个股提及 | {len(sampled)} | ? | ?% |")
          lines.append(f"| B. 风格特征 | {len(style_checks)} | ? | ?% |")
          lines.append(f"| C. 投资体系 | {len(system_checks)} | ? | ?% |")
          lines.append("| **综合** | ? | ? | **?%** |")
          lines.append("")
          lines.append("**准出标准:综合准确率 ≥ 80%**")
          lines.append("**< 80% 需补充蒸馏:仔细阅读文章正文,更新画像中不准确的字段**")
          lines.append("")
          lines.append("## 四、补充蒸馏建议(如准确率 < 80%)\n")
          lines.append("请列出画像中需要修正的具体字段和建议修正内容:\n")
          lines.append("| 字段 | 当前值 | 建议修正为 | 依据 |")
          lines.append("|------|--------|-----------|------|")
      
          return "\n".join(lines)
      
      
      # ─── 主流程 ────────────────────────────────────────────────────────────────────────
      def main():
          parser = argparse.ArgumentParser(description="蒸馏准确性校验")
          parser.add_argument("--author", required=True, help="大V名称")
          parser.add_argument("--sample", type=int, default=10, help="抽样文章数(默认10)")
          parser.add_argument("--days", type=int, default=30, help="拉取近N天文章(默认30)")
          parser.add_argument("--api-key", help="API Key")
      
          args = parser.parse_args()
      
          # 加载画像
          profile, _ = load_profile_json(args.author)
          if not profile:
              error(f"未找到大V「{args.author}」的风格画像")
              sys.exit(1)
          info(f"已加载画像:{args.author}")
      
          # 获取 API Key
          api_key = get_api_key(args.api_key)
          if not api_key:
              error("蒸馏校验需要 API Key 拉取文章")
              sys.exit(1)
      
          # 查找微信号
          account = ACCOUNT_IDS.get(args.author)
          if not account:
              error(f"未知分析师:{args.author},可用:{', '.join(ACCOUNT_IDS.keys())}")
              sys.exit(1)
      
          # 拉取文章
          step(f"拉取{args.author}({account})近{args.days}天文章...")
          articles = fetch_articles_paginated(
              api_key, account, account_name=args.author,
              days=args.days, count=max(args.sample * 3, 50),
              label=f"获取{args.author}文章",
          )
      
          if not articles:
              error(f"未获取到{args.author}的文章,请检查 API Key 和账号名")
              sys.exit(1)
      
          info(f"共获取 {len(articles)} 篇文章")
      
          # 生成校验任务
          OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
          today = datetime.now().strftime("%Y-%m-%d")
          task = generate_validation_task(args.author, profile, articles, args.sample)
      
          task_file = OUTPUT_DIR / f"{args.author}_蒸馏校验_{today}.md"
          task_file.write_text(task, encoding="utf-8")
          info(f"校验任务已生成:{task_file}")
      
          # 输出摘要
          print(f"\n{BOLD}{'='*60}{RESET}")
          print(f"{BOLD}  蒸馏准确性校验任务已就绪{RESET}")
          print(f"{'='*60}")
          print(f"  大V:{args.author}")
          print(f"  画像置信度:{profile.get('蒸馏置信度', '未知')}")
          print(f"  样本文章数:{profile.get('样本文章数', '未知')}")
          print(f"  本次拉取:{len(articles)} 篇")
          print(f"  抽样校验:{min(args.sample, len(articles))} 篇")
          print(f"  任务文件:{task_file}")
          print(f"{'─'*60}")
          print(f"  {BOLD}校验流程:{RESET}")
          print(f"  1. AI 仅凭画像声明回答校验问题(不看文章正文)")
          print(f"  2. 然后阅读文章正文,比对预测与实际内容")
          print(f"  3. 计算综合准确率")
          print(f"  4. {GREEN}≥ 80% → 蒸馏通过{RESET}")
          print(f"  5. {RED}< 80% → 需补充蒸馏,更新画像{RESET}")
          print(f"{'='*60}\n")
      
      
      if __name__ == "__main__":
          main()
      
  • README.en.md 5.3 KB
    # Stock Analysis Expert / stock-analysis
    
    ---
    
    ## Overview
    
    Distills 200+ articles from 5 top WeChat public account stock analysts into structured style profiles, enabling multi-mode AI-powered investment analysis with natural language.
    
    **Core Value**
    
    - **Multi-Perspective Cross-Validation**: Analyze the same target through 5 analyst styles, uncovering consensus and divergence to avoid single-view bias
    - **Stylized Deep Interpretation**: Each analyst has a unique trading system and expressive style, with analysis closely matching authentic thinking patterns
    - **Dual Quality Assurance**: All generated content passes dual-layer auditing (surface features + thinking-layer features) before output
    
    **Target Users**
    
    - 📈 **Individual Investors** — Get multi-style, multi-dimensional investment analysis references to support decision-making
    - 📝 **Content Creators** — Learn analyst frameworks and expressive techniques to improve content quality
    - 🔍 **Investment Beginners** — Quickly understand different investment schools of thought through stylized analysis
    
    ---
    
    ## Features
    
    ### Core Capabilities
    
    - **Multi-Analyst Comparison**: 5 analysts analyze the same stock/sector side by side, highlighting consensus and divergence
    - **Daily Market Review**: Generate daily market reviews in any analyst's style for a unique perspective on market sentiment
    - **Sector Funnel**: Four-layer screening (trend → leaders → certainty → strategy) under analyst frameworks to penetrate market hotspots
    - **Short-Term Stock Screening**: Extract stock mention frequency from analyst articles, quantifying analyst attention for screening
    - **Portfolio Review**: Retrieve each analyst's historical views on your holdings and diagnose your portfolio through their trading frameworks
    - **Earnings Analysis**: Deep-dive into company financial reports through any analyst's lens with multi-dimensional fundamental analysis
    
    ### Highlights
    
    - **5 Analyst Style Profiles**: Extracted from 200+ real articles across six DNA dimensions (trading system / market judgment / express style / content depth / interaction patterns / focus map)
    - **Real-Time Market Data Collection**: Latest market data gathered via web search with every figure sourced, eliminating AI fabrication
    - **Dual-Layer Content Audit**: Surface-level features + thinking-layer features verified together; only output when composite score meets threshold
    - **Continuously Evolving Profiles**: Supports incremental syncing of latest analyst articles with automatic profile updates
    
    ---
    
    ## API Key Acquisition & Security
    
    - This skill requires the environment variable: `REDFOX_API_KEY`.
    - `REDFOX_API_KEY` is provided by [RedFoxHub](https://redfox.hk/settings/api-keys?source=github) (`https://redfox.hk`).
    - Please visit [RedFoxHub](https://redfox.hk?source=github) to register and obtain your `REDFOX_API_KEY`.
    - Configure the `REDFOX_API_KEY` environment variable on your device before using this skill.
    - Before providing your key, verify its source, scope, validity period, and whether reset/revocation is supported.
    - Do not hardcode or expose the key in plain text within code, prompts, logs, or output files.
    
    ---
    
    ## Usage Guide
    
    Describe your needs in natural language — no commands to memorize.
    
    ### Quick Reference
    
    | Intent | Example | Result |
    |--------|---------|--------|
    | Single Analyst | "Analyze CATL in Maobidao's style" | Generates stylized stock analysis |
    | Multi-Analyst | "Analyze Moutai in all 5 analysts' styles" | Side-by-side comparison of all perspectives |
    | Sector Analysis | "Let's analyze the semiconductor sector together" | Funnel screening with certainty assessment |
    | Stock Screening | "Screen semiconductor shorts in Caitangping's style" | Stocks ranked by analyst attention frequency |
    | Portfolio Review | "Review my holdings in Touzimingjian's style" | Portfolio diagnosis through analyst framework |
    | Earnings Review | "Review Moutai's latest earnings" | Stylized multi-dimensional earnings analysis |
    | Thesis Tracking | "Check Caitangping's view on China Satellite" | Historical view retrieval with track record |
    
    ---
    
    ## Use Cases
    
    | Scenario | Role | Example Query | Benefit |
    |----------|------|--------------|---------|
    | Stock Selection | Individual Investor | "Is Moutai worth buying now in Zhongshenheibai's style?" | Value-investing safety margin assessment |
    | Sector Research | Industry Analyst | "Analyze the semiconductor sector" | Cross-perspective industrial logic validation |
    | Daily Review | Short-Term Trader | "Write today's market review in Maobidao's style" | Stylized understanding of daily market dynamics |
    | Portfolio Diagnosis | Portfolio Manager | "Review my holdings in all 5 analysts' styles" | Uncover overlooked risks and opportunities |
    | Learning Frameworks | Investment Beginner | "How does Gelantouyan analyze an industry?" | Rapid understanding of different investment approaches |
    
    ---
    
    ## Important Notes
    
    - ⚠️ All analysis content is **AI style simulation**, unrelated to the actual analysts, and **does NOT constitute investment advice**
    - 📊 Market data is collected via web search with sources annotated per figure; if inaccuracies exist, provide real data for regeneration
    - 🔄 On first daily use, you'll be asked whether to sync latest analyst articles (article sync consumes credits per analyst; call recording is free)
    
  • README.md 4.7 KB
    # 股票分析专家 / stock-analysis
    
    ---
    
    ## 简介
    
    将5位知名公众号股票投资大V(猫笔刀、格兰投研、投资明见、财躺平、终身黑白)200+篇文章的投资理念和操作方法蒸馏为结构化风格画像,用自然语言就能让AI模拟大V风格进行多模式投资分析。
    
    **核心价值**
    
    - **多视角交叉验证**:同一标的用5位大V风格分别分析,发现共识与分歧,避免单一偏见
    - **风格化深度解读**:每位大V有独特的交易体系和表达风格,分析贴合真实思维模式
    - **双重质量保障**:生成内容经表层特征(标志性表达/文章结构)+思维层特征(核心思维/论证方式)双重审计后才输出
    
    **适用对象**
    
    - 📈 **个人投资者** — 获取多风格、多维度的投资分析参考,辅助决策
    - 📝 **内容创作者** — 学习大V的分析框架和表达技巧,提升内容质量
    - 🔍 **投研新手** — 通过风格化分析快速理解不同投资流派的思维方式
    
    ---
    
    ## 功能特性
    
    ### 核心功能
    
    - **多V并行对比**:5位大V风格化分析同一标的或板块,并排展示共识点与分歧点
    - **每日复盘**:按指定大V风格生成当日盘面复盘文章,从独特视角理解市场情绪
    - **板块漏斗**:在大V分析框架下四层筛选(赛道→龙头→确定性→操作),穿透市场热点
    - **短线选股**:从公众号热文中提取个股被提及的频率,按大V关注度量化筛选
    - **组合复盘**:检索大V对各持仓标的的历史观点,按交易框架全面诊断你的持仓
    - **财报点评**:按大V风格深度解读公司财报,多维度分析基本面
    
    ### 特色亮点
    
    - **5位大V风格蒸馏**:基于200+篇真实文章提炼六大维度DNA(交易体系/市场判断/表达风格/内容深度/互动特征/热点图谱),非模板化生成
    - **盘面数据实时采集**:通过联网搜索获取最新行情数据,每个数值标注来源,杜绝AI编造
    - **内容质量双审计**:表层特征+思维层特征双重校验,综合评分达标才输出
    - **画像持续进化**:支持增量同步大V最新文章,自动更新风格画像
    
    ---
    
    ## 密钥获取与安全说明
    
    - 本技能需要使用环境变量:`REDFOX_API_KEY`。
    - `REDFOX_API_KEY` 由 [红狐 hub](https://redfox.hk/settings/api-keys?source=github) (`https://redfox.hk`)提供。
    - 请前往 [红狐 hub](https://redfox.hk?source=github) 注册账号,获取 `REDFOX_API_KEY`。
    - 配置设备环境变量 `REDFOX_API_KEY` 后使用本技能。
    - 在提供密钥前,请先确认密钥来源、可用范围、有效期及是否支持重置/撤销。
    - 禁止在代码、提示词、日志或输出文件中硬编码/明文暴露密钥。
    
    ---
    
    ## 使用指南
    
    直接用自然语言描述需求,无需记忆命令。
    
    ### 常用说法速查
    
    | 意图 | 示例话术 | 效果 |
    |------|---------|------|
    | 单大V分析 | "用猫笔刀的风格分析宁德时代适不适合买入" | 生成猫笔刀风格的个股分析 |
    | 多V对比 | "用5位大V的风格分别分析茅台" | 并排展示5位大V对同一标的的看法 |
    | 板块分析 | "共同分析航天板块" | 漏斗筛选板块内标的,评估确定性 |
    | 短线选股 | "用财躺平的风格筛选半导体短线" | 从大V热文中提取高关注度个股 |
    | 组合复盘 | "用投资明见的风格复盘我的持仓:中国卫星、航发动力" | 按大V交易框架诊断持仓组合 |
    | 财报分析 | "点评贵州茅台最新财报" | 风格化解读财报,多维度分析 |
    | 观点追踪 | "查下财躺平对中国卫星的观点" | 检索大V历史观点,跟踪判断兑现情况 |
    
    ---
    
    ## 使用场景
    
    | 场景 | 角色 | 示例问法 | 收益 |
    |------|------|---------|------|
    | 选股决策 | 个人投资者 | "用终身黑白的风格分析茅台现在还值不值得买" | 从价值投资视角评估安全边际 |
    | 板块研究 | 行业研究员 | "共同分析半导体板块" | 多视角产业链逻辑交叉验证 |
    | 每日复盘 | 短线交易者 | "用猫笔刀的风格写一篇今日复盘" | 风格化理解当日盘面与情绪 |
    | 持仓诊断 | 组合管理者 | "用5位大V的风格复盘我的持仓:中国卫星、航发动力" | 发现持仓中被忽视的风险或机会 |
    | 学习框架 | 投资新手 | "格兰投研是怎么分析一个行业的" | 快速理解不同投资流派的思维模式 |
    
    ---
    
    ## 重要说明
    
    - ⚠️ 所有分析内容均为 **AI 风格模拟**,与各公众号大V本人无关,**不构成任何投资建议**
    - 📊 盘面数据通过联网搜索获取,每个数据标注来源,如有偏差可提供真实数据重新生成
    - 🔄 每日首次使用会询问是否同步大V最新文章(文章同步按次扣费,调用记录上报不扣费)
    
  • SKILL.md 8.5 KB
    ---
    name: stock-analysis
    description: 基于5位公众号股票投资大V(猫笔刀、格兰投研、投资明见、财躺平、终身黑白)200+篇文章蒸馏形成的风格画像,通过 AI Agent 实现风格化投资分析,支持单大V复盘、多V并行对比、板块漏斗筛选、短线选股、组合复盘、财报点评、观点跟踪等多模式。当用户请求股票复盘、大V风格分析、多V对比、板块分析、短线选股、组合复盘、财报点评时使用。触发词:股票复盘、大V分析、模拟大V、风格化复盘、多V对比、每日复盘、板块分析、选股、组合复盘、财报点评、大V观点。
    ---
    
    # 股票分析专家-公众号股票大V蒸馏
    
    > ⚠️ **免责声明**:本工具仅供学习研究使用,所有生成内容均为 AI 风格模拟,**不构成任何投资建议**。
    > 盘面数据必须实时采集,**禁止 LLM 编造涨跌停、资金流向等数据**。
    
    ---
    
    ## 📝 简介
    
    将5位公众号股票投资大V的200+篇文章蒸馏为结构化风格画像(六维DNA:交易体系/市场判断/表达风格/内容深度/互动特征/热点图谱),通过 AI Agent 实现多模式风格化分析。盘面数据通过 WebSearch 实时采集并逐数值标注来源,生成内容经质量审计(表层+思维层双重校验)后准出。
    
    ---
    
    ## ✨ 功能特性
    
    | 功能模块 | 能力描述 | 核心价值 |
    |---------|---------|----------|
    | 多V并行对比 | 5位大V风格化分析同一标的/板块,输出共识与分歧 | 多视角交叉验证,避免单一偏见 |
    | 每日复盘 | 按指定大V风格生成当日盘面复盘文章 | 风格化视角理解市场情绪与操作逻辑 |
    | 板块漏斗 | 大V分析框架下四层筛选:赛道→龙头→确定性→操作 | 产业链逻辑穿透市场热点 |
    | 短线选股 | 从公众号热文提取个股提及频率,按交易类型筛选 | 大V关注度量化选股 |
    | 组合复盘 | 检索大V对各标的历史观点,按交易框架评估持仓 | 风格化持仓诊断与风险评估 |
    | 财报点评 | 按大V风格解读公司财报,多维度分析基本面 | 基本面专业解读风格化输出 |
    
    ---
    
    ## 🔑 鉴权
    
    前往 [redfox.hk](https://www.redfox.hk/login?source=github) 注册获取 API Key,通过以下方式配置:
    
    ```bash
    # 环境变量
    export REDFOX_API_KEY="ak_你的密钥"
    
    # 或配置文件 ~/.qoder/apis/redfox.json
    {"api_key": "ak_你的密钥"}
    ```
    
    API Key 用于:
    - **queryWorkList**(文章同步):按次扣费,每位大V同步扣 1 次积分
    - **record/save**(调用记录上报):仅用于统计,**不扣费**
    
    ---
    
    ## 快速开始
    
    自然语言直接描述需求,Agent 自动识别模式:
    
    | 场景 | 触发示例 |
    |------|---------|
    | 单大V分析 | "用猫笔刀的风格分析宁德时代" |
    | 多V对比 | "用5位大V的风格分别分析茅台" |
    | 板块分析 | "共同分析航天板块" |
    | 短线选股 | "用财躺平的风格筛选半导体短线" |
    | 组合复盘 | "用投资明见的风格复盘我的持仓:中国卫星、航发动力" |
    | 财报点评 | "点评贵州茅台最新财报" |
    | 每日复盘 | "用猫笔刀的风格写一篇今日复盘" |
    | 观点跟踪 | "查下财躺平对中国卫星的观点" |
    
    ---
    
    ## 预置大V画像
    
    `profiles/` 目录下每位大V一份 JSON 画像,开箱即用:
    
    | 大V | 交易类型 | 核心风格 |
    |-----|---------|---------|
    | 财躺平 | 短线+趋势 | 宏观趋势+逆向思维,道法自然哲学,"老粉都知道" |
    | 猫笔刀 | 短线 | 多资产视角+周期推理,幽默随性,"今晚舅酱" |
    | 格兰投研 | 长线 | 产业链深度研报型,数据驱动,"产业景气决定一切" |
    | 投资明见 | 长线 | 纯技术面趋势跟踪,指数为核心,"趋势是最好的朋友" |
    | 终身黑白 | 长线 | 深度价值投资,护城河+安全边际,"慢慢变富" |
    
    ---
    
    ## 执行流程
    
    ### Phase -1:增量文章同步(触发时询问)
    
    > 当日首次触发时**必须先询问用户**是否同步大V最新文章(每位扣 1 次积分)。
    
    三种选择:跳过 / 部分同步(指定大V)/ 全部同步(5位,完成后**必须进入 Phase 5 补充蒸馏**)。
    
    ```bash
    python3 "$SKILL_PATH/scripts/analyze.py" --author "财躺平" --mode sync
    ```
    
    ### Phase 0:环境准备 + AI 偏见自查
    
    ```bash
    python3 "$SKILL_PATH/scripts/analyze.py" --check-env
    ```
    
    检查依赖和画像就绪后,按以下标准评估信息丰富度并写入生成内容开头:
    
    | 等级 | 特征 | 应对策略 |
    |------|------|---------|
    | **A 级**(信息充裕) | 画像高置信、盘面完整 | **反面检验**:大V看多时,聪明人为什么看空? |
    | **B 级**(信息适中) | 画像中置信、部分数据缺失 | 每个推算数据标注置信度 |
    | **C 级**(信息稀缺) | 画像低置信、数据缺失 | **第一性原理提问**,不拼凑"看起来完整"的报告 |
    
    偏见自查清单:
    - "确定性"感受来自盘面数据还是大V画像语气?
    - 去掉大V画像仅看盘面,结论会变吗?
    - 是否只呈现了看多理由,忽略反面论据?
    
    ### Phase 1:盘面数据采集
    
    > **铁律:盘面数据必须实时采集,禁止 LLM 编造。**
    
    数据获取:用户手动输入 > WebSearch 搜索 > **禁止 LLM 编造**。
    
    关键数据交叉验证(≥2 个来源,差异 > 1% 须标注):
    
    | 数据项 | 主源 | 副源 |
    |--------|------|------|
    | 涨跌停家数 | 交易所官网 | 东方财富/同花顺 |
    | 成交额 | 交易所官网 | 万得/同花顺 |
    | 北向资金 | 沪深港通官网 | 东方财富 |
    | 主力资金流向 | 东方财富 | 同花顺 |
    
    ### Phase 2:风格画像加载
    
    ```bash
    python3 "$SKILL_PATH/scripts/analyze.py" --list-profiles
    ```
    
    ### Phase 3:内容生成
    
    **数据来源披露(所有场景强制)**:每个盘面数据后括号标注来源域名(如"同花顺 10jqka.com.cn"),禁止只写"AI 推断";文末添加纠错引导"数据如有不准确请提供真实数据,我将重新生成"。
    
    **质量审计规则**:表层特征(标志性表达/交易体系/文章结构/盘面指标/语气/术语)权重 50%,思维层特征(核心思维/论证方式/哲学价值观)权重 50%。综合评分 > 70 准出,< 70 打回重生成。
    
    **模式命令速查**:
    
    | 模式 | CLI 命令 | 输出 |
    |------|---------|------|
    | daily | `--author "财躺平" --mode daily` | `output/{大V}_复盘_{日期}.md` |
    | team | `--mode team --authors "财躺平,格兰投研"` | `output/多V对比_{日期}.md` |
    | sector | `--author "财躺平" --mode sector --sector "航天"` | `output/{大V}_{板块}分析_{日期}.md` |
    | track | `--author "财躺平" --mode track --stock "中国卫星"` | `output/{大V}_{标的}观点跟踪_{日期}.md` |
    | screen | `--author "财躺平" --mode screen --sector "半导体"` | `output/{大V}_选股_{日期}.md` |
    | portfolio | `--author "财躺平" --mode portfolio --stocks "A,B,C"` | `output/{大V}_组合复盘_{日期}.md` |
    | earnings | `--author "格兰投研" --mode earnings --stock "贵州茅台"` | `output/{大V}_{标的}财报_{日期}.md` |
    | sync | `--author "财躺平" --mode sync` | `output/{大V}_增量同步_{日期}.md` |
    
    ### Phase 4:质量审计
    
    ```bash
    python3 "$SKILL_PATH/scripts/quality_audit.py" --article "output/财躺平_复盘_2026-01-15.md" --author "财躺平"
    ```
    
    **合规硬规则(任一违反直接打回)**:
    
    | # | 规则 | 禁止行为 |
    |---|------|---------|
    | 1 | 不承诺收益 | "保证赚钱"、"稳赚不赔" |
    | 2 | 不预测具体收益率 | 承诺具体数字 |
    | 3 | 不暗示内幕 | 暗示有内幕信息 |
    | 4 | 标注数据来源 | 标注"基于公开数据" |
    | 5 | 标注 AI 模拟 | "与原大V无关,仅为 AI 风格模拟" |
    | 6 | 不极端指令 | "全仓买入 xxx" |
    
    所有内容标注「AI 风格模拟,不构成投资建议」;不冒充大V;不直接荐股。
    
    ### Phase 5:画像增量更新
    
    > **铁律:每次同步后必须完成补充蒸馏,不得跳过。**
    
    触发条件:Phase -1 用户选择了同步。
    
    执行流程:
    1. 阅读增量同步报告
    2. 逐项对比需更新内容(新标志性表达/新股票/新指标/分析框架变化/字数变化)→ 修改 JSON,更新蒸馏日期和样本文章数
    3. 无更新则明确告知理由
    4. 显著变化标注**风格漂移警告**
    5. 每月全量校验一次 5 位画像
    
    ---
    
    ## 其他资源
    
    - 质量标准详解:[质量准出标准.md](references/质量准出标准.md)
    - 文章模板:[article_template.md](assets/article_template.md)
    - 大V画像:`profiles/{大V名}_profile.json`
    
    ---
    
    ## 依赖
    
    ```bash
    pip install requests
    ```
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related