Japanese stock screening

How to screen Japanese net-net stocks with SQL

A reproducible JPX screen for below-NCAV, Graham-style, double-net, and triple-net candidates, with date-aware statements, current market capitalization, and liquidity checks.

A net-net screen asks whether the stock market values an entire company below the current assets left after every liability is deducted. The arithmetic is short. Building one comparable, current row per company is the real work.

This guide runs a current screen across StockQL’s active JPX common-share universe. It keeps the financial-statement date, price date, market-cap source, yen units, and trading liquidity visible. The output is a review queue, not a buy list.

1. Define the valuation tiers

This screen uses a transparent NCAV proxy:

NCAV proxy    = current assets - total liabilities
NCAV multiple = NCAV proxy / current market capitalization

It is a proxy because a strict claim available to common shareholders should also account for preferred equity and noncontrolling interests where they exist. Those fields are not consistently available across the provider’s JPX statements. Missing claims remain unknown; the query never turns them into zero.

Label in this guideMinimum NCAV multipleMaximum market cap as a share of NCAV
Below NCAV1.00×100%
Graham-style1.50×66.7%
Double-net2.00×50%
Triple-net3.00×33.3%

The 1.50× tier is equivalent to paying no more than two-thirds of NCAV, the familiar Graham valuation threshold. “Double-net” and “triple-net” are descriptive extensions used here, not standardized accounting terms. Triple-net in this article has nothing to do with a triple-net real-estate lease.

2. Set the data contract before ranking anything

The screen admits a company only when all of these conditions are true:

  • The security is an active, unquarantined common share on JPX and is not marked delisted.
  • A positive closing price is available.
  • The latest eligible annual balance sheet is denominated in JPY, was effective by the price date, and ended no more than 18 months earlier.
  • current_assets, total_liabilities, and a positive current market_cap are present.
  • Twenty-session average traded value is calculated from observed closes and volume; it is reported for review, not used to hide illiquid results.

The date test matters. A fiscal period ending in March was not public in March. StockQL uses the provider’s filing date when it is credible and a conservative lag when the provider merely repeats the period end as the filing date. The query then requires effective_date <= price_date.

That failure is useful evidence. Price coverage, company profiles, and statements can arrive from separate provider endpoints. A polished symbol list does not prove that the accounting facts needed by a screen exist underneath it.

3. Run the screen

The statement below ran unchanged against the live StockQL database. It creates a session-local view, so the filtered rows remain available for inspection and export without adding permanent database objects.

CREATE OR REPLACE TEMP VIEW jpx_ncav_screen AS
WITH universe AS (
  SELECT symbol, exchange_code, name
  FROM stocks
  WHERE exchange_code = 'JPX'
    AND security_class = 'common'
    AND is_active
    AND NOT is_delisted
    AND quarantined_since IS NULL
)
SELECT
  u.symbol,
  u.name,
  px.price_date,
  px.close,
  bal.period_end,
  bal.effective_date,
  bal.currency,
  bal.current_assets,
  bal.total_liabilities,
  metrics.fetched_at::date AS metrics_fetched,
  metrics.market_cap,
  bal.current_assets - bal.total_liabilities AS ncav,
  (bal.current_assets - bal.total_liabilities)::numeric
    / metrics.market_cap::numeric AS ncav_multiple,
  liquidity.avg_traded_value_20d
FROM universe u
JOIN LATERAL (
  SELECT p.date AS price_date, p.close
  FROM stock_prices p
  WHERE p.exchange_code = u.exchange_code
    AND p.symbol = u.symbol
  ORDER BY p.date DESC
  LIMIT 1
) px ON px.close > 0
JOIN LATERAL (
  SELECT
    f.period_end,
    f.effective_date,
    f.currency,
    f.current_assets,
    f.total_liabilities
  FROM stock_financial_periods f
  WHERE f.exchange_code = u.exchange_code
    AND f.symbol = u.symbol
    AND f.statement = 'balance'
    AND f.frequency = 'annual'
    AND f.effective_date <= px.price_date
    AND f.period_end >= px.price_date - INTERVAL '18 months'
    AND f.currency = 'JPY'
    AND f.current_assets IS NOT NULL
    AND f.total_liabilities IS NOT NULL
  ORDER BY f.period_end DESC, f.effective_date DESC
  LIMIT 1
) bal ON true
JOIN stock_fundamental_metrics metrics
  ON metrics.exchange_code = u.exchange_code
 AND metrics.symbol = u.symbol
 AND metrics.market_cap > 0
