Lección 0.3.5 · 55 min

Funciones: argumentos, *args y **kwargs, lambdas y ámbito

Un cálculo que se repite se escribe una vez, con nombre, y se prueba. La lección construye funciones de trading —tamaño de posición por riesgo, drawdown máximo, ordenar operaciones— y muestra los dos errores clásicos de las funciones: el ámbito de las variables y el argumento por defecto mutable.

Fig. — Acciones de SINT a 142,22 que arriesgan 250 € según la distancia al stop: cuanto más cerca el stop, mayor la posición
05/06
Módulo
0.3 · Fundamentos del lenguaje
Duración
55 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

Por qué funciones

En las lecciones anteriores, cada cálculo se escribía donde se necesitaba. El trailing stop de la lección 0.3.4 se escribió dos veces, una con el 5 % y otra con el 8 %, cambiando un número en dos sitios. Así empiezan los errores: se cambia uno y se olvida el otro. Una función da nombre a un cálculo, lo escribe una sola vez y lo deja listo para usarlo con datos distintos. Además, una función se puede probar por separado (lección 0.4.5): si el cálculo del tamaño de posición está en una función, se comprueba una vez y se confía en él en todas partes.

Una función se define con def, su nombre, sus parámetros entre paréntesis y dos puntos. El cuerpo va sangrado, y return devuelve el resultado a quien la llama:

IN [01]python
def pnl_operacion(entrada, salida, cantidad, lado="largo"):
    """P&L bruto de una operación cerrada.

    `lado`: "largo" (ganas si sube) o "corto" (si baja).
    """
    signo = 1 if lado == "largo" else -1
    return signo * (salida - entrada) * cantidad

print(pnl_operacion(102.83, 110.07, 200))
print(pnl_operacion(104.30, 111.86, 40, "corto"))
print(round(pnl_operacion(104.30, 111.86, 40, lado="corto"), 2))
OUT [01]Una función con su docstring
1447.999999999999
-302.4000000000001
-302.4

El texto entre triples comillas justo debajo del def es el docstring: la documentación de la función, que muestran help(pnl_operacion) y los editores al pasar el ratón. Los valores que se pasan al llamarla son los argumentos; se asignan a los parámetros por posición (el primero a entrada, el segundo a salida…) o por nombre (lado="corto"). lado="largo" en la definición es un valor por defecto: si no se pasa, vale "largo". Las colas de decimales de las dos primeras líneas son la coma flotante de la lección 0.3.1; round las quita al mostrar el resultado.

Una función sin return devuelve None. Es un error frecuente: el cálculo se hace, pero el resultado se pierde.

IN [02]python
def rentabilidad_sin_return(entrada, salida):
    r = salida / entrada - 1          # se calcula… y se pierde

resultado = rentabilidad_sin_return(102.83, 110.07)
print(resultado)
OUT [02]Sin return no hay resultado
None

Tamaño de posición: argumentos por defecto y por nombre

El tamaño de posición por riesgo es la función más importante de este nivel. La idea: decidir cuánto dinero se está dispuesto a perder si salta el stop (un porcentaje fijo del capital, el riesgo por operación) y comprar las acciones que hacen que, en ese caso, la pérdida sea exactamente esa. Si se arriesga una fracción ff de un capital CC, con entrada EE y stop SS:

acciones=⌊f⋅CE−S⌋\text{acciones} = \left\lfloor \frac{f \cdot C}{E - S} \right\rfloor

Los corchetes ⌊⋅⌋\lfloor \cdot \rfloor indican redondeo hacia abajo: nunca se compra una acción de más, porque la pérdida superaría el riesgo fijado. Si el activo se opera por lotes, se redondea hacia abajo al lote.

IN [03]python
def tamano_posicion(capital, entrada, stop, *, riesgo=0.01,
                    lote=1):
    """Acciones que arriesgan `riesgo` × `capital` hasta el stop.

    Solo posiciones largas: `stop` por debajo de `entrada`.
    """
    riesgo_por_accion = entrada - stop
    acciones = int(capital * riesgo / riesgo_por_accion)
    return acciones // lote * lote

n = tamano_posicion(25_000, 142.22, 137.95)
print(n, f"{n * (142.22 - 137.95):.2f}", f"{n * 142.22:,.2f}")
print(tamano_posicion(25_000, 142.22, 137.95, riesgo=0.02))
print(tamano_posicion(25_000, 142.22, 137.95, lote=10))
OUT [03]Tamaño de posición por riesgo
58 247.66 8,248.76
117
50

Con 25.000 € y un 1 % de riesgo, la pérdida máxima es 250 €. La distancia al stop es 4,27 € por acción, así que caben 58 acciones: si salta el stop se pierden 247,66 €, y la posición vale unos 8.250 €, un tercio de la cuenta. int() corta los decimales, que con números positivos es redondear hacia abajo, lo que aquí se busca; para un número de ticks no lo era (lección 0.3.1), porque allí el resultado debía ser exacto.

El asterisco solo, *, en la lista de parámetros cambia las reglas: todo lo que va detrás solo se puede pasar por nombre. Es una protección deliberada. Una función con cinco números seguidos es fácil de llamar mal, y tamano_posicion(25_000, 142.22, 137.95, 0.02) no dice si 0,02 es un riesgo, una comisión o un lote. Python lo rechaza:

IN [04]python
tamano_posicion(25_000, 142.22, 137.95, 0.02)
OUT [04]riesgo solo se puede pasar por nombre
TypeError: tamano_posicion() takes 3 positional arguments but 4 were given

La función tiene un hueco: si el stop está por encima de la entrada, la distancia es negativa y el resultado también. Validar las entradas y avisar con una excepción es materia de la lección 0.3.6.

La relación entre stop y tamaño es inversa: cuanto más cerca el stop, más acciones. La figura de la cabecera la dibuja con los números de la celda siguiente:

IN [05]python
for distancia in (0.01, 0.02, 0.03, 0.05, 0.08, 0.10):
    stop = round(142.22 * (1 - distancia), 2)
    n = tamano_posicion(25_000, 142.22, stop)
    print(f"stop a {distancia:>4.0%} ({stop:>6.2f}): {n:>3} acciones,"
          f" exposición {n * 142.22 / 25_000:>4.0%}")
OUT [05]Tamaño según la distancia al stop
stop a   1% (140.80): 176 acciones, exposición 100%
stop a   2% (139.38):  88 acciones, exposición  50%
stop a   3% (137.95):  58 acciones, exposición  33%
stop a   5% (135.11):  35 acciones, exposición  20%
stop a   8% (130.84):  21 acciones, exposición  12%
stop a  10% (128.00):  17 acciones, exposición  10%

Con un stop al 1 %, el mismo riesgo de 250 € exige una posición del tamaño de toda la cuenta; de hecho algo mayor, porque 176 acciones valen 25.031 €, y la función no lo impide. Un stop muy cercano no reduce el riesgo, lo cambia de sitio: la pérdida prevista es la misma, pero la exposición es enorme, y un hueco de apertura por debajo del stop (lección 0.3.4) se lleva mucho más de 250 €. El nivel 4 vuelve sobre el tamaño de posición con volatilidad y con límites de exposición.

Una función que recorre una serie: el drawdown máximo

El drawdown es la caída desde el máximo anterior; el drawdown máximo, la mayor de esas caídas en todo el periodo. Es la medida de dolor de una estrategia (lección 0.1.2): cuánto llegó a perder quien entró en el peor momento. Calcularlo exige recorrer la serie recordando el máximo visto hasta cada día:

IN [06]python
def drawdown_maximo(valores):
    """Mayor caída desde un máximo previo y sus posiciones.

    Devuelve (drawdown, posición del máximo, posición del mínimo).
    """
    pico, i_pico = valores[0], 0
    peor, i_ini, i_fin = 0.0, 0, 0
    for i, v in enumerate(valores):
        if v > pico:
            pico, i_pico = v, i
        caida = v / pico - 1
        if caida < peor:
            peor, i_ini, i_fin = caida, i_pico, i
    return peor, i_ini, i_fin

print(drawdown_maximo([100, 110, 99, 105, 120, 90, 130]))
OUT [06]Drawdown máximo de una serie
(-0.25, 4, 5)

Una función puede devolver varios valores separándolos con comas: lo que devuelve es una tupla, que se desempaqueta al recibirla (lección 0.3.3). En la serie de prueba, la peor caída va de 120 (posición 4) a 90 (posición 5): un −25 %. Probar una función con datos pequeños cuyo resultado se conoce de antemano es la costumbre que convierte el código en algo fiable.

Con la función escrita y probada, aplicarla a los cierres de SINT es una línea:

IN [07]python
import csv

with open("sint_diario.csv", encoding="utf-8") as f:
    filas = list(csv.DictReader(f))
fechas = [r["fecha"] for r in filas]
cierres = [float(r["cierre"]) for r in filas]

dd, i_ini, i_fin = drawdown_maximo(cierres)
print(f"Drawdown máximo {dd:.1%}: de {cierres[i_ini]:.2f} el "
      f"{fechas[i_ini]} a {cierres[i_fin]:.2f} el {fechas[i_fin]}")
