diff --git a/clis/xueqiu/groups.yaml b/clis/xueqiu/groups.yaml new file mode 100644 index 000000000..62d6214aa --- /dev/null +++ b/clis/xueqiu/groups.yaml @@ -0,0 +1,23 @@ +site: xueqiu +name: groups +description: 获取雪球自选股分组列表(含模拟组合) +domain: xueqiu.com +browser: true + +pipeline: + - navigate: https://xueqiu.com + - evaluate: | + (async () => { + const resp = await fetch('https://stock.xueqiu.com/v5/stock/portfolio/list.json?category=1&size=20', {credentials: 'include'}); + if (!resp.ok) throw new Error('HTTP ' + resp.status + ' Hint: Not logged in?'); + const d = await resp.json(); + if (!d.data || !d.data.stocks) throw new Error('获取失败,可能未登录'); + + return d.data.stocks.map(g => ({ + pid: String(g.id), + name: g.name, + count: g.symbol_count || 0 + })); + })() + +columns: [pid, name, count] diff --git a/clis/xueqiu/kline.yaml b/clis/xueqiu/kline.yaml new file mode 100644 index 000000000..fa63d1632 --- /dev/null +++ b/clis/xueqiu/kline.yaml @@ -0,0 +1,65 @@ +site: xueqiu +name: kline +description: 获取雪球股票K线(历史行情)数据 +domain: xueqiu.com +browser: true + +args: + symbol: + positional: true + type: str + required: true + description: 股票代码,如 SH600519、SZ000858、AAPL + days: + type: int + default: 14 + description: 回溯天数(默认14天) + +pipeline: + - navigate: https://xueqiu.com + + - evaluate: | + (async () => { + const symbol = (${{ args.symbol | json }} || '').toUpperCase(); + const days = parseInt(${{ args.days | json }}) || 14; + if (!symbol) throw new Error('Missing argument: symbol'); + + // begin = now minus days (for count=-N, returns N items ending at begin) + const beginTs = Date.now(); + const resp = await fetch('https://stock.xueqiu.com/v5/stock/chart/kline.json?symbol=' + encodeURIComponent(symbol) + '&begin=' + beginTs + '&period=day&type=before&count=-' + days, {credentials: 'include'}); + if (!resp.ok) throw new Error('HTTP ' + resp.status + ' Hint: Not logged in?'); + const d = await resp.json(); + + if (!d.data || !d.data.item || d.data.item.length === 0) return []; + + const columns = d.data.column || []; + const items = d.data.item || []; + const colIdx = {}; + columns.forEach((name, i) => { colIdx[name] = i; }); + + function fmt(v) { return v == null ? null : v; } + + return items.map(row => ({ + date: colIdx.timestamp != null ? new Date(row[colIdx.timestamp]).toISOString().split('T')[0] : null, + open: fmt(row[colIdx.open]), + high: fmt(row[colIdx.high]), + low: fmt(row[colIdx.low]), + close: fmt(row[colIdx.close]), + volume: fmt(row[colIdx.volume]), + amount: fmt(row[colIdx.amount]), + chg: fmt(row[colIdx.chg]), + percent: fmt(row[colIdx.percent]), + symbol: symbol + })); + })() + + - map: + date: ${{ item.date }} + open: ${{ item.open }} + high: ${{ item.high }} + low: ${{ item.low }} + close: ${{ item.close }} + volume: ${{ item.volume }} + percent: ${{ item.percent }} + +columns: [date, open, high, low, close, volume] diff --git a/clis/xueqiu/watchlist.yaml b/clis/xueqiu/watchlist.yaml index 7304f73d8..b20eb9e10 100644 --- a/clis/xueqiu/watchlist.yaml +++ b/clis/xueqiu/watchlist.yaml @@ -1,14 +1,14 @@ site: xueqiu name: watchlist -description: 获取雪球自选股列表 +description: 获取雪球自选股/模拟组合股票列表 domain: xueqiu.com browser: true args: - category: - type: str # using str to prevent parsing issues like 01 - default: "1" - description: "分类:1=自选(默认) 2=持仓 3=关注" + pid: + type: str + default: "-1" + description: "分组ID:-1=全部(默认) -4=模拟 -5=沪深 -6=美股 -7=港股 -10=实盘 0=持仓(通过 xueqiu groups 获取)" limit: type: int default: 100 @@ -18,12 +18,12 @@ pipeline: - navigate: https://xueqiu.com - evaluate: | (async () => { - const category = parseInt(${{ args.category | json }}) || 1; - const resp = await fetch(`https://stock.xueqiu.com/v5/stock/portfolio/stock/list.json?size=100&category=${category}&pid=-1`, {credentials: 'include'}); + const pid = ${{ args.pid | json }} || '-1'; + const resp = await fetch(`https://stock.xueqiu.com/v5/stock/portfolio/stock/list.json?size=100&category=1&pid=${encodeURIComponent(pid)}`, {credentials: 'include'}); if (!resp.ok) throw new Error('HTTP ' + resp.status + ' Hint: Not logged in?'); const d = await resp.json(); if (!d.data || !d.data.stocks) throw new Error('获取失败,可能未登录'); - + return d.data.stocks.map(s => ({ symbol: s.symbol, name: s.name, @@ -40,7 +40,7 @@ pipeline: name: ${{ item.name }} price: ${{ item.price }} changePercent: ${{ item.changePercent }} - + - limit: ${{ args.limit }} columns: [symbol, name, price, changePercent]