LEFT JOIN LATERAL (
  SELECT AVG(recent.close * recent.volume) AS avg_traded_value_20d
  FROM (
    SELECT p.close, p.volume
    FROM stock_prices p
    WHERE p.exchange_code = u.exchange_code
      AND p.symbol = u.symbol
      AND p.date <= px.price_date
    ORDER BY p.date DESC
    LIMIT 20
  ) recent
) liquidity ON true;

The two lateral joins select one price and one eligible balance per company through their indexed symbol and exchange keys. The balance must have been effective by that company’s price date; changing the final threshold cannot bypass that rule.

Count each tier from the unrounded ratio:

SELECT
  (
    SELECT COUNT(*)
    FROM stocks
    WHERE exchange_code = 'JPX'
      AND security_class = 'common'
      AND is_active
      AND NOT is_delisted
      AND quarantined_since IS NULL
  ) AS universe,
  COUNT(*) AS complete_rows,
  COUNT(*) FILTER (WHERE ncav_multiple >= 1.0) AS below_ncav,
  COUNT(*) FILTER (WHERE ncav_multiple >= 1.5) AS graham_style,
  COUNT(*) FILTER (WHERE ncav_multiple >= 2.0) AS double_net,
  COUNT(*) FILTER (WHERE ncav_multiple >= 3.0) AS triple_net
FROM jpx_ncav_screen;

Then rank the review set while retaining its dates and liquidity:

SELECT
  symbol,
  name,
  ROUND((market_cap / 1e6)::numeric) AS market_cap_jpym,
  ROUND((ncav / 1e6)::numeric) AS ncav_jpym,
  ROUND(ncav_multiple, 2) AS multiple,
  CASE
    WHEN ncav_multiple >= 3.0 THEN '3.0+'
    WHEN ncav_multiple >= 2.0 THEN '2.0+'
    WHEN ncav_multiple >= 1.5 THEN '1.5+'
    ELSE '1.0+'
  END AS tier,
  period_end,
  effective_date,
  price_date,
  metrics_fetched,
  ROUND((avg_traded_value_20d / 1e6)::numeric, 1) AS adv20_jpym
FROM jpx_ncav_screen
WHERE ncav_multiple >= 1.0
ORDER BY ncav_multiple DESC, symbol
LIMIT 20;

On the final verification installation, creating the view took 11 ms, the count query 214 ms, and the ranked query 175 ms. Those timings describe one database snapshot, not a general benchmark.

The query uses the stored market capitalization produced from the latest positive close and a current outstanding-share count that passed the fail-closed comparison. It trusts neither an older weighted-average diluted count nor the provider’s market-cap field on its own. Both shortcuts failed on real rows during this screen.

4. Read the live result

The snapshot used prices available through September 11, 2026. Of 3,776 active JPX common shares, StockQL assembled 3,749 current market capitalizations through its share-count trust ladder; EDINET issuer filings overrode 26 conflicting provider counts. Applying the price, eligible annual-balance, currency, and accounting-field rules left 3,639 complete rows.

JPX common-share universe3,776
Complete screen rows3,639
Below NCAV · ≥1.0×138
Graham-style · ≥1.5×26
Double-net · ≥2.0×4
Triple-net · ≥3.0×0

The honest triple-net result was zero. Four companies cleared 2× and 26 cleared the Graham-style 1.5× threshold. Every current share count below is anchored to an issuer-filed EDINET count or a provider count that passed the trust ladder:

