stock-analysis
基于5位公众号股票投资大V(猫笔刀、格兰投研、投资明见、财躺平、终身黑白)200+篇文章蒸馏形成的风格画像,通过 AI Agent 实现风格化投资分析,支持单大V复盘、多V并行对比、板块漏斗筛选、短线选股、组合复盘、财报点评、观点跟踪等多模式。当用户请求股票复盘、大V风格分析、多V对比、板块分析、短线选股、组合复盘、财报点评时使用。触发词:股票复盘、大V分析、模拟大V、风格化复盘、多V对比、每日复盘、板块分析、选股、组合复盘、财报点评、大V观点。
Install
npx skills add https://github.com/redfox-data/redfox-community/tree/main/skills/stock-analysis
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install redfox-data-redfox-community@llmmart
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 用户选择了同步。
执行流程:
- 阅读增量同步报告
- 逐项对比需更新内容(新标志性表达/新股票/新指标/分析框架变化/字数变化)→ 修改 JSON,更新蒸馏日期和样本文章数
- 无更新则明确告知理由
- 显著变化标注风格漂移警告
- 每月全量校验一次 5 位画像
其他资源
- 质量标准详解:质量准出标准.md
- 文章模板:article_template.md
- 大V画像:
profiles/{大V名}_profile.json
依赖
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.
Reviews (0)
No reviews yet.
No comments yet.