Back to blog
tutorial

Build a Japan Stock Screener with Python and Streamlit

Onkar Dahale

We're going to build a stock screener for Japanese companies in 90 lines of Python, deploy it to the internet for free, and share the URL with anyone.

The data comes from the same place as Bloomberg's fundamentals — audited EDINET filings, XBRL-parsed. No real-time prices or market data, but for screening by ROE, revenue, and margins from annual reports, this is all you need.

What You'll Build

Japan Stock Screener

A web app that lets you:

  • Filter 4,125+ Japanese companies by ROE, revenue, and sector
  • Sort by any financial metric
  • Click through to individual company details
  • Deploy to Streamlit Cloud with a shareable URL

Setup

pip install axiora streamlit

You'll need an Axiora API key — free, no credit card.

The Screener: 90 Lines

Create screener.py:

import os
import streamlit as st
from axiora import Axiora

st.set_page_config(page_title="Japan Stock Screener", layout="wide")
st.title("Japan Stock Screener")
st.caption("Data: audited EDINET filings, XBRL-parsed · Powered by Axiora")

# Init client — reads AXIORA_API_KEY from env or Streamlit secrets
api_key = os.environ.get("AXIORA_API_KEY") or st.secrets.get("AXIORA_API_KEY", "")
if not api_key:
    st.error("Set AXIORA_API_KEY in environment or .streamlit/secrets.toml")
    st.stop()

client = Axiora(api_key=api_key)

# --- Sidebar filters ---
st.sidebar.header("Filters")

sectors = client.sectors.list()
sector_names = ["All"] + [s.sector for s in sectors.data]
sector = st.sidebar.selectbox("Sector", sector_names)

col1, col2 = st.sidebar.columns(2)
roe_min = col1.number_input("Min ROE (%)", value=0.0, step=1.0)
roe_max = col2.number_input("Max ROE (%)", value=100.0, step=1.0)

revenue_min_b = st.sidebar.number_input(
    "Min Revenue (¥ Billion)", value=0, step=10
)

sort_options = {
    "ROE (high → low)": "roe_desc",
    "ROE (low → high)": "roe_asc",
    "Revenue (high → low)": "revenue_desc",
    "Revenue (low → high)": "revenue_asc",
    "Net Margin (high → low)": "net_margin_desc",
    "Operating Margin (high → low)": "operating_margin_desc",
}
sort_label = st.sidebar.selectbox("Sort by", list(sort_options.keys()))
sort_value = sort_options[sort_label]

limit = st.sidebar.slider("Results", 10, 100, 50)

# --- Fetch data ---
params = {
    "sort": sort_value,
    "limit": limit,
}
if sector != "All":
    params["sector"] = sector
if roe_min > 0:
    params["roe_min"] = roe_min
if roe_max < 100:
    params["roe_max"] = roe_max
if revenue_min_b > 0:
    params["revenue_min"] = revenue_min_b * 1_000_000_000

with st.spinner("Screening..."):
    results = client.screen.retrieve(**params)

# --- Display results ---
st.subheader(f"{len(results.data)} companies found")

if not results.data:
    st.info("No companies match your filters. Try adjusting the criteria.")
    st.stop()

# Build table
rows = []
for r in results.data:
    name = r.company.name_en or r.company.name_jp
    rows.append({
        "Ticker": r.company.securities_code or "",
        "Company": name,
        "Sector": r.company.sector or "",
        "Revenue (¥B)": f"¥{r.financials.revenue / 1e9:,.0f}" if r.financials.revenue else "—",
        "Net Income (¥B)": f"¥{r.financials.net_income / 1e9:,.0f}" if r.financials.net_income else "—",
        "ROE": f"{r.metrics.roe:.1f}%" if r.metrics.roe else "—",
        "ROA": f"{r.metrics.roa:.1f}%" if r.metrics.roa else "—",
        "Op. Margin": f"{r.metrics.operating_margin:.1f}%" if r.metrics.operating_margin else "—",
        "Net Margin": f"{r.metrics.net_margin:.1f}%" if r.metrics.net_margin else "—",
    })

st.dataframe(rows, use_container_width=True, hide_index=True)

# --- Company detail expander ---
st.subheader("Company Detail")
codes = [r.company.securities_code for r in results.data if r.company.securities_code]
selected = st.selectbox(
    "Select a company",
    codes,
    format_func=lambda c: next(
        (f"{c} — {r.company.name_en or r.company.name_jp}"
         for r in results.data if r.company.securities_code == c),
        c
    ),
)

if selected:
    with st.spinner("Loading financials..."):
        financials = client.companies.retrieve_financials(selected, years=5)

    if financials.data:
        years = [f.fiscal_year for f in reversed(financials.data)]
        rev = [f.revenue / 1e9 if f.revenue else 0 for f in reversed(financials.data)]
        ni = [f.net_income / 1e9 if f.net_income else 0 for f in reversed(financials.data)]

        col1, col2 = st.columns(2)
        col1.line_chart({"Revenue (¥B)": rev}, x_label="Year", y_label="¥ Billion")
        col2.line_chart({"Net Income (¥B)": ni}, x_label="Year", y_label="¥ Billion")

Run It

export AXIORA_API_KEY="your-key"
streamlit run screener.py

Opens at localhost:8501. Filter by sector, set ROE thresholds, sort by margin — a working fundamentals screener for 4,125+ Japanese companies, running on your laptop.

Deploy to Streamlit Cloud

Push to GitHub, then:

  1. Go to share.streamlit.io
  2. Connect your repo
  3. Add AXIORA_API_KEY to Secrets (Settings → Secrets)
  4. Deploy

Your screener is now a shareable URL. Send it to your team, your PM, your portfolio manager.

Create requirements.txt:

axiora
streamlit

That's the entire dependency list.

Extend It

Some ideas, all achievable with the existing API:

  • Growth overlay: Add client.companies.retrieve_growth() to show CAGR alongside screening results
  • Peer comparison: When a user selects a company, use client.companies.retrieve_peers() to show similar companies
  • Health score: Add client.companies.retrieve_health() for the financial health indicator
  • Export: Streamlit's built-in st.download_button to export results as CSV

The API has 20+ endpoints. The screener above uses two (screen and retrieve_financials). There's a lot of room to build.

Keep Going

Free tier. No credit card. Get your API key →