---
title: "0.3.2 Cadenas, f-strings y formato de números y fechas"
description: "Cadenas y sus métodos para limpiar símbolos, f-strings para informes de operaciones y fechas ISO 8601, con formato español sin depender del locale."
url: "https://pythonparatrading.com/curso-python-trading/nivel-0/cadenas-f-strings-y-formato/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-0/cadenas-f-strings-y-formato.png"
---

# 0.3.2 · Cadenas, f-strings y formato de números y fechas

> Un ticker, una fecha y una línea de un informe son texto. La lección enseña a trocear símbolos como «ES=F» o «EURUSD», a dar formato a precios, porcentajes y P&L con f-strings y a escribir y leer fechas sin ambigüedad.

- 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: 45 min
- Requisitos: [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/)

## Al terminar esta lección

- Extraer partes de un símbolo (`ES=F`, `EURUSD`, `BTC-USD`) con índices, cortes y métodos de cadena como `partition`, `split` y `removesuffix`.
- Dar formato a precios, porcentajes y P&L con f-strings: decimales, separador de miles, signo, ancho y alineación.
- Escribir números en formato español (`1.234,56`) sin depender del `locale` del sistema.
- Convertir fechas entre texto y `date` con ISO 8601, `strftime` y `strptime`, y explicar por qué ISO 8601 es el formato de los datos.

## Cadenas: el texto de un programa de trading

Tickers, nombres de mercado, lados de una orden, líneas de un fichero y mensajes de un informe: todo eso es texto, y
en Python el texto es el tipo `str` (lección 0.3.1). Una **cadena** se escribe entre comillas simples o dobles, que
son equivalentes; las triples (`"""…"""`) permiten varias líneas.

Una cadena es una secuencia de caracteres en orden, y cada carácter tiene una posición, su **índice**, que empieza en
0. Con corchetes se lee un carácter (`s[0]`) o un trozo (`s[inicio:fin]`, el **corte**, que incluye `inicio` y excluye
`fin`). Los índices negativos cuentan desde el final: `s[-1]` es el último carácter.

```python
par = "EURUSD"
print(len(par), par[0], par[-1])
print(par[:3], par[3:])
print(par[::-1])
print("USD" in par, "JPY" in par)
```
```output
6 E D
EUR USD
DSURUE
True False
```

`len` da el número de caracteres. `[:3]` toma los tres primeros (de 0 a 2) y `[3:]`, del índice 3 al final: las dos
divisas del par. Un tercer número en el corte es el paso; `[::-1]` recorre la cadena al revés. El operador `in`
pregunta si un trozo aparece dentro de la cadena y devuelve un `bool`.

Las cadenas son **inmutables**: no se puede cambiar un carácter (`par[0] = "G"` da un error). Todas las operaciones
que parecen modificarlas devuelven una cadena nueva. Con `+` se concatenan y con `*` se repiten:

```python
base, cotizada = "EUR", "USD"
simbolo = base + "/" + cotizada
print(simbolo)
print("-" * 20)
```
```output
EUR/USD
--------------------
```

La primera línea asigna dos variables a la vez; la forma se explica con las tuplas en la lección 0.3.3.

## Métodos para limpiar y trocear símbolos

Los símbolos llegan escritos de muchas formas: en minúsculas desde un formulario, con espacios desde una hoja de
cálculo, con sufijos que cada proveedor de datos usa a su manera. En Yahoo Finance, por ejemplo, `ES=F` es el futuro
E-mini S&P 500 (el contrato más cercano a vencimiento) y `EURUSD=X`, el cruce euro-dólar; en otros proveedores el
mismo par es `EURUSD` o `EUR/USD`, y una criptomoneda puede ser `BTC-USD` o `BTCUSDT`[^1].

Los **métodos** de las cadenas son funciones que se llaman con un punto detrás del valor (`simbolo.upper()`). Los que
más se usan para este trabajo:

