0% encontró este documento útil (0 votos)
152 vistas376 páginas

Material de Estudio Intermedio - Python UCEMA - Versión de Preliminar

Cargado por

Federico Meyer
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
152 vistas376 páginas

Material de Estudio Intermedio - Python UCEMA - Versión de Preliminar

Cargado por

Federico Meyer
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 376

Material de estudio de Python aplicado a finanzas

Apuntes de clases de la UCEMA

Stack Overflow es un sitio de preguntas y respuestas para programadores profesionales y


aficionados. En este podremos preguntar y encontrar respuestas códigos que resuelvan nuestra
inquietudes, por ende, aquí podremos copiar y pegar código.

NOTA: cuando importamos una librería lo que hacemos es lo sugerido por la palabra importar, sin
embrago, esto es una posibilidad porque la librería se encuentra en el paquete anaconda. Cuando la
librería deseado no está incluida, es necesario escribir el siguiente código antes de importarla:
𝑝𝑖𝑝 𝑖𝑛𝑠𝑡𝑎𝑙𝑙 𝑛𝑜𝑚𝑏𝑟𝑒
Donde “nombre” es el nombre de la librería.
INDICE

PRIMERA PARTE: BASES - INTRODUCCION ........................................................................10


Números aleatorios ..................................................................................................................11
Colecciones de datos ................................................................................................................12
Estructuras de colección en Python...........................................................................................13
Listas .......................................................................................................................................14
Ejercicios simples de listas ...................................................................................................16
Funciones y métodos de listas...............................................................................................17
Generadores .........................................................................................................................22
Listas por comprensión.........................................................................................................22
Sets ..........................................................................................................................................24
SET - Intersección de colecciones ........................................................................................27
Ejercicio de intersección.......................................................................................................27
Tuplas ......................................................................................................................................29
Bucle for - Introducción ...........................................................................................................31
Bucle for – Creando una lista................................................................................................32
Bucle for – Ciclo con randomzación: Montecarlo .................................................................32
Bucle for – Productorias y sumatorias...................................................................................34
Operadores de asignación.........................................................................................................35
Diccionarios.............................................................................................................................36
Métodos y funciones para los diccionarios ............................................................................38
Recorriendo un diccionario – Bucle for.................................................................................39
Función zip ..........................................................................................................................40
De zip a diccionario & de zip a lista ..................................................................................41
Operaciones con diccionarios ...............................................................................................42
Numpy (array) – Operatoria matricial.......................................................................................44
Métodos, funciones, y atributos de numpy ............................................................................45
Números aleatorios con Numpy ............................................................................................46
Generadores numpy..............................................................................................................48
Sumatorias y productorias con numpy ..................................................................................48
DataFrame: ¿Qué es? – 1/2.......................................................................................................51
DataFrame: algunas funciones y métodos (1/3) .....................................................................57
DataFrame: Importación y exportación de datos de mercado.................................................60
DataFrame: algunas funciones y métodos (2/3) .....................................................................63
DataFrame: Estadística descriptiva y algo más (3/3) .............................................................66
Excepciones y errores ..............................................................................................................77
Operadores lógicos...................................................................................................................79
Sentencia if & comando break ..................................................................................................80
SEGUNDA PARTE: BASES – PROFUNDIZANDO DATAFRAME ..........................................84
DataFrame: ¿Qué es? – 2/2.......................................................................................................85
Renombrar columnas ............................................................................................................87
Renombrar filas....................................................................................................................88
Armar DataFrame a partir de series ......................................................................................88
Armar DataFrame a partir de valores de una columna ...........................................................89
Puesta a punto de un DataFrame al importarlo ......................................................................90
Ordenando el DataFrame ......................................................................................................92
Filtro de columnas ................................................................................................................93
Ubicando índice con valores de columna ..............................................................................94
Slicing de filas y columnas (iloc) ..........................................................................................94
Función between() ................................................................................................................96
Columna nueva a partir del índice.........................................................................................96
Columna nueva a partir de otra pre-existente ........................................................................97
Extrayendo atributos de la columna creada ...........................................................................98
Comentario sobre el índice ................................................................................................. 100
Diferencia entre LOC e ILOC............................................................................................. 100
Leyendo un archivo CSV/TXT ........................................................................................... 103
Leyendo un HTML............................................................................................................. 104
Ordenando una DataFrame ................................................................................................. 105
Importar un archivo XLS.................................................................................................... 106
Accediendo a una Sub-matriz ............................................................................................. 107
Accediendo a una Sub-matriz con filtros condicionales....................................................... 108
Configuración de salida al importar (print) ......................................................................... 109
Tratamiento para valores perdidos “Nan” (sin información) ................................................ 110
Reseteo del índice .............................................................................................................. 112
Operaciones matriciales...................................................................................................... 112
Mascaras y función where() de numpy................................................................................ 114
Where() de DataFrame ....................................................................................................... 117
Resampleo ......................................................................................................................... 118
Cómo calcular estadísticas agrupando categorías ................................................................ 119
Quantiles y Ranks .................................................................................................................. 121
Rank .................................................................................................................................. 121
Quantiles ............................................................................................................................ 123
Matriz de resumen y retornos ................................................................................................. 124
Persistir objeto serializado...................................................................................................... 127
Histogramas de un DataFrame................................................................................................ 128
Diagramas de caja para DataFrame......................................................................................... 130
Funciones acumulativas ......................................................................................................... 131
Cummax() .......................................................................................................................... 131
Cummin()........................................................................................................................... 132
Uso combinado de cummax() y cummin() .......................................................................... 133
Cumsum() .......................................................................................................................... 134
Cumprod() ......................................................................................................................... 134
Librería MatPlotLib ............................................................................................................... 136
Escalas lineales y logarítimica ............................................................................................ 139
Seteo de escalas de eje xlim/ylim........................................................................................ 140
Sub gráficos ....................................................................................................................... 141
Estilos ................................................................................................................................ 142
Graficando diferentes parámetros estadísticos ..................................................................... 142
Sub librería Stats de la Librería scipy ..................................................................................... 147
Backtesting: Aproximación a look-ahead Bias y leakage ........................................................ 147
Ventana móvil: función Rolling() ....................................................................................... 147
Función de agrupamiento: Groupby() ................................................................................. 148
Segmentación: método cut() ............................................................................................... 151
Función clip() ........................................................................................................................ 153
Método apply() ...................................................................................................................... 155
TERCERA PARTE: BASES – CREACION DE FUNCIONES .................................................. 156
Creando funciones propias ..................................................................................................... 157
Funciones sin argumentos y sin return ................................................................................ 157
Funciones sin argumentos y con return ............................................................................... 158
Funciones con argumentos nativos y sin return ................................................................... 158
Funciones con argumentos nativos y con return .................................................................. 159
Funciones con argumentos obligatorios y/o opcionales ....................................................... 159
Funciones con argumentos especiales ................................................................................. 160
Funciones que modifican los argumentos (o no) ................................................................. 161
Funciones dentro de funciones............................................................................................ 162
Funciones que aceptan otras funciones como argumentos ................................................... 163
Funciones con argumentos que son colecciones ................................................................. 164
Importar nuestras propias funciones ....................................................................................... 166
Funciones para análisis técnico............................................................................................... 167
Bandas de bollinger ............................................................................................................ 167
Relative Strenght Index (RSI) ............................................................................................. 172
Moving Average Convergence Divergence (MACD) .......................................................... 176
Cruces de medias................................................................................................................ 178
Gráficos de correlaciones ................................................................................................... 180
CUARTA PARTE: BACK-TESTING........................................................................................ 181
Backtesting ............................................................................................................................ 182
Tipos de Backtests.............................................................................................................. 183
Etapas de un backtest ........................................................................................................ 184
Diferencia entre Backtesting y simulación .......................................................................... 188
Tipos de Bots de trading ..................................................................................................... 188
Enfoque matricial: Armado de las matrices básicas................................................................. 189
Ideas de parametrización ........................................................................................................ 194
Construyendo una cartera ....................................................................................................... 199
Análisis de métricas ............................................................................................................... 202
Retorno acumulado ............................................................................................................ 202
CAGR ................................................................................................................................ 203
Volatilidad ......................................................................................................................... 203
Indice de Sharpe ................................................................................................................. 204
Ratio Sortino ...................................................................................................................... 205
Max DrawDown ................................................................................................................. 206
Max DrawDown días.......................................................................................................... 207
Correlación: r^2 ................................................................................................................. 207
Calmar CAGR.................................................................................................................... 208
Asimetría y Kurtosis........................................................................................................... 208
Rendimientos diario, mensual, anual, máximo, y mínimo ................................................... 209
Kelly .................................................................................................................................. 210
Riesgo de ruina .................................................................................................................. 211
Value at risk (VaR) ............................................................................................................ 214
Conditional Value at Risk (CVaR) – Expected shortfall ...................................................... 216
PayOff Ratio ...................................................................................................................... 217
Profit Factor ....................................................................................................................... 217
Ratios de cola – Rachev Ratio ............................................................................................ 218
Outlier win/loss Ratio......................................................................................................... 219
Gráfico comparativo de la TEA .......................................................................................... 220
BackTest DashBoard – “Magia” ......................................................................................... 220
QUINTA PARTE: WEB SCRAPING ........................................................................................ 224
Introducción conceptual ......................................................................................................... 225
Interface Web. Web Scraping – Creando página de búsqueda ............................................. 229
Interface Web. Web Scraping – Obteniendo lo buscado ...................................................... 232
Application Programming Interface (API) – Alphavantage, datos ....................................... 234
Application Programming Interface (API) – Alpaca, uso de bot .......................................... 245
Scraping por ingeniería inversa........................................................................................... 246
SEXTA PARTE: BACK OFFICE .............................................................................................. 251
Flujo de tarea ......................................................................................................................... 252
Leer listado de clientes ....................................................................................................... 253
Conocer el directorio donde estamos............................................................................... 254
Cómo crear un directorio ................................................................................................ 254
Cómo ir hacia determinado directorio ............................................................................. 255
Cómo retroceder en el directorio ..................................................................................... 256
Cómo leer el listado de clientes....................................................................................... 256
Cómo armar una lista de activos para cada cliente ........................................................... 256
Preparando los datos........................................................................................................... 257
Importando los datos ...................................................................................................... 258
Analizando los datos....................................................................................................... 259
Carpetas por clientes .......................................................................................................... 260
Enviando correos................................................................................................................ 263
Comprimiendo y eliminando los archivos ........................................................................... 268
SEPTIMA PARTE: INTELIGENCIA ARTIFICIAL (IA) .......................................................... 269
Campos de estudio de la IA .................................................................................................... 270
Campos de estudio de la IA ................................................................................................ 270
Paradigmas de la IA ........................................................................................................... 271
Tipos de problemas a resolver ............................................................................................ 272
Entrenamiento supervisado – Regresiones .............................................................................. 273
Research previo.................................................................................................................. 273
Multicolinealidad ........................................................................................................... 275
Eficiencia ....................................................................................................................... 276
Diagrama de caja de regresores....................................................................................... 276
Autocorrelación.............................................................................................................. 278
Realizando la regresión ...................................................................................................... 279
Linealidad de las variables.............................................................................................. 280
Independencia de los errores ........................................................................................... 281
Normalidad de los residuos............................................................................................. 282
Multicolinealidad ........................................................................................................... 282
Homocedasticidad .......................................................................................................... 283
Ensayo de pre sensibilidad.................................................................................................. 285
Críticas a las regresiones .................................................................................................... 285
Entrenamiento supervisado – Clasificaciones ......................................................................... 287
Arboles de decisión ............................................................................................................ 288
Preparando la base de datos ............................................................................................ 290
Definiendo qué vamos a predecir .................................................................................... 292
Separando datos de entrenamiento de validación............................................................. 294
Importamos el modelo .................................................................................................... 296
Entrenando el modelo..................................................................................................... 297
Validando el modelo....................................................................................................... 297
Prediciendo .................................................................................................................... 302
Crítica a la técnica .......................................................................................................... 303
Graficando el modelo ..................................................................................................... 304
Guardando el modelo - Serialización .............................................................................. 305
Oversampling y Undersampling...................................................................................... 306
Regresión logística ............................................................................................................. 311
Preparando la base de datos ............................................................................................ 317
Entrenando el modelo..................................................................................................... 319
Prediciendo con el modelo .............................................................................................. 320
Construyendo la matriz de confusión .............................................................................. 320
Prediciendo con algún valor............................................................................................ 321
Regresión logística versus árbol de decisión ................................................................... 322
Máquinas de vectores de soporte (SVM)............................................................................. 322
Aplicación...................................................................................................................... 324
Comparación entre SVM y los otros métodos ................................................................. 327
Bosques aleatorios.............................................................................................................. 328
Aplicación...................................................................................................................... 329
Comparación entre Bosques aleatorios y los otros métodos ............................................. 331
Métricas para modelos predictivos ......................................................................................... 332
Entrenamiento no supervisado - Clustering............................................................................. 333
Clusterización – Conceptos ................................................................................................ 334
Clusterización – Sección gráfica ......................................................................................... 340
Dendograma jerárquico – Euclideo por distancia mínima ................................................ 340
Dendograma jerárquico – Euclideo por distancia máxima ............................................... 342
Dendograma jerárquico – Euclideo por varianza ............................................................. 344
Clusterización – Sección analítica....................................................................................... 345
Clusterización – Aplicación real ......................................................................................... 347
Entrenamiento no supervisado - Kmeans ................................................................................ 353
Ejemplo de aplicación ........................................................................................................ 355
Cantidad óptima de clusters ................................................................................................ 357
Método del Codo............................................................................................................ 359
Método de la silueta ....................................................................................................... 360
Método Taylor made ...................................................................................................... 362
Críticas al modelo Kmeans ................................................................................................. 363
Entrenamiento no supervisado – Mezcla Gaussiana ................................................................ 365
Entrenamiento no supervisado – DBSCAN ............................................................................ 368
Las ventajas de este modelo se resumen en los siguientes puntos: ....................................... 370
Mientras que las desventajas son: ....................................................................................... 371
Entrenamiento no supervisado – MeanShift ............................................................................ 373
Ejemplo de aplicación ............................................................................................................ 374
PRIMERA PARTE: BASES - INTRODUCCION
Números aleatorios
Si bien los apuntes manuscritos presentan funciones para generar números aleatorios a partir de la
librería random(), en ellos no se explica la posibilidad de generar al azar números enteros (sólo
float), ni tampoco, el cómo trabajar azarosamente con strings. En esta sección se presentarán
funciones de la librería random() que sí permiten lograr estos objetivos.
Entre las funciones que permiten crear al azar un número entero, tenemos el siguiente par:
randint(a,b), y randrange(a,b,salto). Veamos cada una a continuación:
 Randint(a,b). Esta función genera un número aleatorio entero entre a y b. Estos extremos
se incluyen como una posibilidad. Veamos un ejemplo:
𝑏 = 𝑟𝑎𝑛𝑑𝑜𝑚. 𝑟𝑎𝑛𝑑𝑖𝑛𝑡(0,1)
 Randrange(a,b,salto). Esta función es similar a la anterior, sólo que suma la posibilidad de
indicar saltos en los posibles números aleatorios, es decir, genera un número aleatorio entre
a y b (ambos inclusive), separados por el salto. Veamos un ejemplo:
𝑏 = 𝑟𝑎𝑛𝑑𝑜𝑚. 𝑟𝑎𝑛𝑑𝑟𝑎𝑛𝑔𝑒(0,10,2)
NOTA. Aclaración sobre la línea de código.
Como detalle, recuerde que al utilizar métodos, funciones, o atributos no nativos, siempre se
deberá escribir el nombre de la librería previo al nombre del método, función o atributo.

Finalmente, entre las funciones que permiten introducir el azar en listas con strings, veremos dos
funciones más. La primera es choice(), y permite elegir al azar un string o elemento de la lista que
se incorpora como argumento. La segunda es shuffle(), y modifica el orden de la lista de forma
aleatoria. Veamos cómo se escribe el código de cada una:
 Choice(). 𝑟𝑎𝑛𝑑𝑜𝑚. 𝑐ℎ𝑜𝑖𝑐𝑒(𝑎)
 Shuffle().𝑟𝑎𝑛𝑑𝑜𝑚. 𝑠ℎ𝑢𝑓𝑓𝑙𝑒(𝑎)
Como puede apreciarse, este último par de funciones no devuelven un resultado, pero sí modifican
la lista en cuestión, que en este caso llamamos “a”.
Colecciones de datos
Son un conjunto de datos que comparten características ¿Para qué sirven? Muchas veces
tendremos series de datos, tal vez precios, tal vez volumen, tal vez fechas, etcétera, y en lugar
asignar una variable a cada dato, será mejor utilizar colecciones, donde guardaremos todos estos
datos en una única variable. En este sentido, se puede decir, que todas las colecciones son vectores.
Un detalle no menor, es que un string es una colección de caracteres y, del mismo modo que
recortamos los strings para acceder a fracciones de ellos, podremos recordar las colecciones para
acceder a sus contenidos (el procedimiento es el mismo).
¿Cuándo necesito una colección en lugar de muchas variables?
Estructuras de colección en Python
 Estructuras Nativas. Estas ya vienen incorporadas en el lenguaje de Python.
o Generadores (funciones). Estos permiten completar una colección sin necesidad de
crear un objeto a la vez.
o Listas (ordenados, no únicos, modificable). Son colecciones de elementos
ordenados que no necesariamente son únicos. Además, las listas son objetos
“mutables”, es decir, las listas pueden modificarse al cambiar al menos uno de los
objetos que las integran. La gracia de las listas es que sus objetos sean homogéneos
o, de un mismo tipo de variable.
o Sets (no ordenados, únicos, modificable). Los elementos que lo conforman no
están ordenados y son únicos, pero pueden modificarse como las listas. Los sets
suelen utilizarse como conjuntos en lógica.
o Tuplas (ordenados, no únicos, no modificable). Son similares a las listas, su única
particularidad, y que las diferencia de ellas, es que sus objetos no pueden
modificarse.
o Diccionarios (Estructuras clave valor, cada elemento tiene un etiqueta). Este tipo
de colección de objetos permite almacenar datos en forma de llave y valor. En los
diccionarios, cada objeto tiene una llave (key) y un valor (value).
 Estructuras no nativas. Estas no vienen incorporadas en el lenguaje de Python y deben
llamarse a través de librerías, no obstante, estas toman las colecciones nativas y les otorgan
funciones. A continuación se mencionan tres:
o Numpy arrays. Utiliza la librería numpy, y son objetos multidimensionales y sirven
para trabajar con matrices numéricas.
o DataFrames. Utiliza la librería numpy, y son objetos o matrices bidimensionales
que pueden llevar etiquetas (nombres) en sus filas y columnas. Esto permite
trabajar con series de tiempo, por ejemplo, con un cuadro con fechas, precios de
cierre, precios de cierre ajustados, etcétera.
o Dask Arrays/DataFRames.
Listas
Una lista se crea utilizando corchetes [ ] y colocando dentro los objetos de interés, además, estos
objetos deben separarse por una coma. Estos elementos pueden ser de diferentes tipos, string,
integer, float, booleano… Veamos varios ejemplos:

Las listas también pueden crearse usando el método list(), poniendo dentro del paréntesis los
objetos de interés. Por ejemplo:

Como puede verse, las listas son diferentes a una cadena de caracteres, pues en la lista cada objeto
se separa con una coma, mientras en una lista de caracteres no necesariamente, pues depende de lo
que se desea escribir.
En este sentido, las listas pueden clasificarse del siguiente modo:
 Lista vacía. Una lista vacía no es más que escribir:
𝑙𝑖𝑠𝑡𝑎 = [ ]
 Sintaxis y slicing de listas. Como se mencionó antes, los string que se trabajaron son
colecciones de caracteres, por ende, su fraccionamiento también sirve para tomar porciones
de listas. Recordemos que, para tomar una porción trabajamos con el nombre de la variable
e inmediatamente escribimos [desde: hasta: paso], por ejemplo:
 Asignar valores a una lista. Supongamos que la lista trabajada es la siguiente:
𝑙𝑖𝑠𝑡𝑎 = [0,1,2,3,4,5,6,7,8,9,10]
Si escribimos lista[4], estaremos llamando al objeto en la posición 4 de la variable lista
llamada lista. Lo que se puede hacer, es modificar este objeto sobre escribiéndolo (lo
mismo se podría hacer con cualquier otro objeto en cualquier otra posición). Para hacer
esto debemos escribir:
𝑙𝑖𝑠𝑡𝑎[4] = "𝑗𝑢𝑎𝑛"
Por ende, la lista quedará así:
𝑙𝑖𝑠𝑡𝑎 = [0,1,2,3, ′𝑗𝑢𝑎𝑛′, 5,6,7,8,9,10]
 Sumar listas. Esto lo hacemos simplemente sumándolas, por ejemplo, si continuamos
trabajando con la lista creada hace un momento, podemos sumar dos porciones de la
misma del siguiente modo:
𝑙𝑖𝑠𝑡[: 3] + 𝑙𝑖𝑠𝑡𝑎[: 2]
El resultado es:
𝑙𝑖𝑠𝑡𝑎𝑛𝑢𝑒𝑣𝑎 = [0,1,2,0,1]
Esta posibilidad es importante, pues permite expandir la cantidad de objetos dentro de una
lista. En el ejemplo de hace un momento se sumaron dos elementos nuevos, pero bien se
podría sumar sólo uno y, no sería necesario que éste único elemento sea de una lista ya
conocida. En este sentido, sólo basta escribir lo siguiente:
𝑙𝑖𝑠𝑡𝑎 + ["𝑎"]
Haciendo esto sumamos una posición más hacia el final de la lista, cuyo objeto es un
string. El resultado es:
𝑙𝑖𝑠𝑡𝑎 = [0,1,2,3,4,5,6,7,8,9,10, ′𝑎′]
La conclusión de esto es que para sumar nuevas posiciones a una lista, lo que se adhiera
tiene que ser una lista, por ello o es una variable tipo lista o, es una colección escrita entre
corchetes.
Sumar listas es simple, sólo debemos escribir el código como si estuviésemos sumando
números o variables tipo int o float. La particularidad de las listas es que el resultado es la
suma de ubicaciones, es decir, si las listas sumadas tienen n y m ubicaciones, entonces la
nueva lista o el resultado, tendrá n+m ubicaciones. Asimismo, la suma de las lista implica
un ensamble, o sea, las m ubicaciones serán colocadas luego de las n ubicaciones. Por
ende, la suma de listas es en realidad una concatenación de listas.
Supongamos que tenemos la siguiente lista:
𝑙𝑖𝑠𝑡𝑎 = [0,1,2,5,6,7]
𝑙𝑖𝑠𝑡𝑎2 = [′ 𝑎′ , ′𝑏′]
𝑝𝑟𝑖𝑛𝑡(𝑙𝑖𝑠𝑡𝑎 + 𝑙𝑖𝑠𝑡𝑎2)
[1,2,3,5,6,′ 𝑎′ , ′𝑏′]
Imaginemos ahora lo siguiente:
𝑙𝑖𝑠𝑡𝑎 = [0,1,2,5,6,7]
𝑙𝑖𝑠𝑡𝑎2 = [′ 𝑎′ ,′ 𝑏 ′ , [1,2,3]]
𝑙𝑖𝑠𝑡𝑎3 = 𝑙𝑖𝑠𝑡𝑎 + 𝑙𝑖𝑠𝑡𝑎2
𝑝𝑟𝑖𝑛𝑡(𝑙𝑖𝑠𝑡𝑎3)
[1,2,3,5,6,′ 𝑎′ ,′ 𝑏 ′ , [1,2,3]]
¿Cómo hacemos para llamar algún objeto de la última ubicación? En otras palabras, el
objeto de la última ubicación es 7, y es una lista cuyo valor es [1,2,3], entonces ¿Cómo
hacemos para llamar alguno de sus valores? Primero debemos conocer su ubicación dentro
de la lista3, sabemos que es la ubicación 7, no obstante, si no conocemos la cantidad de
objetos que hay, pero sí sabemos que está en la última posición, podemos escribir
𝑙𝑖𝑠𝑡𝑎3[−1]. Luego, lo que acabo de escribir es otra lista, por ende, debemos abrir
nuevamente corchetes, por lo tanto, la solución es, si buscamos el valor 2, la siguiente:
𝑙𝑖𝑠𝑡𝑎3[−1][1]

Ejercicios simples de listas


Teniendo la siguiente lista:
𝑙𝑖𝑠𝑡𝑎 = [0,1,2,3,4,5,6,7,8,9,10]
Responder qué resultado arroja el compilador al hacer correr el siguiente código:
o 𝑙𝑖𝑠𝑡𝑎[∷ 3]. Este código recorre toda la colección lista y arroja como resultado los
elementos que corresponden a la posición 0,3, 6 y 9, es decir: [0,3,6,9]
o 𝑙𝑖𝑠𝑡𝑎[∷ 2]. Este código hace lo mismo que el anterior, pero en lugar de ir de a tres
posiciones a la vez, recorre de a dos posiciones a la vez. El resultado es: [0,2,4,6,8,10]
o 𝑙𝑖𝑠𝑡𝑎[1 ∷ 2]. Este código hace lo mismo que el anterior, pero en lugar de comenzar desde
la posición 0, lo hace desde la posición 1. El resultado es: [1,3,5,7,9]
o 𝑙𝑖𝑠𝑡𝑎[∷ −1]. Este código recorre toda la colección lista pero de derecha a izquierda (en el
original es de derecha a izquierda). El resultado es: [10,9,8,7,6,5,4,3,2,1,0]
o 𝑙𝑖𝑠𝑡𝑎[∷ −2]. Este código hace lo mismo que el anterior, pero en lugar de recorrer de a una
posición a la vez, lo hace de a dos posiciones. El resultado es: [10,8,6,4,2,0]
o 𝑙𝑖𝑠𝑡𝑎[: −2: ]. Este código no recorre toda la colección lista, lo hace desde la posición 0
hasta la posición -2. Esta posición -2 corresponde a la posición asignada de derecha a
izquierda comenzando por el -1. El resultado es: [0,1,2,3,4,5,6,7,8]
o 𝑙𝑖𝑠𝑡𝑎[−2 ∷]. Este código no recorre toda la lista, comienza desde la posición -2 y la
recorre de izquierda hacia derecha. El resultado es: [9,10]
o 𝑙𝑖𝑠𝑡𝑎[: 3] + 𝑙𝑖𝑠𝑡𝑎[: 2]. Este código suma dos fracciones de la lista. La primera fracción
recorre la lista desde la posición 0 hasta la 3, y la segunda hace lo mismo pero hasta la
posición dos, por ende, los resultados son [0,1,2] y [0,1] respectivamente. Luego, la suma
arroja el siguiente resultado: [0,1,2,0,1]

Funciones y métodos de listas


Antes de comenzar describiendo lo indicado en el título, resulta importante conocer la diferencia
entre un método y una función. Una función no requiere ser escrita luego de una variable, además,
ésta es escrita entre los paréntesis de la función. En cambio, un método sí requiere ser escrito luego
de especificar la variable. Veamos un ejemplo, donde suponemos que la variable se llama ‘lista’:
Función: 𝑙𝑒𝑛(𝑙𝑖𝑠𝑡𝑎)
Método: 𝑙𝑖𝑠𝑡𝑎. 𝑐𝑜𝑢𝑛𝑡(4)
Otra diferencia, es que el método puede invocarse tanto como método como función, mientras que
la función sólo puede invocarse como función.
Existen tres tipos de funciones, dos de estas devuelven un resultado, la restante no. La devolución
de un resultado significa que la función genera un valor que puede guardarse en una variable, por
ende, si deseamos conocer el valor contenido en una variable donde se guardó una función que no
devuelve un resultado, no podremos ver nada o, aparecerá “none”.
Las dos funciones que devuelven un resultado (funciones con return) son las siguientes:
o Que alteran al objeto (pop, sort, remove). El método pop() elimina un objetivo de una lista
y lo retorna (por eso “return”) como resultado, es por esto que se puede guardar en una
variable. Entre sus paréntesis se escribe la posición del objeto a eliminar. Si no se
especifica la posición, por defecto se elimina el último objetivo (quien se ubica en la última
posición). Veamos un ejemplo, supongamos la siguiente lista:
𝑙𝑖𝑠𝑡𝑎 = [0,1,2,3,4,5,6,7]
𝑟𝑒𝑠 = 𝑙𝑖𝑠𝑡. 𝑝𝑜𝑝(3)
Al hacer esto, la variable res estará guardando el valor 3 y, en simultáneo, estaremos
eliminando el objeto que está ubicado en la posición 3 de la lista, es decir, el valor 3.
Finalmente, la lista quedará del siguiente modo:
𝑙𝑖𝑠𝑡𝑎 = [0,1,2,4,5,6,7]
Otro método es el remove(), donde el argumento es el valor a eliminar, es decir, si es un
string, entonces escribimos el nombre del mismo, y si es un valor numérico, también.
Veamos un ejemplo, primero la lista con strings y luego cómo eliminamos uno de los
string. Si la lista tuviese int o float, el procedimiento es similar. Veamos:
𝑎 = [′ 𝑐𝑎𝑐𝑎′ ,′ 𝑝𝑖𝑠 ′ ,′ 𝑑𝑖𝑎𝑟𝑟𝑒𝑎′ ,′ 𝑓𝑒𝑟𝑛𝑒𝑡 ′ , ′𝑏𝑖𝑟𝑟𝑎′]
𝑎. 𝑟𝑒𝑚𝑜𝑣𝑒(′𝑐𝑎𝑐𝑎′ )
𝑎 = [′ 𝑝𝑖𝑠 ′ ,′ 𝑑𝑖𝑎𝑟𝑟𝑒𝑎′ ,′ 𝑓𝑒𝑟𝑛𝑒𝑡 ′ , ′𝑏𝑖𝑟𝑟𝑎′]
o Que no alteran al objeto (sum, len, count, index). La función len() aplicada a una colección
nativa (lista, set, tupla, y diccionario) permite conocer la cantidad de objetos que la
componen, por ende, si tenemos:
𝑙𝑖𝑠𝑡𝑎 = [0,1,2,3,4,5]
𝑙𝑒𝑛(𝑙𝑖𝑠𝑡𝑎)
El resultado será 6, porque tenemos 6 objetos en la lista.
El método index() devuelve como resultado la posición de la primera aparición del objeto
indicado entre los paréntesis y, que está contenido en la colección (por ende, no es útil para
elementos que se repiten, por otro lado, si el objeto buscado no está en la lista, el valor
retornado es ValueError). Veamos un ejemplo de index():

El método count() sirve para conocer la cantidad de veces que un objeto en particular está
contenido en la colección. Por ejemplo:
𝑙𝑖𝑠𝑡𝑎 = [1,1,2,3,4,4,4,5,6,4,4]
𝑙𝑖𝑠𝑡𝑎. 𝑐𝑜𝑢𝑛𝑡(4)
El resultado devuelto es cinco, pues el número 4 aparece cinco veces. La función sum()
suma los elementos dentro de la colección, devolviendo como resultado dicha suma, para
ello, debemos colocar la lista como argumento.

Finalmente, dentro las funciones que no devuelven un resultado (función sin return) se encuentran
aquellas que alteran al objeto (una función que no devuelve resultado, y tampoco modifica al
objeto, no hace nada). Ejemplos son sort(), append(), e insert(). El método sort() permite ordenar
una lista de forma ascendente o descendente. Es así como aplicado a una lista, la cambia de forma
permanente. Si la lista ya está ordenada, entonces la consola devuelve el valor “None”. Veamos un
ejemplo sin argumentos:

Este método cuenta con dos argumentos opcionales, reverse y key. El segundo es una función que
establece el criterio para ordenar la lista, por ejemplo, si se escribe key=len, entonces se ordena la
lista por longitud, del más pequeño al más largo. Veamos un ejemplo:
Reverse, por otro lado, asume un valor booleano (True o False). Por ende, si escribimos
reverse=True, la lista se ordenará en orden alfabético inverso. Veamos un ejemplo:

El método append(), por otro lado, permite agregar un elemento al final de la lista. Este nuevo dato
puede ser de cualquier tipo, por ende, también puede ser otra lista (no obstante, esta se agregará
como un objetivo, no como un conjunto de objetos). Cuando este método sea aplicado, recuerde
que no devuelve un resultado, se ejecuta y opera, pero no devuelve ningún resultado. Su sintaxis es
la siguiente:

Por ejemplo:
Un detalle alrededor de append() es que, cada vez que sea ejecutado agregará el objeto que está
entre paréntesis sin eliminar el que ya agregó en las ejecuciones anteriores (a menos que se vuelva
a definir la lista original previamente).
Finalmente, el método insert() permite agregar un objeto en la lista, sin importar la posición. Su
sintaxis es la siguiente:

Como ocurre con append(), si el método es ejecutado una y otra vez sin volver a partir de la lista
original, se estará agregando constantemente lo indicado con insert(). En otras palabras, si por
ejemplo la lista es la siguiente:
𝑙𝑖𝑠𝑡𝑎 = [1,2,3,5,6]
Y escribimos
𝑙𝑖𝑠𝑡𝑎. 𝑖𝑛𝑠𝑒𝑟𝑡(2,′ 𝑝𝑒𝑝𝑒 ′ )
𝑝𝑟𝑖𝑛𝑡(𝑙𝑖𝑠𝑡𝑎)
Tendremos lo siguiente:
[1,2,′ 𝑝𝑒𝑝𝑒 ′ , 3,5,6]
Si volvemos a ejecutar el método, pero no la lista, al imprimir el resultado obtendremos:
[1,2,′ 𝑝𝑒𝑝𝑒 ′ ,′ 𝑝𝑒𝑝𝑒 ′ , 3,5,6]
Finalmente, también se pueden agregar otras listas, sólo que no serán concatenadas, sino que se
ubicarán en el orden indicado y estarán identificadas como listas, por ejemplo:
𝑙𝑖𝑠𝑡𝑎2 = [′𝑎′, ′𝑏′]
𝑙𝑖𝑠𝑡𝑎. 𝑖𝑛𝑠𝑒𝑟𝑡(2, 𝑙𝑖𝑠𝑡𝑎2)
𝑝𝑟𝑖𝑛𝑡(𝑙𝑖𝑠𝑡𝑎)
[1,2, [′𝑎′, ′𝑏′],3,5,6]

Generadores
Estos son métodos que permiten escribir una colección sin la necesidad de escribir cada uno de sus
elementos. En otras palabras, son métodos que permiten llamar aquello que se quiere generar, pero
sin generarlo. Imaginemos, por poner un sencillo ejemplo, que queremos una lista con un millón de
objetos, cuyos valores pueden ser cualquier cosa. En lugar de escribirlos, podemos utilizar un
método que genere el millón de valores y los ubique en la lista. A esto se le llama generador.
El método que veremos es range(). Siguiendo el ejemplo, podemos escribir:
𝑟𝑎𝑛𝑔𝑒(1,1_000_000)
NOTA: El guión bajo se puede usar como separador de miles.
A priori, esto no es más que un rango entre 1 y un millón, de hecho, su tipo es range. Para
convertirlo en lista debemos escribir lo siguiente:
𝑙𝑖𝑠𝑡(𝑟𝑎𝑛𝑔𝑒(1,1_000_000))
Con esto ya creamos la lista con el millón de objetos, cuyos valores van desde el 1 hasta el millón.
Los argumentos de este método son:
𝑟𝑎𝑛𝑔𝑒(𝑑𝑒𝑠𝑑𝑒, ℎ𝑎𝑠𝑡𝑎, 𝑠𝑎𝑙𝑡𝑜)
El “desde” es inclusive, y el “hasta” es exclusive. El “salto” es un argumento donde se coloca el
tamaño del salto deseado, por ejemplo, si ponemos 1, se creará un rango que vaya de uno en uno,
en cambio, si colocamos 2, el rango irá de dos en dos.

Listas por comprensión


Se llama así a la capacidad de llamar a una lista que previamente es modificada de acuerdo a algún
criterio y, donde esta modificación se realiza sobre cada uno de sus elementos. La sentencia clave
es la siguiente:
𝑥 𝑓𝑜𝑟 𝑥 𝑖𝑛 …
La “x” representa una ubicación en la colección (lista en este caso), y los puntos suspensivos
representan el sitio donde deberemos escribir el nombre de la colección (de la lista) o el rango de la
lista que nos interesa modificar y llamar. En lugar de escribir “x” podemos escribir “i” o utilizar
otra letra, es lo mismo. Por lo tanto, si escribimos:
𝑥 𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑟𝑎𝑛𝑔𝑒(1,11)
Estaremos diciendo algo así: “para todos los objetos que están entre la ubicación 1 y 11
𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑟𝑎𝑛𝑔𝑒(1,11), aplicar 𝑥”. Es decir, en este caso no se los modifica. Veamos un ejemplo:
𝑙𝑖𝑠𝑡𝑎 = [𝑥 𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑟𝑎𝑛𝑔𝑒(1,11)]
El resultado será:
[1,2,3,4,5,6,7,8,9,10]
Ahora, también podemos modificarlos, por ejemplo escribiendo:
𝑙𝑖𝑠𝑡𝑎 = [2 ∗ 𝑥 + 1 𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑟𝑎𝑛𝑔𝑒(1,11)]
En este caso, el resultado será:
[2,5,7,9,11,13,15,17,19,21]
En definitiva, las listas por comprensión permiten modificar las listas sin necesidad de ir objeto
por objeto con un código en particular.
Veamos otro ejemplo, supongamos que la lista es una lista de precios, y deseamos crear otra lista
cuyos objetos sean los mismos precios pero un 5% más grande. Veamos primero la lista de precios
original:
𝑙𝑖𝑠𝑡𝑎 = [100,103.5,102,98.5]
Para obtener la nueva lista escribimos lo siguiente:
𝑙𝑖𝑠𝑡𝑎2 = [𝑥 ∗ 1.05 𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑙𝑖𝑠𝑡𝑎]
Para redondear, podemos utilizar round(variable,decimales), dentro de la lista por comprensión:
𝑙𝑖𝑠𝑡𝑎2 = [𝑟𝑜𝑢𝑛𝑑(𝑥 ∗ 1.05,2) 𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑙𝑖𝑠𝑡𝑎]
Sets
La característica principal de esta colección de objetos es que entre ellos no hay orden alguno (por
esto es que los objetos no tienen posición) y son únicos. La inexistencia de orden implica que la
colección y ubicación de cada objeto cambia constantemente. Que sean objetos únicos significa que
no pueden modificarse, sin embargo, sí pueden eliminarse y también pueden añadirse nuevos,
siempre que no se repitan, pues en estos casos no se incorporarán. No obstante, los Sets al igual que
las listas pueden iterarse, y es por esto que los Sets se utilizan para reconocer si un elemento
pertenece o no a determinada colección.
Una lista puede transformarse en un set del siguiente modo:
𝑠𝑒𝑡(𝑛𝑜𝑚𝑏𝑟𝑒)
Del mismo modo se puede transformar un set en una lista:
𝑙𝑖𝑠𝑡(𝑛𝑜𝑚𝑏𝑟𝑒)
Aquí, “nombre” es el nombre de la lista y el set, respectivamente.
Como se menciona al principio, los sets son utilizados para realizar operaciones lógicas, que son
las siguientes:
 Intersecciones (&). Que los elementos estén en un set y en el otro). Este sería sólo la
intersección.
 Uniones (|). Que los elementos estén en un set o en el otro. Este sería todo.
 Uniones sin intersección o XOR (^). Que los elementos estén en un set o en el otro, pero
no en ambos. Este sería la unión menos la intersección.
Para crear un conjunto se utilizan llaves { }, y no corchetes como con las listas. También se puede
usar el constructor set(). Para añadir elementos nuevos se puede utilizar add() si se desea añadir un
elemento nuevo, o update() si se desea añadir más de un nuevo elemento. Veamos un ejemplo:
Para remover elementos de un Set, se pueden utilizar los métodos remove(), discard(), clear(), y
del. Si el elemento que se desea remover no existe, la función remove() marcará error, pero la
función discard() no.

Clear() y del son funciones que sirven para vaciar de contenido al set.
También está el método update(), que inserta todos los elementos de un set en otro.

Los principales usos de los sets son:


.difference(), este método entrega un set que contiene elementos que no están en el otro, por
ejemplo:
𝑠1 = {1,2,3,4,5,6}
𝑠2 = {5,6,7,8,9,10}
𝑠1. 𝑑𝑖𝑓𝑓𝑒𝑟𝑒𝑛𝑐𝑒(𝑠2)
{1,2,3,4}
.union() es un método que une los sets creando otro que contiene todos los elementos. Continuando
con el ejemplo anterior, si escribimos lo siguiente, obtendremos el resultado que se muestra
inmediatamente:
𝑠1. 𝑢𝑛𝑖𝑜𝑛(𝑠2)
{1,2,3,4,5,6,7,8,9,10}
.issubset(). Entrega un valor booleano que indica si un set determinado contiene a otro, siendo este
otro un set que se indica dentro de los paréntesis. si este set contiene otro set o no. Por ejemplo:
𝑠𝑒𝑡1 = {1,2,3}
𝑠𝑒𝑡2 = {4,5,6}
𝑠𝑒𝑡3 = 𝑠𝑒𝑡1 | 𝑠𝑒𝑡2
𝑠𝑒𝑡1. 𝑖𝑠𝑠𝑢𝑏𝑠𝑒𝑡(𝑠𝑒𝑡2)
El resultado será:
𝑓𝑎𝑙𝑠𝑒
Y si escribimos:
𝑠𝑒𝑡3. 𝑖𝑠𝑠𝑢𝑏𝑠𝑒𝑡(𝑠𝑒𝑡2)
El resultado será:
𝑡𝑟𝑢𝑒

.issuperset(). Este trabaja de forma similar al anterior, pues devuelve un valor booleano de acuerdo
a si un set es contenedor de otro.
.len(), podemos conocer la longitud del Set o, la cantidad de objetos que tiene (objetos únicos).
¿Para qué sirven los operadores? El | permite realizar la unión de dos sets, es decir, es equivalente
al uso del método union():

Para saber si un objeto está o no en el Set, usamos el operador in. Si el mismo está, entonces el
valor será verdadero o True:
SET - Intersección de colecciones
Aunque con las listas se puede obtener la intersección, siempre será necesario utilizar al menos las
listas por comprensión. En cambio, si utilizamos sets, sólo deberemos utilizar operadores lógicos.
Veamos un ejemplo, supongamos los siguientes sets:
𝑠1 = {1,2,3,4,5,6}
𝑠2 = {5,6,7,8,9,10}
La intersección serían los elementos cuyo valor es 5 y 6. El código para obtener esto es el
siguiente:
Intersección: 𝑠1 & 𝑠2
{5,6}
Unión: 𝑠1 | 𝑠2
{1,2,3,4,5,6,7,8,9,10}
XOR: 𝑠1 ^ 𝑠2
{1,2,3,4,7,8,9,10}
Asimismo, si queremos que los elementos estén en un set y no en otro, por ejemplo, si deseamos
conocer los elementos del set 1 que no están en el set 2, entonces escribimos:
𝑠1 − 𝑠2
El resultado es:
{1,2,3,4}

Ejercicio de intersección
El ejercicio propuesto es escribir el código que cree el diagrama de “ven” para tres sets, y que en su
interior aparezcan los elementos que a cada subconjunto corresponden. Los tres sets fueron
definidos por el profesor, resta escribir el código para lograr lo mencionado. La solución se obtuvo
“googleando”, se encontró una web que explica cómo funciona el código de esta librería. A partir
de la lectura de esta página, se escribió el siguiente código, logrando el objetivo propuesto:
Tuplas
Las tuplas se escriben utilizando paréntesis, dentro de los cuales están los objetos separados por
comas.

Las tuplas pueden contener un objeto vacío y, si se les aplica la función len(), se obtiene la cantidad
de objetos dentro de ellas. En el caso de una tupla vacía, el resultado será cero:

En este sentido, si una Tupla tiene sólo un objeto, será necesario que vaya acompañado de una
coma, de lo contrario, Python no la reconocerá como Tupla.

Como ocurre con la cadena de caracteres, podemos referirnos a los elementos de las Tuplas
utilizando corchetes []. Por ejemplo:
𝑡1 = (0,1,2,3,4,5)
𝑡1[2]
2
Como se mencionó antes, las tuplas se caracterizan por tener objetos que no pueden modificarse,
así, si deseamos reemplazar el objeto de la posición 2 por ‘juan’, obtendremos un error. En este
sentido, las tuplas tampoco admiten la incorporación de nuevos objetos (usando por ejemplo la
función append()). ¿Por qué usar una tupla entonces? Para asegurarnos que su contenido no pueda
modificarse por un error al escribir el código. En paralelo, las tuplas pueden concatenarse dando
nacimiento a una nueva Tupla, es decir, la concatenación no modifica a las tuplas concatenadas.

Una lista puede transformarse en tupla con la función tuple():


También se puede asignar el valor de una tupla con “n” elementos a “n” variables:
Bucle for - Introducción
Antes de explicar este bucle, recordemos el ejemplo visto al presentar la lista por comprensión, en
este se definió lo siguiente (la variable llamada lista que es del tipo lista se debe definir
previamente, aquí suponemos que ya se lo hizo):
𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝 = [𝑟𝑜𝑢𝑛𝑑(1.05 ∗ 𝑥, 2) 𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑙𝑖𝑠𝑡𝑎]
Siendo su resultado:
[1,2,3,4,5,6,7,8,9,10]
El código escrito en la primera línea indica que: “Para todo ‘x’ (objeto) en la variable ‘lista’ (que es
del tipo lista), se aplicará la siguiente modificación ‘1.05*x’, resultado que será redondeado a dos
decimales, y a la lista modificada de este modo, se la guardará en la variable llamada ‘bandasup’”.
Este mismo procedimiento puede realizarse utilizando un bucle for, de hecho, la lógica es la
misma, sólo cambia el orden de los ‘elementos’ del código. Primero conozcamos la lógica del
bucle, veamos el siguiente código:
𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑙𝑖𝑠𝑡𝑎 :
𝑏𝑎𝑛𝑑𝑠𝑢𝑝 = 𝑟𝑜𝑢𝑛𝑑(1.05 ∗ 𝑥, 2)
𝑝𝑟𝑖𝑛𝑡(𝑏𝑎𝑛𝑑𝑠𝑢𝑝)
Antes de explicar el código, es importante notar que el bucle necesariamente debe escribirse con la
diferencia de espacios que claramente se aprecia, a esto se le llama identación (es como una
sangría). Si la identación no se respeta, python no leerá el código como un bucle y, por ende, al
ejecutarlo arrojará un error. En general, la identación es automática al escribir el código. En este
código estamos diciendo: “Para todo x (que bien podría ser otro variable como ‘i’, pues es una
variable interna del bucle) ubicado en la variable llamada ‘lista’, aplicar ‘1.05*x y redondearlo en
dos decimales’, al resultado guardarlo en la variable ‘bandsup’”. El detalle del print() es que el
resultado de este bucle serán una serie de precios separados entre sí, es decir, no formarán una
colección.
Este bucle seguirá trabajando hasta terminar de recorrer toda la variable ‘lista’, aplicarle la
modificación mencionada, guardarla en ‘bandsup’ e imprimirla. Al terminar de hacer esto, si
escribimos algo más, recién en ese momento lo ejecutará, o sea, cuando termine de ejecutarse el
bucle.
Ahora, si deseamos utilizar el bucle for para obtener el mismo resultado que con la lista por
comprensión, deberemos escribir lo siguiente:
𝑙𝑖𝑠𝑡𝑎 = [23.1,23.3,23.2,23,23.25]
𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝 = [ ]
𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑙𝑖𝑠𝑡𝑎 ∶
𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝. 𝑎𝑝𝑝𝑒𝑛𝑑(𝑟𝑜𝑢𝑛𝑑(1.05 ∗ 𝑥, 2))
𝑝𝑟𝑖𝑛𝑡(𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝)
Con este código obtendremos el mismo resultado que al utilizar la lista por comprensión. ¿Qué
hicimos? Tenemos la lista que modificaremos, y creamos otra lista pero la mantenemos vacía, pues
es en esta que guardaremos los valores modificados. Al crear el bucle indicamos que, para todos los
elementos ‘x’ que están en la variable ‘lista’, se los modifique multiplicándolos por 1.05 y se los
redondee por dos decimales, además, al resultado lo agregaremos al final de la lista vacía que
llamamos ‘bandasup’. Cuando el bucle termine de repasar toda la variable ‘lista’, que imprima el
resultado final de ‘bandasup’.

Bucle for – Creando una lista


Los ejemplos de la sección anterior asumen la existencia de una lista, y con el bucle for se los
modifica. En esta sección veremos cómo crear una lista utilizando este bucle, o mejor dicho,
veremos cómo introducirle objetos utilizando este bucle. Para esto haremos uso del generador
range(). El código es el siguiente:

𝑙𝑖𝑠𝑡𝑎 = [ ]
𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑟𝑎𝑛𝑔𝑒(10):
𝑣𝑎𝑙𝑜𝑟 = 𝑥 ∗ 2 + 1
𝑙𝑖𝑠𝑡𝑎. 𝑎𝑝𝑝𝑒𝑛𝑑(𝑣𝑎𝑙𝑜𝑟)
El resultado es el siguiente:
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
Observe que el generador rang() sirve en este caso para establecer la cantidad de iteraciones que
deseamos que sucedan o, la cantidad de objetos que deseamos se cree. Por otro lado, a diferencia de
la lista por comprensión, el bucle for permite modificar los objetos de la lista de diversas formas,
pues es posible escribir varias líneas de código para ello, mientras que la lista por comprensión
sólo permite un tipo de modificación.

Bucle for – Ciclo con randomzación: Montecarlo


La creación de objetos para ser incorporados en las listas, utilizando iteraciones, pueden
combinarse con una multiplicidad de líneas de código, en este caso veremos cómo utilizar
distribuciones de probabilidad.
Utilizando lo mencionado se pueden simular situaciones, por ejemplo, si dadas determinadas
condiciones (probabilidades de suba y baja de un activo) ¿Qué probabilidad hay de observar una
suba en el activo? Al responderlo utilizando distribuciones de probabilidad, estaremos trabajando
con lo que se conoce como Montecarlo, y estaremos utilizando lo que en jerga se dice “uso de
fuerza bruta”. En estos casos es determinante la función de distribución de probabilidad del activo
o de la variable relacionada a él (variación de su precio), junto con los estimadores de sus
parámetros (quienes pueden obtenerse de la serie de precios – si trabajamos con precios, claro). En
lo que sigue, siempre supondremos una distribución normal, o normal estándar.
Para ejemplificar cómo podemos crear una lista utilizando un bucle for combinado con
Montecarlo, supongamos que las variaciones del precio de un activo se distribuyen como una
normal, con media cero y desvío estándar de 1,5. Para simplificar, supongamos que el precio inicial
del activo es de $ 100, entonces ¿Cómo podemos crear una lista que nos muestre diez posibles
precios de la siguiente rueda? El código es el siguiente:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑟𝑎𝑛𝑑𝑜𝑚
𝑝𝑟𝑒𝑐𝑖𝑜 = 100
𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = [ ]
𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑟𝑎𝑛𝑔𝑒(10):
𝑣𝑎𝑟𝑖𝑎𝑐𝑖𝑜𝑛 = 𝑟𝑎𝑛𝑑𝑜𝑚. 𝑛𝑜𝑟𝑚𝑎𝑙𝑣𝑎𝑟𝑖𝑎𝑡𝑒(0, 1.5)/precio
𝑝𝑟𝑒𝑐𝑖𝑜𝑛𝑢𝑒𝑣𝑜 = 𝑟𝑜𝑢𝑛𝑑(𝑝𝑟𝑒𝑐𝑖𝑜 ∗ (1 + 𝑣𝑎𝑟𝑖𝑎𝑐𝑖𝑜𝑛),2)
𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = 𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠. 𝑎𝑝𝑝𝑒𝑛𝑑(𝑝𝑟𝑒𝑐𝑖𝑜𝑛𝑢𝑒𝑣𝑜)
𝑝𝑟𝑖𝑛𝑡(𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠)
Un posible resultado es el siguiente:
[100.29, 99.95, 100.19, 99.21, 99.94, 99.74, 99.23, 98.78, 97.89, 99.66]
Por ende, el precio en la rueda t es de $ 100, y en la rueda siguiente, t+1, podría ser uno de los
mostrados en el vector inmediatamente arriba.

NOTA: “CAGR medio del activo es del 5,4%”. El CAGR son las siglas en inglés que significan
“Compound annual Growth rate” o, en español, “Tasa anual de crecimiento compuesta”. En otras
palabras, es una tasa efectiva anual (TEA). En este caso, la frase se obtuvo de un ejercicio que
asume implícitamente que esta tasa tiene una frecuencia de capitalización de diaria, en otras
palabras, a partir de una serie de precios diaria, se calculó el rendimiento diario promedio y luego
se calculó su TEA equivalente. En estos casos, siempre debemos recordar que se asume que
existen, en promedio 252 ruedas al año, por ende, el año bursátil tiene esta cantidad total de días.
Bucle for – Productorias y sumatorias
Además de crear objetos para completar una lista, el bucle for también es útil para calcular
sumatorias y productorias. A continuación lo aplicaremos para calcular el desvío estándar, para
esto, supondremos el siguiente conjunto de diez precios diarios:
𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = [100.29, 99.95, 100.19, 99.21, 99.94, 99.74, 99.23, 98.78, 97.89, 99.66]
La fórmula del desvío estándar es:

𝑗
1
𝜎=√ ∑(𝜇 − 𝑥𝑖 )2
𝑛−1
𝑖=1

El código para calcular el desvío estándar es el siguiente:


𝑠𝑢𝑚𝑎 = 𝑠𝑢𝑚(𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠)
𝑐𝑎𝑛𝑡𝑖𝑑𝑎𝑑 = 𝑙𝑒𝑛(𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠)
𝑝𝑟𝑜𝑚𝑒𝑑𝑖𝑜 = 𝑠𝑢𝑚𝑎/𝑐𝑎𝑛𝑡𝑖𝑑𝑎𝑑
𝑙𝑖𝑠𝑡𝑎2 = []
𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠:
𝑑𝑖𝑓𝑒𝑟𝑒𝑛𝑐𝑖𝑎 = (𝑝𝑟𝑜𝑚𝑒𝑑𝑖𝑜 − 𝑥) ∗∗ 2
𝑙𝑖𝑠𝑡𝑎2. 𝑎𝑝𝑝𝑒𝑛𝑑(𝑑𝑖𝑓𝑒𝑟𝑒𝑛𝑐𝑖𝑎)
𝑠𝑢𝑚𝑎2 = 𝑠𝑢𝑚(𝑙𝑖𝑠𝑡𝑎2)
𝑑𝑒𝑠𝑣𝑖𝑜 = 𝑟𝑜𝑢𝑛𝑑((𝑠𝑢𝑚𝑎2/(𝑙𝑒𝑛(𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠) − 1)) ∗∗ 0.5,3)
𝑝𝑟𝑖𝑛𝑡(𝑟𝑜𝑢𝑛𝑑(𝑝𝑟𝑜𝑚𝑒𝑑𝑖𝑜, 2), 𝑑𝑒𝑠𝑣𝑖𝑜)
El resultado es el siguiente:
𝑃𝑟𝑜𝑚𝑒𝑑𝑖𝑜 = 99,49
𝐷𝑒𝑠𝑣í𝑜 = 0,734
Operadores de asignación
Este concepto es útil en el uso de bucles. Estos operadores siempre comienzan con un valor, que
durante el ciclo crece porque sobre su valor anterior se acumula uno nuevo (se suma una constante
en cada iteración, por ejemplo). Un ejemplo básico es lo siguiente:
𝑎 = 10
𝑎=𝑎+1
Cuando imprimamos el valor de “a”, veremos que es 11. En un bucle, “a” comienza valiendo 10,
en la primera iteración termina valiendo 11, en la segunda iteración “a” comienza valiendo 11 y
termina con valor 12. Esto se repite, con un valor de “a” que crece de 1 en 1, hasta que el bucle
termina.
En lugar de escribir la segunda línea de código, podemos escribir lo siguiente:
𝑎+= 1
Esta línea hace lo mismo, y también es un operador de asignación. Lo mismo ocurre con las
restas:
𝑎−= 1
Donde cada ejecución resta 1 a “a”.
Un uso simple y básico para esto surge de su combinación con un generador y un bucle for.
Supongamos que deseamos generar 5 elementos, donde cada uno es mayor al anterior en un valor
de 1. El código correspondiente es:
𝑎 = 10
𝑓𝑜𝑟 𝑖 𝑖𝑛 𝑟𝑎𝑛𝑔𝑒(5):
𝑎+= 1
𝑝𝑟𝑖𝑛𝑡(𝑎)
Este código genera 5 elementos, el primero valdrá 11, el segundo 12, y así hasta completar los
cinco.
Este operador de asignación también se puede utilizar con multiplicaciones, sólo debemos
escribir lo siguiente, por ejemplo:
𝑎 ∗= 1.05
Diccionarios
Los diccionarios se crean utilizando lleves { } y, dentro de estas, asignándole valores a un objeto
compuesto por un par de argumentos, key y value. Cada objeto compuesto por este par de
argumentos se separa de los demás por una coma. Asimismo, el argumento key se separa del
argumento value utilizando dos puntos. Finalmente, tanto key como value pueden ser cualquier tipo
de variable (string, integer, float, booleano – así, también puede ser una lista), y key se debe
escribir entre comillas, mientras que el value no necesariamente, sólo si su valor es un string.
Antes de abordar un ejemplo, es útil contrastar el diccionario con la lista. En este sentido, la
colección en cuestión permite asignar etiquetas a las filas y columnas, mientras que la lista no. En
línea con esto, con una lista se podrían guardar los precios de cierra ajustados de diferentes
acciones, mientras que, con un diccionario se guardaría estos precios asignándolos a un nombre en
particular (etiqueta).
Veamos un ejemplo de diccionario:
𝑑𝑖𝑐 = {"𝐴𝐿𝑈𝐴": 29.35, "𝐵𝐵𝐴𝑅": 120.85, "𝐵𝑀𝐴": 265.2, "𝐵𝑌𝑀𝐴": [290,299]}
Otro modo de crear un diccionario es utilizando dict() e introduciendo el valor que corresponde a
cada par key – value, separados por un coma. En este caso, en lugar de usar dos puntos para separar
la clave del valor, se utiliza un igual “=”. Veamos un ejemplo:

Los diccionarios son dinámicos, es decir, pueden crecer o decrecer (añadiendo o eliminando
objetos); son indexados, pues sus objetos son accesibles a través del key; y son anidados, pues
pueden contener otra colección en su campo value. Una importante consideración respecto a estas
características, es que cada valor debe estar asociado con una única clave, pero, una clave puede
asociarse a muchos valores. En este sentido, para que una clave pueda asociarse a muchos valores,
estos deben pertenecer a una misma lista, de lo contrario, el diccionario sobre escribirá la clave
asignándole como valor el último que se ha introducido.
Para acceder a los valores contenidos en los diccionarios, debemos aplicar la misma lógica que la
utilizada con las listas y tuplas, aunque en este caso, en lugar de llamar una ubicación, llamamos la
clave. En este sentido, se puede obtener un valor particular llamándolo con su etiqueta, para esto se
llama al diccionario y se utilizan corchetes. Veamos un ejemplo:
𝑑𝑖𝑐["𝐴𝐿𝑈𝐴"]
Obteniendo 29.35. Este modo de acceder a los valores implica que el diccionario no cuente con
varios valores para una misma etiqueta o clave, pues de lo contrario, esta línea de código nos traerá
de vuelta la lista que contiene todos los elementos vinculados con esta etiqueta. Por ejemplo, si
llamamos a:
𝑑𝑖𝑐["𝐵𝑌𝑀𝐴"]
Obtendremos la lista [290,299]. Para poder acceder a uno de estos datos, la lógica a aplicar es la
misma, a continuación se escribe la línea de código correspondiente:
𝑑𝑖𝑐["𝐵𝑌𝑀𝐴"][1]
De este modo estaremos llamando el valor ubicado en la posición uno de la lista vinculada a la
clave “BYMA”, es decir, obtendremos como devolución el valor 299.
En línea con la búsqueda de valores en el diccionario, si introducimos una clave que en el mismo
no existe, el resultado que tendremos será un error que indica la inexistencia de dicha clave. El
problema con los errores es que cortan la ejecución del código, por ende, debemos evitar estas
situaciones. En este caso, para evitarlo utilizaremos la función get(), quien cumple la misma
función que los código de hace un momento, nos permite acceder al valor que corresponde a
determinada clave. Al utilizar esta función introduciendo una clave que no existe, la respuesta es
“none”. Esto se explica en la siguiente sección.
En penúltimo lugar, para agregar nueva información en un diccionario que ya existe, debemos
utilizar corchetes [ ], dentro de los cuales escribiremos la clave, luego usaremos el “=”, y seguido a
esto introduciremos su clave. Veamos un ejemplo:

Finalmente, si deseamos modificar la información del diccionario, esto es, si deseamos cambiar el
valor asociado a una clave en particular, sólo debemos escribir su clave y colocar el nuevo valor.
Por ejemplo, si el diccionario es “jota” y la clave es “aire” y el valor es 16000, y a éste queremos
modificarlo por 20000, debemos escribir lo siguiente:
𝑗𝑜𝑡𝑎[′ 𝑎𝑖𝑟𝑒 ′ ] = 20_000
Métodos y funciones para los diccionarios
Como se explicó antes, para acceder a los valores de los diccionarios utilizamos corchetes [ ] o la
función get(). En cualquier caso, lo que se busca es el value a partir del key. Veamos un ejemplo:

Utilizando get(), para acceder a un valor particular de una lista, utilizamos la misma lógica,
escribimos lo siguiente:
𝑑𝑖𝑐. 𝑔𝑒𝑡(′𝐵𝑌𝑀𝐴′)[0]
Vimos al final de la sección anterior que, si utilizamos la función get() para conseguir el valor de
una clave que no existe, obtendremos como resultado “none”. Este resultado podemos cambiarlo,
para ello, al llamar al valor utilizando la clave, el siguiente argumento debe ser la frase que
deseamos aparezca en lugar de “none”. Veamos esto:
𝑑𝑖𝑐. 𝑔𝑒𝑡(′𝐴𝑃𝑃𝐿′ ,′ 𝐿𝑎 𝑟𝑢𝑏𝑖𝑎 𝑑𝑒 𝑎𝑙 𝑙𝑎𝑑𝑜 𝑒𝑠𝑡á 𝑚𝑢𝑦 𝑙𝑖𝑛𝑑𝑎′ )
Al ejecutar este código, y como APPL no está en la colección dic, aparecerá el siguiente resultado
en lugar de “none”:
𝐿𝑎 𝑟𝑢𝑏𝑖𝑎 𝑑𝑒 𝑎𝑙 𝑙𝑎𝑑𝑜 𝑒𝑠𝑡á 𝑚𝑢𝑦 𝑙𝑖𝑛𝑑𝑎
Veamos los métodos keys(), values(), e ítems(). Estas funciones permiten recorrer al diccionario.
Veamos cada una:
o Keys(). Este método permite conocer todas las claves presentes en el diccionario. Este
método devuelve un vector del tipo especial de diccionario (dict_keys). Veamos un
ejemplo utilizando el diccionario de la sección anterior:
𝑑𝑖𝑐. 𝑘𝑒𝑦𝑠()
([′ 𝐴𝐿𝑈𝐴′ ,′ 𝐵𝐵𝐴𝑅 ′ ,′ 𝐵𝑀𝐴′ , ′𝐵𝑌𝑀𝐴′ ])
o Values(). Del mismo modo que el método anterior, este permite conocer todos los valores
del diccionario en cuestión. Este método devuelve un vector del tipo especial de
diccionario (dict_values). Veamos un ejemplo utilizando el diccionario de la sección
anterior:
𝑑𝑖𝑐. 𝑣𝑎𝑙𝑢𝑒𝑠()
([29.35, 120.85, 265.2, [290,299]])
o Items(). Como los métodos anteriores, este también devuelve un tipo especial de
diccionario (dict_items), pero en este caso será uno compuesto de elementos que son
tuplas conformadas por dos objetos: el string (clave) y el valor. Veamos un ejemplo:
𝑑𝑖𝑐. 𝑖𝑡𝑒𝑚𝑠()
([('ALUA', 29.35), ('BBAR',120.85), ('BMA', 265.2), (′BYMA', [290,299])])
Recorriendo un diccionario – Bucle for
Recordemos el diccionario ejemplo de las dos secciones anteriores:
𝑑𝑖𝑐 = {"𝐴𝐿𝑈𝐴": 29.35, "𝐵𝐵𝐴𝑅": 120.85, "𝐵𝑀𝐴": 265.2, "𝐵𝑌𝑀𝐴": [290,299]}
Para recorrer las claves, podemos escribir el siguiente código:
𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑑𝑖𝑐 ∶
𝑝𝑟𝑖𝑛𝑡(𝑑𝑖𝑐)
Quien arroja el siguiente resultado:
𝐴𝐿𝑈𝐴
𝐵𝐵𝐴𝑅
𝐵𝑀𝐴
𝐵𝑌𝑀𝐴
Pero si deseamos recorrer los valores, sólo debemos aplicar lo visto hasta ahora, veamos:
𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑑𝑖𝑐 ∶
𝑝𝑟𝑖𝑛𝑡(𝑑𝑖𝑐[𝑥])
Devolviéndonos lo siguiente:
29.35
120.85
265.2
[290, 299]
Este par de modos logra el cometido, pero es ineficiente. El siguiente modo es mejor en estos
términos:
𝑓𝑜𝑟 𝑡𝑖𝑐𝑘𝑒𝑟, 𝑝𝑟𝑒𝑐𝑖𝑜𝑠 𝑖𝑛 𝑑𝑖𝑐. 𝑖𝑡𝑒𝑚𝑠() ∶
𝑝𝑟𝑖𝑛𝑡(𝑡𝑖𝑐𝑘𝑒𝑟, 𝑝𝑟𝑒𝑐𝑖𝑜𝑠)
El resultado es el siguiente:

¿Cómo funciona este código? Las variables internas que nombramos, ticker y precios, se asocian a
la clave y al valor de la lista con tuplas que llamamos al escribir dic.items(). Siguiendo esta lógica,
bien podríamos haber declarado sólo una variable interna, por ejemplo ‘caca’, en este caso, el
intérprete la tomaría como una tupla y, por ende, al imprimirla nos devolvería justamente una tupla
con dos objetos en cada iteración. Esto es así porque recuerdo que al escribir dic.items() estamos
creando una lista con objetos que son tuplas. Veamos el ejemplo:
𝑓𝑜𝑟 𝑐𝑎𝑐𝑎 𝑖𝑛 𝑑𝑖𝑐. 𝑖𝑡𝑒𝑚𝑠():
𝑝𝑟𝑖𝑛𝑡(𝑐𝑎𝑐𝑎)
El resultado es:
(′𝐴𝐿𝑈𝐴′ , 29.35)
(′𝐵𝐵𝐴𝑅 ′, 120.85)
(′𝐵𝑀𝐴′, 265.2)
(′𝐵𝑌𝑀𝐴′ , [290, 299])
Ahora, para acceder a cada objeto de cada tupla, sólo debemos indicar su ubicación y, como son
sólo dos objetos, la ubicación será 0 y 1. Veamos el ejemplo:
𝑓𝑜𝑟 𝑐𝑎𝑐𝑎 𝑖𝑛 𝑑𝑖𝑐. 𝑖𝑡𝑒𝑚𝑠():
𝑝𝑟𝑖𝑛𝑡(𝑐𝑎𝑐𝑎[0], 𝑐𝑎𝑐𝑎[1])
Con esto obtenemos el resultado siguiente:

Función zip
Esta función permite empaquetar diferentes listas y diferentes tipos de colecciones. Por ejemplo,
pensemos que tenemos tres tipos de listas, una con los nombres de acciones, otra con sus precios de
cierre ajustados de la última rueda, y otra con los volúmenes operados durante la última rueda.
Concretamente, pensemos que tenemos lo siguiente:
𝑡𝑖𝑐𝑘𝑒𝑡𝑠 = [′ 𝐴𝐿𝑈𝐴′ , ′𝐵𝐵𝐴𝑅 ′ , ′𝐵𝑀𝐴′ , ′𝐵𝑌𝑀𝐴′]
𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = [1,2,3,4]
𝑣𝑜𝑙𝑢𝑚𝑒𝑛 = [100,120,130,140]
A estas listas podemos empaquetarlas con la función zip(), veamos:
𝑝𝑎𝑞𝑢𝑒𝑡𝑒 = 𝑧𝑖𝑝(𝑡𝑖𝑐𝑘𝑒𝑡𝑠, 𝑝𝑟𝑒𝑐𝑖𝑜𝑠, 𝑣𝑜𝑙𝑢𝑚𝑒𝑛)
Esta variable tipo zip no se puede imprimir con la función print(), pues es un objeto que aun no
está desplegado, por ello recurrimos al bucle for, tanto para recorrerlo como para imprimirlo:
𝑓𝑜𝑟 𝑡, 𝑝, 𝑣 𝑖𝑛 𝑝𝑎𝑞𝑢𝑒𝑡𝑒:
𝑝𝑟𝑖𝑛𝑡(𝑡, 𝑝, 𝑣)
Obteniendo:
El detalle con los paquetes, es que cada lista o colección que lo constituye, siempre se debe
corresponder con otro elemento de otra colección incorporada, es decir, si hay dos listas en el
paquete, estas deben tener la misma cantidad de objetos, lo mismo si hay tres listas, cuatro,
etcétera. Si una de estas listas tiene más elementos que las demás, el intérprete no considerará
dichos elementos, los eliminará. Por ejemplo, si tenemos tres listas, una con 3 elementos, otra con
8, y otra con 24 elementos, al empaquetarlas, tendremos un paquete con 3 elementos compuestos
de 3 objetos.

De zip a diccionario & de zip a lista

¿Cómo podemos armar un diccionario a partir de un paquete zip? Imaginemos que partimos de un
conjunto de listas, a estas las empaquetaremos, y finalmente convertiremos el paquete en un
diccionario. Veamos este proceso con el siguiente ejemplo:

𝑡𝑖𝑐𝑘𝑒𝑡𝑠 = [′ 𝐴𝐿𝑈𝐴′ , ′𝐵𝐵𝐴𝑅′]


𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = [1,2]
𝑑𝑖𝑐 = 𝑑𝑖𝑐𝑡(𝑧𝑖𝑝(𝑡𝑖𝑐𝑘𝑒𝑡𝑠, 𝑝𝑟𝑒𝑐𝑖𝑜𝑠))
El resultado es el siguiente:
{′ 𝐴𝐿𝑈𝐴′ : 1,′ 𝐵𝐵𝐴𝑅 ′ : 2}
¿Cómo podemos armar una lista a partir de un zip? a
t𝑖𝑐𝑘𝑒𝑡𝑠 = [′ 𝐴𝐿𝑈𝐴′ , ′𝐵𝐵𝐴𝑅′]
𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = [1,2]
𝑙𝑖𝑠𝑡𝑎 = 𝑙𝑖𝑠𝑡(𝑧𝑖𝑝(𝑡𝑖𝑐𝑘𝑒𝑡𝑠, 𝑝𝑟𝑒𝑐𝑖𝑜𝑠))
El resultado es una lista con dos elementos que son tuplas compuestas por dos objetos, un string y
un int:
[(′𝐴𝐿𝑈𝐴′ , 1), (′𝐵𝐵𝐴𝑅 ′ , 2)]
Finalmente, imaginemos que tenemos dos listas y deseamos crear un diccionario utilizando el bucle
for ¿Cómo lo logramos? Veamos un ejemplo para ilustrar el procedimiento:
𝑡 = [′ 𝑎′ ,′ 𝑏 ′ ,′ 𝑐 ′ , ′𝑑′]
𝑝 = [1,2,3,4]
𝑑𝑖𝑐 = {}
𝑓𝑜𝑟 𝑚, 𝑛 𝑖𝑛 𝑧𝑖𝑝(𝑡, 𝑝):
𝑑𝑖𝑐[𝑚] = 𝑛
𝑝𝑟𝑖𝑛𝑡(𝑑𝑖𝑐)
Vemos que este código trabaja del siguiente modo: a partir de las variables internas m y n, se
recorre el zip t y p respectivamente, y a cada elemento de t se lo coloca en dic[m] y, a cada
elemento de p se lo introduce en n y se lo asocia a dic[m].

Operaciones con diccionarios


Imaginemos que tenemos dos listas, una con los tickets de los activos y otra con sus precios de
cierre de la última rueda, e imaginemos que nuestro cliente tiene un activo de cada uno. Las listas,
su empaquetamiento, y su conversión en diccionario se logran con las siguientes líneas de código:

𝑡𝑖𝑐𝑘𝑒𝑡 = [′ 𝑇𝑆𝐿𝐴′ ,′ 𝐴𝑀𝑍𝑁 ′ ,′ 𝐺𝑂𝑂𝐺𝐿′ ,′ 𝑇𝐿𝑇 ′ ,′ 𝑆𝐻𝑌 ′ , ′𝐵𝑇𝐶′]


𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = [12500,5000,10500,20_000,7 + 7 ∗ 10 ∗∗ 1,7𝑒1]
𝑑𝑖𝑐 = 𝑑𝑖𝑐𝑡(𝑧𝑖𝑝(𝑡𝑖𝑐𝑘𝑒𝑡, 𝑝𝑟𝑒𝑐𝑖𝑜𝑠))

NOTA: El guión bajo es un separador de mil, y “e1” es lo mismo que escribir 7*10**1, pero
escrito de forma más abreviada (por ende, e1=10**1).

Con las siguientes preguntas se pretende mostrar cómo resolver algunas cuestiones básicas, pero
principalmente, dejar en evidencia el trabajo a realizar al usar diccionarios. Este objetivo principal
permite valorar más el uso de DataFrame que se aborda en otra sección. Las preguntas son las
siguientes:
1. ¿Cómo podemos sumar la totalidad de los precios? Aquí veremos dos modos, iterando, y
aplicando los métodos de los diccionarios. Comencemos iterando, en este caso deberemos
crear una variable cuyo valor inicial sea cero, y a quien le asignaremos con cada iteración
el valor que corresponde a cada etiqueta. Por ende, en la iteración estaremos llamando a los
valores usando las claves. Veamos:
𝑠𝑢𝑚𝑎 = 0
𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑑𝑖𝑐:
𝑎 = 𝑑𝑖𝑐[𝑥]
𝑠𝑢𝑚𝑎+= 𝑎
𝑝𝑟𝑖𝑛𝑡(𝑠𝑢𝑚𝑎)
El resultado es: 48.180.
La alternativa más eficiente es utilizar un método del diccionario, el value(). La idea detrás
de la siguiente línea de código es extraer los valores del diccionario, convertirlos en listas,
y sumarlos con la función sum(). Veamos:
𝑝𝑟𝑖𝑛𝑡(𝑠𝑢𝑚(𝑙𝑖𝑠𝑡(𝑑𝑖𝑐. 𝑣𝑎𝑙𝑢𝑒𝑠())))
El resultado es el mismo: 48.180.
2. ¿Cómo podemos calcular el porcentaje de tenencia de cada activo? Veamos dos formas,
ambas son la continuación de las soluciones anteriores. En el caso del bucle for, la
solución planteada es otro bucle for. Primero creamos una lista vacía donde se contendrán
los porcentajes, luego creamos un bucle for para que se itere el recorrido de la lista, en este
bucle llamaremos a los valores utilizando las claves, y a cada valor lo dividiremos por el
total (suma) que calculamos antes. Además, en cada llamado, a cada cociente lo
multiplicaremos por 100 y lo redondearemos a 2 decimales, para posteriormente
adicionarlo como objeto en la lista vacía. Finalmente, imprimimos el resultado. Las líneas
de código son las siguientes:
𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒 = []
𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑑𝑖𝑐:
𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒. 𝑎𝑝𝑝𝑒𝑛𝑑(𝑟𝑜𝑢𝑛𝑑(𝑑𝑖𝑐[𝑥]/𝑠𝑢𝑚𝑎 ∗ 100,2))
𝑝𝑟𝑖𝑛𝑡(𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒)
Respecto al segundo modo de resolver esto, podemos utilizar una lista por comprensión. La
idea es recorrer la lista obtenida a partir del método value(), y en ese recorrido, a cada valor
lo dividiremos por la suma obtenida antes (deberemos crear una variable que contenga esta
suma), y lo multiplicaremos por 100 y redondearemos a 2 decimales. Veamos:
𝑛 = [𝑟𝑜𝑢𝑛𝑑(𝑥 ∗ 100/𝑠𝑢𝑚𝑎, 2) 𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑙𝑖𝑠𝑡(𝑑𝑖𝑐. 𝑣𝑎𝑙𝑢𝑒𝑠())]
𝑝𝑟𝑖𝑛𝑡(𝑛)
3. ¿Cómo podemos obtener el monto correspondiente a TLT? Sólo debemos escribir lo
siguiente:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑖𝑐[′ 𝑇𝐿𝑇′])
O, utilizamos la función get(). En este sentido, es recomendable utilizarlo como buena
práctica, pues recuerde que si el valor buscado no existe en el diccionario, la ejecución se
detendrá por un error, situación que se evita al utilizar get():
𝑝𝑟𝑖𝑛𝑡(𝑑𝑖𝑐. 𝑔𝑒𝑡(′𝑇𝐿𝐸′ ))
4. ¿Cómo obtenemos los tres activos cuya tenencia es la mayor? Este objetivo se puede
lograr ordenado de mayor a menor o, de menor a mayor el diccionario. También puede
obtenerse iterando, tomando el primer valor y preguntando si es mayor al siguiente, y en
caso negativo se toma el nuevo valor en reemplazo del anterior. Las líneas
correspondientes a estas soluciones no se escribirán aquí, pues como se comentó, la idea
principal es mostrar el esfuerzo que requiere trabajar con diccionarios, trabajo que puede
realizarse de forma más eficiente con DataFrame.
5. Imaginemos que tenemos 100 tenencias diferentes ¿Cómo conseguimos los tickets del
primer decil con mayor tenencia? ¿Y lo del percentil 6? La solución en este caso es
parecida a la ofrecida anteriormente, en otras palabras, es mejor trabajar con DataFrame.

Numpy (array) – Operatoria matricial


Esta es un tipo de colección o estructura no nativa que, para poder ser utilizada es necesario
importar su librería, quien permite trabajar con matrices y vectores. Las matrices y vectores se
nombran array (sus parámetros permiten definir la dimensión y con ello si son matrices o
vectores). Estas colecciones son sólo numéricas, y con ellas, podremos realizar cálculos de forma
más simple, por ejemplo, del ejercicio donde quisimos calcular la banda superior de precios a partir
de una lista, debimos utilizar un bucle, pero, si utilizamos vectores y matrices, sólo deberemos
escribir el nombre del vector de precios y multiplicarlo por el número deseado. Antes de ver el
código que resuelve aquél ejercicio, debemos conocer cómo crear un vector y una matriz.
Para trabajar con matrices es necesario descargar esta librería escribiendo lo siguiente:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑛𝑢𝑚𝑝𝑦 𝑎𝑠 𝑛𝑝
Imaginemos que tenemos la siguiente lista:
𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = [23.1,23.3,23.2,23,23.25]
Para transformar una lista en vector se llama la librería y el método array(). Entonces, escribimos
lo siguiente:
𝑝𝑟𝑒𝑐𝑖𝑜𝑠𝑠 = 𝑛𝑝. 𝑎𝑟𝑟𝑎𝑦(𝑝𝑟𝑒𝑐𝑖𝑜𝑠)
Si lo imprimimos veremos su formato de vector:
𝑝𝑟𝑖𝑛𝑡(𝑝𝑟𝑒𝑐𝑖𝑜𝑠𝑠)
𝑎𝑟𝑟𝑎𝑦([23.1,23.3,23.2,23,23.25])
A partir de esto podemos obtener la banda superior de precios con un 5% más. Para redondear el
resultado deberemos escribir otra línea y usar el método round(), quien no puede usarse como
función en el caso de matrices (ya probamos). El código es el siguiente:
𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝 = 𝑝𝑟𝑒𝑐𝑖𝑜𝑠𝑠 ∗ 1.05
𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝 = 𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝. 𝑟𝑜𝑢𝑛𝑑(2)
𝑝𝑟𝑖𝑛𝑡(𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝)
NOTA. Utilizar vectores y matrices para hacer cálculos como el de banda superior ¿Es lo mismo
que utilizar bucles o listas de comprensión? No, aunque el resultado es el mismo, la forma difiere
y el uso de memoria también. En este sentido, el uso de vectores y matrices requiere menos
memoria de cálculo que el uso de las otras alternativas.

Métodos, funciones, y atributos de numpy


Los métodos descriptos a continuación también pueden ser utilizados como funciones. Para
ejemplificar su uso, se utiliza el vector de precios de hace un momento:

o Min. Permite conocer el valor más pequeño dentro del vector o matriz. Veamos el caso del
vector:
𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝. min()
o Max. Permite conocer el valor más grande dentro del vector o matriz. Veamos el caso del
vector:
𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝. max()
o Ptp. Permite conocer el rango entre el valor máximo y mínimo del conjunto de elementos
que conforman al vector o matriz. Veamos el caso del vector:
𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝. ptp()
o Mean. Calcula la media o promedio de los valores que están en el vector o matriz. Veamos
el caso del vector:
𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝. mean()
o Std. Permite conocer el desvío estándar (poblacional) del conjunto de elementos que
componen al vector o matriz. Veamos el caso del vector:
𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝. std()
o Suma. Suma todos los objetos del array:
𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝. sum()
o Len. Esta es una función que devuelve la cantidad de objetos que constituyen al array:
len(𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝)

Las funciones siguientes no pueden utilizarse como métodos. Los ejemplos que se ilustran también
utilizan el vector de precios del ejemplo de la sección anterior:
o Median(). Sirve para obtener la mediana del conjunto de elementos que conforman al
vector o matriz. Veamos un ejemplo:
𝑛𝑝. 𝑚𝑒𝑑𝑖𝑎𝑛(𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝)
o Quantile(). Sirve para obtener un cuantil del conjunto de elementos que conforman al
vector o matriz. Veamos un ejemplo donde pedimos el cuartil inferior (podemos pedir el
que querramos):
𝑛𝑝. 𝑞𝑢𝑎𝑛𝑡𝑖𝑙𝑒(𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝 , 0,25)
o Cumsum(). Esta función genera un vector a partir de otro, cuyos elementos son la suma
acumulativa y secuencial de los elementos del vector original. Veamos un ejemplo:
𝑎 = [(1,2,3,4,5)]
𝑏 = 𝑛𝑝. 𝑐𝑢𝑚𝑠𝑢𝑚(𝑎)
𝑝𝑟𝑖𝑛𝑡(𝑏)
[(1,3,6,10,15)]

Los atributos siguientes se utilizan como métodos, pero a diferencia de los métodos, no se
escriben los paréntesis. Para su ejemplificación se continúa utilizando el vector de precios de la
sección anterior:
o Ndim. Permite conocer la dimensión del objeto, es decir si es vector será 1 (tiene sólo
columnas o sólo filas), y si es matriz será igual a 2 (tiene filas y columnas). Una matriz con
más de dos dimensiones es aquella compuesta por varias matrices, es decir, una matriz
dentro de otra (ejemplo de esto se ve en la siguiente sección). El código para conocer la
dimensión es el siguiente:
𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝. ndim
o Shape. Permite conocer la cantidad de filas y columna del objeto. Su resultado es del tipo
tupla.
𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝. shape
o Size. Permite conocer el número de elementos que conforman al vector o matriz.
𝑏𝑎𝑛𝑑𝑎𝑠𝑢𝑝. size

Números aleatorios con Numpy


Con números aleatorios podremos generar elementos para los vectores. Llamemos las librerías de
matrices y números aleatorios:

𝑖𝑚𝑝𝑜𝑟𝑡 𝑛𝑢𝑚𝑝𝑦 𝑎𝑠 𝑛𝑝
𝑖𝑚𝑝𝑜𝑟𝑡 𝑟𝑎𝑛𝑑𝑜𝑚
La librería random se aplica sobre la librería numpy. Debido a esto, sobre las funciones de
random se incorpora otro argumento que pide especificar la cantidad de elementos (size) de
acuerdo a la a la forma (shape). Con este nuevo argumento podremos crear matrices
multidimensionales ¿Para qué sirve hacerlo? En el contexto financiero, en una misma matriz
podemos trabajar con varios clientes por separado, donde cada dimensión es un cliente. Antes de
ilustrar esto con un ejemplo, veamos lo más simple, creemos un vector con diez elementos
generados aleatoriamente, pero que sigan una distribución normal estándar:
𝑛𝑝. 𝑟𝑎𝑛𝑑𝑜𝑚. 𝑛𝑜𝑟𝑚𝑎𝑙(0,1,10)
Con esto se realiza lo siguiente:

En este ejemplo tenemos un vector de tamaño diez (diez elementos), de una dimensión de 1. Si
deseamos ampliar la dimensión, por ejemplo a 3, e incorporar filas y columnas debemos escribir el
siguiente código:
𝑛𝑝. 𝑟𝑎𝑛𝑑𝑜𝑚. 𝑛𝑜𝑟𝑚𝑎𝑙(0,1, 𝑠𝑖𝑧𝑒 = (3,5,2))
Con este código se obtienen 3 matrices de orden 5x2 cada una, por ende, los argumentos de size
son: cantidad de matrices, cantidad de filas que cada una tiene, y cantidad de columnas que cada
una tiene. Este tipo de matrices se conoce como matrices multidimensionales. Ejecutando el
código, el resultado es el siguiente:

¿De qué sirve utilizar matrices multidimensionales? Como se mencionó antes, en el contexto de las
finanzas nos permite trabajar con varios clientes en simultáneo, donde cada uno tiene la misma
cantidad carteras (filas, en este caso, cinco), y cada cartera tiene la misma cantidad de activos
(columnas, en este caso, dos)
Del mismo modo se puede utilizar la distribución uniforme. En este caso, además de especificar
la cantidad de dimensiones, filas, y columnas, también será necesario definir los valores máximos y
mínimos. El código es el siguiente:
𝑛𝑝. 𝑟𝑎𝑛𝑑𝑜𝑚. 𝑢𝑛𝑖𝑓𝑜𝑟𝑚(𝑙𝑜𝑤 = −10, ℎ𝑖𝑔ℎ = 10, 𝑠𝑖𝑧𝑒 = (2,5)). 𝑟𝑜𝑢𝑛𝑑(3)
Se obtiene una matriz de orden 2x5, cuyos valores siguen una distribución uniforme y no pueden
ser mayores a 10 ni menos a -10:

Finalmente, si a todas las ejecuciones de números aleatorios se les precede con una semilla,
entonces cada vez que las ejecutemos, los resultados serán siempre iguales, de lo contrario, en cada
oportunidad tendremos diferentes valores para cada uno de los elementos. El código de la semilla
que debe preceder al código de número aleatorio es el siguiente:
𝑛𝑝. 𝑟𝑎𝑛𝑑𝑜𝑚. 𝑠𝑒𝑒𝑑(0)

Generadores numpy
Arange.

𝑛𝑝. 𝑎𝑟𝑎𝑛𝑔𝑒(0,20)

Linspace.
𝑛𝑝. 𝑙𝑖𝑛𝑠𝑝𝑎𝑐𝑒(0,2,10)

𝑛𝑝. 𝑙𝑖𝑛𝑠𝑝𝑎𝑐𝑒(0,2,10, 𝑒𝑛𝑑𝑝𝑜𝑖𝑛𝑡 = 𝑓𝑎𝑙𝑠𝑒)

Sumatorias y productorias con numpy


Al abordar el bucle for, presentamos un ejemplo para ilustrar cómo utilizarlo para calcular el
desvío estándar. En aquél momento se utilizó una lista con diez objetos, aunque bien podría
haberse usado un generador y Montecarlo. Usar un bucle for para hacer cálculos de este tipo no
es lo más eficiente, pues son iteraciones, en su lugar, lo mejor es el uso de matrices y vectores. Es
por esta razón que a continuación se calculará, como ejemplo, el desvío estándar utilizando
matrices y vectores, primero con la misma lista que en la sección del bucle for, y luego, utilizando
Montecarlo para ejemplificar la potencia de cálculo. Montecarlo nos permitirá realizar
simulaciones, pues sólo requeriremos el tipo de distribución de probabilidad y sus parámetros,
quienes pueden obtenerse de la misma serie de precios.
La fórmula del desvío estándar es la siguiente:

𝑗
1
𝜎=√ ∑(𝜇 − 𝑥𝑖 )2
𝑛−1
𝑖=1

La lista de los diez precios es la siguiente:


𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = [100.29, 99.95, 100.19, 99.21, 99.94, 99.74, 99.23, 98.78, 97.89, 99.66]
El código para obtener el desvío estándar a partir de matrices y vectores es:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑛𝑢𝑚𝑝𝑦 𝑎𝑠 𝑛𝑝
𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = 𝑛𝑝. 𝑎𝑟𝑟𝑎𝑦(𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠)
𝑚𝑒𝑑𝑖𝑎 = 𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠. 𝑚𝑒𝑎𝑛()
𝑑𝑒𝑠𝑣𝑖𝑜 = 𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠. 𝑠𝑡𝑑()
En este caso, el desvío calculado es el poblacional, no el muestral. Por otro lado, si utilizamos
Montecarlo, el código es el siguiente:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑛𝑢𝑚𝑝𝑦 𝑎𝑠 𝑛𝑝
𝑖𝑚𝑝𝑜𝑟𝑡 𝑟𝑎𝑛𝑑𝑜𝑚
𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = []
𝑛 = 500000
𝑟𝑎𝑛𝑑𝑜𝑚. 𝑠𝑒𝑒𝑑(0)
𝑓𝑜𝑟 𝑥 𝑖𝑛 𝑟𝑎𝑛𝑔𝑒(𝑛):
𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠. 𝑎𝑝𝑝𝑒𝑛𝑑(𝑟𝑎𝑛𝑑𝑜𝑚. 𝑛𝑜𝑟𝑚𝑎𝑙𝑣𝑎𝑟𝑖𝑎𝑡𝑒(100,50.5))
𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = 𝑛𝑝. 𝑎𝑟𝑟𝑎𝑦(𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠)
𝑝𝑟𝑖𝑛𝑡(𝑡𝑦𝑝𝑒(𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠))
𝑚𝑒𝑑𝑖𝑎 = 𝑟𝑜𝑢𝑛𝑑(𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠. 𝑚𝑒𝑎𝑛(),2)
𝑑𝑒𝑠𝑣𝑖𝑜 = 𝑟𝑜𝑢𝑛𝑑(𝑙𝑖𝑠𝑡𝑎𝑝𝑟𝑒𝑐𝑖𝑜𝑠. 𝑠𝑡𝑑(),2)
𝑝𝑟𝑖𝑛𝑡(𝑓′𝐸𝐿 𝑝𝑟𝑒𝑐𝑖𝑜 𝑝𝑟𝑜𝑚𝑒𝑑𝑖𝑜 𝑒𝑠 {𝑚𝑒𝑑𝑖𝑎}, 𝑦 𝑒𝑙 𝑑𝑒𝑠𝑣í𝑜 𝑒𝑠𝑡á𝑛𝑑𝑎𝑟 𝑑𝑒 {𝑑𝑒𝑠𝑣𝑖𝑜}′)
Este método combina bucle for con matrices, sin embargo, no es el más eficiente, aun es posible
reemplazar el bucle for. Para lograr esto debemos incorporar un nuevo argumento de la
distribución de probabilidad, argumento que indica la cantidad de objetos que se generarán. Este
argumento es el tercero, luego del desvío. Entonces, la línea de código es la siguiente:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑛𝑢𝑚𝑝𝑦 𝑎𝑠 𝑛𝑝
𝑖𝑚𝑝𝑜𝑟𝑡 𝑟𝑎𝑛𝑑𝑜𝑚
𝑛 = 50000
𝑚𝑎𝑡𝑟𝑖𝑧 = 𝑛𝑝. 𝑟𝑎𝑛𝑑𝑜𝑚. 𝑛𝑜𝑟𝑚𝑎𝑙(100,80, 𝑛)
𝑚𝑒𝑑𝑖𝑎 = 𝑟𝑜𝑢𝑛𝑑(𝑚𝑎𝑡𝑟𝑖𝑧. 𝑚𝑒𝑎𝑛(),2)
𝑑𝑒𝑠𝑣𝑖𝑜 = 𝑟𝑜𝑢𝑛𝑑(𝑚𝑎𝑡𝑟𝑖𝑧. 𝑠𝑡𝑑(),2)
𝑝𝑟𝑖𝑛𝑡(𝑚𝑒𝑑𝑖𝑎, 𝑑𝑒𝑠𝑣𝑖𝑜)
Este código es mucho más eficiente que los dos anteriores, de hecho, al correrlo en spider se
modificó la cantidad de simulaciones (n) y, con las iteraciones la computadora se tomó unos
segundos para calcular los resultados, en cambio, con este último código no fue necesario. Por otro
lado, observe que la librería numpy tiene en ella una función de distribución de probabilidad
normal. En este sentido, esta función requiere que primero se llame la librería, para luego llamar al
atributo random y finalmente a la distribución. Es en esta distribución que aparece el tercer
argumento que permite indicar la cantidad de objetos. Del mismo modo, cada uno de los “n”
objetos será generado y guardado en la variable ‘matriz’, quien es del tipo array, pues es generada
a partir de la librería numpy.
DataFrame: ¿Qué es? – 1/2
Un DataFrame es una matriz de dos dimensiones (filas y columnas), donde es posible ubicar
strings en la primera fila y columna, a modo de nombre de las variables. En otras palabras, a cada
fila y a cada columna se le puede asignar un nombre (label). A la vez, a cada fila y a cada columna
se puede asignar tipos de datos especiales, como fechas, float, int, etcétera. En definitiva, y
salvando las distancias, un DataFrame es muy parecido a una planilla de Excel. En línea con esto,
el DataFrame es comparable con un diccionario, pues podemos pensar que las etiquetas de cada
fila son una clave asociada a un elemento compuesto por dos valores, la etiqueta de la columna y el
valor de intersección entre fila y columna.
Un DataFrame no es un objeto nativo de Python, por ende, es necesario trabajar con su librería.
Esta se llama “pandas”, y se importa con el siguiente código:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑝𝑎𝑛𝑑𝑎𝑠 𝑎𝑠 𝑝𝑑
En general, el punto de partida de los datos serán listas, que deberemos transformar en DataFrame.
Veamos un ejemplo, supongamos el siguiente par de listas:
𝑡𝑖𝑐𝑘𝑒𝑡 = [′ 𝑇𝑆𝐿𝐴′ ,′ 𝐴𝑀𝑍𝑁 ′ ,′ 𝐺𝑂𝑂𝐺𝐿′ ,′ 𝑇𝐿𝑇 ′ ,′ 𝑆𝐻𝑌 ′ , ′𝐵𝑇𝐶′]
𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = [12500,5000,10500,20_000,7 + 7 ∗ 10 ∗∗ 1,7𝑒1]
Para crear el DataFrame o, para instanciarlo, debemos escribir lo siguiente:
𝑎 = 𝑝𝑑. 𝐷𝑎𝑡𝑎𝐹𝑟𝑎𝑚𝑒(𝑑𝑎𝑡𝑎 = 𝑝𝑟𝑒𝑐𝑖𝑜𝑠, 𝑖𝑛𝑑𝑒𝑥 = 𝑡𝑖𝑐𝑘𝑒𝑡)
El resultado es el siguiente:

NOTA. Concepto: Instanciar un objeto. Esta frase significa que se va a crear un objeto de
determinada clase, con determinadas características.

Aquí debemos prestar atención a dos cuestiones:


o Mayúsculas. Para instanciar el objeto DataFrame a partir de las listas debemos escribir el
nombre de la librería y utilizar la función DataFrame. El nombre de esta función se
escribe como se puede observar (con mayúsculas) sólo por una cuestión de hábitos de
programación, en otras palabras, no es necesario colocar las mayúsculas.
o Argumentos. El objeto DataFrame tiene dos, data e index, ninguno tiene que estar antes
que el otro necesariamente, en otras palabras, que primero se escriba data y luego index o,
se lo haga al revés, es lo mismo. No obstante, si no escribimos data ni index, por defecto
primero se deben colocar las colecciones que sean index y luego las que sean data. Por
ejemplo, si no colocamos data ni index, y se escribe la lista de string luego de la lista de
float, el resultado es el siguiente:

Por otro lado, ambos argumentos admiten diferentes tipos de datos, desde string, float, e
int, hasta colecciones, como listas. Por ende, si deseamos tener varias columnas con datos
float, en data deberemos crearlas luego de crear el objeto DataFrame, esto se explica a
continuación, en el punto 2.
Finalmente, un DataFrame también puede pensarse como una lista donde cada objeto es un
diccionario, y donde cada uno de estos diccionarios es una línea del DataFrame. Veamos un
ejemplo, si escribimos lo siguiente, tendremos una lista con una estructura de DataFrame, de
hecho, al convertirla en uno, el resultado no cambia más allá de su caracterización como objeto.
Veamos:
𝑎𝑐𝑐𝑖𝑜𝑛𝑒𝑠_𝑙𝑖𝑠𝑡
= [{′𝑇𝑖𝑐𝑘𝑒𝑟′: ′𝐺𝐺𝐴𝐿′, ′𝑁𝑜𝑚𝑏𝑟𝑒′: ′𝐵𝑎𝑛𝑐𝑜 𝐺𝑎𝑙𝑖𝑐𝑖𝑎′, ′𝑃𝑎𝑖𝑠′: ′𝐴𝑟𝑔𝑒𝑛𝑡𝑖𝑛𝑎′, ′𝑅𝑢𝑏𝑟𝑜′: ′𝐵𝑎𝑛𝑐𝑜𝑠′},
{′𝑇𝑖𝑐𝑘𝑒𝑟′: ′𝑃𝐴𝑀𝑃′, ′𝑁𝑜𝑚𝑏𝑟𝑒′: ′𝑃𝑎𝑚𝑝𝑎 𝐸𝑛𝑒𝑟𝑔í𝑎′, ′𝑃𝑎𝑖𝑠′: ′𝐴𝑟𝑔𝑒𝑛𝑡𝑖𝑛𝑎′, ′𝑅𝑢𝑏𝑟𝑜′: ′𝐸𝑛𝑒𝑟𝑔é𝑡𝑖𝑐𝑎𝑠′},
{′𝑇𝑖𝑐𝑘𝑒𝑟′: ′𝑀𝑆𝐹𝑇′, ′𝑁𝑜𝑚𝑏𝑟𝑒′: ′𝑀𝑖𝑐𝑟𝑜𝑠𝑜𝑓𝑡′, ′𝑃𝑎𝑖𝑠′: ′𝑈𝑆𝐴′, ′𝑅𝑢𝑏𝑟𝑜′: ′𝑇𝑒𝑐𝑛𝑜𝑙ó𝑔𝑖𝑐𝑎𝑠′},
{′𝑇𝑖𝑐𝑘𝑒𝑟′: ′𝐵𝐴𝐵𝐴′, ′𝑁𝑜𝑚𝑏𝑟𝑒′: ′𝐴𝑙𝑖𝑏𝑎𝑏𝑎′, ′𝑃𝑎𝑖𝑠′: ′𝐶ℎ𝑖𝑛𝑎′, ′𝑅𝑢𝑏𝑟𝑜′: ′𝐶𝑜𝑛𝑠𝑢𝑚𝑜′}]
𝑝𝑟𝑖𝑛𝑡(𝑎𝑐𝑐𝑖𝑜𝑛𝑒𝑠_𝑙𝑖𝑠𝑡)

Convirtamos esto en un DataFrame:


𝑎𝑐𝑐𝑖𝑜𝑛𝑒𝑠_𝑑𝑖𝑐 = 𝑝𝑑. 𝐷𝑎𝑡𝑎𝐹𝑟𝑎𝑚𝑒(𝑑𝑎𝑡𝑎 = 𝑎𝑐𝑐𝑖𝑜𝑛𝑒𝑠_𝑙𝑖𝑠𝑡). 𝑠𝑒𝑡_𝑖𝑛𝑑𝑒𝑥(′𝑇𝑖𝑐𝑘𝑒𝑟′)
𝑝𝑟𝑖𝑛𝑡(𝑎𝑐𝑐𝑖𝑜𝑛𝑒𝑠_𝑑𝑖𝑐)

Vemos aquí que, en lugar de utilizar el argumento index de la función pd.DataFrame, usamos la
función set_index. Esto lo hacemos porque el argumento de la primera función es útil para cuando
el índice a colocar es una colección de algún tipo, como una lista. En cambio, si el índice a elegir es
un valor o clave, como en este caso, será necesario utilizar la segunda función.
Del mismo modo, un DataFrame puede obtenerse como un diccionario de listas, esto es,
definiendo las claves y, definiendo los valores como elementos de listas. Veamos un ejemplo:
𝑑𝑎𝑡𝑎 = {′𝑇𝑖𝑐𝑘𝑒𝑟′: [′𝐴𝐿𝑈𝐴′, ′𝐵𝐵𝐴𝑅′, ′𝐵𝑀𝐴′, ′𝐵𝑌𝑀𝐴′], ′𝑃𝑟𝑒𝑐𝑖𝑜𝑠′: [19.15,73.7,234,144.4]}
𝑡𝑎𝑏𝑙𝑎 = 𝑝𝑑. 𝐷𝑎𝑡𝑎𝐹𝑟𝑎𝑚𝑒(𝑑𝑎𝑡𝑎)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)

Existe una gran diferencia entre este modo de construir el DataFrame y el de hace un momento, y
es que en este último, las listas necesariamente deben tener la misma extensión o cantidad de
elementos (por ende, un cero o vacío también debe escribirse), de lo contrario, el DataFrame no se
armará, de hecho el código tendrá un error. En cambio, con el modo anterior no hay problema con
omitir un elemento, de hecho, al hacerlo, se interpreta que dicho valor está vacío o es igual a cero.

DataFrame a diccionario. Finalmente, podemos revertir el DataFrame en un diccionario, sólo


debemos escribir lo siguiente:
𝑝𝑟𝑖𝑛𝑡(𝑡𝑎𝑏𝑙𝑎. 𝑡𝑜_𝑑𝑖𝑐𝑡())

Así obtenemos un diccionario con diccionarios anidados.


DataFrame a lista. Del mismo modo podemos transformar cada columna del DataFrame en una
lista, sólo debemos escribir lo siguiente:
𝑝𝑟𝑖𝑛𝑡(𝑡𝑎𝑏𝑙𝑎. 𝑃𝑟𝑒𝑐𝑖𝑜. 𝑡𝑜_𝑙𝑖𝑠𝑡())

DataFrame de un DataFrame. Imaginemos que deseamos obtener de un DataFrame “original”,


sólo tres columnas. Para lograrlo sólo debemos escribir el código como si estuviésemos llamando a
una columna, sólo en que en lugar de escribir el nombre de ella, escribimos el nombre de dos
columnas de interés, y las colocamos como una lista. Veamos, supongamos que tenemos una
matriz de 5.033 filas y 8 columnas, nosotros queremos sólo las columnas “open” y “close’,
entonces escribimos:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎[[′ 𝑜𝑝𝑒𝑛 ′ ,′ 𝑐𝑙𝑜𝑠𝑒 ′ ]])
Del mismo modo, en lugar de tomar columnas podemos eliminarlas, para ello escribimos qué
columnas sacar, aclarando que son columnas:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑑𝑟𝑜𝑝([′𝑜𝑝𝑒𝑛′ , ′𝑐𝑙𝑜𝑠𝑒′], 𝑎𝑥𝑖𝑠 = 1))

NOTA. Cómo conocer las características del DataFrame que importamos.


Veamos cómo lograr lo propuesto utilizando como ejemplo el DataFrame del archivo APPL.xlsx
que se encuentra en el directorio de Python:
 Nombre de columnas: Imagine que hemos descargado información de internet o, que
hemos importado algún archivo de la máquina. En cualquier caso, no recordamos los
nombres de las columnas ¿Cómo podemos saber cuáles son? Con el siguiente código:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑐𝑜𝑙𝑢𝑚𝑛𝑠. 𝑣𝑎𝑙𝑢𝑒𝑠)

𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑐𝑜𝑙𝑢𝑚𝑛𝑠)

Ambos códigos generan el mismo resultado a la luz de lo que se pretende saber, sin
embargo, el segundo brinda más información al señalar el tipo de objeto que son las
etiquetas de las columnas.
 Dimensiones del DataFrame: Con esto podemos conocer las dimensiones del archivo
importado, es decir, cuántas filas y columnas tiene. La información se presenta con el
formato habitual de las matrices. Veamos:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑠ℎ𝑎𝑝𝑒)

 Toda la información del DataFrame: Con el siguiente código podemos conocer no sólo
los datos conseguidos a través de los dos códigos anteriores, sino también que sabremos el
tipo de datos que forma parte de cada columna (string, int, float, datetime, etc). Veamos:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑖𝑛𝑓𝑜)

La versión resumida de esto, mucho mejor por su orden al presentar la información, se


obtiene con el siguiente código, quien también presenta más información sobre el
DataFrame:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑖𝑛𝑓𝑜())

Veamos ahora algunas particularidades del DataFrame:


1. ¿Cómo colocamos nombre a las columnas? Siguiendo con el ejemplo anterior, debemos
asignarle el nombre a la columna. Siguiendo el ejemplo, escribimos lo siguiente:
𝑎. 𝑐𝑜𝑙𝑢𝑚𝑛𝑠 = [′ 𝑀𝑜𝑛𝑡𝑜𝑠′]
𝑝𝑟𝑖𝑛𝑡(𝑎)

2. ¿Cómo incorporamos más columnas al DataFrame? Primero declararemos el nombre de la


nueva columna, y luego le asignaremos el contenido. Siguiendo con el ejemplo anterior,
veamos cómo incorporar una columna de porcentajes de tenencia:
𝑎[′ 𝑃𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠 ′ ] = 𝑟𝑜𝑢𝑛𝑑(𝑎. 𝑚𝑜𝑛𝑡𝑜𝑠/𝑎. 𝑚𝑜𝑛𝑡𝑜𝑠. 𝑠𝑢𝑚() ∗ 100,2)
𝑝𝑟𝑖𝑛𝑡(𝑎)
Vemos que, a cada elemento de la columna montos se lo divide por la suma de todos los
elementos de la columna montos. El resultado es:

Vemos que trabajar de este modo modifica automáticamente el objeto DataFrame. Como
alternativa, si no deseamos modificar el objeto DataFrame, escribimos directamente lo
siguiente:
𝑟𝑜𝑢𝑛𝑑(𝑎. 𝑚𝑜𝑛𝑡𝑜𝑠/𝑎. 𝑚𝑜𝑛𝑡𝑜𝑠. 𝑠𝑢𝑚() ∗ 100,2)
Si imprimimos esto, veremos como resultado lo siguiente:

En definitiva, para agregar más columnas debemos escribir el nombre que le asignaremos e
inmediatamente asignarle una lista o valor. Por ende, la lógica es como cuando usamos la
función append() de las listas, o sea, incorporamos columnas una a la vez, y las nuevas se
colocan como la última columna.
3. ¿Cómo podemos transponer un DataFrame? Para hacerlo usamos el método
“.transpose()”. Supongamos que contamos con la siguiente información de partida, y
deseamos transponer el DataFrame correspondiente:
𝑡𝑖𝑐𝑘𝑒𝑡 = [′𝑇𝑆𝐿𝐴′ , ′𝐴𝑀𝑍𝑁′ , ′𝐺𝑂𝑂𝐺𝐿′ , ′𝑇𝐿𝑇′ , ′𝑆𝐻𝑌′, ′𝐵𝑇𝐶′]
𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = [12500,5000,10500,20_000,7 + 7 ∗ 10 ∗∗ 1,7𝑒1]
𝑚𝑎𝑡𝑟𝑖𝑧 = 𝑝𝑑. 𝐷𝑎𝑡𝑎𝐹𝑟𝑎𝑚𝑒(𝑑𝑎𝑡𝑎 = 𝑝𝑟𝑒𝑐𝑖𝑜𝑠, 𝑖𝑛𝑑𝑒𝑥 = 𝑡𝑖𝑐𝑘𝑒𝑡)
𝑚𝑎𝑡𝑟𝑖𝑧. 𝑐𝑜𝑙𝑢𝑚𝑛𝑠 = [′𝑀𝑜𝑛𝑡𝑜𝑠′]
𝑝𝑟𝑖𝑛𝑡(𝑚𝑎𝑡𝑟𝑖𝑧. 𝑡𝑟𝑎𝑛𝑠𝑝𝑜𝑛𝑠𝑒)

Lo mismo se logra con:


𝑝𝑟𝑖𝑛𝑡(𝑚𝑎𝑡𝑟𝑖𝑧. 𝑇)
Nota. Aclaración. Al escribir:
𝑎[′ 𝑃𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠 ′ ] = 𝑟𝑜𝑢𝑛𝑑(𝑎. 𝑚𝑜𝑛𝑡𝑜𝑠/𝑎. 𝑚𝑜𝑛𝑡𝑜𝑠. 𝑠𝑢𝑚() ∗ 100,2)
Estamos llamando del DataFrame “a” a su atributo, sobre el cual aplicaremos la función
indicada a continuación. En cambio, si escribimos:
𝑎[′ 𝑃𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠 ′ ] = 𝑟𝑜𝑢𝑛𝑑(𝑎. [′𝑚𝑜𝑛𝑡𝑜𝑠′]/𝑎. [′𝑚𝑜𝑛𝑡𝑜𝑠′]. 𝑠𝑢𝑚() ∗ 100,2)
Obteniendo el mismo resultado que antes, pero, ahora en lugar de llamar al atributo
“Monto”, estaremos llamando a la columna “Montos”. En este caso, es apropiado escribirlo
de este segundo modo.

Finalmente, el objeto DataFrame creado hasta ahora es un objeto del tipo data, mientras que cada
una de sus columnas numéricas son del tipo series. Para ver esto sólo basta con escribir lo siguiente
(que vimos cómo hacer):
𝑝𝑟𝑖𝑛𝑡(𝑡𝑦𝑝𝑒(𝑎. 𝑀𝑜𝑛𝑡𝑜𝑠))

DataFrame: algunas funciones y métodos (1/3)


Recordamos los datos iniciales del ejemplo tratado en la sección anterior:
𝑡𝑖𝑐𝑘𝑒𝑡 = [′ 𝑇𝑆𝐿𝐴′ ,′ 𝐴𝑀𝑍𝑁 ′,′ 𝐺𝑂𝑂𝐺𝐿′ ,′ 𝑇𝐿𝑇 ′ ,′ 𝑆𝐻𝑌 ′ , ′𝐵𝑇𝐶′]
𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = [12500,5000,10500,20_000,7 + 7 ∗ 10 ∗∗ 1,7𝑒1]
𝑎 = 𝑝𝑑. 𝐷𝑎𝑡𝑎𝐹𝑟𝑎𝑚𝑒(𝑑𝑎𝑡𝑎 = 𝑝𝑟𝑒𝑐𝑖𝑜𝑠, 𝑖𝑛𝑑𝑒𝑥 = 𝑡𝑖𝑐𝑘𝑒𝑡)
𝑎[′ 𝑃𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠 ′ ] = 𝑟𝑜𝑢𝑛𝑑(𝑎. 𝑚𝑜𝑛𝑡𝑜𝑠/𝑎. 𝑚𝑜𝑛𝑡𝑜𝑠. 𝑠𝑢𝑚() ∗ 100,2)
Sobre esto aplicaremos algunas funciones y métodos. Las funciones y métodos se aplican sobre los
objetos tipo series del DataFrame. Por ende, siempre se escribe el nombre del objeto DataFrame,
seguido del tipo series, y finalmente el método o función a aplicar. Veamos algunos casos:
o ¿Cómo podemos obtener las tres mayores participaciones? Lo logramos en dos pasos,
primero ordenaremos el DataFrame o, crearemos otro objeto, tipo serie, a partir del cual
se ordenarán los montos, para luego extraer de alguno de estos objetos las tres mayores
participaciones.
Utilizando la función sort_values(), quien crea y devuelve un objeto tipo serie y, quien por
defecto ordena de menor a mayor. Entonces, escribimos lo siguiente y obtenemos el
resultado:
𝑝𝑟𝑖𝑛𝑡(𝑎. 𝑀𝑜𝑛𝑡𝑜𝑠. 𝑠𝑜𝑟𝑡_𝑣𝑎𝑙𝑢𝑒𝑠())

Si deseamos que esté ordenado de mayor a menor escribimos:


𝑝𝑟𝑖𝑛𝑡(𝑎. 𝑀𝑜𝑛𝑡𝑜𝑠. 𝑠𝑜𝑟𝑡_𝑣𝑎𝑙𝑢𝑒𝑠(𝑎𝑠𝑐𝑒𝑛𝑑𝑖𝑛𝑔 = 𝑓𝑎𝑙𝑠𝑒))

Vemos aquí que la columna de porcentajes no aparece. Si deseamos que sí lo haga y


también esté ordenada de mayor a menor de acuerdo a los montos invertidos, debemos usar
otro código. En este sentido, podemos obtener el mismo resultado aplicando la misma
función, pero sobre el objeto tipo data, obteniendo a su vez, otro objeto tipo data. La línea
es la siguiente:
𝑝𝑟𝑖𝑛𝑡(𝑎. 𝑠𝑜𝑟𝑡_𝑣𝑎𝑙𝑢𝑒𝑠(′𝑀𝑜𝑛𝑡𝑜𝑠 ′ , 𝑎𝑠𝑐𝑒𝑛𝑑𝑖𝑛𝑔 = 𝐹𝑎𝑙𝑠𝑒))

Finalmente, para extraer las tres mayores participaciones debemos utilizar la función
head(), donde su argumento es un número tipo int que debemos elegir en función de la
cantidad de objetos que queremos como retorno. Por defecto, este método trae siempre los
primeros cinco elementos del objeto. En otras palabras, si escribimos tres, obtendremos los
tres primeros elementos del objeto. Asumiendo que los objetos están ordenados de mayor a
menor, y que estamos trabajando con el objeto tipo Data, escribimos:
𝑝𝑟𝑖𝑛𝑡(𝑎. ℎ𝑒𝑎𝑑(3))

En cambio, si trabajamos con el objeto tipo series, escribimos:


𝑏 = 𝑎. 𝑀𝑜𝑛𝑡𝑜𝑠. 𝑠𝑜𝑟𝑡_𝑣𝑎𝑙𝑢𝑒𝑠(𝑎𝑠𝑐𝑒𝑛𝑑𝑖𝑛𝑔 = 𝐹𝑎𝑙𝑠𝑒)
𝑝𝑟𝑖𝑛𝑡(𝑏. ℎ𝑒𝑎𝑑(3))

Ahora, si en lugar de ticket y montos, sólo queremos los tickets, debemos modificar estos
código adicionándoles index. Veamos (ambos casos son lo mismo):
𝑝𝑟𝑖𝑛𝑡(𝑎. ℎ𝑒𝑎𝑑(3). 𝑖𝑛𝑑𝑒𝑥)
𝑝𝑟𝑖𝑛𝑡(𝑏. ℎ𝑒𝑎𝑑(3). 𝑖𝑛𝑑𝑒𝑥)

En el mismo sentido, si deseamos acceder sólo a los montos más grandes, adicionamos a lo
anterior tolist(). Veamos, aquí es sólo aplicable al caso del objeto tipo serie, no al objeto
tipo data:
𝑝𝑟𝑖𝑛𝑡(𝑏. ℎ𝑒𝑎𝑑(3). 𝑡𝑜𝑙𝑖𝑠𝑡())

En el caso del objeto tipo data, debemos acceder a la columna de interés (Montos), luego
seleccionar los tres primeros objetos, y luego aplicar este método:
𝑝𝑟𝑖𝑛𝑡(𝑎. 𝑀𝑜𝑛𝑡𝑜𝑠. ℎ𝑒𝑎𝑑(3). 𝑡𝑜𝑙𝑖𝑠𝑡())

Siguiendo con el objeto tipo data, también podemos aplicarle la función iloc[], cuyo
argumento es del tipo slicing, es decir, tiene un argumento desde, otro hasta, y otro
argumento de salto. Por ende, aplicándolo sobre la columna de interés (Montos), podemos
seleccionar la cantidad de objetos que queremos como resultado, indicando la ubicación de
ellos:
𝑝𝑟𝑖𝑛𝑡(𝑎. 𝑀𝑜𝑛𝑡𝑜𝑠. 𝑖𝑙𝑜𝑐[: 3])
Como detalle, si deseamos obtener los papeles con menor participación, en lugar de
reordenar de menor a mayor, se puede utilizar la función tail(). La lógica de la línea del
código es la misma, por ello se prescinde del ejemplo.
o ¿Cómo podemos acumular los valores de una columna? Para esto utilizamos dos métodos,
el cumsum() para crear la columna de sumas acumuladas, y el método loc[] para pedir los
tickets hasta cierto monto dentro de la columna de interés.
Antes de aplicar ambos métodos debemos ordenar de mayor a menor o, de menor a mayor.
Siempre se trabaja con un objeto tipo data. Asumiendo que ya hemos ordenado de mayor a
menor, ahora aplicamos el método mencionado y lo asignamos a una nueva columna, asi:
𝑎[′𝑀𝑜𝑛𝑡𝑜𝑠_𝑎𝑐𝑢𝑚′] = 𝑎[′𝑀𝑜𝑛𝑡𝑜𝑠′]. 𝑐𝑢𝑚𝑠𝑢𝑚()
𝑝𝑟𝑖𝑛𝑡(𝑎)

Si deseamos obtener tickets cuyo valor acumulado sea menor a 40 mil, entonces
escribimos:
𝑝𝑟𝑖𝑛𝑡(𝑎. 𝑙𝑜𝑐[𝑎. 𝑀𝑜𝑛𝑡𝑜𝑠_𝑎𝑐𝑢𝑚 < 40_000])

Este método loc[] no modifica al objeto.

DataFrame: Importación y exportación de datos de mercado


Lo que veremos ahora no está instalado por defecto en anaconda, por ende, debemos realizar un
“pip install”. Estas instalaciones se realizan desde el “Anaconda powershell prompt”. Entonces,
accedemos a esto y aquí escribimos la siguiente línea (la instalación es automática):
𝑝𝑖𝑝 𝑖𝑛𝑠𝑡𝑎𝑙𝑙 𝑦𝑓𝑖𝑛𝑎𝑛𝑐𝑒
Luego de esto importamos la librería yfinance:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑦𝑓𝑖𝑛𝑎𝑛𝑐𝑒 𝑎𝑠 𝑦𝑓
Esta librería se conecta a yahoofinance y permite obtener todos los datos de allí. A pesar de esta
capacidad, NO SE RECOMIENDA utilizar esta librería para producir informes y realizar análisis,
en su lugar, SE RECOMIENDA utilizar otra fuente de información. Esta librería es útil sólo a los
fines educativos, para mostrar cómo utilizar un DataFrame.
NOTA. Siempre que se utilice esta librería debe importarse “pandas”.

Para obtener las cotizaciones y volúmenes de cualquier papel, utilizaremos la función download().
Veamos un ejemplo para el papel de AAPL:
𝑝𝑟𝑒𝑐𝑖𝑜𝑠_𝑎𝑎𝑝𝑙 = 𝑦𝑓. 𝑑𝑜𝑤𝑛𝑙𝑜𝑎𝑑(′𝐴𝐴𝑃𝐿′ , 𝑠𝑡𝑎𝑟𝑡 =′ 2010 − 01 − 01′, 𝑎𝑢𝑡𝑜_𝑎𝑑𝑗𝑢𝑠𝑡 = 𝑇𝑟𝑢𝑒)
Vemos tres argumentos, el primero es el precio del papel de interés, el segundo es la fecha inicial
de serie, y el tercero permite definir si deseamos traer o no la columna con precios de cierre
ajustados. Si escribimos True, no obtenemos los precios de cierre ajustados, sólo los precios de
cierre, y si escribimos False obtendremos ambos En este caso, si imprimimos el objeto data
tendremos lo siguiente:

NOTA. Para conocer los parámetros de una función debemos utilizar la función help(). Por
ejemplo:
ℎ𝑒𝑙𝑝(𝑦𝑓. 𝑑𝑜𝑤𝑛𝑙𝑜𝑎𝑑)

Esta información puede exportarse a Excel, para ello debemos escribir el nombre del objeto tipo
DataFrame a exportar, y utilizar la función to_excel() cuyo argumento es el nombre del archivo
Excel (tipo string). El siguiente sería el código para este caso:
𝑝𝑟𝑒𝑐𝑖𝑜𝑠_𝑎𝑎𝑝𝑙. 𝑡𝑜_𝑒𝑥𝑐𝑒𝑙("𝐶𝑎𝑐𝑎𝑡𝑢𝑎. 𝑥𝑙𝑠𝑥")
En línea con esto, para importar archivos de Excel es necesario utilizar la librería de pandas.
Imaginemos que lo hemos hecho y la llamamos “pd”. Ahora debemos escribir, utilizando la
función read_excel(), cuyo argumento es el mismo que la función anterior, el código es el
siguiente código:
𝑏 = 𝑝𝑑. 𝑟𝑒𝑎𝑑_𝑒𝑥𝑐𝑒𝑙("𝐶𝑎𝑐𝑎𝑡𝑢𝑎. 𝑥𝑙𝑠𝑥")
Vea que hemos asignado a la variable “b” el archivo Excel importado. En este sentido, si
imprimimos el resultado obtendremos lo siguiente:
Este resultado tiene una particularidad, aunque es un objeto tipo DataFrame, su columna index no
tiene fecha, en su lugar se tiene el número de filas y las fechas están como segunda columna. Para
solucionar esto, en el mismo momento donde importamos el archivo Excel debemos utilizar la
función set_index(), y en su argumento debemos colocar el nombre de la columna que queremos
que funcione como index. Entonces, en lugar de lo anterior, escribimos lo siguiente:
𝑏 = 𝑝𝑑. 𝑟𝑒𝑎𝑑_𝑒𝑥𝑐𝑒𝑙("𝐶𝑎𝑐𝑎𝑡𝑢𝑎. 𝑥𝑙𝑠𝑥"). 𝑠𝑒𝑡_𝑖𝑛𝑑𝑒𝑥(′𝐷𝑎𝑡𝑒 ′ )
Al imprimir obtenemos:

Para descargar más de un ticket a la vez, aplicamos la misma línea de código mencionada antes,
pero en lugar de escribir el nombre del ticket, colocamos una lista que contiene el nombre los
papeles de interés. En paralelo, si estamos interesados sólo en un tipo de dato para cada papel,
debemos realizar un slice, en este sentido, podemos obtener sólo los precios de cierre. Veamos un
ejemplo:
𝑡𝑖𝑐𝑘𝑒𝑡 = [′ 𝐴𝐴𝑃𝐿′ ,′ 𝐹𝐵′ ,′ 𝐴𝑀𝑍𝑁 ′,′ 𝐺𝑂𝑂𝐺𝐿′ ,′ 𝑁𝐹𝐿𝑋 ′ ,′ 𝑄𝑄𝑄 ′ , ′𝑆𝑄𝑄𝑄′]
𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = 𝑦𝑓. 𝑑𝑜𝑤𝑛𝑙𝑜𝑎𝑑(𝑡𝑖𝑐𝑘𝑒𝑡𝑠, 𝑠𝑡𝑎𝑟𝑡 =′ 2010 − 01 − 01′ , 𝑎𝑢𝑡𝑜𝑎𝑑𝑗𝑢𝑠𝑡 = 𝑡𝑟𝑢𝑒)[′ 𝐶𝑙𝑜𝑠𝑒 ′ ]
El resultado es el siguiente:
La alternativa es descargar toda la información y aplicar el slice luego sobre la variable a quien se
le asigna todo esto.

DataFrame: algunas funciones y métodos (2/3)


Para presentar estas funciones y métodos utilizaremos los datos descargados en la sección anterior,
donde se obtienen los precios de cierre de una serie de papeles. Para recordarlos, el código
trabajado fue el siguiente:

𝑖𝑚𝑝𝑜𝑟𝑡 𝑦𝑓𝑖𝑛𝑎𝑛𝑐𝑒 𝑎𝑠 𝑦𝑓
𝑖𝑚𝑝𝑜𝑟𝑡 𝑝𝑎𝑛𝑑𝑎𝑠 𝑎𝑠 𝑝𝑑
𝑡𝑖𝑐𝑘𝑒𝑡 = [′ 𝐴𝐴𝑃𝐿′ ,′ 𝐹𝐵′ ,′ 𝐴𝑀𝑍𝑁 ′ ,′ 𝐺𝑂𝑂𝐺𝐿′ ,′ 𝑁𝐹𝐿𝑋 ′ ,′ 𝑄𝑄𝑄 ′ , ′𝑆𝑄𝑄𝑄′]
𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = 𝑦𝑓. 𝑑𝑜𝑤𝑛𝑙𝑜𝑎𝑑(𝑡𝑖𝑐𝑘𝑒𝑡𝑠, 𝑠𝑡𝑎𝑟𝑡 =′ 2019 − 12 − 31′ , 𝑎𝑢𝑡𝑜𝑎𝑑𝑗𝑢𝑠𝑡 = 𝑡𝑟𝑢𝑒)[′ 𝐶𝑙𝑜𝑠𝑒 ′ ]

Veamos cómo conseguir otro tipo de datos a partir de este DataFrame:


1. ¿Cómo calculamos porcentajes? Utilizamos la función pct_change(), aplicada sobre el
objeto en cuestión y se lo asignamos a la variable que deseemos, luego imprimimos para
ver el resultado. El argumento de esta función es la cantidad de ruedas utilizadas para
calcular el cambio porcentual, por defecto es una rueda, pero si colocamos 3, entonces el
cambio porcentual se calculará con el precio actual y el de tres ruedas después. Veamos:
𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠 = 𝑝𝑟𝑒𝑐𝑖𝑜𝑠. 𝑝𝑐𝑡_𝑐ℎ𝑎𝑛𝑔𝑒()

2. ¿Cómo podemos calcular medias? Para lograr utilizamos los métodos rolling() y mean().
El primero método mencionado agrupa los elementos de cada columna en grupos de la
cantidad mencionada en su argumento, por ejemplo, si escribimos rolling(3), agrupará los
elementos en grupo de 3. El segundo método calcula el promedio sobre lo señalado. Por
ende, si combinamos ambos métodos podemos obtener las medias móviles. Veamos cómo
obtener las medias móviles de 3 ruedas:
𝑚𝑒𝑑𝑖𝑎_𝑚𝑜𝑣𝑖𝑙_3 = 𝑝𝑟𝑒𝑐𝑖𝑜𝑠. 𝑟𝑜𝑙𝑙𝑖𝑛𝑔(3). 𝑚𝑒𝑎𝑛()
𝑝𝑟𝑖𝑛𝑡(𝑚𝑒𝑑𝑖𝑎_𝑚𝑜𝑣𝑖𝑙_3)

ACLARACION: función mean().


Uno de los argumentos de esta función es ‘axis’, quien toma el valor 0 o 1. Este argumento
permite señalar si el promedio será tomado sobre la columna, valor 0, o sobre la fila, valor 1. Por
defecto, su valor es 0.

3. ¿Cómo calculamos la varianza? Definido el objeto de interés, aplicamos el método var().


En nuestro ejemplo, lo aplicaremos sobre la variable “porcentajes”. Veamos:
𝑣𝑎𝑟 = 𝑟𝑜𝑢𝑛𝑑(𝑝𝑟𝑒𝑐𝑖𝑜𝑠2. 𝑣𝑎𝑟(),4)
𝑝𝑟𝑖𝑛𝑡(𝑣𝑎𝑟)

4. ¿Cómo calculamos la matriz de varianzas y covarianza entre cada uno de los papeles?
Una vez que contamos con las series de variaciones entre rueda y rueda, aplicamos el
método cov(). En nuestro ejemplo, el objeto en cuestión se llama “porcentajes”, veamos
cómo es el código:
𝑣𝑎𝑟_𝑐𝑜𝑣 = 𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠. 𝑐𝑜𝑣()
𝑝𝑟𝑖𝑛𝑡(𝑣𝑎𝑟_𝑐𝑜𝑣)

NOTA. Como los objetos DataFrame son matrices, sobre estos aplican las reglas operativas de
ellas. Del mismo modo, también podemos aplicarles la función round().

5. ¿Cómo calculamos el coeficiente de pearson/correlación? Para resolverlo aplicaremos el


método corr() sobre el objeto de interés, en este caso y, siguiendo el ejemplo de siempre,
lo usaremos sobre el DataFrame “porcentajes”. El código es el siguiente:
𝑐𝑜𝑟𝑟𝑒𝑙𝑎𝑐𝑖𝑜𝑛 = 𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠. 𝑐𝑜𝑟𝑟()
𝑝𝑟𝑖𝑛𝑡(𝑐𝑜𝑟𝑟𝑒𝑙𝑎𝑐𝑖𝑜𝑛)
DataFrame: Estadística descriptiva y algo más (3/3)
Aquí veremos algunos métodos que permite obtener estadísticas descriptivas. Los cálculos los
ejemplificaremos con el DataFrame de la sección anterior, donde se elimina FB debido a la
imposibilidad de acceder a sus precios. Los códigos siguientes permiten traer de nuevo este
ejemplo:

𝑖𝑚𝑝𝑜𝑟𝑡 𝑦𝑓𝑖𝑛𝑎𝑛𝑐𝑒 𝑎𝑠 𝑦𝑓
𝑖𝑚𝑝𝑜𝑟𝑡 𝑝𝑎𝑛𝑑𝑎𝑠 𝑎𝑠 𝑝𝑑
𝑡𝑖𝑐𝑘𝑒𝑡 = [′ 𝐴𝐴𝑃𝐿′ ,′ 𝐴𝑀𝑍𝑁 ′ ,′ 𝐺𝑂𝑂𝐺𝐿′ ,′ 𝑁𝐹𝐿𝑋 ′ ,′ 𝑄𝑄𝑄 ′ , ′𝑆𝑄𝑄𝑄′]
𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = 𝑦𝑓. 𝑑𝑜𝑤𝑛𝑙𝑜𝑎𝑑(𝑡𝑖𝑐𝑘𝑒𝑡𝑠, 𝑠𝑡𝑎𝑟𝑡 =′ 2019 − 12 − 31′ , 𝑎𝑢𝑡𝑜𝑎𝑑𝑗𝑢𝑠𝑡 = 𝑡𝑟𝑢𝑒)[′ 𝐶𝑙𝑜𝑠𝑒 ′ ]
𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠 = 𝑝𝑟𝑒𝑐𝑖𝑜𝑠. 𝑝𝑐𝑡_𝑐ℎ𝑎𝑛𝑔𝑒()
Los métodos se ven a continuación:
1. ¿Cómo podemos aplicar modificaciones sobre los valores del objeto DataFrame? Aquí
asumimos que estos valores son numéricos, entonces, si deseamos realizarles algún cambio
podemos utilizar, entre otras cosas, la función mul() que multiplica estos valores por lo que
deseemos. El argumento de esta función es el número que aplicaremos sobre los valores
del DataFrame. Veamos:
𝑝𝑟𝑖𝑛𝑡(𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠. 𝑚𝑢𝑙(100))
No obstante esta función, también es posible alcanzar este mismo resultado utilizando las
propiedades matriciales, es decir, escribiendo lo siguiente:
𝑝𝑟𝑖𝑛𝑡(𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠 ∗ 100)

Otro método aplicable es divide(), con características similares al mul(), pero que en lugar
de multiplicar los valores del DataFrame, lo divide. Veamos:
𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠2 = 𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠 ∗ 100
𝑝𝑟𝑖𝑛𝑡(𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠. 𝑑𝑖𝑣𝑖𝑑𝑒(100))
2. El método describe() permite obtener varias estadísticas descriptivas. Por ejemplo, si
deseamos caracterizar las series de precios de cierre podemos aplicar. Veamos esto
aplicado a “porcentajes”:
𝑝𝑟𝑖𝑛𝑡(𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠. 𝑑𝑒𝑠𝑐𝑟𝑖𝑏𝑒())

El error estándar se obtiene con la función sem().


3. El método rank() ordena de menor a mayor los valores de cada serie, y por defecto, les
asigna el número de orden, por ende, los elementos de la serie cuyo número de orden esté
más cerca del final serán los valores más grandes de toda la serie. Veamos un ejemplo:
𝑝𝑟𝑖𝑛𝑡(𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠. 𝑟𝑎𝑛𝑘())

En lugar de utilizar el argumento por defecto, se puede se puede establecer como


argumento pct=true, esto reemplaza el número de orden por el percentil, por ende, un
percentil de 0,77 implica que el 77% de todas las ruedas obtuvo una variación diaria de
precios menor a la que corresponde a este elemento.
𝑝𝑟𝑖𝑛𝑡(𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠. 𝑟𝑎𝑛𝑘(𝑝𝑐𝑡 = 𝑇𝑟𝑢𝑒))
4. El método quantile() permite conocer los valores de la serie que corresponden a
determinado cuantil. Este método permite obtener cuantiles, percentiles, deciles, etcétera.
Para poder calcular esto sólo debemos colocar en el argumento el valor correspondiente,
por ejemplo, para el primer cuantil será el 0,25, y para el primer percentil será el 0,01.
Veamos un ejemplo:
𝑝𝑟𝑖𝑛𝑡(𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠. 𝑞𝑢𝑎𝑛𝑡𝑖𝑙𝑒(0.01))

5. El método std() es idéntico al visto en una sección pasada, permite obtener el desvío
estándar. Veamos:
𝑝𝑟𝑖𝑛𝑡(𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠. 𝑠𝑡𝑑())

6. El método kurt() permite conocer la curtósis. Veamos:


𝑝𝑟𝑖𝑛𝑡(𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠. 𝑘𝑢𝑟𝑡())
ACLARACIONES: CONCEPTOS

Aquí se describen dos conceptos relacionados con las distribuciones de probabilidad, la


curtosis y el coeficiente de asimetría. Veamos cada uno:

CURTOSIS

También conocido como coeficiente de apuntamiento, la curtosis de una variable


estadística/aleatoria es una característica de la forma de su distribución de
frecuencias/probabilidad.
Según su concepción clásica, una curtosis grande implica una mayor concentración de valores de
la variable tanto muy cerca de la media de la distribución (pico) como muy lejos de ella (colas), al
tiempo que existe una relativamente menor frecuencia de valores intermedios. Esto explica una
forma de la distribución de frecuencias/probabilidad con colas más gruesas, con un centro más
apuntado y una menor proporción de valores intermedios entre el pico y colas.
Una mayor curtosis no implica una mayor varianza, ni viceversa.
Un coeficiente de apuntamiento o de curtosis es el cuarto momento con respecto a la media
estandarizado que se define como:
𝜇4
𝛽2 =
𝜎4
Donde 𝜇4 es el 4º momento centrado o con respecto a la media y 𝜎 es la desviación estándar. Su
forma algebraica es:

Observando el numerador, al estar elevado a la cuarta, los números grandes (valores extremos)
tienen mayor influencia que en el caso donde se eleva al cuadrado. Esto es lo que permite darle
más peso a las colas de la distribución de probabilidad. En paralelo, su denominador permite
capturar la concentración alrededor de la media, pues la varianza (sigma a la cuarta es igual a
multiplicar por sí misma la varianza) será pequeña mientras más acampanada sea la distribución y,
en consecuencia, la curtosis crecerá.
En la distribución normal se verifica que 𝜇4 = 3𝜎 4, donde 𝜇4 es el momento de orden 4 respecto a
la media, y 𝜎 es la desviación típica. Por eso, está más extendida la siguiente definición del
coeficiente de curtosis, también denominada exceso de curtosis:
𝜇4
𝑔2 = 4 − 3
𝜎
Donde se ha sustraído 3 (que es la curtosis de la distribución normal o gaussiana) con objeto de
generar un coeficiente que valga 0 para la Normal y tome a ésta como referencia de curtosis.
Tomando, pues, la distribución normal como referencia, una distribución puede ser:
o Leptocúrtica: Ocurre cuando 𝛽2 > 3 y 𝑔2 > 0. En estos casos, la distribución de
frecuencias está más apuntada y con colas más gruesas de lo normal.
o Platicúrtica. Ocurre cuando 𝛽2 < 3 y 𝑔2 < 0. En estos casos, la distribución de
frecuencias está menos apuntada y con colas menos gruesas de lo normal.
o Mesocúrtica. Ocurre cuando 𝛽2 = 3 y 𝑔2 = 0. En estos casos, la distribución de
frecuencias tiene una distribución normal.
La librería Pandas calcula el exceso de curtosis.

COEFICIENTE DE ASIMETRIA
Esta es una medida que permite conocer el sesgo y grado de una distribución de probabilidad. Las
medidas de asimetría son indicadores que permiten establecer el grado de simetría (o asimetría)
que presenta una distribución de probabilidad de una variable aleatoria sin tener que hacer su
representación gráfica. Como eje de simetría consideramos una recta paralela al eje de ordenadas
que pasa por la media de la distribución. Si una distribución es simétrica, existe el mismo número
de valores a la derecha que a la izquierda de la media, por tanto, el mismo número de desviaciones
con signo positivo que con signo negativo. Decimos que hay asimetría positiva (o a la derecha) si
la "cola" a la derecha de la media es más larga que la de la izquierda, es decir, si hay valores más
separados de la media a la derecha. Diremos que hay asimetría negativa (o a la izquierda) si la
"cola" a la izquierda de la media es más larga que la de la derecha, es decir, si hay valores más
separados de la media a la izquierda.
En teoría de la probabilidad y estadística, la medida de asimetría más utilizada parte del uso del
tercer momento estándar. La razón de esto es que nos interesa mantener el signo de las
desviaciones con respecto a la media, para obtener si son mayores las que ocurren a la derecha de
la media que las de la izquierda.
El coeficiente de asimetría de Fisher, representado por 𝛾1 , se define como
𝜇
𝛾1 = 3
𝜎3
Donde 𝜇3 es el tercer momento en torno a la media y, 𝜎3 es la desviación estándar. Su forma
algebraica es la siguiente:

Al estar elevado al cubo, su signo toma relevancia o significado, indicado el sentido del sesgo,
mientras que su resultado numérico refiere a la importancia del mismo.

Si 𝛾1 > 0, la distribución es asimétrica positiva o a la derecha.


Si 𝛾1 < 0, la distribución es asimétrica negativa o a la izquierda.
Si la distribución es simétrica, entonces sabemos que 𝛾1 = 0. El recíproco no es cierto: es un error
común asegurar que si 𝛾1 = 0 entonces la distribución es simétrica (lo cual es falso).

7. El método skew() caracteriza el grado de asimetría de una distribución con respecto a su


media. Una asimetría positiva indica una distribución unilateral que se extiende hacia
valores más positivos. Veamos un ejemplo:
𝑝𝑟𝑖𝑛𝑡(𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠. 𝑠𝑘𝑒𝑤())
Nota. Sesgo. El coeficiente de asimetría es una medida del sesgo respecto a la media. Un valor
positivo de este indicador significa que la distribución se encuentra sesgada hacia la izquierda
(orientación positiva). Un resultado negativo significa que la distribución se sesga a la derecha.

8. ¿Cómo calculamos la covarianza? Con cov(), indicando el DataFrame, la columna, y


luego introduciendo esta función. Además, dentro de la función deberemos colocar como
argumento la otra variable con quien deseamos comparar. Veamos un ejemplo:
𝑑𝑎𝑡𝑎. 𝐺𝐺𝐴𝐿. 𝑐𝑜𝑣(𝑑𝑎𝑡𝑎. 𝐵𝑀𝐴)
Ahora, la matriz de covarianzas se puede calcular del siguiente modo:
𝑑𝑎𝑡𝑎. 𝑐𝑜𝑣()
9. ¿Cómo calculamos la correlación? Con la función corr(), veamos:
𝑑𝑎𝑡𝑎. 𝑐𝑜𝑟𝑟()
10. ¿Cómo convertimos un objeto tipo DataFrame en un diccionario? Para hacerlo aplicamos
el método to_dict() sobre el objeto tipo DataFrame. Al hacerlo, estaremos creando un
diccionario anidado como se aprecia en el siguiente ejemplo:
𝑝𝑟𝑖𝑛𝑡(𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠. 𝑡𝑜_𝑑𝑖𝑐𝑡())
Como puede apreciarse en esta imagen, el diccionario formado tiene dos estructuras, el
primer tipo de clave son los ticket de los papeles, y sus valores son las fechas y cambios
porcentuales. El segundo nivel es otro diccionario, donde la clave es la fecha y el valor es
el cambio porcentual. Estas estructuras se repiten para cada ticket.

NOTA. Transponer una tabla o matriz. Para transponer sólo debemos utilizar el método T, por
ejemplo, imaginemos que tenemos el siguiente DataFrame:
𝑝𝑟𝑖𝑛𝑡(𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠)

Entonces, si el objetivo es transponer esta tabla escribimos lo siguiente:


𝑝𝑟𝑖𝑛𝑡(𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒𝑠. 𝑇)

11. ¿Cómo podemos graficar a partir del DataFrame? Para presentar esta respuesta, primero
veamos cómo armar un índice. Partiendo del objeto original “precios”, nuestro objetivo es
lograr una serie de índices, donde el precio base es el primer precio de la serie. Esto lo
podemos lograr utilizando la función divide() e iloc[], pero también es posible haciendo
uso de las propiedades de las matrices y la función iloc[]. Veamos ambos casos,
comenzado por la función divide():
𝑝𝑟𝑖𝑛𝑡(𝑝𝑟𝑒𝑐𝑖𝑜𝑠. 𝑑𝑖𝑣𝑖𝑑𝑒(𝑝𝑟𝑒𝑐𝑖𝑜𝑠. 𝑖𝑙𝑜𝑐[0]))
Este mismo resultado se obtiene con este otro código:

𝑝𝑟𝑖𝑛𝑡(𝑝𝑟𝑒𝑐𝑖𝑜𝑠 ∗ 1/𝑝𝑟𝑒𝑐𝑖𝑜𝑠. 𝑖𝑙𝑜𝑐[0])


Asumamos que este resultado se lo asignó a un objeto llamado “índice”, luego, para
graficar utilizamos el método plot(). El código y resultado serían los siguientes:
𝑖𝑛𝑑𝑖𝑐𝑒. 𝑝𝑙𝑜𝑡()

Entre los argumentos de este método plot(), tenemos a figsize, quien permite definir el
tamaño de la gráfica y su leyenda, veamos cómo queda la gráfica cuando lo usamos:
𝑖𝑛𝑑𝑖𝑐𝑒. 𝑝𝑙𝑜𝑡(𝑓𝑖𝑔𝑠𝑖𝑧𝑒 = (16,8))
Asimismo, también tenemos el argumento grid (que asume valores booleanos) y permite
incorporar las líneas de coordenadas, veamos:
𝑖𝑛𝑑𝑖𝑐𝑒. 𝑝𝑙𝑜𝑡(𝑓𝑖𝑔𝑠𝑖𝑧𝑒 = (16,6), 𝑔𝑟𝑖𝑑 = 𝑇𝑟𝑢𝑒)

Finalmente, imaginemos que deseamos concentrar la atención sólo sobre los papeles que
suben, esto es, descartamos SQQQ. Para esto, usamos la función iloc[] del siguiente modo:
𝑖𝑛𝑑𝑖𝑐𝑒. 𝑖𝑙𝑜𝑐[: , : 5]. 𝑝𝑙𝑜𝑡(𝑓𝑖𝑔𝑠𝑖𝑧𝑒 = (16,6), 𝑔𝑟𝑖𝑑 = 𝑇𝑟𝑢𝑒)
Lo que importa en este caso es cómo utilizamos iloc[], observe que hemos utilizado las
propiedades “desde”, “hasta”, y “salto”, para las filas y las columnas. En este sentido, para
las filas definimos utilizarlas todas, pero para las columnas nos limitamos a todos los
papeles excepto el SQQQ.

Excepciones y errores
Cuando tenemos un error en la ejecución del código, el mismo se interrumpe, para solucionar esto
podemos utilizar sentencias condicionales o el bloque “try, except”. Aquí veremos este segundo, a
partir del siguiente ejemplo:

𝑎 = 20
𝑏=0
𝑐 = 𝑎/𝑏
𝑝𝑟𝑖𝑛𝑡(′𝑁𝑒𝑐𝑒𝑠𝑖𝑡𝑜 𝑢𝑛𝑎𝑠 𝑏𝑢𝑒𝑛𝑎𝑠 𝑣𝑎𝑐𝑖𝑜𝑛𝑒𝑠 ′ )
Al ejecutar esto tendremos un error, pues no existe resultado alguno de una división por cero.
Aplicando el bloque mencionado podemos escribir:
𝑎 = 20
𝑏=0
𝑡𝑟𝑦 :
𝑐 = 𝑎/𝑏
𝑒𝑥𝑐𝑒𝑝𝑡 :
𝑝𝑟𝑖𝑛𝑡(′𝑁𝑒𝑐𝑒𝑠𝑖𝑡𝑜 𝑢𝑛𝑎𝑠 𝑏𝑢𝑒𝑛𝑎𝑠 𝑣𝑎𝑐𝑖𝑜𝑛𝑒𝑠 ′ )
Con esto decimos algo así: “intentá hacer la división, y si no se puede (except) haz esto otro…”. En
línea con esto, una vez que el código ejecuta el “except” continúa con el resto de las líneas sin
interrumpir la ejecución.
En paralelo, si el bloque que corresponde a “except” no se define aun, el código puede correrse
igual, pero en este lugar deberá escribirse “pass”. En el ejemplo anterior sería:
𝑎 = 20
𝑏=0
𝑡𝑟𝑦 :
𝑐 = 𝑎/𝑏
𝑒𝑥𝑐𝑒𝑝𝑡 :
𝑝𝑎𝑠𝑠
En este caso, si “b” es igual a cero el código no arrojará error, pero claramente tampoco arrojará
alguna leyenda como antes. Si no se coloca “pass” u otra línea de código, lo escrito producirá un
error al ejecutarse.
El uso de condicionales trae consigo al bloque if else. En este caso, el código para este ejemplo es:
𝑎 = 20
𝑏=0
𝑖𝑓 𝑏! = 0 ∶
𝑐 = 𝑎/𝑏
𝑒𝑙𝑠𝑒 ∶ :
𝑝𝑟𝑖𝑛𝑡(′𝑁𝑒𝑐𝑒𝑠𝑖𝑡𝑜 𝑢𝑛𝑎𝑠 𝑏𝑢𝑒𝑛𝑎𝑠 𝑣𝑎𝑐𝑖𝑜𝑛𝑒𝑠 ′ )
Con esto estamos diciendo que si b es diferente de cero, entonces se ejecute la división, y si es
igual a cero, que se ejecute la leyenda.
La diferencia entre utilizar el primer bloque y el segundo es, que el primero es más general, para
todo tipo de errores que no permiten el cálculo, en cambio, en el caso del segundo tipo de bloque,
sólo se salva el error producido por una situación donde b es igual a cero, pero aun es un número.
En otras palabras, si el error fuese producido porque b es un string, el primer bloque sí salva el
código, pero el segundo no. En conclusión, se recomienda usar el primer tipo de bloques para
resolver errores, no el segundo, pues no siempre podremos imaginar todos los tipos de problemas.
Operadores lógicos
Los más comunes son los siguientes:

Vamos a concentrarnos en los últimos dos, pues asumimos conocidos al resto. Respecto a in
imaginemos que tenemos la siguiente lista y deseamos saber si determinado ticket está en ella,
escribimos lo siguiente:
𝑙𝑖𝑠𝑡𝑎 = [′ 𝐺𝐺𝐴𝐿′ ,′ 𝑃𝐴𝑀𝑃′ ,′ 𝑌𝑃𝐹𝐷′ ,′ 𝐶𝐸𝑃𝑈 ′ ,′ 𝐸𝐷𝑁 ′ ,′ 𝐿𝑂𝑀𝐴′ , ′𝐶𝑅𝐸𝑆′]
𝑡𝑖𝑐𝑘𝑒𝑡 = ′𝐺𝐺𝐴𝐿′
𝑡𝑖𝑐𝑘𝑒𝑡 𝑖𝑛 𝑙𝑖𝑠𝑡𝑎
El resultado será True. En línea con esto, el not in es su negación.
Sentencia if & comando break
Este es un condicional y como tal define ramas del flujo de acción del código. Con esta sentencia
se “pregunta” si se cumple determinada condición, y de ser así aplica otra línea de código, pero en
caso de no cumplirse la condición, entonces aplica otra línea de código. Como en Excel, este
sentencia puede anidarse o concatenarse con otras sentencias if. Su estructura básica es la siguiente:
𝑖𝑓 (𝑐𝑜𝑛𝑑𝑖𝑐𝑖𝑜𝑛):
𝑙𝑖𝑛𝑒𝑎𝑠 𝑑𝑒 𝑐ó𝑑𝑖𝑔𝑜𝑠
𝑒𝑙𝑠𝑒 ∶
𝑙𝑖𝑛𝑒𝑎𝑠 𝑑𝑒 𝑐ó𝑑𝑖𝑔𝑜𝑠
Una particularidad de esta sentencia, es que el else puede no estar.
Por otro lado, esta sentencia puede utilizarse de dos modos:
o Concatenar al no cumplir condición. En este caso, la concatenación de sentencias if se
produce sólo cuando las condiciones no se cumplen. En estos casos, la concatenación
siempre es incorporando nuevos elif. La siguiente imagen ilustra esto:

o Concatenar siempre. En estos casos la concatenación de sentencias if se realiza tanto para


la situación donde se cumple la condición, como cuando no.
A continuación se ilustra esto:
Un ejemplo sencillo que sirve para ilustrar el uso de este condicional es el siguiente, donde se
escribe un código que genera una leyenda de acuerdo al precio actual en contraste con tres rangos
de precios. En este sentido, si el precio actual está debajo del stoploss, entonces la leyenda será
“vender para recortar pérdida”, si está por encima del takeprofit, la leyenda será “vender para tomar
ganancia”, y si está entre ambos valores, la leyenda será “Mantener el papel”. El código
correspondiente es el siguiente:

Por otro lado, el comando break permite interrumpir la ejecución del bucle for cuando se cumple
determinada condición. Veamos un ejemplo, donde generamos un bucle for que iterará 10 veces y,
en cada oportunidad imprimirá el valor de la iteración, aunque, la condición del break será que
dicha ejecución se detenga luego de la quinta iteración. Veamos:
𝑓𝑜𝑟 𝑖 𝑖𝑛 𝑟𝑎𝑛𝑔𝑒(10):
𝑝𝑟𝑖𝑛𝑡(𝑖)
𝑖𝑓 𝑖 == 5:
𝑏𝑟𝑒𝑎𝑘
Veamos a continuación un ejemplo de aplicación del condicional if else, el bucle for, y el
comando break. En este ejemplo, la intención es crear un código que imprima una leyenda según
el contraste entre el precio actual de cotización y las resistencias. A continuación se muestra una
posible solución:

Este código puede mejorarse combinando bucle for, condicional if else, y comando break. Es
necesario mejorarlo, pues de lo contrario, en un escenario donde se cuente con 10 o más
resistencias sería demasiado engorroso utilizar este encadenamiento. A continuación se comparte el
código correspondiente:
𝑝𝑟𝑒𝑐𝑖𝑜𝑠 = []
𝑓𝑜𝑟 𝑖 𝑖𝑛 𝑟𝑎𝑛𝑔𝑒(0,170,10):
𝑝𝑟𝑒𝑐𝑖𝑜𝑠. 𝑎𝑝𝑝𝑒𝑛𝑑(𝑖)
𝑝𝑟𝑒𝑐𝑖𝑜 = 𝑓𝑙𝑜𝑎𝑡(𝑖𝑛𝑝𝑢𝑡("Introduzca el precio actual:")
𝑓𝑜𝑟 𝑖 𝑖𝑛 𝑝𝑟𝑒𝑐𝑖𝑜𝑠:
𝑖𝑓 𝑝𝑟𝑒𝑐𝑖𝑜 < 𝑖:
𝑝𝑟𝑖𝑛𝑡("La resistencia a vencer es: ", 𝑖)
𝑏𝑟𝑒𝑎𝑘
𝑒𝑙𝑖𝑓 𝑝𝑟𝑒𝑐𝑖𝑜 ≥ 𝑝𝑟𝑒𝑐𝑖𝑜𝑠[−1]:
𝑝𝑟𝑖𝑛𝑡("Estamos en máximos históricos")
𝑏𝑟𝑒𝑎𝑘
SEGUNDA PARTE: BASES – PROFUNDIZANDO
DATAFRAME
DataFrame: ¿Qué es? – 2/2
Existen dos modos de “levantar” un DataFrame utilizando la librería pandas:
1. Levantarlo online. Esto implica navegar por internet para encontrar el archivo de interés y
leerlo. Por ejemplo, cuando usamos un CSV, como será el caso a continuación. El CSV es
parecido a Excel, sólo que su formato está cifrado en texto (como un txt), así cada
valor/columna está separado por una coma, y los saltos de línea por saltos de línea. Un
ejemplo de esto es la siguiente imagen:

El código de la librería de pandas es el siguiente, el mismo lee la dirección en internet y


arma el DataFrame:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑝𝑎𝑛𝑑𝑎𝑠 𝑎𝑠 𝑝𝑑
𝑟𝑢𝑡𝑎 = ′https://raw. githubusercontent. com/gauss314/ucema/main/SP500. csv′
𝑑𝑎𝑡𝑎 = 𝑝𝑑. 𝑟𝑒𝑎𝑑_𝑐𝑠𝑣(𝑟𝑢𝑡𝑎, 𝑖𝑛𝑑𝑒𝑥_𝑐𝑜𝑙 = ′𝐷𝑎𝑡𝑒′)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)
Vemos dos argumentos en la función read_csv, el primero es la dirección de internet, y el
segundo es el nombre de la columna que ordena el resto de los datos, o sea, la columna de
datos tipo índice.
2. Levantarlo localmente. Esto implica leer, por ejemplo, un CSV u otra extensión de archivo
que esté guardada en la computadora de uno. Esto lo haremos en dos pasos, primero
veremos si tenemos el archivo en la carpeta donde python busca usualmente, y luego
escribiremos el nombre de dicho archivo. Veamos:
Primero: El código correspondiente a la búsqueda de la carpeta para una extensión en
particular es el siguiente:
! 𝑑𝑖𝑟 ∗. 𝑐𝑣𝑠
Con esta línea estamos pidiendo que en el lector de código aparezcan todo los archivos .csv
que están guardados en la computadora. El signo invertido de exclamación es el símbolo
que “abre” la consola y otorga permiso para hacer esto. En paralelo, dir es la orden de
buscar en la computadora, en todas sus carpetas. Finalmente, *.cvs indica que esa búsqueda
se limite a los archivos con esta extensión. En este sentido, si queremos que aparezcan
todos los archivos de la carpeta donde python guarda información, escribimos lo siguiente:
! 𝑑𝑖𝑟 ∗.∗
Actualmente aparece esto (hoy es 23/11/2022):

Segundo: Habiendo constatado esto, escribimos el código para importar el archivo. En este
caso tenemos varios archivos CSV guardados, de los cuales elegiremos el llamado
AAPL.CSV (como se indica en el video de la clase). Para importarlo escribimos:
𝑟𝑢𝑡𝑎 = ′𝐴𝐴𝑃𝐿. 𝑐𝑠𝑣′
𝑑𝑎𝑡𝑎 = 𝑝𝑑. 𝑟𝑒𝑎𝑑_𝑐𝑠𝑣(𝑟𝑢𝑡𝑎)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)
Entonces, en ruta escribimos el nombre del archivo, de este modo, el código buscará el
archivo en la máquina (esto se conoce como ruta relativa). En este sentido, si conocemos
específicamente la dirección donde está ubicado el archivo, la escribimos en lugar de poner
sólo su nombre (a esto se le conoce como ruta absoluta). El resultado es el siguiente:
Renombrar columnas
Supongamos que tenemos un DataFrame llamado “a”, y tiene una cantidad “n” de columnas. Para
renombrar sus columnas, podemos utilizar uno de dos métodos. Veamos cada método a través de
un ejemplo, lo siguiente es la imagen del vector fila, y a continuación se explica cada método:

 Cambio de todas las columnas. Este método requiere conocer la cantidad “n” de columnas
y su orden o contenido, pues el mismo cambia el nombre de todas las variables, y el modo
de hacerlo es similar a la forma utilizada cuando creamos una nueva columna. Lo que se
hace es asignar al vector fila correspondiente una lista con los nuevos nombres. Veamos un
ejemplo:

 Cambio de algunas columnas. Con esta alternativa utilizamos el método rename(), cuyo
argumento principal es ‘columns’. Lo siguiente es colocar un diccionario, donde la clave es
el nombre original que deseamos modificar, y el valor es el nuevo nombre a asignar.
Veamos cómo modificar lo señalado:
Esto lo escribimos asignándolo a un nuevo objeto por si no deseamos reescribir el objeto
original, sin embargo, si no importa, esto lo asignamos al objeto original.
Parte de renombrar columnas consiste en pasar de mayúsculas a minúsculas. Para hacer esto
utilizamos el código str.lower(). Esto debemos aplicarlo sobre las columnas, valga la redundancia,
veamos:
𝑑𝑓. 𝑐𝑜𝑙𝑢𝑚𝑛𝑠 = 𝑑𝑓. 𝑐𝑜𝑙𝑢𝑚𝑛𝑠. 𝑠𝑡𝑟. 𝑙𝑜𝑤𝑒𝑟()

Renombrar filas

Para lograr esto utilizamos el método rename(), cuyo argumento principal será index. Modificar el
nombre de las filas implica modificar el valor de cada celda del índice. Una alternativa es modificar
el nombre de las columnas y luego transponer, sin embargo, si no hacemos esto, debemos utilizar el
método mencionado. Los valores del argumento index se deben escribir como un diccionario,
señalando en lugar de la clave la ubicación o nombre original de la fila, y en su valor el nuevo
nombre. Veamos un ejemplo, supongamos que el vector fila con los nombres originales del
DataFrame son los siguientes:

Para cambiarlos usaremos el método descripto del siguiente modo:

Armar DataFrame a partir de series


Imaginemos que tenemos un DataFrame base con cinco columnas, y de éstas, apartamos dos para
luego construir con ambas otro DataFrame ¿Cómo logramos esto? Veamos tres modos de
realizarlo a través de un mismo ejemplo base. Supongamos que estamos trabajando con el archivo
CSV llamado ‘ko_comas.csv’, es decir, que ya lo importamos y lo asignamos a la variable ‘df’,
además, sus columnas son ‘open’, ‘high’, ‘low’, ‘close’, y ‘volume’. Las columnas que aislaremos
son ‘open’ y ‘close’, asignándolas a ‘apertura_1’ y ‘cierre_1’, para posteriormente armar un
DataFrame con ambas llamado ‘data_1’. Veamos tres modos de lograrlo:
 Función DataFrame() versión 1. Primero aislamos las columnas, luego creamos el nuevo
DataFrame vació, y luego introducimos en él las columnas:
𝑎𝑝𝑒𝑟𝑡𝑢𝑟𝑎_1 = 𝑑𝑎𝑡𝑎. 𝑜𝑝𝑒𝑛
𝑐𝑖𝑒𝑟𝑟𝑒_1 = 𝑑𝑎𝑡𝑎. 𝑐𝑙𝑜𝑠𝑒
𝑑𝑎𝑡𝑎_1 = 𝑝𝑑. 𝐷𝑎𝑡𝑎𝐹𝑟𝑎𝑚𝑒()
𝑑𝑎𝑡𝑎_1[′𝑜𝑝𝑒𝑛′] = 𝑎𝑝𝑒𝑟𝑡𝑢𝑟𝑎_1
𝑑𝑎𝑡𝑎_1[′𝑐𝑙𝑜𝑠𝑒′] = 𝑐𝑖𝑒𝑟𝑟𝑒_1
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎_1)
 Función concat(). Primero aislamos las columnas, y luego aplicamos esta función
asignando el resultado al nuevo DataFrame. En los argumentos de esta función estarán
estas columnas y, deberemos indicar que las mismas se aplicarán como columnas:
𝑎𝑝𝑒𝑟𝑡𝑢𝑟𝑎_1 = 𝑑𝑎𝑡𝑎. 𝑜𝑝𝑒𝑛
𝑐𝑖𝑒𝑟𝑟𝑒_1 = 𝑑𝑎𝑡𝑎. 𝑐𝑙𝑜𝑠𝑒
𝑑𝑎𝑡𝑎_1 = 𝑝𝑑. 𝑐𝑜𝑛𝑐𝑎𝑡([𝑎𝑝𝑒𝑟𝑡𝑢𝑟𝑎_1, 𝑐𝑖𝑒𝑟𝑟𝑒_1], 𝑎𝑥𝑖𝑠 = 1)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎_1)
 Función DataFrame() versión 2. Primero aislamos las columnas, y luego aplicamos esta
función, colocando en su argumento “data” ambas columnas aisladas como una lista, y
transponiendo el resultado. Veamos:
𝑎𝑝𝑒𝑟𝑡𝑢𝑟𝑎_1 = 𝑑𝑓. 𝑜𝑝𝑒𝑛
𝑐𝑖𝑒𝑟𝑟𝑒_1 = 𝑑𝑓. 𝑐𝑙𝑜𝑠𝑒
𝑑𝑎𝑡𝑎_1 = 𝑝𝑑. 𝐷𝑎𝑡𝑎𝐹𝑟𝑎𝑚𝑒(𝑑𝑎𝑡𝑎 = [𝑎𝑝𝑒𝑟𝑡𝑢𝑟𝑎_1, 𝑐𝑖𝑒𝑟𝑟𝑒_1]). 𝑇
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎_1)

Armar DataFrame a partir de valores de una columna


Imaginemos que tenemos un DataFrame base con cinco columnas, y de éstas, apartamos los
valores de dos (sin sus nombres) para luego construir con ambas otro DataFrame ¿Cómo logramos
esto? Supongamos que estamos trabajando con el archivo CSV llamado ‘ko_comas.csv’, es decir,
que ya lo importamos y lo asignamos a la variable ‘df’, además, sus columnas son ‘open’, ‘high’,
‘low’, ‘close’, y ‘volume’. Las columnas que aislaremos son ‘open’ y ‘close’, asignándolas a
‘apertura_2’ y ‘cierre_2’, para posteriormente armar un DataFrame con ambas llamado ‘data_2’.
Para resolver esto se pueden utilizar cualquiera de los tres métodos señalados en la sección anterior
inmediata, la única diferencia es el uso de valores en lugar de series. A continuación mostramos
cómo resolverlo utilizando la función DataFrame() versión 2:
𝑎𝑝𝑒𝑟𝑡𝑢𝑟𝑎_2 = 𝑑𝑓. 𝑜𝑝𝑒𝑛. 𝑣𝑎𝑙𝑢𝑒𝑠
𝑐𝑖𝑒𝑟𝑟𝑒_2 = 𝑑𝑓[′𝑐𝑙𝑜𝑠𝑒′]. 𝑣𝑎𝑙𝑢𝑒𝑠
𝑑𝑎𝑡𝑎_2 = 𝑝𝑑. 𝐷𝑎𝑡𝑎𝐹𝑟𝑎𝑚𝑒(𝑑𝑎𝑡𝑎 = [𝑎𝑝𝑒𝑟𝑡𝑢𝑟𝑎_2, 𝑐𝑖𝑒𝑟𝑟𝑒_2]). 𝑇
𝑑𝑎𝑡𝑎_2. 𝑐𝑜𝑙𝑢𝑚𝑛𝑠 = [′𝑎𝑝𝑒𝑟𝑡𝑢𝑟𝑎′, ′𝑐𝑖𝑒𝑟𝑟𝑒′]
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎_2)

Puesta a punto de un DataFrame al importarlo


Imaginemos que importamos un archivo Excel, que está en la segunda hoja llamada ‘ko’ y que está
construido en las celdas que se muestran a continuación:

Al importar el archivo indicando la hoja dos como señala el código siguiente, tenemos como
resultado un DataFrame muy desordenado, veamos:
𝑒𝑥𝑐𝑒𝑙 = 𝑝𝑑. 𝑟𝑒𝑎𝑑_𝑒𝑥𝑐𝑒𝑙(′𝑘𝑜_𝑒𝑥𝑐𝑒𝑙. 𝑥𝑙𝑠𝑥′, 𝑠ℎ𝑒𝑒𝑡_𝑛𝑎𝑚𝑒 = ′𝑘𝑜′)
𝑝𝑟𝑖𝑛𝑡(𝑒𝑥𝑐𝑒𝑙)
Vemos que se importó la tabla, pero junto a ella también las dos primeras columnas vacías, y las
dos filas vacías. Además, la primera de estas filas vacías fue interpretada como índice de columna.
Para solucionar esto utilizaremos tres argumentos de la función read_excel(). Veamos cada
argumento:
 Para indicar desde la fila que se comienza a leer. Utilizamos el argumento skiprows,
igualando el argumento al número de fila hasta la que incluso se borra o, desde la cual
comienza la tabla sin incluir dicha fila. Recuerde que como toda ubicación, las filas
comienzan desde 0.
 Para indicar desde la columna que se comienza a leer. Utilizamos el argumento usecols, y
lo igualemos al rango de columnas que abarcan al DataFrame. Para señalar este rango
escribimos range(), cuyos extremos son inclusivos.
 Para indicar la fila que será índice de columna. Utilizamos el argumento head,
asignándole la ubicación de la fila que será utilizada como índice de columnas.
Atendiendo a estos argumentos escribimos el siguiente código en reemplazo del anterior:
𝑒𝑥𝑐𝑒𝑙 = 𝑝𝑑. 𝑟𝑒𝑎𝑑_𝑒𝑥𝑐𝑒𝑙(′𝑘𝑜_𝑒𝑥𝑐𝑒𝑙. 𝑥𝑙𝑠𝑥′, 𝑠ℎ𝑒𝑒𝑡_𝑛𝑎𝑚𝑒 = ′𝑘𝑜′, 𝑠𝑘𝑖𝑝𝑟𝑜𝑤𝑠 = 1, ℎ𝑒𝑎𝑑𝑒𝑟 = 1, 𝑢𝑠𝑒𝑐𝑜𝑙𝑠 = 𝑟𝑎𝑛𝑔𝑒(2,8))

Para resolver el índice de filas se recomienda la lectura se la sección sobre importar archivos de
Excel.
Otro tema a evaluar es la presencia de valores nulos. Para corroborar la existencia o no de ellos
utilizamos la función/método insull(). Si la utilizamos como función, podremos imprimir el
resultado para observar visualmente si existe o no un valor nulo. Como función, reemplaza
temporariamente los valores de cada celda por un ‘false’ si en dicha celda hay un valor, y por un
‘true’ si en dicha celda hay un valor nulo. Como método, podemos utilizarla junto a sum() para
calcular la cantidad de valores nulos en cada variable/columna. Veamos ambos casos a
continuación:
 Como función. El problema con este uso ocurre cuando tenemos grandes bases de datos,
pues no podremos ver una celda a la vez, es impráctico. Veamos:
𝑝𝑟𝑖𝑛𝑡(𝑝𝑑. 𝑖𝑛𝑠𝑢𝑙𝑙(𝑒𝑥𝑐𝑒𝑙))

 Como método. Aunque no podamos conocer exactamente la ubicación del valor nulo, saber
que existe y que está ubicado en determinada columna permite construir un criterio para
solucionar su presencia, decidiendo borrar la fila o reemplazando dicho valor por otra cosa.
Veamos cómo utilizar este método:
𝑝𝑟𝑖𝑛𝑡(𝑝𝑑. 𝑖𝑛𝑠𝑢𝑙𝑙(𝑒𝑥𝑐𝑒𝑙). 𝑠𝑢𝑚())

Vemos que este DataFrame no tiene valores nulos.

Ordenando el DataFrame
Imaginemos que queremos reordenar la información de un DataFrame de acuerdo a los valores de
una de sus columnas/variables. Por ejemplo, supongamos que una de estas variables es la fecha, o
ésta, es el índice del DataFrame ¿Cómo reordenamos todo? Veamos dos modos según la columna
sea o no el índice:
 Columna cualquiera. En este caso utilizamos la función sort_values(), y como argumento
se coloca el nombre de la columna de interés. Otro argumento de la función es
“ascending”, si lo igualamos a True, entonces el DataFrame se ordenará de menor a
mayor, y si es False, se ordenará de mayor a menor (por defecto es True). El código sería,
para un DataFrame llamado df, el siguiente:
𝑑𝑓 = 𝑑𝑓. 𝑠𝑜𝑟𝑡_𝑣𝑎𝑙𝑢𝑒𝑠(′𝑜𝑝𝑒𝑛 ′ , 𝑎𝑠𝑐𝑒𝑛𝑑𝑖𝑛𝑔 = 𝑇𝑟𝑢𝑒)
También puede escribirse del siguiente modo:
𝑑𝑓 = 𝑑𝑓. 𝑜𝑝𝑒𝑛. 𝑠𝑜𝑟𝑡_𝑣𝑎𝑙𝑢𝑒𝑠(𝑎𝑠𝑐𝑒𝑛𝑑𝑖𝑛𝑔 = 𝑇𝑟𝑢𝑒)
 Columna índice. En este caso usamos sort_index(), cuyo uso y argumento es similar al
explicado anteriormente. Por ejemplo:
𝑑𝑓 = 𝑑𝑓. 𝑠𝑜𝑟𝑡_𝑖𝑛𝑑𝑒𝑥(𝑎𝑠𝑐𝑒𝑛𝑑𝑖𝑛𝑔 = 𝐹𝑎𝑙𝑠𝑒)

Filtro de columnas
Veamos ahora cómo filtrar una columna, es decir, veamos cómo trabajar con cada columna por
separado. Para esto usaremos como ejemplo el archivo levantado desde internet. Veamos una pre-
visualización del mismo:

Imaginemos que deseamos trabajar primero la columna de precios de cierre. La forma más común
de trabajarla es la siguiente:
𝑑𝑎𝑡𝑎[′ 𝐶𝑙𝑜𝑠𝑒 ′ ]
Otro modo es escribiendo el llamado como si la columna fuese un atributo del DataFrame, de
hecho, todas las columnas de los DataFrame son atributos de la misma. Veamos:
𝑑𝑎𝑡𝑎. 𝐶𝑙𝑜𝑠𝑒
Si deseamos filtrar más de una columna a la vez utilizamos corchetes dobles, es decir, escribimos el
siguiente código:
𝑑𝑎𝑡𝑎[[′ 𝑂𝑝𝑒𝑛 ′ , ′𝐶𝑙𝑜𝑠𝑒′]]

Ubicando índice con valores de columna


Imaginemos que hemos encontrado el precio máximo de cierre ajustado y, lo que deseamos ahora
es conocer la fecha donde dicho valor sucedió. Entre la multitud de formas que esto puede lograrse,
aquí veremos dos que nos permitirán conocer dicha fecha sin necesidad de realizar un gráfico, ni
tampoco aplicar el método describe() junto con variables día, mes, y año. Veamos cómo resolver
esto suponiendo que partimos de un DataFrame llamado ‘excel’:
 Método idmax(). Utilizamos el método idmax(), quien busca el índice correspondiente al
valor máximo. Para aplicar este método debemos señalar la columna, sí o sí. Veamos:
𝑝𝑟𝑖𝑛𝑡(𝑒𝑥𝑐𝑒𝑙. 𝑐𝑙𝑜𝑠𝑒. 𝑖𝑑𝑚𝑎𝑥())
 Método eq() + index. Utilizamos el método eq() y el atributo index. El argumento del
método será el valor buscado, y a continuación escribiremos el atributo mencionado:
𝑝𝑟𝑖𝑛𝑡(𝑒𝑥𝑐𝑒𝑙[𝑒𝑥𝑐𝑒𝑙. 𝑐𝑙𝑜𝑠𝑒. 𝑒𝑞(𝑒𝑥𝑐𝑒𝑙. 𝑐𝑙𝑜𝑠𝑒. 𝑚𝑎𝑥())]. 𝑖𝑛𝑑𝑒𝑥)
El método eq() permite evaluar en términos booleanos los valores de todo el Dataframe a
partir del valor introducido como argumento, en otras palabras, si allí colocamos el valor 2,
todas las celdas diferentes a 2 serán false, y las iguales serán true. Ahora, al combinarlo
con index, logramos ubicar lo que buscamos.

Slicing de filas y columnas (iloc)


Por otro lado, podemos realizar un slicing de DataFrame. Al hacerlo obtendremos otro
DataFrame y, para hacerlo, sólo debemos proceder del mismo modo que lo hecho sobre otras
colecciones, sólo que en este caso se aplicará sobre todas las columnas del DataFrame. Por
ejemplo, si escribimos:

𝑑𝑎𝑡𝑎[: 10: 2]
Estaremos pidiendo los 8 primeros valores de ubicación par (salto de dos en dos) del DataFrame
para cada una de las columnas, en otras palabras, obtendremos lo siguiente:
Esta lógica también puede aplicarse con las columnas de forma individual, veamos:
𝑑𝑎𝑡𝑎[′ 𝐶𝑙𝑜𝑠𝑒 ′ ][: 10: 2]

Del mismo modo, todo esto también puede lograrse utilizando la función iloc[] (locación por
índice, pues son las siglas de “index location”) quien cumple la misma función que el slicing. Esta
ya se describió en una de las primeras secciones donde se presentó por primera vez el DataFrame.
No obstante, aquí lo volvemos a presentar para refrescarlo y, para mostrar que a diferencia del
Slicing, esta función también permite trabajar las columnas, incluso en simultáneo a las filas.
Veamos cómo hacer esto con el siguiente código:
𝑑𝑎𝑡𝑎. 𝑖𝑙𝑜𝑐[: 10: 2, ∶ 2]
Con esto pedimos en filas lo mismo de antes, pero ahora lo pedimos sólo para las dos primeras
columnas, indicándolo con un “: 2” separado con una coma (la lógica es como la utilizada en
notación matricial, primero filas, luego columnas). En este sentido, la lógica del slicing de
columnas es la misma que para las filas. El resultado es el siguiente:

En línea con esto, si traemos sólo una línea del DataFrame, en lugar de obtener otro DataFrame
como mencionamos al inicio, lo que obtendremos será un objeto tipo “Data Series”. Veamos:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑖𝑙𝑜𝑐[0])
𝑝𝑟𝑖𝑛𝑡(𝑡𝑦𝑝𝑒(𝑑𝑎𝑡𝑎. 𝑖𝑙𝑜𝑐[0]))
Siguiendo en esta línea, si deseamos un dato en particular, podemos obtenerlo de dos modos: 1)
indicando la fila y luego la columna; o 2) al revés, indicando la columna y luego la fila. Veamos:
𝑑𝑎𝑡𝑎. 𝑖𝑙𝑜𝑐[0]. 𝑉𝑜𝑙𝑢𝑚𝑒
𝑑𝑎𝑡𝑎[′ 𝑉𝑜𝑙𝑢𝑚𝑒 ′ ]. 𝑖𝑙𝑜𝑐[0]
Con cualquiera de las líneas obtenemos 1260000.0.

Función between()
Esta función evalúa los valores del DataFrame como falsos o verdaderos al compararlos con los
argumentos que tiene en ella. De este modo, la función puede ser utilizada como ubicación al ser
combinada con otras funciones, por ejemplo, con el slicing, y de este modo, se puede obtener un
DataFrame que sea un recorte de otro.
Veamos cómo aplicar esto asumiendo que contamos con un DataFrame llamado ‘excel’ que puede
trabajarse sin problemas. Lo que queremos es recortarlo, para obtener otro que sólo contenga datos
de los años 2007 a 2009 (ambos inclusive). Sabemos, además, que existe una variable llamada
‘year’, cuyos valores son los años que se corresponden con el índice. Veamos cómo utilizar esta
función:
𝑝𝑟𝑖𝑛𝑡(𝑒𝑥𝑐𝑒𝑙[𝑒𝑥𝑐𝑒𝑙[year]. 𝑏𝑒𝑡𝑤𝑒𝑒𝑛(2007, 2009)])
Vea que, para lograr lo indicado se realizó el slice, luego, dentro del mismo se colocó la ubicación
seleccionando la columna y, al final, utilizando la función between().

Columna nueva a partir del índice


Imaginemos que tenemos un DataFrame donde el índice son fechas, y deseamos crear cuatro
nuevas columnas, una cuyos valores sean el año, otra el mes, otra el día, y otra el nombre del día.
Para poder hacerlo hacemos uso de la función index y su atributo year, month, day, day_name().
Veamos cómo hacer esto utilizando el archivo ‘ko_excel.xlsx’, asumiendo que ya lo hemos
ordenado como corresponde para poder trabajarlo como este ejercicio lo requiere. La línea de
código es la siguiente:
𝑒𝑥𝑐𝑒𝑙["𝑑𝑖𝑎"] = 𝑒𝑥𝑐𝑒𝑙. 𝑖𝑛𝑑𝑒𝑥. 𝑑𝑎𝑦
𝑒𝑥𝑐𝑒𝑙["𝑚𝑒𝑠"] = 𝑒𝑥𝑐𝑒𝑙. 𝑖𝑛𝑑𝑒𝑥. 𝑚𝑜𝑛𝑡ℎ
𝑒𝑥𝑐𝑒𝑙["𝑦𝑒𝑎𝑟"] = 𝑒𝑥𝑐𝑒𝑙. 𝑖𝑛𝑑𝑒𝑥. 𝑦𝑒𝑎𝑟
𝑒𝑥𝑐𝑒𝑙["𝑑𝑎𝑦_𝑛𝑎𝑚𝑒"] = 𝑒𝑥𝑐𝑒𝑙. 𝑖𝑛𝑑𝑒𝑥. 𝑑𝑎𝑦_𝑛𝑎𝑚𝑒()
𝑝𝑟𝑖𝑛𝑡(𝑒𝑥𝑐𝑒𝑙. 𝑖𝑙𝑜𝑐[: , −4: ])

Columna nueva a partir de otra pre-existente


Ahora trabajemos con el DataFrame importado desde la máquina. En este vemos que la columna
de fechas no es el índice, además, las fechas en lugar de tener formato fecha tienen un formato
string. Veamos nuevamente esto:

Entonces, podemos crear una nueva columna a partir de la columna “timestamp”, cuyos valores
sean efectivamente tipo fecha. El cómo crear columnas nuevas sigue la misma lógica que la
explicada para diccionarios. Veamos el siguiente código para lograr el cometido:
𝑑𝑎𝑡𝑎[′ 𝐹𝑒𝑐ℎ𝑎_𝑝𝑜𝑠𝑡𝑎′] = 𝑝𝑑. 𝑡𝑜_𝑑𝑎𝑡𝑒𝑡𝑖𝑚𝑒(𝑑𝑎𝑡𝑎. 𝑡𝑖𝑚𝑒𝑠𝑡𝑎𝑚𝑝, 𝑓𝑜𝑟𝑚𝑎𝑡 =′ %𝑑/%𝑚/%𝑌′)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)
Vea que el primer argumento de la función to_datetime() es la columna a modificar, y el segundo
es el formato del string a transformar en fecha (formato Timestamp). Como se indicó en el párrafo
anterior, con esta función estamos creando los valores de la nueva columna, y asignándosela a su
nombre data[‘Fecha_posta’]. Veamos, el resultado en cuanto el tipo de dato:
𝑝𝑟𝑖𝑛𝑡(𝑡𝑦𝑝𝑒(𝑑𝑎𝑡𝑎[′𝐹𝑒𝑐ℎ𝑎_𝑝𝑜𝑠𝑡𝑎′]. 𝑖𝑙𝑜𝑐[0]))

Extrayendo atributos de la columna creada


Continuando con el último ejemplo, de dicha columna podemos extraer sólo los años, los meses, e
incluso los días. Para lograr esto debemos escribir, respectivamente, algunas de las siguientes líneas
de código:

𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝐹𝑒𝑐ℎ𝑎_𝑝𝑜𝑠𝑡𝑎. 𝑑𝑡. 𝑦𝑒𝑎𝑟)


𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝐹𝑒𝑐ℎ𝑎_𝑝𝑜𝑠𝑡𝑎. 𝑑𝑡. 𝑚𝑜𝑛𝑡ℎ)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝐹𝑒𝑐ℎ𝑎_𝑝𝑜𝑠𝑡𝑎. 𝑑𝑡. 𝑑𝑎𝑦)
Aquí es importante notar la secuencia del código. Primero llamamos por el nombre al DataFrame,
luego a su columna, luego indicamos que deseamos que esa columna sea trabajada como un
datetime o sea, dt, y finalmente elegimos si deseamos el año, mes, o día. El ejemplo del año tiene
por resultado lo siguiente:
Estos atributos pueden asignarse a una variable nueva, por ejemplo, “año”, para luego crear una
nueva columna con este nombre. Veamos:
𝑎ñ𝑜 = 𝑑𝑎𝑡𝑎. 𝐹𝑒𝑐ℎ𝑎_𝑝𝑜𝑠𝑡𝑎. 𝑑𝑡. 𝑦𝑒𝑎𝑟
𝑑𝑎𝑡𝑎[′ 𝐴ñ𝑜 ′ ] = 𝑎ñ𝑜
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)

¿Cómo fue posible realizar esto? Sucede que al crear la variable “año”, la misma se vincula
directamente con los índices del DataFrame “data”, por ende, al introducirla como una nueva
columna, sus valores se ordenan para que los índices de cada una se correspondan. En este sentido,
la variable “año” tiene un índice “oculto” o implícito, por ello puede posteriormente ordenarse en el
DataFrame. Finalmente, la referencia de orden es el mismo DataFrame llamado “data”, en otras
palabras, “año” se ordena para adaptarse a él. Esto es evidente hacer lo siguiente:
𝑝𝑟𝑖𝑛𝑡(𝑎ñ𝑜)
La primera columna no es una columna en sí misma, son los índices ocultos. Entonces, estos
índices también están presentes en “data”, y por ello es posible ordenar “año” adaptándolo a “data”.

Comentario sobre el índice


Como se vio en el ejemplo anterior, el índice es clave para ordenar los valores de cada una de las
columnas. El valor que toma el índice puede ser un int como en el ejemplo de la sección anterior, o
también un float, e incluso pueden ser fechas. Lo importante es que cada uno de sus valores sea
diferente al resto, único.

Diferencia entre LOC e ILOC


La función loc[] busca nombres o etiquetas, mientras que iloc[], busca índices o ubicaciones. Para
ejemplificar sus usos, trabajemos con la siguiente lista, que inmediatamente convertiremos en un
DataFrame:
𝑎𝑐𝑐𝑖𝑜𝑛𝑒𝑠 = [{′𝑇𝑖𝑐𝑘𝑒𝑟′: ′𝐺𝐺𝐴𝐿′, ′𝑁𝑜𝑚𝑏𝑟𝑒′: ′𝐵𝑎𝑛𝑐𝑜 𝐺𝑎𝑙𝑖𝑐𝑖𝑎′, ′𝑃𝑎𝑖𝑠′: ′𝐴𝑟𝑔𝑒𝑛𝑡𝑖𝑛𝑎′, ′𝑅𝑢𝑏𝑟𝑜′: ′𝐵𝑎𝑛𝑐𝑜𝑠′},
{′𝑇𝑖𝑐𝑘𝑒𝑟′: ′𝑃𝐴𝑀𝑃′, ′𝑁𝑜𝑚𝑏𝑟𝑒′: ′𝑃𝑎𝑚𝑝𝑎 𝐸𝑛𝑒𝑟𝑔í𝑎′, ′𝑃𝑎𝑖𝑠′: ′𝐴𝑟𝑔𝑒𝑛𝑡𝑖𝑛𝑎′, ′𝑅𝑢𝑏𝑟𝑜′: ′𝐸𝑛𝑒𝑟𝑔é𝑡𝑖𝑐𝑎𝑠′},
{′𝑇𝑖𝑐𝑘𝑒𝑟′: ′𝑀𝑆𝐹𝑇′, ′𝑁𝑜𝑚𝑏𝑟𝑒′: ′𝑀𝑖𝑐𝑟𝑜𝑠𝑜𝑓𝑡′, ′𝑃𝑎𝑖𝑠′: ′𝑈𝑆𝐴′, ′𝑅𝑢𝑏𝑟𝑜′: ′𝑇𝑒𝑐𝑛𝑜𝑙ó𝑔𝑖𝑐𝑎𝑠′},
{′𝑇𝑖𝑐𝑘𝑒𝑟′: ′𝐵𝐴𝐵𝐴′, ′𝑁𝑜𝑚𝑏𝑟𝑒′: ′𝐴𝑙𝑖𝑏𝑎𝑏𝑎′, ′𝑃𝑎𝑖𝑠′: ′𝐶ℎ𝑖𝑛𝑎′, ′𝑅𝑢𝑏𝑟𝑜′: ′𝐶𝑜𝑛𝑠𝑢𝑚𝑜′}]
𝑎𝑐𝑐𝑖𝑜𝑛𝑒𝑠 = 𝑝𝑑. 𝐷𝑎𝑡𝑎𝐹𝑟𝑎𝑚𝑒(𝑑𝑎𝑡𝑎 = 𝑎𝑐𝑐𝑖𝑜𝑛𝑒𝑠). 𝑠𝑒𝑡_𝑖𝑛𝑑𝑒𝑥(′𝑇𝑖𝑐𝑘𝑒𝑟′)
𝑝𝑟𝑖𝑛𝑡(𝑎𝑐𝑐𝑖𝑜𝑛𝑒𝑠)
Ahora imaginemos que de este DataFrame queremos las dos primeras filas. Esto se puede lograr
tanto con iloc[] como con loc[]. Veamos las líneas de código correspondientes junto a sus
resultados:
𝑎𝑐𝑐𝑖𝑜𝑛𝑒𝑠. 𝑖𝑙𝑜𝑐[: 2]

𝑎𝑐𝑐𝑖𝑜𝑛𝑒𝑠. 𝑙𝑜𝑐[′ 𝐺𝐺𝐴𝐿′ : ′𝑃𝐴𝑀𝑃′]

Obsérvese que los argumentos de ambas funciones trabajan con la misma lógica, “desde : hasta”,
sin embargo, como la primera maneja ubicaciones, también permite saltos. Por otro lado, se
observa claramente cómo mientras iloc[] busca ubicación (índice), loc[] busca el nombre en el
índice. Finalmente, en el caso de loc[] el “desde: hasta” es inclusivo en ambos casos (como también
se aprecia al contrastar el resultado con su línea de código).
Una particularidad de la función loc[] es que permite obtener específicamente una u otra columna
sin necesidad de establecer el rango “desde:hasta”. Para esto, el código es:
𝑎𝑐𝑐𝑖𝑜𝑛𝑒𝑠. 𝑙𝑜𝑐[[′𝑃𝐴𝑀𝑃′ , ′𝐵𝐴𝐵𝐴′]]

Estas funciones trabajan de este modo en el caso de filas y columnas. El ejemplo de columnas se
omite por considerarse redundante.
Todas estas posibilidades de cortar el DataFrame permiten, en combinación con lo visto en las
secciones anteriores, construir nuevos DataFrame a partir de sólo uno o varios, como cuando se
trabaja con Excel y se crean nuevas variables a partir de otras.
A pesar de todo esto, la diferencia más importante entre estas funciones son sus argumentos, iloc[]
lee ubicación, mientras loc[] label o etiqueta. En este sentido, la columna de índice se lee de ambos
modos, por ende, si modificamos el orden del DataFrame, para hacer un slicing usando loc[]
deberemos prestar atención al índice que acompaña a cada valor, mientras que si usamos iloc[] no
deberemos cuidarnos de esto.
Para comprender mejor lo mencionado en el párrafo anterior basta con ver que la siguiente
DataFrame está “desordenada”. Veamos esto comenzando por el DataFrame original:
Vemos que las fechas están desordenadas, pues se comienza desde el 6 de marzo del 2020 y se
termina el 6 de marzo del 2000, y debería ser al revés. Para resolver esto aplicamos la siguiente
línea de código:
𝑑𝑎𝑡𝑎[∷ −1]
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)

Vemos que hemos resuelto este “desorden”, pero las etiquetas de la columna de índice no
cambiaron redefiniéndose, sino que se reordenaron junto al resto de la tabla. Ahora usemos las
funciones en cuestión:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑖𝑙𝑜𝑐[: 4])

𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[: 4])


Es importante observar que si queremos sólo las primeras cuatro filas, con iloc[] deberemos
escribir el código como estamos acostumbrados, mientras que, con loc[] deberemos prestar
atención a cuáles son los índices (sus etiquetas), pues si escribimos lo mismo que con la otra
función, nos equivocaremos.

Leyendo un archivo CSV/TXT


Hay veces donde los archivos tienen un formato txt o CSV, donde cada valor y columna y fila están
separados por comas y puntos. Para poder observar estas características hacemos uso del comando
!type seguido del nombre del archivo. Por ejemplo:
! type 𝑆𝑃𝑌. 𝑐𝑠𝑣

Vemos que cada dato está separado del siguiente por puntos y comas. Este tipo de separación
implica un problema al momento de querer pasar los datos a un DataFrame, veamos:
𝑑𝑎𝑡𝑎 = 𝑝𝑑. 𝑟𝑒𝑎𝑑_𝑐𝑠𝑣(′𝑆𝑃𝑌. 𝑐𝑠𝑣 ′ )
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)
Vemos que los datos no están ordenados según columnas, sólo por filas. Este es el problema que
acarrean los puntos y comas. Para resolverlo utilizamos el argumento de la función que utilizamos
recién, indicando cuál es el separador:
𝑑𝑎𝑡𝑎 = 𝑝𝑑. 𝑟𝑒𝑎𝑑_𝑐𝑠𝑣(′𝑆𝑃𝑌. 𝑐𝑠𝑣 ′ , 𝑠𝑒𝑝 =′ ; ′)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)

Leyendo un HTML
Recordemos que siempre que importamos o leemos un archivo, debemos hacerlo utilizando la
librería pandas, en otras palabras, debemos importarla. Asimismo, en este caso usaremos la función
read_html() cuyo argumento es la página web desde donde descargamos el DataFrame.
Comencemos con el siguiente código:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑝𝑎𝑛𝑑𝑎𝑠 𝑎𝑠 𝑝𝑑
𝑑𝑎𝑡𝑎 = 𝑝𝑑. 𝑟𝑒𝑎𝑑_ℎ𝑡𝑚𝑙(′https://en. wikipedia. org/wiki/List_of_S%26P_500_companies′)[0]
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)
Ahora, para conseguir lo que queremos, los tickers, usaremos el código del profe, que no explica
cómo se escribe, así que aquí directamente lo transcribimos:

Ordenando una DataFrame


Partiendo del DataFrame anterior, pasamos a ordenarlo. El objetivo es que timestamp sea el
índice, que las fechas en lugar de ser string sean datetime, y que éstas estén ordenadas desde la más
antigua hasta la más actual (al revés de su orden actual). Para conseguir esto escribimos los
siguientes códigos:
𝑑𝑎𝑡𝑎[′ 𝑡𝑖𝑚𝑒𝑠𝑡𝑎𝑚𝑝′ ] = 𝑝𝑑. 𝑡𝑜_𝑑𝑎𝑡𝑒𝑡𝑖𝑚𝑒(𝑑𝑎𝑡𝑎[′ 𝑡𝑖𝑚𝑒𝑠𝑡𝑎𝑚𝑝′ ], 𝑓𝑜𝑟𝑚𝑎𝑡 = ′%𝑑/%𝑚/%𝑌′]
𝑑𝑎𝑡𝑎 = 𝑑𝑎𝑡𝑎. 𝑠𝑒𝑡_𝑖𝑛𝑑𝑒𝑥(′𝑡𝑖𝑚𝑒𝑠𝑡𝑎𝑚𝑝′ )
𝑑𝑎𝑡𝑎. 𝑠𝑜𝑟𝑡_𝑖𝑛𝑑𝑒𝑥(𝑖𝑛𝑝𝑙𝑎𝑐𝑒 = 𝑇𝑟𝑢𝑒)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)
Con la primera línea cambiamos el formato de las fechas, pasándolas de string a datetime. Con la
segunda línea convertimos la columna “timestamp” en la columna índice. Finalmente, la tercera
línea ordena todo desde la fecha más antigua hasta la actual.

NOTA. Ordenar el índice.


Vimos que se puede ordenar del siguiente modo:
𝑑𝑎𝑡𝑎[∷ −1]
Pero también se lo puede hacer como se vio hace un momento:
𝑑𝑎𝑡𝑎. 𝑠𝑜𝑟𝑡_𝑖𝑛𝑑𝑒𝑥(𝑖𝑛𝑝𝑙𝑎𝑐𝑒 = 𝑇𝑟𝑢𝑒)
Finalmente, la siguiente línea de código es similar a la inmediatamente anterior, sólo que en este
caso el resultado debe asignarse, mientras que en el otro caso se da por sentado esta asignación:
𝑑𝑎𝑡𝑎 = 𝑑𝑎𝑡𝑎. 𝑠𝑜𝑟𝑡_𝑖𝑛𝑑𝑒𝑥()

Importar un archivo XLS


En este caso la estructura del código es similar a la utilizada para importar archivos CSV. Para
ejemplificar esto veamos el siguiente código, con el cual se importa un archivo xls con los precios
de AAPL. Veamos:
! 𝑑𝑖𝑟 ∗. 𝑥𝑙𝑠
𝑑𝑎𝑡𝑎 = 𝑝𝑑. 𝑟𝑒𝑎𝑑_𝑒𝑥𝑐𝑒𝑙(′𝐴𝐴𝑃𝐿. 𝑥𝑙𝑠𝑥′)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)
Una peculiaridad de trabajar importando archivos XLS versus la importación de archivos CSV, es
que los primeros reconocen instantáneamente el formato fecha, mientras que los segundos no
(como se vio).
Los parámetros más utilizados con la función read_excel() son los siguientes:
o .sheet_name: Indica la hoja deseada.
o .index_col: Permite tomar la columna que se desea como índice.
o .usecols: Devuelve las columnas deseadas. Se coloca su nombre o, en caso de que no lo
tengan, su ubicación.
o .names: Permite colocar nombre a las columnas que no lo tienen. Cuando hay más de una
columna, se escribe como una lista.
o .sikprows: Permite indicar qué filas del comienzo se ignorarán.
o .skipfooter: Permite indicar qué filas del final se ignorarán.
o .nrows: Se indica las filas que se desea traer.
o .thousands: Permite que, en caso de haber números con separadores de miles.

Como comentario: En general, al importar archivos se suelen importar formatos CSV, pues estos
son texto compatible con cualquier sistema operativo, incluso MAC, mientras que el formato XLS
lo es sólo con Windows. Asimismo, XLS sólo cuenta con un máximo de 1.048.575 de filas,
mientras que un formato CSV no tiene límite.

Accediendo a una Sub-matriz


Continuando con el archivo AAPL.xlsx, ahora veremos cómo acceder a fracciones del mismo
utilizando las funciones iloc[] y loc[]. Para esto veamos cómo obtener los últimos 10 valores de las
columnas de precios de cierre y sus volúmenes ¿Cómo lo logramos? Veamos:
 Utilizando sólo iloc[]. Veamos:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑖𝑙𝑜𝑐[−10: , −2: ])

 Utilizando sólo loc[]. Veamos, se omite el resultado por ser el mismo:


𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[′2020 − 02 − 24′: , ′𝑎𝑑𝑗𝑢𝑠𝑡𝑒𝑑_𝑐𝑙𝑜𝑠𝑒′: ])
 Utilizando ambas funciones. Veamos, se omite el resultado por ser el mismo:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑖𝑙𝑜𝑐[−10: ]. 𝑙𝑜𝑐[: , ′𝑎𝑑𝑗𝑢𝑠𝑡𝑒𝑑_𝑐𝑙𝑜𝑠𝑒′: ])

Accediendo a una Sub-matriz con filtros condicionales


Continuamos trabajando con el archivo AAPL.xlsx y con la función loc[]. Aunque continuamos
utilizando un “desde – hasta”, ahora, en lugar de indicar posiciones o etiquetas, utilizaremos
condicionales, quienes requieren del uso de operadores lógicos. Veamos cómo trabajar con esto
utilizando ejemplos. Supongamos que deseamos todos los datos del año 2005 y del mes de marzo:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[(𝑑𝑎𝑡𝑎. 𝑖𝑛𝑑𝑒𝑥. 𝑦𝑒𝑎𝑟 == 2005) & (𝑑𝑎𝑡𝑎. 𝑖𝑛𝑑𝑒𝑥. 𝑚𝑜𝑛𝑡ℎ == 3)])
Configuración de salida al importar (print)
Lo siguiente permite modificar lo que el título indica, pues en algunas oportunidades y por alguna
razón nos es conveniente modificar su visualización de salida. Para lograr primero debemos
conocer los valores por defecto, y lograr implica utilizar el comando siguiente:
𝑝𝑑. 𝑜𝑝𝑡𝑖𝑜𝑛𝑠. 𝑑𝑖𝑠𝑝𝑙𝑎𝑦. __𝑑𝑖𝑐𝑡__

Aquí vemos que, por ejemplo, si el DataFrame tiene más de 60 filas, entonces automáticamente lo
corta en la quinta para mostrar los tres puntos y luego mostrar las últimas cinco filas. Así ocurre
con el resto de los valores por defecto.
Para cambiar estos valores por defecto usamos todo lo anterior como atributos de display. Veamos
un ejemplo donde el máximo de 60 filas lo reducimos hasta 8, y el de columnas lo reducimos de 20
a 10. Veamos cómo escribirlo:
𝑝𝑑. 𝑜𝑝𝑡𝑖𝑜𝑛𝑠. 𝑑𝑖𝑠𝑝𝑙𝑎𝑦. max _𝑟𝑜𝑤𝑠 = 8
𝑝𝑑. 𝑜𝑝𝑡𝑖𝑜𝑛𝑠. 𝑑𝑖𝑠𝑝𝑙𝑎𝑦. max _𝑐𝑜𝑙𝑢𝑚𝑛𝑠 = 10
Luego de esto escribimos el código de importación nuevamente. NOTA: por alguna razón, no
podemos hacer esto en nuestro spider, sin embargo, el profe sí tuvo éxito al aplicarlo, en este
sentido se muestra una foto de su código a continuación.

Finalmente, el argumento “precision” permite indicar la cantidad de decimales en el float. Cuando


hacemos esto, estamos sólo “recortando” estéticamente los decimales, pues como en Excel, aunque
no se vean todos, ellos están. En cambio, su usamos la función round(), el cambio será estético y
de fondo, es decir, se mostrará la cantidad señalada de decimales y sólo serán esos los que están.
Para aplicar esta función escribimos lo siguiente:
𝑑𝑎𝑡𝑎. 𝑟𝑜𝑢𝑛𝑑(2)
Tratamiento para valores perdidos “Nan” (sin información)
Muchas veces nos encontraremos con DataFrame que tiene algún dato perdido, en estos casos, la
librería “pandas” los lee como “Nan”. A estos valores podemos eliminarlos, lo cual implica
eliminar toda la fila o toda la columna, pero también pueden reemplazarse por otro valor, por
ejemplo, por un cero. A continuación veremos ambos casos.
Comencemos viendo cómo eliminarlos. En estos casos, uno de sus tratamientos usuales consiste en
la eliminación de la fila que lo contiene. Para hacerlo se utiliza el método dropna(), veamos cómo
lo utilizamos a través de un ejemplo con el archivo AAPL.xlsx. De este archivo creemos una nueva
columna con las variaciones diarias, donde veremos que la primera fila tiene lógicamente un dato
“Nan”, luego veamos qué ocurre al aplicar el método mencionado.
! 𝑑𝑖𝑟 ∗. 𝑥𝑙𝑠𝑥
𝑑𝑎𝑡𝑎 = 𝑝𝑑. 𝑟𝑒𝑎𝑑_𝑒𝑥𝑐𝑒𝑙(′𝐴𝐴𝑃𝐿. 𝑥𝑙𝑠𝑥′, 𝑖𝑛𝑑𝑒𝑥_𝑐𝑜𝑙 = (′𝑡𝑖𝑚𝑒𝑠𝑡𝑎𝑚𝑝′)). 𝑠𝑜𝑟𝑡_𝑖𝑛𝑑𝑒𝑥()
𝑑𝑎𝑡𝑎[′𝐶𝑎𝑚𝑏𝑖𝑜 𝑑𝑖𝑎𝑟𝑖𝑜′] = 𝑑𝑎𝑡𝑎. 𝑎𝑑𝑗𝑢𝑠𝑡𝑒𝑑_𝑐𝑙𝑜𝑠𝑒. 𝑝𝑐𝑡_𝑐ℎ𝑎𝑛𝑔𝑒()

Para eliminar la fila con el dato nulo (Nan) usamos el método mencionado, quien por defecto ya
elimina columnas con este tipo de datos:
𝑑𝑎𝑡𝑎 = 𝑑𝑎𝑡𝑎. 𝑑𝑟𝑜𝑝𝑛𝑎()
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)
Si deseamos que en lugar de filas se eliminen las columnas que tengan estos datos, entonces
debemos modificar el argumento del método, veamos:
𝑑𝑎𝑡𝑎 = 𝑑𝑎𝑡𝑎. 𝑑𝑟𝑜𝑝𝑛𝑎(𝑎𝑥𝑖𝑠 = 1)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)

Vemos que se ha eliminado la última columna, donde estaba el valor “Nan”.


Por otro lado, si deseamos reemplazarlos, por ejemplo por un cero, debemos usar el método
fillna(), cuyos argumento es el valor que reemplaza y en este caso es 0. Veamos:
𝑑𝑎𝑡𝑎 = 𝑑𝑎𝑡𝑎. 𝑓𝑖𝑙𝑙𝑛𝑎(0)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)
Reseteo del índice
Ya hemos visto cómo asignar una columna como índice, ahora veamos cómo dar un paso hacia
atrás con dicha transformación. Para esto utilizaremos la siguiente función:
𝑑𝑎𝑡𝑎. 𝑟𝑒𝑠𝑒𝑡_𝑖𝑛𝑑𝑒𝑥(𝑖𝑛𝑝𝑙𝑎𝑐𝑒 = 𝑇𝑟𝑢𝑒)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)

Esto puede ser útil para cuando tomamos una sub matriz del DataFrame y deseamos que el índice
comience de 0 nuevamente.

Operaciones matriciales
Cuando deseamos modificar alguna columna o fila, si bien es posible utilizar un bucle for, lo mejor
es utilizar las propiedades matriciales o una combinación. Veremos tres modos de hacerlo (aunque
hay más), el primero, pero no el más eficiente es multiplicar la matriz o vector por un escalar y
luego sumar. El segundo consiste en combinar el bucle for con las propiedades matriciales. Y el
tercero es aplicar las propiedades matriciales relacionadas al producto entre matrices.
Veamos el primero, ya mencionado en alguna sección anterior, supongamos que estamos
trabajando con el archivo AAPL.xlsx y deseamos obtener el precio promedio ponderado entre
“open” y “close”, otorgando un 30% y un 70% de peso respectivamente. Veamos:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑜𝑝𝑒𝑛 ∗ 0.3 + 𝑑𝑎𝑡𝑎. 𝑐𝑙𝑜𝑠𝑒 ∗ 0.7)

Ahora apliquemos lo mismo, pero como una combinación de bucle for y lo anterior (se pasa foto de
lo que hizo el profe porque ahora estoy cansado como para pasarlo a mano). Como se verá, “p” es
un vector lista que tiene las ponderaciones.

Un detalle sobre este código, px comienza siendo un int, pero termine siendo una serie. Esto ocurre
en el bucle for, donde le asignamos el resultado de multiplicar el vector columna por el escalar. Es
gracias a esto que hacia el final del código podemos asignar px al DataFrame.
Finalmente, la multiplicación entre matrices puede hacerse utilizando la librería pandas o, la
librería numpy. Como veremos ambos modos, descargaremos la librería que falta:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑛𝑢𝑚𝑝𝑦 𝑎𝑠 𝑛𝑝
Lo siguiente es utilizar el método dot() que sirve para productos matriciales. En el caso de la
librería numpy, los argumentos de este método son las matrices o vectores que se multiplicarán, y
claro, ambos deben respetar las propiedades del producto matricial, esto es, que las columnas de la
matriz de la izquierda del producto coincidan en cantidad con las filas de la matriz de la derecha.
En este sentido, si uno de los argumentos es una lista, el programa la “acomodará” como
corresponda para poder calcular el producto, esto es, una lista puede pensarse como un vector fila o
columna, lo que hace anaconda es interpretarla de uno u otro modo de acuerdo a lo que permita
realizar el cálculo sin errores.
Lo que vemos a continuación es la aplicación de este método de dos modos diferentes, pero con el
mismo resultado. La aplicación se hace sobre el archivo AAPL.xlsx. Veamos:
𝑝 = [0.2,0.15,0.15,0.5]
𝑑𝑎𝑡𝑎[′𝑃𝑅𝐸𝐶𝐼𝑂_𝑂𝐻𝐿𝐶′] = 𝑛𝑝. 𝑑𝑜𝑡(𝑑𝑎𝑡𝑎. 𝑖𝑙𝑜𝑐[: , : 4], 𝑝)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)

El segundo modo de resolver esto es con la librería pandas, quien también utiliza el método dot(),
pero en este caso, tiene un único argumento, que será la matriz o vector que está a la derecha del
producto. El resultado obtenido es el mismo, veamos el código:
𝑝 = [0.2,0.15,0.15,0.5]
𝑑𝑎𝑡𝑎[′𝑃𝑅𝐸𝐶𝐼𝑂_𝑂𝐻𝐿𝐶′] = 𝑑𝑎𝑡𝑎. 𝑖𝑙𝑜𝑐[: , : 4]. 𝑑𝑜𝑡(𝑝)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)

Mascaras y función where() de numpy


Imaginemos que trabajando sobre el archivo AAPL.xlsx deseamos crear una columna que indique
“Verde” para los casos donde el precio close es mayor al open, y “Rojo” para el caso contrario, esto
lo podemos completar utilizando una combinación entre un condicional If else y un bucle for, o
utilizando la función where() de numpy, o también, utilizando máscaras (filtros condicionales).
Asimismo, las máscaras también son útiles para filtrar valores categóricos que interesen en el
análisis.
Veamos cómo resolver esto con el condicional y el bucle:
𝑑𝑎𝑡𝑎 = 𝑝𝑑. 𝑟𝑒𝑎𝑑_𝑒𝑥𝑐𝑒𝑙(′𝐴𝐴𝑃𝐿. 𝑥𝑙𝑠𝑥′)
𝑓𝑜𝑟 𝑖 𝑖𝑛 𝑟𝑎𝑛𝑔𝑒(𝑙𝑒𝑛(𝑑𝑎𝑡𝑎)):
𝑖𝑓 𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[𝑖, ′𝑜𝑝𝑒𝑛′] > 𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[𝑖, ′𝑐𝑙𝑜𝑠𝑒′]:
𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[𝑖, ′𝐶𝑜𝑙𝑜𝑟_𝑉𝑒𝑙𝑎′] = ′𝑅𝑜𝑗𝑜′
𝑒𝑙𝑖𝑓 𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[𝑖, ′𝑜𝑝𝑒𝑛′] < 𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[𝑖, ′𝑐𝑙𝑜𝑠𝑒′]:
𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[𝑖, ′𝐶𝑜𝑙𝑜𝑟_𝑉𝑒𝑙𝑎′] = ′𝑉𝑒𝑟𝑑𝑒′
𝑒𝑙𝑠𝑒:
𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[𝑖, ′𝐶𝑜𝑙𝑜𝑟_𝑉𝑒𝑙𝑎′] = ′𝐷𝑜𝑗𝑖′
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)

Ahora veamos cómo utilizar la función where() de numpy. Para esto debemos trabajar con las
librerías pandas (por el uso del DataFrame), y con la librería numpy (por la función). Los
argumentos de esta función son similares a un condicional if, desde que primero se escribe una
condición, le sigue el resultado a aplicar si ella se cumple, y luego el resultado si no se cumple.
Asimismo, y como el condicional if, también puede concatenarse, volviendo a llamar a la función
where() en lugar del resultado aplicado cuando la condición no se cumple. Lo que haremos será
crear el valor o resultado deseado utilizando esta función, y lo asignaremos a la nueva columna.
Veamos cómo utilizamos esta función para resolver lo propuesto:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑛𝑢𝑚𝑝𝑦 𝑎𝑠 𝑛𝑝
𝑑𝑎𝑡𝑎[′𝐶𝑜𝑙𝑜𝑟_𝑉𝑒𝑙𝑎′] = 𝑛𝑝. 𝑤ℎ𝑒𝑟𝑒(𝑑𝑎𝑡𝑎[′𝑜𝑝𝑒𝑛′]
> 𝑑𝑎𝑡𝑎[′𝑐𝑙𝑜𝑠𝑒′], ′𝑅𝑜𝑗𝑜′, 𝑛𝑝. 𝑤ℎ𝑒𝑟𝑒(𝑑𝑎𝑡𝑎[′𝑜𝑝𝑒𝑛′]
< 𝑑𝑎𝑡𝑎[′𝑐𝑙𝑜𝑠𝑒′], ′𝑉𝑒𝑟𝑑𝑒′, ′𝐷𝑜𝑗𝑖′))
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)
Esto también lo podemos conseguir usando máscaras (filtros condicionales). Estas son útiles como
método de trabajo cuando queremos crear una nueva columna o fila en un DataFrame, y estamos
utilizando variables categóricas. Una máscara es una fracción del DataFrame obtenido a partir de
un operador lógico, y al ser una fracción, es posible utilizarlo para crear nuevas variables y valores.
Entonces, para trabajar con una máscara, primero debemos crearla a partir de un DataFrame, la
función .loc[], y operadores lógicos, y luego la podremos utilizar. Veamos cómo la creamos en este
caso:
𝑑𝑎𝑡𝑎[′𝑜𝑝𝑒𝑛′] > 𝑑𝑎𝑡𝑎[′𝑐𝑙𝑜𝑠𝑒′]
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎[′𝑜𝑝𝑒𝑛′] > 𝑑𝑎𝑡𝑎[′𝑐𝑙𝑜𝑠𝑒′])

Lo que haremos ahora será crear la máscara, quien tendrá la fracción de valores booleanos
verdaderos dentro de esta fracción del DataFrame. Para esto sólo debemos pedir esta fracción
recortada para los valores verdaderos:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[𝑑𝑎𝑡𝑎[′𝑜𝑝𝑒𝑛′] > 𝑑𝑎𝑡𝑎[′𝑐𝑙𝑜𝑠𝑒′]])

Vemos que la cantidad de filas disminuyó, lógicamente, quedando sólo aquellas donde el precio de
apertura es mayor al de cierre. Esto es una máscara.
Finalmente, para colocar “Rojo” en una nueva columna llamada “Color_Vela”, cuando el precio de
apertura es mayor al de cierre, sólo asignamos el string a la columna. En otras palabras, utilizando
la máscara e iloc[] creamos la columna y le asignamos el valor a cada celda de acuerdo al operador
lógico. Veamos:
𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[𝑑𝑎𝑡𝑎[′𝑜𝑝𝑒𝑛′] > 𝑑𝑎𝑡𝑎[′𝑐𝑙𝑜𝑠𝑒′], ′𝐶𝑜𝑙𝑜𝑟_𝑉𝑒𝑙𝑎′] = ′𝑅𝑜𝑗𝑜′
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)

Observe que, 𝑑𝑎𝑡𝑎[′𝑜𝑝𝑒𝑛′] > 𝑑𝑎𝑡𝑎[′𝑐𝑙𝑜𝑠𝑒′] como argumento de la función iloc[] significa que se
busca el número de la ubicación siempre que se cumpla la condición lógica. Por ejemplo, la
primera fila, 0, cumple la condición, por ende, dicha expresión es igual a 0.
En definitiva, para completar el objetivo planteado al inicio, se escribe el siguiente código:
𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[𝑑𝑎𝑡𝑎[′𝑜𝑝𝑒𝑛′] > 𝑑𝑎𝑡𝑎[′𝑐𝑙𝑜𝑠𝑒′], ′𝐶𝑜𝑙𝑜𝑟_𝑉𝑒𝑙𝑎′] = ′𝑅𝑜𝑗𝑜′
𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[𝑑𝑎𝑡𝑎[′ 𝑜𝑝𝑒𝑛 ′ ] < 𝑑𝑎𝑡𝑎[′ 𝑐𝑙𝑜𝑠𝑒 ′ ,′ 𝐶𝑜𝑙𝑜𝑟_𝑉𝑒𝑙𝑎′] =′ 𝑉𝑒𝑟𝑑𝑒′
𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[𝑑𝑎𝑡𝑎[′ 𝑜𝑝𝑒𝑛 ′ ] == 𝑑𝑎𝑡𝑎[′𝑐𝑙𝑜𝑠𝑒′], ′𝐶𝑜𝑙𝑜𝑟_𝑉𝑒𝑙𝑎′] = ′𝐷𝑜𝑗𝑖′
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)

Where() de DataFrame
Esta función es similar al where() de numpy, pero difiere en la relación condición – resultado. En
este caso, sólo hay dos argumentos (no tres), y el primero es la condición a cumplirse, y el segundo
es el resultado que deberá aplicarse en caso donde la condición no se cumpla. Por ende, dicha
condición sirve para mantener el valor que actualmente existe. Veamos el ejemplo dado en clase
por el profesor:

Aquí estamos diciendo que por defecto, el valor es ‘Verde’, y éste se mantendrá siempre que se
cumpla que el precio de cierre sea menor al de apertura, en caso contrario, el valor será ‘Roja’.

Resampleo
Mediante el método resample podemos reagrupar rápidamente una serie dada en función de
diferentes timeframes. Es importante aclarar que para que funcione el resampleo, el índice de la
tabla debe ser el timestamp. Las Frecuencias posibles son:
 B = business day frequency
 D = calendar day frequency
 W = weekly frequency
 M = month end frequency
 BM = business month end frequency
 MS = month start frequency
 BMS = business month start frequency
 Q = quarter end frequency
 BQ = business quarter endfrequency
 QS = quarter start frequency
 BQS = business quarter start frequency
 A = year end frequency
 BA = business year end frequency
 AS = year start frequency
 BAS = business year start frequency
 BH = business hour frequency
 H = hourly frequency
 T = minutely frequency
 S = secondly frequency
 L = milliseonds
Veamos el ejemplo que usó el profesor:
Cómo calcular estadísticas agrupando categorías

Partiendo de la imagen siguiente, la idea es lograr la frecuencia de las velas de acuerdo a cada
categoría, es decir, queremos saber cuántas velas verdes hay, cuántas rojas, y cuántas doji. Para
esto utilizamos la función groupby() cuyo argumento es el nombre de la columna que agrupa las
categorías con quienes agruparemos. Veamos cómo utilizarla:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑔𝑟𝑜𝑢𝑝𝑏𝑦(′𝐶𝑜𝑙𝑜𝑟_𝑉𝑒𝑙𝑎′). 𝑠𝑖𝑧𝑒())

Del mismo modo, si deseamos conocer cuál es el cambio promedio del precio de cierre para cada
grupo de velas, primero creamos las columnas de cambio diario, y luego utilizamos esta función
para obtener lo buscado. Veamos:
𝑑𝑎𝑡𝑎[′𝑣𝑎𝑟𝑖𝑎𝑐𝑖ó𝑛′] = 𝑑𝑎𝑡𝑎. 𝑐𝑙𝑜𝑠𝑒. 𝑝𝑐𝑡_𝑐ℎ𝑎𝑛𝑔𝑒()
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑔𝑟𝑜𝑢𝑝𝑏𝑦(′𝐶𝑜𝑙𝑜𝑟_𝑉𝑒𝑙𝑎′). 𝑣𝑎𝑟𝑖𝑎𝑐𝑖𝑜𝑛. 𝑚𝑒𝑎𝑛())

Veamos que primero agrupamos y después llamamos la columna, para finalmente aplicar la media.
Quantiles y Ranks
En esta sección veremos estadística con DataFrame, por ende, es importante tener en claro las
líneas de código anteriormente descriptas. En particular, aquí nos concentraremos en Rank(), quien
asigna un número a cada índice de acuerdo al orden en el ranking, el cual puede armarse de menor
a mayor o, de mayor a menor. También nos concentraremos en la función Quantile(), quien
permite conocer el valor de todas las variables asociadas a determinado ranking, sólo se debe
colocar el número de orden como argumento.
Trabajaremos con la serie de precios y volumen correspondiente a GGAL, que descargaremos de
Yahoo Finance, por ende, primero prepararemos esta información:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑦𝑓𝑖𝑛𝑎𝑛𝑐𝑒 𝑎𝑠 𝑦𝑓
𝑑𝑎𝑡𝑎 = 𝑦𝑓. 𝑑𝑜𝑤𝑛𝑙𝑜𝑎𝑑(′𝐺𝐺𝐴𝐿′)
𝑑𝑎𝑡𝑎[′𝑉𝑎𝑟𝑖𝑎𝑐𝑖𝑜𝑛′] = 𝑑𝑎𝑡𝑎[′𝐴𝑑𝑗 𝐶𝑙𝑜𝑠𝑒′]. 𝑝𝑐𝑡_𝑐ℎ𝑎𝑛𝑔𝑒() ∗ 100
𝑑𝑎𝑡𝑎 = 𝑑𝑎𝑡𝑎. 𝑑𝑟𝑜𝑝𝑛𝑎()
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)

Rank
Como se mencionó anteriormente, este método rank() asigna un número de orden a cada índice, el
cual señala la ubicación de dicho índice en un ranking construido con referencia a una variable en
particular. Si no escribimos nada en su argumento, entonces se ordena de menor a mayor, siendo el
número 1 el menor de todos. Veamos un ejemplo utilizando los datos de GGAL preparados en la
sección anterior, la intención es crear un ranking a partir de los valores de la variable “Variacion”.
Veamos cómo lo hacemos:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑉𝑎𝑟𝑖𝑎𝑐𝑖𝑜𝑛. 𝑟𝑎𝑛𝑘())
Aquí se puede ver que para cada índice (fecha) hay un número. Este número indica el orden de la
variación asociada al mismo índice. Veamos que esta información puede asignarse a una columna
nueva del DataFrame:
𝑑𝑎𝑡𝑎[′ 𝑟𝑎𝑛𝑘𝑖𝑛𝑔 ′ ] = 𝑑𝑎𝑡𝑎. 𝑉𝑎𝑟𝑖𝑎𝑐𝑖𝑜𝑛. 𝑟𝑎𝑛𝑘()
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)

Si queremos conocer cuáles son las diez variaciones diaria de cierre más bajas, primero re
ordenamos la tabla de acuerdo a la columna “ranking” y luego seleccionamos las 10 primeras filas.
Para esto utilizaremos la función sort_values(), y el método head(). La función tiene como
argumento la variable con quien se ordena, y el método tiene como argumento la cantidad de filas
que queremos ver, contando desde la primera. Veamos:
𝑑𝑎𝑡𝑎2 = 𝑑𝑎𝑡𝑎. 𝑠𝑜𝑟𝑡_𝑣𝑎𝑙𝑢𝑒𝑠(′𝑟𝑎𝑛𝑘𝑖𝑛𝑔′, 𝑎𝑠𝑐𝑒𝑛𝑑𝑖𝑛𝑔 = 𝑇𝑟𝑢𝑒). ℎ𝑒𝑎𝑑(10)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎2)
Esta información es en términos absolutos y por ello no es útil para comparar, pues necesitamos
relativizarlo respecto al tamaño del ranking, por ello utilizamos quantiles, percentiles, etc. En este
caso podemos utilizar percentiles, lo cual requiere dividir el número de orden por el total de filas, o,
utilizar el argumento pct=True de Rank(). Veamos esto reescribiendo el código:
𝑑𝑎𝑡𝑎[′ 𝑟𝑎𝑛𝑘𝑖𝑛𝑔′ ] = 𝑑𝑎𝑡𝑎. 𝑉𝑎𝑟𝑖𝑎𝑐𝑖𝑜𝑛. 𝑟𝑎𝑛𝑘(𝑝𝑐𝑡 = 𝑇𝑟𝑢𝑒)
𝑑𝑎𝑡𝑎2 = 𝑑𝑎𝑡𝑎. 𝑠𝑜𝑟𝑡_𝑣𝑎𝑙𝑢𝑒𝑠(′𝑟𝑎𝑛𝑘𝑖𝑛𝑔′, 𝑎𝑠𝑐𝑒𝑛𝑑𝑖𝑛𝑔 = 𝑇𝑟𝑢𝑒). ℎ𝑒𝑎𝑑(10)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎2)

Quantiles
La función Quantile() permite conocer el valor de todas las variables asociadas al índice rankeado.
Para utilizarla debemos colocar el ranking de la variable como argumento. Entonces, partiendo de
la misma base de datos, escribimos nuevamente las mismas líneas de código que antes, quienes
asignaron una posición relativa a cada índice de acuerdo al ranking menor a mayor de la variable
“Variación”. Estas líneas pueden buscar en la sección anterior (Rank), aquí sólo mostraremos el
resultado, quien es nuestro punto de partida:
Si aplicamos la función Quantile() para el ranking 0,5, tendremos el valor de todas las variables:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑞𝑢𝑎𝑛𝑡𝑖𝑙𝑒(0.5))

Ahora apliquemos la función para el mismo ranking, pero sólo para la variable “Variacion”:
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑉𝑎𝑟𝑖𝑎𝑐𝑖𝑜𝑛. 𝑞𝑢𝑎𝑛𝑡𝑖𝑙𝑒(0.5))

Matriz de resumen y retornos


Veamos ahora cómo obtener las matrices mencionadas en el título. Trabajaremos con una fuente
HTML, descargando información del SP500. Buscaremos completar una matriz resumen para cada
día de la semana y cada papel que está incluido en el SP500. La hipótesis de trabajo es: “La
variabilidad de los retornos diarios cambia significativamente según el día de la semana”.
Los códigos son los siguientes:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑦𝑓𝑖𝑛𝑎𝑛𝑐𝑒 𝑎𝑠 𝑦𝑓
𝑖𝑚𝑝𝑜𝑟𝑡 𝑛𝑢𝑚𝑝𝑦 𝑎𝑠 𝑛𝑝
𝑖𝑚𝑝𝑜𝑟𝑡 𝑝𝑎𝑛𝑑𝑎𝑠 𝑎𝑠 𝑝𝑑
𝑠𝑝500 = 𝑝𝑑. 𝑟𝑒𝑎𝑑_ℎ𝑡𝑚𝑙(′ℎ𝑡𝑡𝑝𝑠://𝑒𝑛. 𝑤𝑖𝑘𝑖𝑝𝑒𝑑𝑖𝑎. 𝑜𝑟𝑔/𝑤𝑖𝑘𝑖/𝐿𝑖𝑠𝑡_𝑜𝑓_𝑆%26𝑃_500_𝑐𝑜𝑚𝑝𝑎𝑛𝑖𝑒𝑠′)[0]

𝑠𝑝500_𝑡𝑖𝑐𝑘𝑒𝑟𝑠 = 𝑙𝑖𝑠𝑡(𝑠𝑝500. 𝑆𝑦𝑚𝑏𝑜𝑙)


𝑠𝑝500_𝑡𝑖𝑐𝑘𝑒𝑟𝑠 = [𝑒 𝑓𝑜𝑟 𝑒 𝑖𝑛 𝑠𝑝500_𝑡𝑖𝑐𝑘𝑒𝑟𝑠 𝑖𝑓 𝑒 𝑛𝑜𝑡 𝑖𝑛 (′𝐵𝑅𝐾. 𝐵′, ′𝐵𝐹. 𝐵′)]
𝑡𝑖𝑐𝑘𝑒𝑟𝑠 = 𝑠𝑝500_𝑡𝑖𝑐𝑘𝑒𝑟𝑠
𝑐𝑖𝑒𝑟𝑟𝑒𝑠 = 𝑦𝑓. 𝑑𝑜𝑤𝑛𝑙𝑜𝑎𝑑(𝑡𝑖𝑐𝑘𝑒𝑟𝑠, 𝑠𝑡𝑎𝑟𝑡 = ′2000 − 01 − 01′)[′𝐴𝑑𝑗 𝐶𝑙𝑜𝑠𝑒′]
𝑝𝑟𝑖𝑛𝑡(𝑐𝑖𝑒𝑟𝑟𝑒𝑠)

Lo siguiente es calcular el retorno diario logarítmico de estos precios de cierre. La función que
utilizaremos es log() de numpy, por ende, en su argumento colocaremos los vectores filas que
usaremos. En este sentido, el método shift() será útil para indicar que queremos una cotización
anterior a la actual, y para esto, en su argumento pondremos 1.
Luego de calcular estos retornos, deberemos obtener el retorno promedio para cada día de la
semana, por ello, primero los agruparemos según el día, y después obtendremos su promedio.
Finalmente, a este promedio lo anualizaremos.
Lo siguiente será crear un nuevo índice, donde cada fila será un día de la semana, para luego
transponerlo así el mismo queda como columna. Finalmente aplicaremos la función describe(), que
es útil para media, meadiana, máx, mín, std y conteos para una columna en particular. La
particularidad de esta función es que calcula estas medidas tomando todos los datos, es decir, no la
calcula para cada papel, sino para todos. Veamos el código:
𝑟𝑒𝑡𝑜𝑟𝑛𝑜𝑠 = 𝑛𝑝. 𝑙𝑜𝑔((𝑐𝑖𝑒𝑟𝑟𝑒𝑠/𝑐𝑖𝑒𝑟𝑟𝑒𝑠. 𝑠ℎ𝑖𝑓𝑡(1)))
𝑚𝑎𝑡_𝑟𝑒𝑡 = 𝑟𝑒𝑡𝑜𝑟𝑛𝑜𝑠. 𝑔𝑟𝑜𝑢𝑝𝑏𝑦(𝑟𝑒𝑡𝑜𝑟𝑛𝑜𝑠. 𝑖𝑛𝑑𝑒𝑥. 𝑑𝑎𝑦𝑜𝑓𝑤𝑒𝑒𝑘). 𝑚𝑒𝑎𝑛() ∗ 250
𝑚𝑎𝑡_𝑟𝑒𝑡 = 𝑚𝑎𝑡_𝑟𝑒𝑡. 𝑐𝑙𝑖𝑝(𝑙𝑜𝑤𝑒𝑟 = −1, 𝑢𝑝𝑝𝑒𝑟 = 1)
𝑚𝑎𝑡_𝑟𝑒𝑡. 𝑖𝑛𝑑𝑒𝑥 = [′𝐿𝑢𝑛𝑒𝑠′, ′𝑀𝑎𝑟𝑡𝑒𝑠′, ′𝑀𝑖𝑒𝑟𝑐𝑜𝑙𝑒𝑠′, ′𝐽𝑢𝑒𝑣𝑒𝑠′, ′𝑉𝑖𝑒𝑟𝑛𝑒𝑠′]
𝑟𝑒𝑠𝑢𝑚𝑒𝑛 = 𝑚𝑎𝑡_𝑟𝑒𝑡. 𝑡𝑟𝑎𝑛𝑠𝑝𝑜𝑠𝑒()
𝑟𝑒𝑠𝑢𝑚𝑒𝑛. 𝑑𝑒𝑠𝑐𝑟𝑖𝑏𝑒()
𝑝𝑟𝑖𝑛𝑡(𝑟𝑒𝑠𝑢𝑚𝑒𝑛)

Como surge de este ejercicio, el archivo manejado es muy grande para la capacidad de la máquina
actual (i3), por ende, cuando esto ocurre se puede guardar el archivo en formato CSV o Excel, para
luego importarlo desde la misma máquina. Este proceso de importación también tardará, pero
mucho menos. Recuerdo que en estos casos se deberá indicar que el índice corresponde a la
columna cuyo nombre tiene a las fechas como valores.
Persistir objeto serializado
Lo que veremos a continuación es útil para guardar objetos muy grandes tipo DataFrame, quienes
al tener un tamaño inmenso vuelven lento los cálculos. Con la librería pickle podremos serializar y
guardar objetos serializados. Para serializar importamos la librería y serializamos del siguiente
modo:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑝𝑖𝑐𝑘𝑙𝑒
𝑤𝑖𝑡ℎ 𝑜𝑝𝑒𝑛(′𝑟𝑒𝑡𝑜𝑟𝑛𝑜𝑠. 𝑑𝑎𝑡′, ′𝑤𝑏′) 𝑎𝑠 𝑓𝑖𝑙𝑒:
𝑝𝑖𝑐𝑘𝑙𝑒. 𝑑𝑢𝑚𝑝(𝑟𝑒𝑡𝑜𝑟𝑛𝑜𝑠, 𝑓𝑖𝑙𝑒)
El archivo serializado se levanta del siguiente modo:
𝑤𝑖𝑡ℎ 𝑜𝑝𝑒𝑛(′𝑟𝑒𝑡𝑜𝑟𝑛𝑜𝑠. 𝑑𝑎𝑡′, ′𝑟𝑏′) 𝑎𝑠 𝑓𝑖𝑙𝑒:
𝑟𝑒𝑡𝑜𝑟𝑛𝑜𝑠_𝑙𝑜𝑎𝑑 = 𝑝𝑖𝑐𝑘𝑙𝑒. 𝑙𝑜𝑎𝑑(𝑓𝑖𝑙𝑒)
𝑝𝑟𝑖𝑛𝑡(𝑟𝑒𝑡𝑜𝑟𝑛𝑜𝑠_𝑙𝑜𝑎𝑑)
Histogramas de un DataFrame

Continuamos utilizando los archivos de las últimas dos secciones, por si acaso, esto implica que
importadores el archivo serializados siguiente:
𝑤𝑖𝑡ℎ 𝑜𝑝𝑒𝑛(′𝑟𝑒𝑡𝑜𝑟𝑛𝑜𝑠. 𝑑𝑎𝑡′, ′𝑟𝑏′) 𝑎𝑠 𝑓𝑖𝑙𝑒:
𝑟𝑒𝑡𝑜𝑟𝑛𝑜𝑠_𝑙𝑜𝑎𝑑 = 𝑝𝑖𝑐𝑘𝑙𝑒. 𝑙𝑜𝑎𝑑(𝑓𝑖𝑙𝑒)
𝑚𝑎𝑡_𝑟𝑒𝑡 = 𝑟𝑒𝑡𝑜𝑟𝑛𝑜𝑠_𝑙𝑜𝑎𝑑. 𝑔𝑟𝑜𝑢𝑝𝑏𝑦(𝑟𝑒𝑡𝑜𝑟𝑛𝑜𝑠_𝑙𝑜𝑎𝑑. 𝑖𝑛𝑑𝑒𝑥. 𝑑𝑎𝑦𝑜𝑓𝑤𝑒𝑒𝑘). 𝑚𝑒𝑎𝑛() ∗ 250

𝑚𝑎𝑡_𝑟𝑒𝑡 = 𝑚𝑎𝑡_𝑟𝑒𝑡. 𝑐𝑙𝑖𝑝(𝑙𝑜𝑤𝑒𝑟 = −1, 𝑢𝑝𝑝𝑒𝑟 = 1)


𝑚𝑎𝑡_𝑟𝑒𝑡. 𝑖𝑛𝑑𝑒𝑥 = [′𝐿𝑢𝑛𝑒𝑠′, ′𝑀𝑎𝑟𝑡𝑒𝑠′, ′𝑀𝑖𝑒𝑟𝑐𝑜𝑙𝑒𝑠′, ′𝐽𝑢𝑒𝑣𝑒𝑠′, ′𝑉𝑖𝑒𝑟𝑛𝑒𝑠′]
𝑟𝑒𝑠𝑢𝑚𝑒𝑛 = 𝑚𝑎𝑡_𝑟𝑒𝑡. 𝑡𝑟𝑎𝑛𝑠𝑝𝑜𝑠𝑒()
Graficaremos los histogramas de los retornos logarítmicos diarios contenidos en el objeto
“resumen”, y para esto utilizaremos la función hist(). Esta función es similar a describe(), en tanto
que permitirá obtener el histograma de la distribución de frecuencias de cada día, pero no
discriminará por papel, los unirá a todos. Veamos:
𝑟𝑒𝑠𝑢𝑚𝑒𝑛. ℎ𝑖𝑠𝑡(𝑓𝑖𝑔𝑠𝑖𝑧𝑒 = (15,10), 𝑏𝑖𝑛𝑠 = 100)

.bins es el argumento que indica la cantidad de barras (grupos) en cada histograma.


Esto también puede hacerse utilizando el método de pandas ya visto antes, plot(). En este caso
deberemos indicar en los argumentos el tipo de gráfico, entre otras cosas. Veamos el código:
𝑟𝑒𝑠𝑢𝑚𝑒𝑛. 𝐿𝑢𝑛𝑒𝑠. 𝑝𝑙𝑜𝑡(𝑘𝑖𝑛𝑑 = ′ℎ𝑖𝑠𝑡′, 𝑏𝑖𝑛𝑠 = 100, 𝑔𝑟𝑖𝑑 = 𝑇𝑟𝑢𝑒, 𝑓𝑖𝑔𝑠𝑖𝑧𝑒 = (12,4), 𝑟𝑎𝑛𝑔𝑒 = (−1,1))
Diagramas de caja para DataFrame

Los histogramas visto antes tienen un problema, no permiten comparar visualmente y con facilidad
las distribuciones de frecuencias para los diferentes días. La herramienta que sí permite realizar tal
cosa es el diagrama de caja o BoxPlot. Para conseguirlo escribimos el siguiente código:
𝑝𝑑. 𝑝𝑙𝑜𝑡𝑡𝑖𝑛𝑔. 𝑏𝑜𝑥𝑝𝑙𝑜𝑡_𝑓𝑟𝑎𝑚𝑒(𝑟𝑒𝑠𝑢𝑚𝑒𝑛, 𝑤ℎ𝑖𝑠 = 1.5, 𝑠ℎ𝑜𝑤𝑚𝑒𝑎𝑛𝑠 = 𝑇𝑟𝑢𝑒, 𝑓𝑖𝑔𝑠𝑖𝑧𝑒 = (12,7))
Este código pertenece a la librería pandas y nos permite conseguir lo siguiente:
Funciones acumulativas

Estas funciones son útiles para acumular desde un valor que se considera inicial, hasta el valor que
se considera final. Estas acumulaciones pueden ser por suma o por producto. En términos de un
DataFrame, una función acumulativa permite trabajar en la columna "n" de la tabla con todos los
datos de las filas "0 a n". Dentro de este tipo de funciones encontramos:
 .cummax() Permite conseguir el valor máximo acumulado para el rango de fila
seleccionado, por ejemplo, es ideal para máximo histórico por fecha. Aplicado a una serie
de precios, calculará el precio máximo para la primera fila, luego para el conjunto
compuesto por la primera y segunda fila, luego este conjunto se extenderá hasta la tercera,
y así sucesivamente.
 .cummin() Permite conseguir el valor mínimo acumulado para el rango de fila
seleccionado, por ejemplo, es ideal para mínimo histórico por fecha. Aplicado a una serie
de precios, calculará el precio mínimo para la primera fila, luego para el conjunto
compuesto por la primera y segunda fila, luego este conjunto se extenderá hasta la tercera,
y así sucesivamente.
 .cumsum() Es una sumatoria de las filas indicadas, por ejemplo, es ideal para armado de
subtotales por fecha.
 .cumprod() Es una productoria de las filas indicadas, por ejemplo, es ideal para
rendimiento compuesto.

Cummax()
Para ejemplificar su uso utilicemos la serie de precios de GGAL descargada con la librería
yfinance. Como veremos, el argumento “auto_adjust” de la función “yf.dowload” cambia la serie
de precios de cierre por la de cierre ajustados, pero siempre que coloquemos “True”. De todas las
series de precios descargadas, trabajemos exclusivamente con la de precios de cierre ajustados.
Crearemos una variable/columna nueva, cuyos valores serán el precio de cierre ajustado máximo
registrado hasta ese momento (índice). Veamos el código:
𝑑𝑎𝑡𝑎 = 𝑦𝑓. 𝑑𝑜𝑤𝑛𝑙𝑜𝑎𝑑(′𝐺𝐺𝐴𝐿′, 𝑎𝑢𝑡𝑜_𝑎𝑑𝑗𝑢𝑠𝑡 = 𝑇𝑟𝑢𝑒)
𝑑𝑎𝑡𝑎 = 𝑑𝑎𝑡𝑎. 𝑑𝑟𝑜𝑝(["𝐻𝑖𝑔ℎ", "𝐿𝑜𝑤", "𝑉𝑜𝑙𝑢𝑚𝑒"], 𝑎𝑥𝑖𝑠 = 1)
𝑑𝑎𝑡𝑎[′𝑚𝑎𝑥𝐻𝑖𝑠𝑡′] = 𝑑𝑎𝑡𝑎. 𝐶𝑙𝑜𝑠𝑒. 𝑐𝑢𝑚𝑚𝑎𝑥()
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. ℎ𝑒𝑎𝑑(6))
Cummin()
Para ejemplificar su uso utilicemos la serie de precios de GGAL descargada con la librería
yfinance. Como veremos, el argumento “auto_adjust” de la función “yf.dowload” cambia la serie
de precios de cierre por la de cierre ajustados, pero siempre que coloquemos “True”. De todas las
series de precios descargadas, trabajemos exclusivamente con la de precios de cierre ajustados.
Crearemos una variable/columna nueva, cuyos valores serán el precio de cierre ajustado mínimo
registrado hasta ese momento (índice). Veamos el código:
𝑑𝑎𝑡𝑎 = 𝑦𝑓. 𝑑𝑜𝑤𝑛𝑙𝑜𝑎𝑑(′𝐺𝐺𝐴𝐿′, 𝑎𝑢𝑡𝑜_𝑎𝑑𝑗𝑢𝑠𝑡 = 𝑇𝑟𝑢𝑒)
𝑑𝑎𝑡𝑎 = 𝑑𝑎𝑡𝑎. 𝑑𝑟𝑜𝑝(["𝐻𝑖𝑔ℎ", "𝐿𝑜𝑤", "𝑉𝑜𝑙𝑢𝑚𝑒"], 𝑎𝑥𝑖𝑠 = 1)
𝑑𝑎𝑡𝑎[′𝑚𝑖𝑛𝐻𝑖𝑠𝑡′] = 𝑑𝑎𝑡𝑎. 𝐶𝑙𝑜𝑠𝑒. 𝑐𝑢𝑚𝑚𝑖𝑛()
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)
𝑑𝑎𝑡𝑎. ℎ𝑒𝑎𝑑(6)
.

Uso combinado de cummax() y cummin()

Habiendo calculado los máximos y mínimos históricos alcanzados en cada fecha, los mismos
pueden ordenarse gráficamente junto con la serie de precios de cierre ajustados. Esto permite
apreciar si la cotización actual se encuentra cerca de su mínimo o máximo histórico. Veamos:
𝑑𝑎𝑡𝑎. 𝑖𝑙𝑜𝑐[: , −3: ]. 𝑝𝑙𝑜𝑡(𝑙𝑜𝑔𝑦 = 𝑇𝑟𝑢𝑒)

El argumento “logy” es la escala logarítmica.


Cumsum()
Cumsum() es obviamente una función de sumas acumuladas, que en la posición de la fila "n" nos
devuelve la suma de "0 a n" (inclusive).
𝑛

𝑐𝑢𝑚𝑠𝑢𝑚(𝑋𝑛) = ∑ 𝑥𝑖
𝑖=0

Para ejemplificar su uso continuamos utilizando la serie de precios de cierre ajustados de GGAL.
Lo que haremos será descargar esta serie, y crear la columna que contenga la suma acumulada del
volumen por millón de nominales (por ello, la suma se hará sobre el volumen operado dividido un
millón). Veamos:
𝑑𝑎𝑡𝑎 = 𝑦𝑓. 𝑑𝑜𝑤𝑛𝑙𝑜𝑎𝑑(′𝐺𝐺𝐴𝐿′, 𝑎𝑢𝑡𝑜_𝑎𝑑𝑗𝑢𝑠𝑡 = 𝑇𝑟𝑢𝑒)
𝑑𝑎𝑡𝑎[′𝑣𝑜𝑙𝑢𝑚𝑒𝑛𝐴𝑐𝑢𝑚′] = 𝑑𝑎𝑡𝑎. 𝑉𝑜𝑙𝑢𝑚𝑒. 𝑐𝑢𝑚𝑠𝑢𝑚()/1000000
𝑑𝑎𝑡𝑎 = 𝑑𝑎𝑡𝑎. 𝑑𝑟𝑜𝑝([′𝑂𝑝𝑒𝑛′, ′𝐻𝑖𝑔ℎ′, ′𝐿𝑜𝑤′, ′𝐶𝑙𝑜𝑠𝑒′], 𝑎𝑥𝑖𝑠 = 1). 𝑑𝑟𝑜𝑝𝑛𝑎(). 𝑟𝑜𝑢𝑛𝑑(2)
𝑑𝑎𝑡𝑎. ℎ𝑒𝑎𝑑(6)

Cumprod()
Cumprod() es una función de productoria, es decir el producto acumulado de 0 a n, para la fila n.
Algebraicamente:
𝑛

𝐶𝑢𝑚𝑝𝑟𝑜𝑑(𝑋𝑛) = ∏ 𝑥𝑖
𝑖=0

Para ejemplificar su uso calculemos el rendimiento compuesto. Para esto, deberemos cumplir tres
pasos:
1. Creamos una columna "variación" con el valor "r", rendimiento porcentual diario.
2. Creamos una columna "factor" con el valor (1+r).
3. Luego vamos a aplicar el productorio para cada fila de esa columna "factor" y le restamos
1 al resultado.
Veamos las líneas de código:
𝑑𝑎𝑡𝑎 = 𝑦𝑓. 𝑑𝑜𝑤𝑛𝑙𝑜𝑎𝑑(′𝐺𝐺𝐴𝐿′, 𝑎𝑢𝑡𝑜_𝑎𝑑𝑗𝑢𝑠𝑡 = 𝑇𝑟𝑢𝑒)
𝑑𝑎𝑡𝑎[′𝑣𝑎𝑟𝑖𝑎𝑐𝑖𝑜𝑛′] = 𝑑𝑎𝑡𝑎[′𝐶𝑙𝑜𝑠𝑒′]. 𝑝𝑐𝑡_𝑐ℎ𝑎𝑛𝑔𝑒()
𝑑𝑎𝑡𝑎[′𝑓𝑎𝑐𝑡𝑜𝑟′] = 1 + 𝑑𝑎𝑡𝑎[′𝑣𝑎𝑟𝑖𝑎𝑐𝑖𝑜𝑛′]
𝑑𝑎𝑡𝑎[′𝑟𝑒𝑛𝑑𝑖𝑚𝑖𝑒𝑛𝑡𝑜𝐴𝑐𝑢𝑚′] = (𝑑𝑎𝑡𝑎. 𝑓𝑎𝑐𝑡𝑜𝑟. 𝑐𝑢𝑚𝑝𝑟𝑜𝑑() − 1) ∗ 100
𝑑𝑎𝑡𝑎[′𝑏𝑎𝑠𝑒100′] = (𝑑𝑎𝑡𝑎. 𝑓𝑎𝑐𝑡𝑜𝑟. 𝑐𝑢𝑚𝑝𝑟𝑜𝑑()) ∗ 100
𝑑𝑎𝑡𝑎 = 𝑑𝑎𝑡𝑎. 𝑑𝑟𝑜𝑝([′𝑂𝑝𝑒𝑛′, ′𝐻𝑖𝑔ℎ′, ′𝐿𝑜𝑤′, ′𝐶𝑙𝑜𝑠𝑒′, ′𝑉𝑜𝑙𝑢𝑚𝑒′], 𝑎𝑥𝑖𝑠 = 1). 𝑑𝑟𝑜𝑝𝑛𝑎(). 𝑟𝑜𝑢𝑛𝑑(4)

𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)
Librería MatPlotLib
Partiendo de los datos trabajados en la sección anterior, quienes se trabajaron con la función
cumprod(), veremos cómo graficarlos de un modo mucho mejor utilizando la librería MatPlotLib.
La intención es graficar la ganancia acumulada que se tiene en cada momento del tiempo, siendo
que la compra de la acción fue en el año 2000. En este sentido, la intención es mostrar esto junto
con los eventos políticos de la última elección.
Para lograr esto utilizaremos dos sub-paquetes de esta librería: “pyplot”, y “dates”, este último
permite trabajar fechas (reformatearlas). También utilizaremos la librería “datetime” que ya fue
presentada en una de las tres primeras clases (está en el resumen hecho en papel). En la siguiente
web se pueden encontrar explicaciones sobre cómo usar el módulo de esta librería:
https://aprendeconalf.es/docencia/python/manual/matplotlib/
Un detalle sobre el uso de esta nueva librería, cuando grafiquemos es necesario instanciar la figura
y su eje de forma conjunta, esto es crearlas. Esto se realiza en una misma línea de código, que en
este caso la escribimos con color rojo para identificarla a los fines educativos.
Los comentarios sobre el código se marcarán en verde oscuro:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑚𝑎𝑡𝑝𝑙𝑜𝑡𝑙𝑖𝑏. 𝑝𝑦𝑝𝑙𝑜𝑡 𝑎𝑠 𝑝𝑙𝑡
𝑖𝑚𝑝𝑜𝑟𝑡 𝑚𝑎𝑡𝑝𝑙𝑜𝑡𝑙𝑖𝑏. 𝑑𝑎𝑡𝑒𝑠 𝑎𝑠 𝑚𝑑𝑎𝑡𝑒𝑠
𝑓𝑟𝑜𝑚 𝑑𝑎𝑡𝑒𝑡𝑖𝑚𝑒 𝑖𝑚𝑝𝑜𝑟𝑡 𝑑𝑎𝑡𝑒𝑡𝑖𝑚𝑒 𝑎𝑠 𝑑𝑡𝑑𝑡
# 𝐶𝑜𝑛 𝑒𝑙 𝑠𝑖𝑔𝑢𝑖𝑒𝑛𝑡𝑒 𝑐ó𝑑𝑖𝑔𝑜 𝑠𝑒 𝑐𝑟𝑒𝑎 𝑙𝑎 𝑓𝑖𝑔𝑢𝑟𝑎 𝑜 𝑙𝑜𝑠 𝑒𝑗𝑒.
𝑓𝑖𝑔, 𝑎𝑥 = 𝑝𝑙𝑡. 𝑠𝑢𝑏𝑝𝑙𝑜𝑡𝑠(𝑓𝑖𝑔𝑠𝑖𝑧𝑒 = (10,5))
𝑐𝑢𝑟𝑣𝑎 = 𝑑𝑎𝑡𝑎. 𝑟𝑒𝑛𝑑𝑖𝑚𝑖𝑒𝑛𝑡𝑜𝐴𝑐𝑢𝑚. 𝑟𝑜𝑙𝑙𝑖𝑛𝑔(30). 𝑚𝑒𝑎𝑛()
# 𝐿𝑜𝑠 𝑎𝑟𝑔𝑢𝑚𝑒𝑛𝑡𝑜𝑠 𝑑𝑒 𝑙𝑎 𝑓𝑢𝑛𝑐𝑖ó𝑛 . 𝑝𝑙𝑜𝑡 𝑞𝑢𝑒 𝑠𝑒 𝑚𝑢𝑒𝑠𝑡𝑟𝑎 𝑎 𝑐𝑜𝑛𝑡𝑖𝑛𝑢𝑎𝑐𝑖ó𝑛 𝑠𝑜𝑛:
# 𝑐 𝑒𝑠 𝑐𝑜𝑙𝑜𝑟
# 𝑙𝑠 𝑒𝑠 𝑙𝑖𝑛𝑒𝑠𝑡𝑦𝑙𝑒
# 𝑙𝑤 𝑒𝑠 𝑙𝑖𝑛𝑒𝑤𝑖𝑑𝑡ℎ
𝑎𝑥. 𝑝𝑙𝑜𝑡(𝑐𝑢𝑟𝑣𝑎, 𝑐 = ′𝑘′, 𝑙𝑠 = ′ − ′, 𝑙𝑤 = .5, 𝑙𝑎𝑏𝑒𝑙 = ′𝐺𝐺𝐴𝐿 𝐵𝑢𝑦&𝐻𝑜𝑙𝑑 𝑑𝑒𝑠𝑑𝑒 𝑎ñ𝑜 2000′)
𝑎𝑥. 𝑓𝑖𝑙𝑙_𝑏𝑒𝑡𝑤𝑒𝑒𝑛(𝑑𝑎𝑡𝑎. 𝑖𝑛𝑑𝑒𝑥, 𝑐𝑢𝑟𝑣𝑎, 0, 𝑤ℎ𝑒𝑟𝑒 = 𝑐𝑢𝑟𝑣𝑎 < 0 , 𝑐𝑜𝑙𝑜𝑟 = ′𝑝𝑖𝑛𝑘′)
𝑎𝑥. 𝑓𝑖𝑙𝑙_𝑏𝑒𝑡𝑤𝑒𝑒𝑛(𝑑𝑎𝑡𝑎. 𝑖𝑛𝑑𝑒𝑥, 𝑐𝑢𝑟𝑣𝑎, 0, 𝑤ℎ𝑒𝑟𝑒 = 𝑐𝑢𝑟𝑣𝑎 > 0 , 𝑐𝑜𝑙𝑜𝑟 = ′𝑙𝑖𝑔ℎ𝑡𝑔𝑟𝑒𝑒𝑛′)
𝑎𝑥. 𝑙𝑒𝑔𝑒𝑛𝑑(𝑙𝑜𝑐 = ′𝑢𝑝𝑝𝑒𝑟 𝑙𝑒𝑓𝑡′, 𝑓𝑜𝑛𝑡𝑠𝑖𝑧𝑒 = 15)
# 𝑡𝑖𝑐𝑘𝑠 𝑦 𝑙𝑎𝑏𝑒𝑙𝑠
𝑎𝑥. 𝑥𝑎𝑥𝑖𝑠. 𝑠𝑒𝑡_𝑚𝑎𝑗𝑜𝑟_𝑙𝑜𝑐𝑎𝑡𝑜𝑟(𝑚𝑑𝑎𝑡𝑒𝑠. 𝑀𝑜𝑛𝑡ℎ𝐿𝑜𝑐𝑎𝑡𝑜𝑟(𝑖𝑛𝑡𝑒𝑟𝑣𝑎𝑙 = 18))
𝑎𝑥. 𝑥𝑎𝑥𝑖𝑠. 𝑠𝑒𝑡_𝑚𝑎𝑗𝑜𝑟_𝑓𝑜𝑟𝑚𝑎𝑡𝑡𝑒𝑟(𝑚𝑑𝑎𝑡𝑒𝑠. 𝐷𝑎𝑡𝑒𝐹𝑜𝑟𝑚𝑎𝑡𝑡𝑒𝑟(′%𝑌 − %𝑚′))
𝑎𝑥. 𝑡𝑖𝑐𝑘_𝑝𝑎𝑟𝑎𝑚𝑠(𝑎𝑥𝑖𝑠 = ′𝑥′, 𝑟𝑜𝑡𝑎𝑡𝑖𝑜𝑛 = 45, 𝑙𝑎𝑏𝑒𝑙𝑠𝑖𝑧𝑒 = 10, 𝑤𝑖𝑑𝑡ℎ = 5)
𝑎𝑥. 𝑔𝑟𝑖𝑑()
# 𝐴𝑛𝑜𝑡𝑎𝑐𝑖𝑜𝑛𝑒𝑠
𝑒𝑣𝑒𝑛𝑡𝑜𝑠 = { "𝐸𝑙𝑒𝑐𝑐 2015": 𝑑𝑡𝑑𝑡(2015,10,23), " 28 − 𝐷" ∶ 𝑑𝑡𝑑𝑡(2017,12,28), ′ 𝑃𝑅𝐸 − 𝑃𝐴𝑆𝑂′ ∶
𝑑𝑡𝑑𝑡(2019,8,9) }

# 𝐸𝑛 𝑒𝑙 𝑏𝑢𝑐𝑙𝑒 𝑓𝑜𝑟 𝑠𝑖𝑔𝑢𝑖𝑒𝑛𝑡𝑒, kℎ𝑎𝑐𝑒 𝑟𝑒𝑓𝑒𝑟𝑒𝑛𝑐𝑖𝑎 𝑎𝑙 𝑐𝑜𝑙𝑜𝑟 𝑛𝑒𝑔𝑟𝑜, 𝑦 o 𝑎 𝑙𝑎𝑓𝑜𝑟𝑚𝑎 𝑐í𝑟𝑐𝑢𝑙𝑜


𝑓𝑜𝑟 𝑘, 𝑣 𝑖𝑛 𝑒𝑣𝑒𝑛𝑡𝑜𝑠. 𝑖𝑡𝑒𝑚𝑠():
𝑎𝑥. 𝑝𝑙𝑜𝑡(𝑒𝑣𝑒𝑛𝑡𝑜𝑠[𝑘], 𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[𝑣]. 𝑟𝑒𝑛𝑑𝑖𝑚𝑖𝑒𝑛𝑡𝑜𝐴𝑐𝑢𝑚, ′𝑘𝑜′, 𝑚𝑎𝑟𝑘𝑒𝑟𝑠𝑖𝑧𝑒 = 15, 𝑎𝑙𝑝ℎ𝑎 = .3)
𝑎𝑥. 𝑎𝑛𝑛𝑜𝑡𝑎𝑡𝑒(𝑘, 𝑥𝑦 = (𝑣, 𝑑𝑎𝑡𝑎. 𝑙𝑜𝑐[𝑣]. 𝑟𝑒𝑛𝑑𝑖𝑚𝑖𝑒𝑛𝑡𝑜𝐴𝑐𝑢𝑚), 𝑓𝑜𝑛𝑡𝑠𝑖𝑧𝑒 = 14)

Veamos otro ejemplo de uso de esta librería. Para esto usaremos directamente el ejemplo
construido por el profesor en clase. Comencemos con el armado del DataFrame de interés, la idea
es continuar trabajando con volatilidad:
Ahora graficamos el precio de cierre ajustado junto con la volatilidad. En este caso, el problema es
que las escalas son diferentes, como se ve a continuación:
Para resolver situaciones donde tenemos escalas diferentes, escribimos un código que permita
colocar en el eje vertical derecho una escala, y otra en el izquierdo. Para esto utilizamos el método
twinx(). Veamos:

Escalas lineales y logarítimica


Podemos mostrar una misma serie con diferentes escalas, es decir, una con escala lineal y
logarítmica. Veamos:
Por defecto, las escalas son lineales, no obstante, si se desea explicitarla de todos modos, el código
es:
𝑎𝑥2. 𝑠𝑒𝑡_𝑦𝑠𝑐𝑎𝑙𝑒(′𝑙𝑖𝑛𝑒𝑎𝑟 ′ )

Seteo de escalas de eje xlim/ylim


Para establecer límites en las escalas, debemos utilizar set_ylim() para cada eje de interés. Por
ejemplo, hagamos el eje dos tenga como valor límite máximo el número 100. Veamos:
𝑓𝑖𝑔, 𝑎𝑥 = 𝑝𝑙𝑡. 𝑠𝑢𝑏𝑝𝑙𝑜𝑡𝑠(𝑓𝑖𝑔𝑠𝑖𝑧𝑒 = (10,6))
𝑎𝑥. 𝑝𝑙𝑜𝑡(𝑑𝑓[′𝐴𝑑𝑗 𝐶𝑙𝑜𝑠𝑒′], ′𝑔: ′, 𝑙𝑎𝑏𝑒𝑙 = ′𝑃𝑟𝑒𝑐𝑖𝑜 𝐿𝑜𝑔′)
𝑎𝑥. 𝑠𝑒𝑡_𝑦𝑠𝑐𝑎𝑙𝑒(′𝑙𝑜𝑔′)
𝑎𝑥. 𝑙𝑒𝑔𝑒𝑛𝑑(𝑙𝑜𝑐 = ′𝑢𝑝𝑝𝑒𝑟 𝑙𝑒𝑓𝑡′, 𝑓𝑜𝑛𝑡𝑠𝑖𝑧𝑒 = 14)
𝑎𝑥2 = 𝑎𝑥. 𝑡𝑤𝑖𝑛𝑥()
𝑎𝑥2. 𝑝𝑙𝑜𝑡(𝑑𝑓[′𝐴𝑑𝑗 𝐶𝑙𝑜𝑠𝑒′], ′𝑟 − ′, 𝑙𝑎𝑏𝑒𝑙 = ′𝑃𝑟𝑒𝑐𝑖𝑜 𝐿𝑖𝑛𝑒𝑎𝑙′)
𝑎𝑥2. 𝑠𝑒𝑡_𝑦𝑙𝑖𝑚(0,100)
𝑎𝑥2. 𝑙𝑒𝑔𝑒𝑛𝑑(𝑙𝑜𝑐 = ′𝑢𝑝𝑝𝑒𝑟 𝑟𝑖𝑔ℎ𝑡′, 𝑓𝑜𝑛𝑡𝑠𝑖𝑧𝑒 = 14)
𝑝𝑙𝑡. 𝑠ℎ𝑜𝑤()
Sub gráficos
Habrá veces donde querremos graficas más de una serie o gráfica a la vez, pues lo importante es
poder apreciar todo en una misma imagen. En estas ocasiones, y en lugar de dibujar todo en una
misma gráfica, se pueden realizar dos o más gráficas a la vez. Para esto utilizamos la función
subplots() de esta librería MatPlotLib (plt). Veamos cómo utilizarlo siguiendo el ejemplo de
clase:

Con este código definimos la cantidad de filas y columnas, es decir, el cuadriculado de sub
gráficas. Por defecto, es una fila y una columna. Al hacer esto, el eje u objeto “axs” pasa a ser un
array, por ello es que luego llamamos a su ubicación.
El subpltos_adjust(), es útil para justar cuestiones estéticas entre los sub gráficas. En este sentido,
“hspace” es la distancia de vacío/separado entre las filas de una sub gráfica y la otra. El “wspace”
es lo mismo, pero para las columnas.

Estilos
Los diferentes estilos son los siguientes:

Por ejemplo, para utilizar escribimos:

Graficando diferentes parámetros estadísticos


Veamos varios ejemplos sobre cómo graficar una serie de estadísticos como la volatilidad, la
curtosis, y el coeficiente de asimetría. Descarguemos los precios de AAPL utilizando la librería de
YahooFinance. Creemos la columna de variaciones diarias a partir de los precios de cierre, y
reagrupemos todo en función de la volatilidad anual. Calculemos también el coeficiente de
asimetría y la curtósis. Todo esto se resume en la siguiente línea de código:

Hecho esto, creemos un DataFrame con estas medidas:

Es a partir de esto que comenzamos a graficar:


Veamos cómo graficar en líneas curtosis y volatilidad:

Aplicando rolling() sobre volatilidad y curtosis:


Ahora veamos cómo graficar en líneas el coeficiente de asimetría:
Sub librería Stats de la Librería scipy
La sublibrería mencionada en el título permite graficar una normal, incluso una normal estándar,
pudiendo también indicar la cantidad de observaciones a generar. Veamos:

“rvs” genera valores aleatorios.


Lo bueno de esta sublibrería es que podemos utilizar cualquier distribución, por ejemplo, “laplace”.
Esto es una diferencia a favor de esta librería frente a la librería random.

Backtesting: Aproximación a look-ahead Bias y leakage


“Look-ahaed Bias” es estimar a partir de una regresión o una base de datos, es decir, en base a
información pasada, y habiendo identificado algún patrón en el comportamiento de los datos, se
realiza una proyección sobre una variable fijando los valores de otras variables. En este sentido,
esta expresión también hace referencia a la construcción de escenarios.
Por otro lado, “leakage” refiere a un error en el proceso relacionado al entrenamiento de modelos.
Cuando se está construyendo el modelo, o mejor dicho, estimando sus parámetros, se deben utilizar
datos de una fracción de la base de datos, pues luego se hará el backtesting de dos modos: 1)
comparando el resultado del modelo contra la base de datos que lo generó, y 2) realizando
estimaciones con el modelo para el período de tiempo no tenido en cuenta, y comparando los
resultados con los datos que no se tuvieron en cuenta. Entonces, el error mencionado implica
introducir esta fracción de datos en el modelo.
Para realizar el backtesting usaremos mínimamente funciones acumulativas, ventanas móviles
(función rolling()), y funciones de agrupamiento (groupby). Las funciones acumulativas ya fueron
explicadas en la sección que lleva el mismo nombre, aquí sólo recordaremos que permiten obtener,
por ejemplo, soportes y techos. Respecto a las ventas móviles y funciones de agrupamiento, se
explicarán a continuación.

Ventana móvil: función Rolling()


Estas funciones permiten construir medias móviles. A diferencia de las funciones acumulativas,
quienes extienden su suma o multiplicación, o zona de influencia a medida que se avanza sobre la
base de datos, la función rolling() mantiene fijo su horizonte de influencia, pero cambia su
posición en la base de datos. Su argumento es la cantidad de datos que tendrá en cuenta. Esta
función siempre debe combinarse con otras para que su uso tenga sentido, por ejemplo, para las
medias móviles se combinará con mean(), pero también se puede usar con cummax(), cummin(),
std(), etcétera. Por ejemplo:
𝑑𝑎𝑡𝑎. 𝑐𝑢𝑚𝑚𝑎𝑥(). 𝑐𝑙𝑜𝑠𝑒
Esto nos arrojará una serie extraída del DataFrame “data”, particularmente de su columna “close”,
donde veremos los precios máximos de cierre vigentes (techos) hasta cada índice.

Función de agrupamiento: Groupby()


Esta función ya fue utilizada en la sección “Matriz de resumen y retornos”, pero no se explicó su
uso. Groupby(), al igual que rolling(), se debe utilizar con otras funciones (sum(), max(), etcétera).
La susodicha nos permite construir un DataFrame nuevo a partir de otro, utilizando como criterio
de ordenamiento a los valores de alguna variable/columna referenciada. Por ejemplo, si tenemos
una columna con los diferentes meses del año, podríamos crear una nueva que indique el precio de
cierre ajustado promedio para cada mes. Veamos esto utilizando el archivo ‘AAPL.xlsx’, a
continuación se escriben los códigos para limpiarlo, lo que buscaremos será la variación diaria
promedio para cada mes. Veamos:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑛𝑢𝑚𝑝𝑦 𝑎𝑠 𝑛𝑝
𝑖𝑚𝑝𝑜𝑟𝑡 𝑝𝑎𝑛𝑑𝑎𝑠 𝑎𝑠 𝑝𝑑
𝑑𝑎𝑡𝑎 = 𝑝𝑑. 𝑟𝑒𝑎𝑑_𝑒𝑥𝑐𝑒𝑙(′𝐴𝐴𝑃𝐿. 𝑥𝑙𝑠𝑥′, 𝑖𝑛𝑑𝑒𝑥_𝑐𝑜𝑙 = ′𝑡𝑖𝑚𝑒𝑠𝑡𝑎𝑚𝑝′)
𝑑𝑎𝑡𝑎[′𝑣𝑎𝑟𝑖𝑎𝑐𝑖𝑜𝑛′] = 𝑑𝑎𝑡𝑎[′𝑎𝑑𝑗𝑢𝑠𝑡𝑒𝑑_𝑐𝑙𝑜𝑠𝑒′]. 𝑝𝑐𝑡_𝑐ℎ𝑎𝑛𝑔𝑒()
𝑑𝑎𝑡𝑎 = 𝑑𝑎𝑡𝑎. 𝑑𝑟𝑜𝑝𝑛𝑎()
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)
Apliquemos la función groupby() y mean() como corresponde:
𝑣𝑎𝑟_𝑚𝑒𝑠 = 𝑑𝑎𝑡𝑎. 𝑔𝑟𝑜𝑢𝑝𝑏𝑦(𝑑𝑎𝑡𝑎. 𝑖𝑛𝑑𝑒𝑥. 𝑚𝑜𝑛𝑡ℎ). 𝑣𝑎𝑟𝑖𝑎𝑐𝑖𝑜𝑛. 𝑚𝑒𝑎𝑛()
𝑝𝑟𝑖𝑛𝑡(𝑣𝑎𝑟_𝑚𝑒𝑠)

El DataFrame se extiende entre los años 2000 y 2020, aquí tenemos la variación diaria promedio
para cada uno de los doce meses de cada uno de estos años.

CURIOSIDADES DE INDEX
.index también tiene ‘dayofweek’ que extrae el día de la semana, no su fecha, si no el día en sí
mismo.
Asimismo, también podemos usar to_period() e indicar en el argumento ‘Q’ que indica un
cuatrimestre (“quarter”). Si colocamos ‘Y’ será año (“year”).

Del mismo modo, se puede utilizar esta función para agrupar con más de un criterio, la
concatenación de criterios ocurre dentro de la función, por supuesto, separando cada condición por
una coma, y encerrando a ambos criterios entre corchetes (pues se pasan como una lista). Veamos
el ejemplo del profesor:
En paralelo, el argumento de groupby() no tiene porqué ser una variable (junto a sus valores),
también puede ser un valor en sí mismo. Saber esto es importante, pues así podremos agrupar sólo
por esa categoría, para posteriormente ver cómo se distribuye alguna otra variable. Veamos el
ejemplo dado el profesor en clase:

Del mismo modo que se señaló antes, podemos agrupar por más de un criterio:

Esto puede hacerse con mejor estética escribiendo lo siguiente:


Respecto a las funciones que acompañan a groupby(), las más utilizadas son las siguientes:
 first()
 last()
 min()
 max()
 sum()
 prod(). Producto.
 mean()
 median()
 std(). Desvió estándar (sigma).
 var(). Varianza (sigma^2).
 skew(). Coeficiente de asimetría.
 kurtosis(). Curtosis. La librería Pandas, a través de esta función, calcula el exceso de
curtosis. Diferencia entre la curtosis y 3 (tres porque este es el valor de la curtosis de una
distribución normal estándar).
 quantile()

Segmentación: método cut()


Este método es similar a groupby() pues permite segmentar el DataFrame original, sin embargo, se
diferencia porque no construye un DataFrame diferente, modifica el original, por ejemplo, creando
una nueva columna (si se lo indicamos). Además, groupby() no reagrupa utilizando float, sólo
string (cierto es que podemos armar clases y agrupar mediante estas, pero esta posibilidad no anula
la afirmación hecha). En definitiva, este método permite agrupar los datos con un criterio float,
asignando como nombre de clase un string.
Este método, cut(), requiere que se señale la columna del DataFrame desde donde se utilizarán los
datos a clasificar, luego, el argumento ‘bins’ requiere que se le asigne el criterio de clasificación
float contra el cual se compararán los valores de la columna. Finalmente, el argumento ‘label’
requiere que se señale el nombre de las categorías o clases que se asignarán. Como se mencionó, el
resultado será el mismo DataFrame, pero con una nueva columna.
Veamos el ejemplo trabajado por el profesor, donde lo que hace es agrupar los datos de acuerdo al
régimen de volatilidad. El grupo de volatilidad está predefinido, y es de acuerdo a él que se realiza
el ejercicio:

𝑛𝑝. 𝑖𝑛𝑓 es infinito, y es un “número” de la librería numpy.


Función clip()
Esta función permite recortar el DataFrame, concretamente, es útil para eliminar los datos alejados.
No obstante, el software desconoce este concepto, por ello, al utilizar clip(), deberemos colocar los
valores a partir de los cuales el dato es considerado alejado y por ello se recorta.
Veamos el ejemplo brindado en clase por el profesor:

Supongamos que queremos acortar las variaciones diarias a menos de 4%, luego:
Método apply()
Este permite concatenar métodos, es decir, hay métodos que no permiten concatenación con
algunos otros, por ejemplo, rolling() no se puede concatenar con quantile() o con kurtosis() o kurt(),
es por ello que utilizamos apply(). Veamos un ejemplo con el archivo ‘AAPL.xlsx’:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑛𝑢𝑚𝑝𝑦 𝑎𝑠 𝑛𝑝
𝑖𝑚𝑝𝑜𝑟𝑡 𝑝𝑎𝑛𝑑𝑎𝑠 𝑎𝑠 𝑝𝑑
𝑑𝑎𝑡𝑎 = 𝑝𝑑. 𝑟𝑒𝑎𝑑_𝑒𝑥𝑐𝑒𝑙(′𝐴𝐴𝑃𝐿. 𝑥𝑙𝑠𝑥′, 𝑖𝑛𝑑𝑒𝑥_𝑐𝑜𝑙 = ′𝑡𝑖𝑚𝑒𝑠𝑡𝑎𝑚𝑝′)
𝑑𝑎𝑡𝑎[′𝑣𝑎𝑟𝑖𝑎𝑐𝑖𝑜𝑛′] = 𝑑𝑎𝑡𝑎[′𝑎𝑑𝑗𝑢𝑠𝑡𝑒𝑑_𝑐𝑙𝑜𝑠𝑒′]. 𝑝𝑐𝑡_𝑐ℎ𝑎𝑛𝑔𝑒()
𝑑𝑎𝑡𝑎 = 𝑑𝑎𝑡𝑎. 𝑑𝑟𝑜𝑝𝑛𝑎()
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎. 𝑣𝑎𝑟𝑖𝑎𝑐𝑖𝑜𝑛. 𝑔𝑟𝑜𝑢𝑝𝑏𝑦(𝑑𝑎𝑡𝑎. 𝑖𝑛𝑑𝑒𝑥. 𝑦𝑒𝑎𝑟). 𝑎𝑝𝑝𝑙𝑦(𝑝𝑑. 𝐷𝑎𝑡𝑎𝐹𝑟𝑎𝑚𝑒. 𝑘𝑢𝑟𝑡𝑜𝑠𝑖𝑠))
TERCERA PARTE: BASES – CREACION DE
FUNCIONES
Creando funciones propias
Una función permite ahorrar líneas de código sobre una acción que generalmente la repetimos.
Entonces, al crear la función que resume esta acción, para ejecutarla sólo deberemos llamarla. En
otras palabras, crear una función es “empaquetar” un conjunto de acciones como lo hacen las
funciones nativas o de las librerías que importamos.
Antes de abordar la explicación sobre cómo crear funciones propias, veamos la clasificación de las
funciones o, las posibles funciones que podemos construir:
 Sin Argumentos
 Sin return.
 Con return.
 Con argumentos nativos
 Sin return.
 Con return.
 Funciones con argumentos obligatorios y/o opcionales
 Con argumentos específicos
 Funciones que modifican los argumentos
En las siguientes secciones veremos cada una y cómo declararlas. Una característica de todas las
funciones, nativas y no nativas, es que las variables utilizadas en sus líneas de código internas
“mueren” dentro de la función, por ende, nunca habrá solapamiento entre estas variables y una que
nosotros creemos por fuera de la función.

Funciones sin argumentos y sin return


Estas funciones sólo ejecutan una acción. Veamos:
𝑑𝑒𝑓 𝑚𝑎𝑛𝑑𝑎𝑟_𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜_1():
𝑝𝑟𝑖𝑛𝑡("𝐸𝑙 𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜 𝑑𝑒 𝑒𝑠𝑡𝑒 𝑏𝑟𝑜𝑘𝑒𝑟 𝑒𝑠 𝑞𝑢𝑒 𝑒𝑙 𝑢𝑠𝑢𝑎𝑟𝑖𝑜. . . . ")
Vemos que la palabra clave es def, luego le sigue el nombre de la función y los paréntesis, para
cerrar con los dos puntos. Luego de esto siguen las líneas de códigos.
La ejecutamos:
𝑚𝑎𝑛𝑑𝑎𝑟_𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜_1()
𝐸𝑙 𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜 𝑑𝑒 𝑒𝑠𝑡𝑒 𝑏𝑟𝑜𝑘𝑒𝑟 𝑒𝑠 𝑞𝑢𝑒 𝑒𝑙 𝑢𝑠𝑢𝑎𝑟𝑖𝑜. . ..
Si este tipo de función es asignada en una variable, no se guardará nada, pues la función no guarda
nada, sólo ejecuta. Esto quiere decir que al imprimir la variable a la que fue asignada la función,
nada ocurrirá. Esto sucede porque la función no tiene return. Veamos:
𝑟𝑒𝑠𝑢𝑙𝑡𝑎𝑑𝑜 = 𝑚𝑎𝑛𝑑𝑎𝑟_𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜_1()
𝑝𝑟𝑖𝑛𝑡(𝑟𝑒𝑠𝑢𝑙𝑡𝑎𝑑𝑜)
𝑁𝑜𝑛𝑒
Funciones sin argumentos y con return
Para definir una función de este tipo, sólo debemos incorporar el comando return al final del
código, seguido de lo que queremos que se arroje como resultado. Veamos un ejemplo utilizando
try y except (quienes fueron explicados en otro apartado):
𝑑𝑒𝑓 𝑚𝑎𝑛𝑑𝑎𝑟_𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜_2():
𝑡𝑟𝑦:
𝑝𝑟𝑖𝑛𝑡("𝐸𝑙 𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜 𝑑𝑒 𝑒𝑠𝑡𝑒 𝑏𝑟𝑜𝑘𝑒𝑟 𝑒𝑠 𝑞𝑢𝑒 𝑒𝑙 𝑢𝑠𝑢𝑎𝑟𝑖𝑜. . . . ")
𝑟𝑒𝑡𝑢𝑟𝑛 𝑇𝑟𝑢𝑒
𝑒𝑥𝑐𝑒𝑝𝑡:
𝑝𝑟𝑖𝑛𝑡("𝑁𝑜 𝑠𝑒 𝑝𝑢𝑑𝑜 𝑒𝑛𝑣𝑖𝑎𝑟 𝑒𝑙 𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜")
𝑟𝑒𝑡𝑢𝑟𝑛 𝐹𝑎𝑙𝑠𝑒
¿Cómo funciona el return? Si las líneas de código funcionan, no sólo se ejecutarán, también se
devolverá el return ó, el valor asignado como return (True en este caso). En paralelo, es el return
señalado lo que se guardará en la variable en cuestión:
𝑟𝑒𝑠𝑢𝑙𝑡𝑎𝑑𝑜 = 𝑚𝑎𝑛𝑑𝑎𝑟_𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜_2()
𝑝𝑟𝑖𝑛𝑡(𝑟𝑒𝑠𝑢𝑙𝑡𝑎𝑑𝑜)
𝑇𝑟𝑢𝑒
El return puede ser cualquier tipo de datos, e incluso puede ser más de un tipo de dato.

Funciones con argumentos nativos y sin return


Estas funciones modifican su acción de acuerdo al valor que toma el argumento. Veamos un
ejemplo:
𝑑𝑒𝑓 𝑚𝑎𝑛𝑑𝑎𝑟_𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜_3(𝑡𝑖𝑝𝑜_𝑐𝑙𝑖𝑒𝑛𝑡𝑒):
𝑝𝑟𝑖𝑛𝑡(𝑓"𝐸𝑙 𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜 𝑑𝑒 𝑒𝑠𝑡𝑒 𝑏𝑟𝑜𝑘𝑒𝑟 𝑝𝑎𝑟𝑎 𝑐𝑙𝑖𝑒𝑛𝑡𝑒𝑠 𝑑𝑒𝑙 𝑡𝑖𝑝𝑜 {𝑡𝑖𝑝𝑜_𝑐𝑙𝑖𝑒𝑛𝑡𝑒} 𝑒𝑠. . . . ")
Vemos que el argumento es ‘tipo_cliente’, y la acción variará (el mensaje cambiará) de acuerdo al
valor que asuma dicho argumento.
Si a esta función la llamamos y no indicamos el valor del argumento, tendremos un error. ¿Cómo
se declara un argumento? Veamos las dos formas:
𝑚𝑎𝑛𝑑𝑎𝑟_𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜_3(𝑡𝑖𝑝𝑜_𝑐𝑙𝑖𝑒𝑛𝑡𝑒 = ′𝑂𝑝𝑒𝑟𝑎𝑑𝑜𝑟𝑒𝑠′)
𝑚𝑎𝑛𝑑𝑎𝑟_𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜_3(′𝑂𝑝𝑒𝑟𝑎𝑑𝑜𝑟𝑒𝑠′)

Funciones con argumentos nativos y con return


Este tipo de funciones es una combinación de las explicadas en las últimas dos secciones:
𝑑𝑒𝑓 𝑚𝑎𝑛𝑑𝑎𝑟_𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜_4(𝑡𝑖𝑝𝑜_𝑐𝑙𝑖𝑒𝑛𝑡𝑒):
𝑡𝑟𝑦:
𝑝𝑟𝑖𝑛𝑡(𝑓"𝐸𝑙 𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜 𝑑𝑒 𝑒𝑠𝑡𝑒 𝑏𝑟𝑜𝑘𝑒𝑟 𝑝𝑎𝑟𝑎 𝑐𝑙𝑖𝑒𝑛𝑡𝑒𝑠 𝑑𝑒𝑙 𝑡𝑖𝑝𝑜 {𝑡𝑖𝑝𝑜_𝑐𝑙𝑖𝑒𝑛𝑡𝑒} 𝑒𝑠. . . . ")
𝑟𝑒𝑡𝑢𝑟𝑛 𝑇𝑟𝑢𝑒
𝑒𝑥𝑐𝑒𝑝𝑡:
𝑝𝑟𝑖𝑛𝑡("𝑁𝑜 𝑠𝑒 𝑝𝑢𝑑𝑜 𝑒𝑛𝑣𝑖𝑎𝑟 𝑒𝑙 𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜")
𝑟𝑒𝑡𝑢𝑟𝑛 𝐹𝑎𝑙𝑠𝑒
En este caso, podemos asignar el resultado de la función a una variable, quien requerirá indicar el
valor del argumento:
𝑟𝑒𝑠𝑢𝑙𝑡𝑎𝑑𝑜 = 𝑚𝑎𝑛𝑑𝑎𝑟_𝑟𝑒𝑔𝑙𝑎𝑚𝑒𝑛𝑡𝑜_4("𝑂𝑝𝑒𝑟𝑎𝑑𝑜𝑟𝑒𝑠")
Como antes, y dado el return escrito, si imprimimos esto tendremos como resultado el True.

Funciones con argumentos obligatorios y/o opcionales


Creemos una función con dos argumentos, uno obligatorio y otro opcional, para esto, imaginemos
que queremos crear una función que como acción calcule la banda inferior y superior de un precio,
dado el precio y el porcentaje indicados como argumentos, y como resultado, arroje las bandas
inferior y superior. El argumento precio será obligatorio, mientras el argumento porcentaje será
opcional, y tomará el valor 1 por defecto.
Veamos cómo hacerlo:
𝑑𝑒𝑓 𝑏𝑎𝑛𝑑𝑎_𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑢𝑎𝑙(𝑝𝑟𝑒𝑐𝑖𝑜, 𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒 = 1):
𝑖𝑛𝑓 = 𝑟𝑜𝑢𝑛𝑑(𝑝𝑟𝑒𝑐𝑖𝑜 ∗ (1 − (𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒 / 100)),4)
𝑠𝑢𝑝 = 𝑟𝑜𝑢𝑛𝑑(𝑝𝑟𝑒𝑐𝑖𝑜 ∗ (1 + (𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒 / 100)),4)
𝑟𝑒𝑡𝑢𝑟𝑛 𝑖𝑛𝑓, 𝑠𝑢𝑝
Vamos a imprimir la función para demostrar que el orden en el que se escriben los argumentos, si
es que se explicitan, no importa:
𝑝𝑟𝑖𝑛𝑡(𝑏𝑎𝑛𝑑𝑎_𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑢𝑎𝑙(𝑝𝑟𝑒𝑐𝑖𝑜 = 131, 𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒 = 7))

𝑝𝑟𝑖𝑛𝑡(𝑏𝑎𝑛𝑑𝑎_𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑢𝑎𝑙(𝑝𝑜𝑟𝑐𝑒𝑛𝑡𝑎𝑗𝑒 = 7, 𝑝𝑟𝑒𝑐𝑖𝑜 = 131))

Vea que el resultado devuelto es una tupla, por defecto. No obstante, esto puede modificarse al
indicar qué debe devolverse.
Si no explicitamos el nombre de los argumentos, entonces el orden sí importa, y el que se respeta es
el orden con que fueron creados los argumentos cuando se armó la función, es decir, primero
tendremos que colocar el valor del precio y luego el del porcentaje.

NOTA: argumentos obligatorios y opcionales.


En la jerga, los argumentos obligatorios se conocen como “args”, mientras que los opcionales
como “kwargs”.

Funciones con argumentos especiales


Para ejemplificar esto veamos primero una librería especial, la librería sympy, es para incorporar
lenguaje simbólico, útil para introducir fórmulas matemáticas. Imaginemos que queremos escribir
una función con dos variables, “x” e “y”, para esto debemos declarar estas letras como símbolos.
Veamos cómo hacerlo:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑠𝑦𝑚𝑝𝑦
𝑥, 𝑦 = 𝑠𝑦𝑚𝑝𝑦. 𝑠𝑦𝑚𝑏𝑜𝑙𝑠(′𝑥, 𝑦′)
Ahora estamos en condiciones de escribir la función con argumentos especiales. Supongamos que
queremos escribir la siguiente:

Para lograrlo usamos el siguiente código:


𝑓𝑟𝑜𝑚 𝑠𝑦𝑚𝑝𝑦 𝑖𝑚𝑝𝑜𝑟𝑡 ∗
𝑥, 𝑦 = 𝑠𝑦𝑚𝑏𝑜𝑙𝑠(′𝑥, 𝑦′)
𝑦 = 1 ∗ 𝑥 ∗∗ 2 + 2 ∗ 𝑥 − 3
Veamos qué hay en “y”:
𝑝𝑟𝑖𝑛𝑡(𝑦)

Ahora podríamos graficar esta función:


𝑝𝑟𝑖𝑛𝑡(𝑝𝑙𝑜𝑡(𝑦, (𝑥, −5,5), (𝑦, −5,5)))

Podríamos calcular las raíces de esta función, para ello utilizamos la función solve(). Veamos:
𝑝𝑟𝑖𝑛𝑡(𝑠𝑜𝑙𝑣𝑒(𝑦))

Funciones que modifican los argumentos (o no)


En estos casos, la función realiza una acción y devuelve un return, pero también e internamente,
modifica el argumento original. En otras palabras, entre las líneas de código de la función, hay al
menos una que modifica el argumento.
En línea con lo anterior, es importante saber que, existen dos modos de establecer el valor del
argumento de una función, como valor, o como referencia. En el primer caso la función no
modificará este argumento, pues aquí ocurre algo similar a lo sucedido en Excel cuando copiamos
el valor de la celda y no la función. En cambio, en el segundo caso, “copiamos la función”, y por
esto es que la función que creamos sí modifica el argumento. En Python, los elementos simples
(números y strings) son valores, mientras que las colecciones son referencias.
Veamos un ejemplo:
𝑑𝑒𝑓 𝑙𝑜𝑛𝑔𝑖𝑡𝑢𝑑(𝑑𝑎𝑡𝑜_𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙):
𝑙 = 𝑙𝑒𝑛(𝑑𝑎𝑡𝑜_𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙)
𝑑𝑎𝑡𝑜_𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙 += "𝐻𝑜𝑙𝑖𝑠. . "
𝑟𝑒𝑡𝑢𝑟𝑛 𝑙
𝑑𝑎𝑡𝑜_𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙 = [1,2,3,4]
𝑝𝑟𝑖𝑛𝑡(𝑙𝑜𝑛𝑔𝑖𝑡𝑢𝑑(𝑑𝑎𝑡𝑜_𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙))

𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑜_𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙)
En este caso, utilizar como valor del argumento una colección implica usar una referencia. En
cambio, si utilizamos un string, por ejemplo, usaremos un valor y la función no lo modificará:
𝑑𝑎𝑡𝑜_𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙 = "𝐸𝑙 𝑡𝑎𝑐𝑜 𝑛𝑜"
𝑝𝑟𝑖𝑛𝑡(𝑙𝑜𝑛𝑔𝑖𝑡𝑢𝑑(𝑑𝑎𝑡𝑜_𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙))

𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑜_𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙)

Funciones dentro de funciones


Estos casos refieren a funciones que creamos y donde utilizamos otras funciones, nativas o no,
dentro de las líneas de código que ejecutará nuestra función. Por ejemplo, imaginemos que la
función a crear debe tener como return un DataFrame con los precios del ticker mencionado a partir
de una fecha predeterminada, además, también debe tener una columna con la variación diaria de
los precios, y otra con la media móvil del precio de cierre de determinada cantidad de ruedas. En
este sentido, el argumento del ticket debe ser obligatorio, mientras los otros dos opcionales.
Asimismo, si en el proceso de ejecución llega a producirse algún error, la función deberá devolver
como leyenda que el ticket introducido no es correcto.
En este ejercicio, la función que creamos requiere de la incorporación de otra función, como lo es
aquella que importará el archivo de precios del ticket elegido. Veamos el siguiente código, quien
permite lograr lo propuesto:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑦𝑓𝑖𝑛𝑎𝑛𝑐𝑒 𝑎𝑠 𝑦𝑓
𝑑𝑒𝑓 𝑏𝑎𝑗𝑎𝑟𝐷𝑎𝑡𝑜𝑠(𝑡𝑖𝑐𝑘𝑒𝑟, 𝑑𝑒𝑠𝑑𝑒 = ′2011 − 01 − 01′, 𝑚𝑒𝑑𝑖𝑎_𝑚𝑜𝑣𝑖𝑙 = 200):
𝑡𝑟𝑦:
𝑑𝑎𝑡𝑎 = 𝑦𝑓. 𝑑𝑜𝑤𝑛𝑙𝑜𝑎𝑑(𝑡𝑖𝑐𝑘𝑒𝑟, 𝑠𝑡𝑎𝑟𝑡 = 𝑑𝑒𝑠𝑑𝑒)
𝑑𝑎𝑡𝑎[′𝑣𝑎𝑟𝑖𝑎𝑐𝑖𝑜𝑛′] = 𝑑𝑎𝑡𝑎[′𝐴𝑑𝑗 𝐶𝑙𝑜𝑠𝑒′]. 𝑝𝑐𝑡_𝑐ℎ𝑎𝑛𝑔𝑒()
𝑐𝑙𝑎𝑣𝑒 = ′𝑠𝑚𝑎_′ + 𝑠𝑡𝑟(𝑚𝑒𝑑𝑖𝑎_𝑚𝑜𝑣𝑖𝑙) # "𝑠𝑚𝑎_50"
𝑑𝑎𝑡𝑎[𝑐𝑙𝑎𝑣𝑒] = 𝑑𝑎𝑡𝑎[′𝐴𝑑𝑗 𝐶𝑙𝑜𝑠𝑒′]. 𝑟𝑜𝑙𝑙𝑖𝑛𝑔(𝑚𝑒𝑑𝑖𝑎_𝑚𝑜𝑣𝑖𝑙). 𝑚𝑒𝑎𝑛()
𝑟𝑒𝑡𝑢𝑟𝑛 𝑑𝑎𝑡𝑎. 𝑑𝑟𝑜𝑝𝑛𝑎()
𝑒𝑥𝑐𝑒𝑝𝑡:
# 𝑝𝑎𝑠𝑠
𝑝𝑟𝑖𝑛𝑡(′𝑚𝑒 𝑚𝑎𝑛𝑑𝑎𝑠𝑡𝑒 𝑐𝑢𝑎𝑙𝑞𝑢𝑖𝑒𝑟𝑎 𝑒𝑛 𝑎𝑙𝑔𝑢𝑛 𝑎𝑟𝑔𝑢𝑚𝑒𝑛𝑡𝑜′)
Veamos cómo trabaja:
𝑑𝑎𝑡𝑎 = 𝑏𝑎𝑗𝑎𝑟𝐷𝑎𝑡𝑜𝑠(′𝐺𝐺𝐴𝐿′, 𝑑𝑒𝑠𝑑𝑒 = ′2019 − 01 − 01′, 𝑚𝑒𝑑𝑖𝑎_𝑚𝑜𝑣𝑖𝑙 = 50)
𝑝𝑟𝑖𝑛𝑡(𝑑𝑎𝑡𝑎)

Funciones que aceptan otras funciones como argumentos


En estos casos y como lo indica el título de la sección, al menos uno de los argumentos de la
función a crear será otra función. Veamos un ejemplo donde queremos crear una calculadora,
donde introducimos dos números y el tipo de operación que deseamos realizar. En este caso, el tipo
de operación a realizar será el argumento que es otra función. Veamos el código:
𝑑𝑒𝑓 𝑐𝑎𝑙𝑐𝑢𝑙𝑎𝑑𝑜𝑟𝑎(𝑜𝑝𝑒𝑟𝑎𝑐𝑖𝑜𝑛, 𝑎𝑟𝑔1, 𝑎𝑟𝑔2):
𝑟𝑒𝑠𝑢𝑙𝑡𝑎𝑑𝑜 = 𝑜𝑝𝑒𝑟𝑎𝑐𝑖𝑜𝑛(𝑎𝑟𝑔1, 𝑎𝑟𝑔2)
𝑟𝑒𝑡𝑢𝑟𝑛 𝑟𝑒𝑠𝑢𝑙𝑡𝑎𝑑𝑜

𝑑𝑒𝑓 𝑠𝑢𝑚𝑎𝑟(𝑎, 𝑏):


𝑟𝑒𝑡𝑢𝑟𝑛 𝑎 + 𝑏

𝑑𝑒𝑓 𝑚𝑢𝑙𝑡𝑖𝑝𝑙𝑖𝑐𝑎𝑟(𝑎, 𝑏):


𝑟𝑒𝑡𝑢𝑟𝑛 𝑎 ∗ 𝑏

𝑑𝑒𝑓 𝑟𝑎𝑖𝑧(𝑎, 𝑏):


𝑟𝑒𝑡𝑢𝑟𝑛(𝑎 ∗∗ (1/𝑏))

𝑝𝑟𝑖𝑛𝑡(𝑐𝑎𝑙𝑐𝑢𝑙𝑎𝑑𝑜𝑟𝑎(𝑟𝑎𝑖𝑧, 8,3))
2.0 # 𝑅𝑒𝑠𝑢𝑙𝑡𝑎𝑑𝑜
Observe que, el orden de creación de las funciones es irrelevante, en otras palabras, aunque su
creación sigue el orden lógico de arriba hacia abajo, la ejecución al momento de llamar la función
“calculadora” es como otro bloque de código que no depende del orden mencionado.

Funciones con argumentos que son colecciones


Los argumentos pueden ser desde valores hasta referencias, es decir, pueden ser un número o
string, y también pueden ser colecciones. Si no se especifica el tipo de dato que acepta con
exclusividad el argumento, Python interpretará que el mismo puede ser cualquier tipo de dato. Esto
es así siempre, incluso cuando se señala el tipo de dato exclusivo que corresponde en el argumento.
Por ejemplo, pensemos que queremos que uno de los argumentos sea un diccionario, para indicarlo,
en lugar de colocar un igual después del nombre del argumento, deberemos colocar dos puntos y el
tipo de dato. En este caso, si no se coloca un diccionario, la función no arrojará error a menos que
las líneas de código no puedan ejecutarse. Indicar el tipo de dato a pesar de esto es una buena
práctica de programación, pues en el futuro, al leer la función sabremos que lo mejor es que en
dicho argumento se coloque un diccionario en lugar de una lista, tupla, DataFrame, etc.
Veamos un ejemplo, donde además de lo mencionado, la función creada también modifica el
argumento:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑑𝑎𝑡𝑒𝑡𝑖𝑚𝑒 𝑎𝑠 𝑑𝑡
𝑑𝑒𝑓 𝑝𝑟𝑒𝑝𝑎𝑟𝑎𝑟𝑇𝑟𝑎𝑑𝑒(𝑡𝑟𝑎𝑑𝑒: 𝑑𝑖𝑐𝑡, 𝑡𝑝 = 1.1, 𝑠𝑙 = 0.97):
𝑟𝑒𝑠𝑢𝑙𝑡𝑎𝑑𝑜 = 𝐹𝑎𝑙𝑠𝑒
𝑡𝑟𝑦:
𝑡𝑟𝑎𝑑𝑒[′𝑠𝑡𝑜𝑝𝐿𝑜𝑠𝑠′] = 𝑟𝑜𝑢𝑛𝑑(𝑡𝑟𝑎𝑑𝑒. 𝑔𝑒𝑡(′𝑃𝑟𝑒𝑐𝑖𝑜′, 0) ∗ 𝑠𝑙, 4)
𝑡𝑟𝑎𝑑𝑒[′𝑡𝑎𝑘𝑒𝑃𝑟𝑜𝑓𝑖𝑡′] = 𝑟𝑜𝑢𝑛𝑑(𝑡𝑟𝑎𝑑𝑒. 𝑔𝑒𝑡(′𝑃𝑟𝑒𝑐𝑖𝑜′, 0) ∗ 𝑡𝑝, 4)
𝑡𝑟𝑎𝑑𝑒[′𝑓𝑒𝑐ℎ𝑎′] = 𝑑𝑡. 𝑑𝑎𝑡𝑒𝑡𝑖𝑚𝑒. 𝑛𝑜𝑤(). 𝑐𝑡𝑖𝑚𝑒()
𝑟𝑒𝑠𝑢𝑙𝑡𝑎𝑑𝑜 = 𝑇𝑟𝑢𝑒
𝑒𝑥𝑐𝑒𝑝𝑡:
𝑝𝑟𝑖𝑛𝑡(′𝑀𝑒 𝑚𝑎𝑛𝑑𝑎𝑠𝑡𝑒 𝑎𝑙𝑔𝑜 𝑞𝑢𝑒 𝑛𝑜 𝑒𝑠 𝑢𝑛 𝑑𝑖𝑐𝑐𝑖𝑜𝑛𝑎𝑟𝑖𝑜′)
𝑟𝑒𝑡𝑢𝑟𝑛 𝑟𝑒𝑠𝑢𝑙𝑡𝑎𝑑𝑜
En este código, el argumento “trade” tiene que ser un diccionario, y si no lo es, la función generará
la leyenda “Me mandaste algo que no es un diccionario” en lugar de un error. Vamos a ejecutar
esta función:
𝑡𝑟𝑎𝑑𝑒 = {′𝑇𝑖𝑐𝑘𝑒𝑟′: ′𝐺𝐺𝐴𝐿′, ′𝑆𝑖𝑑𝑒′: ′𝐵𝑢𝑦′, ′𝑃𝑟𝑒𝑐𝑖𝑜′: 180}
𝑟 = 𝑝𝑟𝑒𝑝𝑎𝑟𝑎𝑟𝑇𝑟𝑎𝑑𝑒(𝑡𝑟𝑎𝑑𝑒)
𝑝𝑟𝑖𝑛𝑡(𝑡𝑟𝑎𝑑𝑒)
Importar nuestras propias funciones
Los archivos con extensión .py pueden armarse desde otros sitios diferentes al entorno de jupiter
notebook o spyder, no obstante, el punto es que cuando tenemos un archivo .py donde hemos
creado funciones, a éstas las podemos importar sin necesidad de abrir dicho archivo. Los siguientes
son editores de códigos y entorno de desarrollo, presentados en este orden, desde donde se pueden
crear códigos con extensión .py, entre otras extensiones:

Una vez creado el archivo .py con la función propia, podemos importarla al entorno de trabajo
como hacemos cuando importamos cualquier librería, es decir, escribimos “import” seguido del
nombre del archivo.
Funciones para análisis técnico
A continuación veremos aplicaciones prácticas de la generación de funciones propias, en estos
casos veremos cómo crear los indicadores técnicos usuales: Bandas de bollinger, RSI, MACD,
Cruces de medias, Ichimoku, y gráficos de correlaciones. Veamos cada uno en una sección
individual. En la siguiente página web se describe la matemática detrás de cada indicador:
https://docs.anychart.com/Stock_Charts/Technical_Indicators/Mathematical_Description
De esta web se obtienen las descripciones que se realizan en las siguientes secciones antes de
introducir el código correspondiente.
Otras dos páginas para conocer más sobre indicadores técnicos son las siguientes:
 https://www.fmlabs.com/reference/default.htm
 https://docs.anychart.com/Stock_Charts/Technical_Indicators/Mathematical_Description#o
verview

Bandas de bollinger
Las bandas de bollinger permiten tener un rango de movimiento probable del precio.
Concretamente, estas se definen del siguiente modo:
El “BBands” son las bandas de bollinger, y “Bollinger Bands %B” es el indicador utilizado para
tener señales de compra y de venta. Este indicador incorpora el precio de cotización y lo compara
con las bandas. Si el indicador es positivo es porque la cotización es mayor a la banda inferior, y si
a su vez es mayor a 1, es porque el precio de cotización se encuentra en un valor históricamente
elevado (en términos de los promedios para la ventana móvil analizada). Si el indicador es
negativo, el precio de cotización es bajo en relación al promedio móvil. Con este indicador se
pueden generar señales de compra y venta, pues si está por arriba de 1 conviene vender, y si es
negativo conviene comprar.
Este indicador tiene un sentido estadístico, pues suponiendo que la distribución de los retornos
periódicos es normal, la cantidad de observaciones dentro del más menos dos desvíos estándar es
de 95,45%. Por ende, cuando el precio de cotización supera las bandas, es muy probable que el
mismo regrese dentro de las mismas.
Veamos cómo lo hizo el profesor en clase sin definir la función:
Ahora construyamos la función incorporando los argumentos, y sumando algo más: si el volumen
es superior a dos veces su promedio, se sumarán más desvío, y si es inferior, se le restará desvío.
Veamos cómo queda el código:

𝑖𝑚𝑝𝑜𝑟𝑡 𝑦𝑓𝑖𝑛𝑎𝑛𝑐𝑒 𝑎𝑠 𝑦𝑓
𝑖𝑚𝑝𝑜𝑟𝑡 𝑛𝑢𝑚𝑝𝑦 𝑎𝑠 𝑛𝑝
𝑑𝑒𝑓 𝑏𝑜𝑙𝑙𝑖𝑛𝑔𝑒𝑟(𝑡𝑖𝑐𝑘𝑒𝑟, 𝑣𝑒𝑛𝑡𝑎𝑛𝑎 = 20, 𝑐𝑎𝑛𝑡_𝑑𝑒𝑠𝑣𝑖𝑜𝑠 = 2, 𝑖𝑛𝑖𝑐𝑖𝑜 = ′2010 − 01 − 01′, 𝑎𝑗𝑢𝑠𝑡𝑎𝑟_𝑣𝑜𝑙𝑢𝑚𝑒𝑛 = 𝐹𝑎𝑙𝑠𝑒):
"""
𝐴𝑗𝑢𝑠𝑡𝑒 𝑥 𝑉𝑜𝑙𝑢𝑚𝑛𝑒𝑛:
𝐶𝑢𝑎𝑛𝑑𝑜 𝑒𝑙 𝑉𝑜𝑙𝑢𝑚𝑒𝑛 𝐷𝑈𝑃𝐿𝐼𝐶𝐴 𝑎𝑙 𝑉𝑜𝑙𝑢𝑚𝑒𝑛 𝑀𝑒𝑑𝑖𝑜 => + 0.5 𝐷𝑒𝑠𝑣𝑖𝑜𝑠
𝐶𝑢𝑎𝑛𝑑𝑜 𝑒𝑙 𝑉𝑜𝑙𝑢𝑚𝑒𝑛 𝐸𝑆 𝑀𝐸𝑁𝑂𝑅 𝑉𝑜𝑙𝑢𝑚𝑒𝑛 𝑀𝑒𝑑𝑖𝑜 => − 0.5 𝐷𝑒𝑠𝑣𝑖𝑜𝑠
"""
𝑛𝑜𝑚𝑏𝑟𝑒_𝑐𝑜𝑙𝑢𝑚𝑛𝑎_𝑠𝑚𝑎 = ′𝑠𝑚𝑎__′ + 𝑠𝑡𝑟(𝑣𝑒𝑛𝑡𝑎𝑛𝑎)
𝑑𝑎𝑡𝑎 = 𝑦𝑓. 𝑑𝑜𝑤𝑛𝑙𝑜𝑎𝑑(𝑡𝑖𝑐𝑘𝑒𝑟, 𝑎𝑢𝑡𝑜_𝑎𝑑𝑗𝑢𝑠𝑡 = 𝑇𝑟𝑢𝑒, 𝑠𝑡𝑎𝑟𝑡 = 𝑖𝑛𝑖𝑐𝑖𝑜)
𝑑𝑒𝑠𝑣𝑖𝑜 = 𝑑𝑎𝑡𝑎. 𝐶𝑙𝑜𝑠𝑒. 𝑟𝑜𝑙𝑙𝑖𝑛𝑔(𝑣𝑒𝑛𝑡𝑎𝑛𝑎). 𝑠𝑡𝑑()
𝑣𝑜𝑙𝑢𝑚𝑒𝑛_𝑚𝑒𝑑𝑖𝑜 = 𝑑𝑎𝑡𝑎. 𝑉𝑜𝑙𝑢𝑚𝑒. 𝑚𝑒𝑎𝑛()
𝑑𝑎𝑡𝑎[′𝑘_𝑣𝑜𝑙′] = 𝑑𝑎𝑡𝑎. 𝑉𝑜𝑙𝑢𝑚𝑒 / 𝑣𝑜𝑙𝑢𝑚𝑒𝑛_𝑚𝑒𝑑𝑖𝑜
𝑑𝑎𝑡𝑎[′𝑝𝑙𝑢𝑠_𝑑𝑒𝑠𝑣𝑖𝑜𝑠′] = 𝑛𝑝. 𝑤ℎ𝑒𝑟𝑒(𝑑𝑎𝑡𝑎. 𝑘_𝑣𝑜𝑙 > 2, 0.5, 𝑛𝑝. 𝑤ℎ𝑒𝑟𝑒(𝑑𝑎𝑡𝑎. 𝑘_𝑣𝑜𝑙 > 1, 0, −0.5))
𝑑𝑎𝑡𝑎[𝑛𝑜𝑚𝑏𝑟𝑒_𝑐𝑜𝑙𝑢𝑚𝑛𝑎_𝑠𝑚𝑎] = 𝑑𝑎𝑡𝑎[′𝐶𝑙𝑜𝑠𝑒′]. 𝑟𝑜𝑙𝑙𝑖𝑛𝑔(𝑣𝑒𝑛𝑡𝑎𝑛𝑎). 𝑚𝑒𝑎𝑛()
𝑖𝑓 𝑎𝑗𝑢𝑠𝑡𝑎𝑟_𝑣𝑜𝑙𝑢𝑚𝑒𝑛:
𝑑𝑎𝑡𝑎[′𝑏𝑜𝑙𝑙_𝑠𝑢𝑝′] = 𝑑𝑎𝑡𝑎[𝑛𝑜𝑚𝑏𝑟𝑒_𝑐𝑜𝑙𝑢𝑚𝑛𝑎_𝑠𝑚𝑎] + (𝑐𝑎𝑛𝑡_𝑑𝑒𝑠𝑣𝑖𝑜𝑠 + 𝑑𝑎𝑡𝑎. 𝑝𝑙𝑢𝑠_𝑑𝑒𝑠𝑣𝑖𝑜𝑠) ∗ 𝑑𝑒𝑠𝑣𝑖𝑜
𝑑𝑎𝑡𝑎[′𝑏𝑜𝑙𝑙_𝑖𝑛𝑓′] = 𝑑𝑎𝑡𝑎[𝑛𝑜𝑚𝑏𝑟𝑒_𝑐𝑜𝑙𝑢𝑚𝑛𝑎_𝑠𝑚𝑎] − (𝑐𝑎𝑛𝑡_𝑑𝑒𝑠𝑣𝑖𝑜𝑠 + 𝑑𝑎𝑡𝑎. 𝑝𝑙𝑢𝑠_𝑑𝑒𝑠𝑣𝑖𝑜𝑠) ∗ 𝑑𝑒𝑠𝑣𝑖𝑜
𝑒𝑙𝑠𝑒:
𝑑𝑎𝑡𝑎[′𝑏𝑜𝑙𝑙_𝑠𝑢𝑝′] = 𝑑𝑎𝑡𝑎[𝑛𝑜𝑚𝑏𝑟𝑒_𝑐𝑜𝑙𝑢𝑚𝑛𝑎_𝑠𝑚𝑎] + 𝑐𝑎𝑛𝑡_𝑑𝑒𝑠𝑣𝑖𝑜𝑠 ∗ 𝑑𝑒𝑠𝑣𝑖𝑜
𝑑𝑎𝑡𝑎[′𝑏𝑜𝑙𝑙_𝑖𝑛𝑓′] = 𝑑𝑎𝑡𝑎[𝑛𝑜𝑚𝑏𝑟𝑒_𝑐𝑜𝑙𝑢𝑚𝑛𝑎_𝑠𝑚𝑎] − 𝑐𝑎𝑛𝑡_𝑑𝑒𝑠𝑣𝑖𝑜𝑠 ∗ 𝑑𝑒𝑠𝑣𝑖𝑜
𝑟𝑒𝑡𝑢𝑟𝑛 𝑑𝑎𝑡𝑎. 𝑑𝑟𝑜𝑝𝑛𝑎(). 𝑟𝑜𝑢𝑛𝑑 (2)

Vamos a ejecutarlo con el ajuste de volumen y sin él:


𝑏𝑜𝑙𝑙𝑖𝑛𝑔𝑒𝑟(′𝐴𝑀𝑍𝑁′, 𝑎𝑗𝑢𝑠𝑡𝑎𝑟_𝑣𝑜𝑙𝑢𝑚𝑒𝑛 = 𝑇𝑟𝑢𝑒)

𝑏𝑜𝑙𝑙𝑖𝑛𝑔𝑒𝑟(′𝐴𝑀𝑍𝑁′, 𝑎𝑗𝑢𝑠𝑡𝑎𝑟_𝑣𝑜𝑙𝑢𝑚𝑒𝑛 = 𝐹𝑎𝑙𝑠𝑒)


Vamos a graficar la media móvil del precio de cierre ajustado con sus correspondientes bandas de
bollinger:
𝑏𝑜𝑙𝑙𝑖𝑛𝑔𝑒𝑟(′𝑇𝑆𝐿𝐴′, 𝑎𝑗𝑢𝑠𝑡𝑎𝑟_𝑣𝑜𝑙𝑢𝑚𝑒𝑛 = 𝑇𝑟𝑢𝑒). 𝑖𝑙𝑜𝑐[−250: , −3: ]. 𝑝𝑙𝑜𝑡(𝑙𝑜𝑔𝑦 = 𝐹𝑎𝑙𝑠𝑒, 𝑓𝑖𝑔𝑠𝑖𝑧𝑒 = (15,6))

𝑏𝑜𝑙𝑙𝑖𝑛𝑔𝑒𝑟(′𝑇𝑆𝐿𝐴′, 𝑎𝑗𝑢𝑠𝑡𝑎𝑟_𝑣𝑜𝑙𝑢𝑚𝑒𝑛 = 𝐹𝑎𝑙𝑠𝑒). 𝑖𝑙𝑜𝑐[−250:, −3: ]. 𝑝𝑙𝑜𝑡(𝑙𝑜𝑔𝑦 = 𝐹𝑎𝑙𝑠𝑒, 𝑓𝑖𝑔𝑠𝑖𝑧𝑒 = (15,6))

Ahora crearemos el oscilador o indicador llamado “BANDS BOLLINGER %B” descripto al inicio
de la sección:
Relative Strenght Index (RSI)
Este indicador es por sí sólo un indicador que brinda señales de compra y venta. El indicador es un
oscilador de medias móviles de velas que suben contra velas que bajan, por ende, compara la
“potencia” de las velas verdes contra la “potencia” de las velas rojas. El indicador oscila entre 0 y
100, y brinda señales de venta al estar cercano a 100 y señales de compra al estar cercano a 0.
Concretamente, este indicador se define como se describe a continuación:
“MA” es la media móvil. En este caso se utiliza el precio actual versus el precio inmediatamente
anterior, es por esto que deberemos utilizar el método shift(), indicando en su argumento el número
1 para indicar que se desea el valor inmediatamente anterior. Como alternativa, se puede utilizar el
método diff(), que permite calcular la diferencia entre el valor actual y otro anterior
correspondiente al período que se indica en el argumento, es decir, si colocamos 1, hará la
diferencia entre el precio de hoy y de ayer. Finalmente, la media móvil exponencial tiene como
función ewm(), donde el argumento es la ventana móvil (entre sus argumentos se puede especificar
el tipo de ponderación que se desea, sólo se debe mirar cuáles tiene con la función help()).
Veamos cómo lo hizo el profesor sin definir función:
Ahora veamos cómo construir el indicador sin usar ewm():
Para obtener una señal de compra y venta deberemos fijar límites, uno superior y otro inferior.
Estos límites podemos testearlos de tal modo de elegir aquél que maximice la ganancia del período
bajo estudio.
Ahora creemos la función:
CONSEJO
Como en la sección donde mostramos que podemos utilizar una función como argumento de otra,
aquí podemos hacer lo mismo, preparando individualmente las funciones correspondientes a cada
indicador técnico, y luego construyendo otra función donde sólo indiquemos el ticket y el tipo de
indicador a calcular. Veamos lo que hizo el profesor:

Moving Average Convergence Divergence (MACD)


Este indicador permite anticipar cambios de tendencias, indicándolas cuando la media móvil más
rápida cruza a la más lenta. En este sentido, si lo hace de abajo hacia arriba la tendencia cambiará
hacia alcista, y si lo hace de arriba hacia abajo, cambiará a bajista. Concretamente, el indicador se
obtiene como se describe a continuación:
El profesor escribió el siguiente código:

Restaría crear el indicador que permita contar con señales de compra y venta.
Ahora construyamos la función:
Cruces de medias
Estos son los cruces de medias móviles, generando la señal de compra cuando la media rápida
cruza de arriba hacia abajo a la lenta, y la señal de venta cuando la cruza de arriba hacia abajo.
Veamos la función:
Graficándolo:

Aquí vemos que podemos tomar señales de compra y venta en el punto cero, o también, tomando
límites superiores e inferiores de acuerdo a esta serie que está en la gráfica, definiendo dichos
límites como aquellos que maximizan la ganancia de la operatoria.
Gráficos de correlaciones
Estudiar las correlaciones en análisis técnico permite evaluar las señales de compra y venta, por
ejemplo, con las tendencias de precios. Para esto, comparamos la tendencia del precio con la
tendencia del indicador utilizando la correlación. La tendencia del precio la calculamos como un
cociente entre el precio de cierre ajustado de “tantas ruedas en el futuro” y el precio de cierre
ajustado actual menos 1.
asdasd
CUARTA PARTE: BACK-TESTING
Backtesting
¿En qué consiste el backtesting? Con este nombre se señala la evaluación de la estrategia de
trabajo/trading aplicándola a la serie de precios. En otras palabras, se elige un período de tiempo
sobre el cual se aplicará la estrategia. Luego se evalúa el desempeño de la estrategia.
Por ejemplo, si la estrategia de trading consiste en comprar y vender con cruces de medias móviles,
su backtesting será aplicar dicho cruce sobre una base de datos/serie de precios pasados, valga la
redundancia. Por lo tanto, el backtesting es una evaluación necesaria pero no suficiente para
evaluar el desempeño de una estrategia de trading.
En resumidas cuentas, el backtesting sirve para:
 Descartar ideas que no van.
 Comprender la sensibilidad de una idea ante cambios en sus parámetros. Retomando el
ejemplo del cruce de medias, la sensibilidad refiere a cómo cambia la ganancia ante
cambios en la venta móvil de cada media, y también, a cómo cambia la ganancia ante el
reemplazo de la media móvil simple por una exponencial.
 Para entender qué tan “portable” es la idea. Una idea/estrategia de trading es “portable”
cuando puede aplicarse sobre dos o más activos financieros, en otras palabras, si la
estrategia sólo sirve para AAPL y no para otros activos, no será “portable”.
 Evaluar las razones por las cuales sí funciona y no funciona. Como se indica, el
backtesting permite inspirar hipótesis sobre las razones que explican el por qué no funciona
la estrategia, o por qué sí lo hace.
En general, una estrategia con muchos parámetros no es “portable”. En paralelo, el backtesting NO
sirve para:
 Aprobar una idea o asumir que es rentable. Como se mencionó, es una evaluación
necesaria pero no suficiente, pues el pasado no asegura el futuro.
 Optimizar la parametrización (overfiting). Se sostiene esto por la misma razón esgrimida
en el punto anterior, que algo haya funcionado antes no es garantía para el futuro.
 Para buscar activos donde la idea sí funciona. No tiene sentido plantear una estrategia y
buscar los activos para los cuales funciona. El trabajo debe enfocarse y adaptarse a la
realidad, es decir, se plantea una estrategia que deberá adoptar valores para sus parámetros
de acuerdo al patrón oculto en la serie de precios, la estrategia se adapta a los datos.
Entonces, el backtesting se utiliza para realizar un análisis de sensibilidad, que permitirá conocer el
rango de variación de los parámetros a partir del cual o dentro del cual la ganancia de la estrategia
de trading mejora. En otras palabras, en lugar de buscar un valor óptimo para los parámetros, se
busca un rango de valores, o sea, un rango óptimo.
NOTA: Riskmanagement.
El manejo de riesgos consiste en analizar cuál es la pérdida máxima para determinado período.
Esto dependerá de la exposición al tipo de riesgos y de la distribución de frecuencias de la serie
del activo analizado. En este sentido, al identificar los riesgos es posible parametrizar el análisis,
pero también, sólo se puede realizar un análisis de probabilidad a través de un Montecarlo, con el
cual se cuantificará la máxima pérdida posible.
Con un análisis de Riskmanagement se inspiran estrategias de cobertura utilizando derivados.

Tipos de Backtests
A grandes rasgos se identifican tres grandes tipos de backtesting:
 Enfoque matricial. Este es un análisis discreto, donde el análisis se realiza a partir de
diferentes momentos del tiempo. Por ejemplo, habiendo tomado posición larga, se vende
luego de determinada cantidad de ruedas o en la rueda donde se cumple determinada
condición sobre los precios de cierre. En estos casos, el momento es el precio de cierre
(por ejemplo), y el análisis se realiza sobre estos, sin importar la vela o el resto de precios
que han ocurrido durante el día.
 Enfoque even-driven. Este enfoque se concentra y opera sobre cada orden de compra/venta
que se realiza en el mercado.
Los elementos en común entre los enfoques son las “condiciones de realismo”, que en general, no
son tenidos en cuenta en los primeros backtests. En este sentido, aunque no se tengan en cuenta, es
importante saber que son elementos presentes en la realidad, y por ello afectan las ganancias de la
estrategia. Veamos algunos ejemplos:
 Tipos activos/takers ¿Que condicion impongo para volúmenes/ventanas de tiempo? Por
ejemplo, al establecer como condición de compra/venta el cruce de medias, deberemos
especificar el precio de compra venta, y para esto deberemos tener presente el libro de
órdenes. En concreto, los precios de compra y venta que se operan en el mercado junto a
los volúmenes ofertados serán otro conjunto de datos a analizar, pues será en función de
dicho rango que deberemos establecer el precio para operar.
 Tipos posivos/makers ¿Que % de ejecución bajo X Circunstancias asumo?
 En gral ¿hubiera afectado mi orden al mercado? ¿como lo se? ¿como lo valido? ¿que pue
do asumir?
 Costos tranasaccionales, comision, derechos de mercado, spread, impuestos, costos infrae
str, mkt data etc
Etapas de un backtest
En esta cuarta etapa veremos el backtest básico y de sensibilidad, no obstante, es importante
conocer el resto. Veamos.
 PreBackTest (Etapa de Research). El objetivo es plantear todo tipo de idea posible,
verificar antes que nada la correlación entre los inputs planteados y la reacción del
mercado, etc. La idea es plantear la mayor cantidad de hipótesis posibles, ser amplio en la
visión y riguroso en al momento de señalar para qué sirve y para qué no sirve, ante la duda
dejar para validar más adelante.
El siguiente conjunto no necesariamente debe desarrollarse completo, en otras palabras,
hay etapas que pueden omitirse, por ejemplo, el análisis de correlación. Veamos cada
etapa:
o Armado de tablas de indicadores potenciales. Se seleccionan los indicadores que
permitirían cumplir con la hipótesis de trabajo.
o Planteo de "Racional" o Hipótesis de trabajo. Aquí se responde cuándo vamos a
comprar y cuándo a vender. En esta etapa se estructura la estrategia de trading, los
pasos lógicos para establecer las señales de compra y de venta, por ende, se
establecen las razones/criterios. Por ejemplo, la hipótesis de trabajo es comprar y
vender anticipando la tendencia, de tal modo que nos subiremos a ella cuando
comience a desarrollarse, y saldremos de ella cuando pierda fuerza.
o Análisis de correlación
 Regresiones inputs/forwards. Por forward se entiende a precios futuros,
por ende, se estudia la correlación entre el indicador de trading y los
precios forward.
 Algos de clasificacion o probabilidades de suba o baja en funcion de
inputs.
o Tabla de posibles trades pasados el racional de trading.
o Tabla de resultados o reporting básico.
 % de trades positivos y negativos
 Esperanza matemática del método
 Tiempo comprado / libre
 Backtest básico. El objetivo principal es empezar a validar en forma rápida la
conveniencia de aplicar un método algorítmico, tanto por el riesgo asumido como contra el
benchmark de su mercado y la variabilidad en diferentes contextos, épocas etc.
Este es similar al realizado en el trabajo final de la especialización en mercado de capitales.
La esencia de toda estrategia es ganarle al benchmark, y ganarle a la estrategia de “buy and
hold”. Entonces, este backtest básico es un informe compuesto de los siguientes elementos:
o Trades en un grupo de activos, en un rango de parámetros.
o Tabla de resultados intermedia.
 Resultados año a año
 Comparación con el buy&Hold
 Comparación con el banchmark
 Ratios de riesgo (Sharpe, Sortino, etc)
 Backtest más profundo: Análisis de sensibilidad. El objetivo principal de esta etapa
es el análisis de cada parámetro del sistema, ver como varían los resultados variando todo
tipo de parámetro, donde aumentan o disminuyen la cantidad de señales, de eventos
positivos, negativos, extremos, medios, largos, cortos, etc. Claramente es la etapa más
abierta que puede dar pie a volver a la etapa de research y empezar a replantear
indicadores.
Habiendo superado la evaluación básica, se procede a realizar el análisis de sensibilidad
con el objetivo de establecer el rango de valores para los parámetros, y para conocer las
razones detrás del éxito y fracaso de la estrategia. Veamos:
o Parametrización de variables.
o Cambio de indicadores.
o Uso de grupos de control.
 Análisis de portabilidad. Es el anteúltimo filtro del método, es la prueba de fuego al
overfiting, aquí se pone a prueba el método frente a diferentes mercados, a diferentes
timeframes, a diferentes activos o grupos de activos etc., obviamente no necesariamente
tiene que ser portable a todo, pero tampoco puede ser un método aplicable a un solo activo,
en un solo timeframe, en una sola época.
Si bien siempre hay variabilidad, un método bien robusto se mueve en rangos acotados, es
decir no puede dar lo contrario en un activo que en otro, puede tener un desempeño un
poco mejor, un poco peor, etc., de hecho si construyen una distribución de rendimientos a
varios activos, mientras menor desvió del rendimiento más robusto el método o mas
portable entre activos.
En este punto resultan de mucha utilidad los algos de clusterizacion, pues permiten ver
comportamientos en diferentes clusters (que pueden ser regímenes de volatilidad, volumen,
tendencias, etc..).
Habiendo determinado los elementos fundamentales detrás del activo analizado, se procede
a seleccionar otros activos similares para evaluar la “portabilidad” de la estrategia. En este
sentido, Juan Pablo (profesor) señala que, si de 500 activos la estrategia sirve para 100
activos o más, será “portable”, en cambio, si sirve para 20 de 500, no será “portable”. En
este sentido, en el caso de los 100 de cada 500 habrá que evaluar qué tienen en común
dichos activos, por ejemplo, podríamos ver como resultado de este análisis, que la
estrategia sirve para los activos con determinado nivel de volatilidad o de volumen. Ahora,
si de esos 100 de 500 activos no se encuentra al menos un elemento común, la estrategia
tampoco será “portable”. Veamos:
o Riesgo de overfiting
o Cruce y armado de matrices de resultados
o Matrices de correlación cross mkt
o Matrices de correlación cross time-frame
o Cauterización por regímenes (volatilidad, épocas, ciclos etc.)
 Backtest Avanzado. Esta es la etapa final, aquí se valida la viabilidad en cuanto a
liquidez, costo transaccional, spreads, posibles fallas del mercado, apis, tiempos,
volúmenes, reglamentaciones, etc. También se valida la exposición real y el manejo de
posición buscando un tamaño de posición óptima al riesgo a asumir, esto puede estimarse
vía montecarlo o modelarse con criterios como Kelly.
o Manejo de posición/riesgo, exposición óptima (Kelly, Montecarlo etc.). Refiere a
testear dos estrategias en una, por ejemplo, una cartera compuesta por otras dos,
una que representa el 95% del capital y que tiene un bajo riesgo, y el 5% restante
(la otra sub cartera), que es la más riesgosa.
o Factibilidad técnica (volúmenes, liquidez, spreads, fallas, tiempos etc.). Esto
implica incorporar los costos de transacción, la posibilidad de operar los
volúmenes deseados dada la profundidad del mercado, etcétera.

Un ejemplo sobre la estructura de un informe/reporting básico de “Pre – Backtest + Backtest


Básico” es el siguiente:
En el informe se puede leer “Método + Free Risk”, el “Free Risk” hace referencia a la tasa libre de
riesgo, quien marca un piso de rentabilidad. Esta tasa se tiene en cuenta porque en muchas
oportunidades, la estrategia planteada nos habrá dejado fuera del mercado a la espera de una señal
de entrada y, hasta este momento deberemos colocar el capital en algún activo con riesgo bajo, por
ejemplo una caución. Si no hacemos esto, el capital inmovilizado tendrá un costo de oportunidad
que no estaremos aprovechando.
Juan Pablo (profesor), señala que en general, las estrategias que siguen tendencia sirven para
“zafar” de los malos resultados, es decir, se caracterizan por perder contra el “buy and hold”
durante los años buenos, sin embargo, durante los años malos son una excelente alternativa. No
obstante, durante los años malos, la estrategia “buy and hold” presenta grandes pérdidas, mientras
que las estrategias de trading no, de hecho, pocas veces presentan pérdidas, y cuando lo hacen,
éstas son significativamente menores. Por ende, en general las estrategias de trading son buenas
para recortar pérdidas.

Diferencia entre Backtesting y simulación


La simulación tiene por objetivo representar del modo más fiel posible la realidad, para proyectar
variables endógenas (por ejemplo, para esto se establece como hipótesis una distribución de
probabilidad para la serie de precios). En cambio, el backtest utiliza como realidad el pasado (la
base de datos) y como ya se dijo, el pasado no garantiza el futuro.

Tipos de Bots de trading


Bots de trading:
 Bots de Colocación de grandes órdenes, para no alterar precio. El objetivo de este bot es no
mover el precio, no es la búsqueda de ganancia.
 Bots de Colocación de órdenes, para obtener el mejor precio
Bots de Market Making. Estos bots buscan ganancias capturando spreads de las puntas de
las velas. Esto claramente requiere estar atento al libro de operaciones durante el día.
 Bots de Arbitrajes en un mercado
 Bots de Arbitrajes entre mercados
 Bots de Arbitrajes estadísticos (en un mercado o entre mercados)
 Bots de Hedging
 Bots de Balanceo de carteras (optimización, riesgo, etc)
 Bots de ruteo para seguimiento de índices (fondos, etfs)
 Bots de scalping
 Bots de swing trading. Un ejemplo de éste es el cruce de medias.
Bots estratégicos:
 Bots de detección de otros bots
 Bots de risk Management
Enfoque matricial: Armado de las matrices básicas
Esto implica construir la matriz de la estrategia, evaluar su desempeño para las posiciones de la
estrategia y, si su desempeño es aceptable, construir la matriz de trades. Con esta segunda matriz

Se construye, a continuación, una estrategia de trading sobre cruce de medias. Preste atención al
uso de la función shif(). Recuerde que esta función señala el valor anterior al actual, y en su
argumento se señala con un número qué tan atrás está ese valor pasado respecto del actual. Si no
usáramos el shift() en la línea señalada con un comentario, entonces estaríamos cometiendo el error
de establecer la estrategia de trading con datos que aun no han ocurrido. En otras palabras, al día de
hoy, nosotros tomamos una u otra posición con los datos de ayer, no los de hoy, pues estos aun no
existen. Esto es lo que nos permite representar el uso de esta función shift().
En paralelo, la línea de código que crea la variable “cruce”, establece un valor superior a 0 para
general la señal de entrada, no obstante, esta puede cambiarse para que sea mayor a 1% para tener
mayor seguridad, o menor a 1% para adelantarnos al cruce, o utilizar otros porcentajes. Es decir,
colocar 0 es tan arbitrario como colocar cualquier otro porcentaje, queda todo al criterio del
analista.
El siguiente código tiene por objetivo utilizar como estrategia de tranding el cruce de medias, por
ende, a partir de la serie de precios del papel de interés, se crean las variables requeridas, para
posteriormente generar las señales de compra y venta pertinentes. Es a partir del momento donde se
tienen estas señales que estamos en condiciones de realizar el pre backtesting, el backtesting
básico, y el análisis de sensibilidad.
Comencemos con el código base que permite crear las señales de compra y de venta dada la serie
de precios del activo de interés:
Ahora graficaremos esto para poder realizar una inspección visual de lo que creamos, para ello
deberemos escribir el siguiente código:

Vea que las primeras tres líneas permiten acotar la serie de precios, y crean las series de precios
que se corresponden a los que se pagaron al entrar en el papel, y los precios que se cobraron al salir
del papel. Las siguientes líneas corresponden al código de la gráfica, quien se ilustra a
continuación:
Ahora evaluemos esta estrategia, comencemos indagando la tasa nominal correspondiente a las
posiciones compradas versus las posiciones vendidas. Para esto evaluaremos la variable “estado”
(comprado = “in”, vendido = “out”), sumando las variaciones diarias del precio de cierre ajustado.
Veamos:

Dos son los errores de proceder de este modo son: 1) estamos sumando posiciones largas sin tener
en cuenta la duración de cada una, y 2) sumamos variaciones diarias cuando en realidad deberías
multiplicar factores de capitalización. No obstante, Juan Pablo (Profesor), afirma que este análisis
interno es válido como primera evaluación superficial de la estrategia de trading, por ende, la
misma tendrá un desempeño aceptable si la suma de las variaciones diarias en la posición
comprada es superior a la posición vendida. Estos errores se solucionan tomando variaciones
logarítimicas y con ellas construyendo factores de capitalización, que luego de utilizan para
calcular la multiplicatoria (tasa efectiva, de este modo no importará la extensión de la posición
comprada).
Lo siguiente es evaluar la cantidad de ruedas en la que mantuvimos una posición de compra versus
la cantidad de ruedas en las que se mantuvo la posición vendida. Veamos:

A esta estrategia le falta incorporar los días que se estuvo fuera del papel, pues se asume que en
esas ocasiones hicimos tasas con cauciones, o se realizó otra inversión. Luego de esto debemos
comparar esta estrategia con la estrategia “buy and hold”, para lo cual deberemos crear la variable
de factor de capitalización utilizando la función cumproud() sobre la variación diaria de los precios
de cierre ajustados más uno.
Para evaluar el costo de transacción debemos conocer la cantidad de compras y ventas, quienes
también se deben relativizar respecto a la cantidad total de ruedas. Para esto se escribe el siguiente
código:

Viendo que la estrategia tiene un desempeño aceptable para las posiciones compradas (rendimiento
superior al rendimiento de las posiciones donde no se operó), se procede a realizar la matriz de
trades. Esta matriz permite que veamos directamente los precios de cierre que pagamos al comprar
y que recibimos al vender
Con esta matriz de trades podremos realizar los cálculos que permiten realizar los análisis de
sensibilidad posteriores.
Ideas de parametrización
En esta sección veremos varias ideas que serán presentadas una a continuación de otra:
 Buscamos el mejor momento para anticipar o retrasar la entrada. Sobre el código anterior,
en lugar de generar la señal cuando el cruce es mayor a cero, podríamos pedir que sea
mayor a un porcentaje positivo o uno negativo. Esto lo hacemos y a la par evaluamos la
rentabilidad compuesta anual. La función add(1) está sumando uno a las variaciones
diarias, prod() está multiplicando los factores de capitalización diarios de toda la serie, y
luego se calcula la raíz de 42 (arbitrariamente, pues la serie original tiene aproximadamente
esa cantidad de años, pues comienza en 1980), para finalmente restarle uno y obtener la
TEA. Esta tasa la imprimimos junto al valor del parámetro que la generó, y así
comparamos visualmente para determinar el rango de tasas donde se obtiene el mejor
rendimiento.
Esto puede graficarse para poder apreciar mucho mejor el rango de conveniencia,
colocando la TEA en el eje vertical, y el valor del parámetro en el horizontal. Veamos:

Además de poder establecer el rango de valores para el parámetro, queda claro que lo
mejor es adelantarse al cruce antes que sobrepasarlo (lo cual es lógico).
Ahora calcularemos la TEA de la estrategia “Buy and hold”:
Entonces, comparamos esta TEA de “Buy and hold” contra la TEA de la gráfica según el
valor del parámetro. De este modo elegimos el rango de valores del parámetro que le gana
a esta estrategia. Recuerde que a la estrategia de trading resta incorporarle la tasa de
caución, mínimamente.

 Busquemos el mejor cruce de medias. Este análisis tiene por objetivo lograr identificar el
rango de valores para las medias que se cruzan y generan la señal de compra y de venta.
Veamos el código: El primer ciclo for es para la media móvil rápida, el segundo para la
lenta, y el condicional if es para ejecutar la evaluación. Esta evaluación debe ejecutarse
sólo se la venta móvil de la media móvil rápida es menor a la lenta. La mecánica es la
siguiente: se comienza con media móvil rápida de 10 y lenta de 25, luego se sigue con
rápida de 11 y lenta de 25, luego rápida de 12 y lenta de 25, y así sucesivamente hasta que
la rápida es de 24 y la lenta de 25. Cuando la rápida es de 25, la lenta salta a 27, cuando la
rápida crece a 27, la lenta salta a 29, y así sucesivamente hasta la última evaluación donde
la rápida será de 39 y la lenta de 69.
Con cada combinación de medias rápidas y lentas, las acciones ejecutadas son:
construcción de las medias móviles, construcción de la señal de cruce, construcción de la
variable “estado”, cálculo de la TEA de todas las posiciones compradas, y construcción de
tabla que relaciona esta TEA con los valores de las ventas móviles que las generaron.
Finalmente, se grafica la tabla para poder realizar un análisis visual rápido.
Veamos el código:
Este código sirve para graficar la relación entre la TEA de la estrategia de tranding y el par de
valores para el cruce de medias. Veamos:
Construyendo una cartera
Supongamos que el capital es partido en tres porciones, colocando 1/3 en cada una. Supongamos,
además, que cada estrategia es un cruce de medias, que difieren entre sí por el valor de sus ventanas
móviles. ¿Cómo podemos analizar esto? Con el siguiente código:

Partiendo de esta base, ahora construiremos nuestra cartera, compuesta por estas tres estrategias de
trading. Vale aclarar que, “frisk” es la tasa libre de riesgo, claramente colocada de forma arbitraria.
Se la coloca allí porque en caso de no estar comprados, la rentabilidad diaria obtenida
corresponderá a esta tasa.

Ahora vamos a graficar. Podemos apreciar cómo la rentabilidad de cada estrategia, incluida la
cartera, se ubica en el eje vertical, y las fechas en el eje horizontal. En la siguiente gráfica se
compara la rentabilidad acumulada de la estrategia “buy and hold”, de cada estrategia de media por
separado, y de la cartera. Veamos:
Ahora comparemos exclusivamente la cartera contra la estrategia “buy and hold”:
Análisis de métricas
Con ciertos indicadores podremos cuantificar lo que se observa en una gráfica. Por ejemplo,
siguiendo la línea de análisis de la sección anterior, y en particular la última gráfica, podemos
evaluar la volatilidad de cada estrategia. Entonces, lo primero a hacer es calcular el cambio diario
de cada estrategia, en este sentido, calculemos el que corresponde a la cartera compuesta por las
tres estrategias, vista en la sección anterior, y por la estrategia de “buy and hold”, quien actuará
como el benchmark. Veamos:

Aquí creamos los cambios porcentuales para cada rueda. Ahora calcularemos el retorno acumulado,
el retorno por rueda, y la volatilidad. Esto lo haremos en las siguientes secciones.

Retorno acumulado
Lo calculamos para el período completo de análisis, tanto para la cartera como para el benchmark:
CAGR
Ahora calculamos lo mismo que antes pero lo anualizamos. El CAGR es la TEA:

Veamos el código:

Volatilidad
Ahora calculamos la volatilidad anualizada para cada estrategia:
Indice de Sharpe
Ahora calculamos lo indicado en el título, y lo haremos para una venta móvil (suavizado) y para el
caso acumulado para todo el período. Recuerde que continuamos con el ejemplo brindado en la
sección anterior.
 Para una ventana móvil. Veamos el código para el caso de la cartera:

 Para todo el período de análisis. Veamos el caso de la cartera:

 Anualizado con los retornos diarios. Veamos el caso de la cartera:


Ratio Sortino
El índice Sharpe incorpora la volatilidad castigándola de forma simétrica, es decir, no discrimina
volatilidad en tendencia alcista versus volatilidad en tendencia bajista o en tendencia lateral. En
otras palabras, “sufrir” volatilidad en una tendencia alcista no está tan mal, hasta algunas personas
podrían señalar que no es ningún perjuicio, sin embargo, volatilidad en tendencia bajista sí es mala.
Por ejemplo, en la gráfica siguiente hemos recuadrado tres casos: el índice de Sharpe castiga la
tendencia alcista recuadra en azul en la sección izquierda de la figura (pues el denominador será
grande por la inclinación de la pendiente, que indica mayor volatilidad), lo mismo hará con la
tendencia bajista recuadrada en rojo, sin embargo, en la tendencia lateral (que no es buena, pues no
se gana rentabilidad), la volatilidad es baja y dicha situación no es castigada.

Es por ello que entra en escena la ratio Sortino, incorporando la idea de “volatilidad buena”,
volatilidad en tendencia alcista, y “volatilidad mala”, volatilidad en tendencia lateral y bajista.
Entonces, la ratio Sortino toma sólo la volatilidad de las ruedas donde el retorno por rueda es
negativo (tendencia negativa). Calculemos esta ratio para la cartera y el benchmark.
Observe que se han mostrado dos modos de calcular la ratio de Sortino, la diferencia está en el
denominador (cantidad de observaciones). En el primer caso se toman sólo las ruedas donde el
retorno fue negativo, esto es lo que sugiere la bibliografía, mientras que en la segunda, se toman
todas las ruedas de la serie.

Max DrawDown
Este indicador mide para cada rueda qué tal abajo se está respecto al último techo de precio.
Entonces se calcula como el valor de cada día dividido el máximo histórico hasta dicho momento.
Veamos:

El DrawDown más grande, para el período analizado, lo tiene el benchmark.


Max DrawDown días
Este indicador permite conocer la cantidad de días que duró el máximo DrawDown. Entonces,
primero filtramos del DataFrame aquellos datos donde los precios fueron iguales al máximo
histórico, para después hacer una diferencia con el índice (tiempo).

Correlación: r^2
Esta métrica busca conocer la correlación lineal entre la cartera y el benchmark, en otras palabras,
podremos conocer qué tan parecido son los movimientos de la cartera a los movimientos del
Benchmark. Veamos:
Calmar CAGR
Este indicador relativiza el CAGR respecto a la situación actual del precio en relación al último
techo. La idea es contar con una ratio que sea mayor mientras más grande sea la TEA, y también,
que sea más grande mientras más bajo esté el precio en relación al último techo histórico. Del
mismo modo, será bajo mientras más baja sea la TEA y más cerca esté el precio de su techo. Este
cociente se obtiene del siguiente modo:

Asimetría y Kurtosis
Estos indicadores son explicados en un recuadro en la parte uno de este documento, concretamente,
en la sección llamada “DataFrame: Estadística descriptiva y algo más (3/3)”. Son útiles para
derivados,1 es decir, para estrategias donde las colas importan, o donde hay mucha concentración
en el centro de la distribución de frecuencias (para comprender la razón de esto se recomienda la
lectura del material de derivados del posgrado “especialización en mercado de capitales”). En este
sentido, para estrategias de seguimiento de tendencia no son relevantes. Veamos el código:

1 Una kurtosis alta implica colas pesadas y/o mucha concentración en el centro de la distribución de frecuencias (en
estrategias de salto de volatilidad de usa mucho la kurtosis, dice Juan Pablo). El sesgo es un buen indicador para las
estrategias de neutralización de riesgos (letras griegas).
Rendimientos diario, mensual, anual, máximo, y mínimo
Este conjunto de indicadores permite caracterizar la estrategia de cartera y el “buy and hold”, por
ejemplo, permitiendo conocer el último rendimiento período del mes (mensualizado), y los
rendimientos mensuales más bajos y más altos, para después compararlos con las mismas
características del benchmark.
El código siguiente responde o tiene la estructura que se verá porque la serie utilizada es de
factores de capitalización, lo cual queda en evidencia al revisar todas las secciones pasadas, desde
que se inicia el ejemplo. No obstante la aclaración, lo buscado en el rendimiento diario, la TEM, y
la TEA. Veamos:

Todo esto podemos incorporarlo en una función, veamos:


Veamos el resultado:

Estos son útiles para los gestores de cartera, principalmente los valores máximos y mínimos.

Kelly
Este criterio mide la eficiencia del capital, maximizando el valor esperado del logaritmo de la
riqueza, y es útil para el largo plazo. Es muy útil para opciones, pero más allá de esto, lo que se
busca en términos generales es penalizar las bajas potenciales en el precio del papel, y premiar las
subas potenciales en el precio del papel. Las penalizaciones y premios consisten en colocar menos
y más capital respectivamente, o sea, apostar menos capital cuando el potencial de baja es alto, y
más cuando el potencial de suba es grande.
El siguiente link contiene una variedad de definiciones:
https://en.wikipedia.org/wiki/Kelly_criterion
El código es el siguiente:

Armemos la función:

Riesgo de ruina
El riesgo de ruina es la probabilidad de que un individuo pierda significativas cantidades de
dinero, de tal modo que ya no sea probable su recuperación y que tampoco pueda continuar
apostando/invirtiendo. Por convención, esta probabilidad se la calcula como una probabilidad de
pérdida. Este concepto es general, pues puede calcularse utilizando value at risk (VaR) u otras
medidas.
Esto permite cuantificar la probabilidad de quebrar a partir de los estadísticos media y desvío, y
una distribución de probabilidad muestral o, en su defecto de la distribución de frecuencias. Esta la
medimos siempre a través de una distribución de probabilidad supuesta o una distribución de
frecuencias (serie). Lo que veremos es cómo obtener esta medida suponiendo una distribución de
probabilidad normal, y también, utilizando la distribución de frecuencias que tenemos a mano
gracias a la serie de precios.
Las fórmulas a programar son las siguientes, donde “C” es el capital inicial, quien siempre se
asume igual a uno por convención. Veamos el álgebra:

La web donde puede encontrarse una descripción es:


https://www.investopedia.com/terms/r/risk-of-
ruin.asp#:~:text=What%20Is%20Risk%20of%20Ruin,recover%20the%20losses%20or%20continu
e.
Veamos el código para cada caso a continuación:
 Asumiendo una distribución de probabilidad. Supondremos una distribución de
probabilidad normal:

Para la cartera, el riesgo de ruina nos dice que 1 de cada 20 veces la estrategia nos arruina,
mientras que la estrategia benchmark o “buy and hold”, casi 1 de cada 2 veces nos lleva a
la ruina.
 Utilizando distribución de frecuencias. En estos casos debemos trabajar con la fracción de
la distribución donde los retornos son negativos. En este caso nosotros debemos definir el
tope o situación donde se considera que hemos caído en bancarrota. A partir de esta
definición, calculamos el riesgo de superar dicha pérdida máxima dada la distribución de
frecuencias. Entonces, calculemos la cantidad de rachas o ruedas malas que debemos
atravesar para llegar a esa pérdida máxima a partir de la pérdida media, calculemos U:

Entonces:

.
El riesgo de ruina en este caso se calcula del siguiente modo, es una probabilidad
binomial, donde W y L son probabilidades de ganar y perder respectivamente, U es la
cantidad de días con malas rachas. El cociente termina siendo una probabilidad que, al
estar elevado a U se revela como una variable aleatoria binomial. Veamos:

Esta fórmula tiene en cuenta situaciones donde superamos el tope máximo de pérdida
soportado e indicado al comienzo.
Volvamos a calcular todo de nuevo, pero esta vez también con el riesgo de ruina y para la
cartera y el benchmark, veamos:
Value at risk (VaR)
Esta métrica de riesgo es la más utilizada, y permite conocer qué porcentaje del capital se tiene en
riesgo en determinado porcentaje de las peores ruedas. Este porcentaje determinado de las peores
ruedas es un parámetro que elegimos nosotros. Por ejemplo, en general se fija el cuantil 5%, quien
el retorno que corresponde como límite máximo al 5% de las peores ruedas. Si la distribución de
frecuencias de los retornos es tal que este valor es un -3% entonces, el cuantil éste indica que 5 de
cada 100 veces perderemos un 3% o más del capital.
Veamos esto siguiendo el ejemplo:

También podemos calcular el VaR simulándolo con una distribución que elijamos, por ejemplo,
supongamos una distribución normal, y la más utilizada en finanzas que es la distribución de
probabilidad de jhonsonsu. Veamos estas simulaciones:
El método rvs() genera de forma aleatoria la cantidad de valores que se indiquen en su argumento.
Luego, el método hist() es el histograma, donde le pedimos 100 barras.
Ahora hacemos lo mismo con la distribución de probabilidad Jhonsonsu:

Con el método fit(), y brindando la distribución de frecuencias, le estamos “diciendo” al programa


que calcule él mismo los cuatro parámetros de la distribución de probabilidad Jhonsonsu. En
resumidas cuentas tenemos:

Finalmente, ahora comparamos la estrategia de cartera con la estrategia que representa el


benchmark:
Conditional Value at Risk (CVaR) – Expected shortfall
El VaR tiene un problema, sólo refleja la probabilidad de la “barra” o punto a partir de donde
comienza el cuantil 5%, pero nada nos dice sobre el área que se forma a su izquierda, lo cual es un
problema, porque esa cola podría ser pesada, o no. Para resolver esto utilizamos el CVaR, con
quien calculamos dicha área. En otras palabras, el CVaR es esta área.
Juan Pablo (Profesor) sostiene que resulta polémico calcular el CVaR con valores empíricos, por
eso sólo lo veremos con simulaciones. Antes de pasar al código comprendamos el concepto de esta
métrica: Supongamos que el cuantil 5% de la distribución de probabilidad está indicado por el área
roja, mientras que la zona verde indica el cuantil 95%.

La fórmula para calcular el CVaR es:

Donde la “fi” mayúscula es la función de densidad de probabilidades, y la minúscula es la


distribución de probabilidad. Para más distribuciones se puede leer lo siguiente:
https://en.wikipedia.org/wiki/Expected_shortfall#Johnson's_SU-distribution
Veamos el código:

El método “pdf” es la función probabilidad, y “cdf” es el método de distribución de probabilidad


acumulativa. En este ejemplo se calcula el cuantil 1%, y el -12,28% representa el porcentaje de
pérdida esperada de capital en el peor 1% de los casos.
PayOff Ratio
Es el cociente entre la media de retornos de las ruedas ganadoras y perdedoras. Por ende,
comparamos lo que ganamos cuando ganamos contra lo que perdemos cuando perdemos.

Para el caso de la cartera, vemos que el resultado indicaría que cuando se gana, en promedio se
obtiene dos tercios de lo que se pierde los días malos. En este sentido, el benchmark es mejor, sin
embargo, es necesario realizar una importante aclaración alrededor de esta ratio. Esta ratio no tiene
en cuenta la cantidad de días ganadores, ni tampoco la cantidad de días perdedores. Por ende, esta
ratio permite caracterizar comparativamente una estrategia en función de su benchmark, por
ejemplo, en este caso y sabiendo que la estrategia le gana al benchmark, vemos que la cantidad de
días ganadores es muy importante, ya que la rentabilidad promedio de los días ganadores es menor
a la rentabilidad promedio de los días perdedores.

Profit Factor
Esta métrica sí tiene en cuenta las ganancias totales y las pérdidas totales, de hecho, se calcula
como el cociente entre las ganancias totales y las pérdidas totales. Veamos:
Esta ratio tiene que ser mayor a 1, de lo contrario la estrategia genera pérdidas de capital. Lo malo
de esta ratio es que está expuesta a valores extremos, por ende, podría ser mayor a 1 sólo gracias a
unas pocas ruedas en las que nos fue bien, ruedas donde el resultado fue extraordinario y súper
raro. Por ende, esta ratio está afectada por las colas de la distribución de frecuencias.

Ratios de cola – Rachev Ratio


Veamos tres formas diferentes de calcular la ratio de colas:
 Sólo se tienen en cuenta las puntas (modelo más chouzeano). Este caso es similar al VaR,
en tanto que sólo se toma una foto o barra de la distribución, y no su área.

Siempre es mejor utilizar el logarítmico, no obstante, si estamos comparando entre dos


estrategias, utilizar el lineal no está mal.
 Las medias. En este caso el cociente es el mismo, pero en lugar de utilizar los valores
correspondientes a los cuantiles, se utiliza la media de todos los valores a partir de cada
cuantil. Veamos:
 Con las áreas. Este es el más exacto de los tres. En estos casos se calcula el mismo
cociente, pero utilizando las áreas bajo la curva a partir de cada cuantil. Veamos.

Outlier win/loss Ratio


Mide qué tan fuertes son las voladuras respecto a una suba media, y también, qué tan fuertes son
los derrapes contra una bajada media. Veamos el código:

En este caso vemos que la distribución de frecuencias de la cartera está estirada en favor de los
retornos positivos.
Gráfico comparativo de la TEA
Aquí comparamos el rendimiento anual de la cartera año a año versus el del benchmark. Veamos:

BackTest DashBoard – “Magia”


Lo siguiente no es una librería en sí misma, pues es necesario instalarla con el “!pip”. Una vez
instalada se escribe el siguiente código, con quien se puede obtener todas las métricas que vimos
hasta el momento, e incluso más. Algunas métricas están mal calculadas (bugs), pero en general
están bien. Sirve como para un primer pantallazo, luego, lo mejor sería calcular todo por cuenta de
uno, o seleccionar el tipo de métrica que calcularemos por cuenta propia. Veamos el código:
Hay muchas más gráficas, pero no las copié porque no considera que valga la pena.
QUINTA PARTE: WEB SCRAPING
Introducción conceptual
Hasta ahora estuvimos utilizando información de Yahoo Finance, sin embargo, la información de
esta fuente no es buena, es por ello que consideraremos otras fuentes, quienes requieren realizar
Web Scraping. Esto será necesario siempre que no tengamos acceso a fuentes como bloomberg,
Roiters, etc., por ejemplo, cosa que sería posible al trabajar en un banco. En esta parte del
documento veremos cómo hacer Web Scraping con APIs (“Application Programming Interface”) y
sin éstas.
Antes de adentrarnos en el tema, es importante comprender el vocabulario que se utiliza. Cuando
navegamos por internet, el “cliente” es nuestro navegador o explorador web (Chrome, firefox, etc.).
Cuando estamos viendo contenido en internet a través del “cliente”, quien interactúa con nosotros
es el “backend” o servidores (YahooFinance, Bloomberg, etc.), y nos brindan los datos que estamos
viendo en el “cliente”. En la siguiente figura se ilustra lo que implica navegar por internet, el
FrontEnd es nuestro, mientras que el BackEnd son los servidores que nos proveen de información
cuando navegamos.

El mejor modo de consumir datos al navegar es utilizando APIs o, el “consumo de APIs”. Las APIs
son interfaces creadas por los servidores, y existen para humanos y para bots. La diferencia es
importante, porque para el bots, la estética de la página no importa, sólo importan los datos que se
quieren descargar. En este sentido, los humanos utilizan la interface web, mientras que los Bots
utilizarán un Jason, que son como un diccionario de Python.
Ahora, no todas las interfaces tienen APIs, por ejemplo, cuando se pueden ver en la web, pero no
podemos descargarlos, es porque no tienen APIs. Cuando esto ocurre, debemos buscar la forma de
descargarlos con un Bot utilizando la interface web.
En resumen, hay tres maneras de capturar datos en internet:
 Interface web. Un robot que leerá la página web.
 Consumo de API. Este es el camino serie. Están preparadas para enviar un bot y consumir
los datos. Las otras dos formas pueden generar un error cuando cambia la interface web,
por ende, en ellas es necesario brindar un “mantenimiento” al robot.
 Captura de datos. Aquí se espera que la API interna del servidor envíe los datos al
interface web. Cuando los datos “están llegando” a la interface web, los capturamos.
Estas son las tres que veremos. Juan Pablo (profesor) sugiere usar siempre APIs, si es que existen y
el servidor la ofrece, si no, deberemos analizar alguna de las otras dos opciones. Si no es posible
con APIs, entonces intentaremos capturar datos, y sólo si esto no es posible utilizaremos la
interface web.
Veamos rápidamente las ventas y desventajas de las API y el Web Scraping:
Las siguientes APIs, que no sabemos si continúan en línea, son gratuitas:

GLOSARIO. Términos utilizados en web scraping y uso de APIs.


Acostúmbrense a esas palabritas porque las vamos a nombrar un montón a partir de ahora
 Endpoint: Es una URL (dirección web) a la que vamos a apuntar para que me devuelva un
dato. Veamos un ejemplo:

Lo indicado es un Endpoint.
 Llamados/Requests/Calls: Es la acción de entrar/comunicarse con el Endpoint, es decir,
con la URL.
 Response: Es la respuesta de la API a ese llamado.
 StatusCode: Es un código estandarizado que nos devuelve junto con el Response (abajo
los explico mejor).
 Servidor: Es la compu de la API.
 Cliente: Es la compu nuestra o nuestro programa que hace el request.
 Credenciales: Son las claves o tokens de autenticación.
 Body: Es el cuerpo de los mensajes (contenido) entre cliente y servidor.
 Headers: Son encabezados de los mensajes entre cliente y servidor (A veces aquí viajan
las credenciales).
 Parámetros: Son justamente los parámetros (variables) que necesita el endpoint que le
mandemos en el mensaje, hay opcionales y obligatorios.
 SandBox: Son ambientes de prueba, para testear las funciones sin efecto real (como un
simulador).
 POST, GET, PATCH, DELETE, UPDATE: Son los principales métodos de comunicación
cliente/servidor, a grandes rasgos GET es el más común, POST es más seguro y se usa
para autenticación o para envío de info. sensible ya que cuando viaja por GET podría ser
interceptada más fácilmente.
RATE LIMITS. Todas las APIs tienen esto, refiere al número de llamadas simultáneas o para
un período determinado de tiempo que se tiene. Estos límites están, algunas veces, porque los
servidores no dan abasto con todas las llamadas a la vez.
 Por día, mes, semana
 Por minuto, segundo
 Ponderados según "peso" de cada endpoint
 Por token o por IP.
STATUS CODE. Esto es una comunicación, como una llamada telefónica, en otras palabras,
es un intercambio entre nosotros, el cliente, que queremos el dato, y el servidor.
En general, los códigos de respuesta Http que nos devuelven todas las APIs en cada llamada,
pueden significar que la llamada salió bien o mal. La siguiente tabla muestra el código de
respuesta del servidor y su significado (si salió bien o mal). El código también funciona como
un mínimo estándar que orienta sobre dónde está la falla, en caso de haberla. Estos códigos
siempre son de tres dígitos, y siempre son numéricos. Veamos:

Aclaración: Un error del cliente es un error de nuestro, pues nosotros somos el cliente.
Interface Web. Web Scraping – Creando página de búsqueda
Para hacerlo debemos inspeccionar el código HTML del objeto de interés, por ejemplo, la tabla con
los datos que queremos, los nombres de los resultados que obtenemos al buscar en google, etc. Para
realizar esta inspección, y si tenemos el explorador Google Chrome, sólo debemos hacer clic
derecho sobre el objeto de interés, y luego clic izquierdo sobre “inspeccionar”. Al hacer esto se
abrirá una sub ventana con el código HTML, donde deberemos buscar la “class” del objeto de
interés. Con el código de dicha clase habremos identificado su “nombre”, y con ello podremos
indicarle al robot que enviaremos qué queremos que haga.
Veamos las siguientes imágenes para ejemplificar la inspección descripta:

Hemos buscado “rofex” en google.

Aquí podemos ver que hasta google.com/search? Es la URL de búsqueda, mientras que q=rofex
indica que se buscará dicha palabra. Por otro lado, y siguiendo lo descripto antes:
Ahora hacemos clic derecho sobre el primer resultado, pues es el que “nos interesa”. Vemos que se
abre una venta con la opción “inspeccionar”. Hacemos clic izquierdo y obtenemos lo mostrado en
la siguiente imagen.

Aquí podemos ver a la derecha de la imagen el código HTML y también el código de la clase que
nos interesa: “LC20lb MBeuO DKV0Md”. La siguiente fue la búsqueda que hizo Juan Pablo
(profesor) en julio 2022, la intención es que se vea que lo indicado es un nombre de objeto, pues
tanto ahora (06/12/2022) como en julio del mismo año, tiene el mismo nombre. Veamos la
búsqueda mencionada:
Todo lo que aparezca como resultado de búsqueda “Rofex” será de la misma clase, y por tanto,
tendrá asignado el mismo nombre de clase.
Ahora veamos cómo armar el robot. Importaremos la librería “requests” y escribiremos el siguiente
código, donde la intención es buscar los primeros 100 resultados de búsqueda que aparecen al
buscar “rofex”. Veamos:

Escribiendo “start=0” estamos diciendo que la búsqueda comience desde el 0, luego, al escribir
“&num=” estaremos indicando dónde queremos que termine, por ello es que luego le sumamos las
100 búsquedas. A esto le sumamos lo que queremos buscar. Con este código automatizamos la
búsqueda de lo que queremos encontrar.
Ahora debemos resolver el cómo obtener de dicha búsqueda lo que buscamos. Antes, veamos qué
forma tiene este HTML que obtuvimos:
Interface Web. Web Scraping – Obteniendo lo buscado
Aunque la búsqueda de páginas dentro del HTML creado se puede hacer de muchas formas, la
manera más “cómoda” es con librerías. Veamos el código anterior y sumemos más:
ACLARACION.
Esta parte del código “sirve” para engañar al servidor, pues nos “colocamos” el disfraz de un
navegador, haciéndonos pasar por uno para pedir los datos. La variable “h” contiene los diferentes
clientes/agents, que luego se coloca en el método get() de esta librería:

Por otro lado, imaginemos que deseamos descargar ratios fundamentales de acciones. Para
ejemplificar el Web Scraping de esto usaremos la web de MacroTrends. Veamos:
El código mostrado es posible de lograr lo querido porque la información está contenida en una
tabla en la interface web.
Finalmente, es importante saber que existen técnicas de Web Scraping más complejas como
solución a situación más difíciles. A continuación se copia la descripción hecha por Juan Pablo
(profesor):

Application Programming Interface (API) – Alphavantage, datos


Una API es un puente entre usuario/consumidor de datos y las bases de datos, no solo para lectura,
sino también para escritura. Otro modo de definirlas es mediante la imagen facilitada al inicio de
esta quinta parte del documento, en este sentido, las APIs son intermediarios, pues toman los datos
de la base de datos del servidor y la vuelcan en la interface Web.
Las API públicas son explícitas en la interface web o incluso en el cliente (nuestro navegador),
pero las privadas no, y en estos casos deberemos intentar dar con ellas de algún modo.
Ejemplos muy buenos de APIs con tablas con información financiera son:
 TDA Ameritrade. Este es uno de los brokers más grandes de USA, tiene información de
opciones en tiempo real.
 Tilingo. Tiene información de WebSocket para renta variable, forex, y cripto.
 FED. Tiene información sobre deuda, bonos como Bills, Notas, Bonos, más hojas de
balances de la FED, entre muchas otras cosas.
¿Conviene usar una librería/paquete o usar las APIs con funciones propias? Siempre conviene
saber utilizar las APIs de modo manual, es decir, utilizando sus objetos, funciones y métodos para
armar funciones propias. Esto es importante, porque muchas veces las APIs se actualizan, no así los
paquetes, también porque armar funciones propias permiten personalizar el trabajo, y finalmente,
porque muchas veces, los paquetes tienen bugs.
Todas las APIs tienen su propia política, no obstante y a grandes rasgos, podemos clasificarlas
entre tres grupos, veamos:
 APIs públicas 100%. Estas no piden que el cliente se identifique. Ejemplo, la API de la
FED.
 APIs mixtas (Típico de las APIs de cryptos). Estas APIs exigen que el cliente se identifique
(crear una cuenta) para que pueda acceder a determinados datos, pero para otro tipo de
datos no pide identificación.
 Endpoints públicos para marketData.
 Endpoints privados para manejo de cuenta.
 APIs privadas. Estas funcionan totalmente con autenticación, y para el acceso a algunos
datos el cliente también debe abonar.
 Auth por token simple en request GET (IEX, FPM, Alpha Vantage, Finnhub).
 Auth por token o par de tokens en encabezados con metodos POST o combinacion
POST/GET (Rofex).
 Auth con tokens que expiran cada tanto y hay que ir renovandolos (IOL, Deribit).
 Auth con inicio de session (HitBTC).
 Auth con firma encriptada (FTX).

Caso práctico: API de alphavatage, privada.


Veamos cómo trabajar con APIs a través de un ejemplo sencillo. Lo primero es visitar y leer la
documentación, en este caso debemos entrar en la siguiente página:
https://www.alphavantage.co
No todos los documentos de APIs son tan sencillos como éste, no obstante, la simpleza de éste
permite introducirnos en este mundo. Debemos leer su documentación para conocer qué datos
podemos tomar y cómo llamamos a su API. La API es una función, por ello, en su documento se
presentan sus parámetros, qué significan y si son obligatorios u opcionales, no obstante, las APIs
no se escriben como una función convencional (como las que hemos visto hasta ahora), se escriben
como se muestra a continuación con el siguiente ejemplo:

Donde “function”, “symbol”, y “outputsize” son los parámetros que elegimos y debemos señalar el
valor requerido. El valor requerido es algo que también se busca en este documento de API.
En python, para llamar esta API debemos escribir lo siguiente según el ejemplo ubicado en el
mismo documento:

“json()” es una estructura de datos estándar en las APIs similar a un diccionario. Difieren por
ejemplo en que las comillas deben ser doble, no simples.
El ejemplo de Juan Pablo (Profesor) con esta API es el siguiente. Facilitando la siguiente página
señalada a continuación:
https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&symbol=AAPL&inte
rval=15min&apikey=2RG2NEF3IPXMIPX3
Como se puede leer en la documentación de esta API, la serie de precios intradiarios requiere la
definición de un conjunto de parámetros obligatorios y otros opcionales:
 function: El nombre de la función (en esta API es obligatorio siempre este parámetro)
 symbol: El ticker por ejemplo "AAPL"
 interval: El intervalo entre cada vela (1min, 5min, 15min, 30min 60min)
 apikey: Es nuestra clave, o token como definimos antes
Y una serie de parámetros optativos:
 outputsize: Compacto o Full (aclara que por default compacto devuelve solo 100 datos)
 datatype: Nos da la posibilidad de descargarlo en un CSV (tipo excel) o devolver un JSON
(texto que leerá python), por default es JSON y es lo que siempre vamos a usar, a menos
que sen la API para descargarse excels, pero no tendría mucho sentido
Al hacer Web Scraping, el procedimiento es el mismo, la diferencia es que la interface web
requiere buscar las claves, y uno está a la merced del cambio de la web. En cambio, con una API
todo está preparado para que el bot baje los datos sin mayores inconvenientes.
Las URL siempre tienen la misma estructura:
La URL base es: https://www.alphavantage.co/query
Luego le debemos poner los parámetros, para ello:
 Al primer parámetro lo antecedemos por el signo "?"
 Y para concatenar los otros usamos el signo "&"
Quedaría del siguiente modo:
https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&symbol=AAPL&inte
rval=15min&apikey=2RG2NEF3IPXMIPX3
Para escribir el código que nos permita tomar los datos debemos importar dos librerías:
 requests: Para hacer el llamado HTTP a la API.
 pandas: Para guardar los datos en un DataFrame.
Además, en el caso de esta API, la autenticación implica la solicitud de una API KEY (es un
token), que se ofrece gratuitamente (debemos colocar un correo que ni siquiera hace falta validar)
Teniendo la API KEY debemos identificar el EndPoint de interés a través del cual podremos
obtener los datos que queremos. Este EndPoint lo encontraremos en la documentación de la API.
Dentro de los parámetros de la API está el de API KEY, donde claramente deberemos colocar la
que hemos conseguido al aprobar el proceso de autenticación.
El código es el siguiente:

Vemos que el “r” es un objeto “response”. Para conocer sus atributos, donde veremos que contiene
un “json()”, escribimos dir(r). Veamos:
Esto continúa, el objeto tipo “json()” está más abajo en esta lista.
El “r” que obtuvimos es un objeto de la librería requests, el cual tiene varios elementos y funciones,
veamos algunas:
 text o content: Es el texto plano como lo vemos en la web.
 headers: Los encabezados.
 status_code: Como vimos el código que ya vimos que es 200, es decir que la comunicación
esta ok.
 url: la url.
 cookies: Las cookies.
 json(): El método para obtener el objeto json, o diccionario del contenido.
En este caso, y para si el código funcionó debemos conocer el status_code, y para ello escribimos
lo siguiente:

Como es 200, está todo bien, recuerde que los código que indican algún problema comienzan con 4
o 5. Más adelante utilizaremos los headers, veamos cómo llamarlos para conocerlos:

Esto es útil, porque en requests más avanzados, además de colocar la URL también indicaremos
los headers que queremos, tenga en cuenta que estos son diccionarios, como se aprecia en la
imagen.
Para ver la información que pedimos escribimos lo siguiente, de tal modo de verlo como variable
string:

El formato texto se utiliza en casos extremos cuando lo demás falla. En general, lo utilizado es la
función json(), que al introducirla en python transforma automáticamente el objeto tipo “json” a un
objeto diccionario. En otras palabras, utilizamos un diccionario en lugar de texto o un json.
Veamos:

Lo siguiente es guardar este diccionario en una variable. En este ejemplo se guarda en la variable
nombrada “mi_dicc”. Hecho esto, ahora pasamos esto a una DataFrame, quien deberá ser
transpuesto porque el diccionario tiene como claves las fechas en lugar del tipo de precio. Además,
las claves y las fechas son string, pues son números entre comillas simples, por ende, debemos
trabajar esto también. Finalmente, el orden de los datos está alterado, el primero es el más actual y
el último es el más viejo, y nosotros necesitamos que sea al revés por razones obvias.
Como ejercicio, veremos el paso a paso de este trabajo. Comencemos por la transformación y
transposición, veamos:

Este ejemplo de transposición y transformación de diccionario a DataFrame es eso, un ejemplo, lo


que haremos ahora es trabajar todos los problemas más usuales en un código más prolijo. En otras
palabras, todo lo señalado ocurre casi siempre, con algunas cuestiones adicionales, por ello es
conveniente armar una función que nos facilite el trabajo. Veamos un resumen de lo que siempre
debemos trabajar:
 Que el código sea más prolijo. En lugar de concatenar la URL entera con todos los
parámetros, lo que podemos hacer y es una buena práctica hacer, es aprovechar la librería
requests, en particular su método “get()” quien tiene como parámetro a “params”, quien
automáticamente reconoce los parámetros para la URL y los separará con ? y & según
corresponda (el parámetro “data” y “json” son similares, o sea, podríamos usar uno de
estos en lugar de “params”, aunque en general es éste última la que funciona siempre, pues
es lo más usual en las APIs) 2 que es un diccionario. Utilizar esto sirve para reemplazar el
uso del concatenando de la URL. Habiendo pedido la información obtenemos el objeto
“json”, al cual le pediremos los valores de la clave que corresponde a la serie de tiempo
con todos los tipos de precios (las velas). Luego, a esto lo transformamos en un DataFrame
y lo transponemos o, como en este caso, indicamos que el índice son las claves. Veamos:

2Detalle: Si este diccionario es plano, entonces sí podemos usar “params”, pero si el diccionario tiene saltos de línea,
deberemos utilizar sí o sí “json” o “data”.
 Meter todo el código en una función. Ahora vamos a crear la función que realice el mismo
procedimiento que el código que acabamos de hacer más prolijo:

Lo probamos:
 Mecanismo para evitar errores (API/Usuario). Por ejemplo, hay errores evitables, como el
colocar mal un ticket, pues en estos casos, el código puede señalarnos que lo hemos escrito
mal. Para ejemplificar este trabajo, provoquemos un error en la función anterior, colocando
mal el ticket:

Para resolver esto tenemos que agrandar el código introduciendo, por ejemplo, un
condicional que revise el status_code y el ticket introducido, para posteriormente tomar
una acción en consecuencia (imprimir una frase que explique qué ocurre, por ejemplo).
 Que el DataFrame tenga valores numéricos. Para cambiar los valores de string a float
aplicamos el siguiente código que sirve sólo aplicado para todo el DataFrame:
Veamos:

Como alternativa, podríamos aplicar un método columna por columna, utilizando el


método de pandas to_numeri(), y colocando como argumento la columna de interés:

También se pueden aplicar alguna de las siguientes alternativas:

 Que el DataFrame esté ordenado cronológicamente. Etc.


 Estandarizar los nombres de las columnas del DataFrame. Etc.
Entonces, construyendo la función y aplicando en ella todas las modificaciones necesarias para
tener como resultado un DataFrame listo para ser usado, tendremos lo siguiente:
Veamos:
Application Programming Interface (API) – Alpaca, uso de bot
La idea de esta sección es mostrar que las operaciones de compra y venta las podemos hacer con un
bot (códigos).
Este es un bróker con comisiones cero del exterior. Ofrece una cuenta que permite simular
inversiones, lo cual es muy bueno porque podemos utilizarla para poner a prueba todo lo que
pensemos. Las cuentas de “mentira” reciben el nombre de “paper account”. Esta cuenta simulada
comienza con cierta cantidad de dinero que siempre podemos modificar y recetear para comenzar
desde cero nuevamente.
La web es la siguiente:
https://alpaca.markets/
La documentación está en:
https://alpaca.markets/docs/
Habiendo entrado en la cuenta de “mentira”, hacia la derecha se encuentra el ícono que nos
brindará la API KEY (al menos se encontraba en dicha ubicación el 12/09/2022), además, también
brinda una clave secreta:

Dentro de la documentación está todo explicado acerca de cuáles son los parámetros
correspondientes para descargar los datos, para colocar órdenes, y para hacer otras operaciones. En
este sentido, en la sección anterior siempre utilizamos la función get(), pues sólo descargamos los
datos, pero ahora utilizaremos otras funciones, pues debemos realizar otras operaciones
adicionales. Por ejemplo, patch() se utiliza para modificar órdenes, delete() para eliminar, etc. Lo
conveniente es crear una función para cada acción, de tal modo que al querer operar se utilice
directamente la función.
Piense que estos códigos pueden combinarse con las estrategias de trading, brindando órdenes de
compra ante señales de compra, y órdenes de venta ante señales de venta. Los códigos de las
funciones que generan automáticamente las órdenes están en el google drive, “clase 11
complemento”. Aquí no se transcribieron por comodidad. En paralelo, Alpaca como otros brokers,
pueden conectarse con TradingView, para ello sólo tenemos que ingresar en la página de
TradingView, elegir “Trading panel” y buscar Alpaca para elegir “connect”.
Además de órdenes, también podemos generar WatchList, es decir, listas que contienen a los
papeles que cumplen determinadas condiciones, fundamentales y/o técnicas. Para armar esto
usamos CRUD, es decir, cuatro comandos: create, read, update, y delete. Estas también tienen
APIs con sus parámetros y demás (primero creamos las condiciones y después creamos la
WatchList).

Scraping por ingeniería inversa


Esta estrategia sirve por ejemplo para casos como el de la web de BYMA
(https://www.byma.com.ar), donde no tienen API pública, y muestran los datos de mercado, pero
no los permiten copiar para descargarlos. Por ejemplo, como se ve en la fotografía siguiente, Juan
Pablo (profesor) muestra cómo al posicionar el cursor sobre la serie de tiempo, el valor del
MERVAL se puede leer, pero no se puede descargar.

Para aplicar esta estrategia usaremos nuevamente las librerías pandas y requests.
Lo que haremos será inspeccionar como antes cuando hicimos Web Scraping, es decir, haremos
clic derecho e “inspeccionar” sobre lo que querramos. En la sub ventana que se abra, iremos a la
pestaña de “Network” (el código HTML está en la pestaña “Element”), como se muestra en la
siguiente figura:
En esta pestaña se pueden ver cuáles son los procesos que se ejecutan en la web, incluso los
asincrónicos (aquellos que cargan luego de la aparición de otros), y para evaluar esto basta con
realizar un “refresh” de la página, lo que se verá es cómo se reinician todos los procesos.

En esta pestaña ordenaremos los procesos según su tipo, “Type”, haciendo clic izquierdo, y
buscamos y buscamos los tipos “xhr”, pues son los datos que provienen de algún sitio.
Luego buscaremos los procesos que en su nombre tengan la palabra “ajax”, pues estos tienen los
datos que buscamos en forma de json. En estos procesos haremos doble clic izquierdo para que se
abra una página o, sólo un clic y copiaremos en el navegador la URL que aparecerá.

Abriremos cada “ajax” para buscar los datos que necesitamos, por ejemplo, el que mostramos en la
imagen anterior. Entonces, en términos de la jerga, todos estos “xhr” + “ajax” tienen datos, son
todos EndPoints que potencialmente nos sirven dependiendo lo que queramos. En otras palabras,
todas estas URL son APIs, pero no son públicas ni privadas, por ende, no tienen documentación.

¡¡¡OJO!!!
Juan Pablo (profesor) menciona que, aun cuando hay APIs privadas, siempre podremos realizar
esta estrategia, sin embargo, para no tener problemas legales nunca deberemos utilizar esos datos
para venderlos u hacer nuestro propio negocio. Si lo hacemos, nos pondríamos en riesgo de tener
problemas legales, siempre que el demandante pueda demostrar que utilizamos sin pagar los datos
suyos en nuestro negocio.

Además de la búsqueda de los datos como se explicó, también tendremos que “adivinar” cómo
funcionan, es decir, cuál es su URL y cuáles son sus parámetros y posibles valores. No obstante,
los símbolos separadores son los mismos siempre, primero “?” y luego & como separador de cada
parámetro.
Por ejemplo, el profesor hizo este trabajo para BYMA y resumió lo siguientes EndPoints:

Antes de escribir el código es importante notar lo siguiente, es posible que con algunos
navegadores aparezca el siguiente mensaje:
# SSLError: HTTPSConnectionPool(host='www.byma.com.ar', port=443) =>
verify=False
Es por esto que la segunda línea del código se escribe utilizando la librería warnings. Esto ocurre
por lo siguiente: Al conectarnos a internet vía HTTP, el servidor siempre busca identificar si el
cliente consulta con navegador o un software diferente (como Python), entonces, para evitar un
error porque estamos con Python, le decimos que estamos navegando con cualquiera de los
navegadores que allí se muestran. No obstante, aun es posible que surja el error mostrado en verde,
pues no contamos con un encriptado que utilizan ellos. Recordar siempre que, la comunicación
HTTP es entre nosotros, cliente, y el servidor, y si la conversación no está encriptada, entonces
cualquiera puede espiar. Para que no surja el error señalado colocamos “verify=False”.
Cuando hagamos esto, aparecerá una advertencia recomendando colocar True. Para que ésta no
aparezca utilizamos las dos primeras líneas del código que se muestran a continuación. El código
sería el siguiente:
SEXTA PARTE: BACK OFFICE
Flujo de tarea
Esta sección la desarrollaremos teniendo como eje el siguiente conjunto de tareas, quienes son un
ejemplo de acciones habituales de backoffice. En este sentido, deberemos imaginar que tenemos un
listado de clientes, y que habitualmente realizamos las tareas listadas a continuación, por ende, el
objetivo de esta sección es desarrollar un código que las automatice. Concretamente, debemos
imaginar que:
 Contamos con un listado de clientes.
 Cada cliente tiene sus papeles en cartera.
Las tareas son:
1. Leer ese listado.
2. Ver para cada cliente si alguno de sus activos está entrando en bear-mkt, y en caso
afirmativo:
a. Preparar reporte de opciones (el derivado) en un rango dado de vencimientos, para
armar estrategia de cobertura (comprar un put del papel que se tiene y está en bear-
mkt).
b. Mandarle un mail avisando potencial bearMkt y adjuntarle listado de cobertura.
3. Mandar un reporte semanal con información sobre la cartera, por ejemplo: variaciones
semanales y mensuales por activo.
4. Adjuntar un gráfico en el mail con variacion de sus activos en base100 para los últimos 30
días.
5. Guardar en carpeta de cada cliente todos los reportes separados (el texto, la imagen, y el
Excel de coberturas).
6. Enviar un mail con el texto, la imagen y los excels de coberturas al cliente.
7. Guardar un ZIP en la carpeta del cliente con todos lo enviado por email, por fecha.
8. Borrar los excels sueltos que habiamos guardado para enviar el email.
Adicionalmente, calcularemos lo siguiente:
 Volatilidad histórica (usaremos 40 ruedas)
 Variación % de los últimos 7 y 30 días.
 El bear-mkt será cuando (condición puesta para el ejemplo):
o Variación semanal es menor a -2%
o Variación mensual menor a -5%
Puts sugeridos:
 Vencimientos de 20 a 90 días
 Volatilidad implícita no más del 20% más alta que la histórica en 40 ruedas
El diagrama del “flujo de tareas” es el siguiente:
Por convención, en los diagramas de flujo siempre se coloca un rombo en la tarea que implica
tomar una decisión, quien por ende, implica codear un condicional (if else). Del mismo modo, los
cuadrados y rectángulos son acciones que no implican el tener que tomar una decisión. El tercer
tipo de figura, Juan Pablo (profesor) la colocó porque sí. Finalmente, todo lo que está dentro de la
línea punteada es un ciclo for o un while, pues el proceso se debe repetir para cada cliente.
Este diagrama de flujo es un ejemplo, las tareas no tienen porqué encadenarse de este modo, de
hecho, si tenemos muchos clientes, es muy probable que muchos tengan las mismas posiciones
sobre los mismos activos, por ende, este diagrama de flujo repetiría tareas de análisis de datos que
serían redundantes luego del primer análisis. Esto tiene solución, pero como el objetivo de esta
sección es mostrar cómo son los códigos para realizar tareas BackOffice, no viene al caso 3.

Leer listado de clientes


Antes de comenzar, crearemos el archivo Excel que contiene la lista de clientes, de lo contrario no
podremos desarrollar el ejemplo:

3 Una posible solución consiste en leer todos los activos que tienen los clientes, para a partir de esto descargar los datos,
realizar el análisis de bear-mkt y, luego de esto, seguir con el resto del código.
Conocer el directorio donde estamos

Vamos a utilizar la librería “os”, que es del sistema operativo. Con esta librería podremos
movernos por los directorios, crear y eliminar carpetas, y también moverlas de lugar. La función
“getcwd()” de esta librería indica el directorio (dirección) donde el programa está ubicado al
exportar e importar archivos (actualmente). En la notebook ASUS al 08/12/2022, tenemos lo
siguiente:
𝑖𝑚𝑝𝑜𝑟𝑡 𝑜𝑠
𝑝𝑟𝑖𝑛𝑡(𝑜𝑠. 𝑔𝑒𝑡𝑐𝑤𝑑())

En paralelo, y dependiendo del sistema operativo, para escribir la dirección de alguna carpeta hay
que utilizar barras simples en algunos casos, en otros barras dobles, y también, barras inclinadas
hacia la derecha, y en otros casos, inclinadas hacia la izquierda. Veamos el ejemplo que muestra
Juan Pablo (profesor):

Cómo crear un directorio

Crearemos uno, pero lo haremos con rutas relativas y no absolutas (una absoluta es aquella donde
se indica paso a pasa el recorrido entre las carpetas, mientras que una relativa indica sólo el nombre
de la carpeta, y el software la busca sólo en el directorio donde estamos ubicados). Utilizar rutas
absolutas y relativas es útil, con cualquiera de ambas. Si sólo utilizamos rutas relativas, siempre
deberemos conocer dónde estamos ubicados, en cambio, si utilizamos absolutas, siempre
deberemos conocer la ruta previamente.
Usando la librería os, tomaremos su atributo “path” (ruta donde estamos), y preguntaremos,
utilizando el método “exists()” determinada carpeta, cuyo nombre indicaremos como argumento
del método. Hacer esto devuelve un resultado booleano, si existe la carpeta tenemos True, si no,
tenemos False.
Para crear un directorio utilizamos el método “mkdir()” indicando en su argumento el nombre de
la carpeta que deseamos generar. Si no es necesario crear la carpeta, entonces pedimos su ruta con
el método indicado antes “getcwd()”.
El código que construiremos tiene que preguntar si existe la carpeta, si no existe la creamos, y si
existe, entonces debe devolvernos su ruta. El código es el siguiente:

Cómo ir hacia determinado directorio

Continuando el hilo de la sección anterior, si el directorio existe, entonces nos moveremos dentro
del mismo utilizando el método “chdir()” cuyo argumento es el nombre de la carpeta. Estando
dentro, pediremos nuevamente la ruta hacia ella con “getcwd()”. Si, en cambio, el directorio no
existe, indicaremos que no está en la ruta designada con la función “getcwd()”. El código quedaría
del siguiente modo:

Este archivo se guarda donde está ubicado el intérprete, y no donde indicamos que se dirija
utilizando el código.
Cómo retroceder en el directorio

Para retroceder utilizamos el mismo método que para avanzar, o sea, “chdir()”, lo que cambia es el
argumento. Para ejemplificarlo, veamos dónde estamos parados, apliquemos el retroceso, y veamos
dónde quedamos:

Cómo leer el listado de clientes

Sólo debemos indicar la ruta, posicionar allí al intérprete, e importar el archivo, que este caso es un
Excel. Veamos:

Cómo armar una lista de activos para cada cliente

No es buena idea iterar DataFrame, pues ello requeriría recorrer una matriz anidando bucles for, es
más sencillo interar un diccionario. Es por esto que crearemos un diccionario para cada tipo de
variable, o sea, para correos por un lado y para activos por el otro. Esto lo hacemos para cada
cliente, y en este sentido, el DataFrame que hemos hecho los tiene cómo índice, por ende, debemos
iterar el índice y pedir que cada fila (cliente) del índice sea asociada con cada valor de cada
columna. Además, con la variable “stocks” deberemos separar los papeles. Veamos el código:

Vemos que los activos son una lista en forma de clave en el diccionario. La idea es imprimir un
mensaje que señale los activos que tiene cada cliente. Hagamos el siguiente código:

Preparando los datos


Haremos una lectura de los datos históricos de cada activo, y calcularemos:
 La volatilidad histórica (usaremos 40 ruedas).
 La variación % últimos 7 días y 30 días.
Condición para mandar reporte con puts sugeridos:
 Variación semanal es menor a -2%.
 Variación mensual menor a -5%.
Puts sugeridos:
 Vencimientos de 20 a 90 días.
 Volatilidad implícita no más del 20% más alta que la histórica en 40 ruedas.
Importando los datos

Para obtener los datos utilizaremos la API de AmeriTrade. Para el caso de las acciones:

Para el caso de las opciones:


Definidas las funciones, comencemos con la importación de los datos:

Analizando los datos

La función que creó el profesor es la siguiente:


Carpetas por clientes
Ahora crearemos las carpetas para cada cliente, por ello debemos saber si ya tienen una carpet o no.
En caso de no tenerla se crea una y se guardan los archivos, y en caso de tenerla se entra en ella
para guardar estos archivos. El código es el siguiente:
Las gráficas en formato jpg se han guardado con el siguiente código, utilizando el método
“savefig()” cuyo argumento es el nombre del archivo:

Enviando correos
Antes de pasar al código, primero debemos dar permiso a Python. Para esto, y partiendo de un
correo Gmail (de google), entramos en la página de inicio de google y hacemos clic izquierdo sobre
el logo de nuestra cuenta. Abriéndose una ventana, elegiremos “Gestionar tu cuenta de Google”,
como indica la imagen:

Entramos e nos dirigimos a “seguridad”:


Un paso obligatorio y relacionado con la seguridad es la activación de la verificación en dos pasos

Debemos crear una contraseña y señalar el tipo de app (que es el conjunto de código que creamos
para enviar los correos). Con esto generaremos una clave de forma automática que sólo sirve para
que Python pueda enviar correos.
El código para enviar correos es el siguiente:
Primero traemos la contraseña y nos posicionamos en el directorio donde están las carpetas de
nuestros clientes:

Ahora comenzamos con el código para enviar los correos. Necesitamos crear las variables
remitente (nosotros que enviamos la información) y destinatario (cada uno de nuestros clientes –
recuerde que en este ejemplo todos los clientes tienen el mismo mail, al no ser así debemos trabajar
con un diccionario e iterarlo). También importamos la librería “smtplib”, que es útil para enviar
correos con Gmail.

Los pasos para enviar el mail son:


1. Primero tenemos que definir el servidor de email. Al usar gmail, utilizamos la librería
mencionada.
2. Utilizamos el objeto “SMTP()”, a quien le pasamos dos argumentos: el servidor
(‘smtp.gmail.com’), y el número de puerto, 587 (utilizado por convención).
3. Iniciamos sesión: “server.starttls()” Esto permite cifrar la sesión punta a punta. Entonces,
entre esto y el tipo de puerto hay seguridad.
4. Nos logueamos. Indicando nuestro correo y la contraseña generada al activar la
verificación en dos etapas. Definimos el correo. El detalle está en el código que se muestra
más adelante.
5. Enviamos el correo. Indicamos el correo del destinatario, el remitente, y el mensaje.
Veamos el código:
Así es como le llegaría (se envió un mail así mismo):

Ahora pasamos este código a un ciclo for, para loguarse sólo una vez, y enviar el correo tantas
veces como cliente, cambiando el mensaje según corresponda:

A este código le hace falta adjuntar los archivos, pero antes de hacerlo es necesario aprender a leer
los directorios. Para esto usaremos el método “scandir()” de la librería “os”, que recorre todo el
directorio. También usaremos is_dir() que nos permite saber si cada elementos del directorio es
otro directorio o sólo un archivo (a nosotros sólo nos interesan los archivos). También utilizaremos
el método Split() indicando como argumento el símbolo a partir del cual se separa el string,
creando así un objeto lista con elementos que se identifican por estar, en su versión original,
separados por ese símbolo. Veamos un ejemplo de Split:
Todo esto nos permitirá identificar los archivos que no son zip. Veamos el código, quien nos dará
como resultado el nombre de los archivos que no son zip en la carpeta del cliente 2:

Ahora ya podemos enviar los correos con los archivos adjuntos. Por la complejidad evidente del
código, y el poco tiempo que queda para ver las clases restantes, sólo se copia el código, pero no se
coloca ninguna explicación. Lo único que se dirá es que la librería “email” es nativa de Python.
Comprimiendo y eliminando los archivos
Para hacerlo debemos importar la librería os y zipfile. Código:
SEPTIMA PARTE: INTELIGENCIA ARTIFICIAL
(IA)
Campos de estudio de la IA
Podemos ordenarlos entre cuatro grandes niveles:
 Campo de estudio de la IA.
 Paradigmas de IA.
o Tipo de Problema a resolver.
 Algoritmos de IA que resuelven estos problemas.

Campos de estudio de la IA
Dentro de los campos de estudio de la IA se identifican tres grandes áreas, no obstante, en el fondo,
todas las IA son pura fuerza bruta, pues requieren de un “aprendizaje” indicado paso a paso,
repitiendo la receta a base de una fuente de datos, hasta que luego de repetir miles de veces los
mismos pasos, aprende. Más allá de esto, las áreas son:
 Inteligencia Artificial. Es un intento por emular la inteligencia humana. Abarca campos de
especialización, como los que se menciona a continuación, incluyendo todo aquello que
aprende del entorno.
 Machine Learning. Son algoritmos de aprendizaje automático. Su aprendizaje se nutre de
la actualización de la base de datos, con la cual se mejora el modelo. Permite resolver
muchos problemas, la industria está madura (hay muchas librerías)
 Redes Neuronales: Deep Learning. Es más específico y técnico que el Machine learning.
Permite acceder a mejor soluciones. La industria está inmadura.
A continuación se ilustra la relación entre éstos campos.
Paradigmas de la IA
Cada paradigma reconoce diferentes tipos de problemas. Se reconocen tres, los primeros dos que
veremos, comparten una característica, tienen una entrada y salida de datos (input y output),
mientras que en el tercero sólo importa el cómo. Los tres siguientes:
 Aprendizaje Supervisado. Es “fuerza bruta”, es decir, si deseamos que el código diferencia
a un perro de un gato damos al algoritmo una base de datos, y con ésta, aprenderá
buscando patrones a partir de los criterios que le indicamos. 4 Técnicas comprendidas aquí
son las denominadas de “clasificación” para variable categóricas, y regresión para variables
numéricas. Sus características/requerimientos del algoritmo son:
o Necesita datos "etiquetados" (diario del lunes).
o Se entrena un modelo para poder predecir.
 Aprendizaje No-Supervisado. En este caso se pide lo mismo, diferenciar un perro de un
gato, y se otorga la base de datos, pero no se indica ningún criterio. Entonces, en base a una
variedad de criterios, elige cuál es el más relevante para clasificar. En este tipo
encontramos la IA llamadas “clustering”.
o No se "entrena", hace un análisis "indirecto".
o No predice, sino que más bien: separa, organiza etc.
 Aprendizaje Reforzado. Este escapa al área de las finanzas, es muy aplicado en la industria
de los videojuegos.
o Se basa en recompensas.
o El aprendizaje es adaptativo.
o Ambientes dinámicos y cambiantes.
o El Output son las "acciones" que llevan a la meta.
Gráficamente:

4 Por ejemplo, el largo de la oreja, la distancia entre oreja y hocico, etcétera. Como complemento, una red neuronal
compararía pixel por pixel.
Tipos de problemas a resolver
Por ejemplo, en el aprendizaje supervisado encontramos diferentes tipos de problemas y por ello
diferentes tipos de soluciones. Con la regresión queremos conocer, por ejemplo, la temperatura que
hará mañana, mientras que con la clasificación querremos saber si una cosa es o no, o también,
obtener respuestas categóricas. En general, los problemas a resolver se clasifican entre los
siguientes:
 Regresión.
 Clasificación.
 Agrupamiento/Clustering. Esto es lo más útil en finanzas.
 Reducción de Dimensionalidad.
La mezcla entre clasificación y agrupamiento también es muy útil en finanzas. Las regresiones
prácticamente no se utilizan en el campo de las finanzas, al menos eso dice Juan Pablo (profesor),
pero todo lo que ellas implican, junto con sus supuestos, forman la base de lo demás.
Entrenamiento supervisado – Regresiones
Antes de aplicar una regresión debemos plantear para qué la queremos utilizar, y luego debemos
construir la base de datos, y luego de construir la base y testearla, haremos un ensayo de pre
sensibilidad. Es por esto que esta sección se divide en tres, una primera donde se construye la base
de datos, una segunda donde se realiza la regresión, y la tercera donde se realiza el ensayo de pre
sensibilidad.
Los algoritmos lo que se hacen es utilizar “fuerza bruta”, es decir, no aplican las fórmulas
vinculadas a la regresión, si no que van probando diferentes valores para los estimadores (betas),
con quienes obtienen un valor predicho y comparan con el valor real. Es así como los algoritmos
calculan el error. Habiendo obtenido un conjunto de pruebas y errores de estimación, eligen el
modelo o valores de estimadores que minimizan el error de estimación.

Research previo
Esta etapa implica construir la base de datos que usaremos para realizar la regresión, en otras
palabras, es necesario definir cuáles son los regresores y cuál es el regresando. Además, también
debemos asegurarnos que los supuestos de la regresión se cumplan, por esto también veremos qué
análisis hacer sobre los datos para después decidir si eliminar el regresor o parametrizarlo, si es que
es un indicador AT (Juan Pablo – profesor – dice que pocas veces se cumplen, pero que no obstante
los modelos se usan igual). Aplicado a finanzas, lo que haremos es regresar la rentabilidad
logarítmica de dos meses (60 días) respecto a seis indicadores, tres tipos de cruces de medias
móviles, un RSI, y dos volatilidades. En otras palabras, nuestra intención es predecir el movimiento
de un papel utilizando estos indicadores. Lo que buscamos con estos modelos es predecir con el
menor grado de error posible, y en este sentido, desecharemos los modelos cuando el porcentaje no
explicado de la variación sea mayor al sí explicado. Veamos la preparación:
La columna “fw” es el “diario del lunes”, mientras el resto de las columnas son el “feature”. Esto es
entrenamiento supervisado, pues como se puede ver en la tabla, brindamos las variables predictoras
y también indicamos qué es lo que ocurrió cuando esas predictoras asumían determinados valores.
Luego de construir la base de datos pasamos a evaluar las condiciones que debe cumplir la
regresión, veamos cada condición en las siguientes sub-secciones.
Multicolinealidad

Existe multicolinealidad cuando un regresor se explica con otro (correlación alta/significativa). Por
ende, debemos evaluar la matriz de correlaciones sobre los regresores o features. Veamos:

Los indicadores han sido elegidos a propósito, para mostrar justamente esto, la alta correlación
entre el RSI y las medias móviles, y el RSI y el otro indicador de volatilidad. La sugerencia de Juan
Pablo (profesor) es eliminar alguna de las dos variables que tienen una correlación igual o mayor al
50% (en sí, no existe un número estándar).
Este análisis también se puede realizar mirando una gráfica de dispersión entre cada una de los
regresores, veamos:
Eficiencia

También podemos mirar la distribución de frecuencias simples de los regresores, siendo que lo
ideal que todos tengan distribuciones diferentes, pues así, habrá mayor dispersión (menos de lo
mismo para explicar algo). Veamos:

En base a esto podemos eliminar uno de dos regresores con distribuciones similares.

Diagrama de caja de regresores

Tenemos que hacer también diagramas de caja para visualizar valores extremos, su dispersión
alrededor de sus medias, y su distribución intercuartículica. Para esto, primero normalizamos todas
las variables (para trabajar con las mismas escalas):
En función de esto, podemos eliminar regresores en base a la cantidad de valores extremos que
tiene.
Autocorrelación

Esto es muy importante en serie de tiempo. La autocorrelación mide un regresor consigo mismo,
pero en el pasado. Lo siguiente es la autocorrelación de un cruce de medias para un lag de una
rueda. Es lógico que el resultado sea el que se observa:

Es lógico esperar autocorrelación alta en los primeros lags, no así en lags más largos. Lo que
buscamos con este análisis es que no exista una especie de “estacionalidad” en las
autocorrelaciones, es decir, que a partir de determinado tiempo o con cierto patrón de
comportamiento, la autocorrelación suba y baje.
Para evaluar lo mencionado utilizamos el gráfico de autocorrelación, veamos:
Donde en el eje horizontal tenemos los “lags” y en el eje vertical tenemos el r-cuadrado. Vemos,
que en este caso, el cruce de medias número 3 debe descartarse.

Realizando la regresión
Como otro repaso, recordemos los supuestos sobre la regresión múltiple, junto con sus tests.
Recordemos que se supone que los residuos se distribuyen como una normal (la distancia entre el
valor predicho y el real), por ende, para que la regresión minimice el error/residuo, debe cumplir
con las siguientes condiciones:
 Linealidad de las variables (regplot). Esto implica que todos los regresores deben tener
una dispersión, versus los residuos, que sea lineal, de lo contrario, habrá algún patrón de
cambio que no se estará capturando con dichos regresores.
 Independencia/Auto correlación de residuos (Durbin/Watson). Esto puede solucionarse
evitando la autocorrelación entre regresores.
 Normalidad en los residuos (test Omnibus c/Kurtosis/skew, Test Shapiro-Wilk etc, qqplot).
 Homocedasticidad (Varianza constante).
 No existe multicolinealidad (heatmap de correlación entre predictores).
 No hay exogeneidad o es débil. Esto significa que no existen otras variables predictoras
además de los regresores elegidos para explicar la variable endógena. Esto es un supuesto
más teórico que práctico.
Para realizar la regresión utilizaremos la librería “statsmodels.formula.api”, y particularmente, la
sub librería “ols”. Para ver su uso y cómo se escriben sus parámetros, veamos la aplicación a la
base de datos que trabajamos al principio:
NOTA. R cuadro ajustado.
Como se mencionó antes, las regresiones prácticamente no se utilizan, no obstante, Juan Pablo
(profesor), sugiere que no nos preocupemos por valores bajos de este, siempre que esté cerca a 0,3
o sea mayor, pues esto significa que podemos explicar al menos la mitad de las variaciones de los
precios, dejando al azar el resto. Esto en finanzas es bastante (dice).

Hagamos unos chequeos sobre de los supuestos sobre el modelo en cada una de las siguientes sub
secciones.

Linealidad de las variables

Para evaluar esto haremos una regresión lineal entre las predicciones del modelo y los valores
verdaderos. La relación debe ser lineal para respetar el supuesto.
El eje horizontal son las predicciones, mientras el vertical los valores verdaderos. Vemos que hay
tramos de linealidad, lo vemos en la siguiente figura:

Vemos entonces que en dicho tramo, el modelo sí cumple con la hipótesis de linealidad.

Independencia de los errores

Veamos:
Normalidad de los residuos

Veamos

También graficamos los valores predichos estandarizados versus los verdaderos, para después
graficarlos según su ubicación en los cuantiles.

Multicolinealidad

Veamos:
Vemos que por multicolinealidad, como vimos antes, convendría borrar la variable “ema_vol” o
“roll_vol”, y por inflación de la varianza (que lo normal sería que sea menor a tres en la tabla de
arriba), vemos que conviene borrar “ema_vol”.

Homocedasticidad

Veamos la gráfica entre las predicciones y sus residuos (eje x predicción, y residuos):
Vemos nuevamente que la zona de la derecha tiene un buen desempeño, no así la zona de la
izquierda. En paralelo, Juan Pablo (profesor) dice que modelos con más – menos un sigma de
varianza es ideal, y por arriba de 2 estamos mal. Aunque lo principal es que se mantenga constante.
Ensayo de pre sensibilidad
En esta etapa buscamos mejorar el modelo, por ende, podemos probar diferentes valores de
parámetros para los regresores (cruce de medias, por ejemplo). Para esto haremos una simulación
de Montecarlo. Luego de esto se usa el modelo para evaluar portabilidad, es decir, se lo aplica a
otros papeles.

NOTA. Overfitting.
Es la situación donde el ajuste del modelo lo hacemos de tal modo que queda casi perfecto para
determinado papel/activo, pero al momento de realizar la evaluación de portabilidad (su
aplicabilidad para otros papales), vemos que no sirve. Entonces, lo que se sugiere al realizar el
análisis de sensibilidad, es que de la distribución de frecuencias de los valores de los parámetros,
se tome un rango que corresponde al más concentrado (pensando en una distribución, sería el
rango cercano a la campana de la distribución). De este modo se aumentan las chances de tener un
modelo portable.

Críticas a las regresiones


Estos métodos reciben las siguientes críticas en general:
 Se presta mucho a interpretaciones muy heterogéneas.
 Se presta mucho a parametrizaciones (tipos) que llevan a resultados muy diferentes.
 Asumen muchos supuestos que en la práctica es imposible que no se violen.
Veamos esto gráficamente con una misma nube de dispersión, donde podemos ver que se pueden
plantear varios ajustes de curvas para los mismos datos ¿Cuál es mejor? Como alternativa, también
podemos establecer intervalos de confianza en lugar del punto sobre la recta, partir la nube de
dispersión y establecer una doble regresión, o establecer regresiones logísticas, entonces y
nuevamente ¿Cuál es mejor?
Entrenamiento supervisado – Clasificaciones
Esta rama de la IA utiliza el método de árbol de decisión, “análisis de componentes principales”
(ACP), y el modelo de regresión logística para identificar, entre un extenso conjunto de variables,
cuáles son las más relevantes para explicar a otra. De esta forma se obtienen chances de ocurrencia,
es decir, dado el conjunto relevante de variables/características identificadas, se puede conocer la
probabilidad de ocurrencia de un valor particular de la variable explicada. Por ende, el modelo se
“entrena” con una base de datos enorme (entrenamiento supervizado). Finalmente, una vez que se
tiene el modelo, podemos evaluarlo (scoring) para conocer qué tan bueno es para predecir.
Lo que hacemos con esto es machine learning, gráficamente:

Para contrastar y comprender mejor, un ejemplo de programación clásica sería lo que hecho con el
cruce de medias, donde brindamos la base de datos y establecemos la regla del cruce para decidir
comprar y vender, para que posteriormente el algoritmo aplique esta regla a la base de datos y nos
señale los momentos de compra y venta. En cambio, con machine learning introducimos la base de
datos y los momentos donde compramos y vendemos, para que el algoritmo cree reglas que estén
acorde.
Los algoritmos que resuelven una clasificación son:
 Arboles de decisión.
 Regresión Logística.
 Máquinas de vectores de soporte.
 Naive Bayes.
 Bosques Aleatorios.
Estos son los algoritmos que veremos en las siguientes secciones. Pero antes de pasar a cada tipo de
algoritmo, es importante que en la base de datos con que se entrene el algoritmo, estén las
respuestas o valores de la variable a explicar, y para esto es importante haber construido predictores
o features. En finanzas, estos predictores pueden clasificarse entre algunas de las siguientes
categorías, y la elección y uso es un arte y parte del marco teórico que se debe realizar en el
research previo:
 Indicadores sobre serie de precios:
o Indicadores tardíos (trend-following).
o Indicadores de saturación (contrarians).
o Indicadores absolutos/referencia: Osciladores acotados (sobre precio o sobre
indicadores).
 Indicadores de flujos (volumen y derivados).
 Indicadores combinados. Por ejemplo, cruce de medias ponderados por volumen.
 Conteos discretos. Por ejemplo, de todos los papeles que se está analizando ¿cuántos están
por encima de su media y cuántos por debajo?
 Estacionalidad.
 Indicadores Estadísticos. Por ejemplo, sesgos, curtosis, varianza, etcétera.
 Referenciales (Benchmarks).
 Ratios y series de Análisis fundamental.
 Sentiment.
 Exógenos al mercado.
Juan Pablo – profesor – señala que el modelo debe trabajarse con al menos 9 de los 10 indicadores,
explicando por qué no está el que falta. En este sentido, al observar los indicadores, y teniendo en
cuenta el trabajo que se hizo para el posgrado (especialización en mercado de capitales), queda
claro que deben estar todos los indicadores (tal vez no el de “sentiment”).
Finalmente, también es vital el tipo de datos, identificando los momentos o épocas de cada uno.
Algunos consejos son, tomar:
 Distintas épocas (por ejemplo, que tengan bear markets, derrapes fuertes, grandes
voladuras, y mercados laterales).
 Variabilidad de activos similares.
 Variabilidad de industrias.
 Variabilidad de países.
 Cantidades de datos acordes al fiting que se pretenda.
En definitiva, tenemos que tener en claro qué queremos hacer, pues esto será la referencia.

Arboles de decisión
Esta técnica busca como resultado “la estrategia” que genera las chances más altas de obtener los
resultados a partir de la base de datos que brindamos. En otras palabras, con esta técnica buscamos
la obtención de una regla/patrón que genere los mismos resultados a partir de una idéntica base de
datos. Dicho de otra manera, dada la base de datos ¿qué regla permite obtener los resultados? La
lógica detrás de esta técnica es la entropía de la información, que más adelante se definirá,
mientras que ahora la ejemplificaremos con un juego de mesa.
En el siguiente juego de mesa, que damos por entendido que se conoce, debemos adivinar al
personaje que ha seleccionado nuestro rival. Para esto realizamos una pregunta por turno, la cual
nos permitirá eliminar a los personajes que no cumplan con las características mencionadas en la
pregunta, por ejemplo ¿es hombre o mujer? Si la respuesta es “mujer” entonces se eliminan a todos
los personajes hombre. En un juego así, la estrategia que más chances de victoria genera es aquella
donde en pocas preguntas podemos eliminar a la mayor cantidad de personajes. Por ejemplo,
preguntar por el sexo permite eliminar una gran cantidad de personajes, por ello, lo que debemos
hacer siempre es realizar preguntas que permitan eliminar muchos personas en un mismo turno.

En el área de finanzas, buscaremos determinar chance de subas y bajas en la cotización, por ende,
el algoritmo preguntaría algo como ¿El RSI es mayor a 20? ¿Es mayor a 40? Y así sucesivamente.
Así procederá con cada feature, teniendo como resultado el siguiente árbol (ejemplo):

Mirando el inicio del árbol, vemos que si el indicador “ema_vol” es mayor a 1,33, entonces
tendremos que esperar una suba en la cotización del precio, pero si es menor o igual a este valor,
entonces tendremos que mirar el “cruce_2”, que si es mayor a 0,798 entonces esperaremos una
suba como lo más probable, pero si es menor o igual, tendremos que mirar nuevamente el
“ema_vol”… y así sucesivamente. Esto tiene un final como se puede apreciar en la segunda figura.
Obsérvese que existe un indicador llamado “entropy”. Cuando éste tiene el valor cero, indica que la
base de datos está completamente ordenada. Cuando es distinto de cero (positivo), indica que
existen chances o, que no hay certeza sobre las subas y bajas. Esta es la clave de la técnica, el
modelo busca aquellas variables y aquellos rango de valor de las variables que permiten bajar más
rápidamente la entropía (tener más certeza). El “riesgo” de trabajo con esta técnica es el de
overfitting (que haya muchas preguntas o ramas), por ende, perder capacidad de portabilidad.

NOTA PERSONAL. Podemos utilizar esta técnica para análisis fundamental, incorporando
indicadores macro.

Preparando la base de datos

El valor a predecir es la variación, a partir de hoy y dentro de cien ruedas, de GGAL, que
llamaremos “fw” (cotización de 100 ruedas en el futuro dividido por la cotización actual menos 1).
Los features que calcularemos son los mismos de antes: dos volatilidades, tres cruces de medias, y
un RSI. Veamos la línea de código:

Para que se comprenda, a riesgo de ser redundante: vamos a intentar predecir los valores de la
columna “fw” a partir de las columnas de indicadores:
¿Cómo escribir este código si queremos que se analice más de un papel? Veamos lo que hizo Juan
Pablo (profesor):

Definiendo qué vamos a predecir

Esto se hace antes de tener la base de datos, así que en esta sección, más que definir qué queremos
predecir, vamos a ver cómo podemos traducir ese objetivo en código. No obstante, y como
ejemplos, lo que podemos buscar predecir son resultados categóricos para clasificar, por ejemplo:
 Un resultado binario, con probabilidades.
 Un suceso de cola direccional, por ejemplo, solamente me interesa predecir cuándo se
vuele más del 50% en un mes.
 Un evento de cola no direccional, por ejemplo, que se mueva más o menos un 20% en una
semana. Juan Pablo (profesor) señala que esto se usa mucho para opciones.
 También se puede contrastar verosimilitud entre predicciones.
También podemos combinar modelos con diferentes features, lo cual serviría para reafirmar las
predicciones entre ellos, y cuando éstas se contradigan, servirá para ponerlos en duda y pensar en
cómo solucionar sus errores. En el modelo que veremos, buscaremos:
1. Modelo 1: predecir si sube o si baja con cruces de medias.
2. Modelo 2: predecir si se vuela con volatilidades.
El código correspondiente es el siguiente:

Aquí creamos la variable “target” y le damos valor cero. Adicionalmente, la segunda línea de
código revaloriza los valores de “target”, colocándoles valor 1 para aquellos que se corresponden
con un “fw” mayor o igual a cero.
Asimismo, guardaremos una copia de la base de datos sin redondear y sin eliminar las filas con
valores “NaN”. Esto nos servirá más adelante, pero para adelantar, esto nos servirá para cuando
tengamos nuevos datos.
Finalmente, creamos dos series, una de respuestas (y o respuestas/labels) y otra de features (x, o
características). Veamos cómo quedó la base de datos “data”:

Caractericemos ahora la base de datos ¿Cuántas ruedas sube y cuántas baja? Veamos:

Mientras más cerca de 50% - 50% esté, mejor será el modelo, pues menos sesgo habrá. Que el
sesgo casi cero es lo mejor, pues en caso contrario, los features estarán muy relacionados con alzas,
en otras palabras, habrá pocos datos para relacionarlos con bajas. En otras palabras, en un modelo
muy sesgado, el vínculo entre features y label probablemente sea espúreo, y sea necesario
incrementar el período de análisis. Esta reflexión tiene detrás la idea de representatividad, que en
este caso debe ser reflejada por la base de datos. O sea, para poder predecir bajas y subas, debemos
tener muchos casos de ambos tipos, para poder compararlos con las features.
Como se mencionó, esto se puede solucionar reemplazando la base de datos, pero también puede
ser modificada a través de un “Oversampling” o de un “Undersampling”. El primero implica
replicar los casos menos frecuentes, o sea, copiar esos datos hasta que el sesgo desaparezca. El
segundo significa eliminar casos del grupo más frecuente, hasta que el sesgo desaparezca.

(Los modelos de árboles de decisión suelen exagerar los sesgos)

Separando datos de entrenamiento de validación

Primero corroboramos que la matriz de features tenga la misma cantidad de filas que el vector de
resultados:

Ahora separaremos la base de datos, pues con una parte construiremos el modelo, y con la otra
evaluaremos su capacidad de predicción, es decir, con la otra parte haremos el test de validación.
En general, se entrena con el 70% u 80% de la base de datos (no hay regla, aunque se dice que para
validar lo mejor es tener dos mil datos o más – por cuestiones estadísticas –). Aquí lo haremos con
el 60%. ¿Cómo realizamos esta separación? Lo haremos de forma aleatoria con el siguiente código
y librería:

Veamos por ejemplo qué hay en “X_train” y “X_test” (validación):


Como comentario, esta técnica de separación aplicada a series de tiempo tiene de malo la
autocorrelación natural de las series, es decir, si en la matriz de entrenamiento quedan valores de
series de hoy, mañana, y pasado, y en la matriz de validación tenemos los valores de las series que
vienen luego de estos días, como la autocorrelación es alta, la validación saldrá con buenos
resultados sólo por la autocorrelación, será una validación espuria. Esto aplica con series de tiempo
como los cruces de media. Ante estas situaciones, podemos partir nosotros la base de datos del
siguiente modo:

Esta caso soluciona la autocorrelación, pues entrenamos con los primeros 7 años (aprox), y
validamos con los 7 años (aprox) siguientes. Y como los cruces de medias se autocorrelacionan en
meses, no tendremos el problema mencionado.

Importamos el modelo

Veamos la librería y cómo es el código para aplicar el modelo de árbol de decisión:

La profundidad es la cantidad de ramas o nodos, o también, la cantidad de preguntas que se hará.


Esta profundidad ¿Cómo se decide? En función de la cantidad de datos, siempre buscaremos tener
pocas preguntas o profundidad, pero si es necesario hacer varias preguntas, tendremos que tener
presente que la cantidad de datos dividido por 2 elevado a la profundidad, nos dirá la cantidad de
datos que se utilizan para determinar las chances por casillero. Por ejemplo, con 5 mil datos y 7
preguntas tendremos 39 datos por casillero (lo cual es mínimamente aceptable para Juan Pablo –
profesor-). Es por esto que siempre es mejor incrementar la base de datos. Los criterios más
conocidos son el de entropía y el de gini. Estos criterios determinan el criterio de optimización. El
criterio de entropía es más robusto.

NOTA. Algebra del criterio de entropía y el criterio de Gini.


Entrenando el modelo

Veamos:

Validando el modelo

Habiendo creado el modelo, ahora debemos validarlo, para ello haremos predicciones utilizando los
features que dejamos fuera al crear el modelo. Luego compararemos los resultados. En este sentido,
recuerde que las predicciones son valores de 0 (cero) y 1 (uno), o sea, rendimiento para dentro de
100 días menores a cero, o iguales o mayores a cero, respectivamente. Este vector de valores lo
guardaremos en un objeto:
Este modelo no sólo puede predecir si el precio sube o baja, también las chances de que ello
suceda. Para obtener las probabilidades escribimos lo siguiente (lo aplicamos para los primeros
diez datos para mostrar un ejemplo):

Ahora resta comparar la predicción con el dato verdadero. En este sentido, señalaremos de un
modo y de otro cuándo la predicción acierte y cuándo no lo haga. Si se predice que sube y sube,
entonces tenemos un acierto positivo, si se predice que baja y baja, tenemos un acierto negativo.
Del mismo modo, si se predice que sube y baja, tenemos un falso positivo, y si se predice que baja
y sube, tenemos un falso negativo. Esto lo podemos resumir en la matriz de confusión siguiente:

En función de la matriz de confusión podemos medir qué tan bueno es nuestro modelo, y
compararlo con alternativas. En este sentido, aunque tengamos falsos positivos y negativos, mirar
su magnitud relativa importa, pues si nos interesan sólo las predicciones bajistas y los falsos
negativos son bajos, entonces el modelo será bueno para estos fines.
Esta matriz la podemos construir con las siguientes líneas de código:
En paralelo, podemos obtener la matriz de confusión normalizada directamente (con porcentajes).
Para esto dividimos los valores por el total de observaciones – las utilizadas para el modelo y las
utilizadas para la validación-, obteniendo un 100% con la suma de los 4 casos. Como alternativa
podemos utilizar sólo las observaciones utilizadas para el modelo, en cuyo caso, en lugar de dar el
valor “all” al argumento, deberemos escribir “pred”, de este modo la suma de las columnas será
100%. Otra alternativa es dividir por los valores verdaderos, por ende, obtendremos el 100% como
la suma de las filas, y el valor del argumento a colocar es “True”. Veamos cada caso:

Ahora grafiquemos:

(OJO que estos resultados son espurios, pues hemos separado la base de datos, entre
entrenamiento y validación, de un modo random, incrementando las chances de validar
espuriamente por autocorrelación. De hecho, si utilizamos la separación alternativa que se
mencionó, el modelo dará malos resultados – dice Juan Pablo (profesor)-).

A continuación mostramos otro método para graficar la matriz de confusión, pues las chances de
que el anterior quede obsoleto son altas (lo sacarán). Veamos:
Con esto ya tenemos el resultado, no obstante, también podemos resumirlo del siguiente modo,
mostrando el sesgo alcista. Esto es, recuerde que en los datos reales, el modelo tenía un sesgo
alcista, es decir, el 56% de los fw erán iguales a cero o mayores. También dijimos que la técnica de
árboles de decisión tiende a exagerar los sesgos, esto es, cuando reproducimos los valores
predichos en la validación, se generará el mismo sesgo un poco más grande. Es lo que ocurre en
este caso, como se ve a continuación:
Prediciendo

El ejercicio que haremos ahora requiere del uso de tuplas, pues el valor predicho se arroja como
una tupla. En este sentido, recuerde que una tupla con un único valor distinto a cero se escribe
como se muestra a continuación:
𝑥 = (1, )
Asimismo, una tupla con lista única se escribe:
𝑥 = ([1,2,3,4], )
Retomemos la base de datos que guardamos al comienzo de este ejercicio:
La idea es tomar los datos que habíamos eliminado por tener corresponderse con algún “NaN”, y
los utilizaremos para predecir. Por ejemplo, utilicemos la última fila de valores de cada feature:

Vemos que el valor predicho para dentro de 100 días es una caída en la cotización, y que la
probabilidad de que esto ocurra es del 57%.

Crítica a la técnica

El problema que tienen los modelos de árboles de decisión son los “clusters”, veamos primero
gráficamente este problema:
Las situaciones como la indicada con un rectángulo verde, son situaciones a las que se asigna la
misma probabilidad a pesar de tener valores diferentes para los features. Esto sucede por la
autocorrelación. Esto también sucedería si se parte de la base de datos con la técnica alternativa (lo
dice Juan Pablo – profesor-). En definitiva, el problema es que está “muy” clusterizado. En
palabras simples, entrenamos con datos similares a los datos con que validamos (esto es lo que
genera la autocorrelación).
Para resolver la autocorrelación debemos estudiar la multicolinealidad entre los features, y la
autorregresión de estos consigo mismos. Habiéndolo analizado, debemos reemplazas/eliminar los
features con multicolinealidad o autorregresión.

Graficando el modelo

Veamos:
Guardando el modelo - Serialización

Veamos:

Con este código guardamos el modelo entrenado bajo el nombre de “modelo_arboles.dat”. Lo que
hacemos luego si queremos usarlo es llamar el archivo, es decir, ya no es necesario hacerlo correr o
crearlo nuevamente. Veamos cómo llamarlo, descarguemos la información y utilicemos los últimos
valores de las series, para con ellos utilizar el modelo y predecir. Veamos:
Oversampling y Undersampling

Supongamos que queremos predecir un evento de cola, por ejemplo, predecir la probabilidad de
que GGAL crezca un 20% o más el próximo mes. En este caso, el label o variable verdadera que
construiremos, tendrá muy pocas observaciones con éxito y muchas observaciones con fracasos,
por la naturaleza del evento (muy pocas ruedas con crecimiento por arriba del 20% y muchas por
debajo). Estaríamos del siguiente modo, con las naranjas como éxito y las azules como fracasos:

En estos casos, el sesgo será muy grande, por ende, el modelo predecirá siempre que el evento no
ocurrirá (recuerde que la técnica de árboles de decisión sobre exagera los sesgos).
Para resolver esto se puede hacer es eliminar datos azules, es decir, eliminar datos donde el
crecimiento fue menor al 20%. Esto se llama “Undersampling”. Como alternativa, se pueden
duplicar o más, las ruedas donde la cotización supera el 20%, a esto se llama “Oversampling”.
Gráficamente:

Ahora ¿Cómo procedemos en cada caso? Veamos cada técnica a continuación:


 Oversampling. Imaginemos que tenemos sólo 50 ruedas donde GGAL sube más del 20%
(“se vuela”), y que tenemos 5 mil donde no se vuela, por ende, necesitamos generar
aproximadamente 5 mil ruedas donde sí se vuela.
o Repitiendo al azar. Repetimos cada dato una cantidad determinada de veces hasta
alcanzar los 5 mil. Para esto podemos utilizar el método “RandomOverSampler”.
Este método no es recomendado para la técnica de árboles de decisión.
o SMOTE. La idea es generar datos de forma aleatoria, de tal modo que respeten un
orden de representatividad. Esta representatividad se entiende la tienen los datos
reales, por ende, se los utiliza para generar los datos sintéticos. Consiste en generar
nuevos datos como el resultado de un promedio entre los datos originales. Trabajo
original en:
https://www.jair.org/index.php/jair/article/view/10302/24590
Gráficamente:
¿Cómo funciona el método? Por ejemplo, tomamos los valores de las abscisas de
cada valor que queremos copiar, con ellos armamos una distribución que
supondremos que es normal y por ende tiene una media y un sigma, y luego
ejecutamos la creación de una abscisa de forma aleatoria utilizando estos valores
como base de la distribución. Lo mismo se hace con las ordenadas. Veamos esto
gráficamente:

o BorderlineSMOTE (Líneas de separación jerarárquicas). BorderLine DOI


DOI:10.1007/11538059_91
https://sci2s.ugr.es/keel/keel-dataset/pdfs/2005-Han-LNCS.pdf
o KMeansSMOTE (c/modelo de agrupamiento KMeans).
o SVMSMOTE (c/modelo de agrupamiento SVM).
o SMOTEN (para predictores discretos).
o SMOTENC (mezcla de predictores continuos y discretos).
o ADASYN (sinteticos adaptativos). ADASYN DOI:10.1109/IJCNN.2008.4633969
https://sci2s.ugr.es/keel/pdf/algorithm/congreso/2008-He-ieee.pdf
 Undersampling. Como el método anterior, en este podemos eliminar datos de forma
azarosa o, podemos seguir ciertos criterios para ello. Veamos:
o RandomUnderSampler.
o NearMiss (usa KNN, busca mas separacion de grupos).
https://www.site.uottawa.ca/~nat/Workshop2003/jzhang.pdf
o ClusterCentroids (Usa KMeans, remueve primero los mas parecidos, busca mas
variabilidad, me gusta mas en la bolsa).
o CondensedNearestNeighbour (Busca la zona mas densa posible).
https://sci2s.ugr.es/keel/pdf/algorithm/articulo/1979-IEEE_TIT-Chidananda-IS-
MNV.pdf
o EditedNearestNeighbours ENN (remueve primeros los korea del centro).
o RepeatedEditedNearestNeighbours (ENN recursivo).
o AllKNN (ENN adaptativo).
o InstanceHardnessThreshold (Usa cortes de clustering jerárquico y
dendrogramas).
https://link.springer.com/content/pdf/10.1007/s10994-013-5422-z.pdf
o NeighbourhoodCleaningRule (remueve primero los mas discordantes, saca ruido).
https://www.scirp.org/pdf/JBiSE20101000009_16323745.pdf
o OneSidedSelection.
https://sci2s.ugr.es/keel/pdf/algorithm/congreso/kubat97addressing.pdf
o TomekLinks.
https://www.scirp.org/(S(i43dyn45teexjx455qlt3d2q))/journal/paperinformation.as
px?paperid=60996

Para poder aplicar alguno de estos métodos debemos instalar las librerías:

Veamos un ejemplo de aplicación:


Vemos que está desbalanceado. Para “correjirlo” haremos un Oversampler. Veamos:
Regresión logística
A grandes rasgos, y comparando con la técnica de regresión y de árbol de decisión, la técnica que
se explica en esta sección es una especie de combinación de las otras dos. Esta técnica utiliza una
regresión para clasificar. Veamos:

Estas líneas de código son sólo para establecer cuestiones estéticas sobre las figuras que se
construirán más adelante. Lo que haremos ahora es descargar las series de precios y preparar la
base de datos para trabajarla con esta técnica. Lo que intentaremos hacer es predecir si una vela
diaria será verde (alcista) o roja (bajista) en base al GAP entre el precio de de cierre del día anterior
y el precio de apertura del día. Además, clasificaremos estos resultados de acuerdo a la tendencia,
que se define como la posición del precio de cierre respecto al precio de cierre promedio de las
últimas 100 ruedas. Del mismo modo, incorporaremos los días de la semana para clasificar estos
resultados. Veamos:

La lógica del razonamiento, al aplicar esta técnica, consiste en agrupar al conjunto de pares
ordenados entre diferentes categorías, por ejemplo, según el día de la semana, o si la tendencia es
“Bear” o “Bull”. Para que se comprenda mejor la hipótesis de trabajo, comencemos a buscar
correlación con un diagrama de dispersión sobre el conjunto total de pares ordenados entre
regresando y regresores. Es muy probable que al observar el gráfico de dispersión no se observe
correlación alguna, sin embargo, y como hipótesis de trabajo, tal vez sí exista correlación entre los
diferentes sub conjuntos/grupos. Por ende, la clave se encuentra en el mismo sitio que al aplicar la
técnica de árbol de decisión, la creación de la variable label, pero en este caso, la creación de los
diferentes grupos que sirven para clasificar al regresando.
Lo mencionado puede apreciarse en la siguiente gráfica, donde se presenta el diagrama de
dispersión de todo el conjunto de pares ordenados, y donde a simple vista no se observa correlación
alguna entre las variables (GAP y cierre de vela). En esta gráfica, se presentan diferentes tonos de
colores, quienes permiten revisar visualmente cada subconjunto definido según su frecuencia.
Veamos la figura:

Este diagrama de dispersión lo veremos en “anillos” e intentaremos buscar correlación en cada


anillo:
Ahora, si como hipótesis de trabajo establecemos la probable existencia de correlación entre los
subconjuntos de pares ordenados, quienes se clasifican de acuerdo a la tendencia o al día de la
semana, se puede observar lo siguiente:

Aquí podemos ver cómo para el sub conjunto de pares ordenados en el grupo de tendencia alcista,
la correlación es leve y negativa, mientras que, para el subconjunto de pares ordenados del grupo
de tendencia bajista, la correlación es nula.
Del mismo modo se grafican estos diagramas de dispersión para los grupos formados según el día
de la semana:
Antes de aplicar la técnica de regresión logística, veamos algo de su álgebra y las hipótesis de
trabajo. El modelo es el siguiente:

Otras funciones de reactivación (del tipo de la segunda ecuación) son las siguientes:

Corremos la regresión que se ve en la primera ecuación, donde el regresando es el logaritmo en


base “e” de dicho cociente de probabilidades. Luego, y utilizando la segunda línea (ecuación),
introducimos el resultado de la regresión como exponente, para de dicho modo obtener la
probabilidad. Esta segunda línea (ecuación) transforma el resultado de la regresión en un valor que
siempre variará entre cero y uno. En paralelo, los supuestos que deben chequearse son:
 No hay multicolinealidad.
 Independencia de errores (no-auto correlación).
Los supuestos sobre los que no debemos preocuparnos son los siguientes:
 Linealidad (ya que la variable dependiente va a ser categórica).
 Normalidad de los residuos.
 Homocedasticidad.
Estas son las razones por las cuales es conveniente transformar un problema de regresión en uno
de regresión logística, pues en general, estos tres supuestos no se cumplen en la bolsa.
Si aplicamos el modelo de regresión logística utilizando las dos ecuaciones explicitadas arriba, y si
graficamos los resultados predichos según los grupos de tendencia alcista (Bull) y bajista (Bear),
obtenemos lo siguiente:

Lo que se puede apreciar, como se marca en la siguiente imagen, es que el modelo no es muy
bueno al clasificar, no obstante, se puede ver alguna diferente dentro de cada grupo. Por ejemplo,
para el caso de tendencia bajista (Bull), se puede ver cómo las predicciones que se ubican en el
techo son menos frecuentes que las predicciones que se ubican en el piso, no obstante la gran
frecuencia en alrededor del centro tanto para techo como para piso.
Preparando la base de datos

Descarguemos la serie de precios de GGAL, es decir, la misma base de datos que se trabajó con
árbol de decisión. Asimismo, buscaremos predecir la posibilidad de un incremento del precio
dentro de 100 ruedas (crece o no crece), utilizando como regresores tres tipos de cruces de media
móvil, el RSI, y dos medidas de volatilidad. Por ende, descargaremos la base de datos y la
prepararemos para aplicar la regresión logística:

Preparamos la matriz de regresores por un lado (X) y el vector de respuesta o con valores del
regresando (Y). Veamos la matriz de regresores:
Como antes, separaremos base de datos entre aquella que se utilizará para “entrenar” el modelo, y
aquella otra que utilizaremos para la “validación”:
 Separación Random:

 Separación arbitraria según serie de tiempo:

En este caso aplicamos la separación Random. Recuerde que para el caso donde existe
autocorrelación en los regresores, lo que se debe es separar del segundo modo, no del primero, pues
en este tipo de casos corremos el riesgo de validar datos con valores fuertemente
autocorrelacionados. En otras palabras, con el segundo tipo de separación de datos, no importa si
existe autocorrelación, no habrá problemas en la validación.
Finalmente, debemos escalar los datos, esto es, como en general los regresores no comparten una
misma escala, se producen problemas que se traducirían en estimadores ineficientes (betas), pues
estos darán mayor ponderación a los regresores con escalas más grandes sobre los que tienen
escalas más pequeñas. Es por esto que se normalizan con una u otra técnica, en este caso lo
haremos será: a cada valor se le resta su media histórica para luego dividirlo por su desvío estándar
histórico. De este modo logramos el cometido de tener un mismo rango para todos los regresores:
Las últimas dos líneas de código escalan lo datos de la matriz copia que se generó con el código
inicial (al descargar y preparar la base de datos). Este escalamiento se realiza con todos los datos, y
servirá para utilizar los valores más actuales para predecir qué ocurrirá con el regresando. En este
sentido, recuerde que al estar prediciendo a 100 ruedas con medias móviles, hay valores de los
regresores que no utilizamos.
Por último, sepa que hay otros escalamientos que se utilizan cuando los regresores tienen una
distribución de frecuencias muy alejada de una normal, pero en la mayoría de los casos, el utilizado
aquí es útil.

ACLARACION
El escalamiento de los datos debe hacerse después de la partición de la base de datos, pues al
entrenar el modelo utilizamos una parte de la base de datos, y los estadísticos (media y desvío)
utilizados para escalar los datos deben ser construidos con la base de entrenamiento, no con la
base de datos completo, en caso contrario, estaríamos entrenando el modelo con una base partida
que utiliza medias y desvíos de la base completa. Lo mismo aplica a la base de datos de
validación.

Entrenando el modelo

Importamos el modelo de regresión logística:


Prediciendo con el modelo

Veamos, generemos las predicciones, agrupemosla, y veamos sus probabilidades:

Este vector de probabilidades refleja la decisión automática y por defecto que toma el modelo de
regresión logística: El valor predicho se asigna a las probabilidades que están por encima del 50%,
por ejemplo, si tenemos un 56,74% de chance de que el precio dentro de 100 ruedas esté por debajo
del actual, entonces el valor predicho será cero, y si fuese al revés, será uno. Este valor por defecto
puede modificarse, sólo se debe conocer el argumento correspondiente.

Construyendo la matriz de confusión

Veamos:
Recuerde que el aumento de sesgo visto con el árbol de decisión, aquí no surge. En este sentido, en
estos modelos también se debe realizar el OverSampling o el UnderSampling.

Prediciendo con algún valor

Utilizaremos el último dato de la matriz copia escalada:

Ahora hagamos la predicción:

En este sentido, observemos que las probabilidades de ocurrencia de los últimos 5 datos no son las
mismas como sí ocurría con el modelo de árbol de decisión (aunque el valor predicho sí es el
mismo):
Regresión logística versus árbol de decisión

El árbol de decisión es muy sensible a cambios en el valor del regresor, en tanto que pequeños
cambios tienen altas chances de modificar el valor predicho, ceteris paribus el resto de los
predictores. En cambio, en regresión logística, esta sensibilidad es sustancialmente menor.
En paralelo, la regresión logística no puede caer en overfitting, como si sucede con el árbol de
decisión.

Máquinas de vectores de soporte (SVM)


Este es el modelo que más le gusta a Juan Pablo (profesor). Es modelo se apoya en dos elementos:
I) una transformación no lineal de las variables, II) las clasificaciones se realizan con “vectores de
tolerancia”. Para comprender la esencia del método trabajemos el siguiente ejemplo, quien se
acompaña con la figura que puede verse abajo. En esta imagen tenemos pequeños cuadrados de
diferentes colores, si el objetivo es agrupar cada cuadrado de acuerdo a su color, lo mejor que
podríamos lograr sería agrupar por: verdes claro, verdes, azul, y azul oscuro. ¿Qué regla o criterio
nos ayudaría para agrupar de este modo? Si trazamos una recta de regresión, como se observa en la
segunda imagen, nos encontraríamos lejos de lograr el mejor resultado posible.

Otro modo de clasificar sería con círculos, por ejemplo, procediendo como se ve en la siguiente
imagen. Así lograríamos dos grupos, uno de azul oscuro y otro de azul claro. Incluso podríamos
incorporar un segundo círculo y tener tres grupos con tres tonalidades diferentes, el primer círculo
oscuro, el segundo con un tono más claro, y el tercer grupo sería el de colores claros. La diferencia
entre el primer círculo y el segundo se llama “tolerancia/soporte”.
El siguiente es otro ejemplo, más directo:

El problema de los círculos es que no cumplen con la definición de función, pues tienen para un
mismo valor del dominio, más de un valor en la imagen. Esta situación es quien nos acerca a la
transformación no lineal de la función, llevando la función de dos dimensiones a otra de al menos
tres dimensiones.
A partir de esta idea, el orden de trabajo con esta técnica se divide en dos pasos, aunque son
similares a los casos anteriores: I) primero identificamos los grupos, azules claros, azules oscuros,
etcétera, es decir, en la base de datos deberemos crear una variable que permita identificar cada
rueda/fila de acuerdo a los grupos que creemos; y II) segundo, aplicaremos la fórmula con
diferentes parámetros, según el caso, para crear estos grupos de forma abstracta y de acuerdo a lo
que señalamos en el primer paso. Entonces, con esta fórmula y los nuevos valores de features
predeciremos si una nueva observación corresponde a un grupo u otro.
Este modo de clasificar requiere que se defina el modo en que se tratará aquellas observaciones que
“caen” en zonas grises. Es aquí donde se utiliza el concepto de “vectores de soporte”, quienes son
otra transformación algebraica que permite terminar definiendo dicha observación como parte de
un grupo u otro. Gráficamente:

Los cuadrados rojos estaban en la zona del vector de soporte, quien al aplicarse permite ubicarlos
en uno u otro grupo.

Aplicación

Lo que debemos hacer ahora es descargar la serie de precios, definir qué queremos predecir,
trabajar la base de datos para crear los indicadores o features, partir la base de datos para crear (de
forma aletoria o no – recordar la advertencias sobre autocorrelación), escalar los features, entrenar
el modelo, validarlo, y predecir.
Como antes, lo que haremos será predecir si la cotización de GGAL dentro de 100 ruedas es mayor
a la actual o no. Para esto utilizaremos tres tipos de cruces de medias móviles, un RSI, y dos
medidas de volatilidad. La separación de la base de datos se hará de forma aleatoria. Veamos el
código para descargar la serie de precios y preparar la base de datos:
Ahora partimos la base de datos:

Ahora escalemos los datos:


Vamos a importar el modelo y a entrenarlo:

El parámetro “kernel” y particularmente su valor ‘rbf’ es quien transforma la función lineal en una
radial. En la bolsa, este es el argumento principal por no decir el único, en palabras de Juan Pablo
(profesor), los demás argumentos no son muy útiles. En parámetro “C” es el vector de soporte, en
este sentido, este es el parámetro a través del cual el modelo que creemos puede terminar estando
overfitting (por el valor que coloquemos en este argumento).
Ahora realizamos la predicción:

Ahora realizamos la validación, construyendo también la matriz de confusión:

Los valores de acierto son tan buenos que contrastan con la experiencia de Juan Pablo (profesor), y
es a partir de esto que toma forma la sospecha sobre la existencia de algún error. Si bien es
importante este contraste entre resultado y experiencias, lo que siempre debemos hacer es evaluar
el cumplimiento de los supuestos, es decir, que no exista multicolinealidad ni autocorrelación. Por
el tipo de features que utilizamos, y la técnica de separación de base de datos utilizada, lo que está
claro es la presencia de autocorrelación. En este sentido, si utilizamos el método de separación
alternativo, los resultados obtenidos son los siguientes:

Lo que vemos aquí es la solución al error provocado por la autocorrelación entre la matriz de
entrenamiento y de validación. También vemos otro problema, el sesgo positivo, que nos está
reflejando lo indicado al ver árbol de decisión, en la muestra (base de datos de entrenamiento) se
tienen muy pocos datos que reflejen situaciones a la baja. Por ende, este tipo de problemas se
soluciona aumentando el tamaño de la muestra, y/o incorporando períodos de tiempo donde las
cotizaciones han caído. Como alternativa, también se pueden modificar los valores asignados a los
parámetros del modelo, concretamente, los relacionados con “gamma” y el “C”.5
Una última observación sobre este modelo: siempre utiliza una semilla inicial para introducir
aleatoriedad, para fijarla, al modelo debemos explicitarle el argumento opcional “random_state”, y
asignarle el valor cero.

Comparación entre SVM y los otros métodos

A diferencia del árbol de decisión, el SVM genera probabilidades extremas con más frecuencia
(más cercanas a 1 y 0). Y a diferencia de la regresión logística, el SVM es más sensible al
momento de clasificar, es decir, ante un leve cambio en el valor de los features es más probable que
se produzca un cambio de clasificación bajo este método que bajo la regresión logística (en este
sentido, es más parecido al árbol de decisión).

5Apreciación personal: Antes de modificar los valores por defecto, sería conveniente profundizar el entendimiento sobre
cómo funciona esta técnica.
Bosques aleatorios
Este método toma la base de datos y las fracciones en sub muestras, aplicando en cada una un árbol
de decisión. Por ejemplo, toma sólo un feature con la mitad de las filas, luego toma tres features y
toma las dos mil filas que se ubican en la mitad de la base de datos, y así sucesivamente.

Entonces, para cada sub muestra se genera un árbol, hasta general un bosque con K árboles:
A partir del bosque, se aplica nuevamente un criterio de consenso, entropía o gini, por ejemplo,
sobre las predicciones de cada uno de los árboles, y aquella combinación que genere la entropía
más baja es la utilizada para tomar la decisión.
Recordemos que el problema del árbol de decisión es su sensibilidad ante pequeños cambios en los
valores de los features. Con esta combinación de árboles de decisión, esta sensibilidad se reduce
sustancialmente.

Aplicación

Lo que debemos hacer ahora es descargar la serie de precios, definir qué queremos predecir,
trabajar la base de datos para crear los indicadores o features, partir la base de datos para crear (de
forma aletoria o no – recordar la advertencias sobre autocorrelación), escalar los features, entrenar
el modelo, validarlo, y predecir.
Como antes, lo que haremos será predecir si la cotización de GGAL dentro de 100 ruedas es mayor
a la actual o no. Para esto utilizaremos tres tipos de cruces de medias móviles, un RSI, y dos
medidas de volatilidad. La separación de la base de datos se hará de forma aleatoria. Veamos el
código para descargar la serie de precios y preparar la base de datos:
Separamos la base de datos de forma aleatoria (recuerde las aclaraciones hechas sobre la
autocorrelación en la validación):

Escalamos el modelo (aunque no hace falta dado que la técnica es un árbol de decisión):

Importamos y aplicamos el modelo:

Entrenamos el modelo:

Predecimos:

Validamos:
Comparación entre Bosques aleatorios y los otros métodos

A diferencia del árbol de decisión, el bosque aleatorio es menos sensible ante cambios en los
valores de sus features, en este sentido se encuentra más cercano a la regresión logística. Del
mismo modo que los árboles de decisión, el bosque aleatorio puede caer en un overfitting debido a
las capas de profundidad indicadas en relación al tamaño de la base de datos. La gran particularidad
de este tipo de modelos es que tiende a generar probabilidades muy cercanas al 50%.
Métricas para modelos predictivos
Las siguientes métricas permiten comparar modelos entre sí de un modo más completo (además del
uso de la matriz de confusión). Juan Pablo (profesor) menciona que estas métricas no son muy
útiles en la bolsa, no obstante, es importante conocerlas. Son las siguientes:
 Positivos Totales = Positivos detectados + Falsos negativos.
 Negativos Totales = Negativos detectados + Falsos positivos.
 Sensibilidad = Positivos detectados / Positivos Totales.
o Es la probabilidad de que un positivo efectivamente sea pronosticado positivo.
 Especificidad = Negativos detectados / Negativos Totales.
o Es la probabilidad de que un negativo efectivamente sea pronosticado negativo.
 Exactitud (Accuracy) = Mediciones Correctas / Totales.
 Precisión = Positivos detectados / (Positivos detectados + Falsos Positivos).
 F1 score = 2 * Precisión * Sensibilidad / (Precisión + Sensibilidad).
La librería y los códigos son los siguientes:
Entrenamiento no supervisado - Clustering
Una primera diferencia con los algoritmos anteriores, es que estos son útiles sólo para clasificar, no
para predecir. Además, otra diferencia respecto a los modelos anteriores, es que aquellos se ubican
dentro del entrenamiento supervisado, mientras estos no requiere supervisión. La presencia de
supervisión significa que existe un vector de labels, es decir, al descargar la base de datos, y antes
de aplicar el método de clasificación, nosotros debemos etiquetar las observaciones (ruedas) de
acuerdo a algún criterio, luego, el algoritmo que utilicemos establecerá el vínculo entre los features
que elegimos y los lables que establecimos. Cuando la supervisión no existe, para que el algoritmo
pueda clasificar se utiliza un criterio que es especificado por nosotros mismos, y esta es justamente
la clave al trabajar con estos algoritmos, el criterio que definimos. Este algoritmo permitirá crear
las clases o el vector de labels. Veamos la siguiente imagen para ver un ejemplo de esta forma de
razonar:

La base de datos es la imagen de arriba, mientras que la imagen de abajo es el resultado de la


clasificación realizada por el algoritmo. El criterio de clasificación fue la relación entre las velas,
esto es, si la vela siguiente es verde, entonces se clasifica en un grupo, y si es roja, se clasifica en
otro.
Siguiendo este ejemplo, en el código debemos señalar el criterio que debe seguir el algoritmo para
realice la clasificación. Descargamos la base de datos/serie de precios de GGAL, y escribimos el
criterio. Veamos:
Lo primero es escribir un código que permita al algoritmo identificar una vela grande de otra más
pequeña:

Ahora incorporamos código para identificar la forma de las velas anteriores y posteriores a la
actual:

Clusterización – Conceptos
Siempre tenga presente que por Cluster nos referimos a grupos, y que la clusterización es útil
segmentar:
 Clientes. Por ejemplo, se quiere agrupar los clientes para identificar fidelidad y así
establecer promociones, los features podrían ser I) cantidad de compras, II) gasto realizado,
III) variedad de productos comprados, IV) medios de pago utilizados, V) cantidad de
referidos que tiene, VI) cantidad de tiempo que hace que no compra, etcétera. Como no se
sabría qué elemento es más importante para clasificar, se terminaría utilizando esta técnica.
 Nichos de mercado.
 Detectar fraudes.
Estos métodos son muy eficientes siempre que se apliquen a bases de datos de hasta 10 mil
conglomerados iniciales (cantidad de datos inicial), más de esa cantidad empieza a ser ineficiente y
conviene migrar a otro tipo de algoritmo. Esto es así porque a partir de dicha cantidad
(aproximada), aplicar el método tiene un alto costo computacional.
Al momento de crear una clase que permita diferenciar sus integrantes de los miembros de otras
clases, el criterio de diferenciación debe definirse para los tres puntos siguientes:
 Grupos que me diferencien bien los distintos tipos de datos.
 COHESION (distancia intra cluster).
 SEPARACION (Distancia entre clusteres).
Antes de aplicar algún método fijando el criterio de clasificación, introduciremos una base de datos
creada por nosotros mismos. El código de la base de datos es el siguiente:

En este diagrama de dispersión se pueden apreciar cinco “nubes”, pero también se podría decir que
sólo hay dos grupos, el conformado por las tres nubes en la zona baja izquierda, y el conformado
por dos nubes en la zona alta derecha. ¿Hay cinco grupos o dos? ¿O hay más grupos? Todo
depende del criterio que utilicemos. Las variables de los ejes bien pueden ser el RSI en el eje de
abscisas y la cotización forward en el eje de ordenadas. No se introducen más variables porque,
valga la redundancia, es un eje cartesiano, y a los efectos didácticos es mejor presentar el tema de
este modo, sin embargo, presentarlo así no perjudica el método, pues
simbólicamente/algebraicamente, es lo mismo.
Como se mencionó antes, el criterio debe cumplir definir con precisión qué se entiende por
cohesión y separación, y en este sentido, el concepto que sintetiza a ambos es la distancia, por
ende, la elección del criterio de agrupamiento se reduce a una elección que implica definir qué es
distancia.
Antes de describir algunos conceptos de distancia, mejoremos la gráfica facilitada arriba, para esto
colocaremos etiquetas (números) a cada punto del diagrama, así facilitaremos la identificación de
cada punto. Veamos el código que nos permite hacer esto:
Las coordenadas de cada punto permiten identificarlos simbólicamente. Por ejemplo:

Entonces, buscaremos agrupar los puntos según la distancia que cada uno tenga con los demás.
¿Qué entendemos por distancia? Un concepto puede ser la distancia geométrica o hipotenusa, y si
el movimiento diagonal está prohibido, puede ser la suma de los catetos ¿Y la escala importa? Si
importa, entonces deberíamos tener escalas logarítmicas. El punto a tener presente aquí es la
variedad de definiciones que existen al momento de definir distancia. Veamos algunas
definiciones:
 Distancia geométrica (euclidea): Es el teorema de Pitágoras pero generalizado:

Aquí, p y q son ejes x e y, mientras que el sub índice son las dimensiones. Por ende,
podemos hablar del x e y de la dimensión 1, 2, 3, etcétera. Por ejemplo, para el caso de una
dimensión:

 Cityblock geomtry (Manhattan): Esta distancia es la suma de las distancias absolutas de los
ejes en cada dimensión (como antes, p y q son los ejes x e y, mientras que el subíndice es
la dimensión):

Por ejemplo:
 Chebyshev: Aquí, la distancia se define como la máxima diferencia absoluta entre las los
ejes x e y (p y q) entre todas las dimensiones.

Por ejemplo:

 Geometría de Canberra: Es como una mezcla entre Chebyshev y CityBlock (Manhattan).


Lo que se hace aquí es ponderar la distancia (diferencia absoluta entre los ejes de una
misma dimensión) por el tamaño. Esta idea de distancia es muy útil para las ganancias
empresariales.

La librería que se utiliza para medir distancia con una u otra geometría es la que se muestra a
continuación:
Aquí también podemos conocer el nombre de otros tipos de geometrías que no se describieron aquí.
Para continuar con el ejemplo, y antes de aplicar el método, calcularemos la distancia entre los
puntos utilizando el criterio euclideo (donde además, todas las dimensiones tienen el mismo
peso/ponderación). Veamos:
Clusterización – Sección gráfica
En las siguientes sub-secciones se describen los diferentes criterios que terminan por definir
diferentes modelos de clasificación. Según Juan Pablo (profesor), los más utilizados son los
primeros dos que se mencionan a continuación. Por otro lado, en cada sub-sección veremos que
importamos una librería y alguna sub-librería, estas son:

La “dendrogram” nos sirve para graficar, “linkage” sirve para agrupar, y con “fcluster” obtenemos
las etiquetas de cada grupo una vez definido el corte, es decir, con esta última sub librería logramos
establecer un corte para definir la cantidad de grupos con quienes trabajaremos.

Dendograma jerárquico – Euclideo por distancia mínima

Un Dendograma jerárquico es un árbol que ubica cada punto de acuerdo a su cercanía, y lo ubica
de acuerdo a una jerarquía. Entonces, se establece una cantidad determinada de distancias que
permite crear grupos grandes, y dentro de estos se aplican otras distancias para crear otros grupos, y
dentro de estos se repite el procedimiento. El resultado final es algo parecido a lo siguiente, donde
se agruparon los puntos generados en la sección anterior.
Este criterio sirve cuando queremos hacer hincapié en la cohesión antes que en la separación. En
el código debemos asignar el valor “single” al argumento “method”. Siempre se comienza uniendo
los puntos más cercanos entre sí, pero luego de unir dos puntos, es decir, de haber creado el primer
grupo, el tercer punto se elije evaluando la distancia de los puntos al grupo, y en particular, al punto
más cercano que ya pertenece al grupo. Se miden las distancias y se toma el punto más cercano:

El argumento “color_threshold” nos permite establecer un corte para establecer los grupos. Si le
asignamos el valor 3, estaremos haciendo un corte gráfico en el eje vertical, y obtendríamos los dos
grupos que se han formado por debajo de tres, o sea:
En el mismo sentido, si el corte lo realizamos en 1,5, obtendríamos una mayor cantidad de grupos
(5 grupos), pues los cinco grupos anteriores se ramifican en varios más. En definitiva, el corte y la
construcción de clases dependen del criterio de distancia que usemos y del corte que realicemos (en
este último sentido es similar al modelo logit). En definitiva, este parámetro representa la distancia
mínima al más cercano a partir de la cual ya se considera que un punto es parte del grupo.

Dendograma jerárquico – Euclideo por distancia máxima

Un Dendograma jerárquico es un árbol que ubica cada punto de acuerdo a su cercanía, y lo ubica
de acuerdo a una jerarquía. Entonces, se establece una cantidad determinada de distancias que
permite crear grupos grandes, y dentro de estos se aplican otras distancias para crear otros grupos, y
dentro de estos se repite el procedimiento. El resultado final es algo parecido a lo siguiente, donde
se agruparon los puntos generados en la sección anterior.
Este criterio sirve cuando queremos hacer hincapié en la separación antes que en la cohesión. En
el código debemos asignar el valor “complete” al argumento “method”. Siempre se comienza
uniendo los puntos más cercanos entre sí, pero luego de unir dos puntos, es decir, de haber creado
el primer grupo, el tercer punto se elije evaluando la distancia de los puntos al grupo, y en
particular, al punto más alejado que ya pertenece al grupo. Se miden las distancias y se toma el
punto más cercano:

El argumento “color_threshold” nos permite establecer un corte para establecer los grupos. Si le
asignamos el valor 3, estaremos haciendo un corte gráfico en el eje vertical, y obtendríamos los
cinco grupos que se han formado por debajo de tres, o sea:

En el mismo sentido, si el corte lo realizamos en 1,5, obtendríamos una mayor cantidad de grupos,
pues los cinco grupos anteriores se ramifican en varios más. En definitiva, este parámetro
representa la distancia mínima al más lejano a partir de la cual ya se considera que un punto es
parte del grupo
En definitiva, el corte y la construcción de clases dependen del criterio de distancia que usemos y
del corte que realicemos (en este último sentido es similar al modelo logit).

Dendograma jerárquico – Euclideo por varianza

Un Dendograma jerárquico es un árbol que ubica cada punto de acuerdo a su cercanía, y lo ubica
de acuerdo a una jerarquía. Entonces, se establece una cantidad determinada de distancias que
permite crear grupos grandes, y dentro de estos se aplican otras distancias para crear otros grupos, y
dentro de estos se repite el procedimiento. El resultado final es algo parecido a lo siguiente, donde
se agruparon los puntos generados en la sección anterior.

Este criterio utiliza como medida de distancia la varianza de las distancias euclideas, es decir,
habiendo calculado las distancias, se las normaliza (se toma la diferencia respecto de la media y al
resultado se lo divide por la varianza). En el código debemos asignar el valor “ward” al argumento
“method”. Luego de obtener las distancias normalizadas, se unen los puntos de acuerdo a la
cercanía entre sí, aplicando cualquiera de los criterios (máximo o mínimo), pues da igual. Esta
indiferencia entre elegir entre el punto más cerca y lejano se aprecia en la siguiente imagen:

Aquí se puede ver cómo el punto 3 está más cerca del grupo de la izquierda según distancia
absoluta, tanto para el punto más cerca como para el más lejano. Pero, según el criterio de la
varianza, será más parecido al grupo de la derecha, donde los puntos están más dispersos entre sí.
En este caso, el valor asignado al argumento “color_threshold” tiene que ver con los puntos de
desvío, y no a la distancia absoluta como en los dos métodos anteriores. Por ende, este parámetro
representa la cantidad de varianzas mínimas necesarias para incorporar una observación a un grupo.

Clusterización – Sección analítica


En las
Por otro lado, en cada sub-sección veremos que importamos una librería y alguna sub-librería, estas
son:

La “dendrogram” nos sirve para graficar, “linkage” sirve para agrupar, y con “fcluster” obtenemos
las etiquetas de cada grupo una vez definido el corte, es decir, con esta última sub librería logramos
establecer un corte para definir la cantidad de grupos con quienes trabajaremos (se define la
distancia mínima hacia el punto más cercano a partir de la cual se considera que una observación es
parte de un grupo). Veamos las líneas de código utilizando sólo las últimas dos sub librerías (la
primera línea de código es quien crea los puntos/base de datos):
La variable que llamamos “clusters” tiene asignado un array donde cada elemento es la etiqueta
que se corresponde con cada punto generado en la primera línea de código.
Ahora graficaremos este resultado en un diagrama de dispersión, distinguiendo cada grupo a partir
de los colores de los puntos:
Clusterización – Aplicación real
Armaremos grupos de acciones norteamericanas utilizando cuatro medidas. Para obtener los datos
utilizaremos la siguiente FMP API (Juan Pablo –profesor- sostiene que es muy buena, pero al
utilizarse de manera gratuita sólo podremos realizar 200 requests como máximo):
https://fmpcloud.io/
La información que se puede encontrar es la siguiente:
 ETFs (424).
 Commodities (28).
 Equity Europa: Euronext (1248).
 Equity Nyse (4380).
 Equity Amex (274).
 Equity Nasdaq (3825).
 Equity Canadá: TSX (1413).
 Indices (56).
 Fondos Mutuales (1504).
Nosotros buscaremos renta variable con capitalización superior a los 10 mil millones de USD:
Esta API nos permite conseguir datos de papeles de exchanges de diferentes países:

Es por esto que filtraremos la base de datos para quedarnos sólo con los 75 primeros papeles del
NASDAQ, y nos quedaremos sólo con las columnas que se pueden leer en el código:

Las columnas que no son el index y el ticket son los features, las características con quienes
construiremos los grupos, son las dimensiones. Por lo tanto, cada fila es un punto, cuyo nombre es
el ticket, y se corresponde con cuatro dimensiones. Entonces, al aplicar la técnica de clusterización
estaremos respondiendo para cada empresa a ¿qué tal lejos está la empresa X de la empresa Y de
acuerdo a los valores que las cuatro dimensiones toman? Con la respuesta lograremos formar
grupos, pues se miden la distancia entre los puntos para cada una de las dimensiones.
Un asunto importante con las características elegidas es su escala. Como se puede apreciar en la
tabla, los valores/escalas difieren significativamente, por ende, de utilizar esta base de datos se
estaría dando mucha más importancia para clasificar a la capitalización antes que a la beta. Para
resolver esto sin cambiar el concepto de distancia (utilizamos la euclidea), estandarizaremos los
valores de las variables (a los valores le restamos la media y al resultado lo dividimos por la
varianza). Veamos:

Como alternativa, podríamos haber utilizado el escalamiento de antes, el resultado es el mismo:

Ahora armaremos un Dendograma utilizando el método de la varianza:

Vemos que hay 19 grupos. Para analizar las características del agrupamiento, primero creemos los
labels y asignemoslos a la base de datos:
Veamos ahora un gráfico de barra que nos permita apreciar visualmente la frecuencia o cantidad de
empresas en cada grupo:

Para realizar el análisis prestemos atención principalmente a los grupos más grandes.
A estos grupos los graficaremos (recordar que los features están normalizados, por ende, son
puntos de desvío respecto de la media):
Ahora hagamos el análisis, pero cuantitativamente y sin escalar:
Entrenamiento no supervisado - Kmeans
Este método tiene la misma base que la clusterización, es decir, debemos parametrizar el criterio,
pero a diferencia del método anterior, en lugar de seleccionar la distancia mínima, aquí elegiremos
la cantidad de grupos que deseamos tener. En términos de cohesión y separación (los conceptos que
deben definirse con el criterio), al señalar la cantidad de grupos estamos definiendo una posición
sobre estos aspectos de forma indirecta.
Este método utiliza como criterio de creación de grupos a la técnica de centros (centroides),
alrededor de los cuales los datos se agrupan uniformemente (esféricamente si son puntos en 3D,
radialmente si son datos en 2D y linealmente si son datos unidimensionales, etc.). Para ejemplificar
el uso de la técnica, imaginemos que nuestra base de datos se compone de datos que, en un gráfico
de dispersión de dos dimensiones es igual al siguiente:

Lo que tenemos que hacer ahora es definir la cantidad de grupos que deseamos se construyan, a
partir de lo cual, el algoritmo ubica dos centroides (un punto) de forma aleatoria6 en el mapa de
dispersión. Habiendo hecho esto, lo siguiente es medir la distancia entre cada centroide y cada
observación, y con estas medidas realizará las agrupaciones, clasificando los puntos en los
centroides más cercanos. Las distancias pueden medirse de cualquiera de las formas que hemos
descripto antes (sección de clusterización), aquí como antes utilizaremos la distancia euclidea.
Habiendo terminado con esto, lo siguiente es etiquetar a cada observación según el grupo al que
pertenece:

6 En el método “Kmeans ++”, esta ubicación no es tan al azar, dice Juan Pablo (profesor).
Luego de esto, lo siguiente es reiniciar el proceso y volver a ubicar dos centroides en el mapa de
dispersión, tomar medidas y etiquetar las observaciones. Este proceso se repite N veces (el
algoritmo repite esto una cantidad determinada de veces por defecto, no obstante, podemos
indicarle cuántas repeticiones deseamos que realice). En esta segunda oportunidad, y en las
siguientes, para ubicar los centroides se tomará cada grupo por separado y se calculará el promedio
de los valores de cada dimensión (promedio del eje x y promedio del eje y para cada grupo),
obteniendo de este modo la coordenada de ubicación del centroide para la segunda ronda.

Partiendo de la nueva ubicación, se vuelve a calcular la distancia de cada observación a cada


centroide, y a etiquetar y agruparlas alrededor de los centroides más cercanos.
Ahora volvemos a reubicar los centroides tomando los promedios de cada dimensión para definir la
nueva ubicación. Luego se repite el proceso. Este proceso iterativo continúa hasta que ya no se
observan cambios en la ubicación de los centroides.

Por lo tanto, la clave de la técnica es la definición de la cantidad de grupos que deseamos se armen.
Es por esto que siempre deberemos definir diferentes cantidades de grupos y comparar los
diferentes resultados obtenidos.

NOTA: Comparación Kmeans versus Kmeans++


El método explicado ubica azarosamente a los centroides en la primera ronda, mientras el método
Kmeans++ no, pues busca colocarlos a la mayor distancia posible de cada punto (esto es lo óptimo
para reducir la cantidad de iteraciones, dice Juan Pablo –profesor-). La librería sklearn que
usaremos utiliza este método, el Kmeas++.

Ejemplo de aplicación
Para ejemplificar el uso de la librería sklearn, donde se encuentra este método, primero crearemos
nuestra base de datos y la graficaremos:
Ahora aplicaremos el método Kmeans++. En la siguientes líneas “entrenaremos” al modelo
(antepenúltima línea) y también, haremos el etiquetado (penúltima línea). La última línea de código
es una variable que tiene guardadas las coordenadas de los centroides finales (característica de cada
grupo). Veamos:

Un detalle importante es la interpretación de la cantidad de grupos. En este caso elegimos 6 grupos,


cuyas etiquetas no serán 1, 2, 3, 4, 5, y 6, sino, 0, 1, 2, 3, 4, 5, es decir, comienza desde el cero,
como si de ubicaciones estuviésemos tratando. Por ende, si pidiésemos 10 grupos, tendríamos
etiquetas desde el 0 al 9.
La base de datos es:

Las labels o etiquetas están guardados en “y_means”, veamos los primeros 50:
Las coordenadas de los centroides son:

Gráficamente, los grupos definitivos son (resultado de la última iteración):

Este ejemplo permite ver claramente lo mencionado antes, la clave se encuentra en cómo definir la
cantidad de grupos (centroides). En este sentido, piense que al contar con dos dimensiones, o
incluso tres, un espacio de dispersión puede ser de gran ayuda, sin embargo, esta herramienta ya no
será accesible cuando tengamos cuatro o más dimensiones (características). Por lo tanto, definir la
cantidad de grupos (centroides) no es un asunto de sencilla resolución.

Cantidad óptima de clusters


Para determinar la cantidad óptima de grupos de forma cuantitativa, podemos utilizar uno de los
siguientes métodos, dentro de los que se encuentran los más conocidos “elbow curves” y
“silhouette plots”. En este sentido, este par de métodos son parciales al momento de establecer la
cantidad óptima de grupos, pues cambian el problema de definir cantidad de grupos por definir
distancia entre grupos o distancia entre observaciones de un mismo grupo. Veamos el listado:
 Método del codo (elbow curves).
 Gradiente del codo.
 Método de silueta (silhouette plots).
 Fórmulas propias tipo taylor-made.
 Índice de Dunn (ratio menor distancia extra-cluster y la mayor intra-cluster). Este método
no está disponible en la librería sklearn. Es una ratio entre la menor distancia entre clúster
y la mayor distancia intra clúster.
https://python-bloggers.com/2022/03/dunn-index-for-k-means-clustering-evaluation/
 Índice de Davies-Bouldin (ratio de dist intra/extra clúster, a menor ratio mejor). Es una
ratio entre la distancia intra clúster y la distancia inter clúster, a menor ratio mejor.
https://python-bloggers.com/2021/06/davies-bouldin-index-for-k-means-clustering-
evaluation-in-python/
 Índice Calinski-Harabasz (ratio de varianzas extra/intra clúster, a mayor, mejor). Es una
ratio entre las varianzas inter clúster e intra clúster, a mayor ratio mejor.
https://python-bloggers.com/2022/03/calinski-harabasz-index-for-k-means-clustering-
evaluation-using-python/
Veamos en las siguientes sub secciones el método del codo, de la silueta, y el Taylor made. Para
explicar cada método y compararlo con el resto, utilizaremos el mismo ejemplo de partida, la base
de datos construida antes, y las siguientes situaciones donde se puede ver diferentes cantidades de
grupos:
No obstante la variedad de métodos, bien se podría tomar varios métodos, siempre que todos
incorporen los mismos conceptos (por ejemplo, todos incorporan cohesión y separación, o todos
incorporan sólo cohesión o sólo separación), y se toma un promedio de los resultados que cada uno
ofrece, o se suman los índices del resultado óptimo de cada uno para definir cuál es la cantidad
óptima de grupos (claro que se deben normalizar los índices, en tanto que algunos indican
optimalidad mientras más grandes son y otros mientras más pequeños son).

Método del Codo

Este método mide distancias intra - grupos, es decir, define diferentes cantidades de grupos, y para
cada modelo (cada cantidad de grupo) calcula la distancia que cada miembro tiene con los demás
(hace esto para cada grupo), luego calcula la distancia promedio de cada grupo, y finalmente
calcula la distancia promedio del modelo. Por ende, a mayor cantidad de grupos, menor será la
distancia intra cluster

Es con esta gráfica que se decide la cantidad óptima de grupos. Para hacerlo se toma el punto a
partir del cual la pendiente de la recta trazada de izquierda a derecha cambia más bruscamente. En
la siguiente imagen se ejemplifica esto, vemos que 6 grupos es el punto a partir del cual la
pendiente tiene el mayor cambio:

Analíticamente debemos calcular las pendientes, sabemos que el cambio en la cantidad de grupos
siempre es uno, mientras que el cambio en las distancias se obtiene tomando la diferencia entre los
valores correspondientes de cada punto. Una vez calculadas las derivadas para cada punto,
calculamos el cociente entre ellas o tasa de cambio de la pendiente, quedándonos con la más
pequeña, pues este indicará el mayor cambio de pendiente y así el grupo óptimo.

Vemos que aplicando esto, la cantidad óptima de grupos es dos.

Método de la silueta

Este método contempla tanto la cohesión como la separación, es decir, distancia entre grupos y
distancia intra grupo. En este método se elige la cantidad de grupos cuya ratio es la más alta.
Donde i es el centroide, y:
 a: distancia promedio de i a todos los demás puntos en el mismo cluster.
 b: distancia promedio de i a todos los demás puntos en el cluster más cercano.
Donde el valor de s(i) puede variar entre -1 y 1,
 -1 si es un mal agrupamiento.
 0 si es indiferente.
 1 si es un buen agrupamiento.
Por lo tanto, el coeficiente de la silueta para cada modelo es:
𝑁
1
𝑆𝐶 = ∑ 𝑠(𝑖)
𝑁
𝑖=1

Donde N es la cantidad de grupos.


Para más explicaciones, se puede visitar la siguiente URL de donde se obtuve más detalles sobre
este método:
https://medium.com/@jonathanrmzg/k-means-elbow-method-and-silhouette-e565d7ab87aa
Como veremos a continuación, parece nuevamente que la cantidad óptima de grupos es dos:
Método Taylor made

Este método tiene en cuenta los conceptos de cohesión y separación, pero también penaliza la
mayor cantidad de grupos. Para lograr esto realiza una combinación de métodos, su fórmula es casi
artesanal, en tanto que la penalización sobre la cantidad de grupos puede modificarse a criterio
propio. Su fórmula es un cociente, donde:
 Silueta en el numerador (a mayor valor mejor).
 Método del codo o distancia media intra clúster en el denominador (a menor distancia
mejor).
 Numero de clústeres en el denominador (penalizando el sobre-ajuste). Podríamos elevar
esta cantidad a alguna potencia para modificar la penalización.
La cantidad óptima de grupos estará indicada por el valor más alto del índice, que este caso será
para seis agrupaciones.
Críticas al modelo Kmeans
El caso extremo que permite ilustrar la situación donde falla con seguridad, es el siguiente, veamos
la gráfica de dispersión:
Ahora, estos ejemplos son sencillos pues tenemos dos dimensiones ¿cómo podríamos estudiar los
casos con cuatro o más dimensiones? Juan Pablo (profesor) sugiere tomar cada dimensión y
estudiar su distribución de frecuencias. Cuando las distribuciones no son uniformes, este método no
es recomendable.
Entrenamiento no supervisado – Mezcla Gaussiana
Este método resuelve situaciones que el método Kmeans no, concretamente, es útil para resolver
casos donde la dispersión se alarga (última figura mostrada en la sección de crítica al Kmeans),
pero no resuelve para dispersiones como la medialuna. Como eje central también tiene la necesidad
de definir la cantidad de grupos, por ende, para encontrar su óptimo podemos utilizar cualquiera de
los métodos mencionados antes. Por otro lado, este método no utiliza el concepto de centroide, en
su lugar asume lo siguiente:
 Los puntos no se distribuyen uniformemente en las dimensiones que tenga el set de datos.
 Se distribuyen según la distribución normal.
 Los clústeres pueden tomar formas elípticas siguiendo distribución normal para dimensión
X e Y (dist focales).
 IMPORTANTE: La mayoría de los puntos están aglomerados en el centro del clúster, pero
permite outliers como en una distribución normal.
 Cada clúster puede tener una matriz de covarianzas independiente (covariance_type=Full)
o bien compartir todos la misma matriz (covariance_type=tied) incluso puede generalizarse
a un kmeans usando covariance_type=spherical tratando a cada clúster con una varianza
única}. O sea, las varianzas de cada dimensión clúster no tienen porqué ser iguales.
Este método simula diferentes distribuciones de probabilidad para cada dimensión (característica),
pero siempre son distribuciones normales. Luego calcula matrices de varianza y covarianza
(correlaciones entre las diferentes dimensiones) y se selecciona aquella matriz con la mayor
correlación.
Veamos un ejemplo básico para entender cómo trabaja este método. La base de datos con dos
dimensiones es la siguiente, gráficamente:
Ahora importamos el modelo y lo aplicamos, indicando que deseamos darla la misma importancia
a la relación de todas las dimensiones, por ello, en el argumento “covariance_type” asignamos el
valor “full”.

Para evaluar estos modelos, y poder encontrar la cantidad óptima de grupos podemos utilizar el
método de la silueta, y también, utilizar los criterios de Akaike (CA) y el criterio Bayesiano (CB).
Con estos criterios, la cantidad óptima se define a partir del valor más pequeño del índice. En este
sentido, si el índice continua reduciendo su valor a medida que la cantidad de grupos aumenta,
entonces el modelo mezcla Gaussiana no funciona para agrupar. En resumidas cuentas, ambos
criterios permiten identificar la cantidad óptima de grupos y, si el método es adecuado para la base
de datos.
Veamos su aplicación en estos casos:

Vemos que la cantidad óptima de grupos es 7.


En los casos donde estos modelos no funcionan para agrupar, los criterios de Akaike y Bayesiano
se caracterizan por la siguiente figura, como se indicó hace un momento. Vemos que el criterio de
Akaike nos indica que no funciona este modelo. Lo recomendable es buscar otro para una base de
datos donde esto suceda, aun cuando el criterio bayesiano indique lo contrario.
Entrenamiento no supervisado – DBSCAN
Este tipo de modelos pueden ser la alternativa correcta cuando los modeles de Kmeans y de mezcla
gaussiana no funcionan. Con este método es posible resolver el agrupamiento de figuras extrañas
como las medialunas. No es un método recomendable con bases de datos grandes, pero con bases
relativamente pequeñas funcionan bien. Veamos cómo funciona:
 Empieza seleccionando arbitrariamente un punto (hasta visitar todos).
 Se define un entorno alrededor de dicho punto, y una cantidad mínima de puntos para
formar el grupo. Entonces, a partir del entorno mencionado y dicha cantidad, se
adicionarán nuevos puntos siempre que, dentro del entorno del punto del grupo “caigan”
como mínimo la cantidad de puntos nuevos señalada. Asimismo, alrededor de estos puntos
se armará un nuevo entorno con el mismo tamaño que antes, y se adicionarán nuevos
puntos siempre que estén alcanzados por el entorno y en suma sean al menos la cantidad
mínima señalada.
 Los grupos se expanden sucesivamente hasta barrer a todos o no encontrar nuevos puntos
que adherir
Veamos este proceso con la siguiente sucesión de imágenes:

.
Las ventajas de este modelo se resumen en los siguientes puntos:
 No requiere un número K de clústeres como input, lo determina solo (pero requiere una
parametrizacion de distancia mínima y cantidad de observaciones).
 Permite clasificar o etiquetar como "ruido" a los puntos visitados que no entran en la
distancia mínima de ningún clúster.
Mientras que las desventajas son:
 Cuando hay clústeres de densidad variable anda muy mal, porque la épsilon es un
parámetro único. En este sentido si este parámetro es:
o Grande genera muchos clúster en las zonas menos densas.
o Chico deja las zonas menos densas como "ruido".
Veamos todo esto con el gráfico de dispersión en forma de medialuna:
Vemos cómo a medida que modificamos el tamaño del entorno, la cantidad de grupos crece, al
igual que los puntos aislados (que no forman grupos por no contener la cantidad mínima de puntos
predefinida).
Entrenamiento no supervisado – MeanShift
Este es similar al Kmeans, no es igual porque la diferencia es que automatiza la selección de la
cantidad de grupos, sin embargo, en lugar de ello lo que elegimos es el “ancho de banda” o
distancia entre puntos intra clúster. Por ende, sus ventajas son:
 No necesitas pasarle el número de clúster, lo calcula solo.
 No hay riesgo de overfiting.
Y sus desventajas son:
 Es muy lento.
 Igual que Kmeans no sirve para nubes de puntos no uniformes y radiales.
No se coloca el ejemplo porque es un método similar al Kmeans, no obstante, su aplicación
(ejemplo sencillo) se encuentra en el archivo de clase número 16.
Ejemplo de aplicación
Lo que mostraremos a continuación es el problema a resolver y su resultado, es decir, no se copian
las líneas de código (que se pueden buscar en el archivo de clase número 16). El problema a
resolver es el siguiente: ¿Existen zonas de precios para las cuales el precio de la acción es más o
menos sensible? En otras palabras, se entiende que existen zonas de precios del petróleo para las
cuales la cotización de las diferentes petroleras son más y menos sensibles. Por lo tanto, el objetivo
es encontrar estas zonas y calcular dicha correlación. Para resolverlo se aplica algún método de
clasificación sobre las dos dimensiones y para cada compañía (precio materia prima y cotización de
la firma en la bolsa), luego, teniendo cada grupo, se calcula la correlación con una regresión
simple. El resultado para el caso de la firma XOM es el siguiente:

A esto se aplicaron los métodos de cantidad óptima de grupos y también, otros métodos de
clasificación como el de mezcla Gaussinaa.
Aunque no está escrito, el ejercicio quiere que calculemos un retorno logarítmico (neperiano claro)
ACLARACION: El valor absoluto del quantil 95 vs 5 significa conocer qué valores de variación
corresponden a esos cuantiles para después calcular su cociente. En este sentido, siempre es
conveniente que la ratio sea mayor a 1, pues esto significa que en general, los mejores días
presentan subas más altas que las bajas de los peores días. Entonces, si vamos largo en un activo,
esta es una característica deseada. En este sentido, si la distribución fuese normal, el ratio entre
estos cuantiles sería 1, pero en general, dicha distribución no es una normal.
El aspecto que escapa a esta ratio es que se desconoce hasta dónde llegan los máximos y los
mínimos. En otras palabras, no tenemos el área de la distribución, sólo un punto a partir del cual
está cada quantil. Esto es importante, porque para recuperarse de una pérdida diaria de 10% vamos
a necesitar una suba de más del 10%, pero, si usamos retornos logarítmicos, esto no será así, pues
las bajas se recuperarán con aumentos similares. CONCLUSION: para usar ratios de percentiles,
los retornos a utilizar deben ser logarítimicos.
En la carpeta de la UCEMA hay un archivo metodológico sobre estos ratios.

Se toma la volatilidad de las ruedas del último año (en promedio, un año tiene 250 ruedas)
Podemos hacerlo con el where()

Utilizando la matriz de trades completar la siguiente tarea:


Un buen ejercicio con las API es descargar de la misma algún indicador como el cruce de medias o
el RSI, y luego comparar dicho resultado con el logrado por el código que uno mismo prepara
(claro que utilizamos el mismo precio)

También podría gustarte