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:
Dados varios valores de se pueden obtener los valores de las funciones aplicando la definición anterior:
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 suele denominarse variable independiente e la variable dependiente. En consecuencia, considera los siguientes datos:
El problema consiste en encontrar, por ejemplo, los parámetros tales que
Otra forma de escribirlo es incluyendo valores residuales :
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 los valores reales . El problema de minimización, por tanto, es el siguiente
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:
Aquí, representa la covarianza, para la varianza, y para los valores medios de .
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 :
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
(
)
beta
Out
[
7
]
:
0.49999999999999994
In
[
8
]
:
alpha
=
y
.
mean
(
)
-
beta
*
x
.
mean
(
)
alpha
Out
[
8
]
:
2.0
In
[
9
]
:
y_
=
alpha
+
beta
*
x
In
[
10
]
:
np
.
allclose
(
y_
,
y
)
Out
[
10
]
:
True
derivada de la matriz de covarianza y de la varianza
derivado de y los valores medios
Valores estimados dada
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.
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
,
05
9
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
'
]
In
[
15
]
:
data
=
ek
.
get_timeseries
(
symbols
,
fields
=
'
CLOSE
'
,
start_date
=
'
2019-07-01
'
,
end_date
=
'
2020-07-01
'
)
In
[
16
]
:
data
.
info
(
)
<
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
(
)
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
Define una lista de
RICs
(símbolos) para recuperar datos de3Recupera los precios EOD
Close
de la lista deRICs
Muestra la metainformación del objeto
DataFrame
devueltoMuestra 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
'
)
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
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
'
]
)
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
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
'
)
In
[
25
]
:
oa
.
stream_data
(
'
BTC_USD
'
,
stop
=
5
)
2020
-
08
-
04
T08
:
30
:
38.621075583
Z
11298.8
11334.8
2020
-
08
-
04
T08
:
30
:
50.485678488
Z
11298.3
11334.3
2020
-
08
-
04
T08
:
30
:
50.801666847
Z
11297.3
11333.3
2020
-
08
-
04
T08
:
30
:
51.326269990
Z
11296.0
11332.0
2020
-
08
-
04
T08
:
30
:
54.423973431
Z
11296.6
11332.6
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 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
'
)
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
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
)
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
]
In
[
32
]
:
from
IPython.display
import
HTML
In
[
33
]
:
HTML
(
ek
.
get_news_story
(
storyId
)
[
:
1148
]
)
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.
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.
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).
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
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
]
In
[
36
]
:
html
=
[
requests
.
get
(
url
)
.
text
for
url
in
sources
]
In
[
37
]
:
data
=
[
nlp
.
clean_up_text
(
t
)
for
t
in
html
]
In
[
38
]
:
data
[
0
]
[
536
:
1001
]
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
'
Importa las funciones de ayuda de PNL
Define las URL de los tres comunicados de prensa
Recupera los códigos HTML en bruto de los tres comunicados de prensa
Limpia los códigos HTML sin procesar (por ejemplo, se eliminan las etiquetas HTML)
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
import
,
OAuth
In
[
40
]
:
t
=
(
auth
=
OAuth
(
c
[
'
'
]
[
'
access_token
'
]
,
c
[
'
'
]
[
'
access_secret_token
'
]
,
c
[
'
'
]
[
'
api_key
'
]
,
c
[
'
'
]
[
'
api_secret_key
'
]
)
,
retry
=
True
)
In
[
41
]
:
l
=
t
.
statuses
.
home_timeline
(
count
=
5
)
In
[
42
]
:
for
e
in
l
:
(
e
[
'
text
'
]
)
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
)
In
[
44
]
:
for
e
in
l
:
(
e
[
'
text
'
]
)
#Python for #AlgoTrading (focus on the process) & #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
(
or
)
.
Then
you
will
notice
for
sure
when
it
is
out
.
Conecta con la API de Twitter
Recupera e imprime cinco tweets (los más recientes) de la cronología de inicio
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
)
In
[
46
]
:
for
e
in
d
[
'
statuses
'
]
:
(
e
[
'
text
'
]
)
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
:
:
(
'
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
#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…
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
)
In
[
48
]
:
tl
=
[
e
[
'
text
'
]
for
e
in
l
]
In
[
49
]
:
tl
[
:
5
]
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
didn
’
t
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
'
)
Recupera los 50 tweets más recientes del usuario
elonmusk
Recoge los textos en un objeto
list
Muestra extractos de los últimos cinco tweets
Genera un resumen de la nube de palabras y muéstralo
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 . 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:
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 :
La probabilidad de ganar un premio así es bastante baja. Para ser exactos, es sólo 0.001953125. Por otra parte, la probabilidad de que se produzca una ganancia de este tipo o menor es bastante alta:
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 que asigna a cada resultado positivo un valor real . La utilidad marginal positiva pero decreciente se traduce entonces formalmente en lo siguiente:
Como se vio en el capítulo 3, una de esas funciones candidatas es con:
La utilidad esperada es entonces finita, como ilustra el cálculo de la siguiente suma infinita:
La utilidad esperada de = 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 . La clasificación debe hacerse para los dos pares y . El axioma de independencia postula que la primera fila de la tabla no debe influir en la ordenación de ya que la recompensa es la misma para ambos juegos.
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: y . La clasificación conduce a las siguientes desigualdades, donde :
La clasificación conduce a su vez a las siguientes desigualdades:
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
'
In
[
53
]
:
raw
=
pd
.
read_csv
(
url
,
index_col
=
0
,
parse_dates
=
True
)
.
dropna
(
)
In
[
54
]
:
raw
.
info
(
)
<
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
'
]
In
[
56
]
:
rets
=
np
.
log
(
raw
[
symbols
]
/
raw
[
symbols
]
.
shift
(
1
)
)
.
dropna
(
)
In
[
57
]
:
(
raw
[
symbols
]
/
raw
[
symbols
]
.
iloc
[
0
]
)
.
plot
(
figsize
=
(
10
,
6
)
)
;
Recupera datos históricos de EOD desde una ubicación remota
Especifica los símbolos (
RICs
) en los que se va a invertirCalcula los rendimientos logarítmicos de todas las series temporales
Traza la serie temporal financiera normalizada de los símbolos seleccionados
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
)
]
In
[
59
]
:
def
port_return
(
rets
,
weights
)
:
return
np
.
dot
(
rets
.
mean
(
)
,
weights
)
*
252
In
[
60
]
:
port_return
(
rets
,
weights
)
Out
[
60
]
:
0.15694764653018106
In
[
61
]
:
def
port_volatility
(
rets
,
weights
)
:
return
np
.
dot
(
weights
,
np
.
dot
(
rets
.
cov
(
)
*
252
,
weights
)
)
*
*
0.5
In
[
62
]
:
port_volatility
(
rets
,
weights
)
Out
[
62
]
:
0.16106507848480675
In
[
63
]
:
def
port_sharpe
(
rets
,
weights
)
:
return
port_return
(
rets
,
weights
)
/
port_volatility
(
rets
,
weights
)
In
[
64
]
:
port_sharpe
(
rets
,
weights
)
Out
[
64
]
:
0.97443622172255
Cartera con igual ponderación
Rendimiento de la cartera
Volatilidad de la cartera
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
)
)
)
w
=
(
w
.
T
/
w
.
sum
(
axis
=
1
)
)
.
T
In
[
66
]
:
w
[
:
5
]
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
]
pvr
=
np
.
array
(
pvr
)
In
[
68
]
:
psr
=
pvr
[
:
,
1
]
/
pvr
[
:
,
0
]
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
)
)
;
Simula que las ponderaciones de la cartera suman el 100%
Deduce las volatilidades y rendimientos resultantes de la cartera
Calcula los ratios de Sharpe resultantes
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
)
,
]
bnds
Out
[
70
]
:
[
(
0
,
1
)
,
(
0
,
1
)
,
(
0
,
1
)
,
(
0
,
1
)
,
(
0
,
1
)
]
In
[
71
]
:
cons
=
{
'
type
'
:
'
eq
'
,
'
fun
'
:
lambda
weights
:
weights
.
sum
(
)
-
1
}
In
[
72
]
:
opt_weights
=
{
}
for
year
in
range
(
2010
,
2019
)
:
rets_
=
rets
[
symbols
]
.
loc
[
f
'
{year}-01-01
'
:
f
'
{year}-12-31
'
]
ow
=
minimize
(
lambda
weights
:
-
port_sharpe
(
rets_
,
weights
)
,
len
(
symbols
)
*
[
1
/
len
(
symbols
)
]
,
bounds
=
bnds
,
constraints
=
cons
)
[
'
x
'
]
opt_weights
[
year
]
=
ow
In
[
73
]
:
opt_weights
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
]
)
}
Especifica los límites de las ponderaciones de un único activo
Especifica que todos los pesos deben sumar 100%.
Selecciona el conjunto de datos relevante para el año dado
Deduce las ponderaciones de la cartera que maximizan el ratio de Sharpe
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
]
)
epr
=
port_return
(
rets_
,
opt_weights
[
year
]
)
esr
=
epr
/
epv
rets_
=
rets
[
symbols
]
.
loc
[
f
'
{year + 1}-01-01
'
:
f
'
{year + 1}-12-31
'
]
rpv
=
port_volatility
(
rets_
,
opt_weights
[
year
]
)
rpr
=
port_return
(
rets_
,
opt_weights
[
year
]
)
rsr
=
rpr
/
rpv
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
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'
);
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'
);
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'
);
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
In
[
84
]
:
market
=
'
.SPX
'
In
[
85
]
:
rets
=
np
.
log
(
raw
/
raw
.
shift
(
1
)
)
.
dropna
(
)
In
[
86
]
:
res
=
pd
.
DataFrame
(
)
In
[
87
]
:
for
sym
in
rets
.
columns
[
:
4
]
:
(
'
\n
'
+
sym
)
(
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
]
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
,
'
mu_capm
'
:
mu_capm
,
'
mu_real
'
:
mu_real
}
,
index
=
[
year
+
1
]
)
,
sort
=
True
)
(
'
{} | beta: {:.3f} | mu_capm: {:6.3f} | mu_real: {:6.3f}
'
.
format
(
year
+
1
,
beta
,
mu_capm
,
mu_real
)
)
Especifica el tipo a corto sin riesgo
Define la cartera de mercado
Obtiene la beta de la acción
Calcula el rendimiento esperado dada la beta del año anterior y el rendimiento de la cartera de mercado del año en curso
Calcula el rendimiento realizado de la acción para el año en curso
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
);
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'
);
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=
'
]
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
]
:
(
'
\n
'
+
sym
)
(
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
]
rets_
=
rets
.
loc
[
f
'
{year + 1}-01-01
'
:
f
'
{year + 1}-12-31
'
]
mu_apt
=
np
.
dot
(
rets_
[
factors
]
.
mean
(
)
*
252
,
reg
)
mu_real
=
rets_
[
sym
]
.
mean
(
)
*
252
res
=
res
.
append
(
pd
.
DataFrame
(
{
'
symbol
'
:
sym
,
'
mu_apt
'
:
mu_apt
,
'
mu_real
'
:
mu_real
}
,
index
=
[
year
+
1
]
)
)
(
'
{} | fl: {} | mu_apt: {:6.3f} | mu_real: {:6.3f}
'
.
format
(
year
+
1
,
reg
.
round
(
2
)
,
mu_apt
,
mu_real
)
)
Los cuatro factores
La regresión multivariante
El rendimiento de las acciones previsto por el APT
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
);
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.
Factor | Descripción | RIC |
---|---|---|
Mercado |
MSCI World Rentabilidad Bruta Diaria USD (PUS = Rentabilidad del Precio) |
|
Talla |
Índice MSCI World Equal Weight Price Net EOD |
|
Volatilidad |
MSCI World Rentabilidad neta de volatilidad mínima |
|
Valor |
MSCI World Valor Ponderado Bruto (NUS para Neto) |
|
Riesgo |
MSCI World Bruto Ponderado por Riesgo USD EOD |
|
Crecimiento |
MSCI World Quality Net Return USD |
|
Impulso |
Índice MSCI World Momentum Gross USD EOD |
|
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
)
In
[
103
]
:
(
factors
/
factors
.
iloc
[
0
]
)
.
plot
(
figsize
=
(
10
,
6
)
)
;
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
'
end
=
'
2020-01-01
'
In
[
105
]
:
retsd
=
rets
.
loc
[
start
:
end
]
.
copy
(
)
retsd
.
dropna
(
inplace
=
True
)
In
[
106
]
:
retsf
=
np
.
log
(
factors
/
factors
.
shift
(
1
)
)
retsf
=
retsf
.
loc
[
start
:
end
]
retsf
.
dropna
(
inplace
=
True
)
retsf
=
retsf
.
loc
[
retsd
.
index
]
.
dropna
(
)
In
[
107
]
:
retsf
.
corr
(
)
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
Define las fechas de inicio y fin de la selección de datos
Selecciona el subconjunto de datos de devoluciones pertinente
Calcula y procesa los rendimientos logarítmicos de los factores
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
]:
(
'
\n
'
+
sym
)
(
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
)
(
'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
));
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
)
In
[
114
]
:
rets_sym
=
pd
.
DataFrame
(
rets_sym
,
columns
=
[
sym
+
'
_apt
'
]
,
index
=
retsf_
.
index
)
In
[
115
]
:
rets_sym
[
sym
+
'
_real
'
]
=
retsd_
[
sym
]
In
[
116
]
:
rets_sym
.
mean
(
)
*
252
Out
[
116
]
:
AMZN
.
O_apt
0.019401
AMZN
.
O_real
0.050344
dtype
:
float64
In
[
117
]
:
rets_sym
.
std
(
)
*
252
*
*
0.5
Out
[
117
]
:
AMZN
.
O_apt
0.270995
AMZN
.
O_real
0.307653
dtype
:
float64
In
[
118
]
:
rets_sym
.
corr
(
)
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
)
)
;
Predice los rendimientos diarios de las cotizaciones bursátiles dados los rendimientos realizados de los factores
Almacena los resultados en un objeto
DataFrame
y añade los datos de la columna y el índiceAñade la rentabilidad de las acciones realizadas al objeto
DataFrame
Calcula los rendimientos anualizados
Calcula la volatilidad anualizada
Calcula el factor de correlación
¿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
)
snrn
-
=
snrn
.
mean
(
)
snrn
/
=
snrn
.
std
(
)
In
[
4
]
:
round
(
snrn
.
mean
(
)
,
4
)
Out
[
4
]
:
-
0.0
In
[
5
]
:
round
(
snrn
.
std
(
)
,
4
)
Out
[
5
]
:
1.0
In
[
6
]
:
plt
.
figure
(
figsize
=
(
10
,
6
)
)
plt
.
hist
(
snrn
,
bins
=
35
)
;
Extrae números aleatorios estándar distribuidos normalmente
Corrige el primer momento (expectativa) a 0,0
Corrige el segundo momento (desviación típica) a 1,0
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
split
=
int
(
0.25
*
N
)
numbers
[
split
:
3
*
split
]
=
-
1
numbers
[
3
*
split
:
4
*
split
]
=
0
In
[
8
]
:
numbers
-
=
numbers
.
mean
(
)
numbers
/
=
numbers
.
std
(
)
In
[
9
]
:
round
(
numbers
.
mean
(
)
,
4
)
Out
[
9
]
:
0.0
In
[
10
]
:
round
(
numbers
.
std
(
)
,
4
)
Out
[
10
]
:
1.0
In
[
11
]
:
plt
.
figure
(
figsize
=
(
10
,
6
)
)
plt
.
hist
(
numbers
,
bins
=
35
)
;
Un conjunto de números con sólo tres valores discretos
Corrige el primer momento (expectativa) a 0,0
Corrige el segundo momento (desviación típica) a 1,0
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
=
np
.
exp
(
-
0.5
*
z
*
*
2
)
/
math
.
sqrt
(
2
*
math
.
pi
*
sigma
*
*
2
)
return
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
'
)
y
=
dN
(
x
,
np
.
mean
(
rets
)
,
np
.
std
(
rets
)
)
plt
.
plot
(
x
,
y
,
linewidth
=
2
,
label
=
'
'
)
plt
.
xlabel
(
'
log returns
'
)
plt
.
ylabel
(
'
frequency/probability
'
)
plt
.
title
(
title
)
plt
.
legend
(
)
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
)
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
)
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
)
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
)
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
):
(
'RETURN SAMPLE STATISTICS'
)
(
'---------------------------------------------'
)
(
'Skew of Sample Log Returns {:9.6f}'
.
format
(
scs
.
skew
(
rets
)))
(
'Skew Normal Test p-value {:9.6f}'
.
format
(
scs
.
skewtest
(
rets
)[
1
]))
(
'---------------------------------------------'
)
(
'Kurt of Sample Log Returns {:9.6f}'
.
format
(
scs
.
kurtosis
(
rets
)))
(
'Kurt Normal Test p-value {:9.6f}'
.
format
(
scs
.
kurtosistest
(
rets
)[
1
]))
(
'---------------------------------------------'
)
(
'Normal Test p-value {:9.6f}'
.
format
(
scs
.
normaltest
(
rets
)[
1
]))
(
'---------------------------------------------'
)
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
)
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
)
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
:
(
'
\n
{}'
.
format
(
sym
))
(
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 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. 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
();
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
();
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.