Saltar al contenido
ALX Quant
← M0.5 · Puesta a punto: Python y tu primer script

Lección 225 min60 XPGratis

Tu primer script quant: de precios a métricas en 40 líneas

Al terminar sabrás

  • Ejecutar y leer un script completo que carga precios y calcula retorno, volatilidad y drawdown
  • Entender la estructura (funciones, __main__, argumentos) que usará todo el código del curso
  • Modificar el script para un dato distinto y comprobar el resultado a mano

Antes de esta conviene haber hecho Instalar Python sin romper nada: uv, VSCode y tu primer entorno.


Cuarenta líneas de Python hacen lo que en M0.1 hiciste con calculadora: cargar una serie de precios, calcular retornos, volatilidad anualizada y drawdown máximo. No necesitas saber programar para ejecutarlo. Necesitas ejecutarlo para empezar a saber programar.

El problema

El código de trading que circula por internet es ilegible: cientos de líneas sin funciones, variables llamadas x1, todo en un bloque. Aprender con eso es aprender mal. Este script está escrito como todo el código de la academia: pequeño, en funciones con nombre, reproducible, y con el cálculo separado de la carga de datos. Si lo entiendes, entiendes la estructura de todo lo que viene.

Tres capas

Intuición

Un script es una receta que el ordenador sigue de arriba abajo. Este tiene tres partes:

  1. Cargar: consigue los precios. Si le das un archivo CSV, lo lee; si no, se inventa un año de precios con la semilla 42, para que puedas probar sin datos.
  2. Calcular: a partir de los precios, saca los retornos diarios (M0.1.2), la volatilidad anualizada y el drawdown (M0.1.3), y el mejor y peor día.
  3. Mostrar: imprime los resultados en pantalla.

Cada parte es una función, una caja con un nombre que recibe algo y devuelve algo. cargar recibe una ruta y devuelve precios. metricas recibe precios y devuelve un diccionario de números. Separarlas es lo que permite cambiar los datos sin tocar el cálculo, o cambiar el cálculo sin tocar los datos.

Ejemplo numérico

Ejecutas uv run python primer_script.py sin argumentos. El script genera 252 días de precios simulados y calcula:

MétricaValorDe dónde sale
Días251252 precios dan 251 retornos
Retorno total−7,19 %Producto de (1 + r) menos 1
Volatilidad anual17,93 %Desviación típica diaria × √252
Drawdown máximo−17,80 %Mínimo de equity / máximo acumulado − 1
Día del máximo drawdown2025-12-11El día en que la caída desde el máximo fue mayor
Mejor día+3,59 %Máximo retorno diario
Peor día−2,51 %Mínimo retorno diario

Los precios se generaron con media diaria positiva (0,03 %) y sin embargo el año acabó en −7 %. Es M0.1.3 otra vez: con 252 días, la media que ves no es la media que hay. Un drawdown del 18 % en un activo con volatilidad del 18 %: un año entero de sustos para acabar por debajo de donde empezaste.

Formal

Estructura del script (patrón que repite todo el código de la academia):

  • Cabecera: comentario con versiones probadas y uso. Lo primero que lee quien abre el archivo.
  • Imports al principio, agrupados: estándar (sys), científicos (numpy, pandas).
  • Funciones puras con anotaciones de tipo (ruta: str | None -> pd.Series) y docstring de una línea. cargar tiene efectos (lee disco); metricas es pura: mismos precios, mismo resultado, sin leer ni escribir nada.
  • Bloque if __name__ == "__main__":: lo que se ejecuta al lanzar el archivo directamente, y no cuando otro archivo hace from primer_script import metricas. Separa la lógica reutilizable del punto de entrada.
  • Argumentos vía sys.argv: python primer_script.py datos.csv pasa la ruta. Para más de un argumento se usará argparse (Nivel 2).

Detalles de pandas que importan: pct_change() calcula Rt=Pt/Pt−1−1R_t = P_t/P_{t-1} - 1 y deja un NaN en la primera fila que dropna() elimina; cumprod() es el producto acumulado (agregación temporal correcta de retornos simples, M0.1.2); cummax() el máximo acumulado (el high-water mark); idxmin() devuelve el índice (la fecha) del mínimo, no el valor. std(ddof=1) es la desviación típica muestral. El índice de la Serie es de fechas con zona horaria UTC (M0.2.5).

La semilla fija (default_rng(42)) hace que el modo simulado sea idéntico en cualquier máquina: si tu salida no coincide con la de la lección al decimal, algo es distinto en tu entorno o en tu código, y eso es exactamente la comprobación que queremos.

Ejemplo trabajado con código

# Python 3.11+ · pandas 3.0 · numpy 2.4 — Tu primer script quant: de una serie de precios a métricas, en 40 líneas
# Uso: python primer_script.py [ruta.csv]   Sin argumento, genera datos simulados con semilla fija.
import sys
import numpy as np
import pandas as pd
 
