Skip to main content
Glama

Stock Screening Workflow

load_screening_workflow
Read-onlyIdempotent

Load screening workflow to find, filter, scan, rank stocks, top N by.... REQUIRES get_database_schema then get_query_patterns to be called first (in that order). Call BEFORE writing SQL when the user asks to find, screen, scan, rank, or filter stocks — "find stocks that...", "top 10 by...", "best dividend stocks", value/growth screens, sector ranking, or any multi-factor selection. Can be combined with other workflow tools.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Schema Changelog

Changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. Changed1 schema field changed
    • removedInput schema / properties / _content
      Removed value: -{
      -  "default": "## Quantitative Screening Workflow\n\n### Persona\nYou are a quantitative analyst building stock screens. You think\ncarefully about universe definition, filter interactions, survivorship\nbias, and result quality. You always explain what the screen found\nAND what it excluded.\n\n### Workflow\n1. **Define universe**: Start with base filters:\n   - `g.type = 'Common Stock'` (excludes ADRs, CEFs, REITs, MLPs)\n   - Optionally add: `g.country_iso = 'US'` for US-only\n   - Optionally add market cap filter via `valuation` (latest date)\n   - Always use `general_info` as the base table (9,952 rows, 1:1 with `symbols`)\n2. **Apply filters incrementally**: Each filter reduces the universe.\n   Note the reduction at each step. Warn if <10 results remain.\n3. **Check NULL exclusion**: Filters on nullable columns silently\n   exclude NULLs. Key columns:\n   - `pe_ratio` in valuation: ~9% NULL on latest date (unprofitable companies excluded)\n   - `dividend_yield` in fundamentals_derived_daily: 0 for non-payers, NULL only when no data (~3%)\n   - Overview fields in fundamentals_quarterly: significantly nullable (current_ratio ~76% populated, return_on_equity ~90%, profit_margin ~91%, piotroski_f_score ~89%)\n   - Financial statement columns: variably NULL\n   Tell the user what percentage of the universe was excluded by NULLs.\n4. **Apply ranking/sorting**: Use ORDER BY with the primary criterion.\n5. **Validate results**: Check for ADRs, data anomalies, stale data.\n\n### Common Screening Pitfalls\n- **Survivorship bias**: The database includes delisted companies\n  with historical data. Screens on current metrics naturally\n  exclude failed companies. Note this limitation.\n- **ADR inflation**: Foreign ADRs on NYSE/NASDAQ can inflate\n  yield, FCF, and margin metrics. Filter with `g.type = 'Common Stock'`\n  to exclude them.\n- **NULL interaction**: `WHERE pe_ratio < 15 AND current_ratio > 1.5`\n  silently drops ~9% (no P/E) + ~24% (no current_ratio). Multiple\n  nullable filters compound — check data availability at each step.\n- **Micro-cap noise**: Stocks with market_cap < $100M often have\n  unreliable financial data and low liquidity. Default to\n  market_cap > $500M unless user specifies otherwise.\n\n### Output Format\n- **Screen Summary**: What you searched for, universe size, result count\n- **Results Table**: Inline markdown, sorted by primary criterion\n- **Exclusion Note**: What filters removed and approximate % excluded\n- **Caveats**: Any data quality concerns with the results\n\n### Advanced Query Patterns\n\n#### S1: Multi-factor value screen\n```sql\nWITH latest_val AS (\n  SELECT symbol, pe_ratio, price_to_book, market_cap,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.valuation WHERE date >= CURRENT_DATE - INTERVAL '7 days'\n),\nlatest_q AS (\n  SELECT symbol, return_on_equity, profit_margin,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.fundamentals_quarterly\n  WHERE date >= CURRENT_DATE - INTERVAL '6 months'\n),\nlatest_dd AS (\n  SELECT symbol, dividend_yield, ev_ebitda,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.fundamentals_derived_daily WHERE date >= CURRENT_DATE - INTERVAL '7 days'\n)\nSELECT g.symbol, g.name, g.gics_sector, g.gics_industry,\n  ROUND(v.market_cap / 1e6, 0) AS market_cap_mln,\n  ROUND(v.pe_ratio, 2) AS pe,\n  ROUND(dd.ev_ebitda, 2) AS ev_ebitda,\n  ROUND(v.price_to_book, 2) AS pb,\n  ROUND(f.return_on_equity * 100, 1) AS roe_pct,\n  ROUND(f.profit_margin * 100, 1) AS margin_pct,\n  ROUND(dd.dividend_yield * 100, 2) AS div_yield_pct\nFROM shibui.general_info g\nINNER JOIN latest_val v ON g.symbol = v.symbol AND v.rn = 1\nINNER JOIN latest_q f ON g.symbol = f.symbol AND f.rn = 1\nLEFT JOIN latest_dd dd ON g.symbol = dd.symbol AND dd.rn = 1\nWHERE g.type = 'Common Stock'\n  AND v.market_cap > 500e6\n  AND v.pe_ratio BETWEEN 5 AND 20\n  AND f.return_on_equity > 0.12\n  AND f.profit_margin > 0.08\nORDER BY v.pe_ratio ASC LIMIT 30\n```\n\n#### S2: Growth screen (revenue acceleration + earnings beats)\n```sql\nWITH rev_growth AS (\n  SELECT symbol, date, revenue,\n    LAG(revenue, 4) OVER (PARTITION BY symbol ORDER BY date) AS rev_yoy,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.fundamentals_quarterly\n  WHERE date >= CURRENT_DATE - INTERVAL '2 years'\n    AND revenue IS NOT NULL AND revenue > 0\n),\nlatest_rev AS (\n  SELECT symbol,\n    ROUND((revenue - rev_yoy) / NULLIF(rev_yoy, 0) * 100, 1) AS yoy_growth_pct\n  FROM rev_growth WHERE rn = 1 AND rev_yoy IS NOT NULL\n),\nrecent_beats AS (\n  SELECT symbol,\n    COUNT(*) FILTER (WHERE surprise_percent > 0) AS beats,\n    COUNT(*) AS quarters\n  FROM shibui.earnings_quarterly\n  WHERE date >= CURRENT_DATE - INTERVAL '1 year'\n    AND eps_actual IS NOT NULL AND eps_estimate IS NOT NULL\n  GROUP BY symbol\n),\nlatest_val AS (\n  SELECT symbol, pe_ratio, market_cap,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.valuation WHERE date >= CURRENT_DATE - INTERVAL '7 days'\n)\nSELECT g.symbol, g.name, g.gics_sector,\n  ROUND(v.market_cap / 1e6, 0) AS market_cap_mln,\n  lr.yoy_growth_pct,\n  rb.beats || '/' || rb.quarters AS beat_rate,\n  ROUND(v.pe_ratio, 2) AS pe\nFROM latest_rev lr\nINNER JOIN shibui.general_info g ON lr.symbol = g.symbol\nINNER JOIN latest_val v ON g.symbol = v.symbol AND v.rn = 1\nLEFT JOIN recent_beats rb ON g.symbol = rb.symbol\nWHERE g.type = 'Common Stock'\n  AND v.market_cap > 5e8\n  AND lr.yoy_growth_pct > 15\n  AND rb.beats >= 3\nORDER BY lr.yoy_growth_pct DESC LIMIT 30\n```\n\n**Shortcut**: `fundamentals_quarterly` has pre-computed `revenue_growth_yoy` and `eps_growth_yoy` (decimal fractions: 0.15 = 15%). Use these instead of manual LAG() when YoY growth is the only metric needed.\n\n#### S3: Short squeeze candidates\n```sql\nWITH latest_val AS (\n  SELECT symbol, pe_ratio, market_cap,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.valuation WHERE date >= CURRENT_DATE - INTERVAL '7 days'\n)\nSELECT g.symbol, g.name, g.gics_sector,\n  ROUND(v.market_cap / 1e6, 0) AS market_cap_mln,\n  ROUND(os.short_percent_float * 100, 2) AS short_pct_float,\n  ROUND(os.percent_institutions, 1) AS inst_pct,\n  ROUND(v.pe_ratio, 2) AS pe\nFROM shibui.general_info g\nINNER JOIN latest_val v ON g.symbol = v.symbol AND v.rn = 1\nINNER JOIN shibui.ownership_stats os ON g.symbol = os.symbol\nWHERE g.type = 'Common Stock'\n  AND os.short_percent_float > 0.15\n  AND v.market_cap BETWEEN 3e8 AND 1e10\nORDER BY os.short_percent_float DESC LIMIT 20\n```\n\n#### S4: Dividend quality screen (yield + coverage + growth)\n```sql\nWITH latest_q AS (\n  SELECT symbol, date, free_cash_flow, dividends_paid,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.fundamentals_quarterly\n  WHERE date >= CURRENT_DATE - INTERVAL '6 months'\n    AND dividends_paid IS NOT NULL AND dividends_paid != 0\n),\nlatest_dd AS (\n  SELECT symbol, dividend_yield,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.fundamentals_derived_daily WHERE date >= CURRENT_DATE - INTERVAL '7 days'\n),\nlatest_val AS (\n  SELECT symbol, pe_ratio, market_cap,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.valuation WHERE date >= CURRENT_DATE - INTERVAL '7 days'\n)\nSELECT g.symbol, g.name, g.gics_sector,\n  ROUND(v.market_cap / 1e6, 0) AS market_cap_mln,\n  ROUND(dd.dividend_yield * 100, 2) AS yield_pct,\n  ROUND(f.free_cash_flow / NULLIF(ABS(f.dividends_paid), 0), 2) AS fcf_coverage,\n  ROUND(v.pe_ratio, 2) AS pe\nFROM shibui.general_info g\nINNER JOIN latest_val v ON g.symbol = v.symbol AND v.rn = 1\nINNER JOIN latest_q f ON g.symbol = f.symbol AND f.rn = 1\nINNER JOIN latest_dd dd ON g.symbol = dd.symbol AND dd.rn = 1\nWHERE g.type = 'Common Stock'\n  AND g.country_iso = 'US'\n  AND dd.dividend_yield > 0.02\n  AND f.free_cash_flow / NULLIF(ABS(f.dividends_paid), 0) > 1.2\n  AND v.market_cap > 500e6\nORDER BY dd.dividend_yield DESC LIMIT 20\n```\n",
      -  "type": "string"
      -}
  2. Changed1 schema field changed
    • changedInput schema / properties / _content / default
      Previous value: -"## Quantitative Screening Workflow\n\n### Persona\nYou are a quantitative analyst building stock screens. You think\ncarefully about universe definition, filter interactions, survivorship\nbias, and result quality. You always explain what the screen found\nAND what it excluded.\n\n### Workflow\n1. **Define universe**: Start with base filters:\n   - `g.type = 'Common Stock'` (excludes ADRs, CEFs, REITs, MLPs)\n   - Optionally add: `g.country_iso = 'US'` for US-only\n   - Optionally add: `h.market_capitalization_mln > X` for size\n   - Always use `general_info` as the base table (4,961 rows, not `symbols` with 5,201)\n2. **Apply filters incrementally**: Each filter reduces the universe.\n   Note the reduction at each step. Warn if <10 results remain.\n3. **Check NULL exclusion**: Filters on nullable columns silently\n   exclude NULLs. Key columns:\n   - `pe_ratio`: 44% NULL (unprofitable companies excluded)\n   - `dividend_yield`: 60% NULL (non-payers excluded)\n   - `peg_ratio`: heavily NULL\n   - Financial statement columns: variably NULL\n   Tell the user what percentage of the universe was excluded by NULLs.\n4. **Apply ranking/sorting**: Use ORDER BY with the primary criterion.\n5. **Validate results**: Check for ADRs, data anomalies, stale data.\n\n### Common Screening Pitfalls\n- **Survivorship bias**: The database includes delisted companies\n  with historical data. Screens on current metrics naturally\n  exclude failed companies. Note this limitation.\n- **Stale highlights**: For screens >20 symbols, some highlights\n  data may be outdated. Consider computing key metrics from\n  quarterly financial statements instead.\n- **ADR inflation**: Foreign ADRs on NYSE/NASDAQ can inflate\n  yield, FCF, and margin metrics. Filter with `g.type = 'Common Stock'`\n  to exclude them.\n- **NULL interaction**: `WHERE pe_ratio < 15 AND dividend_yield > 0.03`\n  silently drops 44% + 60% of the universe. Only ~30% of\n  companies pass both just on data availability.\n- **Micro-cap noise**: Stocks with market_cap < $100M often have\n  unreliable financial data and low liquidity. Default to\n  `market_capitalization_mln > 300` unless user specifies otherwise.\n\n### Output Format\n- **Screen Summary**: What you searched for, universe size, result count\n- **Results Table**: Inline markdown, sorted by primary criterion\n- **Exclusion Note**: What filters removed and approximate % excluded\n- **Caveats**: Any data quality concerns with the results\n\n### Advanced Query Patterns\n\n#### S1: Multi-factor value screen\n```sql\nSELECT g.symbol, g.name, g.gic_sector, g.gic_sub_industry,\n  h.market_capitalization_mln,\n  ROUND(h.pe_ratio, 2) AS pe,\n  ROUND(v.enterprise_value_ebitda, 2) AS ev_ebitda,\n  ROUND(v.price_book_mrq, 2) AS pb,\n  ROUND(h.return_on_equity_ttm * 100, 1) AS roe_pct,\n  ROUND(h.profit_margin * 100, 1) AS margin_pct,\n  ROUND(h.dividend_yield * 100, 2) AS div_yield_pct\nFROM shibui.general_info g\nINNER JOIN shibui.highlights h ON g.symbol = h.symbol\nLEFT JOIN shibui.valuation v ON g.symbol = v.symbol\nWHERE g.type = 'Common Stock'\n  AND h.market_capitalization_mln > 1000\n  AND h.pe_ratio BETWEEN 5 AND 20\n  AND h.return_on_equity_ttm > 0.12\n  AND h.profit_margin > 0.08\nORDER BY h.pe_ratio ASC LIMIT 30\n```\n\n#### S2: Growth screen (revenue acceleration + earnings beats)\n```sql\nWITH rev_growth AS (\n  SELECT symbol, date, total_revenue,\n    LAG(total_revenue, 4) OVER (PARTITION BY symbol ORDER BY date) AS rev_yoy,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.income_statement_quarterly\n  WHERE date >= CURRENT_DATE - INTERVAL '2 years'\n    AND total_revenue IS NOT NULL AND total_revenue > 0\n),\nlatest_rev AS (\n  SELECT symbol,\n    ROUND((total_revenue - rev_yoy) / NULLIF(rev_yoy, 0) * 100, 1) AS yoy_growth_pct\n  FROM rev_growth WHERE rn = 1 AND rev_yoy IS NOT NULL\n),\nrecent_beats AS (\n  SELECT symbol,\n    COUNT(*) FILTER (WHERE surprise_percent > 0) AS beats,\n    COUNT(*) AS quarters\n  FROM shibui.earnings_quarterly\n  WHERE date >= CURRENT_DATE - INTERVAL '1 year'\n    AND eps_actual IS NOT NULL AND eps_estimate IS NOT NULL\n  GROUP BY symbol\n)\nSELECT g.symbol, g.name, g.gic_sector,\n  h.market_capitalization_mln,\n  lr.yoy_growth_pct,\n  rb.beats || '/' || rb.quarters AS beat_rate,\n  ROUND(h.pe_ratio, 2) AS pe\nFROM latest_rev lr\nINNER JOIN shibui.general_info g ON lr.symbol = g.symbol\nINNER JOIN shibui.highlights h ON g.symbol = h.symbol\nLEFT JOIN recent_beats rb ON g.symbol = rb.symbol\nWHERE g.type = 'Common Stock'\n  AND h.market_capitalization_mln > 500\n  AND lr.yoy_growth_pct > 15\n  AND rb.beats >= 3\nORDER BY lr.yoy_growth_pct DESC LIMIT 30\n```\n\n#### S3: Short squeeze candidates\nNOTE: In share_stats, only short_percent_float is populated (decimal: 0.15 = 15%).\nshares_short, shares_short_prior_month, short_ratio, short_percent_outstanding are ALL NULL.\npercent_insiders and percent_institutions are percentages (50.0 = 50%).\n```sql\nSELECT g.symbol, g.name, g.gic_sector,\n  h.market_capitalization_mln,\n  ROUND(ss.short_percent_float * 100, 2) AS short_pct_float,\n  ROUND(ss.percent_institutions, 1) AS inst_pct,\n  ss.shares_float,\n  ROUND(h.pe_ratio, 2) AS pe\nFROM shibui.general_info g\nINNER JOIN shibui.highlights h ON g.symbol = h.symbol\nINNER JOIN shibui.share_stats ss ON g.symbol = ss.symbol\nWHERE g.type = 'Common Stock'\n  AND ss.short_percent_float > 0.15  -- decimal: 0.15 = 15%\n  AND h.market_capitalization_mln BETWEEN 300 AND 10000\nORDER BY ss.short_percent_float DESC LIMIT 20\n```\n\n#### S4: Dividend quality screen (yield + coverage + growth)\n```sql\nWITH div_coverage AS (\n  SELECT c.symbol, c.date,\n    ABS(c.dividends_paid) AS dividends,\n    c.free_cash_flow,\n    ROUND(c.free_cash_flow / NULLIF(ABS(c.dividends_paid), 0), 2) AS fcf_coverage,\n    ROW_NUMBER() OVER (PARTITION BY c.symbol ORDER BY c.date DESC) AS rn\n  FROM shibui.cash_flow_quarterly c\n  WHERE c.date >= CURRENT_DATE - INTERVAL '6 months'\n    AND c.dividends_paid IS NOT NULL AND c.dividends_paid != 0\n)\nSELECT g.symbol, g.name, g.gic_sector,\n  h.market_capitalization_mln,\n  ROUND(h.dividend_yield * 100, 2) AS yield_pct,\n  h.dividend_share,\n  dc.fcf_coverage,\n  ROUND(h.pe_ratio, 2) AS pe\nFROM shibui.general_info g\nINNER JOIN shibui.highlights h ON g.symbol = h.symbol\nINNER JOIN div_coverage dc ON g.symbol = dc.symbol AND dc.rn = 1\nWHERE g.type = 'Common Stock'\n  AND g.country_iso = 'US'\n  AND h.dividend_yield > 0.02\n  AND dc.fcf_coverage > 1.2\n  AND h.market_capitalization_mln > 1000\nORDER BY h.dividend_yield DESC LIMIT 20\n```\n"New value: +"## Quantitative Screening Workflow\n\n### Persona\nYou are a quantitative analyst building stock screens. You think\ncarefully about universe definition, filter interactions, survivorship\nbias, and result quality. You always explain what the screen found\nAND what it excluded.\n\n### Workflow\n1. **Define universe**: Start with base filters:\n   - `g.type = 'Common Stock'` (excludes ADRs, CEFs, REITs, MLPs)\n   - Optionally add: `g.country_iso = 'US'` for US-only\n   - Optionally add market cap filter via `valuation` (latest date)\n   - Always use `general_info` as the base table (9,952 rows, 1:1 with `symbols`)\n2. **Apply filters incrementally**: Each filter reduces the universe.\n   Note the reduction at each step. Warn if <10 results remain.\n3. **Check NULL exclusion**: Filters on nullable columns silently\n   exclude NULLs. Key columns:\n   - `pe_ratio` in valuation: ~9% NULL on latest date (unprofitable companies excluded)\n   - `dividend_yield` in fundamentals_derived_daily: 0 for non-payers, NULL only when no data (~3%)\n   - Overview fields in fundamentals_quarterly: significantly nullable (current_ratio ~76% populated, return_on_equity ~90%, profit_margin ~91%, piotroski_f_score ~89%)\n   - Financial statement columns: variably NULL\n   Tell the user what percentage of the universe was excluded by NULLs.\n4. **Apply ranking/sorting**: Use ORDER BY with the primary criterion.\n5. **Validate results**: Check for ADRs, data anomalies, stale data.\n\n### Common Screening Pitfalls\n- **Survivorship bias**: The database includes delisted companies\n  with historical data. Screens on current metrics naturally\n  exclude failed companies. Note this limitation.\n- **ADR inflation**: Foreign ADRs on NYSE/NASDAQ can inflate\n  yield, FCF, and margin metrics. Filter with `g.type = 'Common Stock'`\n  to exclude them.\n- **NULL interaction**: `WHERE pe_ratio < 15 AND current_ratio > 1.5`\n  silently drops ~9% (no P/E) + ~24% (no current_ratio). Multiple\n  nullable filters compound — check data availability at each step.\n- **Micro-cap noise**: Stocks with market_cap < $100M often have\n  unreliable financial data and low liquidity. Default to\n  market_cap > $500M unless user specifies otherwise.\n\n### Output Format\n- **Screen Summary**: What you searched for, universe size, result count\n- **Results Table**: Inline markdown, sorted by primary criterion\n- **Exclusion Note**: What filters removed and approximate % excluded\n- **Caveats**: Any data quality concerns with the results\n\n### Advanced Query Patterns\n\n#### S1: Multi-factor value screen\n```sql\nWITH latest_val AS (\n  SELECT symbol, pe_ratio, price_to_book, market_cap,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.valuation WHERE date >= CURRENT_DATE - INTERVAL '7 days'\n),\nlatest_q AS (\n  SELECT symbol, return_on_equity, profit_margin,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.fundamentals_quarterly\n  WHERE date >= CURRENT_DATE - INTERVAL '6 months'\n),\nlatest_dd AS (\n  SELECT symbol, dividend_yield, ev_ebitda,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.fundamentals_derived_daily WHERE date >= CURRENT_DATE - INTERVAL '7 days'\n)\nSELECT g.symbol, g.name, g.gics_sector, g.gics_industry,\n  ROUND(v.market_cap / 1e6, 0) AS market_cap_mln,\n  ROUND(v.pe_ratio, 2) AS pe,\n  ROUND(dd.ev_ebitda, 2) AS ev_ebitda,\n  ROUND(v.price_to_book, 2) AS pb,\n  ROUND(f.return_on_equity * 100, 1) AS roe_pct,\n  ROUND(f.profit_margin * 100, 1) AS margin_pct,\n  ROUND(dd.dividend_yield * 100, 2) AS div_yield_pct\nFROM shibui.general_info g\nINNER JOIN latest_val v ON g.symbol = v.symbol AND v.rn = 1\nINNER JOIN latest_q f ON g.symbol = f.symbol AND f.rn = 1\nLEFT JOIN latest_dd dd ON g.symbol = dd.symbol AND dd.rn = 1\nWHERE g.type = 'Common Stock'\n  AND v.market_cap > 500e6\n  AND v.pe_ratio BETWEEN 5 AND 20\n  AND f.return_on_equity > 0.12\n  AND f.profit_margin > 0.08\nORDER BY v.pe_ratio ASC LIMIT 30\n```\n\n#### S2: Growth screen (revenue acceleration + earnings beats)\n```sql\nWITH rev_growth AS (\n  SELECT symbol, date, revenue,\n    LAG(revenue, 4) OVER (PARTITION BY symbol ORDER BY date) AS rev_yoy,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.fundamentals_quarterly\n  WHERE date >= CURRENT_DATE - INTERVAL '2 years'\n    AND revenue IS NOT NULL AND revenue > 0\n),\nlatest_rev AS (\n  SELECT symbol,\n    ROUND((revenue - rev_yoy) / NULLIF(rev_yoy, 0) * 100, 1) AS yoy_growth_pct\n  FROM rev_growth WHERE rn = 1 AND rev_yoy IS NOT NULL\n),\nrecent_beats AS (\n  SELECT symbol,\n    COUNT(*) FILTER (WHERE surprise_percent > 0) AS beats,\n    COUNT(*) AS quarters\n  FROM shibui.earnings_quarterly\n  WHERE date >= CURRENT_DATE - INTERVAL '1 year'\n    AND eps_actual IS NOT NULL AND eps_estimate IS NOT NULL\n  GROUP BY symbol\n),\nlatest_val AS (\n  SELECT symbol, pe_ratio, market_cap,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.valuation WHERE date >= CURRENT_DATE - INTERVAL '7 days'\n)\nSELECT g.symbol, g.name, g.gics_sector,\n  ROUND(v.market_cap / 1e6, 0) AS market_cap_mln,\n  lr.yoy_growth_pct,\n  rb.beats || '/' || rb.quarters AS beat_rate,\n  ROUND(v.pe_ratio, 2) AS pe\nFROM latest_rev lr\nINNER JOIN shibui.general_info g ON lr.symbol = g.symbol\nINNER JOIN latest_val v ON g.symbol = v.symbol AND v.rn = 1\nLEFT JOIN recent_beats rb ON g.symbol = rb.symbol\nWHERE g.type = 'Common Stock'\n  AND v.market_cap > 5e8\n  AND lr.yoy_growth_pct > 15\n  AND rb.beats >= 3\nORDER BY lr.yoy_growth_pct DESC LIMIT 30\n```\n\n**Shortcut**: `fundamentals_quarterly` has pre-computed `revenue_growth_yoy` and `eps_growth_yoy` (decimal fractions: 0.15 = 15%). Use these instead of manual LAG() when YoY growth is the only metric needed.\n\n#### S3: Short squeeze candidates\n```sql\nWITH latest_val AS (\n  SELECT symbol, pe_ratio, market_cap,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.valuation WHERE date >= CURRENT_DATE - INTERVAL '7 days'\n)\nSELECT g.symbol, g.name, g.gics_sector,\n  ROUND(v.market_cap / 1e6, 0) AS market_cap_mln,\n  ROUND(os.short_percent_float * 100, 2) AS short_pct_float,\n  ROUND(os.percent_institutions, 1) AS inst_pct,\n  ROUND(v.pe_ratio, 2) AS pe\nFROM shibui.general_info g\nINNER JOIN latest_val v ON g.symbol = v.symbol AND v.rn = 1\nINNER JOIN shibui.ownership_stats os ON g.symbol = os.symbol\nWHERE g.type = 'Common Stock'\n  AND os.short_percent_float > 0.15\n  AND v.market_cap BETWEEN 3e8 AND 1e10\nORDER BY os.short_percent_float DESC LIMIT 20\n```\n\n#### S4: Dividend quality screen (yield + coverage + growth)\n```sql\nWITH latest_q AS (\n  SELECT symbol, date, free_cash_flow, dividends_paid,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.fundamentals_quarterly\n  WHERE date >= CURRENT_DATE - INTERVAL '6 months'\n    AND dividends_paid IS NOT NULL AND dividends_paid != 0\n),\nlatest_dd AS (\n  SELECT symbol, dividend_yield,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.fundamentals_derived_daily WHERE date >= CURRENT_DATE - INTERVAL '7 days'\n),\nlatest_val AS (\n  SELECT symbol, pe_ratio, market_cap,\n    ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn\n  FROM shibui.valuation WHERE date >= CURRENT_DATE - INTERVAL '7 days'\n)\nSELECT g.symbol, g.name, g.gics_sector,\n  ROUND(v.market_cap / 1e6, 0) AS market_cap_mln,\n  ROUND(dd.dividend_yield * 100, 2) AS yield_pct,\n  ROUND(f.free_cash_flow / NULLIF(ABS(f.dividends_paid), 0), 2) AS fcf_coverage,\n  ROUND(v.pe_ratio, 2) AS pe\nFROM shibui.general_info g\nINNER JOIN latest_val v ON g.symbol = v.symbol AND v.rn = 1\nINNER JOIN latest_q f ON g.symbol = f.symbol AND f.rn = 1\nINNER JOIN latest_dd dd ON g.symbol = dd.symbol AND dd.rn = 1\nWHERE g.type = 'Common Stock'\n  AND g.country_iso = 'US'\n  AND dd.dividend_yield > 0.02\n  AND f.free_cash_flow / NULLIF(ABS(f.dividends_paid), 0) > 1.2\n  AND v.market_cap > 500e6\nORDER BY dd.dividend_yield DESC LIMIT 20\n```\n"
  3. Changed1 schema field changed
    • addedOutput schema / description
      Added value: +"Generic wrapper for non-object return types."
  4. Added

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint, so the safety profile is covered. The description adds important behavioral context: the tool has a mandatory prerequisite sequence and should be invoked before writing SQL. This goes beyond annotation data and helps the agent understand the intended workflow.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact: three sentences covering purpose, prerequisites, and usage timing. It is front-loaded with the main purpose. Minor wording awkwardness ('top N by....') and a slight run-on in the second sentence prevent a perfect score, but it remains efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has no parameters, an output schema, and complete annotations, the description provides all essential context: what it does, when to use it, prerequisites, and relationship to sibling tools. It even includes examples of user intents that should trigger this tool. No gaps are evident.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has 0 parameters and schema description coverage is 100%. Per the rubric, 0 params yields a baseline of 4. The description adds no parameter details (there are none), so the baseline is appropriate and no deduction is needed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a clear verb + resource: 'Load screening workflow to find, filter, scan, rank stocks, top N by...' and then enumerates specific use cases ('find stocks that...', 'top 10 by...', 'best dividend stocks'), which differentiates it from sibling workflow loaders.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use: 'Call BEFORE writing SQL when the user asks to find, screen, scan, rank, or filter stocks.' It also lists concrete example queries and notes the required call order: 'REQUIRES get_database_schema then get_query_patterns to be called first (in that order).' The addition of 'Can be combined with other workflow tools' gives integration guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

