Capítulo 4. Finanzas basadas en datos

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

Si la inteligencia artificial es la nueva electricidad, el big data es el aceite que alimenta losgeneradores.

Kai-Fu Lee (2018)

Hoy en día, los analistas examinan información no tradicional, como imágenes por satélite y datos de tarjetas de crédito, o utilizan técnicas de inteligencia artificial, como el aprendizaje automático y el procesamiento del lenguaje natural, para extraer nuevas perspectivas de fuentes tradicionales, como datos económicos y transcripciones de llamadas de beneficios.

Robin Wigglesworth (2019)

Este capítulo trata aspectos centrales de las finanzas basadas en datos. A efectos de este libro, se entiende por finanzas basadas en datos un contexto financiero (teoría, modelo, aplicación, etc.) que se rige principalmente por los datos y se basa en ellos.

"Método Científico" trata sobre el método científico, que trata sobre los principios generalmente aceptados que deben guiar el esfuerzo científico. "Econometría financiera y regresión" trata sobre econometría financiera y temas relacionados. "Disponibilidad de datos" arroja luz sobre qué tipos de datos (financieros) están disponibles hoy en día y en qué calidad y cantidad a través de API programáticas. "Teorías normativas revisitadas" retoma las teorías normativas del Capítulo 3 y las analiza basándose en datos reales de series temporales financieras. También basado en datos financieros reales, "Desacreditando los supuestos centrales" desacredita dos de los supuestos más habituales en los modelos y teorías financieras: la normalidad de los rendimientos ylas relaciones lineales.

Método científico

El método científico se refiere a un conjunto de principios generalmente aceptados que deben guiar cualquier proyecto científico. Wikipedia define el método científico de la siguiente manera:

El método científico es un método empírico de adquisición de conocimientos que ha caracterizado el desarrollo de la ciencia desde al menos el siglo XVII. Implica una observación cuidadosa, aplicando un escepticismo riguroso sobre lo que se observa, dado que los supuestos cognitivos pueden distorsionar la forma de interpretar la observación. Implica la formulación de hipótesis, mediante inducción, basadas en dichas observaciones; la comprobación experimental y basada en mediciones de las deducciones extraídas de las hipótesis; y el refinamiento (o eliminación) de las hipótesis basándose en los resultados experimentales. Éstos sonprincipios del método científico, a diferencia de una serie definitiva de pasosaplicables a todas las empresas científicas.

Dada esta definición, las finanzas normativas, tal y como se expuso en el Capítulo 3,contrastan fuertemente con el método científico. Las teorías financieras normativas se basan principalmente en suposiciones y axiomas en combinación con la deducción como principal método analítico para llegar a sus resultados centrales.

  • La teoría de la utilidad esperada (TUE) supone que los agentes tienen la misma función de utilidad independientemente del estado en que se desarrolle el mundo y que maximizan la utilidad esperada en condiciones de incertidumbre.

  • La teoría de la cartera media-varianza (MVP) describe cómo deben invertir los inversores en condiciones de incertidumbre , suponiendo que sólo cuentan el rendimiento esperado y la volatilidad esperada de una cartera durante un periodo.

  • El modelo de valoración de activos de capital (CAPM) supone que sólo el riesgo de mercado no diversificable explica el rendimiento esperado y la volatilidad esperada de una acción durante un periodo.

  • La teoría de los precios de arbitraje (APT) supone que una serie de factores de riesgo identificables explican la rentabilidad esperada y la volatilidad esperada de una acción a lo largo del tiempo; hay que reconocer que, en comparación con las demás teorías, la formulación de la APT es bastante amplia y permite interpretaciones de gran alcance.

Lo que caracteriza a las teorías financieras normativas mencionadas es que se derivaron originalmente bajo ciertos supuestos y axiomas utilizando únicamente "lápiz y papel", sin recurrir a datos u observaciones del mundo real. Desde un punto de vista histórico, muchas de estas teorías no se probaron rigurosamente con datos del mundo real hasta mucho después de sus fechas de publicación. Esto puede explicarse principalmente por la mayor disponibilidad de datos y el aumento de las capacidades computacionales a lo largo del tiempo. Al fin y al cabo, los datos y el cálculo son los ingredientes principales para la aplicación de los métodos estadísticos en la práctica. La disciplina en la intersección de las matemáticas, la estadística y las finanzas que aplica dichos métodos a los datos de los mercados financieros suele denominarse econometría financiera, tema de la siguiente sección.

Econometría financiera y regresión

Adaptando la definición que da Investopedia para la econometría, se puede definir la econometría financiera del siguiente modo:

La econometría [financiera] es la aplicación cuantitativa de modelos estadísticos y matemáticos que utilizan datos [financieros] para desarrollar teorías financieras o probar hipótesis existentes en finanzas y para predecir tendencias futuras a partir de datos históricos. Somete los datos [financieros] del mundo real a pruebas estadísticas y, a continuación, compara y contrasta los resultados con la teoría o teorías [financieras] puestas a prueba.

Alexander (2008b) ofrece una introducción completa y amplia al campo de la econometría financiera. El segundo capítulo del libro abarca los modelos monofactoriales y multifactoriales, como el CAPM y el APT. Alexander (2008b) forma parte de una serie de cuatro libros titulada Análisis del Riesgo de Mercado. El primero de la serie, Alexander (2008a), abarca conceptos teóricos de base, temas y métodos, como la teoría MVP y el propio CAPM. El libro de Campbell (2018) es otro recurso exhaustivo para la teoría financiera y la investigación econométrica relacionada.

Una de las principales herramientas de la econometría financiera es la regresión, tanto en su forma univariante como multivariante. La regresión es también una herramienta central del aprendizaje estadístico en general. ¿Cuál es la diferencia entre las matemáticas tradicionales y el aprendizaje estadístico? Aunque no existe una respuesta general a esta pregunta (al fin y al cabo, la estadística es un subcampo de las matemáticas), un ejemplo sencillo debería poner de relieve una diferencia importante relevante para el contexto de este libro.

La primera es la forma matemática estándar. Supongamos que una función matemática viene dada de la siguiente manera:

f : + , x 2 + 1 2 x

Dados varios valores de x i , i = 1 , 2 , ... , n se pueden obtener los valores de las funciones f aplicando la definición anterior:

y i = f ( x i ) , i = 1 , 2 , ... , n

El siguiente código Python lo ilustra a partir de un sencillo ejemplo numérico:

In [1]: import numpy as np

In [2]: def f(x):
            return 2 + 1 / 2 * x

In [3]: x = np.arange(-4, 5)
        x
Out[3]: array([-4, -3, -2, -1,  0,  1,  2,  3,  4])

In [4]: y = f(x)
        y
Out[4]: array([0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. ])

El segundo es el enfoque adoptado en el aprendizaje estadístico. Mientras que en el ejemplo anterior, primero se da la función y luego se obtienen los datos, esta secuencia se invierte en el aprendizaje estadístico. Aquí, por lo general, se dan los datos y hay que encontrar una relación funcional. En este contexto x suele denominarse variable independiente e y la variable dependiente. En consecuencia, considera los siguientes datos:

( x i , y i ) , i = 1 , 2 , ... , n

El problema consiste en encontrar, por ejemplo, los parámetros α , β tales que

f ^ ( x i ) α + β x i = y ^ i y i , i = 1 , 2 , ... , n

Otra forma de escribirlo es incluyendo valores residuales ϵ i , i = 1 , 2 , ... , n :

α + β x i + ϵ i = y i , i = 1 , 2 , ... , n

En el contexto de la regresión por mínimos cuadrados ordinarios (MCO), α , β se eligen para minimizar el error cuadrático medio entre los valores aproximados y ^ i y los valores reales y i . El problema de minimización, por tanto, es el siguiente

min α,β 1 n i n (y ^ i -y i ) 2

En el caso de la regresión MCO simple, como se ha descrito anteriormente, las soluciones óptimas se conocen en forma cerrada y son las siguientes:

β = Cov(x,y) Var(x) α = y ¯ - β x ¯

Aquí, Cov ( ) representa la covarianza, Var ( ) para la varianza, y x ¯ , y ¯ para los valores medios de x , y .

Volviendo al ejemplo numérico anterior, estas ideas pueden utilizarse para obtener los parámetros óptimos α , β y, en este caso concreto, para recuperar la definición original de f ( x ) :

In [5]: x
Out[5]: array([-4, -3, -2, -1,  0,  1,  2,  3,  4])

In [6]: y
Out[6]: array([0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. ])

In [7]: beta = np.cov(x, y, ddof=0)[0, 1] / x.var()  1
        beta  1
Out[7]: 0.49999999999999994

In [8]: alpha = y.mean() - beta * x.mean()  2
        alpha  2
Out[8]: 2.0

In [9]: y_ = alpha + beta * x  3

In [10]: np.allclose(y_, y)  4
Out[10]: True
1

β derivada de la matriz de covarianza y de la varianza

2

α derivado de β y los valores medios

3

Valores estimados y ^ i , i = 1 , 2 , ... , n dada α , β

4

Comprueba si y ^ i , y i son numéricamente iguales

El ejemplo anterior y los del Capítulo 1 ilustran que la aplicación de la regresión MCO a un conjunto de datos dado es, en general, sencilla. Hay más razones por las que la regresión MCO se ha convertido en una de las herramientas centrales de la econometría y la econometría financiera. Entre ellas están las siguientes:

Siglos de antigüedad

El enfoque de mínimos cuadrados, sobre todo en combinación con la regresión, se utiliza desde hace más de 200 años.1

Simplicidad

Las matemáticas que hay detrás de la regresión MCO son fáciles de entender y de aplicar en programación.

Escalabilidad

Básicamente, no hay límite en cuanto al tamaño de los datos a los que se puede aplicar la regresión MCO.

Flexibilidad

La regresión MCO puede aplicarse a una amplia gama de problemas y conjuntos de datos.

Velocidad

La regresión MCO es rápida de evaluar, incluso en grandes conjuntos de datos.

Disponibilidad

Existen implementaciones eficientes en Python y en muchos otros lenguajes de programación.

Sin embargo, por muy fácil y sencilla que sea la aplicación de la regresión MCO en general, el método se basa en una serie de supuestos -la mayoría de ellos relacionados con los residuos- que no siempre se cumplen en la práctica.

Linealidad

El modelo es lineal en sus parámetros, tanto en lo que respecta a los coeficientes como a los residuos.

Independencia

Las variables independientes no están perfectamente correlacionadas entre sí (en un alto grado) (no hay multicolinealidad).

Media cero

El valor medio de los residuos es (próximo a) cero.

Sin correlación

Los residuos no están (fuertemente) correlacionados con las variables independientes.

Homocedasticidad

La desviación típica de los residuos es (casi) constante.

Sin autocorrelación

Los residuos no están (fuertemente) correlacionados entre sí.

En la práctica, en general es bastante sencillo comprobar la validez de los supuestos a partir de un conjunto de datos concreto.

Disponibilidad de datos

La econometría financiera se rige por métodos estadísticos, como la regresión, y por la disponibilidad de datos financieros. Desde la década de 1950 hasta la de 1990, e incluso hasta principios de la década de 2000, la investigación financiera teórica y empírica se basaba principalmente en conjuntos de datosrelativamente pequeños en comparación con los estándares actuales, y se componía sobre todo de datosdel final del día (EOD). La disponibilidad de datos es algo que ha cambiado drásticamente en la última década aproximadamente, con cada vez más tipos de datos financieros y de otro tipo disponibles en una granularidad, cantidad y velocidad cada vez mayores.

API programáticas

En lo que respecta a las finanzas basadas en datos, lo importante no es sólo qué datos están disponibles, sino también cómo se puede acceder a ellos y procesarlos. Desde hace tiempo, los profesionales de las finanzas confían en los terminales de datos de empresas como Refinitiv (ver Terminal Eikon) o Bloomberg (ver Terminal Bloomberg), por mencionar sólo dos de los principales proveedores. Los periódicos, revistas, informes financieros y similares han sido sustituidos hace tiempo por dichos terminales como fuente principal de información financiera. Sin embargo, el enorme volumen y variedad de datos que proporcionan estos terminales no pueden ser consumidos sistemáticamente por un solo usuario, ni siquiera por grandes grupos de profesionales de las finanzas. Por lo tanto, el mayor avance en las finanzas basadas en datos se encuentra en la disponibilidad programática de datos a través de interfaces de programación de aplicaciones (API) que permiten el uso de código informático para seleccionar, recuperar y procesar conjuntos de datos arbitrarios.

El resto de esta sección está dedicado a la ilustración de tales API mediante las cuales incluso los académicos y los inversores minoristas pueden recuperar una gran cantidad de conjuntos de datos diferentes. Antes de proporcionar dichos ejemplos, la Tabla 4-1 ofrece una visión general de las categorías de datos que, en general, son relevantes en un contexto financiero, así como ejemplos típicos. En la tabla, los datos estructurados se refieren a tipos de datos numéricos que a menudo vienen en estructuras tabulares, mientras que los datos no estructurados se refieren a datos en forma de texto estándar que a menudo no tienen estructura más allá de encabezados o párrafos, por ejemplo. Los datos alternativos se refieren a tipos de datos que no suelen considerarse datos financieros.

Tabla 4-1. Tipos de datos financieros relevantes
Tiempo Datos estructurados Datos no estructurados Datos alternativos

Histórico

Precios, fundamentos

Noticias, textos

Web, redes sociales, satélites

Streaming

Precios, volúmenes

Noticias, archivos

Web, redes sociales, satélites, Internet de las Cosas

Datos históricos estructurados

En primer lugar, los tipos de datos históricos estructurados se recuperarán mediante programación. Para ello, el siguiente código Python utiliza la API de datos de Eikon.2

Para acceder a los datos a través de la API de datos de Eikon, debe estar ejecutándose una aplicación local, como Refinitiv Workspace, y el acceso a la API debe estar configurado en el nivel Python:

In [11]: import eikon as ek
         import configparser

In [12]: c = configparser.ConfigParser()
         c.read('../aiif.cfg')
         ek.set_app_key(c['eikon']['app_id'])
         2020-08-04 10:30:18,059 P[14938] [MainThread 4521459136] Error on handshake
          port 9000 : ReadTimeout(ReadTimeout())

