Lección 0.3.2 · 45 min
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.
- Módulo
- 0.3 · Fundamentos del lenguaje
- Duración
- 45 min
Lecciones del nivel 0
0.1 · Bienvenida y expectativas
0.2 · Entorno de trabajo moderno
- 0.2.1 Instalación de Python con uv: versiones y ciclo de soporte
- 0.2.2 Entornos virtuales y dependencias: uv init, uv add, pyproject.toml y lockfiles
- 0.2.3 Editores (VS Code, Cursor, PyCharm) y notebooks (Jupyter, JupyterLab, marimo)
- 0.2.4 Terminal básica, rutas, variables de entorno y ficheros .env
- 0.2.5 Git y GitHub: commits, ramas, pull requests y .gitignore
- 0.2.6 Introducción a los asistentes de código con IA: prompts, revisión y tests
0.3 · Fundamentos del lenguaje
- 0.3.1 Variables, tipos y operadores; coma flotante y decimal para precios y ticks
- 0.3.2 Cadenas, f-strings y formato de números y fechas
- 0.3.3 Listas, tuplas, diccionarios, conjuntos y comprensiones
- 0.3.4 Control de flujo: if, elif, else, for, while y match-case
- 0.3.5 Funciones: argumentos, *args y **kwargs, lambdas y ámbito
- 0.3.6 Excepciones; módulos y paquetes, con los imports perezosos de Python 3.15
0.4 · Python intermedio
- 0.4.1 Programación orientada a objetos: clases, herencia, composición y dataclasses
- 0.4.2 Tipado: type hints, mypy o ty y Pydantic para configuraciones
- 0.4.3 Iteradores, generadores, decoradores y context managers
- 0.4.4 Ficheros (CSV, JSON, YAML, TOML) y fechas con zonas horarias
- 0.4.5 Logging profesional y testing con pytest
- 0.4.6 Concurrencia: threading, multiprocessing, asyncio y free-threading
0.5 · Computación numérica y datos
- 0.5.1 NumPy 2: arrays, broadcasting, vectorización, álgebra lineal y generadores aleatorios
- 0.5.2 pandas 3.0: Series, DataFrame, índices temporales, Copy-on-Write, tipo str y pd.col()
- 0.5.3 Selección, filtrado, groupby, merge, pivot, resample y ventanas móviles
- 0.5.4 Polars: API lazy frente a eager, expresiones, rendimiento y cuándo elegirlo frente a pandas
- 0.5.5 DuckDB y SQL esencial: SELECT, JOIN, GROUP BY y funciones de ventana sobre Parquet
0.6 · Visualización
0.7 · Matemáticas y estadística mínimas
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.
par = "EURUSD"
print(len(par), par[0], par[-1])
print(par[:3], par[3:])
print(par[::-1])
print("USD" in par, "JPY" in par)6 E D
EUR USD
DSURUE
True Falselen 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:
base, cotizada = "EUR", "USD"
simbolo = base + "/" + cotizada
print(simbolo)
print("-" * 20)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 BTCUSDT1Nota 1 · Yahoo Finance: ES=F y EURUSD=X.
Cada proveedor documenta sus símbolos; al cambiar de proveedor, hay que traducirlos..
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:
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("/", ""))' es=f \n' -> 'ES=F'
ES F True
BTC USD
EURUSD
EURUSDrepr 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.
El troceado sigue el mismo esquema para cualquier proveedor: limpiar, comprobar el formato y separar las partes.
Fuente 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:
ticker = "SINT"
cierre = 142.22
anterior = 139.98
print(f"{ticker} cierra en {cierre}")
print(f"Variación: {cierre - anterior}")SINT cierra en 142.22
Variación: 2.240000000000009Dentro 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.
print(f"{cierre=} {anterior=}")
print(f"{cierre - anterior = :.2f}")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 |
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}]")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:
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}")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.71La 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:
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)} €")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.
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.
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))['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 |
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())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:00Las 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.
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")miércoles, 31 de diciembre de 2025
Informe_20251231.txtDIAS[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.
Ejercicios
- Escribe el código que, a partir de
" usdjpy ", obtenga el símbolo normalizadoUSDJPY, la divisa base y la cotizada, y lo muestre comoUSD/JPY.
Solución
texto = " usdjpy "
par = texto.strip().upper()
base, cotizada = par[:3], par[3:]
print(par, base, cotizada)
print(f"{base}/{cotizada}")USDJPY USD JPY
USD/JPYstrip quita los espacios, upper pasa a mayúsculas y los cortes [:3] y [3:] separan las dos divisas, que
siempre tienen tres letras.
- Con
pnl = -1288.04yrent = -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
pnl = -1288.04
rent = -0.10063
print(f"P&L: {pnl:>12,.2f} | Rent.: {rent:>8.2%}")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.
- 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
momento = datetime.strptime("04/03/2025 09:30", "%m/%d/%Y %H:%M")
print(momento.isoformat())
print(DIAS[momento.weekday()])2025-04-03T09:30:00
juevesEl 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.
Footnotes
Respuesta plegada en cada pregunta
Autoevaluación
¿Qué devuelve
"EURUSD"[3:]?- A
"EUR" - B
"USD" - C
"RUSD" - D
"URUSD"
Ver respuesta
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íaEUR.- A
¿Qué muestra
f"{-1288.04:+,.2f}"?- A
-1288.04 - B
-1.288,04 - C
-1,288.04 - D
+1,288.04
Ver respuesta
Respuesta C.
-1,288.04+pide el signo siempre (aquí el número ya es negativo),,agrupa los miles con comas y.2fdeja dos decimales. El formato es el inglés: la coma separa miles y el punto, decimales.- A
¿Por qué los ficheros de datos guardan las fechas como
2025-12-31y no como31/12/2025?- APorque Python no sabe leer el formato español
- BPorque ISO 8601 no es ambiguo y, como texto, se ordena igual que en el calendario
- CPorque ocupa menos
- DPorque lo exige pandas
Ver respuesta
Respuesta B. Porque ISO 8601 no es ambiguo y, como texto, se ordena igual que en el calendario
03/04/2025es 3 de abril en España y 4 de marzo en Estados Unidos;2025-04-03solo 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.Un script formatea los importes con
f"{x:n}"para que salgan con el formato del sistema. ¿Qué problema tiene?- ANinguno: es la forma recomendada
- BQue
nno admite decimales - CQue el resultado depende del
localede cada máquina: el mismo script escribe informes distintos en el portátil y en el servidor - DQue es más lento
Ver respuesta
Respuesta C. Que el resultado depende del
localede cada máquina: el mismo script escribe informes distintos en el portátil y en el servidornusa la configuración regional activa, que cambia entre máquinas y en muchos servidores esC(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,
partitionysplitseparan por un carácter yremovesuffixquita un sufijo conocido. Normaliza constripyupperantes 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 ellocale: 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.strftimeystrptimeconvierten entredatey cualquier otro formato.