基金量化面板 模块化核心代码拆分(独立可复制复用,脱离本次对话直接新建 HTML 可用)
前置统一说明
依赖唯一外部 CDN:ECharts 图表库,所有图表模块必须引入:
1 | <script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script> |
数据源 API:东方财富基金净值历史接口,永久可用格式
1 | `https://fund.eastmoney.com/pingzhongdata/${基金代码}.js?t=${时间戳}` |
量化统一规则(所有信号模块共用判断逻辑)
加仓:2 年估值分位 ≤30% 且 当前净值 ≤ 布林下轨
减仓出售:2 年估值分位 ≥70% 且 当前净值 ≥ 布林上轨
持有:其余区间 无持仓、无交易台账,仅净值 + 布林分位量化
模块 1:全局基础基金配置数据(独立复制,修改基金仅改这里)
JS 基金基础信息(FUND_LIST)
1 | const FUND_LIST = { "011609":{ name:"易方达上证科创50ETF联接C", alloc:{stock:"0.30%",bond:"0.00%",cash:"5.22%",asset:"98.62亿"} }, "024481":{ name:"财通品质甄选混合C", alloc:{stock:"68.22%",bond:"2.15%",cash:"8.76%",asset:"2.73亿"} }, "016873":{ name:"广发远见智选混合A", alloc:{stock:"89.35%",bond:"0.12%",cash:"7.64%",asset:"13.31亿"} }, "007339":{ name:"易方达沪深300ETF联接C", alloc:{stock:"0.45%",bond:"0.00%",cash:"4.16%",asset:"72.14亿"} }, "018124":{ name:"永赢先进制造智选混合发起A", alloc:{stock:"88.64%",bond:"0.08%",cash:"8.37%",asset:"30.82亿"} } }; // 快速提取所有基金代码数组 const FUND_CODES = Object.keys(FUND_LIST); // 全局缓存容器:存放每只基金历史净值、量化指标 let fundCache = {}; |
模块 2:通用数学工具函数(量化计算核心,不可缺失)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | // 平均值 function mean(arr){return arr.reduce((s,v)=>s+v,0)/arr.length;} // 中位数 function median(arr){ const sort = [...arr].sort((a,b)=>a-b); const m = Math.floor(sort.length/2); return sort.length%2 ? sort[m] : (sort[m-1]+sort[m])/2; } // 标准差 function stdDev(arr,avg){ const m = avg ?? mean(arr); let sum = 0; arr.forEach(v=>sum += Math.pow(v-m,2)); return Math.sqrt(sum/arr.length); } // 计算数值在数组内的历史分位百分比 function getPercent(val,arr){ let cnt = 0; arr.forEach(v=>{if(v<val)cnt++;}); return (cnt/arr.length)*100; } // 分位数计算(30%、70%阈值使用) function quantile(arr,q){ const s = [...arr].sort((a,b)=>a-b); const pos = (s.length-1)*q; const f = Math.floor(pos); const c = Math.ceil(pos); if(f===c) return s[pos]; const w = pos-f; return s[f]*(1-w)+s[c]*w; } // 时间戳转YYYY-MM-DD日期 function formatDate(ts){ const d = new Date(ts); const pad = n=>String(n).padStart(2,"0"); return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`; } // 估值星级★转换 function starStr(num){return "★".repeat(num)+"☆".repeat(5-num);} // 更新页面刷新时间 function updateTime(){ const d = new Date(); const pad = n=>String(n).padStart(2,"0"); document.getElementById("updateTime").innerText = `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getMinutes())}:${pad(d.getSeconds())}:${pad(d.getSeconds())}`; } |
模块 3:核心量化计算函数(布林带 + 2 年历史分位,信号生成源头)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | function calcQuant(history){ // 取近两年净值数据 const twoYear = history.slice(-500); const vals = twoYear.map(d=>d.value); const latest = history[history.length-1].value; // 60日均线数据 const last60 = history.slice(-60).map(d=>d.value); const ma60 = mean(last60); const avg2 = mean(vals); const med2 = median(vals); const std = stdDev(vals,avg2); // 布林轨道 1.2倍标准差 const bollDown = avg2 - 1.2 * std; const bollUp = avg2 + 1.2 * std; // 当前净值两年历史分位 const pct = getPercent(latest,vals); const minV = Math.min(...vals); const maxV = Math.max(...vals); // 估值星级 let star = 3; if(pct<=20)star=1; else if(pct<=30)star=2; else if(pct>=70)star=4; else if(pct>=80)star=5; return { avg2y:avg2, med2y:med2, bollDown:bollDown, bollUp:bollUp, min:minV, max:maxV, pct:pct, ma60:ma60, latest:latest, star:star, std:std }; } |
模块 4:API 净值拉取函数(2 套:前台渲染加载 / 后台静默预加载)
4.1 前台加载单基金(切换标签、刷新当前基金使用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | function loadNavData(code){ const tabEl = document.querySelector(`.fund-tab[data-code="${code}"]`); tabEl.classList.add("loading"); const script = document.createElement("script"); script.src = `https://fund.eastmoney.com/pingzhongdata/${code}.js?t=${Date.now()}`; script.onload = function(){ const rawData = window.Data_netWorthTrend; window.Data_netWorthTrend = null; if(!rawData || rawData.length === 0){ showError("净值接口无数据"); tabEl.classList.remove("loading"); return; } const history = []; rawData.forEach(item=>{ if(item && typeof item.y === "number" && item.y > 0){ history.push({date:formatDate(item.x),value:item.y,change:item.equityReturn||0}); } }); if(history.length < 150){ showError("净值历史不足150条,无法量化"); tabEl.classList.remove("loading"); return; } fundCache[code] = {history:history,quant:calcQuant(history),loaded:true}; renderAll(code); renderSignalBoard(); tabEl.classList.remove("loading"); updateTime(); }; script.onerror = function(){ showError("净值API请求失败"); tabEl.classList.remove("loading"); }; document.body.appendChild(script); } // 快捷刷新当前选中基金 function loadCurrentFund(){loadNavData(currentFundCode);} |
4.2 静默后台预加载(一键刷新全部基金看板使用,无页面渲染)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | function loadBgData(code,cb){ const script = document.createElement("script"); script.src = `https://fund.eastmoney.com/pingzhongdata/${code}.js?t=${Date.now()}`; script.onload = function(){ const raw = window.Data_netWorthTrend; window.Data_netWorthTrend = null; if(!raw || raw.length === 0){cb();return;} const history = []; raw.forEach(item=>{ if(item && typeof item.y === "number" && item.y > 0){ history.push({date:formatDate(item.x),value:item.y,change:item.equityReturn||0}); } }); if(history.length >= 150){ fundCache[code] = {history:history,quant:calcQuant(history),loaded:true}; } cb(); }; script.onerror = ()=>cb(); document.body.appendChild(script); } // 一键刷新全部5只基金,更新顶部总览看板 function refreshAllBoard(){ let loadIndex = 0; function loadNext(){ if(loadIndex >= FUND_CODES.length){ renderSignalBoard(); updateTime(); return; } const code = FUND_CODES[loadIndex]; loadBgData(code,()=>{ loadIndex++; setTimeout(loadNext,600); }) } loadNext(); } |
模块 5:页面渲染函数(分 5 个独立渲染单元,按需复制)
5.1 顶部 5 基金总览看板渲染(核心直观加仓 / 出售板块)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | function renderSignalBoard(){ const boardWrap = document.getElementById("signalBoard"); boardWrap.innerHTML = ""; FUND_CODES.forEach(code=>{ const fundInfo = FUND_LIST[code]; const cache = fundCache[code]; let cardClass, signalText, signalColor, pctText; if(!cache || !cache.loaded){ cardClass = "board-card board-loading"; signalText = "数据加载中"; pctText = "-"; }else{ const q = cache.quant; pctText = `估值分位:${q.pct.toFixed(1)}%`; if(q.pct <= 30 && q.latest <= q.bollDown){ cardClass = "board-card board-buy"; signalText = "✅ 适合加仓"; signalColor = "#2e7d32"; }else if(q.pct >= 70 && q.latest >= q.bollUp){ cardClass = "board-card board-sell"; signalText = "⚠️ 适合减仓出售"; signalColor = "#e53935"; }else{ cardClass = "board-card board-watch"; signalText = "⏸️ 持有观望"; signalColor = "#ff8800"; } } const div = document.createElement("div"); div.className = cardClass; div.innerHTML = ` <div class="board-code">${code}</div> <div class="board-name">${fundInfo.name}</div> <div class="board-signal" style="color:${signalColor}">${signalText}</div> <div class="board-pct">${pctText}</div> `; div.onclick = ()=>switchFund(code); boardWrap.appendChild(div); }) } |
5.2 单基金切换逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | let currentFundCode = "011609"; function switchFund(code){ currentFundCode = code; document.querySelectorAll(".fund-tab").forEach(t=>t.classList.remove("active")); document.querySelector(`.fund-tab[data-code="${code}"]`).classList.add("active"); document.getElementById("fundNameTitle").innerText = `${code} ${FUND_LIST[code].name}`; document.getElementById("navContent").innerHTML = '<div class="loading">切换基金,加载净值API</div>'; document.getElementById("signalContent").innerHTML = '<div class="loading">量化计算中</div>'; document.getElementById("allocWrap").innerHTML = '<div class="loading">资产配置</div>'; if(fundCache[code] && fundCache[code].loaded){ renderAll(code); }else{ loadNavData(code); } } // 统一渲染当前基金全部面板 function renderAll(code){ const cache = fundCache[code]; const h = cache.history; const q = cache.quant; renderNav(h,q); renderSignal(q,h); renderAlloc(code); renderChart(h,q); } |
5.3 单位净值面板渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function renderNav(history,q){ const last = history[history.length-1]; const prev = history.length>=2 ? history[history.length-2].value : last.value; const dayRate = ((last.value-prev)/prev)*100; const cls = dayRate>=0 ? "up" : "down"; const sign = dayRate>=0 ? "+" : ""; const star = starStr(q.star); document.getElementById("navContent").innerHTML = ` <div class="star-rating">${star}</div> <div class="nav-value">${last.value.toFixed(4)}</div> <div class="nav-date">净值日期:${last.date} <span class="badge">API自动获取</span></div> <div class="nav-change ${cls}">当日涨跌幅 ${sign}${dayRate.toFixed(2)}%</div> <div style="margin-top:8px;font-size:13px;color:#666;">60日均线:${q.ma60.toFixed(4)}</div> `; } |
5.4 详细布林量化信号面板渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | function renderSignal(q,history){ let tip, cls; if(q.pct <= 30 && q.latest <= q.bollDown){ tip = "深度低估 · 分批加仓信号"; cls = "status-buy"; }else if(q.pct >= 70 && q.latest >= q.bollUp){ tip = "显著高估 · 分批减仓出售信号"; cls = "status-sell"; }else{ tip = "中性估值区间 · 持有观望"; cls = "status-watch"; } const devDown = ((q.latest - q.bollDown)/q.bollDown*100).toFixed(2); const devUp = ((q.bollUp - q.latest)/q.latest*100).toFixed(2); const devAvg = ((q.latest - q.avg2y)/q.avg2y*100).toFixed(2); const pos = Math.max(0, Math.min(100, ((q.latest - q.min)/(q.max - q.min))*100)); const p30 = ((quantile(history.map(d=>d.value),0.3)-q.min)/(q.max-q.min))*100; const p70 = ((quantile(history.map(d=>d.value),0.7)-q.min)/(q.max-q.min))*100; document.getElementById("signalContent").innerHTML = ` <div class="signal-grid"> <div class="signal-side"> <div class="signal-label">布林下轨(加仓参考)</div> <div class="signal-price buy">${q.bollDown.toFixed(4)}</div> <div class="signal-gap">距下轨 ${devDown>0?"+":""}${devDown}%</div> </div> <div class="signal-status ${cls}">${tip}</div> <div class="signal-side"> <div class="signal-label">布林上轨(减仓出售参考)</div> <div class="signal-price sell">${q.bollUp.toFixed(4)}</div> <div class="signal-gap">距上轨 +${devUp}%</div> </div> </div> <div class="signal-mid-info">2年均值:${q.avg2y.toFixed(4)}|中位数:${q.med2y.toFixed(4)}<br/>当前净值相对2年均值偏离:${devAvg>0?"+":""}${devAvg}%|两年历史估值分位:${q.pct.toFixed(1)}%</div> <div class="progress-bar"> <div class="progress-line-mark" style="left:${p30}%"></div> <div class="progress-line-mark" style="left:${p70}%"></div> <div class="progress-marker" style="left:${pos}%"></div> </div> <div class="progress-labels"><span>2年最低 ${q.min.toFixed(4)}</span><span>估值30%分位</span><span>估值70%分位</span><span>2年最高 ${q.max.toFixed(4)}</span></div> <div class="signal-desc">量化规则:估值分位≤30%且净值跌破布林下轨=加仓;估值分位≥70%且净值突破布林上轨=减仓出售;中间区间持有不动</div> `; } |
5.5 资产配置卡片渲染
1 2 3 4 5 6 7 8 9 10 11 | function renderAlloc(code){ const a = FUND_LIST[code].alloc; document.getElementById("allocWrap").innerHTML = ` <div class="alloc-grid"> <div class="alloc-item stock"><div class="alloc-value">${a.stock}</div><div class="alloc-label">股票占比</div></div> <div class="alloc-item bond"><div class="alloc-value">${a.bond}</div><div class="alloc-label">债券占比</div></div> <div class="alloc-item cash"><div class="alloc-value">${a.cash}</div><div class="alloc-label">现金占比</div></div> <div class="alloc-item"><div class="alloc-value">${a.asset}</div><div class="alloc-label">基金总规模</div></div> </div> `; } |
模块 6:辅助报错弹窗
1 2 3 4 | function showError(msg){ document.getElementById("navContent").innerHTML = `<div class="error">${msg}<br><button class="retry-btn" onclick="loadCurrentFund()">重新加载</button></div>`; document.getElementById("signalContent").innerHTML = `<div class="error">无法生成布林带+分位量化信号</div>`; } |
模块 7:页面加载入口(初始化执行)
1 2 3 4 | window.onload = function () { refreshAllBoard(); loadNavData(currentFundCode); } |
模块 8:完整配套 CSS 样式(一次性复制,页面所有元素样式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | *{margin:0;padding:0;box-sizing:border-box} body{font-family:system-ui,-apple-system;background:#f0f2f5;color:#333;padding:20px} .container{max-width:1200px;margin:0 auto} .header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;flex-wrap:wrap;gap:10px} .header h1{font-size:22px;color:#1a3a6c} .header .meta{font-size:13px;color:#888} .btn-group{display:flex;gap:8px;flex-wrap:wrap} .refresh-btn{background:#1a3a6c;color:#fff;border:none;padding:8px 14px;border-radius:6px;cursor:pointer;font-size:14px} .refresh-btn.batch{background:#2e7d32} .refresh-btn:hover{background:#2a5a9c} .refresh-btn.batch:hover{background:#249b29} .refresh-btn:disabled{background:#999;cursor:not-allowed} .tip-auto{font-size:12px;color:#2e7d32;background:#f0fff4;padding:4px 8px;border-radius:4px;margin:6px 0} .tip-static{font-size:12px;color:#e65100;background:#fff3e0;padding:4px 8px;border-radius:4px;margin:6px 0} .fund-tabs{display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap} .fund-tab{padding:8px 16px;border-radius:6px;border:1px solid #dcdcdc;cursor:pointer;font-size:14px;background:#fff} .fund-tab.active{background:#1a3a6c;color:#fff;border-color:#1a3a6c} .fund-tab.loading{opacity:0.6;cursor:not-allowed} .card{background:#fff;border-radius:12px;padding:24px;margin-bottom:16px;box-shadow:0 2px 8px rgba(0,0,0,0.06)} .card-title{font-size:14px;color:#888;margin-bottom:12px;display:flex;justify-content:space-between;align-items:center;font-weight:500} .nav-card{text-align:center} .nav-value{font-size:48px;font-weight:bold;color:#1a3a6c;margin:10px 0} .nav-date{font-size:14px;color:#888} .nav-change{font-size:18px;margin-top:8px} .nav-change.up{color:#e53935} .nav-change.down{color:#2e7d32} .badge{display:inline-block;padding:2px 8px;border-radius:4px;font-size:12px;background:#e8f5e9;color:#2e7d32;margin-left:8px} .star-rating{font-size:20px;letter-spacing:2px;margin:6px 0} .signal-grid{display:grid;grid-template-columns:1fr auto 1fr;gap:20px;align-items:center} .signal-side{text-align:center} .signal-label{font-size:13px;color:#888;margin-bottom:6px} .signal-price{font-size:24px;font-weight:bold} .signal-price.buy{color:#2e7d32} .signal-price.sell{color:#e53935} .signal-mid-info{color:#1a3a6c;font-size:13px;margin-top:6px;line-height:1.5} .signal-gap{font-size:12px;color:#888;margin-top:4px} .signal-status{padding:12px 24px;border-radius:8px;font-size:18px;font-weight:bold;text-align:center;min-width:140px} .status-watch{background:#fff3e0;color:#e65100} .status-buy{background:#e8f5e9;color:#2e7d32} .status-sell{background:#ffebee;color:#c62828} .signal-desc{margin-top:14px;font-size:12px;color:#666;line-height:1.7;background:#f7f8fa;padding:10px;border-radius:6px} .progress-bar{margin-top:20px;height:8px;background:linear-gradient(90deg,#2e7d32 30%,#ff9800 30%,#ff9800 70%,#e53935 70%);border-radius:4px;position:relative;overflow:visible} .progress-marker{position:absolute;top:-6px;width:20px;height:20px;background:#1a3a6c;border-radius:50%;transform:translateX(-50%);border:3px solid #fff;box-shadow:0 2px 4px rgba(0,0,0,0.2)} .progress-line-mark{position:absolute;width:1px;height:12px;background:#333;top:-2px;transform:translateX(-50%)} .progress-labels{display:flex;justify-content:space-between;font-size:11px;color:#aaa;margin-top:6px} .chart-container{width:100%;height:320px} .chart-tabs{display:flex;gap:8px;margin-bottom:12px} .chart-tab{padding:4px 12px;border-radius:4px;cursor:pointer;font-size:13px;background:#f0f0f0;color:#666} .chart-tab.active{background:#1a3a6c;color:#fff} .alloc-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;text-align:center} .alloc-item .alloc-value{font-size:22px;font-weight:bold;color:#1a3a6c} .alloc-item .alloc-label{font-size:12px;color:#888;margin-top:4px} .alloc-item.stock .alloc-value{color:#e53935} .alloc-item.bond .alloc-value{color:#1565c0} .alloc-item.cash .alloc-value{color:#2e7d32} .footer{text-align:center;font-size:12px;color:#aaa;margin-top:20px} .loading{text-align:center;padding:30px;color:#999} .error{text-align:center;padding:20px;color:#e53935} .retry-btn{margin-top:10px;padding:6px 16px;border:1px solid #e53935;background:#fff;color:#e53935;border-radius:4px;cursor:pointer} .board-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-top:10px} .board-card{border-radius:8px;padding:14px;text-align:center;border:1px solid #eee} .board-buy{border:2px solid #2e7d32;background:#f0fff4} .board-watch{border:2px solid #ff9800;background:#fffbf0} .board-sell{border:2px solid #e53935;background:#fff0f0} .board-loading{background:#f5f5f5;color:#999} .board-code{font-weight:bold;font-size:15px;margin-bottom:4px} .board-name{font-size:12px;color:#666;margin-bottom:8px} .board-signal{font-size:16px;font-weight:bold;margin-bottom:6px} .board-pct{font-size:12px;color:#555} @media (max-width:992px){ .board-grid{grid-template-columns:repeat(2,1fr)} } @media (max-width:768px){ body{padding:12px} .card{padding:16px} .nav-value{font-size:36px} .signal-grid{grid-template-columns:1fr;gap:12px} .signal-status{order:-1} .chart-container{height:260px} .header h1{font-size:18px} .alloc-grid{grid-template-columns:repeat(2,1fr)} } |
模块 9:页面 HTML 骨架(DOM 结构,ID 与 JS 严格对应,不能修改 ID)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>基金量化面板</title> <script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script> <!-- 粘贴上面完整CSS --> </head> <body> <div class="container"> <div class="header"> <div> <h1>场外基金量化监控面板</h1> <div class="meta">最后刷新时间:<span id="updateTime">未执行刷新</span> <span class="data-source">净值自动API:东方财富;量化模型:布林轨道+两年历史分位</span> </div> </div> <div class="btn-group"> <button class="refresh-btn" onclick="refreshAllBoard()">一键刷新全部5只基金信号</button> <button class="refresh-btn" id="refreshSingleBtn" onclick="loadCurrentFund()">刷新当前选中基金</button> </div> </div> <div class="tip-auto">✅ 自动拉取净值、自动计算量化信号;绿色=加仓,黄色=持有观望,红色=适合减仓出售</div> <div class="tip-static">⚠️ 资产配置为静态基础数据,无持仓、无交易台账</div> <!-- 五基金总览看板容器 --> <div class="card"> <div class="card-title">五只基金今日量化信号总览(快速判断加仓/持有/减仓出售)</div> <div class="board-grid" id="signalBoard"></div> </div> <!-- 基金切换标签 --> <div class="fund-tabs" id="fundTabWrap"> <div class="fund-tab active" data-code="011609" onclick="switchFund('011609')">011609 易方达上证科创50ETF联接C</div> <div class="fund-tab" data-code="024481" onclick="switchFund('024481')">024481 财通品质甄选混合C</div> <div class="fund-tab" data-code="016873" onclick="switchFund('016873')">016873 广发远见智选混合A</div> <div class="fund-tab" data-code="007339" onclick="switchFund('007339')">007339 易方达沪深300ETF联接C</div> <div class="fund-tab" data-code="018124" onclick="switchFund('018124')">018124 永赢先进制造智选混合发起A</div> </div> <!-- 单位净值卡片 --> <div class="card nav-card"> <div class="card-title">单位净值 <span id="fundNameTitle">011609 易方达上证科创50ETF联接C</span></div> <div id="navContent" class="loading">等待自动加载净值API</div> </div> <!-- 详细布林量化信号 --> <div class="card"> <div class="card-title">单基详细量化买卖信号(布林带 + 2年历史估值分位)</div> <div id="signalContent" class="loading">自动计算估值与交易信号</div> </div> <!-- K线图表 --> <div class="card"> <div class="chart-tabs"> <div class="chart-tab active" data-range="6m" onclick="switchChart('6m')">半年走势</div> <div class="chart-tab" data-range="2y" onclick="switchChart('2y')">两年走势</div> </div> <div class="chart-container" id="chart"></div> </div> <!-- 资产配置 --> <div class="card"> <div class="card-title">基金资产配置比例</div> <div id="allocWrap" class="loading">加载配置数据</div> </div> <div class="footer">净值API:东方财富公开接口;量化规则:分位≤30%+跌破布林下轨加仓|分位≥70%+突破布林上轨减仓出售</div> </div> <!-- 依次粘贴所有JS模块代码 --> </body> </html> |
复用搭建新网站操作步骤(下次新建页面直接照做)
- 复制 HTML 骨架,粘贴到新 html 文件
- 将完整 CSS 粘贴至
<head>内 - 按顺序粘贴 JS 模块(顺序不能乱)
- FUND_LIST 基金配置
- 全局数学工具函数
- calcQuant 量化核心计算
- API 净值拉取函数
- 所有渲染函数
- 报错函数 showError
- window.onload 页面初始化
- 修改 FUND_LIST 内基金代码 / 名称 / 资产配置,即可适配自己的基金
- 如需拓展功能,可单独复制对应渲染模块,不影响其他功能运行
还有什么 API 没有出现的么?
一、你当前代码只用的唯一接口
1 2 3 | // 历史净值K线(仅这个在你文档里用到) `https://fund.eastmoney.com/pingzhongdata/${code}.js?t=${Date.now()}` // 只解析其中变量 Data_netWorthTrend 做布林计算 |
二、未使用、前端可直接引入 JS 的接口(和现有加载逻辑完全一致)
1. 实时盘中估值接口(最实用,盘中看涨跌)
地址:
https://fundgz.1234567.com.cn/js/${基金代码}.js返回全局变量 gzfund,包含:
- 实时估算净值、实时涨跌幅、今日预估收益、昨日净值、更新时间
适用场景:顶部总览看板增加盘中实时估值,不用等收盘净值
加载方式(和 loadNavData 逻辑一模一样):
1 2 3 4 5 6 7 8 9 | function loadRealTimeValuation(code, callback) { const script = document.createElement('script'); script.src = `https://fundgz.1234567.com.cn/js/${code}.js?t=${Date.now()}`; script.onload = () => { callback(window.gzfund); delete window.gzfund; }; document.body.appendChild(script); } |
2. 全市场所有基金代码 + 名称列表接口
地址:
https://fund.eastmoney.com/js/fundcode_search.js用途:不用手动写死 FUND_LIST,自动下拉搜索基金、新增基金无需改代码
返回全局数组 r,每条包含 基金代码、基金简称、基金类型
3. 全部基金公司列表接口
地址:
https://fund.eastmoney.com/js/jjjz_gs.js用途:按基金公司筛选基金,拓展筛选面板
4. F10 定期报告系列分页接口(分红、持仓、历史净值区间查询)
基础地址:
https://fundf10.eastmoney.com/F10DataApi.aspx
参数说明:
type=lsjz:指定起止日期历史净值(自定义时间区间,不限制只能取近两年)type=fhps:分红送配记录type=cgch:前十大持仓股票code:基金代码sdate/edate:起始 / 结束日期per:每页条数,page页码用途:做分红统计、持仓分析、自定义区间收益计算
5. pingzhongdata 同文件内其他隐藏变量(同一个接口,但你代码没解析)
你现在只取
Data_netWorthTrend,同一个 js 文件里还有大量数据不用额外请求:Data_ACWorthTrend:累计净值走势Data_income:各阶段收益率(近 1 月 / 3 月 / 6 月 / 1 年 / 3 年)Data_assetAllocation:资产配置明细(股票 / 债券 / 现金占比,比你静态 alloc 更实时)Data_manager:基金经理、任职时间、历史业绩Data_dividend:历史分红记录Data_scale:基金最新规模变化曲线
三、第三方商用付费 API(不在东方财富免费 JS 体系,按需选用)
- 极速数据基金 API:https://api.jisuapi.com/fund/openquery(需要 appkey,JSON 标准接口)
- Twelve Data 海外基金 API:境外公募、ETF 完整数据(英文市场)
- iFinD 同花顺专业金融接口:机构级分钟估值、宏观经济数据(付费)
四、可拓展功能对应的缺失 API 总结(对应你现有面板)
- 盘中实时估值 → fundgz 实时 JS 接口(缺失)
- 自动基金列表搜索 → fundcode_search.js(缺失,目前手动硬编码 5 只)
- 实时资产配置、基金经理、阶段收益 → pingzhongdata 内置变量未解析(缺失)
- 分红记录、自定义区间净值、持仓明细 → F10DataApi 分页接口(缺失)
- 基金公司筛选 → jjjz_gs.js(缺失)
五、最简拓展示例(给顶部看板增加实时估值,直接插入现有代码)
1 2 3 4 5 6 7 8 9 10 11 | // 在renderSignalBoard卡片内部增加实时估值一行 function loadRealTimeForBoard(code, domCallback) { const s = document.createElement("script"); s.src = `https://fundgz.1234567.com.cn/js/${code}.js?t=${Date.now()}`; s.onload = () => { const data = window.gzfund; domCallback(data); delete window.gzfund; }; document.body.appendChild(s); } |