SymbolCompanyMarket capNCAV proxyMultipleBalance / effectivePrice dateADV20
7034Prored Partners¥3.56bn¥10.25bn2.88×2025-10-31 / 2026-01-292026-09-11¥10.6m
3189ANAP Holdings¥4.68bn¥12.28bn2.62×2025-08-31 / 2025-11-292026-09-11¥183.8m
2961Nitcho¥1.73bn¥4.25bn2.45×2025-09-30 / 2025-12-292026-09-11¥4.0m
3600FUJIX¥2.37bn¥4.76bn2.01×2026-03-31 / 2026-06-292026-09-07¥0.5m

Market capitalization and NCAV are rounded JPY values; the tier uses the unrounded ratio. ADV20 is the mean of close × volume over the latest 20 observed sessions. “Effective” is the first date StockQL allows the annual balance in an as-of query.

The final backfill normalized 3,687 issuer filings from EDINET, Japan’s official disclosure system. When a provider count differs by more than 2× from EDINET, StockQL uses issued shares minus treasury shares from the filing; 26 market caps were arbitrated this way. Only names without an EDINET row reach the older statement-comparison fallback, and unresolved values still fail closed.

ANAP also demonstrates why NCAV is not a synonym for cash. Its fiscal 2025 securities report showed about ¥17.95 billion of current assets and ¥5.68 billion of liabilities, but digital assets accounted for roughly ¥16.25 billion of current assets. A standardized balance-sheet field makes that row comparable enough to find; only the filing explains what the field contains.

5. Handle the Japan-specific and provider-specific traps

Symbol mapping
FMP identifies Tokyo listings with a provider suffix such as 3189.T. StockQL stores the exchange separately, as symbol = '3189' and exchange_code = 'JPX'. Map at the provider boundary rather than leaking vendor notation into every query.
Currency
The JPX security rows in this snapshot have a blank per-stock currency, while the exchange metadata says JPY. Normalized financial periods preserve their reported currency, so the query explicitly requires f.currency = 'JPY' instead of treating a blank as yen.
Statement availability
An annual period is not usable merely because its period end exists. Keep the filing or conservative effective date and reject facts that were not yet knowable.
Current equity value
Weighted-average shares are built for earnings-per-share calculations over a period, while a provider’s “current” market cap can still miss a split. Derive market cap from the latest close and current outstanding shares, compare the count with a dated statement or issuer source, and fail closed on unresolved disagreement.
Trading units and liquidity
JPX standardized domestic common-stock trading units at 100 shares. A low quoted price can still require a meaningful lot, and a wide NCAV discount can coexist with very little daily traded value. The screen reports 20-session average traded value in yen for that reason.

For primary-source review, use the JPX listed-company search, the exchange’s timely-disclosure resources, and the issuer’s latest securities report. The JPX trading-unit guide documents the 100-share convention.

6. Treat every result as the start of diligence

For each high-ranked row, reconcile the standardized numbers to the latest filing and ask:

  • How much of current assets is unrestricted cash rather than receivables, inventory, securities, or digital assets?
  • Were there share issues, options, conversions, buybacks, or treasury-stock changes after the balance date?
  • Are there lease obligations, guarantees, preferred claims, noncontrolling interests, or litigation outside the proxy?
  • Is the company profitable, or how quickly is it consuming the reported surplus?
  • Could a realistic position be entered and exited at the observed traded value?

Change the threshold without changing the definition:

SELECT *
FROM jpx_ncav_screen
WHERE ncav_multiple >= 2.0
ORDER BY ncav_multiple DESC, symbol;

Or export the complete review set from the same PostgreSQL session:

\copy (SELECT * FROM jpx_ncav_screen ORDER BY ncav_multiple DESC, symbol) TO 'jpx-ncav-screen.csv' CSV HEADER

What StockQL contributes

The formula in this guide is ordinary SQL. The durable work sits beneath it: provider-symbol mapping, normalized financial periods, explicit currencies, conservative effective dates, retained source facts, current valuation fields, and enough price history to expose liquidity.

That separation keeps the screen auditable. StockQL maintains the research database; PostgreSQL, a notebook, or your own agent can decide what to test next.

See how StockQL handles the data →