ios app
This commit is contained in:
304
argon-pro-react-native/src/services/ztService.js
Normal file
304
argon-pro-react-native/src/services/ztService.js
Normal file
@@ -0,0 +1,304 @@
|
||||
/**
|
||||
* 涨停数据服务
|
||||
* 获取涨停板块、个股、统计等数据
|
||||
*/
|
||||
|
||||
import { apiRequest, API_BASE } from './api';
|
||||
|
||||
export const ztService = {
|
||||
/**
|
||||
* 获取指定日期的涨停数据
|
||||
* @param {string} dateStr - 日期 YYYYMMDD 格式
|
||||
* @returns {Promise<object>} 涨停数据
|
||||
*/
|
||||
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<object>} 最新涨停数据
|
||||
*/
|
||||
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<object>} 日期列表
|
||||
*/
|
||||
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<object>} 板块详情
|
||||
*/
|
||||
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<object>} 日历数据
|
||||
*/
|
||||
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<object>} 日历数据
|
||||
*/
|
||||
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;
|
||||
Reference in New Issue
Block a user