Si se cumplen estos requisitos, se pueden recuperar datos históricos estructurados mediante una sola llamada a una función. Por ejemplo, el siguiente código Python recupera datos EOD para un conjunto de símbolos y un intervalo de tiempo especificado:

In [14]: symbols = ['AAPL.O', 'MSFT.O', 'NFLX.O', 'AMZN.O']  1

In [15]: data = ek.get_timeseries(symbols,
                                  fields='CLOSE',
                                  start_date='2019-07-01',
                                  end_date='2020-07-01')  2


In [16]: data.info()  3
         <class 'pandas.core.frame.DataFrame'>
         DatetimeIndex: 254 entries, 2019-07-01 to 2020-07-01
         Data columns (total 4 columns):
          #   Column  Non-Null Count  Dtype
         ---  ------  --------------  -----
          0   AAPL.O  254 non-null    float64
          1   MSFT.O  254 non-null    float64
          2   NFLX.O  254 non-null    float64
          3   AMZN.O  254 non-null    float64
         dtypes: float64(4)
         memory usage: 9.9 KB


In [17]: data.tail()  4
Out[17]: CLOSE       AAPL.O  MSFT.O  NFLX.O   AMZN.O
         Date
         2020-06-25  364.84  200.34  465.91  2754.58
         2020-06-26  353.63  196.33  443.40  2692.87
         2020-06-29  361.78  198.44  447.24  2680.38
         2020-06-30  364.80  203.51  455.04  2758.82
         2020-07-01  364.11  204.70  485.64  2878.70
1

Define una lista de RICs (símbolos) para recuperar datos de3

2

Recupera los precios EOD Close de la lista de RICs

3

Muestra la metainformación del objeto DataFrame devuelto

4

Muestra las filas finales del objeto DataFrame

Del mismo modo, se pueden recuperar barras de un minuto con campos OHLC con los ajustes adecuados de los parámetros:

In [18]: data = ek.get_timeseries('AMZN.O',
                                  fields='*',
                                  start_date='2020-08-03',
                                  end_date='2020-08-04',
                                  interval='minute')  1

In [19]: data.info()
         <class 'pandas.core.frame.DataFrame'>
         DatetimeIndex: 911 entries, 2020-08-03 08:01:00 to 2020-08-04 00:00:00
         Data columns (total 6 columns):
          #   Column  Non-Null Count  Dtype
         ---  ------  --------------  -----
          0   HIGH    911 non-null    float64
          1   LOW     911 non-null    float64
          2   OPEN    911 non-null    float64
          3   CLOSE   911 non-null    float64
          4   COUNT   911 non-null    float64
          5   VOLUME  911 non-null    float64
         dtypes: float64(6)
         memory usage: 49.8 KB

In [20]: data.head()
Out[20]: AMZN.O                  HIGH      LOW     OPEN    CLOSE  COUNT  VOLUME
         Date
         2020-08-03 08:01:00  3190.00  3176.03  3176.03  3178.17   18.0   383.0
         2020-08-03 08:02:00  3183.02  3176.03  3180.00  3177.01   15.0   513.0
         2020-08-03 08:03:00  3179.91  3177.05  3179.91  3177.05    5.0    14.0
         2020-08-03 08:04:00  3184.00  3179.91  3179.91  3184.00    8.0   102.0
         2020-08-03 08:05:00  3184.91  3182.91  3183.30  3184.00   12.0   403.0
1

Recupera barras de un minuto con todos los campos disponibles para un solo RIC

Se pueden recuperar más que datos estructurados de series temporales financieras a partir de la API de Datos de Eikon. También se pueden recuperar datos fundamentales para varios RICs y varios campos de datos diferentes al mismo tiempo, como ilustra el siguiente código Python:

In [21]: data_grid, err = ek.get_data(['AAPL.O', 'IBM', 'GOOG.O', 'AMZN.O'],
                                      ['TR.TotalReturnYTD', 'TR.WACCBeta',
                                       'YRHIGH', 'YRLOW',
                                       'TR.Ebitda', 'TR.GrossProfit'])  1

In [22]: data_grid
Out[22]:   Instrument  YTD Total Return      Beta   YRHIGH      YRLOW        EBITDA  \
         0     AAPL.O         49.141271  1.221249   425.66   192.5800  7.647700e+10
         1        IBM         -5.019570  1.208156   158.75    90.5600  1.898600e+10
         2     GOOG.O         10.278829  1.067084  1586.99  1013.5361  4.757900e+10
         3     AMZN.O         68.406897  1.338106  3344.29  1626.0318  3.025600e+10

            Gross Profit
         0   98392000000
         1   36488000000
         2   89961000000
         3  114986000000
1

Recupera datos de múltiples RICs y múltiples campos de datos

Disponibilidad de datos programáticos

Hoy en día, prácticamente todos los datos financieros estructurados están disponibles de forma programática. Los datos de series temporales financieras, en este contexto, son el ejemplo supremo. Sin embargo, otros tipos de datos estructurados, como los datos fundamentales, están disponibles del mismo modo, lo que simplifica considerablemente el trabajo de los analistas cuantitativos, operadores, gestores de carteras y similares.

Datos de flujo estructurados

Muchas aplicaciones en finanzas requieren datos estructurados en tiempo real, como en la negociación algorítmica o la gestión del riesgo de mercado. El siguiente código Python hace uso de la API de la Plataforma de Negociación Oanda y transmite en tiempo real una serie de marcas de tiempo, cotizaciones de compra y venta para el precio del Bitcoin en USD:

In [23]: import tpqoa

In [24]: oa = tpqoa.tpqoa('../aiif.cfg')  1

In [25]: oa.stream_data('BTC_USD', stop=5)  2
         2020-08-04T08:30:38.621075583Z 11298.8 11334.8
         2020-08-04T08:30:50.485678488Z 11298.3 11334.3
         2020-08-04T08:30:50.801666847Z 11297.3 11333.3
         2020-08-04T08:30:51.326269990Z 11296.0 11332.0
         2020-08-04T08:30:54.423973431Z 11296.6 11332.6
1

Conecta con la API de Oanda

2

Transmite un número fijo de ticks para un símbolo determinado

La impresión de los campos de datos transmitidos es, por supuesto, sólo ilustrativa. Algunas aplicaciones financieras pueden requerir un procesamiento sofisticado de los datos recuperados y la generación de señales o estadísticas, por ejemplo. Especialmente durante los días laborables y las horas de negociación, el número de ticks de precios transmitidos para los instrumentos financieros aumenta constantemente, lo que exige potentes capacidades de procesamiento de datos por parte de las instituciones financieras que necesitan procesar dichos datos en tiempo real o, al menos, en tiempo casi real ("near time").

La importancia de esta observación queda clara al observar los precios de las acciones de Apple Inc. Se puede calcular que hay aproximadamente 252 - 40 = 10 , 080 Cotizaciones de cierre EOD de las acciones de Apple durante un periodo de 40 años. (Apple Inc. salió a bolsa el 12 de diciembre de 1980.) El código siguiente recupera datos de ticks para el precio de las acciones de Apple durante una hora solamente. El conjunto de datos recuperado, que puede que ni siquiera esté completo para el intervalo de tiempo dado, tiene 50.000 filas de datos, o cinco veces más cotizaciones tick que las cotizaciones EOD acumuladas durante 40 años de cotización:

In [26]: data = ek.get_timeseries('AAPL.O',
                                  fields='*',
                                  start_date='2020-08-03 15:00:00',
                                  end_date='2020-08-03 16:00:00',
                                  interval='tick')  1

In [27]: data.info()
         <class 'pandas.core.frame.DataFrame'>
         DatetimeIndex: 50000 entries, 2020-08-03 15:26:24.889000 to 2020-08-03
          15:59:59.762000
         Data columns (total 2 columns):
          #   Column  Non-Null Count  Dtype
         ---  ------  --------------  -----
          0   VALUE   49953 non-null  float64
          1   VOLUME  50000 non-null  float64
         dtypes: float64(2)
         memory usage: 1.1 MB

In [28]: data.head()
Out[28]: AAPL.O                    VALUE  VOLUME
         Date
         2020-08-03 15:26:24.889  439.06   175.0
         2020-08-03 15:26:24.889  439.08     3.0
         2020-08-03 15:26:24.890  439.08   100.0
         2020-08-03 15:26:24.890  439.08     5.0
         2020-08-03 15:26:24.899  439.10    35.0
1

Recupera los datos de la cotización de las acciones de Apple

Datos EOD frente a datos Tick

La mayoría de las teorías financieras que se siguen aplicando hoy en día tienen su origen en la época en que los datos EOD eran básicamente el único tipo de datos financieros disponibles. Hoy en día, las instituciones financieras, e incluso los operadores e inversores minoristas, se enfrentan a flujos interminables de datos en tiempo real. El ejemplo de las acciones de Apple ilustra que, para una sola acción durante una hora de negociación, puede haber cuatro veces más ticks que la cantidad de datos EOD acumulados durante un periodo de 40 años. Esto no sólo desafía a los agentes de los mercados financieros, sino que también pone en duda si las teorías financieras existentes pueden aplicarse a un entorno así.

Datos históricos no estructurados

Muchas fuentes de datos importantes en finanzas sólo proporcionan datos no estructurados, como noticias financieras o archivos de empresas. Sin duda, las máquinas son mucho mejores y más rápidas que los humanos a la hora de procesar grandes cantidades de datos numéricos estructurados. Sin embargo, los recientes avances en el procesamiento del lenguaje natural (PLN ) hacen que las máquinas también sean mejores y más rápidas procesando noticias financieras, por ejemplo. En 2020, los proveedores de servicios de datos ingieren aproximadamente 1,5 millones de artículos de noticias al día. Está claro que esta ingente cantidad de datos basados en texto no puede ser procesada adecuadamente por los seres humanos.

Afortunadamente, hoy en día los datos no estructurados también están disponibles en gran medida a través de API programáticas. El siguiente código Python recupera varios artículos de noticias de la API de datos de Eikon relacionados con la empresa Tesla, Inc. y su producción. Se selecciona un artículo y se muestra completo:

In [29]: news = ek.get_news_headlines('R:TSLA.O PRODUCTION',
                                  date_from='2020-06-01',
                                  date_to='2020-08-01',
                                  count=7
                                 )  1

In [30]: news
Out[30]:                                           versionCreated  \
         2020-07-29 11:02:31.276 2020-07-29 11:02:31.276000+00:00
         2020-07-28 00:59:48.000        2020-07-28 00:59:48+00:00
         2020-07-23 21:20:36.090 2020-07-23 21:20:36.090000+00:00
         2020-07-23 08:22:17.000        2020-07-23 08:22:17+00:00
         2020-07-23 07:08:48.000        2020-07-23 07:46:56+00:00
         2020-07-23 00:55:54.000        2020-07-23 00:55:54+00:00
         2020-07-22 21:35:42.640 2020-07-22 22:13:26.597000+00:00

                                                                          text  \
         2020-07-29 11:02:31.276  Tesla Launches Hiring Spree in China as It Pre...
         2020-07-28 00:59:48.000    Tesla hiring in Shanghai as production ramps up
         2020-07-23 21:20:36.090     Tesla speeds up Model 3 production in Shanghai
         2020-07-23 08:22:17.000  UPDATE 1-'Please mine more nickel,' Musk urges...
         2020-07-23 07:08:48.000  'Please mine more nickel,' Musk urges as Tesla...
         2020-07-23 00:55:54.000  USA-Tesla choisit le Texas pour la production ...
         2020-07-22 21:35:42.640  TESLA INC - THE REAL LIMITATION ON TESLA GROWT...

                                                                       storyId  \
         2020-07-29 11:02:31.276  urn:newsml:reuters.com:20200729:nCXG3W8s9X:1
         2020-07-28 00:59:48.000  urn:newsml:reuters.com:20200728:nL3N2EY3PG:8
         2020-07-23 21:20:36.090  urn:newsml:reuters.com:20200723:nNRAcf1v8f:1
         2020-07-23 08:22:17.000  urn:newsml:reuters.com:20200723:nL3N2EU1P9:1
         2020-07-23 07:08:48.000  urn:newsml:reuters.com:20200723:nL3N2EU0HH:1
         2020-07-23 00:55:54.000  urn:newsml:reuters.com:20200723:nL5N2EU03M:1
         2020-07-22 21:35:42.640  urn:newsml:reuters.com:20200722:nFWN2ET120:2

                                 sourceCode
         2020-07-29 11:02:31.276  NS:CAIXIN
         2020-07-28 00:59:48.000    NS:RTRS
         2020-07-23 21:20:36.090  NS:SOUTHC
         2020-07-23 08:22:17.000    NS:RTRS
         2020-07-23 07:08:48.000    NS:RTRS
         2020-07-23 00:55:54.000    NS:RTRS
         2020-07-22 21:35:42.640    NS:RTRS

In [31]: storyId = news['storyId'][1]  2

In [32]: from IPython.display import HTML

In [33]: HTML(ek.get_news_story(storyId)[:1148])  3
Out[33]: <IPython.core.display.HTML object>
Jan 06, 2020

Tesla, Inc.TSLA registered record production and deliveries of 104,891 and
112,000 vehicles, respectively, in the fourth quarter of 2019.

Notably, the company's Model S/X and Model 3 reported record production and
deliveries in the fourth quarter. The Model S/X division recorded production
and delivery volume of 17,933 and 19,450 vehicles, respectively. The Model 3
division registered production of 86,958 vehicles, while 92,550 vehicles were
delivered.

In 2019, Tesla delivered 367,500 vehicles, reflecting an increase of 50%, year
over year, and nearly in line with the company's full-year guidance of 360,000
vehicles.
1

Recupera los metadatos de una serie de artículos de noticias comprendidos en el intervalo de parámetros

2

Selecciona un storyId del que recuperar el texto completo

3

Recupera el texto completo del artículo seleccionado y lo muestra

Datos de flujo no estructurados

Del mismo modo que se recuperan los datos históricos no estructurados, pueden utilizarse API programáticas para transmitir datos de noticias no estructurados, por ejemplo, en tiempo real o al menos casi. Una de estas API está disponible para DNA: la plataforma Datos, Noticias, Análisis de Dow Jones. La Figura 4-1 muestra la captura de pantalla de una aplicación web que transmite artículos de "Noticias financieras y sobre materias primas" y los procesa con técnicas de PNL en tiempo real.

