From bb3046da628f96014555c3957e210705615da4b1 Mon Sep 17 00:00:00 2001 From: RonaldJEN Date: Mon, 31 Mar 2025 13:32:52 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=9F=BA=E4=BA=8EOWL/Camel?= =?UTF-8?q?=E6=A1=86=E6=9E=B6=E7=9A=84A=E8=82=A1=E6=8A=95=E8=B5=84?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E7=B3=BB=E7=BB=9F1.=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E5=A4=9A=E4=BB=A3=E7=90=86A=E8=82=A1=E6=8A=95=E8=B5=84?= =?UTF-8?q?=E5=88=86=E6=9E=90=E7=B3=BB=E7=BB=9F=EF=BC=8C=E5=9F=BA=E4=BA=8E?= =?UTF-8?q?OWL=E5=92=8CCamel=E6=A1=86=E6=9E=B62.=20=E6=94=AF=E6=8C=8110?= =?UTF-8?q?=E4=B8=AA=E4=B8=93=E4=B8=9A=E8=A7=92=E8=89=B2=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E5=8D=8F=E4=BD=9C=E5=AE=8C=E6=88=90=E6=8A=95=E8=B5=84=E5=88=86?= =?UTF-8?q?=E6=9E=903.=20=E6=8F=90=E4=BE=9B=E5=A4=9A=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E6=94=AF=E6=8C=81(Gemini=E3=80=81OpenAI=E3=80=81Qwen)=E5=92=8C?= =?UTF-8?q?=E7=81=B5=E6=B4=BB=E9=85=8D=E7=BD=AE4.=20=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=AE=8C=E6=95=B4=E7=9A=84=E6=95=B0=E6=8D=AE=E5=A4=84=E7=90=86?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=EF=BC=8C=E4=BB=8E=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=94=B6=E9=9B=86=E5=88=B0=E5=86=B3=E7=AD=965.=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=B8=AD=E8=8B=B1=E6=96=87=E5=8F=8C=E8=AF=AD=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E6=94=AF=E6=8C=81=E8=AF=A5=E7=B3=BB=E7=BB=9F=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E5=A4=9A=E4=BB=A3=E7=90=86=E5=8D=8F=E4=BD=9C=E4=BD=93?= =?UTF-8?q?=E7=B3=BB=EF=BC=8C=E5=AE=9E=E7=8E=B0=E6=9B=B4=E5=85=A8=E9=9D=A2?= =?UTF-8?q?=E3=80=81=E4=B8=93=E4=B8=9A=E7=9A=84A=E8=82=A1=E6=8A=95?= =?UTF-8?q?=E8=B5=84=E5=86=B3=E7=AD=96=EF=BC=8C=E4=B8=BA=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=8F=90=E4=BE=9B=E8=BE=A9=E8=AE=BA=E5=BC=8F=E3=80=81=E5=A4=9A?= =?UTF-8?q?=E8=A7=92=E5=BA=A6=E7=9A=84=E6=8A=95=E8=B5=84=E5=88=86=E6=9E=90?= =?UTF-8?q?=E5=92=8C=E5=BB=BA=E8=AE=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../.env.example | 15 + .../a_share_investment_agent_camel/Dockerfile | 58 ++ .../a_share_investment_agent_camel/LICENSE | 21 + .../a_share_investment_agent_camel/README.md | 390 ++++++++++ .../README_EN.md | 391 ++++++++++ .../requirements.txt | 25 + .../screenshots/system_architecture.png | Bin 0 -> 223661 bytes .../src/__init__.py | 5 + .../src/agents/__init__.py | 31 + .../src/agents/base_agent.py | 143 ++++ .../src/agents/debate_room.py | 198 +++++ .../src/agents/fundamentals_analyst.py | 169 +++++ .../src/agents/investment_agent.py | 351 +++++++++ .../src/agents/market_data_agent.py | 714 ++++++++++++++++++ .../src/agents/portfolio_manager.py | 239 ++++++ .../src/agents/researcher_bear.py | 189 +++++ .../src/agents/researcher_bull.py | 189 +++++ .../src/agents/risk_manager.py | 185 +++++ .../src/agents/sentiment_analyst.py | 170 +++++ .../src/agents/technical_analyst.py | 202 +++++ .../src/agents/valuation_analyst.py | 182 +++++ .../src/main.py | 319 ++++++++ .../src/models.py | 90 +++ .../src/roles.py | 251 ++++++ .../src/tools/__init__.py | 3 + .../src/tools/api.py | 79 ++ .../src/tools/data_helper.py | 278 +++++++ .../src/utils/__init__.py | 3 + .../src/utils/logging_utils.py | 103 +++ 29 files changed, 4993 insertions(+) create mode 100644 community_usecase/a_share_investment_agent_camel/.env.example create mode 100644 community_usecase/a_share_investment_agent_camel/Dockerfile create mode 100644 community_usecase/a_share_investment_agent_camel/LICENSE create mode 100644 community_usecase/a_share_investment_agent_camel/README.md create mode 100644 community_usecase/a_share_investment_agent_camel/README_EN.md create mode 100644 community_usecase/a_share_investment_agent_camel/requirements.txt create mode 100644 community_usecase/a_share_investment_agent_camel/screenshots/system_architecture.png create mode 100644 community_usecase/a_share_investment_agent_camel/src/__init__.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/agents/__init__.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/agents/base_agent.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/agents/debate_room.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/agents/fundamentals_analyst.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/agents/investment_agent.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/agents/market_data_agent.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/agents/portfolio_manager.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/agents/researcher_bear.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/agents/researcher_bull.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/agents/risk_manager.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/agents/sentiment_analyst.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/agents/technical_analyst.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/agents/valuation_analyst.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/main.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/models.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/roles.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/tools/__init__.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/tools/api.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/tools/data_helper.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/utils/__init__.py create mode 100644 community_usecase/a_share_investment_agent_camel/src/utils/logging_utils.py diff --git a/community_usecase/a_share_investment_agent_camel/.env.example b/community_usecase/a_share_investment_agent_camel/.env.example new file mode 100644 index 0000000..d6fa580 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/.env.example @@ -0,0 +1,15 @@ +# Gemini配置 +GEMINI_API_KEY= +GEMINI_MODEL= + +# OpenAI配置 +OPENAI_API_TYPE= +OPENAI_API_VERSION= +OPENAI_API_BASE= +OPENAI_API_KEY= +OPENAI_DEPLOYMENT_NAME= +OPENAI_MODEL= +# Qwen配置 +QWEN_API_KEY= +QWEN_MODEL= +QWEN_API_URL= \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/Dockerfile b/community_usecase/a_share_investment_agent_camel/Dockerfile new file mode 100644 index 0000000..502efa9 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/Dockerfile @@ -0,0 +1,58 @@ +FROM python:3.9-slim + +# 设置工作目录 +WORKDIR /app + +# 安装系统依赖,包括 ta-lib 所需的编译工具和依赖 +RUN apt-get update && apt-get install -y \ + build-essential \ + wget \ + curl \ + gcc \ + g++ \ + make \ + unzip \ + git \ + && rm -rf /var/lib/apt/lists/* + +# 安装 TA-Lib +RUN wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz && \ + tar -xvzf ta-lib-0.4.0-src.tar.gz && \ + cd ta-lib/ && \ + ./configure --prefix=/usr && \ + make && \ + make install && \ + cd .. && \ + rm -rf ta-lib ta-lib-0.4.0-src.tar.gz + +# 复制 requirements.txt +COPY requirements.txt . + +# 安装依赖 +RUN pip install --no-cache-dir -r requirements.txt + +# 复制项目文件 +COPY . . + +# 创建日志目录 +RUN mkdir -p logs screenshots + +# 设置环境变量 +ENV PYTHONPATH="${PYTHONPATH}:/app" + +# 设置entrypoint脚本 +RUN echo '#!/bin/bash\n\ +if [ "$1" = "test" ]; then\n\ + python src/main.py --test\n\ +elif [ -n "$1" ]; then\n\ + python src/main.py --ticker=$1 --model=${2:-qwen}\n\ +else\n\ + python src/main.py --ticker=000001 --model=qwen\n\ +fi' > /app/entrypoint.sh && \ + chmod +x /app/entrypoint.sh + +# 设置entrypoint +ENTRYPOINT ["/app/entrypoint.sh"] + +# 默认参数 +CMD ["000001", "qwen"] \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/LICENSE b/community_usecase/a_share_investment_agent_camel/LICENSE new file mode 100644 index 0000000..ec12d4d --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Virat Singh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/README.md b/community_usecase/a_share_investment_agent_camel/README.md new file mode 100644 index 0000000..6ea94c0 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/README.md @@ -0,0 +1,390 @@ +

+ 🦉A股投资代理系统 +

+ + + +
+

