wiis/wiːs/Write It, I'll Share

How an LLM Discovers Stocks: The Full Pipeline Under the Hood

by psind707

A technical walkthrough of how a large language model screens for high-conviction equity candidates across global exchanges — documented as pseudocode


Why This Matters

Most equity screening is quantitative: filter a database by P/E, revenue growth, market cap, and sort. An LLM-based screen works fundamentally differently — it reasons from supply chain structure to specific companies, using domain knowledge to construct precise search queries that a traditional screener cannot express.

This document reverse-engineers the full pipeline used to screen for stocks with structural characteristics similar to AXT Inc (AXTI), which returned 10x+ in under 12 months. The methodology is exchange-agnostic and was applied across Frankfurt, LSE AIM, HKSE, SGX, and Bursa Malaysia.


Stage 0: Reference Case Decomposition

Before any search, the LLM decomposes the reference stock into testable attributes. This happens entirely in-context — no tools, no search. The goal is to separate the generalizable pattern from the ticker-specific details.

INPUT: "Find stocks like $AXTI on [Exchange]"

# Decompose the reference case into atomic attributes
reference_case = {
    ticker: "AXTI",
    what_it_does: "InP compound semiconductor substrates",
    supply_chain_position: "Layer 3 - critical input material",
    market_structure: "oligopoly, 3 players = 90% share",
    market_cap_at_start: "$200-400M",
    revenue_at_inflection: "$88M, real customers",
    margin_trajectory: "8% GM -> 30% -> 40% in 3 quarters",
    narrative: "only US-listed InP pure-play for AI optics",
    catalyst_type: "demand inflection + capacity constraint",
    move_magnitude: "10-15x in 12 months"
}

# Strip ticker-specific details -> extract generalizable pattern
pattern = {
    "supply_demand_mismatch": True,
    "mission_critical_input": True,
    "oligopoly_or_monopoly": True,
    "micro_cap": True,
    "revenue_real_but_small": True,
    "margin_inflecting": True,
    "narrative_scarcity": True,  # only listed pure-play
    "under_followed": True,     # <5 analysts
    "capacity_constrained": True
}

# Build scoring checklist from pattern
checklist = [f for f in pattern.keys()]  # 9 factors
threshold_for_candidate = 6  # minimum score to qualify

Key insight: The checklist is not arbitrary. Each factor maps to a specific driver of AXTI's 10x move. "Micro-cap" = small denominator for large percentage moves. "Narrative scarcity" = no substitute for the trade, forcing all capital into one name. "Capacity-constrained" = pricing power that drives the margin inflection. Remove any three factors and the 10x doesn't happen.


Stage 1: AI Supply Chain Mapping

This is pure reasoning — the LLM constructs a mental model of the full AI hardware stack before touching any search tool. The quality of everything downstream depends on how well this map is built.

supply_chain_layers = {
    1: {
        "name": "Compute Silicon",
        "components": ["GPUs", "ASICs", "HBM", "DRAM"],
        "bottleneck_status": "discovered",
        "micro_cap_opportunity": False
        # NVDA, AMD, AVGO already mega-cap. No 10x here.
    },
    2: {
        "name": "Advanced Packaging",
        "components": ["CoWoS interposers", "organic substrates",
                       "glass substrates", "hybrid bonding",
                       "underfill materials", "redistribution layers"],
        "bottleneck_status": "active",  # TSMC CoWoS sold out
        "micro_cap_opportunity": True,
        "key_question": "Who makes the TOOLS and MATERIALS?"
    },
    3: {
        "name": "Optical Interconnect",
        "components": ["InP substrates", "epi wafers", "lasers",
                       "modulators", "transceivers", "fiber",
                       "CPO integration", "optical subcomponents"],
        "bottleneck_status": "active",  # AXTI sits here
        "micro_cap_opportunity": True,
        "key_question": "Who else in this chain is small + critical?"
    },
    4: {
        "name": "Raw Materials & Equipment",
        "components": ["crystal growth furnaces", "MOCVD reactors",
                       "pBN crucibles", "specialty gases",
                       "CMP slurries", "high-purity metals"],
        "bottleneck_status": "undiscovered",  # deepest layer
        "micro_cap_opportunity": True,
        "key_question": "Who builds the machines that make the materials?"
    },
    5: {
        "name": "Assembly & Test (OSAT)",
        "components": ["die attach", "wire bonding", "test handlers",
                       "consumable tools", "inspection"],
        "bottleneck_status": "competitive",  # many players
        "micro_cap_opportunity": False,
        "key_question": "Best-of-breed only, no monopoly here"
    },
    6: {
        "name": "Data Center Infrastructure",
        "components": ["power delivery", "cooling", "electrification",
                       "connectors", "cable assemblies"],
        "bottleneck_status": "emerging",
        "micro_cap_opportunity": "maybe",
        "key_question": "Power/cooling could be next bottleneck"
    }
}

