init repo
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Env
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
|
||||||
|
# Node
|
||||||
|
node_modules/
|
||||||
|
.next/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.nuxt/
|
||||||
|
.output/
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
*.log
|
||||||
3
.memory/worklog.json
Normal file
3
.memory/worklog.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"entries": []
|
||||||
|
}
|
||||||
15
.project.json
Normal file
15
.project.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "美股低价值公司分析系统",
|
||||||
|
"description": "一个专门用于分析美股低价值投资机会的智能分析系统,提供全面的财务分析、估值计算和投资建议。",
|
||||||
|
"status": "archived",
|
||||||
|
"kind": "research",
|
||||||
|
"created": "2025-09-20",
|
||||||
|
"stack": [
|
||||||
|
"Python"
|
||||||
|
],
|
||||||
|
"urls": [],
|
||||||
|
"worklog": {
|
||||||
|
"path": ".memory/worklog.json",
|
||||||
|
"auto": true
|
||||||
|
}
|
||||||
|
}
|
||||||
21
AGENTS.md
Normal file
21
AGENTS.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# 美股低价值公司分析系统 Agent Rules
|
||||||
|
|
||||||
|
## Must Read First
|
||||||
|
|
||||||
|
- `.project.json` 是机器真源:公网链接、快捷登录、凭证引用都以它为准
|
||||||
|
- `RULES.md` 是人工规则和部署事实:启动命令、平台、域名、注意事项都写这里
|
||||||
|
- 不允许编造不存在的域名、账号、密码;未知就保持空白并明确标记待补充
|
||||||
|
|
||||||
|
## Deployment Metadata Contract
|
||||||
|
|
||||||
|
- 任何任务只要新增、删除或修改公网地址,必须在同一次任务里更新 `.project.json`
|
||||||
|
- `urls[]` 推荐显式写 `type`:`app`、`backend`、`docs`、`admin`、`repo`
|
||||||
|
- 项目专属的网页登录信息,如果允许放进仓库,就写 `.project.json.quick_login`
|
||||||
|
- 不能直接入库的敏感登录,不要伪造 `quick_login`,改为写 `.project.json.credentials` 引用
|
||||||
|
- 数据库密码、API Key、服务器 root 密码,不属于 `quick_login`
|
||||||
|
|
||||||
|
## Completion Gate
|
||||||
|
|
||||||
|
- 部署完成后,不允许在 `.project.json` 缺少最新公网链接的状态下结束任务
|
||||||
|
- 部署完成后,必须同步更新 `RULES.md` 的部署事实
|
||||||
|
- 如果只更新了代码但没回写部署元数据,这个任务不算完成
|
||||||
21
CLAUDE.md
Normal file
21
CLAUDE.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# 美股低价值公司分析系统 Agent Rules
|
||||||
|
|
||||||
|
## Must Read First
|
||||||
|
|
||||||
|
- `.project.json` 是机器真源:公网链接、快捷登录、凭证引用都以它为准
|
||||||
|
- `RULES.md` 是人工规则和部署事实:启动命令、平台、域名、注意事项都写这里
|
||||||
|
- 不允许编造不存在的域名、账号、密码;未知就保持空白并明确标记待补充
|
||||||
|
|
||||||
|
## Deployment Metadata Contract
|
||||||
|
|
||||||
|
- 任何任务只要新增、删除或修改公网地址,必须在同一次任务里更新 `.project.json`
|
||||||
|
- `urls[]` 推荐显式写 `type`:`app`、`backend`、`docs`、`admin`、`repo`
|
||||||
|
- 项目专属的网页登录信息,如果允许放进仓库,就写 `.project.json.quick_login`
|
||||||
|
- 不能直接入库的敏感登录,不要伪造 `quick_login`,改为写 `.project.json.credentials` 引用
|
||||||
|
- 数据库密码、API Key、服务器 root 密码,不属于 `quick_login`
|
||||||
|
|
||||||
|
## Completion Gate
|
||||||
|
|
||||||
|
- 部署完成后,不允许在 `.project.json` 缺少最新公网链接的状态下结束任务
|
||||||
|
- 部署完成后,必须同步更新 `RULES.md` 的部署事实
|
||||||
|
- 如果只更新了代码但没回写部署元数据,这个任务不算完成
|
||||||
177
README.md
Normal file
177
README.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# 美股低价值公司分析系统
|
||||||
|
|
||||||
|
一个专门用于分析美股低价值投资机会的智能分析系统,提供全面的财务分析、估值计算和投资建议。
|
||||||
|
|
||||||
|
## 功能特点
|
||||||
|
|
||||||
|
### 🎯 核心功能
|
||||||
|
- **实时数据收集**: 从Yahoo Finance获取最新股价和财务数据
|
||||||
|
- **智能估值分析**: DCF模型、相对估值法、财务指标分析
|
||||||
|
- **风险评估**: 波动率、Beta系数、财务健康度评估
|
||||||
|
- **投资建议**: 基于多维度分析的买入/持有/卖出建议
|
||||||
|
- **报告生成**: 自动生成PDF详细报告和文本摘要
|
||||||
|
|
||||||
|
### 📊 分析维度
|
||||||
|
1. **估值吸引力** (40%权重)
|
||||||
|
- PE、PB、PS、PEG比率分析
|
||||||
|
- DCF内在价值计算
|
||||||
|
- 相对估值对比
|
||||||
|
|
||||||
|
2. **财务健康度** (30%权重)
|
||||||
|
- 流动性指标 (流动比率、速动比率)
|
||||||
|
- 偿债能力 (债务比率、利息覆盖倍数)
|
||||||
|
- 盈利能力 (ROE、ROA、利润率)
|
||||||
|
|
||||||
|
3. **成长性** (20%权重)
|
||||||
|
- 收入增长率分析
|
||||||
|
- 利润增长率分析
|
||||||
|
- 历史增长趋势
|
||||||
|
|
||||||
|
4. **风险控制** (10%权重)
|
||||||
|
- Beta系数分析
|
||||||
|
- 波动率计算
|
||||||
|
- 最大回撤分析
|
||||||
|
|
||||||
|
## 安装和使用
|
||||||
|
|
||||||
|
### 环境要求
|
||||||
|
- Python 3.8+
|
||||||
|
- 网络连接(用于获取实时数据)
|
||||||
|
|
||||||
|
### 安装依赖
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用方法
|
||||||
|
|
||||||
|
#### 1. 单只股票完整分析
|
||||||
|
```bash
|
||||||
|
python main.py AAPL
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 强制刷新数据重新分析
|
||||||
|
```bash
|
||||||
|
python main.py AAPL --refresh
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 快速筛选(检查是否符合低价值标准)
|
||||||
|
```bash
|
||||||
|
python main.py --screen AAPL
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. 批量分析多只股票
|
||||||
|
```bash
|
||||||
|
python main.py --batch AAPL,MSFT,GOOGL,TSLA
|
||||||
|
```
|
||||||
|
|
||||||
|
## 输出文件
|
||||||
|
|
||||||
|
### 报告文件
|
||||||
|
- `reports/{股票代码}_analysis_report.pdf` - 详细PDF分析报告
|
||||||
|
- `reports/{股票代码}_summary.txt` - 简要文本摘要
|
||||||
|
- `reports/charts/` - 图表文件(HTML格式)
|
||||||
|
|
||||||
|
### 数据库
|
||||||
|
- `stock_analysis.db` - SQLite数据库,存储历史数据和分析结果
|
||||||
|
|
||||||
|
## 分析流程
|
||||||
|
|
||||||
|
### 每次分析的标准流程:
|
||||||
|
|
||||||
|
1. **数据获取阶段** (30秒内)
|
||||||
|
- 验证股票代码和基本信息
|
||||||
|
- 获取最新股价和交易数据
|
||||||
|
- 下载最近4个季度的财务数据
|
||||||
|
|
||||||
|
2. **快速筛选阶段** (1分钟内)
|
||||||
|
- 计算关键估值指标(PE、PB、PS等)
|
||||||
|
- 检查是否符合"低价值"标准
|
||||||
|
- 识别异常财务指标
|
||||||
|
|
||||||
|
3. **深度分析阶段** (2-3分钟)
|
||||||
|
- 进行DCF估值计算
|
||||||
|
- 分析财务健康度
|
||||||
|
- 评估行业地位和竞争优势
|
||||||
|
- 计算风险指标
|
||||||
|
|
||||||
|
4. **报告生成阶段** (1分钟内)
|
||||||
|
- 生成综合分析报告
|
||||||
|
- 提供投资建议和风险提示
|
||||||
|
- 保存分析结果到数据库
|
||||||
|
|
||||||
|
## 低价值投资标准
|
||||||
|
|
||||||
|
系统默认的低价值投资筛选标准:
|
||||||
|
|
||||||
|
- **PE比率**: ≤ 15
|
||||||
|
- **PB比率**: ≤ 1.5
|
||||||
|
- **PS比率**: ≤ 2.0
|
||||||
|
- **最小市值**: ≥ 1亿美元
|
||||||
|
- **债务比率**: ≤ 60%
|
||||||
|
- **流动比率**: ≥ 1.2
|
||||||
|
- **ROE**: ≥ 5%
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
可以在 `config.py` 中修改分析参数:
|
||||||
|
|
||||||
|
```python
|
||||||
|
LOW_VALUE_CRITERIA = {
|
||||||
|
'max_pe_ratio': 15, # 最大市盈率
|
||||||
|
'max_pb_ratio': 1.5, # 最大市净率
|
||||||
|
'max_ps_ratio': 2.0, # 最大市销率
|
||||||
|
'min_market_cap': 100_000_000, # 最小市值
|
||||||
|
'max_debt_ratio': 0.6, # 最大债务比率
|
||||||
|
'min_current_ratio': 1.2, # 最小流动比率
|
||||||
|
'min_roe': 0.05, # 最小ROE
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **数据来源**: 系统使用Yahoo Finance作为主要数据源,数据可能存在延迟
|
||||||
|
2. **投资风险**: 本系统仅供投资参考,不构成投资建议
|
||||||
|
3. **数据准确性**: 建议结合其他数据源进行交叉验证
|
||||||
|
4. **网络要求**: 需要稳定的网络连接获取实时数据
|
||||||
|
|
||||||
|
## 系统架构
|
||||||
|
|
||||||
|
```
|
||||||
|
├── main.py # 主程序入口
|
||||||
|
├── config.py # 配置文件
|
||||||
|
├── database.py # 数据库管理
|
||||||
|
├── data_collector.py # 数据收集模块
|
||||||
|
├── analysis_engine.py # 分析引擎
|
||||||
|
├── report_generator.py # 报告生成器
|
||||||
|
├── requirements.txt # 依赖包列表
|
||||||
|
└── README.md # 使用说明
|
||||||
|
```
|
||||||
|
|
||||||
|
## 示例输出
|
||||||
|
|
||||||
|
### 控制台输出示例
|
||||||
|
```
|
||||||
|
=== AAPL 分析结果 ===
|
||||||
|
投资建议: 买入
|
||||||
|
综合评分: 75.2/100
|
||||||
|
|
||||||
|
关键优势:
|
||||||
|
• 估值吸引力高
|
||||||
|
• 财务健康度良好
|
||||||
|
• 成长性优秀
|
||||||
|
|
||||||
|
关键担忧:
|
||||||
|
• 风险较高
|
||||||
|
|
||||||
|
详细报告已生成: reports/AAPL_analysis_report.pdf
|
||||||
|
简要报告已生成: reports/AAPL_summary.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技术支持
|
||||||
|
|
||||||
|
如有问题或建议,请检查:
|
||||||
|
1. 网络连接是否正常
|
||||||
|
2. 股票代码是否正确
|
||||||
|
3. 依赖包是否完整安装
|
||||||
|
4. 查看日志文件 `stock_analysis.log`
|
||||||
25
RULES.md
Normal file
25
RULES.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# 20250920-股票分析
|
||||||
|
|
||||||
|
## 启动
|
||||||
|
|
||||||
|
- `python3 main.py`
|
||||||
|
|
||||||
|
## 核心文件
|
||||||
|
|
||||||
|
- `README.md`
|
||||||
|
- `analysis_engine.py`
|
||||||
|
- `analysis_reports/`
|
||||||
|
- `analysis_storage.py`
|
||||||
|
- `config.py`
|
||||||
|
- `create_rvph_report.py`
|
||||||
|
- `data_collector.py`
|
||||||
|
- `database.py`
|
||||||
|
- `main.py`
|
||||||
|
- `report_generator.py`
|
||||||
|
- `reports/`
|
||||||
|
- `run_analysis.py`
|
||||||
|
|
||||||
|
## 规则
|
||||||
|
|
||||||
|
- 研究/分析类项目,核心产出为文档
|
||||||
|
- 修改前先通读已有文档,保持结论一致性
|
||||||
430
analysis_engine.py
Normal file
430
analysis_engine.py
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
"""
|
||||||
|
分析引擎 - 核心估值和财务分析模块
|
||||||
|
"""
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
from typing import Dict, List, Tuple, Optional
|
||||||
|
import logging
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from config import LOW_VALUE_CRITERIA
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class StockAnalyzer:
|
||||||
|
def __init__(self):
|
||||||
|
self.criteria = LOW_VALUE_CRITERIA
|
||||||
|
|
||||||
|
def calculate_valuation_metrics(self, data: Dict) -> Dict:
|
||||||
|
"""计算估值指标"""
|
||||||
|
try:
|
||||||
|
key_metrics = data.get('key_metrics', {})
|
||||||
|
company_info = data.get('company_info', {})
|
||||||
|
|
||||||
|
# 基础估值指标
|
||||||
|
pe_ratio = key_metrics.get('pe_ratio', 0)
|
||||||
|
pb_ratio = key_metrics.get('pb_ratio', 0)
|
||||||
|
ps_ratio = key_metrics.get('ps_ratio', 0)
|
||||||
|
peg_ratio = key_metrics.get('peg_ratio', 0)
|
||||||
|
|
||||||
|
# 市值
|
||||||
|
market_cap = company_info.get('market_cap', 0)
|
||||||
|
|
||||||
|
# 计算估值分数 (0-100分,分数越高表示越被低估)
|
||||||
|
valuation_score = 0
|
||||||
|
|
||||||
|
# PE比率评分
|
||||||
|
if pe_ratio > 0 and pe_ratio <= self.criteria['max_pe_ratio']:
|
||||||
|
pe_score = max(0, 100 - (pe_ratio / self.criteria['max_pe_ratio']) * 50)
|
||||||
|
valuation_score += pe_score * 0.3
|
||||||
|
|
||||||
|
# PB比率评分
|
||||||
|
if pb_ratio > 0 and pb_ratio <= self.criteria['max_pb_ratio']:
|
||||||
|
pb_score = max(0, 100 - (pb_ratio / self.criteria['max_pb_ratio']) * 50)
|
||||||
|
valuation_score += pb_score * 0.3
|
||||||
|
|
||||||
|
# PS比率评分
|
||||||
|
if ps_ratio > 0 and ps_ratio <= self.criteria['max_ps_ratio']:
|
||||||
|
ps_score = max(0, 100 - (ps_ratio / self.criteria['max_ps_ratio']) * 50)
|
||||||
|
valuation_score += ps_score * 0.2
|
||||||
|
|
||||||
|
# PEG比率评分
|
||||||
|
if peg_ratio > 0 and peg_ratio <= 1.0:
|
||||||
|
peg_score = max(0, 100 - peg_ratio * 50)
|
||||||
|
valuation_score += peg_score * 0.2
|
||||||
|
|
||||||
|
return {
|
||||||
|
'pe_ratio': pe_ratio,
|
||||||
|
'pb_ratio': pb_ratio,
|
||||||
|
'ps_ratio': ps_ratio,
|
||||||
|
'peg_ratio': peg_ratio,
|
||||||
|
'market_cap': market_cap,
|
||||||
|
'valuation_score': min(100, max(0, valuation_score)),
|
||||||
|
'is_undervalued': pe_ratio <= self.criteria['max_pe_ratio'] and
|
||||||
|
pb_ratio <= self.criteria['max_pb_ratio'] and
|
||||||
|
ps_ratio <= self.criteria['max_ps_ratio']
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"计算估值指标失败: {e}")
|
||||||
|
return {'valuation_score': 0, 'is_undervalued': False}
|
||||||
|
|
||||||
|
def calculate_financial_health(self, data: Dict) -> Dict:
|
||||||
|
"""计算财务健康度"""
|
||||||
|
try:
|
||||||
|
key_metrics = data.get('key_metrics', {})
|
||||||
|
financial_statements = data.get('financial_statements', {})
|
||||||
|
|
||||||
|
# 获取最新财务数据
|
||||||
|
latest_financial = self._get_latest_financial_data(financial_statements)
|
||||||
|
|
||||||
|
# 流动性指标
|
||||||
|
current_ratio = key_metrics.get('current_ratio', 0)
|
||||||
|
quick_ratio = key_metrics.get('quick_ratio', 0)
|
||||||
|
|
||||||
|
# 偿债能力指标
|
||||||
|
debt_to_equity = key_metrics.get('debt_to_equity', 0)
|
||||||
|
debt_ratio = self._calculate_debt_ratio(latest_financial)
|
||||||
|
|
||||||
|
# 盈利能力指标
|
||||||
|
roe = key_metrics.get('return_on_equity', 0)
|
||||||
|
roa = key_metrics.get('return_on_assets', 0)
|
||||||
|
profit_margin = key_metrics.get('profit_margin', 0)
|
||||||
|
operating_margin = key_metrics.get('operating_margin', 0)
|
||||||
|
|
||||||
|
# 计算财务健康分数
|
||||||
|
health_score = 0
|
||||||
|
|
||||||
|
# 流动性评分 (30%)
|
||||||
|
if current_ratio >= self.criteria['min_current_ratio']:
|
||||||
|
liquidity_score = min(100, (current_ratio / 2.0) * 100)
|
||||||
|
health_score += liquidity_score * 0.3
|
||||||
|
|
||||||
|
# 偿债能力评分 (30%)
|
||||||
|
if debt_ratio <= self.criteria['max_debt_ratio']:
|
||||||
|
debt_score = max(0, 100 - (debt_ratio / self.criteria['max_debt_ratio']) * 100)
|
||||||
|
health_score += debt_score * 0.3
|
||||||
|
|
||||||
|
# 盈利能力评分 (40%)
|
||||||
|
if roe >= self.criteria['min_roe']:
|
||||||
|
profitability_score = min(100, (roe / 0.2) * 100) # 20% ROE为满分
|
||||||
|
health_score += profitability_score * 0.4
|
||||||
|
|
||||||
|
return {
|
||||||
|
'current_ratio': current_ratio,
|
||||||
|
'quick_ratio': quick_ratio,
|
||||||
|
'debt_to_equity': debt_to_equity,
|
||||||
|
'debt_ratio': debt_ratio,
|
||||||
|
'roe': roe,
|
||||||
|
'roa': roa,
|
||||||
|
'profit_margin': profit_margin,
|
||||||
|
'operating_margin': operating_margin,
|
||||||
|
'health_score': min(100, max(0, health_score)),
|
||||||
|
'is_healthy': (current_ratio >= self.criteria['min_current_ratio'] and
|
||||||
|
debt_ratio <= self.criteria['max_debt_ratio'] and
|
||||||
|
roe >= self.criteria['min_roe'])
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"计算财务健康度失败: {e}")
|
||||||
|
return {'health_score': 0, 'is_healthy': False}
|
||||||
|
|
||||||
|
def calculate_growth_metrics(self, data: Dict) -> Dict:
|
||||||
|
"""计算成长性指标"""
|
||||||
|
try:
|
||||||
|
key_metrics = data.get('key_metrics', {})
|
||||||
|
financial_statements = data.get('financial_statements', {})
|
||||||
|
|
||||||
|
# 收入增长率
|
||||||
|
revenue_growth = key_metrics.get('revenue_growth', 0)
|
||||||
|
earnings_growth = key_metrics.get('earnings_growth', 0)
|
||||||
|
|
||||||
|
# 计算历史增长率
|
||||||
|
historical_growth = self._calculate_historical_growth(financial_statements)
|
||||||
|
|
||||||
|
# 成长性评分
|
||||||
|
growth_score = 0
|
||||||
|
|
||||||
|
# 收入增长率评分
|
||||||
|
if revenue_growth > 0:
|
||||||
|
revenue_score = min(100, revenue_growth * 100)
|
||||||
|
growth_score += revenue_score * 0.4
|
||||||
|
|
||||||
|
# 利润增长率评分
|
||||||
|
if earnings_growth > 0:
|
||||||
|
earnings_score = min(100, earnings_growth * 100)
|
||||||
|
growth_score += earnings_score * 0.4
|
||||||
|
|
||||||
|
# 历史增长率评分
|
||||||
|
if historical_growth > 0:
|
||||||
|
historical_score = min(100, historical_growth * 100)
|
||||||
|
growth_score += historical_score * 0.2
|
||||||
|
|
||||||
|
return {
|
||||||
|
'revenue_growth': revenue_growth,
|
||||||
|
'earnings_growth': earnings_growth,
|
||||||
|
'historical_growth': historical_growth,
|
||||||
|
'growth_score': min(100, max(0, growth_score)),
|
||||||
|
'is_growing': revenue_growth > 0.05 and earnings_growth > 0.05 # 5%以上增长
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"计算成长性指标失败: {e}")
|
||||||
|
return {'growth_score': 0, 'is_growing': False}
|
||||||
|
|
||||||
|
def calculate_risk_metrics(self, data: Dict) -> Dict:
|
||||||
|
"""计算风险指标"""
|
||||||
|
try:
|
||||||
|
key_metrics = data.get('key_metrics', {})
|
||||||
|
stock_prices = data.get('stock_prices', pd.DataFrame())
|
||||||
|
|
||||||
|
# Beta系数
|
||||||
|
beta = key_metrics.get('beta', 1.0)
|
||||||
|
|
||||||
|
# 计算波动率
|
||||||
|
volatility = self._calculate_volatility(stock_prices)
|
||||||
|
|
||||||
|
# 计算最大回撤
|
||||||
|
max_drawdown = self._calculate_max_drawdown(stock_prices)
|
||||||
|
|
||||||
|
# 风险评分 (分数越高表示风险越低)
|
||||||
|
risk_score = 100
|
||||||
|
|
||||||
|
# Beta风险评分
|
||||||
|
if beta > 1.5:
|
||||||
|
risk_score -= (beta - 1.5) * 20
|
||||||
|
elif beta < 0.8:
|
||||||
|
risk_score -= (0.8 - beta) * 10
|
||||||
|
|
||||||
|
# 波动率风险评分
|
||||||
|
if volatility > 0.3: # 30%以上波动率
|
||||||
|
risk_score -= (volatility - 0.3) * 100
|
||||||
|
|
||||||
|
# 回撤风险评分
|
||||||
|
if max_drawdown > 0.2: # 20%以上回撤
|
||||||
|
risk_score -= (max_drawdown - 0.2) * 100
|
||||||
|
|
||||||
|
return {
|
||||||
|
'beta': beta,
|
||||||
|
'volatility': volatility,
|
||||||
|
'max_drawdown': max_drawdown,
|
||||||
|
'risk_score': max(0, min(100, risk_score)),
|
||||||
|
'risk_level': self._get_risk_level(risk_score)
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"计算风险指标失败: {e}")
|
||||||
|
return {'risk_score': 50, 'risk_level': '中等'}
|
||||||
|
|
||||||
|
def perform_dcf_valuation(self, data: Dict) -> Dict:
|
||||||
|
"""执行DCF估值分析"""
|
||||||
|
try:
|
||||||
|
financial_statements = data.get('financial_statements', {})
|
||||||
|
key_metrics = data.get('key_metrics', {})
|
||||||
|
|
||||||
|
# 获取最新财务数据
|
||||||
|
latest_financial = self._get_latest_financial_data(financial_statements)
|
||||||
|
|
||||||
|
if not latest_financial:
|
||||||
|
return {'dcf_value': 0, 'dcf_score': 0}
|
||||||
|
|
||||||
|
# 基础参数
|
||||||
|
revenue = latest_financial.get('revenue', 0)
|
||||||
|
net_income = latest_financial.get('net_income', 0)
|
||||||
|
total_assets = latest_financial.get('total_assets', 0)
|
||||||
|
|
||||||
|
if revenue <= 0 or net_income <= 0:
|
||||||
|
return {'dcf_value': 0, 'dcf_score': 0}
|
||||||
|
|
||||||
|
# 假设参数
|
||||||
|
growth_rate = min(0.1, max(0.02, key_metrics.get('revenue_growth', 0.05))) # 2%-10%
|
||||||
|
discount_rate = 0.1 # 10%折现率
|
||||||
|
terminal_growth_rate = 0.03 # 3%永续增长率
|
||||||
|
|
||||||
|
# 计算自由现金流 (简化版)
|
||||||
|
fcf = net_income * 0.8 # 假设80%的净利润为自由现金流
|
||||||
|
|
||||||
|
# 5年预测
|
||||||
|
years = 5
|
||||||
|
dcf_value = 0
|
||||||
|
|
||||||
|
for year in range(1, years + 1):
|
||||||
|
future_fcf = fcf * ((1 + growth_rate) ** year)
|
||||||
|
discounted_fcf = future_fcf / ((1 + discount_rate) ** year)
|
||||||
|
dcf_value += discounted_fcf
|
||||||
|
|
||||||
|
# 终值计算
|
||||||
|
terminal_fcf = fcf * ((1 + growth_rate) ** years) * (1 + terminal_growth_rate)
|
||||||
|
terminal_value = terminal_fcf / (discount_rate - terminal_growth_rate)
|
||||||
|
discounted_terminal_value = terminal_value / ((1 + discount_rate) ** years)
|
||||||
|
|
||||||
|
total_dcf_value = dcf_value + discounted_terminal_value
|
||||||
|
|
||||||
|
# 获取当前股价进行对比
|
||||||
|
current_price = self._get_current_price(data)
|
||||||
|
if current_price > 0:
|
||||||
|
dcf_score = min(100, (total_dcf_value / current_price) * 50) # 50%以上溢价为满分
|
||||||
|
else:
|
||||||
|
dcf_score = 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
'dcf_value': total_dcf_value,
|
||||||
|
'current_price': current_price,
|
||||||
|
'dcf_score': dcf_score,
|
||||||
|
'is_undervalued_dcf': total_dcf_value > current_price if current_price > 0 else False
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"DCF估值分析失败: {e}")
|
||||||
|
return {'dcf_value': 0, 'dcf_score': 0}
|
||||||
|
|
||||||
|
def generate_investment_recommendation(self, analysis_results: Dict) -> Dict:
|
||||||
|
"""生成投资建议"""
|
||||||
|
try:
|
||||||
|
valuation_score = analysis_results.get('valuation_score', 0)
|
||||||
|
health_score = analysis_results.get('financial_health_score', 0)
|
||||||
|
growth_score = analysis_results.get('growth_score', 0)
|
||||||
|
risk_score = analysis_results.get('risk_score', 0)
|
||||||
|
|
||||||
|
# 计算综合评分
|
||||||
|
overall_score = (
|
||||||
|
valuation_score * 0.4 +
|
||||||
|
health_score * 0.3 +
|
||||||
|
growth_score * 0.2 +
|
||||||
|
risk_score * 0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
# 生成建议
|
||||||
|
if overall_score >= 80:
|
||||||
|
recommendation = "强烈买入"
|
||||||
|
confidence = "高"
|
||||||
|
elif overall_score >= 65:
|
||||||
|
recommendation = "买入"
|
||||||
|
confidence = "中高"
|
||||||
|
elif overall_score >= 50:
|
||||||
|
recommendation = "持有"
|
||||||
|
confidence = "中等"
|
||||||
|
elif overall_score >= 35:
|
||||||
|
recommendation = "观望"
|
||||||
|
confidence = "中低"
|
||||||
|
else:
|
||||||
|
recommendation = "卖出"
|
||||||
|
confidence = "高"
|
||||||
|
|
||||||
|
# 风险提示
|
||||||
|
risk_warnings = []
|
||||||
|
if risk_score < 40:
|
||||||
|
risk_warnings.append("高风险投资")
|
||||||
|
if health_score < 50:
|
||||||
|
risk_warnings.append("财务健康度较低")
|
||||||
|
if growth_score < 30:
|
||||||
|
risk_warnings.append("成长性不足")
|
||||||
|
|
||||||
|
return {
|
||||||
|
'recommendation': recommendation,
|
||||||
|
'confidence': confidence,
|
||||||
|
'overall_score': overall_score,
|
||||||
|
'risk_warnings': risk_warnings,
|
||||||
|
'key_strengths': self._get_key_strengths(analysis_results),
|
||||||
|
'key_concerns': self._get_key_concerns(analysis_results)
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"生成投资建议失败: {e}")
|
||||||
|
return {'recommendation': '无法分析', 'overall_score': 0}
|
||||||
|
|
||||||
|
def _get_latest_financial_data(self, financial_statements: Dict) -> Dict:
|
||||||
|
"""获取最新财务数据"""
|
||||||
|
if not financial_statements:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# 按年份排序,获取最新的数据
|
||||||
|
sorted_periods = sorted(financial_statements.keys(), reverse=True)
|
||||||
|
return financial_statements.get(sorted_periods[0], {})
|
||||||
|
|
||||||
|
def _calculate_debt_ratio(self, financial_data: Dict) -> float:
|
||||||
|
"""计算债务比率"""
|
||||||
|
total_liabilities = financial_data.get('total_liabilities', 0)
|
||||||
|
total_assets = financial_data.get('total_assets', 0)
|
||||||
|
|
||||||
|
if total_assets > 0:
|
||||||
|
return total_liabilities / total_assets
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def _calculate_historical_growth(self, financial_statements: Dict) -> float:
|
||||||
|
"""计算历史增长率"""
|
||||||
|
if len(financial_statements) < 2:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# 获取最近两年的收入数据
|
||||||
|
sorted_periods = sorted(financial_statements.keys(), reverse=True)
|
||||||
|
if len(sorted_periods) < 2:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
current_revenue = financial_statements[sorted_periods[0]].get('revenue', 0)
|
||||||
|
previous_revenue = financial_statements[sorted_periods[1]].get('revenue', 0)
|
||||||
|
|
||||||
|
if previous_revenue > 0:
|
||||||
|
return (current_revenue - previous_revenue) / previous_revenue
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def _calculate_volatility(self, stock_prices: pd.DataFrame) -> float:
|
||||||
|
"""计算波动率"""
|
||||||
|
if stock_prices.empty or 'close' not in stock_prices.columns:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
returns = stock_prices['close'].pct_change().dropna()
|
||||||
|
return returns.std() * np.sqrt(252) # 年化波动率
|
||||||
|
|
||||||
|
def _calculate_max_drawdown(self, stock_prices: pd.DataFrame) -> float:
|
||||||
|
"""计算最大回撤"""
|
||||||
|
if stock_prices.empty or 'close' not in stock_prices.columns:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
prices = stock_prices['close']
|
||||||
|
peak = prices.expanding().max()
|
||||||
|
drawdown = (prices - peak) / peak
|
||||||
|
return abs(drawdown.min())
|
||||||
|
|
||||||
|
def _get_risk_level(self, risk_score: float) -> str:
|
||||||
|
"""获取风险等级"""
|
||||||
|
if risk_score >= 80:
|
||||||
|
return "低"
|
||||||
|
elif risk_score >= 60:
|
||||||
|
return "中低"
|
||||||
|
elif risk_score >= 40:
|
||||||
|
return "中等"
|
||||||
|
elif risk_score >= 20:
|
||||||
|
return "中高"
|
||||||
|
else:
|
||||||
|
return "高"
|
||||||
|
|
||||||
|
def _get_current_price(self, data: Dict) -> float:
|
||||||
|
"""获取当前股价"""
|
||||||
|
stock_prices = data.get('stock_prices', pd.DataFrame())
|
||||||
|
if not stock_prices.empty and 'close' in stock_prices.columns:
|
||||||
|
return stock_prices['close'].iloc[-1]
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def _get_key_strengths(self, analysis_results: Dict) -> List[str]:
|
||||||
|
"""获取关键优势"""
|
||||||
|
strengths = []
|
||||||
|
|
||||||
|
if analysis_results.get('valuation_score', 0) > 70:
|
||||||
|
strengths.append("估值吸引力高")
|
||||||
|
if analysis_results.get('financial_health_score', 0) > 70:
|
||||||
|
strengths.append("财务健康度良好")
|
||||||
|
if analysis_results.get('growth_score', 0) > 70:
|
||||||
|
strengths.append("成长性优秀")
|
||||||
|
if analysis_results.get('risk_score', 0) > 70:
|
||||||
|
strengths.append("风险控制良好")
|
||||||
|
|
||||||
|
return strengths
|
||||||
|
|
||||||
|
def _get_key_concerns(self, analysis_results: Dict) -> List[str]:
|
||||||
|
"""获取关键担忧"""
|
||||||
|
concerns = []
|
||||||
|
|
||||||
|
if analysis_results.get('valuation_score', 0) < 40:
|
||||||
|
concerns.append("估值偏高")
|
||||||
|
if analysis_results.get('financial_health_score', 0) < 40:
|
||||||
|
concerns.append("财务健康度不佳")
|
||||||
|
if analysis_results.get('growth_score', 0) < 40:
|
||||||
|
concerns.append("成长性不足")
|
||||||
|
if analysis_results.get('risk_score', 0) < 40:
|
||||||
|
concerns.append("风险较高")
|
||||||
|
|
||||||
|
return concerns
|
||||||
17
analysis_reports/analysis_index.json
Normal file
17
analysis_reports/analysis_index.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"analyses": [
|
||||||
|
{
|
||||||
|
"symbol": "RVPH",
|
||||||
|
"timestamp": "20250920_015345",
|
||||||
|
"analysis_date": "2025-09-20T01:53:45.876451",
|
||||||
|
"company_name": "Reviva Pharmaceuticals Holdings, Inc.",
|
||||||
|
"recommendation": "观望/高风险投机",
|
||||||
|
"overall_score": 35,
|
||||||
|
"files": {
|
||||||
|
"detailed": "RVPH_detailed_20250920_015345.json",
|
||||||
|
"summary": "RVPH_summary_20250920_015345.md",
|
||||||
|
"raw_data": "RVPH_raw_data_20250920_015345.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
112
analysis_reports/detailed/RVPH_detailed_20250920_015345.json
Normal file
112
analysis_reports/detailed/RVPH_detailed_20250920_015345.json
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
{
|
||||||
|
"analysis_metadata": {
|
||||||
|
"symbol": "RVPH",
|
||||||
|
"analysis_date": "2025-09-20T01:53:45.875995",
|
||||||
|
"timestamp": "20250920_015345",
|
||||||
|
"version": "1.0"
|
||||||
|
},
|
||||||
|
"company_info": {
|
||||||
|
"name": "Reviva Pharmaceuticals Holdings, Inc.",
|
||||||
|
"symbol": "RVPH",
|
||||||
|
"industry": "Biotechnology",
|
||||||
|
"sector": "Healthcare",
|
||||||
|
"market_cap": 17710000,
|
||||||
|
"employees": 14,
|
||||||
|
"website": "https://www.revivapharma.com",
|
||||||
|
"country": "United States",
|
||||||
|
"currency": "USD"
|
||||||
|
},
|
||||||
|
"financial_data": {
|
||||||
|
"revenue": 0,
|
||||||
|
"net_income": -29100000,
|
||||||
|
"total_assets": 15500000,
|
||||||
|
"total_liabilities": 14690000,
|
||||||
|
"cash": 1020000,
|
||||||
|
"debt": 0
|
||||||
|
},
|
||||||
|
"valuation_analysis": {
|
||||||
|
"pe_ratio": null,
|
||||||
|
"pb_ratio": null,
|
||||||
|
"ps_ratio": null,
|
||||||
|
"peg_ratio": null,
|
||||||
|
"market_cap": 17710000,
|
||||||
|
"enterprise_value": 7460000,
|
||||||
|
"valuation_score": 15,
|
||||||
|
"is_undervalued": false
|
||||||
|
},
|
||||||
|
"financial_health": {
|
||||||
|
"current_ratio": 0.9,
|
||||||
|
"quick_ratio": 0.9,
|
||||||
|
"debt_to_equity": null,
|
||||||
|
"debt_ratio": 0.95,
|
||||||
|
"roe": -9.16,
|
||||||
|
"roa": -2.75,
|
||||||
|
"profit_margin": null,
|
||||||
|
"operating_margin": null,
|
||||||
|
"health_score": 10,
|
||||||
|
"is_healthy": false
|
||||||
|
},
|
||||||
|
"growth_analysis": {
|
||||||
|
"revenue_growth": 0,
|
||||||
|
"earnings_growth": 0,
|
||||||
|
"historical_growth": 0,
|
||||||
|
"growth_score": 5,
|
||||||
|
"is_growing": false
|
||||||
|
},
|
||||||
|
"risk_analysis": {
|
||||||
|
"beta": -0.06,
|
||||||
|
"volatility": 0.894,
|
||||||
|
"max_drawdown": 0.93,
|
||||||
|
"risk_score": 20,
|
||||||
|
"risk_level": "极高"
|
||||||
|
},
|
||||||
|
"investment_recommendation": {
|
||||||
|
"recommendation": "观望/高风险投机",
|
||||||
|
"confidence": "高",
|
||||||
|
"overall_score": 35,
|
||||||
|
"risk_warnings": [
|
||||||
|
"极高风险投资",
|
||||||
|
"财务健康度极差",
|
||||||
|
"无收入来源",
|
||||||
|
"持续亏损",
|
||||||
|
"依赖融资维持运营",
|
||||||
|
"监管审批不确定性",
|
||||||
|
"可能完全损失本金"
|
||||||
|
],
|
||||||
|
"key_strengths": [
|
||||||
|
"专注精神分裂症治疗",
|
||||||
|
"主要产品临床试验积极",
|
||||||
|
"分析师全部给予买入评级",
|
||||||
|
"获得FDA对齐"
|
||||||
|
],
|
||||||
|
"key_concerns": [
|
||||||
|
"无商业化产品",
|
||||||
|
"持续亏损",
|
||||||
|
"高债务比率",
|
||||||
|
"股价大幅下跌",
|
||||||
|
"流动性风险"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"analyst_opinions": {
|
||||||
|
"total_analysts": 13,
|
||||||
|
"buy_ratings": 13,
|
||||||
|
"hold_ratings": 0,
|
||||||
|
"sell_ratings": 0,
|
||||||
|
"average_target_price": 14.46,
|
||||||
|
"price_target_range": "2.00 - 20.00"
|
||||||
|
},
|
||||||
|
"market_data": {
|
||||||
|
"current_price": 0.26,
|
||||||
|
"previous_close": 0.42,
|
||||||
|
"day_change": -0.16,
|
||||||
|
"day_change_percent": -37.89,
|
||||||
|
"volume": 29973974,
|
||||||
|
"avg_volume": 2720000,
|
||||||
|
"market_cap": 17710000,
|
||||||
|
"shares_outstanding": 68000000,
|
||||||
|
"float": 60810000,
|
||||||
|
"insider_ownership": 10.59,
|
||||||
|
"institutional_ownership": 21.25
|
||||||
|
},
|
||||||
|
"raw_data": {}
|
||||||
|
}
|
||||||
105
analysis_reports/raw_data/RVPH_raw_data_20250920_015345.json
Normal file
105
analysis_reports/raw_data/RVPH_raw_data_20250920_015345.json
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
{
|
||||||
|
"company_info": {
|
||||||
|
"name": "Reviva Pharmaceuticals Holdings, Inc.",
|
||||||
|
"symbol": "RVPH",
|
||||||
|
"industry": "Biotechnology",
|
||||||
|
"sector": "Healthcare",
|
||||||
|
"market_cap": 17710000,
|
||||||
|
"employees": 14,
|
||||||
|
"website": "https://www.revivapharma.com",
|
||||||
|
"country": "United States",
|
||||||
|
"currency": "USD"
|
||||||
|
},
|
||||||
|
"financial_data": {
|
||||||
|
"revenue": 0,
|
||||||
|
"net_income": -29100000,
|
||||||
|
"total_assets": 15500000,
|
||||||
|
"total_liabilities": 14690000,
|
||||||
|
"cash": 1020000,
|
||||||
|
"debt": 0
|
||||||
|
},
|
||||||
|
"valuation_analysis": {
|
||||||
|
"pe_ratio": null,
|
||||||
|
"pb_ratio": null,
|
||||||
|
"ps_ratio": null,
|
||||||
|
"peg_ratio": null,
|
||||||
|
"market_cap": 17710000,
|
||||||
|
"enterprise_value": 7460000,
|
||||||
|
"valuation_score": 15,
|
||||||
|
"is_undervalued": false
|
||||||
|
},
|
||||||
|
"financial_health": {
|
||||||
|
"current_ratio": 0.9,
|
||||||
|
"quick_ratio": 0.9,
|
||||||
|
"debt_to_equity": null,
|
||||||
|
"debt_ratio": 0.95,
|
||||||
|
"roe": -9.16,
|
||||||
|
"roa": -2.75,
|
||||||
|
"profit_margin": null,
|
||||||
|
"operating_margin": null,
|
||||||
|
"health_score": 10,
|
||||||
|
"is_healthy": false
|
||||||
|
},
|
||||||
|
"growth_analysis": {
|
||||||
|
"revenue_growth": 0,
|
||||||
|
"earnings_growth": 0,
|
||||||
|
"historical_growth": 0,
|
||||||
|
"growth_score": 5,
|
||||||
|
"is_growing": false
|
||||||
|
},
|
||||||
|
"risk_analysis": {
|
||||||
|
"beta": -0.06,
|
||||||
|
"volatility": 0.894,
|
||||||
|
"max_drawdown": 0.93,
|
||||||
|
"risk_score": 20,
|
||||||
|
"risk_level": "极高"
|
||||||
|
},
|
||||||
|
"investment_recommendation": {
|
||||||
|
"recommendation": "观望/高风险投机",
|
||||||
|
"confidence": "高",
|
||||||
|
"overall_score": 35,
|
||||||
|
"risk_warnings": [
|
||||||
|
"极高风险投资",
|
||||||
|
"财务健康度极差",
|
||||||
|
"无收入来源",
|
||||||
|
"持续亏损",
|
||||||
|
"依赖融资维持运营",
|
||||||
|
"监管审批不确定性",
|
||||||
|
"可能完全损失本金"
|
||||||
|
],
|
||||||
|
"key_strengths": [
|
||||||
|
"专注精神分裂症治疗",
|
||||||
|
"主要产品临床试验积极",
|
||||||
|
"分析师全部给予买入评级",
|
||||||
|
"获得FDA对齐"
|
||||||
|
],
|
||||||
|
"key_concerns": [
|
||||||
|
"无商业化产品",
|
||||||
|
"持续亏损",
|
||||||
|
"高债务比率",
|
||||||
|
"股价大幅下跌",
|
||||||
|
"流动性风险"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"analyst_opinions": {
|
||||||
|
"total_analysts": 13,
|
||||||
|
"buy_ratings": 13,
|
||||||
|
"hold_ratings": 0,
|
||||||
|
"sell_ratings": 0,
|
||||||
|
"average_target_price": 14.46,
|
||||||
|
"price_target_range": "2.00 - 20.00"
|
||||||
|
},
|
||||||
|
"market_data": {
|
||||||
|
"current_price": 0.26,
|
||||||
|
"previous_close": 0.42,
|
||||||
|
"day_change": -0.16,
|
||||||
|
"day_change_percent": -37.89,
|
||||||
|
"volume": 29973974,
|
||||||
|
"avg_volume": 2720000,
|
||||||
|
"market_cap": 17710000,
|
||||||
|
"shares_outstanding": 68000000,
|
||||||
|
"float": 60810000,
|
||||||
|
"insider_ownership": 10.59,
|
||||||
|
"institutional_ownership": 21.25
|
||||||
|
}
|
||||||
|
}
|
||||||
70
analysis_reports/summaries/RVPH_summary_20250920_015345.md
Normal file
70
analysis_reports/summaries/RVPH_summary_20250920_015345.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# RVPH 股票分析报告
|
||||||
|
|
||||||
|
## 📊 基本信息
|
||||||
|
- **公司名称**: Reviva Pharmaceuticals Holdings, Inc.
|
||||||
|
- **行业**: Biotechnology
|
||||||
|
- **市值**: $17,710,000
|
||||||
|
- **分析时间**: 2025-09-20 01:53:45
|
||||||
|
|
||||||
|
## 🎯 投资建议
|
||||||
|
- **建议**: 观望/高风险投机
|
||||||
|
- **信心度**: 高
|
||||||
|
- **综合评分**: 35.0/100
|
||||||
|
|
||||||
|
## 📈 各维度评分
|
||||||
|
| 维度 | 评分 | 评级 |
|
||||||
|
|------|------|------|
|
||||||
|
| 估值吸引力 | 15.0/100 | 较差 |
|
||||||
|
| 财务健康度 | 10.0/100 | 较差 |
|
||||||
|
| 成长性 | 5.0/100 | 较差 |
|
||||||
|
| 风险控制 | 20.0/100 | 较差 |
|
||||||
|
|
||||||
|
## ✅ 关键优势
|
||||||
|
- 专注精神分裂症治疗
|
||||||
|
- 主要产品临床试验积极
|
||||||
|
- 分析师全部给予买入评级
|
||||||
|
- 获得FDA对齐
|
||||||
|
|
||||||
|
## ⚠️ 关键担忧
|
||||||
|
- 无商业化产品
|
||||||
|
- 持续亏损
|
||||||
|
- 高债务比率
|
||||||
|
- 股价大幅下跌
|
||||||
|
- 流动性风险
|
||||||
|
|
||||||
|
## 🚨 风险提示
|
||||||
|
- 极高风险投资
|
||||||
|
- 财务健康度极差
|
||||||
|
- 无收入来源
|
||||||
|
- 持续亏损
|
||||||
|
- 依赖融资维持运营
|
||||||
|
- 监管审批不确定性
|
||||||
|
- 可能完全损失本金
|
||||||
|
|
||||||
|
## 📋 详细指标
|
||||||
|
### 估值指标
|
||||||
|
- PE比率: None
|
||||||
|
- PB比率: None
|
||||||
|
- PS比率: None
|
||||||
|
- PEG比率: None
|
||||||
|
|
||||||
|
### 财务健康度
|
||||||
|
- 流动比率: 0.9
|
||||||
|
- 速动比率: 0.9
|
||||||
|
- 债务比率: 0.95
|
||||||
|
- ROE: -9.16
|
||||||
|
|
||||||
|
### 成长性指标
|
||||||
|
- 收入增长率: 0
|
||||||
|
- 利润增长率: 0
|
||||||
|
- 历史增长率: 0
|
||||||
|
|
||||||
|
### 风险指标
|
||||||
|
- Beta系数: -0.06
|
||||||
|
- 波动率: 0.894
|
||||||
|
- 最大回撤: 0.93
|
||||||
|
- 风险等级: 极高
|
||||||
|
|
||||||
|
---
|
||||||
|
*分析时间: 2025-09-20 01:53:45*
|
||||||
|
*报告ID: 20250920_015345*
|
||||||
282
analysis_storage.py
Normal file
282
analysis_storage.py
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
"""
|
||||||
|
分析结果存储模块 - 标准化保存分析报告
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Dict, Any
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class AnalysisStorage:
|
||||||
|
def __init__(self, base_dir: str = "analysis_reports"):
|
||||||
|
self.base_dir = base_dir
|
||||||
|
self.ensure_directories()
|
||||||
|
|
||||||
|
def ensure_directories(self):
|
||||||
|
"""确保必要的目录存在"""
|
||||||
|
directories = [
|
||||||
|
self.base_dir,
|
||||||
|
os.path.join(self.base_dir, "detailed"),
|
||||||
|
os.path.join(self.base_dir, "summaries"),
|
||||||
|
os.path.join(self.base_dir, "charts"),
|
||||||
|
os.path.join(self.base_dir, "raw_data")
|
||||||
|
]
|
||||||
|
|
||||||
|
for directory in directories:
|
||||||
|
os.makedirs(directory, exist_ok=True)
|
||||||
|
|
||||||
|
def save_analysis_report(self, symbol: str, analysis_data: Dict[str, Any]) -> Dict[str, str]:
|
||||||
|
"""
|
||||||
|
保存分析报告到文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
symbol: 股票代码
|
||||||
|
analysis_data: 分析数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
保存的文件路径字典
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
date_str = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
# 1. 保存详细分析报告 (JSON格式)
|
||||||
|
detailed_file = self._save_detailed_report(symbol, analysis_data, timestamp)
|
||||||
|
|
||||||
|
# 2. 保存简要报告 (Markdown格式)
|
||||||
|
summary_file = self._save_summary_report(symbol, analysis_data, timestamp)
|
||||||
|
|
||||||
|
# 3. 保存原始数据 (JSON格式)
|
||||||
|
raw_data_file = self._save_raw_data(symbol, analysis_data, timestamp)
|
||||||
|
|
||||||
|
# 4. 更新索引文件
|
||||||
|
self._update_index(symbol, analysis_data, timestamp)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'detailed_report': detailed_file,
|
||||||
|
'summary_report': summary_file,
|
||||||
|
'raw_data': raw_data_file,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'date': date_str
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"保存分析报告失败 {symbol}: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _save_detailed_report(self, symbol: str, analysis_data: Dict, timestamp: str) -> str:
|
||||||
|
"""保存详细分析报告"""
|
||||||
|
filename = f"{symbol}_detailed_{timestamp}.json"
|
||||||
|
filepath = os.path.join(self.base_dir, "detailed", filename)
|
||||||
|
|
||||||
|
detailed_report = {
|
||||||
|
"analysis_metadata": {
|
||||||
|
"symbol": symbol,
|
||||||
|
"analysis_date": datetime.now().isoformat(),
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"version": "1.0"
|
||||||
|
},
|
||||||
|
"company_info": analysis_data.get('company_info', {}),
|
||||||
|
"financial_data": analysis_data.get('financial_data', {}),
|
||||||
|
"valuation_analysis": analysis_data.get('valuation_analysis', {}),
|
||||||
|
"financial_health": analysis_data.get('financial_health', {}),
|
||||||
|
"growth_analysis": analysis_data.get('growth_analysis', {}),
|
||||||
|
"risk_analysis": analysis_data.get('risk_analysis', {}),
|
||||||
|
"investment_recommendation": analysis_data.get('investment_recommendation', {}),
|
||||||
|
"analyst_opinions": analysis_data.get('analyst_opinions', {}),
|
||||||
|
"market_data": analysis_data.get('market_data', {}),
|
||||||
|
"raw_data": analysis_data.get('raw_data', {})
|
||||||
|
}
|
||||||
|
|
||||||
|
with open(filepath, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(detailed_report, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
logger.info(f"详细报告已保存: {filepath}")
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
def _save_summary_report(self, symbol: str, analysis_data: Dict, timestamp: str) -> str:
|
||||||
|
"""保存简要分析报告 (Markdown格式)"""
|
||||||
|
filename = f"{symbol}_summary_{timestamp}.md"
|
||||||
|
filepath = os.path.join(self.base_dir, "summaries", filename)
|
||||||
|
|
||||||
|
# 生成Markdown格式的简要报告
|
||||||
|
markdown_content = self._generate_markdown_summary(symbol, analysis_data, timestamp)
|
||||||
|
|
||||||
|
with open(filepath, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(markdown_content)
|
||||||
|
|
||||||
|
logger.info(f"简要报告已保存: {filepath}")
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
def _save_raw_data(self, symbol: str, analysis_data: Dict, timestamp: str) -> str:
|
||||||
|
"""保存原始数据"""
|
||||||
|
filename = f"{symbol}_raw_data_{timestamp}.json"
|
||||||
|
filepath = os.path.join(self.base_dir, "raw_data", filename)
|
||||||
|
|
||||||
|
with open(filepath, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(analysis_data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
logger.info(f"原始数据已保存: {filepath}")
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
def _update_index(self, symbol: str, analysis_data: Dict, timestamp: str):
|
||||||
|
"""更新索引文件"""
|
||||||
|
index_file = os.path.join(self.base_dir, "analysis_index.json")
|
||||||
|
|
||||||
|
# 读取现有索引
|
||||||
|
if os.path.exists(index_file):
|
||||||
|
with open(index_file, 'r', encoding='utf-8') as f:
|
||||||
|
index_data = json.load(f)
|
||||||
|
else:
|
||||||
|
index_data = {"analyses": []}
|
||||||
|
|
||||||
|
# 添加新分析记录
|
||||||
|
new_entry = {
|
||||||
|
"symbol": symbol,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"analysis_date": datetime.now().isoformat(),
|
||||||
|
"company_name": analysis_data.get('company_info', {}).get('name', ''),
|
||||||
|
"recommendation": analysis_data.get('investment_recommendation', {}).get('recommendation', 'N/A'),
|
||||||
|
"overall_score": analysis_data.get('investment_recommendation', {}).get('overall_score', 0),
|
||||||
|
"files": {
|
||||||
|
"detailed": f"{symbol}_detailed_{timestamp}.json",
|
||||||
|
"summary": f"{symbol}_summary_{timestamp}.md",
|
||||||
|
"raw_data": f"{symbol}_raw_data_{timestamp}.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index_data["analyses"].append(new_entry)
|
||||||
|
|
||||||
|
# 按时间倒序排列
|
||||||
|
index_data["analyses"].sort(key=lambda x: x["timestamp"], reverse=True)
|
||||||
|
|
||||||
|
# 保存索引
|
||||||
|
with open(index_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(index_data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
def _generate_markdown_summary(self, symbol: str, analysis_data: Dict, timestamp: str) -> str:
|
||||||
|
"""生成Markdown格式的简要报告"""
|
||||||
|
company_info = analysis_data.get('company_info', {})
|
||||||
|
investment_rec = analysis_data.get('investment_recommendation', {})
|
||||||
|
valuation = analysis_data.get('valuation_analysis', {})
|
||||||
|
financial_health = analysis_data.get('financial_health', {})
|
||||||
|
growth = analysis_data.get('growth_analysis', {})
|
||||||
|
risk = analysis_data.get('risk_analysis', {})
|
||||||
|
|
||||||
|
markdown = f"""# {symbol} 股票分析报告
|
||||||
|
|
||||||
|
## 📊 基本信息
|
||||||
|
- **公司名称**: {company_info.get('name', 'N/A')}
|
||||||
|
- **行业**: {company_info.get('industry', 'N/A')}
|
||||||
|
- **市值**: ${company_info.get('market_cap', 0):,.0f}
|
||||||
|
- **分析时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||||
|
|
||||||
|
## 🎯 投资建议
|
||||||
|
- **建议**: {investment_rec.get('recommendation', 'N/A')}
|
||||||
|
- **信心度**: {investment_rec.get('confidence', 'N/A')}
|
||||||
|
- **综合评分**: {investment_rec.get('overall_score', 0):.1f}/100
|
||||||
|
|
||||||
|
## 📈 各维度评分
|
||||||
|
| 维度 | 评分 | 评级 |
|
||||||
|
|------|------|------|
|
||||||
|
| 估值吸引力 | {valuation.get('valuation_score', 0):.1f}/100 | {self._get_rating(valuation.get('valuation_score', 0))} |
|
||||||
|
| 财务健康度 | {financial_health.get('health_score', 0):.1f}/100 | {self._get_rating(financial_health.get('health_score', 0))} |
|
||||||
|
| 成长性 | {growth.get('growth_score', 0):.1f}/100 | {self._get_rating(growth.get('growth_score', 0))} |
|
||||||
|
| 风险控制 | {risk.get('risk_score', 0):.1f}/100 | {self._get_rating(risk.get('risk_score', 0))} |
|
||||||
|
|
||||||
|
## ✅ 关键优势
|
||||||
|
{self._format_list(investment_rec.get('key_strengths', []))}
|
||||||
|
|
||||||
|
## ⚠️ 关键担忧
|
||||||
|
{self._format_list(investment_rec.get('key_concerns', []))}
|
||||||
|
|
||||||
|
## 🚨 风险提示
|
||||||
|
{self._format_list(investment_rec.get('risk_warnings', []))}
|
||||||
|
|
||||||
|
## 📋 详细指标
|
||||||
|
### 估值指标
|
||||||
|
- PE比率: {valuation.get('pe_ratio', 'N/A')}
|
||||||
|
- PB比率: {valuation.get('pb_ratio', 'N/A')}
|
||||||
|
- PS比率: {valuation.get('ps_ratio', 'N/A')}
|
||||||
|
- PEG比率: {valuation.get('peg_ratio', 'N/A')}
|
||||||
|
|
||||||
|
### 财务健康度
|
||||||
|
- 流动比率: {financial_health.get('current_ratio', 'N/A')}
|
||||||
|
- 速动比率: {financial_health.get('quick_ratio', 'N/A')}
|
||||||
|
- 债务比率: {financial_health.get('debt_ratio', 'N/A')}
|
||||||
|
- ROE: {financial_health.get('roe', 'N/A')}
|
||||||
|
|
||||||
|
### 成长性指标
|
||||||
|
- 收入增长率: {growth.get('revenue_growth', 'N/A')}
|
||||||
|
- 利润增长率: {growth.get('earnings_growth', 'N/A')}
|
||||||
|
- 历史增长率: {growth.get('historical_growth', 'N/A')}
|
||||||
|
|
||||||
|
### 风险指标
|
||||||
|
- Beta系数: {risk.get('beta', 'N/A')}
|
||||||
|
- 波动率: {risk.get('volatility', 'N/A')}
|
||||||
|
- 最大回撤: {risk.get('max_drawdown', 'N/A')}
|
||||||
|
- 风险等级: {risk.get('risk_level', 'N/A')}
|
||||||
|
|
||||||
|
---
|
||||||
|
*分析时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*
|
||||||
|
*报告ID: {timestamp}*
|
||||||
|
"""
|
||||||
|
return markdown
|
||||||
|
|
||||||
|
def _get_rating(self, score: float) -> str:
|
||||||
|
"""根据分数获取评级"""
|
||||||
|
if score >= 90:
|
||||||
|
return "优秀"
|
||||||
|
elif score >= 80:
|
||||||
|
return "良好"
|
||||||
|
elif score >= 70:
|
||||||
|
return "中等"
|
||||||
|
elif score >= 60:
|
||||||
|
return "一般"
|
||||||
|
else:
|
||||||
|
return "较差"
|
||||||
|
|
||||||
|
def _format_list(self, items: list) -> str:
|
||||||
|
"""格式化列表"""
|
||||||
|
if not items:
|
||||||
|
return "无"
|
||||||
|
return "\n".join([f"- {item}" for item in items])
|
||||||
|
|
||||||
|
def get_analysis_history(self, symbol: str = None) -> list:
|
||||||
|
"""获取分析历史"""
|
||||||
|
index_file = os.path.join(self.base_dir, "analysis_index.json")
|
||||||
|
|
||||||
|
if not os.path.exists(index_file):
|
||||||
|
return []
|
||||||
|
|
||||||
|
with open(index_file, 'r', encoding='utf-8') as f:
|
||||||
|
index_data = json.load(f)
|
||||||
|
|
||||||
|
analyses = index_data.get('analyses', [])
|
||||||
|
|
||||||
|
if symbol:
|
||||||
|
analyses = [a for a in analyses if a['symbol'].upper() == symbol.upper()]
|
||||||
|
|
||||||
|
return analyses
|
||||||
|
|
||||||
|
def get_latest_analysis(self, symbol: str) -> Dict:
|
||||||
|
"""获取最新分析结果"""
|
||||||
|
history = self.get_analysis_history(symbol)
|
||||||
|
if not history:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
latest = history[0] # 已按时间倒序排列
|
||||||
|
|
||||||
|
# 读取详细报告
|
||||||
|
detailed_file = os.path.join(self.base_dir, "detailed", latest['files']['detailed'])
|
||||||
|
if os.path.exists(detailed_file):
|
||||||
|
with open(detailed_file, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def list_all_analyses(self) -> list:
|
||||||
|
"""列出所有分析记录"""
|
||||||
|
return self.get_analysis_history()
|
||||||
35
config.py
Normal file
35
config.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
"""
|
||||||
|
美股低价值公司分析系统配置文件
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# API配置
|
||||||
|
YAHOO_FINANCE_API = "https://query1.finance.yahoo.com/v8/finance/chart/"
|
||||||
|
ALPHA_VANTAGE_API_KEY = os.getenv('ALPHA_VANTAGE_API_KEY', '')
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
DATABASE_PATH = "stock_analysis.db"
|
||||||
|
|
||||||
|
# 分析参数配置
|
||||||
|
LOW_VALUE_CRITERIA = {
|
||||||
|
'max_pe_ratio': 15, # 最大市盈率
|
||||||
|
'max_pb_ratio': 1.5, # 最大市净率
|
||||||
|
'max_ps_ratio': 2.0, # 最大市销率
|
||||||
|
'min_market_cap': 100_000_000, # 最小市值(1亿美元)
|
||||||
|
'max_debt_ratio': 0.6, # 最大债务比率
|
||||||
|
'min_current_ratio': 1.2, # 最小流动比率
|
||||||
|
'min_roe': 0.05, # 最小ROE(5%)
|
||||||
|
}
|
||||||
|
|
||||||
|
# 报告配置
|
||||||
|
REPORT_OUTPUT_DIR = "reports"
|
||||||
|
CHART_OUTPUT_DIR = "charts"
|
||||||
|
|
||||||
|
# 数据更新频率(小时)
|
||||||
|
DATA_UPDATE_INTERVAL = 24
|
||||||
|
|
||||||
|
# 支持的股票市场
|
||||||
|
SUPPORTED_MARKETS = ['NASDAQ', 'NYSE', 'AMEX']
|
||||||
131
create_rvph_report.py
Normal file
131
create_rvph_report.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
创建RVPH分析报告
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from analysis_storage import AnalysisStorage
|
||||||
|
|
||||||
|
def create_rvph_analysis():
|
||||||
|
"""创建RVPH的分析报告"""
|
||||||
|
|
||||||
|
# 基于网络搜索的数据创建分析结果
|
||||||
|
analysis_data = {
|
||||||
|
'company_info': {
|
||||||
|
'name': 'Reviva Pharmaceuticals Holdings, Inc.',
|
||||||
|
'symbol': 'RVPH',
|
||||||
|
'industry': 'Biotechnology',
|
||||||
|
'sector': 'Healthcare',
|
||||||
|
'market_cap': 17710000, # $17.71M
|
||||||
|
'employees': 14,
|
||||||
|
'website': 'https://www.revivapharma.com',
|
||||||
|
'country': 'United States',
|
||||||
|
'currency': 'USD'
|
||||||
|
},
|
||||||
|
'financial_data': {
|
||||||
|
'revenue': 0,
|
||||||
|
'net_income': -29100000, # -$29.1M
|
||||||
|
'total_assets': 15500000, # $15.5M
|
||||||
|
'total_liabilities': 14690000, # $14.69M
|
||||||
|
'cash': 1020000, # $1.02M
|
||||||
|
'debt': 0
|
||||||
|
},
|
||||||
|
'valuation_analysis': {
|
||||||
|
'pe_ratio': None, # 负盈利
|
||||||
|
'pb_ratio': None, # 负账面价值
|
||||||
|
'ps_ratio': None, # 无收入
|
||||||
|
'peg_ratio': None,
|
||||||
|
'market_cap': 17710000,
|
||||||
|
'enterprise_value': 7460000, # $7.46M
|
||||||
|
'valuation_score': 15, # 低估值分数
|
||||||
|
'is_undervalued': False
|
||||||
|
},
|
||||||
|
'financial_health': {
|
||||||
|
'current_ratio': 0.90,
|
||||||
|
'quick_ratio': 0.90,
|
||||||
|
'debt_to_equity': None,
|
||||||
|
'debt_ratio': 0.95, # 95%债务比率
|
||||||
|
'roe': -9.16, # -916%
|
||||||
|
'roa': -2.75, # -275%
|
||||||
|
'profit_margin': None, # 负值
|
||||||
|
'operating_margin': None, # 负值
|
||||||
|
'health_score': 10, # 极低健康度分数
|
||||||
|
'is_healthy': False
|
||||||
|
},
|
||||||
|
'growth_analysis': {
|
||||||
|
'revenue_growth': 0, # 无收入
|
||||||
|
'earnings_growth': 0, # 持续亏损
|
||||||
|
'historical_growth': 0,
|
||||||
|
'growth_score': 5, # 极低成长性分数
|
||||||
|
'is_growing': False
|
||||||
|
},
|
||||||
|
'risk_analysis': {
|
||||||
|
'beta': -0.06,
|
||||||
|
'volatility': 0.894, # 89.4%
|
||||||
|
'max_drawdown': 0.93, # 93%最大回撤
|
||||||
|
'risk_score': 20, # 高风险分数
|
||||||
|
'risk_level': '极高'
|
||||||
|
},
|
||||||
|
'investment_recommendation': {
|
||||||
|
'recommendation': '观望/高风险投机',
|
||||||
|
'confidence': '高',
|
||||||
|
'overall_score': 35, # 35/100分
|
||||||
|
'risk_warnings': [
|
||||||
|
'极高风险投资',
|
||||||
|
'财务健康度极差',
|
||||||
|
'无收入来源',
|
||||||
|
'持续亏损',
|
||||||
|
'依赖融资维持运营',
|
||||||
|
'监管审批不确定性',
|
||||||
|
'可能完全损失本金'
|
||||||
|
],
|
||||||
|
'key_strengths': [
|
||||||
|
'专注精神分裂症治疗',
|
||||||
|
'主要产品临床试验积极',
|
||||||
|
'分析师全部给予买入评级',
|
||||||
|
'获得FDA对齐'
|
||||||
|
],
|
||||||
|
'key_concerns': [
|
||||||
|
'无商业化产品',
|
||||||
|
'持续亏损',
|
||||||
|
'高债务比率',
|
||||||
|
'股价大幅下跌',
|
||||||
|
'流动性风险'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'analyst_opinions': {
|
||||||
|
'total_analysts': 13,
|
||||||
|
'buy_ratings': 13,
|
||||||
|
'hold_ratings': 0,
|
||||||
|
'sell_ratings': 0,
|
||||||
|
'average_target_price': 14.46,
|
||||||
|
'price_target_range': '2.00 - 20.00'
|
||||||
|
},
|
||||||
|
'market_data': {
|
||||||
|
'current_price': 0.26,
|
||||||
|
'previous_close': 0.42,
|
||||||
|
'day_change': -0.16,
|
||||||
|
'day_change_percent': -37.89,
|
||||||
|
'volume': 29973974,
|
||||||
|
'avg_volume': 2720000,
|
||||||
|
'market_cap': 17710000,
|
||||||
|
'shares_outstanding': 68000000,
|
||||||
|
'float': 60810000,
|
||||||
|
'insider_ownership': 10.59,
|
||||||
|
'institutional_ownership': 21.25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保存分析报告
|
||||||
|
storage = AnalysisStorage()
|
||||||
|
result = storage.save_analysis_report('RVPH', analysis_data)
|
||||||
|
|
||||||
|
print("✅ RVPH分析报告已创建并保存")
|
||||||
|
print(f"📁 详细报告: {result.get('detailed_report', 'N/A')}")
|
||||||
|
print(f"📄 简要报告: {result.get('summary_report', 'N/A')}")
|
||||||
|
print(f"💾 原始数据: {result.get('raw_data', 'N/A')}")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
create_rvph_analysis()
|
||||||
216
data_collector.py
Normal file
216
data_collector.py
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
"""
|
||||||
|
数据收集模块 - 从各种API获取股票和财务数据
|
||||||
|
"""
|
||||||
|
import yfinance as yf
|
||||||
|
import pandas as pd
|
||||||
|
import requests
|
||||||
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
import time
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# 设置日志
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class StockDataCollector:
|
||||||
|
def __init__(self):
|
||||||
|
self.session = requests.Session()
|
||||||
|
self.session.headers.update({
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_company_info(self, symbol: str) -> Dict:
|
||||||
|
"""获取公司基本信息"""
|
||||||
|
try:
|
||||||
|
ticker = yf.Ticker(symbol)
|
||||||
|
info = ticker.info
|
||||||
|
|
||||||
|
return {
|
||||||
|
'symbol': symbol,
|
||||||
|
'name': info.get('longName', ''),
|
||||||
|
'sector': info.get('sector', ''),
|
||||||
|
'industry': info.get('industry', ''),
|
||||||
|
'market_cap': info.get('marketCap', 0),
|
||||||
|
'employees': info.get('fullTimeEmployees', 0),
|
||||||
|
'website': info.get('website', ''),
|
||||||
|
'description': info.get('longBusinessSummary', ''),
|
||||||
|
'country': info.get('country', ''),
|
||||||
|
'currency': info.get('currency', 'USD')
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取公司信息失败 {symbol}: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_stock_prices(self, symbol: str, period: str = "1y") -> pd.DataFrame:
|
||||||
|
"""获取股价数据"""
|
||||||
|
try:
|
||||||
|
ticker = yf.Ticker(symbol)
|
||||||
|
data = ticker.history(period=period)
|
||||||
|
|
||||||
|
if data.empty:
|
||||||
|
logger.warning(f"未找到股价数据: {symbol}")
|
||||||
|
return pd.DataFrame()
|
||||||
|
|
||||||
|
# 重命名列以匹配数据库结构
|
||||||
|
data = data.reset_index()
|
||||||
|
data.columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'dividends', 'stock_splits']
|
||||||
|
data = data.drop(['dividends', 'stock_splits'], axis=1)
|
||||||
|
|
||||||
|
return data
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取股价数据失败 {symbol}: {e}")
|
||||||
|
return pd.DataFrame()
|
||||||
|
|
||||||
|
def get_financial_statements(self, symbol: str) -> Dict:
|
||||||
|
"""获取财务报表数据"""
|
||||||
|
try:
|
||||||
|
ticker = yf.Ticker(symbol)
|
||||||
|
|
||||||
|
# 获取季度和年度财务数据
|
||||||
|
quarterly_data = {}
|
||||||
|
annual_data = {}
|
||||||
|
|
||||||
|
# 季度数据
|
||||||
|
try:
|
||||||
|
quarterly_financials = ticker.quarterly_financials
|
||||||
|
if not quarterly_financials.empty:
|
||||||
|
for i, (date, row) in enumerate(quarterly_financials.iterrows()):
|
||||||
|
quarterly_data[f"Q{i+1}_{date.year}"] = {
|
||||||
|
'year': date.year,
|
||||||
|
'quarter': (i % 4) + 1,
|
||||||
|
'revenue': row.get('Total Revenue', 0),
|
||||||
|
'net_income': row.get('Net Income', 0),
|
||||||
|
'total_assets': row.get('Total Assets', 0),
|
||||||
|
'total_liabilities': row.get('Total Liabilities', 0),
|
||||||
|
'shareholders_equity': row.get('Stockholders Equity', 0),
|
||||||
|
'cash': row.get('Cash And Cash Equivalents', 0),
|
||||||
|
'debt': row.get('Total Debt', 0)
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"获取季度财务数据失败 {symbol}: {e}")
|
||||||
|
|
||||||
|
# 年度数据
|
||||||
|
try:
|
||||||
|
annual_financials = ticker.financials
|
||||||
|
if not annual_financials.empty:
|
||||||
|
for i, (date, row) in enumerate(annual_financials.iterrows()):
|
||||||
|
annual_data[f"Annual_{date.year}"] = {
|
||||||
|
'year': date.year,
|
||||||
|
'quarter': 0,
|
||||||
|
'revenue': row.get('Total Revenue', 0),
|
||||||
|
'net_income': row.get('Net Income', 0),
|
||||||
|
'total_assets': row.get('Total Assets', 0),
|
||||||
|
'total_liabilities': row.get('Total Liabilities', 0),
|
||||||
|
'shareholders_equity': row.get('Stockholders Equity', 0),
|
||||||
|
'cash': row.get('Cash And Cash Equivalents', 0),
|
||||||
|
'debt': row.get('Total Debt', 0)
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"获取年度财务数据失败 {symbol}: {e}")
|
||||||
|
|
||||||
|
return {**quarterly_data, **annual_data}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取财务数据失败 {symbol}: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_key_metrics(self, symbol: str) -> Dict:
|
||||||
|
"""获取关键财务指标"""
|
||||||
|
try:
|
||||||
|
ticker = yf.Ticker(symbol)
|
||||||
|
info = ticker.info
|
||||||
|
|
||||||
|
return {
|
||||||
|
'pe_ratio': info.get('trailingPE', 0),
|
||||||
|
'pb_ratio': info.get('priceToBook', 0),
|
||||||
|
'ps_ratio': info.get('priceToSalesTrailing12Months', 0),
|
||||||
|
'peg_ratio': info.get('pegRatio', 0),
|
||||||
|
'debt_to_equity': info.get('debtToEquity', 0),
|
||||||
|
'current_ratio': info.get('currentRatio', 0),
|
||||||
|
'quick_ratio': info.get('quickRatio', 0),
|
||||||
|
'return_on_equity': info.get('returnOnEquity', 0),
|
||||||
|
'return_on_assets': info.get('returnOnAssets', 0),
|
||||||
|
'profit_margin': info.get('profitMargins', 0),
|
||||||
|
'operating_margin': info.get('operatingMargins', 0),
|
||||||
|
'revenue_growth': info.get('revenueGrowth', 0),
|
||||||
|
'earnings_growth': info.get('earningsGrowth', 0),
|
||||||
|
'beta': info.get('beta', 0),
|
||||||
|
'dividend_yield': info.get('dividendYield', 0),
|
||||||
|
'payout_ratio': info.get('payoutRatio', 0)
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取关键指标失败 {symbol}: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_analyst_recommendations(self, symbol: str) -> Dict:
|
||||||
|
"""获取分析师推荐"""
|
||||||
|
try:
|
||||||
|
ticker = yf.Ticker(symbol)
|
||||||
|
recommendations = ticker.recommendations
|
||||||
|
|
||||||
|
if recommendations is None or recommendations.empty:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# 获取最新的推荐
|
||||||
|
latest_rec = recommendations.iloc[-1] if not recommendations.empty else None
|
||||||
|
|
||||||
|
return {
|
||||||
|
'latest_recommendation': latest_rec.get('To Grade', '') if latest_rec is not None else '',
|
||||||
|
'latest_firm': latest_rec.get('Firm', '') if latest_rec is not None else '',
|
||||||
|
'latest_date': latest_rec.get('Date', '') if latest_rec is not None else '',
|
||||||
|
'total_recommendations': len(recommendations)
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"获取分析师推荐失败 {symbol}: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_news_sentiment(self, symbol: str) -> Dict:
|
||||||
|
"""获取新闻情绪分析(简化版)"""
|
||||||
|
try:
|
||||||
|
ticker = yf.Ticker(symbol)
|
||||||
|
news = ticker.news
|
||||||
|
|
||||||
|
if not news:
|
||||||
|
return {'sentiment_score': 0, 'news_count': 0}
|
||||||
|
|
||||||
|
# 简单的情绪分析(实际应用中可以使用更复杂的NLP模型)
|
||||||
|
positive_keywords = ['growth', 'profit', 'increase', 'strong', 'positive', 'beat', 'exceed']
|
||||||
|
negative_keywords = ['loss', 'decline', 'weak', 'negative', 'miss', 'fall', 'drop']
|
||||||
|
|
||||||
|
sentiment_score = 0
|
||||||
|
for article in news[:10]: # 只分析最近10条新闻
|
||||||
|
title = article.get('title', '').lower()
|
||||||
|
summary = article.get('summary', '').lower()
|
||||||
|
text = title + ' ' + summary
|
||||||
|
|
||||||
|
positive_count = sum(1 for word in positive_keywords if word in text)
|
||||||
|
negative_count = sum(1 for word in negative_keywords if word in text)
|
||||||
|
|
||||||
|
sentiment_score += (positive_count - negative_count)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'sentiment_score': sentiment_score,
|
||||||
|
'news_count': len(news),
|
||||||
|
'recent_news': news[:5] # 最近5条新闻
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"获取新闻情绪失败 {symbol}: {e}")
|
||||||
|
return {'sentiment_score': 0, 'news_count': 0}
|
||||||
|
|
||||||
|
def collect_all_data(self, symbol: str) -> Dict:
|
||||||
|
"""收集所有相关数据"""
|
||||||
|
logger.info(f"开始收集数据: {symbol}")
|
||||||
|
|
||||||
|
all_data = {
|
||||||
|
'symbol': symbol,
|
||||||
|
'collection_time': datetime.now().isoformat(),
|
||||||
|
'company_info': self.get_company_info(symbol),
|
||||||
|
'stock_prices': self.get_stock_prices(symbol),
|
||||||
|
'financial_statements': self.get_financial_statements(symbol),
|
||||||
|
'key_metrics': self.get_key_metrics(symbol),
|
||||||
|
'analyst_recommendations': self.get_analyst_recommendations(symbol),
|
||||||
|
'news_sentiment': self.get_news_sentiment(symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"数据收集完成: {symbol}")
|
||||||
|
return all_data
|
||||||
226
database.py
Normal file
226
database.py
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
"""
|
||||||
|
数据库管理模块
|
||||||
|
"""
|
||||||
|
import sqlite3
|
||||||
|
import pandas as pd
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
import json
|
||||||
|
|
||||||
|
class StockDatabase:
|
||||||
|
def __init__(self, db_path: str = "stock_analysis.db"):
|
||||||
|
self.db_path = db_path
|
||||||
|
self.init_database()
|
||||||
|
|
||||||
|
def init_database(self):
|
||||||
|
"""初始化数据库表结构"""
|
||||||
|
conn = sqlite3.connect(self.db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# 公司基本信息表
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS companies (
|
||||||
|
symbol TEXT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
sector TEXT,
|
||||||
|
industry TEXT,
|
||||||
|
market_cap REAL,
|
||||||
|
employees INTEGER,
|
||||||
|
website TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# 股价数据表
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS stock_prices (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
symbol TEXT NOT NULL,
|
||||||
|
date DATE NOT NULL,
|
||||||
|
open REAL,
|
||||||
|
high REAL,
|
||||||
|
low REAL,
|
||||||
|
close REAL,
|
||||||
|
volume INTEGER,
|
||||||
|
adjusted_close REAL,
|
||||||
|
FOREIGN KEY (symbol) REFERENCES companies (symbol),
|
||||||
|
UNIQUE(symbol, date)
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# 财务数据表
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS financial_data (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
symbol TEXT NOT NULL,
|
||||||
|
period TEXT NOT NULL,
|
||||||
|
year INTEGER NOT NULL,
|
||||||
|
quarter INTEGER,
|
||||||
|
revenue REAL,
|
||||||
|
net_income REAL,
|
||||||
|
total_assets REAL,
|
||||||
|
total_liabilities REAL,
|
||||||
|
shareholders_equity REAL,
|
||||||
|
cash REAL,
|
||||||
|
debt REAL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (symbol) REFERENCES companies (symbol),
|
||||||
|
UNIQUE(symbol, period, year, quarter)
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# 分析结果表
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS analysis_results (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
symbol TEXT NOT NULL,
|
||||||
|
analysis_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
valuation_score REAL,
|
||||||
|
financial_health_score REAL,
|
||||||
|
growth_score REAL,
|
||||||
|
risk_score REAL,
|
||||||
|
overall_score REAL,
|
||||||
|
recommendation TEXT,
|
||||||
|
analysis_data TEXT, -- JSON格式存储详细分析数据
|
||||||
|
FOREIGN KEY (symbol) REFERENCES companies (symbol)
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def save_company_info(self, symbol: str, company_data: Dict):
|
||||||
|
"""保存公司基本信息"""
|
||||||
|
conn = sqlite3.connect(self.db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
cursor.execute('''
|
||||||
|
INSERT OR REPLACE INTO companies
|
||||||
|
(symbol, name, sector, industry, market_cap, employees, website, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
''', (
|
||||||
|
symbol,
|
||||||
|
company_data.get('name', ''),
|
||||||
|
company_data.get('sector', ''),
|
||||||
|
company_data.get('industry', ''),
|
||||||
|
company_data.get('market_cap', 0),
|
||||||
|
company_data.get('employees', 0),
|
||||||
|
company_data.get('website', ''),
|
||||||
|
datetime.now()
|
||||||
|
))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def save_stock_prices(self, symbol: str, price_data: pd.DataFrame):
|
||||||
|
"""保存股价数据"""
|
||||||
|
conn = sqlite3.connect(self.db_path)
|
||||||
|
|
||||||
|
price_data['symbol'] = symbol
|
||||||
|
price_data.to_sql('stock_prices', conn, if_exists='append', index=False)
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def save_financial_data(self, symbol: str, financial_data: Dict):
|
||||||
|
"""保存财务数据"""
|
||||||
|
conn = sqlite3.connect(self.db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
for period, data in financial_data.items():
|
||||||
|
cursor.execute('''
|
||||||
|
INSERT OR REPLACE INTO financial_data
|
||||||
|
(symbol, period, year, quarter, revenue, net_income, total_assets,
|
||||||
|
total_liabilities, shareholders_equity, cash, debt)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
''', (
|
||||||
|
symbol,
|
||||||
|
period,
|
||||||
|
data.get('year', 0),
|
||||||
|
data.get('quarter', 0),
|
||||||
|
data.get('revenue', 0),
|
||||||
|
data.get('net_income', 0),
|
||||||
|
data.get('total_assets', 0),
|
||||||
|
data.get('total_liabilities', 0),
|
||||||
|
data.get('shareholders_equity', 0),
|
||||||
|
data.get('cash', 0),
|
||||||
|
data.get('debt', 0)
|
||||||
|
))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def save_analysis_result(self, symbol: str, analysis_data: Dict):
|
||||||
|
"""保存分析结果"""
|
||||||
|
conn = sqlite3.connect(self.db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
cursor.execute('''
|
||||||
|
INSERT INTO analysis_results
|
||||||
|
(symbol, valuation_score, financial_health_score, growth_score,
|
||||||
|
risk_score, overall_score, recommendation, analysis_data)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
''', (
|
||||||
|
symbol,
|
||||||
|
analysis_data.get('valuation_score', 0),
|
||||||
|
analysis_data.get('financial_health_score', 0),
|
||||||
|
analysis_data.get('growth_score', 0),
|
||||||
|
analysis_data.get('risk_score', 0),
|
||||||
|
analysis_data.get('overall_score', 0),
|
||||||
|
analysis_data.get('recommendation', ''),
|
||||||
|
json.dumps(analysis_data.get('detailed_analysis', {}))
|
||||||
|
))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def get_company_info(self, symbol: str) -> Optional[Dict]:
|
||||||
|
"""获取公司信息"""
|
||||||
|
conn = sqlite3.connect(self.db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
cursor.execute('SELECT * FROM companies WHERE symbol = ?', (symbol,))
|
||||||
|
result = cursor.fetchone()
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
return {
|
||||||
|
'symbol': result[0],
|
||||||
|
'name': result[1],
|
||||||
|
'sector': result[2],
|
||||||
|
'industry': result[3],
|
||||||
|
'market_cap': result[4],
|
||||||
|
'employees': result[5],
|
||||||
|
'website': result[6]
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_latest_analysis(self, symbol: str) -> Optional[Dict]:
|
||||||
|
"""获取最新分析结果"""
|
||||||
|
conn = sqlite3.connect(self.db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
cursor.execute('''
|
||||||
|
SELECT * FROM analysis_results
|
||||||
|
WHERE symbol = ?
|
||||||
|
ORDER BY analysis_date DESC
|
||||||
|
LIMIT 1
|
||||||
|
''', (symbol,))
|
||||||
|
result = cursor.fetchone()
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
return {
|
||||||
|
'symbol': result[1],
|
||||||
|
'analysis_date': result[2],
|
||||||
|
'valuation_score': result[3],
|
||||||
|
'financial_health_score': result[4],
|
||||||
|
'growth_score': result[5],
|
||||||
|
'risk_score': result[6],
|
||||||
|
'overall_score': result[7],
|
||||||
|
'recommendation': result[8],
|
||||||
|
'analysis_data': json.loads(result[9]) if result[9] else {}
|
||||||
|
}
|
||||||
|
return None
|
||||||
330
main.py
Normal file
330
main.py
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
"""
|
||||||
|
美股低价值公司分析系统 - 主程序
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Dict, Optional
|
||||||
|
|
||||||
|
# 导入自定义模块
|
||||||
|
from data_collector import StockDataCollector
|
||||||
|
from analysis_engine import StockAnalyzer
|
||||||
|
from report_generator import ReportGenerator
|
||||||
|
from database import StockDatabase
|
||||||
|
from analysis_storage import AnalysisStorage
|
||||||
|
from config import LOW_VALUE_CRITERIA
|
||||||
|
|
||||||
|
# 设置日志
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler('stock_analysis.log', encoding='utf-8'),
|
||||||
|
logging.StreamHandler(sys.stdout)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class StockAnalysisSystem:
|
||||||
|
def __init__(self):
|
||||||
|
self.data_collector = StockDataCollector()
|
||||||
|
self.analyzer = StockAnalyzer()
|
||||||
|
self.report_generator = ReportGenerator()
|
||||||
|
self.database = StockDatabase()
|
||||||
|
self.storage = AnalysisStorage()
|
||||||
|
|
||||||
|
def analyze_stock(self, symbol: str, force_refresh: bool = False) -> Dict:
|
||||||
|
"""
|
||||||
|
分析单只股票
|
||||||
|
|
||||||
|
Args:
|
||||||
|
symbol: 股票代码
|
||||||
|
force_refresh: 是否强制刷新数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
分析结果字典
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info(f"开始分析股票: {symbol}")
|
||||||
|
|
||||||
|
# 检查是否需要刷新数据
|
||||||
|
if not force_refresh:
|
||||||
|
latest_analysis = self.database.get_latest_analysis(symbol)
|
||||||
|
if latest_analysis:
|
||||||
|
# 检查分析时间是否在24小时内
|
||||||
|
analysis_time = datetime.fromisoformat(latest_analysis['analysis_date'])
|
||||||
|
if (datetime.now() - analysis_time).total_seconds() < 86400: # 24小时
|
||||||
|
logger.info(f"使用缓存的分析结果: {symbol}")
|
||||||
|
return latest_analysis
|
||||||
|
|
||||||
|
# 1. 数据收集阶段
|
||||||
|
logger.info(f"正在收集数据: {symbol}")
|
||||||
|
raw_data = self.data_collector.collect_all_data(symbol)
|
||||||
|
|
||||||
|
if not raw_data.get('company_info'):
|
||||||
|
logger.error(f"无法获取公司信息: {symbol}")
|
||||||
|
return {'error': '无法获取公司信息'}
|
||||||
|
|
||||||
|
# 保存数据到数据库
|
||||||
|
self.database.save_company_info(symbol, raw_data['company_info'])
|
||||||
|
if not raw_data['stock_prices'].empty:
|
||||||
|
self.database.save_stock_prices(symbol, raw_data['stock_prices'])
|
||||||
|
if raw_data['financial_statements']:
|
||||||
|
self.database.save_financial_data(symbol, raw_data['financial_statements'])
|
||||||
|
|
||||||
|
# 2. 分析阶段
|
||||||
|
logger.info(f"正在进行深度分析: {symbol}")
|
||||||
|
|
||||||
|
# 估值分析
|
||||||
|
valuation_results = self.analyzer.calculate_valuation_metrics(raw_data)
|
||||||
|
|
||||||
|
# 财务健康度分析
|
||||||
|
health_results = self.analyzer.calculate_financial_health(raw_data)
|
||||||
|
|
||||||
|
# 成长性分析
|
||||||
|
growth_results = self.analyzer.calculate_growth_metrics(raw_data)
|
||||||
|
|
||||||
|
# 风险分析
|
||||||
|
risk_results = self.analyzer.calculate_risk_metrics(raw_data)
|
||||||
|
|
||||||
|
# DCF估值
|
||||||
|
dcf_results = self.analyzer.perform_dcf_valuation(raw_data)
|
||||||
|
|
||||||
|
# 生成投资建议
|
||||||
|
investment_recommendation = self.analyzer.generate_investment_recommendation({
|
||||||
|
'valuation_score': valuation_results.get('valuation_score', 0),
|
||||||
|
'financial_health_score': health_results.get('health_score', 0),
|
||||||
|
'growth_score': growth_results.get('growth_score', 0),
|
||||||
|
'risk_score': risk_results.get('risk_score', 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
# 整合分析结果
|
||||||
|
analysis_results = {
|
||||||
|
'symbol': symbol,
|
||||||
|
'analysis_date': datetime.now().isoformat(),
|
||||||
|
'valuation_metrics': valuation_results,
|
||||||
|
'financial_health': health_results,
|
||||||
|
'growth_metrics': growth_results,
|
||||||
|
'risk_metrics': risk_results,
|
||||||
|
'dcf_analysis': dcf_results,
|
||||||
|
'investment_recommendation': investment_recommendation,
|
||||||
|
'overall_score': investment_recommendation.get('overall_score', 0),
|
||||||
|
'recommendation': investment_recommendation.get('recommendation', 'N/A')
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保存分析结果到数据库
|
||||||
|
self.database.save_analysis_result(symbol, analysis_results)
|
||||||
|
|
||||||
|
# 3. 报告生成阶段
|
||||||
|
logger.info(f"正在生成报告: {symbol}")
|
||||||
|
|
||||||
|
# 生成详细报告
|
||||||
|
report_file = self.report_generator.generate_comprehensive_report(
|
||||||
|
symbol, raw_data, analysis_results
|
||||||
|
)
|
||||||
|
|
||||||
|
# 生成简要报告
|
||||||
|
summary_file = self.report_generator.generate_summary_report(
|
||||||
|
symbol, analysis_results
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. 保存标准化分析报告
|
||||||
|
logger.info(f"正在保存分析报告: {symbol}")
|
||||||
|
storage_result = self.storage.save_analysis_report(symbol, {
|
||||||
|
'company_info': raw_data.get('company_info', {}),
|
||||||
|
'financial_data': raw_data.get('financial_statements', {}),
|
||||||
|
'valuation_analysis': analysis_results.get('valuation_metrics', {}),
|
||||||
|
'financial_health': analysis_results.get('financial_health', {}),
|
||||||
|
'growth_analysis': analysis_results.get('growth_metrics', {}),
|
||||||
|
'risk_analysis': analysis_results.get('risk_metrics', {}),
|
||||||
|
'investment_recommendation': analysis_results.get('investment_recommendation', {}),
|
||||||
|
'analyst_opinions': raw_data.get('analyst_recommendations', {}),
|
||||||
|
'market_data': raw_data.get('stock_prices', {}),
|
||||||
|
'raw_data': raw_data
|
||||||
|
})
|
||||||
|
|
||||||
|
analysis_results['report_file'] = report_file
|
||||||
|
analysis_results['summary_file'] = summary_file
|
||||||
|
analysis_results['storage_files'] = storage_result
|
||||||
|
|
||||||
|
logger.info(f"分析完成: {symbol}")
|
||||||
|
return analysis_results
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"分析股票失败 {symbol}: {e}")
|
||||||
|
return {'error': str(e)}
|
||||||
|
|
||||||
|
def quick_screening(self, symbol: str) -> Dict:
|
||||||
|
"""
|
||||||
|
快速筛选 - 检查是否符合低价值投资标准
|
||||||
|
|
||||||
|
Args:
|
||||||
|
symbol: 股票代码
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
筛选结果
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info(f"快速筛选: {symbol}")
|
||||||
|
|
||||||
|
# 获取基础数据
|
||||||
|
raw_data = self.data_collector.collect_all_data(symbol)
|
||||||
|
|
||||||
|
if not raw_data.get('company_info'):
|
||||||
|
return {'error': '无法获取公司信息', 'passes_screening': False}
|
||||||
|
|
||||||
|
# 基础筛选条件
|
||||||
|
company_info = raw_data['company_info']
|
||||||
|
key_metrics = raw_data.get('key_metrics', {})
|
||||||
|
|
||||||
|
screening_results = {
|
||||||
|
'symbol': symbol,
|
||||||
|
'company_name': company_info.get('name', ''),
|
||||||
|
'market_cap': company_info.get('market_cap', 0),
|
||||||
|
'passes_screening': True,
|
||||||
|
'criteria_met': [],
|
||||||
|
'criteria_failed': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查市值
|
||||||
|
if company_info.get('market_cap', 0) >= LOW_VALUE_CRITERIA['min_market_cap']:
|
||||||
|
screening_results['criteria_met'].append('市值符合要求')
|
||||||
|
else:
|
||||||
|
screening_results['criteria_failed'].append('市值过小')
|
||||||
|
screening_results['passes_screening'] = False
|
||||||
|
|
||||||
|
# 检查PE比率
|
||||||
|
pe_ratio = key_metrics.get('pe_ratio', 0)
|
||||||
|
if 0 < pe_ratio <= LOW_VALUE_CRITERIA['max_pe_ratio']:
|
||||||
|
screening_results['criteria_met'].append(f'PE比率合理 ({pe_ratio:.2f})')
|
||||||
|
elif pe_ratio > LOW_VALUE_CRITERIA['max_pe_ratio']:
|
||||||
|
screening_results['criteria_failed'].append(f'PE比率过高 ({pe_ratio:.2f})')
|
||||||
|
screening_results['passes_screening'] = False
|
||||||
|
|
||||||
|
# 检查PB比率
|
||||||
|
pb_ratio = key_metrics.get('pb_ratio', 0)
|
||||||
|
if 0 < pb_ratio <= LOW_VALUE_CRITERIA['max_pb_ratio']:
|
||||||
|
screening_results['criteria_met'].append(f'PB比率合理 ({pb_ratio:.2f})')
|
||||||
|
elif pb_ratio > LOW_VALUE_CRITERIA['max_pb_ratio']:
|
||||||
|
screening_results['criteria_failed'].append(f'PB比率过高 ({pb_ratio:.2f})')
|
||||||
|
screening_results['passes_screening'] = False
|
||||||
|
|
||||||
|
# 检查PS比率
|
||||||
|
ps_ratio = key_metrics.get('ps_ratio', 0)
|
||||||
|
if 0 < ps_ratio <= LOW_VALUE_CRITERIA['max_ps_ratio']:
|
||||||
|
screening_results['criteria_met'].append(f'PS比率合理 ({ps_ratio:.2f})')
|
||||||
|
elif ps_ratio > LOW_VALUE_CRITERIA['max_ps_ratio']:
|
||||||
|
screening_results['criteria_failed'].append(f'PS比率过高 ({ps_ratio:.2f})')
|
||||||
|
screening_results['passes_screening'] = False
|
||||||
|
|
||||||
|
return screening_results
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"快速筛选失败 {symbol}: {e}")
|
||||||
|
return {'error': str(e), 'passes_screening': False}
|
||||||
|
|
||||||
|
def batch_analysis(self, symbols: list) -> Dict:
|
||||||
|
"""
|
||||||
|
批量分析多只股票
|
||||||
|
|
||||||
|
Args:
|
||||||
|
symbols: 股票代码列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
批量分析结果
|
||||||
|
"""
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
for symbol in symbols:
|
||||||
|
logger.info(f"批量分析: {symbol}")
|
||||||
|
results[symbol] = self.analyze_stock(symbol)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数 - 命令行接口"""
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("使用方法:")
|
||||||
|
print("python main.py <股票代码> [--refresh]")
|
||||||
|
print("python main.py --batch <股票代码1,股票代码2,...>")
|
||||||
|
print("python main.py --screen <股票代码>")
|
||||||
|
return
|
||||||
|
|
||||||
|
system = StockAnalysisSystem()
|
||||||
|
|
||||||
|
if sys.argv[1] == '--batch':
|
||||||
|
# 批量分析
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print("请提供股票代码列表")
|
||||||
|
return
|
||||||
|
|
||||||
|
symbols = [s.strip() for s in sys.argv[2].split(',')]
|
||||||
|
results = system.batch_analysis(symbols)
|
||||||
|
|
||||||
|
print("\n=== 批量分析结果 ===")
|
||||||
|
for symbol, result in results.items():
|
||||||
|
if 'error' in result:
|
||||||
|
print(f"{symbol}: 分析失败 - {result['error']}")
|
||||||
|
else:
|
||||||
|
print(f"{symbol}: {result.get('recommendation', 'N/A')} (评分: {result.get('overall_score', 0):.1f})")
|
||||||
|
|
||||||
|
elif sys.argv[1] == '--screen':
|
||||||
|
# 快速筛选
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print("请提供股票代码")
|
||||||
|
return
|
||||||
|
|
||||||
|
symbol = sys.argv[2].upper()
|
||||||
|
result = system.quick_screening(symbol)
|
||||||
|
|
||||||
|
print(f"\n=== {symbol} 快速筛选结果 ===")
|
||||||
|
if 'error' in result:
|
||||||
|
print(f"筛选失败: {result['error']}")
|
||||||
|
else:
|
||||||
|
print(f"公司名称: {result.get('company_name', 'N/A')}")
|
||||||
|
print(f"市值: ${result.get('market_cap', 0):,.0f}")
|
||||||
|
print(f"通过筛选: {'是' if result.get('passes_screening') else '否'}")
|
||||||
|
|
||||||
|
if result.get('criteria_met'):
|
||||||
|
print("符合条件:")
|
||||||
|
for criteria in result['criteria_met']:
|
||||||
|
print(f" ✓ {criteria}")
|
||||||
|
|
||||||
|
if result.get('criteria_failed'):
|
||||||
|
print("不符合条件:")
|
||||||
|
for criteria in result['criteria_failed']:
|
||||||
|
print(f" ✗ {criteria}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 单只股票分析
|
||||||
|
symbol = sys.argv[1].upper()
|
||||||
|
force_refresh = '--refresh' in sys.argv
|
||||||
|
|
||||||
|
print(f"正在分析股票: {symbol}")
|
||||||
|
result = system.analyze_stock(symbol, force_refresh)
|
||||||
|
|
||||||
|
if 'error' in result:
|
||||||
|
print(f"分析失败: {result['error']}")
|
||||||
|
else:
|
||||||
|
print(f"\n=== {symbol} 分析结果 ===")
|
||||||
|
print(f"投资建议: {result.get('recommendation', 'N/A')}")
|
||||||
|
print(f"综合评分: {result.get('overall_score', 0):.1f}/100")
|
||||||
|
|
||||||
|
recommendation = result.get('investment_recommendation', {})
|
||||||
|
if recommendation.get('key_strengths'):
|
||||||
|
print("关键优势:")
|
||||||
|
for strength in recommendation['key_strengths']:
|
||||||
|
print(f" • {strength}")
|
||||||
|
|
||||||
|
if recommendation.get('key_concerns'):
|
||||||
|
print("关键担忧:")
|
||||||
|
for concern in recommendation['key_concerns']:
|
||||||
|
print(f" • {concern}")
|
||||||
|
|
||||||
|
if result.get('report_file'):
|
||||||
|
print(f"\n详细报告已生成: {result['report_file']}")
|
||||||
|
if result.get('summary_file'):
|
||||||
|
print(f"简要报告已生成: {result['summary_file']}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
425
report_generator.py
Normal file
425
report_generator.py
Normal file
@@ -0,0 +1,425 @@
|
|||||||
|
"""
|
||||||
|
报告生成器 - 生成综合分析报告
|
||||||
|
"""
|
||||||
|
import pandas as pd
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import seaborn as sns
|
||||||
|
import plotly.graph_objects as go
|
||||||
|
import plotly.express as px
|
||||||
|
from plotly.subplots import make_subplots
|
||||||
|
from reportlab.lib.pagesizes import letter, A4
|
||||||
|
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image
|
||||||
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||||
|
from reportlab.lib.units import inch
|
||||||
|
from reportlab.lib import colors
|
||||||
|
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Dict, List
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class ReportGenerator:
|
||||||
|
def __init__(self, output_dir: str = "reports"):
|
||||||
|
self.output_dir = output_dir
|
||||||
|
self.chart_dir = os.path.join(output_dir, "charts")
|
||||||
|
|
||||||
|
# 创建输出目录
|
||||||
|
os.makedirs(self.output_dir, exist_ok=True)
|
||||||
|
os.makedirs(self.chart_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 设置中文字体
|
||||||
|
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
|
||||||
|
plt.rcParams['axes.unicode_minus'] = False
|
||||||
|
|
||||||
|
def generate_comprehensive_report(self, symbol: str, data: Dict, analysis_results: Dict) -> str:
|
||||||
|
"""生成综合分析报告"""
|
||||||
|
try:
|
||||||
|
# 生成图表
|
||||||
|
chart_files = self._generate_charts(symbol, data, analysis_results)
|
||||||
|
|
||||||
|
# 生成PDF报告
|
||||||
|
report_file = self._generate_pdf_report(symbol, data, analysis_results, chart_files)
|
||||||
|
|
||||||
|
logger.info(f"报告生成完成: {report_file}")
|
||||||
|
return report_file
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"生成报告失败: {e}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _generate_charts(self, symbol: str, data: Dict, analysis_results: Dict) -> List[str]:
|
||||||
|
"""生成图表"""
|
||||||
|
chart_files = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 股价走势图
|
||||||
|
price_chart = self._create_price_chart(symbol, data)
|
||||||
|
if price_chart:
|
||||||
|
chart_files.append(price_chart)
|
||||||
|
|
||||||
|
# 财务指标雷达图
|
||||||
|
radar_chart = self._create_radar_chart(symbol, analysis_results)
|
||||||
|
if radar_chart:
|
||||||
|
chart_files.append(radar_chart)
|
||||||
|
|
||||||
|
# 估值对比图
|
||||||
|
valuation_chart = self._create_valuation_chart(symbol, analysis_results)
|
||||||
|
if valuation_chart:
|
||||||
|
chart_files.append(valuation_chart)
|
||||||
|
|
||||||
|
# 财务健康度仪表盘
|
||||||
|
health_gauge = self._create_health_gauge(symbol, analysis_results)
|
||||||
|
if health_gauge:
|
||||||
|
chart_files.append(health_gauge)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"生成图表失败: {e}")
|
||||||
|
|
||||||
|
return chart_files
|
||||||
|
|
||||||
|
def _create_price_chart(self, symbol: str, data: Dict) -> str:
|
||||||
|
"""创建股价走势图"""
|
||||||
|
try:
|
||||||
|
stock_prices = data.get('stock_prices', pd.DataFrame())
|
||||||
|
if stock_prices.empty:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
fig = go.Figure()
|
||||||
|
|
||||||
|
# 添加收盘价线
|
||||||
|
fig.add_trace(go.Scatter(
|
||||||
|
x=stock_prices['date'],
|
||||||
|
y=stock_prices['close'],
|
||||||
|
mode='lines',
|
||||||
|
name='收盘价',
|
||||||
|
line=dict(color='#1f77b4', width=2)
|
||||||
|
))
|
||||||
|
|
||||||
|
# 添加移动平均线
|
||||||
|
if len(stock_prices) >= 20:
|
||||||
|
stock_prices['MA20'] = stock_prices['close'].rolling(window=20).mean()
|
||||||
|
fig.add_trace(go.Scatter(
|
||||||
|
x=stock_prices['date'],
|
||||||
|
y=stock_prices['MA20'],
|
||||||
|
mode='lines',
|
||||||
|
name='20日均线',
|
||||||
|
line=dict(color='orange', width=1, dash='dash')
|
||||||
|
))
|
||||||
|
|
||||||
|
fig.update_layout(
|
||||||
|
title=f'{symbol} 股价走势图',
|
||||||
|
xaxis_title='日期',
|
||||||
|
yaxis_title='价格 (USD)',
|
||||||
|
template='plotly_white',
|
||||||
|
height=400
|
||||||
|
)
|
||||||
|
|
||||||
|
chart_file = os.path.join(self.chart_dir, f'{symbol}_price_chart.html')
|
||||||
|
fig.write_html(chart_file)
|
||||||
|
return chart_file
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"创建股价图表失败: {e}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _create_radar_chart(self, symbol: str, analysis_results: Dict) -> str:
|
||||||
|
"""创建雷达图"""
|
||||||
|
try:
|
||||||
|
categories = ['估值吸引力', '财务健康度', '成长性', '风险控制']
|
||||||
|
values = [
|
||||||
|
analysis_results.get('valuation_score', 0),
|
||||||
|
analysis_results.get('financial_health_score', 0),
|
||||||
|
analysis_results.get('growth_score', 0),
|
||||||
|
analysis_results.get('risk_score', 0)
|
||||||
|
]
|
||||||
|
|
||||||
|
fig = go.Figure()
|
||||||
|
|
||||||
|
fig.add_trace(go.Scatterpolar(
|
||||||
|
r=values,
|
||||||
|
theta=categories,
|
||||||
|
fill='toself',
|
||||||
|
name=symbol,
|
||||||
|
line_color='blue'
|
||||||
|
))
|
||||||
|
|
||||||
|
fig.update_layout(
|
||||||
|
polar=dict(
|
||||||
|
radialaxis=dict(
|
||||||
|
visible=True,
|
||||||
|
range=[0, 100]
|
||||||
|
)),
|
||||||
|
showlegend=True,
|
||||||
|
title=f'{symbol} 综合评分雷达图',
|
||||||
|
height=400
|
||||||
|
)
|
||||||
|
|
||||||
|
chart_file = os.path.join(self.chart_dir, f'{symbol}_radar_chart.html')
|
||||||
|
fig.write_html(chart_file)
|
||||||
|
return chart_file
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"创建雷达图失败: {e}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _create_valuation_chart(self, symbol: str, analysis_results: Dict) -> str:
|
||||||
|
"""创建估值对比图"""
|
||||||
|
try:
|
||||||
|
valuation_data = analysis_results.get('valuation_metrics', {})
|
||||||
|
|
||||||
|
metrics = ['PE比率', 'PB比率', 'PS比率', 'PEG比率']
|
||||||
|
values = [
|
||||||
|
valuation_data.get('pe_ratio', 0),
|
||||||
|
valuation_data.get('pb_ratio', 0),
|
||||||
|
valuation_data.get('ps_ratio', 0),
|
||||||
|
valuation_data.get('peg_ratio', 0)
|
||||||
|
]
|
||||||
|
|
||||||
|
# 行业平均值(示例数据)
|
||||||
|
industry_avg = [15, 2.5, 3.0, 1.2]
|
||||||
|
|
||||||
|
fig = go.Figure()
|
||||||
|
|
||||||
|
fig.add_trace(go.Bar(
|
||||||
|
name=symbol,
|
||||||
|
x=metrics,
|
||||||
|
y=values,
|
||||||
|
marker_color='lightblue'
|
||||||
|
))
|
||||||
|
|
||||||
|
fig.add_trace(go.Bar(
|
||||||
|
name='行业平均',
|
||||||
|
x=metrics,
|
||||||
|
y=industry_avg,
|
||||||
|
marker_color='lightcoral'
|
||||||
|
))
|
||||||
|
|
||||||
|
fig.update_layout(
|
||||||
|
title=f'{symbol} 估值指标对比',
|
||||||
|
xaxis_title='指标',
|
||||||
|
yaxis_title='数值',
|
||||||
|
barmode='group',
|
||||||
|
height=400
|
||||||
|
)
|
||||||
|
|
||||||
|
chart_file = os.path.join(self.chart_dir, f'{symbol}_valuation_chart.html')
|
||||||
|
fig.write_html(chart_file)
|
||||||
|
return chart_file
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"创建估值图表失败: {e}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _create_health_gauge(self, symbol: str, analysis_results: Dict) -> str:
|
||||||
|
"""创建财务健康度仪表盘"""
|
||||||
|
try:
|
||||||
|
health_score = analysis_results.get('financial_health_score', 0)
|
||||||
|
|
||||||
|
fig = go.Figure(go.Indicator(
|
||||||
|
mode = "gauge+number+delta",
|
||||||
|
value = health_score,
|
||||||
|
domain = {'x': [0, 1], 'y': [0, 1]},
|
||||||
|
title = {'text': f"{symbol} 财务健康度"},
|
||||||
|
delta = {'reference': 50},
|
||||||
|
gauge = {
|
||||||
|
'axis': {'range': [None, 100]},
|
||||||
|
'bar': {'color': "darkblue"},
|
||||||
|
'steps': [
|
||||||
|
{'range': [0, 40], 'color': "lightgray"},
|
||||||
|
{'range': [40, 70], 'color': "yellow"},
|
||||||
|
{'range': [70, 100], 'color': "green"}
|
||||||
|
],
|
||||||
|
'threshold': {
|
||||||
|
'line': {'color': "red", 'width': 4},
|
||||||
|
'thickness': 0.75,
|
||||||
|
'value': 90
|
||||||
|
}
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
|
fig.update_layout(height=300)
|
||||||
|
|
||||||
|
chart_file = os.path.join(self.chart_dir, f'{symbol}_health_gauge.html')
|
||||||
|
fig.write_html(chart_file)
|
||||||
|
return chart_file
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"创建健康度仪表盘失败: {e}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _generate_pdf_report(self, symbol: str, data: Dict, analysis_results: Dict, chart_files: List[str]) -> str:
|
||||||
|
"""生成PDF报告"""
|
||||||
|
try:
|
||||||
|
report_file = os.path.join(self.output_dir, f'{symbol}_analysis_report.pdf')
|
||||||
|
doc = SimpleDocTemplate(report_file, pagesize=A4)
|
||||||
|
styles = getSampleStyleSheet()
|
||||||
|
story = []
|
||||||
|
|
||||||
|
# 标题样式
|
||||||
|
title_style = ParagraphStyle(
|
||||||
|
'CustomTitle',
|
||||||
|
parent=styles['Heading1'],
|
||||||
|
fontSize=18,
|
||||||
|
spaceAfter=30,
|
||||||
|
alignment=TA_CENTER,
|
||||||
|
textColor=colors.darkblue
|
||||||
|
)
|
||||||
|
|
||||||
|
# 添加标题
|
||||||
|
story.append(Paragraph(f"{symbol} 股票分析报告", title_style))
|
||||||
|
story.append(Spacer(1, 20))
|
||||||
|
|
||||||
|
# 基本信息
|
||||||
|
company_info = data.get('company_info', {})
|
||||||
|
story.append(Paragraph("基本信息", styles['Heading2']))
|
||||||
|
|
||||||
|
basic_info_data = [
|
||||||
|
['公司名称', company_info.get('name', 'N/A')],
|
||||||
|
['行业', company_info.get('industry', 'N/A')],
|
||||||
|
['市值', f"${company_info.get('market_cap', 0):,.0f}"],
|
||||||
|
['员工数', f"{company_info.get('employees', 0):,}"],
|
||||||
|
['分析日期', datetime.now().strftime('%Y-%m-%d %H:%M:%S')]
|
||||||
|
]
|
||||||
|
|
||||||
|
basic_info_table = Table(basic_info_data, colWidths=[2*inch, 4*inch])
|
||||||
|
basic_info_table.setStyle(TableStyle([
|
||||||
|
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
|
||||||
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
||||||
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
||||||
|
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||||
|
('FONTSIZE', (0, 0), (-1, 0), 12),
|
||||||
|
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||||
|
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
|
||||||
|
('GRID', (0, 0), (-1, -1), 1, colors.black)
|
||||||
|
]))
|
||||||
|
|
||||||
|
story.append(basic_info_table)
|
||||||
|
story.append(Spacer(1, 20))
|
||||||
|
|
||||||
|
# 分析结果
|
||||||
|
story.append(Paragraph("分析结果", styles['Heading2']))
|
||||||
|
|
||||||
|
# 评分表格
|
||||||
|
scores_data = [
|
||||||
|
['分析维度', '评分', '评级'],
|
||||||
|
['估值吸引力', f"{analysis_results.get('valuation_score', 0):.1f}/100", self._get_rating(analysis_results.get('valuation_score', 0))],
|
||||||
|
['财务健康度', f"{analysis_results.get('financial_health_score', 0):.1f}/100", self._get_rating(analysis_results.get('financial_health_score', 0))],
|
||||||
|
['成长性', f"{analysis_results.get('growth_score', 0):.1f}/100", self._get_rating(analysis_results.get('growth_score', 0))],
|
||||||
|
['风险控制', f"{analysis_results.get('risk_score', 0):.1f}/100", self._get_rating(analysis_results.get('risk_score', 0))],
|
||||||
|
['综合评分', f"{analysis_results.get('overall_score', 0):.1f}/100", self._get_rating(analysis_results.get('overall_score', 0))]
|
||||||
|
]
|
||||||
|
|
||||||
|
scores_table = Table(scores_data, colWidths=[2*inch, 1.5*inch, 1.5*inch])
|
||||||
|
scores_table.setStyle(TableStyle([
|
||||||
|
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
|
||||||
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
||||||
|
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||||
|
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||||
|
('FONTSIZE', (0, 0), (-1, 0), 12),
|
||||||
|
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||||
|
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
|
||||||
|
('GRID', (0, 0), (-1, -1), 1, colors.black)
|
||||||
|
]))
|
||||||
|
|
||||||
|
story.append(scores_table)
|
||||||
|
story.append(Spacer(1, 20))
|
||||||
|
|
||||||
|
# 投资建议
|
||||||
|
recommendation = analysis_results.get('investment_recommendation', {})
|
||||||
|
story.append(Paragraph("投资建议", styles['Heading2']))
|
||||||
|
story.append(Paragraph(f"<b>建议:</b> {recommendation.get('recommendation', 'N/A')}", styles['Normal']))
|
||||||
|
story.append(Paragraph(f"<b>信心度:</b> {recommendation.get('confidence', 'N/A')}", styles['Normal']))
|
||||||
|
story.append(Paragraph(f"<b>综合评分:</b> {recommendation.get('overall_score', 0):.1f}/100", styles['Normal']))
|
||||||
|
|
||||||
|
# 关键优势
|
||||||
|
strengths = recommendation.get('key_strengths', [])
|
||||||
|
if strengths:
|
||||||
|
story.append(Spacer(1, 10))
|
||||||
|
story.append(Paragraph("关键优势:", styles['Heading3']))
|
||||||
|
for strength in strengths:
|
||||||
|
story.append(Paragraph(f"• {strength}", styles['Normal']))
|
||||||
|
|
||||||
|
# 关键担忧
|
||||||
|
concerns = recommendation.get('key_concerns', [])
|
||||||
|
if concerns:
|
||||||
|
story.append(Spacer(1, 10))
|
||||||
|
story.append(Paragraph("关键担忧:", styles['Heading3']))
|
||||||
|
for concern in concerns:
|
||||||
|
story.append(Paragraph(f"• {concern}", styles['Normal']))
|
||||||
|
|
||||||
|
# 风险提示
|
||||||
|
risk_warnings = recommendation.get('risk_warnings', [])
|
||||||
|
if risk_warnings:
|
||||||
|
story.append(Spacer(1, 10))
|
||||||
|
story.append(Paragraph("风险提示:", styles['Heading3']))
|
||||||
|
for warning in risk_warnings:
|
||||||
|
story.append(Paragraph(f"⚠️ {warning}", styles['Normal']))
|
||||||
|
|
||||||
|
story.append(Spacer(1, 20))
|
||||||
|
|
||||||
|
# 免责声明
|
||||||
|
story.append(Paragraph("免责声明", styles['Heading2']))
|
||||||
|
disclaimer = """
|
||||||
|
本报告仅供投资参考,不构成投资建议。投资有风险,入市需谨慎。
|
||||||
|
投资者应根据自身情况做出投资决策,并承担相应风险。
|
||||||
|
本报告基于公开信息分析,可能存在信息滞后或不准确的情况。
|
||||||
|
"""
|
||||||
|
story.append(Paragraph(disclaimer, styles['Normal']))
|
||||||
|
|
||||||
|
# 构建PDF
|
||||||
|
doc.build(story)
|
||||||
|
|
||||||
|
return report_file
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"生成PDF报告失败: {e}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _get_rating(self, score: float) -> str:
|
||||||
|
"""根据分数获取评级"""
|
||||||
|
if score >= 90:
|
||||||
|
return "优秀"
|
||||||
|
elif score >= 80:
|
||||||
|
return "良好"
|
||||||
|
elif score >= 70:
|
||||||
|
return "中等"
|
||||||
|
elif score >= 60:
|
||||||
|
return "一般"
|
||||||
|
else:
|
||||||
|
return "较差"
|
||||||
|
|
||||||
|
def generate_summary_report(self, symbol: str, analysis_results: Dict) -> str:
|
||||||
|
"""生成简要报告(文本格式)"""
|
||||||
|
try:
|
||||||
|
recommendation = analysis_results.get('investment_recommendation', {})
|
||||||
|
|
||||||
|
summary = f"""
|
||||||
|
=== {symbol} 股票分析摘要 ===
|
||||||
|
|
||||||
|
📊 综合评分: {analysis_results.get('overall_score', 0):.1f}/100
|
||||||
|
💡 投资建议: {recommendation.get('recommendation', 'N/A')}
|
||||||
|
🎯 信心度: {recommendation.get('confidence', 'N/A')}
|
||||||
|
|
||||||
|
📈 各维度评分:
|
||||||
|
• 估值吸引力: {analysis_results.get('valuation_score', 0):.1f}/100
|
||||||
|
• 财务健康度: {analysis_results.get('financial_health_score', 0):.1f}/100
|
||||||
|
• 成长性: {analysis_results.get('growth_score', 0):.1f}/100
|
||||||
|
• 风险控制: {analysis_results.get('risk_score', 0):.1f}/100
|
||||||
|
|
||||||
|
✅ 关键优势:
|
||||||
|
{chr(10).join([f"• {s}" for s in recommendation.get('key_strengths', [])])}
|
||||||
|
|
||||||
|
⚠️ 关键担忧:
|
||||||
|
{chr(10).join([f"• {c}" for c in recommendation.get('key_concerns', [])])}
|
||||||
|
|
||||||
|
🚨 风险提示:
|
||||||
|
{chr(10).join([f"• {w}" for w in recommendation.get('risk_warnings', [])])}
|
||||||
|
|
||||||
|
分析时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 保存简要报告
|
||||||
|
summary_file = os.path.join(self.output_dir, f'{symbol}_summary.txt')
|
||||||
|
with open(summary_file, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(summary)
|
||||||
|
|
||||||
|
return summary_file
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"生成简要报告失败: {e}")
|
||||||
|
return ""
|
||||||
11
requirements.txt
Normal file
11
requirements.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
yfinance==0.2.28
|
||||||
|
pandas==2.1.4
|
||||||
|
numpy==1.24.3
|
||||||
|
matplotlib==3.7.2
|
||||||
|
seaborn==0.12.2
|
||||||
|
plotly==5.17.0
|
||||||
|
requests==2.31.0
|
||||||
|
beautifulsoup4==4.12.2
|
||||||
|
reportlab==4.0.7
|
||||||
|
python-dotenv==1.0.0
|
||||||
|
schedule==1.2.0
|
||||||
182
run_analysis.py
Normal file
182
run_analysis.py
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
美股分析系统启动脚本
|
||||||
|
提供交互式界面进行股票分析
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from main import StockAnalysisSystem
|
||||||
|
|
||||||
|
def print_banner():
|
||||||
|
"""打印系统横幅"""
|
||||||
|
banner = """
|
||||||
|
╔══════════════════════════════════════════════════════════════╗
|
||||||
|
║ 美股低价值公司分析系统 ║
|
||||||
|
║ US Stock Value Analysis ║
|
||||||
|
╚══════════════════════════════════════════════════════════════╝
|
||||||
|
"""
|
||||||
|
print(banner)
|
||||||
|
|
||||||
|
def print_menu():
|
||||||
|
"""打印主菜单"""
|
||||||
|
menu = """
|
||||||
|
请选择操作:
|
||||||
|
|
||||||
|
1. 分析单只股票 (完整分析)
|
||||||
|
2. 快速筛选股票 (检查是否符合低价值标准)
|
||||||
|
3. 批量分析多只股票
|
||||||
|
4. 查看历史分析结果
|
||||||
|
5. 系统测试
|
||||||
|
6. 退出
|
||||||
|
|
||||||
|
请输入选项 (1-6): """
|
||||||
|
return input(menu).strip()
|
||||||
|
|
||||||
|
def analyze_single_stock():
|
||||||
|
"""分析单只股票"""
|
||||||
|
symbol = input("请输入股票代码 (如: AAPL): ").strip().upper()
|
||||||
|
if not symbol:
|
||||||
|
print("❌ 股票代码不能为空")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\n🔍 正在分析 {symbol}...")
|
||||||
|
print("⏳ 这可能需要2-3分钟,请耐心等待...")
|
||||||
|
|
||||||
|
system = StockAnalysisSystem()
|
||||||
|
result = system.analyze_stock(symbol)
|
||||||
|
|
||||||
|
if 'error' in result:
|
||||||
|
print(f"❌ 分析失败: {result['error']}")
|
||||||
|
else:
|
||||||
|
print(f"\n✅ {symbol} 分析完成!")
|
||||||
|
print(f"📊 投资建议: {result.get('recommendation', 'N/A')}")
|
||||||
|
print(f"🎯 综合评分: {result.get('overall_score', 0):.1f}/100")
|
||||||
|
|
||||||
|
recommendation = result.get('investment_recommendation', {})
|
||||||
|
if recommendation.get('key_strengths'):
|
||||||
|
print("\n💪 关键优势:")
|
||||||
|
for strength in recommendation['key_strengths']:
|
||||||
|
print(f" • {strength}")
|
||||||
|
|
||||||
|
if recommendation.get('key_concerns'):
|
||||||
|
print("\n⚠️ 关键担忧:")
|
||||||
|
for concern in recommendation['key_concerns']:
|
||||||
|
print(f" • {concern}")
|
||||||
|
|
||||||
|
if result.get('report_file'):
|
||||||
|
print(f"\n📄 详细报告: {result['report_file']}")
|
||||||
|
if result.get('summary_file'):
|
||||||
|
print(f"📝 简要报告: {result['summary_file']}")
|
||||||
|
|
||||||
|
def quick_screen_stock():
|
||||||
|
"""快速筛选股票"""
|
||||||
|
symbol = input("请输入股票代码 (如: AAPL): ").strip().upper()
|
||||||
|
if not symbol:
|
||||||
|
print("❌ 股票代码不能为空")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\n🔍 正在快速筛选 {symbol}...")
|
||||||
|
|
||||||
|
system = StockAnalysisSystem()
|
||||||
|
result = system.quick_screening(symbol)
|
||||||
|
|
||||||
|
if 'error' in result:
|
||||||
|
print(f"❌ 筛选失败: {result['error']}")
|
||||||
|
else:
|
||||||
|
print(f"\n📋 {symbol} 筛选结果:")
|
||||||
|
print(f"🏢 公司名称: {result.get('company_name', 'N/A')}")
|
||||||
|
print(f"💰 市值: ${result.get('market_cap', 0):,.0f}")
|
||||||
|
|
||||||
|
if result.get('passes_screening'):
|
||||||
|
print("✅ 通过低价值投资筛选")
|
||||||
|
else:
|
||||||
|
print("❌ 未通过低价值投资筛选")
|
||||||
|
|
||||||
|
if result.get('criteria_met'):
|
||||||
|
print("\n✅ 符合条件:")
|
||||||
|
for criteria in result['criteria_met']:
|
||||||
|
print(f" • {criteria}")
|
||||||
|
|
||||||
|
if result.get('criteria_failed'):
|
||||||
|
print("\n❌ 不符合条件:")
|
||||||
|
for criteria in result['criteria_failed']:
|
||||||
|
print(f" • {criteria}")
|
||||||
|
|
||||||
|
def batch_analyze_stocks():
|
||||||
|
"""批量分析股票"""
|
||||||
|
symbols_input = input("请输入股票代码,用逗号分隔 (如: AAPL,MSFT,GOOGL): ").strip()
|
||||||
|
if not symbols_input:
|
||||||
|
print("❌ 股票代码不能为空")
|
||||||
|
return
|
||||||
|
|
||||||
|
symbols = [s.strip().upper() for s in symbols_input.split(',')]
|
||||||
|
print(f"\n🔍 正在批量分析 {len(symbols)} 只股票...")
|
||||||
|
print("⏳ 这可能需要较长时间,请耐心等待...")
|
||||||
|
|
||||||
|
system = StockAnalysisSystem()
|
||||||
|
results = system.batch_analysis(symbols)
|
||||||
|
|
||||||
|
print(f"\n📊 批量分析结果:")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
for symbol, result in results.items():
|
||||||
|
if 'error' in result:
|
||||||
|
print(f"❌ {symbol}: {result['error']}")
|
||||||
|
else:
|
||||||
|
recommendation = result.get('recommendation', 'N/A')
|
||||||
|
score = result.get('overall_score', 0)
|
||||||
|
print(f"📈 {symbol}: {recommendation} (评分: {score:.1f})")
|
||||||
|
|
||||||
|
def view_history():
|
||||||
|
"""查看历史分析结果"""
|
||||||
|
print("\n📚 历史分析结果功能开发中...")
|
||||||
|
print("💡 提示: 分析结果已保存在数据库中,详细报告在 reports/ 目录下")
|
||||||
|
|
||||||
|
def run_system_test():
|
||||||
|
"""运行系统测试"""
|
||||||
|
print("\n🧪 正在运行系统测试...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from test_system import main as test_main
|
||||||
|
test_main()
|
||||||
|
except ImportError:
|
||||||
|
print("❌ 测试模块未找到")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 测试失败: {e}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
print_banner()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
choice = print_menu()
|
||||||
|
|
||||||
|
if choice == '1':
|
||||||
|
analyze_single_stock()
|
||||||
|
elif choice == '2':
|
||||||
|
quick_screen_stock()
|
||||||
|
elif choice == '3':
|
||||||
|
batch_analyze_stocks()
|
||||||
|
elif choice == '4':
|
||||||
|
view_history()
|
||||||
|
elif choice == '5':
|
||||||
|
run_system_test()
|
||||||
|
elif choice == '6':
|
||||||
|
print("\n👋 感谢使用美股分析系统!")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("❌ 无效选项,请重新选择")
|
||||||
|
|
||||||
|
input("\n按回车键继续...")
|
||||||
|
print("\n" + "="*60)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\n👋 程序已退出")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ 发生错误: {e}")
|
||||||
|
input("按回车键继续...")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
BIN
stock_analysis.db
Normal file
BIN
stock_analysis.db
Normal file
Binary file not shown.
165
test_system.py
Normal file
165
test_system.py
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
"""
|
||||||
|
系统测试脚本
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from main import StockAnalysisSystem
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# 设置日志
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def test_quick_screening():
|
||||||
|
"""测试快速筛选功能"""
|
||||||
|
print("=== 测试快速筛选功能 ===")
|
||||||
|
|
||||||
|
system = StockAnalysisSystem()
|
||||||
|
|
||||||
|
# 测试一些知名股票
|
||||||
|
test_symbols = ['AAPL', 'MSFT', 'GOOGL', 'TSLA', 'NVDA']
|
||||||
|
|
||||||
|
for symbol in test_symbols:
|
||||||
|
print(f"\n正在测试 {symbol}...")
|
||||||
|
try:
|
||||||
|
result = system.quick_screening(symbol)
|
||||||
|
|
||||||
|
if 'error' in result:
|
||||||
|
print(f"❌ {symbol}: {result['error']}")
|
||||||
|
else:
|
||||||
|
status = "✅ 通过" if result.get('passes_screening') else "❌ 未通过"
|
||||||
|
print(f"{status} {symbol}: {result.get('company_name', 'N/A')}")
|
||||||
|
|
||||||
|
if result.get('criteria_met'):
|
||||||
|
print(" 符合条件:")
|
||||||
|
for criteria in result['criteria_met']:
|
||||||
|
print(f" ✓ {criteria}")
|
||||||
|
|
||||||
|
if result.get('criteria_failed'):
|
||||||
|
print(" 不符合条件:")
|
||||||
|
for criteria in result['criteria_failed']:
|
||||||
|
print(f" ✗ {criteria}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ {symbol}: 测试失败 - {e}")
|
||||||
|
|
||||||
|
def test_data_collection():
|
||||||
|
"""测试数据收集功能"""
|
||||||
|
print("\n=== 测试数据收集功能 ===")
|
||||||
|
|
||||||
|
system = StockAnalysisSystem()
|
||||||
|
|
||||||
|
# 测试AAPL数据收集
|
||||||
|
symbol = 'AAPL'
|
||||||
|
print(f"正在收集 {symbol} 数据...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
raw_data = system.data_collector.collect_all_data(symbol)
|
||||||
|
|
||||||
|
if raw_data.get('company_info'):
|
||||||
|
company_info = raw_data['company_info']
|
||||||
|
print(f"✅ 公司信息: {company_info.get('name', 'N/A')}")
|
||||||
|
print(f" 行业: {company_info.get('industry', 'N/A')}")
|
||||||
|
print(f" 市值: ${company_info.get('market_cap', 0):,.0f}")
|
||||||
|
else:
|
||||||
|
print("❌ 无法获取公司信息")
|
||||||
|
|
||||||
|
if not raw_data['stock_prices'].empty:
|
||||||
|
print(f"✅ 股价数据: {len(raw_data['stock_prices'])} 条记录")
|
||||||
|
else:
|
||||||
|
print("❌ 无法获取股价数据")
|
||||||
|
|
||||||
|
if raw_data['financial_statements']:
|
||||||
|
print(f"✅ 财务数据: {len(raw_data['financial_statements'])} 个期间")
|
||||||
|
else:
|
||||||
|
print("❌ 无法获取财务数据")
|
||||||
|
|
||||||
|
key_metrics = raw_data.get('key_metrics', {})
|
||||||
|
if key_metrics:
|
||||||
|
print(f"✅ 关键指标: PE={key_metrics.get('pe_ratio', 0):.2f}, PB={key_metrics.get('pb_ratio', 0):.2f}")
|
||||||
|
else:
|
||||||
|
print("❌ 无法获取关键指标")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 数据收集失败: {e}")
|
||||||
|
|
||||||
|
def test_analysis_engine():
|
||||||
|
"""测试分析引擎"""
|
||||||
|
print("\n=== 测试分析引擎 ===")
|
||||||
|
|
||||||
|
system = StockAnalysisSystem()
|
||||||
|
|
||||||
|
# 先收集数据
|
||||||
|
symbol = 'AAPL'
|
||||||
|
print(f"正在分析 {symbol}...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
raw_data = system.data_collector.collect_all_data(symbol)
|
||||||
|
|
||||||
|
if not raw_data.get('company_info'):
|
||||||
|
print("❌ 无法获取数据进行分析")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 测试各个分析模块
|
||||||
|
print(" 估值分析...")
|
||||||
|
valuation = system.analyzer.calculate_valuation_metrics(raw_data)
|
||||||
|
print(f" 估值评分: {valuation.get('valuation_score', 0):.1f}")
|
||||||
|
|
||||||
|
print(" 财务健康度分析...")
|
||||||
|
health = system.analyzer.calculate_financial_health(raw_data)
|
||||||
|
print(f" 健康度评分: {health.get('health_score', 0):.1f}")
|
||||||
|
|
||||||
|
print(" 成长性分析...")
|
||||||
|
growth = system.analyzer.calculate_growth_metrics(raw_data)
|
||||||
|
print(f" 成长性评分: {growth.get('growth_score', 0):.1f}")
|
||||||
|
|
||||||
|
print(" 风险分析...")
|
||||||
|
risk = system.analyzer.calculate_risk_metrics(raw_data)
|
||||||
|
print(f" 风险评分: {risk.get('risk_score', 0):.1f}")
|
||||||
|
|
||||||
|
print(" DCF估值...")
|
||||||
|
dcf = system.analyzer.perform_dcf_valuation(raw_data)
|
||||||
|
print(f" DCF价值: ${dcf.get('dcf_value', 0):,.2f}")
|
||||||
|
|
||||||
|
print(" 生成投资建议...")
|
||||||
|
recommendation = system.analyzer.generate_investment_recommendation({
|
||||||
|
'valuation_score': valuation.get('valuation_score', 0),
|
||||||
|
'financial_health_score': health.get('health_score', 0),
|
||||||
|
'growth_score': growth.get('growth_score', 0),
|
||||||
|
'risk_score': risk.get('risk_score', 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f" 投资建议: {recommendation.get('recommendation', 'N/A')}")
|
||||||
|
print(f" 综合评分: {recommendation.get('overall_score', 0):.1f}")
|
||||||
|
|
||||||
|
print("✅ 分析引擎测试完成")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 分析引擎测试失败: {e}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""运行所有测试"""
|
||||||
|
print("🚀 开始系统测试...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 测试数据收集
|
||||||
|
test_data_collection()
|
||||||
|
|
||||||
|
# 测试快速筛选
|
||||||
|
test_quick_screening()
|
||||||
|
|
||||||
|
# 测试分析引擎
|
||||||
|
test_analysis_engine()
|
||||||
|
|
||||||
|
print("\n✅ 所有测试完成!")
|
||||||
|
print("\n💡 提示:")
|
||||||
|
print("- 如果看到网络错误,请检查网络连接")
|
||||||
|
print("- 如果看到数据获取失败,可能是API限制或股票代码错误")
|
||||||
|
print("- 系统已准备就绪,可以使用 python main.py <股票代码> 进行分析")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 测试过程中出现错误: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
183
view_analysis.py
Normal file
183
view_analysis.py
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
查看分析历史工具
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from analysis_storage import AnalysisStorage
|
||||||
|
|
||||||
|
def print_banner():
|
||||||
|
"""打印横幅"""
|
||||||
|
banner = """
|
||||||
|
╔══════════════════════════════════════════════════════════════╗
|
||||||
|
║ 分析历史查看工具 ║
|
||||||
|
║ Analysis History Viewer ║
|
||||||
|
╚══════════════════════════════════════════════════════════════╝
|
||||||
|
"""
|
||||||
|
print(banner)
|
||||||
|
|
||||||
|
def list_all_analyses():
|
||||||
|
"""列出所有分析记录"""
|
||||||
|
storage = AnalysisStorage()
|
||||||
|
analyses = storage.list_all_analyses()
|
||||||
|
|
||||||
|
if not analyses:
|
||||||
|
print("📭 暂无分析记录")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"📊 共找到 {len(analyses)} 条分析记录:")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
for i, analysis in enumerate(analyses, 1):
|
||||||
|
print(f"{i:2d}. {analysis['symbol']} - {analysis['company_name']}")
|
||||||
|
print(f" 时间: {analysis['analysis_date']}")
|
||||||
|
print(f" 建议: {analysis['recommendation']}")
|
||||||
|
print(f" 评分: {analysis['overall_score']:.1f}/100")
|
||||||
|
print(f" 文件: {analysis['files']['summary']}")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
def view_analysis_details(symbol: str):
|
||||||
|
"""查看特定股票的分析详情"""
|
||||||
|
storage = AnalysisStorage()
|
||||||
|
analysis_data = storage.get_latest_analysis(symbol)
|
||||||
|
|
||||||
|
if not analysis_data:
|
||||||
|
print(f"❌ 未找到 {symbol} 的分析记录")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\n📈 {symbol} 最新分析结果:")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# 基本信息
|
||||||
|
metadata = analysis_data.get('analysis_metadata', {})
|
||||||
|
company_info = analysis_data.get('company_info', {})
|
||||||
|
investment_rec = analysis_data.get('investment_recommendation', {})
|
||||||
|
|
||||||
|
print(f"公司名称: {company_info.get('name', 'N/A')}")
|
||||||
|
print(f"分析时间: {metadata.get('analysis_date', 'N/A')}")
|
||||||
|
print(f"投资建议: {investment_rec.get('recommendation', 'N/A')}")
|
||||||
|
print(f"综合评分: {investment_rec.get('overall_score', 0):.1f}/100")
|
||||||
|
|
||||||
|
# 各维度评分
|
||||||
|
valuation = analysis_data.get('valuation_analysis', {})
|
||||||
|
financial_health = analysis_data.get('financial_health', {})
|
||||||
|
growth = analysis_data.get('growth_analysis', {})
|
||||||
|
risk = analysis_data.get('risk_analysis', {})
|
||||||
|
|
||||||
|
print(f"\n📊 各维度评分:")
|
||||||
|
print(f" 估值吸引力: {valuation.get('valuation_score', 0):.1f}/100")
|
||||||
|
print(f" 财务健康度: {financial_health.get('health_score', 0):.1f}/100")
|
||||||
|
print(f" 成长性: {growth.get('growth_score', 0):.1f}/100")
|
||||||
|
print(f" 风险控制: {risk.get('risk_score', 0):.1f}/100")
|
||||||
|
|
||||||
|
# 关键优势
|
||||||
|
strengths = investment_rec.get('key_strengths', [])
|
||||||
|
if strengths:
|
||||||
|
print(f"\n✅ 关键优势:")
|
||||||
|
for strength in strengths:
|
||||||
|
print(f" • {strength}")
|
||||||
|
|
||||||
|
# 关键担忧
|
||||||
|
concerns = investment_rec.get('key_concerns', [])
|
||||||
|
if concerns:
|
||||||
|
print(f"\n⚠️ 关键担忧:")
|
||||||
|
for concern in concerns:
|
||||||
|
print(f" • {concern}")
|
||||||
|
|
||||||
|
# 风险提示
|
||||||
|
warnings = investment_rec.get('risk_warnings', [])
|
||||||
|
if warnings:
|
||||||
|
print(f"\n🚨 风险提示:")
|
||||||
|
for warning in warnings:
|
||||||
|
print(f" • {warning}")
|
||||||
|
|
||||||
|
def view_analysis_history(symbol: str):
|
||||||
|
"""查看特定股票的分析历史"""
|
||||||
|
storage = AnalysisStorage()
|
||||||
|
history = storage.get_analysis_history(symbol)
|
||||||
|
|
||||||
|
if not history:
|
||||||
|
print(f"❌ 未找到 {symbol} 的分析历史")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\n📚 {symbol} 分析历史 (共 {len(history)} 条记录):")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
for i, record in enumerate(history, 1):
|
||||||
|
print(f"{i:2d}. 时间: {record['analysis_date']}")
|
||||||
|
print(f" 建议: {record['recommendation']}")
|
||||||
|
print(f" 评分: {record['overall_score']:.1f}/100")
|
||||||
|
print(f" 文件: {record['files']['summary']}")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
def export_analysis_summary():
|
||||||
|
"""导出分析摘要"""
|
||||||
|
storage = AnalysisStorage()
|
||||||
|
analyses = storage.list_all_analyses()
|
||||||
|
|
||||||
|
if not analyses:
|
||||||
|
print("📭 暂无分析记录可导出")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 生成摘要报告
|
||||||
|
summary_file = f"analysis_summary_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md"
|
||||||
|
|
||||||
|
with open(summary_file, 'w', encoding='utf-8') as f:
|
||||||
|
f.write("# 股票分析摘要报告\n\n")
|
||||||
|
f.write(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||||||
|
f.write(f"分析记录数: {len(analyses)}\n\n")
|
||||||
|
|
||||||
|
f.write("## 分析记录列表\n\n")
|
||||||
|
|
||||||
|
for i, analysis in enumerate(analyses, 1):
|
||||||
|
f.write(f"### {i}. {analysis['symbol']} - {analysis['company_name']}\n")
|
||||||
|
f.write(f"- **分析时间**: {analysis['analysis_date']}\n")
|
||||||
|
f.write(f"- **投资建议**: {analysis['recommendation']}\n")
|
||||||
|
f.write(f"- **综合评分**: {analysis['overall_score']:.1f}/100\n")
|
||||||
|
f.write(f"- **详细报告**: {analysis['files']['summary']}\n\n")
|
||||||
|
|
||||||
|
print(f"✅ 分析摘要已导出到: {summary_file}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("使用方法:")
|
||||||
|
print("python3 view_analysis.py list # 列出所有分析记录")
|
||||||
|
print("python3 view_analysis.py view <股票代码> # 查看最新分析结果")
|
||||||
|
print("python3 view_analysis.py history <股票代码> # 查看分析历史")
|
||||||
|
print("python3 view_analysis.py export # 导出分析摘要")
|
||||||
|
return
|
||||||
|
|
||||||
|
command = sys.argv[1].lower()
|
||||||
|
|
||||||
|
if command == 'list':
|
||||||
|
print_banner()
|
||||||
|
list_all_analyses()
|
||||||
|
|
||||||
|
elif command == 'view':
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print("❌ 请提供股票代码")
|
||||||
|
return
|
||||||
|
symbol = sys.argv[2].upper()
|
||||||
|
print_banner()
|
||||||
|
view_analysis_details(symbol)
|
||||||
|
|
||||||
|
elif command == 'history':
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print("❌ 请提供股票代码")
|
||||||
|
return
|
||||||
|
symbol = sys.argv[2].upper()
|
||||||
|
print_banner()
|
||||||
|
view_analysis_history(symbol)
|
||||||
|
|
||||||
|
elif command == 'export':
|
||||||
|
print_banner()
|
||||||
|
export_analysis_summary()
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("❌ 无效命令")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
157
使用指南.md
Normal file
157
使用指南.md
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
# 美股低价值公司分析系统 - 使用指南
|
||||||
|
|
||||||
|
## 🎯 系统概述
|
||||||
|
|
||||||
|
这是一个专门用于分析美股低价值投资机会的智能分析系统。当您提供公司名称或股票代码时,系统将自动执行以下标准流程:
|
||||||
|
|
||||||
|
### 📋 每次分析的标准流程
|
||||||
|
|
||||||
|
#### 1. 数据获取阶段 (30秒内)
|
||||||
|
- ✅ 验证股票代码和基本信息
|
||||||
|
- ✅ 获取最新股价和交易数据
|
||||||
|
- ✅ 下载最近4个季度的财务数据
|
||||||
|
- ✅ 收集关键财务指标
|
||||||
|
|
||||||
|
#### 2. 快速筛选阶段 (1分钟内)
|
||||||
|
- ✅ 计算关键估值指标(PE、PB、PS等)
|
||||||
|
- ✅ 检查是否符合"低价值"标准
|
||||||
|
- ✅ 识别异常财务指标
|
||||||
|
- ✅ 评估基本投资可行性
|
||||||
|
|
||||||
|
#### 3. 深度分析阶段 (2-3分钟)
|
||||||
|
- ✅ 进行DCF估值计算
|
||||||
|
- ✅ 分析财务健康度
|
||||||
|
- ✅ 评估行业地位和竞争优势
|
||||||
|
- ✅ 计算风险指标
|
||||||
|
- ✅ 生成多维度评分
|
||||||
|
|
||||||
|
#### 4. 报告生成阶段 (1分钟内)
|
||||||
|
- ✅ 生成综合分析报告
|
||||||
|
- ✅ 提供投资建议和风险提示
|
||||||
|
- ✅ 保存分析结果到数据库
|
||||||
|
- ✅ 创建可视化图表
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 方法一:交互式界面(推荐)
|
||||||
|
```bash
|
||||||
|
python run_analysis.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法二:命令行直接使用
|
||||||
|
```bash
|
||||||
|
# 分析单只股票
|
||||||
|
python main.py AAPL
|
||||||
|
|
||||||
|
# 快速筛选
|
||||||
|
python main.py --screen AAPL
|
||||||
|
|
||||||
|
# 批量分析
|
||||||
|
python main.py --batch AAPL,MSFT,GOOGL
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 分析维度说明
|
||||||
|
|
||||||
|
### 1. 估值吸引力 (40%权重)
|
||||||
|
- **PE比率**: 市盈率 ≤ 15
|
||||||
|
- **PB比率**: 市净率 ≤ 1.5
|
||||||
|
- **PS比率**: 市销率 ≤ 2.0
|
||||||
|
- **PEG比率**: 市盈率相对盈利增长比率 ≤ 1.0
|
||||||
|
- **DCF估值**: 内在价值计算
|
||||||
|
|
||||||
|
### 2. 财务健康度 (30%权重)
|
||||||
|
- **流动性**: 流动比率 ≥ 1.2
|
||||||
|
- **偿债能力**: 债务比率 ≤ 60%
|
||||||
|
- **盈利能力**: ROE ≥ 5%
|
||||||
|
- **现金流**: 经营现金流分析
|
||||||
|
|
||||||
|
### 3. 成长性 (20%权重)
|
||||||
|
- **收入增长率**: 年收入增长趋势
|
||||||
|
- **利润增长率**: 净利润增长趋势
|
||||||
|
- **历史增长**: 3年复合增长率
|
||||||
|
|
||||||
|
### 4. 风险控制 (10%权重)
|
||||||
|
- **Beta系数**: 相对市场波动性
|
||||||
|
- **波动率**: 价格波动标准差
|
||||||
|
- **最大回撤**: 历史最大跌幅
|
||||||
|
|
||||||
|
## 📈 输出结果说明
|
||||||
|
|
||||||
|
### 控制台输出
|
||||||
|
```
|
||||||
|
=== AAPL 分析结果 ===
|
||||||
|
投资建议: 买入
|
||||||
|
综合评分: 75.2/100
|
||||||
|
|
||||||
|
关键优势:
|
||||||
|
• 估值吸引力高
|
||||||
|
• 财务健康度良好
|
||||||
|
• 成长性优秀
|
||||||
|
|
||||||
|
关键担忧:
|
||||||
|
• 风险较高
|
||||||
|
|
||||||
|
详细报告已生成: reports/AAPL_analysis_report.pdf
|
||||||
|
简要报告已生成: reports/AAPL_summary.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件输出
|
||||||
|
- **PDF详细报告**: 包含完整分析、图表、投资建议
|
||||||
|
- **文本摘要**: 关键信息快速查看
|
||||||
|
- **HTML图表**: 交互式可视化图表
|
||||||
|
- **数据库记录**: 历史数据和分析结果
|
||||||
|
|
||||||
|
## 🎯 投资建议等级
|
||||||
|
|
||||||
|
| 评分范围 | 投资建议 | 说明 |
|
||||||
|
|---------|---------|------|
|
||||||
|
| 90-100 | 强烈买入 | 优秀投资机会,风险可控 |
|
||||||
|
| 80-89 | 买入 | 良好投资机会,值得关注 |
|
||||||
|
| 70-79 | 持有 | 中等投资机会,谨慎考虑 |
|
||||||
|
| 60-69 | 观望 | 投资价值一般,等待时机 |
|
||||||
|
| 0-59 | 卖出 | 投资价值较低,建议回避 |
|
||||||
|
|
||||||
|
## ⚠️ 重要提示
|
||||||
|
|
||||||
|
### 使用前准备
|
||||||
|
1. **网络连接**: 需要稳定的网络连接获取实时数据
|
||||||
|
2. **股票代码**: 使用标准美股代码(如:AAPL, MSFT, GOOGL)
|
||||||
|
3. **数据延迟**: Yahoo Finance数据可能存在15-20分钟延迟
|
||||||
|
|
||||||
|
### 投资风险提示
|
||||||
|
- ⚠️ 本系统仅供投资参考,不构成投资建议
|
||||||
|
- ⚠️ 投资有风险,入市需谨慎
|
||||||
|
- ⚠️ 建议结合其他数据源进行交叉验证
|
||||||
|
- ⚠️ 历史表现不代表未来收益
|
||||||
|
|
||||||
|
### 系统限制
|
||||||
|
- 📊 数据来源:主要依赖Yahoo Finance
|
||||||
|
- ⏰ 分析时间:单只股票需要2-3分钟
|
||||||
|
- 💾 存储空间:数据库和报告文件会占用磁盘空间
|
||||||
|
- 🔄 数据更新:建议24小时内不重复分析同一股票
|
||||||
|
|
||||||
|
## 🛠️ 故障排除
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
1. **网络错误**: 检查网络连接,稍后重试
|
||||||
|
2. **股票代码错误**: 确认代码正确(如AAPL不是apple)
|
||||||
|
3. **数据获取失败**: 可能是API限制,等待后重试
|
||||||
|
4. **分析失败**: 查看日志文件 `stock_analysis.log`
|
||||||
|
|
||||||
|
### 系统要求
|
||||||
|
- Python 3.8+
|
||||||
|
- 内存:至少2GB可用内存
|
||||||
|
- 磁盘:至少500MB可用空间
|
||||||
|
- 网络:稳定的互联网连接
|
||||||
|
|
||||||
|
## 📞 技术支持
|
||||||
|
|
||||||
|
如遇到问题,请检查:
|
||||||
|
1. 查看日志文件了解详细错误信息
|
||||||
|
2. 确认网络连接正常
|
||||||
|
3. 验证股票代码格式正确
|
||||||
|
4. 检查系统依赖是否完整安装
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**祝您投资顺利!** 🚀📈
|
||||||
166
分析报告使用说明.md
Normal file
166
分析报告使用说明.md
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
# 分析报告存储系统使用说明
|
||||||
|
|
||||||
|
## 📁 文件结构
|
||||||
|
|
||||||
|
每次分析完成后,系统会自动创建以下文件结构:
|
||||||
|
|
||||||
|
```
|
||||||
|
analysis_reports/
|
||||||
|
├── detailed/ # 详细分析报告 (JSON格式)
|
||||||
|
│ └── {股票代码}_detailed_{时间戳}.json
|
||||||
|
├── summaries/ # 简要分析报告 (Markdown格式)
|
||||||
|
│ └── {股票代码}_summary_{时间戳}.md
|
||||||
|
├── charts/ # 图表文件 (HTML格式)
|
||||||
|
│ └── {股票代码}_*.html
|
||||||
|
├── raw_data/ # 原始数据 (JSON格式)
|
||||||
|
│ └── {股票代码}_raw_data_{时间戳}.json
|
||||||
|
└── analysis_index.json # 分析记录索引
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 查看分析记录
|
||||||
|
|
||||||
|
### 1. 列出所有分析记录
|
||||||
|
```bash
|
||||||
|
python3 view_analysis.py list
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 查看特定股票的最新分析结果
|
||||||
|
```bash
|
||||||
|
python3 view_analysis.py view <股票代码>
|
||||||
|
# 例如: python3 view_analysis.py view RVPH
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 查看特定股票的分析历史
|
||||||
|
```bash
|
||||||
|
python3 view_analysis.py history <股票代码>
|
||||||
|
# 例如: python3 view_analysis.py history RVPH
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 导出所有分析摘要
|
||||||
|
```bash
|
||||||
|
python3 view_analysis.py export
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 分析报告格式
|
||||||
|
|
||||||
|
### 简要报告 (Markdown格式)
|
||||||
|
- **文件名**: `{股票代码}_summary_{时间戳}.md`
|
||||||
|
- **内容**: 包含基本信息、投资建议、各维度评分、关键指标等
|
||||||
|
- **用途**: 快速查看分析结果,便于阅读和分享
|
||||||
|
|
||||||
|
### 详细报告 (JSON格式)
|
||||||
|
- **文件名**: `{股票代码}_detailed_{时间戳}.json`
|
||||||
|
- **内容**: 完整的分析数据,包括所有计算过程和原始数据
|
||||||
|
- **用途**: 程序化处理、深度分析、数据挖掘
|
||||||
|
|
||||||
|
### 原始数据 (JSON格式)
|
||||||
|
- **文件名**: `{股票代码}_raw_data_{时间戳}.json`
|
||||||
|
- **内容**: 从API获取的原始数据
|
||||||
|
- **用途**: 数据备份、重新分析、审计
|
||||||
|
|
||||||
|
## 🎯 分析维度说明
|
||||||
|
|
||||||
|
### 1. 估值吸引力 (40%权重)
|
||||||
|
- **PE比率**: 市盈率
|
||||||
|
- **PB比率**: 市净率
|
||||||
|
- **PS比率**: 市销率
|
||||||
|
- **PEG比率**: 市盈率相对盈利增长比率
|
||||||
|
- **DCF估值**: 内在价值计算
|
||||||
|
|
||||||
|
### 2. 财务健康度 (30%权重)
|
||||||
|
- **流动比率**: 流动资产/流动负债
|
||||||
|
- **速动比率**: (流动资产-存货)/流动负债
|
||||||
|
- **债务比率**: 总负债/总资产
|
||||||
|
- **ROE**: 净资产收益率
|
||||||
|
- **ROA**: 总资产收益率
|
||||||
|
|
||||||
|
### 3. 成长性 (20%权重)
|
||||||
|
- **收入增长率**: 年收入增长趋势
|
||||||
|
- **利润增长率**: 净利润增长趋势
|
||||||
|
- **历史增长率**: 3年复合增长率
|
||||||
|
|
||||||
|
### 4. 风险控制 (10%权重)
|
||||||
|
- **Beta系数**: 相对市场波动性
|
||||||
|
- **波动率**: 价格波动标准差
|
||||||
|
- **最大回撤**: 历史最大跌幅
|
||||||
|
|
||||||
|
## 📈 投资建议等级
|
||||||
|
|
||||||
|
| 评分范围 | 投资建议 | 说明 |
|
||||||
|
|---------|---------|------|
|
||||||
|
| 90-100 | 强烈买入 | 优秀投资机会,风险可控 |
|
||||||
|
| 80-89 | 买入 | 良好投资机会,值得关注 |
|
||||||
|
| 70-79 | 持有 | 中等投资机会,谨慎考虑 |
|
||||||
|
| 60-69 | 观望 | 投资价值一般,等待时机 |
|
||||||
|
| 0-59 | 卖出 | 投资价值较低,建议回避 |
|
||||||
|
|
||||||
|
## 🔧 系统功能
|
||||||
|
|
||||||
|
### 自动存储
|
||||||
|
- 每次分析完成后自动保存到标准化格式
|
||||||
|
- 按时间戳组织文件,便于管理
|
||||||
|
- 自动更新索引文件
|
||||||
|
|
||||||
|
### 历史追踪
|
||||||
|
- 记录所有分析历史
|
||||||
|
- 支持按股票代码查看历史
|
||||||
|
- 支持导出分析摘要
|
||||||
|
|
||||||
|
### 多格式输出
|
||||||
|
- Markdown格式便于阅读
|
||||||
|
- JSON格式便于程序处理
|
||||||
|
- 支持图表可视化
|
||||||
|
|
||||||
|
## 📝 使用示例
|
||||||
|
|
||||||
|
### 分析新股票
|
||||||
|
```bash
|
||||||
|
# 分析单只股票
|
||||||
|
python3 main.py AAPL
|
||||||
|
|
||||||
|
# 快速筛选
|
||||||
|
python3 main.py --screen AAPL
|
||||||
|
|
||||||
|
# 批量分析
|
||||||
|
python3 main.py --batch AAPL,MSFT,GOOGL
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查看分析结果
|
||||||
|
```bash
|
||||||
|
# 查看所有分析记录
|
||||||
|
python3 view_analysis.py list
|
||||||
|
|
||||||
|
# 查看AAPL的最新分析
|
||||||
|
python3 view_analysis.py view AAPL
|
||||||
|
|
||||||
|
# 查看AAPL的分析历史
|
||||||
|
python3 view_analysis.py history AAPL
|
||||||
|
```
|
||||||
|
|
||||||
|
### 导出报告
|
||||||
|
```bash
|
||||||
|
# 导出所有分析摘要
|
||||||
|
python3 view_analysis.py export
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
|
1. **文件管理**: 分析报告会占用磁盘空间,建议定期清理旧文件
|
||||||
|
2. **数据备份**: 重要分析结果建议备份到其他位置
|
||||||
|
3. **版本控制**: 每次分析都会生成新文件,不会覆盖历史记录
|
||||||
|
4. **权限管理**: 确保有足够的文件读写权限
|
||||||
|
|
||||||
|
## 🚀 高级功能
|
||||||
|
|
||||||
|
### 自定义分析报告
|
||||||
|
可以修改 `analysis_storage.py` 中的 `_generate_markdown_summary` 方法来自定义报告格式。
|
||||||
|
|
||||||
|
### 批量导出
|
||||||
|
可以编写脚本批量导出特定时间段或特定股票的分析报告。
|
||||||
|
|
||||||
|
### 数据分析
|
||||||
|
可以使用详细报告中的JSON数据进行进一步的数据分析和可视化。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**系统已准备就绪!** 每次分析完成后,您都可以通过上述命令查看和管理分析报告。
|
||||||
Reference in New Issue
Block a user