"""Standalone global niche-market discovery using Brave Search + Graham filter. Uses shared core modules for search, ticker extraction, and logging. """ import os import re import yfinance as yf from dotenv import load_dotenv from src.core.logger import get_logger from src.core.search import brave_search_raw from src.core.ticker_utils import normalize_price from src.finance_tools import check_financial_health from src.email_utils import send_email_report from src.global_router import MARKET_CONFIG, normalize_ticker, get_official_filing_link load_dotenv() logger = get_logger(__name__) def extract_tickers(text: str, region: str) -> list[str]: """Extract stock ticker patterns from raw text with regional awareness.""" found_tickers: set[str] = set() # Cashtags: $AAPL cashtags = re.findall(r"\$([A-Za-z]{2,5})", text) found_tickers.update(t.upper() for t in cashtags) # Parenthesised tickers: (AAPL) — skip 2-letter to avoid (US), (UK) parentheses = re.findall(r"\(([A-Z]{3,5})\)", text) found_tickers.update(parentheses) # Regional exchange prefixes if region == "UK": uk_tags = re.findall(r"LON:\s?([A-Za-z]{2,5})", text) found_tickers.update(t.upper() for t in uk_tags) elif region == "CANADA": ca_tags = re.findall(r"TSX:\s?([A-Za-z]{2,5})", text) found_tickers.update(t.upper() for t in ca_tags) blacklist = {"IPO", "CEO", "YTD", "USD", "GBP", "EUR", "ETF", "EPS", "FYI", "AGM"} suffix = MARKET_CONFIG[region]["suffix"] normalized: list[str] = [] for t in found_tickers: clean = t.strip().upper() if clean in blacklist: continue if suffix and not clean.endswith(suffix): clean = f"{clean}{suffix}" normalized.append(clean) return list(set(normalized)) def run_global_hunt(): """Execute the global niche hunt across all configured markets.""" logger.info("Starting Agentic Global Scout (Brave Powered)...") report_html = "

Daily Agentic Scout Report

" report_html += "

Generated by scraping real-time market discussions via Brave Search.

" search_queries = { "USA": "undervalued microcap stocks reddit 2026 buy list", "UK": "best aim stocks to buy 2026 undervalued ukinvesting", "CANADA": "tsx venture deep value stocks mining reddit", "AUSTRALIA": "asx small cap gems hotcopper undervalued", } for region, config in MARKET_CONFIG.items(): query = search_queries.get(region) logger.info("Scout searching %s: '%s'", region, query) raw_text = brave_search_raw(query) candidates = extract_tickers(raw_text, region) logger.info("Found %d potential tickers: %s", len(candidates), candidates) region_gems = [] for ticker in candidates: try: stock = yf.Ticker(ticker) info = stock.info price = info.get("currentPrice") if not price: continue # BUG FIX: pass both ticker AND info to check_financial_health health = check_financial_health(ticker, info) if health["status"] == "PASS" or health.get("metrics", {}).get("margin_of_safety", "N/A") != "N/A": link = get_official_filing_link(ticker, region) safety = health["metrics"].get("margin_of_safety", "N/A") color = "green" if "%" in str(safety) and "-" not in str(safety) else "black" raw_price = info.get("currentPrice", 0) currency = info.get("currency", "USD") display_price = normalize_price(raw_price, ticker, currency) if region == "UK": display_price_str = f"£{display_price:.2f}" else: display_price_str = f"{display_price:.2f} {currency}" region_gems.append(f"""
  • {ticker} ({info.get('shortName', 'Unknown')})
    Price: {display_price_str}
    Verdict: {health['reason']}
    Safety Margin: {safety}
    Verify at {config['gov_source']}
  • """) except Exception as exc: logger.debug("Error processing %s: %s", ticker, exc) continue if region_gems: report_html += f"

    {region} Discovery

    " else: report_html += f"

    {region}

    Scouted {len(candidates)} tickers, but none met Graham's strict criteria.

    " # Email dispatch users = [ {"name": "Cisco", "email": os.getenv("EMAIL_CISCO"), "key": os.getenv("RESEND_API_KEY_CISCO")}, {"name": "Raul", "email": os.getenv("EMAIL_RAUL"), "key": os.getenv("RESEND_API_KEY_RAUL")}, {"name": "David", "email": os.getenv("EMAIL_DAVID"), "key": os.getenv("RESEND_API_KEY_DAVID")}, ] logger.info("Preparing dispatch for %d agents...", len(users)) for user in users: if user["email"] and user["key"]: logger.info("Sending to %s...", user["name"]) try: send_email_report( subject="Agentic Scout Report", html_content=report_html, recipient=user["email"], api_key=user["key"], ) except Exception as exc: logger.error("Failed to send to %s: %s", user["name"], exc) else: logger.info("Skipping %s (missing credentials)", user["name"]) if __name__ == "__main__": run_global_hunt()