# DECISION: Only Layers 2-4 produce AXTI-type setups.
# Layer 5-6 = competitive services (2-3x trades, not 10x).
# Layer 1 = already mega-cap (no small denominator).
target_layers = [2, 3, 4]

Why this matters: Without this map, you'd search "best AI stocks Frankfurt" and get NVIDIA ADRs and SAP. The map tells the LLM to search for "MOCVD equipment maker" and "glass substrate laser tool" — queries that a human analyst might construct but that no traditional screener can express.


Stage 2: Exchange-Specific Industry Mapping

Before searching, the LLM uses prior knowledge (training data) to map which exchanges have companies in which supply chain layers. This pre-filters the search space and prevents wasted queries.

exchange_industry_map = {
    "Frankfurt/Xetra": {
        "known_clusters": [
            "Jena/Thuringia photonics corridor",
            "Dresden semiconductor equipment",
            "Munich precision optics"
        ],
        "expected_layers": [2, 3, 4],  # equipment + materials
        "known_names": ["Aixtron", "SUSS MicroTec", "Jenoptik",
                        "PVA TePla", "LPKF"],
        "language": "English coverage adequate",
        "investor_base": "under-followed by US investors"
    },
    "LSE/AIM": {
        "known_clusters": ["Cardiff compound semi (IQE)"],
        "expected_layers": [3],  # epi wafers
        "known_names": ["IQE"],
        "language": "English native",
        "investor_base": "UK small-cap specialists"
    },
    "HKSE": {
        "known_clusters": [
            "Chinese optical component makers",
            "AI chip IPO pipeline"
        ],
        "expected_layers": [1, 3],  # chips + optics
        "known_names": ["O-Net", "Zhongji Innolight (pending)"],
        "language": "Mandarin primary — English coverage ~60-70%",
        "investor_base": "HK/China retail + sovereign wealth"
    },
    "SGX": {
        "known_clusters": ["Precision engineering for semi equipment"],
        "expected_layers": [4, 5],  # equipment parts + OSAT
        "known_names": ["UMS", "Micro-Mechanics", "AEM", "Frencken"],
        "language": "English adequate",
        "investor_base": "Singapore retail + institutions"
    },
    "Bursa Malaysia": {
        "known_clusters": ["Penang OSAT ecosystem"],
        "expected_layers": [5],  # assembly & test
        "known_names": ["Inari", "Frontken", "Vitrox", "Pentamaster"],
        "language": "English adequate, Malay for color",
        "investor_base": "Malaysian retail dominated"
    }
}

Key insight: This stage determines which exchanges are worth deep-diving and which are structurally unlikely to produce an AXTI analog. SGX and Bursa are Layer 5 ecosystems (services) — the LLM knows going in that it's screening for "best compounder" not "next monopolist." Frankfurt and LSE are Layer 2-4 (equipment and materials) — higher prior probability of finding a chokepoint.


Stage 3: Search Query Construction

For each exchange, the LLM runs layered searches. Each layer uses different keyword strategies optimized for different types of discovery.

