Skip to content

fix(prices): stop rescaling an ADS market cap onto the issuer's ordinary share count - #4417

Merged
daniel3303 merged 1 commit into
mainfrom
fix/ads-listing-market-cap
Aug 20, 2026
Merged

fix(prices): stop rescaling an ADS market cap onto the issuer's ordinary share count#4417
daniel3303 merged 1 commit into
mainfrom
fix/ads-listing-market-cap

Conversation

@daniel3303

Copy link
Copy Markdown
Owner

Problem

A company whose US listing is an American Depositary Share has an SEC cover page counting ordinary shares, while its price is quoted per receipt. YahooPriceImportService rescales the feed's market cap onto the EDGAR share base, so for these issuers it multiplied a correct figure by the deposit ratio.

BeOne Medicines (ONC, 13 ordinary per ADS) in production:

stored true
MarketCapitalization 557,045,957,486.84 ~42.85e9
SharesOutStanding 1,478,124,405 (ordinary) 113,701,877 (ADS)

557,045,957,486.84 / 1,478,124,405 = 376.86, which is the real ADS close that session. That is what makes it dangerous: the stored pair is internally consistent, so nothing that inspects the pair can tell it apart from a correctly priced company, and the inflated cap flows into every surface that ranks on market capitalization.

Neither existing guard sees it:

  • IsForeignPrivateIssuer is form-based, and ONC files 10-K/10-Q.
  • ShareBasisPlausibility.MaxPlausibleSameUnitRatio is 300x, far above any real deposit ratio (ONC 13x, SNY and AZN 2x), so the figures alone can never expose it.

Fix

Ask the issuer's own registered 12(b) title, which states the listed unit outright and is already materialized on CommonStock.ListedSecurityTitle by XbrlFactExtractionService for the stock's own ticker.

  • New ListedSecurityClassifier.IsAmericanDepositary(title), a separate question from Classify (an ADS over ordinary shares is still common equity and still classifies as such). Matched against the raw title, because a filer can bury the phrase in a parenthetical that Classify strips.
  • Consulted by both writers of CommonStock.SharesOutStanding. Only guarding the price lane would leave the facts importer putting the count back on the ordinary base every cycle while the price lane kept the cap on the ADS base, and the two would undo each other forever.
  • It runs before the ratio guard, not instead of it: the ratio guard still catches an ADS issuer with no title on record (AKTX, 80,000 ordinary per ADS) and a garbage cover-page count (ABTC 458x, CNDA 768x).
  • The ratio is never read out of the title, only the fact that the listing is a receipt. So the repair is to stop rescaling rather than to divide: the feed's figure is already on the listed unit and now stands verbatim, exactly as it does for a 20-F filer.

The pattern accepts the Depository misspelling four filers actually registered (ARBK, IX, ALAR, NICM), and requires American so the far commoner preferred form (Depositary Shares, each representing a 1/1,000th interest in ... Preferred Stock) cannot match. Global Depositary Shares are deliberately out of scope and documented as such: the one GDS issuer on record files as an FPI, so the form-based guard answers for it.

Tests

+27 tests, all values read from the production store rather than invented:

  • ListedSecurityClassifierDepositaryTests (26) works real dei:Security12bTitle strings, both spellings, singular/plural, receipts, parenthetical-embedded, and the negatives that must not fire.
  • YahooPriceImportServiceAdsMarketCapTests (3) pins the defect in ONC's own numbers (reproducing the $557.0B to within a rounding error of the close), pins that withholding the count leaves the feed's cap verbatim, and pins that 13x sits far inside the same-unit tolerance, which is why the title has to be asked first.

dotnet csharpier check . clean, dotnet build Equibles.sln -c Release 0 errors, full unit suite 4,466 passed / 0 failed.

Rollout

Additive, no migration. Self-heals on the next enrichment rotation for each affected stock, so no manual data fix.

Blast radius in production: 332 stocks carry an ADS title and a non-zero market cap.

A.L.V.I.S.

…ary share count

A company whose US listing is an American Depositary Share has an SEC cover
page counting ORDINARY shares while its price is quoted per receipt. The Yahoo
key-stats sync rescales the feed's market cap onto the EDGAR share base, so for
these issuers it multiplied a correct figure by the deposit ratio: BeOne
Medicines (ONC, 13 ordinary per ADS) stored $557.0B against a true ~$42.9B.

Neither existing guard sees it. The foreign-private-issuer check is form-based
and ONC files 10-K, and the same-unit ratio check draws its line at 300x, far
above any real deposit ratio (ONC 13x, SNY and AZN 2x). The damage is also
invisible downstream, because the stored pair stays self-consistent: cap over
shares still equals the real ADS close, so nothing that inspects the pair can
tell it apart from a correctly priced company.

Ask the issuer's own registered 12(b) title instead. It is already materialized
on the stock for its own ticker, and it states the listed unit outright. Both
writers of CommonStock.SharesOutStanding consult it, or the facts importer
would put the count back on the ordinary base every cycle while the price lane
kept the cap on the ADS base. The ratio is never read out of the title, only
the fact that the listing is a receipt, so the repair is to stop rescaling
rather than to divide: the feed's figure is already on the listed unit and now
stands verbatim, exactly as it does for a 20-F filer.

The pattern accepts the "Depository" misspelling that four filers registered,
and requires "American" so the far commoner preferred form ("Depositary Shares,
each representing a 1/1,000th interest in ... Preferred Stock") cannot match.

Self-heals on the next enrichment rotation; no data fix needed.
@daniel3303
daniel3303 merged commit 4f5321e into main Aug 20, 2026
7 checks passed
@daniel3303
daniel3303 deleted the fix/ads-listing-market-cap branch August 20, 2026 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant