Lección 0.4.4 · 50 min

Ficheros (CSV, JSON, YAML, TOML) y fechas con zonas horarias

Los datos de un sistema de trading viven en ficheros y sus horas viven en zonas horarias. Esta lección cubre los cuatro formatos de texto habituales con pathlib y el módulo zoneinfo, con el que la apertura de Nueva York deja de ser «a las 15:30» y pasa a ser lo que es: una hora local que se mueve.

Fig. — Hora de Madrid a la que abre la Bolsa de Nueva York (09:30 en Nueva York), cada lunes de 2026
04/06
Módulo
0.4 · Python intermedio
Duración
50 min
Lecciones del nivel 0
  1. 0.1 · Bienvenida y expectativas

    1. 0.1.1 Trading sistemático, algorítmico y cuantitativo frente al discrecional
    2. 0.1.2 Expectativas realistas: costes, tiempo y riesgo de pérdida
    3. 0.1.3 Estructura del curso, tests de entrada, repositorio personal y aprendizaje con IA
  2. 0.2 · Entorno de trabajo moderno

    1. 0.2.1 Instalación de Python con uv: versiones y ciclo de soporte
    2. 0.2.2 Entornos virtuales y dependencias: uv init, uv add, pyproject.toml y lockfiles
    3. 0.2.3 Editores (VS Code, Cursor, PyCharm) y notebooks (Jupyter, JupyterLab, marimo)
    4. 0.2.4 Terminal básica, rutas, variables de entorno y ficheros .env
    5. 0.2.5 Git y GitHub: commits, ramas, pull requests y .gitignore
    6. 0.2.6 Introducción a los asistentes de código con IA: prompts, revisión y tests
  3. 0.3 · Fundamentos del lenguaje

    1. 0.3.1 Variables, tipos y operadores; coma flotante y decimal para precios y ticks
    2. 0.3.2 Cadenas, f-strings y formato de números y fechas
    3. 0.3.3 Listas, tuplas, diccionarios, conjuntos y comprensiones
    4. 0.3.4 Control de flujo: if, elif, else, for, while y match-case
    5. 0.3.5 Funciones: argumentos, *args y **kwargs, lambdas y ámbito
    6. 0.3.6 Excepciones; módulos y paquetes, con los imports perezosos de Python 3.15
  4. 0.4 · Python intermedio

    1. 0.4.1 Programación orientada a objetos: clases, herencia, composición y dataclasses
    2. 0.4.2 Tipado: type hints, mypy o ty y Pydantic para configuraciones
    3. 0.4.3 Iteradores, generadores, decoradores y context managers
    4. 0.4.4 Ficheros (CSV, JSON, YAML, TOML) y fechas con zonas horarias
    5. 0.4.5 Logging profesional y testing con pytest
    6. 0.4.6 Concurrencia: threading, multiprocessing, asyncio y free-threading
  5. 0.5 · Computación numérica y datos

    1. 0.5.1 NumPy 2: arrays, broadcasting, vectorización, álgebra lineal y generadores aleatorios
    2. 0.5.2 pandas 3.0: Series, DataFrame, índices temporales, Copy-on-Write, tipo str y pd.col()
    3. 0.5.3 Selección, filtrado, groupby, merge, pivot, resample y ventanas móviles
    4. 0.5.4 Polars: API lazy frente a eager, expresiones, rendimiento y cuándo elegirlo frente a pandas
    5. 0.5.5 DuckDB y SQL esencial: SELECT, JOIN, GROUP BY y funciones de ventana sobre Parquet
  6. 0.6 · Visualización

    1. 0.6.1 Matplotlib orientado a objetos, Seaborn y Plotly: gráficos de precios, drawdown y velas
    2. 0.6.2 Dashboards rápidos con Streamlit: un panel de seguimiento en un solo script
  7. 0.7 · Matemáticas y estadística mínimas

    1. 0.7.1 Rentabilidades simples y logarítmicas, interés compuesto y estadística descriptiva
    2. 0.7.2 Probabilidad y distribuciones, correlación frente a causalidad y regresión simple
    3. 0.7.3 Álgebra lineal intuitiva: vectores y matrices como preparación para carteras