def generate_search_queries(exchange, target_layers):
    queries = []

    # ─── PASS 1: BROAD DISCOVERY ───
    # Purpose: Surface obvious names + recent momentum stories
    # Keywords: exchange name + sector + size + current year
    # Keep queries short (3-5 words) for best search results
    #
    # Example: "Frankfurt small cap semiconductor AI 2026"
    #
    # Expected output: news articles, broker notes, listicles
    # What I extract: company names, tickers, recent price moves

    queries.append({
        "query": f"{exchange} small cap semiconductor AI 2026",
        "purpose": "discover what names are being discussed"
    })

    # ─── PASS 2: SUPPLY CHAIN SPECIFIC ───
    # Purpose: Find companies at specific bottleneck positions
    # Keywords: specific technology/material + geography
    # This is where domain knowledge from Stage 1 shapes
    # query quality dramatically
    #
    # Without knowing MOCVD is a bottleneck, you'd never
    # search for "MOCVD equipment maker Germany."
    # Without knowing glass substrates are the next packaging
    # material, you'd never search for "glass substrate laser tool."

    if exchange == "Frankfurt":
        queries.extend([
            {
                "query": "MOCVD equipment InP epitaxial Germany",
                "purpose": "find the tool maker for InP epi growth",
                "reasoning": "Industry analysis identified MOCVD as "
                             "hard bottleneck with 10-12 month delivery. "
                             "Who makes these machines? Likely German "
                             "(Aixtron) or US (Veeco). Confirm."
            },
            {
                "query": "crystal growth furnace VGF semiconductor",
                "purpose": "find who builds the furnaces AXT uses",
                "reasoning": "AXT grows crystals in VGF furnaces. "
                             "Someone manufactures those furnaces. "
                             "PVA TePla is a guess — confirm."
            },
            {
                "query": "glass substrate laser tool advanced packaging",
                "purpose": "find tools for next-gen packaging materials",
                "reasoning": "Layer 2 bottleneck = packaging. Glass is "
                             "the next substrate material (Intel committed "
                             "2030). Who makes the processing tools?"
            },
            {
                "query": "Frankfurt Xetra photonics optical laser small cap",
                "purpose": "catch any names missed from training data",
                "reasoning": "German photonics cluster may have names "
                             "not in prior knowledge. Broad sweep to "
                             "catch unknown entities."
            }
        ])

    elif exchange == "HKSE":
        queries.extend([
            {
                "query": "Hong Kong optical transceiver component stock",
                "purpose": "find listed optical component suppliers",
                "reasoning": "Chinese transceiver makers (Innolight, "
                             "Eoptolink) source subcomponents from "
                             "smaller suppliers. Who makes them and is "
                             "listed in HK?"
            },
            {
                "query": "Hong Kong silicon photonics AI chip IPO 2026",
                "purpose": "find recent/upcoming IPOs in the space",
                "reasoning": "HKSE opportunity is dynamic — new names "
                             "listing monthly. The next candidate might "
                             "have IPO'd last week."
            },
            {
                "query": "compound semiconductor Hong Kong listed",
                "purpose": "find any HK-listed material/substrate maker",
                "reasoning": "Direct AXTI analog search — does any HK "
                             "company make III-V substrates?"
            }
        ])

    elif exchange == "SGX":
        queries.extend([
            {
                "query": "Singapore semiconductor precision engineering AI",
                "purpose": "map the SGX semi ecosystem",
                "reasoning": "SGX semi = service/precision eng. Not "
                             "expecting chokepoints. Looking for best-of-"
                             "breed with highest margins and most "
                             "defensible customer relationships."
            }
            # Fewer passes needed — SGX semi universe is small
            # and well-known. 5-6 names cover the entire space.
        ])

    elif exchange == "Bursa":
        queries.extend([
            {
                "query": "Penang semiconductor photonics OSAT packaging",
                "purpose": "find any Bursa name pivoting to photonics",
                "reasoning": "OSAT companies could upgrade from commodity "
                             "packaging to photonics packaging. That "
                             "pivot = potential re-rating catalyst."
            }
        ])

    return queries

# ─── QUERY QUALITY PRINCIPLES ───
#
# BAD query:  "best stocks to buy 2026"
#   -> returns SEO spam, listicles, noise
#
# GOOD query: "pBN crucible manufacturer listed stock"
#   -> returns the exact company needed
#
# BAD query:  "semiconductor AI investment opportunity"
#   -> too broad, returns NVDA/AMD articles
#
# GOOD query: "indium phosphide substrate supply chain Lumentum"
#   -> returns supply chain analysis with specific names
#
# The domain knowledge from Stage 1-2 is what makes queries
# precise. The LLM's edge is not in searching — it's in
# knowing WHAT to search for.

Stage 4: Search Result Processing

Each web search returns approximately 10 results with titles and snippets. The LLM processes these through a triage pipeline before committing tokens to full-page reads.

