天天基金网API
Contents
天天基金网获取某基金净值的API接口主要有以下几个:
- 基金实时信息接口:用于获取基金的实时信息,包括单位净值等数据。
http://fundgz.1234567.com.cn/js/{基金代码}.js
其中 {基金代码}
是要查询的基金代码,例如 001186
。
- 基金详细信息接口:用于获取基金的详细信息,包括历史单位净值、累计净值等数据。
http://fund.eastmoney.com/pingzhongdata/{基金代码}.js
同样,{基金代码}
是要查询的基金代码,例如 001186
。
- 所有基金代码接口:用于获取所有基金的代码列表。
http://fund.eastmoney.com/js/fundcode_search.js
这个接口返回一个JavaScript数组,包含所有基金的代码和其他信息。
这些API接口可以直接在浏览器中访问,或者通过编程方式(如Python、JavaScript等)请求并解析数据。需要注意的是,API的具体使用可能受到天天基金网的使用条款和条件限制,因此在实际使用中应遵守相关规定。
一个获取指定基金并计算收益的代码示例:
/**
* 天天基金网指定基金查询,并生成日报
* 可对指定基金进行持仓收益和年化收益的计算
* 并推送nfty消息通知
*/
const axios = require('axios');
/**用户基金列表的内容
* 修改此处,以便本脚本分析指定的基金产品。
* 格式为: * 基金代码,确认日期(可选)
* 每行只写一个基金代码和确认日期。中间有英文逗号隔开
* 确认日期可以为空,为空时不分析此基金的持仓情况
* 确认日期必须是yyyy-mm-dd的格式
*/
const foundListText = `
006435,2022-08-09
009909,2020-11-23
`;
/*
006297
003025,2021-06-18
006392,2021-12-28
006435,2022-08-09
009909,2020-11-23
010027,2020-12-16
010277,2021-01-15
010534,2021-01-27
010659,2020-12-17
011099,2021-01-13
011752,2021-04-27
012967,2021-08-26
013323,2022-01-18
014188,2021-12-14
014809,2022-01-25
015769,2022-06-28
*/
//天天基金网中的时间戳使用的是UTC标准时间
//时间偏移量,天天基金网中所有的时间戳都是UTC时间,比东八区时间要晚(小)28800000毫秒
//应该在所有的时间输出显示中使用,以获取正确的时间。(在时间计算、对比时,不需要用)
//例如:new Date(1629907200000+dataOffset)
const dataOffset = 28800000;
/**
* 发送推送消息到ntfy服务
* @param {string} topic - 消息的主题
* @param {string} message - 要发送的消息内容
* @param {string} title - 消息的大标题(默认不使用大标题)
* @param {int} [priority=3] - 消息的优先级,可以是1-5的整数,分别是最小、小、默认、大、最大
* @param {array} [tags] - 消息的标签,字符串数组。
* @param {array} [attach] - 附件、图片URL。
* @param {array} [click] - 消息被点击时跳转的url。
* @param {string} [serverUrl='https://ntfy.sh'] - ntfy服务的URL,默认为官方服务器
*/
async function sendNtfyMessage(topic, message, title = null, priority = 3, tags = null, attach = null, click = null, serverUrl = 'https://ntfy.sh') {
try {
if (topic == null || message == null || priority > 5 || priority < 1) {
console.error("topic、message不能为空,priority的值只能取1、2、3、4、5!");
}
// 构建请求的headers
const headers = new Headers({
'Content-Type': 'application/json',
});
// 创建消息Object
const payload = { topic, message, priority };
if (title) payload.title = title;
if (tags) payload.tags = tags;
if (attach) payload.attach = attach;
if (click) payload.click = click;
// 构建请求的body
const body = JSON.stringify(payload);
//console.log('拟发出的消息body:', body);
// 发送POST请求到ntfy服务
const response = await fetch(serverUrl, { method: 'POST', headers: headers, body: body });
// 检查响应状态
if (!response.ok) {
throw new Error(`HTTP 错误! 状态: ${response.status}`);
}
// 获取响应数据
const data = await response.json();
console.log('消息发送成功:\n', ""/*data*/);
} catch (error) {
console.error('消息发送失败:\n', error);
}
}
/**
* 解析基金列表文本,并生成一个包含基金代码和基金确认日的二维数组(如果基金日期不存在或不正确,则解析的日期值会为:NaN)
*/
function readFoundListFile(text) {
console.log(`readFoundListFile基金列表文本`);
try {
const lines = text.split('\n');
let resultArray = lines.map(line => {
const [fundCode, dateString] = line.split(',');
// 将时间字符串转换为Date对象
let date = new Date(dateString + "T00:00:00+08:00");
// 获取毫秒级别的Unix时间戳
let formattedDate = date.getTime();
// 如果配置文件没有填写时间或者解析错误,则把时间设置为0
if (isNaN(formattedDate)) {
formattedDate = 0;
}
//console.log(`读取到:基金代码${fundCode.trim()},确认日 ${new Date(formattedDate+dataOffset).toDateString()}`);
return [fundCode.trim(), formattedDate];
});
//console.log('解析基金列表文本结果:\n', resultArray);
//剔除空行
resultArray = resultArray.filter(row => row[0] !== null && row[0] !== undefined);
return resultArray;
} catch (err) {
console.error('解析基金列表文本异常:\n', err);
return [];
}
}
/**
* 读取并分析天天基金网的某基金信息。
* @param {*} url 包含基金数据的url,例如:http://fund.eastmoney.com/pingzhongdata/012967.js
* @param {*} confirmDate 基金确认日,格式例如:1629907200000
* @param {*} units 基金持有份额(预留,目前没用)
* @returns
*/
async function fetchFoundData(url, confirmDate = null, units = null) {
//基金信息Object
let info = null;
try {
//读取基金信息url的数据并分析其中的txt数据
const response = await axios.get(url);
const dataText = response.data;
info = {}
//基金持有份额(没用)
info.units = units ? units : 0;
//最新净值的日期
info.nowDate = 0;
//当前累计净值
info.nowACWorthTrend = 0
//成立日
info.startDate = 0;
//成立日净值
info.startACWorthTrend = 0
//确认日
info.confirmDate = confirmDate ? confirmDate : 0;
//确认时累计净值
info.confirmACWorthTrend = 0
//成立以来收益率
info.syl_total = 0
//持有收益率
info.syl_cfm = 0
//持有收益率-年化
info.syl_cfm_y = 0
// 解析加载的JS文件。这段代码莫名其妙的,明明可以直接给info.xx赋值
const extractVariables = (script) => {
let fS_nameMatch = script.match(/var\s*fS_name\s*=\s*"(.*?)";/);//基金名
let fS_codeMatch = script.match(/var\s*fS_code\s*=\s*"(.*?)";/);//基金代码
let Data_ACWorthTrendMatch = script.match(/var\s*Data_ACWorthTrend\s*=\s*(\[.*?\]);/s);
let rateInSimilarPersentMatch = script.match(/var\s*Data_rateInSimilarPersent\s*=\s*(\[.*?\]);/s);//同类排名百分比
let syl_1yMatch = script.match(/var\s*syl_1n\s*=\s*"(.*?)";/);//近1年收益
let syl_6mMatch = script.match(/var\s*syl_6y\s*=\s*"(.*?)";/);//近半年收益
let syl_3mMatch = script.match(/var\s*syl_3y\s*=\s*"(.*?)";/);//近3个月收益
let syl_1mMatch = script.match(/var\s*syl_1y\s*=\s*"(.*?)";/);//近1个月收益率
let fS_name = fS_nameMatch ? fS_nameMatch[1] : "-";//基金名
let fS_code = fS_codeMatch ? fS_codeMatch[1] : "-";//基金代码
let syl_1y = syl_1yMatch ? parseFloat(syl_1yMatch[1]) : 0;//近1年收益
let syl_6m = syl_6mMatch ? parseFloat(syl_6mMatch[1]) : 0;//近半年收益
let syl_3m = syl_3mMatch ? parseFloat(syl_3mMatch[1]) : 0;//近3个月收益
let syl_1m = syl_1mMatch ? parseFloat(syl_1mMatch[1]) : 0;//近1个月收益率
let Data_ACWorthTrend = Data_ACWorthTrendMatch ? JSON.parse(Data_ACWorthTrendMatch[1]) : [];//基金累计净值数组
let rateInSimilarPersent = rateInSimilarPersentMatch ? JSON.parse(rateInSimilarPersentMatch[1]) : [];//同类排名百分比
return { fS_name, fS_code, Data_ACWorthTrend, rateInSimilarPersent, syl_1y, syl_6m, syl_3m, syl_1m };
};
Object.assign(info, extractVariables(dataText));
//处理基金累计净值数组Data_ACWorthTrend,计算成立以来收益率,获取确认时净值、最新净值、最新净值日期
if (info.Data_ACWorthTrend && info.Data_ACWorthTrend.length > 0) {
//最新净值日期
info.nowDate = info.Data_ACWorthTrend[info.Data_ACWorthTrend.length - 1][0];//最新净值日
//最新净值
info.nowACWorthTrend = info.Data_ACWorthTrend[info.Data_ACWorthTrend.length - 1][1];
//最早净值日(成立日吗???)
info.startDate = info.Data_ACWorthTrend[0][0];
//成立日(最早那一天)净值
info.startACWorthTrend = info.Data_ACWorthTrend[0][1];
//累计收益率
info.syl_total = ((info.nowACWorthTrend - info.startACWorthTrend) / info.startACWorthTrend) * 100;
//保留两位小数
info.syl_total = Math.round(info.syl_total * 100) / 100;
//判断确认日的日期是否符合要求(不为0且不晚于最近净值日),如果符合要求,则计算持有份额的收益里
if (info.confirmDate == 0 || info.confirmDate > info.nowDate) {
console.log(`【提示】基金${info.fS_name} ${info.fS_code} 的确认日期${new Date(info.confirmDate + dataOffset).toDateString()}为0或大于最近净值日${new Date(info.nowDate + dataOffset).toDateString()},将视同无确认日期,未持有。`);
info.confirmDate = 0;
info.syl_cfm = -9999;
} else {
//查找"确认日"的累计净值,并计算收益
//二分查找第一个大于等于“确认日”的元素
let low = 0;
let high = info.Data_ACWorthTrend.length - 1;
while (low <= high) {
const mid = Math.floor((low + high) / 2);
if (info.Data_ACWorthTrend[mid][0] < info.confirmDate) {
low = mid + 1;
} else {
high = mid - 1;
}
}
// 检查是否存在大于等于“确认日”的元素
if (low < info.Data_ACWorthTrend.length) {
if (info.confirmDate != info.Data_ACWorthTrend[low][0]) {
console.log(`【提示】基金${info.fS_name} ${info.fS_code} 的确认日${new Date(info.confirmDate + dataOffset)}当天无净值数据,确认日将修改到最近日期${new Date(info.Data_ACWorthTrend[low][0] + dataOffset)}`);
info.confirmDate = info.Data_ACWorthTrend[low][0];
}
info.confirmACWorthTrend = info.Data_ACWorthTrend[low][1]; // 找到了,返回对应的值
} else {
info.confirmACWorthTrend = 0; // 没有找到,返回0
}
//如果找到了确认日对应的净值,则计算持有收益率
if (info.confirmACWorthTrend != 0) {
info.syl_cfm = ((info.nowACWorthTrend - info.confirmACWorthTrend) / info.confirmACWorthTrend) * 100;
info.syl_cfm_y = info.syl_cfm / ((info.confirmACWorthTrend - info.confirmDate) / 86400000) * 365;
info.syl_cfm = Math.round(info.syl_cfm * 100) / 100;
info.syl_cfm_y = Math.round(info.syl_cfm_y * 100) / 100;
if (info.syl_cfm === Infinity) info.syl_cfm = 0;
if (info.syl_cfm_y === Infinity) info.syl_cfm_y = 0;
//console.log(`基金${info.fS_code}的确认日${info.confirmDate}净值${info.confirmACWorthTrend}--最近日${info.nowDate}净值${info.nowACWorthTrend}`);
}
}
} else {
console.error('基金累计净值数据Data_acWorthTrend不可用或为空');
}
//当前同类排名百分比
info.rank_total = 0;
if (info.rateInSimilarPersent && info.rateInSimilarPersent.length > 0) {
//获取百分比
info.rank_total = info.rateInSimilarPersent[info.rateInSimilarPersent.length - 1][1];
} else {
console.error('基金百分比排名数据Data_ACWorthTrend不可用或为空');
}
//使用示例:
/*
console.log(`基金名称: ${info.fS_name}--------------------------`);
console.log(`基金代码: ${info.fS_code}`);
console.log(`成立日: ${new Date(info.startDate+dataOffset).toDateString()},净值:${info.startACWorthTrend}`);
console.log(`基金确认日: ${new Date(info.confirmDate+dataOffset).toDateString()},净值:${info.confirmACWorthTrend}`);
console.log(`最新净值日: ${new Date(info.nowDate+dataOffset).toDateString()},净值:${info.nowACWorthTrend}`);
console.log(`成立以来收益: ${info.syl_total}%`);
console.log(`近1年收益: ${info.syl_1y}%`);
console.log(`近6月收益: ${info.syl_6m}%`);
console.log(`近3月收益: ${info.syl_3m}%`);
console.log(`近1月收益: ${info.syl_1m}%`);
console.log(`持仓收益: ${info.syl_cfm}%`);
console.log(`百分比排名: ${info.rank_total}%`);
*/
return info;
} catch (error) {
console.error('解析基金数据时出错:', /*error*/"这里被注释掉了,可能是基金代码不存在或者foundListText字符串中有空白行。");
return null;
}
}
async function main() {
// 获取基金名单
const foundList = readFoundListFile(foundListText);
// 定义基金信息数组
let foundInfoList = [];
// 创建一个数组,包含所有异步调用的Promise
const promises = foundList.map(item => {
const url = `http://fund.eastmoney.com/pingzhongdata/${item[0]}.js`;
return fetchFoundData(url, item[1]);
});
try {
// 使用Promise.all等待所有Promise完成
foundInfoList = await Promise.all(promises);
// 删除null元素
foundInfoList = foundInfoList.filter(item => item !== null);
console.log('异步操作全部遍历完毕');
} catch (error) {
// 如果任何一个Promise失败,会进入这里
console.error('异步操作中出现错误:', error);
}
// 对基金数组进行排序:syl_cfm降序排序,rank_total降序排序
if (foundInfoList.length > 0) {
console.log("对信息进行排序...")
foundInfoList.sort((a, b) => {
// 首先按照syl_cfm(持仓收益)降序排序
if (b.syl_cfm - a.syl_cfm !== 0) {
return b.syl_cfm - a.syl_cfm;
}
// 如果syl_cfm相同,则按照rank_total降序排序
return b.rank_total - a.rank_total;
});
}
//基金报告文本
console.log("开始打印基金日报...")
//昨天的时间戳
const dateYesterday = new Date(new Date().setDate(new Date().getDate() - 1)).setHours(0, 0, 0, 0);
let reporttxt = `📰基金日报(${new Date().getMonth() + 1}月${new Date().getDate()}日)📰\n\n`;
//打印基金信息
foundInfoList.forEach(item => {
////如果有持仓日,则根据持仓收益显示对应的图标
if (item.confirmDate != 0) {
if (item.syl_cfm > 0) {
reporttxt += "💰";
}
else {
reporttxt += "💸";
}
}
else {
reporttxt += "📊";
}
reporttxt += `${item.fS_name} ${item.fS_code} `;
//最新净值日如果不是昨天的,则显示最新净值日期,否则隐藏
if (item.nowDate <= dateYesterday) {
reporttxt += `${new Date(item.nowDate + dataOffset).getMonth() + 1}月${new Date(item.nowDate + dataOffset).getDate()}日\n`;
} else {
reporttxt += `\n`;
}
//如果有持仓日,则显示持仓收益
if (item.confirmDate != 0) {
reporttxt += ` - 持仓收益${item.syl_cfm}% 年化${item.syl_cfm_y}%\n`;
reporttxt += ` - 持仓日${new Date(item.confirmDate + dataOffset).getFullYear()}年${new Date(item.confirmDate + dataOffset).getMonth() + 1}月${new Date(item.confirmDate + dataOffset).getDate()}日\n`
}
reporttxt += ` - 近1月${item.syl_1m}%|近3月${item.syl_3m}%|近半年${item.syl_6m}%|近1年${item.syl_1y}%|成立来${item.syl_total}%\n`;
//排名${item.rank_total}%
reporttxt += ` - 排名${Math.round(100 - item.rank_total)}/100\n\n`;
});
reporttxt += `*持仓收益根据当前净值与持仓当日净值计算而成,未考虑持仓成本(如手续费)。`;
console.log("\n------------\n" + reporttxt + "------------\n");
// 发送消息
sendNtfyMessage('hotine', reporttxt, null, 4, ['基金日报', 'hotine']);
}
// 运行主函数
main();