| Método | Qué hace | Ejemplo | Resultado |
|---|---|---|---|
| `strip()` | Quita espacios al principio y al final | `" es=f ".strip()` | `"es=f"` |
| `upper()` / `lower()` | Mayúsculas / minúsculas | `"es=f".upper()` | `"ES=F"` |
| `partition(sep)` | Parte en tres por la primera aparición de `sep` | `"ES=F".partition("=")` | `("ES", "=", "F")` |
| `split(sep)` | Parte por todas las apariciones de `sep` | `"BTC-USD".split("-")` | `["BTC", "USD"]` |
| `replace(a, b)` | Cambia `a` por `b` | `"EUR/USD".replace("/", "")` | `"EURUSD"` |
| `startswith` / `endswith` | ¿Empieza o termina por…? | `"ES=F".endswith("=F")` | `True` |
| `removesuffix` / `removeprefix` | Quita un sufijo o prefijo si está | `"EURUSD=X".removesuffix("=X")` | `"EURUSD"` |

Con ellos, un símbolo mal escrito se normaliza en una línea, y un símbolo bien escrito se descompone en sus partes:

```python
entrada = "  es=f \n"
simbolo = entrada.strip().upper()
print(repr(entrada), "->", repr(simbolo))

raiz, sep, tipo = simbolo.partition("=")
print(raiz, tipo, simbolo.endswith("=F"))

base, cotizada = "BTC-USD".split("-")
print(base, cotizada)

print("EURUSD=X".removesuffix("=X"))
print("EUR/USD".replace("/", ""))
```
```output
'  es=f \n' -> 'ES=F'
ES F True
BTC USD
EURUSD
EURUSD
```

`repr` muestra la cadena tal como es, con comillas y con el salto de línea escrito como `\n`; `print` la mostraría sin
ellos y los espacios pasarían desapercibidos. Es la forma de ver qué hay de verdad dentro de un texto que no se
comporta como se espera.

`partition` siempre devuelve tres trozos, aunque no encuentre el separador (los dos últimos quedan vacíos); `split`
devuelve tantos como haya, y asignarlos a dos variables solo funciona si son exactamente dos. Por eso, cuando un
símbolo tiene una única marca (`=` en Yahoo), `partition` es la opción robusta.

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

Normaliza los símbolos antes de compararlos. `"es=f" == "ES=F"` es `False`: para Python son textos distintos. Un
símbolo con un espacio al final no se encuentra en la lista de posiciones, la orden no se cancela y nadie se entera
hasta que llega el extracto.

</aside>

El troceado sigue el mismo esquema para cualquier proveedor: limpiar, comprobar el formato y separar las partes.

```mermaid
flowchart TB
  E[Texto recibido] --> N[strip y upper]
  N --> F{¿Contiene =?}
  F -- sí --> P[partition]
  F -- no --> C[Corte por posición]
```

Cuando el formato no tiene separador, como `EURUSD`, se trocea por posición: las divisas siempre tienen tres letras
(son códigos ISO 4217), así que `[:3]` y `[3:]` bastan. Si no hay ni separador ni longitud fija (`BTCUSDT`), hace falta
una tabla con los símbolos que publica el propio mercado; adivinar las partes es una fuente segura de errores.

## f-strings: texto con valores dentro

Una **f-string** es una cadena con una `f` delante. Dentro, todo lo que va entre llaves se evalúa como código y se
sustituye por su valor. Es la forma de construir mensajes, nombres de fichero y líneas de informe:

```python
ticker = "SINT"
cierre = 142.22
anterior = 139.98
print(f"{ticker} cierra en {cierre}")
print(f"Variación: {cierre - anterior}")
```
```output
SINT cierra en 142.22
Variación: 2.240000000000009
```

Dentro de las llaves puede ir cualquier expresión, como la resta de la segunda línea, que muestra otra vez la coma
flotante de la lección 0.3.1: el texto necesita un formato que fije los decimales. Si se olvida la `f`, las llaves
salen tal cual en el texto y no hay ningún error que avise. Un atajo útil: un `=` al final de la expresión escribe
también su nombre.