Rutas con pathlib

Un sistema de trading lee precios, configuraciones y operaciones, y escribe informes, registros y resultados de backtests. Antes de abrir un fichero hay que saber dónde está, y las rutas escritas como texto ("datos/" + simbolo + ".csv") se rompen con facilidad: barras de más o de menos, separadores distintos en Windows y Linux, extensiones duplicadas.

pathlib.Path representa una ruta como un objeto. El operador / une partes, y sus atributos dan el nombre, la extensión o la carpeta sin manipular texto:

IN [01]python
from pathlib import Path

for p in sorted(Path(".").glob("*.csv")):
    print(f"{p.name:<22} {p.stem:<16} {p.stat().st_size:>8,} B")

informes = Path("informes") / "2025" / "sint"
informes.mkdir(parents=True, exist_ok=True)
destino = informes / "resumen.csv"
print(destino, destino.suffix, destino.parent.exists())
OUT [01]Los ficheros de datos del nivel
cartera_sint.csv       cartera_sint       49,207 B
operaciones_sint.csv   operaciones_sint    2,440 B
sint_diario.csv        sint_diario        60,757 B
informes/2025/sint/resumen.csv .csv True

glob("*.csv") lista los ficheros que encajan con el patrón (rglob busca también en subcarpetas). mkdir con parents=True crea las carpetas intermedias y con exist_ok=True no falla si ya existen: un script de informes puede ejecutarse cada día sin comprobar nada antes.

Las rutas relativas, como Path(".") o "sint_diario.csv", se resuelven desde el directorio de trabajo, el lugar desde el que se lanzó el programa (lección 0.2.4). Un script que funciona en la terminal y falla en un programador de tareas casi siempre tiene ese problema. La solución habitual es construir las rutas a partir de la ubicación del propio script: Path(__file__).parent / "datos".

CSV: todo llega como texto

CSV es el formato más común para precios y operaciones: una fila por registro, columnas separadas por comas y una cabecera. El módulo csv de la biblioteca estándar se encarga de las comillas y los separadores; DictReader entrega cada fila como un diccionario con las claves de la cabecera. Lo que no hace es convertir tipos: todo llega como texto.

IN [02]python
import csv

with open("operaciones_sint.csv", newline="", encoding="utf-8") as f:
    filas = list(csv.DictReader(f))

print(len(filas), list(filas[0]))
print(filas[0]["cantidad"], type(filas[0]["cantidad"]).__name__)

def pnl(fila: dict[str, str]) -> float:
    signo = 1 if fila["lado"] == "largo" else -1
    bruto = signo * int(fila["cantidad"]) * (
        float(fila["precio_salida"]) - float(fila["precio_entrada"]))
    return bruto - float(fila["comision"])

por_simbolo: dict[str, float] = {}
for fila in filas:
    s = fila["simbolo"]
    por_simbolo[s] = por_simbolo.get(s, 0.0) + pnl(fila)
for s, total in sorted(por_simbolo.items()):
    print(f"{s:<5} {total:>10,.2f}")
OUT [02]Leer las operaciones de SINT
40 ['id', 'fecha_entrada', 'fecha_salida', 'simbolo', 'lado', 'cantidad', 'precio_entrada', 'precio_salida', 'comision']
200 str
ORO    -3,237.96
RVAR       17.09
SINT    4,405.08
TECH      -35.02

"200" es texto: sumarlo daría un error y multiplicarlo repetiría la cadena (lección 0.4.2). Por eso cada campo numérico pasa por int() o float() antes de operar. pandas (lección 0.5.2) infiere los tipos solo, y aun así conviene revisarlos.

Escribir es simétrico. csv.writer recibe filas como listas y se encarga de separadores y comillas. El argumento newline="" en open no es decorativo: sin él, en Windows cada fila acabaría con un salto de línea de más.

IN [03]python
with destino.open("w", newline="", encoding="utf-8") as f:
    w = csv.writer(f)
    w.writerow(["simbolo", "pnl"])
    for s, total in sorted(por_simbolo.items()):
        w.writerow([s, round(total, 2)])

