---
title: "0.3.3 Listas, tuplas, diccionarios y conjuntos"
description: "Las cuatro colecciones de Python con datos de trading: watchlists, barras OHLC, carteras, universos de un índice y rentabilidades con comprensiones."
url: "https://pythonparatrading.com/curso-python-trading/nivel-0/listas-tuplas-diccionarios-y-conjuntos/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-0/listas-tuplas-diccionarios-y-conjuntos.png"
---

# 0.3.3 · Listas, tuplas, diccionarios, conjuntos y comprensiones

> Una serie de cierres, una barra OHLC, una cartera y el universo de un índice son colecciones de datos, y cada una tiene su estructura en Python. La lección enseña las cuatro y las comprensiones, la forma corta de construirlas.

- Curso: [Nivel 0: Python desde cero y mentalidad de datos](https://pythonparatrading.com/curso-python-trading/nivel-0/)
- Módulo: 0.3 · Fundamentos del lenguaje
- Duración: 50 min
- Requisitos: [0.3.2 · Cadenas, f-strings y formato de números y fechas](https://pythonparatrading.com/curso-python-trading/nivel-0/cadenas-f-strings-y-formato/)

## Al terminar esta lección

- Crear y modificar listas (una watchlist, una serie de cierres) con índices, cortes y métodos como `append`, `remove` y `sort`.
- Representar una barra OHLC con una tupla y desempaquetarla en variables.
- Valorar una cartera con diccionarios de posiciones y precios, y leer claves que pueden faltar con `get`.
- Calcular altas y bajas de un índice con operaciones de conjuntos y rentabilidades diarias con una comprensión.

## Colecciones: muchos datos bajo un nombre

Hasta ahora cada variable guardaba un dato: un precio, una cantidad, un ticker (lecciones 0.3.1 y 0.3.2). Un programa
de trading trabaja casi siempre con muchos: los cierres de un mes, los activos que se vigilan, las posiciones de una
cartera, los componentes de un índice. Python tiene cuatro **colecciones** integradas para guardarlos, y cada una
responde a una necesidad distinta:

| Colección | Literal | ¿Ordenada? | ¿Modificable? | ¿Duplicados? | Uso típico en trading |
|---|---|---|---|---|---|
| Lista (`list`) | `[139.32, 139.23]` | Sí | Sí | Sí | Serie de cierres, watchlist, órdenes pendientes |
| Tupla (`tuple`) | `(140.01, 143.40, 139.67, 142.22)` | Sí | No | Sí | Una barra OHLC, un registro fijo |
| Diccionario (`dict`) | `{"SINT": 100, "ORO": 20}` | Por inserción | Sí | Claves únicas | Cartera, precios por ticker, parámetros |
| Conjunto (`set`) | `{"SINT", "ORO"}` | No | Sí | No | Universo de un índice, símbolos únicos |

## Listas: series y watchlists

Una **lista** es una secuencia ordenada de valores entre corchetes. Se indexa y se corta exactamente igual que una
cadena (lección 0.3.2): el primer elemento es el `[0]`, el último el `[-1]` y `[inicio:fin]` devuelve una lista nueva
con ese tramo. Los cierres de SINT de la segunda mitad de diciembre de 2025 (del fichero `sint_diario.csv`, enlace en
«Recursos») son una lista de `float`:

```python
cierres = [146.36, 145.13, 144.22, 143.66, 139.91, 139.43,
           139.36, 139.32, 139.23, 139.56, 139.98, 142.22]
print(len(cierres), cierres[0], cierres[-1])
print(cierres[-3:])
print(max(cierres), min(cierres))
print(sum(cierres) / len(cierres))
```
```output
12 146.36 142.22
[139.56, 139.98, 142.22]
146.36 139.23
141.53166666666667
```

`len`, `max`, `min` y `sum` funcionan con cualquier colección de números. `cierres[-3:]` son los tres últimos cierres,
y la última línea es la media de la serie. Con la misma idea, `cierres[-20:]` daría las últimas veinte sesiones de una
serie más larga: así empieza el cálculo de una media móvil.

A diferencia de las cadenas, las listas son **modificables**. Sus métodos cambian la propia lista y no devuelven nada
(devuelven `None`). Una watchlist, la lista de activos que se vigilan, es el ejemplo natural:

```python
watchlist = ["SINT", "RVAR", "ORO"]
watchlist.append("TECH")      # añade al final
watchlist.insert(0, "RFIJ")   # inserta en una posición
watchlist.remove("ORO")       # quita por valor
ultimo = watchlist.pop()      # quita y devuelve el último
print(watchlist, ultimo)

watchlist.sort()
print(watchlist, "SINT" in watchlist, watchlist.index("SINT"))
print(sorted(cierres)[:3])
```
```output
['RFIJ', 'SINT', 'RVAR'] TECH
['RFIJ', 'RVAR', 'SINT'] True 2
[139.23, 139.32, 139.36]
```

`sort()` ordena la lista en su sitio; `sorted()` devuelve una lista nueva ordenada y deja la original como estaba. La
diferencia importa con una serie de precios: ordenar los cierres en su sitio destruye el orden temporal, y con él
cualquier cálculo de rentabilidades que venga después. Para una serie temporal, `sorted`, nunca `sort`.

La trampa más común de las listas es esta: asignar una lista a otro nombre no la copia. Los dos nombres apuntan a la
**misma** lista, y lo que se haga con uno se ve en el otro.

```python
pendientes = ["SINT", "ORO"]
para_enviar = pendientes          # el mismo objeto
para_enviar.append("TECH")
print(pendientes)

copia = pendientes.copy()         # un objeto nuevo
copia.clear()
print(pendientes, copia)
print(para_enviar is pendientes, copia is pendientes)
```
```output
['SINT', 'ORO', 'TECH']
['SINT', 'ORO', 'TECH'] []
True False
```

El operador `is` pregunta si dos nombres apuntan al mismo objeto. En un sistema de órdenes, este error tiene
consecuencias: un script que «vacía la copia» de la lista de órdenes pendientes después de enviarlas puede estar
vaciando la original. La misma trampa reaparece con los argumentos por defecto de las funciones (lección 0.3.5).

## Tuplas: una barra OHLC

Una **tupla** es una secuencia como la lista, pero **inmutable**: una vez creada no se puede cambiar. Se escribe entre
paréntesis, o simplemente con comas. Es la estructura adecuada para un registro con un número fijo de campos que no
deben cambiar, como una barra de precios: apertura, máximo, mínimo y cierre (OHLC, por *open, high, low, close*).

```python
barra = ("2025-12-31", 140.01, 143.40, 139.67, 142.22)
fecha, apertura, maximo, minimo, cierre = barra
print(fecha, cierre)
print(f"Rango: {maximo - minimo:.2f}  Cuerpo: {cierre - apertura:+.2f}")
print(barra[1:])
```
```output
2025-12-31 142.22
Rango: 3.73  Cuerpo: +2.21
(140.01, 143.4, 139.67, 142.22)
```

La segunda línea es el **desempaquetado**: una tupla (o cualquier secuencia) se reparte en tantas variables como
elementos tenga. Es lo que hacía `base, cotizada = "EUR", "USD"` en la lección 0.3.2: a la derecha había una tupla
sin paréntesis. Si el número de variables no coincide con el de elementos, Python da un error.

La inmutabilidad es una garantía: una barra histórica no debería cambiar, y una tupla no deja hacerlo
(`barra[4] = 150.0` da un error). Una barra que se pudiera modificar por descuido sería un dato histórico falsificado
en medio de un backtest.

## Diccionarios: cartera y precios por ticker

Un **diccionario** asocia **claves** a **valores**. Se escribe entre llaves con pares `clave: valor`, y se consulta
por la clave, no por la posición. Una cartera es un diccionario de ticker → acciones; los precios, otro de ticker →
último cierre (los de SINT y de los activos de `cartera_sint.csv` el 31 de diciembre de 2025):

```python
cartera = {"SINT": 100, "ORO": 20, "RFIJ": 150}
precios = {"SINT": 142.22, "ORO": 171.44, "RFIJ": 102.29,
           "TECH": 77.82}

print(cartera["SINT"], len(cartera), "TECH" in cartera)
cartera["TECH"] = 30          # alta de una posición
cartera["ORO"] += 10          # aumentar una posición
del cartera["RFIJ"]           # cierre de una posición
print(cartera)
print(list(cartera.keys()), list(cartera.values()))
```
```output
100 3 False
{'SINT': 100, 'ORO': 30, 'TECH': 30}
['SINT', 'ORO', 'TECH'] [100, 30, 30]
```

Asignar a una clave que no existe la crea; asignar a una que existe sustituye su valor. `in` pregunta por las claves,
no por los valores. Desde Python 3.7, los diccionarios conservan el orden en que se insertaron las claves.

Para valorar la cartera hay que recorrerla. `items()` da las parejas clave-valor, y el bucle `for` (lección 0.3.4)
las procesa una a una:

```python
valor_total = 0.0
for ticker, acciones in cartera.items():
    valor = acciones * precios[ticker]
    valor_total += valor
    print(f"{ticker:<5} {acciones:>4} x {precios[ticker]:>7.2f}"
          f" = {valor:>9,.2f}")
print(f"{'Total':<22}{valor_total:>9,.2f}")
```
```output
SINT   100 x  142.22 = 14,222.00
ORO     30 x  171.44 =  5,143.20
TECH    30 x   77.82 =  2,334.60
Total                 21,699.80
```

Leer una clave que no existe con corchetes es un error (`KeyError`), y en trading pasa a menudo: un ticker sin precio
ese día, un símbolo mal escrito. Si la ausencia es normal y tiene un valor razonable, `get` lo da sin error:

```python
print(cartera.get("SINT", 0), cartera.get("BTC", 0))
print(precios.get("BTC"))
```
```output
100 0
None
```

Sin segundo argumento, `get` devuelve `None`. Cuidado con usarlo para tapar errores: una cartera que no tiene BTC
tiene 0 BTC, pero un precio que falta **no** es un precio de 0. Valorar una posición con precio 0 la hace desaparecer
del valor de la cartera sin ningún aviso. Cuándo conviene dejar que salte el error y cómo tratarlo se ve en la
lección 0.3.6.

## Conjuntos: el universo de un índice

Un **conjunto** es una colección de elementos únicos y sin orden, entre llaves sin pares. Sirve para dos cosas:
quitar duplicados y hacer operaciones de conjuntos, que son exactamente las preguntas que se hacen sobre el universo
de un índice. Los componentes de un índice cambian con el tiempo: entran empresas que han crecido y salen las que han
caído, se han fusionado o han quebrado.

El ejemplo usa un índice ficticio de seis componentes en dos fechas:

```python
universo_2024 = {"ALFA", "BETA", "GAMMA", "DELTA", "SIGMA", "OMEGA"}
universo_2025 = {"ALFA", "BETA", "DELTA", "SIGMA", "KAPPA", "LAMBDA"}

altas = universo_2025 - universo_2024
bajas = universo_2024 - universo_2025
siguen = universo_2024 & universo_2025
alguna_vez = universo_2024 | universo_2025
print("Altas:", sorted(altas))
print("Bajas:", sorted(bajas))
print("Siguen:", len(siguen), " Alguna vez:", len(alguna_vez))
print("Cambios:", sorted(universo_2024 ^ universo_2025))
```
```output
Altas: ['KAPPA', 'LAMBDA']
Bajas: ['GAMMA', 'OMEGA']
Siguen: 4  Alguna vez: 8
Cambios: ['GAMMA', 'KAPPA', 'LAMBDA', 'OMEGA']
```

| Operación | Operador | Pregunta |
|---|---|---|
| Diferencia | `a - b` | ¿Qué está en `a` y no en `b`? (altas o bajas) |
| Intersección | `a & b` | ¿Qué está en los dos? |
| Unión | `a \| b` | ¿Qué está en alguno? |
| Diferencia simétrica | `a ^ b` | ¿Qué está en uno solo? (todos los cambios) |

Los conjuntos no tienen orden, así que `print` los mostraría en un orden cualquiera; `sorted` los convierte en una
lista ordenada para que la salida sea siempre la misma.

La línea de las bajas es la que más importa en trading. Si un backtest de los últimos diez años usa solo los
componentes de **hoy**, nunca compra GAMMA ni OMEGA: las empresas que salieron, muchas veces después de caer, no están
en la lista. El backtest solo ve a las supervivientes y sale mejor de lo que habría sido de verdad. Es el **sesgo de
supervivencia**, y el único remedio es usar el universo que existía en cada fecha, `alguna_vez` incluido:

*Fig. — Sesgo de supervivencia: el mismo índice con y sin las empresas que desaparecieron (esquema).* Dos líneas durante diez años desde 100: el universo completo, que incluye las empresas deslistadas, acaba muy por debajo de la versión que solo conserva las supervivientes, destacada.

La otra utilidad de los conjuntos es quitar duplicados. Los símbolos operados en un registro de operaciones se
repiten; `set` los deja una vez cada uno:

```python
simbolos_operados = ["ORO", "SINT", "SINT", "SINT", "ORO", "TECH"]
unicos = set(simbolos_operados)
print(sorted(unicos), len(unicos))
```
```output
['ORO', 'SINT', 'TECH'] 3
```

## Comprensiones: rentabilidades en una línea

Una **comprensión** construye una colección a partir de otra en una sola expresión: `[expresión for elemento in
colección]`. Se lee casi como una frase: «la expresión, para cada elemento de la colección». Es la forma idiomática de
transformar una serie.

La rentabilidad de un día es $r_t = P_t / P_{t-1} - 1$: necesita cada cierre y el anterior. `zip` empareja los
elementos de dos colecciones; si se le pasan la serie y la serie desplazada una posición (`cierres[1:]`), cada pareja
es (cierre anterior, cierre del día):

```python
rentabilidades = [c1 / c0 - 1
                  for c0, c1 in zip(cierres, cierres[1:])]
print(len(rentabilidades))
print(" ".join([f"{r:+.2%}" for r in rentabilidades]))

peor = min(rentabilidades)
print(f"Peor día: {peor:+.2%}, sesión {rentabilidades.index(peor) + 1}")
```
```output
11
-0.84% -0.63% -0.39% -2.61% -0.34% -0.05% -0.03% -0.06% +0.24% +0.30% +1.60%
Peor día: -2.61%, sesión 4
```

Doce cierres dan once rentabilidades: `zip` se detiene al acabarse la lista más corta. La segunda comprensión
transforma cada rentabilidad en un texto con formato, y `" ".join(…)` une esos textos en uno solo, separados por un
espacio. La figura de la cabecera dibuja esas once rentabilidades; la
peor, la del 22 de diciembre, en ámbar.

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

En el nivel 0 las rentabilidades se calculan con listas para entender qué hacen. En la práctica se usan NumPy y pandas
(módulo 0.5), que hacen el mismo cálculo sobre millones de datos en una línea y mucho más rápido:
`cierre.pct_change()`.

</aside>

Una comprensión puede **filtrar** con un `if` al final, y hay versiones para diccionarios y conjuntos:

```python
subidas = [r for r in rentabilidades if r > 0]
print(f"Días al alza: {len(subidas)} de {len(rentabilidades)}")

valores = {t: n * precios[t] for t, n in cartera.items()}
total = sum(valores.values())
pesos = {t: round(v / total, 3) for t, v in valores.items()}
print(pesos)

operaciones = [("SINT", 7.43), ("ORO", -3123.56),
               ("SINT", -31.50), ("TECH", 1426.71)]
con_perdidas = {s for s, pnl in operaciones if pnl < 0}
print(sorted(con_perdidas))
```
```output
Días al alza: 3 de 11
{'SINT': 0.655, 'ORO': 0.237, 'TECH': 0.108}
['ORO', 'SINT']
```

Las llaves con `clave: valor` dan un diccionario; sin los dos puntos, un conjunto. Los pesos de la cartera salen en
dos pasos: primero el valor de cada posición y después su fracción del total.

Las comprensiones son cómodas mientras caben en una o dos líneas y se leen de un vistazo. Si hace falta más de un
`if`, o la expresión ocupa media línea, un bucle `for` normal (lección 0.3.4) es más claro.

## Qué colección usar

La decisión sale de tres preguntas, en este orden:

```mermaid
flowchart TB
  Q1{¿Clave y valor?} -- sí --> D[dict]
  Q1 -- no --> Q2{¿Solo únicos?}
  Q2 -- sí --> S[set]
  Q2 -- no --> Q3{¿Va a cambiar?}
  Q3 -- sí --> L[list]
  Q3 -- no --> T[tuple]
```

Si cada dato se busca por un nombre (un ticker, un parámetro), diccionario. Si lo que importa es pertenecer o no
(está en el índice, se ha operado), conjunto. Si es una secuencia que crece o cambia (una serie que recibe un cierre
nuevo cada día, una cola de órdenes), lista. Si es un registro fijo (una barra, una operación cerrada), tupla.

## Ejercicios

1. Con la lista `cierres` de la lección, calcula la rentabilidad total del periodo (último cierre entre el primero,
   menos 1) y el número de sesiones en las que el cierre bajó respecto al anterior.

**Solución.**

```python
total_periodo = cierres[-1] / cierres[0] - 1
bajadas = [r for r in rentabilidades if r < 0]
print(f"Rentabilidad del periodo: {total_periodo:+.2%}")
print(f"Sesiones a la baja: {len(bajadas)}")
```
```output
Rentabilidad del periodo: -2.83%
Sesiones a la baja: 8
```

La rentabilidad total solo necesita el primer y el último cierre. Las sesiones a la baja salen de filtrar las
rentabilidades negativas con una comprensión.

2. El índice ficticio cambia otra vez en 2026: salen BETA y KAPPA y entran OMEGA y RHO. Construye `universo_2026` a
   partir de `universo_2025` con operaciones de conjuntos, y calcula cuántas empresas han formado parte del índice en
   algún momento de los tres años.

**Solución.**

```python
universo_2026 = (universo_2025 - {"BETA", "KAPPA"}) | {"OMEGA", "RHO"}
print(sorted(universo_2026))
historico = universo_2024 | universo_2025 | universo_2026
print(len(historico), sorted(historico))
```
```output
['ALFA', 'DELTA', 'LAMBDA', 'OMEGA', 'RHO', 'SIGMA']
9 ['ALFA', 'BETA', 'DELTA', 'GAMMA', 'KAPPA', 'LAMBDA', 'OMEGA', 'RHO', 'SIGMA']
```

OMEGA sale en 2025 y vuelve en 2026; el conjunto la cuenta una sola vez. Un backtest honesto de 2024 a 2026 necesita
los nueve nombres, con las fechas en que cada uno estuvo dentro.

3. Con el diccionario `precios` de la lección, crea un diccionario con el valor de una cartera que tuviera 10 acciones
   de cada ticker, y di qué ticker tiene el mayor valor.

**Solución.**

```python
diez = {t: round(10 * p, 2) for t, p in precios.items()}
print(diez)
mayor = max(diez, key=diez.get)
print(mayor, diez[mayor])
```
```output
{'SINT': 1422.2, 'ORO': 1714.4, 'RFIJ': 1022.9, 'TECH': 778.2}
ORO 1714.4
```

`round` quita la cola de decimales que deja la coma flotante en `10 * 102.29` (lección 0.3.1). `max` recorre las
claves del diccionario; `key=diez.get` le dice que las compare por su valor y no por el nombre. La
lección 0.3.5 explica qué es ese argumento `key` y cómo usarlo con funciones propias.

## Autoevaluación

1. `cierres = [139.32, 139.23, 139.56]` y después `copia = cierres; copia.append(139.98)`. ¿Cuántos elementos tiene `cierres`?

   - A. 3, porque se modificó la copia
   - B. 4, porque `copia` y `cierres` son la misma lista
   - C. Da un error
   - D. 1

   *Respuesta B: 4, porque `copia` y `cierres` son la misma lista.* `copia = cierres` no copia nada: crea otro nombre para la misma lista. Para una copia independiente hace falta `cierres.copy()` o `list(cierres)`.

2. ¿Qué estructura es la adecuada para guardar las acciones que tienes de cada ticker y consultarlas por su nombre?

   - A. Una lista
   - B. Una tupla
   - C. Un diccionario
   - D. Un conjunto

   *Respuesta C: Un diccionario.* Un diccionario asocia cada clave (el ticker) a un valor (las acciones) y la búsqueda por clave es inmediata. Una lista obligaría a recorrerla entera.

3. `antes = {"A", "B", "C"}` y `ahora = {"B", "C", "D"}`. ¿Qué devuelve `antes - ahora`?

   - A. `{"D"}`
   - B. `{"A"}`
   - C. `{"B", "C"}`
   - D. `{"A", "D"}`

   *Respuesta B: `{"A"}`.* La diferencia `antes - ahora` son los elementos que estaban y ya no están: las bajas del índice. Las altas serían `ahora - antes`, y `^` daría los dos cambios juntos.

4. Un backtest usa solo las empresas que forman hoy un índice para simular los últimos diez años. ¿Qué sesgo tiene?

   - A. Sesgo de anticipación
   - B. Sesgo de supervivencia: faltan las empresas que salieron del índice, muchas de ellas porque cayeron
   - C. Ninguno, si los precios están ajustados
   - D. Sesgo de costes

   *Respuesta B: Sesgo de supervivencia: faltan las empresas que salieron del índice, muchas de ellas porque cayeron.* Las empresas que están hoy en el índice son las que sobrevivieron. Las que quebraron o cayeron lo suficiente para salir no aparecen, y el backtest sale mejor de lo que habría sido. Hace falta el universo de cada fecha.

5. ¿Qué hace `[c1 / c0 - 1 for c0, c1 in zip(cierres, cierres[1:])]`?

   - A. Calcula la rentabilidad de cada día respecto al anterior
   - B. Calcula la rentabilidad total
   - C. Ordena los cierres
   - D. Da un error porque las listas tienen distinta longitud

   *Respuesta A: Calcula la rentabilidad de cada día respecto al anterior.* `zip` empareja cada cierre con el siguiente (`zip` se detiene en la lista más corta) y la comprensión calcula `c1 / c0 - 1` para cada pareja: una rentabilidad por día, una menos que cierres.

## Ideas clave

- Lista: secuencia ordenada y modificable (cierres, watchlist). Tupla: secuencia fija, ideal para un registro como una barra OHLC.
- Asignar una lista a otro nombre no la copia: los dos nombres apuntan a la misma lista. Para copiar, `.copy()`.
- Diccionario: clave → valor (ticker → acciones). `d[clave]` falla si la clave no existe; `d.get(clave, 0)` devuelve un valor por defecto.
- Conjunto: elementos únicos sin orden. La diferencia entre el universo de dos fechas da las altas y las bajas; ignorar las bajas es el sesgo de supervivencia.
- Una comprensión construye una colección en una línea: `[c1 / c0 - 1 for c0, c1 in zip(cierres, cierres[1:])]` da las rentabilidades diarias.

## Recursos

### Research

- [Alpha Factors y Alpha Trading en Python](https://pythonparatrading.com/alpha-factors-y-alpha-trading-en-python/) · Research-0007 · 5 min

### Documentación

- [Python: estructuras de datos (tutorial oficial)](https://docs.python.org/3/tutorial/datastructures.html) · docs.python.org
- [Python: tipos de secuencia, mapeo y conjunto (documentación oficial)](https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range) · docs.python.org

### 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.3.2 · Cadenas, f-strings y formato de números y fechas](https://pythonparatrading.com/curso-python-trading/nivel-0/cadenas-f-strings-y-formato/)

Lección siguiente: [0.3.4 · Control de flujo: if, elif, else, for, while y match-case](https://pythonparatrading.com/curso-python-trading/nivel-0/control-de-flujo-if-for-while-match/)