def process_search_results(results):

    # ─── STEP 4a: ENTITY EXTRACTION ───
    # Pull company names, tickers, and key numbers from
    # snippets WITHOUT clicking through yet.
    # This is fast triage — decide what deserves a deep dive.

    entities = []
    for result in results:
        extract = {
            "company_name": parse_name(result.snippet),
            "ticker": parse_ticker(result.snippet),
            "market_cap": parse_number(result.snippet, "market cap"),
            "revenue": parse_number(result.snippet, "revenue"),
            "price_move": parse_percentage(result.snippet),
            "source_quality": assess_source(result.url)
            # Source quality hierarchy:
            # SEC/exchange filing > broker note > earnings transcript
            # > financial data site > news article > blog > forum
        }
        entities.append(extract)

    # ─── STEP 4b: QUICK TRIAGE ───
    # Apply hard filters before spending tokens on web_fetch.

    for entity in entities:
        if entity.market_cap and entity.market_cap > 10_000_000_000:
            entity.status = "too_large"
            # No 10x math from $10B+ starting point
            continue
        if entity.market_cap and entity.market_cap < 10_000_000:
            entity.status = "too_small"
            # Probably not a real operating company
            continue
        if entity.source_quality == "forum":
            entity.status = "low_confidence"
            # Need to verify from better source first
            continue
        entity.status = "investigate"

    # ─── STEP 4c: DEEP DIVE (web_fetch) ───
    # Only for "investigate" entities. Read full articles to
    # extract business model, customers, competitors, margins.
    # This is the most token-expensive step — be selective.

    for entity in entities:
        if entity.status == "investigate":
            full_page = web_fetch(entity.best_url)
            entity.business_model = extract_business_model(full_page)
            entity.customers = extract_customers(full_page)
            entity.competitors = extract_competitors(full_page)
            entity.financials = extract_financials(full_page)

    return entities

Stage 5: The Chokepoint Test

This is the most critical filter. Most candidates fail here. The entire test reduces to a single question.

def chokepoint_test(entity):
    """
    THE QUESTION:
    'If this company disappeared tomorrow, could its
    customers find an alternative within 6 months?'

    YES   -> Not a chokepoint. Cyclical beneficiary at best.
             Score: 0 on factor #2
    MAYBE -> Weak chokepoint. Customers could switch but
             it would take 12-18 months and cost money.
             Score: 0.5 on factor #2
    NO    -> Genuine chokepoint. Customers are locked in
             by technology, qualification, or physics.
             Score: 1.0 on factor #2
    """

    # Sub-questions that determine the answer:

    competitors = count_exact_competitors(entity.product)
    # 0-1 competitors = monopoly/duopoly  -> answer is NO
    # 2-4 competitors = oligopoly         -> MAYBE to NO
    # 5+  competitors = competitive       -> YES

    qualification_required = entity.has_customer_qualification
    # If switching requires 12-24 month requalification,
    # that IS the moat. This is the strongest barrier.
    # (See: AXTI substrate qualification, TSMC node qualification)

    differentiation = entity.product_differentiation
    # Commodity (standard chemicals, machined parts) -> switchable
    # Differentiated (specific crystal quality, proprietary
    # tool architecture, patented process) -> not switchable

    cost_share = entity.pct_of_customer_cogs
    # < 5% of customer COGS = customer won't bother switching
    #   to save pennies. Gives pricing power.
    #   (InP substrates = 2% of transceiver module cost.
    #    AXT raised prices 70%. Nobody flinched.)
    # > 20% of COGS = customer actively multi-sources to
    #   reduce concentration risk.

    # ─── SCORING LOGIC ───
    if competitors <= 1 and qualification_required:
        return 1.0   # Genuine chokepoint
    elif competitors <= 3 and (qualification_required or differentiation):
        return 0.75   # Strong oligopoly
    elif competitors <= 3:
        return 0.5   # Oligopoly without lock-in
    else:
        return 0.0   # Competitive market