print(destino.read_text(encoding="utf-8"))
OUT [03]Escribir un resumen
simbolo,pnl
ORO,-3237.96
RVAR,17.09
SINT,4405.08
TECH,-35.02

Una trampa típica en España: los CSV exportados desde una hoja de cálculo en español usan ; como separador y coma decimal (142,22). float("142,22") lanza ValueError. Se lee con csv.reader(f, delimiter=";") y se cambia la coma por un punto antes de convertir, o con pd.read_csv(sep=";", decimal=","). Lo que nunca hay que hacer es quitar la coma: 14222 es otro precio.

JSON: sin fechas ni decimales

JSON es el formato de las APIs: casi todos los brókeres y proveedores de datos responden en JSON. Admite objetos (diccionarios), listas, texto, números, true, false y null. Nada más: ni fechas, ni Decimal, ni conjuntos.

IN [04]python
import json
from datetime import date

resumen = {"estrategia": "cruce-50-200", "fecha": date(2025, 12, 31),
           "operaciones": len(filas),
           "pnl": {s: round(v, 2) for s, v in por_simbolo.items()}}
try:
    json.dumps(resumen)
except TypeError as e:
    print("TypeError:", e)

resumen["fecha"] = resumen["fecha"].isoformat()
texto = json.dumps(resumen, ensure_ascii=False, indent=2)
Path("resumen.json").write_text(texto, encoding="utf-8")
leido = json.loads(Path("resumen.json").read_text(encoding="utf-8"))
print(leido["fecha"], type(leido["fecha"]).__name__)
print(leido == resumen)
OUT [04]Guardar un resumen en JSON
TypeError: Object of type date is not JSON serializable
2025-12-31 str
True

La fecha se guarda como texto ISO 8601 (2025-12-31) y vuelve como texto: quien lee el JSON tiene que saber que ese campo es una fecha y convertirlo con date.fromisoformat. Es el trabajo que hace Pydantic (lección 0.4.2) cuando valida la respuesta de una API: el modelo dice que el campo es una fecha y Pydantic la convierte.

Dos detalles más. ensure_ascii=False deja las tildes como tales en lugar de ó. Y json.dumps escribe NaN para un float no numérico, que no es JSON estándar: muchos lectores, incluidos los navegadores, lo rechazan. Con allow_nan=False el error salta al escribir y no al leer.

YAML y TOML: configuraciones

Las configuraciones se escriben a mano, y para eso JSON es incómodo: no admite comentarios y exige comillas en todo. Los dos formatos habituales son YAML y TOML. YAML se lee con PyYAML (lección 0.4.2); TOML, con tomllib, de la biblioteca estándar desde Python 3.11.

YAML adivina los tipos a partir del texto, y a veces adivina mal. PyYAML sigue la versión 1.1 del estándar, donde yes, no, on y off son booleanos. Hay empresas que cotizan con esos símbolos:

IN [05]python
import yaml

Path("config.yaml").write_text("""\
simbolos: [SPY, ON, NO, "ON"]
inicio: 2021-01-04
version_modelo: 1.10
""", encoding="utf-8")
print(yaml.safe_load(Path("config.yaml").read_text(encoding="utf-8")))
OUT [05]YAML adivina los tipos
{'simbolos': ['SPY', True, False, 'ON'], 'inicio': datetime.date(2021, 1, 4), 'version_modelo': 1.1}

ON (onsemi, en el Nasdaq) se ha convertido en True y NO en False; solo el "ON" entre comillas sigue siendo texto. La fecha sin comillas se ha convertido en un date y la versión 1.10, en el número 1.1. En una lista de símbolos el error no lanza ninguna excepción: la estrategia simplemente no opera esos activos, o falla mucho después al pedir el precio de True. Por eso las configuraciones se validan con un modelo (lección 0.4.2) que exige list[str].

TOML exige comillas en todos los textos, así que no tiene ese problema. Es el formato de pyproject.toml (lección 0.2.2). tomllib.load necesita el fichero abierto en modo binario ("rb"):

IN [06]python
import tomllib