OUT [07]Drawdown máximo de SINT
Drawdown máximo -25.0%: de 125.98 el 2022-03-07 a 94.45 el 2022-10-20

Del máximo de marzo de 2022 al mínimo de octubre, SINT cae un 25 %. Según la cuenta de la lección 0.1.2, recuperar esa caída exige después una subida del 33,3 %. La lección 0.6.1 dibuja esta serie y su drawdown completo.

Argumentos variables: *args y **kwargs

Algunas funciones no saben de antemano cuántos argumentos van a recibir. Un parámetro con un asterisco, *args, recoge todos los argumentos posicionales que sobren en una tupla; uno con dos, **kwargs, recoge los que se pasan por nombre y no tienen parámetro propio en un diccionario. Los nombres args y kwargs son una convención: lo que importa son los asteriscos.

IN [08]python
def coste_total(*comisiones, minimo=1.0):
    """Suma comisiones; ninguna por debajo del mínimo."""
    return round(sum(max(c, minimo) for c in comisiones), 2)

print(coste_total(5.14, 0.42, 7.10))
print(coste_total(5.14, 0.42, 7.10, minimo=0.0))

def crear_orden(simbolo, cantidad, **opciones):
    """Orden de mercado por defecto; el resto, por nombre."""
    orden = {"simbolo": simbolo, "cantidad": cantidad,
             "tipo": "mercado"}
    orden.update(opciones)
    return orden

print(crear_orden("SINT", 58))
print(crear_orden("SINT", 58, tipo="limite", precio=141.50,
                  validez="dia"))
OUT [08]Comisiones sueltas y opciones de una orden
13.24
12.66
{'simbolo': 'SINT', 'cantidad': 58, 'tipo': 'mercado'}
{'simbolo': 'SINT', 'cantidad': 58, 'tipo': 'limite', 'precio': 141.5, 'validez': 'dia'}

max(c, minimo) for c in comisiones dentro de sum es una comprensión sin corchetes (una expresión generadora, que se ve en la lección 0.4.3). update añade al diccionario de la orden las parejas de otro diccionario, sustituyendo las claves que ya existan: por eso tipo="limite" reemplaza al tipo por defecto.

Los asteriscos también funcionan al revés, al llamar: *lista reparte una secuencia en argumentos posicionales y **diccionario reparte un diccionario en argumentos por nombre. Es la forma de guardar la configuración de una estrategia en un diccionario y pasársela a una función:

IN [09]python
comisiones_dia = [5.14, 0.42, 7.10, 2.00]
print(coste_total(*comisiones_dia))

config = {"riesgo": 0.005, "lote": 5}
print(tamano_posicion(25_000, 142.22, 137.95, **config))
OUT [09]Desempaquetar al llamar
15.24
25

Úsalos con mesura. Una función con **kwargs acepta cualquier nombre, también uno mal escrito: crear_orden("SINT", 58, presio=141.50) crea una orden con una clave presio que nadie va a leer, sin ningún aviso. Cuando los parámetros se conocen, es mejor escribirlos.

Lambdas: funciones de una línea

Una lambda es una función sin nombre y de una sola expresión: lambda op: op["pnl"] equivale a una función que recibe op y devuelve op["pnl"]. Su uso principal es el argumento key de sorted, max y min, que dice por qué comparar los elementos. Con las 40 operaciones de operaciones_sint.csv:

IN [10]python
with open("operaciones_sint.csv", encoding="utf-8") as f:
    ops = list(csv.DictReader(f))
for op in ops:
    bruto = pnl_operacion(float(op["precio_entrada"]),
                          float(op["precio_salida"]),
                          int(op["cantidad"]), op["lado"])
    op["pnl"] = round(bruto - float(op["comision"]), 2)

por_pnl = sorted(ops, key=lambda op: op["pnl"])
for op in por_pnl[:2] + por_pnl[-2:]:
    print(op["id"], op["simbolo"], op["lado"], f"{op['pnl']:+,.2f}")

mayor = max(ops, key=lambda op: int(op["cantidad"]))
print("Mayor tamaño:", mayor["id"], mayor["cantidad"])
OUT [10]Las mejores y peores operaciones
1 ORO corto -3,123.56
8 SINT largo -1,288.04
13 SINT largo +1,011.70
7 SINT largo +1,426.71
Mayor tamaño: 1 200

sorted devuelve las operaciones completas, ordenadas de menor a mayor P&L; por_pnl[:2] son las dos peores y por_pnl[-2:], las dos mejores. max con key devuelve el elemento entero, no el valor de la clave. pnl_operacion, la función del principio de la lección, se reutiliza tal cual: para eso se escribió.