```python
print(f"{cierre=}  {anterior=}")
print(f"{cierre - anterior = :.2f}")
```
```output
cierre=142.22  anterior=139.98
cierre - anterior = 2.24
```

`{cierre=}` escribe el nombre de la variable, un igual y su valor: útil para seguir un cálculo sin escribir dos veces
el nombre. La segunda línea añade un formato tras los dos puntos, `.2f`, que es lo que se ve a continuación.

## El mini-lenguaje de formato

Lo que va tras los dos puntos dentro de las llaves es la **especificación de formato**. Tiene sus campos en un orden
fijo, y cada uno es opcional. La figura de la cabecera descompone `{pnl:>+12,.2f}`, el formato típico de una columna de
P&L:

| Campo | Ejemplo | Qué hace |
|---|---|---|
| Alineación | `<` `>` `^` | A la izquierda, a la derecha, centrado (con un carácter de relleno delante, si se quiere: `*>`) |
| Signo | `+` | Escribe el signo también en los positivos (por defecto, solo en los negativos) |
| Ancho | `12` | Ocupa al menos 12 caracteres, rellenando con espacios |
| Agrupación | `,` o `_` | Separa los miles con coma o guion bajo |
| Precisión | `.2` | Número de decimales (con `f` y `%`) |
| Tipo | `f` `%` `e` `d` | Decimal fijo, porcentaje (multiplica por 100), notación científica, entero |

```python
pnl = 1426.71
rent = 0.07041
volumen = 2_322_609

print(f"{cierre:.2f}  {cierre:.4f}  {cierre:.0f}")
print(f"{rent:.2%}  {rent:+.1%}  {-rent:+.1%}")
print(f"{volumen:,}  {volumen:_}  {volumen:.2e}")
print(f"{pnl:+,.2f}  {-pnl:+,.2f}")
print(f"[{pnl:>12,.2f}]  [{pnl:<12,.2f}]  [{'SINT':^10}]")
```
```output
142.22  142.2200  142
7.04%  +7.0%  -7.0%
2,322,609  2_322_609  2.32e+06
+1,426.71  -1,426.71
[    1,426.71]  [1,426.71    ]  [   SINT   ]
```

Tres detalles que conviene fijar. El tipo `%` multiplica por 100: la rentabilidad se guarda como tanto por uno
(0,07041) y se muestra como porcentaje, nunca al revés. El formato `.2f` redondea **solo lo que se muestra**: la
variable sigue valiendo lo mismo, con todos sus decimales. Y el redondeo del formato es el de la coma flotante, con los
mismos empates engañosos que `round()` en la lección 0.3.1; para importes que tienen que cuadrar al céntimo, el
redondeo se hace antes con `Decimal`.

El signo explícito (`+`) es la convención de los informes de P&L y de las variaciones de precio: `+1,426.71` y
`-1,426.71` se distinguen de un vistazo, y una columna de cifras con signo siempre del mismo ancho se lee mejor.

## Un informe de operaciones alineado

El ancho y la alineación sirven para construir tablas de texto: el registro de un script que opera, un correo con el
resumen del día o la salida de un backtest en la terminal. Los números se alinean a la derecha, para que las unidades
queden una debajo de otra, y el texto a la izquierda.

Las cinco operaciones de la celda son las primeras de `operaciones_sint.csv` (enlace en «Recursos»), con el P&L ya
neto de comisiones. La lista de tuplas y el bucle `for` se explican en las lecciones 0.3.3 y 0.3.4; aquí basta con
saber que la línea de dentro del bucle se repite para cada operación:

```python
operaciones = [
    ("2021-03-04", "ORO", "corto", 200, 190.08, -3123.56),
    ("2021-09-02", "SINT", "largo", 180, 108.64, 7.43),
    ("2021-10-27", "SINT", "corto", 10, 112.07, -31.50),
    ("2021-11-10", "SINT", "corto", 40, 104.30, -306.72),
    ("2022-07-22", "SINT", "largo", 200, 102.83, 1426.71),
]
cabecera = (f"{'Fecha':<10}  {'Símbolo':<7}  {'Lado':<5}  "
            f"{'Cant.':>5}  {'Entrada':>8}  {'P&L':>10}")
print(cabecera)
print("-" * len(cabecera))
for fecha, simbolo, lado, cant, entrada, pnl in operaciones:
    print(f"{fecha:<10}  {simbolo:<7}  {lado:<5}  "
          f"{cant:>5}  {entrada:>8.2f}  {pnl:>+10,.2f}")
```
```output
Fecha       Símbolo  Lado   Cant.   Entrada         P&L
-------------------------------------------------------
2021-03-04  ORO      corto    200    190.08   -3,123.56
2021-09-02  SINT     largo    180    108.64       +7.43
2021-10-27  SINT     corto     10    112.07      -31.50
2021-11-10  SINT     corto     40    104.30     -306.72
2022-07-22  SINT     largo    200    102.83   +1,426.71
```

La cabecera usa los mismos anchos que las filas, y la raya mide lo mismo que la cabecera gracias a `len`. Dos
f-strings seguidas entre paréntesis se unen en una sola cadena: es la forma de partir una línea larga sin que el código
pase de 72 caracteres. Las cadenas de dentro de las llaves (`{'Fecha':<10}`) usan comillas simples para distinguirse de las dobles de la
f-string; desde Python 3.12 se podrían repetir las dobles, pero se lee peor.

## Formato español sin tocar el locale

Python formatea los números a la inglesa: coma para los miles y punto para los decimales. En España se escribe al
revés, `1.426,71`, y el símbolo de porcentaje va separado de la cifra (`7,04 %`). Para un informe que lee una persona,
el formato local importa: `1,426` es mil cuatrocientos veintiséis para un lector inglés y uno coma cuatro para uno
español.

Python tiene una forma «oficial» de hacerlo, el módulo `locale` y el tipo de formato `n`, que usan la configuración
regional del sistema. Tiene dos problemas. El resultado depende de la máquina: el mismo script escribe `1.426,71` en un
portátil configurado en español y `1426.71` en un servidor con el locale `C`, el habitual en contenedores. Y
`locale.setlocale` cambia una configuración global del proceso, que afecta a todo lo que se ejecuta en él, incluidas
las librerías.

La alternativa robusta es formatear a la inglesa, que es determinista, e intercambiar después los dos signos.
`str.maketrans(",.", ".,")` crea una tabla de traducción (cada coma por un punto y cada punto por una coma) y
`translate` la aplica en una sola pasada. La celda lo envuelve en una función para no repetirlo; las funciones se ven
en la lección 0.3.5, y aquí basta con saber que `def` da nombre a un cálculo y `return` devuelve su resultado:

```python
A_ESPANOL = str.maketrans(",.", ".,")

def es(numero, decimales=2):
    """Número con miles en punto y decimales en coma."""
    return f"{numero:,.{decimales}f}".translate(A_ESPANOL)

def es_pct(tanto_por_uno, decimales=2):
    """Porcentaje en formato español: 7,04 %."""
    return es(tanto_por_uno * 100, decimales) + " %"

print(es(1426.71), es(-3123.56), es(2_322_609, 0))
print(es_pct(0.07041), es_pct(-0.25, 1))
print(f"P&L del día: {es(pnl)} €")
```
```output
1.426,71 -3.123,56 2.322.609
7,04 % -25,0 %
P&L del día: 1.426,71 €
```

La precisión también puede venir de una variable: `{numero:,.{decimales}f}` anida unas llaves dentro de la
especificación. El resultado es el mismo en cualquier máquina, y los números se guardan siempre como números: el
formato español es solo para mostrarlos. Un fichero de datos (CSV, Parquet) nunca lleva números formateados, ni en
español ni en inglés.

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

