stock_data
Retrieve historical stock data for A-shares, U.S. stocks, Hong Kong stocks, forex, futures, funds, bonds, and options. Specify codes, market types, date ranges, and technical indicators for comprehensive analysis.
Instructions
获取指定股票的历史行情数据,支持A股、美股、港股、外汇、期货、基金、债券逆回购、可转债、期权
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| code | Yes | 股票代码,如'000001.SZ'表示平安银行(A股),'AAPL'表示苹果(美股),'00700.HK'表示腾讯(港股),'USDCNH.FXCM'表示美元人民币(外汇),'CU2501.SHF'表示铜期货,'159919.SZ'表示沪深300ETF(基金),'204001.SH'表示GC001国债逆回购,'113008.SH'表示可转债,'10001313.SH'表示期权合约 | |
| end_date | No | 结束日期,格式为YYYYMMDD,如'20230131' | |
| indicators | No | 需要计算的技术指标,多个指标用空格分隔。支持的指标:macd(MACD指标)、rsi(相对强弱指标)、kdj(随机指标)、boll(布林带)、ma(均线指标)。必须明确指定参数,例如:'macd(12,26,9) rsi(14) kdj(9,3,3) boll(20,2) ma(10)' | |
| market_type | Yes | 市场类型(必需),可选值:cn(A股),us(美股),hk(港股),fx(外汇),futures(期货),fund(基金),repo(债券逆回购),convertible_bond(可转债),options(期权) | |
| start_date | No | 起始日期,格式为YYYYMMDD,如'20230101' |
Input Schema (JSON Schema)
{
"properties": {
"code": {
"description": "股票代码,如'000001.SZ'表示平安银行(A股),'AAPL'表示苹果(美股),'00700.HK'表示腾讯(港股),'USDCNH.FXCM'表示美元人民币(外汇),'CU2501.SHF'表示铜期货,'159919.SZ'表示沪深300ETF(基金),'204001.SH'表示GC001国债逆回购,'113008.SH'表示可转债,'10001313.SH'表示期权合约",
"type": "string"
},
"end_date": {
"description": "结束日期,格式为YYYYMMDD,如'20230131'",
"type": "string"
},
"indicators": {
"description": "需要计算的技术指标,多个指标用空格分隔。支持的指标:macd(MACD指标)、rsi(相对强弱指标)、kdj(随机指标)、boll(布林带)、ma(均线指标)。必须明确指定参数,例如:'macd(12,26,9) rsi(14) kdj(9,3,3) boll(20,2) ma(10)'",
"type": "string"
},
"market_type": {
"description": "市场类型(必需),可选值:cn(A股),us(美股),hk(港股),fx(外汇),futures(期货),fund(基金),repo(债券逆回购),convertible_bond(可转债),options(期权)",
"type": "string"
},
"start_date": {
"description": "起始日期,格式为YYYYMMDD,如'20230101'",
"type": "string"
}
},
"required": [
"code",
"market_type"
],
"type": "object"
}
Implementation Reference
- src/tools/stockData.ts:45-1078 (handler)The core handler function for the 'stock_data' tool. Handles input validation, fetches historical market data from Tushare API (for stocks, funds, etc.) or Binance (for crypto), computes technical indicators (MACD, RSI, KDJ, BOLL, MA) if requested, applies adjustments, formats output as a Markdown table with explanations.async run(args: { code: string; market_type: string; start_date?: string; end_date?: string; indicators?: string }) { try { // 添加调试日志 console.log('接收到的参数:', args); // 检查market_type参数 if (!args.market_type) { throw new Error('请指定market_type参数:cn(A股)、us(美股)、hk(港股)、fx(外汇)、futures(期货)、fund(基金)、repo(债券逆回购)、convertible_bond(可转债)、options(期权)'); } const marketType = args.market_type.trim().toLowerCase(); console.log(`使用的市场类型: ${marketType}`); console.log(`使用Tushare API获取${marketType}市场股票${args.code}的行情数据`); // 解析技术指标参数 const requestedIndicators = args.indicators ? args.indicators.trim().split(/\s+/) : []; console.log('请求的技术指标:', requestedIndicators); // 使用全局配置中的Tushare API设置 const TUSHARE_API_KEY = TUSHARE_CONFIG.API_TOKEN; const TUSHARE_API_URL = TUSHARE_CONFIG.API_URL; // 默认参数设置 const today = new Date(); const defaultEndDate = today.toISOString().slice(0, 10).replace(/-/g, ''); const oneMonthAgo = new Date(); oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1); const defaultStartDate = oneMonthAgo.toISOString().slice(0, 10).replace(/-/g, ''); // 用户请求的时间范围 const userStartDate = args.start_date || defaultStartDate; const userEndDate = args.end_date || defaultEndDate; // 如果有技术指标请求,计算需要的历史数据并扩展获取范围 let actualStartDate = userStartDate; let actualEndDate = userEndDate; if (requestedIndicators.length > 0) { const requiredDays = calculateRequiredDays(requestedIndicators); actualStartDate = calculateExtendedStartDate(userStartDate, requiredDays); console.log(`技术指标需要${requiredDays}天历史数据,扩展开始日期从 ${userStartDate} 到 ${actualStartDate}`); } // 验证市场类型 const validMarkets = ['cn', 'us', 'hk', 'fx', 'futures', 'fund', 'repo', 'convertible_bond', 'options', 'crypto']; if (!validMarkets.includes(marketType)) { throw new Error(`不支持的市场类型: ${marketType}。支持的类型有: ${validMarkets.join(', ')}`); } // 加密货币市场(Binance)分支: if (marketType === 'crypto') { console.log(`使用 Binance 获取加密资产 ${args.code} 的逐日K线 (OHLCV)`); // 日期与符号解析 const toYMD = (d: Date): string => { const y = d.getUTCFullYear(); const m = String(d.getUTCMonth() + 1).padStart(2, '0'); const day = String(d.getUTCDate()).padStart(2, '0'); return `${y}${m}${day}`; }; const ymdToStartMs = (s: string): number => Date.UTC(parseInt(s.slice(0,4)), parseInt(s.slice(4,6)) - 1, parseInt(s.slice(6,8)), 0, 0, 0, 0); const ymdToEndMs = (s: string): number => Date.UTC(parseInt(s.slice(0,4)), parseInt(s.slice(4,6)) - 1, parseInt(s.slice(6,8)), 23, 59, 59, 999); const idToTicker: Record<string, string> = { 'bitcoin': 'BTC', 'ethereum': 'ETH', 'tether': 'USDT', 'usd-coin': 'USDC', 'solana': 'SOL', 'binancecoin': 'BNB', 'ripple': 'XRP', 'cardano': 'ADA', 'polkadot': 'DOT', 'chainlink': 'LINK', 'litecoin': 'LTC', 'shiba-inu': 'SHIB', 'tron': 'TRX', 'toncoin': 'TON', 'bitcoin-cash': 'BCH', 'ethereum-classic': 'ETC' }; const parseBinanceSymbol = (raw: string): string => { const trimmed = raw.trim(); const upper = trimmed.toUpperCase(); const validQuotes = new Set(['USDT','USDC','FDUSD','TUSD','BUSD','BTC','ETH']); // 已经是诸如 BTCUSDT if (!upper.includes('-') && !upper.includes('/') && !upper.includes('.')) { return upper; } // 支持 TICKER-QUOTE / TICKER/QUOTE / id.vs let base = ''; let quote = ''; if (upper.includes('-') || upper.includes('/')) { const sep = upper.includes('-') ? '-' : '/'; const [b, q] = upper.split(sep); base = b; quote = q; } else if (upper.includes('.')) { const [id, vs] = upper.split('.'); base = idToTicker[id.toLowerCase()] || id; // id -> ticker quote = vs; } if (quote === 'USD') quote = 'USDT'; if (!validQuotes.has(quote)) { throw new Error(`不支持的报价资产: ${quote}。支持: ${Array.from(validQuotes).join(', ')}`); } return `${base}${quote}`; }; const symbol = parseBinanceSymbol(args.code); const startYmd = requestedIndicators.length > 0 ? actualStartDate : userStartDate; let startMs = ymdToStartMs(startYmd); const endMs = ymdToEndMs(userEndDate); const allKlines: any[] = []; let pageIndex = 0; const maxPages = 100; // 安全上限,防止极端情况下的无限循环 while (startMs < endMs && pageIndex < maxPages) { const url = `https://api.binance.com/api/v3/klines?symbol=${encodeURIComponent(symbol)}&interval=1d&startTime=${startMs}&endTime=${endMs}&limit=1000`; console.log(`Binance Klines URL[${pageIndex + 1}]:`, url); const resp = await fetch(url); if (!resp.ok) { try { const contentType = resp.headers.get('content-type') || ''; if (contentType.includes('application/json')) { const errJson: any = await resp.json(); const errMsg = errJson?.msg || `HTTP ${resp.status}`; if (Number(errJson?.code) === -1121 || /invalid symbol/i.test(String(errMsg))) { throw new Error(`Binance 无效交易对: ${symbol}。该币对在 Binance 不存在或已下线,请更换有效币对(例如:BTCUSDT、ETHUSDT、SOLUSDT)。也支持 BTC-USDT、BTC/USDT、或 coinid.USDT 写法。`); } throw new Error(`Binance K线请求失败: ${resp.status} - ${errMsg}`); } else { const text = await resp.text(); throw new Error(`Binance K线请求失败: ${resp.status}${text ? ` - ${text}` : ''}`); } } catch (e) { if (e instanceof Error) throw e; throw new Error(`Binance K线请求失败: ${resp.status}`); } } const klines: any[] = await resp.json(); if (!Array.isArray(klines)) throw new Error('Binance 返回的 K 线数据格式异常'); if (klines.length === 0) break; allKlines.push(...klines); const lastOpenTime = Number(klines[klines.length - 1][0]); if (!(lastOpenTime > startMs)) break; // 保护,避免相同时间导致死循环 startMs = lastOpenTime + 1; // 下一页从最后一根K线的下一毫秒开始 pageIndex += 1; if (klines.length < 1000) break; // 已取完 } let stockData = allKlines.map(row => { const openTime = Number(row[0]); const d = toYMD(new Date(openTime)); return { trade_date: d, open: Number(row[1]), high: Number(row[2]), low: Number(row[3]), close: Number(row[4]), vol: Number(row[5]) } as Record<string, any>; }); // 若未请求技术指标,才在此处严格按用户区间过滤; // 若请求了技术指标,需保留扩展区间用于计算,稍后再截回用户区间 if (requestedIndicators.length === 0) { stockData = stockData.filter(r => r.trade_date >= userStartDate && r.trade_date <= userEndDate); } stockData.sort((a, b) => b.trade_date.localeCompare(a.trade_date)); console.log(`Binance 分页返回 共${allKlines.length} 根K线,过滤后 ${stockData.length} 条记录`); // 计算技术指标 let indicators: Record<string, any> = {}; if (requestedIndicators.length > 0) { let closes: number[] = stockData.map(d => parseFloat(d.close)).reverse(); let highs: number[] = stockData.map(d => parseFloat(d.high)).reverse(); let lows: number[] = stockData.map(d => parseFloat(d.low)).reverse(); for (const indicator of requestedIndicators) { try { const { name, params } = parseIndicatorParams(indicator); switch (name) { case 'macd': if (params.length !== 3) throw new Error('MACD指标需要3个参数,格式:macd(快线,慢线,信号线)'); indicators.macd = calculateMACD(closes, params[0], params[1], params[2]); break; case 'rsi': if (params.length !== 1) throw new Error('RSI指标需要1个参数,格式:rsi(周期)'); indicators.rsi = calculateRSI(closes, params[0]); break; case 'kdj': if (params.length !== 3) throw new Error('KDJ指标需要3个参数,格式:kdj(9,3,3)'); indicators.kdj = calculateKDJ(highs, lows, closes, params[0], params[1], params[2]); break; case 'boll': if (params.length !== 2) throw new Error('布林带指标需要2个参数,格式:boll(周期,标准差倍数)'); indicators.boll = calculateBOLL(closes, params[0], params[1]); break; case 'ma': if (params.length !== 1) throw new Error('移动平均线需要1个参数,格式:ma(周期)'); indicators[`ma${params[0]}`] = calculateSMA(closes, params[0]); break; default: throw new Error(`不支持的技术指标: ${name}`); } } catch (e) { console.error(`解析技术指标 ${indicator} 时出错:`, e); throw new Error(`技术指标参数错误: ${indicator}`); } } // 指标逆序以匹配最新在前 Object.keys(indicators).forEach(key => { if (typeof indicators[key] === 'object' && indicators[key] !== null) { if (Array.isArray(indicators[key])) { indicators[key] = indicators[key].reverse(); } else { Object.keys(indicators[key]).forEach(subKey => { if (Array.isArray(indicators[key][subKey])) { indicators[key][subKey] = indicators[key][subKey].reverse(); } }); } } }); // 过滤到用户指定区间 stockData = filterDataToUserRange(stockData, userStartDate, userEndDate); console.log(`过滤到用户请求时间范围,剩余${stockData.length}条记录`); } // 表格输出(走默认分支样式) const marketTitleMap: Record<string, string> = { 'crypto': '加密货币' }; const fieldNameMap: Record<string, string> = { 'trade_date': '交易日期', 'open': '开盘', 'close': '收盘', 'high': '最高', 'low': '最低', 'vol': '成交量' }; let formattedData = ''; let indicatorData = ''; if (stockData.length > 0) { const coreFields = ['trade_date', 'open', 'close', 'high', 'low', 'vol']; const availableFields = Object.keys(stockData[0]); const displayFields = coreFields.filter(field => availableFields.includes(field)); const indicatorHeaders: string[] = []; const hasIndicators = Object.keys(indicators).length > 0; if (hasIndicators) { if (indicators.macd) indicatorHeaders.push('MACD_DIF', 'MACD_DEA', 'MACD'); if (indicators.rsi) indicatorHeaders.push('RSI'); if (indicators.kdj) indicatorHeaders.push('KDJ_K', 'KDJ_D', 'KDJ_J'); if (indicators.boll) indicatorHeaders.push('BOLL_UP', 'BOLL_MID', 'BOLL_LOW'); const maIndicators = Object.keys(indicators).filter(key => key.startsWith('ma') && key !== 'macd'); maIndicators.forEach(ma => indicatorHeaders.push(ma.toUpperCase())); } const allHeaders = [ ...displayFields.map(field => fieldNameMap[field] || field), ...indicatorHeaders ]; formattedData = `| ${allHeaders.join(' | ')} |\n`; formattedData += `|${allHeaders.map(() => '--------').join('|')}|\n`; stockData.forEach((data: Record<string, any>, index: number) => { const basicRow = displayFields.map(field => data[field] ?? 'N/A'); const indicatorRow: string[] = []; if (hasIndicators) { if (indicators.macd) { indicatorRow.push( isNaN(indicators.macd.dif[index]) ? 'N/A' : indicators.macd.dif[index].toFixed(4), isNaN(indicators.macd.dea[index]) ? 'N/A' : indicators.macd.dea[index].toFixed(4), isNaN(indicators.macd.macd[index]) ? 'N/A' : indicators.macd.macd[index].toFixed(4) ); } if (indicators.rsi) indicatorRow.push(isNaN(indicators.rsi[index]) ? 'N/A' : indicators.rsi[index].toFixed(2)); if (indicators.kdj) indicatorRow.push( isNaN(indicators.kdj.k[index]) ? 'N/A' : indicators.kdj.k[index].toFixed(2), isNaN(indicators.kdj.d[index]) ? 'N/A' : indicators.kdj.d[index].toFixed(2), isNaN(indicators.kdj.j[index]) ? 'N/A' : indicators.kdj.j[index].toFixed(2) ); if (indicators.boll) indicatorRow.push( isNaN(indicators.boll.upper[index]) ? 'N/A' : indicators.boll.upper[index].toFixed(2), isNaN(indicators.boll.middle[index]) ? 'N/A' : indicators.boll.middle[index].toFixed(2), isNaN(indicators.boll.lower[index]) ? 'N/A' : indicators.boll.lower[index].toFixed(2) ); const maIndicators = Object.keys(indicators).filter(key => key.startsWith('ma') && key !== 'macd'); maIndicators.forEach(ma => { indicatorRow.push(isNaN(indicators[ma][index]) ? 'N/A' : indicators[ma][index].toFixed(2)); }); } const fullRow = [...basicRow, ...indicatorRow]; formattedData += `| ${fullRow.join(' | ')} |\n`; }); } if (Object.keys(indicators).length > 0) { indicatorData = `\n\n## 📊 技术指标说明\n`; const indicatorParams: Record<string, string> = {}; for (const indicator of requestedIndicators) { try { const { name, params } = parseIndicatorParams(indicator); indicatorParams[name] = formatIndicatorParams(name, params); } catch {} } if (indicators.macd) indicatorData += `- **MACD${indicatorParams.macd || '(参数未知)'}**: DIF(快线)、DEA(慢线)、MACD(柱状图)\n`; if (indicators.rsi) indicatorData += `- **RSI${indicatorParams.rsi || '(参数未知)'}**: 相对强弱指标,范围0-100,>70超买,<30超卖\n`; if (indicators.kdj) indicatorData += `- **KDJ${indicatorParams.kdj || '(参数未知)'}**: 随机指标,K线、D线、J线,>80超买,<20超卖\n`; if (indicators.boll) indicatorData += `- **BOLL${indicatorParams.boll || '(参数未知)'}**: 布林带,上轨、中轨、下轨\n`; const maIndicators = Object.keys(indicators).filter(key => key.startsWith('ma') && key !== 'macd'); if (maIndicators.length > 0) { maIndicators.forEach(ma => { const period = ma.replace('ma', ''); indicatorData += `- **${ma.toUpperCase()}(${period})**: 移动平均线,常用判断趋势方向\n`; }); } } return { content: [ { type: "text", text: `# ${args.code} ${marketTitleMap[marketType]}行情数据\n\n${formattedData}${indicatorData}` } ] }; } // 构建请求参数 const params: any = { token: TUSHARE_API_KEY, params: { ts_code: args.code, start_date: actualStartDate, end_date: actualEndDate } // 不设置fields参数,默认返回所有字段 }; // 根据不同市场类型设置不同的API名称和参数,默认返回所有字段 switch(marketType) { case 'cn': params.api_name = "daily"; // 不设置fields,返回所有可用字段 break; case 'us': params.api_name = "us_daily"; // 不设置fields,返回所有可用字段 break; case 'hk': params.api_name = "hk_daily"; // 不设置fields,返回所有可用字段 break; case 'fx': params.api_name = "fx_daily"; // 不设置fields,返回所有可用字段 break; case 'futures': params.api_name = "fut_daily"; // 不设置fields,返回所有可用字段 break; case 'fund': params.api_name = "fund_daily"; // 不设置fields,返回所有可用字段 break; case 'repo': params.api_name = "repo_daily"; // 不设置fields,返回所有可用字段 break; case 'convertible_bond': params.api_name = "cb_daily"; // 不设置fields,返回所有可用字段 break; case 'options': params.api_name = "opt_daily"; // 不设置fields,返回所有可用字段 // 期权接口优先使用trade_date,如果没有指定则使用end_date作为trade_date if (requestedIndicators.length > 0) { // 如请求技术指标,必须用区间以便获取足够历史 params.params = { ts_code: args.code, start_date: actualStartDate, end_date: actualEndDate }; // 统一将 amount 列名标注为(万元) } else if (!args.start_date && !args.end_date) { // 如果都没指定,使用默认的end_date作为trade_date params.params = { trade_date: actualEndDate }; } else if (args.end_date && !args.start_date) { // 只指定了end_date,使用作为trade_date params.params = { trade_date: actualEndDate }; } else { // 如果指定了start_date或日期范围,保持原有逻辑但添加ts_code params.params = { ts_code: args.code, start_date: actualStartDate, end_date: actualEndDate }; } // 如果指定了具体的期权代码,添加到params中 if (args.code && args.code.length > 0) { params.params.ts_code = args.code; } break; } console.log(`选择的API接口: ${params.api_name}`); console.log(`字段设置: 返回所有可用字段`); // 设置请求超时 const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), TUSHARE_CONFIG.TIMEOUT); try { console.log(`请求Tushare API: ${params.api_name},参数:`, params.params); // 发送请求 const response = await fetch(TUSHARE_API_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params), signal: controller.signal }); if (!response.ok) { throw new Error(`Tushare API请求失败: ${response.status}`); } const data = await response.json(); // 处理响应数据 if (data.code !== 0) { throw new Error(`Tushare API错误: ${data.msg}`); } // 确保data.data和data.data.items存在 if (!data.data || !data.data.items || data.data.items.length === 0) { throw new Error(`未找到${marketType}市场股票${args.code}的行情数据`); } // 获取字段名 const fields = data.data.fields; // 将数据转换为对象数组 let stockData = data.data.items.map((item: any) => { const result: Record<string, any> = {}; fields.forEach((field: string, index: number) => { result[field] = item[index]; }); return result; }); console.log(`成功获取到${stockData.length}条${args.code}股票数据记录(扩展数据范围)`); // 对A股强制应用前复权(qfq):使用最新交易日因子进行归一 if (marketType === 'cn' && stockData.length > 0) { try { const afParams = { api_name: 'adj_factor', token: TUSHARE_API_KEY, params: { ts_code: args.code, start_date: actualStartDate, end_date: actualEndDate }, fields: 'trade_date,adj_factor' } as any; const afResp = await fetch(TUSHARE_API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(afParams), signal: controller.signal }); if (!afResp.ok) throw new Error(`adj_factor 请求失败: ${afResp.status}`); const afJson = await afResp.json(); if (afJson.code !== 0) throw new Error(`adj_factor 返回错误: ${afJson.msg}`); const afFields: string[] = afJson.data?.fields ?? []; const afItems: any[] = afJson.data?.items ?? []; const idxDate = afFields.indexOf('trade_date'); const idxFactor = afFields.indexOf('adj_factor'); const factorMap = new Map<string, number>(); for (const row of afItems) { const d = String(row[idxDate]); const f = Number(row[idxFactor]); if (!isNaN(f)) factorMap.set(d, f); } // 找到stockData中最新交易日的因子 const latestDate = stockData .map((r: any) => String(r.trade_date)) .sort((a: string, b: string) => b.localeCompare(a))[0]; const latestFactor = factorMap.get(latestDate); if (latestFactor && !isNaN(latestFactor)) { stockData = stockData.map((row: any) => { const f = factorMap.get(String(row.trade_date)); if (f && !isNaN(f)) { const ratio = f / latestFactor; // 前复权:price * f / f_latest const adj = (v: any) => (v == null || v === '' || isNaN(Number(v))) ? v : Number(v) * ratio; return { ...row, open: adj(row.open), high: adj(row.high), low: adj(row.low), close: adj(row.close) }; } return row; }); console.log(`已应用前复权(基于最新交易日因子)到 ${args.code} 的OHLC价格`); } else { console.warn('未找到最新交易日复权因子,跳过前复权'); } } catch (e) { console.warn('应用前复权失败,继续返回未复权数据:', e); } } // 计算技术指标 let indicators: Record<string, any> = {}; if (requestedIndicators.length > 0 && ['cn', 'us', 'hk', 'fund', 'futures', 'convertible_bond', 'options', 'fx', 'crypto'].includes(marketType)) { // 对具有可用于OHLC的市场计算技术指标 // 构建按时间正序的价格序列 const mid = (a: any, b: any): number => { const x = parseFloat(a); const y = parseFloat(b); if (!isNaN(x) && !isNaN(y)) return (x + y) / 2; if (!isNaN(x)) return x; if (!isNaN(y)) return y; return NaN; }; let closes: number[] = []; let highs: number[] = []; let lows: number[] = []; if (marketType === 'fx') { closes = stockData.map((d: Record<string, any>) => mid(d.bid_close, d.ask_close)).reverse(); highs = stockData.map((d: Record<string, any>) => mid(d.bid_high, d.ask_high)).reverse(); lows = stockData.map((d: Record<string, any>) => mid(d.bid_low, d.ask_low)).reverse(); } else { closes = stockData.map((d: Record<string, any>) => parseFloat(d.close)).reverse(); highs = stockData.map((d: Record<string, any>) => parseFloat(d.high)).reverse(); lows = stockData.map((d: Record<string, any>) => parseFloat(d.low)).reverse(); } for (const indicator of requestedIndicators) { try { const { name, params } = parseIndicatorParams(indicator); switch (name) { case 'macd': if (params.length !== 3) { throw new Error(`MACD指标需要3个参数,格式:macd(快线,慢线,信号线),如:macd(12,26,9)`); } indicators.macd = calculateMACD(closes, params[0], params[1], params[2]); break; case 'rsi': if (params.length !== 1) { throw new Error(`RSI指标需要1个参数,格式:rsi(周期),如:rsi(14)`); } indicators.rsi = calculateRSI(closes, params[0]); break; case 'kdj': if (params.length !== 3) { throw new Error(`KDJ指标需要3个参数,格式:kdj(K周期,K平滑,D平滑),如:kdj(9,3,3)`); } indicators.kdj = calculateKDJ(highs, lows, closes, params[0], params[1], params[2]); break; case 'boll': if (params.length !== 2) { throw new Error(`布林带指标需要2个参数,格式:boll(周期,标准差倍数),如:boll(20,2)`); } indicators.boll = calculateBOLL(closes, params[0], params[1]); break; case 'ma': if (params.length !== 1) { throw new Error(`移动平均线需要1个参数,格式:ma(周期),如:ma(5)、ma(10)、ma(20)`); } const maPeriod = params[0]; indicators[`ma${maPeriod}`] = calculateSMA(closes, maPeriod); break; default: throw new Error(`不支持的技术指标: ${name},支持的指标:macd(12,26,9)、rsi(14)、kdj(9,3,3)、boll(20,2)、ma(周期)`); } } catch (error) { console.error(`解析技术指标 ${indicator} 时出错:`, error); throw new Error(`技术指标参数错误: ${indicator}`); } } // 将技术指标数据逆序回来,以匹配原始数据的时间顺序(最新日期在前) Object.keys(indicators).forEach(key => { if (typeof indicators[key] === 'object' && indicators[key] !== null) { if (Array.isArray(indicators[key])) { indicators[key] = indicators[key].reverse(); } else { // 对于MACD、KDJ、BOLL等对象类型的指标 Object.keys(indicators[key]).forEach(subKey => { if (Array.isArray(indicators[key][subKey])) { indicators[key][subKey] = indicators[key][subKey].reverse(); } }); } } }); } // 过滤数据到用户请求的时间范围 if (requestedIndicators.length > 0) { stockData = filterDataToUserRange(stockData, userStartDate, userEndDate); console.log(`过滤到用户请求时间范围,剩余${stockData.length}条记录`); } // 生成市场类型标题 const marketTitleMap: Record<string, string> = { 'cn': 'A股', 'us': '美股', 'hk': '港股', 'fx': '外汇', 'futures': '期货', 'fund': '基金', 'repo': '债券逆回购', 'convertible_bond': '可转债', 'options': '期权', 'crypto': '加密货币' }; // 金额(amount)统一以“万元”为单位展示:amount(千) -> amount/10(万) const formatAmountWan = (val: any): string => { const num = Number(val); if (val == null || val === '' || isNaN(num)) return 'N/A'; return (num / 10).toFixed(2); }; // 格式化输出(根据不同市场类型构建表格格式) let formattedData = ''; let indicatorData = ''; const titleSuffix = marketType === 'cn' ? '(前复权)' : ''; if (marketType === 'fx') { // 外汇数据表格展示(追加技术指标列) const hasIndicators = Object.keys(indicators).length > 0; const indicatorHeaders: string[] = []; if (hasIndicators) { if (indicators.macd) indicatorHeaders.push('MACD_DIF', 'MACD_DEA', 'MACD'); if (indicators.rsi) indicatorHeaders.push('RSI'); if (indicators.kdj) indicatorHeaders.push('KDJ_K', 'KDJ_D', 'KDJ_J'); if (indicators.boll) indicatorHeaders.push('BOLL_UP', 'BOLL_MID', 'BOLL_LOW'); const maIndicators = Object.keys(indicators).filter(key => key.startsWith('ma') && key !== 'macd'); maIndicators.forEach(ma => indicatorHeaders.push(ma.toUpperCase())); } const baseHeaders = ['交易日期','买入开盘','买入最高','买入最低','买入收盘','卖出开盘','卖出最高','卖出最低','卖出收盘','报价笔数']; const headers = [...baseHeaders, ...indicatorHeaders]; formattedData = `| ${headers.join(' | ')} |\n`; formattedData += `|${headers.map(() => '--------').join('|')}|\n`; stockData.forEach((data: Record<string, any>, index: number) => { const baseRow = [data.trade_date, data.bid_open || 'N/A', data.bid_high || 'N/A', data.bid_low || 'N/A', data.bid_close || 'N/A', data.ask_open || 'N/A', data.ask_high || 'N/A', data.ask_low || 'N/A', data.ask_close || 'N/A', data.tick_qty || 'N/A']; const indicatorRow: string[] = []; if (hasIndicators) { if (indicators.macd) { indicatorRow.push( isNaN(indicators.macd.dif[index]) ? 'N/A' : indicators.macd.dif[index].toFixed(4), isNaN(indicators.macd.dea[index]) ? 'N/A' : indicators.macd.dea[index].toFixed(4), isNaN(indicators.macd.macd[index]) ? 'N/A' : indicators.macd.macd[index].toFixed(4) ); } if (indicators.rsi) indicatorRow.push(isNaN(indicators.rsi[index]) ? 'N/A' : indicators.rsi[index].toFixed(2)); if (indicators.kdj) indicatorRow.push( isNaN(indicators.kdj.k[index]) ? 'N/A' : indicators.kdj.k[index].toFixed(2), isNaN(indicators.kdj.d[index]) ? 'N/A' : indicators.kdj.d[index].toFixed(2), isNaN(indicators.kdj.j[index]) ? 'N/A' : indicators.kdj.j[index].toFixed(2) ); if (indicators.boll) indicatorRow.push( isNaN(indicators.boll.upper[index]) ? 'N/A' : indicators.boll.upper[index].toFixed(2), isNaN(indicators.boll.middle[index]) ? 'N/A' : indicators.boll.middle[index].toFixed(2), isNaN(indicators.boll.lower[index]) ? 'N/A' : indicators.boll.lower[index].toFixed(2) ); const maIndicators = Object.keys(indicators).filter(key => key.startsWith('ma') && key !== 'macd'); maIndicators.forEach(ma => { indicatorRow.push(isNaN(indicators[ma][index]) ? 'N/A' : indicators[ma][index].toFixed(2)); }); } const row = [...baseRow, ...indicatorRow]; formattedData += `| ${row.join(' | ')} |\n`; }); } else if (marketType === 'futures') { // 期货数据表格展示(追加技术指标列) const hasIndicators = Object.keys(indicators).length > 0; const indicatorHeaders: string[] = []; if (hasIndicators) { if (indicators.macd) indicatorHeaders.push('MACD_DIF', 'MACD_DEA', 'MACD'); if (indicators.rsi) indicatorHeaders.push('RSI'); if (indicators.kdj) indicatorHeaders.push('KDJ_K', 'KDJ_D', 'KDJ_J'); if (indicators.boll) indicatorHeaders.push('BOLL_UP', 'BOLL_MID', 'BOLL_LOW'); const maIndicators = Object.keys(indicators).filter(key => key.startsWith('ma') && key !== 'macd'); maIndicators.forEach(ma => indicatorHeaders.push(ma.toUpperCase())); } const baseHeaders = ['交易日期','开盘','最高','最低','收盘','结算','涨跌1','涨跌2','成交量','持仓量']; const headers = [...baseHeaders, ...indicatorHeaders]; formattedData = `| ${headers.join(' | ')} |\n`; formattedData += `|${headers.map(() => '--------').join('|')}|\n`; stockData.forEach((data: Record<string, any>, index: number) => { const baseRow = [data.trade_date, data.open || 'N/A', data.high || 'N/A', data.low || 'N/A', data.close || 'N/A', data.settle || 'N/A', data.change1 || 'N/A', data.change2 || 'N/A', data.vol || 'N/A', data.oi || 'N/A']; const indicatorRow: string[] = []; if (hasIndicators) { if (indicators.macd) { indicatorRow.push( isNaN(indicators.macd.dif[index]) ? 'N/A' : indicators.macd.dif[index].toFixed(4), isNaN(indicators.macd.dea[index]) ? 'N/A' : indicators.macd.dea[index].toFixed(4), isNaN(indicators.macd.macd[index]) ? 'N/A' : indicators.macd.macd[index].toFixed(4) ); } if (indicators.rsi) indicatorRow.push(isNaN(indicators.rsi[index]) ? 'N/A' : indicators.rsi[index].toFixed(2)); if (indicators.kdj) indicatorRow.push( isNaN(indicators.kdj.k[index]) ? 'N/A' : indicators.kdj.k[index].toFixed(2), isNaN(indicators.kdj.d[index]) ? 'N/A' : indicators.kdj.d[index].toFixed(2), isNaN(indicators.kdj.j[index]) ? 'N/A' : indicators.kdj.j[index].toFixed(2) ); if (indicators.boll) indicatorRow.push( isNaN(indicators.boll.upper[index]) ? 'N/A' : indicators.boll.upper[index].toFixed(2), isNaN(indicators.boll.middle[index]) ? 'N/A' : indicators.boll.middle[index].toFixed(2), isNaN(indicators.boll.lower[index]) ? 'N/A' : indicators.boll.lower[index].toFixed(2) ); const maIndicators = Object.keys(indicators).filter(key => key.startsWith('ma') && key !== 'macd'); maIndicators.forEach(ma => { indicatorRow.push(isNaN(indicators[ma][index]) ? 'N/A' : indicators[ma][index].toFixed(2)); }); } const row = [...baseRow, ...indicatorRow]; formattedData += `| ${row.join(' | ')} |\n`; }); } else if (marketType === 'repo') { // 债券逆回购数据表格展示 formattedData = `| 交易日期 | 品种名称 | 利率(%) | 成交金额(万元) |\n`; formattedData += `|---------|---------|---------|---------------|\n`; stockData.forEach((data: Record<string, any>) => { const amtWan = formatAmountWan(data.amount); formattedData += `| ${data.trade_date} | ${data.name || 'N/A'} | ${data.rate || 'N/A'} | ${amtWan} |\n`; }); } else if (marketType === 'convertible_bond') { // 可转债数据表格展示(追加技术指标列) const hasIndicators = Object.keys(indicators).length > 0; const indicatorHeaders: string[] = []; if (hasIndicators) { if (indicators.macd) indicatorHeaders.push('MACD_DIF', 'MACD_DEA', 'MACD'); if (indicators.rsi) indicatorHeaders.push('RSI'); if (indicators.kdj) indicatorHeaders.push('KDJ_K', 'KDJ_D', 'KDJ_J'); if (indicators.boll) indicatorHeaders.push('BOLL_UP', 'BOLL_MID', 'BOLL_LOW'); const maIndicators = Object.keys(indicators).filter(key => key.startsWith('ma') && key !== 'macd'); maIndicators.forEach(ma => indicatorHeaders.push(ma.toUpperCase())); } const baseHeaders = ['交易日期','开盘','最高','最低','收盘','涨跌','涨跌幅(%)','成交量(手)','成交金额(万元)','纯债价值','纯债溢价率(%)','转股价值','转股溢价率(%)']; const headers = [...baseHeaders, ...indicatorHeaders]; formattedData = `| ${headers.join(' | ')} |\n`; formattedData += `|${headers.map(() => '--------').join('|')}|\n`; stockData.forEach((data: Record<string, any>, index: number) => { const baseRow = [ data.trade_date, data.open || 'N/A', data.high || 'N/A', data.low || 'N/A', data.close || 'N/A', data.change || 'N/A', data.pct_chg || 'N/A', data.vol || 'N/A', formatAmountWan(data.amount), data.bond_value || 'N/A', data.bond_over_rate || 'N/A', data.cb_value || 'N/A', data.cb_over_rate || 'N/A' ]; const indicatorRow: string[] = []; if (hasIndicators) { if (indicators.macd) { indicatorRow.push( isNaN(indicators.macd.dif[index]) ? 'N/A' : indicators.macd.dif[index].toFixed(4), isNaN(indicators.macd.dea[index]) ? 'N/A' : indicators.macd.dea[index].toFixed(4), isNaN(indicators.macd.macd[index]) ? 'N/A' : indicators.macd.macd[index].toFixed(4) ); } if (indicators.rsi) indicatorRow.push(isNaN(indicators.rsi[index]) ? 'N/A' : indicators.rsi[index].toFixed(2)); if (indicators.kdj) indicatorRow.push( isNaN(indicators.kdj.k[index]) ? 'N/A' : indicators.kdj.k[index].toFixed(2), isNaN(indicators.kdj.d[index]) ? 'N/A' : indicators.kdj.d[index].toFixed(2), isNaN(indicators.kdj.j[index]) ? 'N/A' : indicators.kdj.j[index].toFixed(2) ); if (indicators.boll) indicatorRow.push( isNaN(indicators.boll.upper[index]) ? 'N/A' : indicators.boll.upper[index].toFixed(2), isNaN(indicators.boll.middle[index]) ? 'N/A' : indicators.boll.middle[index].toFixed(2), isNaN(indicators.boll.lower[index]) ? 'N/A' : indicators.boll.lower[index].toFixed(2) ); const maIndicators = Object.keys(indicators).filter(key => key.startsWith('ma') && key !== 'macd'); maIndicators.forEach(ma => { indicatorRow.push(isNaN(indicators[ma][index]) ? 'N/A' : indicators[ma][index].toFixed(2)); }); } const row = [...baseRow, ...indicatorRow]; formattedData += `| ${row.join(' | ')} |\n`; }); } else if (marketType === 'options') { // 期权数据表格展示(追加技术指标列) const hasIndicators = Object.keys(indicators).length > 0; const indicatorHeaders: string[] = []; if (hasIndicators) { if (indicators.macd) indicatorHeaders.push('MACD_DIF', 'MACD_DEA', 'MACD'); if (indicators.rsi) indicatorHeaders.push('RSI'); if (indicators.kdj) indicatorHeaders.push('KDJ_K', 'KDJ_D', 'KDJ_J'); if (indicators.boll) indicatorHeaders.push('BOLL_UP', 'BOLL_MID', 'BOLL_LOW'); const maIndicators = Object.keys(indicators).filter(key => key.startsWith('ma') && key !== 'macd'); maIndicators.forEach(ma => indicatorHeaders.push(ma.toUpperCase())); } const baseHeaders = ['交易日期','交易所','昨结算','前收盘','开盘','最高','最低','收盘','结算','成交量(手)','成交金额(万元)','持仓量(手)']; const headers = [...baseHeaders, ...indicatorHeaders]; formattedData = `| ${headers.join(' | ')} |\n`; formattedData += `|${headers.map(() => '--------').join('|')}|\n`; stockData.forEach((data: Record<string, any>, index: number) => { const baseRow = [ data.trade_date, data.exchange || 'N/A', data.pre_settle || 'N/A', data.pre_close || 'N/A', data.open || 'N/A', data.high || 'N/A', data.low || 'N/A', data.close || 'N/A', data.settle || 'N/A', data.vol || 'N/A', formatAmountWan(data.amount), data.oi || 'N/A' ]; const indicatorRow: string[] = []; if (hasIndicators) { if (indicators.macd) { indicatorRow.push( isNaN(indicators.macd.dif[index]) ? 'N/A' : indicators.macd.dif[index].toFixed(4), isNaN(indicators.macd.dea[index]) ? 'N/A' : indicators.macd.dea[index].toFixed(4), isNaN(indicators.macd.macd[index]) ? 'N/A' : indicators.macd.macd[index].toFixed(4) ); } if (indicators.rsi) indicatorRow.push(isNaN(indicators.rsi[index]) ? 'N/A' : indicators.rsi[index].toFixed(2)); if (indicators.kdj) indicatorRow.push( isNaN(indicators.kdj.k[index]) ? 'N/A' : indicators.kdj.k[index].toFixed(2), isNaN(indicators.kdj.d[index]) ? 'N/A' : indicators.kdj.d[index].toFixed(2), isNaN(indicators.kdj.j[index]) ? 'N/A' : indicators.kdj.j[index].toFixed(2) ); if (indicators.boll) indicatorRow.push( isNaN(indicators.boll.upper[index]) ? 'N/A' : indicators.boll.upper[index].toFixed(2), isNaN(indicators.boll.middle[index]) ? 'N/A' : indicators.boll.middle[index].toFixed(2), isNaN(indicators.boll.lower[index]) ? 'N/A' : indicators.boll.lower[index].toFixed(2) ); const maIndicators = Object.keys(indicators).filter(key => key.startsWith('ma') && key !== 'macd'); maIndicators.forEach(ma => { indicatorRow.push(isNaN(indicators[ma][index]) ? 'N/A' : indicators[ma][index].toFixed(2)); }); } const row = [...baseRow, ...indicatorRow]; formattedData += `| ${row.join(' | ')} |\n`; }); } else { // 股票数据表格展示(A股、美股、港股、基金等) if (stockData.length > 0) { // 基础字段 const coreFields = ['trade_date', 'open', 'close', 'high', 'low', 'vol', 'amount']; const availableFields = Object.keys(stockData[0]); const displayFields = coreFields.filter(field => availableFields.includes(field)); // 生成字段名映射 const fieldNameMap: Record<string, string> = { 'trade_date': '交易日期', 'open': '开盘', 'close': '收盘', 'high': '最高', 'low': '最低', 'vol': '成交量', 'amount': '成交额' }; fieldNameMap['amount'] = '成交额(万元)'; // 如果有技术指标,添加技术指标列 const indicatorHeaders: string[] = []; const hasIndicators = Object.keys(indicators).length > 0; if (hasIndicators) { // 添加技术指标表头 if (indicators.macd) { indicatorHeaders.push('MACD_DIF', 'MACD_DEA', 'MACD'); } if (indicators.rsi) { indicatorHeaders.push('RSI'); } if (indicators.kdj) { indicatorHeaders.push('KDJ_K', 'KDJ_D', 'KDJ_J'); } if (indicators.boll) { indicatorHeaders.push('BOLL_UP', 'BOLL_MID', 'BOLL_LOW'); } // 添加移动平均线 const maIndicators = Object.keys(indicators).filter(key => key.startsWith('ma') && key !== 'macd'); maIndicators.forEach(ma => { indicatorHeaders.push(ma.toUpperCase()); }); } // 组合所有表头 const allHeaders = [ ...displayFields.map(field => field === 'amount' ? '成交额(万元)' : (fieldNameMap[field] || field)), ...indicatorHeaders ]; formattedData = `| ${allHeaders.join(' | ')} |\n`; formattedData += `|${allHeaders.map(() => '--------').join('|')}|\n`; // 生成数据行 stockData.forEach((data: Record<string, any>, index: number) => { const basicRow = displayFields.map(field => { if (field === 'amount') return formatAmountWan(data.amount); return data[field] || 'N/A'; }); // 添加技术指标数据 const indicatorRow: string[] = []; if (hasIndicators) { if (indicators.macd) { indicatorRow.push( isNaN(indicators.macd.dif[index]) ? 'N/A' : indicators.macd.dif[index].toFixed(4), isNaN(indicators.macd.dea[index]) ? 'N/A' : indicators.macd.dea[index].toFixed(4), isNaN(indicators.macd.macd[index]) ? 'N/A' : indicators.macd.macd[index].toFixed(4) ); } if (indicators.rsi) { indicatorRow.push(isNaN(indicators.rsi[index]) ? 'N/A' : indicators.rsi[index].toFixed(2)); } if (indicators.kdj) { indicatorRow.push( isNaN(indicators.kdj.k[index]) ? 'N/A' : indicators.kdj.k[index].toFixed(2), isNaN(indicators.kdj.d[index]) ? 'N/A' : indicators.kdj.d[index].toFixed(2), isNaN(indicators.kdj.j[index]) ? 'N/A' : indicators.kdj.j[index].toFixed(2) ); } if (indicators.boll) { indicatorRow.push( isNaN(indicators.boll.upper[index]) ? 'N/A' : indicators.boll.upper[index].toFixed(2), isNaN(indicators.boll.middle[index]) ? 'N/A' : indicators.boll.middle[index].toFixed(2), isNaN(indicators.boll.lower[index]) ? 'N/A' : indicators.boll.lower[index].toFixed(2) ); } // 添加移动平均线数据 const maIndicators = Object.keys(indicators).filter(key => key.startsWith('ma') && key !== 'macd'); maIndicators.forEach(ma => { indicatorRow.push(isNaN(indicators[ma][index]) ? 'N/A' : indicators[ma][index].toFixed(2)); }); } const fullRow = [...basicRow, ...indicatorRow]; formattedData += `| ${fullRow.join(' | ')} |\n`; }); } } // 生成技术指标说明(如果有技术指标) if (Object.keys(indicators).length > 0) { indicatorData = `\n\n## 📊 技术指标说明\n`; // 记录实际使用的参数,用于说明中显示 const indicatorParams: Record<string, string> = {}; for (const indicator of requestedIndicators) { try { const { name, params } = parseIndicatorParams(indicator); indicatorParams[name] = formatIndicatorParams(name, params); } catch { // 忽略解析错误,继续处理其他指标 } } if (indicators.macd) { const params = indicatorParams.macd || '(参数未知)'; indicatorData += `- **MACD${params}**: DIF(快线)、DEA(慢线)、MACD(柱状图)\n`; } if (indicators.rsi) { const params = indicatorParams.rsi || '(参数未知)'; indicatorData += `- **RSI${params}**: 相对强弱指标,范围0-100,>70超买,<30超卖\n`; } if (indicators.kdj) { const params = indicatorParams.kdj || '(参数未知)'; indicatorData += `- **KDJ${params}**: 随机指标,K线、D线、J线,>80超买,<20超卖\n`; } if (indicators.boll) { const params = indicatorParams.boll || '(参数未知)'; indicatorData += `- **BOLL${params}**: 布林带,上轨、中轨、下轨\n`; } // 处理各种MA指标,过滤掉非MA指标 const maIndicators = Object.keys(indicators).filter(key => key.startsWith('ma') && key !== 'macd'); if (maIndicators.length > 0) { maIndicators.forEach(ma => { const period = ma.replace('ma', ''); indicatorData += `- **${ma.toUpperCase()}(${period})**: 移动平均线,常用判断趋势方向\n`; }); } } // 收集股票代码并生成说明(仅对股票市场) let stockExplanation = ''; if (['cn', 'us', 'hk'].includes(marketType)) { stockExplanation = await resolveStockCodes([args.code]); } return { content: [ { type: "text", text: `# ${args.code} ${marketTitleMap[marketType]}行情数据${titleSuffix}\n\n${formattedData}${indicatorData}${stockExplanation}` } ] }; } finally { clearTimeout(timeoutId); } } catch (error) { console.error("获取股票数据失败:", error); return { content: [ { type: "text", text: `# 获取股票${args.code}数据失败\n\n无法从Tushare API获取数据:${error instanceof Error ? error.message : String(error)}\n\n请检查股票代码和市场类型是否正确:\n- A股格式:"000001.SZ"\n- 美股格式:"AAPL"\n- 港股格式:"00700.HK"\n- 外汇格式:"USDCNH.FXCM"(美元人民币)\n- 期货格式:"CU2501.SHF"\n- 基金格式:"159919.SZ"\n- 债券逆回购格式:"204001.SH"\n- 可转债格式:"113008.SH"\n- 期权格式:"10001313.SH"\n\n技术指标使用说明(必须明确指定参数):\n- **MACD**: macd(快线,慢线,信号线) - 例:macd(12,26,9)\n- **RSI**: rsi(周期) - 例:rsi(14)\n- **KDJ**: kdj(K周期,K平滑,D平滑) - 例:kdj(9,3,3)\n- **布林带**: boll(周期,标准差倍数) - 例:boll(20,2)\n- **移动平均线**: ma(周期) - 例:ma(5)、ma(10)、ma(20)\n\n使用示例:\n- "macd(12,26,9) rsi(14)"\n- "kdj(9,3,3) boll(20,2) ma(30)"\n- "macd(5,10,5) ma(5) ma(10)"` } ] }; } }
- src/tools/stockData.ts:19-44 (schema)Input schema (parameters) defining the expected arguments for the stock_data tool, including required code and market_type, optional dates and indicators string.parameters: { type: "object", properties: { code: { type: "string", description: "股票/合约/加密资产代码。股票示例:'000001.SZ'(A股平安银行)、'AAPL'(美股)、'00700.HK'(港股)、'USDCNH.FXCM'(外汇)、'CU2501.SHF'(期货)、'159919.SZ'(基金)、'204001.SH'(逆回购)、'113008.SH'(可转债)、'10001313.SH'(期权)。加密示例(需 market_type=crypto,Binance):推荐标准写法 'BTCUSDT'、'ETHUSDT'、'USDCUSDT'、'FDUSDUSDT' 等;也兼容 'BTC-USDT' 或 'BTC/USDT'。常见报价币:USDT、USDC、FDUSD、TUSD、BUSD、BTC、ETH。注意:若写 'USD' 会自动映射为 'USDT'(如 'BTC-USD' → 'BTCUSDT')。" }, market_type: { type: "string", description: "市场类型(必需),可选值:cn(A股),us(美股),hk(港股),fx(外汇),futures(期货),fund(基金),repo(债券逆回购),convertible_bond(可转债),options(期权),crypto(加密货币/CoinGecko)" }, start_date: { type: "string", description: "起始日期,格式为YYYYMMDD,如'20230101'" }, end_date: { type: "string", description: "结束日期,格式为YYYYMMDD,如'20230131'" }, indicators: { type: "string", description: "需要计算的技术指标,多个指标用空格分隔。支持的指标:macd(MACD指标)、rsi(相对强弱指标)、kdj(随机指标)、boll(布林带)、ma(均线指标)。必须明确指定参数,例如:'macd(12,26,9) rsi(14) kdj(9,3,3) boll(20,2) ma(10)'" } }, required: ["code", "market_type"] },
- src/index.ts:238-245 (registration)Registration in the CallToolRequest handler switch statement: extracts arguments from MCP request and invokes the stockData.run handler.case "stock_data": { const code = String(request.params.arguments?.code); const market_type = String(request.params.arguments?.market_type); const start_date = request.params.arguments?.start_date ? String(request.params.arguments.start_date) : undefined; const end_date = request.params.arguments?.end_date ? String(request.params.arguments.end_date) : undefined; const indicators = request.params.arguments?.indicators ? String(request.params.arguments.indicators) : undefined; return await stockData.run({ code, market_type, start_date, end_date, indicators }); }
- src/index.ts:138-141 (registration)Registration in the ListToolsRequest handler: exposes the tool's name, description, and input schema to MCP clients.{ name: stockData.name, description: stockData.description, inputSchema: stockData.parameters
- Re-export of helper functions for technical indicators (MACD, KDJ, RSI, BOLL, MA/SMA/EMA), parameter parsing, and data calculation utilities used by the stock_data handler.// 技术指标模块统一导出 export { calculateMACD } from './macd.js'; export { calculateKDJ } from './kdj.js'; export { calculateRSI } from './rsi.js'; export { calculateBOLL } from './boll.js'; export { calculateSMA, calculateEMA } from './ma.js'; export { parseIndicatorParams, formatIndicatorParams } from './paramParser.js'; export { calculateRequiredDays, calculateExtendedStartDate, filterDataToUserRange } from './dataCalculator.js'; export type { IndicatorParams } from './paramParser.js';