Una lambda que se asigna a un nombre (riesgo = lambda c: c * 0.01) es una mala idea: para eso está def, que da un nombre visible en los errores y admite docstring.

Ámbito: dónde vive cada variable

Las variables que se asignan dentro de una función son locales: existen solo mientras la función se ejecuta y no afectan a las de fuera aunque se llamen igual. Para leer un nombre, Python lo busca en cuatro sitios, en este orden (la regla LEGB):

LocalFunción externaGlobalIntegrados
Fuente Mermaid
flowchart LR
  L[Local] --> E[Función externa] --> G[Global] --> B[Integrados]

Primero en la función (local), después en la función que la contiene si la hay (enclosing), después en el módulo (global) y por último en los nombres integrados de Python (print, len, max…). La consecuencia práctica:

IN [11]python
capital = 25_000

def capital_tras_perdida(perdida):
    capital = 25_000 - perdida      # local: otra variable
    return capital

print(capital_tras_perdida(250), capital)
OUT [11]Local frente a global
24750 25000

La función tiene su propio capital, y el global sigue intacto. Si dentro de la función se intenta modificar la variable global a partir de sí misma, Python da un error, porque una asignación hace local el nombre en toda la función, también en la línea que intenta leerlo antes:

IN [12]python
def pagar_comision(comision):
    capital -= comision            # lee y asigna: es local

pagar_comision(5.14)
OUT [12]Modificar una global desde una función
UnboundLocalError: cannot access local variable 'capital' where it is not associated with a value

Existe la palabra global para saltarse esta regla, y no conviene usarla: una función que cambia variables de fuera es difícil de probar y de seguir. La forma limpia es recibir el valor como argumento y devolver el nuevo: capital = pagar_comision(capital, 5.14).

El argumento por defecto mutable

El último error es el más traicionero porque no da ningún mensaje. El valor por defecto de un parámetro se evalúa una sola vez, al definir la función, no en cada llamada. Si ese valor es una lista o un diccionario, todas las llamadas comparten el mismo objeto (la trampa de «asignar no es copiar» de la lección 0.3.3):

IN [13]python
def registrar_orden(orden, libro=[]):
    libro.append(orden)
    return libro

dia_1 = registrar_orden({"simbolo": "SINT", "cantidad": 58})
dia_2 = registrar_orden({"simbolo": "ORO", "cantidad": 20})
print(len(dia_2), dia_1 is dia_2)
print([o["simbolo"] for o in dia_2])
OUT [13]Un libro de órdenes que no se vacía
2 True
['SINT', 'ORO']

El segundo día empieza con la orden del primero dentro. En un sistema real, la orden de ayer se volvería a enviar. La solución es usar None como valor por defecto y crear la lista dentro, en cada llamada:

IN [14]python
def registrar_orden(orden, libro=None):
    if libro is None:
        libro = []
    libro.append(orden)
    return libro

dia_1 = registrar_orden({"simbolo": "SINT", "cantidad": 58})
dia_2 = registrar_orden({"simbolo": "ORO", "cantidad": 20})
print(len(dia_1), len(dia_2), dia_1 is dia_2)
OUT [14]La forma correcta
1 1 False

La regla: nunca un [], {} o set() como valor por defecto. Números, cadenas, tuplas, True, False y None son inmutables y no tienen el problema.

Ejercicios

  1. Escribe rentabilidades(cierres), que devuelva la lista de rentabilidades diarias de una lista de cierres (usa la comprensión de la lección 0.3.3), y pruébala con [100, 110, 99]. Después calcula cuántas sesiones de SINT bajaron más de un 3 %.
Solución
IN [15]python
def rentabilidades(cierres):
    """Rentabilidad de cada cierre respecto al anterior."""
    return [c1 / c0 - 1 for c0, c1 in zip(cierres, cierres[1:])]

print([round(r, 4) for r in rentabilidades([100, 110, 99])])
caidas = [r for r in rentabilidades(cierres) if r < -0.03]
print(len(caidas))
OUT [15]Rentabilidades como función
[0.1, -0.1]
11

La prueba con tres números confirma la función (+10 % y −10 %) antes de usarla con los 1.303 cierres de SINT.

  1. Amplía tamano_posicion con un parámetro solo por nombre max_exposicion=1.0 (fracción del capital) que limite el valor de la posición. Con un stop al 1 % (140,80), ¿cuántas acciones da con max_exposicion=0.5?