# ─── EXAMPLES OF HOW THIS TEST PLAYS OUT ───
#
# LPKF (glass substrate LIDE tools):
#   Competitors making LIDE-equivalent? 0-1  -> NO substitute
#   Customer qualification needed? Yes (Intel, TSMC)  -> locked in
#   Differentiation? Patented LIDE process  -> not replicable
#   Cost share? Tool cost is tiny vs fab capex  -> price insensitive
#   RESULT: GENUINE CHOKEPOINT. Score = 1.0
#
# IQE (InP/GaAs epitaxial wafers):
#   Competitors? 3-4 (LandMark, VPEC, IntelliEPI)  -> oligopoly
#   Qualification? Yes (12-24 month epi qualification)  -> locked
#   Differentiation? Moderate (epi quality varies)
#   RESULT: STRONG OLIGOPOLY. Score = 0.75
#
# Aixtron (MOCVD equipment):
#   Competitors? 1 (Veeco)  -> duopoly
#   Qualification? Yes (tool qual takes 6-12 months)  -> locked
#   Differentiation? Yes (G10-AsP architecture is proprietary)
#   RESULT: GENUINE CHOKEPOINT. Score = 1.0
#
# UMS Integration (vacuum chamber parts for AMAT):
#   Competitors making same parts? 10+  -> competitive
#   Qualification? Minimal (commodity precision machining)
#   Differentiation? Low (parts made to AMAT specs)
#   RESULT: NOT A CHOKEPOINT. Score = 0.0
#
# Inari Amertron (RF OSAT packaging):
#   Competitors? Many (ASE, Amkor, JCET, others)  -> competitive
#   But: Broadcom relationship is sticky  -> MAYBE
#   Differentiation? Moderate (Broadcom-specific process)
#   RESULT: WEAK CHOKEPOINT. Score = 0.5
#
# Micro-Mechanics (die-attach consumables):
#   Competitors? Several (K&S, ASM Pacific, Shinkawa)
#   Qualification? Low (consumable tools, easy to switch)
#   Differentiation? Low (commodity consumable)
#   RESULT: NOT A CHOKEPOINT. Score = 0.0
#   (Good compounder, not a 10-bagger candidate)

Stage 6: 9-Factor Scoring

Each candidate is scored against all nine factors from the AXTI pattern.

def score_candidate(entity):
    scores = {}

    # ─── FACTOR 1: MICRO-CAP ───
    if entity.market_cap < 500_000_000:
        scores["micro_cap"] = 1.0       # Sweet spot
    elif entity.market_cap < 1_000_000_000:
        scores["micro_cap"] = 0.75
    elif entity.market_cap < 2_000_000_000:
        scores["micro_cap"] = 0.5       # Can still 5x but not 10x
    else:
        scores["micro_cap"] = 0.0       # Too large

    # ─── FACTOR 2: CHOKEPOINT ───
    scores["chokepoint"] = chokepoint_test(entity)
    # (from Stage 5)

    # ─── FACTOR 3: REAL REVENUE ───
    if entity.revenue > 50_000_000 and entity.has_named_customers:
        scores["real_revenue"] = 1.0    # Proven demand
    elif entity.revenue > 10_000_000:
        scores["real_revenue"] = 0.75
    elif entity.revenue > 0:
        scores["real_revenue"] = 0.5    # Early but real
    else:
        scores["real_revenue"] = 0.0    # Pre-revenue = binary

    # ─── FACTOR 4: REVENUE INFLECTION ───
    # Signals: sequential acceleration, backlog growth,
    # consensus guide beats, new customer wins
    inflection_signals = [
        entity.revenue_growing_sequentially,
        entity.backlog_increasing,
        entity.beat_consensus_last_quarter,
        entity.new_customer_wins_announced
    ]
    scores["inflection"] = sum(inflection_signals) / len(inflection_signals)

    # ─── FACTOR 5: LOSS-TO-PROFIT TRANSITION ───
    if entity.is_loss_making and entity.margins_improving:
        scores["l2p"] = 1.0
        # HIGHEST re-rating potential. Market reprices from
        # "will they survive?" to "how fast do they grow?"
        # This drove AXTI from $5 to $20 alone.
    elif entity.is_loss_making and not entity.margins_improving:
        scores["l2p"] = 0.25            # Still burning, no hope
    elif entity.is_profitable:
        scores["l2p"] = 0.5             # Less upside from re-rating

    # ─── FACTOR 6: NARRATIVE SCARCITY ───
    # Is this the ONLY listed pure-play on this specific theme?
    listed_peers = count_listed_pureplay_peers(entity.theme)
    if listed_peers == 0:
        scores["narrative_scarcity"] = 1.0
        # AXTI had this — no other way to play InP substrates.
        # All capital flows into one name.
    elif listed_peers <= 2:
        scores["narrative_scarcity"] = 0.5
    else:
        scores["narrative_scarcity"] = 0.0

    # ─── FACTOR 7: CAPACITY-CONSTRAINED ───
    capacity_signals = [
        entity.sold_out,                     # backlog > capacity
        entity.raising_prices,               # pricing power confirmed
        entity.customers_signing_LTAs,       # long-term supply deals
        entity.competitors_also_sold_out     # industry-wide shortage
    ]
    scores["capacity_constrained"] = sum(capacity_signals) / len(capacity_signals)

    # ─── FACTOR 8: UNDER-FOLLOWED ───
    if entity.analyst_coverage <= 2:
        scores["under_followed"] = 1.0       # Price discovery hasn't happened
    elif entity.analyst_coverage <= 5:
        scores["under_followed"] = 0.5
    else:
        scores["under_followed"] = 0.0       # Already discovered

    # ─── FACTOR 9: FLOAT DYNAMICS ───
    float_turnover_days = entity.free_float / entity.avg_daily_volume
    if float_turnover_days < 5 and entity.short_interest_pct > 10:
        scores["float"] = 1.0                # Squeeze setup
    elif float_turnover_days < 10:
        scores["float"] = 0.5
    else:
        scores["float"] = 0.0

    # ─── TOTAL ───
    total = sum(scores.values())
    return total, scores

