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 @@ 01 02 +## ✨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 @@ image +### 🎯 主力选股功能(新增)⭐️ +- **智能选股**:基于主力资金流向,筛选优质投资标的 +- **数据来源**:问财数据,获取区间主力资金净流入前100名股票 +- **多维筛选**: + - 起始日期:自定义区间开始日期 + - 市值范围:50亿-5000亿灵活设置 + - 涨跌幅限制:过滤涨幅过高的股票,避免追高 +- **AI分析团队**: + - 💰 **资金流向分析师**:分析主力资金动向、热点板块、资金持续性 + - 📊 **行业板块分析师**:解读行业轮动、板块联动、市场情绪 + - 📈 **财务基本面分析师**:评估公司盈利能力、成长性、财务健康度 + - 👔 **资深研究员**:综合三大分析师意见,精选3-5只优质标的 +- **批量分析**:一次性分析100只股票,效率高 +- **完整报告**: + - 📄 Markdown格式:便于编辑和二次处理 + - 🌐 HTML格式:美观易读,支持浏览器打开 + - 📊 CSV格式:候选股票完整数据导出 +- **候选列表**:前100名股票按主力资金流向排序展示 +- **关键指标**:区间主力资金流向、区间涨跌幅、市值、市盈率、市净率等 +主力选股界面 +AI分析报告 + ### 🔍 实时监测功能 - **智能监测**:自动监控股票价格变动 - **关键位置提醒**:进场区间、止盈位、止损位触发通知 @@ -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 +**状态**:✅ 已完成测试 +