/ clis / eastmoney / longhu.js
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  });