Path("estrategia.toml").write_text("""\
[estrategia]
nombre = "cruce"
simbolos = ["SPY", "ON", "NO"]
inicio = 2021-01-04

[costes]
comision_pb = 1.5
deslizamiento_pb = 3.0
""", encoding="utf-8")
with open("estrategia.toml", "rb") as f:
    cfg = tomllib.load(f)
print(cfg["estrategia"])
print(sum(cfg["costes"].values()))
OUT [06]TOML con tomllib
{'nombre': 'cruce', 'simbolos': ['SPY', 'ON', 'NO'], 'inicio': datetime.date(2021, 1, 4)}
4.5

Los símbolos siguen siendo texto y la fecha, una fecha. tomllib solo lee: para escribir TOML hace falta un paquete externo (tomli-w). Encaja con su uso: una configuración se escribe a mano y el programa la lee.

FormatoLectura en PythonComentariosTiposUso típico
CSVcsv (stdlib), pandasNoTodo textoPrecios, operaciones
JSONjson (stdlib)NoBásicos, sin fechasAPIs, resultados
YAMLPyYAML (safe_load)SíLos adivina (cuidado)Configuraciones
TOMLtomllib (stdlib, solo lee)SíExplícitos, con fechaspyproject.toml, configuraciones

Fechas con zona horaria: zoneinfo

Un datetime sin zona horaria (naive) es una hora de reloj sin lugar: «9 de marzo de 2026, 09:30» no dice si es en Nueva York, en Madrid o en Tokio. Uno con zona (aware) sí representa un instante concreto. zoneinfo, en la biblioteca estándar desde Python 3.9, da las zonas de la base de datos IANA, con su historia completa de cambios de hora, por nombre: America/New_York, Europe/Madrid, Asia/Tokyo.

La sesión principal de la NYSE va de 09:30 a 16:00, hora de Nueva York.1Nota 1 · NYSE, «Holidays & Trading Hours»: Core Trading Session: 9:30 a.m. to 4:00 p.m. ET. Enlace en «Recursos». Convertir la apertura a otras zonas es una llamada a astimezone:

IN [07]python
from datetime import datetime, time, timedelta
from zoneinfo import ZoneInfo

NY = ZoneInfo("America/New_York")
MAD = ZoneInfo("Europe/Madrid")
UTC = ZoneInfo("UTC")

for dia in (date(2026, 3, 2), date(2026, 3, 9), date(2026, 3, 30)):
    apertura = datetime.combine(dia, time(9, 30), NY)
    print(apertura.isoformat(), "→",
          apertura.astimezone(MAD).strftime("%H:%M Madrid"), "|",
          apertura.astimezone(UTC).strftime("%H:%M UTC"))
OUT [07]La apertura de Nueva York en Madrid y en UTC
2026-03-02T09:30:00-05:00 → 15:30 Madrid | 14:30 UTC
2026-03-09T09:30:00-04:00 → 14:30 Madrid | 13:30 UTC
2026-03-30T09:30:00-04:00 → 15:30 Madrid | 13:30 UTC

La misma apertura, a la misma hora de Nueva York, cae a las 15:30 de Madrid el 2 de marzo, a las 14:30 el 9 y otra vez a las 15:30 el 30. El desfase sale de las reglas del cambio de hora: Estados Unidos adelanta el reloj el segundo domingo de marzo y lo retrasa el primero de noviembre;2Nota 2 · NIST, «Daylight Saving Time Rules»: el horario de verano empieza el segundo domingo de marzo a las 2:00 y termina el primer domingo de noviembre a las 2:00, hora local. Enlace en «Recursos». la Unión Europea lo hace el último domingo de marzo y el último de octubre.3Nota 3 · Directiva 2000/84/CE, incorporada en España por el Real Decreto 236/2002: el último domingo de marzo y el último domingo de octubre, a la 1:00 UTC. Enlace en «Recursos». Entre esas fechas, la diferencia entre las dos ciudades es de 5 horas en lugar de 6. En 2026, en días hábiles:

IN [08]python
def diferencia(dia: date) -> timedelta:
    """Horas entre Madrid y Nueva York a la apertura de la NYSE."""
    ap = datetime.combine(dia, time(9, 30), NY)
    return ap.astimezone(MAD).utcoffset() - ap.utcoffset()