aiif 0401
Figura 4-1. Aplicación de flujo de noticias basada en el ADN (Dow Jones)

La aplicación de transmisión de noticias tiene las siguientes características principales:

Texto completo

El texto completo de cada artículo está disponible haciendo clic en el encabezamiento del artículo.

Resumen de palabras clave

Se crea un resumen de palabras clave y se imprime en la pantalla.

Análisis del sentimiento

Las puntuaciones de sentimiento se calculan y visualizan como flechas de colores. Los detalles se hacen visibles haciendo clic en las flechas.

Nube de palabras

Se crea un mapa de bits de resumen de la nube de palabras, que se muestra en miniatura y es visible tras hacer clic en la miniatura (ver Figura 4-2).

aiif 0402
Figura 4-2. Mapa de bits de nube de palabras mostrado en aplicación de flujo de noticias

Datos alternativos

Hoy en día, las instituciones financieras, y en particular los fondos de alto riesgo, explotan sistemáticamente una serie de fuentes de datos alternativas para obtener un perímetro en el comercio y la inversión. Un artículo reciente de Bloomberg enumera, entre otras, las siguientes fuentes de datos alternativas:

  • Datos extraídos de la web

  • Datos de origen colectivo

  • Tarjetas de crédito y sistemas de punto de venta (TPV)

  • Sentimiento en las redes sociales

  • Tendencias de búsqueda

  • Tráfico web

  • Datos de la cadena de suministro

  • Datos de producción de energía

  • Perfiles de consumidores

  • Imágenes de satélite/datos geoespaciales

  • Instalación de aplicaciones

  • Seguimiento de buques oceánicos

  • Wearables, drones, sensores del Internet de las Cosas (IoT)

A continuación se ilustra el uso de datos alternativos con dos ejemplos. El primero recupera y procesa los comunicados de prensa de Apple Inc. en forma de páginas HTML. Elsiguiente código Python hace uso de un conjunto de funciones de ayuda, como se muestra en "Código Python". En el código, se define una lista de URL, cada una de las cuales representa una página HTML con un comunicado de prensa de Apple Inc. A continuación, se recupera el código HTML sin procesar de cada comunicado de prensa. A continuación, se limpia el código sin procesar y se imprime un extracto de un comunicado de prensa:

In [34]: import nlp  1
         import requests

In [35]: sources = [
             'https://nr.apple.com/dE0b1T5G3u',  # iPad Pro
             'https://nr.apple.com/dE4c7T6g1K',  # MacBook Air
             'https://nr.apple.com/dE4q4r8A2A',  # Mac Mini
         ]  2

In [36]: html = [requests.get(url).text for url in sources]  3

In [37]: data = [nlp.clean_up_text(t) for t in html]  4

In [38]: data[0][536:1001]  5
Out[38]: ' display, powerful a12x bionic chip and face id introducing the new ipad pro
          with all-screen design and next-generation performance. new york apple today
          introduced the new ipad pro with all-screen design and next-generation
          performance, marking the biggest change to ipad ever. the all-new design
          pushes 11-inch and 12.9-inch liquid retina displays to the edges of ipad pro
          and integrates face id to securely unlock ipad with just a glance.1 the a12x
          bionic chip w'
1

Importa las funciones de ayuda de PNL

2

Define las URL de los tres comunicados de prensa

3

Recupera los códigos HTML en bruto de los tres comunicados de prensa

4

Limpia los códigos HTML sin procesar (por ejemplo, se eliminan las etiquetas HTML)

5

Imprime un extracto de un comunicado de prensa

Por supuesto, definir los datos alternativos de forma tan amplia como se hace en esta sección implica que hay una cantidad ilimitada de datos que se pueden recuperar y procesar con fines económicos. En el fondo, éste es el negocio de los motores de búsqueda como el de Google LLC. En un contexto financiero, sería de vital importancia especificar exactamente qué fuentes de datos alternativas no estructuradas se pueden aprovechar.

El segundo ejemplo trata sobre la recuperación de datos de la red social Twitter, Inc. Para ello, Twitter proporciona acceso mediante la API a los tweets de su plataforma, siempre que uno haya configurado una cuenta de Twitter adecuadamente. El siguiente código Python se conecta a la API de Twitter y recupera e imprime los cinco tweets más recientes de mi cronología de inicio y de usuario, respectivamente:

In [39]: from twitter import Twitter, OAuth

In [40]: t = Twitter(auth=OAuth(c['twitter']['access_token'],
                                c['twitter']['access_secret_token'],
                                c['twitter']['api_key'],
                                c['twitter']['api_secret_key']),
                     retry=True)  1

In [41]: l = t.statuses.home_timeline(count=5)  2

In [42]: for e in l:
             print(e['text'])  2
         The Bank of England is effectively subsidizing polluting industries in its
          pandemic rescue program, a think tank sa https://t.co/Fq5jl2CIcp
         Cool shared task: mining scientific contributions (by @SeeTedTalk @SoerenAuer
          and Jennifer D'Souza)
         https://t.co/dm56DMUrWm
         Twelve people were hospitalized in Wyoming on Monday after a hot air balloon
          crash, officials said.

         Three hot air https://t.co/EaNBBRXVar
         President Trump directed controversial Pentagon pick into new role with
          similar duties after nomination failed https://t.co/ZyXpPcJkcQ
         Company announcement: Revolut launches Open Banking for its 400,000 Italian...
          https://t.co/OfvbgwbeJW #fintech

In [43]: l = t.statuses.user_timeline(screen_name='dyjh', count=5)  3

In [44]: for e in l:
             print(e['text'])  3
         #Python for #AlgoTrading (focus on the process) &amp; #AI in #Finance (focus
          on prediction methods) will complement eac https://t.co/P1s8fXCp42
         Currently putting finishing touches on #AI in #Finance (@OReillyMedia). Book
          going into production shortly. https://t.co/JsOSA3sfBL
         Chinatown Is Coming Back, One Noodle at a Time https://t.co/In5kXNeVc5
         Alt data industry balloons as hedge funds strive for Covid edge via @FT |
         "We remain of the view that alternative d… https://t.co/9HtUOjoEdz
         @Wolf_Of_BTC Just follow me on Twitter (or LinkedIn). Then you will notice for
          sure when it is out.
1

Conecta con la API de Twitter

2

Recupera e imprime cinco tweets (los más recientes) de la cronología de inicio

3

Recupera e imprime cinco tweets (los más recientes) de la línea de tiempo del usuario

La API de Twitter también permite realizar búsquedas, a partir de las cuales se pueden recuperar y procesar los tweets más recientes:

In [45]: d = t.search.tweets(q='#Python', count=7)  1

In [46]: for e in d['statuses']:
             print(e['text'])  1
         RT @KirkDBorne: #AI is Reshaping Programming — Tips on How to Stay on Top:
          https://t.co/CFNu1i352C
         
         Courses:
         1: #MachineLearning — Jupyte…
         RT @reuvenmlerner: Today, a #Python student's code didn't print:

         x = 5
         if x == 5:
             print: ('yes!')

         There was a typo, namely : after pr
         RT @GavLaaaaaaaa: Javascript Does Not Need a StringBuilder
          https://t.co/aS7NzHLO65 #programming #softwareengineering #bigdata
          #datascience…
         RT @CodeFlawCo: It is necessary to publish regular updates on Twitter
          #programmer #coder #developer #technology RT @pak_aims: Learning to C…
         RT @GavLaaaaaaaa: Javascript Does Not Need a StringBuilder
          https://t.co/aS7NzHLO65 #programming #softwareengineering #bigdata
          #datascience…
1

Busca tweets con el hashtag "Python" e imprime los cinco más recientes

También se puede recopilar un mayor número de tweets de un usuario de Twitter y crear un resumen en forma de nube de palabras (ver Figura 4-3). El siguiente código Python vuelve a utilizar las funciones de ayuda de PNL, como se muestra en "Código Python":

In [47]: l = t.statuses.user_timeline(screen_name='elonmusk', count=50)  1

In [48]: tl = [e['text'] for e in l]  2

In [49]: tl[:5]  3
Out[49]: ['@flcnhvy @Lindw0rm @cleantechnica True',
          '@Lindw0rm @cleantechnica Highly likely down the road',
          '@cleantechnica True fact',
         '@NASASpaceflight Scrubbed for the day. A Raptor turbopump spin start valve
          didnt open, triggering an automatic abo https://t.co/QDdlNXFgJg',
          '@Erdayastronaut I’m in the Boca control room. Hop attempt in ~33 minutes.']

In [50]: wc = nlp.generate_word_cloud(' '.join(tl), 35,
                     name='../../images/ch04/musk_twitter_wc.png'
                     )  4
1

Recupera los 50 tweets más recientes del usuario elonmusk

2

Recoge los textos en un objeto list

3

Muestra extractos de los últimos cinco tweets

4

Genera un resumen de la nube de palabras y muéstralo

aiif 0403
Figura 4-3. Nube de palabras como resumen para un mayor número de tweets

Una vez que un profesional financiero define los "datos financieros relevantes" para ir más allá de los datos de series temporales financieras estructuradas, las fuentes de datos parecen ilimitadas en cuanto a volumen, variedad y velocidad. La forma en que se recuperan los tweets de la API de Twitter es casi en tiempo casi real, ya que en los ejemplos se accede a los tweets más recientes. Estas y otras fuentes de datos similares basadas en API proporcionan, por tanto, un flujo interminable de datos alternativos para los que, como se ha señalado anteriormente, es importante especificar exactamente lo que se busca. De lo contrario, cualquier esfuerzo de ciencia de datos financieros podría ahogarse fácilmente en demasiados datos y/o datos demasiado ruidosos.

Revisión de las teorías normativas

El Capítulo 3 presenta las teorías financieras normativas, como la teoría MVP o el CAPM. Durante mucho tiempo, los estudiantes y académicos que aprendían y estudiaban dichas teorías estaban más o menos limitados a la teoría en sí. Con todos los datos financieros disponibles, como se ha comentado e ilustrado en la sección anterior, en combinación con un potente software de código abierto para el análisis de datos -como Python, NumPy, pandas, etc.-, se ha vuelto bastante fácil y sencillo poner a prueba las teorías financieras en el mundo real. Ya no hacen falta pequeños equipos ni grandes estudios para hacerlo. Basta con un cuaderno típico, acceso a Internet y un entorno Python estándar. De esto trata esta sección. Sin embargo, antes de sumergirnos en las finanzas basadas en datos, la siguiente subsección analiza brevemente algunas famosas paradojas en el contexto de la EUT y cómo las empresas modelan y predicen el comportamiento de los individuos en la práctica.

Utilidad Esperada y Realidad

En economía, el riesgo describe una situación en la que los posibles estados futuros y las probabilidades de que esos estados se desarrollen son conocidos de antemano por quien toma la decisión. Éste es el supuesto estándar en finanzas y en el contexto de la EUT. Por otro lado, la ambigüedad describe situaciones en economía en las que las probabilidades, o incluso los posibles estados futuros, no son conocidos de antemano por quien toma la decisión. La incertidumbre subsume las dos situaciones diferentes de toma de decisiones.

Existe una larga tradición de análisis del comportamiento concreto en la toma de decisiones de los individuos ("agentes") en condiciones de incertidumbre. Se han realizado innumerables estudios y experimentos para observar y analizar cómo se comportan los agentes ante la incertidumbre en comparación con lo que predicen teorías como la EUT. Durante siglos, las paradojas han desempeñado un papel importante en la teoría y la investigación sobre la toma de decisiones.

Una de ellas, la paradoja de San Petersburgo, dio lugar a la invención de las funciones de utilidad y la EUT en primer lugar. Daniel Bernoulli presentó la paradoja -y su solución- en 1738. La paradoja se basa en el siguiente juego de lanzamiento de monedas G . Un agente se enfrenta a un juego en el que se lanza una moneda (perfecta) potencialmente infinitas veces. Si tras el primer lanzamiento sale cara, el agente recibe una recompensa de 1 (unidad monetaria). Si sale cara, se vuelve a lanzar la moneda. En caso contrario, el juego termina. Si sale cara por segunda vez, el agente recibe una recompensa adicional de 2. Si sale cara por tercera vez, la recompensa adicional es de 4. Por cuarta vez es de 8, y así sucesivamente. Se trata de una situación de riesgo, ya que todos los posibles estados futuros, así como sus probabilidades asociadas, se conocen de antemano.

La ganancia esperada de este juego es infinita. Esto se deduce de la siguiente suma infinita, en la que cada elemento es estrictamente positivo:

𝐄 ( G ) = 1 2 - 1 + 1 4 - 2 + 1 8 - 4 + 1 16 - 8 + ... = k=1 1 2 k 2 k-1 = k=1 1 2 =

Sin embargo, ante un juego así, un decisor en general estaría dispuesto a pagar una suma finita sólo para jugar al juego. Una razón importante para ello es el hecho de que las ganancias relativamente grandes sólo ocurren con una probabilidad relativamente pequeña. Considera la ganancia potencial W = 511 :

W = 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 + 256 = 511

La probabilidad de ganar un premio así es bastante baja. Para ser exactos, es sólo P ( x = W ) = 1 512 = 0.001953125. Por otra parte, la probabilidad de que se produzca una ganancia de este tipo o menor es bastante alta:

P ( x W ) = k=1 9 1 2 k = 0 . 998046875

En otras palabras, en 998 de cada 1.000 partidas el premio es 511 o menor. Por lo tanto, un agente probablemente no apostaría mucho más de 511 para jugar a este juego. La salida a esta paradoja es la introducción de una función de utilidad con utilidad marginal positiva pero decreciente. En el contexto de la paradoja de San Petersburgo, esto significa que existe una función u : + que asigna a cada resultado positivo x un valor real u ( x ) . La utilidad marginal positiva pero decreciente se traduce entonces formalmente en lo siguiente:

u x > 0 2 u x 2 < 0

Como se vio en el capítulo 3, una de esas funciones candidatas es u ( x ) = ln ( x ) con:

u x = 1 x 2 u x 2 = - 1 x 2

La utilidad esperada es entonces finita, como ilustra el cálculo de la siguiente suma infinita:

𝐄 u ( G ) = k=1 1 2 k u 2 k-1 = k=1 ln2 k-1 2 k = k=1 (k-1) 2 k - ln ( 2 ) = ln ( 2 ) <

