添加基于OWL/Camel框架的A股投资代理系统1. 实现多代理A股投资分析系统,基于OWL和Camel框架2. 支持10个专业角色代理协作完成投资分析3. 提供多模型支持(Gemini、OpenAI、Qwen)和灵活配置4. 配置完整的数据处理流水线,从数据收集到决策5. 添加中英文双语文档支持该系统通过多代理协作体系,实现更全面、专业的A股投资决策,为用户提供辩论式、多角度的投资分析和建议。

This commit is contained in:
RonaldJEN 2025-03-31 13:32:52 +08:00
parent df2b688e2d
commit bb3046da62
29 changed files with 4993 additions and 0 deletions

View File

@ -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=

View File

@ -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"]

View File

@ -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.

View File

@ -0,0 +1,390 @@
<h1 align="center">
🦉A股投资代理系统
</h1>
<div align="center">
<h4 align="center">
<div align="center">
基于OWL/camel框架的多代理A股投资分析系统使用Camel框架实现。
</div>
[English](README_EN.md) |
[中文](README.md) |
[致谢](#-致谢) |
[示例运行结果](#-示例运行结果)
</h4>
</div>
<div align="center">
<img src="screenshots/system_architecture.png" alt="系统架构图" width="700">
</div>
## 📋 项目简介
本项目是一个基于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) - 开源多智能体协作框架
感谢所有原作者的贡献和启发,为本项目提供了坚实的基础。

View File

@ -0,0 +1,391 @@
<h1 align="center">
🦉A-Share Investment Agent System
</h1>
<div align="center">
<h4 align="center">
<div align="center">
A multi-agent A-share investment analysis system based on OWL/camel framework, implemented using the Camel framework.
</div>
[English](README_EN.md) |
[中文](README.md) |
[Acknowledgements](#-acknowledgements) |
[Example Results](#-example-results)
</h4>
</div>
<div align="center">
<img src="screenshots/system_architecture.png" alt="System Architecture Diagram" width="700">
</div>
## 📋 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.

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

View File

@ -0,0 +1,5 @@
"""
基于Camel框架的A股投资代理系统
"""
__version__ = "0.1.0"

View File

@ -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'
]

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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. 主要技术指标分析RSIMACD布林带
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)
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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. 当前市场估值如PEPBPS等
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

View File

@ -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()

View File

@ -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

View File

@ -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]
)

View File

@ -0,0 +1,3 @@
"""
工具模块
"""

View File

@ -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

View File

@ -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)}

View File

@ -0,0 +1,3 @@
"""
实用工具模块
"""

View File

@ -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