def cargar(ruta: str | None) -> pd.Series:
    """Devuelve una Serie de cierres diarios indexada por fecha (UTC)."""
    if ruta:
        df = pd.read_csv(ruta, parse_dates=["fecha"])
        return df.set_index("fecha")["cierre"].astype(float).sort_index()
    rng = np.random.default_rng(42)                                    # simulado, reproducible
    fechas = pd.bdate_range("2025-01-01", periods=252, tz="UTC")
    retornos = rng.normal(0.0003, 0.012, len(fechas))
    return pd.Series(5000 * np.exp(np.cumsum(retornos)), index=fechas, name="cierre")
 
def metricas(cierre: pd.Series) -> dict:
    r = cierre.pct_change().dropna()                                   # retornos simples (M0.1.2)
    equity = (1 + r).cumprod()
    dd = equity / equity.cummax() - 1                                  # drawdown (M0.1.3)
    return {
        "dias": len(r),
        "retorno_total": equity.iloc[-1] - 1,
        "vol_anual": r.std(ddof=1) * np.sqrt(252),
        "max_drawdown": dd.min(),
        "dia_max_dd": dd.idxmin().date(),
        "mejor_dia": r.max(),
        "peor_dia": r.min(),
    }
 
if __name__ == "__main__":
    ruta = sys.argv[1] if len(sys.argv) > 1 else None
    cierre = cargar(ruta)
    print(f"Fuente: {'CSV ' + ruta if ruta else 'simulado (semilla 42)'} | {cierre.index[0].date()} → {cierre.index[-1].date()}")
    for k, v in metricas(cierre).items():
        print(f"  {k:15s} {v:>12.2%}" if isinstance(v, float) else f"  {k:15s} {v!s:>12}")
Fuente: simulado (semilla 42) | 2025-01-01 → 2025-12-18
  dias                     251
  retorno_total         -7.19%
  vol_anual             17.93%
  max_drawdown         -17.80%
  dia_max_dd        2025-12-11
  mejor_dia              3.59%
  peor_dia              -2.51%

Figura

El script dibujado como tres cajas apiladas conectadas por flechas: 'cargar(ruta) → Serie de cierres' arriba, 'metricas(cierre) → dict' en medio, 'main: imprime' abajo. A la izquierda de cargar, dos entradas alternativas: 'CSV con fecha,cierre' y 'simulado, semilla 42'. A la derecha de metricas, las siete métricas listadas. Una nota en teal junto a metricas: 'pura: mismos precios, mismo resultado'.
Cargar y calcular están separados a propósito. Mañana cambias la fuente de datos y el cálculo no se entera.

(Figura estática en SVG.)

El error típico

Copiar código de internet, ejecutarlo, ver que imprime números, y creer que está bien porque no dio error. Que un script no falle no significa que calcule lo correcto: puede sumar retornos en vez de multiplicarlos (M0.1.2) o calcular el drawdown respecto al precio inicial en vez del máximo acumulado (M0.1.3) y no dar ningún error. La defensa es la de esta lección: ejecutar con datos pequeños que puedas comprobar a mano (la serie de 10 precios del Nivel 0) y verificar que el número coincide con tu calculadora antes de confiar en el script con datos grandes.

Quiz

5 preguntas · se aprueba con 60 %

  1. 1.¿Por qué `metricas` está separada de `cargar`?

  2. 2.`cierre.pct_change().dropna()` devuelve:

  3. 3.`equity / equity.cummax() - 1` calcula:

  4. 4.El bloque `if __name__ == "__main__":`:

  5. 5.Ejecutas el script en modo simulado en tu ordenador y el retorno total no es −7,19 %. Conclusión:

0 de 5 respondidas

Ejercicio

  1. Crea data/serie10.csv con dos columnas, fecha,cierre, y los 10 precios del Nivel 0 (100, 102, 99, 101, 105, 103, 98, 96, 100, 104) en 10 días laborables cualesquiera. Ejecuta uv run python primer_script.py data/serie10.csv.
  2. Comprueba a mano el retorno total y el drawdown máximo.
  3. Añade a metricas una entrada "retorno_medio_diario" y otra "log_retorno_total" (suma de log-retornos). Comprueba que exp(log_total) − 1 coincide con el retorno total.

Resultado verificable: retorno total +4,00 %; volatilidad anual 54,70 % (3,45 % × √252); drawdown máximo −8,57 % el día del precio 96 (M0.1.3); log-retorno total 0,03922 (M0.1.2). Si vol_anual te sale 50 %, estás usando ddof=0.

Qué te llevas

  1. Cargar, calcular, mostrar: tres funciones, no un bloque. El cálculo es puro y se prueba con datos pequeños.
  2. pct_change, cumprod, cummax, idxmin: cuatro métodos de pandas que calculan medio Nivel 0.
  3. Si un script con semilla fija no da el mismo número que la lección, algo no es reproducible. Encuéntralo antes de seguir.

Siguiente lección: Descargar datos reales: tu primer CSV de mercado. De Binance Vision a tu disco en 30 líneas, y las cuatro cosas que hay que comprobar antes de usarlos.