Kevin Meneses
Volver a artículos

Le di a Claude un screener de acciones. Esto es lo que eligió

Claude CodeStocksStock ScreenerAPIMcp Server
Le di a Claude un screener de acciones. Esto es lo que eligió

La mayoría de las demos de "la IA elige acciones" son ficción.

Le pides a un chatbot empresas infravaloradas, te da cinco tickers, y suenan razonables. AAPL. MSFT. Un banco del que has oído hablar. Se siente como análisis.

No lo es.

El modelo está recitando nombres de sus datos de entrenamiento. No tiene ni idea de a qué cotizan hoy esas empresas, cuál es su PER este trimestre, o si siquiera siguen siendo rentables. Las cifras que cita están congeladas en el tiempo, y la mitad son inventadas.

Así que hice un experimento distinto. Le di a Claude un screener de acciones real como herramienta, le planteé un objetivo de mercado de verdad, y dejé que él mismo hiciera el filtrado.

Esto es lo que pasó.

Si estás:

  • construyendo agentes de IA que tocan datos financieros,
  • automatizando tu propia investigación de acciones,
  • o simplemente tienes curiosidad por saber si un LLM puede filtrar el mercado sin alucinar,

esto es para ti.

El problema de dejar que un LLM elija acciones

Un modelo de lenguaje es un predictor de texto. Es muy bueno sonando como un analista financiero y muy malo siéndolo.

Pídele "las acciones de semiconductores rentables más baratas" y producirá una lista con total confianza. Pero no tiene acceso en vivo a capitalizaciones de mercado, resultados o valoraciones. Está haciendo pattern-matching contra lo que absorbió durante el entrenamiento, que terminó hace meses.

Los desarrolladores lo descubren por las malas. Las elecciones se ven bien hasta que las compruebas y te das cuenta de que la acción de "PER bajo" que el modelo adoraba ahora cotiza al triple, o hizo split, o fue excluida de cotización.

Esto no se arregla con un mejor prompt. Los datos simplemente no están en el modelo.

Claude no necesita conocer el mercado. Necesita una forma de consultarlo.

Ese único cambio transforma todo el problema.

En el momento en que Claude puede llamar a un screener, deja de adivinar y empieza a orquestar. Decide qué buscar, la API decide qué es cierto de verdad, y Claude razona sobre resultados reales en lugar de inventarlos.

El modelo se convierte en el analista. La API se convierte en el escritorio de datos.

La solución: darle a Claude una API de screener como herramienta

Para la capa de datos usé la API Screener de EODHD. Expone todo el universo de acciones detrás de un endpoint y te deja filtrarlo con condiciones simples.

Le pasas filtros y un orden, y recibes JSON estructurado de vuelta. Cada resultado trae campos sobre los que realmente puedes filtrar:

  • capitalización de mercado
  • beneficio por acción
  • sector y mercado
  • rentabilidad por dividendo

Sin scraping. Sin endpoints no oficiales que se rompen los lunes. Una llamada REST limpia que devuelve la misma forma cada vez, que es exactamente lo que necesitas cuando un LLM va a consumir la salida.

Ese último detalle importa más de lo que parece. El tool use solo funciona si los datos que vuelven son predecibles. Un screener que devuelve JSON limpio y tipado es la diferencia entre un agente que razona y uno que se atasca.

Si quieres seguir el hilo, el endpoint del screener y sus campos de filtro están documentados aquí.

Conectando el screener de EODHD a Claude

La arquitectura tiene tres piezas: una función que ejecuta el screener, una definición de herramienta que se lo describe a Claude, y un bucle que deja que Claude la llame.

Empieza por la función. Es la única parte que habla con el mercado.

import os
import json
import requests
EODHD_API_KEY = os.environ["EODHD_API_KEY"]
def run_screener(filters, sort="market_capitalization.desc", limit=20):
    url = "https://eodhd.com/api/screener"
    params = {
        "api_token": EODHD_API_KEY,
        "fmt": "json",
        "filters": json.dumps(filters),
        "sort": sort,
        "limit": limit,
    }
    r = requests.get(url, params=params, timeout=30)
    r.raise_for_status()
    return r.json()["data"]

Los filtros son solo una lista de [campo, operador, valor]. Acciones tecnológicas rentables por encima de mil millones de capitalización son dos líneas:

run_screener(
    filters=[
        ["sector", "=", "Technology"],
        ["market_capitalization", ">", 1_000_000_000],
        ["earnings_share", ">", 0],
    ],
    sort="earnings_share.desc",
)

Ahora describe esa función a Claude como herramienta. La descripción es la parte que la mayoría se salta a la ligera. No lo hagas. Así es como el modelo sabe qué puede pedir.

import anthropic
client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY from env
screener_tool = {
    "name": "run_screener",
    "description": (
        "Screen US stocks by fundamentals using a live market data API. "
        "Returns matching tickers with fields like code, name, "
        "market_capitalization, earnings_share, sector, and dividend_yield."
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "filters": {
                "type": "array",
                "description": (
                    "List of [field, operator, value]. Fields include "
                    "market_capitalization, earnings_share, sector, exchange, "
                    "dividend_yield. Operators: '=', '>', '<', '>=', '<='."
                ),
                "items": {"type": "array"},
            },
            "sort": {
                "type": "string",
                "description": "field.direction, e.g. 'earnings_share.desc'",
            },
            "limit": {"type": "integer"},
        },
        "required": ["filters"],
    },
}