El problema inverso aparece al leer: un CSV exportado desde una hoja de cálculo en español trae `1.426,71`, y
`float("1.426,71")` falla. Se convierte quitando los puntos y cambiando la coma por un punto
(`texto.replace(".", "").replace(",", ".")`) o, mejor, diciéndoselo al lector de ficheros: pandas tiene los
parámetros `decimal=","` y `thousands="."` (módulo 0.5).

</aside>

## Fechas: ISO 8601, strftime y strptime

Una fecha escrita como texto es ambigua: `03/04/2025` es el 3 de abril en España y el 4 de marzo en Estados Unidos.
La norma **ISO 8601** resuelve el problema escribiendo de lo mayor a lo menor, `2025-04-03`, con un único orden
posible. Es el formato de los ficheros de datos del curso, de las APIs de los brókeres y de pandas. Tiene una ventaja
más: como texto, las fechas ISO se ordenan igual que en el calendario.

```python
iso = ["2025-04-03", "2024-12-31", "2025-01-15"]
espanol = ["03/04/2025", "31/12/2024", "15/01/2025"]
print(sorted(iso))
print(sorted(espanol))
```
```output
['2024-12-31', '2025-01-15', '2025-04-03']
['03/04/2025', '15/01/2025', '31/12/2024']
```

`sorted` ordena alfabéticamente. Con ISO 8601 el resultado es cronológico; con el formato español, el 31 de diciembre
de 2024 queda el último porque «31» va después de «15» en el alfabeto.

Para operar con fechas (sumar días, saber el día de la semana) no se usa el texto, sino el tipo `date` del módulo
`datetime`. `date.fromisoformat` convierte un texto ISO en una fecha, e `isoformat` hace lo contrario. Para cualquier
otro formato, `strftime` («string from time») escribe una fecha con un patrón, y `strptime` («string parse time») la lee:

| Código | Significa | Ejemplo |
|---|---|---|
| `%Y` | Año con cuatro cifras | `2025` |
| `%m` | Mes con dos cifras | `12` |
| `%d` | Día con dos cifras | `31` |
| `%H:%M:%S` | Hora, minutos y segundos | `15:30:00` |
| `%A` / `%B` | Nombre del día / del mes (según el locale) | `Wednesday` / `December` |

```python
from datetime import date, datetime

fecha = date.fromisoformat("2025-12-31")
print(fecha, fecha.year, fecha.month, fecha.day)
print(fecha.strftime("%d/%m/%Y"), f"{fecha:%d/%m/%Y}")
print(fecha.weekday(), f"{fecha:%A}")

leida = datetime.strptime("31/12/2025", "%d/%m/%Y").date()
print(leida == fecha, leida.isoformat())

orden = datetime.fromisoformat("2025-12-31T15:30:00")
print(orden, orden.date(), orden.time())
```
```output
2025-12-31 2025 12 31
31/12/2025 31/12/2025
2 Wednesday
True 2025-12-31
2025-12-31 15:30:00 2025-12-31 15:30:00
```

Las f-strings aceptan los mismos códigos tras los dos puntos: `{fecha:%d/%m/%Y}` equivale a `strftime`. `weekday`
numera los días desde el lunes (0) hasta el domingo (6): el 31 de diciembre de 2025 es miércoles. `datetime` añade la
hora a la fecha; en ISO 8601, la `T` separa las dos partes.

Los nombres de días y meses (`%A`, `%B`) tienen el mismo problema que el formato de los números: dependen del locale y
en el runner del curso, como en muchos servidores, salen en inglés. La solución es la misma: no depender de él.