La utilidad esperada de ln ( 2 ) = 0,693147 es, obviamente, una cifra bastante pequeña en comparación con la utilidad esperada del infinito. Las funciones de utilidad Bernoulli y la EUT resuelven la paradoja de San Petersburgo.

Otras paradojas, como la paradoja de Allais publicada en Allais (1953), se refieren a la propia EUT. Esta paradoja se basa en un experimento con cuatro juegos diferentes que los sujetos deben clasificar. La Tabla 4-2 muestra los cuatro juegos ( A , B , A ' , B ' ) . La clasificación debe hacerse para los dos pares ( A , B ) y ( A ' , B ' ) . El axioma de independencia postula que la primera fila de la tabla no debe influir en la ordenación de ( A ' , B ' ) ya que la recompensa es la misma para ambos juegos.

Tabla 4-2. Juegos en la paradoja de Allais
Probabilidad Juego A Juego B Juego A Juego B

0.66

2,400

2,400

0

0

0.33

2,500

2,400

2,500

2,400

0.01

0

2,400

0

2,400

En los experimentos, la mayoría de los responsables de la toma de decisiones clasifican los juegos del siguiente modo: B A y A ' B ' . La clasificación B A conduce a las siguientes desigualdades, donde u 1 u ( 2400 ) , u 2 u ( 2500 ) , u 3 u ( 0 ) :

u 1 > 0 . 66 - u 1 + 0 . 33 - u 2 + 0 . 01 - u 3 0 . 34 - u 1 > 0 . 33 - u 2 + 0 . 01 - u 3

La clasificación A ' B ' conduce a su vez a las siguientes desigualdades:

0 . 33 - u 2 + 0 . 01 - u 3 > 0 . 33 - u 1 + 0 . 01 - u 1 0 . 34 - u 1 < 0 . 33 - u 2 + 0 . 01 - u 3

Obviamente, estas desigualdades se contradicen y dan lugar a la paradoja de Allais. Una posible explicación es que, en general, los decisores valoran la certeza más de lo que predicen los modelos típicos, como el EUT. La mayoría de la gente probablemente preferiría elegir recibir 1 millón de dólares con certeza que jugar a un juego en el que puede ganar 100 millones de dólares con una probabilidad del 5%, aunque existen varias funciones de utilidad adecuadas que, según la EUT, harían que el decisor eligiera el juego en lugar de la cantidad segura.

Otra explicación reside en el encuadre de las decisiones y en la psicología de quienes las toman. Es bien sabido que más gente aceptaría una intervención quirúrgica si tiene un "95% de probabilidades de éxito" que un "5% de probabilidades de muerte". Cambiar simplemente el enunciado podría conducir a un comportamiento incoherente con las teorías de la toma de decisiones, como la EUT.

Otra famosa paradoja que aborda las deficiencias de la EUT en su forma subjetiva, según Savage (1954, 1972), es la paradoja de Ellsberg, que se remonta al artículo seminal de Ellsberg (1961). Aborda la importancia de la ambigüedad en muchas situaciones de decisión del mundo real. Un escenario estándar para esta paradoja comprende dos urnas diferentes, ambas con exactamente 100 bolas. Para la urna 1, se sabe que contiene exactamente 50 bolas negras y 50 rojas. En el caso de la urna 2, sólo se sabe que contiene bolas negras y rojas, pero no en qué proporción.

Los sujetos de la prueba pueden elegir entre las siguientes opciones de juego:

  • Juego 1: rojo 1, negro 1, o indiferente

  • Juego 2: rojo 2, negro 2, o indiferente

  • Juego 3: rojo 1, rojo 2 o indiferente

  • Juego 4: negro 1, negro 2, o indiferente

Aquí, "rojo 1", por ejemplo, significa que se extrae una bola roja de la urna 1. Normalmente, un sujeto de pruebas respondería lo siguiente:

  • Juego 1: indiferente

  • Juego 2: indiferente

  • Juego 3: rojo 1

  • Juego 4: negro 1

Este conjunto de decisiones -que no es el único que se observa, pero sí uno habitual- ejemplifica lo que se denomina aversión a la ambigüedad. Puesto que en la urna 2 se desconocen las probabilidades de las bolas negra y roja, respectivamente, los que toman las decisiones prefieren una situación de riesgo a una de ambigüedad.

Las dos paradojas de Allais y Ellsberg demuestran que los sujetos de prueba reales se comportan muy a menudo de forma contraria a lo que predicen las teorías de la decisión bien establecidas en economía. En otras palabras, los seres humanos, como responsables de la toma de decisiones, no pueden compararse, en general, con máquinas que recopilan datos cuidadosamente y luego hacen números para tomar una decisión en condiciones de incertidumbre, ya sea en forma de riesgo o de ambigüedad. El comportamiento humano es más complejo de lo que sugieren actualmente la mayoría de las teorías, si no todas. Lo difícil y complejo que puede ser explicar el comportamiento humano queda claro tras leer, por ejemplo, el libro de 800 páginas Behave de Sapolsky (2018). Abarca múltiples facetas de este tema, desde los procesos bioquímicos hasta la genética, la evolución humana, las tribus, el lenguaje, la religión y mucho más, de forma integradora.

Si los paradigmas de decisión económica estándar, como el EUT, no explican demasiado bien la toma de decisiones en el mundo real, ¿qué alternativas existen? Los experimentos económicos que sientan las bases de la paradoxa de Allais y Ellsberg son un buen punto de partida para saber cómo se comportan los decisores en situaciones específicas y controladas. Dichos experimentos y sus resultados, a veces sorprendentes y paradójicos, han motivado de hecho a un gran número de investigadores a idear teorías y modelos alternativos que resuelvan la paradoxa. El libro El Experimento en la Historia de la Economía de Fontaine y Leonard (2005) trata sobre el papel histórico de los experimentos en economía. Existe, por ejemplo, toda una serie de literatura que aborda las cuestiones derivadas de la paradoja de Ellsberg. Esta literatura trata, entre otros temas, de las probabilidades no aditivas, las integrales de Choquet y heurísticas de decisión como la maximización de la ganancia mínima ("max-min") o la minimización de la pérdida máxima ("min-max"). Estos enfoques alternativos han demostrado ser superiores a la EUT, al menos en determinados escenarios de toma de decisiones. Pero están lejos de ser la corriente dominante en las finanzas.

Después de todo, ¿qué ha demostrado ser útil en la práctica? No es sorprendente que la respuesta esté en los datos y los algoritmos de aprendizaje automático. Internet, con sus miles de millones de usuarios, genera un tesoro de datos que describen el comportamiento humano en el mundo real, o lo que a veces se denomina preferencias reveladas. El big data generado en la web tiene una escala que es varios órdenes de magnitud mayor que lo que pueden generar los experimentos individuales. Empresas como Amazon, Facebook, Google y Twitter pueden ganar miles de millones de dólares registrando el comportamiento de los usuarios (es decir, sus preferencias reveladas) y aprovechando las ideas generadas por algoritmos de ML entrenados con estos datos.

El enfoque ML por defecto que se adopta en este contexto es el aprendizaje supervisado. Los algoritmos en sí son, en general, libres de teorías y modelos; a menudo se aplican variantes de redes neuronales. Por lo tanto, cuando las empresas de hoy en día predicen el comportamiento de sus usuarios o clientes, la mayoría de las veces se implementa un algoritmo de ML libre de modelos. Las teorías de decisión tradicionales, como la EUT o alguna de sus sucesoras, no suelen desempeñar ningún papel. Esto hace que resulte un tanto sorprendente que dichas teorías sigan siendo, a principios de la década de 2020, la piedra angular de la mayoría de las teorías económicas y financieras aplicadas en la práctica. Y esto sin mencionar el gran número de libros de texto financieros que tratan en detalle las teorías de decisión tradicionales. Si uno de los pilares más fundamentales de la teoría financiera parece carecer de apoyo empírico significativo o de beneficios prácticos, ¿qué ocurre con los modelos financieros que se construyen sobre él? Encontrarás más información al respecto en secciones y capítulos posteriores.

Predicciones del comportamiento basadas en datos

Las teorías económicas estándar de la decisión son intelectualmente atractivas para muchos, incluso para aquellos que, enfrentados a una decisión concreta bajo incertidumbre, se comportarían en contraste con las predicciones de las teorías. Por otra parte, los big data y los enfoques de aprendizaje supervisado sin modelos resultan útiles y exitosos en la práctica para predecir el comportamiento de usuarios y clientes. En un contexto financiero, esto podría implicar que uno no debería preocuparse realmente de por qué y cómo deciden los agentes financieros de la forma en que deciden. Más bien habría que centrarse en sus preferencias reveladas indirectamente a partir de datos de características (información nueva) que describen el estado de un mercado financiero y datos de etiquetas (resultados) que reflejan el impacto de las decisiones tomadas por los agentes financieros. Esto conduce a una visión de la toma de decisiones en los mercados financieros basada en datos en lugar de en teorías o modelos. Los agentes financieros se convierten en organismos procesadores de datos que pueden modelarse mucho mejor, por ejemplo, mediante redes neuronales complejas que, digamos, una simple función de utilidad en combinación con una distribución de probabilidad supuesta.

Teoría de la Cartera de Varianza Media

Supongamos que un inversor basado en datos quiere aplicar la teoría MVP para invertir en una cartera de valores tecnológicos y quiere añadir un fondo cotizado en bolsa (ETF) relacionado con el oro para diversificarse. Probablemente, el inversor accedería a los datos históricos de precios relevantes a través de una API de una plataforma de negociación o de un proveedor de datos. Para que el siguiente análisis sea reproducible, se basa en un archivo de datos CSV almacenado en una ubicación remota. El siguiente código Python recupera el archivo de datos, selecciona un número de símbolos dado el objetivo del inversor, y calcula los rendimientos logarítmicos a partir de los datos de las series temporales de precios. La Figura 4-4 compara las series temporales de precios normalizadas de lossímbolos seleccionados:

In [51]: import numpy as np
         import pandas as pd
         from pylab import plt, mpl
         from scipy.optimize import minimize
         plt.style.use('seaborn')
         mpl.rcParams['savefig.dpi'] = 300
         mpl.rcParams['font.family'] = 'serif'
         np.set_printoptions(precision=5, suppress=True,
                            formatter={'float': lambda x: f'{x:6.3f}'})

In [52]: url = 'http://hilpisch.com/aiif_eikon_eod_data.csv'  1

In [53]: raw = pd.read_csv(url, index_col=0, parse_dates=True).dropna()  1

In [54]: raw.info()  1
         <class 'pandas.core.frame.DataFrame'>
         DatetimeIndex: 2516 entries, 2010-01-04 to 2019-12-31
         Data columns (total 12 columns):
          #   Column  Non-Null Count  Dtype
         ---  ------  --------------  -----
          0   AAPL.O  2516 non-null   float64
          1   MSFT.O  2516 non-null   float64
          2   INTC.O  2516 non-null   float64
          3   AMZN.O  2516 non-null   float64
          4   GS.N    2516 non-null   float64
          5   SPY     2516 non-null   float64
          6   .SPX    2516 non-null   float64
          7   .VIX    2516 non-null   float64
          8   EUR=    2516 non-null   float64
          9   XAU=    2516 non-null   float64
          10  GDX     2516 non-null   float64
          11  GLD     2516 non-null   float64
         dtypes: float64(12)
         memory usage: 255.5 KB

In [55]: symbols = ['AAPL.O', 'MSFT.O', 'INTC.O', 'AMZN.O', 'GLD']  2

In [56]: rets = np.log(raw[symbols] / raw[symbols].shift(1)).dropna()  3

In [57]: (raw[symbols] / raw[symbols].iloc[0]).plot(figsize=(10, 6));  4
1

Recupera datos históricos de EOD desde una ubicación remota

2

Especifica los símbolos (RICs) en los que se va a invertir

3

Calcula los rendimientos logarítmicos de todas las series temporales

4

Traza la serie temporal financiera normalizada de los símbolos seleccionados

aiif 0404
Figura 4-4. Datos de series temporales financieras normalizadas

El inversor basado en datos quiere establecer primero una base de referencia para el rendimiento, dada por una cartera con la misma ponderación a lo largo de todo el periodo de datos disponibles. Para ello, el siguiente código Python define funciones para calcular la rentabilidad de la cartera, la volatilidad de la cartera y la ratio de Sharpe de la cartera, dado un conjunto de ponderaciones para los símbolos seleccionados:

In [58]: weights = len(rets.columns) * [1 / len(rets.columns)]  1

In [59]: def port_return(rets, weights):
             return np.dot(rets.mean(), weights) * 252  2

In [60]: port_return(rets, weights)  2
Out[60]: 0.15694764653018106

In [61]: def port_volatility(rets, weights):
             return np.dot(weights, np.dot(rets.cov() * 252 , weights)) ** 0.5  3

In [62]: port_volatility(rets, weights)  3
Out[62]: 0.16106507848480675

In [63]: def port_sharpe(rets, weights):
             return port_return(rets, weights) / port_volatility(rets, weights)  4

In [64]: port_sharpe(rets, weights)  4
Out[64]: 0.97443622172255
1

Cartera con igual ponderación

2

Rendimiento de la cartera

3

Volatilidad de la cartera

4

Ratio de Sharpe de la cartera (con tipo a corto cero)

El inversor también quiere analizar qué combinaciones de riesgo y rentabilidad de la cartera -y, en consecuencia, de la ratio de Sharpe- son aproximadamente posibles aplicando la simulación de Montecarlo para aleatorizar las ponderaciones de la cartera. Se excluyen las ventas en corto y se supone que las ponderaciones de la cartera suman 100%. El siguiente código Python implementa la simulación y visualiza los resultados (ver Figura 4-5):

In [65]: w = np.random.random((1000, len(symbols)))  1
         w = (w.T / w.sum(axis=1)).T  1

In [66]: w[:5]  1
Out[66]: array([[ 0.184,  0.157,  0.227,  0.353,  0.079],
                [ 0.207,  0.282,  0.258,  0.023,  0.230],
                [ 0.313,  0.284,  0.051,  0.340,  0.012],
                [ 0.238,  0.181,  0.145,  0.191,  0.245],
                [ 0.246,  0.256,  0.315,  0.181,  0.002]])