dias = [date(2026, 1, 1) + timedelta(days=i) for i in range(365)]
habiles = [d for d in dias if d.weekday() < 5]
raros = [d for d in habiles if diferencia(d) != timedelta(hours=6)]
print(len(raros), "días hábiles con", diferencia(raros[0]))
tramos = [[raros[0]]]
for ayer, hoy in zip(raros, raros[1:]):
    if (hoy - ayer).days > 3:
        tramos.append([])
    tramos[-1].append(hoy)
for t in tramos:
    print(f"  del {t[0]} al {t[-1]}: {len(t)} días")
OUT [08]Los días de 2026 con 5 horas de diferencia
20 días hábiles con 5:00:00
  del 2026-03-09 al 2026-03-27: 15 días
  del 2026-10-26 al 2026-10-30: 5 días

Veinte días hábiles de 2026 en los que Nueva York abre una hora antes para quien opera desde Madrid: del 9 al 27 de marzo y del 26 al 30 de octubre. La figura de la cabecera dibuja cada lunes de 2026 con la hora de Madrid de la apertura; las cuatro semanas destacadas son las del desfase.

Las consecuencias son prácticas. Un programa que lanza las órdenes «a las 15:25 de Madrid, cinco minutos antes de la apertura» llega una hora tarde esas semanas. Un filtro que descarta «la primera media hora de Nueva York» con horas de Madrid fijas se aplica a la hora equivocada. Y un backtest con barras intradía en hora local de Madrid mezcla dos horarios de sesión sin que nadie lo note. La solución es siempre la misma: expresar los horarios de cada mercado en su propia zona y convertir.

Horas que no existen y horas que se repiten

El cambio de hora crea dos anomalías en la hora local. En Madrid, el 29 de marzo de 2026 el reloj salta de 02:00 a 03:00: las 02:30 de ese día no existen. El 25 de octubre, de 03:00 vuelve a 02:00: las 02:30 ocurren dos veces. zoneinfo no lanza errores; el atributo fold (0 o 1) elige entre las dos ocurrencias de una hora repetida:

IN [09]python
inexistente = datetime(2026, 3, 29, 2, 30, tzinfo=MAD)
print(inexistente.astimezone(UTC).astimezone(MAD))

for fold in (0, 1):
    repetida = datetime(2026, 10, 25, 2, 30, tzinfo=MAD, fold=fold)
    print(fold, repetida.astimezone(UTC).strftime("%H:%M UTC"),
          repetida.tzname())

try:
    print(datetime(2026, 3, 9, 15, 0) < datetime.now(UTC))
except TypeError as e:
    print("TypeError:", e)
OUT [09]Horas inexistentes y repetidas en Madrid
2026-03-29 03:30:00+02:00
0 00:30 UTC CEST
1 01:30 UTC CET
TypeError: can't compare offset-naive and offset-aware datetimes

La hora inexistente, ida y vuelta por UTC, se convierte en las 03:30 de verano. La repetida tiene dos instantes distintos, separados una hora: 00:30 y 01:30 UTC. Y Python se niega a comparar una fecha sin zona con otra con zona, en lugar de suponer nada.

Los mercados casi nunca negocian a esas horas, pero los datos sí se generan: los ficheros de criptomonedas, que cotizan las 24 horas, o los registros de un servidor en hora local, tienen una hora duplicada cada otoño y un hueco cada primavera. Las reglas que evitan casi todos los problemas:

  • Guarda en UTC. Las marcas de tiempo de datos, órdenes y registros van en UTC, que no tiene cambios de hora.
  • Convierte al mostrar. La hora local es para personas: informes, gráficos, avisos.
  • Define los horarios de mercado en su zona. «09:30 America/New_York», no «15:30 de Madrid».
  • Nunca mezcles naive y aware. Si un fichero trae fechas sin zona, decide al leerlo en qué zona están y conviértelas.

El desfase entre cierres se calcula igual que antes, cada uno en su zona:

