diff --git a/scripts/update-data.ts b/scripts/update-data.ts index c2af86a..822af5d 100755 --- a/scripts/update-data.ts +++ b/scripts/update-data.ts @@ -1203,6 +1203,14 @@ function loadCompanyNames(config: UpdaterConfig): Promise> { return secState.names; } +/** Nasdaq Trader symbol directories for the N-PORT rows (shared nport-reader `readUsListings`), loaded once per run: a row with a + * foreign ISIN keeps a ticker only for the company's own U.S. listing in U.S. dollars (Linde, NXP, IMAX). null when either failed. */ +let usListings: Promise | null> | null = null; +function loadUsListings(getText: (url: string, label: string) => Promise): Promise | null> { + if (!usListings) usListings = readUsListings(getText); + return usListings; +} + /** * The WisdomTree holdings sheet of an N-PORT filing: HOLDINGS_HEADERS, plus Coupon and Maturity when the filing holds debt. A * missing number stays empty (never 0), a missing text is '-'; tickers come from nportTickers (U.S. equities only). @@ -1738,7 +1746,7 @@ async function processFund(fund: CatalogFund, config: UpdaterConfig, previous: J : { problem: `${fund.ticker}: not in the SEC fund ticker table` }; if ('problem' in read) outputNote(`[ ${'nport'.padEnd(9)}] ${read.problem}`); else { - const sheet = nportHoldingsSheet(read.filing, nportTickers(read.filing.positions, await loadCompanyNames(config).catch(() => null))); + const sheet = nportHoldingsSheet(read.filing, nportTickers(read.filing.positions, await loadCompanyNames(config).catch(() => null), await loadUsListings((url, label) => fetchText(url, `[nport ] ${label}`, config, secHeaders())))); holdingsRows = sheet.rows; holdingsHeaders = sheet.headers; holdingsFresh = true; @@ -2586,7 +2594,9 @@ export function withoutClosedFunds(queue: ReadonlyArray, tickerOf: (item: * ``; a derivative's reference instrument (inside ``) never lends its identifiers to the position * - CUSIP `000000000` and `N/A` are missing; a missing weight or value is null (an empty cell), never 0 * - a ticker only for a common-stock row (assetCat EC, not a CVR, right, warrant or escrow line) traded in USD with a U.S. - * ISIN or none, and never one ticker on two different securities (`nportTickers`) + * ISIN or none, or with a foreign ISIN when the Nasdaq Trader directories list that ticker for the same company (a U.S. + * listing such as Linde, NXP, IMAX); renamed issuers through `nportRenamedIssuers`; never one ticker on two different + * securities (`nportTickers`) * - identity: the filing must name the fund's own series id, or, with no series id known, the fund's name (`nportIdentity`) * - freshness: the report period must be strictly newer than the published holdings (`nportIsFresh`) * - filings come newest report period first from the registrant's own archive folder, never from the filer agent's CIK @@ -2826,28 +2836,216 @@ function nportIsRight(position: NportPosition): boolean { return [position.name, position.title].some((text) => /\S\s*\bCVRS?\b|\bCONTINGENT\b|\bRIGHTS?\b|\bRTS\b|\bWARRANTS?\b|\bWTS\b|\bESCROW\b/i.test(text)); } +/** + * Renamed issuers (open-items etfs Q33): N-PORT filers keep an issuer's old name for months while SEC's company_tickers.json + * titles only the new one, so the name match misses (JPMorgan JEPI's Exxon). Old name key (`nportNameKey`) -> [ticker, CUSIP + * issuer number (the first six characters)]. Used only when the SEC table has no such key, the row's CUSIP (or U.S. ISIN) has + * that issuer number and the SEC table still lists the ticker. Each entry was confirmed on 2026-10-26: an EC row without a + * ticker in the N-PORT-P filings of large U.S. equity funds (VTI, SCHB, ITOT, JEPI, IWB, VB, SCHA, FNDX, FNDA, ...) whose name + * is a former EDGAR name of the CIK that has the ticker today (data.sec.gov submissions `formerNames`, the date the old name + * ended), the ticker the CIK's only common stock in the Nasdaq Trader directories. Add an entry only with the same evidence. + * A function, not a const: blocks sit at the end of update-data.ts, below the top-level run, so only declarations are hoisted. + */ +export function nportRenamedIssuers(): Array<[string, string, string]> { + return [ + ['AADI BIOSCIENCE', 'WHWK', '00032Q'], // CIK 1422142 Whitehawk Therapeutics, Inc., former EDGAR name until 2025-03-18 + ['ABACUS LIFE', 'ABX', '00258Y'], // CIK 1814287 Abacus Global Management, Inc., former EDGAR name until 2025-02-04 + ['ACUITY BRANDS', 'AYI', '00508Y'], // CIK 1144215 ACUITY INC. (DE), former EDGAR name until 2025-04-17 + ['AEROVATE THERAPEUTICS', 'JBIO', '008064'], // CIK 1798749 Jade Biosciences, Inc., former EDGAR name until 2025-04-30 + ['AILERON THERAPEUTICS', 'RNTX', '00887A'], // CIK 1420565 Rein Therapeutics, Inc., former EDGAR name until 2024-12-09 + ['ALLOVIR', 'KLRS', '482929'], // CIK 1754068 Kalaris Therapeutics, Inc., former EDGAR name until 2025-03-12 + ['AMC NETWORKS', 'AMCX', '00164V'], // CIK 1514991 AMC Global Media Inc., former EDGAR name until 2026-04-03 + ['AMMO', 'POWW', '00175J'], // CIK 1015383 Outdoor Holding Co, former EDGAR name until 2025-04-24 + ['ARDENT HEALTH PARTNERS', 'ARDT', '03980N'], // CIK 1756655 Ardent Health, Inc., former EDGAR name until 2025-05-23 + ['ARENA GROUP HOLDINGS', 'PAAI', '040044'], // CIK 894871 Paradium.AI, Inc., former EDGAR name until 2026-08-13 + ['ATEGRITY SPECIALTY HOLDINGS LLC', 'ASIC', '04681Y'], // CIK 2040491 Ategrity Specialty Insurance Co Holdings, former EDGAR name until 2025-06-11 + ['BAKKT HOLDINGS', 'BKKT', '05759B'], // CIK 1820302 Bakkt, Inc., former EDGAR name until 2026-01-20 + ['BED BATH AND BEYOND', 'NXH', '690370'], // CIK 1130713 NEIGHBORHOOD INTELLIGENCE, INC., former EDGAR name until 2026-08-14 + ['BLACKROCK FUNDING', 'BLK', '09290D'], // CIK 2012383 BlackRock, Inc., former EDGAR name until 2024-10-01 + ['BRIGHTSPHERE INVESTMENT GROUP', 'AAMI', '10948W'], // CIK 1748824 Acadian Asset Management Inc., former EDGAR name until 2024-11-14 + ['CARA THERAPEUTICS', 'TVRD', '140755'], // CIK 1346830 Tvardi Therapeutics, Inc., former EDGAR name until 2025-04-11 + ['CEA INDUSTRIES', 'BNC', '86887P'], // CIK 1482541 BNB Standard Corp, former EDGAR name until 2026-09-21 + ['CHROMADEX', 'NAGE', '171077'], // CIK 1386570 Niagen Bioscience, Inc., former EDGAR name until 2025-03-10 + ['CLEANCORE SOLUTIONS', 'ZONE', '184492'], // CIK 1956741 Zone Frontier Inc., former EDGAR name until 2026-08-20 + ['CORPORATE OFFICE PROPERTIES TRUST', 'CDP', '22002T'], // CIK 860546 COPT DEFENSE PROPERTIES, former EDGAR name until 2023-09-14 + ['CULLINAN ONCOLOGY', 'CGEM', '230031'], // CIK 1789972 Cullinan Therapeutics, Inc., former EDGAR name until 2024-04-02 + ['DIME COMMUNITY BANCSHARES', 'DCOM', '25432X'], // CIK 846617 Dime Commercial Bancshares, Inc. /NY/, former EDGAR name until 2026-05-27 + ['DOMO', 'HUCK', '257554'], // CIK 1505952 Huckleberry.ai, Inc., former EDGAR name until 2026-09-03 + ['EASTSIDE DISTILLING', 'BLNE', '277802'], // CIK 1534708 Beeline Holdings, Inc., former EDGAR name until 2025-03-11 + ['EQUITY RESIDENTIAL', 'VMRK', '29476L'], // CIK 906107 VIVMARK RESIDENTIAL, former EDGAR name until 2026-08-12 + ['EXP WORLD HOLDINGS', 'AGNT', '30212W'], // CIK 1495932 AGNT, Inc., former EDGAR name until 2026-06-01 + ['EXXON MOBIL', 'XOM', '30231G'], // holding company reorganization: XOM is CIK 2115436 ExxonMobil Holdings Corp since 2026, the old Exxon Mobil Corp is CIK 34088 + ['EYEPOINT PHARMACEUTICALS', 'EYPT', '30233G'], // CIK 1314102 EyePoint, Inc., former EDGAR name until 2025-12-08 + ['EZFILL HOLDINGS', 'NXXT', '652941'], // CIK 1817004 NEXTNRG, INC., former EDGAR name until 2025-02-14 + ['FORTUNE BRANDS HOME AND SECURITY', 'FBIN', '34964C'], // CIK 1519751 Fortune Brands Innovations, Inc., former EDGAR name until 2022-12-14 + ['FRANKLIN RESOURCES', 'BEN', '354613'], // CIK 38777 FRANKLIN TEMPLETON INC, former EDGAR name until 2026-08-14 + ['FREYR BATTERY', 'TE', '35834F'], // CIK 1992243 T1 Energy Inc., former EDGAR name until 2025-02-14 + ['GALLAGHER ARTHUR J AND', 'AJG', '363576'], // CIK 354190 Arthur J. Gallagher & Co., former EDGAR name until 2019-08-14 + ['GRAINGER WW', 'GWW', '384802'], // CIK 277135 W.W. GRAINGER, INC., former EDGAR name until 2019-10-15 + ['GRAY TELEVISION', 'GTN', '389375'], // CIK 43196 GRAY MEDIA, INC, former EDGAR name until 2025-02-24 + ['GREAT AJAX', 'RPT', '38983D'], // CIK 1614806 Rithm Property Trust Inc., former EDGAR name until 2024-11-13 + ['GREENIDGE GENERATION HOLDINGS', 'VIP', '39531G'], // CIK 1844971 Vulcan Infrastructure & Power Inc., former EDGAR name until 2026-07-09 + ['HANNON ARMSTRONG SUSTAINABLE INFRASTRUCTURE CAPITAL', 'HASI', '41068X'], // CIK 1561894 HA Sustainable Infrastructure Capital, Inc., former EDGAR name until 2024-07-02 + ['HARTFORD FINANCIAL SERVICES GROUP', 'HIG', '416515'], // CIK 874766 HARTFORD INSURANCE GROUP, INC., former EDGAR name until 2025-01-30 + ['HELIX ENERGY SOLUTIONS GROUP', 'HOS', '42330P'], // CIK 866829 HORNBECK OFFSHORE SERVICES, INC., former EDGAR name until 2026-08-31 + ['HYSTER YALE MATERIALS HANDLING', 'HY', '449172'], // CIK 1173514 HYSTER-YALE, INC., former EDGAR name until 2024-05-30 + ['IAC', 'PPLI', '44891N'], // CIK 1800227 People Inc, former EDGAR name until 2026-06-02 + ['IDENTIV', 'INVE', '45170X'], // CIK 1036044 INVE Technologies, Inc., former EDGAR name until 2026-08-25 + ['IRHYTHM TECHNOLOGIES', 'IRTC', '450056'], // CIK 1388658 iRhythm Holdings, Inc., former EDGAR name until 2026-01-12 + ['ISSUER DIRECT', 'ACCS', '46520M'], // CIK 843006 ACCESS Newswire Inc., former EDGAR name until 2025-01-16 + ['JANOVER', 'DFDV', '47100L'], // CIK 1805526 DeFi Development Corp., former EDGAR name until 2025-04-15 + ['JOHN BEAN TECHNOLOGIES', 'JBTM', '477839'], // CIK 1433660 JBT MAREL Corp, former EDGAR name until 2024-12-20 + ['LENDINGCLUB', 'HAPN', '52603A'], // CIK 1409970 Happen, Inc., former EDGAR name until 2026-06-18 + ['LIXTE BIOTECHNOLOGY HOLDINGS', 'NMAD', '539319'], // CIK 1335105 NOMAD POWER SOLUTIONS, INC., former EDGAR name until 2026-07-02 + ['MKS INSTRUMENTS', 'MKSI', '55306N'], // CIK 1049502 MKS INC, former EDGAR name until 2025-05-15 + ['MONTANA TECHNOLOGIES', 'AIRJ', '612160'], // CIK 1855474 AirJoule Technologies Corp., former EDGAR name until 2024-10-21 + ['MULTIPLAN', 'CTEV', '62548M'], // CIK 1793229 Claritev Corp, former EDGAR name until 2025-02-07 + ['NEWPARK RESOURCES', 'NPKI', '651718'], // CIK 71829 NPK International Inc., former EDGAR name until 2024-12-03 + ['ONCOLOGY INSTITUTE', 'STLN', '68236X'], // CIK 1799191 Starling Oncology, Inc., former EDGAR name until 2026-08-03 + ['OREILLY AUTOMOTIVE', 'ORLY', '67103H'], // CIK 898173 O REILLY AUTOMOTIVE INC, former EDGAR name until 1998-04-10 + ['ORION OFFICE REIT', 'ONL', '68629Y'], // CIK 1873923 Orion Properties Inc., former EDGAR name until 2025-03-07 + ['PALTALK', 'IPM', '69764K'], // CIK 1355839 INTELLIGENT PROTECTION MANAGEMENT CORP., former EDGAR name until 2025-01-02 + ['PARAMOUNT SKYDANCE', 'SKYD', '69932A'], // CIK 2041610 Skydance Corp, former EDGAR name until 2026-10-06 + ['PIEDMONT OFFICE REALTY TRUST', 'PDM', '720190'], // CIK 1042776 Piedmont Realty Trust, Inc., former EDGAR name until 2025-06-02 + ['PIERIS PHARMACEUTICALS', 'PVLA', '697947'], // CIK 1583648 PALVELLA THERAPEUTICS, INC., former EDGAR name until 2024-12-12 + ['PLUS THERAPEUTICS', 'CNSY', '72941H'], // CIK 1095981 CERENOME, INC., former EDGAR name until 2026-07-01 + ['RUMBLE', 'RUM', '78137L'], // CIK 1830081 RUM Group Inc., former EDGAR name until 2026-06-17 + ['SCHWEITZER MAUDUIT INTERNATIONAL', 'MATV', '808541'], // CIK 1000623 Mativ Holdings, Inc., former EDGAR name until 2022-07-01 + ['SEQLL', 'CIRC', '048592'], // CIK 1605888 CIRCLE8 GROUP INC, former EDGAR name until 2024-06-06 + ['SHOE CARNIVAL', 'SHOE', '824889'], // CIK 895447 SHOE STATION GROUP INC, former EDGAR name until 2026-06-11 + ['TANGER FACTORY OUTLET CENTERS', 'SKT', '875465'], // CIK 899715 TANGER INC., former EDGAR name until 2023-11-15 + ['TECNOGLASS', 'TGLS', '87877F'], // CIK 1534675 Tecnoglass Holdings Inc., former EDGAR name until 2026-07-07 + ['TEMPUR SEALY INTERNATIONAL', 'SGI', '88023U'], // CIK 1206264 SOMNIGROUP INTERNATIONAL INC., former EDGAR name until 2025-02-14 + ['TEVOGEN BIO HOLDINGS', 'TVGN', '88165K'], // CIK 1860871 Tevogen Inc., former EDGAR name until 2026-07-30 + ['TORO COMBINECO', 'TTGT', '87874R'], // CIK 2018064 TechTarget, Inc., former EDGAR name until 2024-11-20 + ['VIAD', 'PRSU', '92552R'], // CIK 884219 Pursuit Attractions & Hospitality, Inc., former EDGAR name until 2024-12-31 + ['WELLGISTICS HEALTH', 'MEDS', '949503'], // CIK 2030763 DataMeds AI, Inc., former EDGAR name until 2026-07-21 + ['ZOOM VIDEO COMMUNICATIONS', 'ZM', '98980L'], // CIK 1585521 Zoom Communications, Inc., former EDGAR name until 2024-11-12 + ['ZYMEWORKS DELAWARE', 'ZYME', '98985Y'], // CIK 1937653 Zymeworks Inc., former EDGAR name until 2022-10-13 + ]; +} + +/** Nasdaq Trader symbol directories: Nasdaq listings, and NYSE, NYSE American, NYSE Arca, Cboe and IEX listings. */ +export function nportUsListingsUrls(): string[] { + return ['https://www.nasdaqtrader.com/dynamic/SymDir/nasdaqlisted.txt', 'https://www.nasdaqtrader.com/dynamic/SymDir/otherlisted.txt']; +} + +/** + * The Nasdaq Trader symbol directories (the texts of `nportUsListingsUrls()`, in any order) as U.S.-listed symbol -> security + * name; test issues and ETFs left out, CQS class dots as SEC dashes (`BRK.B` -> `BRK-B`). An issuer listed under several + * symbols (Liberty Global LBTYA, LBTYB, LBTYK) also gets the entry `\u0000` -> its symbols, which `nportUsListed` reads + * to require the share class. null unless every text is a whole directory (a header with Security Name and the File Creation + * Time footer), so a failed or cut download changes nothing. + */ +export function parseUsListings(texts: ReadonlyArray): Map | null { + const listings = new Map(); + if (!texts.length) return null; + for (let t = 0; t < texts.length; t++) { + const lines = String(texts[t] === null || texts[t] === undefined ? '' : texts[t]).split(/\r?\n/).filter((line) => line.trim()); + const header = (lines[0] || '').split('|').map((cell) => cell.trim().toUpperCase()); + const nameAt = header.indexOf('SECURITY NAME'), testAt = header.indexOf('TEST ISSUE'), etfAt = header.indexOf('ETF'); + if (nameAt < 0 || !/^FILE CREATION TIME/i.test(lines[lines.length - 1] || '')) return null; + for (let i = 1; i < lines.length - 1; i++) { + const cells = lines[i].split('|'); + const symbol = String(cells[0] || '').trim().toUpperCase().replace(/[./ ]/g, '-'); + if (!symbol || String(cells[testAt] || '').trim() === 'Y' || String(cells[etfAt] || '').trim() === 'Y' || listings.has(symbol)) continue; + listings.set(symbol, String(cells[nameAt] || '').trim()); + } + } + const issuers = new Map(); + listings.forEach((name, symbol) => { + const key = nportNameKey(nportIssuerName(name)); + if (key) issuers.set(key, (issuers.get(key) || []).concat(symbol)); + }); + issuers.forEach((symbols, key) => { if (symbols.length > 1) listings.set(`\u0000${key}`, symbols.join(' ')); }); + return listings; +} + +/** The share class a name or title states (`Class C`, `CL C`, `Series A`, `LIBERTY GLOBAL-C`), '' for none. */ +function nportShareClass(...texts: unknown[]): string { + for (let i = 0; i < texts.length; i++) { + const text = String(texts[i] === null || texts[i] === undefined ? '' : texts[i]); + const match = /\b(?:CLASS|CL|SERIES|SER)\.?\s+([A-Z])\b/i.exec(text) || /[A-Z]-\s?([A-Z])(?:\s|$)/i.exec(text); + if (match) return match[1].toUpperCase(); + } + return ''; +} + +/** Both symbol directories through the brand's `getText`, null when one fails (`parseUsListings`); load once per run. */ +export async function readUsListings(getText: (url: string, label: string) => Promise): Promise | null> { + try { + return parseUsListings(await Promise.all(nportUsListingsUrls().map((url) => getText(url, `Nasdaq Trader ${url.replace(/^.*\//, '')}`)))); + } catch { + return null; + } +} + +/** A security or sheet name without its share description: `Accenture plc Class A Ordinary Shares (Ireland)` -> `Accenture plc`. */ +function nportIssuerName(value: unknown): string { + return String(value === null || value === undefined ? '' : value).split(' - ')[0] + .replace(/\s+(?:\(|(?:CLASS|SERIES)\s+[A-Z]\b|COMMON\b|ORDINARY\b|SUBORDINATE\b|SUB\.?\s+VOT|(?:LIMITED|RESTRICTED)\s+VOTING\b|VOTING\b|SHARES?\b|STOCK\b|AMERICAN\s+DEPOSITARY\b|TRADING\s+UNDER\b)[\s\S]*$/i, ''); +} + +/** + * Is `ticker` the company's own U.S. listing? The Nasdaq Trader directories (`parseUsListings`) list it under the same company + * (same name key, or one key the start of the other: `BUNGE GLOBAL` and the directory's `BUNGE`, a name cut short after two words + * or more: `CHECK POINT SOFTWARE TECH`), and not as depositary or New York registry shares, which are another security than + * the foreign line (Shell's SHEL ADR, ASML's New York shares). When the issuer has several listed classes, the name or ]2;]1; + * must state the listed class (Liberty Latin America Class C is LILAK, not LILA). False without listings. Callers check that + * the line trades in U.S. dollars: a Toronto or London line of the same company in its own currency never gets the U.S. ticker. + */ +export function nportUsListed(listings: Map | null, ticker: string, name: unknown, title: unknown = ''): boolean { + if (!listings) return false; + const listed = listings.get(String(ticker).trim().toUpperCase().replace(/[./ ]/g, '-')); + if (listed === undefined || /\bDEPOSITARY\b|\bADSS?\b|\bADRS?\b|\bREGISTRY\b/i.test(listed)) return false; + const a = nportNameKey(nportIssuerName(listed)), b = nportNameKey(nportIssuerName(name)); + if (!a || !b) return false; + const short = a.length < b.length ? a : b, long = a.length < b.length ? b : a; + if (a !== b && !(short.length >= 4 && long.indexOf(`${short} `) === 0) && !(short.split(' ').length >= 2 && long.indexOf(short) === 0)) return false; + const listedClass = nportShareClass(listed); + return !listedClass || !listings.has(`\u0000${a}`) || nportShareClass(name, title) === listedClass; +} + /** * Exchange tickers for the positions, in their order ('' for none). A ticker is given only to a common-stock row (assetCat - * EC; a preferred EP, a CVR, right, warrant or escrow line gets none, a name match would give it the issuer's common ticker) - * traded in USD (or with no currency) whose ISIN is U.S. or missing: first the filer's own ``, else the - * SEC issuer-name table (`names`) when the row has a CUSIP or a U.S. ISIN. A ticker that lands on two different securities - * (Alphabet A and C, an ordinary share and its ADR) is dropped from the name-matched rows; when the filer itself gave it - * to two securities it is dropped from all of them. Bonds, cash, funds and derivatives stay identifier-keyed. + * EC; a preferred EP, a CVR, right, warrant or escrow line gets none, a name match would give it the issuer's common ticker): + * - U.S. ISIN or none, traded in USD (or with no currency): the filer's own ``, else the SEC issuer-name table + * (`names`) when the row has a CUSIP or a U.S. ISIN, else a renamed issuer (`nportRenamedIssuers`) + * - foreign ISIN (Linde IE, NXP NL, IMAX CA): only traded in USD and only when the symbol directories (`listings`, + * `parseUsListings`) confirm the filer's or the name-matched ticker for the same company (`nportUsListed`); a local line in + * another currency, and every foreign row without listings, gets none + * A ticker that lands on two different securities (Alphabet A and C, an ordinary share and its ADR) is dropped from the + * name-matched rows; when the filer itself gave it to two securities it is dropped from all of them. Bonds, cash, funds and + * derivatives stay identifier-keyed. */ -export function nportTickers(positions: ReadonlyArray, names: Map | null = null): string[] { +export function nportTickers(positions: ReadonlyArray, names: Map | null = null, listings: Map | null = null): string[] { const fromFiler: boolean[] = []; + const secTickers = new Set(); + if (names) names.forEach((ticker) => secTickers.add(ticker)); + const renames = nportRenamedIssuers(); + const renamed = (position: NportPosition, key: string): string => { + const issuer = (position.cusip || (/^US[0-9A-Z]{10}$/.test(position.isin) ? position.isin.slice(2, 11) : '')).slice(0, 6); + const hit = renames.filter((entry) => entry[0] === key && entry[2] === issuer && secTickers.has(entry[1]))[0]; + return hit ? hit[1] : ''; + }; const tickers = positions.map((position, index) => { fromFiler[index] = false; const equity = position.assetCat === 'EC' && !position.derivative && !nportIsRight(position); - const usd = !position.currency || position.currency === 'USD'; - if (!equity || !usd || (position.isin && position.isin.slice(0, 2) !== 'US')) return ''; - if (/^[A-Z][A-Z0-9.-]{0,9}$/.test(position.ticker)) { + const foreign = Boolean(position.isin) && position.isin.slice(0, 2) !== 'US'; + const usd = foreign ? position.currency === 'USD' && Boolean(listings) : !position.currency || position.currency === 'USD'; + if (!equity || !usd) return ''; + const filer = /^[A-Z][A-Z0-9.-]{0,9}$/.test(position.ticker) ? position.ticker : ''; + const key = nportNameKey(position.name); + const named = names && (/^[0-9A-Z]{9}$/.test(position.cusip) || /^[A-Z]{2}[0-9A-Z]{10}$/.test(position.isin)) ? names.get(key) || names.get(key.replace(/ CL [A-Z]$/, '')) || '' : ''; + if (!foreign) { + fromFiler[index] = Boolean(filer); + return filer || named || (names ? renamed(position, key) : ''); + } + if (filer && nportUsListed(listings, filer, position.name, position.title)) { fromFiler[index] = true; - return position.ticker; + return filer; } - if (!names || !(/^[0-9A-Z]{9}$/.test(position.cusip) || /^US[0-9A-Z]{10}$/.test(position.isin))) return ''; - const key = nportNameKey(position.name); - return names.get(key) || names.get(key.replace(/ CL [A-Z]$/, '')) || ''; + return named && nportUsListed(listings, named, position.name, position.title) ? named : ''; }); const securities = new Map(); tickers.forEach((ticker, index) => {