In [67]: pvr = [(port_volatility(rets[symbols], weights),
                 port_return(rets[symbols], weights))
                for weights in w]  2
         pvr = np.array(pvr)  2

In [68]: psr = pvr[:, 1] / pvr[:, 0]  3

In [69]: plt.figure(figsize=(10, 6))
         fig = plt.scatter(pvr[:, 0], pvr[:, 1],
                           c=psr, cmap='coolwarm')
         cb = plt.colorbar(fig)
         cb.set_label('Sharpe ratio')
         plt.xlabel('expected volatility')
         plt.ylabel('expected return')
         plt.title(' | '.join(symbols));
1

Simula que las ponderaciones de la cartera suman el 100%

2

Deduce las volatilidades y rendimientos resultantes de la cartera

3

Calcula los ratios de Sharpe resultantes

aiif 0405
Figura 4-5. Volatilidades, rendimientos y ratios de Sharpe de las carteras simuladas

El inversor basado en datos quiere ahora hacer un backtest del rendimiento de una cartera que se creó a principios de 2011. La composición óptima de la cartera se obtuvo a partir de los datos de series temporales financieras disponibles desde 2010. A principios de 2012, se ajustó la composición de la cartera a partir de los datos disponibles de 2011, y así sucesivamente. Para ello, el siguiente código Python obtiene las ponderaciones de la cartera para cada año relevante que maximiza la ratio de Sharpe:

In [70]: bnds = len(symbols) * [(0, 1),]  1
         bnds  1
Out[70]: [(0, 1), (0, 1), (0, 1), (0, 1), (0, 1)]

In [71]: cons = {'type': 'eq', 'fun': lambda weights: weights.sum() - 1}  2

In [72]: opt_weights = {}
         for year in range(2010, 2019):
             rets_ = rets[symbols].loc[f'{year}-01-01':f'{year}-12-31']  3
             ow = minimize(lambda weights: -port_sharpe(rets_, weights),
                           len(symbols) * [1 / len(symbols)],
                           bounds=bnds,
                           constraints=cons)['x']  4
             opt_weights[year] = ow  5

In [73]: opt_weights  5
Out[73]: {2010: array([ 0.366,  0.000,  0.000,  0.056,  0.578]),
          2011: array([ 0.543,  0.000,  0.077,  0.000,  0.380]),
          2012: array([ 0.324,  0.000,  0.000,  0.471,  0.205]),
          2013: array([ 0.012,  0.305,  0.219,  0.464,  0.000]),
          2014: array([ 0.452,  0.115,  0.419,  0.000,  0.015]),
          2015: array([ 0.000,  0.000,  0.000,  1.000,  0.000]),
          2016: array([ 0.150,  0.260,  0.000,  0.058,  0.533]),
          2017: array([ 0.231,  0.203,  0.031,  0.109,  0.426]),
          2018: array([ 0.000,  0.295,  0.000,  0.705,  0.000])}
1

Especifica los límites de las ponderaciones de un único activo

2

Especifica que todos los pesos deben sumar 100%.

3

Selecciona el conjunto de datos relevante para el año dado

4

Deduce las ponderaciones de la cartera que maximizan el ratio de Sharpe

5

Almacena estos pesos en un objeto dict

Las composiciones óptimas de cartera derivadas para los años pertinentes ilustran que la teoría MVP en su forma original conduce con bastante frecuencia a situaciones extremas (relativas) en el sentido de que uno o más activos no se incluyen en absoluto o que incluso un solo activo constituye el 100% de la cartera. Por supuesto, esto puede evitarse activamente estableciendo, por ejemplo, una ponderación mínima para cada activo considerado. Los resultados también indican que este enfoque conduce a reequilibrios significativos en la cartera, impulsados por las estadísticas y correlaciones realizadas el año anterior.

Para completar el backtest, el código siguiente compara las estadísticas previstas de la cartera (a partir de la composición óptima del año anterior aplicada a los datos del año anterior) con las estadísticas realizadas de la cartera del año en curso (a partir de la composición óptima del año anterior aplicada a los datos del año en curso):

In [74]: res = pd.DataFrame()
         for year in range(2010, 2019):
             rets_ = rets[symbols].loc[f'{year}-01-01':f'{year}-12-31']
             epv = port_volatility(rets_, opt_weights[year])  1
             epr = port_return(rets_, opt_weights[year])  1
             esr = epr / epv  1
             rets_ = rets[symbols].loc[f'{year + 1}-01-01':f'{year + 1}-12-31']
             rpv = port_volatility(rets_, opt_weights[year]) 2
             rpr = port_return(rets_, opt_weights[year])  2
             rsr = rpr / rpv  2
             res = res.append(pd.DataFrame({'epv': epv, 'epr': epr, 'esr': esr,
                                            'rpv': rpv, 'rpr': rpr, 'rsr': rsr},
                                           index=[year + 1]))

In [75]: res
Out[75]:            epv       epr       esr       rpv       rpr       rsr
         2011  0.157440  0.303003  1.924564  0.160622  0.133836  0.833235
         2012  0.173279  0.169321  0.977156  0.182292  0.161375  0.885256
         2013  0.202460  0.278459  1.375378  0.168714  0.166897  0.989228
         2014  0.181544  0.368961  2.032353  0.197798  0.026830  0.135645
         2015  0.160340  0.309486  1.930190  0.211368 -0.024560 -0.116194
         2016  0.326730  0.778330  2.382179  0.296565  0.103870  0.350242
         2017  0.106148  0.090933  0.856663  0.079521  0.230630  2.900235
         2018  0.086548  0.260702  3.012226  0.157337  0.038234  0.243004
         2019  0.323796  0.228008  0.704174  0.207672  0.275819  1.328147

In [76]: res.mean()
Out[76]: epv    0.190920
         epr    0.309689
         esr    1.688320
         rpv    0.184654
         rpr    0.123659
         rsr    0.838755
         dtype: float64
1

Estadísticas previstas de la cartera

2

Estadísticas de la cartera realizada

La Figura 4-6 compara las volatilidades esperada y realizada de la cartera para los años individuales. La teoría MVP hace un trabajo bastante bueno en la predicción de la volatilidad de la cartera. Esto también se ve respaldado por una correlación relativamente alta entre las dos series temporales:

In [77]: res[['epv', 'rpv']].corr()
Out[77]:           epv       rpv
         epv  1.000000  0.765733
         rpv  0.765733  1.000000

In [78]: res[['epv', 'rpv']].plot(kind='bar', figsize=(10, 6),
                 title='Expected vs. Realized Portfolio Volatility');
aiif 0406
Figura 4-6. Volatilidades esperadas frente a volatilidades realizadas de la cartera

Sin embargo, las conclusiones son opuestas cuando se comparan los rendimientos esperados de la cartera con los realizados (véase la Figura 4-7). Es evidente que la teoría MVP fracasa en la predicción de los rendimientos de la cartera, como confirma la correlación negativa entre las dos series temporales:

In [79]: res[['epr', 'rpr']].corr()
Out[79]:           epr       rpr
         epr  1.000000 -0.350437
         rpr -0.350437  1.000000

In [80]: res[['epr', 'rpr']].plot(kind='bar', figsize=(10, 6),
                 title='Expected vs. Realized Portfolio Return');
aiif 0407
Figura 4-7. Rentabilidad esperada de la cartera frente a la realizada

Hay que sacar conclusiones similares, o incluso peores, con respecto a la ratio de Sharpe (ver Figura 4-8). Para el inversor basado en datos que pretende maximizar la ratio de Sharpe de la cartera, las predicciones de la teoría suelen alejarse considerablemente de los valores realizados. La correlación entre las dos series temporales es incluso menor que para los rendimientos:

In [81]: res[['esr', 'rsr']].corr()
Out[81]:           esr       rsr
         esr  1.000000 -0.698607
         rsr -0.698607  1.000000

In [82]: res[['esr', 'rsr']].plot(kind='bar', figsize=(10, 6),
                 title='Expected vs. Realized Sharpe Ratio');
aiif 0408
Figura 4-8. Ratios de Sharpe de la cartera esperada frente a la realizada

Poder predictivo de la teoría MVP

La teoría MVP aplicada a los datos del mundo real revela sus deficiencias prácticas. Sin restricciones adicionales, las composiciones y reequilibrios óptimos de la cartera pueden ser extremos. El poder predictivo con respecto a la rentabilidad de la cartera y el ratio de Sharpe es bastante malo en el ejemplo numérico, mientras que el poder predictivo con respecto al riesgo de la cartera parece aceptable. Sin embargo, los inversores suelen estar interesados en medidas de rendimiento ajustadas al riesgo, como la ratio de Sharpe, y ésta es la estadística para la que la teoría MVP falla peor en el ejemplo.

Modelo de valoración de activos de capital

Se puede aplicar un enfoque similar para poner el CAPM a prueba en el mundo real. Supongamos que el inversor en tecnología basado en datos de antes quiere aplicar el CAPM para obtener la rentabilidad esperada de las cuatro acciones tecnológicas de antes. El siguiente código Python deduce primero la beta de cada acción para un año determinado y, a continuación, calcula el rendimiento esperado de la acción para el año siguiente, dada su beta y el rendimiento de la cartera de mercado. La cartera de mercado se aproxima mediante el índice bursátil S&P 500:

In [83]: r = 0.005  1

In [84]: market = '.SPX'  2

In [85]: rets = np.log(raw / raw.shift(1)).dropna()

In [86]: res = pd.DataFrame()

In [87]: for sym in rets.columns[:4]:
             print('\n' + sym)
             print(54 * '=')
             for year in range(2010, 2019):
                 rets_ = rets.loc[f'{year}-01-01':f'{year}-12-31']
                 muM = rets_[market].mean() * 252
                 cov = rets_.cov().loc[sym, market]  3
                 var = rets_[market].var()  3
                 beta = cov / var  3
                 rets_ = rets.loc[f'{year + 1}-01-01':f'{year + 1}-12-31']
                 muM = rets_[market].mean() * 252
                 mu_capm = r + beta * (muM - r)  4
                 mu_real = rets_[sym].mean() * 252  5
                 res = res.append(pd.DataFrame({'symbol': sym,
                                                'mu_capm': mu_capm,
                                                'mu_real': mu_real},
                                               index=[year + 1]),
                                 sort=True)  6
                 print('{} | beta: {:.3f} | mu_capm: {:6.3f} | mu_real: {:6.3f}'
                       .format(year + 1, beta, mu_capm, mu_real))  6
1

Especifica el tipo a corto sin riesgo

2

Define la cartera de mercado

3

Obtiene la beta de la acción

4

Calcula el rendimiento esperado dada la beta del año anterior y el rendimiento de la cartera de mercado del año en curso

5

Calcula el rendimiento realizado de la acción para el año en curso

6

Recoge e imprime todos los resultados

El código anterior proporciona la siguiente salida:

         AAPL.O
         ======================================================
         2011 | beta: 1.052 | mu_capm: -0.000 | mu_real:  0.228
         2012 | beta: 0.764 | mu_capm:  0.098 | mu_real:  0.275
         2013 | beta: 1.266 | mu_capm:  0.327 | mu_real:  0.053
         2014 | beta: 0.630 | mu_capm:  0.070 | mu_real:  0.320
         2015 | beta: 0.833 | mu_capm: -0.005 | mu_real: -0.047
         2016 | beta: 1.144 | mu_capm:  0.103 | mu_real:  0.096
         2017 | beta: 1.009 | mu_capm:  0.180 | mu_real:  0.381
         2018 | beta: 1.379 | mu_capm: -0.091 | mu_real: -0.071
         2019 | beta: 1.252 | mu_capm:  0.316 | mu_real:  0.621

         MSFT.O
         ======================================================
         2011 | beta: 0.890 | mu_capm:  0.001 | mu_real: -0.072
         2012 | beta: 0.816 | mu_capm:  0.104 | mu_real:  0.029
         2013 | beta: 1.109 | mu_capm:  0.287 | mu_real:  0.337
         2014 | beta: 0.876 | mu_capm:  0.095 | mu_real:  0.216
         2015 | beta: 0.955 | mu_capm: -0.007 | mu_real:  0.178
         2016 | beta: 1.249 | mu_capm:  0.113 | mu_real:  0.113
         2017 | beta: 1.224 | mu_capm:  0.217 | mu_real:  0.321
         2018 | beta: 1.303 | mu_capm: -0.086 | mu_real:  0.172
         2019 | beta: 1.442 | mu_capm:  0.364 | mu_real:  0.440

         INTC.O
         ======================================================
         2011 | beta: 1.081 | mu_capm: -0.000 | mu_real:  0.142
         2012 | beta: 0.842 | mu_capm:  0.108 | mu_real: -0.163
         2013 | beta: 1.081 | mu_capm:  0.280 | mu_real:  0.230
         2014 | beta: 0.883 | mu_capm:  0.096 | mu_real:  0.335
         2015 | beta: 1.055 | mu_capm: -0.008 | mu_real: -0.052
         2016 | beta: 1.009 | mu_capm:  0.092 | mu_real:  0.051
         2017 | beta: 1.261 | mu_capm:  0.223 | mu_real:  0.242
         2018 | beta: 1.163 | mu_capm: -0.076 | mu_real:  0.017
         2019 | beta: 1.376 | mu_capm:  0.347 | mu_real:  0.243

         AMZN.O
         ======================================================
         2011 | beta: 1.102 | mu_capm: -0.001 | mu_real: -0.039
         2012 | beta: 0.958 | mu_capm:  0.122 | mu_real:  0.374
         2013 | beta: 1.116 | mu_capm:  0.289 | mu_real:  0.464
         2014 | beta: 1.262 | mu_capm:  0.135 | mu_real: -0.251
         2015 | beta: 1.473 | mu_capm: -0.013 | mu_real:  0.778
         2016 | beta: 1.122 | mu_capm:  0.102 | mu_real:  0.104
         2017 | beta: 1.118 | mu_capm:  0.199 | mu_real:  0.446
         2018 | beta: 1.300 | mu_capm: -0.086 | mu_real:  0.251
         2019 | beta: 1.619 | mu_capm:  0.408 | mu_real:  0.207

La Figura 4-9 compara la rentabilidad prevista (esperada) de una sola acción, dada la beta del año anterior y el rendimiento de la cartera de mercado del año en curso, con la rentabilidad realizada de la acción en el año en curso. Evidentemente, el CAPM en su forma original no resulta realmente útil para predecir el rendimiento de una acción basándose únicamente en la beta:

In [88]: sym = 'AMZN.O'

In [89]: res[res['symbol'] == sym].corr()
Out[89]:           mu_capm   mu_real
         mu_capm  1.000000 -0.004826
         mu_real -0.004826  1.000000

In [90]: res[res['symbol'] == sym].plot(kind='bar',
                         figsize=(10, 6), title=sym);
aiif 0409
Figura 4-9. Rendimientos de las acciones predichos por el CAPM frente a los realizados para una sola acción

La Figura 4-10 compara las medias de los rendimientos bursátiles previstos por el CAPM con las medias de los rendimientos realizados. También aquí, el CAPM no hace un buen trabajo.

Lo que es fácil de ver es que las predicciones del CAPM no varían mucho por término medio para las acciones analizadas; se sitúan entre el 12,2% y el 14,4%. Sin embargo, los rendimientos medios realizados de las acciones muestran una gran variabilidad; se sitúan entre el 9,4% y el 29,2%. Evidentemente, el rendimiento de la cartera de mercado y la beta por sí solos no pueden explicar los rendimientos observados de las acciones (tecnológicas):

In [91]: grouped = res.groupby('symbol').mean()
         grouped
Out[91]:          mu_capm   mu_real
         symbol
         AAPL.O  0.110855  0.206158
         AMZN.O  0.128223  0.259395
         INTC.O  0.117929  0.116180
         MSFT.O  0.120844  0.192655

In [92]: grouped.plot(kind='bar', figsize=(10, 6), title='Average Values');
aiif 0410
Figura 4-10. Rentabilidad media de las acciones predicha por el CAPM frente a la media realizada para múltiples acciones

Poder predictivo del CAPM

El poder predictivo del CAPM con respecto al rendimiento futuro de las acciones, en relación con la cartera del mercado, es bastante bajo o incluso inexistente para determinadas acciones. Una de las razones es probablemente el hecho de que el CAPM se basa en los mismos supuestos centrales que la teoría del MVP, a saber, que a los inversores sólo les importa el rendimiento (esperado) y la volatilidad (esperada) de una cartera y/o de una acción. Desde el punto de vista de la modelización, cabe preguntarse si el único factor de riesgo basta para explicar la variabilidad de los rendimientos de las acciones o si podría existir una relación no lineal entre el rendimiento de una acción y el rendimiento de la cartera de mercado.

Teoría de la fijación de precios de arbitraje

El poder predictivo del CAPM parece bastante limitado dados los resultados del ejemplo numérico anterior. Una pregunta válida es si el rendimiento de la cartera de mercado basta por sí solo para explicar la variabilidad de los rendimientos de las acciones. La respuesta de la APT es no:puede haber más factores (incluso muchos más) que juntos expliquen la variabilidad de los rendimientos de las acciones. La "Teoría de los Precios de Arbitraje" describe formalmente el marco de la APT, que también se basa en una relación lineal entre los factores y el rendimiento de una acción.

El inversor basado en datos reconoce que el CAPM no es suficiente para predecir con fiabilidad el rendimiento de una acción en relación con el rendimiento de la cartera de mercado. Por tanto, el inversor decide añadir a la cartera de mercado tres factores adicionales que podrían impulsar el rendimiento de una acción:

  • Volatilidad del mercado (representada por el índice VIX, .VIX)

  • Tipos de cambio (representados por el tipo EUR/USD, EUR=)

  • Precios de las materias primas (representados por el precio del oro, XAU=)

El siguiente código Python implementa un enfoque APT simple utilizando los cuatrofactores en combinación con una regresión multivariante para explicar el rendimiento futuro de una acción en relación con los factores:

In [93]: factors = ['.SPX', '.VIX', 'EUR=', 'XAU=']  1

In [94]: res = pd.DataFrame()

In [95]: np.set_printoptions(formatter={'float': lambda x: f'{x:5.2f}'})

In [96]: for sym in rets.columns[:4]:
             print('\n' + sym)
             print(71 * '=')
             for year in range(2010, 2019):
                 rets_ = rets.loc[f'{year}-01-01':f'{year}-12-31']
                 reg = np.linalg.lstsq(rets_[factors],
                                       rets_[sym], rcond=-1)[0]  2
                 rets_ = rets.loc[f'{year + 1}-01-01':f'{year + 1}-12-31']
                 mu_apt = np.dot(rets_[factors].mean() * 252, reg)  3
                 mu_real =  rets_[sym].mean() * 252  4
                 res = res.append(pd.DataFrame({'symbol': sym,
                                 'mu_apt': mu_apt, 'mu_real': mu_real},
                                  index=[year + 1]))
                 print('{} | fl: {} | mu_apt: {:6.3f} | mu_real: {:6.3f}'
                       .format(year + 1, reg.round(2), mu_apt, mu_real))
1

Los cuatro factores

2

La regresión multivariante

3

El rendimiento de las acciones previsto por el APT

4

El rendimiento realizado de la acción

El código anterior proporciona la siguiente salida:

         AAPL.O
         =======================================================================
         2011 | fl: [ 0.91 -0.04 -0.35  0.12] | mu_apt:  0.011 | mu_real:  0.228
         2012 | fl: [ 0.76 -0.02 -0.24  0.05] | mu_apt:  0.099 | mu_real:  0.275
         2013 | fl: [ 1.67  0.04 -0.56  0.10] | mu_apt:  0.366 | mu_real:  0.053
         2014 | fl: [ 0.53 -0.00  0.02  0.16] | mu_apt:  0.050 | mu_real:  0.320
         2015 | fl: [ 1.07  0.02  0.25  0.01] | mu_apt: -0.038 | mu_real: -0.047
         2016 | fl: [ 1.21  0.01 -0.14 -0.02] | mu_apt:  0.110 | mu_real:  0.096
         2017 | fl: [ 1.10  0.01 -0.15 -0.02] | mu_apt:  0.170 | mu_real:  0.381
         2018 | fl: [ 1.06 -0.03 -0.15  0.12] | mu_apt: -0.088 | mu_real: -0.071
         2019 | fl: [ 1.37  0.01 -0.20  0.13] | mu_apt:  0.364 | mu_real:  0.621

         MSFT.O
         =======================================================================
         2011 | fl: [ 0.98  0.01  0.02 -0.11] | mu_apt: -0.008 | mu_real: -0.072
         2012 | fl: [ 0.82  0.00 -0.03 -0.01] | mu_apt:  0.103 | mu_real:  0.029
         2013 | fl: [ 1.14  0.00 -0.07 -0.01] | mu_apt:  0.294 | mu_real:  0.337
         2014 | fl: [ 1.28  0.05  0.04  0.07] | mu_apt:  0.149 | mu_real:  0.216
         2015 | fl: [ 1.20  0.03  0.05  0.01] | mu_apt: -0.016 | mu_real:  0.178
         2016 | fl: [ 1.44  0.03 -0.17 -0.02] | mu_apt:  0.127 | mu_real:  0.113
         2017 | fl: [ 1.33  0.01 -0.14  0.00] | mu_apt:  0.216 | mu_real:  0.321
         2018 | fl: [ 1.10 -0.02 -0.14  0.22] | mu_apt: -0.087 | mu_real:  0.172
         2019 | fl: [ 1.51  0.01 -0.16 -0.02] | mu_apt:  0.378 | mu_real:  0.440

         INTC.O
         =======================================================================
         2011 | fl: [ 1.17  0.01  0.05 -0.13] | mu_apt: -0.010 | mu_real:  0.142
         2012 | fl: [ 1.03  0.04  0.01  0.03] | mu_apt:  0.122 | mu_real: -0.163
         2013 | fl: [ 1.06 -0.01 -0.10  0.01] | mu_apt:  0.267 | mu_real:  0.230
         2014 | fl: [ 0.96  0.02  0.36 -0.02] | mu_apt:  0.063 | mu_real:  0.335
         2015 | fl: [ 0.93 -0.01 -0.09  0.02] | mu_apt:  0.001 | mu_real: -0.052
         2016 | fl: [ 1.02  0.00 -0.05  0.06] | mu_apt:  0.099 | mu_real:  0.051
         2017 | fl: [ 1.41  0.02 -0.18  0.03] | mu_apt:  0.226 | mu_real:  0.242
         2018 | fl: [ 1.12 -0.01 -0.11  0.17] | mu_apt: -0.076 | mu_real:  0.017
         2019 | fl: [ 1.50  0.01 -0.34  0.30] | mu_apt:  0.431 | mu_real:  0.243

         AMZN.O
         =======================================================================
         2011 | fl: [ 1.02 -0.03 -0.18 -0.14] | mu_apt: -0.016 | mu_real: -0.039
         2012 | fl: [ 0.98 -0.01 -0.17 -0.09] | mu_apt:  0.117 | mu_real:  0.374
         2013 | fl: [ 1.07 -0.00  0.09  0.00] | mu_apt:  0.282 | mu_real:  0.464
         2014 | fl: [ 1.54  0.03  0.01 -0.08] | mu_apt:  0.176 | mu_real: -0.251
         2015 | fl: [ 1.26 -0.02  0.45 -0.11] | mu_apt: -0.044 | mu_real:  0.778
         2016 | fl: [ 1.06 -0.00 -0.15 -0.04] | mu_apt:  0.099 | mu_real:  0.104
         2017 | fl: [ 0.94 -0.02  0.12 -0.03] | mu_apt:  0.185 | mu_real:  0.446
         2018 | fl: [ 0.90 -0.04 -0.25  0.28] | mu_apt: -0.085 | mu_real:  0.251
         2019 | fl: [ 1.99  0.05 -0.37  0.12] | mu_apt:  0.506 | mu_real:  0.207

La Figura 4-11 compara los rendimientos previstos por el APT para una acción y sus rendimientos realizados a lo largo del tiempo. En comparación con el CAPM de un solo factor, apenas parece haber mejoras:

In [97]: sym = 'AMZN.O'

In [98]: res[res['symbol'] == sym].corr()
Out[98]:            mu_apt   mu_real
         mu_apt   1.000000 -0.098281
         mu_real -0.098281  1.000000

In [99]: res[res['symbol'] == sym].plot(kind='bar',
                         figsize=(10, 6), title=sym);
aiif 0411
Figura 4-11. Rendimientos de las acciones predichos por APT frente a los realizados para una acción

La misma imagen aparece en la Figura 4-12, producida por el siguiente fragmento, que compara las medias de varias acciones. Como apenas hay variación en las predicciones medias APT, hay grandes diferencias medias con los rendimientos realizados:

In [100]: grouped = res.groupby('symbol').mean()
          grouped
Out[100]:           mu_apt   mu_real
          symbol
          AAPL.O  0.116116  0.206158
          AMZN.O  0.135528  0.259395
          INTC.O  0.124811  0.116180
          MSFT.O  0.128441  0.192655

In [101]: grouped.plot(kind='bar', figsize=(10, 6), title='Average Values');

Por supuesto, la selección de los factores de riesgo es de vital importancia en este contexto. El inversor basado en datos decide averiguar qué factores de riesgo suelen considerarse relevantes para las acciones. Tras estudiar el artículo de Bender et al. (2013), el inversor sustituye los factores de riesgo originales por un nuevo conjunto. En concreto, el inversor elige el conjunto que se presenta en la Tabla 4-3.

aiif 0412
Figura 4-12. Rentabilidad media de las acciones predicha por APT frente a la media realizada para múltiples acciones
Tabla 4-3. Factores de riesgo de APT
Factor Descripción RIC

Mercado

MSCI World Rentabilidad Bruta Diaria USD (PUS = Rentabilidad del Precio)

.dMIWO00000GUS

Talla

Índice MSCI World Equal Weight Price Net EOD

.dMIWO0000ENUS

Volatilidad

MSCI World Rentabilidad neta de volatilidad mínima

.dMIWO0000YNUS

Valor

MSCI World Valor Ponderado Bruto (NUS para Neto)

.dMIWO000PkGUS

Riesgo

MSCI World Bruto Ponderado por Riesgo USD EOD

.dMIWO000PlGUS

Crecimiento

MSCI World Quality Net Return USD

.MIWO0000vNUS

Impulso

Índice MSCI World Momentum Gross USD EOD

.dMIWO0000NGUS

El siguiente código Python recupera un conjunto de datos respectivo de una ubicación remota y visualiza los datos normalizados de las series temporales (ver Figura 4-13). Un breve vistazo revela que las series temporales parecen estar muy correlacionadas positivamente:

In [102]: factors = pd.read_csv('http://hilpisch.com/aiif_eikon_eod_factors.csv',
                                index_col=0, parse_dates=True) 1

In [103]: (factors / factors.iloc[0]).plot(figsize=(10, 6));  2
1

Recupera datos de series temporales de factores

2

Normaliza y traza los datos

aiif 0413
Figura 4-13. Datos de la serie temporal de factores normalizados

Esta impresión queda confirmada por el cálculo siguiente y la matriz de correlación resultante para los rendimientos de los factores. Todos los factores de correlación son iguales o superiores a 0,75:

In [104]: start = '2017-01-01'  1
          end = '2020-01-01'  1

In [105]: retsd = rets.loc[start:end].copy()  2
          retsd.dropna(inplace=True)  2

In [106]: retsf = np.log(factors / factors.shift(1))  3
          retsf = retsf.loc[start:end]  3
          retsf.dropna(inplace=True)  3
          retsf = retsf.loc[retsd.index].dropna()  3

In [107]: retsf.corr()  4
Out[107]:               market      size  volatility     value      risk    growth  \
          market      1.000000  0.935867    0.845010  0.964124  0.947150  0.959038
          size        0.935867  1.000000    0.791767  0.965739  0.983238  0.835477
          volatility  0.845010  0.791767    1.000000  0.778294  0.865467  0.818280
          value       0.964124  0.965739    0.778294  1.000000  0.958359  0.864222
          risk        0.947150  0.983238    0.865467  0.958359  1.000000  0.858546
          growth      0.959038  0.835477    0.818280  0.864222  0.858546  1.000000
          momentum    0.928705  0.796420    0.819585  0.818796  0.825563  0.952956

                      momentum
          market      0.928705
          size        0.796420
          volatility  0.819585
          value       0.818796
          risk        0.825563
          growth      0.952956
          momentum    1.000000