IN [10]python
dia = date(2025, 12, 31)
cierre_bme = datetime.combine(dia, time(17, 30), MAD)
cierre_nyse = datetime.combine(dia, time(16, 0), NY)
print(cierre_bme.astimezone(UTC).strftime("BME   %H:%M UTC"))
print(cierre_nyse.astimezone(UTC).strftime("NYSE  %H:%M UTC"))
print("NYSE cierra", cierre_nyse - cierre_bme, "después")
OUT [10]Dos cierres con la misma fecha
BME   16:30 UTC
NYSE  21:00 UTC
NYSE cierra 4:30:00 después

Las restas entre fechas aware dan la duración real, porque Python las convierte a UTC antes de restar. Restar dos horas locales sin zona en días de cambio de hora daría una hora de más o de menos.

Ejercicios

  1. Calcula a qué hora de Madrid abre la Bolsa de Tokio (09:00, Asia/Tokyo) el 2 de marzo y el 30 de marzo de 2026. ¿Por qué cambia, si Japón no tiene horario de verano?
Solución
IN [11]python
TOK = ZoneInfo("Asia/Tokyo")
for dia in (date(2026, 3, 2), date(2026, 3, 30)):
    ap = datetime.combine(dia, time(9, 0), TOK)
    print(dia, ap.astimezone(MAD).strftime("%H:%M Madrid (%Z)"))
OUT [11]Tokio desde Madrid
2026-03-02 01:00 Madrid (CET)
2026-03-30 02:00 Madrid (CEST)

Japón no cambia de hora; cambia Madrid. En invierno (CET, UTC+1) Tokio va 8 horas por delante; en verano (CEST, UTC+2), 7. Un horario de mercado se mueve visto desde fuera aunque el mercado no cambie nada.

  1. Lee operaciones_sint.csv y escribe en largas.json solo las operaciones largas de SINT, con cantidad como entero, los precios como float y las fechas como texto ISO. Comprueba cuántas hay leyendo el JSON.
Solución
IN [12]python
largas = [
    {"entrada": f["fecha_entrada"], "salida": f["fecha_salida"],
     "cantidad": int(f["cantidad"]),
     "precio_entrada": float(f["precio_entrada"]),
     "precio_salida": float(f["precio_salida"])}
    for f in filas if f["simbolo"] == "SINT" and f["lado"] == "largo"
]
Path("largas.json").write_text(json.dumps(largas, indent=2),
                               encoding="utf-8")
datos = json.loads(Path("largas.json").read_text(encoding="utf-8"))
print(len(datos), datos[0])
OUT [12]Filtrar un CSV a JSON
24 {'entrada': '2021-09-02', 'salida': '2021-10-12', 'cantidad': 180, 'precio_entrada': 108.64, 'precio_salida': 108.79}

Las fechas ya llegan del CSV como texto ISO, así que se copian tal cual. Las cantidades y los precios se convierten antes de escribir: el JSON guarda números, y quien lo lea no tendrá que convertirlos.

  1. Escribe una función en_sesion(instante, zona, abre, cierra) que diga si un datetime aware cae dentro del horario de un mercado expresado en su zona. Comprueba las 15:00 de Madrid del 16 y del 30 de marzo de 2026 contra la NYSE.
Solución
IN [13]python
def en_sesion(instante: datetime, zona: ZoneInfo,
              abre: time, cierra: time) -> bool:
    local = instante.astimezone(zona)
    return local.weekday() < 5 and abre <= local.time() < cierra

for dia in (16, 30):
    t = datetime(2026, 3, dia, 15, 0, tzinfo=MAD)
    print(t.date(), en_sesion(t, NY, time(9, 30), time(16, 0)))
OUT [13]¿Está abierto el mercado?
2026-03-16 True
2026-03-30 False

A las 15:00 de Madrid del 16 de marzo la NYSE ya lleva media hora abierta (son las 10:00 en Nueva York); el 30 de marzo todavía no ha abierto (son las 09:00). La función no sabe nada de festivos: un calendario de mercado completo (nivel 1) añade la lista de días sin sesión.