```python
DIAS = ("lunes", "martes", "miércoles", "jueves",
        "viernes", "sábado", "domingo")
MESES = ("enero", "febrero", "marzo", "abril", "mayo", "junio",
         "julio", "agosto", "septiembre", "octubre", "noviembre",
         "diciembre")

print(f"{DIAS[fecha.weekday()]}, {fecha.day} de "
      f"{MESES[fecha.month - 1]} de {fecha.year}")
print(f"Informe_{fecha:%Y%m%d}.txt")
```
```output
miércoles, 31 de diciembre de 2025
Informe_20251231.txt
```

`DIAS[fecha.weekday()]` usa el número del día como índice, igual que con los caracteres de una cadena. A los meses se
les resta 1 porque `month` va de 1 a 12 y los índices empiezan en 0. La última línea muestra otra aplicación de ISO
8601: los nombres de fichero con la fecha en `AAAAMMDD` aparecen ordenados en cualquier carpeta.

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

Una fecha sin hora y una hora sin zona horaria son incompletas en cuanto se opera en más de un mercado. Casi todo el
año, Wall Street abre a las 15:30 de Madrid, que son las 9:30 de Nueva York; durante unas semanas de marzo y de
octubre, cuando un lado del Atlántico ya ha cambiado de horario y el otro no, abre a las 14:30. Las zonas horarias y los cambios de horario se ven en la
lección 0.4.4; hasta entonces, el curso trabaja con fechas de sesión.

</aside>

## Ejercicios

1. Escribe el código que, a partir de `"  usdjpy "`, obtenga el símbolo normalizado `USDJPY`, la divisa base y la
   cotizada, y lo muestre como `USD/JPY`.

**Solución.**

```python
texto = "  usdjpy "
par = texto.strip().upper()
base, cotizada = par[:3], par[3:]
print(par, base, cotizada)
print(f"{base}/{cotizada}")
```
```output
USDJPY USD JPY
USD/JPY
```

`strip` quita los espacios, `upper` pasa a mayúsculas y los cortes `[:3]` y `[3:]` separan las dos divisas, que
siempre tienen tres letras.

2. Con `pnl = -1288.04` y `rent = -0.10063`, escribe una línea con este aspecto exacto (ancho 12 para el P&L y 8 para
   la rentabilidad, alineados a la derecha): `P&L:    -1,288.04 | Rent.:  -10.06%`.

**Solución.**

```python
pnl = -1288.04
rent = -0.10063
print(f"P&L: {pnl:>12,.2f} | Rent.: {rent:>8.2%}")
```
```output
P&L:    -1,288.04 | Rent.:  -10.06%
```

`>12` reserva doce caracteres y alinea a la derecha; `,` separa los miles y `.2f` deja dos decimales. En la
rentabilidad, `.2%` multiplica por 100 y añade el símbolo: «-10.06%» ocupa siete caracteres, así que el ancho 8 deja
un espacio delante.

3. Un fichero de un bróker trae la fecha `"04/03/2025 09:30"` en formato estadounidense (mes/día/año). Conviértela a
   ISO 8601 y escribe el día de la semana en español.

**Solución.**

```python
momento = datetime.strptime("04/03/2025 09:30", "%m/%d/%Y %H:%M")
print(momento.isoformat())
print(DIAS[momento.weekday()])
```
```output
2025-04-03T09:30:00
jueves
```

El patrón de `strptime` tiene que describir el texto tal como llega: `%m/%d` y no `%d/%m`. Con el patrón español, la
fecha se habría leído como 4 de marzo sin ningún error: es el fallo más peligroso, porque no avisa.

