UNPKG

mcp-invest-assistant

Version:
110 lines 5.49 kB
import { z } from "zod"; import getIndexFundamentalData from "../services/getIndexFundamentalData.js"; import logger from "../utils/logger.js"; import percentRank from '../utils/percentRank.js'; import { MARKET } from '../config/index.js'; // 获取最新指数温度 export function registerGetIndexTemperature(server) { server.tool('mcp_get_index_temperature', '定投指数ETF买入卖出时机参考,获取最新指数温度,指数温度越高,说明股市越火爆,风险越大,上涨空间越小。温度越低,说明股市越冷清,风险则越小,上涨空间更大。温度在0-10度之间,代表着股票市场正值寒冬、萎靡不振,这是时候是最佳的买入时机。10-20°之间,代表股票市场开始回暖,蓄势待发,仍然是买入良机。20-30°之间,股票市场欣欣向荣,可以继续买入。30-40℃之间,可以继续持有,提防风险。40-50℃之间,考虑逐步卖出。>90℃,随时有灰飞烟灭的风险。-100表示获取失败', { 'marketCn': z.string().describe(`市场,必传,可选值:${Object.keys(MARKET).join('、')}`), 'stockCodes': z.array(z.string()).describe('指数代码数组,必传,数组长度大于0,格式如下:["000016"]') }, async ({ stockCodes, marketCn }) => { const token = process.env.TOKEN || ''; logger.info(`marketCn: ${marketCn}`); if (!token) { return { content: [ { type: "text", text: "参数异常:必须要传入token" } ] }; } if (!MARKET[marketCn]) { return { content: [ { type: "text", text: "参数异常:必须要传入市场类型" } ] }; } if (!Array.isArray(stockCodes) || stockCodes.length === 0) { return { content: [ { type: "text", text: "参数异常:必须要传入指数代码数组" } ] }; } try { const batchPromise = await Promise.all(stockCodes.map((stockCode) => getIndexFundamentalData({ stockCodes: [stockCode], market: MARKET[marketCn], token }))); const fundamentalDataList = batchPromise.map((res) => res.data); logger.info(`fundamentalDataList: ${JSON.stringify(fundamentalDataList)}`); const heatList = fundamentalDataList.map((fundamentalData, index) => { if (fundamentalData.length === 0) { return { code: stockCodes[index], temperature: '', error: '获取基本面数据失败,可能原因:提供的股票代码非指数,或该指数在理杏仁网站拿不到数据,可提issue' }; } // 第一步:获取当前最新数据(数组第一个元素) const currentData = fundamentalData[0]; const currentPE = currentData['pe_ttm.mcw']; const currentPB = currentData['pb.mcw']; const stockCode = currentData['stockCode']; // 第二步:提取历史 PE 和 PB 数据(近 5-10 年) const peList = fundamentalData.map((item) => item['pe_ttm.mcw']); const pbList = fundamentalData.map((item) => item['pb.mcw']); // 第三步:计算 PE 百分位 // 公式:(当前PE排名 - 1) / (总数据量 - 1) * 100% const pePercentile = percentRank(peList, currentPE); // 第四步:计算 PB 百分位 // 公式:(当前PB排名 - 1) / (总数据量 - 1) * 100% const pbPercentile = percentRank(pbList, currentPB); // 第五步:合并计算温度(双指标平均法) // 温度 = (PE百分位 + PB百分位) / 2 const temperature = ((pePercentile + pbPercentile) / 2).toFixed(2); return { code: stockCode, temperature, error: '' }; }); logger.info(`get heatList detail: ${JSON.stringify(heatList)}`); const temperature = heatList.map((item) => { const { code, error, temperature } = item; if (error) { return `${code}获取报错:${error}`; } return `${code}的指数温度是${temperature}`; }).join(','); logger.info(`response info temperature: ${temperature}`); return { content: [ { type: "text", text: temperature } ] }; } catch (e) { logger.info(`[getIndexTemperature]Error: ${e.message || '未知错误'}`); return { content: [ { type: "text", text: "获取指数温度失败" } ] }; } }); } //# sourceMappingURL=getIndexTemperature.js.map