---
title: "0.2.6 Asistentes de código con IA: prompts, revisión y tests"
description: "Qué hacen los asistentes de código con IA, un caso de señal generada con sesgo de anticipación, el test que lo detecta y el prompt que lo habría evitado."
url: "https://pythonparatrading.com/curso-python-trading/nivel-0/asistentes-de-codigo-con-ia/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-0/asistentes-de-codigo-con-ia.png"
---

# 0.2.6 · Introducción a los asistentes de código con IA: prompts, revisión y tests

> Un asistente escribe en segundos una señal de trading que parece correcta. Esta lección sigue un caso completo: la función que propone, el backtest espectacular que produce, el test que demuestra que usa datos del futuro y el prompt que lo habría evitado.

- Curso: [Nivel 0: Python desde cero y mentalidad de datos](https://pythonparatrading.com/curso-python-trading/nivel-0/)
- Módulo: 0.2 · Entorno de trabajo moderno
- Duración: 40 min
- Requisitos: [0.1.3 · Estructura del curso, tests de entrada, repositorio personal y aprendizaje con IA](https://pythonparatrading.com/curso-python-trading/nivel-0/estructura-del-curso-y-aprendizaje-con-ia/), [0.2.3 · Editores (VS Code, Cursor, PyCharm) y notebooks (Jupyter, JupyterLab, marimo)](https://pythonparatrading.com/curso-python-trading/nivel-0/editores-y-notebooks-jupyter-marimo/)

## Al terminar esta lección

- Distinguir autocompletado, chat y agente, y saber qué contexto necesita cada uno para proponer código útil.
- Revisar una función generada por IA buscando los errores típicos del trading: anticipación, costes, huecos y dependencias inventadas.
- Escribir un test de truncado que detecta si una señal usa datos posteriores a la fecha en que se calcula.
- Redactar un prompt con contexto, contrato de datos, regla temporal y tests, coherente con las reglas del curso para usar IA.

## Qué es un asistente de código

Un asistente de código es un modelo de lenguaje entrenado con enormes cantidades de código y texto que propone código a
partir de una descripción y del contexto que le des. Hay tres formas de usarlo, que se diferencian en cuánto hace por
su cuenta:

| Forma | Qué hace | Ejemplos | Qué revisar |
|---|---|---|---|
| Autocompletado | Sugiere la línea o la función que estás escribiendo | Copilot en VS Code, Cursor | Cada sugerencia antes de aceptarla |
| Chat | Responde preguntas y escribe fragmentos que copias tú | Chat del editor, Claude, ChatGPT | El fragmento entero y cómo encaja en tu código |
| Agente | Lee el repositorio, edita ficheros y ejecuta comandos y tests | Claude Code, Codex, el agente de Cursor | El diff completo, como una pull request (lección 0.2.5) |

Las tres comparten una propiedad: el código que producen es **plausible**. Tiene el aspecto del código que resolvería
el problema, con nombres razonables y comentarios convincentes. Muchas veces es también correcto. Pero el modelo no
conoce tus datos, no sabe a qué hora se opera tu señal y no ha ejecutado tu backtest. Lo que no le digas, lo decide
él sin avisar.

En trading esto tiene un coste concreto. Un error de programación normal suele romper algo: una excepción, un gráfico
vacío. Un error de **anticipación** (lección 0.1.1) no rompe nada: produce un backtest mejor. Es el error que más
cuesta ver y el que más dinero hace perder, porque convence.

La lección 0.1.3 fijó tres reglas para usar asistentes. Siguen en pie y esta lección explica el porqué de la
segunda:

1. **Nivel 0 sin asistente.** El código de los ejercicios lo escribes tú. Puedes preguntar dudas conceptuales a un
   chat. Esta lección no cambia eso: enseña a **juzgar** código generado, que es justo lo que no se puede hacer sin
   saber escribirlo.
2. **Después, la IA propone y tú decides**: primero la prueba, después el código, lectura completa, pruebas y commit.
3. **Nada sensible en el prompt**: ni claves, ni datos con licencia restrictiva, ni datos personales.

El resto de la lección es un caso completo, de los que se repiten a diario.

## Un caso: la señal que propone el asistente

Un usuario pide a un chat, sin más contexto:

> Escribe una función de pandas que dé una señal de compra cuando la media móvil de 20 sesiones del precio de cierre
> tenga pendiente positiva. Quiero una media suavizada y sin retraso.

Los precios son los de SINT, el ETF ficticio del curso:

```python
import pandas as pd

precios = pd.read_csv("sint_diario.csv", parse_dates=["fecha"],
                      index_col="fecha")
cierre = precios["cierre"]
rent = cierre.pct_change().fillna(0)
print(len(cierre), "sesiones;", cierre.index[0].date(), "a",
      cierre.index[-1].date())
```
```output
1303 sesiones; 2021-01-04 a 2025-12-31
```

La respuesta, con el aspecto típico de un asistente (docstring, nombres claros, un comentario que justifica la
decisión):

```python {6}
def senal_pendiente_ia(cierre: pd.Series,
                       ventana: int = 20) -> pd.Series:
    """1 si la media móvil sube, 0 si no.

    Se usa una media centrada para eliminar el retraso."""
    media = cierre.rolling(ventana, center=True).mean()
    return (media.diff() > 0).astype(int)
```

El usuario hace lo que aprendió en la lección 0.1.1: desplaza la posición un día para operar después de la señal, y
mide el resultado frente a comprar y mantener:

```python
def backtest(senal: pd.Series) -> pd.Series:
    """Rentabilidad diaria; la señal del cierre se opera al día
    siguiente."""
    posicion = senal.shift(1).fillna(0)
    return posicion * rent

def resumen(r: pd.Series) -> str:
    total = (1 + r).prod() - 1
    sharpe = r.mean() / r.std() * 252 ** 0.5
    return f"total {total:+7.1%}   Sharpe {sharpe:5.2f}"

r_ia = backtest(senal_pendiente_ia(cierre))
print(f"{'Asistente':<20}", resumen(r_ia))
print(f"{'Comprar y mantener':<20}", resumen(rent))
```
```output
Asistente            total +324.1%   Sharpe  2.10
Comprar y mantener   total  +43.5%   Sharpe  0.48
```

Un Sharpe por encima de 2 con una media móvil, en un activo que en el mismo periodo se queda en 0,48. La curva de
la cabecera (en ámbar) sube casi sin retrocesos. Es el momento de sospechar: una regla tan simple no debería batir
así al mercado, y menos en un activo con una caída del 25 % en 2022.

La línea resaltada es el problema: `center=True`. Por defecto, `rolling` etiqueta cada ventana con su **último** día:
la media del 3 de junio usa los 20 cierres que terminan el 3 de junio. Con `center=True`, la etiqueta es el **centro**
de la ventana: la media del 3 de junio usa diez cierres anteriores, el del propio día y nueve posteriores. «Sin retraso», sí, porque mira
al futuro. Se puede comprobar qué días entran en cada ventana sustituyendo los precios por su posición en la serie:

```python
pos = pd.Series(range(len(cierre)), index=cierre.index)
dia = pd.Timestamp("2024-06-03")
for centrada in (False, True):
    v = pos.rolling(20, center=centrada)
    primero = cierre.index[int(v.min()[dia])].date()
    ultimo = cierre.index[int(v.max()[dia])].date()
    print(f"center={centrada!s:<5}: de {primero} a {ultimo}")
```
```output
center=False: de 2024-05-07 a 2024-06-03
center=True : de 2024-05-20 a 2024-06-14
```

Con la media centrada, la señal del 3 de junio sabe cómo cerró el mercado hasta el 14 de junio. Desplazar la posición
un día no sirve de nada: el futuro ya está dentro de la señal, no en el desfase. Y nada en el código lo delata a
simple vista; el comentario incluso lo presenta como una ventaja.

## Un test que lo detecta

Leer con atención ayuda, pero no escala: hay decenas de formas de colar el futuro en una señal (`center=True`,
`shift(-1)`, rellenar huecos hacia atrás con `bfill`, normalizar con la media de toda la muestra…). Una prueba que no
depende de la forma del error es mejor. La idea: **la señal de un día no puede cambiar si se añaden datos
posteriores**. Si se calcula con los datos hasta una fecha y después con todos, las señales de esas fechas deben
coincidir:

```python
def comprobar_sin_anticipacion(funcion, cierre: pd.Series,
                               cortes=(250, 500, 750, 1000)):
    completa = funcion(cierre)
    for n in cortes:
        parcial = funcion(cierre.iloc[:n])
        distintos = int((parcial != completa.iloc[:n]).sum())
        assert distintos == 0, (
            f"hasta {cierre.index[n - 1].date()}: {distintos} días "
            "de la señal cambian al añadir datos posteriores")
```

Es una función con `assert` sencillos: si la condición no se cumple, lanza un error con el mensaje. Con pytest
(lección 0.4.5) se convierte en un test que se ejecuta solo antes de cada commit. Aplicada a la función del
asistente:

```python raises
comprobar_sin_anticipacion(senal_pendiente_ia, cierre)
```
```output
AssertionError: hasta 2021-12-17: 9 días de la señal cambian al añadir datos posteriores
```

Nueve señales de diciembre de 2021 cambian cuando se añaden los cierres posteriores al 17 de diciembre. El test no
sabe nada de medias centradas: solo comprueba una propiedad que cualquier señal honesta cumple. Por eso sirve también
para la próxima función que te proponga un asistente, sea cual sea su error.

La corrección es quitar `center=True`: la media de cada día usa ese día y los 19 anteriores. Tiene retraso, y ese
retraso es el precio de no conocer el futuro. La versión corregida pasa el test:

```python
def senal_pendiente(cierre: pd.Series,
                    ventana: int = 20) -> pd.Series:
    """1 si la media móvil (solo datos pasados) sube; 0 si no."""
    media = cierre.rolling(ventana).mean()
    return (media.diff() > 0).astype(int)

comprobar_sin_anticipacion(senal_pendiente, cierre)
print("Sin anticipación")
r_ok = backtest(senal_pendiente(cierre))
print(f"{'Corregida':<20}", resumen(r_ok))
```
```output
Sin anticipación
Corregida            total   -8.9%   Sharpe -0.07
```

De +324 % a −8,9 %. La estrategia real no bate a comprar y mantener; la del asistente no existía. Las tres curvas de la
figura de la cabecera salen de estas celdas. Fíjate en que la versión corregida es peor: un test bien hecho a menudo
**empeora** el resultado, porque elimina una ventaja falsa. Si después de corregir un error tu backtest mejora, revisa
dos veces.

## El prompt que lo habría evitado

El primer prompt dejaba al modelo decidir lo importante. Uno mejor da el contexto, el contrato de los datos, la regla
temporal explícita y la prueba que tiene que pasar:

> Contexto: backtest diario en pandas 3.0. `cierre` es una `pd.Series` de cierres diarios con índice de fechas
> ordenado y sin huecos.
>
> Tarea: función `senal_pendiente(cierre, ventana=20) -> pd.Series` que devuelva 1 si la media móvil simple de
> `ventana` sesiones sube respecto al día anterior y 0 si no, con el mismo índice que `cierre`.
>
> Regla temporal: la señal del día t solo puede usar cierres hasta t, incluido. Nada de `center=True`, `shift`
> negativo, `bfill` ni estadísticos de toda la muestra. La posición se desplaza fuera de la función.
>
> Debe pasar este test: (pegar `comprobar_sin_anticipacion`).
>
> Sin dependencias nuevas. Explica en una frase qué hace cada línea.

Esto es lo que la regla 2 del curso pide: la prueba la escribes tú, antes, y el asistente la recibe como parte de la
tarea. Si la prueba la escribiera el mismo asistente que la función, podría heredar el mismo malentendido. La última
línea, pedir que explique cada línea, no garantiza nada, pero facilita la lectura completa que tienes que hacer igual.

## Con un agente: instrucciones y permisos

Un agente no recibe un prompt suelto: trabaja dentro del repositorio, lee ficheros, ejecuta comandos y cambia varios
ficheros a la vez. Eso cambia dos cosas.

La primera, el contexto. Lo que en un chat repetirías en cada prompt se escribe una vez en un fichero de instrucciones
del repositorio, que el agente lee al empezar: `CLAUDE.md` en Claude Code, `AGENTS.md` en Codex y en muchos
otros[^2]. Para el proyecto del curso bastan unas líneas:

```markdown title="AGENTS.md"
# Reglas del proyecto ppt-curso
- Python 3.14 con uv. Dependencias solo con `uv add`, nunca pip.
- Señales: el valor del día t usa datos hasta el cierre de t.
  La posición es la señal desplazada un día. Prohibidos
  center=True, shift negativo, bfill y estadísticos de toda la muestra.
- Toda señal nueva pasa comprobar_sin_anticipacion (tests/).
- Antes de terminar: `uv run pytest`. No cambies un test para
  que pase.
- No leas ni muestres `.env`. Ninguna clave en el código.
```

La línea sobre los tests no es un adorno: un agente al que se le pide que «haga pasar los tests» puede conseguirlo
cambiando el test en lugar del código. Por eso el diff se revisa entero, tests incluidos.

La segunda, los permisos. Un agente que ejecuta comandos puede leer cualquier fichero al que tú tengas acceso,
incluido `.env`. Mientras aprendes: claves de papel o ninguna, trabajo en una rama y cambios que entran por pull
request, como los tuyos (lección 0.2.5). Nunca un agente con acceso a una cuenta real del bróker.

## Qué revisar en código generado

Al revisar código generado para trading, esta lista cubre los errores más comunes:

| Revisa | Señales de alarma |
|---|---|
| Anticipación | `center=True`, `shift(-n)`, `bfill`, medias o desviaciones de toda la muestra, modelos ajustados con todos los datos |
| Desfase | Señal y rentabilidad del mismo día multiplicadas sin `shift(1)` |
| Costes | Ninguna comisión ni deslizamiento; operaciones diarias sin coste |
| Huecos | `fillna(0)` en precios, `dropna` que desalinea series, festivos |
| Dependencias | Paquetes que no conoces: compruébalos en PyPI antes de `uv add` |
| Secretos | Claves escritas en el código o impresas en logs (lección 0.2.4) |

<aside class="callout" data-kind="riesgo">

Los modelos inventan nombres de paquetes. Un estudio con 16 modelos y 576.000 muestras de código encontró paquetes
inexistentes en al menos el 5,2 % de las recomendaciones de los modelos comerciales y el 21,7 % de los abiertos[^1]. Hay
atacantes que publican paquetes maliciosos con esos nombres. Antes de `uv add` de algo que no conoces, búscalo en
pypi.org y en la web del proyecto.

</aside>

## Ejercicios

1. Sin ejecutarla, di si esta señal tiene sesgo de anticipación y por qué; después compruébalo con
   `comprobar_sin_anticipacion`:
   `(cierre > cierre.mean()).astype(int)`.

**Solución.**

```python
def senal_sobre_media_total(cierre: pd.Series) -> pd.Series:
    return (cierre > cierre.mean()).astype(int)

try:
    comprobar_sin_anticipacion(senal_sobre_media_total, cierre)
except AssertionError as error:
    print("Anticipación:", error)
```
```output
Anticipación: hasta 2021-12-17: 115 días de la señal cambian al añadir datos posteriores
```

Sí: `cierre.mean()` es la media de los cinco años, así que la señal de 2021 depende de los precios de 2025. Es uno de
los errores de la tabla (estadísticos de toda la muestra) y el test lo detecta sin saber nada de él.

2. Un asistente rellena los huecos de una serie de precios con `cierre.bfill()`. ¿Por qué es anticipación? ¿Con qué lo
   sustituirías?

**Solución.**

`bfill` rellena cada hueco con el **siguiente** valor conocido: el precio de un día festivo o sin datos sería el
cierre de un día posterior. Si la señal usa ese valor, sabe algo del futuro. La alternativa es `ffill` (el último
valor conocido, que sí existía en ese momento) o, mejor aún, no inventar precios: dejar el hueco y no operar ese día.
La limpieza de huecos se trata en el nivel 1.

3. Escribe, sin enviarlo, el prompt que darías a un asistente para una función que calcule la volatilidad móvil de 20
   sesiones anualizada a partir de `cierre`. Incluye contexto, contrato, regla temporal y al menos una prueba.

**Solución.**

Un ejemplo. Contexto: pandas 3.0, `cierre` es una `pd.Series` diaria con índice de fechas ordenado. Tarea:
`volatilidad_movil(cierre, ventana=20) -> pd.Series`, desviación típica muestral (`ddof=1`, lección 0.2.2) de las
rentabilidades simples de las últimas `ventana` sesiones, multiplicada por la raíz de 252, con el mismo índice. Regla
temporal: el valor del día t solo usa cierres hasta t; las primeras sesiones sin ventana completa, `NaN`. Pruebas: el
test de truncado; con una serie de precios constante, la volatilidad es 0; con una serie que alterna +1 % y −1 %, la
volatilidad es positiva y estable. Sin dependencias nuevas.

[^1]: J. Spracklen et al., «We Have a Package for You! A Comprehensive Analysis of Package Hallucinations by Code
    Generating LLMs» (arXiv:2406.10279), consultado el 21 de octubre de 2026.

[^2]: Claude Code Docs, «Cómo Claude recuerda tu proyecto» (code.claude.com/docs/es/memory), y el formato abierto
    AGENTS.md (agents.md), que lista los agentes compatibles; consultados el 21 de octubre de 2026.

## Autoevaluación

1. ¿Por qué `cierre.rolling(20, center=True).mean()` introduce sesgo de anticipación en una señal?

   - A. Porque `center=True` es más lento
   - B. Porque la media del día t incluye los precios de los días siguientes: el valor de hoy depende de cierres que todavía no existen
   - C. Porque una ventana de 20 sesiones es demasiado corta
   - D. No lo introduce si la posición se desplaza un día

   *Respuesta B: Porque la media del día t incluye los precios de los días siguientes: el valor de hoy depende de cierres que todavía no existen.* Con `center=True`, la etiqueta de la ventana es su centro: la media del día t usa diez sesiones anteriores y nueve posteriores. Desplazar la posición un día no lo arregla, porque el futuro ya está dentro de la señal.

2. ¿Qué comprueba el test de truncado de la lección?

   - A. Que la función es rápida
   - B. Que la señal calculada con datos hasta una fecha coincide con la calculada con todos los datos para esas mismas fechas
   - C. Que la señal tiene tantos unos como ceros
   - D. Que el backtest gana dinero

   *Respuesta B: Que la señal calculada con datos hasta una fecha coincide con la calculada con todos los datos para esas mismas fechas.* Si añadir datos futuros cambia la señal de días pasados, la señal usaba información que en su momento no existía. Es una propiedad que cualquier señal debe cumplir, sea cual sea la regla.

3. Un asistente propone `uv add pandas-ta-pro` para calcular un indicador. ¿Qué haces antes de instalarlo?

   - A. Instalarlo: si el asistente lo nombra, existe
   - B. Comprobar en PyPI y en la web del proyecto que el paquete existe, quién lo publica y si es el que buscas
   - C. Instalarlo con `pip` en lugar de `uv`
   - D. Pedirle al asistente que confirme que existe

   *Respuesta B: Comprobar en PyPI y en la web del proyecto que el paquete existe, quién lo publica y si es el que buscas.* Los modelos inventan nombres de paquetes con frecuencia, y hay atacantes que publican paquetes maliciosos con esos nombres. El asistente no puede comprobarlo por ti: lo compruebas tú en la fuente.

4. Según las reglas del curso, ¿cuál es el orden correcto a partir del nivel 1?

   - A. Pedir el código, ejecutarlo, y si el backtest sale bien, commit
   - B. Escribir la prueba, pedir el código, leerlo entero, ejecutar las pruebas y solo entonces commit
   - C. Pedir el código y las pruebas al asistente a la vez y hacer commit
   - D. Escribir el código a mano y pedir al asistente que lo apruebe

   *Respuesta B: Escribir la prueba, pedir el código, leerlo entero, ejecutar las pruebas y solo entonces commit.* La prueba, escrita por ti antes, fija lo que el código tiene que cumplir. Si la escribe el mismo asistente que el código, puede heredar el mismo error. Un backtest que «sale bien» no es una prueba: el de la lección salía espectacular.

## Ideas clave

- Autocompletado, chat y agente: cuanto más actúa el asistente por su cuenta, más contexto necesita y más hay que revisar.
- El código generado es plausible, no necesariamente correcto. En trading, el error típico es usar datos del futuro sin que se note.
- Un backtest espectacular es un motivo para sospechar, no para hacer commit.
- El test de truncado (la señal hasta t no cambia al añadir datos posteriores) detecta la anticipación en cualquier señal.
- Un buen prompt da el contrato de datos, la regla temporal y los tests que el código debe pasar. En el nivel 0, el código lo escribes tú.

## Recursos

### Research

- [Tu primer sistema en Python: cruce de medias móviles sobre SPY](https://pythonparatrading.com/primer-sistema-cruce-de-medias/) · Research-0018 · 7 min

### Documentación

- [pandas: Series.rolling (parámetro center)](https://pandas.pydata.org/docs/reference/api/pandas.Series.rolling.html) · pandas.pydata.org
- [Documentación de GitHub Copilot](https://docs.github.com/es/copilot) · docs.github.com
- [Claude Code: descripción general](https://code.claude.com/docs/es/overview) · code.claude.com

### Datos

- [Datos del nivel 0: precios diarios del ETF ficticio SINT (CSV)](https://pythonparatrading.com/curso/datos/nivel-0/sint_diario.csv) · Descarga · sint_diario.csv

Lección anterior: [0.2.5 · Git y GitHub: commits, ramas, pull requests y .gitignore](https://pythonparatrading.com/curso-python-trading/nivel-0/git-y-github-para-trading/)

Lección siguiente: [0.3.1 · Variables, tipos y operadores; coma flotante y decimal para precios y ticks](https://pythonparatrading.com/curso-python-trading/nivel-0/variables-tipos-y-decimal/)
