/** * 涨停数据服务 * 获取涨停板块、个股、统计等数据 */ import { apiRequest, API_BASE } from './api'; export const ztService = { /** * 获取指定日期的涨停数据 * @param {string} dateStr - 日期 YYYYMMDD 格式 * @returns {Promise} 涨停数据 */ getDailyZt: async (dateStr) => { try { // 尝试从静态数据获取 const response = await fetch(`${API_BASE}/data/zt/daily/${dateStr}.json`); if (response.ok) { const data = await response.json(); return { success: true, data }; } return { success: false, message: '数据不存在' }; } catch (error) { console.error('获取涨停数据失败:', error); return { success: false, message: error.message }; } }, /** * 获取最新的涨停数据(今日或最近交易日) * @returns {Promise} 最新涨停数据 */ getLatestZt: async () => { try { const response = await fetch(`${API_BASE}/data/zt/latest.json`); if (response.ok) { const data = await response.json(); return { success: true, data }; } // 如果没有 latest.json,尝试获取今天的数据 const today = new Date(); const dateStr = today.toISOString().slice(0, 10).replace(/-/g, ''); return ztService.getDailyZt(dateStr); } catch (error) { console.error('获取最新涨停数据失败:', error); return { success: false, message: error.message }; } }, /** * 获取可用的涨停数据日期列表 * @returns {Promise} 日期列表 */ getAvailableDates: async () => { try { const response = await fetch(`${API_BASE}/data/zt/dates.json`); if (response.ok) { const data = await response.json(); return { success: true, data }; } return { success: false, data: [] }; } catch (error) { return { success: false, data: [] }; } }, /** * 获取板块详情(包含该板块的所有涨停股票) * @param {string} dateStr - 日期 * @param {string} sectorName - 板块名称 * @returns {Promise} 板块详情 */ getSectorDetail: async (dateStr, sectorName) => { const result = await ztService.getDailyZt(dateStr); if (!result.success) return result; const sectorData = result.data.sector_data?.[sectorName]; if (!sectorData) { return { success: false, message: '板块不存在' }; } // 获取该板块的股票详情 const stocks = result.data.stocks?.filter(s => sectorData.stock_codes?.includes(s.scode) ) || []; return { success: true, data: { name: sectorName, count: sectorData.count, stocks, related_events: sectorData.related_events || [], } }; }, /** * 获取连板统计 * @param {object} ztData - 涨停原始数据 * @returns {object} 统计结果 */ calculateStats: (ztData) => { if (!ztData) return null; const stocks = ztData.stocks || ztData.stock_infos || []; // 连板统计 const continuousStats = {}; // 时间统计 const timeStats = { '秒板': 0, '早盘': 0, '盘中': 0, '尾盘': 0 }; // 公告驱动统计 let announcementCount = 0; stocks.forEach(stock => { // 连板统计 const continuous = stock.continuous_days || '首板'; continuousStats[continuous] = (continuousStats[continuous] || 0) + 1; // 时间统计 const time = stock.formatted_time || stock.zt_time; if (time) { const hour = parseInt(time.split(':')[0]); const minute = parseInt(time.split(':')[1]); const totalMinutes = hour * 60 + minute; if (totalMinutes <= 9 * 60 + 31) { timeStats['秒板']++; } else if (totalMinutes <= 10 * 60 + 30) { timeStats['早盘']++; } else if (totalMinutes <= 14 * 60) { timeStats['盘中']++; } else { timeStats['尾盘']++; } } // 公告驱动 if (stock.is_announcement) { announcementCount++; } }); return { total: ztData.total_stocks || stocks.length, continuousStats, timeStats, announcementCount, announcementRatio: stocks.length > 0 ? ((announcementCount / stocks.length) * 100).toFixed(1) : 0, }; }, /** * 获取热门板块排序 * @param {object} ztData - 涨停原始数据 * @param {number} limit - 返回数量 * @returns {Array} 排序后的板块列表 */ getHotSectors: (ztData, limit = 10) => { if (!ztData?.sector_data) return []; // 需要过滤掉的板块名称 const excludedSectors = ['其他', '公告']; const sectors = Object.entries(ztData.sector_data) .filter(([name]) => !excludedSectors.includes(name)) // 过滤掉"其他"和"公告" .map(([name, data]) => ({ name, count: data.count || 0, stock_codes: data.stock_codes || [], related_events: data.related_events || [], // 计算热度分数:涨停数 * 权重 + 关联事件数 * 权重 hotScore: (data.count || 0) * 10 + (data.related_events?.length || 0) * 5, })) .sort((a, b) => b.hotScore - a.hotScore) .slice(0, limit); return sectors; }, /** * 获取连板龙头股 * @param {object} ztData - 涨停原始数据 * @param {number} minDays - 最小连板天数 * @returns {Array} 连板股列表 */ getContinuousLeaders: (ztData, minDays = 2) => { const stocks = ztData?.stocks || ztData?.stock_infos || []; // 解析连板天数 const parseDay = (str) => { if (!str || str === '首板') return 1; const match = str.match(/(\d+)/); return match ? parseInt(match[1]) : 1; }; return stocks .filter(s => parseDay(s.continuous_days) >= minDays) .sort((a, b) => parseDay(b.continuous_days) - parseDay(a.continuous_days)); }, /** * 获取热门关键词 * @param {object} ztData - 涨停原始数据 * @param {number} limit - 返回数量 * @returns {Array} 关键词列表 */ getHotKeywords: (ztData, limit = 12) => { return (ztData?.word_freq_data || []).slice(0, limit); }, /** * 获取日历综合数据(涨停数 + 事件数 + 涨跌幅 + 热门概念) * @param {number} year - 年份 * @param {number} month - 月份 (1-12) * @returns {Promise} 日历数据 */ getCalendarData: async (year, month) => { try { // 获取日期列表 const datesResult = await ztService.getAvailableDates(); if (!datesResult.success) { return { success: false, data: [] }; } const dates = datesResult.data.dates || []; const calendarData = []; // 过滤当月数据 const monthStr = String(month).padStart(2, '0'); const monthPrefix = `${year}${monthStr}`; for (const dateInfo of dates) { const dateStr = dateInfo.date; if (!dateStr.startsWith(monthPrefix)) continue; // 获取每日详细数据 const dailyResult = await ztService.getDailyZt(dateStr); let topSector = ''; let indexChange = null; if (dailyResult.success && dailyResult.data) { // 获取最热板块 const hotSectors = ztService.getHotSectors(dailyResult.data, 1); topSector = hotSectors[0]?.name || ''; // 获取指数涨跌幅(如果数据中有) indexChange = dailyResult.data.index_change || null; } calendarData.push({ date: dateStr, ztCount: dateInfo.count || 0, topSector, eventCount: dateInfo.event_count || 0, indexChange, }); } return { success: true, data: calendarData }; } catch (error) { console.error('获取日历数据失败:', error); return { success: false, data: [] }; } }, /** * 快速获取日历数据(只从 dates.json 获取基础信息) * @param {number} year - 年份 * @param {number} month - 月份 (1-12) * @returns {Promise} 日历数据 */ getCalendarDataFast: async (year, month) => { try { const datesResult = await ztService.getAvailableDates(); if (!datesResult.success) { return { success: false, data: [] }; } const dates = datesResult.data.dates || []; const monthStr = String(month).padStart(2, '0'); const monthPrefix = `${year}${monthStr}`; const calendarData = dates .filter(d => d.date.startsWith(monthPrefix)) .map(d => ({ date: d.date, ztCount: d.count || 0, formattedDate: d.formatted_date, topSector: d.top_sector || '', eventCount: d.event_count || 0, indexChange: d.index_change || null, })); return { success: true, data: calendarData }; } catch (error) { console.error('获取日历数据失败:', error); return { success: false, data: [] }; } }, }; export default ztService;