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

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 streamlitYou'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.pyOpens 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:
- Go to share.streamlit.io
- Connect your repo
- Add
AXIORA_API_KEYto Secrets (Settings → Secrets) - Deploy
Your screener is now a shareable URL. Send it to your team, your PM, your portfolio manager.
Create requirements.txt:
axiora
streamlitThat'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_buttonto 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
- Japanese Financial Data with Python — the SDK tutorial if you haven't done it yet
- Chat with Japan's Financial Data: MCP + Claude — skip the UI entirely, ask Claude in natural language
Free tier. No credit card. Get your API key →