From d2e179ced573c8f9fd0097dcdc6f5b93e8622c8f Mon Sep 17 00:00:00 2001 From: jackwener Date: Wed, 18 Mar 2026 13:23:28 +0800 Subject: [PATCH] fix(barchart): preserve flow semantics and nearest expiry --- src/clis/barchart/flow.ts | 70 ++++++++++++++++--------------------- src/clis/barchart/greeks.ts | 22 ++++++++++-- 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/src/clis/barchart/flow.ts b/src/clis/barchart/flow.ts index 7bae0f6..a7945e8 100644 --- a/src/clis/barchart/flow.ts +++ b/src/clis/barchart/flow.ts @@ -38,51 +38,43 @@ cli({ 'lastPrice','volume','openInterest','volumeOpenInterestRatio','volatility', ].join(','); - // Try unusual_activity list first, fall back to mostActive // Fetch extra rows when filtering by type since server-side filter may not work const fetchLimit = typeFilter !== 'all' ? limit * 3 : limit; - const lists = [ - 'options.unusual_activity.stocks.us', - 'options.mostActive.us', - ]; + try { + const url = '/proxies/core-api/v1/options/get?list=options.unusual_activity.stocks.us' + + '&fields=' + fields + + '&orderBy=volumeOpenInterestRatio&orderDir=desc' + + '&raw=1&limit=' + fetchLimit; - for (const list of lists) { - try { - const url = '/proxies/core-api/v1/options/get?list=' + list - + '&fields=' + fields - + '&orderBy=volumeOpenInterestRatio&orderDir=desc' - + '&raw=1&limit=' + fetchLimit; - - const resp = await fetch(url, { credentials: 'include', headers }); - if (resp.ok) { - const d = await resp.json(); - let items = d?.data || []; - if (items.length > 0) { - // Apply client-side type filter - if (typeFilter !== 'all') { - items = items.filter(i => { - const t = ((i.raw || i).optionType || '').toLowerCase(); - return t === typeFilter; - }); - } - return items.slice(0, limit).map(i => { - const r = i.raw || i; - return { - symbol: r.baseSymbol || r.symbol, - type: r.optionType, - strike: r.strikePrice, - expiration: r.expirationDate, - last: r.lastPrice, - volume: r.volume, - openInterest: r.openInterest, - volOiRatio: r.volumeOpenInterestRatio, - iv: r.volatility, - }; + const resp = await fetch(url, { credentials: 'include', headers }); + if (resp.ok) { + const d = await resp.json(); + let items = d?.data || []; + if (items.length > 0) { + // Apply client-side type filter + if (typeFilter !== 'all') { + items = items.filter(i => { + const t = ((i.raw || i).optionType || '').toLowerCase(); + return t === typeFilter; }); } + return items.slice(0, limit).map(i => { + const r = i.raw || i; + return { + symbol: r.baseSymbol || r.symbol, + type: r.optionType, + strike: r.strikePrice, + expiration: r.expirationDate, + last: r.lastPrice, + volume: r.volume, + openInterest: r.openInterest, + volOiRatio: r.volumeOpenInterestRatio, + iv: r.volatility, + }; + }); } - } catch(e) {} - } + } + } catch(e) {} // Fallback: parse from DOM table try { diff --git a/src/clis/barchart/greeks.ts b/src/clis/barchart/greeks.ts index 2a5732f..de3c493 100644 --- a/src/clis/barchart/greeks.ts +++ b/src/clis/barchart/greeks.ts @@ -13,7 +13,7 @@ cli({ strategy: Strategy.COOKIE, args: [ { name: 'symbol', required: true, help: 'Stock ticker (e.g. AAPL)' }, - { name: 'expiration', type: 'str', help: 'Expiration date (YYYY-MM-DD). Defaults to nearest.' }, + { name: 'expiration', type: 'str', help: 'Expiration date (YYYY-MM-DD). Defaults to the nearest available expiration.' }, { name: 'limit', type: 'int', default: 10, help: 'Number of near-the-money strikes per type' }, ], columns: [ @@ -49,7 +49,25 @@ cli({ const resp = await fetch(url, { credentials: 'include', headers }); if (resp.ok) { const d = await resp.json(); - const items = d?.data || []; + let items = d?.data || []; + + if (!expDate) { + const expirations = items + .map(i => (i.raw || i).expirationDate || null) + .filter(Boolean) + .sort((a, b) => { + const aTime = Date.parse(a); + const bTime = Date.parse(b); + if (Number.isNaN(aTime) && Number.isNaN(bTime)) return 0; + if (Number.isNaN(aTime)) return 1; + if (Number.isNaN(bTime)) return -1; + return aTime - bTime; + }); + const nearestExpiration = expirations[0]; + if (nearestExpiration) { + items = items.filter(i => ((i.raw || i).expirationDate || null) === nearestExpiration); + } + } // Separate calls and puts, sort by distance from current price const calls = items