// 市场数据服务 import { DataSourceFactory, DataSourceType } from './datasource/DataSourceFactory'; import { futuresList, generateFuturesOverview, generateFutureData, generateKlineData, riskAlerts } from '../utils/mockData'; import { config } from '../config'; import { serviceImplementationClient } from './ServiceImplementationClient'; import { logger } from '../utils/logger'; import { cacheService } from './cacheService'; // 获取数据源配置 const getDataSourceConfig = () => { // 从配置文件中读取数据源配置 return config.dataSource; }; // 获取市场概览 export const fetchMarketOverview = async () => { const cacheKey = 'market:overview'; const symbol = 'market'; const type = 'overview'; return cacheService.get(cacheKey, symbol, type, async () => { try { // 首先尝试使用 service_implementation API try { logger.log('尝试使用 service_implementation API 获取市场概览...'); // 先获取合约列表 logger.log('获取合约列表...'); const contractsResponse = await serviceImplementationClient.getContracts(); const allContracts = contractsResponse.data; // 去重,按品种代码分组 const uniqueContracts = new Map(); for (const contract of allContracts) { // 提取品种代码(如从CU2603中提取CU) const code = contract.symbol.slice(0, 2); if (!uniqueContracts.has(code)) { uniqueContracts.set(code, { code: code, name: contract.name || code, exchange: contract.exchange || '' }); } } // 转换为数组 const contractList = Array.from(uniqueContracts.values()); logger.log(`获取到 ${contractList.length} 个独特品种,分别是: ${contractList.map(c => c.code).join(', ')}`); // 使用获取到的合约列表 const overview = []; for (const future of contractList) { try { // 构建合约符号(使用大写代码,因为 service_implementation API 期望大写) const symbol = `${future.code}${new Date().getFullYear().toString().slice(-2)}03`; // 获取合约详情 logger.log(`获取合约${symbol}详情...`); const contractsResponse = await serviceImplementationClient.getContracts(future.exchange, future.code); const contract = contractsResponse.data.find((c: any) => c.symbol === symbol); if (!contract) { logger.warn(`合约${symbol}不存在,跳过`); continue; } // 更新未来合约的中文名称 future.name = `${contract.product_name || future.name}`; // 获取分析数据 logger.log(`分析合约${symbol}...`); logger.log(`before analyzeMarket: symbol=${symbol},time=${new Date().toISOString()}`); const analysisResponse = await serviceImplementationClient.analyzeMarket(symbol); const analysis = analysisResponse.data; logger.log(`after analyzeMarket: symbol=${symbol}, analysis=${JSON.stringify(analysis)}, time=${new Date().toISOString()}`); overview.push({ code: future.code, name: future.name, currentPrice: analysis.current_price || 0, changePercent: analysis.change_percent || 0, winRate: analysis.probability ? Math.round(analysis.probability * 100) : Math.floor(Math.random() * 50) + 30, // 使用分析结果或模拟胜率 atr: analysis.atr || +(Math.random() * 5 + 0.5).toFixed(2), // 使用分析结果或模拟ATR adx: analysis.adx || Math.floor(Math.random() * 60) + 10, // 使用分析结果或模拟ADX adxStatus: (adx: number) => { if (adx < 20) return '无趋势/震荡'; if (adx < 40) return '弱趋势'; return '强趋势'; }, trends: { '5MIN': { direction: analysis.trend === 'up' ? '看多' : analysis.trend === 'down' ? '看空' : '观望', status: analysis.trend === 'up' ? '多头趋势' : analysis.trend === 'down' ? '空头趋势' : '震荡', rsi: Math.floor(Math.random() * 80) + 10 }, '30MIN': { direction: analysis.trend === 'up' ? '看多' : analysis.trend === 'down' ? '看空' : '观望', status: analysis.trend === 'up' ? '多头趋势' : analysis.trend === 'down' ? '空头趋势' : '震荡', rsi: Math.floor(Math.random() * 80) + 10 }, '1HOUR': { direction: analysis.trend === 'up' ? '看多' : analysis.trend === 'down' ? '看空' : '观望', status: analysis.trend === 'up' ? '多头趋势' : analysis.trend === 'down' ? '空头趋势' : '震荡', rsi: Math.floor(Math.random() * 80) + 10 }, '1DAY': { direction: analysis.trend === 'up' ? '看多' : analysis.trend === 'down' ? '看空' : '观望', status: analysis.trend === 'up' ? '多头趋势' : analysis.trend === 'down' ? '空头趋势' : '震荡', rsi: Math.floor(Math.random() * 80) + 10 } }, tradingAdvice: { entry: analysis.entry_price || 0, stopLoss: analysis.stop_loss || 0, target: analysis.target_price || 0, resistance: analysis.resistance || 0, support: analysis.support || 0 }, overallView: analysis.trend === 'up' ? '多头排列' : analysis.trend === 'down' ? '空头排列' : '震荡', aiAnalysis: `趋势:${analysis.trend || '中性'} | 概率:${analysis.probability ? Math.round(analysis.probability * 100) : 50}% | 方向:${analysis.direction || '观望'}` }); } catch (error) { logger.error(`获取合约${future.code}行情失败:`, error); // 跳过获取失败的合约 continue; } } if (overview.length > 0) { logger.log('使用 service_implementation API 获取市场概览成功'); return overview; } logger.warn('service_implementation API 未返回数据,尝试使用其他数据源'); } catch (error) { logger.error('service_implementation API 获取失败:', error); // service_implementation API 失败,尝试使用其他数据源 } // 获取数据源配置 const dataSourceConfig = getDataSourceConfig(); logger.log('获取数据源配置:', dataSourceConfig); // 检查是否有可用的数据源 const hasAvailableDataSource = dataSourceConfig.tqsdk?.enabled || dataSourceConfig.test?.enabled; if (!hasAvailableDataSource) { throw new Error('无可用数据源,请在管理配置中启用至少一个数据源'); } // 尝试使用TQSDK数据源 if (dataSourceConfig.tqsdk?.enabled) { try { const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig); // 使用用户指定的合约列表 const overview = []; for (const future of futuresList) { try { // 构建合约符号(使用小写代码,因为TQAPI期望小写) const symbol = `${future.exchange}.${future.code.toLowerCase()}${new Date().getFullYear().toString().slice(-2)}05`; // 获取合约详情和实时行情 const tick = await dataSource.getTickData(symbol); overview.push({ code: future.code, name: future.name, currentPrice: tick.last_price, changePercent: tick.price_change / tick.pre_close * 100, winRate: Math.floor(Math.random() * 50) + 30, // 模拟胜率 atr: +(Math.random() * 5 + 0.5).toFixed(2), // 模拟ATR adx: Math.floor(Math.random() * 60) + 10, // 模拟ADX adxStatus: (adx: number) => { if (adx < 20) return '无趋势/震荡'; if (adx < 40) return '弱趋势'; return '强趋势'; }, trends: { '5MIN': { direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)], status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)], rsi: Math.floor(Math.random() * 80) + 10 }, '30MIN': { direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)], status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)], rsi: Math.floor(Math.random() * 80) + 10 }, '1HOUR': { direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)], status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)], rsi: Math.floor(Math.random() * 80) + 10 }, '1DAY': { direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)], status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)], rsi: Math.floor(Math.random() * 80) + 10 } }, tradingAdvice: { entry: tick.last_price, stopLoss: tick.last_price * (1 - 0.02 * (Math.random() + 0.5)), target: tick.last_price * (1 + 0.03 * (Math.random() + 0.5)), resistance: tick.last_price * (1 + 0.05 * (Math.random() + 0.5)), support: tick.last_price * (1 - 0.05 * (Math.random() + 0.5)) }, overallView: ['观望', '中线', '多头排列', '空头排列', '震荡'][Math.floor(Math.random() * 5)], aiAnalysis: `MACD:金叉向上 | RSI:${Math.floor(Math.random() * 80) + 10}(中性) | 布林带:中轨附近` }); } catch (error) { logger.error(`获取合约${future.code}行情失败:`, error); // 跳过获取失败的合约 continue; } } return overview; } catch (error) { logger.error('TQSDK数据源获取失败:', error); // TQSDK数据源失败,尝试使用测试数据源 if (dataSourceConfig.test?.enabled) { logger.log('切换到测试数据源'); // 启用了测试数据源,使用测试数据 await new Promise(resolve => setTimeout(resolve, 300)); return generateFuturesOverview(); } else { // 未启用测试数据源,返回友好的错误提示 throw new Error('获取市场概览失败,所有数据源均不可用'); } } } else if (dataSourceConfig.test?.enabled) { // 直接使用测试数据源 logger.log('使用测试数据源'); await new Promise(resolve => setTimeout(resolve, 300)); return generateFuturesOverview(); } else { // 无可用数据源 throw new Error('无可用数据源,请在管理配置中启用至少一个数据源'); } } catch (error) { logger.error('获取市场概览失败:', error); // 直接返回友好的错误提示 throw new Error(error instanceof Error ? error.message : '获取市场概览失败,请检查数据源配置'); } }, { expireTime: 300 }); // 缓存5分钟 }; // 获取品种详情 export const fetchMarketDetail = async (symbol: string) => { const cacheKey = `market:detail:${symbol}`; const type = 'detail'; return cacheService.get(cacheKey, symbol, type, async () => { try { // 提取合约代码(从SHFE.AU2605中提取AU) const code = symbol.includes('.') ? symbol.split('.')[1].slice(0, 2) : symbol; // 查找合约信息 const future = futuresList.find(item => item.code === code); if (!future) { throw new Error('品种不存在'); } // 首先尝试使用 service_implementation API try { logger.log('尝试使用 service_implementation API 获取品种详情...'); // 构建合约符号(使用大写代码,因为 service_implementation API 期望大写) const contractSymbol = `${future.code}${new Date().getFullYear().toString().slice(-2)}03`; // 获取合约详情 logger.log(`获取合约${contractSymbol}详情...`); const contractsResponse = await serviceImplementationClient.getContracts(future.exchange, future.code); const contract = contractsResponse.data.find((c: any) => c.symbol === contractSymbol); if (!contract) { logger.warn(`合约${contractSymbol}不存在,尝试使用其他数据源`); } else { // 更新未来合约的中文名称 future.name = `${contract.product_name || future.name} - ${future.code}`; // 获取分析数据 logger.log(`分析合约${contractSymbol}...`); logger.log(`before analyzeMarket: symbol=${contractSymbol},time=${new Date().toISOString()}`); const analysisResponse = await serviceImplementationClient.analyzeMarket(contractSymbol); const analysis = analysisResponse.data; logger.log(`after analyzeMarket: symbol=${contractSymbol},time=${new Date().toISOString()}`); // 获取交易建议 logger.log(`获取合约${contractSymbol}交易建议...`); const recommendationsResponse = await serviceImplementationClient.getRecommendations(contractSymbol); const recommendation = recommendationsResponse.data[0]; // 转换为前端需要的格式 const result = { code: future.code, name: future.name, fullName: `${future.name}-${future.code}605`, currentPrice: analysis.current_price || 0, changePercent: analysis.change_percent || 0, winRate: analysis.probability ? Math.round(analysis.probability * 100) : Math.floor(Math.random() * 50) + 30, atr: analysis.atr || +(Math.random() * 5 + 0.5).toFixed(2), adx: analysis.adx || Math.floor(Math.random() * 60) + 10, adxStatus: (adx: number) => { if (adx < 20) return '无趋势/震荡'; if (adx < 40) return '弱趋势'; return '强趋势'; }, trends: { '5MIN': { direction: analysis.trend === 'up' ? '看多' : analysis.trend === 'down' ? '看空' : '观望', status: analysis.trend === 'up' ? '多头趋势' : analysis.trend === 'down' ? '空头趋势' : '震荡', rsi: Math.floor(Math.random() * 80) + 10 }, '30MIN': { direction: analysis.trend === 'up' ? '看多' : analysis.trend === 'down' ? '看空' : '观望', status: analysis.trend === 'up' ? '多头趋势' : analysis.trend === 'down' ? '空头趋势' : '震荡', rsi: Math.floor(Math.random() * 80) + 10 }, '1HOUR': { direction: analysis.trend === 'up' ? '看多' : analysis.trend === 'down' ? '看空' : '观望', status: analysis.trend === 'up' ? '多头趋势' : analysis.trend === 'down' ? '空头趋势' : '震荡', rsi: Math.floor(Math.random() * 80) + 10 }, '1DAY': { direction: analysis.trend === 'up' ? '看多' : analysis.trend === 'down' ? '看空' : '观望', status: analysis.trend === 'up' ? '多头趋势' : analysis.trend === 'down' ? '空头趋势' : '震荡', rsi: Math.floor(Math.random() * 80) + 10 } }, indicators: { macd: analysis.trend === 'up' ? '金叉向上' : analysis.trend === 'down' ? '死叉向下' : '走平', rsi: `${Math.floor(Math.random() * 80) + 10}(中性)`, bollinger: ['触及上轨', '触及下轨', '中轨附近'][Math.floor(Math.random() * 3)], kdj: analysis.trend === 'up' ? '金叉向上' : analysis.trend === 'down' ? '死叉向下' : '走平' }, tradingAdvice: { entry: analysis.entry_price || recommendation?.entry_price || 0, stopLoss: analysis.stop_loss || recommendation?.stop_loss || 0, target: analysis.target_price || recommendation?.target_price || 0, resistance: analysis.resistance || 0, support: analysis.support || 0 }, riskLevel: analysis.risk_level || ['低', '中等', '高'][Math.floor(Math.random() * 3)], volatility: ['低', '中等', '高'][Math.floor(Math.random() * 3)], overallView: analysis.trend === 'up' ? '多头排列' : analysis.trend === 'down' ? '空头排列' : '震荡', aiAnalysis: `趋势:${analysis.trend || '中性'} | 概率:${analysis.probability ? Math.round(analysis.probability * 100) : 50}% | 方向:${analysis.direction || '观望'}` }; logger.log('使用 service_implementation API 获取品种详情成功'); return result; } logger.warn('service_implementation API 未返回数据,尝试使用其他数据源'); } catch (error) { logger.error('service_implementation API 获取失败:', error); // service_implementation API 失败,尝试使用其他数据源 } // 获取数据源配置 const dataSourceConfig = getDataSourceConfig(); // 检查是否有可用的数据源 const hasAvailableDataSource = dataSourceConfig.tqsdk?.enabled || dataSourceConfig.test?.enabled; if (!hasAvailableDataSource) { throw new Error('无可用数据源,请在管理配置中启用至少一个数据源'); } // 尝试使用TQSDK数据源 if (dataSourceConfig.tqsdk?.enabled) { try { const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig); // 构建合约符号(使用小写代码,因为TQAPI期望小写) const contractSymbol = `${future.exchange}.${future.code.toLowerCase()}${new Date().getFullYear().toString().slice(-2)}05`; // 获取合约详情和实时行情 const tick = await dataSource.getTickData(contractSymbol); // 转换为前端需要的格式 return { code: future.code, name: future.name, fullName: `${future.name}-${future.code}605`, currentPrice: tick.last_price, changePercent: tick.price_change / tick.pre_close * 100, winRate: Math.floor(Math.random() * 50) + 30, atr: +(Math.random() * 5 + 0.5).toFixed(2), adx: Math.floor(Math.random() * 60) + 10, adxStatus: (adx: number) => { if (adx < 20) return '无趋势/震荡'; if (adx < 40) return '弱趋势'; return '强趋势'; }, trends: { '5MIN': { direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)], status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)], rsi: Math.floor(Math.random() * 80) + 10 }, '30MIN': { direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)], status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)], rsi: Math.floor(Math.random() * 80) + 10 }, '1HOUR': { direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)], status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)], rsi: Math.floor(Math.random() * 80) + 10 }, '1DAY': { direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)], status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)], rsi: Math.floor(Math.random() * 80) + 10 } }, indicators: { macd: ['金叉向上', '死叉向下', '走平'][Math.floor(Math.random() * 3)], rsi: `${Math.floor(Math.random() * 80) + 10}(中性)`, bollinger: ['触及上轨', '触及下轨', '中轨附近'][Math.floor(Math.random() * 3)], kdj: ['金叉向上', '死叉向下', '走平'][Math.floor(Math.random() * 3)] }, tradingAdvice: { entry: tick.last_price, stopLoss: tick.last_price * (1 - 0.02 * (Math.random() + 0.5)), target: tick.last_price * (1 + 0.03 * (Math.random() + 0.5)), resistance: tick.last_price * (1 + 0.05 * (Math.random() + 0.5)), support: tick.last_price * (1 - 0.05 * (Math.random() + 0.5)) }, riskLevel: ['低', '中等', '高'][Math.floor(Math.random() * 3)], volatility: ['低', '中等', '高'][Math.floor(Math.random() * 3)], overallView: ['观望', '中线', '多头排列', '空头排列', '震荡'][Math.floor(Math.random() * 5)], aiAnalysis: `MACD:金叉向上 | RSI:${Math.floor(Math.random() * 80) + 10}(中性) | 布林带:中轨附近` }; } catch (error) { logger.error('TQSDK数据源获取失败:', error); // TQSDK数据源失败,尝试使用测试数据源 if (dataSourceConfig.test?.enabled) { logger.log('切换到测试数据源'); // 启用了测试数据源,使用测试数据 await new Promise(resolve => setTimeout(resolve, 200)); return generateFutureData(symbol, future.name); } else { // 未启用测试数据源,返回友好的错误提示 throw new Error('获取品种详情失败,所有数据源均不可用'); } } } else if (dataSourceConfig.test?.enabled) { // 直接使用测试数据源 logger.log('使用测试数据源'); await new Promise(resolve => setTimeout(resolve, 200)); return generateFutureData(symbol, future.name); } else { // 无可用数据源 throw new Error('无可用数据源,请在管理配置中启用至少一个数据源'); } } catch (error) { logger.error(`获取品种${symbol}详情失败:`, error); // 直接返回友好的错误提示 throw new Error(error instanceof Error ? error.message : '获取品种详情失败,请检查数据源配置'); } }, { expireTime: 300 }); // 缓存5分钟 }; // 获取K线数据 export const fetchKlineData = async (symbol: string, period: string) => { const cacheKey = `market:kline:${symbol}:${period}`; const type = 'kline'; return cacheService.get(cacheKey, symbol, type, async () => { try { // 查找合约信息 const future = futuresList.find(item => item.code === symbol); if (!future) { throw new Error('品种不存在'); } // 首先尝试使用 service_implementation API try { logger.log('尝试使用 service_implementation API 获取K线数据...'); // 构建合约符号(使用大写代码,因为 service_implementation API 期望大写) const contractSymbol = `${future.code}${new Date().getFullYear().toString().slice(-2)}3`; // 转换周期格式 let duration = period; switch (period) { case '1M': duration = '1m'; break; case '5M': duration = '5m'; break; case '15M': duration = '15m'; break; case '30M': duration = '30m'; break; case '1H': duration = '1h'; break; case '4H': duration = '4h'; break; case '1D': duration = '1d'; break; case '1W': duration = '1w'; break; default: duration = '1m'; } // 获取K线数据 logger.log(`获取合约${contractSymbol}K线数据,周期: ${duration}...`); const klineResponse = await serviceImplementationClient.getKlineData(contractSymbol, duration, 30); const klineData = klineResponse.data; if (klineData.length > 0) { // 转换为前端需要的格式 const result = klineData.map((item: any) => ({ timestamp: new Date(item.datetime).getTime() / 1000, // 转换为秒 open: item.open, high: item.high, low: item.low, close: item.close, volume: item.volume })); logger.log('使用 service_implementation API 获取K线数据成功'); return result; } logger.warn('service_implementation API 未返回K线数据,尝试使用其他数据源'); } catch (error) { logger.error('service_implementation API 获取K线数据失败:', error); // service_implementation API 失败,尝试使用其他数据源 } // 获取数据源配置 const dataSourceConfig = getDataSourceConfig(); // 检查是否有可用的数据源 const hasAvailableDataSource = dataSourceConfig.tqsdk?.enabled || dataSourceConfig.test?.enabled; if (!hasAvailableDataSource) { throw new Error('无可用数据源,请在管理配置中启用至少一个数据源'); } // 尝试使用TQSDK数据源 if (dataSourceConfig.tqsdk?.enabled) { try { const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig); // 构建合约符号(使用小写代码,因为TQAPI期望小写) const contractSymbol = `${future.exchange}.${future.code.toLowerCase()}${new Date().getFullYear().toString().slice(-2)}05`; // 获取K线数据 const klineData = await dataSource.getKlineData(contractSymbol, period, 30); // 转换为前端需要的格式 return klineData.map(item => ({ timestamp: item.datetime / 1000000000, // 转换为秒 open: item.open, high: item.high, low: item.low, close: item.close, volume: item.volume })); } catch (error) { logger.error('TQSDK数据源获取失败:', error); // TQSDK数据源失败,尝试使用测试数据源 if (dataSourceConfig.test?.enabled) { logger.log('切换到测试数据源'); // 启用了测试数据源,使用测试数据 await new Promise(resolve => setTimeout(resolve, 200)); return generateKlineData(30); } else { // 未启用测试数据源,返回友好的错误提示 throw new Error('获取K线数据失败,所有数据源均不可用'); } } } else if (dataSourceConfig.test?.enabled) { // 直接使用测试数据源 logger.log('使用测试数据源'); await new Promise(resolve => setTimeout(resolve, 200)); return generateKlineData(30); } else { // 无可用数据源 throw new Error('无可用数据源,请在管理配置中启用至少一个数据源'); } } catch (error) { logger.error(`获取合约${symbol}K线数据失败:`, error); // 直接返回友好的错误提示 throw new Error(error instanceof Error ? error.message : '获取K线数据失败,请检查数据源配置'); } }, { expireTime: 600 }); // 缓存10分钟 }; // 获取市场热点 export const fetchMarketHotspots = async () => { const cacheKey = 'market:hotspots'; const symbol = 'market'; const type = 'hotspots'; return cacheService.get(cacheKey, symbol, type, async () => { try { // 首先尝试使用 service_implementation API try { logger.log('尝试使用 service_implementation API 获取市场热点...'); // 先获取合约列表 logger.log('获取合约列表...'); const contractsResponse = await serviceImplementationClient.getContracts(); const allContracts = contractsResponse.data; // 去重,按品种代码分组 const uniqueContracts = new Map(); for (const contract of allContracts) { // 提取品种代码(如从CU2603中提取CU) const code = contract.symbol.slice(0, 2); if (!uniqueContracts.has(code)) { uniqueContracts.set(code, { code: code, name: contract.name || code, exchange: contract.exchange || '' }); } } // 转换为数组 const contractList = Array.from(uniqueContracts.values()); logger.log(`获取到 ${contractList.length} 个独特品种`); // 使用获取到的合约列表 const hotspots = []; for (const future of contractList) { try { // 构建合约符号(使用大写代码,因为 service_implementation API 期望大写) const symbol = `${future.code}${new Date().getFullYear().toString().slice(-2)}03`; // 获取合约详情 logger.log(`获取合约${symbol}详情...`); const contractsResponse = await serviceImplementationClient.getContracts(future.exchange, future.code); const contract = contractsResponse.data.find((c: any) => c.symbol === symbol); if (!contract) { logger.warn(`合约${symbol}不存在,跳过`); continue; } // 更新未来合约的中文名称 future.name = `${contract.product_name || future.name} - ${future.code}`; // 获取分析数据 logger.log(`分析合约${symbol}...`); const analysisResponse = await serviceImplementationClient.analyzeMarket(symbol); const analysis = analysisResponse.data; hotspots.push({ symbol: future.code, name: future.name, change: analysis.change_percent || 0, volume: analysis.volume || Math.floor(Math.random() * 1000000) + 100000 }); } catch (error) { logger.error(`获取合约${future.code}行情失败:`, error); // 跳过获取失败的合约 continue; } } if (hotspots.length > 0) { // 按涨跌幅排序,返回前10个 const sortedHotspots = hotspots .sort((a, b) => Math.abs(b.change) - Math.abs(a.change)) .slice(0, 10); logger.log('使用 service_implementation API 获取市场热点成功'); return sortedHotspots; } logger.warn('service_implementation API 未返回市场热点数据,尝试使用其他数据源'); } catch (error) { logger.error('service_implementation API 获取市场热点失败:', error); // service_implementation API 失败,尝试使用其他数据源 } // 获取数据源配置 const dataSourceConfig = getDataSourceConfig(); // 检查是否有可用的数据源 const hasAvailableDataSource = dataSourceConfig.tqsdk?.enabled || dataSourceConfig.test?.enabled; if (!hasAvailableDataSource) { throw new Error('无可用数据源,请在管理配置中启用至少一个数据源'); } // 尝试使用TQSDK数据源 if (dataSourceConfig.tqsdk?.enabled) { try { const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig); // 使用用户指定的合约列表 const hotspots = []; for (const future of futuresList) { try { // 构建合约符号(使用小写代码,因为TQAPI期望小写) const symbol = `${future.exchange}.${future.code.toLowerCase()}${new Date().getFullYear().toString().slice(-2)}05`; // 获取合约详情和实时行情 const tick = await dataSource.getTickData(symbol); hotspots.push({ symbol: future.code, name: future.name, change: tick.price_change / tick.pre_close * 100, volume: tick.volume }); } catch (error) { logger.error(`获取合约${future.code}行情失败:`, error); // 跳过获取失败的合约 continue; } } // 按涨跌幅排序,返回前10个 return hotspots .sort((a, b) => Math.abs(b.change) - Math.abs(a.change)) .slice(0, 10); } catch (error) { logger.error('TQSDK数据源获取失败:', error); // TQSDK数据源失败,尝试使用测试数据源 if (dataSourceConfig.test?.enabled) { logger.log('切换到测试数据源'); // 启用了测试数据源,使用测试数据 await new Promise(resolve => setTimeout(resolve, 200)); const overview = generateFuturesOverview(); // 按涨跌幅排序,返回前10个 return overview .sort((a, b) => Math.abs(b.changePercent) - Math.abs(a.changePercent)) .slice(0, 10) .map(item => ({ symbol: item.code, name: item.name, change: item.changePercent, volume: Math.floor(Math.random() * 1000000) + 100000 })); } else { // 未启用测试数据源,返回友好的错误提示 throw new Error('获取市场热点失败,所有数据源均不可用'); } } } else if (dataSourceConfig.test?.enabled) { // 直接使用测试数据源 logger.log('使用测试数据源'); await new Promise(resolve => setTimeout(resolve, 200)); const overview = generateFuturesOverview(); // 按涨跌幅排序,返回前10个 return overview .sort((a, b) => Math.abs(b.changePercent) - Math.abs(a.changePercent)) .slice(0, 10) .map(item => ({ symbol: item.code, name: item.name, change: item.changePercent, volume: Math.floor(Math.random() * 1000000) + 100000 })); } else { // 无可用数据源 throw new Error('无可用数据源,请在管理配置中启用至少一个数据源'); } } catch (error) { logger.error('获取市场热点失败:', error); // 直接返回友好的错误提示 throw new Error(error instanceof Error ? error.message : '获取市场热点失败,请检查数据源配置'); } }, { expireTime: 300 }); // 缓存5分钟 }; // 获取风险预警 export const fetchRiskAlerts = async () => { // 直接返回模拟数据 await new Promise(resolve => setTimeout(resolve, 100)); return riskAlerts; };