
China Stock Analysis
- 2 installs
- 47 repo stars
- Updated February 12, 2026
- agentbay-ai/agentbay-skills
This is a copy of china-stock-analysis by sugarforever - installs and ranking accrue to the original listing.
china-stock-analysis is a Claude Code skill that performs value-investing analysis of China A-share stocks, including screening, financial analysis, peer comparison, and DCF/DDM/relative valuation.
About
china-stock-analysis is a Claude Code skill for value-investing analysis of China A-share stocks, aimed at low-frequency retail investors. It screens stocks by valuation, profitability, growth, dividend, and financial-safety criteria, runs deep single-stock financial analysis, compares peers within an industry, and computes intrinsic value via DCF, DDM, and relative valuation. It uses akshare for public financial data and produces a Chinese analysis report with scoring, risk flags, and a margin-of-safety verdict. A developer invokes it to screen, analyze, compare, or value A-shares.
- Value-investing analysis for China A-shares: screen, analyze, compare, value
- Uses akshare public financial data with DCF, DDM, and relative valuation
- Generates a Chinese analysis report with a margin-of-safety verdict
China Stock Analysis by the numbers
- 2 all-time installs (skills.sh)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
china-stock-analysis capabilities & compatibility
Free; uses akshare public financial data, no API key required.
- Capabilities
- stock screening · financial analysis · valuation · peer comparison
- Use cases
- trading · data analysis · research
- Pricing
- Free
What china-stock-analysis says it does
A股价值投资分析工具,提供股票筛选、个股深度分析、行业对比和估值计算功能。
基于价值投资理论,使用akshare获取公开财务数据,适合低频交易的普通投资者。
npx skills add https://github.com/agentbay-ai/agentbay-skills --skill china-stock-analysisAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 47 |
| Last updated | February 12, 2026 |
| Repository | agentbay-ai/agentbay-skills ↗ |
What it does
An investor uses it to screen A-shares, run financial analysis, compare peers, and compute intrinsic value with a margin of safety.
Who is it for?
Low-frequency retail investors screening, analyzing, comparing, and valuing China A-share stocks by value-investing criteria.
Skip if: High-frequency trading or non-A-share markets; it targets low-frequency ordinary investors.
When should I use this skill?
A user asks to analyze, screen, compare, or value an A-share stock or check its financial health.
What you get
A Chinese analysis report with financial health, valuation, risk flags, a score, and an over/under-valued verdict.
- screening result JSON
- financial analysis report
- valuation result with margin of safety
By the numbers
- 4 core modules (screener, analyzer, comparator, valuation calculator)
- 3 valuation methods (DCF, DDM, relative)
Files
China Stock Analysis Skill
基于价值投资理论的中国A股分析工具,面向低频交易的普通投资者。
When to Use
当用户请求以下操作时调用此skill:
- 分析某只A股股票
- 筛选符合条件的股票
- 对比多只股票或行业内股票
- 计算股票估值或内在价值
- 查看股票的财务健康状况
- 检测财务异常风险
Prerequisites
Python环境要求
pip install akshare pandas numpy依赖检查
在执行任何分析前,先检查akshare是否已安装:
python -c "import akshare; print(akshare.__version__)"如果未安装,提示用户安装:
pip install akshareCore Modules
1. Stock Screener (股票筛选器)
筛选符合条件的股票
2. Financial Analyzer (财务分析器)
个股深度财务分析
3. Industry Comparator (行业对比)
同行业横向对比分析
4. Valuation Calculator (估值计算器)
内在价值测算与安全边际计算
---
Workflow 1: Stock Screening (股票筛选)
用户请求筛选股票时使用。
Step 1: Collect Screening Criteria
向用户询问筛选条件。提供以下选项供用户选择或自定义:
估值指标:
- PE (市盈率): 例如 PE < 15
- PB (市净率): 例如 PB < 2
- PS (市销率): 例如 PS < 3
盈利能力:
- ROE (净资产收益率): 例如 ROE > 15%
- ROA (总资产收益率): 例如 ROA > 8%
- 毛利率: 例如 > 30%
- 净利率: 例如 > 10%
成长性:
- 营收增长率: 例如 > 10%
- 净利润增长率: 例如 > 15%
- 连续增长年数: 例如 >= 3年
股息:
- 股息率: 例如 > 3%
- 连续分红年数: 例如 >= 5年
财务安全:
- 资产负债率: 例如 < 60%
- 流动比率: 例如 > 1.5
- 速动比率: 例如 > 1
筛选范围:
- 全A股
- 沪深300成分股
- 中证500成分股
- 创业板/科创板
- 用户自定义列表
Step 2: Execute Screening
python scripts/stock_screener.py \
--scope "hs300" \
--pe-max 15 \
--roe-min 15 \
--debt-ratio-max 60 \
--dividend-min 2 \
--output screening_result.json参数说明:
--scope: 筛选范围 (all/hs300/zz500/cyb/kcb/custom:600519,000858,...)--pe-max/--pe-min: PE范围--pb-max/--pb-min: PB范围--roe-min: 最低ROE--growth-min: 最低增长率--debt-ratio-max: 最大资产负债率--dividend-min: 最低股息率--output: 输出文件路径
Step 3: Present Results
读取 screening_result.json 并以表格形式呈现给用户:
| 代码 | 名称 | PE | PB | ROE | 股息率 | 评分 |
|---|---|---|---|---|---|---|
| 600519 | 贵州茅台 | 25.3 | 8.5 | 30.2% | 2.1% | 85 |
---
Workflow 2: Stock Analysis (个股分析)
用户请求分析某只股票时使用。
Step 1: Collect Stock Information
询问用户: 1. 股票代码或名称 2. 分析深度级别:
- 摘要级:关键指标 + 投资结论(1页)
- 标准级:财务分析 + 估值 + 行业对比 + 风险提示
- 深度级:完整调研报告,包含历史数据追踪
Step 2: Fetch Stock Data
python scripts/data_fetcher.py \
--code "600519" \
--data-type all \
--years 5 \
--output stock_data.json参数说明:
--code: 股票代码--data-type: 数据类型 (basic/financial/valuation/holder/all)--years: 获取多少年的历史数据--output: 输出文件
Step 3: Run Financial Analysis
python scripts/financial_analyzer.py \
--input stock_data.json \
--level standard \
--output analysis_result.json参数说明:
--input: 输入的股票数据文件--level: 分析深度 (summary/standard/deep)--output: 输出文件
Step 4: Calculate Valuation
python scripts/valuation_calculator.py \
--input stock_data.json \
--methods dcf,ddm,relative \
--discount-rate 10 \
--growth-rate 8 \
--output valuation_result.json参数说明:
--input: 股票数据文件--methods: 估值方法 (dcf/ddm/relative/all)--discount-rate: 折现率(%)--growth-rate: 永续增长率(%)--margin-of-safety: 安全边际(%)--output: 输出文件
Step 5: Generate Report
读取分析结果,参考 templates/analysis_report.md 模板生成中文分析报告。
报告结构(标准级): 1. 公司概况:基本信息、主营业务 2. 财务健康:资产负债表分析 3. 盈利能力:杜邦分析、利润率趋势 4. 成长性分析:营收/利润增长趋势 5. 估值分析:DCF/DDM/相对估值 6. 风险提示:财务异常检测、股东减持 7. 投资结论:综合评分、操作建议
---
Workflow 3: Industry Comparison (行业对比)
Step 1: Collect Comparison Targets
询问用户: 1. 目标股票代码(可多个) 2. 或者:行业分类 + 对比数量
Step 2: Fetch Industry Data
python scripts/data_fetcher.py \
--codes "600519,000858,002304" \
--data-type comparison \
--output industry_data.json或按行业获取:
python scripts/data_fetcher.py \
--industry "白酒" \
--top 10 \
--output industry_data.jsonStep 3: Generate Comparison
python scripts/financial_analyzer.py \
--input industry_data.json \
--mode comparison \
--output comparison_result.jsonStep 4: Present Comparison Table
| 指标 | 贵州茅台 | 五粮液 | 洋河股份 | 行业均值 |
|---|---|---|---|---|
| PE | 25.3 | 18.2 | 15.6 | 22.4 |
| ROE | 30.2% | 22.5% | 20.1% | 18.5% |
| 毛利率 | 91.5% | 75.2% | 72.3% | 65.4% |
| 评分 | 85 | 78 | 75 | - |
---
Workflow 4: Valuation Calculator (估值计算)
Step 1: Collect Valuation Parameters
询问用户估值参数(或使用默认值):
DCF模型参数:
- 折现率 (WACC): 默认10%
- 预测期: 默认5年
- 永续增长率: 默认3%
DDM模型参数:
- 要求回报率: 默认10%
- 股息增长率: 使用历史数据推算
相对估值参数:
- 对比基准: 行业均值 / 历史均值
Step 2: Run Valuation
python scripts/valuation_calculator.py \
--code "600519" \
--methods all \
--discount-rate 10 \
--terminal-growth 3 \
--forecast-years 5 \
--margin-of-safety 30 \
--output valuation.jsonStep 3: Present Valuation Results
| 估值方法 | 内在价值 | 当前价格 | 安全边际价格 | 结论 |
|---|---|---|---|---|
| DCF | ¥2,150 | ¥1,680 | ¥1,505 | 低估 |
| DDM | ¥1,980 | ¥1,680 | ¥1,386 | 低估 |
| 相对估值 | ¥1,850 | ¥1,680 | ¥1,295 | 合理 |
---
Financial Anomaly Detection (财务异常检测)
在分析过程中自动检测以下异常信号:
检测项目
1. 应收账款异常
- 应收账款增速 > 营收增速 × 1.5
- 应收账款周转天数大幅增加
2. 现金流背离
- 净利润持续增长但经营现金流下降
- 现金收入比 < 80%
3. 存货异常
- 存货增速 > 营收增速 × 2
- 存货周转天数大幅增加
4. 毛利率异常
- 毛利率波动 > 行业均值波动 × 2
- 毛利率与同行严重偏离
5. 关联交易
- 关联交易占比过高(> 30%)
6. 股东减持
- 大股东近期减持公告
- 高管集中减持
风险等级
- 🟢 低风险:无明显异常
- 🟡 中风险:1-2项轻微异常
- 🔴 高风险:多项异常或严重异常
---
A-Share Specific Analysis (A股特色分析)
政策敏感度
根据行业分类提供政策相关提示:
- 房地产:房住不炒政策
- 新能源:补贴政策变化
- 医药:集采政策影响
- 互联网:反垄断、数据安全
股东结构分析
1. 控股股东类型(国企/民企/外资) 2. 股权集中度 3. 近期增减持情况 4. 质押比例
---
Output Format
JSON输出格式
所有脚本输出JSON格式,便于后续处理:
{
"code": "600519",
"name": "贵州茅台",
"analysis_date": "2025-01-25",
"level": "standard",
"summary": {
"score": 85,
"conclusion": "低估",
"recommendation": "建议关注"
},
"financials": { ... },
"valuation": { ... },
"risks": [ ... ]
}Markdown报告
生成结构化的中文Markdown报告,参考 templates/analysis_report.md。
---
Error Handling
网络错误
如果akshare数据获取失败,提示用户: 1. 检查网络连接 2. 稍后重试(可能是接口限流) 3. 尝试更换数据源
股票代码无效
提示用户检查股票代码是否正确,提供可能的匹配建议。
数据不完整
对于新上市股票或财务数据不完整的情况,说明数据限制并基于可用数据进行分析。
---
Best Practices
1. 数据时效性:财务数据以最新季报/年报为准,价格数据为当日收盘价 2. 投资建议:所有分析仅供参考,不构成投资建议 3. 风险提示:始终包含风险提示,特别是财务异常检测结果 4. 对比分析:单只股票分析时,自动包含行业均值对比
Important Notes
- 所有分析基于公开财务数据,不涉及任何内幕信息
- 估值模型的参数假设对结果影响较大,需向用户说明
- A股市场受政策影响较大,定量分析需结合定性判断
A股市场特色分析指南
一、A股市场特点
1.1 市场结构
交易所分布:
| 交易所 | 定位 | 特点 |
|---|---|---|
| 上交所主板 | 大型成熟企业 | 蓝筹为主 |
| 深交所主板 | 大中型企业 | 蓝筹为主 |
| 创业板 | 创新创业企业 | 高成长高风险 |
| 科创板 | 科技创新企业 | 注册制、门槛高 |
| 北交所 | 创新型中小企业 | 专精特新 |
投资者结构:
- 散户占比高(约60%交易量)
- 机构化程度提升中
- 公募、私募、外资(北向资金)
1.2 与成熟市场差异
| 特征 | A股 | 美股 |
|---|---|---|
| 涨跌幅限制 | 主板±10%,创/科±20% | 无限制 |
| T+1交易 | 是 | T+0 |
| 退市机制 | 较宽松 | 较严格 |
| 做空机制 | 有限 | 完善 |
| 分红文化 | 逐步加强 | 成熟 |
---
二、政策与行业分析
2.1 政策敏感行业
强监管行业:
| 行业 | 主要政策 | 影响 |
|---|---|---|
| 房地产 | "房住不炒"、三道红线 | 融资、销售双限 |
| 教育 | "双减"政策 | 学科类培训受限 |
| 互联网 | 反垄断、数据安全 | 业务合规成本 |
| 医药 | 集中采购 | 仿制药利润压缩 |
| 游戏 | 版号审批、防沉迷 | 发展受限 |
政策支持行业:
| 行业 | 政策方向 | 机会 |
|---|---|---|
| 新能源 | 碳中和、补贴 | 产业链高增长 |
| 半导体 | 国产替代 | 政策资金支持 |
| 高端制造 | 专精特新 | 税收优惠 |
| 医疗创新 | 创新药支持 | 审批加速 |
| 消费升级 | 扩大内需 | 长期机会 |
2.2 行业周期判断
周期性行业特征:
- 钢铁、煤炭、有色、化工、航运
- 盈利随经济周期大幅波动
- 低谷时PE可能极高,高峰时PE反而低
周期位置判断: 1. 产能利用率 2. 库存水平 3. 产品价格 4. 行业资本开支
周期股投资策略:
- 高PE时买入(周期底部)
- 低PE时卖出(周期顶部)
- 与普通估值逻辑相反
---
三、股东结构分析
3.1 控股股东类型
国有企业:
- 优势:资源获取、政策支持、稳定性
- 劣势:效率可能较低、激励不足
- 关注:混改进展、管理层变动
民营企业:
- 优势:机制灵活、激励充分
- 劣势:治理风险、资金链风险
- 关注:实控人信用、股权质押
外资控股:
- 优势:治理规范、技术先进
- 劣势:可能转移利润、政策风险
- 关注:关联交易、转让定价
3.2 股权结构关注点
股权集中度:
前十大股东持股比例
- > 70%:高度集中
- 50-70%:中度集中
- < 50%:较为分散股权质押:
质押比例 = 已质押股份 / 总持股
- < 20%:正常
- 20-50%:需关注
- > 50%:高风险减持公告:
- 计划减持:关注减持比例和原因
- 实际减持:跟踪减持进度
- 大宗交易:可能是减持信号
3.3 管理层分析
管理层持股:
- 有持股:利益一致
- 股权激励计划:行权条件是否合理
高管变动:
- 核心高管离职需警惕
- 董秘、财务总监变动尤需关注
- 审计师更换是红旗
---
四、财务风险识别
4.1 常见财务问题
收入确认激进:
信号:
- 应收账款增速 > 营收增速 × 1.5
- 第四季度收入占比过高
- 关联方销售占比高费用资本化:
信号:
- 开发支出/研发费用比例异常高
- 在建工程长期不转固
- 无形资产/商誉大幅增加存货问题:
信号:
- 存货周转率持续下降
- 存货跌价准备计提不充分
- 存货增速远超营收增速4.2 现金流验证
盈利质量检验:
# 现金收入比
现金收入比 = 销售商品收到的现金 / 营业收入
# 正常应 > 100%(含增值税)
# 净利润现金含量
净现比 = 经营活动现金流净额 / 净利润
# 正常应 > 80%异常模式:
| 净利润 | 经营现金流 | 含义 |
|---|---|---|
| 增长 | 增长 | 健康 |
| 增长 | 下降 | 警惕 |
| 下降 | 增长 | 可能改善 |
| 下降 | 下降 | 恶化 |
4.3 A股常见雷区
商誉减值:
- 并购形成大额商誉
- 被收购公司业绩不达预期
- 年末集中计提减值
应收账款坏账:
- 大客户集中
- 账龄结构恶化
- 关联方应收款
存货跌价:
- 产品价格下跌
- 库存积压
- 技术更新导致贬值
---
五、A股特有数据源
5.1 重要公告类型
| 公告类型 | 关注点 |
|---|---|
| 业绩预告/快报 | 业绩变动幅度 |
| 股东减持公告 | 减持比例和原因 |
| 股权激励计划 | 行权条件和覆盖范围 |
| 关联交易公告 | 交易定价公允性 |
| 对外担保公告 | 担保风险敞口 |
| 资产重组公告 | 重组方案和估值 |
| 诉讼仲裁公告 | 涉诉金额和进展 |
5.2 监管信息
交易所问询函:
- 年报问询函:关注重点问题
- 关注函:可能存在违规
- 监管函:存在明确问题
证监会公告:
- 立案调查
- 行政处罚
- 市场禁入
5.3 北向资金
北向资金数据:
- 每日净流入/流出
- 个股持仓变动
- 持股市值占比参考价值:
- 外资偏好稳定的白马股
- 大幅增减持可能有信号意义
- 但不宜盲目跟随
---
六、估值调整因素
6.1 A股估值特点
历史估值中枢:
- 沪深300 PE中枢:约12-15倍
- 创业板 PE中枢:约35-50倍
- 受流动性和情绪影响大
估值溢价/折价:
| 因素 | 溢价 | 折价 |
|---|---|---|
| 稀缺性 | 行业龙头、独特资源 | 同质化竞争 |
| 确定性 | 消费、医药 | 周期、概念 |
| 成长性 | 高增长预期 | 增长放缓 |
| 治理 | 良好治理历史 | 历史有问题 |
| 流动性 | 大市值、高换手 | 小市值、低流动性 |
6.2 A股估值方法调整
DCF调整:
- 折现率可能需要更高(政策不确定性)
- 永续增长率需谨慎(产业政策变化)
相对估值调整:
- 参考行业历史估值区间
- 考虑市场情绪周期位置
- 参考港股/美股可比公司
---
七、投资决策框架
7.1 A股价值投资检查清单
基本面筛选:
- [ ] ROE连续5年 > 15%
- [ ] 毛利率稳定或上升
- [ ] 经营现金流/净利润 > 0.8
- [ ] 资产负债率 < 60%(非金融)
- [ ] 营收和利润正增长
风险排查:
- [ ] 无重大诉讼/违规
- [ ] 无异常关联交易
- [ ] 控股股东质押率 < 50%
- [ ] 无频繁减持公告
- [ ] 审计意见为标准无保留
估值判断:
- [ ] PE < 行业均值
- [ ] PB处于历史低位
- [ ] 存在明确的安全边际
政策评估:
- [ ] 行业政策无重大利空
- [ ] 公司受益于政策方向
- [ ] 合规风险可控
7.2 买入/卖出时机
买入时机考量: 1. 估值处于历史低位(如PE分位数 < 30%) 2. 市场悲观情绪过度 3. 基本面稳健,短期利空不改长期逻辑 4. 安全边际 > 30%
卖出时机考量: 1. 估值处于历史高位(如PE分位数 > 70%) 2. 基本面出现实质性恶化 3. 政策环境发生重大不利变化 4. 发现更好的投资机会
7.3 持仓管理
A股仓位建议:
- 单只股票:不超过20%
- 单一行业:不超过30%
- 保持一定现金比例应对波动
定期检视:
- 每季度财报后重新评估
- 关注重大公告
- 跟踪行业政策变化
财务指标详解
一、估值指标
1.1 市盈率 (PE - Price to Earnings)
计算公式:
PE = 股价 / 每股收益 (EPS)
PE = 总市值 / 净利润类型:
- 静态PE:使用上一年度净利润
- 动态PE:使用预期未来净利润
- TTM PE:使用过去12个月滚动净利润
解读标准:
| PE范围 | 通常含义 |
|---|---|
| < 10 | 可能低估或有问题 |
| 10-15 | 相对便宜 |
| 15-25 | 合理估值 |
| 25-40 | 高成长预期 |
| > 40 | 可能高估 |
注意事项:
- 周期股低谷时PE可能极高
- 亏损公司PE无意义
- 需要与行业均值比较
1.2 市净率 (PB - Price to Book)
计算公式:
PB = 股价 / 每股净资产
PB = 总市值 / 净资产(股东权益)解读标准:
| PB范围 | 通常含义 |
|---|---|
| < 1 | 可能低估(破净) |
| 1-2 | 相对合理 |
| 2-5 | 高于账面价值 |
| > 5 | 高度依赖无形资产 |
适用场景:
- 重资产行业:银行、地产、钢铁
- 周期底部估值
- 清算价值参考
1.3 市销率 (PS - Price to Sales)
计算公式:
PS = 股价 / 每股销售额
PS = 总市值 / 营业收入适用场景:
- 亏损但高增长公司
- 互联网、SaaS企业
- 同行业对比
1.4 企业价值倍数 (EV/EBITDA)
计算公式:
EV = 总市值 + 总负债 - 现金
EBITDA = 息税折旧摊销前利润
EV/EBITDA = 企业价值 / EBITDA优势:
- 剔除资本结构差异
- 适合跨国比较
- 适用于高杠杆行业
---
二、盈利能力指标
2.1 净资产收益率 (ROE - Return on Equity)
计算公式:
ROE = 净利润 / 平均净资产 × 100%杜邦分析分解:
ROE = 净利率 × 资产周转率 × 权益乘数
= (净利润/营收) × (营收/总资产) × (总资产/净资产)解读标准:
| ROE范围 | 评价 |
|---|---|
| < 8% | 较低,需关注原因 |
| 8-15% | 一般水平 |
| 15-25% | 优秀 |
| > 25% | 卓越(需验证可持续性) |
Buffett标准: 连续5年ROE > 15%
2.2 总资产收益率 (ROA - Return on Assets)
计算公式:
ROA = 净利润 / 平均总资产 × 100%与ROE对比:
- ROA 反映资产运营效率
- 高杠杆可能导致 ROE高但ROA一般
- 银行等行业ROA通常较低
2.3 毛利率 (Gross Profit Margin)
计算公式:
毛利率 = (营收 - 营业成本) / 营收 × 100%行业参考:
| 行业 | 典型毛利率 |
|---|---|
| 白酒 | 70-90% |
| 医药 | 50-80% |
| 软件 | 60-80% |
| 零售 | 20-40% |
| 制造业 | 15-30% |
2.4 净利率 (Net Profit Margin)
计算公式:
净利率 = 净利润 / 营收 × 100%关注点:
- 毛利率高但净利率低 → 期间费用高
- 净利率波动大 → 非经常性损益多
---
三、财务安全指标
3.1 资产负债率 (Debt Ratio)
计算公式:
资产负债率 = 总负债 / 总资产 × 100%标准参考:
| 资产负债率 | 评价 |
|---|---|
| < 40% | 保守 |
| 40-60% | 适中 |
| 60-70% | 偏高 |
| > 70% | 高风险(非金融行业) |
注意:
- 银行业通常 > 90%,属正常
- 需区分有息负债和经营性负债
3.2 流动比率 (Current Ratio)
计算公式:
流动比率 = 流动资产 / 流动负债标准:
- 一般要求 > 1.5
- < 1 表示短期偿债压力大
3.3 速动比率 (Quick Ratio)
计算公式:
速动比率 = (流动资产 - 存货) / 流动负债标准:
- 一般要求 > 1
- 剔除存货变现不确定性
3.4 利息覆盖倍数 (Interest Coverage)
计算公式:
利息覆盖倍数 = EBIT / 利息费用标准:
- 一般要求 > 3
- < 1.5 存在偿债风险
---
四、运营效率指标
4.1 应收账款周转率/天数
计算公式:
应收周转率 = 营收 / 平均应收账款
应收周转天数 = 365 / 应收周转率解读:
- 周转天数越短越好
- 行业差异大,需对比同行
- 持续延长需警惕
4.2 存货周转率/天数
计算公式:
存货周转率 = 营业成本 / 平均存货
存货周转天数 = 365 / 存货周转率行业参考:
- 零售业:30-60天
- 制造业:60-120天
- 白酒:可能超过1年(正常)
4.3 总资产周转率
计算公式:
总资产周转率 = 营收 / 平均总资产解读:
- 反映资产利用效率
- 轻资产公司通常较高
- 杜邦分析重要组成部分
---
五、成长性指标
5.1 营收增长率
计算公式:
营收增长率 = (本期营收 - 上期营收) / 上期营收 × 100%
复合增长率 CAGR = (末期/初期)^(1/年数) - 15.2 净利润增长率
计算公式:
净利润增长率 = (本期净利 - 上期净利) / 上期净利 × 100%注意:
- 区分扣非净利润与归母净利润
- 排除非经常性损益影响
5.3 每股收益增长率 (EPS Growth)
重要性:
- 直接关系到股东回报
- 需考虑股本变动影响
---
六、股东回报指标
6.1 股息率 (Dividend Yield)
计算公式:
股息率 = 每股股息 / 股价 × 100%A股参考:
- 银行股:4-6%
- 公用事业:3-5%
- 消费品:2-4%
- 平均水平:约2%
6.2 分红比例 (Payout Ratio)
计算公式:
分红比例 = 现金分红总额 / 净利润 × 100%解读:
- 30-50% 较为健康
- 过高可能影响发展
- 过低可能不重视股东回报
6.3 股息增长率
计算公式:
股息CAGR = (末期股息/初期股息)^(1/年数) - 1用于DDM估值模型
---
七、现金流指标
7.1 经营现金流
重要性:
- 反映真实盈利质量
- 比净利润更难操纵
关注点:
现金收入比 = 销售收到的现金 / 营收- 通常应 > 100%
7.2 自由现金流 (FCF)
计算公式:
FCF = 经营现金流净额 - 资本支出解读:
- 正FCF:可用于分红、偿债、回购
- 持续负FCF:需要外部融资
7.3 现金流与净利润对比
净利润/经营现金流 比值:
- < 0.8:盈利质量可疑
- 0.8-1.2:正常
- > 1.2:现金回收好于账面
---
八、财务异常信号
8.1 红旗警示
| 信号 | 可能含义 |
|---|---|
| 应收账款增速 >> 营收增速 | 收入确认激进 |
| 存货增速 >> 营收增速 | 产品滞销或囤积 |
| 净利润增长但现金流下降 | 盈利质量差 |
| 毛利率异常高于同行 | 可能虚增收入 |
| 关联交易占比过高 | 利益输送风险 |
| 频繁更换审计师 | 财务问题风险 |
| 经营现金流持续为负 | 商业模式存疑 |
8.2 Beneish M-Score 简化版
核心检测变量: 1. DSRI:应收账款天数变化 2. GMI:毛利率变化 3. AQI:资产质量变化 4. SGI:销售增长 5. TATA:应计项目/总资产
M-Score > -1.78 时,财务造假概率较高
价值投资核心原则
一、价值投资理论基础
1.1 Benjamin Graham 的核心理念
安全边际 (Margin of Safety)
- 以显著低于内在价值的价格买入
- 建议安全边际至少 25-30%
- 安全边际越大,投资风险越低
内在价值 (Intrinsic Value)
- 公司真实价值由未来现金流决定
- 独立于市场价格波动
- 需要通过财务分析计算
Mr. Market 寓言
- 市场是情绪化的"市场先生"
- 价格常常偏离价值
- 利用市场波动而非被其左右
1.2 Warren Buffett 的投资哲学
护城河 (Economic Moat)
- 品牌优势:如茅台、可口可乐
- 成本优势:规模经济、独特资源
- 网络效应:用户越多价值越大
- 转换成本:客户迁移成本高
- 政府牌照:特许经营权
能力圈原则
- 只投资自己能理解的业务
- 简单的商业模式更容易分析
- 复杂不等于好
长期持有
- "我们最喜欢的持有期是永远"
- 复利效应需要时间发挥
- 减少交易成本和税负
---
二、Graham 经典选股标准
2.1 防御型投资者标准
| 指标 | 标准 | 说明 |
|---|---|---|
| 公司规模 | 年销售额 > 1亿美元 | 避免小公司风险 |
| 财务稳健 | 流动比率 > 2 | 短期偿债能力 |
| 盈利稳定 | 过去10年每年盈利 | 商业模式验证 |
| 分红历史 | 连续20年派息 | 股东回报意识 |
| 盈利增长 | 10年EPS增长 > 33% | 年均 3% 以上 |
| PE比率 | PE < 15 | 避免估值泡沫 |
| PB比率 | PB < 1.5 | 资产保护 |
| PE × PB | < 22.5 | 综合估值限制 |
2.2 积极型投资者标准
在防御型基础上,可接受:
- 规模较小但增长更快的公司
- PE略高但ROE持续优秀
- 行业地位突出的公司
---
三、估值方法论
3.1 DCF 现金流折现模型
基本公式:
内在价值 = Σ (FCFt / (1+r)^t) + 终值自由现金流 (FCF) 计算:
FCF = 经营现金流 - 资本支出关键参数:
- 折现率 (r):通常 8-12%
- 增长率:分阶段估计
- 终值:永续增长模型或退出倍数
适用场景:
- 现金流稳定可预测的公司
- 成熟期企业
- 公用事业、消费品等
3.2 DDM 股息折现模型
Gordon 增长模型:
内在价值 = D1 / (r - g)其中:
- D1 = 下一年预期股息
- r = 要求回报率
- g = 股息永续增长率
适用场景:
- 高分红企业
- 银行、保险、公用事业
- 分红政策稳定的公司
3.3 相对估值法
市盈率 (PE) 法:
合理价格 = EPS × 合理PE参考标准:
- 行业平均PE
- 历史平均PE
- 可比公司PE
市净率 (PB) 法:
合理价格 = 每股净资产 × 合理PB适用于:
- 重资产行业
- 银行等金融机构
- 周期性行业低谷期
---
四、选股检查清单
4.1 定量筛选标准
估值合理:
- [ ] PE < 行业均值
- [ ] PB < 3(非轻资产行业)
- [ ] PE × PB < 22.5
盈利能力强:
- [ ] ROE > 15%(连续3年)
- [ ] 毛利率 > 行业均值
- [ ] 净利率稳定或上升
财务安全:
- [ ] 资产负债率 < 60%
- [ ] 流动比率 > 1.5
- [ ] 利息覆盖倍数 > 3
成长性:
- [ ] 营收年复合增长 > 10%
- [ ] 净利润年复合增长 > 10%
4.2 定性评估要点
商业模式:
- [ ] 业务是否容易理解
- [ ] 是否有护城河
- [ ] 行业竞争格局
管理层:
- [ ] 管理层持股情况
- [ ] 过往诚信记录
- [ ] 资本配置能力
行业前景:
- [ ] 行业增长空间
- [ ] 政策支持/限制
- [ ] 技术变革风险
---
五、风险控制原则
5.1 分散投资
- 单只股票仓位不超过 20%
- 行业分散:不同行业至少 3-5 个
- 避免高度相关的股票
5.2 仓位管理
根据安全边际调整仓位:
- 安全边际 > 50%:可重仓
- 安全边际 30-50%:正常仓位
- 安全边际 < 30%:轻仓或观望
5.3 卖出原则
应该卖出的情况: 1. 公司基本面恶化 2. 股价严重高估(超出内在价值 50%+) 3. 发现更好的投资机会 4. 当初买入的理由不再成立
不应该卖出的理由: 1. 仅仅因为股价下跌 2. 短期市场波动 3. 宏观经济担忧(除非影响公司基本面)
---
六、A股价值投资特点
6.1 与成熟市场的差异
| 特点 | A股 | 美股 |
|---|---|---|
| 投资者结构 | 散户为主 | 机构为主 |
| 市场效率 | 相对较低 | 相对较高 |
| 估值波动 | 较大 | 相对平稳 |
| 政策影响 | 显著 | 较小 |
| 价值投资空间 | 可能更大 | 更成熟 |
6.2 A股价值投资机会
低效市场机会:
- 市场情绪极端时往往出现低估
- 中小盘股可能被机构忽视
- 行业轮动带来的错杀机会
需要额外关注:
- 财务报表真实性
- 大股东行为
- 政策变化影响
- 再融资和减持
#!/usr/bin/env python3
"""
A股数据获取模块
使用akshare获取股票财务数据、行情数据、股东信息等
依赖: pip install akshare pandas
"""
import argparse
import json
import sys
import time
import os
from datetime import datetime, timedelta
from typing import Optional, Callable
from functools import wraps
try:
import akshare as ak
import pandas as pd
except ImportError:
print("错误: 请先安装依赖库")
print("pip install akshare pandas")
sys.exit(1)
def retry_on_failure(max_retries: int = 3, delay: float = 1.0):
"""网络请求重试装饰器"""
def decorator(func: Callable):
@wraps(func)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
last_error = e
if attempt < max_retries - 1:
time.sleep(delay * (attempt + 1)) # 递增等待
return {"error": f"重试{max_retries}次后失败: {str(last_error)}"}
return wrapper
return decorator
def safe_float(value) -> Optional[float]:
"""安全转换为浮点数"""
if value is None or value == '' or value == '--':
return None
try:
if pd.isna(value):
return None
if isinstance(value, str):
value = value.replace('%', '').replace(',', '').replace('亿', '')
return float(value)
except (ValueError, TypeError):
return None
def get_cache_path(code: str, data_type: str) -> str:
"""获取缓存文件路径"""
cache_dir = os.path.join(os.path.dirname(__file__), '.cache')
os.makedirs(cache_dir, exist_ok=True)
today = datetime.now().strftime('%Y%m%d')
return os.path.join(cache_dir, f"{code}_{data_type}_{today}.json")
def load_cache(code: str, data_type: str) -> Optional[dict]:
"""加载缓存数据(当天有效)"""
cache_path = get_cache_path(code, data_type)
if os.path.exists(cache_path):
try:
with open(cache_path, 'r', encoding='utf-8') as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return None
return None
def save_cache(code: str, data_type: str, data: dict):
"""保存缓存数据"""
cache_path = get_cache_path(code, data_type)
try:
with open(cache_path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, default=str)
except IOError:
pass
@retry_on_failure(max_retries=2, delay=1.0)
def get_stock_info(code: str) -> dict:
"""获取股票基本信息"""
try:
df = ak.stock_individual_info_em(symbol=code)
info = {}
for _, row in df.iterrows():
info[row['item']] = row['value']
return {
"code": code,
"name": info.get("股票简称", ""),
"industry": info.get("行业", ""),
"market_cap": safe_float(info.get("总市值")),
"float_cap": safe_float(info.get("流通市值")),
"total_shares": safe_float(info.get("总股本")),
"float_shares": safe_float(info.get("流通股")),
"pe_ttm": safe_float(info.get("市盈率(动态)")),
"pb": safe_float(info.get("市净率")),
"listing_date": info.get("上市时间", "")
}
except Exception as e:
return {"code": code, "error": str(e)}
@retry_on_failure(max_retries=2, delay=1.0)
def get_financial_data(code: str, years: int = 3) -> dict:
"""获取财务数据(资产负债表、利润表、现金流量表)"""
max_records = min(years * 4, 12)
result = {
"balance_sheet": [],
"income_statement": [],
"cash_flow": []
}
fetch_configs = [
("balance_sheet", ak.stock_balance_sheet_by_report_em),
("income_statement", ak.stock_profit_sheet_by_report_em),
("cash_flow", ak.stock_cash_flow_sheet_by_report_em),
]
for key, fetch_func in fetch_configs:
try:
df = fetch_func(symbol=code)
if df is not None and not df.empty:
result[key] = df.head(max_records).to_dict(orient='records')
except Exception as e:
result[f"{key}_error"] = str(e)
return result
def get_financial_indicators(code: str, limit: int = 8) -> dict:
"""获取财务指标,优先使用快速API,失败时降级到备用API"""
apis = [ak.stock_financial_abstract, ak.stock_financial_analysis_indicator]
for api in apis:
try:
df = api(symbol=code)
if df is not None and not df.empty:
return df.head(limit).to_dict(orient='records')
except Exception:
continue
return []
def get_valuation_data(code: str) -> dict:
"""获取估值数据"""
result = {}
try:
df = ak.stock_a_ttm_lyr(symbol=code)
if df is None or df.empty:
return result
latest = df.iloc[-1].to_dict()
result["latest"] = latest
result["history_count"] = len(df)
for col in ['pe_ttm', 'pb']:
val = latest.get(col)
if val and not pd.isna(val):
result[f"{col}_percentile"] = (df[col].dropna() < val).mean() * 100
except Exception as e:
result["error"] = str(e)
result["note"] = "估值历史数据获取失败,将使用基本信息中的估值"
return result
@retry_on_failure(max_retries=2, delay=1.0)
def get_holder_data(code: str) -> dict:
"""获取股东信息"""
result = {}
try:
df_top10 = ak.stock_gdfx_top_10_em(symbol=code)
if df_top10 is not None and not df_top10.empty:
result["top_10_holders"] = df_top10.head(10).to_dict(orient='records')
except Exception as e:
result["top_10_holders_error"] = str(e)
try:
df_holder_num = ak.stock_zh_a_gdhs(symbol=code)
if df_holder_num is not None and not df_holder_num.empty:
result["holder_count_history"] = df_holder_num.head(10).to_dict(orient='records')
except Exception as e:
result["holder_count_error"] = str(e)
return result
@retry_on_failure(max_retries=2, delay=1.0)
def get_dividend_data(code: str) -> dict:
"""获取分红数据,优先使用主API,失败时降级到备用API"""
apis = [
lambda c: ak.stock_dividend_cninfo(symbol=c),
lambda c: ak.stock_history_dividend_detail(symbol=c, indicator="分红"),
]
for api in apis:
try:
df = api(code)
if df is not None and not df.empty:
return {
"dividend_history": df.to_dict(orient='records'),
"dividend_count": len(df)
}
except Exception:
continue
return {"dividend_history": [], "dividend_count": 0}
@retry_on_failure(max_retries=2, delay=1.0)
def get_price_data(code: str, days: int = 60) -> dict:
"""获取价格数据"""
try:
end_date = datetime.now().strftime('%Y%m%d')
start_date = (datetime.now() - timedelta(days=days)).strftime('%Y%m%d')
df = ak.stock_zh_a_hist(symbol=code, period="daily",
start_date=start_date, end_date=end_date, adjust="qfq")
if df is not None and not df.empty:
latest = df.iloc[-1]
return {
"latest_price": safe_float(latest['收盘']),
"latest_date": str(latest['日期']),
"price_change_pct": safe_float(latest['涨跌幅']),
"volume": safe_float(latest['成交量']),
"turnover": safe_float(latest['成交额']),
"high_60d": safe_float(df['最高'].max()),
"low_60d": safe_float(df['最低'].min()),
"avg_volume_20d": safe_float(df.tail(20)['成交量'].mean()),
"price_data": df.tail(30).to_dict(orient='records') # 只保留30天
}
return {}
except Exception as e:
return {"error": str(e)}
@retry_on_failure(max_retries=2, delay=1.0)
def get_index_constituents(index_name: str) -> list:
"""获取指数成分股"""
index_map = {
"hs300": "000300",
"zz500": "000905",
"zz1000": "000852",
"cyb": "399006",
"kcb": "000688"
}
index_code = index_map.get(index_name)
if not index_code:
return []
try:
df = ak.index_stock_cons(symbol=index_code)
if df is not None and not df.empty:
return df['品种代码'].tolist()
return []
except Exception as e:
print(f"获取指数成分股失败: {e}")
return []
def get_all_a_stocks() -> list:
"""获取全部A股代码"""
try:
df = ak.stock_zh_a_spot_em()
if df is not None and not df.empty:
return df['代码'].tolist()
return []
except Exception as e:
print(f"获取全部A股失败: {e}")
return []
def fetch_stock_data(code: str, data_type: str = "all", years: int = 3, use_cache: bool = True) -> dict:
"""获取单只股票的数据"""
# 尝试加载缓存
if use_cache:
cached = load_cache(code, data_type)
if cached:
print(f"使用缓存数据: {code}")
return cached
result = {
"code": code,
"fetch_time": datetime.now().isoformat(),
"data_type": data_type
}
print(f"正在获取 {code} 的数据...")
if data_type in ["all", "basic"]:
print(" - 获取基本信息...")
result["basic_info"] = get_stock_info(code)
if data_type in ["all", "financial"]:
print(" - 获取财务数据...")
result["financial_data"] = get_financial_data(code, years)
print(" - 获取财务指标...")
result["financial_indicators"] = get_financial_indicators(code)
if data_type in ["all", "valuation"]:
print(" - 获取估值数据...")
result["valuation"] = get_valuation_data(code)
print(" - 获取价格数据...")
result["price"] = get_price_data(code)
if data_type in ["all", "holder"]:
print(" - 获取股东数据...")
result["holder"] = get_holder_data(code)
print(" - 获取分红数据...")
result["dividend"] = get_dividend_data(code)
# 保存缓存
if use_cache:
save_cache(code, data_type, result)
print(f"数据获取完成: {code}")
return result
def fetch_multiple_stocks(codes: list, data_type: str = "basic") -> dict:
"""获取多只股票数据"""
result = {
"fetch_time": datetime.now().isoformat(),
"stocks": [],
"success_count": 0,
"fail_count": 0
}
total = len(codes)
for i, code in enumerate(codes):
print(f"[{i+1}/{total}] 获取 {code}...")
try:
stock_data = fetch_stock_data(code, data_type, use_cache=True)
if "error" not in stock_data.get("basic_info", {}):
result["stocks"].append(stock_data)
result["success_count"] += 1
else:
result["fail_count"] += 1
except Exception as e:
print(f" 获取失败: {e}")
result["fail_count"] += 1
# 避免请求过快
if i < total - 1:
time.sleep(0.5)
return result
def main():
parser = argparse.ArgumentParser(description="A股数据获取工具")
parser.add_argument("--code", type=str, help="股票代码 (如: 600519)")
parser.add_argument("--codes", type=str, help="多个股票代码,逗号分隔 (如: 600519,000858)")
parser.add_argument("--data-type", type=str, default="basic",
choices=["all", "basic", "financial", "valuation", "holder"],
help="数据类型 (默认: basic)")
parser.add_argument("--years", type=int, default=3, help="获取多少年的历史数据 (默认: 3)")
parser.add_argument("--scope", type=str, help="筛选范围: hs300/zz500/cyb/kcb/all")
parser.add_argument("--no-cache", action="store_true", help="不使用缓存")
parser.add_argument("--output", type=str, help="输出文件路径 (JSON)")
args = parser.parse_args()
result = {}
if args.code:
result = fetch_stock_data(args.code, args.data_type, args.years,
use_cache=not args.no_cache)
elif args.codes:
codes = [c.strip() for c in args.codes.split(",")]
result = fetch_multiple_stocks(codes, args.data_type)
elif args.scope:
if args.scope == "all":
codes = get_all_a_stocks()
else:
codes = get_index_constituents(args.scope)
result = {"scope": args.scope, "stocks": codes, "count": len(codes)}
else:
print("请提供 --code, --codes 或 --scope 参数")
sys.exit(1)
# 输出结果
output = json.dumps(result, ensure_ascii=False, indent=2, default=str)
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
f.write(output)
print(f"\n数据已保存到: {args.output}")
else:
print(output)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
A股财务分析模块
提供财务健康度分析、杜邦分析、异常检测等功能
依赖: pip install akshare pandas numpy
"""
import argparse
import json
import sys
from datetime import datetime
from typing import Optional, List, Dict
try:
import pandas as pd
import numpy as np
except ImportError:
print("错误: 请先安装依赖库")
print("pip install pandas numpy")
sys.exit(1)
class FinancialAnalyzer:
"""财务分析器"""
def __init__(self, stock_data: Dict = None):
self.stock_data = stock_data or {}
self.analysis_result = {}
def load_data(self, file_path: str):
"""从JSON文件加载股票数据"""
with open(file_path, 'r', encoding='utf-8') as f:
self.stock_data = json.load(f)
def _assess_roe(self, roe: float) -> str:
"""根据ROE评估盈利能力"""
if roe > 20:
return "优秀 - ROE超过20%,盈利能力很强"
elif roe > 15:
return "良好 - ROE在15-20%之间,盈利能力较强"
elif roe > 10:
return "一般 - ROE在10-15%之间,盈利能力中等"
return "较弱 - ROE低于10%,盈利能力需要改善"
def analyze_profitability(self) -> Dict:
"""盈利能力分析"""
result = {
"category": "盈利能力",
"metrics": {},
"trend": [],
"assessment": ""
}
indicators = self.stock_data.get('financial_indicators', [])
if not indicators:
return result
metrics = {}
for indicator in indicators[:8]:
period = indicator.get('日期', '')
if period:
metrics[period] = {
"ROE": self._safe_float(indicator.get('净资产收益率', indicator.get('加权净资产收益率'))),
"ROA": self._safe_float(indicator.get('总资产报酬率')),
"毛利率": self._safe_float(indicator.get('销售毛利率')),
"净利率": self._safe_float(indicator.get('销售净利率'))
}
if not metrics:
return result
latest = list(metrics.values())[0]
result["metrics"] = {
"当前ROE": latest.get("ROE"),
"当前ROA": latest.get("ROA"),
"当前毛利率": latest.get("毛利率"),
"当前净利率": latest.get("净利率")
}
roe_values = [v.get("ROE") for v in metrics.values() if v.get("ROE") is not None]
if len(roe_values) >= 2:
trend = "上升" if roe_values[0] > roe_values[-1] else "下降"
result["trend"].append(f"ROE呈{trend}趋势")
roe = latest.get("ROE")
if roe:
result["assessment"] = self._assess_roe(roe)
return result
def analyze_solvency(self) -> Dict:
"""偿债能力分析"""
result = {
"category": "偿债能力",
"metrics": {},
"risks": [],
"assessment": ""
}
indicators = self.stock_data.get('financial_indicators', [])
if not indicators:
return result
latest = indicators[0]
debt_ratio = self._safe_float(latest.get('资产负债率'))
current_ratio = self._safe_float(latest.get('流动比率'))
quick_ratio = self._safe_float(latest.get('速动比率'))
result["metrics"] = {
"资产负债率": debt_ratio,
"流动比率": current_ratio,
"速动比率": quick_ratio
}
# 风险评估
risk_checks = [
(debt_ratio and debt_ratio > 70, f"资产负债率偏高 ({debt_ratio:.1f}%),需关注偿债压力"),
(current_ratio and current_ratio < 1, f"流动比率偏低 ({current_ratio:.2f}),短期偿债能力较弱"),
(quick_ratio and quick_ratio < 0.8, f"速动比率偏低 ({quick_ratio:.2f}),短期流动性风险"),
]
result["risks"] = [msg for condition, msg in risk_checks if condition]
# 综合评估
risk_count = len(result["risks"])
if risk_count == 0:
result["assessment"] = "良好 - 偿债能力指标正常,财务结构稳健"
elif risk_count == 1:
result["assessment"] = "一般 - 存在一项风险指标,需持续关注"
else:
result["assessment"] = "较弱 - 存在多项风险指标,偿债压力较大"
return result
def analyze_operation(self) -> Dict:
"""运营效率分析"""
result = {
"category": "运营效率",
"metrics": {},
"observations": [],
"assessment": ""
}
indicators = self.stock_data.get('financial_indicators', [])
if not indicators:
return result
latest = indicators[0]
ar_days = self._safe_float(latest.get('应收账款周转天数'))
inventory_days = self._safe_float(latest.get('存货周转天数'))
asset_turnover = self._safe_float(latest.get('总资产周转率'))
result["metrics"] = {
"应收账款周转率": self._safe_float(latest.get('应收账款周转率')),
"应收账款周转天数": ar_days,
"存货周转率": self._safe_float(latest.get('存货周转率')),
"存货周转天数": inventory_days,
"总资产周转率": asset_turnover
}
# 观察分析
observation_checks = [
(ar_days and ar_days > 90, f"应收账款周转天数较长 ({ar_days:.0f}天),回款较慢"),
(inventory_days and inventory_days > 180, f"存货周转天数较长 ({inventory_days:.0f}天),库存管理需关注"),
(asset_turnover and asset_turnover < 0.5, f"总资产周转率较低 ({asset_turnover:.2f}),资产利用效率有待提高"),
]
result["observations"] = [msg for condition, msg in observation_checks if condition]
if not result["observations"]:
result["assessment"] = "良好 - 运营效率指标正常"
else:
result["assessment"] = "需关注 - " + ";".join(result["observations"])
return result
def _assess_growth_rate(self, avg_growth: float) -> str:
"""评估增长率"""
if avg_growth > 20:
return "高成长 - 平均增长率超过20%"
elif avg_growth > 10:
return "稳定成长 - 平均增长率10-20%"
elif avg_growth > 0:
return "低速成长 - 平均增长率0-10%"
return "负增长 - 需要深入分析原因"
def _analyze_growth_trend(self, values: List[float], name: str) -> Optional[str]:
"""分析增长趋势"""
if not values:
return None
if all(g > 0 for g in values[:4]):
return f"{name}持续正增长"
elif values[0] < 0:
return f"{name}负增长,需关注"
return None
def analyze_growth(self) -> Dict:
"""成长性分析"""
result = {
"category": "成长性",
"metrics": {},
"trend": [],
"assessment": ""
}
indicators = self.stock_data.get('financial_indicators', [])
if not indicators:
return result
revenue_growth = []
profit_growth = []
for indicator in indicators[:8]:
rev = self._safe_float(indicator.get('主营业务收入增长率', indicator.get('营业收入增长率')))
net = self._safe_float(indicator.get('净利润增长率'))
if rev is not None:
revenue_growth.append(rev)
if net is not None:
profit_growth.append(net)
if revenue_growth:
result["metrics"]["最近营收增长率"] = revenue_growth[0]
result["metrics"]["平均营收增长率"] = sum(revenue_growth) / len(revenue_growth)
if profit_growth:
result["metrics"]["最近净利润增长率"] = profit_growth[0]
result["metrics"]["平均净利润增长率"] = sum(profit_growth) / len(profit_growth)
# 趋势判断
for values, name in [(revenue_growth, "营收"), (profit_growth, "净利润")]:
trend = self._analyze_growth_trend(values, name)
if trend:
result["trend"].append(trend)
avg_growth = result["metrics"].get("平均净利润增长率", 0) or 0
result["assessment"] = self._assess_growth_rate(avg_growth)
return result
def analyze_dupont(self) -> Dict:
"""杜邦分析"""
result = {
"category": "杜邦分析",
"decomposition": {},
"driver": "",
"assessment": ""
}
indicators = self.stock_data.get('financial_indicators', [])
if not indicators:
return result
latest = indicators[0] if indicators else {}
# 杜邦分解: ROE = 净利率 × 资产周转率 × 权益乘数
net_margin = self._safe_float(latest.get('销售净利率'))
asset_turnover = self._safe_float(latest.get('总资产周转率'))
equity_multiplier = self._safe_float(latest.get('权益乘数'))
roe = self._safe_float(latest.get('净资产收益率', latest.get('加权净资产收益率')))
result["decomposition"] = {
"ROE": roe,
"净利率": net_margin,
"资产周转率": asset_turnover,
"权益乘数": equity_multiplier
}
# 判断ROE驱动因素
if net_margin and asset_turnover and equity_multiplier:
drivers = []
if net_margin > 15:
drivers.append("高净利率")
if asset_turnover > 1:
drivers.append("高周转")
if equity_multiplier > 2.5:
drivers.append("高杠杆")
if drivers:
result["driver"] = "ROE主要由" + "、".join(drivers) + "驱动"
else:
result["driver"] = "ROE驱动因素较为均衡"
return result
def detect_anomalies(self) -> Dict:
"""财务异常检测"""
result = {
"category": "财务异常检测",
"signals": [],
"risk_level": "低",
"details": []
}
indicators = self.stock_data.get('financial_indicators', [])
if len(indicators) < 2:
return result
current = indicators[0]
previous = indicators[1] if len(indicators) > 1 else {}
# 1. 应收账款异常
ar_growth = self._safe_float(current.get('应收账款增长率'))
revenue_growth = self._safe_float(current.get('营业收入增长率', current.get('主营业务收入增长率')))
if ar_growth and revenue_growth and ar_growth > revenue_growth * 1.5:
result["signals"].append({
"type": "应收账款增速异常",
"description": f"应收账款增速({ar_growth:.1f}%)显著高于营收增速({revenue_growth:.1f}%)",
"severity": "中"
})
# 2. 存货异常
inventory_growth = self._safe_float(current.get('存货增长率'))
if inventory_growth and revenue_growth and inventory_growth > revenue_growth * 2:
result["signals"].append({
"type": "存货增速异常",
"description": f"存货增速({inventory_growth:.1f}%)远高于营收增速({revenue_growth:.1f}%)",
"severity": "中"
})
# 3. 毛利率异常波动
current_gm = self._safe_float(current.get('销售毛利率'))
previous_gm = self._safe_float(previous.get('销售毛利率'))
if current_gm and previous_gm:
gm_change = abs(current_gm - previous_gm)
if gm_change > 10:
result["signals"].append({
"type": "毛利率大幅波动",
"description": f"毛利率变动{gm_change:.1f}个百分点,需关注原因",
"severity": "中"
})
# 4. 经营现金流与净利润背离 (需要现金流数据)
cash_flow = self.stock_data.get('financial_data', {}).get('cash_flow', [])
income = self.stock_data.get('financial_data', {}).get('income_statement', [])
if cash_flow and income:
try:
ocf = self._safe_float(cash_flow[0].get('经营活动产生的现金流量净额'))
net_profit = self._safe_float(income[0].get('净利润'))
if ocf and net_profit and net_profit > 0:
ocf_ratio = ocf / net_profit
if ocf_ratio < 0.5:
result["signals"].append({
"type": "现金流与利润背离",
"description": f"经营现金流/净利润 = {ocf_ratio:.1%},盈利质量存疑",
"severity": "高"
})
except (IndexError, KeyError):
pass
# 确定风险等级
high_severity = sum(1 for s in result["signals"] if s.get("severity") == "高")
medium_severity = sum(1 for s in result["signals"] if s.get("severity") == "中")
if high_severity > 0:
result["risk_level"] = "高"
elif medium_severity >= 2:
result["risk_level"] = "中"
else:
result["risk_level"] = "低"
return result
def generate_summary(self, level: str = "standard") -> Dict:
"""生成分析摘要"""
summary = {
"code": self.stock_data.get('code', ''),
"name": self.stock_data.get('basic_info', {}).get('name', ''),
"analysis_date": datetime.now().isoformat(),
"level": level
}
profitability = self.analyze_profitability()
solvency = self.analyze_solvency()
operation = self.analyze_operation()
growth = self.analyze_growth()
anomalies = self.detect_anomalies()
if level == "summary":
summary["profitability"] = profitability["assessment"]
summary["solvency"] = solvency["assessment"]
summary["growth"] = growth["assessment"]
summary["risk_level"] = anomalies["risk_level"]
else:
# standard 和 deep 级别共享基础分析结果
summary["profitability"] = profitability
summary["solvency"] = solvency
summary["operation"] = operation
summary["growth"] = growth
summary["dupont"] = self.analyze_dupont()
summary["anomalies"] = anomalies
if level == "deep":
summary["historical_indicators"] = self.stock_data.get('financial_indicators', [])
summary["score"] = self._calculate_score(profitability, solvency, growth, anomalies)
return summary
def _calculate_score(self, profitability, solvency, growth, anomalies) -> int:
"""计算综合评分 (0-100)"""
score = 50
# 盈利能力评分
roe = profitability.get("metrics", {}).get("当前ROE")
if roe:
if roe > 20:
score += 15
elif roe > 15:
score += 10
elif roe > 10:
score += 5
elif roe < 5:
score -= 5
# 偿债能力评分
risks = solvency.get("risks", [])
score += 10 if not risks else -len(risks) * 3
# 成长性评分
avg_growth = growth.get("metrics", {}).get("平均净利润增长率", 0) or 0
if avg_growth > 20:
score += 15
elif avg_growth > 10:
score += 10
elif avg_growth > 0:
score += 5
else:
score -= 5
# 风险扣分
risk_penalties = {"高": 15, "中": 8}
score -= risk_penalties.get(anomalies.get("risk_level", "低"), 0)
return max(0, min(100, score))
def compare_stocks(self, stocks_data: List[Dict]) -> Dict:
"""对比多只股票"""
comparison = {
"comparison_date": datetime.now().isoformat(),
"stocks": [],
"ranking": {}
}
stock_scores = []
for stock in stocks_data:
self.stock_data = stock
summary = self.generate_summary(level="summary")
comparison["stocks"].append({
"code": stock.get('code', ''),
"name": stock.get('basic_info', {}).get('name', ''),
"score": summary.get("score", 50),
"profitability": summary.get("profitability"),
"solvency": summary.get("solvency"),
"growth": summary.get("growth"),
"risk_level": summary.get("risk_level")
})
stock_scores.append((stock.get('code', ''), summary.get("score", 50)))
# 排名
stock_scores.sort(key=lambda x: x[1], reverse=True)
comparison["ranking"] = {code: rank + 1 for rank, (code, _) in enumerate(stock_scores)}
return comparison
@staticmethod
def _safe_float(value) -> Optional[float]:
"""安全转换为浮点数"""
if value is None or value == '' or value == '--':
return None
try:
if isinstance(value, str):
value = value.replace('%', '').replace(',', '')
return float(value)
except (ValueError, TypeError):
return None
def main():
parser = argparse.ArgumentParser(description="A股财务分析器")
parser.add_argument("--input", type=str, required=True, help="输入数据文件 (JSON)")
parser.add_argument("--level", type=str, default="standard",
choices=["summary", "standard", "deep"],
help="分析深度级别")
parser.add_argument("--mode", type=str, default="single",
choices=["single", "comparison"],
help="分析模式: single(单只)/comparison(对比)")
parser.add_argument("--output", type=str, help="输出文件路径 (JSON)")
args = parser.parse_args()
# 加载数据
with open(args.input, 'r', encoding='utf-8') as f:
data = json.load(f)
analyzer = FinancialAnalyzer()
if args.mode == "single":
analyzer.stock_data = data
result = analyzer.generate_summary(level=args.level)
else:
# 对比模式
stocks = data.get('stocks', [data])
result = analyzer.compare_stocks(stocks)
# 输出
output_json = json.dumps(result, ensure_ascii=False, indent=2, default=str)
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
f.write(output_json)
print(f"分析结果已保存到: {args.output}")
else:
print(output_json)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
A股股票筛选器
根据多种财务指标筛选符合条件的股票
依赖: pip install akshare pandas numpy
"""
import argparse
import json
import sys
import time
from datetime import datetime
from typing import List, Dict
from functools import wraps
try:
import akshare as ak
import pandas as pd
import numpy as np
except ImportError:
print("错误: 请先安装依赖库")
print("pip install akshare pandas numpy")
sys.exit(1)
def retry_on_failure(max_retries: int = 3, delay: float = 1.0):
"""网络请求重试装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
last_error = e
if attempt < max_retries - 1:
print(f" 重试 ({attempt + 1}/{max_retries})...")
time.sleep(delay * (attempt + 1))
raise last_error
return wrapper
return decorator
INDEX_CODE_MAP = {
"hs300": "000300",
"zz500": "000905",
"zz1000": "000852",
"cyb": "399006",
"kcb": "000688"
}
class StockScreener:
"""股票筛选器"""
def __init__(self):
self.all_stocks_data = None
def load_stock_data(self, scope: str = "hs300", custom_codes: List[str] = None) -> pd.DataFrame:
"""加载股票数据"""
print(f"正在加载股票数据 (范围: {scope})...")
try:
if scope == "all":
df = ak.stock_zh_a_spot_em()
elif scope in INDEX_CODE_MAP:
df = self._get_index_stocks_data(INDEX_CODE_MAP[scope])
elif scope.startswith("custom:") or custom_codes:
codes = custom_codes or scope.replace("custom:", "").split(",")
df = self._get_custom_stocks_data(codes)
else:
df = ak.stock_zh_a_spot_em()
self.all_stocks_data = df
print(f"已加载 {len(df)} 只股票数据")
return df
except Exception as e:
print(f"加载数据失败: {e}")
return pd.DataFrame()
@retry_on_failure(max_retries=3, delay=2.0)
def _get_all_stocks_realtime(self) -> pd.DataFrame:
"""获取全部A股实时数据(带重试)"""
return ak.stock_zh_a_spot_em()
@retry_on_failure(max_retries=3, delay=2.0)
def _get_index_constituents(self, index_code: str) -> list:
"""获取指数成分股列表(带重试)"""
df = ak.index_stock_cons(symbol=index_code)
return df['品种代码'].tolist()
def _get_index_stocks_data(self, index_code: str) -> pd.DataFrame:
"""获取指数成分股数据"""
try:
# 获取成分股列表
print(f" 获取指数 {index_code} 成分股...")
codes = self._get_index_constituents(index_code)
print(f" 成分股数量: {len(codes)}")
# 获取实时数据
print(" 获取实时行情...")
all_stocks = self._get_all_stocks_realtime()
df = all_stocks[all_stocks['代码'].isin(codes)]
return df
except Exception as e:
print(f"获取指数成分股失败: {e}")
return pd.DataFrame()
def _get_custom_stocks_data(self, codes: List[str]) -> pd.DataFrame:
"""获取自定义股票列表数据"""
try:
all_stocks = self._get_all_stocks_realtime()
df = all_stocks[all_stocks['代码'].isin(codes)]
return df
except Exception as e:
print(f"获取自定义股票数据失败: {e}")
return pd.DataFrame()
def _apply_numeric_filter(self, df: pd.DataFrame, column: str,
min_val: float = None, max_val: float = None) -> pd.DataFrame:
"""应用数值筛选条件"""
if column not in df.columns:
return df
numeric_col = pd.to_numeric(df[column], errors='coerce')
if min_val is not None:
df = df[numeric_col >= min_val]
if max_val is not None:
df = df[numeric_col <= max_val]
return df
def _find_column(self, df: pd.DataFrame, candidates: List[str]) -> str:
"""从候选列名中找到存在的列"""
for col in candidates:
if col in df.columns:
return col
return None
def apply_filters(self, df: pd.DataFrame, filters: Dict) -> pd.DataFrame:
"""应用筛选条件"""
filtered = df.copy()
# PE筛选
filtered = self._apply_numeric_filter(
filtered, '市盈率-动态',
min_val=filters.get('pe_min'),
max_val=filters.get('pe_max')
)
# PB筛选
filtered = self._apply_numeric_filter(
filtered, '市净率',
min_val=filters.get('pb_min'),
max_val=filters.get('pb_max')
)
# ROE筛选
if filters.get('roe_min') is not None:
roe_col = self._find_column(filtered, ['净资产收益率', 'ROE', '加权净资产收益率'])
if roe_col:
filtered = self._apply_numeric_filter(filtered, roe_col, min_val=filters['roe_min'])
# 资产负债率筛选
filtered = self._apply_numeric_filter(
filtered, '资产负债率',
max_val=filters.get('debt_ratio_max')
)
# 总市值筛选(转换为亿)
if '总市值' in filtered.columns:
if filters.get('market_cap_min') is not None or filters.get('market_cap_max') is not None:
filtered['总市值_亿'] = pd.to_numeric(filtered['总市值'], errors='coerce') / 1e8
filtered = self._apply_numeric_filter(
filtered, '总市值_亿',
min_val=filters.get('market_cap_min'),
max_val=filters.get('market_cap_max')
)
return filtered
def _get_numeric_value(self, row: pd.Series, column: str) -> float:
"""从行中获取数值,无效返回 NaN"""
return pd.to_numeric(row.get(column, np.nan), errors='coerce')
def calculate_score(self, row: pd.Series) -> float:
"""计算综合评分 (0-100)"""
score = 50
try:
# PE评分 (越低越好, 负数除外)
pe = self._get_numeric_value(row, '市盈率-动态')
if not np.isnan(pe) and pe > 0:
if pe < 10:
score += 15
elif pe < 15:
score += 10
elif pe < 20:
score += 5
elif pe > 50:
score -= 10
# PB评分
pb = self._get_numeric_value(row, '市净率')
if not np.isnan(pb) and pb > 0:
if 0.5 < pb < 1.5:
score += 10
elif 1.5 <= pb < 3:
score += 5
elif pb > 5:
score -= 5
# ROE评分
roe_col = self._find_column(row.index.to_frame(), ['净资产收益率', 'ROE', '加权净资产收益率'])
if roe_col:
roe = self._get_numeric_value(row, roe_col)
if not np.isnan(roe):
if roe > 20:
score += 15
elif roe > 15:
score += 10
elif roe > 10:
score += 5
elif roe < 5:
score -= 5
# 涨跌幅评分 (下跌可能是机会)
change = self._get_numeric_value(row, '涨跌幅')
if not np.isnan(change):
if -5 < change < 0:
score += 3
elif change < -5:
score += 5
except Exception:
pass
return max(0, min(100, score))
def screen(self, scope: str = "hs300", filters: Dict = None,
sort_by: str = "score", top_n: int = None) -> List[Dict]:
"""执行筛选"""
# 加载数据
if scope.startswith("custom:"):
codes = scope.replace("custom:", "").split(",")
df = self.load_stock_data(scope="custom", custom_codes=codes)
else:
df = self.load_stock_data(scope=scope)
if df.empty:
return []
# 应用筛选条件
if filters:
df = self.apply_filters(df, filters)
if df.empty:
return []
# 计算评分
df['评分'] = df.apply(self.calculate_score, axis=1)
# 排序
if sort_by == "score":
df = df.sort_values('评分', ascending=False)
elif sort_by == "pe":
pe_col = '市盈率-动态' if '市盈率-动态' in df.columns else None
if pe_col:
df = df.sort_values(pe_col, ascending=True)
elif sort_by == "pb":
if '市净率' in df.columns:
df = df.sort_values('市净率', ascending=True)
elif sort_by == "market_cap":
if '总市值' in df.columns:
df = df.sort_values('总市值', ascending=False)
# 限制数量
if top_n:
df = df.head(top_n)
# 转换为结果列表
results = []
for _, row in df.iterrows():
result = {
"代码": row.get('代码', ''),
"名称": row.get('名称', ''),
"最新价": row.get('最新价', ''),
"涨跌幅": row.get('涨跌幅', ''),
"市盈率": row.get('市盈率-动态', ''),
"市净率": row.get('市净率', ''),
"总市值(亿)": round(float(row.get('总市值', 0)) / 100000000, 2) if row.get('总市值') else '',
"评分": row.get('评分', 50)
}
results.append(result)
return results
def main():
parser = argparse.ArgumentParser(description="A股股票筛选器")
parser.add_argument("--scope", type=str, default="hs300",
help="筛选范围: all/hs300/zz500/zz1000/cyb/kcb/custom:代码1,代码2")
parser.add_argument("--pe-max", type=float, help="最大PE")
parser.add_argument("--pe-min", type=float, help="最小PE")
parser.add_argument("--pb-max", type=float, help="最大PB")
parser.add_argument("--pb-min", type=float, help="最小PB")
parser.add_argument("--roe-min", type=float, help="最小ROE (%)")
parser.add_argument("--debt-ratio-max", type=float, help="最大资产负债率 (%)")
parser.add_argument("--dividend-min", type=float, help="最小股息率 (%)")
parser.add_argument("--market-cap-min", type=float, help="最小市值 (亿)")
parser.add_argument("--market-cap-max", type=float, help="最大市值 (亿)")
parser.add_argument("--sort-by", type=str, default="score",
choices=["score", "pe", "pb", "market_cap"],
help="排序方式")
parser.add_argument("--top", type=int, default=50, help="返回前N只股票")
parser.add_argument("--output", type=str, help="输出文件路径 (JSON)")
args = parser.parse_args()
# 构建筛选条件
filter_keys = [
'pe_max', 'pe_min', 'pb_max', 'pb_min', 'roe_min',
'debt_ratio_max', 'dividend_min', 'market_cap_min', 'market_cap_max'
]
filters = {
k: getattr(args, k.replace('-', '_'))
for k in filter_keys
if getattr(args, k.replace('-', '_')) is not None
}
# 执行筛选
screener = StockScreener()
results = screener.screen(
scope=args.scope,
filters=filters if filters else None,
sort_by=args.sort_by,
top_n=args.top
)
# 输出结果
output = {
"screen_time": datetime.now().isoformat(),
"scope": args.scope,
"filters": filters,
"count": len(results),
"results": results
}
output_json = json.dumps(output, ensure_ascii=False, indent=2, default=str)
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
f.write(output_json)
print(f"筛选结果已保存到: {args.output}")
print(f"共筛选出 {len(results)} 只股票")
else:
print(output_json)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
A股估值计算器
提供DCF、DDM、相对估值等多种估值方法
依赖: pip install pandas numpy
"""
import argparse
import json
import sys
from datetime import datetime
from typing import Optional, Dict
try:
import pandas as pd
import numpy as np
except ImportError:
print("错误: 请先安装依赖库")
print("pip install pandas numpy")
sys.exit(1)
class ValuationCalculator:
"""估值计算器"""
def __init__(self, stock_data: Dict = None):
self.stock_data = stock_data or {}
self.results = {}
def load_data(self, file_path: str):
"""从JSON文件加载股票数据"""
with open(file_path, 'r', encoding='utf-8') as f:
self.stock_data = json.load(f)
def dcf_valuation(self, discount_rate: float = 10,
forecast_years: int = 5,
terminal_growth: float = 3) -> Dict:
"""
DCF现金流折现估值
参数:
discount_rate: 折现率 (%)
forecast_years: 预测期年数
terminal_growth: 永续增长率 (%)
"""
result = {
"method": "DCF现金流折现",
"parameters": {
"discount_rate": discount_rate,
"forecast_years": forecast_years,
"terminal_growth": terminal_growth
},
"calculation": {},
"intrinsic_value": None,
"per_share_value": None
}
# 获取现金流数据
cash_flow = self.stock_data.get('financial_data', {}).get('cash_flow', [])
basic_info = self.stock_data.get('basic_info', {})
if not cash_flow:
result["error"] = "无法获取现金流数据"
return result
try:
# 计算历史自由现金流
fcf_history = []
for cf in cash_flow[:4]: # 最近4个季度
ocf = self._safe_float(cf.get('经营活动产生的现金流量净额', 0))
capex = abs(self._safe_float(cf.get('购建固定资产、无形资产和其他长期资产支付的现金', 0)))
if ocf is not None:
fcf = ocf - capex
fcf_history.append(fcf)
if not fcf_history:
result["error"] = "无法计算自由现金流"
return result
# 年化FCF (简单年化)
annual_fcf = sum(fcf_history)
result["calculation"]["当前年化FCF"] = annual_fcf
# 估算增长率 (使用历史净利润增长率)
indicators = self.stock_data.get('financial_indicators', [])
growth_rate = 10 # 默认10%
if indicators:
hist_growth = self._safe_float(indicators[0].get('净利润增长率'))
if hist_growth and -50 < hist_growth < 100:
growth_rate = min(max(hist_growth, 0), 30) # 限制在0-30%之间
result["calculation"]["预计增长率"] = growth_rate
# 预测未来现金流
r = discount_rate / 100
g = growth_rate / 100
tg = terminal_growth / 100
pv_fcf = 0
future_fcf = []
for year in range(1, forecast_years + 1):
fcf = annual_fcf * ((1 + g) ** year)
pv = fcf / ((1 + r) ** year)
pv_fcf += pv
future_fcf.append({"year": year, "fcf": fcf, "pv": pv})
result["calculation"]["预测期现金流现值"] = pv_fcf
result["calculation"]["future_fcf"] = future_fcf
# 终值计算 (Gordon模型)
terminal_fcf = annual_fcf * ((1 + g) ** forecast_years) * (1 + tg)
terminal_value = terminal_fcf / (r - tg)
pv_terminal = terminal_value / ((1 + r) ** forecast_years)
result["calculation"]["终值"] = terminal_value
result["calculation"]["终值现值"] = pv_terminal
# 总价值
total_value = pv_fcf + pv_terminal
result["intrinsic_value"] = total_value
# 每股价值
total_shares = self._parse_shares(basic_info.get('total_shares', ''))
if total_shares:
per_share = total_value / total_shares
result["per_share_value"] = per_share
except Exception as e:
result["error"] = str(e)
return result
def ddm_valuation(self, required_return: float = 10,
dividend_growth: float = None) -> Dict:
"""
DDM股息折现估值 (Gordon模型)
参数:
required_return: 要求回报率 (%)
dividend_growth: 股息增长率 (%),如果为None则自动计算
"""
result = {
"method": "DDM股息折现",
"parameters": {
"required_return": required_return
},
"calculation": {},
"intrinsic_value": None,
"per_share_value": None
}
# 获取分红数据
dividend = self.stock_data.get('dividend', {})
dividend_history = dividend.get('dividend_history', [])
basic_info = self.stock_data.get('basic_info', {})
if not dividend_history:
result["error"] = "无分红历史数据"
result["note"] = "该公司可能分红较少或不稳定,不适合DDM估值"
return result
try:
# 计算最近股息
recent_dividends = []
for d in dividend_history[:5]: # 最近5年
div = self._safe_float(d.get('每股股利', d.get('派息', 0)))
if div and div > 0:
recent_dividends.append(div)
if len(recent_dividends) < 2:
result["error"] = "分红数据不足,无法使用DDM"
return result
current_dividend = recent_dividends[0]
result["calculation"]["当前每股股息"] = current_dividend
# 计算股息增长率
if dividend_growth is None:
if len(recent_dividends) >= 2:
# 计算复合增长率
years = len(recent_dividends) - 1
cagr = (recent_dividends[0] / recent_dividends[-1]) ** (1 / years) - 1
dividend_growth = cagr * 100
else:
dividend_growth = 3 # 默认3%
# 限制增长率
dividend_growth = min(max(dividend_growth, 0), required_return - 1)
result["parameters"]["dividend_growth"] = dividend_growth
result["calculation"]["股息增长率"] = dividend_growth
# Gordon模型: P = D1 / (r - g)
r = required_return / 100
g = dividend_growth / 100
d1 = current_dividend * (1 + g)
if r <= g:
result["error"] = "增长率不能大于或等于要求回报率"
return result
per_share_value = d1 / (r - g)
result["per_share_value"] = per_share_value
result["calculation"]["下期预期股息(D1)"] = d1
# 总价值
total_shares = self._parse_shares(basic_info.get('total_shares', ''))
if total_shares:
result["intrinsic_value"] = per_share_value * total_shares
except Exception as e:
result["error"] = str(e)
return result
def _assess_percentile(self, percentile: float) -> str:
"""根据分位数评估估值水平"""
if percentile < 20:
return "处于历史低位,可能被低估"
elif percentile < 40:
return "处于历史较低水平"
elif percentile < 60:
return "处于历史中等水平"
elif percentile < 80:
return "处于历史较高水平"
return "处于历史高位,估值偏贵"
def relative_valuation(self) -> Dict:
"""相对估值法,与行业均值和历史均值对比"""
result = {
"method": "相对估值",
"current_valuation": {},
"comparison": {},
"assessment": {}
}
valuation = self.stock_data.get('valuation', {})
price = self.stock_data.get('price', {})
basic_info = self.stock_data.get('basic_info', {})
try:
current_pe = self._safe_float(basic_info.get('pe_ttm'))
current_pb = self._safe_float(basic_info.get('pb'))
if not current_pe and valuation.get('latest'):
current_pe = self._safe_float(valuation['latest'].get('pe'))
current_pb = self._safe_float(valuation['latest'].get('pb'))
result["current_valuation"] = {
"PE_TTM": current_pe,
"PB": current_pb,
"当前价格": price.get('latest_price')
}
pe_percentile = valuation.get('pe_percentile')
pb_percentile = valuation.get('pb_percentile')
result["comparison"]["PE历史分位数"] = pe_percentile
result["comparison"]["PB历史分位数"] = pb_percentile
# 估值评估
if pe_percentile is not None:
result["assessment"]["PE评估"] = self._assess_percentile(pe_percentile)
if pb_percentile is not None:
result["assessment"]["PB评估"] = self._assess_percentile(pb_percentile)
# 计算基于历史均值的合理价格
current_price = price.get('latest_price', 0)
if current_pe and pe_percentile and pe_percentile > 0 and current_price and current_pe > 0:
fair_pe = current_pe * (50 / pe_percentile)
fair_price_pe = current_price * (fair_pe / current_pe)
result["comparison"]["基于PE的合理价格"] = round(fair_price_pe, 2)
except Exception as e:
result["error"] = str(e)
return result
def calculate_margin_of_safety(self, intrinsic_value: float,
current_price: float,
margin_pct: float = 30) -> Dict:
"""计算安全边际"""
result = {
"intrinsic_value": intrinsic_value,
"current_price": current_price,
"margin_of_safety_required": margin_pct
}
if not (intrinsic_value and current_price):
return result
actual_margin = (intrinsic_value - current_price) / intrinsic_value * 100
safety_price = intrinsic_value * (1 - margin_pct / 100)
result["actual_margin_of_safety"] = round(actual_margin, 2)
result["safety_price"] = round(safety_price, 2)
if current_price < safety_price:
result["conclusion"] = "低估 - 当前价格低于安全边际价格,具有投资价值"
elif current_price < intrinsic_value:
result["conclusion"] = "合理偏低 - 当前价格低于内在价值,但未达到安全边际"
else:
result["conclusion"] = "高估 - 当前价格高于内在价值"
return result
def comprehensive_valuation(self, discount_rate: float = 10,
terminal_growth: float = 3,
margin_of_safety: float = 30) -> Dict:
"""综合估值,结合多种方法得出估值结论"""
result = {
"code": self.stock_data.get('code', ''),
"name": self.stock_data.get('basic_info', {}).get('name', ''),
"valuation_date": datetime.now().isoformat(),
"methods": {},
"summary": {}
}
# 获取当前价格
current_price = self.stock_data.get('price', {}).get('latest_price')
# DCF估值
dcf = self.dcf_valuation(discount_rate=discount_rate,
terminal_growth=terminal_growth)
result["methods"]["DCF"] = dcf
# DDM估值
ddm = self.ddm_valuation()
result["methods"]["DDM"] = ddm
# 相对估值
relative = self.relative_valuation()
result["methods"]["相对估值"] = relative
# 汇总估值结果
valuations = []
if dcf.get('per_share_value'):
valuations.append(("DCF", dcf['per_share_value']))
if ddm.get('per_share_value'):
valuations.append(("DDM", ddm['per_share_value']))
if relative.get('comparison', {}).get('基于PE的合理价格'):
valuations.append(("相对估值", relative['comparison']['基于PE的合理价格']))
if valuations:
avg_value = sum(v[1] for v in valuations) / len(valuations)
result["summary"]["平均内在价值"] = round(avg_value, 2)
result["summary"]["估值方法数"] = len(valuations)
result["summary"]["各方法估值"] = {k: round(v, 2) for k, v in valuations}
# 安全边际计算
if current_price:
margin = self.calculate_margin_of_safety(
avg_value, current_price, margin_of_safety
)
result["summary"]["安全边际分析"] = margin
result["summary"]["当前价格"] = current_price
result["summary"]["建议买入价"] = margin.get('safety_price')
result["summary"]["投资结论"] = margin.get('conclusion')
return result
def _safe_float(self, value) -> Optional[float]:
"""安全转换为浮点数"""
if value is None or value == '' or value == '--':
return None
try:
if isinstance(value, str):
value = value.replace('%', '').replace(',', '').replace('亿', '')
return float(value)
except (ValueError, TypeError):
return None
def _parse_shares(self, shares_str: str) -> Optional[float]:
"""解析股份数"""
if not shares_str:
return None
try:
if '亿' in str(shares_str):
return float(shares_str.replace('亿', '')) * 100000000
elif '万' in str(shares_str):
return float(shares_str.replace('万', '')) * 10000
else:
return float(shares_str)
except (ValueError, TypeError):
return None
def main():
parser = argparse.ArgumentParser(description="A股估值计算器")
parser.add_argument("--input", type=str, help="输入数据文件 (JSON)")
parser.add_argument("--code", type=str, help="股票代码 (如果不提供input)")
parser.add_argument("--methods", type=str, default="all",
help="估值方法: dcf/ddm/relative/all")
parser.add_argument("--discount-rate", type=float, default=10,
help="折现率 (%)")
parser.add_argument("--terminal-growth", type=float, default=3,
help="永续增长率 (%)")
parser.add_argument("--margin-of-safety", type=float, default=30,
help="安全边际要求 (%)")
parser.add_argument("--output", type=str, help="输出文件路径 (JSON)")
args = parser.parse_args()
calculator = ValuationCalculator()
if args.input:
calculator.load_data(args.input)
elif args.code:
# 如果只提供代码,需要先获取数据
print("请先使用 data_fetcher.py 获取数据,然后使用 --input 参数")
sys.exit(1)
else:
print("请提供 --input 或 --code 参数")
sys.exit(1)
# 执行估值
valuation_methods = {
"dcf": lambda: calculator.dcf_valuation(
discount_rate=args.discount_rate,
terminal_growth=args.terminal_growth
),
"ddm": calculator.ddm_valuation,
"relative": calculator.relative_valuation,
}
if args.methods in valuation_methods:
result = valuation_methods[args.methods]()
else:
result = calculator.comprehensive_valuation(
discount_rate=args.discount_rate,
terminal_growth=args.terminal_growth,
margin_of_safety=args.margin_of_safety
)
# 输出
output_json = json.dumps(result, ensure_ascii=False, indent=2, default=str)
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
f.write(output_json)
print(f"估值结果已保存到: {args.output}")
else:
print(output_json)
if __name__ == "__main__":
main()
{{stock_name}} ({{stock_code}}) 投资分析报告
分析日期: {{analysis_date}} 分析级别: {{analysis_level}} 综合评分: {{overall_score}}/100
---
一、公司概况
| 项目 | 内容 |
|---|---|
| 股票代码 | {{stock_code}} |
| 股票名称 | {{stock_name}} |
| 所属行业 | {{industry}} |
| 总市值 | {{market_cap}} |
| 流通市值 | {{float_cap}} |
| 上市日期 | {{listing_date}} |
主营业务
{{main_business}}
---
二、财务健康分析
2.1 盈利能力
| 指标 | 当前值 | 行业均值 | 评价 |
|---|---|---|---|
| ROE | {{roe}}% | {{industry_roe}}% | {{roe_assessment}} |
| ROA | {{roa}}% | {{industry_roa}}% | {{roa_assessment}} |
| 毛利率 | {{gross_margin}}% | {{industry_gross_margin}}% | {{gross_margin_assessment}} |
| 净利率 | {{net_margin}}% | {{industry_net_margin}}% | {{net_margin_assessment}} |
杜邦分析:
- 净利率: {{net_margin}}%
- 资产周转率: {{asset_turnover}}
- 权益乘数: {{equity_multiplier}}
- ROE驱动因素: {{roe_driver}}
2.2 偿债能力
| 指标 | 当前值 | 参考标准 | 状态 |
|---|---|---|---|
| 资产负债率 | {{debt_ratio}}% | < 60% | {{debt_ratio_status}} |
| 流动比率 | {{current_ratio}} | > 1.5 | {{current_ratio_status}} |
| 速动比率 | {{quick_ratio}} | > 1 | {{quick_ratio_status}} |
| 利息覆盖倍数 | {{interest_coverage}} | > 3 | {{interest_coverage_status}} |
2.3 运营效率
| 指标 | 当前值 | 趋势 |
|---|---|---|
| 应收账款周转天数 | {{ar_days}}天 | {{ar_trend}} |
| 存货周转天数 | {{inventory_days}}天 | {{inventory_trend}} |
| 总资产周转率 | {{asset_turnover}} | {{asset_turnover_trend}} |
---
三、成长性分析
3.1 增长指标
| 指标 | 最近一期 | 近3年平均 | 趋势 |
|---|---|---|---|
| 营收增长率 | {{revenue_growth}}% | {{avg_revenue_growth}}% | {{revenue_trend}} |
| 净利润增长率 | {{profit_growth}}% | {{avg_profit_growth}}% | {{profit_trend}} |
| EPS增长率 | {{eps_growth}}% | {{avg_eps_growth}}% | {{eps_trend}} |
3.2 成长性评估
{{growth_assessment}}
---
四、估值分析
4.1 当前估值
| 指标 | 当前值 | 历史分位数 | 行业均值 |
|---|---|---|---|
| PE (TTM) | {{pe_ttm}} | {{pe_percentile}}% | {{industry_pe}} |
| PB | {{pb}} | {{pb_percentile}}% | {{industry_pb}} |
| PS | {{ps}} | {{ps_percentile}}% | {{industry_ps}} |
4.2 内在价值估算
| 估值方法 | 每股价值 | 说明 |
|---|---|---|
| DCF现金流折现 | ¥{{dcf_value}} | 折现率{{discount_rate}}%,永续增长{{terminal_growth}}% |
| DDM股息折现 | ¥{{ddm_value}} | 股息增长率{{dividend_growth}}% |
| 相对估值 | ¥{{relative_value}} | 基于历史PE均值 |
| 平均估值 | ¥{{avg_value}} | - |
4.3 安全边际
| 项目 | 数值 |
|---|---|
| 当前价格 | ¥{{current_price}} |
| 平均内在价值 | ¥{{avg_value}} |
| 安全边际 | {{margin_of_safety}}% |
| 建议买入价 | ¥{{safety_price}} |
估值结论: {{valuation_conclusion}}
---
五、风险提示
5.1 财务异常检测
风险等级: {{risk_level}}
{{#if anomalies}}
| 异常类型 | 描述 | 严重程度 |
|---|
{{#each anomalies}} | {{this.type}} | {{this.description}} | {{this.severity}} | {{/each}} {{else}} 未检测到明显财务异常 {{/if}}
5.2 股东相关风险
{{#if holder_risks}} {{#each holder_risks}}
- {{this}}
{{/each}} {{else}}
- 暂无明显股东相关风险
{{/if}}
5.3 行业/政策风险
{{industry_risks}}
---
六、投资结论
综合评分
| 维度 | 评分 | 权重 | 加权得分 |
|---|---|---|---|
| 盈利能力 | {{profitability_score}}/100 | 30% | {{profitability_weighted}} |
| 财务安全 | {{safety_score}}/100 | 20% | {{safety_weighted}} |
| 成长性 | {{growth_score}}/100 | 25% | {{growth_weighted}} |
| 估值水平 | {{valuation_score}}/100 | 25% | {{valuation_weighted}} |
| 综合评分 | {{overall_score}}/100 | - | - |
投资建议
{{investment_recommendation}}
关键观察点
{{#each key_observations}}
- {{this}}
{{/each}}
---
免责声明
本报告基于公开财务数据分析,仅供参考,不构成投资建议。投资有风险,入市需谨慎。
数据来源: akshare (公开财务数据) 分析工具: china-stock-analysis skill 报告生成时间: {{report_time}}
---
报告模板使用说明
上述模板中的 {{变量名}} 需要在生成报告时替换为实际值。
报告级别说明
摘要级 (Summary)
只包含:
- 基本信息
- 关键财务指标
- 投资结论
标准级 (Standard)
包含:
- 完整财务分析
- 估值分析
- 风险提示
- 投资建议
深度级 (Deep)
在标准级基础上增加:
- 历史数据趋势图描述
- 详细财务报表分析
- 竞争对手对比
- 行业深度分析
评分标准
盈利能力评分
- ROE > 20%: +15分
- ROE 15-20%: +10分
- ROE 10-15%: +5分
- ROE < 10%: 0分
财务安全评分
- 无风险指标: +10分
- 每个风险指标: -5分
成长性评分
- 增长率 > 20%: +15分
- 增长率 10-20%: +10分
- 增长率 0-10%: +5分
- 负增长: -5分
估值水平评分
- 历史分位数 < 20%: +15分 (低估)
- 历史分位数 20-40%: +10分
- 历史分位数 40-60%: +5分
- 历史分位数 60-80%: 0分
- 历史分位数 > 80%: -10分 (高估)
Related skills
FAQ
Where does the data come from?
It uses akshare to fetch public financial data; the SKILL.md checks akshare is installed before any analysis.
Which valuation methods are supported?
The valuation calculator supports DCF, DDM, and relative valuation, with configurable discount rate, growth rate, and margin of safety.