[^1]: Yahoo Finance: [ES=F](https://finance.yahoo.com/quote/ES=F/) y [EURUSD=X](https://finance.yahoo.com/quote/EURUSD=X/).
Cada proveedor documenta sus símbolos; al cambiar de proveedor, hay que traducirlos.

## Autoevaluación

1. ¿Qué devuelve `"EURUSD"[3:]`?

   - A. `"EUR"`
   - B. `"USD"`
   - C. `"RUSD"`
   - D. `"URUSD"`

   *Respuesta B: `"USD"`.* Los índices empiezan en 0 y el corte `[3:]` va desde el carácter de índice 3 hasta el final: `U`, `S`, `D`. `[:3]` daría `EUR`.

2. ¿Qué muestra `f"{-1288.04:+,.2f}"`?

   - A. `-1288.04`
   - B. `-1.288,04`
   - C. `-1,288.04`
   - D. `+1,288.04`

   *Respuesta C: `-1,288.04`.* `+` pide el signo siempre (aquí el número ya es negativo), `,` agrupa los miles con comas y `.2f` deja dos decimales. El formato es el inglés: la coma separa miles y el punto, decimales.

3. ¿Por qué los ficheros de datos guardan las fechas como `2025-12-31` y no como `31/12/2025`?

   - A. Porque Python no sabe leer el formato español
   - B. Porque ISO 8601 no es ambiguo y, como texto, se ordena igual que en el calendario
   - C. Porque ocupa menos
   - D. Porque lo exige pandas

   *Respuesta B: Porque ISO 8601 no es ambiguo y, como texto, se ordena igual que en el calendario.* `03/04/2025` es 3 de abril en España y 4 de marzo en Estados Unidos; `2025-04-03` solo tiene una lectura. Y al ir de lo mayor (año) a lo menor (día), el orden alfabético coincide con el cronológico.

4. Un script formatea los importes con `f"{x:n}"` para que salgan con el formato del sistema. ¿Qué problema tiene?

   - A. Ninguno: es la forma recomendada
   - B. Que `n` no admite decimales
   - C. Que el resultado depende del `locale` de cada máquina: el mismo script escribe informes distintos en el portátil y en el servidor
   - D. Que es más lento

   *Respuesta C: Que el resultado depende del `locale` de cada máquina: el mismo script escribe informes distintos en el portátil y en el servidor.* `n` usa la configuración regional activa, que cambia entre máquinas y en muchos servidores es `C` (sin separador de miles). Un informe tiene que salir igual en todas partes: mejor fijar el formato en el código.

## Ideas clave

- Una cadena es una secuencia inmutable de caracteres: se indexa desde 0, se corta con `[inicio:fin]` y sus métodos devuelven cadenas nuevas.
- Para trocear símbolos, `partition` y `split` separan por un carácter y `removesuffix` quita un sufijo conocido. Normaliza con `strip` y `upper` antes de comparar.
- El formato va tras los dos puntos de la f-string: `{pnl:>+12,.2f}` es alineado a la derecha, con signo, 12 de ancho, miles con coma y dos decimales.
- El formato español se obtiene intercambiando comas y puntos con `translate`, sin tocar el `locale`: así el informe sale igual en cualquier máquina.
- Las fechas se guardan en ISO 8601 (`2025-12-31`): no son ambiguas y se ordenan bien como texto. `strftime` y `strptime` convierten entre `date` y cualquier otro formato.

## Recursos

### Research

- [Curso de Python para Trading, parte 1: datos de mercado con OpenBB](https://pythonparatrading.com/openbb-datos-de-mercado-gratuitos/) · Research-0014 · 10 min

### Documentación

- [Python: mini-lenguaje de especificación de formato (documentación oficial)](https://docs.python.org/3/library/string.html#format-specification-mini-language) · docs.python.org
- [Python: métodos de las cadenas (documentación oficial)](https://docs.python.org/3/library/stdtypes.html#string-methods) · docs.python.org
- [Python: códigos de formato de strftime y strptime (documentación oficial)](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes) · docs.python.org

### Datos

- [Datos del nivel 0: 40 operaciones ficticias (CSV)](https://pythonparatrading.com/curso/datos/nivel-0/operaciones_sint.csv) · Descarga · operaciones_sint.csv

Lección anterior: [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/)

Lección siguiente: [0.3.3 · Listas, tuplas, diccionarios, conjuntos y comprensiones](https://pythonparatrading.com/curso-python-trading/nivel-0/listas-tuplas-diccionarios-y-conjuntos/)
