@cxxgo/fund-valuation-query 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +7 -7
  2. package/dist/fund.js +20 -20
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -18,10 +18,10 @@
18
18
 
19
19
  ### 明细
20
20
 
21
- - `收盘涨跌`:每只持仓股票“最近一个已收盘交易日”的日涨跌幅。展示规则:如果当前市场已经收盘,则展示最新收盘日;如果当前仍在交易中,则展示今天之前最近一个已收盘交易日。拿不到历史日线就直接显示 `-`。
22
- - `涨跌`:表示持仓股票当前交易日的实时或最新涨跌幅。只要行情数据日期仍是对应市场的当天就展示,拿不到或日期不匹配时显示 `-`。
23
- - `权重估值`:按“当前拿到盘中涨跌的持仓”做持仓占比加权汇总。
24
- - `覆盖`:当前拿到有效盘中涨跌的持仓权重占比。只有覆盖率达到 `60%` 时才展示 `权重估值`,否则显示 `-`。
21
+ - `收盘`:每只持仓股票“最近一个已收盘交易日”的日涨跌幅。展示规则:如果当前市场已经收盘,则展示最新收盘日;如果当前仍在交易中,则展示今天之前最近一个已收盘交易日。拿不到历史日线就直接显示 `-`。
22
+ - `盘中`:表示持仓股票当前交易日的实时涨跌幅。只要行情数据日期仍是对应市场的当天就展示;但如果当日收盘涨跌已经可用,为避免和 `收盘` 重复,则这里显示 `-`。
23
+ - `权重估值`:按“当前拿到盘中数据的持仓”做持仓占比加权汇总。
24
+ - `覆盖`:当前拿到有效盘中数据的持仓权重占比。只有覆盖率达到 `60%` 时才展示 `权重估值`,否则显示 `-`。
25
25
  - `明细刷新`:只刷新当前基金持仓股的盘中价格,不刷新持仓结构;持仓结构按天缓存。
26
26
 
27
27
  ## 安装
@@ -46,7 +46,7 @@ fund list
46
46
  fund detail <基金代码>
47
47
  ```
48
48
 
49
- 展示该基金最新报告期持仓股票及各自上个交易日、今日涨跌。
49
+ 展示该基金最新报告期持仓股票及各自最近收盘、盘中涨跌。
50
50
 
51
51
  ### Web 服务
52
52
 
@@ -64,9 +64,9 @@ PORT=8080 fund serve
64
64
  启动后访问 `http://localhost:8888` 即可使用可视化界面,功能包括:
65
65
 
66
66
  - 首次访问需选择配置文件(上传 JSON 文件或粘贴内容),配置保存在浏览器 localStorage
67
- - 点击表头按基金代码、名称、上个交易日、今日涨跌排序
67
+ - 点击表头按基金代码、名称、上个交易日、估值排序
68
68
  - 点击基金名称,右侧抽屉展开持仓明细
69
- - 抽屉内支持按占比、涨跌排序
69
+ - 抽屉内支持按占比、盘中/收盘排序
70
70
  - 手动刷新按钮 + 30 秒自动刷新
71
71
  - 切换配置按钮可重新选择配置文件
72
72
 