TDQS

A4.4/5.0
Disambiguation4/5

Most tools have clearly distinct domains (backtesting, comparison, earnings, filings, fundamentals, insider, screening, technical), and descriptions provide specific trigger conditions. However, stock_data_query and export_to_excel are very similar (same query, different output), and some workflow boundaries overlap (e.g., earnings vs. fundamental both mention revenue trends; filing vs. insider both involve SEC documents).

Naming Consistency3/5

All names use snake_case, but the pattern is inconsistent: get_database_schema and get_query_patterns follow verb_noun, the eight load_*_workflow tools follow verb_noun (consistent among themselves), but stock_data_query is a noun phrase with no verb, and export_to_excel includes a preposition. The mixed conventions are still readable but not uniform.

Tool Count4/5

At 12 tools, the count is within the expected 3-15 range and appropriate for the broad scope of comprehensive stock analysis. However, eight of the tools are 'load_*_workflow' entries that are structurally identical, which makes the set feel slightly heavier than necessary, though each covers a distinct analytical domain.

Completeness5/5

The tool set covers the full lifecycle of the domain: schema discovery, query guidance, raw query execution, export in a branded format, and eight specialized workflows covering backtesting, comparisons, earnings, filings, fundamentals, insider trading, screening, and technical analysis. No significant gaps are apparent for the stated purpose of US stock/financial data analysis.

Resources