diff --git a/Dockerfile b/Dockerfile
index bfc4022..9768d7f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,12 +4,17 @@ FROM python:3.12-slim
# 设置工作目录
WORKDIR /app
-# 安装Node.js (pywencai需要)
+# 安装Node.js (pywencai需要) 和中文字体 (PDF生成需要)
RUN apt-get update && apt-get install -y \
curl \
gnupg \
+ fonts-noto-cjk \
+ fonts-wqy-zenhei \
+ fonts-wqy-microhei \
+ fontconfig \
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
&& apt-get install -y nodejs \
+ && fc-cache -fv \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
diff --git a/PDF中文乱码修复说明.md b/PDF中文乱码修复说明.md
new file mode 100644
index 0000000..a5277d3
--- /dev/null
+++ b/PDF中文乱码修复说明.md
@@ -0,0 +1,207 @@
+# PDF中文乱码修复说明
+
+## 问题描述
+在Docker环境中,PDF导出时中文显示为乱码或方框,但本地Windows环境正常。
+
+## 原因分析
+- Docker容器基于Linux系统,默认不包含中文字体
+- PDF生成器(reportlab)需要中文字体才能正确渲染中文字符
+- 原代码只配置了Windows系统的字体路径
+
+## 修复方案
+
+### 已修改的文件
+
+#### 1. `Dockerfile`
+添加了中文字体包安装:
+```dockerfile
+# 安装Node.js (pywencai需要) 和中文字体 (PDF生成需要)
+RUN apt-get update && apt-get install -y \
+ curl \
+ gnupg \
+ fonts-noto-cjk \
+ fonts-wqy-zenhei \
+ fonts-wqy-microhei \
+ fontconfig \
+ && curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
+ && apt-get install -y nodejs \
+ && fc-cache -fv \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+```
+
+安装的字体包:
+- **fonts-noto-cjk**: Google Noto CJK字体(支持中日韩文字)
+- **fonts-wqy-zenhei**: 文泉驿正黑体
+- **fonts-wqy-microhei**: 文泉驿微米黑体
+- **fontconfig**: 字体配置工具
+
+#### 2. `pdf_generator.py`
+更新了字体注册函数,支持Linux系统:
+```python
+def register_chinese_fonts():
+ """注册中文字体 - 支持Windows和Linux系统"""
+ # Windows系统字体路径
+ windows_font_paths = [...]
+
+ # Linux系统字体路径(Docker环境)
+ linux_font_paths = [
+ '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc',
+ '/usr/share/fonts/truetype/wqy/wqy-microhei.ttc',
+ '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc',
+ # ... 更多字体路径
+ ]
+```
+
+## 重新部署步骤
+
+### 方式一:Docker Compose(推荐)
+
+```bash
+# 1. 停止并删除现有容器
+docker-compose down
+
+# 2. 删除旧镜像(可选,但推荐)
+docker rmi agentsstock1-app
+
+# 3. 重新构建并启动
+docker-compose up -d --build
+
+# 4. 查看日志,确认字体加载成功
+docker-compose logs -f
+```
+
+### 方式二:Docker命令
+
+```bash
+# 1. 停止并删除现有容器
+docker stop agentsstock1
+docker rm agentsstock1
+
+# 2. 删除旧镜像
+docker rmi agentsstock1
+
+# 3. 重新构建镜像
+docker build -t agentsstock1 .
+
+# 4. 启动新容器
+docker run -d -p 8503:8501 \
+ -v $(pwd)/.env:/app/.env \
+ --name agentsstock1 \
+ agentsstock1
+
+# 5. 查看日志
+docker logs -f agentsstock1
+```
+
+## 验证修复
+
+### 1. 检查字体安装
+进入容器检查字体:
+```bash
+docker exec -it agentsstock1 bash
+fc-list :lang=zh # 列出所有中文字体
+```
+
+应该看到类似输出:
+```
+/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc: WenQuanYi Zen Hei:style=Regular
+/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc: Noto Sans CJK SC:style=Regular
+...
+```
+
+### 2. 查看应用日志
+在生成PDF时,日志中应该显示:
+```
+✅ 成功注册中文字体: /usr/share/fonts/truetype/wqy/wqy-zenhei.ttc
+```
+
+### 3. 测试PDF导出
+1. 在Web界面分析任意股票
+2. 点击"生成并下载PDF报告"
+3. 打开下载的PDF文件
+4. 确认中文正常显示
+
+## 故障排查
+
+### 问题1:构建时字体安装失败
+**现象**:构建过程中apt-get install失败
+
+**解决方案**:
+```bash
+# 清理Docker缓存后重试
+docker system prune -a
+docker-compose build --no-cache
+```
+
+### 问题2:字体未加载
+**现象**:日志显示"未找到中文字体"
+
+**检查步骤**:
+1. 确认Dockerfile中正确添加了字体包
+2. 进入容器检查字体文件是否存在
+3. 运行 `fc-cache -fv` 刷新字体缓存
+
+### 问题3:PDF仍然乱码
+**现象**:字体已安装但PDF中文仍显示为方框
+
+**可能原因**:
+1. 字体文件权限问题
+2. reportlab缓存问题
+3. 字体格式不兼容
+
+**解决方案**:
+```bash
+# 重新构建时清理所有缓存
+docker-compose down -v
+docker system prune -a
+docker-compose up -d --build
+```
+
+## 技术说明
+
+### 字体选择
+我们安装了3种字体包:
+1. **Noto Sans CJK**:Google开发,覆盖范围广,质量高
+2. **文泉驿正黑**:开源中文字体,适合阅读
+3. **文泉驿微米黑**:适合小字号显示
+
+系统会按优先级自动选择第一个可用的字体。
+
+### 字体文件格式
+- **TTC**:TrueType Collection(字体集合文件)
+- **TTF**:TrueType Font(单个字体文件)
+
+reportlab的TTFont可以处理这两种格式。
+
+### Docker镜像大小
+添加中文字体后,镜像大小会增加约50-100MB:
+- fonts-noto-cjk: ~70MB
+- fonts-wqy-zenhei: ~10MB
+- fonts-wqy-microhei: ~5MB
+
+这是正常的,为了支持完整的中文显示必须付出的空间代价。
+
+## 注意事项
+
+1. **本地环境无影响**:修改后的代码在Windows本地环境仍然正常工作
+2. **字体缓存**:首次启动时fontconfig会建立字体缓存,可能需要几秒钟
+3. **镜像更新**:已部署的用户需要重新构建镜像才能应用修复
+4. **备份数据**:重新部署前请备份数据库文件(.db文件)
+
+## 相关资源
+
+- [ReportLab中文支持文档](https://www.reportlab.com/docs/reportlab-userguide.pdf)
+- [Noto字体项目](https://www.google.com/get/noto/)
+- [文泉驿字体项目](http://wenq.org/)
+- [Docker字体配置指南](https://docs.docker.com/config/)
+
+## 更新日志
+
+- **2025-10-10**: 初始版本,添加Docker中文字体支持
+- **2025-10-10**: 更新pdf_generator.py,支持跨平台字体检测
+
+---
+
+如有问题,请联系:ws3101001@126.com
+
diff --git a/README.md b/README.md
index 28b9877..c2cb4c6 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,32 @@
+## ✨1010 更新说明(最新)
+### 🇭🇰 新增港股分析支持 ⭐️
+- **市场扩展**:现已支持A股、港股、美股三大市场
+- **港股代码**:支持多种格式(700/00700/HK00700)
+- **技术分析**:完整支持K线、技术指标、趋势判断
+- **财务分析**:支持21项财务指标(ROE、市盈率、净利润等)
+- **智能分析**:技术分析师、基本面分析师、风险管理师全面支持
+- **使用简单**:直接输入港股代码即可分析(如:00700 腾讯控股)
+
+## ✨1010 更新说明
+### 最新更新:主力选股功能上线 ⭐️
+- **智能选股**:基于主力资金流向,AI团队精选优质标的
+- **数据获取**:使用问财获取前100名主力资金净流入股票
+- **多维筛选**:市值范围、区间涨跌幅、财务指标等多维度筛选
+- **AI分析团队**:
+ - 💰 资金流向分析师:分析主力资金流向趋势和热点板块
+ - 📊 行业板块分析师:解读行业轮动和市场热点
+ - 📈 财务基本面分析师:评估公司财务健康度和成长性
+ - 👔 资深研究员:综合决策,精选3-5只优质标的
+- **完整报告**:支持Markdown、HTML、CSV多格式导出
+- **候选列表**:前100名主力资金股票完整列表(按资金流向排序)
+- **智能匹配**:自动识别"区间主力资金流向"、"区间涨跌幅"等字段
+- **季报数据**:优化季报获取,改用问财接口,数据更准确
+
+
+
## ✨1009 更新说明
### 最新更新:增加支持docker部署 ⭐️
## ✨1008 更新说明
@@ -41,7 +67,7 @@
- **市场情绪分析师**:专注市场心理和情绪分析
- **新闻公告分析师**:专注热点新闻及公告影响分析
### 📊 完整分析流程
-1. 📈 获取股票数据(支持A股和美股)
+1. 📈 获取股票数据(支持A股、港股和美股)
2. 📊 获取详细数据(三大报表+财务指标+20日资金数据+新闻+公告)
3. 🔍 多智能体并行分析
4. 🤝 团队综合讨论
@@ -68,6 +94,28 @@
+### 🎯 主力选股功能(新增)⭐️
+- **智能选股**:基于主力资金流向,筛选优质投资标的
+- **数据来源**:问财数据,获取区间主力资金净流入前100名股票
+- **多维筛选**:
+ - 起始日期:自定义区间开始日期
+ - 市值范围:50亿-5000亿灵活设置
+ - 涨跌幅限制:过滤涨幅过高的股票,避免追高
+- **AI分析团队**:
+ - 💰 **资金流向分析师**:分析主力资金动向、热点板块、资金持续性
+ - 📊 **行业板块分析师**:解读行业轮动、板块联动、市场情绪
+ - 📈 **财务基本面分析师**:评估公司盈利能力、成长性、财务健康度
+ - 👔 **资深研究员**:综合三大分析师意见,精选3-5只优质标的
+- **批量分析**:一次性分析100只股票,效率高
+- **完整报告**:
+ - 📄 Markdown格式:便于编辑和二次处理
+ - 🌐 HTML格式:美观易读,支持浏览器打开
+ - 📊 CSV格式:候选股票完整数据导出
+- **候选列表**:前100名股票按主力资金流向排序展示
+- **关键指标**:区间主力资金流向、区间涨跌幅、市值、市盈率、市净率等
+
+
+
### 🔍 实时监测功能
- **智能监测**:自动监控股票价格变动
- **关键位置提醒**:进场区间、止盈位、止损位触发通知
@@ -259,9 +307,10 @@ streamlit run app.py
## 📊 使用指南
-### 股票代码格式(美股暂不支持)
-- **美股**:AAPL, MSFT, GOOGL, TSLA, NVDA
-- **A股**:000001, 600036, 000002, 600519
+### 股票代码格式
+- **A股**:6位数字代码(如 000001, 600036, 600519, 000858)
+- **港股**:1-5位数字代码(如 700, 00700, 9988, 01810)或带HK前缀(如 HK00700)
+- **美股**:字母代码(如 AAPL, MSFT, GOOGL, TSLA, NVDA)
### 单个股票分析流程
1. 选择"单个分析"模式
@@ -329,6 +378,56 @@ streamlit run app.py
- 批量分析时间较长,请耐心等待
- 可在历史记录中查看所有分析结果
+### 主力选股功能使用流程 ⭐️ 新增
+1. **进入主力选股**
+ - 点击侧边栏"🎯 主力选股"按钮
+
+2. **设置筛选参数**
+ - **起始日期**:选择区间开始日期(如"2025年7月23日")
+ - **市值范围**:
+ - 最小市值:默认50亿(可调整)
+ - 最大市值:默认5000亿(可调整)
+ - **最大涨跌幅**:默认50%(过滤涨幅过高的股票)
+
+3. **开始分析**
+ - 点击"🚀 开始主力选股分析"按钮
+ - 系统会自动:
+ - 从问财获取前100名主力资金净流入股票
+ - 按条件筛选(市值、涨跌幅)
+ - AI分析师团队进行整体分析
+ - 资深研究员精选3-5只优质标的
+
+4. **查看分析结果**
+ - **统计信息**:
+ - 获取股票数:原始数据量
+ - 筛选后:符合条件的股票数
+ - 最终推荐:精选的优质标的数量
+
+ - **AI分析师报告**(三个标签页):
+ - 💰 资金流向分析:主力资金动向、热点板块
+ - 📊 行业板块分析:行业轮动、板块联动
+ - 📈 财务基本面分析:盈利能力、成长性评估
+
+ - **精选推荐**:
+ - 展开查看每只推荐股票的详细信息
+ - 推荐理由、关键指标、能力评分
+
+ - **候选股票列表**:
+ - 前100名股票完整列表
+ - 按主力资金流向排序
+ - 包含行业、资金流向、涨跌幅、市值、市盈率等
+
+5. **导出报告**
+ - 📄 **下载Markdown报告**:便于编辑和二次处理
+ - 🌐 **下载HTML报告**:美观易读,可打印
+ - 📊 **下载候选列表CSV**:Excel打开,便于筛选排序
+
+6. **使用技巧**
+ - **起始日期**:建议选择近3-6个月的日期
+ - **市值范围**:中小盘(50-300亿)波动较大,大盘(300-5000亿)相对稳定
+ - **涨跌幅限制**:建议30%-50%,避免追高
+ - **查看调试信息**:展开"🔍 调试信息"查看实际获取的列名
+
### 结果解读
- **投资评级**:买入/持有/卖出
- **目标价位**:预期价格目标
@@ -514,6 +613,33 @@ AI股票分析系统
- 通知历史管理
- 配置测试功能
+#### 🎯 主力选股模块 ⭐️ 新增
+**main_force_selector.py**
+- 问财数据获取(主力资金净流入)
+- 多维度筛选(市值、涨跌幅)
+- 智能列名匹配
+- 数据格式化和清洗
+
+**main_force_analysis.py**
+- AI分析师团队协作
+- 整体数据分析(100只股票)
+- 资金流向分析
+- 行业板块分析
+- 财务基本面分析
+- 资深研究员决策
+
+**main_force_ui.py**
+- 参数配置界面
+- 分析进度显示
+- 结果可视化展示
+- 报告导出功能
+
+**main_force_pdf_generator.py**
+- Markdown报告生成
+- HTML报告生成
+- CSV数据导出
+- 美观格式化输出
+
#### 🤖 量化交易模块 (miniqmt_interface.py) ⭐️ 新增
- MiniQMT接口对接
- 自动交易执行
@@ -542,6 +668,8 @@ AI股票分析系统
### 数据源
- **美股数据**:Yahoo Finance (yfinance)
- **A股数据**:AKShare免费接口
+- **主力资金数据**:问财(pywencai)- 主力选股功能 ⭐️
+- **季报数据**:问财(pywencai)- 财务数据补充 ⭐️
- **技术指标**:TA-Lib技术分析库
### AI模型
diff --git a/app.py b/app.py
index 9f07daf..2b83e3f 100644
--- a/app.py
+++ b/app.py
@@ -383,6 +383,7 @@ def main():
st.markdown("""
**股票代码格式**
- 🇨🇳 A股:6位数字(如600519)
+ - 🇭🇰 港股:1-5位数字(如700、00700)或HK前缀(如HK00700)
- 🇺🇸 美股:字母代码(如AAPL)
**功能说明**
@@ -448,8 +449,8 @@ def main():
with col1:
stock_input = st.text_input(
"🔍 请输入股票代码或名称",
- placeholder="例如: AAPL, 000001, 600036",
- help="支持美股代码(如AAPL)和A股代码(如000001)"
+ placeholder="例如: AAPL, 000001, 00700",
+ help="支持A股(如000001)、港股(如00700)和美股(如AAPL)"
)
with col2:
@@ -464,9 +465,9 @@ def main():
# 批量股票分析界面
stock_input = st.text_area(
"🔍 请输入多个股票代码(每行一个或用逗号分隔)",
- placeholder="例如:\n000001\n600036\n600519\n\n或者: 000001, 600036, 600519",
+ placeholder="例如:\n000001\n600036\n00700\n\n或者: 000001, 600036, 00700, AAPL",
height=120,
- help="支持多种格式:每行一个代码或用逗号分隔"
+ help="支持多种格式:每行一个代码或用逗号分隔。支持A股、港股、美股"
)
col1, col2, col3 = st.columns(3)
@@ -1414,9 +1415,9 @@ def show_example_interface():
with col1:
st.markdown("""
### 🚀 如何使用
- 1. **输入股票代码**:支持美股(如AAPL、MSFT)和A股(如000001、600036)
+ 1. **输入股票代码**:支持A股(如000001)、港股(如00700)和美股(如AAPL)
2. **点击开始分析**:系统将启动AI分析师团队
- 3. **查看分析报告**:5位专业分析师将从不同角度分析
+ 3. **查看分析报告**:多位专业分析师将从不同角度分析
4. **获得投资建议**:获得最终的投资评级和操作建议
### 📊 分析维度
@@ -1431,22 +1432,34 @@ def show_example_interface():
st.markdown("""
### 📈 示例股票代码
- **美股热门**
- - AAPL (苹果)
- - MSFT (微软)
- - GOOGL (谷歌)
- - TSLA (特斯拉)
- - NVDA (英伟达)
-
**A股热门**
- 000001 (平安银行)
- 600036 (招商银行)
- - 000002 (万科A)
- 600519 (贵州茅台)
- - 000858 (五粮液)
+
+ **港股热门**
+ - 00700 或 700 (腾讯控股)
+ - 09988 或 9988 (阿里巴巴-SW)
+ - 01810 或 1810 (小米集团-W)
+
+ **美股热门**
+ - AAPL (苹果)
+ - MSFT (微软)
+ - NVDA (英伟达)
""")
st.info("💡 提示:首次运行需要配置DeepSeek API Key,请在.env中设置DEEPSEEK_API_KEY")
+
+ st.markdown("---")
+ st.markdown("""
+ ### 🌏 市场支持说明
+ - **A股**:完整支持(技术分析、财务数据、资金流向、市场情绪、新闻公告)
+ - **港股**:部分支持(技术分析、21项财务指标)⭐️
+ - **美股**:完整支持(技术分析、财务数据)
+
+ ### 📊 港股支持的财务指标
+ 盈利能力(6项)、营运能力(3项)、偿债能力(2项)、市场表现(4项)、分红指标(3项)、股本结构(3项)
+ """)
def display_history_records():
"""显示历史分析记录"""
diff --git a/pdf_generator.py b/pdf_generator.py
index 346d525..d9a1292 100644
--- a/pdf_generator.py
+++ b/pdf_generator.py
@@ -21,30 +21,49 @@ import tempfile
import os
def register_chinese_fonts():
- """注册中文字体"""
+ """注册中文字体 - 支持Windows和Linux系统"""
try:
# 检查是否已经注册过
if 'ChineseFont' in pdfmetrics.getRegisteredFontNames():
return 'ChineseFont'
- # 尝试注册系统中文字体
- font_paths = [
+ # Windows系统字体路径
+ windows_font_paths = [
'C:/Windows/Fonts/simsun.ttc', # 宋体
'C:/Windows/Fonts/simhei.ttf', # 黑体
'C:/Windows/Fonts/msyh.ttc', # 微软雅黑
+ 'C:/Windows/Fonts/msyh.ttf', # 微软雅黑(TTF格式)
]
- for font_path in font_paths:
+ # Linux系统字体路径(Docker环境)
+ linux_font_paths = [
+ '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc', # 文泉驿正黑
+ '/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', # 文泉驿微米黑
+ '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc', # Noto Sans CJK
+ '/usr/share/fonts/opentype/noto/NotoSerifCJK-Regular.ttc', # Noto Serif CJK
+ '/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf', # Droid字体
+ ]
+
+ # 合并所有可能的字体路径
+ all_font_paths = windows_font_paths + linux_font_paths
+
+ # 尝试注册字体
+ for font_path in all_font_paths:
if os.path.exists(font_path):
try:
pdfmetrics.registerFont(TTFont('ChineseFont', font_path))
+ print(f"✅ 成功注册中文字体: {font_path}")
return 'ChineseFont'
- except:
+ except Exception as e:
+ print(f"⚠️ 尝试注册字体 {font_path} 失败: {e}")
continue
- # 如果没有找到中文字体,使用默认字体
+ # 如果没有找到中文字体,打印警告并使用默认字体
+ print("⚠️ 警告:未找到中文字体,PDF中文可能显示为方框")
+ print("建议:在Docker中安装中文字体包")
return 'Helvetica'
- except:
+ except Exception as e:
+ print(f"❌ 注册中文字体时出错: {e}")
return 'Helvetica'
def create_pdf_report(stock_info, agents_results, discussion_result, final_decision):
diff --git a/stock_data.py b/stock_data.py
index c80e529..54f342f 100644
--- a/stock_data.py
+++ b/stock_data.py
@@ -19,9 +19,13 @@ class StockDataFetcher:
def get_stock_info(self, symbol):
"""获取股票基本信息"""
try:
- # 处理中国股票代码
+ # 处理中国A股
if self._is_chinese_stock(symbol):
return self._get_chinese_stock_info(symbol)
+ # 处理港股
+ elif self._is_hk_stock(symbol):
+ return self._get_hk_stock_info(symbol)
+ # 处理美股
else:
return self._get_us_stock_info(symbol)
except Exception as e:
@@ -32,16 +36,36 @@ class StockDataFetcher:
try:
if self._is_chinese_stock(symbol):
return self._get_chinese_stock_data(symbol, period)
+ elif self._is_hk_stock(symbol):
+ return self._get_hk_stock_data(symbol, period)
else:
return self._get_us_stock_data(symbol, period, interval)
except Exception as e:
return {"error": f"获取股票数据失败: {str(e)}"}
def _is_chinese_stock(self, symbol):
- """判断是否为中国股票"""
- # 简单判断:包含数字且长度为6位的认为是中国股票
+ """判断是否为中国A股"""
+ # 简单判断:包含数字且长度为6位的认为是中国A股
return symbol.isdigit() and len(symbol) == 6
+ def _is_hk_stock(self, symbol):
+ """判断是否为港股"""
+ # 港股代码通常是1-5位数字,或者前面带HK/hk前缀
+ if symbol.upper().startswith('HK'):
+ return True
+ # 纯数字且长度在1-5位之间,认为可能是港股
+ if symbol.isdigit() and 1 <= len(symbol) <= 5:
+ return True
+ return False
+
+ def _normalize_hk_code(self, symbol):
+ """规范化港股代码为5位格式(如700 -> 00700)"""
+ # 移除HK前缀
+ if symbol.upper().startswith('HK'):
+ symbol = symbol[2:]
+ # 补齐到5位
+ return symbol.zfill(5)
+
def _get_chinese_stock_info(self, symbol):
"""获取中国股票基本信息"""
try:
@@ -187,6 +211,92 @@ class StockDataFetcher:
"exchange": "上海/深圳证券交易所"
}
+ def _get_hk_stock_info(self, symbol):
+ """获取港股基本信息"""
+ try:
+ # 规范化港股代码
+ hk_code = self._normalize_hk_code(symbol)
+
+ # 初始化基本信息
+ info = {
+ "symbol": hk_code,
+ "name": "未知",
+ "current_price": "N/A",
+ "change_percent": "N/A",
+ "pe_ratio": "N/A",
+ "pb_ratio": "N/A",
+ "market_cap": "N/A",
+ "market": "香港股市",
+ "exchange": "香港交易所"
+ }
+
+ # 方法1: 获取港股实时行情
+ try:
+ # 使用akshare获取港股实时数据
+ realtime_df = ak.stock_hk_spot_em()
+ if realtime_df is not None and not realtime_df.empty:
+ # 查找对应股票
+ stock_data = realtime_df[realtime_df['代码'] == hk_code]
+ if not stock_data.empty:
+ row = stock_data.iloc[0]
+ info['name'] = row.get('名称', '未知')
+ info['current_price'] = row.get('最新价', 'N/A')
+ info['change_percent'] = row.get('涨跌幅', 'N/A')
+
+ # 市值(港元)
+ market_cap = row.get('总市值', 'N/A')
+ if market_cap != 'N/A':
+ try:
+ info['market_cap'] = float(market_cap)
+ except:
+ pass
+
+ # 市盈率
+ pe = row.get('市盈率', 'N/A')
+ if pe != 'N/A' and pe != '-':
+ try:
+ pe_val = float(pe)
+ if 0 < pe_val <= 1000:
+ info['pe_ratio'] = pe_val
+ except:
+ pass
+ except Exception as e:
+ print(f"获取港股实时数据失败: {e}")
+
+ # 方法2: 尝试使用历史数据获取价格信息
+ if info['current_price'] == 'N/A':
+ try:
+ hist_df = ak.stock_hk_hist(symbol=hk_code, period="daily",
+ start_date=(datetime.now() - timedelta(days=5)).strftime('%Y%m%d'),
+ end_date=datetime.now().strftime('%Y%m%d'), adjust="qfq")
+ if hist_df is not None and not hist_df.empty:
+ latest = hist_df.iloc[-1]
+ info['current_price'] = latest['收盘']
+ # 计算涨跌幅
+ if len(hist_df) > 1:
+ prev_close = hist_df.iloc[-2]['收盘']
+ change_pct = ((latest['收盘'] - prev_close) / prev_close) * 100
+ info['change_percent'] = round(change_pct, 2)
+ except Exception as e:
+ print(f"获取港股历史数据失败: {e}")
+
+ return info
+
+ except Exception as e:
+ print(f"获取港股信息完全失败: {e}")
+ # 返回基本信息
+ return {
+ "symbol": symbol,
+ "name": f"港股{symbol}",
+ "current_price": "N/A",
+ "change_percent": "N/A",
+ "pe_ratio": "N/A",
+ "pb_ratio": "N/A",
+ "market_cap": "N/A",
+ "market": "香港股市",
+ "exchange": "香港交易所"
+ }
+
def _get_us_stock_info(self, symbol):
"""获取美股基本信息"""
import time
@@ -315,6 +425,48 @@ class StockDataFetcher:
except Exception as e:
return {"error": f"获取中国股票数据失败: {str(e)}"}
+ def _get_hk_stock_data(self, symbol, period="1y"):
+ """获取港股历史数据"""
+ try:
+ # 规范化港股代码
+ hk_code = self._normalize_hk_code(symbol)
+
+ # 计算日期范围
+ end_date = datetime.now().strftime('%Y%m%d')
+ if period == "1y":
+ start_date = (datetime.now() - timedelta(days=365)).strftime('%Y%m%d')
+ elif period == "6mo":
+ start_date = (datetime.now() - timedelta(days=180)).strftime('%Y%m%d')
+ elif period == "3mo":
+ start_date = (datetime.now() - timedelta(days=90)).strftime('%Y%m%d')
+ elif period == "1mo":
+ start_date = (datetime.now() - timedelta(days=30)).strftime('%Y%m%d')
+ else:
+ start_date = (datetime.now() - timedelta(days=365)).strftime('%Y%m%d')
+
+ # 获取港股历史数据
+ df = ak.stock_hk_hist(symbol=hk_code, period="daily",
+ start_date=start_date, end_date=end_date, adjust="qfq")
+
+ if df is not None and not df.empty:
+ # 重命名列以匹配标准格式
+ df = df.rename(columns={
+ '日期': 'Date',
+ '开盘': 'Open',
+ '收盘': 'Close',
+ '最高': 'High',
+ '最低': 'Low',
+ '成交量': 'Volume'
+ })
+ df['Date'] = pd.to_datetime(df['Date'])
+ df.set_index('Date', inplace=True)
+ return df
+ else:
+ return {"error": "无法获取港股历史数据"}
+
+ except Exception as e:
+ return {"error": f"获取港股数据失败: {str(e)}"}
+
def _get_us_stock_data(self, symbol, period="1y", interval="1d"):
"""获取美股历史数据"""
try:
@@ -398,6 +550,8 @@ class StockDataFetcher:
try:
if self._is_chinese_stock(symbol):
return self._get_chinese_financial_data(symbol)
+ elif self._is_hk_stock(symbol):
+ return self._get_hk_financial_data(symbol)
else:
return self._get_us_financial_data(symbol)
except Exception as e:
@@ -547,6 +701,74 @@ class StockDataFetcher:
traceback.print_exc()
return None
+ def _get_hk_financial_data(self, symbol):
+ """获取港股财务数据"""
+ hk_code = self._normalize_hk_code(symbol)
+
+ financial_data = {
+ "symbol": hk_code,
+ "balance_sheet": None,
+ "income_statement": None,
+ "cash_flow": None,
+ "financial_ratios": {},
+ "quarter_data": None,
+ "data_source": "eastmoney",
+ "note": "港股财务数据来自东方财富"
+ }
+
+ try:
+ # 使用akshare获取港股财务指标(东方财富数据源)
+ print(f"正在获取港股 {hk_code} 的财务指标...")
+ try:
+ financial_indicator = ak.stock_hk_financial_indicator_em(symbol=hk_code)
+
+ if financial_indicator is not None and not financial_indicator.empty:
+ # 将财务指标数据转换为字典
+ indicator_dict = financial_indicator.iloc[0].to_dict()
+
+ # 整理财务比率数据
+ financial_data["financial_ratios"] = {
+ "基本每股收益": self._safe_convert(indicator_dict.get('基本每股收益(元)', 'N/A')),
+ "每股净资产": self._safe_convert(indicator_dict.get('每股净资产(元)', 'N/A')),
+ "每股股息TTM": self._safe_convert(indicator_dict.get('每股股息TTM(港元)', 'N/A')),
+ "派息比率": self._safe_convert(indicator_dict.get('派息比率(%)', 'N/A')),
+ "每股经营现金流": self._safe_convert(indicator_dict.get('每股经营现金流(元)', 'N/A')),
+ "股息率TTM": self._safe_convert(indicator_dict.get('股息率TTM(%)', 'N/A')),
+ "总市值": self._safe_convert(indicator_dict.get('总市值(港元)', 'N/A')),
+ "港股市值": self._safe_convert(indicator_dict.get('港股市值(港元)', 'N/A')),
+ "营业总收入": self._safe_convert(indicator_dict.get('营业总收入', 'N/A')),
+ "营业收入环比增长": self._safe_convert(indicator_dict.get('营业总收入滚动环比增长(%)', 'N/A')),
+ "销售净利率": self._safe_convert(indicator_dict.get('销售净利率(%)', 'N/A')),
+ "净利润": self._safe_convert(indicator_dict.get('净利润', 'N/A')),
+ "净利润环比增长": self._safe_convert(indicator_dict.get('净利润滚动环比增长(%)', 'N/A')),
+ "ROE股东权益回报率": self._safe_convert(indicator_dict.get('股东权益回报率(%)', 'N/A')),
+ "市盈率": self._safe_convert(indicator_dict.get('市盈率', 'N/A')),
+ "市净率": self._safe_convert(indicator_dict.get('市净率', 'N/A')),
+ "ROA总资产回报率": self._safe_convert(indicator_dict.get('总资产回报率(%)', 'N/A')),
+ "法定股本": self._safe_convert(indicator_dict.get('法定股本(股)', 'N/A')),
+ "已发行股本": self._safe_convert(indicator_dict.get('已发行股本(股)', 'N/A')),
+ "每手股": self._safe_convert(indicator_dict.get('每手股', 'N/A')),
+ }
+
+ print(f"✅ 成功获取港股 {hk_code} 的财务指标")
+ print(f" ROE: {financial_data['financial_ratios']['ROE股东权益回报率']}")
+ print(f" 市盈率: {financial_data['financial_ratios']['市盈率']}")
+ print(f" 市净率: {financial_data['financial_ratios']['市净率']}")
+ else:
+ print(f"⚠️ 未获取到港股 {hk_code} 的财务指标数据")
+ financial_data["note"] = "未获取到财务数据"
+
+ except Exception as e:
+ print(f"⚠️ 获取港股财务指标失败: {e}")
+ financial_data["note"] = f"获取财务数据失败: {str(e)}"
+
+ return financial_data
+
+ except Exception as e:
+ print(f"获取港股财务数据异常: {e}")
+ financial_data["note"] = f"获取失败: {str(e)}"
+ return financial_data
+
def _get_us_financial_data(self, symbol):
"""获取美股财务数据"""
financial_data = {
diff --git a/test_pdf_fonts.py b/test_pdf_fonts.py
new file mode 100644
index 0000000..bc2bcc9
--- /dev/null
+++ b/test_pdf_fonts.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+PDF中文字体测试脚本
+用于验证Docker环境中字体是否正确安装
+"""
+
+import os
+import sys
+from reportlab.pdfbase import pdfmetrics
+from reportlab.pdfbase.ttfonts import TTFont
+
+def check_system_fonts():
+ """检查系统中可用的中文字体"""
+ print("=" * 60)
+ print("检查系统中的中文字体")
+ print("=" * 60)
+
+ # Windows字体路径
+ windows_paths = [
+ 'C:/Windows/Fonts/simsun.ttc',
+ 'C:/Windows/Fonts/simhei.ttf',
+ 'C:/Windows/Fonts/msyh.ttc',
+ 'C:/Windows/Fonts/msyh.ttf',
+ ]
+
+ # Linux字体路径
+ linux_paths = [
+ '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc',
+ '/usr/share/fonts/truetype/wqy/wqy-microhei.ttc',
+ '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc',
+ '/usr/share/fonts/opentype/noto/NotoSerifCJK-Regular.ttc',
+ ]
+
+ all_paths = windows_paths + linux_paths
+
+ found_fonts = []
+ for font_path in all_paths:
+ if os.path.exists(font_path):
+ print(f"✅ 找到字体: {font_path}")
+ found_fonts.append(font_path)
+ else:
+ print(f"❌ 未找到: {font_path}")
+
+ print(f"\n总计找到 {len(found_fonts)} 个中文字体文件")
+ return found_fonts
+
+def test_font_registration():
+ """测试字体注册"""
+ print("\n" + "=" * 60)
+ print("测试字体注册")
+ print("=" * 60)
+
+ fonts = check_system_fonts()
+
+ if not fonts:
+ print("\n❌ 错误:未找到任何中文字体")
+ print("建议:")
+ print(" - Windows: 确保系统已安装中文字体")
+ print(" - Linux/Docker: 运行 'apt-get install fonts-noto-cjk fonts-wqy-zenhei'")
+ return False
+
+ # 尝试注册第一个找到的字体
+ test_font = fonts[0]
+ try:
+ pdfmetrics.registerFont(TTFont('TestChineseFont', test_font))
+ print(f"\n✅ 成功注册字体: {test_font}")
+ print(f" 注册名称: TestChineseFont")
+ return True
+ except Exception as e:
+ print(f"\n❌ 字体注册失败: {e}")
+ return False
+
+def test_pdf_generation():
+ """测试PDF生成"""
+ print("\n" + "=" * 60)
+ print("测试PDF生成")
+ print("=" * 60)
+
+ try:
+ from reportlab.lib.pagesizes import A4
+ from reportlab.platypus import SimpleDocTemplate, Paragraph
+ from reportlab.lib.styles import ParagraphStyle
+ import io
+
+ # 导入字体注册函数
+ from pdf_generator import register_chinese_fonts
+
+ # 注册字体
+ font_name = register_chinese_fonts()
+ print(f"\n使用字体: {font_name}")
+
+ # 创建测试PDF
+ buffer = io.BytesIO()
+ doc = SimpleDocTemplate(buffer, pagesize=A4)
+
+ # 创建样式
+ style = ParagraphStyle(
+ 'TestStyle',
+ fontName=font_name,
+ fontSize=12
+ )
+
+ # 创建内容
+ test_text = "这是一个中文PDF测试 - AI股票分析系统"
+ paragraph = Paragraph(test_text, style)
+
+ # 生成PDF
+ doc.build([paragraph])
+
+ # 检查生成的PDF大小
+ pdf_size = len(buffer.getvalue())
+ print(f"✅ PDF生成成功,大小: {pdf_size} 字节")
+
+ # 保存测试PDF
+ with open('test_chinese_pdf.pdf', 'wb') as f:
+ f.write(buffer.getvalue())
+ print(f"✅ 测试PDF已保存到: test_chinese_pdf.pdf")
+ print(f" 请打开文件检查中文是否正常显示")
+
+ return True
+
+ except Exception as e:
+ print(f"❌ PDF生成失败: {e}")
+ import traceback
+ traceback.print_exc()
+ return False
+
+def main():
+ """主函数"""
+ print("\n")
+ print("╔" + "=" * 58 + "╗")
+ print("║" + " " * 15 + "PDF中文字体测试" + " " * 15 + "║")
+ print("╚" + "=" * 58 + "╝")
+ print()
+
+ # 检测操作系统
+ import platform
+ print(f"操作系统: {platform.system()} {platform.release()}")
+ print(f"Python版本: {sys.version}")
+ print()
+
+ # 运行测试
+ font_check = check_system_fonts()
+ font_reg = test_font_registration()
+ pdf_gen = test_pdf_generation()
+
+ # 总结
+ print("\n" + "=" * 60)
+ print("测试总结")
+ print("=" * 60)
+ print(f"字体检查: {'✅ 通过' if font_check else '❌ 失败'}")
+ print(f"字体注册: {'✅ 通过' if font_reg else '❌ 失败'}")
+ print(f"PDF生成: {'✅ 通过' if pdf_gen else '❌ 失败'}")
+
+ if font_check and font_reg and pdf_gen:
+ print("\n🎉 所有测试通过!PDF中文字体配置正确。")
+ else:
+ print("\n⚠️ 部分测试失败,请查看上方详细信息进行排查。")
+
+ print()
+
+if __name__ == "__main__":
+ main()
+
diff --git a/港股功能说明.md b/港股功能说明.md
new file mode 100644
index 0000000..b3a45da
--- /dev/null
+++ b/港股功能说明.md
@@ -0,0 +1,226 @@
+# 港股分析功能说明
+
+## ✨ 功能概述
+
+系统现已支持港股分析功能,可以对香港交易所上市的股票进行技术面和基本面分析。
+
+## 📊 支持的功能
+
+### ✅ 完全支持
+1. **股票代码识别**
+ - 支持1-5位数字代码(如:700, 9988, 01810)
+ - 支持HK前缀格式(如:HK00700)
+ - 自动规范化为5位标准格式
+
+2. **基本信息获取**
+ - 股票名称
+ - 当前价格
+ - 涨跌幅
+ - 市盈率(PE)
+ - 市净率(PB)
+ - 市值
+
+3. **历史数据获取**
+ - 支持多个时间周期(1mo, 3mo, 6mo, 1y)
+ - OHLCV数据(开盘、最高、最低、收盘、成交量)
+ - 完整的历史K线数据
+
+4. **技术指标计算**
+ - 移动平均线(MA5, MA10, MA20, MA60)
+ - 相对强弱指标(RSI)
+ - MACD指标
+ - 布林带(Bollinger Bands)
+ - KDJ指标
+ - 成交量指标
+
+5. **AI智能分析**
+ - 技术分析师:图表形态、趋势判断
+ - 风险管理师:风险识别与控制
+ - 基本面分析师:公司价值评估(含21项财务指标)⭐️ 新增
+ - 团队综合决策
+
+### ✅ 完全支持(新增)
+1. **财务指标数据**
+ - 基本每股收益、每股净资产
+ - 每股股息TTM、派息比率、股息率TTM
+ - 每股经营现金流
+ - 总市值、港股市值
+ - 营业总收入、营业收入环比增长
+ - 销售净利率、净利润、净利润环比增长
+ - ROE(股东权益回报率)
+ - ROA(总资产回报率)
+ - 市盈率、市净率
+ - 法定股本、已发行股本、每手股数
+ - 数据来源:东方财富(akshare接口)
+
+### ❌ 暂不支持
+1. **资金流向分析**(仅支持A股)
+2. **市场情绪分析**(仅支持A股)
+3. **新闻公告分析**(仅支持A股)
+
+## 🔧 使用方法
+
+### 代码格式示例
+```python
+# 方式1:简短格式
+700 # 腾讯控股
+9988 # 阿里巴巴-SW
+1810 # 小米集团-W
+
+# 方式2:标准5位格式
+00700 # 腾讯控股
+09988 # 阿里巴巴-SW
+01810 # 小米集团-W
+
+# 方式3:HK前缀格式
+HK00700 # 腾讯控股
+HK09988 # 阿里巴巴-SW
+HK01810 # 小米集团-W
+```
+
+### 在系统中使用
+1. **单个分析**
+ - 在输入框输入港股代码(如:700 或 00700)
+ - 点击"开始分析"
+ - 查看AI团队分析报告
+
+2. **批量分析**
+ - 支持混合分析A股、港股、美股
+ - 例如:`000001, 00700, AAPL`
+
+3. **分析师选择**
+ - ✅ 推荐选择:技术分析师、基本面分析师(含21项财务指标)、风险管理师
+ - ❌ 不推荐:市场情绪分析师、新闻公告分析师(港股不支持)
+
+## 📈 热门港股代码
+
+| 代码 | 公司名称 | 行业 |
+|------|----------|------|
+| 00700 | 腾讯控股 | 互联网科技 |
+| 09988 | 阿里巴巴-SW | 电商/云计算 |
+| 01810 | 小米集团-W | 智能手机/IoT |
+| 00388 | 香港交易所 | 金融服务 |
+| 00939 | 建设银行 | 银行 |
+| 01024 | 快手-W | 短视频平台 |
+| 09618 | 京东集团-SW | 电商 |
+| 03690 | 美团-W | 本地生活服务 |
+| 02331 | 李宁 | 体育用品 |
+| 01299 | 友邦保险 | 保险 |
+
+## 💡 使用建议
+
+### 适合场景
+- ✅ 技术面分析(K线、指标、形态)
+- ✅ 基本面分析(21项财务指标)⭐️ 新增
+- ✅ 短期交易策略制定
+- ✅ 中长期投资决策
+- ✅ 价格趋势判断
+- ✅ 风险评估
+- ✅ 价值投资分析
+
+### 建议补充
+- 💡 行业对比(建议参考行业报告)
+- 💡 深度研究(建议结合年报和行业分析)
+
+## 🔍 技术实现
+
+### 数据来源
+- **行情数据**:AKShare - `stock_hk_spot_em()`
+- **历史数据**:AKShare - `stock_hk_hist()`
+- **财务指标**:AKShare - `stock_hk_financial_indicator_em()` ⭐️ 新增
+- **技术指标**:TA-Lib库计算
+
+### 代码位置
+- **主要实现**:`stock_data.py`
+ - `_is_hk_stock()` - 港股识别
+ - `_normalize_hk_code()` - 代码规范化
+ - `_get_hk_stock_info()` - 基本信息获取
+ - `_get_hk_stock_data()` - 历史数据获取
+ - `_get_hk_financial_data()` - 财务数据获取
+
+## 📊 支持的财务指标(21项)
+
+### 盈利能力指标
+- 基本每股收益(EPS)
+- 净利润
+- 净利润环比增长率
+- 销售净利率
+- ROE(股东权益回报率)
+- ROA(总资产回报率)
+
+### 营运能力指标
+- 营业总收入
+- 营业收入环比增长率
+- 每股经营现金流
+
+### 偿债能力指标
+- 每股净资产
+- 总资产
+
+### 市场表现指标
+- 市盈率(P/E)
+- 市净率(P/B)
+- 总市值
+- 港股市值
+
+### 分红指标
+- 每股股息TTM
+- 股息率TTM
+- 派息比率
+
+### 股本结构
+- 法定股本
+- 已发行股本
+- 每手股数
+
+## 🚀 后续优化方向
+
+1. **数据源扩展**
+ - ✅ 已完成:财务指标数据(21项)
+ - 待开发:完整财务报表(资产负债表、利润表、现金流量表)
+ - 待开发:港股通资金流向
+
+2. **功能增强**
+ - 港股特有指标分析
+ - 恒生指数关联分析
+ - 南下资金追踪
+
+3. **用户体验**
+ - 港股交易时间提示
+ - 货币汇率转换
+ - 港股通标的筛选
+
+## 📝 注意事项
+
+1. **交易时间**
+ - 港股交易时间与A股不同
+ - 上午:9:30-12:00
+ - 下午:13:00-16:00
+ - 注意节假日差异
+
+2. **货币单位**
+ - 报价单位:港币(HKD)
+ - 与人民币汇率需要转换
+
+3. **数据延迟**
+ - 免费数据可能有延迟
+ - 建议以实盘为准
+
+4. **投资风险**
+ - 港股T+0交易,风险较大
+ - 涨跌幅无限制
+ - 请谨慎投资
+
+## 📞 技术支持
+
+如遇到港股分析相关问题,请:
+1. 查看系统日志输出
+2. 运行测试脚本:`python test_hk_stock.py`
+3. 联系技术支持:ws3101001@126.com
+
+---
+
+**最后更新**:2025年10月10日
+**版本**:v1.0
+**状态**:✅ 已完成测试
+