# DECISION THRESHOLDS:
# total >= 6.0 -> STRONG CANDIDATE (potential 5-10x+)
# total 4.0-5.9 -> INTERESTING TRADE (2-3x cyclical)
# total < 4.0  -> COMPOUNDER at best (steady 15-20% annual)

Stage 7: Cross-Validation (Sanity Checks)

Before finalizing, three sanity checks prevent common errors.

def cross_validate(candidate):

    # ─── CHECK 1: HAS IT ALREADY MOVED? ───
    # If stock is up 300%+ from 12-month low, the 10x
    # is behind you. You're buying AXTI at $73, not $5.

    move_from_low = (candidate.price - candidate.low_52wk) / candidate.low_52wk
    if move_from_low > 3.0:
        candidate.add_warning("ALREADY_MOVED")
        # Don't disqualify — but flag reduced upside.
        # Example: Soitec (SOI.PA) scored high on factors
        # but was already +300% YTD. Remaining upside: 50-100%.
        # That's a trade, not a 10-bagger.

    # ─── CHECK 2: TAM CEILING ───
    # Reverse-engineer: what revenue is implied by a 10x?
    # If required revenue > credible market size, the math fails.

    target_mkt_cap = candidate.market_cap * 10
    reasonable_ev_to_sales = 10  # mature growth company multiple
    implied_revenue = target_mkt_cap / reasonable_ev_to_sales

    if implied_revenue > candidate.total_addressable_market:
        candidate.add_warning("TAM_CEILING")
        # AXTI had this exact problem:
        # $73 share price implied $1.4B revenue at maturity
        # vs InP substrate TAM of $400-600M.
        # Either the TAM estimate is wrong or the stock is
        # pricing in >100% market share — which is impossible.

    # ─── CHECK 3: KILL SWITCH ───
    # Identify the single event that could zero the stock.
    # Every AXTI-type candidate has one. Size positions
    # accordingly.

    kill_switches = {
        "LPKF":    "Glass substrates never adopted (Intel cancels program)",
        "IQE":     "No buyer emerges, balance sheet breaks, equity wiped",
        "Aixtron": "InP capex cycle peaks, MOCVD orders collapse",
        "Inari":   "Broadcom brings photonics packaging in-house",
        "O-Net":   "Chinese optical component oversupply crashes margins"
    }
    candidate.kill_switch = kill_switches.get(candidate.name)
    # If kill switch probability > 40%, reduce position size
    # or structure as options (defined risk).

    return candidate

Stage 8: Final Ranking

The output ranking combines raw factor score with practical adjustments.

def final_rank(candidates):
    for c in candidates:
        c.adjusted_score = c.raw_score

        # ─── TIMING DISCOUNT ───
        # A catalyst 3 years out is worth less than one 6 months out.
        # Capital has opportunity cost.
        if c.catalyst_timeline_months > 24:
            c.adjusted_score -= 0.5
        if c.catalyst_timeline_months > 36:
            c.adjusted_score -= 0.5
        # Example: LPKF scores 6.5 raw but catalyst is 2028-2030.
        # Adjusted score: 5.5-6.0. Still top-ranked, but the
        # timing penalty is real.

        # ─── RISK ADJUSTMENT ───
        if c.kill_switch_probability > 0.4:
            c.adjusted_score -= 0.5
        # Example: IQE scores 6.0 raw but equity wipe risk is
        # ~30-40%. Adjusted score: 5.5-6.0.

        # ─── ACCESSIBILITY ───
        if c.avg_daily_volume < 100_000:
            c.adjusted_score -= 0.25  # thin liquidity
        if c.requires_local_broker_account:
            c.adjusted_score -= 0.25  # friction to access
        # Example: Bursa Malaysia names require Malaysian
        # brokerage or IBKR with Bursa access. Minor friction
        # but real for most international investors.

    return sorted(candidates,
                  key=lambda x: x.adjusted_score,
                  reverse=True)