+
+基于OWL/camel框架的多代理A股投资分析系统,使用Camel框架实现。 +
+ +[English](README_EN.md) | +[中文](README.md) | +[致谢](#-致谢) | +[示例运行结果](#-示例运行结果) + +

+
+ +
+ 系统架构图 +
+ +## 📋 项目简介 + +本项目是一个基于OWL框架和Camel框架(v0.2.36)的多代理A股投资分析系统,通过多个专业角色代理协作完成投资分析,为用户提供投资决策建议。系统采用多模型支持,可灵活使用不同的大语言模型(如Gemini、OpenAI、Qwen)进行分析。 + +本项目参与了OWL社区用例挑战赛,是对开源项目[A_Share_investment_Agent](https://github.com/24mlight/A_Share_investment_Agent)的改进版本,使用OWL和Camel框架重构实现。 + +### 🌟 核心特点 + +- **多代理协作体系**: 通过10个专业角色代理协作进行分析,实现更全面、专业的投资决策 +- **多模型支持**: 支持Gemini、OpenAI、Qwen等多种大语言模型,灵活切换 +- **A股市场特化**: 专为A股市场设计,关注中国股市特性和数据 +- **辩论与决策机制**: 创新的多空观点辩论机制,平衡分析观点 +- **完整的数据处理流水线**: 从数据收集到分析再到决策的全流程支持 + +## 🏗️ 系统架构 + +系统架构包含以下几个核心代理: + +### 📊 数据与分析层 +1. **市场数据代理** (Market Data Agent) - 负责收集和预处理市场数据 +2. **技术分析代理** (Technical Analyst) - 分析技术指标并生成交易信号 +3. **基本面分析代理** (Fundamentals Analyst) - 分析基本面数据并生成交易信号 +4. **情绪分析代理** (Sentiment Analyst) - 分析市场情绪并生成交易信号 +5. **价值评估代理** (Valuation Analyst) - 计算股票内在价值并生成交易信号 + +### 🔍 研究与辩论层 +6. **多头研究员** (Researcher Bull) - 提供看多角度分析 +7. **空头研究员** (Researcher Bear) - 提供看空角度分析 +8. **辩论室** (Debate Room) - 整合多空观点形成最终观点 + +### 🧮 决策层 +9. **风险管理代理** (Risk Manager) - 计算风险指标并设置仓位限制 +10. **投资组合管理代理** (Portfolio Manager) - 制定最终交易决策并生成订单 + +## 💻 安装指南 + +### 前提条件 + +- Python 3.9+ +- 相关API密钥(Gemini/OpenAI/Qwen) + +### 安装步骤 + +1. 克隆OWL仓库并进入项目目录 +```bash +git clone https://github.com/camel-ai/owl.git +cd owl/community_usecase/a_share_investment_agent_camel +``` + +2. 安装依赖 +```bash +pip install -r requirements.txt +``` + +3. 配置环境变量(创建.env文件) +``` +# Gemini配置 +GEMINI_API_KEY=your_gemini_api_key +GEMINI_MODEL=gemini-1.5-flash + +# OpenAI配置 +OPENAI_API_KEY=your_openai_api_key +OPENAI_MODEL=gpt-4o + +# Qwen配置 +QWEN_API_KEY=your_qwen_api_key +QWEN_MODEL=qwen-max +QWEN_API_URL=https://your-qwen-api-endpoint +``` + +### Docker 使用方法 + +为了简化安装流程,我们提供了Docker支持。 + +1. 构建Docker镜像 +```bash +docker build -t a-share-investment-agent . +``` + +2. 创建包含API密钥的.env文件 +```bash +# 创建与上述相同格式的.env文件 +touch .env +# 编辑.env文件添加您的API密钥 +``` + +## 🚀 使用方法 + +### 基本用法 + +```bash +python src/main.py --ticker 000001 --model qwen +``` + +### 测试模式 + +```bash +# 使用默认参数(Qwen模型和000001股票)进行测试 +python src/main.py --test + +# 指定股票进行测试 +python src/main.py --test --ticker 600036 +``` + +### 参数说明 + +- `--ticker`: 股票代码 +- `--start-date`: 开始日期 (YYYY-MM-DD) +- `--end-date`: 结束日期 (YYYY-MM-DD) +- `--show-reasoning`: 显示分析推理过程 +- `--num-of-news`: 情绪分析使用的新闻数量 +- `--initial-capital`: 初始现金金额 +- `--initial-position`: 初始股票持仓 +- `--model`: 使用的模型 (gemini, openai, qwen) +- `--test`: 使用预设参数运行测试功能 + +### 示例运行结果 + +以下是针对寒武纪-U(688256)股票在2025/01/04-2025/03/24期间的分析结果示例: + +```bash +python src/main.py --ticker 688256 --start-date 2025-01-04 --end-date 2025-03-24 --model qwen --show-reasoning +``` + +#### 各代理分析日志 + +**市场数据代理**: +```json +{ +"trend": "短期趋势向下,但长期趋势仍需观察", +"judgment": "当前价格处于布林带中轨下方,RSI接近中性水平,MACD线在信号线下方,表明短期可能继续调整。但考虑到之前的大幅上涨,长期趋势仍需进一步观察。建议关注支撑位670.66和阻力位747.57。" +} +``` + +**技术分析代理**: +```json +{ + "signal": "bearish", + "confidence": 0.8, + "reasoning": "从技术指标来看,该股票目前处于一个相对弱势的状态。首先,5日均线(718.856)已经下穿10日均线(743.927)和20日均线(747.574),这表明短期趋势正在走弱。MACD指标也显示了类似的趋势,MACD线(8.994)低于信号线(20.004),并且MACD柱状图(-11.009)为负值,表明市场动能正在减弱。RSI指标(48.676)处于中性区域,但接近超卖区域,这可能预示着短期内可能会有反弹,但整体趋势仍然偏弱。布林带方面,当前价格(685.05)接近布林带下轨(670.657),这可能提供一定的支撑,但考虑到其他指标的弱势表现,支撑力度可能有限。综合来看,当前的市场环境和技术指标均指向一个偏空的趋势。" +} +``` + +**基本面分析代理**: +```json +{ + "signal": "bearish", + "confidence": 0.8, + "reasoning": "寒武纪-U的财务状况和业务表现显示出明显的负面趋势。首先,公司的净利润持续为负,且亏损额在最近几个季度有所增加,这表明公司在盈利能力方面存在严重问题。其次,公司的营业收入虽然在2023年第四季度有所增长,但随后几个季度出现了显著下降,这可能意味着市场需求或公司市场份额的减少。此外,公司的市盈率为负,市净率高达52.58,表明市场对其估值过高,存在较大的泡沫风险。从财务健康状况来看,尽管公司的资产负债率较低,但其经营现金流持续为负,表明公司在运营过程中面临较大的资金压力。综合以上因素,我们认为寒武纪-U的股票目前处于看跌状态。" +} +``` + +**情绪分析代理**: +```json +{ + "signal": "bullish", + "confidence": 0.8, + "reasoning": "从新闻数据来看,整体市场情绪偏向积极,积极新闻数量远多于消极新闻。尽管有一条新闻提到芯片ETF和寒武纪-U的下跌,但大多数新闻都强调了寒武纪-U及其所在板块的积极表现,包括股价上涨和技术突破等。此外,寒武纪-U被纳入富时中国A50指数的消息也表明市场对其长期前景持乐观态度。社交媒体讨论也主要集中在公司的积极发展上,进一步支持了看涨的观点。", + "key_events": [ + "寒武纪-U被纳入富时中国A50指数", + "寒武纪-U股价多次上涨", + "人工智能AIETF持仓股寒武纪-U大涨", + "智元启元大模型发布" + ] +} +``` + +**估值分析代理**: +```json +{ + "signal": "bearish", + "confidence": 0.85, + "reasoning": "寒武纪-U的财务状况和业务表现显示出明显的负面趋势。首先,公司的净利润持续为负,且亏损额在最近几个季度有所增加,这表明公司在盈利能力方面存在严重问题。其次,公司的营业收入虽然在2023年第四季度有所增长,但随后几个季度出现了显著下降,这可能意味着市场需求或公司市场份额的减少。此外,公司的市盈率为负,市净率高达52.58,表明市场对其估值过高,存在较大的泡沫风险。从财务健康状况来看,尽管公司的资产负债率较低,但其经营现金流持续为负,表明公司在运营过程中面临较大的资金压力。综合以上因素,我们认为寒武纪-U的股票目前处于看跌状态。此外,通过DCF模型和相对估值法计算,公司的内在价值显著低于当前市场价格,进一步支持了看跌的观点。", + "fair_value": 200.0, + "key_metrics": [ + "市盈率", + "市净率", + "营业收入", + "净利润", + "经营现金流" + ] +} +``` + +**多头研究员**: +```json +{ + "key_points": [ + "市场情绪积极,新闻和社交媒体讨论偏向乐观", + "寒武纪-U被纳入富时中国A50指数,表明市场对其长期前景持乐观态度", + "技术突破和股价上涨的积极新闻", + "尽管技术面和基本面显示弱势,但市场情绪和潜在催化剂可能推动股价反弹" + ], + "confidence": 0.6, + "reasoning": "尽管技术面和基本面分析显示寒武纪-U目前处于弱势状态,但市场情绪和潜在催化剂可能推动股价反弹。寒武纪-U被纳入富时中国A50指数,表明市场对其长期前景持乐观态度。此外,公司近期的技术突破和股价上涨的积极新闻也支持了看涨的观点。虽然当前的财务状况和估值较高,但市场情绪和潜在的积极因素可能为股价提供支撑。因此,我们建议在短期内保持谨慎,但长期来看,寒武纪-U仍具有投资潜力。" +} +``` + +**空头研究员**: +```json +{ + "key_points": [ + "技术指标显示短期趋势走弱", + "净利润持续为负且亏损增加", + "营业收入下降表明市场需求减少", + "市净率高达52.58,估值过高", + "经营现金流持续为负,资金压力大", + "DCF模型和相对估值法显示内在价值低于市场价格" + ], + "confidence": 0.8, + "reasoning": "综合技术分析、基本面分析、市场情绪分析和估值分析,寒武纪-U的股票目前处于看跌状态。技术指标显示短期趋势走弱,基本面分析显示公司盈利能力差、市场需求减少、估值过高且资金压力大。尽管市场情绪偏向积极,但这些积极情绪主要集中在短期事件上,无法掩盖长期基本面问题。估值分析进一步支持了看跌的观点,内在价值显著低于当前市场价格。因此,建议卖出寒武纪-U的股票。" +} +``` + +**辩论室**: +```json +{ + "signal": "neutral", + "confidence": 0.7, + "reasoning": "多头和空头研究员的观点都有其合理性和依据。多头研究员强调市场情绪积极、寒武纪-U被纳入富时中国A50指数以及技术突破和股价上涨的积极新闻,这些因素可能在短期内为股价提供支撑。然而,空头研究员指出技术指标显示短期趋势走弱,基本面分析显示公司盈利能力差、市场需求减少、估值过高且资金压力大。尽管市场情绪偏向积极,但这些积极情绪主要集中在短期事件上,无法掩盖长期基本面问题。综合来看,短期内市场情绪和技术突破可能推动股价反弹,但长期基本面问题仍然存在,因此建议保持中立态度,等待更多明确信号。", + "final_verdict": "建议投资者在短期内保持谨慎,关注市场情绪和技术突破的持续性。长期来看,需要密切关注公司的基本面改善情况,特别是盈利能力、市场需求和资金压力等方面。在没有更多明确信号之前,建议保持中立态度,避免过度乐观或悲观。" +} +``` + +**风险管理代理**: +```json +{ + "max_position_size": 0.2, + "volatility": 0.15, + "risk_score": 0.7, + "max_drawdown": 0.25, + "suggested_position_size": 0.15, + "reasoning": "鉴于股票688256较高的波动性和基本面风险,建议采取谨慎态度。虽然短期内存在技术突破的可能性,但长期来看公司面临盈利能力差等问题。因此,建议控制持仓比例不超过20%,并设置适当的止损点以保护资本。同时,通过多样化投资来分散风险。" +} +``` + +**投资组合管理代理(最终决策)**: +```json +{ + "action": "hold", + "quantity": 0, + "confidence": 0.8, + "reasoning": "综合考虑各类分析师的交易信号、辩论结果、风险分析、当前投资组合状况以及最新市场价格,我们决定对股票688256采取持有策略。技术分析和基本面分析均显示该股票处于弱势状态,技术指标显示短期趋势走弱,基本面显示公司盈利能力差、市场需求减少、估值过高且资金压力大。尽管情绪分析显示市场情绪偏向积极,但这些积极情绪主要集中在短期事件上,无法掩盖长期基本面问题。辩论结果也建议在短期内保持谨慎,关注市场情绪和技术突破的持续性。长期来看,需要密切关注公司的基本面改善情况。风险分析建议控制持仓比例不超过20%,并设置适当的止损点以保护资本。因此,我们决定暂时持有该股票,等待更多明确信号。" +} +``` + +#### 综合结论 + +**最终投资决策**: 持有 (置信度: 0.8) + +多个专业代理的分析结果表明,寒武纪-U(688256)股票目前处于技术和基本面的弱势状态,但市场情绪和短期事件可能为股价提供支撑。综合考虑各方面因素,系统建议对该股票采取持有策略,密切关注后续基本面变化和市场情绪变化。同时,风险管理建议将该股票的最大持仓比例控制在投资组合的20%以内,并设置适当的止损点以控制风险。 + +## 🔄 数据流程说明 + +系统的数据流程遵循以下步骤: + +1. **数据采集**: 市场数据代理通过akshare API收集A股市场数据和新闻 +2. **多维分析**: 由4个专业分析代理(技术、基本面、情绪、估值)独立进行分析 +3. **正反研究**: 多头和空头研究员分别从看多和看空角度提供分析报告 +4. **辩论整合**: 辩论室整合多空观点形成最终分析意见 +5. **风险评估**: 风险管理代理评估投资风险并设置交易限制 +6. **最终决策**: 投资组合管理代理制定最终交易决策并生成订单 + + + +## 📋 项目结构 + +``` +community_usecase/a_share_investment_agent_camel/ +├── src/ # 源代码目录 +│ ├── agents/ # 代理实现 +│ │ ├── base_agent.py # 代理基类 +│ │ ├── market_data_agent.py # 市场数据代理 +│ │ ├── technical_analyst.py # 技术分析代理 +│ │ └── ... +│ ├── tools/ # 工具模块 +│ │ └── data_helper.py # 数据辅助工具 +│ ├── utils/ # 实用工具 +│ │ └── logging_utils.py # 日志工具 +│ ├── models.py # 数据模型定义 +│ ├── roles.py # 角色定义 +│ └── main.py # 主程序 +├── tests/ # 测试目录 +├── logs/ # 日志文件 +├── .env # 环境变量 +├── pyproject.toml # Poetry配置 +├── requirements.txt # 依赖项 +└── README.md # 说明文档 +``` + +## 🔍 代码实现亮点 + +### 1. 多代理协作架构 + +本项目实现了10个专业角色的代理,每个代理负责投资分析流程中的特定任务。代理之间通过消息传递进行协作,形成完整的分析决策链条。 + +```python +# 创建代理 +market_data_agent = MarketDataAgent(show_reasoning=show_reasoning, model_name=model_name) +technical_analyst = TechnicalAnalystAgent(show_reasoning=show_reasoning, model_name=model_name) +fundamentals_analyst = FundamentalsAnalystAgent(show_reasoning=show_reasoning, model_name=model_name) +sentiment_analyst = SentimentAnalystAgent(show_reasoning=show_reasoning, model_name=model_name) +valuation_analyst = ValuationAnalystAgent(show_reasoning=show_reasoning, model_name=model_name) +researcher_bull = ResearcherBullAgent(show_reasoning=show_reasoning, model_name=model_name) +researcher_bear = ResearcherBearAgent(show_reasoning=show_reasoning, model_name=model_name) +debate_room = DebateRoomAgent(show_reasoning=show_reasoning, model_name=model_name) +risk_manager = RiskManagerAgent(show_reasoning=show_reasoning, model_name=model_name) +portfolio_manager = PortfolioManagerAgent(show_reasoning=show_reasoning, model_name=model_name) +``` + +### 2. 灵活的多模型支持 + +系统支持包括Gemini、OpenAI和Qwen在内的多种大语言模型,通过统一的接口实现灵活切换。 + +```python +def get_llm_client(model_name: str): + """获取指定的LLM客户端""" + if model_name.lower() == 'gemini': + return GeminiClient() + elif model_name.lower() == 'openai': + return OpenAIClient() + elif model_name.lower() == 'qwen': + return QwenClient() + else: + raise ValueError(f"不支持的模型: {model_name}") +``` + + + +## 📝 改进与创新 + +相较于原始版本,基于Camel框架重构的系统具有以下创新和改进: + +1. **模块化设计**: 更清晰的代理定义和系统结构,便于扩展和维护 +2. **多模型支持**: 灵活支持多种LLM模型,提高系统适应性 +3. **完善的日志系统**: 详细记录各代理的工作过程,便于调试和分析 + + +## ⚠️ 免责声明 + +本项目仅用于**教育和研究目的**。 + +- 不适用于实际交易或投资 +- 不提供任何保证 +- 过往业绩不代表未来表现 +- 创建者不承担任何财务损失责任 +- 投资决策请咨询专业理财顾问 + +使用本软件即表示您同意仅将其用于学习目的。 + +## 📚 相关资源 + +- [OWL框架官方文档](https://github.com/camel-ai/owl) +- [Camel框架官方文档](https://github.com/camel-ai/camel) +- [A_Share_investment_Agent](https://github.com/24mlight/A_Share_investment_Agent) + +## 🙏 致谢 + +本项目基于以下开源项目进行改进: + +1. [A_Share_investment_Agent](https://github.com/24mlight/A_Share_investment_Agent) - 原始A股投资代理项目,为本项目提供了基础架构和投资分析思路,特别感谢其在A股数据处理和分析策略方面的创新设计。 +2. [ai-hedge-fund](https://github.com/virattt/ai-hedge-fund.git) - 原始美股投资代理项目 +3. [Camel框架](https://github.com/camel-ai/camel) - 多代理对话框架 +4. [OWL框架](https://github.com/camel-ai/owl) - 开源多智能体协作框架 + +感谢所有原作者的贡献和启发,为本项目提供了坚实的基础。 \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/README_EN.md b/community_usecase/a_share_investment_agent_camel/README_EN.md new file mode 100644 index 0000000..975d4fb --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/README_EN.md @@ -0,0 +1,391 @@ +

+ 🦉A-Share Investment Agent System +

+ + + +
+

+
+A multi-agent A-share investment analysis system based on OWL/camel framework, implemented using the Camel framework. +
+ +[English](README_EN.md) | +[中文](README.md) | +[Acknowledgements](#-acknowledgements) | +[Example Results](#-example-results) + +

+
+ +
+ System Architecture Diagram +
+ +## 📋 Project Introduction + +This project is a multi-agent A-share investment analysis system based on the OWL framework and Camel framework (v0.2.36), which completes investment analysis through multiple professional role agents working together to provide investment decision recommendations for users. The system adopts multi-model support and can flexibly use different large language models (such as Gemini, OpenAI, Qwen) for analysis. + +This project participated in the OWL community use case challenge and is an improved version of the open-source project [A_Share_investment_Agent](https://github.com/24mlight/A_Share_investment_Agent), restructured using the OWL and Camel frameworks. + +### 🌟 Core Features + +- **Multi-agent Collaboration System**: Analysis through 10 professional role agents working together to achieve more comprehensive and professional investment decisions +- **Multi-model Support**: Support for multiple large language models such as Gemini, OpenAI, Qwen, with flexible switching +- **A-share Market Specialization**: Designed specifically for the A-share market, focusing on Chinese stock market characteristics and data +- **Debate and Decision Mechanism**: Innovative bull and bear perspective debate mechanism to balance analytical viewpoints +- **Complete Data Processing Pipeline**: Full process support from data collection to analysis to decision-making + +## 🏗️ System Architecture + +The system architecture includes the following core agents: + +### 📊 Data and Analysis Layer +1. **Market Data Agent** - Responsible for collecting and preprocessing market data +2. **Technical Analyst** - Analyzes technical indicators and generates trading signals +3. **Fundamentals Analyst** - Analyzes fundamental data and generates trading signals +4. **Sentiment Analyst** - Analyzes market sentiment and generates trading signals +5. **Valuation Analyst** - Calculates the intrinsic value of stocks and generates trading signals + +### 🔍 Research and Debate Layer +6. **Researcher Bull** - Provides bullish perspective analysis +7. **Researcher Bear** - Provides bearish perspective analysis +8. **Debate Room** - Integrates bull and bear perspectives to form a final viewpoint + +### 🧮 Decision Layer +9. **Risk Manager** - Calculates risk indicators and sets position limits +10. **Portfolio Manager** - Formulates the final trading decision and generates orders + +## 💻 Installation Guide + +### Prerequisites + +- Python 3.9+ +- Related API keys (Gemini/OpenAI/Qwen) + +### Installation Steps + +1. Clone the OWL repository and enter the project directory +```bash +git clone https://github.com/camel-ai/owl.git +cd owl/community_usecase/a_share_investment_agent_camel +``` + +2. Install dependencies +```bash +pip install -r requirements.txt +``` + +3. Configure environment variables (create .env file) +``` +# Gemini Configuration +GEMINI_API_KEY=your_gemini_api_key +GEMINI_MODEL=gemini-1.5-flash + +# OpenAI Configuration +OPENAI_API_KEY=your_openai_api_key +OPENAI_MODEL=gpt-4o + +# Qwen Configuration +QWEN_API_KEY=your_qwen_api_key +QWEN_MODEL=qwen-max +QWEN_API_URL=https://your-qwen-api-endpoint +``` + +### Docker Usage Method + +To simplify the installation process, we provide Docker support. + +1. Build Docker image +```bash +docker build -t a-share-investment-agent . +``` + +2. Create a .env file containing API keys +```bash +# Create a .env file with the same format as above +touch .env +# Edit the .env file to add your API keys +``` + +## 🚀 Usage + +### Basic Usage + +```bash +python src/main.py --ticker 000001 --model qwen +``` + +### Test Mode + +```bash +# Use default parameters (Qwen model and 000001 stock) for testing +python src/main.py --test + +# Specify stock for testing +python src/main.py --test --ticker 600036 +``` + +### Parameter Description + +- `--ticker`: Stock code +- `--start-date`: Start date (YYYY-MM-DD) +- `--end-date`: End date (YYYY-MM-DD) +- `--show-reasoning`: Display analysis reasoning process +- `--num-of-news`: Number of news used for sentiment analysis +- `--initial-capital`: Initial cash amount +- `--initial-position`: Initial stock position +- `--model`: Model to use (gemini, openai, qwen) +- `--test`: Run test function with preset parameters + +## 🚀 Example Results + +### Analysis Results + +Below is an example of the analysis results for Cambricon-U (688256) stock during the period 2025/01/04-2025/03/24: + +```bash +python src/main.py --ticker 688256 --start-date 2025-01-04 --end-date 2025-03-24 --model qwen --show-reasoning +``` + +#### Agent Analysis Logs + +**Market Data Agent**: +```json +{ +"trend": "Short-term trend is downward, but long-term trend still needs observation", +"judgment": "Current price is below the middle Bollinger band, RSI is close to neutral level, MACD line is below the signal line, indicating short-term adjustment may continue. However, considering the previous significant rise, the long-term trend still needs further observation. Recommend paying attention to support level 670.66 and resistance level 747.57." +} +``` + +**Technical Analyst**: +```json +{ + "signal": "bearish", + "confidence": 0.8, + "reasoning": "From technical indicators, the stock is currently in a relatively weak state. First, the 5-day moving average (718.856) has crossed below the 10-day (743.927) and 20-day (747.574) moving averages, indicating the short-term trend is weakening. The MACD indicator also shows a similar trend, with the MACD line (8.994) below the signal line (20.004), and the MACD histogram (-11.009) negative, indicating market momentum is weakening. The RSI indicator (48.676) is in neutral territory but approaching oversold territory, which may indicate a short-term rebound, but the overall trend remains weak. On the Bollinger Bands, the current price (685.05) is close to the lower band (670.657), which may provide some support, but considering the weakness shown by other indicators, the support may be limited. Overall, the current market environment and technical indicators point toward a bearish trend." +} +``` + +**Fundamentals Analyst**: +```json +{ + "signal": "bearish", + "confidence": 0.8, + "reasoning": "Cambricon-U's financial condition and business performance show clear negative trends. First, the company's net profit continues to be negative, and the loss amount has increased in recent quarters, indicating serious problems with profitability. Second, although the company's operating revenue increased in the fourth quarter of 2023, it experienced a significant decline in subsequent quarters, which may indicate a decrease in market demand or company market share. Additionally, the company's P/E ratio is negative, and its P/B ratio is as high as 52.58, indicating overvaluation in the market with significant bubble risk. From a financial health perspective, although the company has a low debt-to-asset ratio, its operating cash flow continues to be negative, indicating the company faces significant financial pressure in operations. Considering these factors, we believe Cambricon-U's stock is currently bearish." +} +``` + +**Sentiment Analyst**: +```json +{ + "signal": "bullish", + "confidence": 0.8, + "reasoning": "From the news data, the overall market sentiment is positive, with positive news far outnumbering negative news. Although one news item mentions the decline of chip ETFs and Cambricon-U, most news emphasizes the positive performance of Cambricon-U and its sector, including stock price increases and technological breakthroughs. Additionally, the news of Cambricon-U being included in the FTSE China A50 Index indicates the market's optimistic outlook for its long-term prospects. Social media discussions also mainly focus on the company's positive developments, further supporting the bullish view.", + "key_events": [ + "Cambricon-U included in FTSE China A50 Index", + "Cambricon-U stock price rises multiple times", + "AI AIETF holding stock Cambricon-U surges", + "ZhiYuan QiYuan large model released" + ] +} +``` + +**Valuation Analyst**: +```json +{ + "signal": "bearish", + "confidence": 0.85, + "reasoning": "Cambricon-U's financial condition and business performance show clear negative trends. First, the company's net profit continues to be negative, and the loss amount has increased in recent quarters, indicating serious problems with profitability. Second, although the company's operating revenue increased in the fourth quarter of 2023, it experienced a significant decline in subsequent quarters, which may indicate a decrease in market demand or company market share. Additionally, the company's P/E ratio is negative, and its P/B ratio is as high as 52.58, indicating overvaluation in the market with significant bubble risk. From a financial health perspective, although the company has a low debt-to-asset ratio, its operating cash flow continues to be negative, indicating the company faces significant financial pressure in operations. Considering these factors, we believe Cambricon-U's stock is currently bearish. Furthermore, calculated through DCF model and relative valuation methods, the company's intrinsic value is significantly lower than the current market price, further supporting the bearish view.", + "fair_value": 200.0, + "key_metrics": [ + "P/E Ratio", + "P/B Ratio", + "Operating Revenue", + "Net Profit", + "Operating Cash Flow" + ] +} +``` + +**Researcher Bull**: +```json +{ + "key_points": [ + "Market sentiment is positive, news and social media discussions lean optimistic", + "Cambricon-U included in FTSE China A50 Index, indicating market's optimistic outlook for its long-term prospects", + "Positive news of technological breakthroughs and stock price increases", + "Despite weakness in technical and fundamental aspects, market sentiment and potential catalysts may drive stock price rebound" + ], + "confidence": 0.6, + "reasoning": "Despite technical and fundamental analysis showing Cambricon-U is currently in a weak state, market sentiment and potential catalysts may drive stock price rebound. Cambricon-U being included in the FTSE China A50 Index indicates the market's optimistic outlook for its long-term prospects. Additionally, recent positive news about technological breakthroughs and stock price increases also support the bullish view. Although current financial conditions and valuations are high, market sentiment and potential positive factors may provide support for the stock price. Therefore, we recommend maintaining caution in the short term, but in the long term, Cambricon-U still has investment potential." +} +``` + +**Researcher Bear**: +```json +{ + "key_points": [ + "Technical indicators show short-term trend weakening", + "Net profit continues to be negative and losses increase", + "Declining operating revenue indicates decreased market demand", + "P/B ratio as high as 52.58, overvalued", + "Operating cash flow continues to be negative, significant financial pressure", + "DCF model and relative valuation methods show intrinsic value lower than market price" + ], + "confidence": 0.8, + "reasoning": "Combining technical analysis, fundamental analysis, market sentiment analysis, and valuation analysis, Cambricon-U's stock is currently bearish. Technical indicators show short-term trend weakening, fundamental analysis shows poor profitability, decreased market demand, overvaluation, and significant financial pressure. Although market sentiment leans positive, these positive sentiments mainly focus on short-term events and cannot mask long-term fundamental issues. Valuation analysis further supports the bearish view, with intrinsic value significantly lower than current market price. Therefore, selling Cambricon-U stock is recommended." +} +``` + +**Debate Room**: +```json +{ + "signal": "neutral", + "confidence": 0.7, + "reasoning": "Both bull and bear researchers' views have rationality and basis. The bull researcher emphasizes positive market sentiment, Cambricon-U's inclusion in the FTSE China A50 Index, and positive news about technological breakthroughs and stock price increases, which may provide support for the stock price in the short term. However, the bear researcher points out technical indicators show short-term trend weakening, fundamental analysis shows poor profitability, decreased market demand, overvaluation, and significant financial pressure. Although market sentiment leans positive, these positive sentiments mainly focus on short-term events and cannot mask long-term fundamental issues. Overall, in the short term, market sentiment and technological breakthroughs may drive stock price rebound, but long-term fundamental issues still exist, so maintaining a neutral attitude and waiting for more clear signals is recommended.", + "final_verdict": "Investors are advised to remain cautious in the short term and pay attention to the continuity of market sentiment and technological breakthroughs. In the long term, close attention should be paid to the company's fundamental improvements, especially in areas such as profitability, market demand, and financial pressure. Before more clear signals emerge, maintaining a neutral attitude and avoiding excessive optimism or pessimism is recommended." +} +``` + +**Risk Manager**: +```json +{ + "max_position_size": 0.2, + "volatility": 0.15, + "risk_score": 0.7, + "max_drawdown": 0.25, + "suggested_position_size": 0.15, + "reasoning": "Given stock 688256's high volatility and fundamental risks, a cautious approach is recommended. Although there is potential for technical breakthrough in the short term, the company faces issues such as poor profitability in the long term. Therefore, it is recommended to control position ratio not exceeding 20% and set appropriate stop-loss points to protect capital. At the same time, diversify investments to spread risk." +} +``` + +**Portfolio Manager (Final Decision)**: +```json +{ + "action": "hold", + "quantity": 0, + "confidence": 0.8, + "reasoning": "Considering various analysts' trading signals, debate results, risk analysis, current portfolio status, and latest market price, we decide to adopt a holding strategy for stock 688256. Technical analysis and fundamental analysis both show the stock is in a weak state, with technical indicators showing short-term trend weakening, and fundamentals showing poor profitability, decreased market demand, overvaluation, and significant financial pressure. Although sentiment analysis shows positive market sentiment, these positive sentiments mainly focus on short-term events and cannot mask long-term fundamental issues. The debate result also recommends caution in the short term, paying attention to the continuity of market sentiment and technological breakthroughs. In the long term, close attention should be paid to the company's fundamental improvements. Risk analysis recommends controlling position ratio not exceeding 20% and setting appropriate stop-loss points to protect capital. Therefore, we decide to temporarily hold the stock and wait for more clear signals." +} +``` + +#### Comprehensive Conclusion + +**Final Investment Decision**: Hold (Confidence: 0.8) + +The analysis results from multiple professional agents indicate that Cambricon-U (688256) stock is currently in a weak state in terms of technicals and fundamentals, but market sentiment and short-term events may provide support for the stock price. Considering all factors, the system recommends adopting a holding strategy for this stock and closely monitoring subsequent changes in fundamentals and market sentiment. At the same time, risk management recommends controlling the maximum position ratio of this stock to within 20% of the portfolio and setting appropriate stop-loss points to control risk. + +## 🔄 Data Process Description + +The system's data process follows these steps: + +1. **Data Collection**: Market data agent collects A-share market data and news through akshare API +2. **Multi-dimensional Analysis**: Four professional analysis agents (technical, fundamental, sentiment, valuation) conduct analysis independently +3. **Bull/Bear Research**: Bull and bear researchers provide analysis reports from bullish and bearish perspectives respectively +4. **Debate Integration**: Debate room integrates bull and bear perspectives to form a final analytical opinion +5. **Risk Assessment**: Risk manager assesses investment risk and sets trading restrictions +6. **Final Decision**: Portfolio manager formulates the final trading decision and generates orders + + +## 📋 Project Structure + +``` +community_usecase/a_share_investment_agent_camel/ +├── src/ # Source code directory +│ ├── agents/ # Agent implementations +│ │ ├── base_agent.py # Base agent class +│ │ ├── market_data_agent.py # Market data agent +│ │ ├── technical_analyst.py # Technical analyst agent +│ │ └── ... +│ ├── tools/ # Tool modules +│ │ └── data_helper.py # Data helper tools +│ ├── utils/ # Utility tools +│ │ └── logging_utils.py # Logging tools +│ ├── models.py # Data model definitions +│ ├── roles.py # Role definitions +│ └── main.py # Main program +├── tests/ # Test directory +├── logs/ # Log files +├── .env # Environment variables +├── pyproject.toml # Poetry configuration +├── requirements.txt # Dependencies +└── README.md # Documentation +``` + +## 🔍 Code Implementation Highlights + +### 1. Multi-agent Collaboration Architecture + +This project implements 10 professional role agents, with each agent responsible for specific tasks in the investment analysis process. Agents collaborate through message passing, forming a complete analysis decision chain. + +```python +# Create agents +market_data_agent = MarketDataAgent(show_reasoning=show_reasoning, model_name=model_name) +technical_analyst = TechnicalAnalystAgent(show_reasoning=show_reasoning, model_name=model_name) +fundamentals_analyst = FundamentalsAnalystAgent(show_reasoning=show_reasoning, model_name=model_name) +sentiment_analyst = SentimentAnalystAgent(show_reasoning=show_reasoning, model_name=model_name) +valuation_analyst = ValuationAnalystAgent(show_reasoning=show_reasoning, model_name=model_name) +researcher_bull = ResearcherBullAgent(show_reasoning=show_reasoning, model_name=model_name) +researcher_bear = ResearcherBearAgent(show_reasoning=show_reasoning, model_name=model_name) +debate_room = DebateRoomAgent(show_reasoning=show_reasoning, model_name=model_name) +risk_manager = RiskManagerAgent(show_reasoning=show_reasoning, model_name=model_name) +portfolio_manager = PortfolioManagerAgent(show_reasoning=show_reasoning, model_name=model_name) +``` + +### 2. Flexible Multi-model Support + +The system supports multiple large language models including Gemini, OpenAI, and Qwen, implementing flexible switching through a unified interface. + +```python +def get_llm_client(model_name: str): + """Get the specified LLM client""" + if model_name.lower() == 'gemini': + return GeminiClient() + elif model_name.lower() == 'openai': + return OpenAIClient() + elif model_name.lower() == 'qwen': + return QwenClient() + else: + raise ValueError(f"Unsupported model: {model_name}") +``` + + + +## 📝 Improvements and Innovations + +Compared to the original version, the system restructured based on the Camel framework has the following innovations and improvements: + +1. **Modular Design**: Clearer agent definitions and system structure, facilitating extension and maintenance +2. **Multi-model Support**: Flexible support for multiple LLM models, improving system adaptability +3. **Comprehensive Logging System**: Detailed recording of each agent's work process, facilitating debugging and analysis + + +## ⚠️ Disclaimer + +This project is for **educational and research purposes only**. + +- Not suitable for actual trading or investment +- No guarantees provided +- Past performance does not represent future performance +- Creators bear no responsibility for any financial losses +- Consult professional financial advisors for investment decisions + +By using this software, you agree to use it for learning purposes only. + +## 📚 Related Resources + +- [OWL Framework Official Documentation](https://github.com/camel-ai/owl) +- [Camel Framework Official Documentation](https://github.com/camel-ai/camel) +- [A_Share_investment_Agent](https://github.com/24mlight/A_Share_investment_Agent) + +## 🙏 Acknowledgements + +This project is improved based on the following open-source projects: + +1. [A_Share_investment_Agent](https://github.com/24mlight/A_Share_investment_Agent) - Original A-share investment agent project, providing the foundation architecture and investment analysis ideas for this project. Special thanks for its innovative design in A-share data processing and analysis strategies. +2. [ai-hedge-fund](https://github.com/virattt/ai-hedge-fund.git) - Original US stock investment agent project +3. [Camel Framework](https://github.com/camel-ai/camel) - Multi-agent dialogue framework +4. [OWL Framework](https://github.com/camel-ai/owl) - Open-source multi-agent collaboration framework + +Thanks to all the original authors for their contributions and inspiration, providing a solid foundation for this project. \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/requirements.txt b/community_usecase/a_share_investment_agent_camel/requirements.txt new file mode 100644 index 0000000..6c4e3f4 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/requirements.txt @@ -0,0 +1,25 @@ +camel-ai[all]==0.2.36 +pandas==2.0.3 +numpy==1.24.4 +python-dotenv==1.0.0 +akshare==1.11.57 +matplotlib==3.7.3 +pydantic==1.10.12 +tqdm==4.66.1 +plotly==5.14.1 +requests==2.31.0 +beautifulsoup4==4.12.2 +lxml==4.9.3 +scikit-learn==1.2.2 +statsmodels==0.14.0 +pyecharts==2.0.4 +jieba==0.42.1 +ta-lib==0.4.28 +pytest==7.4.3 +black==23.11.0 +isort==5.12.0 +mypy==1.7.1 +google-generativeai==0.3.1 +openai==1.6.1 +loguru==0.7.2 +pytest-cov==4.1.0 \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/screenshots/system_architecture.png b/community_usecase/a_share_investment_agent_camel/screenshots/system_architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..c327b2fa3d5d4d3b22cd687c5f2b087a60b822b1 GIT binary patch literal 223661 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

    6NW?( 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 I?QKW?3o*1oW&i*H literal 0 HcmV?d00001 diff --git a/community_usecase/a_share_investment_agent_camel/src/__init__.py b/community_usecase/a_share_investment_agent_camel/src/__init__.py new file mode 100644 index 0000000..dd6b836 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/__init__.py @@ -0,0 +1,5 @@ +""" +基于Camel框架的A股投资代理系统 +""" + +__version__ = "0.1.0" \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/agents/__init__.py b/community_usecase/a_share_investment_agent_camel/src/agents/__init__.py new file mode 100644 index 0000000..634f890 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/agents/__init__.py @@ -0,0 +1,31 @@ +""" +代理模块初始化 +""" + +from src.agents.base_agent import BaseAgent +from src.agents.market_data_agent import MarketDataAgent +from src.agents.technical_analyst import TechnicalAnalystAgent +from src.agents.fundamentals_analyst import FundamentalsAnalystAgent +from src.agents.sentiment_analyst import SentimentAnalystAgent +from src.agents.valuation_analyst import ValuationAnalystAgent +from src.agents.researcher_bull import ResearcherBullAgent +from src.agents.researcher_bear import ResearcherBearAgent +from src.agents.debate_room import DebateRoomAgent +from src.agents.risk_manager import RiskManagerAgent +from src.agents.portfolio_manager import PortfolioManagerAgent +from src.agents.investment_agent import InvestmentAgent + +__all__ = [ + 'BaseAgent', + 'MarketDataAgent', + 'TechnicalAnalystAgent', + 'FundamentalsAnalystAgent', + 'SentimentAnalystAgent', + 'ValuationAnalystAgent', + 'ResearcherBullAgent', + 'ResearcherBearAgent', + 'DebateRoomAgent', + 'RiskManagerAgent', + 'PortfolioManagerAgent', + 'InvestmentAgent' +] \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/agents/base_agent.py b/community_usecase/a_share_investment_agent_camel/src/agents/base_agent.py new file mode 100644 index 0000000..df656ab --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/agents/base_agent.py @@ -0,0 +1,143 @@ +""" +代理基类模块 + +定义所有代理的共同基类和接口 +""" +from typing import Dict, Any, Optional, List +from abc import ABC, abstractmethod +import json +import logging +import re +from camel.agents import ChatAgent +from camel.messages import BaseMessage + +# 配置日志 +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler("logs/agents.log"), + logging.StreamHandler() + ] +) + + +class BaseAgent(ABC): + """代理基类""" + + def __init__(self, role_agent: ChatAgent, show_reasoning: bool = False, model_name: str = "gemini"): + """初始化代理 + + Args: + role_agent: Camel框架的聊天代理 + show_reasoning: 是否显示推理过程 + model_name: 使用的模型名称 (gemini, openai, qwen) + """ + self.agent = role_agent + self.show_reasoning = show_reasoning + self.model_name = model_name + self.logger = logging.getLogger(self.__class__.__name__) + + def log_message(self, message: BaseMessage) -> None: + """记录消息 + + Args: + message: 要记录的消息 + """ + if self.show_reasoning: + print(f"\n{'='*80}") + print(f"【{self.__class__.__name__}】推理过程:") + print(f"{'-'*80}") + print(message.content) + print(f"{'='*80}\n") + + # 记录到日志 + self.logger.info(f"推理过程: {message.content[:100]}...") + + def format_data(self, data: Dict[str, Any]) -> str: + """格式化数据为字符串 + + Args: + data: 要格式化的数据 + + Returns: + str: 格式化后的字符串 + """ + return json.dumps(data, ensure_ascii=False, indent=2) + + def parse_json_response(self, response: str) -> Dict[str, Any]: + """从响应中解析JSON + + 尝试从响应文本中提取JSON数据,处理各种边缘情况 + + Args: + response: 响应文本 + + Returns: + Dict[str, Any]: 解析后的JSON数据 + """ + try: + # 尝试直接解析 + return json.loads(response) + except json.JSONDecodeError: + pass + + # 尝试从Markdown代码块中提取JSON + json_pattern = r"```(?:json)?\s*([\s\S]*?)\s*```" + matches = re.findall(json_pattern, response) + + if matches: + for match in matches: + try: + return json.loads(match) + except json.JSONDecodeError: + continue + + # 尝试找到大括号包裹的内容 + brace_pattern = r"\{[\s\S]*\}" + matches = re.findall(brace_pattern, response) + + if matches: + for match in matches: + try: + return json.loads(match) + except json.JSONDecodeError: + continue + + # 如果所有尝试都失败,返回空字典并记录错误 + self.logger.error(f"无法从响应中解析JSON: {response}") + return {} + + @abstractmethod + def process(self, data: Dict[str, Any]) -> Dict[str, Any]: + """处理输入数据并返回结果 + + Args: + data: 输入数据 + + Returns: + Dict[str, Any]: 处理结果 + """ + pass + + def generate_human_message(self, content: str) -> BaseMessage: + """生成人类消息 + + Args: + content: 消息内容 + + Returns: + BaseMessage: 用户消息对象 + """ + return BaseMessage.make_user_message(role_name="user", content=content) + + def generate_ai_message(self, content: str) -> BaseMessage: + """生成AI消息 + + Args: + content: 消息内容 + + Returns: + BaseMessage: 助手消息对象 + """ + return BaseMessage.make_assistant_message(role_name="assistant", content=content) \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/agents/debate_room.py b/community_usecase/a_share_investment_agent_camel/src/agents/debate_room.py new file mode 100644 index 0000000..1e0aea7 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/agents/debate_room.py @@ -0,0 +1,198 @@ +""" +辩论室代理实现 +""" +import logging +from typing import Dict, Any, List, Optional +import pandas as pd + +from src.agents.base_agent import BaseAgent +from src.roles import create_role_agent +from src.models import AnalysisSignal, StockData, ResearchReport + +from camel.messages import BaseMessage + + +class DebateRoomAgent(BaseAgent): + """辩论室代理类""" + + def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"): + """初始化辩论室代理 + + Args: + show_reasoning: 是否显示推理过程 + model_name: 使用的模型名称 (gemini, openai, qwen) + """ + role_agent = create_role_agent("debate_room", model_name) + super().__init__(role_agent, show_reasoning, model_name) + self.logger = logging.getLogger("DebateRoomAgent") + + def process(self, data: Dict[str, Any]) -> Dict[str, Any]: + """处理多空观点辩论 + + Args: + data: 包含以下键的字典: + - stock_data: 股票数据对象 + - bull_research: 多头研究报告 + - bear_research: 空头研究报告 + - messages: 处理过程中的消息 + + Returns: + Dict[str, Any]: 处理后的数据,包含以下内容: + - debate_result: 辩论结果信号 + - messages: 处理过程中的消息 + """ + # 提取股票数据和研究报告 + stock_data = data.get("stock_data") + bull_research = data.get("bull_research") + bear_research = data.get("bear_research") + + if not stock_data: + raise ValueError("缺少股票数据") + + if not bull_research or not bear_research: + raise ValueError("缺少多头或空头研究报告") + + self.logger.info(f"正在召开辩论会议") + + try: + # 提取股票基本信息 + ticker = stock_data.ticker + + # 组织研究报告数据 + debate_data = { + "ticker": ticker, + "bull_research": bull_research.dict(), + "bear_research": bear_research.dict(), + } + + # 使用代理处理数据分析请求 + prompt = f"""请作为辩论室主持人,整合多头和空头研究员对股票 {ticker} 的观点,形成一个平衡的投资视角。 + 任务要求: + 1. 公正评估多头和空头论据的优点和弱点 + 2. 识别最具说服力的论点 + 3. 权衡不同因素的重要性 + 4. 形成综合性的市场观点 + 5. 提出平衡的投资建议 + + 请给出明确的交易信号、置信度和详细理由。 + 返回格式为JSON: + {{ + "signal": "bullish/bearish/neutral", + "confidence": 0.7, + "reasoning": "辩论总结和推理...", + "bull_key_strengths": ["优势1", "优势2"], + "bull_key_weaknesses": ["弱点1", "弱点2"], + "bear_key_strengths": ["优势1", "优势2"], + "bear_key_weaknesses": ["弱点1", "弱点2"], + "final_verdict": "最终投资建议..." + }} + """ + + analysis_result = self._process_data_with_agent(prompt, debate_data) + + # 创建辩论结果信号 + debate_result = self._create_debate_signal(analysis_result, ticker) + + # 返回处理结果 + return { + "debate_result": debate_result, + "messages": [] + } + + except Exception as e: + self.logger.error(f"辩论过程中发生错误: {str(e)}") + + # 返回默认辩论结果 + default_signal = AnalysisSignal( + agent="辩论室", + signal="neutral", + confidence=0.5, + reasoning="辩论过程中发生错误,返回中性信号" + ) + + return { + "debate_result": default_signal, + "messages": [] + } + + def _create_debate_signal(self, analysis_result: Dict[str, Any], ticker: str) -> AnalysisSignal: + """创建辩论结果信号 + + Args: + analysis_result: 分析结果 + ticker: 股票代码 + + Returns: + AnalysisSignal: 分析信号 + """ + signal = analysis_result.get("signal", "neutral") + confidence = analysis_result.get("confidence", 0.5) + reasoning = analysis_result.get("reasoning", "未提供分析理由") + final_verdict = analysis_result.get("final_verdict", "") + + # 整合多空优缺点 + bull_strengths = analysis_result.get("bull_key_strengths", []) + bull_weaknesses = analysis_result.get("bull_key_weaknesses", []) + bear_strengths = analysis_result.get("bear_key_strengths", []) + bear_weaknesses = analysis_result.get("bear_key_weaknesses", []) + + details = { + "ticker": ticker, + "bull_strengths": bull_strengths, + "bull_weaknesses": bull_weaknesses, + "bear_strengths": bear_strengths, + "bear_weaknesses": bear_weaknesses, + "final_verdict": final_verdict + } + + return AnalysisSignal( + agent="辩论室", + signal=signal, + confidence=confidence, + reasoning=reasoning, + details=details + ) + + def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]: + """使用代理处理数据分析请求 + + Args: + prompt: 分析提示 + data: 包含多空研究报告的数据 + + Returns: + Dict[str, Any]: 分析结果 + """ + # 格式化数据 + data_str = self.format_data(data) + + # 创建完整提示 + full_prompt = f"""{prompt} + +数据: +{data_str} + +请以JSON格式返回结果。 +""" + # 发送到Camel代理进行分析 + human_message = self.generate_human_message(content=full_prompt) + response = self.agent.step(human_message) + self.log_message(response.msgs[0]) + + # 解析结果 + result = self.parse_json_response(response.msgs[0].content) + + # 如果解析结果为空,使用默认值 + if not result: + result = { + "signal": "neutral", + "confidence": 0.5, + "reasoning": "无法解析辩论结果", + "bull_key_strengths": [], + "bull_key_weaknesses": [], + "bear_key_strengths": [], + "bear_key_weaknesses": [], + "final_verdict": "由于分析错误,无法给出最终判断" + } + + return result \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/agents/fundamentals_analyst.py b/community_usecase/a_share_investment_agent_camel/src/agents/fundamentals_analyst.py new file mode 100644 index 0000000..4a25b71 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/agents/fundamentals_analyst.py @@ -0,0 +1,169 @@ +""" +基本面分析代理实现 +""" +import logging +from typing import Dict, Any, List, Optional +import pandas as pd + +from src.agents.base_agent import BaseAgent +from src.roles import create_role_agent +from src.models import AnalysisSignal, StockData + +from camel.messages import BaseMessage + + +class FundamentalsAnalystAgent(BaseAgent): + """基本面分析代理类""" + + def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"): + """初始化基本面分析代理 + + Args: + show_reasoning: 是否显示推理过程 + model_name: 使用的模型名称 (gemini, openai, qwen) + """ + role_agent = create_role_agent("fundamentals_analyst", model_name) + super().__init__(role_agent, show_reasoning, model_name) + self.logger = logging.getLogger("FundamentalsAnalystAgent") + + def process(self, data: Dict[str, Any]) -> Dict[str, Any]: + """处理基本面分析 + + Args: + data: 包含以下键的字典: + - stock_data: 股票数据对象 + - messages: 处理过程中的消息 + + Returns: + Dict[str, Any]: 处理后的数据,包含以下内容: + - fundamentals_analysis: 基本面分析信号 + - messages: 处理过程中的消息 + """ + # 提取股票数据 + stock_data = data.get("stock_data") + if not stock_data: + raise ValueError("缺少股票数据") + + self.logger.info(f"正在进行基本面分析") + + try: + # 提取基本面数据和历史数据 + fundamental_data = stock_data.fundamental_data + historical_data = stock_data.historical_data + + # 使用代理处理数据分析请求 + prompt = f"""请对以下股票的基本面数据进行分析,给出明确的交易信号(bullish/bearish/neutral)。 + 分析以下方面: + 1. 财务指标评估(净利润、毛利率、ROE等) + 2. 收入和盈利增长趋势 + 3. 估值水平(市盈率、市净率等) + 4. 财务健康状况(资产负债率、流动性等) + 5. 行业地位与竞争优势 + + 请给出明确的交易信号、置信度(0-1)和详细理由。 + 返回格式为JSON: + {{ + "signal": "bullish/bearish/neutral", + "confidence": 0.7, + "reasoning": "分析理由...", + "key_financials": ["指标1", "指标2"] + }} + """ + + analysis_result = self._process_data_with_agent(prompt, { + "fundamental_data": fundamental_data, + "historical_data": historical_data + }) + + # 创建基本面分析信号 + fundamentals_analysis = self._create_fundamentals_signal(analysis_result, stock_data) + + # 返回处理结果 + return { + "fundamentals_analysis": fundamentals_analysis, + "messages": [] + } + + except Exception as e: + self.logger.error(f"基本面分析过程中发生错误: {str(e)}") + + # 返回默认分析结果 + default_signal = AnalysisSignal( + agent="基本面分析师", + signal="neutral", + confidence=0.5, + reasoning="分析过程中发生错误,返回中性信号" + ) + + return { + "fundamentals_analysis": default_signal, + "messages": [] + } + + def _create_fundamentals_signal(self, analysis_result: Dict[str, Any], stock_data: StockData) -> AnalysisSignal: + """创建基本面分析信号 + + Args: + analysis_result: 分析结果 + stock_data: 股票数据 + + Returns: + AnalysisSignal: 分析信号 + """ + signal = analysis_result.get("signal", "neutral") + confidence = analysis_result.get("confidence", 0.5) + reasoning = analysis_result.get("reasoning", "未提供分析理由") + key_financials = analysis_result.get("key_financials", []) + + details = { + "ticker": stock_data.ticker, + "key_financials": key_financials, + } + + return AnalysisSignal( + agent="基本面分析师", + signal=signal, + confidence=confidence, + reasoning=reasoning, + details=details + ) + + def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]: + """使用代理处理数据分析请求 + + Args: + prompt: 分析提示 + data: 包含基本面数据和历史数据的数据 + + Returns: + Dict[str, Any]: 分析结果 + """ + # 格式化数据 + data_str = self.format_data(data) + + # 创建完整提示 + full_prompt = f"""{prompt} + +数据: +{data_str} + +请以JSON格式返回结果。 +""" + # 发送到Camel代理进行分析 + human_message = self.generate_human_message(content=full_prompt) + response = self.agent.step(human_message) + self.log_message(response.msgs[0]) + + # 解析结果 + result = self.parse_json_response(response.msgs[0].content) + + # 如果解析结果为空,使用默认值 + if not result: + result = { + "signal": "neutral", + "confidence": 0.5, + "reasoning": "无法解析分析结果", + "key_financials": [] + } + + return result \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/agents/investment_agent.py b/community_usecase/a_share_investment_agent_camel/src/agents/investment_agent.py new file mode 100644 index 0000000..9ebedc7 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/agents/investment_agent.py @@ -0,0 +1,351 @@ +""" +投资分析代理实现 +""" +import logging +from typing import Dict, Any, List, Optional +import json +import re + +from src.agents.base_agent import BaseAgent +from src.roles import create_role_agent +from src.models import StockData + +from camel.messages import BaseMessage + + +class InvestmentAgent(BaseAgent): + """投资分析代理类""" + + def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"): + """初始化投资分析代理 + + Args: + show_reasoning: 是否显示推理过程 + model_name: 使用的模型名称 (gemini, openai, qwen) + """ + role_agent = create_role_agent("investment_analyst", model_name) + super().__init__(role_agent, show_reasoning, model_name) + self.logger = logging.getLogger("InvestmentAgent") + + def process(self, data: Dict[str, Any]) -> Dict[str, Any]: + """处理投资分析请求 + + Args: + data: 输入数据,包含股票信息 + + Returns: + Dict[str, Any]: 投资分析结果 + """ + try: + # 获取输入数据 + stock_data = data.get("stock_data") + if not stock_data: + raise ValueError("缺少股票数据") + + ticker = stock_data.ticker + + # 获取财务数据 + fundamental_data = stock_data.fundamental_data + + # 获取历史数据 + historical_data = fundamental_data.get("historical_data", []) + financial_trends = fundamental_data.get("trends", {}) + + # 创建分析提示 + prompt = f"""你是一位专业的投资分析师,请根据以下数据对股票 {ticker} 进行全面的投资分析和推荐: + + 1. 基本面分析: + - 财务状况:分析公司收入、利润、现金流和资产负债情况 + - 增长趋势:评估收入增长、利润增长和ROE变化趋势 + - 估值指标:分析市盈率、市净率等估值指标的合理性 + + 2. 技术面分析: + - 价格趋势:分析股票价格走势 + - 成交量:评估交易量变化 + - 技术指标:解读主要技术指标 + + 3. 投资建议: + - 给出明确的投资建议(买入/持有/卖出) + - 说明理由和风险因素 + - 给出合理的目标价格区间 + + 4. 风险分析: + - 列出主要风险因素 + - 给出风险应对建议 + + 请基于数据给出客观、全面的分析,避免过度乐观或悲观的观点。分析应当深入浅出,使投资者容易理解。 + """ + + # 构建分析输入 + analysis_input = { + "ticker": ticker, + "financial_data": fundamental_data, + "historical_data": historical_data, + "financial_trends": financial_trends, + "technical_indicators": stock_data.technical_indicators, + "historical_prices": stock_data.historical_data, + "news": stock_data.news_data + } + + # 使用代理进行分析 + analysis_result = self._process_data_with_agent(prompt, analysis_input) + + # 提取投资建议 + recommendation = self._extract_recommendation(analysis_result) + + # 返回结果 + return { + "analysis": analysis_result, + "recommendation": recommendation, + "messages": [] # 暂时返回空消息列表 + } + + except Exception as e: + self.logger.error(f"进行投资分析时发生错误: {str(e)}") + raise e + + def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> str: + """使用代理处理数据分析请求 + + Args: + prompt: 分析提示 + data: 包含基本面数据和历史数据的数据 + + Returns: + str: 分析结果文本 + """ + # 格式化数据 + data_str = self.format_data(data) + + # 创建完整提示 + full_prompt = f"""{prompt} + +数据: +{data_str} + +请提供详细的分析报告。 +""" + # 发送到Camel代理进行分析 + human_message = self.generate_human_message(content=full_prompt) + response = self.agent.step(human_message) + self.log_message(response.msgs[0]) + + # 返回结果文本 + return response.msgs[0].content + + def generate_human_message(self, content: str) -> BaseMessage: + """生成人类消息 + + Args: + content: 消息内容 + + Returns: + BaseMessage: 人类消息对象 + """ + return BaseMessage(role_name="Human", role_type="user", meta_info={}, content=content) + + def parse_json_response(self, response: str) -> Dict[str, Any]: + """解析JSON响应 + + Args: + response: 响应文本 + + Returns: + Dict[str, Any]: 解析后的JSON数据 + """ + try: + # 尝试提取JSON部分 + json_match = re.search(r'```json\s*([\s\S]*?)\s*```', response) + if json_match: + json_str = json_match.group(1) + else: + # 尝试找到可能的JSON对象 + json_match = re.search(r'\{[\s\S]*\}', response) + if json_match: + json_str = json_match.group(0) + else: + return {} + + # 解析JSON + return json.loads(json_str) + except Exception as e: + self.logger.error(f"解析JSON响应时发生错误: {str(e)}") + return {} + + def _extract_recommendation(self, analysis: str) -> Dict[str, Any]: + """从分析结果中提取投资建议 + + Args: + analysis: 分析文本 + + Returns: + Dict[str, Any]: 投资建议 + """ + try: + # 使用代理提取关键信息 + prompt = """从以下投资分析报告中提取关键信息,包括: + 1. 投资评级(买入/持有/卖出) + 2. 目标价格区间(如果有) + 3. 主要投资理由(不超过3点) + 4. 主要风险因素(不超过3点) + + 以JSON格式返回结果,包含以下字段: + - rating: 投资评级 + - target_price_low: 目标价格下限(如果有) + - target_price_high: 目标价格上限(如果有) + - reasons: 投资理由列表 + - risks: 风险因素列表 + + 分析报告: + """ + + extraction_result = self._process_data_with_agent(prompt + analysis, {}) + + # 尝试解析JSON结果 + try: + import json + json_match = re.search(r'\{[\s\S]*\}', extraction_result) + if json_match: + json_str = json_match.group(0) + recommendation = json.loads(json_str) + return recommendation + else: + return { + "rating": self._determine_rating(analysis), + "target_price_low": 0, + "target_price_high": 0, + "reasons": [], + "risks": [] + } + except: + # 如果解析失败,返回简化结果 + return { + "rating": self._determine_rating(analysis), + "target_price_low": 0, + "target_price_high": 0, + "reasons": [], + "risks": [] + } + + except Exception as e: + self.logger.error(f"提取投资建议时发生错误: {str(e)}") + return { + "rating": "未知", + "target_price_low": 0, + "target_price_high": 0, + "reasons": [], + "risks": [] + } + + def _determine_rating(self, analysis: str) -> str: + """从分析文本中确定投资评级 + + Args: + analysis: 分析文本 + + Returns: + str: 投资评级 + """ + analysis_lower = analysis.lower() + + if "买入" in analysis or "强烈推荐" in analysis or "buy" in analysis_lower: + return "买入" + elif "卖出" in analysis or "sell" in analysis_lower: + return "卖出" + elif "持有" in analysis or "观望" in analysis or "hold" in analysis_lower: + return "持有" + else: + return "未知" + + def _analyze_financial_trends(self, historical_data: List[Dict[str, Any]], trends: Dict[str, Any]) -> str: + """分析财务趋势 + + Args: + historical_data: 历史财务数据 + trends: 财务趋势数据 + + Returns: + str: 财务趋势分析 + """ + if not historical_data or len(historical_data) < 2: + return "财务数据不足以进行趋势分析。" + + analysis = [] + + # 收入趋势分析 + if "revenue" in trends: + revenue_trend = trends["revenue"] + revenue_values = revenue_trend.get("values", []) + revenue_growth = revenue_trend.get("growth", 0) + revenue_trend_dir = revenue_trend.get("trend", "未知") + + if revenue_values: + if revenue_growth > 20: + analysis.append(f"公司收入呈高速{revenue_trend_dir}趋势,同比增长{revenue_growth:.2f}%,显示出强劲的业务增长。") + elif revenue_growth > 5: + analysis.append(f"公司收入呈稳健{revenue_trend_dir}趋势,同比增长{revenue_growth:.2f}%,业务发展稳定。") + elif revenue_growth >= 0: + analysis.append(f"公司收入略有{revenue_trend_dir},同比增长{revenue_growth:.2f}%,业务增长放缓。") + else: + analysis.append(f"公司收入呈{revenue_trend_dir}趋势,同比下降{abs(revenue_growth):.2f}%,业务面临挑战。") + + # 净利润趋势分析 + if "net_income" in trends: + income_trend = trends["net_income"] + income_values = income_trend.get("values", []) + income_growth = income_trend.get("growth", 0) + income_trend_dir = income_trend.get("trend", "未知") + + if income_values: + if income_growth > 20: + analysis.append(f"公司净利润呈高速{income_trend_dir}趋势,同比增长{income_growth:.2f}%,盈利能力强劲。") + elif income_growth > 5: + analysis.append(f"公司净利润呈稳健{income_trend_dir}趋势,同比增长{income_growth:.2f}%,盈利能力稳定。") + elif income_growth >= 0: + analysis.append(f"公司净利润略有{income_trend_dir},同比增长{income_growth:.2f}%,盈利增长放缓。") + else: + analysis.append(f"公司净利润呈{income_trend_dir}趋势,同比下降{abs(income_growth):.2f}%,盈利能力受到挑战。") + + # ROE趋势分析 + if "roe" in trends: + roe_trend = trends["roe"] + roe_values = roe_trend.get("values", []) + roe_trend_dir = roe_trend.get("trend", "未知") + + if roe_values and len(roe_values) > 0: + latest_roe = roe_values[0] + if latest_roe > 15: + analysis.append(f"公司ROE为{latest_roe:.2f}%,处于较高水平,资本回报率优秀。ROE呈{roe_trend_dir}趋势。") + elif latest_roe > 10: + analysis.append(f"公司ROE为{latest_roe:.2f}%,处于良好水平,资本回报率不错。ROE呈{roe_trend_dir}趋势。") + elif latest_roe > 5: + analysis.append(f"公司ROE为{latest_roe:.2f}%,处于一般水平,资本回报率尚可。ROE呈{roe_trend_dir}趋势。") + else: + analysis.append(f"公司ROE为{latest_roe:.2f}%,处于较低水平,资本回报率不佳。ROE呈{roe_trend_dir}趋势。") + + # 季度对比分析 + if len(historical_data) >= 4: + latest_quarter = historical_data[0] + year_ago_quarter = None + + # 寻找去年同期数据(通常是第4个季度报告) + if len(historical_data) >= 4: + year_ago_quarter = historical_data[3] + + if latest_quarter and year_ago_quarter: + latest_revenue = float(latest_quarter["income_statement"]["revenue"]) + latest_net_income = float(latest_quarter["income_statement"]["net_income"]) + + year_ago_revenue = float(year_ago_quarter["income_statement"]["revenue"]) + year_ago_net_income = float(year_ago_quarter["income_statement"]["net_income"]) + + # 计算同比增长率 + if year_ago_revenue > 0: + yoy_revenue_growth = (latest_revenue - year_ago_revenue) / year_ago_revenue * 100 + analysis.append(f"同比而言,该季度营收增长{yoy_revenue_growth:.2f}%。") + + if year_ago_net_income > 0: + yoy_net_income_growth = (latest_net_income - year_ago_net_income) / year_ago_net_income * 100 + analysis.append(f"同比而言,该季度净利润增长{yoy_net_income_growth:.2f}%。") + + return "\n".join(analysis) \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/agents/market_data_agent.py b/community_usecase/a_share_investment_agent_camel/src/agents/market_data_agent.py new file mode 100644 index 0000000..b58ebd5 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/agents/market_data_agent.py @@ -0,0 +1,714 @@ +""" +市场数据分析代理实现 +""" +import os +import logging +from typing import Dict, Any, List, Optional +import pandas as pd +import akshare as ak +from datetime import datetime, timedelta + +from src.agents.base_agent import BaseAgent +from src.roles import create_role_agent +from src.models import StockData + +from camel.messages import BaseMessage, OpenAIUserMessage, OpenAIAssistantMessage + + +class MarketDataAgent(BaseAgent): + """市场数据分析代理类""" + + def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"): + """初始化市场数据分析代理 + + Args: + show_reasoning: 是否显示推理过程 + model_name: 使用的模型名称 (gemini, openai, qwen) + """ + role_agent = create_role_agent("market_data_analyst", model_name) + super().__init__(role_agent, show_reasoning, model_name) + self.logger = logging.getLogger("MarketDataAgent") + + def process(self, data: Dict[str, Any]) -> Dict[str, Any]: + """处理市场数据获取和预处理 + + Args: + data: 包含以下键的字典: + - ticker: 股票代码 + - start_date: 开始日期 + - end_date: 结束日期 + - num_of_news: 新闻数量 + + Returns: + Dict[str, Any]: 处理后的数据,包含以下内容: + - stock_data: 股票数据对象 + - messages: 处理过程中的消息 + """ + # 提取参数 + ticker = data.get("ticker") + start_date = data.get("start_date") + end_date = data.get("end_date") + num_of_news = data.get("num_of_news", 5) + + if not ticker: + raise ValueError("缺少股票代码") + + self.logger.info(f"正在获取 {ticker} 的市场数据") + + try: + # 获取历史价格数据 + historical_data = self._get_historical_data(ticker, start_date, end_date) + + # 计算技术指标 + technical_indicators = self._calculate_technical_indicators(historical_data) + + # 获取财务数据 + fundamental_data = self._get_financial_data(ticker) + + # 获取新闻数据 + news_data = self._get_news_data(ticker, num_of_news) + + # 创建股票数据对象 + stock_data = StockData( + ticker=ticker, + historical_data=historical_data, + technical_indicators=technical_indicators, + fundamental_data=fundamental_data, + news_data=news_data + ) + + # 使用代理处理数据分析请求 + prompt = f"""请分析以下关于股票 {ticker} 的市场数据,识别重要趋势和关键指标表现。 + 提供以下方面的见解: + 1. 价格走势概要 + 2. 成交量分析 + 3. 主要技术指标分析(RSI、MACD、布林带) + 4. 关键支撑和阻力位 + 5. 市场趋势和整体判断""" + + analysis_result = self._process_data_with_agent(prompt, { + "ticker": ticker, + "historical_data": historical_data, + "technical_indicators": technical_indicators + }) + + # 返回结果 + return { + "stock_data": stock_data, + "analysis": analysis_result, + "messages": [] # 暂时返回空消息列表 + } + + except Exception as e: + self.logger.error(f"获取市场数据时发生错误: {str(e)}") + raise e + + def _get_historical_data(self, ticker: str, start_date: str, end_date: str) -> Dict[str, Any]: + """获取历史价格数据 + + Args: + ticker: 股票代码 + start_date: 开始日期 + end_date: 结束日期 + + Returns: + Dict[str, Any]: 历史价格数据 + """ + try: + self.logger.info(f"获取历史价格数据: {ticker} 从 {start_date} 到 {end_date}") + + # 使用真实API获取数据 + from src.tools.api import get_price_data + + # 获取价格数据 + df = get_price_data(ticker, start_date, end_date) + + if df is None or df.empty: + self.logger.warning(f"无法获取{ticker}的价格数据,将使用空数据继续") + return { + "raw": {"dates": [], "prices": [], "volumes": []}, + "summary": { + "ticker": ticker, + "start_date": start_date, + "end_date": end_date, + "latest_price": 0, + "price_change": 0, + "high_price": 0, + "low_price": 0, + "average_volume": 0 + } + } + + # 将DataFrame转换为可处理的字典格式 + dates = df['date'].dt.strftime('%Y-%m-%d').tolist() + prices = df['close'].tolist() + volumes = df['volume'].tolist() + + # 计算汇总数据 + latest_price = prices[-1] if prices else 0 + previous_price = prices[-2] if len(prices) > 1 else latest_price + price_change = ((latest_price - previous_price) / previous_price * 100) if previous_price else 0 + high_price = max(prices) if prices else 0 + low_price = min(prices) if prices else 0 + + # 构建结果 + result = { + "raw": { + "dates": dates, + "prices": prices, + "volumes": volumes + }, + "summary": { + "ticker": ticker, + "start_date": start_date, + "end_date": end_date, + "latest_price": latest_price, + "price_change": round(price_change, 2), + "high_price": high_price, + "low_price": low_price, + "average_volume": sum(volumes) / len(volumes) if volumes else 0 + } + } + + return result + + except Exception as e: + self.logger.error(f"获取历史价格数据时发生错误: {str(e)}") + # 返回最小数据集以避免整个流程中断 + return { + "raw": {"dates": [], "prices": [], "volumes": []}, + "summary": { + "ticker": ticker, + "start_date": start_date, + "end_date": end_date, + "latest_price": 0, + "price_change": 0, + "high_price": 0, + "low_price": 0, + "average_volume": 0, + "error": str(e) + } + } + + def _calculate_technical_indicators(self, historical_data: Dict[str, Any]) -> Dict[str, Any]: + """计算技术指标 + + Args: + historical_data: 历史价格数据 + + Returns: + Dict[str, Any]: 技术指标数据 + """ + try: + from src.tools.data_helper import calculate_technical_indicators + + raw_data = historical_data.get("raw", {}) + dates = raw_data.get("dates", []) + prices = raw_data.get("prices", []) + volumes = raw_data.get("volumes", []) + + if not prices: + return {} + + # 将数据转换为DataFrame格式,以便使用data_helper中的函数 + df = pd.DataFrame({ + "日期": dates if dates else [], + "收盘": prices if prices else [], + "成交量": volumes if volumes else [] + }) + + # 记录使用的列名 + self.logger.info(f"DataFrame列名: {df.columns.tolist()}") + + # 使用data_helper中的函数计算技术指标 + indicators = calculate_technical_indicators(df) + + if indicators and "error" in indicators: + self.logger.error(f"计算技术指标时出错: {indicators['error']}") + return {} + + # 提取最新的技术指标值用于摘要 + latest_indicators = {} + + # 提取SMA (简单移动平均线) + for period in [5, 10, 20, 50, 200]: + key = f"ma_{period}" + if key in indicators and indicators[key]: + latest_indicators[key] = indicators[key][-1] + + # 提取RSI (相对强弱指数) + if "rsi" in indicators and indicators["rsi"]: + latest_indicators["rsi"] = indicators["rsi"][-1] + + # 提取MACD + if all(k in indicators for k in ["macd", "macd_signal", "macd_histogram"]): + latest_indicators["macd"] = indicators["macd"][-1] if indicators["macd"] else None + latest_indicators["macd_signal"] = indicators["macd_signal"][-1] if indicators["macd_signal"] else None + latest_indicators["macd_histogram"] = indicators["macd_histogram"][-1] if indicators["macd_histogram"] else None + + # 提取布林带 + if all(k in indicators for k in ["bollinger_ma", "bollinger_upper", "bollinger_lower"]): + latest_indicators["bollinger_middle"] = indicators["bollinger_ma"][-1] if indicators["bollinger_ma"] else None + latest_indicators["bollinger_upper"] = indicators["bollinger_upper"][-1] if indicators["bollinger_upper"] else None + latest_indicators["bollinger_lower"] = indicators["bollinger_lower"][-1] if indicators["bollinger_lower"] else None + + # 分析价格位置 + if "ma_20" in latest_indicators and "ma_50" in latest_indicators and prices: + latest_price = prices[-1] + latest_indicators["price_vs_ma20"] = "above" if latest_price > latest_indicators["ma_20"] else "below" + latest_indicators["price_vs_ma50"] = "above" if latest_price > latest_indicators["ma_50"] else "below" + + return { + "full": indicators, + "latest": latest_indicators + } + + except Exception as e: + self.logger.error(f"计算技术指标时发生错误: {str(e)}") + return {} + + def _prepare_summary_prompt(self, ticker: str, stock_data: StockData) -> str: + """准备数据摘要提示 + + Args: + ticker: 股票代码 + stock_data: 股票数据对象 + + Returns: + str: 数据摘要提示 + """ + # 提取相关数据 + historical_summary = stock_data.historical_data.get("summary", {}) + technical_indicators = stock_data.technical_indicators.get("latest", {}) + + # 构建提示 + prompt = f""" +请对以下股票数据进行分析和预处理,确认数据质量并提供简要说明。 + +股票: {ticker} +最新价格: {historical_summary.get('latest_price')} +涨跌幅: {historical_summary.get('price_change')}% +时间范围: {historical_summary.get('start_date')} 至 {historical_summary.get('end_date')} + +主要技术指标: +- MA(5): {technical_indicators.get('ma_5')} +- MA(20): {technical_indicators.get('ma_20')} +- RSI: {technical_indicators.get('rsi')} +- MACD: {technical_indicators.get('macd')} + +请分析这些数据,确认数据是否合理、完整,并提供简要的市场数据状况描述。 +如果发现任何数据问题,请指出并提供可能的解决方法。 +""" + return prompt + + def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]: + """使用代理处理数据并获取结果 + + Args: + prompt: 提示信息 + data: 要处理的数据 + + Returns: + Dict[str, Any]: 处理结果 + """ + # 格式化数据 + data_str = self.format_data(data) + + # 创建完整提示 + full_prompt = f"""{prompt} + +数据: +{data_str} + +请以JSON格式返回结果。 +""" + try: + # 创建消息 - 使用新的消息格式 + msg = self.generate_human_message(content=full_prompt) + + # 获取代理响应 + response = self.agent.step(msg) + + # 记录响应 + self.log_message(response.msgs[0]) + + # 解析响应 + return self.parse_json_response(response.msgs[0].content) + + except Exception as e: + self.logger.error(f"处理数据时发生错误: {str(e)}") + return {} + + def _get_financial_data(self, ticker: str) -> Dict[str, Any]: + """获取股票的财务数据 + + Args: + ticker: 股票代码 + + Returns: + Dict[str, Any]: 财务数据 + """ + try: + from src.tools.data_helper import get_fundamental_data + + self.logger.info(f"获取财务数据: {ticker}") + + # 使用data_helper中的函数获取财务数据 + financial_data = get_fundamental_data(ticker) + + # 如果获取失败,返回空结构 + if not financial_data or "error" in financial_data: + self.logger.error(f"获取财务数据失败: {financial_data.get('error', '未知错误')}") + return { + "income_statement": {}, + "balance_sheet": {}, + "financial_ratios": {}, + "quarterly_results": [], + "dividend_info": {}, + } + + # 提取并组织数据 + financial_indicators = financial_data.get("financial_indicators", {}) + income_statement = financial_data.get("income_statement", {}) + stock_info = financial_data.get("stock_info", {}) + summary = financial_data.get("summary", {}) + + # 获取历史财务数据,默认获取最近4个季度 + historical_data = self._get_historical_financial_data(ticker, 4) + + # 构建返回结构 + result = { + "income_statement": { + "revenue": income_statement.get("营业收入", 0), + "operating_income": income_statement.get("营业利润", 0), + "net_income": income_statement.get("净利润", 0), + "total_profit": income_statement.get("利润总额", 0), + "eps": income_statement.get("基本每股收益", 0), + "income_tax": income_statement.get("减:所得税", 0), + "interest_income": income_statement.get("利息收入", 0), + "interest_expense": income_statement.get("利息支出", 0), + "investment_income": income_statement.get("投资收益", 0), + "operating_expense": income_statement.get("营业支出", 0), + "rd_expense": income_statement.get("研发费用", 0), + "business_tax": income_statement.get("营业税金及附加", 0), + "management_expense": income_statement.get("业务及管理费用", 0), + "asset_impairment_loss": income_statement.get("资产减值损失", 0), + "credit_impairment_loss": income_statement.get("信用减值损失", 0), + "non_operating_income": income_statement.get("加:营业外收入", 0), + "non_operating_expense": income_statement.get("减:营业外支出", 0), + "reporting_date": income_statement.get("报告日", ""), + "year_over_year_growth": financial_indicators.get("净利润同比增长率", 0), + "diluted_eps": income_statement.get("稀释每股收益", 0), + }, + "balance_sheet": { + "total_assets": financial_indicators.get("总资产", 0), + "total_liabilities": financial_indicators.get("总负债", 0), + "total_equity": financial_indicators.get("所有者权益", 0), + "cash_and_equivalents": financial_indicators.get("货币资金", 0), + "total_debt": financial_indicators.get("带息债务", 0), + "retained_earnings": income_statement.get("未分配利润", 0), + }, + "financial_ratios": { + "pe_ratio": stock_info.get("市盈率-动态", 0), + "pb_ratio": stock_info.get("市净率", 0), + "roe": financial_indicators.get("净资产收益率", 0), + "debt_to_equity": financial_indicators.get("资产负债率", 0), + "profit_margin": financial_indicators.get("净利率", 0), + "current_ratio": financial_indicators.get("流动比率", 0), + "turnover_rate": stock_info.get("换手率", 0), + "amplitude": stock_info.get("振幅", 0), + "year_to_date_change": stock_info.get("年初至今涨跌幅", 0), + "sixty_day_change": stock_info.get("60日涨跌幅", 0), + }, + "market_info": { + "market_cap": stock_info.get("总市值", 0), + "circulating_market_value": stock_info.get("流通市值", 0), + "industry": summary.get("industry", ""), + "name": stock_info.get("名称", ""), + "latest_price": stock_info.get("最新价", 0), + "change_percent": stock_info.get("涨跌幅", 0), + "change_amount": stock_info.get("涨跌额", 0), + "volume": stock_info.get("成交量", 0), + "turnover": stock_info.get("成交额", 0), + "highest": stock_info.get("最高", 0), + "lowest": stock_info.get("最低", 0), + "open": stock_info.get("今开", 0), + "prev_close": stock_info.get("昨收", 0), + }, + "historical_data": historical_data, + "trends": self._calculate_financial_trends(historical_data) + } + + return result + + except Exception as e: + self.logger.error(f"获取财务数据时发生错误: {str(e)}") + # 返回基本数据结构以避免流程中断 + return { + "income_statement": {}, + "balance_sheet": {}, + "financial_ratios": {}, + "market_info": {}, + "historical_data": [], + "trends": {}, + "error": str(e) + } + + def _get_historical_financial_data(self, ticker: str, num_quarters: int = 4) -> List[Dict[str, Any]]: + """获取历史财务数据 + + Args: + ticker: 股票代码 + num_quarters: 要获取的季度数量 + + Returns: + List[Dict[str, Any]]: 历史财务数据列表 + """ + try: + import akshare as ak + + self.logger.info(f"获取{ticker}的历史财务数据,共{num_quarters}个季度") + + # 确定股票前缀 + stock_prefix = 'sz' if ticker.startswith('0') or ticker.startswith('3') else 'sh' + formatted_ticker = f"{stock_prefix}{ticker}" + + # 获取利润表数据 + income_statements = ak.stock_financial_report_sina(stock=formatted_ticker, symbol="利润表") + + # 获取资产负债表数据 + try: + balance_sheets = ak.stock_financial_report_sina(stock=formatted_ticker, symbol="资产负债表") + except Exception as e: + self.logger.warning(f"获取{ticker}的资产负债表数据失败: {str(e)}") + balance_sheets = pd.DataFrame() + + # 获取现金流量表数据 + try: + cash_flows = ak.stock_financial_report_sina(stock=formatted_ticker, symbol="现金流量表") + except Exception as e: + self.logger.warning(f"获取{ticker}的现金流量表数据失败: {str(e)}") + cash_flows = pd.DataFrame() + + # 获取财务指标数据 + try: + financial_indicators = ak.stock_financial_analysis_indicator(symbol=ticker) + except Exception as e: + self.logger.warning(f"获取{ticker}的财务指标数据失败: {str(e)}") + financial_indicators = pd.DataFrame() + + # 限制数量 + if not income_statements.empty: + income_statements = income_statements.head(num_quarters) + if not balance_sheets.empty: + balance_sheets = balance_sheets.head(num_quarters) + if not cash_flows.empty: + cash_flows = cash_flows.head(num_quarters) + if not financial_indicators.empty: + financial_indicators = financial_indicators.head(num_quarters) + + # 组织历史数据 + historical_data = [] + + # 使用利润表的报告日期作为基准 + if not income_statements.empty: + for i, row in income_statements.iterrows(): + if i >= num_quarters: + break + + report_date = row.get("报告日", "") + + # 查找相应日期的资产负债表和现金流量表数据 + balance_sheet_row = balance_sheets[balance_sheets["报告日"] == report_date].iloc[0] if not balance_sheets.empty and report_date in balance_sheets["报告日"].values else pd.Series() + cash_flow_row = cash_flows[cash_flows["报告日"] == report_date].iloc[0] if not cash_flows.empty and report_date in cash_flows["报告日"].values else pd.Series() + + # 提取财务指标数据 + financial_indicator_row = pd.Series() + if not financial_indicators.empty: + # 处理财务指标数据,通常日期格式不同,需要转换 + for _, indicator_row in financial_indicators.iterrows(): + indicator_date = indicator_row.get("日期") + if indicator_date and str(indicator_date).replace("-", "").startswith(report_date[:6]): + financial_indicator_row = indicator_row + break + + # 组织单季度数据 + quarter_data = { + "report_date": report_date, + "formatted_date": f"{report_date[:4]}年{report_date[4:6]}月{report_date[6:]}日", + "income_statement": { + "revenue": row.get("营业收入", 0), + "operating_profit": row.get("营业利润", 0), + "net_income": row.get("净利润", 0), + "total_profit": row.get("利润总额", 0), + "eps": row.get("基本每股收益", 0), + }, + "balance_sheet": { + "total_assets": balance_sheet_row.get("资产总计", 0), + "total_liabilities": balance_sheet_row.get("负债合计", 0), + "equity": balance_sheet_row.get("所有者权益(或股东权益)合计", 0), + "cash": balance_sheet_row.get("货币资金", 0), + }, + "cash_flow": { + "operating_cash_flow": cash_flow_row.get("经营活动产生的现金流量净额", 0), + "investing_cash_flow": cash_flow_row.get("投资活动产生的现金流量净额", 0), + "financing_cash_flow": cash_flow_row.get("筹资活动产生的现金流量净额", 0), + }, + "financial_indicators": { + "roe": financial_indicator_row.get("净资产收益率(%)", 0), + "gross_margin": financial_indicator_row.get("销售毛利率(%)", 0), + "debt_ratio": financial_indicator_row.get("资产负债率(%)", 0), + } + } + + historical_data.append(quarter_data) + + self.logger.info(f"成功获取{ticker}的历史财务数据,共{len(historical_data)}个季度") + return historical_data + + except Exception as e: + self.logger.error(f"获取历史财务数据时发生错误: {str(e)}") + return [] + + def _calculate_financial_trends(self, historical_data: List[Dict[str, Any]]) -> Dict[str, Any]: + """计算财务趋势 + + Args: + historical_data: 历史财务数据列表 + + Returns: + Dict[str, Any]: 财务趋势数据 + """ + if not historical_data or len(historical_data) < 2: + return {} + + # 提取关键指标的时间序列 + revenue_trend = [] + net_income_trend = [] + eps_trend = [] + roe_trend = [] + + for quarter in historical_data: + revenue_trend.append(float(quarter["income_statement"]["revenue"])) + net_income_trend.append(float(quarter["income_statement"]["net_income"])) + eps_trend.append(float(quarter["income_statement"]["eps"])) + roe_trend.append(float(quarter["financial_indicators"]["roe"])) + + # 计算同比增长率 + try: + revenue_growth = (revenue_trend[0] - revenue_trend[-1]) / revenue_trend[-1] * 100 if revenue_trend[-1] else 0 + net_income_growth = (net_income_trend[0] - net_income_trend[-1]) / net_income_trend[-1] * 100 if net_income_trend[-1] else 0 + except (IndexError, ZeroDivisionError): + revenue_growth = 0 + net_income_growth = 0 + + # 计算趋势 + trends = { + "revenue": { + "values": revenue_trend, + "growth": revenue_growth, + "trend": "上升" if revenue_growth > 0 else "下降" if revenue_growth < 0 else "持平" + }, + "net_income": { + "values": net_income_trend, + "growth": net_income_growth, + "trend": "上升" if net_income_growth > 0 else "下降" if net_income_growth < 0 else "持平" + }, + "eps": { + "values": eps_trend, + "trend": "上升" if eps_trend[0] > eps_trend[-1] else "下降" if eps_trend[0] < eps_trend[-1] else "持平" + }, + "roe": { + "values": roe_trend, + "trend": "上升" if roe_trend[0] > roe_trend[-1] else "下降" if roe_trend[0] < roe_trend[-1] else "持平" + } + } + + return trends + + def _get_news_data(self, ticker: str, num_of_news: int = 5) -> Dict[str, Any]: + """获取股票相关的新闻数据 + + Args: + ticker: 股票代码 + num_of_news: 获取的新闻数量 + + Returns: + Dict[str, Any]: 新闻数据 + """ + try: + from src.tools.data_helper import get_stock_news + + self.logger.info(f"获取新闻数据: {ticker}, 数量: {num_of_news}") + + # 使用data_helper中的函数获取新闻数据 + news_list = get_stock_news(ticker, num_of_news) + + # 如果没有获取到新闻,返回空结构 + if not news_list: + return { + "news": [], + "overall_sentiment": 0, + "sentiment_breakdown": {"positive": 0, "neutral": 0, "negative": 0} + } + + # 简单情感分析 (实际应用中应使用NLP模型进行分析) + # 这里使用模型分析标题进行简单情感判断 + sentiment_scores = [] + sentiments = {"positive": 0, "neutral": 0, "negative": 0} + + for news in news_list: + # 让LLM对新闻标题进行情感分析 + sentiment_prompt = f""" + 请分析以下新闻标题的情感倾向,仅回复"positive"、"neutral"或"negative": + {news.get('title', '')} + """ + + try: + # 使用LLM分析情感 + msg = self.generate_human_message(content=sentiment_prompt) + response = self.agent.step(msg) + sentiment = response.msgs[0].content.strip().lower() + + # 规范化情感结果 + if "positive" in sentiment: + news["sentiment"] = "positive" + news["sentiment_score"] = 0.8 + sentiments["positive"] += 1 + sentiment_scores.append(0.8) + elif "negative" in sentiment: + news["sentiment"] = "negative" + news["sentiment_score"] = -0.7 + sentiments["negative"] += 1 + sentiment_scores.append(-0.7) + else: + news["sentiment"] = "neutral" + news["sentiment_score"] = 0.1 + sentiments["neutral"] += 1 + sentiment_scores.append(0.1) + + except Exception: + # 如果LLM分析失败,默认为中性 + news["sentiment"] = "neutral" + news["sentiment_score"] = 0.0 + sentiments["neutral"] += 1 + sentiment_scores.append(0.0) + + # 计算总体情感得分 + overall_sentiment = sum(sentiment_scores) / len(sentiment_scores) if sentiment_scores else 0 + + return { + "news": news_list, + "overall_sentiment": overall_sentiment, + "sentiment_breakdown": sentiments + } + + except Exception as e: + self.logger.error(f"获取新闻数据时发生错误: {str(e)}") + # 返回基本数据结构以避免流程中断 + return { + "news": [], + "overall_sentiment": 0, + "sentiment_breakdown": {"positive": 0, "neutral": 0, "negative": 0}, + "error": str(e) + } \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/agents/portfolio_manager.py b/community_usecase/a_share_investment_agent_camel/src/agents/portfolio_manager.py new file mode 100644 index 0000000..6fa178f --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/agents/portfolio_manager.py @@ -0,0 +1,239 @@ +""" +投资组合管理代理实现 +""" +import logging +from typing import Dict, Any, List, Optional +import pandas as pd +import math + +from src.agents.base_agent import BaseAgent +from src.roles import create_role_agent +from src.models import AnalysisSignal, StockData, RiskAnalysis, TradingDecision + +from camel.messages import BaseMessage + + +class PortfolioManagerAgent(BaseAgent): + """投资组合管理代理类""" + + def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"): + """初始化投资组合管理代理 + + Args: + show_reasoning: 是否显示推理过程 + model_name: 使用的模型名称 (gemini, openai, qwen) + """ + role_agent = create_role_agent("portfolio_manager", model_name) + super().__init__(role_agent, show_reasoning, model_name) + self.logger = logging.getLogger("PortfolioManagerAgent") + + def process(self, data: Dict[str, Any]) -> Dict[str, Any]: + """处理投资决策 + + Args: + data: 包含以下键的字典: + - stock_data: 股票数据对象 + - technical_analysis: 技术分析结果 + - fundamentals_analysis: 基本面分析结果 + - sentiment_analysis: 情绪分析结果 + - valuation_analysis: 估值分析结果 + - debate_result: 辩论结果 + - risk_analysis: 风险分析结果 + - portfolio: 投资组合信息 + - messages: 处理过程中的消息 + + Returns: + Dict[str, Any]: 处理后的数据,包含以下内容: + - trading_decision: 交易决策 + - messages: 处理过程中的消息 + """ + # 提取各种分析结果和股票数据 + stock_data = data.get("stock_data") + technical_analysis = data.get("technical_analysis") + fundamentals_analysis = data.get("fundamentals_analysis") + sentiment_analysis = data.get("sentiment_analysis") + valuation_analysis = data.get("valuation_analysis") + debate_result = data.get("debate_result") + risk_analysis = data.get("risk_analysis") + portfolio = data.get("portfolio", {"cash": 100000.0, "stock": 0}) + + if not stock_data: + raise ValueError("缺少股票数据") + + self.logger.info(f"正在制定投资决策") + + try: + # 提取股票基本信息和最新价格 + ticker = stock_data.ticker + historical_data = stock_data.historical_data + latest_price = self._get_latest_price(historical_data) + + # 收集所有分析信号 + agent_signals = [] + if technical_analysis: + agent_signals.append(technical_analysis) + if fundamentals_analysis: + agent_signals.append(fundamentals_analysis) + if sentiment_analysis: + agent_signals.append(sentiment_analysis) + if valuation_analysis: + agent_signals.append(valuation_analysis) + if debate_result: + agent_signals.append(debate_result) + + # 组织投资决策数据 + decision_data = { + "ticker": ticker, + "latest_price": latest_price, + "portfolio": portfolio, + "technical_analysis": technical_analysis.dict() if technical_analysis else None, + "fundamentals_analysis": fundamentals_analysis.dict() if fundamentals_analysis else None, + "sentiment_analysis": sentiment_analysis.dict() if sentiment_analysis else None, + "valuation_analysis": valuation_analysis.dict() if valuation_analysis else None, + "debate_result": debate_result.dict() if debate_result else None, + "risk_analysis": risk_analysis.dict() if risk_analysis else None + } + + # 使用代理处理数据分析请求 + prompt = f"""请作为投资组合经理,对股票 {ticker} 制定最终的投资决策。 + 综合考虑以下因素: + 1. 各类分析师的交易信号 + 2. 辩论结果 + 3. 风险分析 + 4. 当前投资组合状况 + 5. 最新市场价格 + + 请给出明确的交易行动(buy/sell/hold)、交易数量和详细理由。 + 最新价格: {latest_price}元/股 + + 返回格式为JSON: + {{ + "action": "buy/sell/hold", + "quantity": 数量, + "confidence": 0.8, + "reasoning": "投资决策详细理由..." + }} + """ + + analysis_result = self._process_data_with_agent(prompt, decision_data) + + # 创建交易决策 + trading_decision = self._create_trading_decision(analysis_result, agent_signals) + + # 返回处理结果 + return { + "trading_decision": trading_decision, + "messages": [] + } + + except Exception as e: + self.logger.error(f"制定投资决策过程中发生错误: {str(e)}") + + # 返回默认交易决策 + default_decision = TradingDecision( + action="hold", + quantity=0, + confidence=0.5, + agent_signals=[], + reasoning="决策过程中发生错误,默认保持不变" + ) + + return { + "trading_decision": default_decision, + "messages": [] + } + + def _get_latest_price(self, historical_data: Dict[str, Any]) -> float: + """获取最新价格 + + Args: + historical_data: 历史数据 + + Returns: + float: 最新价格 + """ + try: + # 假设历史数据中有'close'字段,并且是按时间顺序排列的 + if isinstance(historical_data, list) and len(historical_data) > 0: + return historical_data[-1].get("close", 0.0) + elif isinstance(historical_data, dict) and "close" in historical_data: + if isinstance(historical_data["close"], list): + return historical_data["close"][-1] + return historical_data["close"] + except Exception as e: + self.logger.error(f"获取最新价格时发生错误: {str(e)}") + + return 0.0 + + def _create_trading_decision(self, analysis_result: Dict[str, Any], agent_signals: List[AnalysisSignal]) -> TradingDecision: + """创建交易决策 + + Args: + analysis_result: 分析结果 + agent_signals: 分析师信号列表 + + Returns: + TradingDecision: 交易决策 + """ + action = analysis_result.get("action", "hold") + quantity = analysis_result.get("quantity", 0) + confidence = analysis_result.get("confidence", 0.5) + reasoning = analysis_result.get("reasoning", "未提供决策理由") + + # 确保数量是整数 + if not isinstance(quantity, int): + try: + quantity = int(quantity) + except: + quantity = 0 + + # 确保数量非负 + quantity = max(0, quantity) + + return TradingDecision( + action=action, + quantity=quantity, + confidence=confidence, + agent_signals=agent_signals, + reasoning=reasoning + ) + + def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]: + """使用代理处理数据分析请求 + + Args: + prompt: 分析提示 + data: 包含各种分析数据 + + Returns: + Dict[str, Any]: 分析结果 + """ + # 格式化数据 + data_str = self.format_data(data) + + # 创建完整提示 + full_prompt = f"""{prompt} + +数据: +{data_str} + +请以JSON格式返回结果。 +""" + # 发送到Camel代理进行分析 + human_message = self.generate_human_message(content=full_prompt) + response = self.agent.step(human_message) + self.log_message(response.msgs[0]) + + # 解析结果 + result = self.parse_json_response(response.msgs[0].content) + + # 如果解析结果为空,使用默认值 + if not result: + result = { + "action": "hold", + "quantity": 0, + "confidence": 0.5, + "reasoning": "无法解析投资决策,默认保持不变" + } + + return result \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/agents/researcher_bear.py b/community_usecase/a_share_investment_agent_camel/src/agents/researcher_bear.py new file mode 100644 index 0000000..0ec987b --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/agents/researcher_bear.py @@ -0,0 +1,189 @@ +""" +空头研究员代理实现 +""" +import logging +from typing import Dict, Any, List, Optional +import pandas as pd + +from src.agents.base_agent import BaseAgent +from src.roles import create_role_agent +from src.models import AnalysisSignal, StockData, ResearchReport + +from camel.messages import BaseMessage + + +class ResearcherBearAgent(BaseAgent): + """空头研究员代理类""" + + def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"): + """初始化空头研究员代理 + + Args: + show_reasoning: 是否显示推理过程 + model_name: 使用的模型名称 (gemini, openai, qwen) + """ + role_agent = create_role_agent("researcher_bear", model_name) + super().__init__(role_agent, show_reasoning, model_name) + self.logger = logging.getLogger("ResearcherBearAgent") + + def process(self, data: Dict[str, Any]) -> Dict[str, Any]: + """处理空头研究报告生成 + + Args: + data: 包含以下键的字典: + - stock_data: 股票数据对象 + - technical_analysis: 技术分析结果 + - fundamentals_analysis: 基本面分析结果 + - sentiment_analysis: 情绪分析结果 + - valuation_analysis: 估值分析结果 + - messages: 处理过程中的消息 + + Returns: + Dict[str, Any]: 处理后的数据,包含以下内容: + - bear_research: 空头研究报告 + - messages: 处理过程中的消息 + """ + # 提取股票数据和各类分析结果 + stock_data = data.get("stock_data") + technical_analysis = data.get("technical_analysis") + fundamentals_analysis = data.get("fundamentals_analysis") + sentiment_analysis = data.get("sentiment_analysis") + valuation_analysis = data.get("valuation_analysis") + + if not stock_data: + raise ValueError("缺少股票数据") + + self.logger.info(f"正在生成空头研究报告") + + try: + # 提取股票基本信息 + ticker = stock_data.ticker + + # 组织各种分析结果 + analysis_data = { + "ticker": ticker, + "technical_analysis": technical_analysis.dict() if technical_analysis else None, + "fundamentals_analysis": fundamentals_analysis.dict() if fundamentals_analysis else None, + "sentiment_analysis": sentiment_analysis.dict() if sentiment_analysis else None, + "valuation_analysis": valuation_analysis.dict() if valuation_analysis else None + } + + # 使用代理处理数据分析请求 + prompt = f"""请作为持有看空观点的研究员,寻找支持卖出股票 {ticker} 的最有力证据和论据。 + 重点关注以下方面: + 1. 技术分析中的看跌信号 + 2. 基本面分析中的负面因素 + 3. 市场情绪分析中的悲观迹象 + 4. 估值分析中的高估证据 + 5. 可能被市场忽视的风险因素 + + 请提供一份全面的看空研究报告,以JSON格式返回: + {{ + "key_points": ["关键点1", "关键点2", ...], + "confidence": 0.8, + "technical_summary": "技术分析总结...", + "fundamental_summary": "基本面分析总结...", + "sentiment_summary": "情绪分析总结...", + "valuation_summary": "估值分析总结...", + "reasoning": "整体推理过程和看空理由..." + }} + """ + + analysis_result = self._process_data_with_agent(prompt, analysis_data) + + # 创建研究报告 + bear_research = self._create_research_report(analysis_result, ticker) + + # 返回处理结果 + return { + "bear_research": bear_research, + "messages": [] + } + + except Exception as e: + self.logger.error(f"生成空头研究报告过程中发生错误: {str(e)}") + + # 返回默认研究报告 + default_report = ResearchReport( + stance="bearish", + key_points=["数据不足以支持详细分析"], + confidence=0.5, + reasoning="处理过程中发生错误,无法生成完整研究报告" + ) + + return { + "bear_research": default_report, + "messages": [] + } + + def _create_research_report(self, analysis_result: Dict[str, Any], ticker: str) -> ResearchReport: + """创建研究报告 + + Args: + analysis_result: 分析结果 + ticker: 股票代码 + + Returns: + ResearchReport: 研究报告 + """ + key_points = analysis_result.get("key_points", ["无关键点"]) + confidence = analysis_result.get("confidence", 0.5) + technical_summary = analysis_result.get("technical_summary") + fundamental_summary = analysis_result.get("fundamental_summary") + sentiment_summary = analysis_result.get("sentiment_summary") + valuation_summary = analysis_result.get("valuation_summary") + reasoning = analysis_result.get("reasoning", "未提供分析理由") + + return ResearchReport( + stance="bearish", + key_points=key_points, + confidence=confidence, + technical_summary=technical_summary, + fundamental_summary=fundamental_summary, + sentiment_summary=sentiment_summary, + valuation_summary=valuation_summary, + reasoning=reasoning + ) + + def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]: + """使用代理处理数据分析请求 + + Args: + prompt: 分析提示 + data: 包含各类分析结果的数据 + + Returns: + Dict[str, Any]: 分析结果 + """ + # 格式化数据 + data_str = self.format_data(data) + + # 创建完整提示 + full_prompt = f"""{prompt} + +数据: +{data_str} + +请以JSON格式返回结果。 +""" + # 发送到Camel代理进行分析 + human_message = self.generate_human_message(content=full_prompt) + response = self.agent.step(human_message) + self.log_message(response.msgs[0]) + + # 解析结果 + result = self.parse_json_response(response.msgs[0].content) + + # 如果解析结果为空,使用默认值 + if not result: + result = { + "key_points": ["无法解析分析结果"], + "confidence": 0.5, + "technical_summary": "无法获取技术分析总结", + "fundamental_summary": "无法获取基本面分析总结", + "sentiment_summary": "无法获取情绪分析总结", + "valuation_summary": "无法获取估值分析总结", + "reasoning": "无法解析空头研究报告" + } + + return result \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/agents/researcher_bull.py b/community_usecase/a_share_investment_agent_camel/src/agents/researcher_bull.py new file mode 100644 index 0000000..e94eaf7 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/agents/researcher_bull.py @@ -0,0 +1,189 @@ +""" +多头研究员代理实现 +""" +import logging +from typing import Dict, Any, List, Optional +import pandas as pd + +from src.agents.base_agent import BaseAgent +from src.roles import create_role_agent +from src.models import AnalysisSignal, StockData, ResearchReport + +from camel.messages import BaseMessage + + +class ResearcherBullAgent(BaseAgent): + """多头研究员代理类""" + + def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"): + """初始化多头研究员代理 + + Args: + show_reasoning: 是否显示推理过程 + model_name: 使用的模型名称 (gemini, openai, qwen) + """ + role_agent = create_role_agent("researcher_bull", model_name) + super().__init__(role_agent, show_reasoning, model_name) + self.logger = logging.getLogger("ResearcherBullAgent") + + def process(self, data: Dict[str, Any]) -> Dict[str, Any]: + """处理多头研究报告生成 + + Args: + data: 包含以下键的字典: + - stock_data: 股票数据对象 + - technical_analysis: 技术分析结果 + - fundamentals_analysis: 基本面分析结果 + - sentiment_analysis: 情绪分析结果 + - valuation_analysis: 估值分析结果 + - messages: 处理过程中的消息 + + Returns: + Dict[str, Any]: 处理后的数据,包含以下内容: + - bull_research: 多头研究报告 + - messages: 处理过程中的消息 + """ + # 提取股票数据和各类分析结果 + stock_data = data.get("stock_data") + technical_analysis = data.get("technical_analysis") + fundamentals_analysis = data.get("fundamentals_analysis") + sentiment_analysis = data.get("sentiment_analysis") + valuation_analysis = data.get("valuation_analysis") + + if not stock_data: + raise ValueError("缺少股票数据") + + self.logger.info(f"正在生成多头研究报告") + + try: + # 提取股票基本信息 + ticker = stock_data.ticker + + # 组织各种分析结果 + analysis_data = { + "ticker": ticker, + "technical_analysis": technical_analysis.dict() if technical_analysis else None, + "fundamentals_analysis": fundamentals_analysis.dict() if fundamentals_analysis else None, + "sentiment_analysis": sentiment_analysis.dict() if sentiment_analysis else None, + "valuation_analysis": valuation_analysis.dict() if valuation_analysis else None + } + + # 使用代理处理数据分析请求 + prompt = f"""请作为持有看多观点的研究员,寻找支持买入股票 {ticker} 的最有力证据和论据。 + 重点关注以下方面: + 1. 技术分析中的看涨信号 + 2. 基本面分析中的积极因素 + 3. 市场情绪分析中的乐观迹象 + 4. 估值分析中的低估证据 + 5. 可能被市场忽视的积极因素 + + 请提供一份全面的看多研究报告,以JSON格式返回: + {{ + "key_points": ["关键点1", "关键点2", ...], + "confidence": 0.8, + "technical_summary": "技术分析总结...", + "fundamental_summary": "基本面分析总结...", + "sentiment_summary": "情绪分析总结...", + "valuation_summary": "估值分析总结...", + "reasoning": "整体推理过程和看多理由..." + }} + """ + + analysis_result = self._process_data_with_agent(prompt, analysis_data) + + # 创建研究报告 + bull_research = self._create_research_report(analysis_result, ticker) + + # 返回处理结果 + return { + "bull_research": bull_research, + "messages": [] + } + + except Exception as e: + self.logger.error(f"生成多头研究报告过程中发生错误: {str(e)}") + + # 返回默认研究报告 + default_report = ResearchReport( + stance="bullish", + key_points=["数据不足以支持详细分析"], + confidence=0.5, + reasoning="处理过程中发生错误,无法生成完整研究报告" + ) + + return { + "bull_research": default_report, + "messages": [] + } + + def _create_research_report(self, analysis_result: Dict[str, Any], ticker: str) -> ResearchReport: + """创建研究报告 + + Args: + analysis_result: 分析结果 + ticker: 股票代码 + + Returns: + ResearchReport: 研究报告 + """ + key_points = analysis_result.get("key_points", ["无关键点"]) + confidence = analysis_result.get("confidence", 0.5) + technical_summary = analysis_result.get("technical_summary") + fundamental_summary = analysis_result.get("fundamental_summary") + sentiment_summary = analysis_result.get("sentiment_summary") + valuation_summary = analysis_result.get("valuation_summary") + reasoning = analysis_result.get("reasoning", "未提供分析理由") + + return ResearchReport( + stance="bullish", + key_points=key_points, + confidence=confidence, + technical_summary=technical_summary, + fundamental_summary=fundamental_summary, + sentiment_summary=sentiment_summary, + valuation_summary=valuation_summary, + reasoning=reasoning + ) + + def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]: + """使用代理处理数据分析请求 + + Args: + prompt: 分析提示 + data: 包含各类分析结果的数据 + + Returns: + Dict[str, Any]: 分析结果 + """ + # 格式化数据 + data_str = self.format_data(data) + + # 创建完整提示 + full_prompt = f"""{prompt} + +数据: +{data_str} + +请以JSON格式返回结果。 +""" + # 发送到Camel代理进行分析 + human_message = self.generate_human_message(content=full_prompt) + response = self.agent.step(human_message) + self.log_message(response.msgs[0]) + + # 解析结果 + result = self.parse_json_response(response.msgs[0].content) + + # 如果解析结果为空,使用默认值 + if not result: + result = { + "key_points": ["无法解析分析结果"], + "confidence": 0.5, + "technical_summary": "无法获取技术分析总结", + "fundamental_summary": "无法获取基本面分析总结", + "sentiment_summary": "无法获取情绪分析总结", + "valuation_summary": "无法获取估值分析总结", + "reasoning": "无法解析多头研究报告" + } + + return result \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/agents/risk_manager.py b/community_usecase/a_share_investment_agent_camel/src/agents/risk_manager.py new file mode 100644 index 0000000..670422f --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/agents/risk_manager.py @@ -0,0 +1,185 @@ +""" +风险管理代理实现 +""" +import logging +from typing import Dict, Any, List, Optional +import pandas as pd + +from src.agents.base_agent import BaseAgent +from src.roles import create_role_agent +from src.models import RiskAnalysis, StockData, Portfolio + +from camel.messages import BaseMessage + + +class RiskManagerAgent(BaseAgent): + """风险管理代理类""" + + def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"): + """初始化风险管理代理 + + Args: + show_reasoning: 是否显示推理过程 + model_name: 使用的模型名称 (gemini, openai, qwen) + """ + role_agent = create_role_agent("risk_manager", model_name) + super().__init__(role_agent, show_reasoning, model_name) + self.logger = logging.getLogger("RiskManagerAgent") + + def process(self, data: Dict[str, Any]) -> Dict[str, Any]: + """处理风险评估 + + Args: + data: 包含以下键的字典: + - stock_data: 股票数据对象 + - debate_result: 辩论结果 + - portfolio: 投资组合信息 + - messages: 处理过程中的消息 + + Returns: + Dict[str, Any]: 处理后的数据,包含以下内容: + - risk_analysis: 风险分析结果 + - messages: 处理过程中的消息 + """ + # 提取股票数据和辩论结果 + stock_data = data.get("stock_data") + debate_result = data.get("debate_result") + portfolio = data.get("portfolio") + + if not stock_data: + raise ValueError("缺少股票数据") + + if not portfolio: + portfolio = {"cash": 100000.0, "stock": 0} + + self.logger.info(f"正在进行风险评估") + + try: + # 提取股票基本信息和历史数据 + ticker = stock_data.ticker + historical_data = stock_data.historical_data + + # 组织风险评估数据 + risk_data = { + "ticker": ticker, + "historical_data": historical_data, + "debate_result": debate_result.dict() if debate_result else None, + "portfolio": portfolio + } + + # 使用代理处理数据分析请求 + prompt = f"""请作为风险管理经理,评估投资股票 {ticker} 的风险水平,并提供风险管理建议。 + 分析以下方面: + 1. 投资组合风险指标(波动率、最大回撤等) + 2. 市场和特定股票的风险水平 + 3. 适当的持仓限制 + 4. 止损水平建议 + 5. 风险分散策略 + + 请根据分析提供详细的风险管理建议。 + 返回格式为JSON: + {{ + "max_position_size": 0.2, // 建议最大持仓比例 + "volatility": 0.15, // 预估股票波动率 + "risk_score": 0.7, // 0-1之间的风险分数 + "max_drawdown": 0.25, // 预估最大回撤 + "suggested_position_size": 0.15, // 建议持仓比例 + "reasoning": "风险评估理由..." + }} + """ + + analysis_result = self._process_data_with_agent(prompt, risk_data) + + # 创建风险分析结果 + risk_analysis = self._create_risk_analysis(analysis_result) + + # 返回处理结果 + return { + "risk_analysis": risk_analysis, + "messages": [] + } + + except Exception as e: + self.logger.error(f"风险评估过程中发生错误: {str(e)}") + + # 返回默认风险分析 + default_analysis = RiskAnalysis( + max_position_size=0.1, + volatility=0.2, + risk_score=0.5, + max_drawdown=0.2, + suggested_position_size=0.05, + reasoning="风险评估过程中发生错误,使用保守默认值" + ) + + return { + "risk_analysis": default_analysis, + "messages": [] + } + + def _create_risk_analysis(self, analysis_result: Dict[str, Any]) -> RiskAnalysis: + """创建风险分析结果 + + Args: + analysis_result: 分析结果 + + Returns: + RiskAnalysis: 风险分析结果 + """ + max_position_size = analysis_result.get("max_position_size", 0.1) + volatility = analysis_result.get("volatility", 0.2) + risk_score = analysis_result.get("risk_score", 0.5) + max_drawdown = analysis_result.get("max_drawdown", 0.2) + suggested_position_size = analysis_result.get("suggested_position_size", 0.05) + reasoning = analysis_result.get("reasoning", "未提供风险评估理由") + + return RiskAnalysis( + max_position_size=max_position_size, + volatility=volatility, + risk_score=risk_score, + max_drawdown=max_drawdown, + suggested_position_size=suggested_position_size, + reasoning=reasoning + ) + + def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]: + """使用代理处理数据分析请求 + + Args: + prompt: 分析提示 + data: 包含风险评估相关数据 + + Returns: + Dict[str, Any]: 分析结果 + """ + # 格式化数据 + data_str = self.format_data(data) + + # 创建完整提示 + full_prompt = f"""{prompt} + +数据: +{data_str} + +请以JSON格式返回结果。 +""" + # 发送到Camel代理进行分析 + human_message = self.generate_human_message(content=full_prompt) + response = self.agent.step(human_message) + self.log_message(response.msgs[0]) + + # 解析结果 + result = self.parse_json_response(response.msgs[0].content) + + # 如果解析结果为空,使用默认值 + if not result: + result = { + "max_position_size": 0.1, + "volatility": 0.2, + "risk_score": 0.5, + "max_drawdown": 0.2, + "suggested_position_size": 0.05, + "reasoning": "无法解析风险分析结果,使用保守默认值" + } + + return result \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/agents/sentiment_analyst.py b/community_usecase/a_share_investment_agent_camel/src/agents/sentiment_analyst.py new file mode 100644 index 0000000..2e69966 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/agents/sentiment_analyst.py @@ -0,0 +1,170 @@ +""" +情绪分析代理实现 +""" +import logging +from typing import Dict, Any, List, Optional +import pandas as pd + +from src.agents.base_agent import BaseAgent +from src.roles import create_role_agent +from src.models import AnalysisSignal, StockData + +from camel.messages import BaseMessage + + +class SentimentAnalystAgent(BaseAgent): + """情绪分析代理类""" + + def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"): + """初始化情绪分析代理 + + Args: + show_reasoning: 是否显示推理过程 + model_name: 使用的模型名称 (gemini, openai, qwen) + """ + role_agent = create_role_agent("sentiment_analyst", model_name) + super().__init__(role_agent, show_reasoning, model_name) + self.logger = logging.getLogger("SentimentAnalystAgent") + + def process(self, data: Dict[str, Any]) -> Dict[str, Any]: + """处理情绪分析 + + Args: + data: 包含以下键的字典: + - stock_data: 股票数据对象 + - messages: 处理过程中的消息 + + Returns: + Dict[str, Any]: 处理后的数据,包含以下内容: + - sentiment_analysis: 情绪分析信号 + - messages: 处理过程中的消息 + """ + # 提取股票数据 + stock_data = data.get("stock_data") + + if not stock_data: + raise ValueError("缺少股票数据") + + self.logger.info(f"正在进行情绪分析") + + try: + # 提取股票基本信息和新闻数据 + ticker = stock_data.ticker + news_data = stock_data.news_data + + # 使用代理处理数据分析请求 + prompt = f"""请对以下与股票相关的新闻和社交媒体数据进行分析,给出明确的市场情绪信号(bullish/bearish/neutral)。 + 分析以下方面: + 1. 整体市场情绪(积极、中性或消极) + 2. 重要事件或新闻的影响 + 3. 机构投资者和分析师观点 + 4. 社交媒体讨论的热度和倾向性 + 5. 情绪变化趋势 + + 请给出明确的交易信号、置信度(0-1)和详细理由。 + 返回格式为JSON: + {{ + "signal": "bullish/bearish/neutral", + "confidence": 0.7, + "reasoning": "分析理由...", + "key_events": ["事件1", "事件2"] + }} + """ + + analysis_result = self._process_data_with_agent(prompt, { + "ticker": ticker, + "news_data": news_data + }) + + # 创建情绪分析信号 + sentiment_analysis = self._create_sentiment_signal(analysis_result, stock_data) + + # 返回处理结果 + return { + "sentiment_analysis": sentiment_analysis, + "messages": [] + } + + except Exception as e: + self.logger.error(f"情绪分析过程中发生错误: {str(e)}") + + # 返回默认分析结果 + default_signal = AnalysisSignal( + agent="情绪分析师", + signal="neutral", + confidence=0.5, + reasoning="分析过程中发生错误,返回中性信号" + ) + + return { + "sentiment_analysis": default_signal, + "messages": [] + } + + def _create_sentiment_signal(self, analysis_result: Dict[str, Any], stock_data: StockData) -> AnalysisSignal: + """创建情绪分析信号 + + Args: + analysis_result: 分析结果 + stock_data: 股票数据 + + Returns: + AnalysisSignal: 分析信号 + """ + signal = analysis_result.get("signal", "neutral") + confidence = analysis_result.get("confidence", 0.5) + reasoning = analysis_result.get("reasoning", "未提供分析理由") + key_events = analysis_result.get("key_events", []) + + details = { + "ticker": stock_data.ticker, + "key_events": key_events, + } + + return AnalysisSignal( + agent="情绪分析师", + signal=signal, + confidence=confidence, + reasoning=reasoning, + details=details + ) + + def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]: + """使用代理处理数据分析请求 + + Args: + prompt: 分析提示 + data: 包含新闻数据的字典 + + Returns: + Dict[str, Any]: 分析结果 + """ + # 格式化数据 + data_str = self.format_data(data) + + # 创建完整提示 + full_prompt = f"""{prompt} + +数据: +{data_str} + +请以JSON格式返回结果。 +""" + # 发送到Camel代理进行分析 + human_message = self.generate_human_message(content=full_prompt) + response = self.agent.step(human_message) + self.log_message(response.msgs[0]) + + # 解析结果 + result = self.parse_json_response(response.msgs[0].content) + + # 如果解析结果为空,使用默认值 + if not result: + result = { + "signal": "neutral", + "confidence": 0.5, + "reasoning": "无法解析分析结果", + "key_events": [] + } + + return result \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/agents/technical_analyst.py b/community_usecase/a_share_investment_agent_camel/src/agents/technical_analyst.py new file mode 100644 index 0000000..6790282 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/agents/technical_analyst.py @@ -0,0 +1,202 @@ +""" +技术分析代理实现 +""" +import logging +from typing import Dict, Any, List, Optional +import pandas as pd +import numpy as np +from datetime import datetime +import json + +from src.agents.base_agent import BaseAgent +from src.roles import create_role_agent +from src.models import StockData, AnalysisSignal + +from camel.messages import BaseMessage + + +class TechnicalAnalystAgent(BaseAgent): + """技术分析代理类""" + + def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"): + """初始化技术分析代理 + + Args: + show_reasoning: 是否显示推理过程 + model_name: 使用的模型名称 (gemini, openai, qwen) + """ + role_agent = create_role_agent("technical_analyst", model_name) + super().__init__(role_agent, show_reasoning, model_name) + self.logger = logging.getLogger("TechnicalAnalystAgent") + + def process(self, data: Dict[str, Any]) -> Dict[str, Any]: + """处理技术分析 + + Args: + data: 包含以下键的字典: + - stock_data: 股票数据对象 + - messages: 处理过程中的消息 + + Returns: + Dict[str, Any]: 处理后的数据,包含以下内容: + - technical_analysis: 技术分析信号 + - messages: 处理过程中的消息 + """ + # 提取股票数据 + stock_data = data.get("stock_data") + if not stock_data: + raise ValueError("缺少股票数据") + + self.logger.info(f"正在进行技术分析") + + try: + # 提取技术指标 + technical_indicators = stock_data.technical_indicators + historical_data = stock_data.historical_data + + # 使用代理处理数据分析请求 + prompt = f"""请对以下股票的技术指标进行分析,给出明确的交易信号(bullish/bearish/neutral)。 + 分析以下方面: + 1. 趋势指标 (移动平均线, MACD等) + 2. 动量指标 (RSI, 随机指标等) + 3. 波动性指标 (布林带, ATR等) + 4. 量价关系 + 5. 支撑位和阻力位 + + 请给出明确的交易信号、置信度(0-1)和详细理由。 + 返回格式为JSON: + {{ + "signal": "bullish/bearish/neutral", + "confidence": 0.7, + "reasoning": "分析理由...", + "key_indicators": ["指标1", "指标2"] + }} + """ + + analysis_result = self._process_data_with_agent(prompt, { + "technical_indicators": technical_indicators, + "historical_data": historical_data + }) + + # 创建技术分析信号 + technical_analysis = self._create_technical_signal(analysis_result, stock_data) + + # 返回处理结果 + return { + "technical_analysis": technical_analysis, + "messages": [] + } + + except Exception as e: + self.logger.error(f"技术分析过程中发生错误: {str(e)}") + + # 返回默认分析结果 + default_signal = AnalysisSignal( + agent="技术分析师", + signal="neutral", + confidence=0.5, + reasoning="分析过程中发生错误,返回中性信号" + ) + + return { + "technical_analysis": default_signal, + "messages": [] + } + + def _prepare_analysis_prompt(self, stock_data: StockData) -> str: + """准备技术分析提示 + + Args: + stock_data: 股票数据对象 + + Returns: + str: 技术分析提示 + """ + # 提取技术指标 + technical_indicators = stock_data.technical_indicators + historical_data = stock_data.historical_data + + # 构建提示 + prompt = f"""请对以下股票的技术指标进行分析,给出明确的交易信号(bullish/bearish/neutral)。 + 分析以下方面: + 1. 趋势指标 (移动平均线, MACD等) + 2. 动量指标 (RSI, 随机指标等) + 3. 波动性指标 (布林带, ATR等) + 4. 量价关系 + 5. 支撑位和阻力位 + + 请给出明确的交易信号、置信度(0-1)和详细理由。 + 返回格式为JSON: + {{ + "signal": "bullish/bearish/neutral", + "confidence": 0.7, + "reasoning": "分析理由...", + "key_indicators": ["指标1", "指标2"] + }} + """ + return prompt + + def _create_technical_signal(self, result: Dict[str, Any], stock_data: StockData) -> AnalysisSignal: + """创建技术分析信号 + + Args: + result: 分析结果 + stock_data: 股票数据对象 + + Returns: + AnalysisSignal: 技术分析信号 + """ + signal = result.get("signal", "neutral") + confidence = result.get("confidence", 0.5) + reasoning = result.get("reasoning", "") + key_indicators = result.get("key_indicators", []) + + return AnalysisSignal( + agent="Technical Analysis", + signal=signal, + confidence=confidence, + reasoning=reasoning, + details={ + "key_indicators": key_indicators + } + ) + + def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]: + """使用代理处理数据分析请求 + + Args: + prompt: 分析提示 + data: 包含技术指标和历史数据的数据 + + Returns: + Dict[str, Any]: 分析结果 + """ + # 格式化数据 + data_str = self.format_data(data) + + # 创建完整提示 + full_prompt = f"""{prompt} + +数据: +{data_str} + +请以JSON格式返回结果。 +""" + # 发送到Camel代理进行分析 + human_message = self.generate_human_message(content=full_prompt) + response = self.agent.step(human_message) + self.log_message(response.msgs[0]) + + # 解析结果 + result = self.parse_json_response(response.msgs[0].content) + + # 如果解析结果为空,使用默认值 + if not result: + result = { + "signal": "neutral", + "confidence": 0.5, + "reasoning": "无法解析分析结果", + "key_indicators": [] + } + + return result \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/agents/valuation_analyst.py b/community_usecase/a_share_investment_agent_camel/src/agents/valuation_analyst.py new file mode 100644 index 0000000..75deafe --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/agents/valuation_analyst.py @@ -0,0 +1,182 @@ +""" +估值分析代理实现 +""" +import logging +from typing import Dict, Any, List, Optional +import pandas as pd + +from src.agents.base_agent import BaseAgent +from src.roles import create_role_agent +from src.models import AnalysisSignal, StockData + +from camel.messages import BaseMessage + + +class ValuationAnalystAgent(BaseAgent): + """估值分析代理类""" + + def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"): + """初始化估值分析代理 + + Args: + show_reasoning: 是否显示推理过程 + model_name: 使用的模型名称 (gemini, openai, qwen) + """ + role_agent = create_role_agent("valuation_analyst", model_name) + super().__init__(role_agent, show_reasoning, model_name) + self.logger = logging.getLogger("ValuationAnalystAgent") + + def process(self, data: Dict[str, Any]) -> Dict[str, Any]: + """处理估值分析 + + Args: + data: 包含以下键的字典: + - stock_data: 股票数据对象 + - fundamentals_analysis: 基本面分析结果 + - messages: 处理过程中的消息 + + Returns: + Dict[str, Any]: 处理后的数据,包含以下内容: + - valuation_analysis: 估值分析信号 + - messages: 处理过程中的消息 + """ + # 提取股票数据和基本面分析 + stock_data = data.get("stock_data") + fundamentals_analysis = data.get("fundamentals_analysis") + + if not stock_data: + raise ValueError("缺少股票数据") + + self.logger.info(f"正在进行估值分析") + + try: + # 提取基本面数据和历史数据 + fundamental_data = stock_data.fundamental_data + historical_data = stock_data.historical_data + + # 使用代理处理数据分析请求 + prompt = f"""请对以下股票进行估值分析,给出明确的交易信号(bullish/bearish/neutral)。 + 分析以下方面: + 1. 当前市场估值(如PE、PB、PS等) + 2. 估值相对于历史水平 + 3. 估值相对于行业平均水平 + 4. 使用不同估值模型(如DCF、相对估值法) + 5. 内在价值与当前市场价格的比较 + + 请给出明确的交易信号、置信度(0-1)和详细理由。 + 返回格式为JSON: + {{ + "signal": "bullish/bearish/neutral", + "confidence": 0.7, + "reasoning": "分析理由...", + "fair_value": 数值, + "key_metrics": ["指标1", "指标2"] + }} + """ + + analysis_data = { + "fundamental_data": fundamental_data, + "historical_data": historical_data + } + + # 如果有基本面分析结果,添加到分析数据中 + if fundamentals_analysis: + analysis_data["fundamentals_analysis"] = fundamentals_analysis.dict() + + analysis_result = self._process_data_with_agent(prompt, analysis_data) + + # 创建估值分析信号 + valuation_analysis = self._create_valuation_signal(analysis_result, stock_data) + + # 返回处理结果 + return { + "valuation_analysis": valuation_analysis, + "messages": [] + } + + except Exception as e: + self.logger.error(f"估值分析过程中发生错误: {str(e)}") + + # 返回默认分析结果 + default_signal = AnalysisSignal( + agent="估值分析师", + signal="neutral", + confidence=0.5, + reasoning="分析过程中发生错误,返回中性信号" + ) + + return { + "valuation_analysis": default_signal, + "messages": [] + } + + def _create_valuation_signal(self, analysis_result: Dict[str, Any], stock_data: StockData) -> AnalysisSignal: + """创建估值分析信号 + + Args: + analysis_result: 分析结果 + stock_data: 股票数据 + + Returns: + AnalysisSignal: 分析信号 + """ + signal = analysis_result.get("signal", "neutral") + confidence = analysis_result.get("confidence", 0.5) + reasoning = analysis_result.get("reasoning", "未提供分析理由") + fair_value = analysis_result.get("fair_value", 0) + key_metrics = analysis_result.get("key_metrics", []) + + details = { + "ticker": stock_data.ticker, + "fair_value": fair_value, + "key_metrics": key_metrics, + } + + return AnalysisSignal( + agent="估值分析师", + signal=signal, + confidence=confidence, + reasoning=reasoning, + details=details + ) + + def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]: + """使用代理处理数据分析请求 + + Args: + prompt: 分析提示 + data: 包含基本面数据和历史数据的数据 + + Returns: + Dict[str, Any]: 分析结果 + """ + # 格式化数据 + data_str = self.format_data(data) + + # 创建完整提示 + full_prompt = f"""{prompt} + +数据: +{data_str} + +请以JSON格式返回结果。 +""" + # 发送到Camel代理进行分析 + human_message = self.generate_human_message(content=full_prompt) + response = self.agent.step(human_message) + self.log_message(response.msgs[0]) + + # 解析结果 + result = self.parse_json_response(response.msgs[0].content) + + # 如果解析结果为空,使用默认值 + if not result: + result = { + "signal": "neutral", + "confidence": 0.5, + "reasoning": "无法解析分析结果", + "fair_value": 0, + "key_metrics": [] + } + + return result \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/main.py b/community_usecase/a_share_investment_agent_camel/src/main.py new file mode 100644 index 0000000..2d6801d --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/main.py @@ -0,0 +1,319 @@ +""" +基于Camel框架的A股投资代理系统主程序 +""" +import argparse +import logging +import json +import os +import sys +from datetime import datetime, timedelta +from typing import Dict, Any, List, Optional + +# 添加项目根目录到Python路径 +current_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.dirname(current_dir) +sys.path.append(project_root) + +from src.agents.market_data_agent import MarketDataAgent +from src.agents.technical_analyst import TechnicalAnalystAgent +from src.agents.fundamentals_analyst import FundamentalsAnalystAgent +from src.agents.sentiment_analyst import SentimentAnalystAgent +from src.agents.valuation_analyst import ValuationAnalystAgent +from src.agents.researcher_bull import ResearcherBullAgent +from src.agents.researcher_bear import ResearcherBearAgent +from src.agents.debate_room import DebateRoomAgent +from src.agents.risk_manager import RiskManagerAgent +from src.agents.portfolio_manager import PortfolioManagerAgent +from src.models import Portfolio, TradingDecision, AnalysisSignal, StockData + +# 设置日志 +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + handlers=[ + logging.FileHandler("logs/main.log"), + logging.StreamHandler() + ] +) +logger = logging.getLogger("Main") + + +def run_investment_analysis( + ticker: str, + start_date: Optional[str] = None, + end_date: Optional[str] = None, + portfolio: Optional[Dict[str, Any]] = None, + show_reasoning: bool = False, + num_of_news: int = 5, + model_name: str = "gemini" +) -> TradingDecision: + """ + 运行投资分析流程 + + Args: + ticker: 股票代码 + start_date: 开始日期 (YYYY-MM-DD) + end_date: 结束日期 (YYYY-MM-DD) + portfolio: 当前投资组合状态 + show_reasoning: 是否显示推理过程 + num_of_news: 情绪分析使用的新闻数量 + model_name: 使用的模型名称 (gemini, openai, qwen) + + Returns: + TradingDecision: 交易决策 + """ + logger.info(f"开始对 {ticker} 进行投资分析,使用模型: {model_name}") + + # 设置默认日期 + if not end_date: + end_date = datetime.now().strftime("%Y-%m-%d") + if not start_date: + start_date_obj = datetime.strptime(end_date, "%Y-%m-%d") - timedelta(days=365) + start_date = start_date_obj.strftime("%Y-%m-%d") + + # 设置默认投资组合 + if not portfolio: + portfolio = {"cash": 100000.0, "stock": 0} + + # 初始数据 + data = { + "ticker": ticker, + "start_date": start_date, + "end_date": end_date, + "portfolio": portfolio, + "num_of_news": num_of_news + } + + # 创建代理 + market_data_agent = MarketDataAgent(show_reasoning=show_reasoning, model_name=model_name) + technical_analyst = TechnicalAnalystAgent(show_reasoning=show_reasoning, model_name=model_name) + fundamentals_analyst = FundamentalsAnalystAgent(show_reasoning=show_reasoning, model_name=model_name) + sentiment_analyst = SentimentAnalystAgent(show_reasoning=show_reasoning, model_name=model_name) + valuation_analyst = ValuationAnalystAgent(show_reasoning=show_reasoning, model_name=model_name) + researcher_bull = ResearcherBullAgent(show_reasoning=show_reasoning, model_name=model_name) + researcher_bear = ResearcherBearAgent(show_reasoning=show_reasoning, model_name=model_name) + debate_room = DebateRoomAgent(show_reasoning=show_reasoning, model_name=model_name) + risk_manager = RiskManagerAgent(show_reasoning=show_reasoning, model_name=model_name) + portfolio_manager = PortfolioManagerAgent(show_reasoning=show_reasoning, model_name=model_name) + + try: + # 第一步: 获取市场数据 + logger.info("步骤1: 获取市场数据") + market_data_result = market_data_agent.process(data) + + # 提取股票数据 + stock_data = market_data_result.get("stock_data") + if not stock_data: + raise ValueError("市场数据代理未返回股票数据") + + # 第二步: 技术分析 + logger.info("步骤2: 进行技术分析") + technical_data = { + "stock_data": stock_data, + "messages": market_data_result.get("messages", []) + } + technical_result = technical_analyst.process(technical_data) + technical_analysis = technical_result.get("technical_analysis") + + # 第三步: 基本面分析 + logger.info("步骤3: 进行基本面分析") + fundamentals_data = { + "stock_data": stock_data, + "messages": technical_result.get("messages", []) + } + fundamentals_result = fundamentals_analyst.process(fundamentals_data) + fundamentals_analysis = fundamentals_result.get("fundamentals_analysis") + + # 第四步: 情绪分析 + logger.info("步骤4: 进行情绪分析") + sentiment_data = { + "stock_data": stock_data, + "messages": fundamentals_result.get("messages", []) + } + sentiment_result = sentiment_analyst.process(sentiment_data) + sentiment_analysis = sentiment_result.get("sentiment_analysis") + + # 第五步: 估值分析 + logger.info("步骤5: 进行估值分析") + valuation_data = { + "stock_data": stock_data, + "fundamentals_analysis": fundamentals_analysis, + "messages": sentiment_result.get("messages", []) + } + valuation_result = valuation_analyst.process(valuation_data) + valuation_analysis = valuation_result.get("valuation_analysis") + + # 第六步: 多头研究员报告 + logger.info("步骤6: 生成多头研究报告") + bull_data = { + "stock_data": stock_data, + "technical_analysis": technical_analysis, + "fundamentals_analysis": fundamentals_analysis, + "sentiment_analysis": sentiment_analysis, + "valuation_analysis": valuation_analysis, + "messages": valuation_result.get("messages", []) + } + bull_result = researcher_bull.process(bull_data) + bull_research = bull_result.get("bull_research") + + # 第七步: 空头研究员报告 + logger.info("步骤7: 生成空头研究报告") + bear_data = { + "stock_data": stock_data, + "technical_analysis": technical_analysis, + "fundamentals_analysis": fundamentals_analysis, + "sentiment_analysis": sentiment_analysis, + "valuation_analysis": valuation_analysis, + "messages": bull_result.get("messages", []) + } + bear_result = researcher_bear.process(bear_data) + bear_research = bear_result.get("bear_research") + + # 第八步: 辩论室 + logger.info("步骤8: 举行辩论会") + debate_data = { + "stock_data": stock_data, + "bull_research": bull_research, + "bear_research": bear_research, + "messages": bear_result.get("messages", []) + } + debate_result = debate_room.process(debate_data) + debate_signal = debate_result.get("debate_result") + + # 第九步: 风险评估 + logger.info("步骤9: 进行风险评估") + risk_data = { + "stock_data": stock_data, + "debate_result": debate_signal, + "portfolio": portfolio, + "messages": debate_result.get("messages", []) + } + risk_result = risk_manager.process(risk_data) + risk_analysis = risk_result.get("risk_analysis") + + # 第十步: 投资组合管理 + logger.info("步骤10: 制定最终投资决策") + portfolio_data = { + "stock_data": stock_data, + "technical_analysis": technical_analysis, + "fundamentals_analysis": fundamentals_analysis, + "sentiment_analysis": sentiment_analysis, + "valuation_analysis": valuation_analysis, + "debate_result": debate_signal, + "risk_analysis": risk_analysis, + "portfolio": portfolio, + "messages": risk_result.get("messages", []) + } + portfolio_result = portfolio_manager.process(portfolio_data) + trading_decision = portfolio_result.get("trading_decision") + + logger.info(f"投资分析完成,决策: {trading_decision.action}, 数量: {trading_decision.quantity}") + return trading_decision + + except Exception as e: + logger.error(f"投资分析过程中发生错误: {str(e)}") + + # 返回默认决策 + default_decision = TradingDecision( + action="hold", + quantity=0, + confidence=0.5, + agent_signals=[], + reasoning=f"分析过程中发生错误: {str(e)}" + ) + return default_decision + + +def test(ticker: str = "000001", model_name: str = "gemini"): + """ + 测试函数,使用预设参数快速测试系统功能 + + Args: + ticker: 股票代码,默认为"000001"(平安银行) + model_name: 使用的模型名称 (gemini, openai, qwen) + """ + logger.info(f"开始测试功能,分析股票: {ticker}") + + # 预设参数 + show_reasoning = True # 显示推理过程 + num_of_news = 5 # 获取5条新闻 + initial_capital = 100000.0 # 初始资金10万元 + + # 日期设置(默认分析最近30天) + end_date = datetime.now().strftime("%Y-%m-%d") + start_date_obj = datetime.strptime(end_date, "%Y-%m-%d") - timedelta(days=30) + start_date = start_date_obj.strftime("%Y-%m-%d") + + # 创建投资组合 + portfolio = { + "cash": initial_capital, + "stock": 0 + } + + print(f"测试配置:") + print(f"- 股票: {ticker}") + print(f"- 模型: {model_name}") + print(f"- 时间范围: {start_date} 至 {end_date}") + print(f"- 初始资金: {initial_capital}") + print(f"- 新闻数量: {num_of_news}") + print("\n开始分析...\n") + + # 运行投资分析 + decision = run_investment_analysis( + ticker=ticker, + start_date=start_date, + end_date=end_date, + portfolio=portfolio, + show_reasoning=show_reasoning, + num_of_news=num_of_news, + model_name=model_name + ) + + # 输出结果 + print("\n测试结果 - 交易决策:") + print(json.dumps(decision.dict(), indent=2, ensure_ascii=False)) + return decision + + +def main(): + """主函数""" + # 解析命令行参数 + parser = argparse.ArgumentParser(description="基于Camel框架的A股投资代理系统") + parser.add_argument("--ticker", type=str, required=True, help="股票代码") + parser.add_argument("--start-date", type=str, help="开始日期 (YYYY-MM-DD)") + parser.add_argument("--end-date", type=str, help="结束日期 (YYYY-MM-DD)") + parser.add_argument("--cash", type=float, default=100000.0, help="初始现金") + parser.add_argument("--stock", type=int, default=0, help="初始股票数量") + parser.add_argument("--model", type=str, default="qwen", choices=["gemini", "openai", "qwen"], help="使用的模型") + parser.add_argument("--news", type=int, default=10, help="情绪分析的新闻数量") + parser.add_argument("--show-reasoning", action="store_true", help="显示详细推理过程") + parser.add_argument("--test", action="store_true", help="以测试模式运行,使用默认参数") + + args = parser.parse_args() + + # 测试模式 + if args.test: + test(ticker=args.ticker, model_name=args.model) + return + + # 正常模式,使用命令行参数 + portfolio = { + "cash": args.cash, + "stock": args.stock + } + + decision = run_investment_analysis( + ticker=args.ticker, + start_date=args.start_date, + end_date=args.end_date, + portfolio=portfolio, + show_reasoning=args.show_reasoning, + num_of_news=args.news, + model_name=args.model + ) + + print(json.dumps(decision.dict(), indent=2, ensure_ascii=False)) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/models.py b/community_usecase/a_share_investment_agent_camel/src/models.py new file mode 100644 index 0000000..c8ba875 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/models.py @@ -0,0 +1,90 @@ +""" +数据模型定义 +""" +from datetime import datetime +from typing import Dict, List, Optional, Union, Any +from pydantic import BaseModel, Field +import json + + +class StockData(BaseModel): + """股票数据模型""" + ticker: str + historical_data: Dict[str, Any] = Field(default_factory=dict) + fundamental_data: Dict[str, Any] = Field(default_factory=dict) + technical_indicators: Dict[str, Any] = Field(default_factory=dict) + news_data: Dict[str, Any] = Field(default_factory=dict) + + +class AnalysisSignal(BaseModel): + """分析信号""" + agent: str + signal: str # bullish, bearish, neutral + confidence: float # 0.0 - 1.0 + reasoning: Optional[str] = None + details: Optional[Dict[str, Any]] = None + + +class DateTimeEncoder(json.JSONEncoder): + """处理datetime的JSON编码器""" + def default(self, obj): + if isinstance(obj, datetime): + return obj.isoformat() + return super().default(obj) + + +class TradingDecision(BaseModel): + """交易决策""" + action: str # buy, sell, hold + quantity: int + confidence: float + agent_signals: List[AnalysisSignal] + reasoning: str + timestamp: Optional[datetime] = None + + def __init__(self, **data): + if 'timestamp' not in data: + data['timestamp'] = datetime.now() + super().__init__(**data) + + def dict(self): + """返回字典表示,可JSON序列化""" + base_dict = super().dict() + # 转换datetime为ISO格式字符串 + base_dict['timestamp'] = base_dict['timestamp'].isoformat() if base_dict['timestamp'] else None + return base_dict + + +class Portfolio(BaseModel): + """投资组合""" + cash: float = 100000.0 + stock: int = 0 + stock_value: float = 0.0 + total_value: float = Field(default=0.0) + holdings: Dict[str, Dict[str, Any]] = Field(default_factory=dict) + + def update_total_value(self): + """更新总价值""" + self.total_value = self.cash + self.stock_value + + +class RiskAnalysis(BaseModel): + """风险分析""" + max_position_size: float + volatility: float + risk_score: float # 0.0 - 1.0 + max_drawdown: float + suggested_position_size: float + reasoning: Optional[str] = None + + +class ResearchReport(BaseModel): + """研究报告""" + stance: str # bullish, bearish + key_points: List[str] + confidence: float + technical_summary: Optional[str] = None + fundamental_summary: Optional[str] = None + sentiment_summary: Optional[str] = None + valuation_summary: Optional[str] = None + reasoning: Optional[str] = None \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/roles.py b/community_usecase/a_share_investment_agent_camel/src/roles.py new file mode 100644 index 0000000..910907b --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/roles.py @@ -0,0 +1,251 @@ +""" +角色定义模块 + +使用Camel框架定义系统中的各种AI代理角色 +""" +from camel.agents import ChatAgent +from camel.messages import BaseMessage +from camel.models import ModelFactory +from camel.types import ModelType, ModelPlatformType +from camel.configs.qwen_config import QwenConfig +from camel.configs.openai_config import ChatGPTConfig +import os +from typing import Dict, Any, List, Optional +from dotenv import load_dotenv + +# 加载环境变量 +load_dotenv() + +# 获取API密钥和模型配置 +GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") +GEMINI_MODEL = os.getenv("GEMINI_MODEL", "gemini-1.5-flash") # 默认模型 + +OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") +OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o") # 默认模型 + +QWEN_API_KEY = os.getenv("QWEN_API_KEY") +QWEN_MODEL = os.getenv("QWEN_MODEL", "qwen-max") # 默认模型 +QWEN_API_URL = os.getenv("QWEN_API_URL", "") # API URL + +# 角色系统提示 +MARKET_DATA_ANALYST_PROMPT = """ +你是一名专业的市场数据分析师,负责收集、处理和分析A股市场数据。 +你的主要职责是: +1. 收集股票的历史价格、交易量和其他市场数据 +2. 计算各种技术指标(如移动平均线、相对强弱指数等) +3. 整理和预处理数据,确保数据质量和准确性 +4. 标记数据异常并进行适当处理 + +请以专业、精确的方式分析数据,确保你提供的数据是正确的,并为后续分析提供坚实基础。 +""" + +TECHNICAL_ANALYST_PROMPT = """ +你是一名经验丰富的技术分析师,专注于通过技术指标和图表模式分析A股市场。 +你的主要职责是: +1. 分析价格走势、交易量和技术指标 +2. 识别支撑位和阻力位 +3. 寻找图表模式(如头肩顶、三角形等) +4. 分析动量和趋势强度 +5. 提供基于技术分析的交易信号 + +请基于市场数据分析师提供的数据,进行深入的技术分析,并给出明确的交易信号(看涨、看跌或中性)及相应的置信度。 +""" + +FUNDAMENTALS_ANALYST_PROMPT = """ +你是一名资深的基本面分析师,专注于分析A股上市公司的财务状况和业务表现。 +你的主要职责是: +1. 分析公司财务报表(资产负债表、利润表、现金流量表) +2. 计算和解释关键财务比率(如市盈率、市净率、ROE等) +3. 评估公司业务模式和竞争优势 +4. 分析行业趋势和公司在行业中的地位 +5. 提供基于基本面的投资建议 + +请基于市场数据分析师提供的数据,进行深入的基本面分析,并给出明确的交易信号(看涨、看跌或中性)及相应的置信度。 +""" + +SENTIMENT_ANALYST_PROMPT = """ +你是一名市场情绪分析师,专注于分析A股市场相关的新闻、社交媒体讨论和市场情绪指标。 +你的主要职责是: +1. 分析与特定股票相关的新闻报道 +2. 评估市场对该股票的整体情绪(积极、中性或消极) +3. 识别可能影响市场情绪的重要事件或新闻 +4. 评估市场情绪指标(如恐惧与贪婪指数) +5. 提供基于情绪分析的交易信号 + +请基于市场数据分析师提供的数据和新闻内容,进行深入的情绪分析,并给出明确的交易信号(看涨、看跌或中性)及相应的置信度。 +""" + +VALUATION_ANALYST_PROMPT = """ +你是一名专业的估值分析师,专注于确定A股上市公司的内在价值。 +你的主要职责是: +1. 应用不同的估值模型(如DCF、相对估值法) +2. 计算公司的内在价值 +3. 比较当前市场价格与内在价值 +4. 评估股票是否被高估或低估 +5. 提供基于估值的交易信号 + +请基于市场数据分析师和基本面分析师提供的数据,进行深入的估值分析,并给出明确的交易信号(看涨、看跌或中性)及相应的置信度。 +""" + +RESEARCHER_BULL_PROMPT = """ +你是一名持有看多观点的研究员,专注于寻找支持买入特定A股的理由。 +你的主要职责是: +1. 分析和解释各类分析师(技术、基本面、情绪、估值)提供的积极信号 +2. 寻找被市场忽视的积极因素 +3. 探索公司未来增长的潜在催化剂 +4. 构建支持买入决策的完整论据 +5. 为投资决策提供看多的研究报告 + +请基于各位分析师提供的数据和分析,提出最有力的看多论据,并提供一份全面的研究报告。 +""" + +RESEARCHER_BEAR_PROMPT = """ +你是一名持有看空观点的研究员,专注于寻找支持卖出特定A股的理由。 +你的主要职责是: +1. 分析和解释各类分析师(技术、基本面、情绪、估值)提供的消极信号 +2. 寻找被市场忽视的风险因素 +3. 探索可能导致股价下跌的潜在风险 +4. 构建支持卖出决策的完整论据 +5. 为投资决策提供看空的研究报告 + +请基于各位分析师提供的数据和分析,提出最有力的看空论据,并提供一份全面的研究报告。 +""" + +DEBATE_ROOM_PROMPT = """ +你是一个投资辩论室的主持人,负责整合多头和空头研究员的观点,形成一个平衡的投资视角。 +你的主要职责是: +1. 公正评估多头和空头研究员的论据 +2. 识别双方论据中的优点和弱点 +3. 权衡不同因素的重要性 +4. 形成综合性的市场观点 +5. 提出平衡的投资建议 + +请基于多头和空头研究员提供的研究报告,进行深入的分析和辩论,并给出一个综合性的市场观点和投资建议。 +""" + +RISK_MANAGER_PROMPT = """ +你是一名资深的风险管理经理,负责评估和管理投资决策的风险。 +你的主要职责是: +1. 计算投资组合的风险指标(如波动率、最大回撤等) +2. 设定风险限制(如持仓限制、止损水平) +3. 评估市场和特定股票的风险水平 +4. 根据投资者风险偏好调整风险暴露 +5. 提供风险管理建议 + +请基于市场数据、投资组合状况和辩论室的综合观点,进行深入的风险分析,并提供明确的风险管理建议,包括建议的持仓规模和风险控制措施。 +""" + +PORTFOLIO_MANAGER_PROMPT = """ +你是一名投资组合经理,负责做出最终的投资决策并管理整体投资组合。 +你的主要职责是: +1. 整合各类分析和建议(技术、基本面、情绪、风险等) +2. 做出最终的投资决策(买入、卖出或持有) +3. 确定具体的交易数量和价格 +4. 平衡投资组合的风险与回报 +5. 优化资金分配 + +请基于各位分析师和风险管理经理提供的信息,做出最终的投资决策,包括具体的交易行动、数量和执行策略。 +""" + +# 创建模型配置 +def get_model_config(model_name: str = "gemini") -> tuple: + """获取模型配置 + + Args: + model_name: 模型名称 (gemini, openai, qwen) + + Returns: + tuple: (ModelPlatformType, ModelType, config_dict) 模型平台类型、模型类型和配置字典 + """ + model_name = model_name.lower() + + if model_name == "gemini": + if not GEMINI_API_KEY: + raise ValueError("缺少Gemini API密钥,请在.env文件中设置GEMINI_API_KEY") + return ModelPlatformType.GEMINI, ModelType.GEMINI_1_5_FLASH, {"api_key": GEMINI_API_KEY, "model": GEMINI_MODEL} + + elif model_name == "openai": + if not OPENAI_API_KEY: + raise ValueError("缺少OpenAI API密钥,请在.env文件中设置OPENAI_API_KEY") + + # 返回GPT-4o配置 + return ModelPlatformType.OPENAI, ModelType.GPT_4O, {"api_key": OPENAI_API_KEY, "model": OPENAI_MODEL} + + elif model_name == "qwen": + if not QWEN_API_KEY: + raise ValueError("缺少Qwen API密钥,请在.env文件中设置QWEN_API_KEY") + + # 使用QwenConfig类创建配置,不包含model参数 + qwen_config = QwenConfig(temperature=0.1) + config = qwen_config.as_dict() + + # 移除可能导致错误的参数 + if "model" in config: + del config["model"] + + # 使用QWEN_MAX类型 + return ModelPlatformType.QWEN, ModelType.QWEN_MAX, config + + else: + raise ValueError(f"不支持的模型: {model_name},支持的模型: gemini, openai, qwen") + + +# 创建角色代理工厂函数 +def create_role_agent(role: str, model_name: str = "gemini") -> ChatAgent: + """创建特定角色的代理 + + Args: + role: 角色名称 + model_name: 模型名称 (gemini, openai, qwen) + + Returns: + ChatAgent: 创建的角色代理 + """ + # 获取模型配置 + model_platform, model_type, model_config = get_model_config(model_name) + + # 获取角色的系统提示 + role_prompts = { + "market_data_analyst": MARKET_DATA_ANALYST_PROMPT, + "technical_analyst": TECHNICAL_ANALYST_PROMPT, + "fundamentals_analyst": FUNDAMENTALS_ANALYST_PROMPT, + "sentiment_analyst": SENTIMENT_ANALYST_PROMPT, + "valuation_analyst": VALUATION_ANALYST_PROMPT, + "researcher_bull": RESEARCHER_BULL_PROMPT, + "researcher_bear": RESEARCHER_BEAR_PROMPT, + "debate_room": DEBATE_ROOM_PROMPT, + "risk_manager": RISK_MANAGER_PROMPT, + "portfolio_manager": PORTFOLIO_MANAGER_PROMPT, + } + + if role not in role_prompts: + raise ValueError(f"未知角色: {role}") + + # 格式化角色名称为更友好的显示名称 + display_names = { + "market_data_analyst": "市场数据分析师", + "technical_analyst": "技术分析师", + "fundamentals_analyst": "基本面分析师", + "sentiment_analyst": "情绪分析师", + "valuation_analyst": "估值分析师", + "researcher_bull": "多头研究员", + "researcher_bear": "空头研究员", + "debate_room": "辩论室", + "risk_manager": "风险管理经理", + "portfolio_manager": "投资组合经理", + } + + display_name = display_names.get(role, role) + + # 创建模型 + model = ModelFactory.create( + model_platform=model_platform, + model_type=model_type, + model_config_dict=model_config + ) + + # 创建并返回代理 + return ChatAgent( + model=model, + system_message=role_prompts[role] + ) \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/tools/__init__.py b/community_usecase/a_share_investment_agent_camel/src/tools/__init__.py new file mode 100644 index 0000000..76b0881 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/tools/__init__.py @@ -0,0 +1,3 @@ +""" +工具模块 +""" \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/tools/api.py b/community_usecase/a_share_investment_agent_camel/src/tools/api.py new file mode 100644 index 0000000..845bbfc --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/tools/api.py @@ -0,0 +1,79 @@ +""" +数据API接口模块 +""" +import pandas as pd +import akshare as ak +from datetime import datetime +import logging +from typing import Optional + +logger = logging.getLogger(__name__) + +def get_price_data(ticker: str, start_date: str, end_date: str) -> Optional[pd.DataFrame]: + """ + 获取股票价格数据 + + Args: + ticker: 股票代码 + start_date: 开始日期 (YYYY-MM-DD) + end_date: 结束日期 (YYYY-MM-DD) + + Returns: + Optional[pd.DataFrame]: 股票价格数据 + """ + try: + logger.info(f"获取股票 {ticker} 的价格数据") + + # 转换日期格式 + start_date_fmt = datetime.strptime(start_date, "%Y-%m-%d").strftime("%Y%m%d") + end_date_fmt = datetime.strptime(end_date, "%Y-%m-%d").strftime("%Y%m%d") + + # 使用akshare获取A股历史数据 + df = ak.stock_zh_a_hist( + symbol=ticker, + period="daily", + start_date=start_date_fmt, + end_date=end_date_fmt, + adjust="qfq" # 前复权 + ) + + # 检查df的列数 + logger.info(f"原始数据列: {df.columns.tolist()}") + + # 调整列名映射以适应实际返回的数据 + column_mappings = { + '日期': 'date', + '股票代码': 'code', + '开盘': 'open', + '收盘': 'close', + '最高': 'high', + '最低': 'low', + '成交量': 'volume', + '成交额': 'amount', + '振幅': 'amplitude', + '涨跌幅': 'pct_change', + '涨跌额': 'change', + '换手率': 'turnover' + } + + # 根据实际列名重命名 + new_columns = [] + for col in df.columns: + if col in column_mappings: + new_columns.append(column_mappings[col]) + else: + # 保留原列名 + new_columns.append(col) + + # 应用新列名 + df.columns = new_columns + + # 将日期列转换为datetime类型 + df['date'] = pd.to_datetime(df['date']) + + logger.info(f"成功获取价格数据,共 {len(df)} 条记录") + return df + + except Exception as e: + logger.error(f"获取价格数据时发生错误: {str(e)}") + return None \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/tools/data_helper.py b/community_usecase/a_share_investment_agent_camel/src/tools/data_helper.py new file mode 100644 index 0000000..9b28a29 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/tools/data_helper.py @@ -0,0 +1,278 @@ +""" +数据辅助工具模块 +""" +import pandas as pd +import akshare as ak +from datetime import datetime, timedelta +import logging +from typing import Dict, Any, List, Optional + +logger = logging.getLogger(__name__) + + +def get_stock_data(ticker: str, start_date: str, end_date: str) -> pd.DataFrame: + """ + 获取股票历史数据 + + Args: + ticker: 股票代码 + start_date: 开始日期 (YYYY-MM-DD) + end_date: 结束日期 (YYYY-MM-DD) + + Returns: + pd.DataFrame: 股票历史数据 + """ + try: + logger.info(f"获取股票 {ticker} 的历史数据") + + # 转换日期格式 + start_date_fmt = datetime.strptime(start_date, "%Y-%m-%d").strftime("%Y%m%d") + end_date_fmt = datetime.strptime(end_date, "%Y-%m-%d").strftime("%Y%m%d") + + # 使用akshare获取A股历史数据 + df = ak.stock_zh_a_hist( + symbol=ticker, + period="daily", + start_date=start_date_fmt, + end_date=end_date_fmt, + adjust="qfq" # 前复权 + ) + + logger.info(f"成功获取历史数据,共 {len(df)} 条记录") + return df + + except Exception as e: + logger.error(f"获取历史数据时发生错误: {str(e)}") + return pd.DataFrame() + + +def get_fundamental_data(ticker: str) -> Dict[str, Any]: + """ + 获取股票基本面数据 + + Args: + ticker: 股票代码 + + Returns: + Dict[str, Any]: 基本面数据 + """ + try: + logger.info(f"获取股票 {ticker} 的基本面数据") + + # 获取财务指标数据 + financial_indicators = ak.stock_financial_analysis_indicator(symbol=ticker) + + # 获取利润表数据 + try: + # 根据akshare文档修正 + # stock参数格式应该为'sh'或'sz'+股票代码,而不是直接使用数字代码 + stock_prefix = 'sz' if ticker.startswith('0') or ticker.startswith('3') else 'sh' + formatted_ticker = f"{stock_prefix}{ticker}" + # 必须传入symbol参数,设置为"利润表" + income_statement = ak.stock_financial_report_sina(stock=formatted_ticker, symbol="利润表") + logger.info(f"成功获取{ticker}的利润表数据") + except Exception as e: + logger.error(f"获取利润表数据时出错: {str(e)}") + income_statement = pd.DataFrame() + + # 获取实时行情 + real_time_quote = ak.stock_zh_a_spot_em() + stock_quote = real_time_quote[real_time_quote['代码'] == ticker] + + # 记录实时行情字段名,帮助调试 + if not stock_quote.empty: + logger.info(f"实时行情数据字段: {stock_quote.columns.tolist()}") + + # 提取关键财务指标 + latest_indicators = {} + if not financial_indicators.empty: + latest_indicators = financial_indicators.iloc[-1].to_dict() + + # 提取关键利润表指标 + income_data = {} + if not income_statement.empty: + income_data = income_statement.iloc[-1].to_dict() + + # 股票基本信息 + stock_info = {} + if not stock_quote.empty: + stock_info = stock_quote.iloc[0].to_dict() + + logger.info(f"成功获取基本面数据") + + # 调整处理市场信息,确保字段名正确匹配 + market_summary = { + "name": stock_info.get("名称", ""), + "pe_ratio": stock_info.get("市盈率-动态", None), # 调整为实际字段名 + "pb_ratio": stock_info.get("市净率", None), + "market_cap": stock_info.get("总市值", None), + "industry": "" # 行情数据中没有行业信息 + } + + return { + "financial_indicators": latest_indicators, + "income_statement": income_data, + "stock_info": stock_info, + "summary": market_summary + } + + except Exception as e: + logger.error(f"获取基本面数据时发生错误: {str(e)}") + return { + "financial_indicators": {}, + "income_statement": {}, + "stock_info": {}, + "summary": { + "name": "", + "pe_ratio": None, + "pb_ratio": None, + "market_cap": None, + "industry": "" + }, + "error": str(e) + } + + +def get_stock_news(ticker: str, num_of_news: int = 5) -> List[Dict[str, Any]]: + """ + 获取股票相关新闻 + + Args: + ticker: 股票代码 + num_of_news: 获取的新闻数量 + + Returns: + List[Dict[str, Any]]: 新闻列表 + """ + try: + logger.info(f"获取股票 {ticker} 的新闻数据 (共{num_of_news}条)") + + # 获取股票名称 + real_time_quote = ak.stock_zh_a_spot_em() + stock_quote = real_time_quote[real_time_quote['代码'] == ticker] + stock_name = stock_quote.iloc[0]['名称'] if not stock_quote.empty else "" + + if not stock_name: + logger.warning(f"无法获取股票 {ticker} 的名称") + return [] + + # 获取财经新闻 + news_df = ak.stock_news_em(symbol=stock_name) + + # 记录新闻数据字段名,帮助调试 + if not news_df.empty: + logger.info(f"新闻数据字段: {news_df.columns.tolist()}") + + # 筛选最近的新闻 + recent_news = news_df.head(min(num_of_news, len(news_df))) + + news_list = [] + for _, row in recent_news.iterrows(): + news_list.append({ + "title": row.get("新闻标题", ""), + "content": row.get("新闻内容", ""), + "date": row.get("发布时间", ""), + "source": row.get("文章来源", "") # 调整为正确的字段名 + }) + + logger.info(f"成功获取 {len(news_list)} 条新闻") + return news_list + + except Exception as e: + logger.error(f"获取新闻数据时发生错误: {str(e)}") + return [] + + +def calculate_technical_indicators(df: pd.DataFrame) -> Dict[str, Any]: + """ + 计算技术指标 + + Args: + df: 股票历史数据DataFrame + + Returns: + Dict[str, Any]: 技术指标 + """ + try: + logger.info("计算技术指标") + + if df.empty: + logger.warning("没有历史数据用于计算技术指标") + return {} + + # 记录实际的列名,帮助调试 + logger.info(f"传入DataFrame的列名: {df.columns.tolist()}") + + # 确保列名符合预期 + price_column = None + volume_column = None + + # 逐个检查可能的列名 + for col_name in ["收盘", "close"]: + if col_name in df.columns: + price_column = col_name + break + + for col_name in ["成交量", "volume"]: + if col_name in df.columns: + volume_column = col_name + break + + if not price_column: + logger.error("找不到有效的价格列,当前列名: " + str(df.columns.tolist())) + return {"error": "找不到有效的价格列"} + + if not volume_column: + logger.warning("找不到有效的成交量列,将只计算价格相关指标") + + # 提取收盘价和成交量数据 + close_prices = df[price_column].values + volumes = df[volume_column].values if volume_column else None + + # 计算常用技术指标 + indicators = {} + + # 1. 移动平均线 + ma_windows = [5, 10, 20, 50, 200] + for window in ma_windows: + if len(close_prices) >= window: + ma = pd.Series(close_prices).rolling(window=window).mean().values + indicators[f"ma_{window}"] = ma.tolist() + + # 2. 相对强弱指数 (RSI) + if len(close_prices) >= 14: + delta = pd.Series(close_prices).diff() + gain = delta.clip(lower=0) + loss = -delta.clip(upper=0) + avg_gain = gain.rolling(window=14).mean() + avg_loss = loss.rolling(window=14).mean() + rs = avg_gain / avg_loss + rsi = 100 - (100 / (1 + rs)) + indicators["rsi"] = rsi.values.tolist() + + # 3. MACD (移动平均收敛/发散) + if len(close_prices) >= 26: + exp12 = pd.Series(close_prices).ewm(span=12, adjust=False).mean() + exp26 = pd.Series(close_prices).ewm(span=26, adjust=False).mean() + macd = exp12 - exp26 + signal = macd.ewm(span=9, adjust=False).mean() + indicators["macd"] = macd.values.tolist() + indicators["macd_signal"] = signal.values.tolist() + indicators["macd_histogram"] = (macd - signal).values.tolist() + + # 4. 布林带 + if len(close_prices) >= 20: + ma20 = pd.Series(close_prices).rolling(window=20).mean() + std20 = pd.Series(close_prices).rolling(window=20).std() + upper_band = ma20 + (std20 * 2) + lower_band = ma20 - (std20 * 2) + indicators["bollinger_ma"] = ma20.values.tolist() + indicators["bollinger_upper"] = upper_band.values.tolist() + indicators["bollinger_lower"] = lower_band.values.tolist() + + logger.info("成功计算技术指标") + return indicators + + except Exception as e: + logger.error(f"计算技术指标时发生错误: {str(e)}") + return {"error": str(e)} \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/utils/__init__.py b/community_usecase/a_share_investment_agent_camel/src/utils/__init__.py new file mode 100644 index 0000000..ea5dff9 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/utils/__init__.py @@ -0,0 +1,3 @@ +""" +实用工具模块 +""" \ No newline at end of file diff --git a/community_usecase/a_share_investment_agent_camel/src/utils/logging_utils.py b/community_usecase/a_share_investment_agent_camel/src/utils/logging_utils.py new file mode 100644 index 0000000..cd86603 --- /dev/null +++ b/community_usecase/a_share_investment_agent_camel/src/utils/logging_utils.py @@ -0,0 +1,103 @@ +""" +日志工具模块 +""" +import logging +import os +from datetime import datetime +from typing import Optional + + +def setup_logger(name: str, log_level: int = logging.INFO, log_dir: str = "logs") -> logging.Logger: + """ + 设置日志记录器 + + Args: + name: 日志记录器名称 + log_level: 日志级别 + log_dir: 日志目录 + + Returns: + logging.Logger: 配置好的日志记录器 + """ + # 确保日志目录存在 + os.makedirs(log_dir, exist_ok=True) + + # 获取日志记录器 + logger = logging.getLogger(name) + + # 如果已配置,直接返回 + if logger.handlers: + return logger + + # 设置日志级别 + logger.setLevel(log_level) + + # 创建文件处理器 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + log_file = f"{log_dir}/{name}_{timestamp}.log" + file_handler = logging.FileHandler(log_file, encoding="utf-8") + file_handler.setLevel(log_level) + + # 创建控制台处理器 + console_handler = logging.StreamHandler() + console_handler.setLevel(log_level) + + # 设置格式化器 + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + file_handler.setFormatter(formatter) + console_handler.setFormatter(formatter) + + # 添加处理器 + logger.addHandler(file_handler) + logger.addHandler(console_handler) + + return logger + + +class OutputLogger: + """ + 输出日志记录器,用于重定向标准输出 + """ + def __init__(self, filename: Optional[str] = None): + """ + 初始化输出日志记录器 + + Args: + filename: 日志文件名,默认为自动生成 + """ + # 确保日志目录存在 + os.makedirs("logs", exist_ok=True) + + # 设置日志文件 + if not filename: + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"logs/output_{timestamp}.log" + + self.terminal = open(filename, "a", encoding="utf-8") + self.terminal.write(f"=== 日志开始于 {datetime.now()} ===\n") + self.stdout = None + + def write(self, message): + """写入消息到终端和日志文件""" + if self.stdout: + self.stdout.write(message) + self.terminal.write(message) + self.terminal.flush() + + def flush(self): + """刷新日志""" + if self.stdout: + self.stdout.flush() + self.terminal.flush() + + def close(self): + """关闭日志文件""" + self.terminal.write(f"=== 日志结束于 {datetime.now()} ===\n") + self.terminal.close() + + def __del__(self): + """析构函数,确保关闭日志文件""" + try: + self.close() + except: + pass \ No newline at end of file