Footnotes

  1. NYSE, «Holidays & Trading Hours»: Core Trading Session: 9:30 a.m. to 4:00 p.m. ET. Enlace en «Recursos». ↩

  2. NIST, «Daylight Saving Time Rules»: el horario de verano empieza el segundo domingo de marzo a las 2:00 y termina el primer domingo de noviembre a las 2:00, hora local. Enlace en «Recursos». ↩

  3. Directiva 2000/84/CE, incorporada en España por el Real Decreto 236/2002: el último domingo de marzo y el último domingo de octubre, a la 1:00 UTC. Enlace en «Recursos». ↩

  4. BME Exchange, «Horario Bolsa Española»: mercado abierto de 9:00 a 17:30 y subasta de cierre de 17:30 a 17:35 (https://www.bolsasymercados.es/es/bme-exchange/horario-del-mercado.html). ↩

Respuesta plegada en cada pregunta

Autoevaluación

  1. Un fichero YAML contiene simbolos: [SPY, ON, NO]. ¿Qué devuelve yaml.safe_load?

    • A['SPY', 'ON', 'NO']
    • B['SPY', True, False]
    • CUn error de sintaxis
    • D['SPY', None, None]
    Ver respuesta

    Respuesta B. ['SPY', True, False]

    PyYAML sigue YAML 1.1, donde on, off, yes y no (en cualquier combinación de mayúsculas) son booleanos. Los símbolos que coincidan con esas palabras hay que ponerlos entre comillas.

  2. ¿A qué hora de Madrid abre la NYSE el lunes 16 de marzo de 2026?

    • A15:30, como siempre
    • B14:30, porque Estados Unidos ya ha adelantado la hora y Europa todavía no
    • C16:30
    • D09:30
    Ver respuesta

    Respuesta B. 14:30, porque Estados Unidos ya ha adelantado la hora y Europa todavía no

    Estados Unidos cambia de hora el segundo domingo de marzo (8 de marzo de 2026) y la UE el último (29 de marzo). Entre ambas fechas, la diferencia es de 5 horas en lugar de 6.

  3. ¿Por qué conviene guardar las marcas de tiempo en UTC?

    • APorque ocupa menos espacio
    • BPorque UTC no tiene cambios de hora: cada instante tiene una sola representación y las diferencias entre marcas son siempre correctas
    • CPorque los mercados abren en UTC
    • DPorque Python no admite otras zonas
    Ver respuesta

    Respuesta B. Porque UTC no tiene cambios de hora: cada instante tiene una sola representación y las diferencias entre marcas son siempre correctas

    En hora local hay horas que no existen (el adelanto de primavera) y horas que se repiten (el retraso de otoño). En UTC no pasa: se guarda en UTC y se convierte a hora local solo al mostrar.

  4. Comparas datetime(2026, 3, 9, 15, 0) (sin zona) con una apertura creada con tzinfo=ZoneInfo("America/New_York"). ¿Qué ocurre?

    • APython supone que la primera está en UTC
    • BPython supone que la primera está en la hora local del ordenador
    • CTypeError: no se pueden comparar fechas con y sin zona horaria
    • DDevuelve siempre False
    Ver respuesta

    Respuesta C. TypeError: no se pueden comparar fechas con y sin zona horaria

    Una fecha sin zona (naive) no representa un instante concreto. Python se niega a compararla con una con zona (aware) en lugar de adivinar.

Ideas clave

  • pathlib.Path construye rutas con /, lista ficheros con glob y lee o escribe texto en una línea. Las rutas relativas dependen del directorio de trabajo.
  • CSV entrega texto: hay que convertir cada columna. JSON no admite fechas ni Decimal: se serializan como texto ISO.
  • YAML es cómodo y traicionero: ON y NO son booleanos y 1.10 es el número 1.1. TOML exige comillas en los textos y no tiene ese problema; tomllib lo lee pero no lo escribe.
  • Estados Unidos y la UE cambian de hora en fechas distintas: en 2026, la NYSE abre a las 14:30 de Madrid del 9 al 27 de marzo y del 26 al 30 de octubre.
  • Guarda en UTC, convierte al mostrar y nunca mezcles fechas con y sin zona. El cierre de dos mercados con la misma fecha no ocurre a la misma hora.

Recursos