# ─── FINAL OUTPUT FORMAT ───
# Rank | Name | Exchange | Score | Upside | Timing | Character
#  1   | LPKF | Frankfurt | 6.5  | 10-20x | 2028-30 | Glass monopoly
#  2   | IQE  | LSE AIM   | 6.0  | 3-10x  | 6-12mo  | Distressed M&A
#  3   | AIXA | Frankfurt | 5.0  | 2-3x   | 2026-27 | MOCVD duopoly
# ...

Stage 9: Known Limitations

"""
What this pipeline CANNOT do:

1. LANGUAGE GATE
   Mandarin/Japanese/Korean/Malay broker reports are invisible
   to English-language web search. Estimated 30-40% of actionable
   information on HKSE, Bursa, and TSE is language-gated.
   The real alpha on Asian micro-caps lives in local-language
   sell-side initiation reports that never get translated.

2. NO QUANTITATIVE SCREENER
   This pipeline reasons from supply chain structure to specific
   companies. It cannot run database queries like:

   SELECT * FROM global_equities
   WHERE market_cap < 1e9
     AND revenue_growth_yoy > 0.50
     AND gross_margin_improving = TRUE
     AND herfindahl_index > 0.25

   Such a screener would catch candidates this pipeline misses —
   especially in sectors outside the LLM's domain expertise.

3. SURVIVORSHIP BIAS
   The 9-factor checklist was reverse-engineered from ONE winner
   (AXTI). Sample size = 1. The next 10-bagger might score 3/9
   on this checklist because it has a completely different driver
   profile — regulatory catalyst, M&A, platform shift, currency
   dislocation — rather than supply-demand mismatch.

4. TEMPORAL BLIND SPOT
   Companies that IPO'd in the last 3-6 months, or that pivoted
   strategy recently, may not appear in training data. Web search
   partially mitigates this but doesn't eliminate it. The best
   candidate might be a company that announced a strategic pivot
   last Tuesday in a Mandarin-language press release.

5. PRIVATE COMPANY BLIND SPOT
   The best AXTI analog might not be public yet. Advanced Micro
   Foundry (AMF) in Singapore was arguably the perfect silicon
   photonics candidate — but GlobalFoundries acquired it before
   retail investors could access it. The IPO pipeline partially
   addresses this gap.

6. SEARCH ENGINE BIAS
   Web search returns results ranked by SEO quality, not
   investment relevance. A well-optimized company blog outranks
   a PDF broker note with genuine analytical alpha. The LLM
   compensates by preferring SEC/exchange filings and known
   financial data sources over generic news articles — but
   cannot fully overcome the SEO ranking distortion.

7. CONFIRMATION BIAS
   The pipeline starts with a supply chain hypothesis and
   searches for companies that fit it. This inherently biases
   toward confirming the hypothesis. A company that doesn't fit
   the AXTI pattern but could 10x for completely different
   reasons (activist investor, regulatory change, currency
   windfall) will be systematically missed.
"""

The Meta-Insight

The most important takeaway from this pipeline: the LLM's edge is not in searching — it's in knowing what to search for.

A traditional screener can filter 50,000 stocks by P/E and revenue growth in milliseconds. But it cannot express the query "find me the company that makes the furnaces used to grow the crystals that become the substrates that go into the lasers that power AI optical interconnects." That query requires understanding the supply chain structure — which is domain knowledge, not data.

The AXTI pattern is fundamentally a supply chain discovery trade. The market is efficient at pricing demand (everyone knows AI capex is growing). The market is inefficient at pricing supply constraints — especially when the constrained input is 3-4 layers deep in the stack, made by a micro-cap in a different country, and covered by 2 analysts who don't understand the downstream demand drivers.

The next 10x is probably sitting at some obscure chokepoint that nobody has mapped yet. The LLM's job is to map the supply chain deeply enough to find it before the market does.


Methodology documentation for the "Hunting the Next AXTI" global equity screen. Published May 2026.

Discussion

No comments yet.

No redactions applied.

Published via wiis. Originals are encrypted at rest; this page reflects the redacted version. report abuse.