longhu.js
1 // eastmoney longhu — dragon & tiger list (龙虎榜). 2 // 3 // opencli eastmoney longhu 4 // opencli eastmoney longhu --date 2025-12-10 --limit 20 5 6 import { cli, Strategy } from '@jackwener/opencli/registry'; 7 import { CliError } from '@jackwener/opencli/errors'; 8 9 function defaultTradeDate() { 10 // Default window = 30 days back; results sorted DESC so latest comes first. 11 // This avoids missing data on weekends/holidays when "yesterday" had no trading. 12 const d = new Date(Date.now() + 8 * 3600 * 1000); 13 d.setUTCDate(d.getUTCDate() - 30); 14 return d.toISOString().slice(0, 10); 15 } 16 17 cli({ 18 site: 'eastmoney', 19 name: 'longhu', 20 description: '龙虎榜明细(A股交易所公开披露榜单)', 21 domain: 'datacenter-web.eastmoney.com', 22 strategy: Strategy.PUBLIC, 23 browser: false, 24 args: [ 25 { name: 'date', type: 'string', default: '', help: '开始交易日 YYYY-MM-DD (默认昨天)' }, 26 { name: 'limit', type: 'int', default: 20, help: '返回数量 (max 100)' }, 27 ], 28 columns: ['tradeDate', 'code', 'name', 'closePrice', 'changeRate', 'boardAmt', 'buyAmt', 'sellAmt', 'netAmt', 'turnover', 'dealRatio', 'market', 'reason'], 29 func: async (_page, args) => { 30 const sinceDate = String(args.date || '').trim() || defaultTradeDate(); 31 const limit = Math.max(1, Math.min(Number(args.limit) || 20, 100)); 32 33 const url = new URL('https://datacenter-web.eastmoney.com/api/data/v1/get'); 34 url.searchParams.set('sortColumns', 'TRADE_DATE,SECURITY_CODE'); 35 url.searchParams.set('sortTypes', '-1,1'); 36 url.searchParams.set('pageSize', String(limit)); 37 url.searchParams.set('pageNumber', '1'); 38 url.searchParams.set('reportName', 'RPT_DAILYBILLBOARD_DETAILS'); 39 url.searchParams.set('columns', 'ALL'); 40 url.searchParams.set('source', 'WEB'); 41 url.searchParams.set('client', 'WEB'); 42 url.searchParams.set('filter', `(TRADE_DATE>='${sinceDate}')`); 43 44 const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }); 45 if (!resp.ok) throw new CliError('HTTP_ERROR', `longhu failed: HTTP ${resp.status}`); 46 const data = await resp.json(); 47 /** @type {any[]} */ 48 const rows = Array.isArray(data?.result?.data) ? data.result.data : []; 49 if (rows.length === 0) throw new CliError('NO_DATA', `No longhu data since ${sinceDate}`); 50 51 return rows.slice(0, limit).map((it) => ({ 52 tradeDate: String(it.TRADE_DATE || '').slice(0, 10), 53 code: it.SECURITY_CODE, 54 name: it.SECURITY_NAME_ABBR, 55 closePrice: it.CLOSE_PRICE, 56 changeRate: it.CHANGE_RATE, 57 boardAmt: it.BILLBOARD_DEAL_AMT, 58 buyAmt: it.BILLBOARD_BUY_AMT, 59 sellAmt: it.BILLBOARD_SELL_AMT, 60 netAmt: it.BILLBOARD_NET_AMT, 61 turnover: it.ACCUM_AMOUNT, 62 dealRatio: it.DEAL_AMOUNT_RATIO, 63 market: it.TRADE_MARKET, 64 reason: it.EXPLANATION, 65 })); 66 }, 67 });