在本文中,我们将详细介绍如何使用 Node.js、Express 和 OpenAI API 构建一个专业级的股票报告生成器。该应用程序将获取股票数据,执行情感和行业分析,最终生成一份全面的投资报告。此外,您可以访问我们的在线网站进行体验:Stock Report Pro。
目录
项目概述
我们的目标是创建一个 API 端点,给定股票代码后,生成一份详细的投资报告。报告将包括:
- 公司概览
- 财务表现
- 管理层讨论与分析(MDA)
- 情感分析
- 行业分析
- 风险与机会
- 投资建议
我们将利用外部 API 获取股票数据,并使用 OpenAI API 执行高级分析。
环境设置
前提条件
- 安装了 Node.js
- 拥有 OpenAI API 密钥(如果没有,请在 OpenAI 注册)
初始化项目
创建新目录并初始化 Node.js 项目:
mkdir stock-report-generator
cd stock-report-generator
npm init -y
安装所需依赖项:
npm install express axios
创建项目结构:
mkdir routes utils data
touch app.js routes/report.js utils/helpers.js
构建 Express 服务器
设置 app.js
// app.js
const express = require('express');
const reportRouter = require('./routes/report');
const app = express();
app.use(express.json());
app.use('/api', reportRouter);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
- Express 初始化:导入 Express 并初始化应用程序。
- 中间件:使用
express.json()
解析 JSON 请求体。 - 路由:在
/api
挂载报告路由。 - 服务器监听:在指定端口启动服务器。
数据获取与处理
创建辅助函数
在 utils/helpers.js
中,我们将定义用于数据获取和处理的实用函数。
// utils/helpers.js
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const BASE_URL = 'https://your-data-api.com'; // 请替换为实际数据 API
/**
* 获取上年度的起始和结束日期。
* @returns {object} - 包含 `start` 和 `end` 日期的对象。
*/
function getLastYearDates() {
const now = new Date();
const end = now.toISOString().split('T')[0];
now.setFullYear(now.getFullYear() - 1);
const start = now.toISOString().split('T')[0];
return { start, end };
}
/**
* 将对象转换为字符串,排除指定的键。
* @param {object} obj - 要转换的对象。
* @param {string[]} excludeKeys - 要排除的键。
* @returns {string} - 转换后的字符串。
*/
function objectToString(obj, excludeKeys = []) {
return Object.entries(obj)
.filter(([key]) => !excludeKeys.includes(key))
.map(([key, value]) => `${key}: ${value}`)
.join('\n');
}
/**
* 从指定的端点获取数据。
* @param {string} endpoint - API 端点。
* @param {object} params - 查询参数。
* @param {any} defaultValue - 请求失败时的默认值。
* @returns {Promise<any>} - 获取的数据或默认值。
*/
async function fetchData(endpoint, params = {}, defaultValue = null) {
try {
const response = await axios.get(`${BASE_URL}${endpoint}`, { params });
return response.data || defaultValue;
} catch (error) {
console.error(`Error fetching data from ${endpoint}:`, error.message);
return defaultValue;
}
}
/**
* 从本地 JSON 文件读取数据。
* @param {string} fileName - JSON 文件名。
* @returns {any} - 读取的数据。
*/
function readLocalJson(fileName) {
const filePath = path.join(__dirname, '../data', fileName);
const data = fs.readFileSync(filePath, 'utf-8');
return JSON.parse(data);
}
module.exports = {
fetchData,
objectToString,
getLastYearDates,
readLocalJson,
};
- getLastYearDates:获取上一年的起始和结束日期。
- objectToString:将对象转换为可读字符串,便于日志记录或准备提示信息。
- fetchData:处理对外部 API 的 GET 请求,返回数据或默认值。
- readLocalJson:从本地 JSON 文件读取数据。
实现股票数据获取
在 routes/report.js
中,我们将定义获取股票数据的函数。
// routes/report.js
const express = require('express');
const {
fetchData,
objectToString,
getLastYearDates,
readLocalJson,
} = require('../utils/helpers');
const router = express.Router();
/**
* 获取股票数据,包括历史价格、财务信息、MDA 和主营业务信息。
* @param {string} ticker - 股票代码。
* @returns {Promise<object>} - 包含所有获取的数据的对象。
*/
async function fetchStockData(ticker) {
try {
const dates = getLastYearDates();
const [historicalData, financialData, mdaData, businessData] = await Promise.all([
fetchData('/stock_zh_a_hist', {
symbol: ticker,
period: 'weekly',
start_date: dates.start,
end_date: dates.end,
}, []),
fetchData('/stock_financial_benefit_ths', {
code: ticker,
indicator: '按年度',
}, [{}]),
fetchData('/stock_mda', { code: ticker }, []),
fetchData('/stock_main_business', { code: ticker }, []),
]);
const hist = historicalData[historicalData.length - 1];
const currentPrice = (hist ? hist['开盘'] : 'N/A') + ' CNY';
const historical = historicalData
.map((item) => objectToString(item, ['股票代码']))
.join('\n----------\n');
const zsfzJson = readLocalJson('zcfz.json');
const balanceSheet = objectToString(zsfzJson.find((item) => item['股票代码'] === ticker));
const financial = objectToString(financialData[0]);
const mda = mdaData.map(item => `${item['报告期']}\n${item['内容']}`).join('\n-----------\n');
const mainBusiness = businessData.map(item =>
`主营业务: ${item['主营业务']}\n产品名称: ${item['产品名称']}\n产品类型: ${item['产品类型']}\n经营范围: ${item['经营范围']}`
).join('\n-----------\n');
return { currentPrice, historical, balanceSheet, mda, mainBusiness, financial };
} catch (error) {
console.error('Error fetching stock data:', error.message);
throw error;
}
}
- fetchStockData:并发地获取多个数据,并处理结果。
- 数据处理:格式化和转换数据,便于后续使用。
- 错误处理:记录错误并重新抛出,以便在更高层级处理。
与 OpenAI API 集成
OpenAI API 交互函数
const axios = require('axios');
const OPENAI_API_KEY = 'your-openai-api-key'; // 请替换为您的 OpenAI API 密钥
/**
* 与 OpenAI API 交互,获取完成结果。
* @param {array} messages - 消息数组,包含系统提示和用户消息。
* @returns {Promise<string>} - AI 的响应。
*/
async function analyzeWithOpenAI(messages) {
try {
const headers = {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json',
};
const requestData = {
model: 'gpt-4',
temperature: 0.3,
messages: messages,
};
const response = await axios.post(
'https://api.openai.com/v1/chat/completions',
requestData,
{ headers }
);
return response.data.choices[0].message.content.trim();
} catch (error) {
console.error('Error fetching analysis from OpenAI:', error.message);
throw error;
}
}
- analyzeWithOpenAI:处理与 OpenAI API 的通信。
- API 配置:设置模型、温度等参数。
- 错误处理:记录错误并抛出。
执行情感分析
/**
* 使用 OpenAI API 对新闻文章执行情感分析。
* @param {string} ticker - 股票代码。
* @returns {Promise<string>} - 情感分析摘要。
*/
async function performSentimentAnalysis(ticker) {
const systemPrompt = `You are a sentiment analysis assistant. Analyze the sentiment of the given news articles for ${ticker} and provide a summary of the overall sentiment and any notable changes over time. Be measured and discerning. You are a skeptical investor.`;
const tickerNewsResponse = await fetchData('/stock_news_specific', { code: ticker }, []);
const newsText = tickerNewsResponse
.map(item => `${item['文章来源']} Date: ${item['发布时间']}\n${item['新闻内容']}`)
.join('\n----------\n');
const messages = [
{ role: 'system', content: systemPrompt },
{
role: 'user',
content: `News articles for ${ticker}:\n${newsText || 'N/A'}\n----\nProvide a summary of the overall sentiment and any notable changes over time.`,
},
];
return await analyzeWithOpenAI(messages);
}
- performSentimentAnalysis:构建提示信息,调用 OpenAI API 进行分析。
- 提示信息设计:确保提示信息清晰,包含必要的上下文。
分析行业
/**
* 使用 OpenAI API 分析行业信息。
* @param {string} industry - 行业名称。
* @returns {Promise<string>} - 行业分析摘要。
*/
async function analyzeIndustry(industry) {
const industryNewsResponse = await fetchData('/stock_news_specific', { code: industry }, []);
const industryNewsArticles = industryNewsResponse
.map(item => `${item['文章来源']} Date: ${item['发布时间']}\n${item['新闻内容']}`)
.join('\n----------\n');
const systemPrompt = `You are an industry analysis assistant. Provide an analysis of the ${industry} industry, including trends, growth prospects, regulatory changes, and competitive landscape.
Consider the following recent news articles relevant to the ${industry} industry:
${industryNewsArticles || 'N/A'}
Be measured and discerning. Truly think about the positives and negatives of the industry. Be sure of your analysis. You are a skeptical investor.`;
const messages = [
{ role: 'system', content: systemPrompt },
{
role: 'user',
content: `Provide an analysis of the ${industry} industry, taking into account the recent news articles provided.`,
},
];
return await analyzeWithOpenAI(messages);
}
- analyzeIndustry:类似于情感分析,但侧重于更广泛的行业背景。
生成最终报告
编译所有数据
/**
* 生成最终的投资报告。
* @param {string} ticker - 股票代码。
* @param {object} data - 收集的数据和分析结果。
* @returns {Promise<string>} - 最终报告。
*/
async function provideFinalAnalysis(ticker, data) {
const {
sentimentAnalysis,
industryAnalysis,
balanceSheet,
mda,
mainBusiness,
financial,
currentPrice,
historical,
} = data;
const systemPrompt = `You are a financial analyst tasked with providing a comprehensive investment recommendation for the stock ${ticker}. Your analysis should utilize the available data and aim for a length of approximately 1500 to 3000 words. Focus on the following key aspects:
1. **Company Overview**: Provide a brief overview of the company, including its business model, core products/services, and market position. Refer to the provided main business data.
2. **Financial Performance**:
- Analyze key financial metrics based on the available data, including the current price (${currentPrice}), historical data, financial data, and balance sheet.
- Highlight any significant financial trends and changes.
3. **Management Discussion and Analysis (MDA)**:
- Summarize the management's insights and future outlook based on the provided MDA data.
4. **Sentiment Analysis**:
- Summarize public sentiment based on recent news articles.
- Include insights into any significant events or announcements that could influence market perception.
5. **Industry Analysis**:
- Outline current industry trends and major players based on the available industry data.
- Identify any regulatory or technological factors affecting the industry.
6. **Macroeconomic Factors**:
- Analyze relevant macroeconomic indicators that may impact the company's performance.
7. **Risks and Opportunities**:
- Identify key risks facing the company, including market, operational, and regulatory risks.
- Highlight potential growth opportunities and strategic initiatives.
8. **Investment Recommendation**:
- Based on your analysis, provide a clear recommendation (buy, hold, or sell) for the stock.
- Support your recommendation with a well-reasoned rationale, considering both positives and negatives.
Please write in a professional tone, using precise language and relevant financial terminology. Respond in Chinese.`;
const userPrompt = `Ticker: ${ticker}\n
1. **Sentiment Analysis**:\n${sentimentAnalysis}\n
2. **Industry Analysis**:\n${industryAnalysis}\n
3. **Financial Data**:\nCurrent Price: ${currentPrice}\nHistorical Data: ${historical}\nBalance Sheet: ${balanceSheet}\nFinancial Data: ${financial}\n
4. **Management Discussion and Analysis (MDA)**:\n${mda}\n
5. **Main Business**:\n${mainBusiness}\n\n
Based on the provided data and analyses, please provide a comprehensive investment analysis and recommendation for ${ticker}. Ensure the report length is controlled between 1500 and 3000 words, ensuring clarity and conciseness in your arguments.`;
const messages = [
{ role: 'system', content: systemPrompt },
{
role: 'user',
content: userPrompt,
},
];
return await analyzeWithOpenAI(messages);
}
- provideFinalAnalysis:精心设计提示信息,包含所有收集的数据。
- 保持提示信息完整性:按照要求,不破坏原有的提示信息部分。
测试应用程序
定义路由处理程序
在 routes/report.js
中添加路由处理程序:
router.post('/generate-report', async (req, res) => {
const { ticker } = req.body;
if (!ticker) {
return res.status(400).json({ error: 'Ticker symbol is required.' });
}
try {
const dataJson = readLocalJson('stock.json');
const stockInfo = dataJson.find((item) => item.symbol === ticker);
if (!stockInfo) {
return res.status(404).json({ error: 'Stock information not found.' });
}
const { industry } = stockInfo;
// 获取股票数据
const stockData = await fetchStockData(ticker);
// 执行分析
const [sentimentAnalysis, industryAnalysis] = await Promise.all([
performSentimentAnalysis(ticker),
analyzeIndustry(industry || 'the industry'),
]);
// 生成最终报告
const finalAnalysis = await provideFinalAnalysis(ticker, {
sentimentAnalysis,
industryAnalysis,
...stockData,
});
res.json({ report: finalAnalysis });
} catch (error) {
console.error('Error generating report:', error.message);
res.status(500).json({ error: 'Failed to generate report.' });
}
});
module.exports = router;
- 输入验证:检查是否提供了股票代码。
- 数据收集:并发地获取股票数据和执行分析。
- 错误处理:记录错误并在失败时发送 500 响应。
启动服务器
确保您的 app.js
和 routes/report.js
已正确设置,然后启动服务器:
node app.js
发送测试请求
使用 curl
或 Postman 发送 POST 请求:
curl -X POST http://localhost:3000/api/generate-report \
-H 'Content-Type: application/json' \
-d '{"ticker": "AAPL"}'
- 响应:服务器应返回包含生成报告的 JSON 对象。
总结
我们构建了一个高质量的股票报告生成器,具备以下功能:
- 从外部 API 获取和处理股票数据。
- 使用 OpenAI API 执行高级分析。
- 生成全面的投资报告,并确保提示信息部分的完整性。
在开发过程中,我们专注于编写专业、可维护的代码,并提供了详细的解释和注释。
实施的最佳实践
- 模块化代码结构:函数模块化,增强可重用性和清晰度。
- 异步操作:使用
async/await
和Promise.all
实现高效的异步编程。 - 错误处理:全面的
try-catch
块和错误消息。 - API 抽象:将 API 交互逻辑分离,便于维护。
- 提示工程:精心设计 OpenAI API 的提示信息,获得期望的输出。
- 输入验证:检查必要的输入参数,防止不必要的错误。
- 代码文档:添加 JSDoc 注释,便于理解和维护。
下一步
- 缓存:实现缓存机制,减少重复的 API 调用。
- 身份验证:使用身份验证和速率限制来保护 API 端点。
- 前端开发:为用户构建前端界面,与应用程序交互。
- 其他分析:加入技术分析或其他金融模型。
参考资料
免责声明:本应用程序仅用于教育目的。确保遵守所有 API 服务条款,并妥善处理敏感数据。