package/dist/fund.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import{Command as ot}from"commander";import{readFileSync as it}from"fs";import y from"axios";import*as G from"cheerio";function Z(e){let t=G.load(e),a=[];return t("table").first().find("tbody tr, tr").each((o,r)=>{let i=t(r).find("td");if(i.length<7)return;let d=i.eq(1).find("a"),s=d.text().trim();if(!s)return;let u=(d.attr("href")||"").match(/\/r\/(\d+)\.(\w+)/),f=u?parseInt(u[1]):s.startsWith("6")?1:0,g=f>=100,p=i.eq(2).find("a").text().trim()||i.eq(2).text().trim(),l=i.eq(6).text().trim().replace("%",""),m=parseFloat(l);isNaN(m)||a.push({stockCode:s,stockName:p,holdingRatio:m,market:f,isOverseas:g})}),a}var I={"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",Referer:"https://fund.eastmoney.com/"},N=1e3,ke=60*N,R=60*ke,B=24*R,T=new Map;async function v(e,t,a,{force:n=!1}={}){let o=Date.now(),r=T.get(e);if(!n&&r?.value!==void 0&&r.expiresAt>o)return r.value;if(r?.promise)return r.promise;let i=(async()=>{try{let d=await a();return T.set(e,{value:d,expiresAt:o+t,promise:null}),d}catch(d){if(r?.value!==void 0&&!n)return T.set(e,r),r.value;throw T.delete(e),d}})();return T.set(e,{value:r?.value,expiresAt:r?.expiresAt||0,promise:i}),i}async function P(e){return v(`fund-holdings:${e}`,B,async()=>{let t=`https://fundf10.eastmoney.com/FundArchivesDatas.aspx?type=jjcc&code=${e}&topline=50`,{data:a}=await y.get(t,{responseType:"text",headers:I,timeout:8e3}),n=a.match(/content:"([\s\S]*?)",arryear:/);return n?Z(n[1]):[]})}async function j(e,{force:t=!1}={}){try{return await v(`fund-estimate:${e}`,30*N,async()=>{let a=`http://fundgz.1234567.com.cn/js/${e}.js`,{data:n}=await y.get(a,{responseType:"text",timeout:5e3}),o=n.match(/jsonpgz\((.+)\)/);if(!o)return null;let r=JSON.parse(o[1]);return r.gszzl==null||!r.gztime?null:{change:parseFloat(r.gszzl),time:r.gztime,navDate:F(r.jzrq)}},{force:t})}catch{return null}}function ee(e){let t=new Intl.DateTimeFormat("en-CA",{timeZone:"Asia/Shanghai",year:"numeric",month:"2-digit",day:"2-digit"}).formatToParts(new Date(e)),a=n=>t.find(o=>o.type===n)?.value||"";return`${a("year")}-${a("month")}-${a("day")}`}function X(e,t="Asia/Shanghai"){let a=new Intl.DateTimeFormat("en-CA",{timeZone:t,year:"numeric",month:"2-digit",day:"2-digit"}).formatToParts(e),n=o=>a.find(r=>r.type===o)?.value||"";return`${n("year")}-${n("month")}-${n("day")}`}async function De(e){return v(`stock-history:nasdaq:${e}`,12*R,async()=>{let t=new Date,a=new Date(t.getTime()-30*B),n=`https://api.nasdaq.com/api/quote/${encodeURIComponent(e)}/historical?assetclass=stocks&fromdate=${X(a)}&limit=12&todate=${X(t)}`,o=null;for(let s=0;s<3;s+=1)try{({data:o}=await y.get(n,{timeout:8e3,headers:{"User-Agent":I["User-Agent"],Accept:"application/json, text/plain, */*",Origin:"https://www.nasdaq.com",Referer:"https://www.nasdaq.com/"}}));break}catch(c){if(c?.response?.status!==429||s===2)throw c;await te(500*(s+1))}let r=o?.data?.tradesTable?.rows;if(!Array.isArray(r)||r.length<2)return{latestDate:null,changesByDate:new Map};let i=r.map(s=>({date:Ce(s?.date),close:Te(s?.close)})).filter(s=>s.date&&Number.isFinite(s.close)).reverse();if(i.length<2)return{latestDate:null,changesByDate:new Map};let d=new Map;for(let s=1;s<i.length;s+=1){let c=i[s-1].close,u=i[s];c!==0&&d.set(u.date,(u.close-c)/c*100)}return{latestDate:i[i.length-1]?.date||null,changesByDate:d}})}function Ce(e){if(!e)return null;let[t,a,n]=String(e).split("/");return!n||!t||!a?null:`${n}-${t.padStart(2,"0")}-${a.padStart(2,"0")}`}function Te(e){return e==null?Number.NaN:Number.parseFloat(String(e).replace(/[$,]/g,""))}function te(e){return new Promise(t=>setTimeout(t,e))}async function L(e){try{return await v(`fund-networth:${e}`,B,async()=>{let t=`https://fund.eastmoney.com/pingzhongdata/${e}.js?v=${Date.now()}`,{data:a}=await y.get(t,{responseType:"text",timeout:8e3,headers:I}),n=a.match(/var Data_netWorthTrend = (\[[\s\S]*?\]);\/\*/);if(!n)return null;let o=JSON.parse(n[1]),r=o[o.length-1];return!r||typeof r.x!="number"||typeof r.equityReturn!="number"?null:{date:ee(r.x),change:r.equityReturn}})}catch{return null}}function Ne({code:e,market:t}){return/^(8|9)\d{5}$/.test(e)?`bj${e}`:t===1?`sh${e}`:t===0?`sz${e}`:t===116?`r_hk${e}`:t>=100?`us${e}`:null}function Se({code:e,market:t}){return/^(8|9)\d{5}$/.test(e)?`bj${e}`:t===1?`sh${e}`:t===0?`sz${e}`:t===116?`hk${e}`:t>=100?`us${e}`:null}function F(e){if(!e)return null;if(/^\d{14}$/.test(e))return`${e.slice(0,4)}-${e.slice(4,6)}-${e.slice(6,8)}`;let t=e.replace(/\//g,"-");return/^\d{4}-\d{2}-\d{2}/.test(t)?t.slice(0,10):null}async function O(){try{return await v("china-market-date",30*N,async()=>{let{data:e}=await y.get("https://qt.gtimg.cn/q=sh000001",{responseType:"arraybuffer",timeout:5e3}),a=new TextDecoder("gbk").decode(e).match(/^v_[^=]+="(.+)";?$/m);if(!a)return null;let n=a[1].split("~");return F(n[30])})}catch{return null}}var Me=[{code:"sh000001",name:"\u4E0A\u8BC1\u6307\u6570"},{code:"sz399001",name:"\u6DF1\u8BC1\u6210\u6307"},{code:"sz399006",name:"\u521B\u4E1A\u677F\u6307"},{code:"bj899050",name:"\u5317\u8BC150"},{code:"sh000688",name:"\u79D1\u521B50"},{code:"sh000016",name:"\u4E0A\u8BC150"},{code:"sz399330",name:"\u6DF1\u8BC1100"},{code:"sh000300",name:"\u6CAA\u6DF1300"},{code:"sh000905",name:"\u4E2D\u8BC1500"},{code:"sh000852",name:"\u4E2D\u8BC11000"},{code:"sh000012",name:"\u56FD\u503A\u6307\u6570"},{code:"sh000013",name:"\u4F01\u503A\u6307\u6570"}];function Le(){return Me.map(e=>({...e}))}function Fe(e){let t=e.match(/^v_[^=]+="(.+)";?$/m);return t?t[1].split("~"):null}function ae(){let e=new Intl.DateTimeFormat("en-GB",{timeZone:"Asia/Shanghai",hour:"2-digit",minute:"2-digit",hour12:!1}).formatToParts(new Date),t=a=>e.find(n=>n.type===a)?.value||"";return`${t("hour")}:${t("minute")}`}function Oe(e){let t=ee(Date.now());if(!e||e!==t)return"closed";let a=ae(),n=Number.parseInt(a.slice(0,2),10)*60+Number.parseInt(a.slice(3,5),10),o=n>=570&&n<=690,r=n>=780&&n<=900;return n>690&&n<780?"midday_break":o||r?"trading":"closed"}async function ne({force:e=!1}={}){let t=Le();try{return await v("market-overview",30*N,async()=>{let a=`https://qt.gtimg.cn/q=${t.map(c=>c.code).join(",")}`,{data:n}=await y.get(a,{responseType:"arraybuffer",timeout:5e3}),o=new TextDecoder("gbk").decode(n),r=new Map;for(let c of o.trim().split(`
3
- `)){let u=c.match(/^v_([^=]+)=/);if(!u)continue;let f=Fe(c);!f||f.length<33||r.set(u[1],f)}let i=r.get(t[0].code),d=F(i?.[30])||null,s=Oe(d);return{status:s,dateLabel:d?d.slice(5):"",timeLabel:s==="trading"?ae():"",items:t.map(c=>{let u=r.get(c.code),f=Number.parseFloat(u?.[3]),g=Number.parseFloat(u?.[4]),p=Number.parseFloat(u?.[32]);return{code:c.code,name:c.name,value:Number.isFinite(f)?f:null,change:Number.isFinite(f)&&Number.isFinite(g)?f-g:null,changePercent:Number.isFinite(p)?p:null}})}},{force:e})}catch{return{status:"closed",dateLabel:"",timeLabel:"",items:t.map(a=>({...a,value:null,change:null,changePercent:null}))}}}async function z(e){let t=new Map;if(e.length===0)return t;await Promise.all(e.map(async({code:n,market:o})=>{if(o>=100)return;let r=Se({code:n,market:o});if(!r){t.set(n,{latestDate:null,changesByDate:new Map});return}try{let i=await v(`stock-history:${r}`,12*R,async()=>{let d=`https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?param=${r},day,,,30,qfq`,{data:s}=await y.get(d,{timeout:5e3,headers:{Referer:"https://gu.qq.com/"}}),c=s?.data?.[r],u=c?.qfqday||c?.day;if(!Array.isArray(u)||u.length===0)return{latestDate:null,changesByDate:new Map};let f=new Map;for(let g=1;g<u.length;g+=1){let p=u[g-1],l=u[g],m=l?.[0],x=parseFloat(p?.[2]),k=parseFloat(l?.[2]);if(!m||Number.isNaN(x)||Number.isNaN(k)||x===0)continue;let D=(k-x)/x*100;f.set(m,D)}return{latestDate:u[u.length-1]?.[0]||null,changesByDate:f}});t.set(n,i)}catch{t.set(n,{latestDate:null,changesByDate:new Map})}}));let a=0;for(let{code:n,market:o}of e)if(!(o<100)){try{a>0&&await te(250);let r=await De(n);t.set(n,r)}catch{t.set(n,{latestDate:null,changesByDate:new Map})}a+=1}return t}async function re(e,{force:t=!1}={}){let a=new Map;if(e.length===0)return a;let n=e.map(({code:r,market:i})=>({code:r,symbol:Ne({code:r,market:i})})).filter(r=>r.symbol);if(n.length===0)return a;let o=new Map(n.map(r=>[r.symbol,r.code]));try{let r=n.map(d=>d.symbol).sort().join(","),i=await v(`stock-quotes:${r}`,20*N,async()=>{let d=`https://qt.gtimg.cn/q=${n.map(g=>g.symbol).join(",")}`,{data:s}=await y.get(d,{responseType:"arraybuffer",timeout:5e3}),c=new TextDecoder("gbk").decode(s),u=new Map,f=c.trim().split(`
4
- `);for(let g of f){let p=g.match(/^v_([^=]+)="(.+)";?$/);if(!p)continue;let l=p[1],m=p[2].split("~");m.length<33||u.set(l,{name:m[1],changePercent:parseFloat(m[32]),quoteDate:F(m[30])})}return u},{force:t});for(let[d,s]of o.entries())i.has(d)&&a.set(s,i.get(d))}catch{}return a}import{readFileSync as H,writeFileSync as $e,existsSync as q}from"fs";import{homedir as Ee}from"os";import{join as Ie,resolve as Re}from"path";var A=Ie(Ee(),".fundrc.json");function K(){return q(A)&&JSON.parse(H(A,"utf-8")).configPath||null}function oe(e){let t=Re(e);q(t)||(console.error(`\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${t}`),process.exit(1));try{let a=JSON.parse(H(t,"utf-8"));(!a||typeof a!="object"||Object.keys(a).length===0)&&(console.error("\u914D\u7F6E\u6587\u4EF6\u4E3A\u7A7A\u6216\u683C\u5F0F\u9519\u8BEF"),process.exit(1))}catch{console.error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF0C\u9700\u8981\u5408\u6CD5\u7684 JSON"),process.exit(1)}$e(A,JSON.stringify({configPath:t},null,2)),console.log(`\u5DF2\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6: ${t}`)}function U(){let e=K();return e||(console.error("\u672A\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6\uFF0C\u8BF7\u8FD0\u884C: fund config <\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84>"),process.exit(1)),q(e)||(console.error(`\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${e}\uFF0C\u8BF7\u91CD\u65B0\u8BBE\u7F6E: fund config <\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84>`),process.exit(1)),JSON.parse(H(e,"utf-8"))}function ie(e,t){return t.some(n=>n.isOverseas)||/全球|QDII|美元|港|海外|纳斯达克|标普|道琼斯/.test(e)?"\u5168\u7403":/ETF|指数/.test(e)?"\u6307\u6570":/债/.test(e)?"\u504F\u503A":"\u504F\u80A1"}var S="Asia/Shanghai";function Be(e=S,t=new Date){return new Date(t.toLocaleString("en-US",{timeZone:e}))}function Pe(e){return[e.getFullYear(),String(e.getMonth()+1).padStart(2,"0"),String(e.getDate()).padStart(2,"0")].join("-")}function je(){return Pe(Be())}function h(e,t=new Date){let a=new Intl.DateTimeFormat("en-CA",{timeZone:e,year:"numeric",month:"2-digit",day:"2-digit",weekday:"short",hour:"2-digit",minute:"2-digit",hour12:!1}).formatToParts(t),n=o=>a.find(r=>r.type===o)?.value||"";return{date:`${n("year")}-${n("month")}-${n("day")}`,weekday:n("weekday"),minutes:Number.parseInt(n("hour"),10)*60+Number.parseInt(n("minute"),10)}}function _(e){return["Mon","Tue","Wed","Thu","Fri"].includes(e)}function ze(e=new Date){let t=h(S,e);return _(t.weekday)?t.minutes>900||t.minutes<570:!0}function Ae(e=new Date){let t=h("Asia/Hong_Kong",e);return _(t.weekday)?t.minutes>960||t.minutes<570:!0}function He(e=new Date){let t=h("America/New_York",e);return _(t.weekday)?t.minutes>960||t.minutes<570:!0}function qe(e,t,a=new Date){return!t?.quoteDate||!w(t.changePercent)?!1:e.market===116?t.quoteDate===h("Asia/Hong_Kong",a).date:e.market>=100?t.quoteDate===h("America/New_York",a).date:t.quoteDate===h(S,a).date}function Ke(e,t){return!!(e&&t&&e===t)}function Ue(e,t=new Date){return e.market===116?h("Asia/Hong_Kong",t).date:e.market>=100?h("America/New_York",t).date:h(S,t).date}function _e(e,t=new Date){return e.market===116?Ae(t):e.market>=100?He(t):ze(t)}function Je(e,t,a=new Date){let n=t.get(e.stockCode);if(!n)return null;let o=[...n.changesByDate.keys()].sort();if(o.length===0)return null;let r=Ue(e,a);if(_e(e,a))return o[o.length-1]||null;let d=o.filter(s=>s<r);return d[d.length-1]||null}function se(e,t,a=new Date){if(!e?.time||!w(e.change))return!1;let n=e.time.slice(0,10);if(t==="\u5168\u7403"){let r=h("America/New_York",a),i=h("Asia/Hong_Kong",a);return n===r.date||n===i.date}let o=h(S,a).date;return n===o}function $(e){return!e||e.length<10?"":e.slice(5)}function w(e){return typeof e=="number"&&!Number.isNaN(e)}function J(e){return e.map(t=>({code:t.stockCode,market:t.market}))}function W(e,t,a,n=null){let o=je(),r=new Date,i=0,d=e.map(l=>{let m=t.get(l.stockCode),x=a.get(l.stockCode),k=Je(l,a,r),D=qe(l,m,r)&&(l.isOverseas||Ke(o,n))?m.changePercent:null,we=k,E=k&&x?x.changesByDate.get(k)??null:null,xe=w(D)?l.holdingRatio*D/100:0;return w(D)&&(i+=l.holdingRatio),{stockCode:l.stockCode,stockName:l.stockName,holdingRatio:l.holdingRatio,todayChange:D,previousTradingDayChange:E,previousTradingDayDate:we,contribution:w(E)?l.holdingRatio*E/100:0,weightedTodayContribution:xe,isOverseas:l.isOverseas}}),c=d.length>0&&d.every(l=>w(l.previousTradingDayChange))?d.reduce((l,m)=>l+m.contribution,0):null,u=i>0?i/100:0,f=u>=.6?d.reduce((l,m)=>l+m.weightedTodayContribution,0):null,g=d.map(l=>l.previousTradingDayDate).filter(Boolean).sort(),p=g.length>0?g[g.length-1]:null;return{previousTradingDayChange:c,weightedTodayChange:f,coverageRatio:u,contributions:d,todayDate:o,previousTradingDate:p}}async function Q(e){let t=new Map;return await Promise.all(Object.keys(e).map(async a=>{try{t.set(a,await P(a))}catch{t.set(a,[])}})),t}function We(e){let t=new Map;for(let[,a]of e)for(let n of a)t.has(n.stockCode)||t.set(n.stockCode,{code:n.stockCode,market:n.market});return[...t.values()]}async function Y(e,t){return re(We(e),t)}function de(e,t,a,n,o){let r=ie(t,a);return{code:e,name:t,todayChange:se(n,r)?n.change:null,previousTradingDayChange:o?.change??null,previousTradingDayLabel:$(o?.date??null),category:r,canDetail:!0}}async function ce(e,t,a,n,o=null){let[r,i,d]=await Promise.all([z(J(a)),j(e),L(e)]),s=W(a,n,r,o);return{...de(e,t,a,i,d),contributions:s.contributions,weightedTodayChange:s.weightedTodayChange,coverageRatio:s.coverageRatio,todayLabel:$(s.todayDate)}}async function V(e,{forceRefresh:t=!1}={}){let a=await Q(e);return(await Promise.all(Object.entries(e).map(async([o,r])=>{let i=a.get(o)||[],[d,s]=await Promise.all([j(o,{force:t}),L(o)]);return{code:o,name:r,holdings:i,estimate:d,latestNetWorth:s}}))).map(({code:o,name:r,holdings:i,estimate:d,latestNetWorth:s})=>de(o,r,i,d,s))}async function le(e){let[t,a]=await Promise.all([V(e,{forceRefresh:!0}),ne({force:!0})]);return{funds:t,marketOverview:a}}async function ue(e,t,{forceRefreshPrices:a=!1}={}){let n=await P(e),o=new Map([[e,n]]),[r,i,d,s]=await Promise.all([Y(o,{force:a}),O(),z(J(n)),L(e)]),c=W(n,r,d,i);return{code:e,name:t,previousTradingDayChange:s?.change??null,previousTradingDayLabel:$(s?.date??null),weightedTodayChange:w(c.weightedTodayChange)?c.weightedTodayChange:null,coverageRatio:c.coverageRatio,contributions:c.contributions}}import M from"chalk";import fe from"cli-table3";function C(e){if(e==null||Number.isNaN(e))return"-";let t=parseFloat(e);return t>0?M.red(`+${t.toFixed(2)}%`):t<0?M.green(`${t.toFixed(2)}%`):`${t.toFixed(2)}%`}function ge(e,t,a){return!t||t===a?C(e):`${C(e)}
5
- ${M.gray(t)}`}function Qe(e,t){return t?`${e} (${t})`:e}function me(e,t){let a="";for(let n of e){let o=t(n);o&&o>a&&(a=o)}return a}function pe(e,t){return t?`${e}
6
- ${t}`:e}function he(e){let{code:t,name:a,todayChange:n,previousTradingDayChange:o,contributions:r}=e,i=me(r,s=>s.previousTradingDayDate?.slice(5)||""),d=new fe({head:["\u6301\u4ED3","\u5360\u6BD4",pe("\u6536\u76D8\u6DA8\u8DCC",i),"\u6DA8\u8DCC"],style:{head:["cyan"]}});for(let s of r)d.push([`${s.stockCode} ${s.stockName}`,`${s.holdingRatio.toFixed(2)}%`,ge(s.previousTradingDayChange,s.previousTradingDayDate?.slice(5),i),C(s.todayChange)]);console.log(`
7
- ${M.bold(t)} ${M.bold(a)}`),console.log(d.toString()),console.log(`${Qe("\u51C0\u503C",e.previousTradingDayLabel)}: ${C(o)}`),console.log(`\u6DA8\u8DCC: ${C(n)}
8
- `)}function be(e){let t=me(e,n=>n.previousTradingDayLabel||""),a=new fe({head:["\u57FA\u91D1\u4EE3\u7801","\u57FA\u91D1\u540D\u79F0",pe("\u51C0\u503C",t),"\u4F30\u503C"],style:{head:["cyan"]}});for(let n of e)a.push([n.code,n.name,ge(n.previousTradingDayChange,n.previousTradingDayLabel,t),C(n.todayChange)]);console.log(a.toString())}import ye from"express";import{spawn as Ye}from"child_process";import{networkInterfaces as Ve}from"os";async function Ge(e){return le(e)}async function Ze(e,t,a=!1){return ue(e,t,{forceRefreshPrices:a})}function Xe(e){let t=null,a=[];process.platform==="darwin"?(t="open",a=[e]):process.platform==="win32"?(t="cmd",a=["/c","start","",e]):(t="xdg-open",a=[e]);try{Ye(t,a,{detached:!0,stdio:"ignore"}).unref()}catch{}}function et(e){let t=Ve(),a=["en0","en1","Ethernet","Wi-Fi","wlan0","eth0"],n=[];for(let[o,r]of Object.entries(t))for(let i of r||[])i.family!=="IPv4"||i.internal||n.push({name:o,address:i.address});return n.length===0?null:(n.sort((o,r)=>{let i=a.indexOf(o.name),d=a.indexOf(r.name),s=i===-1?a.length:i,c=d===-1?a.length:d;return s!==c?s-c:o.name.localeCompare(r.name)}),`http://${n[0].address}:${e}`)}function tt(){return`
2
+ import{Command as it}from"commander";import{readFileSync as st}from"fs";import y from"axios";import*as Z from"cheerio";function X(e){let t=Z.load(e),a=[];return t("table").first().find("tbody tr, tr").each((o,r)=>{let i=t(r).find("td");if(i.length<7)return;let d=i.eq(1).find("a"),s=d.text().trim();if(!s)return;let u=(d.attr("href")||"").match(/\/r\/(\d+)\.(\w+)/),f=u?parseInt(u[1]):s.startsWith("6")?1:0,g=f>=100,p=i.eq(2).find("a").text().trim()||i.eq(2).text().trim(),l=i.eq(6).text().trim().replace("%",""),m=parseFloat(l);isNaN(m)||a.push({stockCode:s,stockName:p,holdingRatio:m,market:f,isOverseas:g})}),a}var I={"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",Referer:"https://fund.eastmoney.com/"},N=1e3,Ce=60*N,R=60*Ce,B=24*R,T=new Map;async function v(e,t,a,{force:n=!1}={}){let o=Date.now(),r=T.get(e);if(!n&&r?.value!==void 0&&r.expiresAt>o)return r.value;if(r?.promise)return r.promise;let i=(async()=>{try{let d=await a();return T.set(e,{value:d,expiresAt:o+t,promise:null}),d}catch(d){if(r?.value!==void 0&&!n)return T.set(e,r),r.value;throw T.delete(e),d}})();return T.set(e,{value:r?.value,expiresAt:r?.expiresAt||0,promise:i}),i}async function P(e){return v(`fund-holdings:${e}`,B,async()=>{let t=`https://fundf10.eastmoney.com/FundArchivesDatas.aspx?type=jjcc&code=${e}&topline=50`,{data:a}=await y.get(t,{responseType:"text",headers:I,timeout:8e3}),n=a.match(/content:"([\s\S]*?)",arryear:/);return n?X(n[1]):[]})}async function j(e,{force:t=!1}={}){try{return await v(`fund-estimate:${e}`,30*N,async()=>{let a=`http://fundgz.1234567.com.cn/js/${e}.js`,{data:n}=await y.get(a,{responseType:"text",timeout:5e3}),o=n.match(/jsonpgz\((.+)\)/);if(!o)return null;let r=JSON.parse(o[1]);return r.gszzl==null||!r.gztime?null:{change:parseFloat(r.gszzl),time:r.gztime,navDate:F(r.jzrq)}},{force:t})}catch{return null}}function te(e){let t=new Intl.DateTimeFormat("en-CA",{timeZone:"Asia/Shanghai",year:"numeric",month:"2-digit",day:"2-digit"}).formatToParts(new Date(e)),a=n=>t.find(o=>o.type===n)?.value||"";return`${a("year")}-${a("month")}-${a("day")}`}function ee(e,t="Asia/Shanghai"){let a=new Intl.DateTimeFormat("en-CA",{timeZone:t,year:"numeric",month:"2-digit",day:"2-digit"}).formatToParts(e),n=o=>a.find(r=>r.type===o)?.value||"";return`${n("year")}-${n("month")}-${n("day")}`}async function Te(e){return v(`stock-history:nasdaq:${e}`,12*R,async()=>{let t=new Date,a=new Date(t.getTime()-30*B),n=`https://api.nasdaq.com/api/quote/${encodeURIComponent(e)}/historical?assetclass=stocks&fromdate=${ee(a)}&limit=12&todate=${ee(t)}`,o=null;for(let s=0;s<3;s+=1)try{({data:o}=await y.get(n,{timeout:8e3,headers:{"User-Agent":I["User-Agent"],Accept:"application/json, text/plain, */*",Origin:"https://www.nasdaq.com",Referer:"https://www.nasdaq.com/"}}));break}catch(c){if(c?.response?.status!==429||s===2)throw c;await ae(500*(s+1))}let r=o?.data?.tradesTable?.rows;if(!Array.isArray(r)||r.length<2)return{latestDate:null,changesByDate:new Map};let i=r.map(s=>({date:Ne(s?.date),close:Se(s?.close)})).filter(s=>s.date&&Number.isFinite(s.close)).reverse();if(i.length<2)return{latestDate:null,changesByDate:new Map};let d=new Map;for(let s=1;s<i.length;s+=1){let c=i[s-1].close,u=i[s];c!==0&&d.set(u.date,(u.close-c)/c*100)}return{latestDate:i[i.length-1]?.date||null,changesByDate:d}})}function Ne(e){if(!e)return null;let[t,a,n]=String(e).split("/");return!n||!t||!a?null:`${n}-${t.padStart(2,"0")}-${a.padStart(2,"0")}`}function Se(e){return e==null?Number.NaN:Number.parseFloat(String(e).replace(/[$,]/g,""))}function ae(e){return new Promise(t=>setTimeout(t,e))}async function L(e,{force:t=!1}={}){try{return await v(`fund-networth:${e}`,B,async()=>{let a=`https://fund.eastmoney.com/pingzhongdata/${e}.js?v=${Date.now()}`,{data:n}=await y.get(a,{responseType:"text",timeout:8e3,headers:I}),o=n.match(/var Data_netWorthTrend = (\[[\s\S]*?\]);\/\*/);if(!o)return null;let r=JSON.parse(o[1]),i=r[r.length-1];return!i||typeof i.x!="number"||typeof i.equityReturn!="number"?null:{date:te(i.x),change:i.equityReturn}},{force:t})}catch{return null}}function Me({code:e,market:t}){return/^(8|9)\d{5}$/.test(e)?`bj${e}`:t===1?`sh${e}`:t===0?`sz${e}`:t===116?`r_hk${e}`:t>=100?`us${e}`:null}function Le({code:e,market:t}){return/^(8|9)\d{5}$/.test(e)?`bj${e}`:t===1?`sh${e}`:t===0?`sz${e}`:t===116?`hk${e}`:t>=100?`us${e}`:null}function F(e){if(!e)return null;if(/^\d{14}$/.test(e))return`${e.slice(0,4)}-${e.slice(4,6)}-${e.slice(6,8)}`;let t=e.replace(/\//g,"-");return/^\d{4}-\d{2}-\d{2}/.test(t)?t.slice(0,10):null}async function O(){try{return await v("china-market-date",30*N,async()=>{let{data:e}=await y.get("https://qt.gtimg.cn/q=sh000001",{responseType:"arraybuffer",timeout:5e3}),a=new TextDecoder("gbk").decode(e).match(/^v_[^=]+="(.+)";?$/m);if(!a)return null;let n=a[1].split("~");return F(n[30])})}catch{return null}}var Fe=[{code:"sh000001",name:"\u4E0A\u8BC1\u6307\u6570"},{code:"sz399001",name:"\u6DF1\u8BC1\u6210\u6307"},{code:"sz399006",name:"\u521B\u4E1A\u677F\u6307"},{code:"bj899050",name:"\u5317\u8BC150"},{code:"sh000688",name:"\u79D1\u521B50"},{code:"sh000016",name:"\u4E0A\u8BC150"},{code:"sz399330",name:"\u6DF1\u8BC1100"},{code:"sh000300",name:"\u6CAA\u6DF1300"},{code:"sh000905",name:"\u4E2D\u8BC1500"},{code:"sh000852",name:"\u4E2D\u8BC11000"},{code:"sh000012",name:"\u56FD\u503A\u6307\u6570"},{code:"sh000013",name:"\u4F01\u503A\u6307\u6570"}];function Oe(){return Fe.map(e=>({...e}))}function $e(e){let t=e.match(/^v_[^=]+="(.+)";?$/m);return t?t[1].split("~"):null}function ne(){let e=new Intl.DateTimeFormat("en-GB",{timeZone:"Asia/Shanghai",hour:"2-digit",minute:"2-digit",hour12:!1}).formatToParts(new Date),t=a=>e.find(n=>n.type===a)?.value||"";return`${t("hour")}:${t("minute")}`}function Ee(e){let t=te(Date.now());if(!e||e!==t)return"closed";let a=ne(),n=Number.parseInt(a.slice(0,2),10)*60+Number.parseInt(a.slice(3,5),10),o=n>=570&&n<=690,r=n>=780&&n<=900;return n>690&&n<780?"midday_break":o||r?"trading":"closed"}async function re({force:e=!1}={}){let t=Oe();try{return await v("market-overview",30*N,async()=>{let a=`https://qt.gtimg.cn/q=${t.map(c=>c.code).join(",")}`,{data:n}=await y.get(a,{responseType:"arraybuffer",timeout:5e3}),o=new TextDecoder("gbk").decode(n),r=new Map;for(let c of o.trim().split(`
3
+ `)){let u=c.match(/^v_([^=]+)=/);if(!u)continue;let f=$e(c);!f||f.length<33||r.set(u[1],f)}let i=r.get(t[0].code),d=F(i?.[30])||null,s=Ee(d);return{status:s,dateLabel:d?d.slice(5):"",timeLabel:s==="trading"?ne():"",items:t.map(c=>{let u=r.get(c.code),f=Number.parseFloat(u?.[3]),g=Number.parseFloat(u?.[4]),p=Number.parseFloat(u?.[32]);return{code:c.code,name:c.name,value:Number.isFinite(f)?f:null,change:Number.isFinite(f)&&Number.isFinite(g)?f-g:null,changePercent:Number.isFinite(p)?p:null}})}},{force:e})}catch{return{status:"closed",dateLabel:"",timeLabel:"",items:t.map(a=>({...a,value:null,change:null,changePercent:null}))}}}async function z(e){let t=new Map;if(e.length===0)return t;await Promise.all(e.map(async({code:n,market:o})=>{if(o>=100)return;let r=Le({code:n,market:o});if(!r){t.set(n,{latestDate:null,changesByDate:new Map});return}try{let i=await v(`stock-history:${r}`,12*R,async()=>{let d=`https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?param=${r},day,,,30,qfq`,{data:s}=await y.get(d,{timeout:5e3,headers:{Referer:"https://gu.qq.com/"}}),c=s?.data?.[r],u=c?.qfqday||c?.day;if(!Array.isArray(u)||u.length===0)return{latestDate:null,changesByDate:new Map};let f=new Map;for(let g=1;g<u.length;g+=1){let p=u[g-1],l=u[g],m=l?.[0],x=parseFloat(p?.[2]),k=parseFloat(l?.[2]);if(!m||Number.isNaN(x)||Number.isNaN(k)||x===0)continue;let D=(k-x)/x*100;f.set(m,D)}return{latestDate:u[u.length-1]?.[0]||null,changesByDate:f}});t.set(n,i)}catch{t.set(n,{latestDate:null,changesByDate:new Map})}}));let a=0;for(let{code:n,market:o}of e)if(!(o<100)){try{a>0&&await ae(250);let r=await Te(n);t.set(n,r)}catch{t.set(n,{latestDate:null,changesByDate:new Map})}a+=1}return t}async function oe(e,{force:t=!1}={}){let a=new Map;if(e.length===0)return a;let n=e.map(({code:r,market:i})=>({code:r,symbol:Me({code:r,market:i})})).filter(r=>r.symbol);if(n.length===0)return a;let o=new Map(n.map(r=>[r.symbol,r.code]));try{let r=n.map(d=>d.symbol).sort().join(","),i=await v(`stock-quotes:${r}`,20*N,async()=>{let d=`https://qt.gtimg.cn/q=${n.map(g=>g.symbol).join(",")}`,{data:s}=await y.get(d,{responseType:"arraybuffer",timeout:5e3}),c=new TextDecoder("gbk").decode(s),u=new Map,f=c.trim().split(`
4
+ `);for(let g of f){let p=g.match(/^v_([^=]+)="(.+)";?$/);if(!p)continue;let l=p[1],m=p[2].split("~");m.length<33||u.set(l,{name:m[1],changePercent:parseFloat(m[32]),quoteDate:F(m[30])})}return u},{force:t});for(let[d,s]of o.entries())i.has(d)&&a.set(s,i.get(d))}catch{}return a}import{readFileSync as H,writeFileSync as Ie,existsSync as q}from"fs";import{homedir as Re}from"os";import{join as Be,resolve as Pe}from"path";var A=Be(Re(),".fundrc.json");function K(){return q(A)&&JSON.parse(H(A,"utf-8")).configPath||null}function ie(e){let t=Pe(e);q(t)||(console.error(`\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${t}`),process.exit(1));try{let a=JSON.parse(H(t,"utf-8"));(!a||typeof a!="object"||Object.keys(a).length===0)&&(console.error("\u914D\u7F6E\u6587\u4EF6\u4E3A\u7A7A\u6216\u683C\u5F0F\u9519\u8BEF"),process.exit(1))}catch{console.error("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF0C\u9700\u8981\u5408\u6CD5\u7684 JSON"),process.exit(1)}Ie(A,JSON.stringify({configPath:t},null,2)),console.log(`\u5DF2\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6: ${t}`)}function U(){let e=K();return e||(console.error("\u672A\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6\uFF0C\u8BF7\u8FD0\u884C: fund config <\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84>"),process.exit(1)),q(e)||(console.error(`\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${e}\uFF0C\u8BF7\u91CD\u65B0\u8BBE\u7F6E: fund config <\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84>`),process.exit(1)),JSON.parse(H(e,"utf-8"))}function se(e,t){return t.some(n=>n.isOverseas)||/全球|QDII|美元|港|海外|纳斯达克|标普|道琼斯/.test(e)?"\u5168\u7403":/ETF|指数/.test(e)?"\u6307\u6570":/债/.test(e)?"\u504F\u503A":"\u504F\u80A1"}var S="Asia/Shanghai";function je(e=S,t=new Date){return new Date(t.toLocaleString("en-US",{timeZone:e}))}function ze(e){return[e.getFullYear(),String(e.getMonth()+1).padStart(2,"0"),String(e.getDate()).padStart(2,"0")].join("-")}function Ae(){return ze(je())}function h(e,t=new Date){let a=new Intl.DateTimeFormat("en-CA",{timeZone:e,year:"numeric",month:"2-digit",day:"2-digit",weekday:"short",hour:"2-digit",minute:"2-digit",hour12:!1}).formatToParts(t),n=o=>a.find(r=>r.type===o)?.value||"";return{date:`${n("year")}-${n("month")}-${n("day")}`,weekday:n("weekday"),minutes:Number.parseInt(n("hour"),10)*60+Number.parseInt(n("minute"),10)}}function _(e){return["Mon","Tue","Wed","Thu","Fri"].includes(e)}function He(e=new Date){let t=h(S,e);return _(t.weekday)?t.minutes>900||t.minutes<570:!0}function qe(e=new Date){let t=h("Asia/Hong_Kong",e);return _(t.weekday)?t.minutes>960||t.minutes<570:!0}function Ke(e=new Date){let t=h("America/New_York",e);return _(t.weekday)?t.minutes>960||t.minutes<570:!0}function Ue(e,t,a=new Date){return!t?.quoteDate||!w(t.changePercent)?!1:e.market===116?t.quoteDate===h("Asia/Hong_Kong",a).date:e.market>=100?t.quoteDate===h("America/New_York",a).date:t.quoteDate===h(S,a).date}function _e(e,t){return!!(e&&t&&e===t)}function de(e,t=new Date){return e.market===116?h("Asia/Hong_Kong",t).date:e.market>=100?h("America/New_York",t).date:h(S,t).date}function Je(e,t=new Date){return e.market===116?qe(t):e.market>=100?Ke(t):He(t)}function We(e,t,a=new Date){let n=t.get(e.stockCode);if(!n)return null;let o=[...n.changesByDate.keys()].sort();if(o.length===0)return null;let r=de(e,a);if(Je(e,a))return o[o.length-1]||null;let d=o.filter(s=>s<r);return d[d.length-1]||null}function ce(e,t,a=new Date){if(!e?.time||!w(e.change))return!1;let n=e.time.slice(0,10);if(t==="\u5168\u7403"){let r=h("America/New_York",a),i=h("Asia/Hong_Kong",a);return n===r.date||n===i.date}let o=h(S,a).date;return n===o}function $(e){return!e||e.length<10?"":e.slice(5)}function w(e){return typeof e=="number"&&!Number.isNaN(e)}function J(e){return e.map(t=>({code:t.stockCode,market:t.market}))}function W(e,t,a,n=null){let o=Ae(),r=new Date,i=0,d=e.map(l=>{let m=t.get(l.stockCode),x=a.get(l.stockCode),k=We(l,a,r),D=Ue(l,m,r)&&(l.isOverseas||_e(o,n))?m.changePercent:null,G=k,E=k&&x?x.changesByDate.get(k)??null:null,De=G===de(l,r)?null:D,ke=w(D)?l.holdingRatio*D/100:0;return w(D)&&(i+=l.holdingRatio),{stockCode:l.stockCode,stockName:l.stockName,holdingRatio:l.holdingRatio,todayChange:D,intradayDisplayChange:De,previousTradingDayChange:E,previousTradingDayDate:G,contribution:w(E)?l.holdingRatio*E/100:0,weightedTodayContribution:ke,isOverseas:l.isOverseas}}),c=d.length>0&&d.every(l=>w(l.previousTradingDayChange))?d.reduce((l,m)=>l+m.contribution,0):null,u=i>0?i/100:0,f=u>=.6?d.reduce((l,m)=>l+m.weightedTodayContribution,0):null,g=d.map(l=>l.previousTradingDayDate).filter(Boolean).sort(),p=g.length>0?g[g.length-1]:null;return{previousTradingDayChange:c,weightedTodayChange:f,coverageRatio:u,contributions:d,todayDate:o,previousTradingDate:p}}async function Q(e){let t=new Map;return await Promise.all(Object.keys(e).map(async a=>{try{t.set(a,await P(a))}catch{t.set(a,[])}})),t}function Qe(e){let t=new Map;for(let[,a]of e)for(let n of a)t.has(n.stockCode)||t.set(n.stockCode,{code:n.stockCode,market:n.market});return[...t.values()]}async function Y(e,t){return oe(Qe(e),t)}function le(e,t,a,n,o){let r=se(t,a);return{code:e,name:t,todayChange:ce(n,r)?n.change:null,previousTradingDayChange:o?.change??null,previousTradingDayLabel:$(o?.date??null),category:r,canDetail:!0}}async function ue(e,t,a,n,o=null){let[r,i,d]=await Promise.all([z(J(a)),j(e),L(e)]),s=W(a,n,r,o);return{...le(e,t,a,i,d),contributions:s.contributions,weightedTodayChange:s.weightedTodayChange,coverageRatio:s.coverageRatio,todayLabel:$(s.todayDate)}}async function V(e,{forceRefresh:t=!1}={}){let a=await Q(e);return(await Promise.all(Object.entries(e).map(async([o,r])=>{let i=a.get(o)||[],[d,s]=await Promise.all([j(o,{force:t}),L(o,{force:t})]);return{code:o,name:r,holdings:i,estimate:d,latestNetWorth:s}}))).map(({code:o,name:r,holdings:i,estimate:d,latestNetWorth:s})=>le(o,r,i,d,s))}async function fe(e){let[t,a]=await Promise.all([V(e,{forceRefresh:!0}),re({force:!0})]);return{funds:t,marketOverview:a}}async function ge(e,t,{forceRefreshPrices:a=!1}={}){let n=await P(e),o=new Map([[e,n]]),[r,i,d,s]=await Promise.all([Y(o,{force:a}),O(),z(J(n)),L(e)]),c=W(n,r,d,i);return{code:e,name:t,previousTradingDayChange:s?.change??null,previousTradingDayLabel:$(s?.date??null),weightedTodayChange:w(c.weightedTodayChange)?c.weightedTodayChange:null,coverageRatio:c.coverageRatio,contributions:c.contributions}}import M from"chalk";import me from"cli-table3";function C(e){if(e==null||Number.isNaN(e))return"-";let t=parseFloat(e);return t>0?M.red(`+${t.toFixed(2)}%`):t<0?M.green(`${t.toFixed(2)}%`):`${t.toFixed(2)}%`}function pe(e,t,a){return!t||t===a?C(e):`${C(e)}
5
+ ${M.gray(t)}`}function Ye(e,t){return t?`${e} (${t})`:e}function he(e,t){let a="";for(let n of e){let o=t(n);o&&o>a&&(a=o)}return a}function be(e,t){return t?`${e}
6
+ ${t}`:e}function ye(e){let{code:t,name:a,todayChange:n,previousTradingDayChange:o,contributions:r}=e,i=he(r,s=>s.previousTradingDayDate?.slice(5)||""),d=new me({head:["\u6301\u4ED3","\u5360\u6BD4",be("\u6536\u76D8",i),"\u76D8\u4E2D"],style:{head:["cyan"]}});for(let s of r)d.push([`${s.stockCode} ${s.stockName}`,`${s.holdingRatio.toFixed(2)}%`,pe(s.previousTradingDayChange,s.previousTradingDayDate?.slice(5),i),C(s.intradayDisplayChange)]);console.log(`
7
+ ${M.bold(t)} ${M.bold(a)}`),console.log(d.toString()),console.log(`${Ye("\u51C0\u503C",e.previousTradingDayLabel)}: ${C(o)}`),console.log(`\u6DA8\u8DCC: ${C(n)}
8
+ `)}function ve(e){let t=he(e,n=>n.previousTradingDayLabel||""),a=new me({head:["\u57FA\u91D1\u4EE3\u7801","\u57FA\u91D1\u540D\u79F0",be("\u51C0\u503C",t),"\u4F30\u503C"],style:{head:["cyan"]}});for(let n of e)a.push([n.code,n.name,pe(n.previousTradingDayChange,n.previousTradingDayLabel,t),C(n.todayChange)]);console.log(a.toString())}import we from"express";import{spawn as Ve}from"child_process";import{networkInterfaces as Ge}from"os";async function Ze(e){return fe(e)}async function Xe(e,t,a=!1){return ge(e,t,{forceRefreshPrices:a})}function et(e){let t=null,a=[];process.platform==="darwin"?(t="open",a=[e]):process.platform==="win32"?(t="cmd",a=["/c","start","",e]):(t="xdg-open",a=[e]);try{Ve(t,a,{detached:!0,stdio:"ignore"}).unref()}catch{}}function tt(e){let t=Ge(),a=["en0","en1","Ethernet","Wi-Fi","wlan0","eth0"],n=[];for(let[o,r]of Object.entries(t))for(let i of r||[])i.family!=="IPv4"||i.internal||n.push({name:o,address:i.address});return n.length===0?null:(n.sort((o,r)=>{let i=a.indexOf(o.name),d=a.indexOf(r.name),s=i===-1?a.length:i,c=d===-1?a.length:d;return s!==c?s-c:o.name.localeCompare(r.name)}),`http://${n[0].address}:${e}`)}function at(){return`
9
9
  * { margin: 0; padding: 0; box-sizing: border-box; }
10
10
  body { background: #f5f0eb; color: #4a4a4a; font-family: -apple-system, "Microsoft YaHei", sans-serif; padding: 24px; }
11
11
  h1 { text-align: center; margin-bottom: 8px; font-size: 24px; color: #5a5a5a; }
@@ -186,7 +186,7 @@ ${M.bold(t)} ${M.bold(a)}`),console.log(d.toString()),console.log(`${Qe("\u51C0\
186
186
  .market-card { min-height: 72px; padding: 9px 9px; }
187
187
  .drawer { width: min(460px, 100vw); }
188
188
  }
189
- `}function at(){return`
189
+ `}function nt(){return`
190
190
  <h1>\u57FA\u91D1\u4F30\u503C\u67E5\u8BE2</h1>
191
191
  <div class="update-time" id="updateTime"><span id="timeText"></span><button class="refresh-btn" id="refreshBtn"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 4v6h-6"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg></button></div>
192
192
  <section class="market-overview hidden" id="marketOverview">
@@ -228,7 +228,7 @@ ${M.bold(t)} ${M.bold(a)}`),console.log(d.toString()),console.log(`${Qe("\u51C0\
228
228
  <div class="error-msg" id="errorMsg"></div>
229
229
  </div>
230
230
  </div>
231
- `}function nt(){return`
231
+ `}function rt(){return`
232
232
  // \u4FDD\u5B58\u5F53\u524D\u9875\u9762\u7684\u6392\u5E8F\u3001\u7B5B\u9009\u3001\u4E3B\u8868\u6570\u636E\u548C\u62BD\u5C49\u72B6\u6001\u3002
233
233
  const state = {
234
234
  sortKey: 'todayChange',
@@ -494,7 +494,7 @@ function getSortedDrawerContributions(fund) {
494
494
  if (state.drawerSortKey) {
495
495
  sorted.sort((a, b) => {
496
496
  if (state.drawerSortKey === 'holdingRatio') return state.drawerSortDir === 'asc' ? a.holdingRatio - b.holdingRatio : b.holdingRatio - a.holdingRatio;
497
- if (state.drawerSortKey === 'todayChange') return compareNullableNumbers(a.todayChange, b.todayChange, state.drawerSortDir);
497
+ if (state.drawerSortKey === 'todayChange') return compareNullableNumbers(a.intradayDisplayChange, b.intradayDisplayChange, state.drawerSortDir);
498
498
  if (state.drawerSortKey === 'previousTradingDayChange') return compareNullableNumbers(a.previousTradingDayChange, b.previousTradingDayChange, state.drawerSortDir);
499
499
  return 0;
500
500
  });
@@ -518,14 +518,14 @@ function buildDrawerTable(fund, sorted) {
518
518
  let html = '<table><thead><tr>' +
519
519
  '<th>\u6301\u4ED3</th>' +
520
520
  buildSortableTh('\u5360\u6BD4', 'holdingRatio', state.drawerSortKey, state.drawerSortDir, 'data-dkey="holdingRatio"') +
521
- buildSortableTh(fmtHeaderLabel('\u6536\u76D8\u6DA8\u8DCC', headerDate), 'previousTradingDayChange', state.drawerSortKey, state.drawerSortDir, 'data-dkey="previousTradingDayChange"') +
522
- buildSortableTh('\u6DA8\u8DCC', 'todayChange', state.drawerSortKey, state.drawerSortDir, 'data-dkey="todayChange"') +
521
+ buildSortableTh(fmtHeaderLabel('\u6536\u76D8', headerDate), 'previousTradingDayChange', state.drawerSortKey, state.drawerSortDir, 'data-dkey="previousTradingDayChange"') +
522
+ buildSortableTh('\u76D8\u4E2D', 'todayChange', state.drawerSortKey, state.drawerSortDir, 'data-dkey="todayChange"') +
523
523
  '</tr></thead><tbody>';
524
524
  for (const c of sorted) {
525
525
  html += '<tr><td>' + escapeHtml(c.stockCode + ' ' + c.stockName) + '</td>' +
526
526
  '<td>' + c.holdingRatio.toFixed(2) + '%</td>' +
527
527
  '<td class="' + colorClass(c.previousTradingDayChange) + '">' + fmtPercentWithDate(c.previousTradingDayChange, c.previousTradingDayDate ? c.previousTradingDayDate.slice(5) : '', headerDate) + '</td>' +
528
- '<td class="' + colorClass(c.todayChange) + '"><span class="cell-value">' + fmtNullablePercent(c.todayChange) + '</span></td></tr>';
528
+ '<td class="' + colorClass(c.intradayDisplayChange) + '"><span class="cell-value">' + fmtNullablePercent(c.intradayDisplayChange) + '</span></td></tr>';
529
529
  }
530
530
  return html + '</tbody></table>';
531
531
  }
@@ -544,8 +544,8 @@ function renderDrawer() {
544
544
  '<div class="drawer-title">' + escapeHtml(fund.name) + '</div>' +
545
545
  '<div class="drawer-subtitle">' + escapeHtml(fund.code) + ' \u6301\u4ED3\u660E\u7EC6</div>' +
546
546
  '</div><div class="drawer-actions">' +
547
- '<div><div class="drawer-estimate"><span class="drawer-info-label" title="\u6309\u5F53\u524D\u6709\u76D8\u4E2D\u6DA8\u8DCC\u7684\u6301\u4ED3\uFF0C\u6309\u6301\u4ED3\u5360\u6BD4\u52A0\u6743\u6C47\u603B\u5F97\u5230\u7684\u4F30\u503C\u3002\u8986\u76D6\u7387\u4E0D\u8DB3\u65F6\u4E0D\u5C55\u793A\u6570\u503C\u3002">\u6743\u91CD\u4F30\u503C</span><strong class="' + colorClass(fund.weightedTodayChange) + '">' + fmtNullablePercent(fund.weightedTodayChange) + '</strong></div>' +
548
- '<div class="drawer-coverage"><span class="drawer-info-label" title="\u5F53\u524D\u62FF\u5230\u76D8\u4E2D\u6DA8\u8DCC\u7684\u6301\u4ED3\u6743\u91CD\u5360\u6BD4\u3002\u8986\u76D6\u7387\u8FBE\u5230 60% \u624D\u5C55\u793A\u6743\u91CD\u4F30\u503C\u3002">\u8986\u76D6</span> ' + fmtCoverage(fund.coverageRatio) + '</div></div>' +
547
+ '<div><div class="drawer-estimate"><span class="drawer-info-label" title="\u6309\u5F53\u524D\u6709\u76D8\u4E2D\u6570\u636E\u7684\u6301\u4ED3\uFF0C\u6309\u6301\u4ED3\u5360\u6BD4\u52A0\u6743\u6C47\u603B\u5F97\u5230\u7684\u4F30\u503C\u3002\u8986\u76D6\u7387\u4E0D\u8DB3\u65F6\u4E0D\u5C55\u793A\u6570\u503C\u3002">\u6743\u91CD\u4F30\u503C</span><strong class="' + colorClass(fund.weightedTodayChange) + '">' + fmtNullablePercent(fund.weightedTodayChange) + '</strong></div>' +
548
+ '<div class="drawer-coverage"><span class="drawer-info-label" title="\u5F53\u524D\u62FF\u5230\u6709\u6548\u76D8\u4E2D\u6570\u636E\u7684\u6301\u4ED3\u6743\u91CD\u5360\u6BD4\u3002\u8986\u76D6\u7387\u8FBE\u5230 60% \u624D\u5C55\u793A\u6743\u91CD\u4F30\u503C\u3002">\u8986\u76D6</span> ' + fmtCoverage(fund.coverageRatio) + '</div></div>' +
549
549
  buildDrawerRefreshButton() +
550
550
  '</div></div>';
551
551
 
@@ -806,17 +806,17 @@ function bindEvents() {
806
806
 
807
807
  // \u6BCF 30 \u79D2\u81EA\u52A8\u5237\u65B0\u4E00\u6B21\u4E3B\u8868\u548C\u5927\u76D8\u5361\u7247\u3002
808
808
  setInterval(doRefresh, 30000);
809
- `}function rt(){return`<!DOCTYPE html>
809
+ `}function ot(){return`<!DOCTYPE html>
810
810
  <html lang="zh-CN">
811
811
  <head>
812
812
  <meta charset="UTF-8">
813
813
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
814
814
  <title>\u57FA\u91D1\u4F30\u503C\u67E5\u8BE2</title>
815
- <style>${tt()}</style>
815
+ <style>${at()}</style>
816
816
  </head>
817
817
  <body>
818
- ${at()}
819
- <script>${nt()}</script>
818
+ ${nt()}
819
+ <script>${rt()}</script>
820
820
  </body>
821
- </html>`}async function ve(e){let t=ye();t.use(ye.json()),t.get("/",(o,r)=>{r.type("html").send(rt())}),t.post("/api/funds",async(o,r)=>{try{let i=o.body?.funds;if(!i||typeof i!="object"||Object.keys(i).length===0)return r.status(400).json({error:"funds \u914D\u7F6E\u65E0\u6548"});let d=await Ge(i);r.json(d)}catch(i){r.status(500).json({error:i.message})}}),t.get("/api/funds/:code/detail",async(o,r)=>{try{let i=o.params.code,d=typeof o.query?.name=="string"?o.query.name:i,s=o.query?.refreshPrices==="1",c=await Ze(i,d,s);r.json(c)}catch(i){r.status(500).json({error:i.message})}});let a=`http://localhost:${e}`,n=et(e);return new Promise(o=>{let r=t.listen(e,()=>{console.log("\u670D\u52A1\u5DF2\u542F\u52A8:"),console.log(`- ${a}`),n&&console.log(`- ${n}`),Xe(a),o(r)});r.on("error",i=>{if(i?.code==="EADDRINUSE"){console.log(`\u7AEF\u53E3 ${e} \u5DF2\u88AB\u5360\u7528\uFF0C\u53EF\u80FD\u670D\u52A1\u5DF2\u7ECF\u542F\u52A8\u3002\u53EF\u5C1D\u8BD5\u8BBF\u95EE:`),console.log(`- ${a}`),n&&console.log(`- ${n}`),o(null);return}console.error(`\u542F\u52A8\u670D\u52A1\u5931\u8D25: ${i.message}`),o(null)})})}var{version:st}=JSON.parse(it(new URL("../package.json",import.meta.url),"utf8"));function dt(e,t){let a=e.todayChange===null||e.todayChange===void 0||Number.isNaN(e.todayChange),n=t.todayChange===null||t.todayChange===void 0||Number.isNaN(t.todayChange);return a&&n?0:a?1:n?-1:t.todayChange-e.todayChange}var b=new ot;b.name("fund").description("\u57FA\u91D1\u4F30\u503C\u67E5\u8BE2\u5DE5\u5177").version(st,"-v, -V, --version");b.action(()=>{b.help()});b.command("config [path]").description("\u8BBE\u7F6E\u6216\u67E5\u770B\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(e=>{if(e)oe(e);else{let t=K();t?console.log(t):(console.error("\u672A\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6\uFF0C\u8BF7\u8FD0\u884C: fund config <\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84>"),process.exit(1))}});b.command("list").description("\u5217\u51FA\u6240\u6709\u914D\u7F6E\u7684\u57FA\u91D1").action(async()=>{let e=U(),t=await V(e);t.sort(dt),be(t)});b.command("detail <code>").description("\u67E5\u770B\u5355\u53EA\u57FA\u91D1\u6301\u4ED3\u8BE6\u60C5").action(async e=>{let a=U()[e];a||(console.error(`\u57FA\u91D1 ${e} \u672A\u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\u914D\u7F6E`),process.exit(1));let n=await Q({[e]:a}),[o,r]=await Promise.all([Y(n),O()]),i=await ce(e,a,n.get(e)||[],o,r);he(i)});b.command("serve").description("\u542F\u52A8 Web \u670D\u52A1\uFF08\u7528\u4E8E\u90E8\u7F72\u5230\u670D\u52A1\u5668\uFF09").option("-p, --port <port>","\u7AEF\u53E3\u53F7",process.env.PORT||"8888").action(async e=>{let t=parseInt(e.port,10);await ve(t)});b.command("*",null,{noHelp:!0}).action(()=>{console.error(`\u672A\u77E5\u547D\u4EE4
821
+ </html>`}async function xe(e){let t=we();t.use(we.json()),t.get("/",(o,r)=>{r.type("html").send(ot())}),t.post("/api/funds",async(o,r)=>{try{let i=o.body?.funds;if(!i||typeof i!="object"||Object.keys(i).length===0)return r.status(400).json({error:"funds \u914D\u7F6E\u65E0\u6548"});let d=await Ze(i);r.json(d)}catch(i){r.status(500).json({error:i.message})}}),t.get("/api/funds/:code/detail",async(o,r)=>{try{let i=o.params.code,d=typeof o.query?.name=="string"?o.query.name:i,s=o.query?.refreshPrices==="1",c=await Xe(i,d,s);r.json(c)}catch(i){r.status(500).json({error:i.message})}});let a=`http://localhost:${e}`,n=tt(e);return new Promise(o=>{let r=t.listen(e,()=>{console.log("\u670D\u52A1\u5DF2\u542F\u52A8:"),console.log(`- ${a}`),n&&console.log(`- ${n}`),et(a),o(r)});r.on("error",i=>{if(i?.code==="EADDRINUSE"){console.log(`\u7AEF\u53E3 ${e} \u5DF2\u88AB\u5360\u7528\uFF0C\u53EF\u80FD\u670D\u52A1\u5DF2\u7ECF\u542F\u52A8\u3002\u53EF\u5C1D\u8BD5\u8BBF\u95EE:`),console.log(`- ${a}`),n&&console.log(`- ${n}`),o(null);return}console.error(`\u542F\u52A8\u670D\u52A1\u5931\u8D25: ${i.message}`),o(null)})})}var{version:dt}=JSON.parse(st(new URL("../package.json",import.meta.url),"utf8"));function ct(e,t){let a=e.todayChange===null||e.todayChange===void 0||Number.isNaN(e.todayChange),n=t.todayChange===null||t.todayChange===void 0||Number.isNaN(t.todayChange);return a&&n?0:a?1:n?-1:t.todayChange-e.todayChange}var b=new it;b.name("fund").description("\u57FA\u91D1\u4F30\u503C\u67E5\u8BE2\u5DE5\u5177").version(dt,"-v, -V, --version");b.action(()=>{b.help()});b.command("config [path]").description("\u8BBE\u7F6E\u6216\u67E5\u770B\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(e=>{if(e)ie(e);else{let t=K();t?console.log(t):(console.error("\u672A\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6\uFF0C\u8BF7\u8FD0\u884C: fund config <\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84>"),process.exit(1))}});b.command("list").description("\u5217\u51FA\u6240\u6709\u914D\u7F6E\u7684\u57FA\u91D1").action(async()=>{let e=U(),t=await V(e);t.sort(ct),ve(t)});b.command("detail <code>").description("\u67E5\u770B\u5355\u53EA\u57FA\u91D1\u6301\u4ED3\u8BE6\u60C5").action(async e=>{let a=U()[e];a||(console.error(`\u57FA\u91D1 ${e} \u672A\u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\u914D\u7F6E`),process.exit(1));let n=await Q({[e]:a}),[o,r]=await Promise.all([Y(n),O()]),i=await ue(e,a,n.get(e)||[],o,r);ye(i)});b.command("serve").description("\u542F\u52A8 Web \u670D\u52A1\uFF08\u7528\u4E8E\u90E8\u7F72\u5230\u670D\u52A1\u5668\uFF09").option("-p, --port <port>","\u7AEF\u53E3\u53F7",process.env.PORT||"8888").action(async e=>{let t=parseInt(e.port,10);await xe(t)});b.command("*",null,{noHelp:!0}).action(()=>{console.error(`\u672A\u77E5\u547D\u4EE4
822
822
  `),b.help()});b.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cxxgo/fund-valuation-query",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "fund": "./dist/fund.js"