La última pieza es el bucle. Le das a Claude un objetivo, dejas que pida el screener, le devuelves los datos reales, y dejas que decida.

goal = (
    "Find profitable, reasonably valued US technology stocks. "
    "Profitable means positive earnings per share. Don't limit yourself to "
    "mega-caps; include mid-caps. Return a shortlist of 5 and explain why "
    "each one made the cut."
)
messages = [{"role": "user", "content": goal}]
while True:
    resp = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=2000,
        tools=[screener_tool],
        messages=messages,
    )
    messages.append({"role": "assistant", "content": resp.content})
    if resp.stop_reason != "tool_use":
        print(resp.content[-1].text)
        break
    results = []
    for block in resp.content:
        if block.type == "tool_use" and block.name == "run_screener":
            data = run_screener(**block.input)
            results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": json.dumps(data),
            })
    messages.append({"role": "user", "content": results})

Ese es todo el sistema. Sin base de datos vectorial, sin fine-tuning, sin framework de agentes. Una función, una herramienta, un bucle.

Esto es lo que pasó

Le di a Claude el objetivo de arriba y observé.

Lo primero que hizo no fue elegir acciones. Eligió filtros.

Tradujo "tecnológicas rentables, razonablemente valoradas" a un screen concreto por su cuenta:

[
  ["sector", "=", "Technology"],
  ["market_capitalization", ">", 2000000000],
  ["market_capitalization", "<", 50000000000],
  ["earnings_share", ">", 0]
]

Fíjate en las decisiones. Puso un suelo y un techo en la capitalización de mercado para respetar el "no solo mega-caps". Exigió BPA positivo para lo de "rentables". Nadie le dijo que hiciera eso. Razonó desde el objetivo hasta la consulta.

Después llamó al screener, obtuvo tickers reales, y afinó la lista con una segunda pasada antes de redactar su shortlist.

Una cosa a tener en cuenta: un screener lee el mercado tal como está hoy. Los componentes exactos cambian cada vez que lo ejecutas, a medida que los resultados se actualizan y las capitalizaciones se mueven. Así que esto es una ejecución representativa, no una lista fija. Ejecútalo mañana y los nombres serán distintos.

Así es la forma de la shortlist que devolvió, junto con el razonamiento que adjuntó a cada una:

Lo que Claude eligió en el screener de acciones

Lo que destacó no fueron las elecciones. Fue la honestidad.

Claude señaló sus propios límites sin que se lo pidieran. Anotó que el BPA por sí solo no significa que una acción esté barata, que no tenía estimaciones a futuro, y que esto era una watchlist inicial, no una señal de compra.

Esa es la respuesta correcta. Un screener reduce el universo. No te dice el futuro. El modelo entendió el trabajo mejor de lo que la mayoría de las demos de "selector de acciones con IA" pretenden.

Dónde se quedó corto: con solo campos de foto fija de fundamentales, no pudo razonar sobre momentum, deuda o noticias recientes. Eso necesita más endpoints. El screener es la puerta de entrada, no la casa entera.

Ideas clave

  • El modelo orquesta, la API decide. Claude es bueno convirtiendo un objetivo vago en una consulta precisa. No es una fuente de datos, y en el momento en que lo tratas como tal, alucina.
  • El JSON limpio es la clave. El tool use solo funciona cuando los datos que vuelven son predecibles. La respuesta estructurada del screener es lo que hace fiable el bucle.
  • Reproducible le gana a las vibras. Ejecutas el mismo prompt dos veces y obtienes los mismos filtros contra los mismos datos en vivo. Eso es un sistema que puedes auditar, no un truco de magia.

Preguntas frecuentes

¿Puede la IA elegir acciones de verdad? ✅ No por sí sola. Un modelo de lenguaje no tiene datos de mercado en vivo y se inventará cifras si le dejas. Conectado a una API de screener, puede traducir un objetivo en filtros reales y razonar sobre resultados reales, que es algo muy distinto y mucho más fiable.

¿Cuál es la mejor API de screener de acciones para Python? ✅ Quieres una que devuelva JSON limpio y estructurado y te deje filtrar por fundamentales como capitalización de mercado, BPA y sector. La API Screener de EODHD hace esto a través de un solo endpoint REST, lo que la hace encajar bien para alimentar a un LLM.

¿Necesito un framework de agentes para construir esto? ✅ No. El ejemplo de arriba es Python plano: una función, una definición de herramienta, un bucle. Los frameworks añaden estructura cuando tienes muchas herramientas, pero un solo screener no necesita uno.

¿Es seguro operar basándose en esto? ✅ Trata la salida como una shortlist de investigación, no como una recomendación. Un screener reduce miles de acciones a un puñado que merece un vistazo más de cerca. El análisis posterior sigue siendo cosa tuya.

Pruébalo tú mismo

Consigue una key gratuita de EODHD y ejecuta el bucle de arriba:

👉 Empieza aquí con la API Screener de EODHD

Obtendrás:

  • un único endpoint para filtrar todo el universo de acciones
  • filtros por capitalización de mercado, BPA, sector, rentabilidad por dividendo y más
  • JSON limpio que encaja directamente en un bucle de tool use

El objetivo nunca fue dejar que una IA apostara con tickers.

Fue dejar que el modelo dejara de adivinar, y darle algo cierto con lo que trabajar.

Ese es todo el truco.

¿Buscas contenido técnico para tu empresa? Puedo ayudarte — LinkedIn · kevinmenesesgonzalez@gmail.com