Skip to content

KB/mcp

MCP tool: screener_board

Last verified

screener_board is the single tool that answers “what should I long/short right now” and “has the board’s picks actually worked.” One MCP tool, three views, each routing to a distinct public /api/v1/screener/* route — the same surface the dashboard’s screener page reads.

The tool is a thin dispatcher (mcp/mcp_server/tools/public.py:screener_board). view selects the backend route; the candidates view additionally forwards a curated set of the route’s filter params. No mutation, no auth — all three underlying routes are public.

Signature

screener_board(
  view: str = "action_board",        # "action_board" | "candidates" | "track_record"
  # candidates-only filters, all optional:
  archetype: str | None = None,      # archetype ID (e.g. "A1") or descriptive name
  direction: str | None = None,      # "long" | "short" | "neutral"
  status: str | None = None,         # new | persisting | decayed | exited | promoted
  sector: str | None = None,         # GICS sector, case-insensitive
  min_confluence: int | None = None, # >= 1
  sort: str = "confluence",          # confluence | conviction | score_norm | days_on_list | ticker | status
  trade_date: str | None = None,     # YYYY-MM-DD ET; omit for today (+ stale-board fallback)
  limit: int | None = None,          # 1-2000; omit for the trimmed default of 25
  active_only: bool = False,         # only status in (new, persisting)
  s2_only: bool = False,             # restrict to the gated S2 winner set
  max_tickers: int | None = None,    # >= 1; cap to top-N distinct tickers
) -> str                             # JSON-serialised dict

view outside the three valid values, or a candidates filter outside its bound, raises ValueError before the round-trip — same pattern as analyze_signals’s unknown-aspect guard and compare_to_history’s bound checks. The bounds mirror the backend route’s own Query(ge=..., le=...) constraints (app/screener/routes.py:get_screener_candidates): min_confluence >= 1, 1 <= limit <= 2000, max_tickers >= 1.

Views

View Backend route What it returns
action_board (default) GET /api/v1/screener/company-action-board The position book — locked long/short pick contracts, plus today’s competing candidates
candidates GET /api/v1/screener/candidates The full ranked candidate pipeline, filterable
track_record GET /api/v1/screener/board-track-record Forward-graded accuracy of the board’s actually-surfaced picks

Returns

view="action_board"

{
  "schema_version": "2026.08",
  "as_of": "2026-07-14",
  "regime_band": "elevated",
  "regime_note": "Drawdown-risk band elevated; shorts amplified.",
  "longs": [
    {
      "ticker": "AAPL", "archetype": "oversold_sympathy", "archetype_label": "Oversold Sympathy",
      "direction": "long", "horizon": 5, "horizon_label": "5 trading days",
      "conviction": 71.2, "conviction_tier": "high",
      "board_score": 62.1, "edge_weight_tier": "proven",
      "trust": { "reasons": [] }, "why": ["confluence 4", "conviction high"], "maturity": "mature",
      "contract": {
        "locked_trade_date": "2026-07-14", "entry_ref_price": 214.5,
        "invalidation_price": 208.0, "horizon_days": 5, "day_k": 1,
        "countdown": "day 1/5", "status": "open"
      },
      "bettability": { "score": 68.0, "chip": "BETTABLE", "gated": false },
      "live_signal": null
    }
  ],
  "shorts": [],
  "competing": [],
  "counts": {
    "long": 1, "short": 0, "suppressed_long": 0, "suppressed_short": 0,
    "suppressed_stale": 0, "open_slots_long": 2, "open_slots_short": 3, "competing": 0
  },
  "empty_reason": null
}

view="candidates"

{
  "trade_date": "2026-07-14",
  "count": 1,
  "filters": { "archetype": null, "status": null },
  "sort": "confluence",
  "candidates": [
    {
      "ticker": "MSFT", "archetype": "oversold_sympathy", "archetype_code": "A2",
      "archetype_label": "Oversold Sympathy", "direction": "long_recovery",
      "score_norm": 0.71, "confluence_count": 3, "blended_score_norm": 0.68,
      "regime_tag": "neutral", "conviction": 55.0, "conviction_tier": "medium",
      "sizing_tier": "watching", "rotation_tag": "tailwind",
      "first_seen": "2026-07-10", "days_on_list": 4, "status": "persisting",
      "rank": 1, "rank_prior": 2, "rank_delta": 1,
      "sector": "Technology", "name": "Microsoft Corp"
    }
  ],
  "is_stale_fallback": false,
  "is_intraday_snapshot": false,
  "what_changed": { "prior_trade_date": "2026-07-13", "new_today": [], "requalified": [], "dropped": [], "promoted_today": [] }
}

view="track_record"

{
  "schema_version": "2026.07",
  "as_of_date": "2026-07-14",
  "warming_up": false,
  "closed": { "n": 42, "hit_rate": 0.57, "mean_excess": 0.31 },
  "open": { "n": 5, "mean_mtm_excess": 0.12 },
  "counts": { "runs": 130, "picks_recorded": 210 },
  "caveats": { "warming_up": false },
  "contracts": { "schema_version": "2026.07", "credibility": "early", "open": {}, "closed": {} }
}

hit_rate and every accuracy fraction in closed / open / contracts is a 0-1 fraction, never 0-100. contracts is the stricter lens — graded on the LOCKED pick contracts held to their actual exit, market-adjusted vs SPY — riding alongside the looser board-picks lens in closed/open. Per DOCTRINE P0, a cold-start substrate returns HTTP 200 with warming_up: true and zero counts, never a fabricated number.

Behaviour

Examples

What should I long/short right now?

Operator: "What's the board running today?"
Agent: screener_board()   # view="action_board" default
  -> read longs[] / shorts[] for the locked picks, competing[] for what's next in line

Browse short candidates in a specific sector

Agent: screener_board(
  view="candidates",
  direction="short",
  sector="Technology",
  sort="conviction",
  limit=10,
)

Has the board’s picks actually worked?

Operator: "Is this screener actually any good?"
Agent: screener_board(view="track_record")
  -> read closed.hit_rate (0-1 fraction) and contracts.closed for the stricter locked-and-held lens

When to use

When NOT to use

See also