1

Define las fechas de inicio y fin de la selección de datos

2

Selecciona el subconjunto de datos de devoluciones pertinente

3

Calcula y procesa los rendimientos logarítmicos de los factores

4

Muestra la matriz de correlaciones de los factores

El siguiente código Python obtiene las cargas factoriales de las acciones originales, pero con los nuevos factores. Se derivan de la primera mitad del conjunto de datos y se aplican para predecir el rendimiento de las acciones de la segunda mitad, dado el rendimiento de los factores individuales. También se calcula el rendimiento realizado. Ambas series temporales se comparan en la Figura 4-14. Como era de esperar, dada la alta correlación de los factores, el poder explicativo del enfoque APT no es mucho mayor que el del CAPM:

In [108]: res = pd.DataFrame()

In [109]: np.set_printoptions(formatter={'float': lambda x: f'{x:5.2f}'})

In [110]: split = int(len(retsf) * 0.5)
          for sym in rets.columns[:4]:
              print('\n' + sym)
              print(74 * '=')
              retsf_, retsd_ = retsf.iloc[:split], retsd.iloc[:split]
              reg = np.linalg.lstsq(retsf_, retsd_[sym], rcond=-1)[0]
              retsf_, retsd_ = retsf.iloc[split:], retsd.iloc[split:]
              mu_apt = np.dot(retsf_.mean() * 252, reg)
              mu_real =  retsd_[sym].mean() * 252
              res = res.append(pd.DataFrame({'mu_apt': mu_apt,
                              'mu_real': mu_real}, index=[sym,]),
                              sort=True)
              print('fl: {} | apt: {:.3f} | real: {:.3f}'
                    .format(reg.round(1), mu_apt, mu_real))


          AAPL.O
          ==========================================================================
          fl: [ 2.30  2.80 -0.70 -1.40 -4.20  2.00 -0.20] | apt: 0.115 | real: 0.301

          MSFT.O
          ==========================================================================
          fl: [ 1.50  0.00  0.10 -1.30 -1.40  0.80  1.00] | apt: 0.181 | real: 0.304

          INTC.O
          ==========================================================================
          fl: [-3.10  1.60  0.40  1.30 -2.60  2.50  1.10] | apt: 0.186 | real: 0.118

          AMZN.O
          ==========================================================================
          fl: [ 9.10  3.30 -1.00 -7.10 -3.10 -1.80  1.20] | apt: 0.019 | real: 0.050

In [111]: res.plot(kind='bar', figsize=(10, 6));
aiif 0414
Figura 4-14. Rendimientos previstos por APT basados en factores típicos comparados con los rendimientos realizados

El inversor basado en datos no está dispuesto a descartar por completo la APT. Por tanto, una prueba adicional podría arrojar algo más de luz sobre el poder explicativo del APT. Para ello, se utilizan las cargas factoriales para comprobar si la APT puede explicar los movimientos del precio de las acciones a lo largo del tiempo (correctamente). Y, efectivamente, aunque la APT no predice correctamente el rendimiento absoluto (se desvía en más de 10 puntos porcentuales), predice correctamente la dirección del movimiento del precio de las acciones en la mayoría de los casos (véase la Figura 4-15). La correlación entre los rendimientos previstos y los realizados también es bastante alta, en torno al 85%. Sin embargo, el análisis utiliza los rendimientos realizados de los factores para generar las predicciones APT, algo que, por supuesto, no está disponible en la práctica un día antes del día de negociación pertinente:

In [112]: sym
Out[112]: 'AMZN.O'

In [113]: rets_sym = np.dot(retsf_, reg)  1

In [114]: rets_sym = pd.DataFrame(rets_sym,
                                  columns=[sym + '_apt'],
                                  index=retsf_.index)  2

In [115]: rets_sym[sym + '_real'] = retsd_[sym]  3

In [116]: rets_sym.mean() * 252  4
Out[116]: AMZN.O_apt     0.019401
          AMZN.O_real    0.050344
          dtype: float64

In [117]: rets_sym.std() * 252 ** 0.5  5
Out[117]: AMZN.O_apt     0.270995
          AMZN.O_real    0.307653
          dtype: float64

In [118]: rets_sym.corr()  6
Out[118]:              AMZN.O_apt  AMZN.O_real
          AMZN.O_apt     1.000000     0.832218
          AMZN.O_real    0.832218     1.000000

In [119]: rets_sym.cumsum().apply(np.exp).plot(figsize=(10, 6));
1

Predice los rendimientos diarios de las cotizaciones bursátiles dados los rendimientos realizados de los factores

2

Almacena los resultados en un objeto DataFrame y añade los datos de la columna y el índice

3

Añade la rentabilidad de las acciones realizadas al objeto DataFrame

4

Calcula los rendimientos anualizados

5

Calcula la volatilidad anualizada

6

Calcula el factor de correlación

aiif 0415
Figura 4-15. Rendimiento previsto por APT y rendimiento real a lo largo del tiempo (bruto)

¿Con qué precisión predice la APT la dirección del movimiento del precio de las acciones dados los rendimientos realizados de los factores? El siguiente código Python muestra que la precisión es algo superior al 75%:

In [120]: rets_sym['same'] = (np.sign(rets_sym[sym + '_apt']) ==
                              np.sign(rets_sym[sym + '_real']))

In [121]: rets_sym['same'].value_counts()
Out[121]: True     288
          False     89
          Name: same, dtype: int64

In [122]: rets_sym['same'].value_counts()[True] / len(rets_sym)
Out[122]: 0.7639257294429708

Desacreditar los supuestos centrales

La sección anterior proporciona una serie de ejemplos numéricos del mundo real que muestran cómo las teorías financieras normativas populares pueden fracasar en la práctica. En esta sección se argumenta que una de las principales razones es que los supuestos centrales de estas teorías financieras populares no son válidos; es decir, simplemente no describen la realidad delos mercados financieros. Los dos supuestos analizados son los rendimientos distribuidos normalmente ylas relaciones lineales.

Rendimientos normalmente distribuidos

De hecho, sólo una distribución normal está completamente especificada a través de su primer (expectativa) y segundo momento (desviación típica).

Conjuntos de datos de muestra

A modo de ilustración, considera un conjunto aleatorio de números estándar distribuidos normalmente, generado por el siguiente código Python.4 La Figura 4-16 muestra la típica forma de campana del histograma resultante:

In [1]: import numpy as np
        import pandas as pd
        from pylab import plt, mpl
        np.random.seed(100)
        plt.style.use('seaborn')
        mpl.rcParams['savefig.dpi'] = 300
        mpl.rcParams['font.family'] = 'serif'

In [2]: N = 10000

In [3]: snrn = np.random.standard_normal(N)  1
        snrn -= snrn.mean()  2
        snrn /= snrn.std()  3

In [4]: round(snrn.mean(), 4)  2
Out[4]: -0.0

In [5]: round(snrn.std(), 4)  3
Out[5]: 1.0

In [6]: plt.figure(figsize=(10, 6))
        plt.hist(snrn, bins=35);
1

Extrae números aleatorios estándar distribuidos normalmente

2

Corrige el primer momento (expectativa) a 0,0

3

Corrige el segundo momento (desviación típica) a 1,0

aiif 0416
Figura 4-16. Números aleatorios estándar distribuidos normalmente

Considera ahora un conjunto de números aleatorios que comparten los mismos valores de primer y segundo momento, pero que tienen una distribución completamente distinta de la que ilustra la Figura 4-17. Aunque los momentos son los mismos, esta distribución sólo consta de tres valores discretos:

In [7]: numbers = np.ones(N) * 1.5  1
        split = int(0.25 * N)  1
        numbers[split:3 * split] = -1  1
        numbers[3 * split:4 * split] = 0  1

In [8]: numbers -= numbers.mean()  2
        numbers /= numbers.std()  3

In [9]: round(numbers.mean(), 4)  2
Out[9]: 0.0

In [10]: round(numbers.std(), 4)  3
Out[10]: 1.0

In [11]: plt.figure(figsize=(10, 6))
         plt.hist(numbers, bins=35);
1

Un conjunto de números con sólo tres valores discretos

2

Corrige el primer momento (expectativa) a 0,0

3

Corrige el segundo momento (desviación típica) a 1,0

aiif 0417
Figura 4-17. Distribución con primer y segundo momento de 0,0 y 1,0, respectivamente

Primer y Segundo Momento

El primer y el segundo momento de una distribución de probabilidad sólo describen completamente una distribución normal. Hay infinitas otras distribuciones que pueden compartir los dos primeros momentos con una distribución normal siendo completamente diferentes.

Como preparación para una prueba de rendimientos financieros reales, considera las siguientes funciones de Python que permiten visualizar los datos como un histograma y añadir una función de densidad de probabilidad (PDF) de una distribución normal con los dos primeros momentos de los datos:

In [12]: import math
         import scipy.stats as scs
         import statsmodels.api as sm

In [13]: def dN(x, mu, sigma):
             ''' Probability density function of a normal random variable x.
             '''
             z = (x - mu) / sigma
             pdf = np.exp(-0.5 * z ** 2) / math.sqrt(2 * math.pi * sigma ** 2)
             return pdf

In [14]: def return_histogram(rets, title=''):
             ''' Plots a histogram of the returns.
             '''
             plt.figure(figsize=(10, 6))
             x = np.linspace(min(rets), max(rets), 100)
             plt.hist(np.array(rets), bins=50,
                      density=True, label='frequency')  1
             y = dN(x, np.mean(rets), np.std(rets))  2
             plt.plot(x, y, linewidth=2, label='PDF')  2
             plt.xlabel('log returns')
             plt.ylabel('frequency/probability')
             plt.title(title)
             plt.legend()
1

Traza el histograma de los datos

2

Traza la PDF de la distribución normal correspondiente

La Figura 4-18 muestra lo bien que el histograma se aproxima a la PDF de los números aleatorios estándar distribuidos normalmente:

In [15]: return_histogram(snrn)
aiif 0418
Figura 4-18. Histograma y PDF de números estándar distribuidos normalmente

Por el contrario, la Figura 4-19 ilustra que la PDF de la distribución normal no tiene nada que ver con los datos mostrados como histograma:

In [16]: return_histogram(numbers)
aiif 0419
Figura 4-19. Histograma y PDF normal para números discretos

Otra forma de comparar una distribución normal con los datos es el gráfico Cuantil-Cuantil (Q-Q). Como muestra la Figura 4-20, en el caso de los números con distribución normal, los propios números se sitúan (en su mayoría) sobre una línea recta en el plano Q-Q:

In [17]: def return_qqplot(rets, title=''):
             ''' Generates a Q-Q plot of the returns.
             '''
             fig = sm.qqplot(rets, line='s', alpha=0.5)
             fig.set_size_inches(10, 6)
             plt.title(title)
             plt.xlabel('theoretical quantiles')
             plt.ylabel('sample quantiles')

In [18]: return_qqplot(snrn)
aiif 0420
Figura 4-20. Gráfico Q-Q para números estándar distribuidos normalmente

De nuevo, el gráfico Q-Q de la Figura 4-21 para los números discretos tiene un aspecto completamente distinto al de la Figura 4-20:

In [19]: return_qqplot(numbers)
aiif 0421
Figura 4-21. Gráfico Q-Q para números discretos

Por último, también se pueden utilizar pruebas estadísticas para comprobar si un conjunto de números se distribuye normalmente o no.

La siguiente función de Python implementa tres pruebas:

  • Prueba de inclinación normal.

  • Prueba de curtosis normal.

  • Prueba de asimetría y curtosis normales combinadas.

Un valor p inferior a 0,05 se considera generalmente un contraindicador de normalidad; es decir, se rechaza la hipótesis de que las cifras se distribuyen normalmente. En este sentido, como en las figuras anteriores, los valores p de los dos conjuntos de datos hablan porsí solos:

In [20]: def print_statistics(rets):
             print('RETURN SAMPLE STATISTICS')
             print('---------------------------------------------')
             print('Skew of Sample Log Returns {:9.6f}'.format(
                         scs.skew(rets)))
             print('Skew Normal Test p-value   {:9.6f}'.format(
                         scs.skewtest(rets)[1]))
             print('---------------------------------------------')
             print('Kurt of Sample Log Returns {:9.6f}'.format(
                         scs.kurtosis(rets)))
             print('Kurt Normal Test p-value   {:9.6f}'.format(
                         scs.kurtosistest(rets)[1]))
             print('---------------------------------------------')
             print('Normal Test p-value        {:9.6f}'.format(
                         scs.normaltest(rets)[1]))
             print('---------------------------------------------')

In [21]: print_statistics(snrn)
         RETURN SAMPLE STATISTICS
         ---------------------------------------------
         Skew of Sample Log Returns  0.016793
         Skew Normal Test p-value    0.492685
         ---------------------------------------------
         Kurt of Sample Log Returns -0.024540
         Kurt Normal Test p-value    0.637637
         ---------------------------------------------
         Normal Test p-value         0.707334
         ---------------------------------------------

In [22]: print_statistics(numbers)
         RETURN SAMPLE STATISTICS
         ---------------------------------------------
         Skew of Sample Log Returns  0.689254
         Skew Normal Test p-value    0.000000
         ---------------------------------------------
         Kurt of Sample Log Returns -1.141902
         Kurt Normal Test p-value    0.000000
         ---------------------------------------------
         Normal Test p-value         0.000000
         ---------------------------------------------

Rendimientos financieros reales

El siguiente código Python recupera los datos EOD de una fuente remota, como se hizo anteriormente en el capítulo, y calcula los rendimientos logarítmicos de todas las series temporales financieras contenidas en el conjunto de datos. La Figura 4-22 muestra que los rendimientos logarítmicos del índice bursátil S&P 500 representados como un histograma muestran un pico mucho más alto y colas más gordas cuando se comparan con la PDF normal con la expectativa muestral y la desviación típica. Estas dos percepciones son hechos estilizados porque pueden observarse sistemáticamente para distintos instrumentos financieros:

In [23]: raw = pd.read_csv('http://hilpisch.com/aiif_eikon_eod_data.csv',
                           index_col=0, parse_dates=True).dropna()

In [24]: rets = np.log(raw / raw.shift(1)).dropna()

In [25]: symbol = '.SPX'

