fix(barchart): preserve flow semantics and nearest expiry

This commit is contained in:
jackwener committed 2026-03-18 13:23:28 +08:00
1 parent c806f795cc
commit d2e179ced5
2 files changed
+51 -41

No files matched your search

+31 -39
View File
@@ -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 {
+20 -2
View File
@@ -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