// ==UserScript==
// @name 天天基金数据抽取
// @namespace http://tampermonkey.net/
// @version 2.0.7
// @description 用以快捷显示累计净值,任期回报,平均回报,一年前累计净值,月高点,月低点
// @author aotmd
// @match *://fund.eastmoney.com/*
// @match *://*.eastmoney.com/*
// @exclude *://fund.eastmoney.com/manager/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @noframes
// @license MIT
// ==/UserScript==
const setting = {
是否查看近一年净值: true,//批量查看时防止卡顿,建议关闭
自动关闭不满5年基金: false,//自动关闭基金经理任期低于5年的基金,选指数基金时关闭
自动关闭平均回报过低基金: false,//自动关闭业绩不好的基金
指数基金年化代替为成立以来年化: false,//用来选指数基金
指数用成立时间不满5年则关闭: false,//用来选指数基金
有历史记录则关闭:false,//用来关闭已有本地记录的基金,不明白请不要开启
满五年日期常量: "2017-12-01",
回报率常量: 19,
};
//GM_setValue对象,通过6位数代码取出
let STATIC = {
max: -99999.9,//近20个交易日最大值累计净值
maxDate: '',//最大值的日期
min: 99999.9,//近20个交易日最小值累计净值
minDate: '',//最小值的日期
yearAgo: 0.0,//一年前的累计净值
yearAgoDate: '',//累计净值的日期
average:-99999.9,//近20个交易日平均数
};
//装净值信息
let list = [];
(function () {
addLoadEvent(() => {
//window.scrollTo(0,2150);
const url = window.location.href;
//主页面
const str = /fund\.eastmoney\.com\/(\d{6})\.html/i;
//净值页面
const str2 = /.+\.eastmoney\.com\/jjjz_(\d{6}).html/i;
if (str.test(url)) {//为主页面
//视图元素生成
//基金名称
let divElement1 = document.createElement('div');
divElement1.className = "gs1";
document.body.appendChild(divElement1);
divElement1.style = "top: calc( 15% - 42px );font-size: 16px;";
//基金基本涨跌
let divElement2 = document.createElement('div');
divElement2.className = "gs1";
document.body.appendChild(divElement2);
divElement2.title='右键复制';
divElement2.oncontextmenu=(e)=>{
e.preventDefault();//阻止浏览器的默认事件
let s=累计净值+'|'+STATIC.yearAgo+'|'+任期回报;
copyString(s);
//添加描边表示生效反馈
divElement2.style="box-shadow: 0 0 5px 0 rgb(18 80 18 / 50%), 0 0 0 2px rgb(0 0 0 / 60%);";
};
//基金短期区间位
let divElement3 = document.createElement('div');
divElement3.className = "gs1";
document.body.appendChild(divElement3);
divElement3.style = "top: calc( 15% + 190px );";
divElement3.title='右键复制';
divElement3.oncontextmenu=(e)=>{
e.preventDefault();//阻止浏览器的默认事件
let s=STATIC.maxDate+'|'+STATIC.max+'|'+STATIC.minDate+'|'+STATIC.min+'|'+STATIC.average;
copyString(s);
//添加描边表示生效反馈
divElement3.style="top: calc( 15% + 190px );box-shadow: 0 0 5px 0 rgb(18 80 18 / 50%), 0 0 0 2px rgb(0 0 0 / 60%);";
};
//显示基金名称,名称面板
const 基金名称 = document.querySelector("#body > div:nth-child(8) > div > div > a:nth-child(7)").innerText;
divElement1.innerHTML = 基金名称;
//基本数据处理与抓取
const 任期回报 = (document.querySelector("tr.noBorder > td.td04.bold").innerText.replace("%", "") / 100).toFixed(4);
const 任职时间 = document.querySelector("tr.noBorder > td.td01").innerText.replace("~至今", "");
const 累计净值 = document.querySelector("dl.dataItem03 > dd.dataNums > span").innerText;
const 净值时间 = document.querySelector("dl.dataItem02 > dt > p").innerText.split('(')[1].replace(')', '');
const 基金代码 = window.location.pathname.match(/(\d{6})/i)[1];
let 平均回报率 = ((Math.pow(Number(任期回报) + 1, 1 / (getNumberOfDays(new Date(), 任职时间) / 365)) - 1) * 100).toFixed(2);
const 成立时间 = document.querySelector("div.infoOfFund > table > tbody > tr:nth-child(2) > td:nth-child(1)").innerText.replace("成 立 日:", "");
/**-------------------------------------设置项判断---------------------------------------- **/
//指数基金年化代替,平均回报计算为成立以来年化
if (setting.指数基金年化代替为成立以来年化) {
平均回报率 = ((Math.pow(累计净值, 1 / (getNumberOfDays(new Date(), 成立时间) / 365)) - 1) * 100).toFixed(2);
}
//指数成立时间不满5年则关闭
if (setting.指数用成立时间不满5年则关闭 && getNumberOfDays(成立时间, setting.满五年日期常量)>0) {
window.close();
return;
}
//不满五年判断
if (setting.自动关闭不满5年基金 && getNumberOfDays(任职时间, setting.满五年日期常量)>0) {
window.close();
return;
}
//回报率判断
if (setting.自动关闭平均回报过低基金 && Number(平均回报率) < setting.回报率常量) {
window.close();
return;
}
//若有本地记录则关闭
if (setting.有历史记录则关闭){
if (GM_getValue(基金代码) !== undefined) {
window.close();
return;
}
}
//打开新窗口,获取净值
if (setting.是否查看近一年净值) {
const 净值网址 = document.querySelector("#Div2 > div.item_title.hd > div.item_more > a").href;
window.setTimeout(() => {
打开子窗口(净值网址 + "?ref=" + 基金代码, 10000, true,100, () => {
//更新完毕后立即执行
主要信息渲染();
});
}, 500);
}
/**-----------------------------------设置项判断结束-------------------------------------- **/
function 主要信息渲染() {
//如果信息没有更新则不运行
if (JSON.stringify(STATIC) === JSON.stringify(GM_getValue(基金代码))) return;
//如果数据存在则更新
if (GM_getValue(基金代码) !== undefined) {
STATIC = GM_getValue(基金代码);
次要信息渲染(true);
}
const 近一年 = ((累计净值 / STATIC.yearAgo - 1) * 100).toFixed(2) + "%";
divElement2.innerHTML =
"近一年: " + 近一年 +
"<br>净值时间: " + 净值时间 +
"<br>累计净值: " + 累计净值 +
"<br>取值时间: " + STATIC.yearAgoDate +
"<br>累计净值: " + STATIC.yearAgo +
"<br>任职时间: " + 任职时间 +
"<br>任期回报: " + 任期回报 +
"<br>平均回报: " + 平均回报率 + "%";
}
let flag = 0.0;
function 次要信息渲染(f) {
let 当前估值 = parseFloat((document.querySelector("#gz_gszzl").innerText.replace("%", "") / 100).toFixed(4));
//若估值不变,且不由主要信息渲染调用则停止函数执行
if (flag === 当前估值 && f !== true) {
return;
}
flag = 当前估值;
//---最大值
const 与月高点的差距 = 1 - 累计净值 * (1 + (当前估值)) / STATIC.max;
//---最小值
const 与月低点的差距 = 累计净值 * (1 + (当前估值)) / STATIC.min - 1;
divElement3.innerHTML =
"当前估值: " + (当前估值 * 100).toFixed(2) + "%" +
"<br>近一个月最大累计净值:" +
"<br>取值时间:" + STATIC.maxDate +
"<br>累计净值: " + STATIC.max +
"<br>与月高点的差距:" + (与月高点的差距 * 100).toFixed(2) + "%" +
"<br>近一个月最小累计净值:" +
"<br>取值时间:" + STATIC.minDate +
"<br>累计净值: " + STATIC.min +
"<br>与月低点的差距:" + (与月低点的差距 * 100).toFixed(2) + "%" +
"<br>均累计净值: " + STATIC.average ;
}
//持续监听变化
window.setInterval(() => {
主要信息渲染();
次要信息渲染();
}, 500);
//立即执行
主要信息渲染();
次要信息渲染();
} else if (str2.test(url) && getQueryString("ref") != null) {
//为净值页面且由本程序打开.
const 基金代码 = getQueryString("ref");
//找到近20个交易日累计净值的最大值
净值收集(() => {
//取最大值,最小值,总数
let sum = 0.0;
//数组原大小
let count=list.length;
list.forEach((value) => {
if (value["累计净值"] > STATIC.max) {
STATIC.max = value["累计净值"];
STATIC.maxDate = value["净值日期"];
}
if (value["累计净值"] < STATIC.min) {
STATIC.min = value["累计净值"];
STATIC.minDate = value["净值日期"];
}
//如果为NaN,即没有累计净值,则该值不计入平均值内
if (Object.is(value["累计净值"], NaN)){
count--;
}else {
sum += value["累计净值"];
}
});
//平均数
STATIC.average = Number((sum / count).toFixed(4));
//找一年前的累计净值,从12页开始找
document.querySelector("#pagebar > div.pagebtns > input.pnum").value = 12;
document.querySelector("#pagebar > div.pagebtns > input.pgo").click();
//清空数组内容
list.length = 0;
//一年前的准确时间
let date = new Date();
date.setFullYear((date.getFullYear() - 1));
净值收集(function 回调1() {
for (let i = 0; i < list.length; i++) {
if (new Date(list[i]["净值日期"]) < date && !Object.is(list[i]["累计净值"], NaN)) {
STATIC.yearAgo = list[i]["累计净值"];
STATIC.yearAgoDate = list[i]["净值日期"];
break;
}
}
//如果没有找到数据
if (STATIC.yearAgoDate.length === 0) {
//清空数组内容
list.length = 0;
//翻页
document.querySelector("#pagebar > div.pagebtns > label:nth-child(9)").click();
//重新收集数据
净值收集(回调1);
} else {
//取得数据,程序出口
//设置数据
GM_setValue(基金代码, STATIC);
//关闭网页
window.close();
}
});
});
}
});
addStyle(`
.gs1 {
padding: 5px 5px;
font-size: 14px;
color: snow;
position: fixed;
text-align: left;
cursor: copy;
border-radius: 10px;
background-color: rgba(135, 134, 241, 0.84);
right: 5px;
top: 15%;
z-index: 999999;
//box-shadow: 0 0 7px 0 rgba(18, 80, 18,0.4), 0 0 0 1px rgba(0,0,0,0.3);
min-width: 144px;
}`);
/**
* 净值数据收集
*/
function 净值收集(func) {
//处理数据,获取不到数据时等待100ms后继续获取.
let trList = document.querySelectorAll("#jztable > table > tbody > tr");
if (trList === undefined || trList.length !== 20 || trList.length === 0) {
setTimeout(() => 净值收集(func), 100);
return;
}
for (let i = 0; i < trList.length; i++) {
let 净值日期, 单位净值, 累计净值;
deleteRedundantEmptyTextNodes(trList[i]);
净值日期 = trList[i].childNodes[0].innerText;
单位净值 = trList[i].childNodes[1].innerText;
累计净值 = trList[i].childNodes[2].innerText;
let temp = {净值日期: '', 单位净值: 1.0, 累计净值: 1.0};
temp["净值日期"] = 净值日期;
temp["单位净值"] = parseFloat(单位净值);
temp["累计净值"] = parseFloat(累计净值);
//添加到数组
list.push(temp);
}
func();
}
/**-------------------------------------------其他函数---------------------------------------------**/
/**
* 添加浏览器执行事件
* @param func 无参匿名函数
*/
function addLoadEvent(func) {
let oldOnload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function () {
try {
oldOnload();
} catch (e) {
console.log(e);
} finally {
func();
}
}
}
}
/**
* 获取网页参数
* @param name 参数名称
* @returns {string|null}
*/
function getQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
var r = decodeURI(window.location.search).substr(1).match(reg);
if (r != null) return (r[2]);
return null;
}
//添加css样式
function addStyle(rules) {
let styleElement = document.createElement('style');
styleElement["type"] = 'text/css';
document.getElementsByTagName('head')[0].appendChild(styleElement);
styleElement.appendChild(document.createTextNode(rules));
}
/**
* 两个时间相减,返回天数
* @param date1 较大的日期
* @param date2 较小的日期
* @returns {number}
*/
function getNumberOfDays(date1, date2) { //获得天数
var a1 = Date.parse(new Date(date1));
var a2 = Date.parse(new Date(date2));
var day = parseInt((a1 - a2) / (1000 * 60 * 60 * 24)); //核心:时间戳相减,然后除以天数
return day
}
/**
* 新窗口打开子窗口,为隐藏模式
* @param url url
* @param timeout 超时时间(ms)
* @param reopen 超时是否自动重开
* @param reopenLimit 重开次数上限
* @param func 回调函数,窗口关闭后回调
*/
function 打开子窗口(url, timeout, reopen, reopenLimit, func) {
let son = window.open(url, '_blank', "height=100,width=100,top=" +
window.screen.height + ",left=" + window.screen.width +
",location=0,menubar=0,status=0,titlebar=0,toolbar=0");
let start = new Date().getTime();//子窗口打开时的时间
let count = 0;
//检测超时
const winTimer = window.setInterval(() => {
let end;
end = new Date().getTime();//现在的时间
if (end - start > timeout* Math.pow(1.5,count)) {//超时
son.close();//关闭窗口
if (reopen && count < reopenLimit) {//如果需要重开,且重开次数没有达到上限
son = window.open(url, '_blank', "height=100,width=100,top=" +
window.screen.height + ",left=" + window.screen.width +
",location=0,menubar=0,status=0,titlebar=0,toolbar=0");
start = new Date().getTime();//重新设置开始时间
count++;//重开次数
}
}
//如果子窗体已关闭
if (son.closed) {
//结束这个定时函数,指这次运行后不再运行
window.clearInterval(winTimer);
if (func !== undefined) {
func();
}
}
}, 100);
}
/**
* 删除多余的空文本节点,为nextSibling,等节点操作一致性做准备
* @param elem 要优化的父节点
*/
function deleteRedundantEmptyTextNodes(elem) {
let elemList = elem.childNodes;
for (let i = 0; i < elemList.length; i++) {
/*当为文本节点并且为不可见字符时删除节点*/
if (elemList[i].nodeName === "#text" && /^\s+$/.test(elemList[i].nodeValue)) {
elem.removeChild(elemList[i])
}
}
}
/**
* 不带格式复制
* @param string 要复制的值
*/
function copyString(string) {
let target = document.createElement('textarea');
target.value=string;
document.body.appendChild(target);
window.getSelection().removeAllRanges();
target.select();
document.execCommand('copy');
window.getSelection().removeAllRanges();
target.remove();
}
})();