In [26]: return_histogram(rets[symbol].values, symbol)
aiif 0422
Figura 4-22. Distribución de frecuencias y PDF normal de los rendimientos logarítmicos del S&P 500

Se puede obtener una visión similar al considerar el gráfico Q-Q para los rendimientos logarítmicos del S&P 500 en la Figura 4-23. En particular, el gráfico Q-Q visualiza bastante bien las colas gruesas (puntos por debajo de la línea recta a la izquierda y por encima de la línea recta a la derecha):

In [27]: return_qqplot(rets[symbol].values, symbol)
aiif 0423
Figura 4-23. Q-Q para los rendimientos logarítmicos del S&P 500

El código Python que sigue realiza las pruebas estadísticas relativas a la normalidad de los rendimientos financieros reales para una selección de series temporales financieras del conjunto de datos. Los rendimientos financieros reales no superan estas pruebas con regularidad. Por lo tanto, se puede concluir que el supuesto de normalidad de los rendimientos financieros apenas describela realidad financiera:

In [28]: symbols = ['.SPX', 'AMZN.O', 'EUR=', 'GLD']

In [29]: for sym in symbols:
             print('\n{}'.format(sym))
             print(45 * '=')
             print_statistics(rets[sym].values)

         .SPX
         =============================================
         RETURN SAMPLE STATISTICS
         ---------------------------------------------
         Skew of Sample Log Returns -0.497160
         Skew Normal Test p-value    0.000000
         ---------------------------------------------
         Kurt of Sample Log Returns  4.598167
         Kurt Normal Test p-value    0.000000
         ---------------------------------------------
         Normal Test p-value         0.000000
         ---------------------------------------------

         AMZN.O
         =============================================
         RETURN SAMPLE STATISTICS
         ---------------------------------------------
         Skew of Sample Log Returns  0.135268
         Skew Normal Test p-value    0.005689
         ---------------------------------------------
         Kurt of Sample Log Returns  7.344837
         Kurt Normal Test p-value    0.000000
         ---------------------------------------------
         Normal Test p-value         0.000000
         ---------------------------------------------

         EUR=
         =============================================
         RETURN SAMPLE STATISTICS
         ---------------------------------------------
         Skew of Sample Log Returns -0.053959
         Skew Normal Test p-value    0.268203
         ---------------------------------------------
         Kurt of Sample Log Returns  1.780899
         Kurt Normal Test p-value    0.000000
         ---------------------------------------------
         Normal Test p-value         0.000000
         ---------------------------------------------

         GLD
         =============================================
         RETURN SAMPLE STATISTICS
         ---------------------------------------------
         Skew of Sample Log Returns -0.581025
         Skew Normal Test p-value    0.000000
         ---------------------------------------------
         Kurt of Sample Log Returns  5.899701
         Kurt Normal Test p-value    0.000000
         ---------------------------------------------
         Normal Test p-value         0.000000
         ---------------------------------------------

Suposición de normalidad

Aunque el supuesto de normalidad es una buena aproximación para muchos fenómenos del mundo real, como en la física, no es apropiado e incluso puede ser peligroso cuando se trata de rendimientos financieros. Casi ningún conjunto de datos de muestra de rendimientos financieros supera las pruebas estadísticas de normalidad. Más allá del hecho de que ha demostrado su utilidad en otros ámbitos, una de las principales razones por las que este supuesto se encuentra en tantos modelos financieros es que conduce a modelos matemáticos, cálculos y pruebas elegantes y relativamente sencillos.

Relaciones lineales

De forma similar a la "omnipresencia" del supuesto de normalidad en los modelos y teorías financieros, las relaciones lineales entre variables parecen ser otro punto de referencia muy extendido. Esta subsección considera una importante, a saber, la supuesta relación lineal en el CAPM entre la beta de una acción y su rendimiento esperado (realizado). En términos generales, cuanto mayor sea la beta, mayor será el rendimiento esperado dado un comportamiento positivo del mercado, de una forma proporcional fija dada por el propio valor de la beta.

Recuerda el cálculo de las betas, los rendimientos esperados CAPM y los rendimientos realizados para una selección de valores tecnológicos de la sección anterior, que se repite en el siguiente código Python por comodidad. Esta vez, los valores beta también se añaden al objeto DataFrame de los resultados.

In [30]: r = 0.005

In [31]: market = '.SPX'

In [32]: res = pd.DataFrame()

In [33]: for sym in rets.columns[:4]:
             for year in range(2010, 2019):
                 rets_ = rets.loc[f'{year}-01-01':f'{year}-12-31']
                 muM = rets_[market].mean() * 252
                 cov = rets_.cov().loc[sym, market]
                 var = rets_[market].var()
                 beta = cov / var
                 rets_ = rets.loc[f'{year + 1}-01-01':f'{year + 1}-12-31']
                 muM = rets_[market].mean() * 252
                 mu_capm = r + beta * (muM - r)
                 mu_real = rets_[sym].mean() * 252
                 res = res.append(pd.DataFrame({'symbol': sym,
                                                'beta': beta,
                                                'mu_capm': mu_capm,
                                                'mu_real': mu_real},
                                               index=[year + 1]),
                                               sort=True)

El siguiente análisis calcula el R 2 para una regresión lineal en la que la beta es la variable independiente y el rendimiento CAPM esperado, dado el rendimiento de la cartera de mercado, es la variable dependiente. R 2 se refiere al coeficiente de determinación y mide el rendimiento de un modelo en comparación con un predictor de referencia en forma de valor medio simple. La regresión lineal sólo puede explicar en torno al 10% de la variabilidad de la rentabilidad CAPM esperada, un valor bastante bajo, que también se confirma a través de la Figura 4-24:

In [34]: from sklearn.metrics import r2_score

In [35]: reg = np.polyfit(res['beta'], res['mu_capm'], deg=1)
         res['mu_capm_ols'] = np.polyval(reg, res['beta'])

In [36]: r2_score(res['mu_capm'], res['mu_capm_ols'])
Out[36]: 0.09272355783573516

In [37]: res.plot(kind='scatter', x='beta', y='mu_capm', figsize=(10, 6))
         x = np.linspace(res['beta'].min(), res['beta'].max())
         plt.plot(x, np.polyval(reg, x), 'g--', label='regression')
         plt.legend();
aiif 0424
Figura 4-24. Rentabilidad CAPM esperada frente a beta (incluida la regresión lineal)

Para el rendimiento realizado, el poder explicativo de la regresión lineal es aún menor, con aproximadamente un 4,5% (ver Figura 4-25). Las regresiones lineales recuperan la relación positiva entre la beta y el rendimiento de las acciones - "cuanto mayor sea la beta, mayor será el rendimiento dado el rendimiento (positivo) de la cartera de mercado"-, como indica la pendiente positiva de las líneas de regresión. Sin embargo, sólo explican una pequeña parte de la variabilidad general observada en los rendimientos de las acciones:

In [38]: reg = np.polyfit(res['beta'], res['mu_real'], deg=1)
         res['mu_real_ols'] = np.polyval(reg, res['beta'])

In [39]: r2_score(res['mu_real'], res['mu_real_ols'])
Out[39]: 0.04466919444752959

In [40]: res.plot(kind='scatter', x='beta', y='mu_real', figsize=(10, 6))
         x = np.linspace(res['beta'].min(), res['beta'].max())
         plt.plot(x, np.polyval(reg, x), 'g--', label='regression')
         plt.legend();
aiif 0425
Figura 4-25. Rentabilidad CAPM esperada frente a beta (incluida la regresión lineal)

Relaciones lineales

Al igual que ocurre con los supuestos de normalidad, las relaciones lineales pueden observarse a menudo en el mundo físico. Sin embargo, en finanzas apenas hay casos en los que las variables dependan unas de otras de forma claramente lineal. Desde el punto de vista de la modelización, las relaciones lineales conducen, al igual que el supuesto de normalidad, a modelos matemáticos, cálculos y pruebas elegantes y relativamente sencillos. Además, la herramienta estándar de la econometría financiera, la regresión MCO, es muy adecuada para tratar relaciones lineales en los datos. Éstas son las principales razones por las que la normalidad y la linealidad se eligen a menudo deliberadamente como convenientes componentes básicos de losmodelos y teorías financieros.

Conclusiones

La ciencia se ha guiado durante siglos por la generación y el análisis rigurosos de datos. Sin embargo, las finanzas solían caracterizarse por teorías normativas basadas en modelos matemáticos simplificados de los mercados financieros, que se basaban en supuestos como la normalidad de los rendimientos y las relaciones lineales. La disponibilidad casi universal y exhaustiva de datos (financieros) ha provocado un cambio de enfoque, que ha pasado de la teoría a las finanzas basadas en datos. Varios ejemplos basados en datos financieros reales ilustran que muchos modelos y teorías financieros populares no pueden sobrevivir a una confrontación con las realidades del mercado financiero. Aunque elegantes, pueden ser demasiado simplistas para captar las complejidades, la naturaleza cambiante y las no linealidades de los mercados financieros.

Referencias

Libros y artículos citados en este capítulo:

Código Python

El siguiente archivo Python contiene una serie de funciones de ayuda para simplificar determinadas tareas en PNL:

#
# NLP Helper Functions
#
# Artificial Intelligence in Finance
# (c) Dr Yves J Hilpisch
# The Python Quants GmbH
#
import re
import nltk
import string
import pandas as pd
from pylab import plt
from wordcloud import WordCloud
from nltk.corpus import stopwords
from nltk.corpus import wordnet as wn
from lxml.html.clean import Cleaner
from sklearn.feature_extraction.text import TfidfVectorizer
plt.style.use('seaborn')

cleaner = Cleaner(style=True, links=True, allow_tags=[''],
                  remove_unknown_tags=False)

stop_words = stopwords.words('english')
stop_words.extend(['new', 'old', 'pro', 'open', 'menu', 'close'])


def remove_non_ascii(s):
    ''' Removes all non-ascii characters.
    '''
    return ''.join(i for i in s if ord(i) < 128)

def clean_up_html(t):
    t = cleaner.clean_html(t)
    t = re.sub('[\n\t\r]', ' ', t)
    t = re.sub(' +', ' ', t)
    t = re.sub('<.*?>', '', t)
    t = remove_non_ascii(t)
    return t

def clean_up_text(t, numbers=False, punctuation=False):
    ''' Cleans up a text, e.g. HTML document,
        from HTML tags and also cleans up the
        text body.
    '''
    try:
        t = clean_up_html(t)
    except:
        pass
    t = t.lower()
    t = re.sub(r"what's", "what is ", t)
    t = t.replace('(ap)', '')
    t = re.sub(r"\'ve", " have ", t)
    t = re.sub(r"can't", "cannot ", t)
    t = re.sub(r"n't", " not ", t)
    t = re.sub(r"i'm", "i am ", t)
    t = re.sub(r"\'s", "", t)
    t = re.sub(r"\'re", " are ", t)
    t = re.sub(r"\'d", " would ", t)
    t = re.sub(r"\'ll", " will ", t)
    t = re.sub(r'\s+', ' ', t)
    t = re.sub(r"\\", "", t)
    t = re.sub(r"\'", "", t)
    t = re.sub(r"\"", "", t)
    if numbers:
        t = re.sub('[^a-zA-Z ?!]+', '', t)
    if punctuation:
        t = re.sub(r'\W+', ' ', t)
    t = remove_non_ascii(t)
    t = t.strip()
    return t

def nltk_lemma(word):
    ''' If one exists, returns the lemma of a word.
        I.e. the base or dictionary version of it.
    '''
    lemma = wn.morphy(word)
    if lemma is None:
        return word
    else:
        return lemma

def tokenize(text, min_char=3, lemma=True, stop=True,
             numbers=False):
    ''' Tokenizes a text and implements some
        transformations.
    '''
    tokens = nltk.word_tokenize(text)
    tokens = [t for t in tokens if len(t) >= min_char]
    if numbers:
        tokens = [t for t in tokens if t[0].lower()
                  in string.ascii_lowercase]
    if stop:
        tokens = [t for t in tokens if t not in stop_words]
    if lemma:
        tokens = [nltk_lemma(t) for t in tokens]
    return tokens

def generate_word_cloud(text, no, name=None, show=True):
    ''' Generates a word cloud bitmap given a
        text document (string).
        It uses the Term Frequency (TF) and
        Inverse Document Frequency (IDF)
        vectorization approach to derive the
        importance of a word -- represented
        by the size of the word in the word cloud.

    Parameters
    ==========
    text: str
        text as the basis
    no: int
        number of words to be included
    name: str
        path to save the image
    show: bool
        whether to show the generated image or not
    '''
    tokens = tokenize(text)
    vec = TfidfVectorizer(min_df=2,
                      analyzer='word',
                      ngram_range=(1, 2),
                      stop_words='english'
                     )
    vec.fit_transform(tokens)
    wc = pd.DataFrame({'words': vec.get_feature_names(),
                       'tfidf': vec.idf_})
    words = ' '.join(wc.sort_values('tfidf', ascending=True)['words'].head(no))
    wordcloud = WordCloud(max_font_size=110,
                      background_color='white',
                      width=1024, height=768,
                      margin=10, max_words=150).generate(words)
    if show:
        plt.figure(figsize=(10, 10))
        plt.imshow(wordcloud, interpolation='bilinear')
        plt.axis('off')
        plt.show()
    if name is not None:
        wordcloud.to_file(name)

def generate_key_words(text, no):
    try:
        tokens = tokenize(text)
        vec = TfidfVectorizer(min_df=2,
                      analyzer='word',
                      ngram_range=(1, 2),
                      stop_words='english'
                     )

        vec.fit_transform(tokens)
        wc = pd.DataFrame({'words': vec.get_feature_names(),
                       'tfidf': vec.idf_})
        words = wc.sort_values('tfidf', ascending=False)['words'].values
        words = [ a for a in words if not a.isnumeric()][:no]
    except:
        words = list()
    return words

1 Véase, por ejemplo, Kopf (2015).

2 Este servicio de datos sólo está disponible mediante una suscripción de pago.

3 RIC significa Código de Instrumentos Reuters.

4 Los números generados por el generador de números aleatorios de NumPy son números pseudoaleatorios, aunque a lo largo del libro se hace referencia a ellos como números aleatorios.

Get Inteligencia Artificial en Finanzas now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.