Solución
IN [16]python
def tamano_posicion(capital, entrada, stop, *, riesgo=0.01,
                    lote=1, max_exposicion=1.0):
    """Acciones por riesgo, con un tope de exposición."""
    por_riesgo = int(capital * riesgo / (entrada - stop))
    por_exposicion = int(capital * max_exposicion / entrada)
    acciones = min(por_riesgo, por_exposicion)
    return acciones // lote * lote

print(tamano_posicion(25_000, 142.22, 140.80))
print(tamano_posicion(25_000, 142.22, 140.80, max_exposicion=0.5))
OUT [16]Límite de exposición
175
87

Sin pasar el tope, la función ya da 175 acciones y no las 176 de antes: con el valor por defecto, 1.0, la posición no puede valer más que la cuenta. Con max_exposicion=0.5, 87. El tope de exposición manda cuando el stop es muy cercano: el riesgo por operación sigue por debajo de 250 €, pero la posición ya no puede comprometer más de la mitad de la cuenta. Como el parámetro tiene valor por defecto, las llamadas antiguas siguen funcionando igual.

  1. Con la lista ops de la lección, usa sorted y una lambda para obtener las tres operaciones con mayor pérdida en porcentaje sobre el valor de entrada (P&L entre precio de entrada por cantidad).
Solución
IN [17]python
def pct(op):
    return op["pnl"] / (float(op["precio_entrada"])
                        * int(op["cantidad"]))

for op in sorted(ops, key=pct)[:3]:
    print(op["id"], op["simbolo"], f"{pct(op):+.2%}",
          f"{op['pnl']:+,.2f}")
OUT [17]Peores operaciones en porcentaje
8 SINT -10.16% -1,288.04
1 ORO -8.22% -3,123.56
4 SINT -7.35% -306.72

Cuando el cálculo de la clave no cabe con claridad en una lambda, una función con def sirve igual como key. Las peores operaciones en porcentaje no tienen por qué ser las peores en euros: dependen del tamaño de cada una.

Respuesta plegada en cada pregunta

Autoevaluación

  1. Una función def tamano(capital, entrada, stop, *, riesgo=0.01) se llama con tamano(25_000, 142.22, 137.95, 0.02). ¿Qué pasa?

    • AUsa un riesgo del 2 %
    • BUsa un riesgo del 1 % e ignora el 0,02
    • CDa un TypeError: riesgo solo se puede pasar por nombre
    • DUsa 0,02 como stop
    Ver respuesta

    Respuesta C. Da un TypeError: riesgo solo se puede pasar por nombre

    Lo que va detrás de * en la definición solo se puede pasar por nombre (riesgo=0.02). Así nadie confunde el riesgo con otro número por su posición.

  2. ¿Qué devuelve una función que calcula un resultado pero no tiene return?

    • AEl último valor calculado
    • BNone
    • C0
    • DDa un error al llamarla
    Ver respuesta

    Respuesta B. None

    Sin return, una función devuelve None. Es un error típico: el cálculo se hace, pero el valor se pierde y quien la llama recibe None.

  3. def registrar(orden, libro=[]) añade la orden a libro y lo devuelve. Tras llamarla dos veces sin libro, ¿cuántas órdenes tiene la lista de la segunda llamada?

    • A1
    • B2: la lista por defecto se crea una sola vez y se comparte entre llamadas
    • C0
    • DDa un error
    Ver respuesta

    Respuesta B. 2: la lista por defecto se crea una sola vez y se comparte entre llamadas

    El valor por defecto se evalúa una vez, al definir la función. Las dos llamadas usan la misma lista, que va acumulando órdenes. La solución es libro=None y crear la lista dentro.

  4. ¿Qué hace sorted(operaciones, key=lambda op: op["pnl"])?

    • AOrdena las operaciones por su P&L, de menor a mayor
    • BDevuelve solo los P&L ordenados
    • COrdena por el nombre del símbolo
    • DModifica la lista operaciones
    Ver respuesta

    Respuesta A. Ordena las operaciones por su P&L, de menor a mayor

    key recibe una función que se aplica a cada elemento para saber por qué ordenar. La lista que devuelve tiene las operaciones completas, y la original no cambia.

Ideas clave

  • Una función da nombre a un cálculo: def, parámetros, docstring y return. Sin return, devuelve None.
  • Los parámetros con valor por defecto se pueden omitir; los que van tras * solo se pasan por nombre, lo que evita confundir riesgo, lote o comisión por su posición.
  • *args recoge argumentos posicionales sueltos en una tupla y **kwargs los que van por nombre en un diccionario.
  • lambda es una función de una expresión, útil como key de sorted, max y min.
  • Las variables asignadas dentro de una función son locales. Un valor por defecto mutable ([], {}) se comparte entre llamadas: usa None.

Recursos

Datos