100% encontró este documento útil (2 votos)
2K vistas380 páginas

Python For Algorithmic Trading - Español

Cargado por

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

Python For Algorithmic Trading - Español

Cargado por

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

Machine Translated by Google

Python
para algorítmica
Comercio
De la idea al despliegue en la nube

Yves Hilpisch
Machine Translated by Google
Machine Translated by Google

Python para el comercio algorítmico


De la idea al despliegue en la nube

Yves Hilpisch

Pekín Boston Farnham Sebastopol Tokio


Machine Translated by Google

Python para el comercio algorítmico


por Yves Hilpisch

Copyright © 2021 Yves Hilpisch. Reservados todos los derechos.

Impreso en los Estados Unidos de América.

Publicado por O'Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472.

Los libros de O'Reilly se pueden comprar para uso educativo, comercial o promocional de ventas. También hay ediciones en línea disponibles
para la mayoría de los títulos (http://oreilly.com). Para obtener más información, comuníquese con nuestro departamento de ventas corporativo/
institucional: 800­998­9938 o [email protected].

Editor de adquisiciones: Michelle Smith Indexador: WordCo Indexing Services, Inc.


Editor de desarrollo: Michele Cronin Editor de Diseñador de interiores: David Futato
producción: Daniel Elfanbaum Editor: Piper Diseñador de portada: Jose Marzan
Editorial LLC Corrector de pruebas: Ilustrador: Kate Dullea

nSight, Inc.

Noviembre 2020: Primera edición

Historial de revisiones de la primera edición

2020­11­11: Primer lanzamiento

Consulte http://oreilly.com/catalog/errata.csp?isbn=9781492053354 para obtener detalles sobre la versión.

El logotipo de O'Reilly es una marca registrada de O'Reilly Media, Inc. Python for Algorithmic Trading, la imagen de portada y la imagen
comercial relacionada son marcas comerciales de O'Reilly Media, Inc.

Las opiniones expresadas en este trabajo son las del autor y no representan las opiniones del editor.
Si bien el editor y el autor han realizado esfuerzos de buena fe para garantizar que la información y las instrucciones contenidas en este trabajo
sean precisas, el editor y el autor renuncian a toda responsabilidad por errores u omisiones, incluida, entre otras, la responsabilidad por los
daños resultantes del uso o confianza en este trabajo. El uso de la información y las instrucciones contenidas en este trabajo es bajo su propio
riesgo. Si algún ejemplo de código u otra tecnología que este trabajo contiene o describe está sujeto a licencias de código abierto o derechos
de propiedad intelectual de otros, es su responsabilidad asegurarse de que su uso cumpla con dichas licencias y/o derechos. Este libro no
pretende ser un consejo financiero. Por favor consulte a un profesional calificado si necesita asesoramiento financiero.

978­1­492­05335­4

[LSI]
Machine Translated by Google

Tabla de contenido

Prefacio. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix

1. Python y el comercio algorítmico. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

Python para finanzas 1


Python versus pseudocódigo NumPy 2
y vectorización pandas y la clase 3
DataFrame Comercio algorítmico Python 5
para el comercio algorítmico 7
Enfoque y requisitos previos Estrategias 11
comerciales Impulso de medias 13
móviles simples 13
14
14
Reversión media 14
Aprendizaje automático y profundo 15
Conclusiones 15
Referencias y recursos adicionales 15

2. Infraestructura de Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Conda como administrador de paquetes 19
Instalación de miniconda 19
Operaciones básicas con Conda 21
Conda como gestor de entorno virtual 27
Usando contenedores Docker 30
Imágenes y contenedores de Docker 31
Creación de una imagen de Docker de Ubuntu y Python 31
Usar instancias en la nube 36
Claves públicas y privadas RSA 38

III
Machine Translated by Google

Archivo de configuración del portátil Jupyter 38

Script de instalación para Python y Jupyter Lab 40

Script para organizar la configuración del droplet 41


Conclusiones 43
Referencias y recursos adicionales 44

3. Trabajar con datos financieros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45


Lectura de datos financieros de diferentes fuentes 46
El conjunto de datos 46

Lectura de un archivo CSV con Python 47

Lectura de un archivo CSV con pandas 49

Exportar a Excel y JSON 50

Lectura de Excel y JSON 51

Trabajar con fuentes de datos abiertas 52


API de datos de Eikon 55

Recuperar datos estructurados históricos 58

Recuperar datos históricos no estructurados 62

Almacenamiento de datos financieros de manera eficiente sesenta y cinco

Almacenamiento de objetos de marco de datos 66

Usando TsTables 70

Almacenamiento de datos con SQLite3 75


Conclusiones 77
Referencias y recursos adicionales 78

Secuencias de comandos de Python


78

4. Dominar el backtesting vectorizado. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81


Haciendo uso de la vectorización 82

Vectorización con NumPy 83

Vectorización con pandas 85

Estrategias basadas en medias móviles simples 88

Entrando en lo básico 89

Generalizando el enfoque 97

Estrategias basadas en el impulso 98

Entrando en lo básico 99

Generalizando el enfoque 104

Estrategias basadas en la reversión a la media 107

Entrando en lo básico 107

Generalizando el enfoque 110

Espionaje de datos y sobreajuste 111


Conclusiones 113
Referencias y recursos adicionales 113

Secuencias de comandos de Python


115

i | Tabla de contenido
Machine Translated by Google

Clase de backtesting de SMA 115

Clase de backtesting de impulso 118

Clase de backtesting de reversión a la media 120

5. Predecir los movimientos del mercado con aprendizaje automático. . . . . . . . . . . . . . . . . . . . . . . . . . 123

Uso de la regresión lineal para la predicción del movimiento del mercado 124

Una revisión rápida de la regresión lineal 125


La idea básica para la predicción de precios 127

Predicción de niveles de índice 129

Predecir rendimientos futuros 132

Predecir la dirección futura del mercado 134

Backtesting vectorizado de estrategia basada en regresión 135

Generalizando el enfoque 137

Uso del aprendizaje automático para predecir el movimiento del mercado 139

Regresión lineal con scikit­learn 139

Un problema de clasificación simple 141

Uso de la regresión logística para predecir la dirección del mercado 146

Generalizando el enfoque 150

Uso del aprendizaje profundo para la predicción del movimiento del mercado 153

El problema de la clasificación simple revisado 154

Uso de redes neuronales profundas para predecir la dirección del mercado 156

Agregar diferentes tipos de funciones 162


Conclusiones 166
Referencias y recursos adicionales 166

Secuencias de comandos de Python


167

Clase de backtesting de regresión lineal 167

Clase de backtesting del algoritmo de clasificación 170

6. Creación de clases para backtesting basado en eventos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175

Backtesting Clase Base 177


Clase de backtesting solo a largo plazo 182

Clase de backtesting a corto plazo 185


Conclusiones 190
Referencias y recursos adicionales 190

Secuencias de comandos de Python


191

Clase base de backtesting 191

Clase de backtesting de larga duración 194

Clase de backtesting larga y corta 197

7. Trabajar con datos y sockets en tiempo real. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201

Ejecución de un servidor de datos de Tick 203

simple Conexión de un cliente de datos de Tick simple 206

Tabla de contenidos | v
Machine Translated by Google

Generación de señal en tiempo real 208

Visualización de datos en streaming con Plotly 211


Los basicos 211
Tres transmisiones en tiempo real 212
Tres subtramas para tres corrientes 214

Transmisión de datos como barras 215


Conclusiones 217
Referencias y recursos adicionales 218

Secuencias de comandos de Python


218

Servidor de datos de ticks de muestra 218


Cliente de datos de marca 219

Algoritmo en línea de impulso 219

Servidor de datos de muestra para gráfico de barras 220

8. Negociación de CFD con Oanda. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223

Configurar una cuenta La API 227


de Oanda Recuperar 229

datos históricos Buscar instrumentos 230

disponibles para operar Realizar pruebas retrospectivas de 230

una estrategia Momentum en barras de minutos Tener en cuenta 231

el apalancamiento y el margen 234

Trabajar con datos en streaming 236

Colocar órdenes de mercado 237

Implementación de estrategias comerciales en tiempo real 239

Recuperar información de la cuenta 244


Conclusiones 246
Referencias y recursos adicionales 247

Secuencia de comandos de Python


247

9. Comercio de divisas con FXCM. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249

Empezando 251

Recuperando datos 251

Recuperar datos de ticks 252

Recuperar datos de velas 254

Trabajando con la API 256

Recuperar datos históricos 257

Recuperar datos de transmisión 259

Colocando órdenes 260


Información de la cuenta 262
Conclusiones 263
Referencias y recursos adicionales 264

vi | Tabla de contenido
Machine Translated by Google

10. Automatización de Operaciones Comerciales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265


Gestión de capital 266
Criterio de Kelly en configuración binomial 266
Criterio de Kelly para acciones e índices 272
Estrategia comercial basada en 277
ML Backtesting vectorizado 278
Apalancamiento 285
óptimo Análisis 287
de riesgo Persistencia del objeto 290
modelo Algoritmo en 291
línea Infraestructura e implementación 296
Registro y monitoreo 297
Descripción general visual paso a 299
paso Configuración de la cuenta 299
de Oanda Configuración del 300
hardware el entorno Python Carga del 301
código Ejecución del 302
código Monitoreo en 302
tiempo real Conclusiones 304
Referencias y 304
recursos adicionales 305
Secuencia de comandos de Python
305
Estrategia comercial automatizada 305
Monitoreo de estrategia 308

Apéndice. Python, NumPy, matplotlib, pandas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309

Índice. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351

Tabla de contenidos | viii


Machine Translated by Google
Machine Translated by Google

Prefacio

El dataísmo dice que el universo consiste en flujos de datos, y el valor de cualquier fenómeno
o entidad está determinado por su contribución al procesamiento de datos... El dataísmo
colapsa así la barrera entre los animales [humanos] y las máquinas, y espera que los
algoritmos electrónicos eventualmente descifren y superan a los algoritmos bioquímicos.1
—Yuval Noé Harari

Encontrar el algoritmo adecuado para operar de forma automática y exitosa en los mercados financieros
es el santo grial en finanzas. No hace mucho tiempo, el comercio algorítmico sólo estaba disponible y
era posible para actores institucionales con mucho dinero y muchos activos bajo gestión. Los recientes
desarrollos en las áreas de código abierto, datos abiertos, computación en la nube y almacenamiento
en la nube, así como plataformas de comercio en línea, han nivelado el campo de juego para las
instituciones más pequeñas y los comerciantes individuales, haciendo posible iniciarse en esta
fascinante disciplina. mientras esté equipado únicamente con una computadora portátil o de escritorio
típica y una conexión a Internet confiable.

Hoy en día, Python y su ecosistema de potentes paquetes es la plataforma tecnológica elegida para el
comercio algorítmico. Entre otras cosas, Python permite hacer análisis de datos eficientes (con pandas,
por ejemplo), aplicar el aprendizaje automático a la predicción bursátil (con scikit­learn, por ejemplo), o
incluso hacer uso de la tecnología de aprendizaje profundo de Google con TensorFlow. .

Este es un libro sobre Python para el comercio algorítmico, principalmente en el contexto de estrategias
de generación de alfa (ver Capítulo 1). Un libro así, en la intersección de dos campos vastos y
apasionantes, difícilmente puede cubrir todos los temas relevantes. Sin embargo, puede cubrir en
profundidad una variedad de metatemas importantes.

1 Harari, Yuval Noah. 2015. Homo Deus: una breve historia del mañana. Londres: Harvill Secker.

ix
Machine Translated by Google

Estos temas incluyen:

Datos financieros

Los datos financieros son el núcleo de todo proyecto de comercio algorítmico. Python y paquetes como NumPy
y pandas hacen un gran trabajo al manejar y trabajar con datos financieros estructurados de cualquier tipo
(final del día, intradiario, de alta frecuencia).

Backtesting No
debería haber operaciones algorítmicas automatizadas sin una prueba rigurosa de la estrategia comercial que
se implementará. El libro cubre, entre otras cosas, estrategias comerciales basadas en promedios móviles
simples, impulso, reversión de la media y predicción basada en aprendizaje automático/profundo.

Datos en tiempo real

El comercio algorítmico requiere trabajar con datos en tiempo real, algoritmos en línea basados en ellos y
visualización en tiempo real. El libro proporciona una introducción a la programación de sockets con ZeroMQ y
visualización de streaming.

Plataformas en línea
No se pueden realizar transacciones sin una plataforma de negociación. El libro cubre dos plataformas de
comercio electrónico populares: Oanda y FXCM.

Automatización

La belleza, así como algunos desafíos importantes, del comercio algorítmico son el resultado de la
automatización de la operación comercial. El libro muestra cómo implementar Python en la nube y cómo
configurar un entorno apropiado para el comercio algorítmico automatizado.

El libro ofrece una experiencia de aprendizaje única con las siguientes características y beneficios:

Cobertura de temas relevantes


Este es el único libro que cubre tal amplitud y profundidad con respecto a temas relevantes en Python para el
comercio algorítmico (consulte lo siguiente).

Base de código autónoma El


libro va acompañado de un repositorio Git con todos los códigos en un formato ejecutable autónomo. El
repositorio está disponible en Quant Platform.

El comercio real como objetivo


La cobertura de dos plataformas de comercio en línea diferentes coloca al lector en condiciones de comenzar
a operar de manera eficiente, tanto en papel como en vivo. Con este fin, el libro dota al lector de conocimientos
previos relevantes, prácticos y valiosos.

Enfoque de hágalo usted mismo y a su propio ritmo


Dado que el material y el código son autónomos y sólo se basan en normas
paquetes de Python, el lector tiene pleno conocimiento y control total sobre lo que se

x | Prefacio
Machine Translated by Google

pasando, cómo utilizar los ejemplos de código, cómo cambiarlos, etc. No es necesario depender de
plataformas de terceros, por ejemplo, para realizar pruebas retrospectivas o conectarse a las
plataformas comerciales. Con este libro, el lector puede hacer todo esto por su cuenta a un ritmo
conveniente y tiene cada línea de código para hacerlo.

Foro de
usuarios Aunque el lector debería poder seguirlo sin problemas, el autor y The Python Quants están
ahí para ayudar. El lector puede publicar preguntas y comentarios en el foro de usuarios de la
plataforma Quant en cualquier momento (las cuentas son gratuitas).

Capacitación en línea/video (suscripción paga)


Python Quants ofrece programas integrales de capacitación en línea que hacen uso de los contenidos
presentados en el libro y que agregan contenido adicional, cubriendo temas importantes como ciencia
de datos financieros, inteligencia artificial en finanzas, Python para Excel y bases de datos, y
herramientas y habilidades adicionales de Python. .

Contenidos y estructura
A continuación se ofrece una descripción general rápida de los temas y contenidos presentados en cada capítulo.

Capítulo 1, Python y el comercio algorítmico


El primer capítulo es una introducción al tema del comercio algorítmico, es decir, el comercio
automatizado de instrumentos financieros basado en algoritmos informáticos. Discute nociones
fundamentales en este contexto y también aborda, entre otras cosas, cuáles son los requisitos
previos esperados para leer el libro.

Capítulo 2, Infraestructura de Python


Este capítulo sienta las bases técnicas para todos los capítulos posteriores, ya que muestra
cómo configurar un entorno Python adecuado. Este capítulo utiliza principalmente conda como
administrador de paquetes y entornos. Ilustra la implementación de Python a través de
contenedores Docker y en la nube.

Capítulo 3, Trabajar con datos financieros


Los datos de series de tiempo financieras son fundamentales para todo proyecto de comercio
algorítmico. Este capítulo le muestra cómo recuperar datos financieros de diferentes fuentes de datos
públicos y de datos propietarios. También demuestra cómo almacenar datos de series de tiempo
financieras de manera eficiente con Python.

Capítulo 4, Dominar el backtesting vectorizado La


vectorización es un enfoque poderoso en computación numérica en general y para análisis financiero
en particular. Este capítulo presenta la vectorización con NumPy y pandas y aplica ese enfoque a las
pruebas retrospectivas de estrategias basadas en SMA, de impulso y de reversión a la media.

Prefacio | xi
Machine Translated by Google

Capítulo 5, Predicción de los movimientos del mercado con aprendizaje


automático Este capítulo está dedicado a generar predicciones de mercado mediante el uso de
enfoques de aprendizaje automático y aprendizaje profundo. Al basarse principalmente en
observaciones de rendimiento pasadas como características, se presentan enfoques para predecir
la dirección del mercado del mañana mediante el uso de paquetes de Python como Keras en
combinación con Tensor Flow y scikit­learn.

Capítulo 6, Creación de clases para backtesting basado en eventos


Si bien el backtesting vectorizado tiene ventajas en lo que respecta a la concisión del código y el
rendimiento, es limitado con respecto a la representación de ciertas características del mercado de
las estrategias comerciales. Por otro lado, el backtesting basado en eventos, técnicamente
implementado mediante el uso de programación orientada a objetos, permite un modelado bastante
granular y más realista de dichas características. Este capítulo presenta y explica en detalle una
clase base, así como dos clases para realizar pruebas retrospectivas de estrategias comerciales
largas y cortas.

Capítulo 7, Trabajar con datos y sockets en tiempo real La


necesidad de lidiar con datos en tiempo real o en streaming es una realidad incluso para el ambicioso
operador algorítmico individual. La herramienta elegida es la programación de sockets, para lo cual
este capítulo presenta ZeroMQ como una tecnología liviana y escalable.
El capítulo también ilustra cómo utilizar Plotly para crear tramas de transmisión interactivas y
atractivas.

Capítulo 8, Negociación de CFD con Oanda


Oanda es una plataforma de negociación de divisas (forex, FX) y contratos por diferencia (CFD) que
ofrece un amplio conjunto de instrumentos negociables, como aquellos basados en pares de divisas,
índices bursátiles, materias primas o instrumentos de tasas (bonos de referencia). Este capítulo
proporciona orientación sobre cómo implementar estrategias comerciales algorítmicas automatizadas
con Oanda, utilizando el paquete contenedor tpqoa de Python.

Capítulo 9, Comercio de divisas con FXCM


FXCM es otra plataforma de comercio de divisas y CFD que lanzó recientemente una API RESTful
moderna para el comercio algorítmico. Los instrumentos disponibles abarcan múltiples clases de
activos, como divisas, índices bursátiles o materias primas. Está disponible un paquete contenedor
de Python que hace que el comercio algorítmico basado en código Python sea bastante conveniente
y eficiente (http://fxcmpy.tpq.io).

Capítulo 10, Automatización de operaciones


comerciales Este capítulo trata de la gestión de capital, análisis y gestión de riesgos, así como de
tareas típicas en la automatización técnica de operaciones comerciales algorítmicas. Cubre, por
ejemplo, en detalle el criterio de Kelly para la asignación de capital y el apalancamiento.

xiii | Prefacio
Machine Translated by Google

Apéndice
El apéndice proporciona una introducción concisa a los temas más importantes de Python,
NumPy y pandas en el contexto del material presentado en los capítulos principales. Representa
un punto de partida desde el cual uno puede ampliar su propio conocimiento de Python con el
tiempo.

La Figura P­1 muestra las capas relacionadas con el comercio algorítmico que cubren los capítulos
de abajo hacia arriba. Necesariamente comienza con la infraestructura de Python (Capítulo 2) y
agrega datos financieros (Capítulo 3), estrategia y código de backtesting vectorizado (Capítulos 4 y
5). Hasta ese momento, los conjuntos de datos se utilizan y manipulan en su conjunto.
El backtesting basado en eventos introduce por primera vez la idea de que los datos del mundo real
llegan de forma incremental (Capítulo 6). Es el puente que conduce a la capa de código de conexión
que cubre la comunicación por socket y el manejo de datos en tiempo real (Capítulo 7). Además de
eso, se requieren plataformas comerciales y sus API para poder realizar pedidos (Capítulos 8 y 9).
Finalmente, se cubren aspectos importantes de la automatización y la implementación (Capítulo 10).
En ese sentido, los capítulos principales del libro se relacionan con las capas como se ve en la Figura
P­1, que proporcionan una secuencia natural para los temas a cubrir.

Figura P­1. Las capas de Python para el comercio algorítmico

Prefacio | xiii
Machine Translated by Google

Para quien es este libro


Este libro está dirigido tanto a estudiantes, académicos y profesionales que quieran aplicar Python
en el fascinante campo del comercio algorítmico. El libro asume que el lector tiene, al menos en un
nivel fundamental, conocimientos previos tanto en programación Python como en operaciones
financieras. Para referencia y revisión, el Apéndice presenta temas importantes de Python, NumPy,
matplotlib y pandas . Las siguientes son buenas referencias para obtener una comprensión sólida
de los temas de Python importantes para este libro. La mayoría de los lectores se beneficiarán al
tener al menos acceso a Hilpisch (2018) como referencia. Con respecto a los enfoques de
aprendizaje automático y profundo aplicados al comercio algorítmico, Hilpisch (2020) proporciona
una gran cantidad de información general y una mayor cantidad de ejemplos específicos. En los
siguientes libros se puede encontrar información general sobre Python aplicado a las finanzas, la
ciencia de datos financieros y la inteligencia artificial:

Hilpisch, Yves. 2018. Python para finanzas: dominar las finanzas basadas en datos. 2da ed.
Sebastopol: O'Reilly.

. 2020. Inteligencia artificial en finanzas: una guía basada en Python. Sebastopol: O'Reilly.

McKinney, Wes. 2017. Python para análisis de datos: gestión de datos con Pandas, NumPy e
IPython. 2da ed. Sebastopol: O'Reilly.

Ramalho, Luciano. 2021. Python fluido: programación clara, concisa y eficaz.


2da ed. Sebastopol: O'Reilly.

Vander Plas, Jake. 2016. Manual de ciencia de datos de Python: herramientas esenciales para
trabajar con datos. Sebastopol: O'Reilly.

Puede encontrar información general sobre el comercio algorítmico, por ejemplo, en estos libros:

Chan, Ernesto. 2009. Comercio cuantitativo: cómo construir su propio comercio algorítmico.
ing Negocios. Hoboken y otros: John Wiley & Sons.

Chan, Ernesto. 2013. Comercio algorítmico: estrategias ganadoras y su justificación.


Hoboken y otros: John Wiley & Sons.

Kissel, Robert. 2013. La ciencia del comercio algorítmico y la gestión de carteras.


Ámsterdam y otros: Elsevier/Academic Press.

Narang, Rishi. 2013. Dentro de la caja negra: una guía sencilla para el comercio cuantitativo y de
alta frecuencia. Hoboken y otros: John Wiley & Sons.

Disfrute de su viaje por el mundo del comercio algorítmico con Python y póngase en contacto
enviando un correo electrónico a [email protected] si tiene preguntas o comentarios.

xiv | Prefacio
Machine Translated by Google

Las convenciones usadas en este libro

En este libro se utilizan las siguientes convenciones tipográficas:

Cursiva

Indica nuevos términos, URL, direcciones de correo electrónico, nombres de archivos y extensiones de archivos.

Ancho constante Se

utiliza para listados de programas, así como dentro de párrafos, para hacer referencia a elementos del
programa como nombres de variables o funciones, bases de datos, tipos de datos, variables de entorno,
declaraciones y palabras clave.

Negrita de ancho constante

Muestra comandos u otro texto que el usuario debe escribir literalmente.

Cursiva de ancho constante


Muestra texto que debe reemplazarse con valores proporcionados por el usuario o por valores
determinados por el contexto.

Este elemento significa un consejo o sugerencia.

Este elemento significa una nota general.

Este elemento indica una advertencia o precaución.

Usando ejemplos de código


Puede acceder y ejecutar el código que acompaña al libro en la plataforma Quant en https://py4at.pqp.io,
para lo cual solo se requiere un registro gratuito.

Si tiene una pregunta técnica o un problema al utilizar los ejemplos de código, envíe un correo electrónico
a [email protected].

Este libro está aquí para ayudarle a realizar su trabajo. En general, si se ofrece código de ejemplo con este
libro, puede utilizarlo en sus programas y documentación. Tu no

Prefacio | xvi
Machine Translated by Google

Debe comunicarse con nosotros para obtener permiso a menos que esté reproduciendo una parte
importante del código. Por ejemplo, escribir un programa que utilice varios fragmentos de código
de este libro no requiere permiso. Vender o distribuir ejemplos de libros de O'Reilly requiere
permiso. Responder una pregunta citando este libro y citando código de ejemplo no requiere
permiso. Incorporar una cantidad significativa de código de ejemplo de este libro en la
documentación de su producto requiere permiso.

Apreciamos la atribución, pero generalmente no la exigimos. Una atribución suele incluir el título,
el autor, la editorial y el ISBN. Por ejemplo, este libro puede atribuirse como: “Python para el
comercio algorítmico por Yves Hilpisch (O'Reilly). Copyright 2021 Yves Hilpisch, 978­1­492­05335­4.”

Si cree que su uso de ejemplos de código queda fuera del uso legítimo o del permiso otorgado
anteriormente, no dude en contactarnos en [email protected].

Aprendizaje en línea de O'Reilly

Durante más de 40 años, O'Reilly Media ha brindado capacitación,


conocimientos y perspectivas en tecnología y negocios para ayudar a
las empresas a tener éxito.

Nuestra red única de expertos e innovadores comparte su conocimiento y experiencia a través de


libros, artículos y nuestra plataforma de aprendizaje en línea. La plataforma de aprendizaje en
línea de O'Reilly le brinda acceso bajo demanda a cursos de capacitación en vivo, rutas de
aprendizaje en profundidad, entornos de codificación interactivos y una amplia colección de textos
y videos de O'Reilly y más de 200 editoriales más. Para obtener más información, visite http://oreilly.com.

Cómo contactarnos

Por favor dirija sus comentarios y preguntas sobre este libro al editor:

O'Reilly Media, Inc.


1005 Gravenstein Highway North
Sebastopol, CA 95472
800­998­9938 (en Estados Unidos o Canadá)
707­829­0515 (internacional o local)
707­829­0104 (fax)

Tenemos una página web para este libro, donde enumeramos erratas, ejemplos y cualquier
información adicional. Puede acceder a esta página en https://oreil.ly/py4at.

xvi | Prefacio
Machine Translated by Google

Envíe un correo electrónico a [email protected] para comentar o hacer preguntas técnicas sobre
este libro.

Para noticias e información sobre nuestros libros y cursos, visite http://oreilly.com.

Encuéntrenos en Facebook: http://facebook.com/oreilly

Síganos en Twitter: http://twitter.com/oreillymedia Mírenos en

YouTube: http://youtube.com/oreillymedia

Expresiones de gratitud

Quiero agradecer a los revisores técnicos (Hugh Brown, McKlayne Marshall, Ramanathan Ramakrishnamoorthy
y Prem Jebaseelan) que proporcionaron comentarios útiles que condujeron a muchas mejoras en el contenido
del libro.

Como siempre, un agradecimiento especial a Michael Schwed, quien me apoya en todos los asuntos técnicos,
tanto simples como muy complejos, con su amplio y profundo conocimiento tecnológico.

Los delegados de los Programas de Certificación en Python para Finanzas Computacionales y Comercio
Algorítmico también ayudaron a mejorar este libro. Sus comentarios continuos me han permitido eliminar
errores y errores y perfeccionar el código y los cuadernos utilizados.

en nuestras clases de formación online y ahora, por fin, en este libro.

También me gustaría agradecer a todo el equipo de O'Reilly Media, especialmente a Michelle Smith, Michele
Cronin, Victoria DeRose y Danny Elfanbaum, por hacer que todo esto sucediera y ayudarme a perfeccionar
el libro de tantas maneras.

Por supuesto, todos los errores restantes son sólo míos.

Además, también me gustaría agradecer al equipo de Refinitiv, en particular a Jason Ramchandani, por
brindar apoyo continuo y acceso a datos financieros. Los principales archivos de datos utilizados a lo largo
del libro y puestos a disposición de los lectores se recibieron de una forma u otra de las API de datos de
Refinitiv.

A mi familia con amor. Dedico este libro a mi padre Adolf, cuyo apoyo a mí y a nuestra familia abarca ya casi
cinco décadas.

Prefacio | xvii
Machine Translated by Google
Machine Translated by Google

CAPÍTULO 1

Python y el comercio algorítmico

En Goldman [Sachs] el número de personas que se dedican a negociar acciones ha caído de un máximo de
600 en 2000 a sólo dos en la
actualidad.1 —The Economist

Este capítulo proporciona información general y una descripción general de los temas tratados en
este libro. Aunque Python para el comercio algorítmico es un nicho en la intersección de la
programación y las finanzas de Python, es un nicho de rápido crecimiento que aborda temas tan
diversos como la implementación de Python, el análisis financiero interactivo, el aprendizaje
automático y profundo, la programación orientada a objetos, comunicación por socket, visualización
de datos en streaming y plataformas comerciales.

Para un repaso rápido sobre temas importantes de Python, lea primero el Apéndice .

Python para finanzas


El lenguaje de programación Python se originó en 1991 con el primer lanzamiento de Guido van
Rossum de una versión denominada 0.9.0. En 1994, siguió la versión 1.0. Sin embargo, Python
tardó casi dos décadas en establecerse como un importante lenguaje de programación y plataforma
tecnológica en la industria financiera. Por supuesto, hubo primeros usuarios, principalmente fondos
de cobertura, pero la adopción generalizada probablemente no comenzó hasta alrededor de 2011.

Un obstáculo importante para la adopción de Python en la industria financiera ha sido el hecho de


que la versión predeterminada de Python, llamada CPython, es un lenguaje interpretado de alto
nivel. Los algoritmos numéricos en general y los algoritmos financieros en particular a menudo se
implementan basándose en estructuras de bucle (anidados). Aunque están compilados, los
lenguajes de bajo nivel como C o C++ son realmente rápidos a la hora de ejecutar dichos bucles, Python, que

1 "Demasiado calamar para fallar". The Economist, 29 de octubre de 2016.

1
Machine Translated by Google

se basa en la interpretación en lugar de la compilación, generalmente es bastante lento al hacerlo. Como


consecuencia, Python puro resultó demasiado lento para muchas aplicaciones financieras del mundo real,
como la fijación de precios de opciones o la gestión de riesgos.

Python versus pseudocódigo Aunque

Python nunca estuvo dirigido específicamente a las comunidades científica y financiera, a muchas personas
de estos campos les gustó la belleza y concisión de su sintaxis. No hace mucho, se consideraba una
buena tradición explicar un algoritmo (financiero) y al mismo tiempo presentar algún pseudocódigo como
paso intermedio hacia su adecuada implementación tecnológica. Muchos sintieron que, con Python, el
paso del pseudocódigo ya no sería necesario. Y se demostró que en su mayoría tenían razón.

Consideremos, por ejemplo, la discretización de Euler del movimiento browniano geométrico, como en la
Ecuación 1­1.

Ecuación 1­1. Discretización de Euler del movimiento browniano

geométrico ST = S0 exp r −0.5σ2 T + σz T

Durante décadas, el compilador y el lenguaje de marcado LaTeX han sido el estándar de oro para la
creación de documentos científicos que contienen fórmulas matemáticas. En muchos sentidos, la sintaxis
de Latex es similar o ya se parece al pseudocódigo cuando, por ejemplo, se establecen ecuaciones, como
en la Ecuación 1­1. En este caso particular, la versión Latex queda así:

S_T = S_0 \exp((r ­ 0.5 \sigma^2) T + \sigma z \sqrt{T})

En Python, esto se traduce en código ejecutable, dadas las respectivas definiciones de variables, que
también está muy cerca de la fórmula financiera y de la representación de Latex:

S_T = S_0 * exp((r ­ 0,5 * sigma ** 2) * T + sigma * z * raíz cuadrada (T))

Sin embargo, el problema de la velocidad persiste. Esta ecuación en diferencias, como una aproximación
numérica de la respectiva ecuación diferencial estocástica, se utiliza generalmente para fijar el precio de
los derivados mediante simulación de Monte Carlo o para realizar análisis y gestión de riesgos basados en
la simulación.2 Estas tareas, a su vez, pueden requerir millones de simulaciones que deben terminarse a
su debido tiempo, a menudo casi en tiempo real o al menos casi en tiempo cercano. Python, como lenguaje
de programación interpretado de alto nivel, nunca fue diseñado para ser lo suficientemente rápido como
para abordar tareas computacionalmente exigentes.

2 Para más detalles, véase Hilpisch (2018, cap. 12).

2 | Capítulo 1: Python y el comercio algorítmico


Machine Translated by Google

NumPy y vectorización
En 2006, Travis Oliphant lanzó la versión 1.0 del paquete NumPy Python .
NumPy significa Python numérico, lo que sugiere que apunta a escenarios que son
numéricamente exigente. El intérprete base de Python intenta ser lo más general posible.
en muchas áreas, lo que a menudo genera bastantes gastos generales en tiempo de ejecución.3 NumPy, en el
Por otro lado, utiliza la especialización como enfoque principal para evitar gastos generales y ser lo más
bueno y lo más rápido posible en ciertos escenarios de aplicación.

La clase principal de NumPy es el objeto de matriz regular, llamado objeto ndarray para una matriz de n
dimensiones. Es inmutable, lo que significa que no se puede cambiar de tamaño,
y sólo puede acomodar un único tipo de datos, llamado dtype. Esta especialización permite
para la implementación de código conciso y rápido. Un enfoque central en este contexto
es la vectorización. Básicamente, este enfoque evita bucles en el nivel de Python y elimina
abre el bucle al código NumPy especializado , generalmente implementado en C y allí.
por lo tanto bastante rápido.

Considere la simulación de 1.000.000 valores de fin de período ST según la ecuación


1­1 con Python puro. La mayor parte del siguiente código es un bucle for con
1.000.000 iteraciones:

En [1]: %%tiempo
importar aleatoriamente
de importación matemática exp, sqrt

S0 = 100
r = 0,05 T
= 1,0
sigma = 0,2

valores = []

para _ en rango(1000000): ST =
S0 * exp((r ­ 0.5 * sigma ** 2) * T +
sigma * aleatorio.gauss(0, 1) * sqrt(T)) valores.append(ST)

Tiempos de CPU: usuario 1,13 s, sistema: 21,7 ms, total: 1,15 s


Tiempo de pared: 1,15 s

El nivel de índice inicial.

El tipo corto constante.

3 Por ejemplo, los objetos de lista no sólo son mutables, lo que significa que pueden cambiarse de tamaño, sino que también pueden
También contienen casi cualquier otro tipo de objeto Python, como objetos int, float, tuple o los propios objetos de lista .

Python para finanzas | 3


Machine Translated by Google

El horizonte temporal en fracciones de año.

El factor de volatilidad constante.

Un objeto de lista vacío para recopilar valores simulados.

El bucle for principal .

La simulación de un único valor de fin de período.

Agrega el valor simulado al objeto de lista .

Con NumPy, puedes evitar completamente los bucles en el nivel de Python mediante el uso de
vectorización. El código es mucho más conciso, más legible y más rápido en un factor de aproximadamente
ocho:

En [2]: %%tiempo
importar numpy como np

S0 = 100r =
0,05
T = 1,0
sigma = 0,2

ST = S0 * np.exp((r ­ 0,5 * sigma ** 2) * T +


sigma * np.random.standard_normal(1000000) * np.sqrt(T))

Tiempos de CPU: usuario 375 ms, sistema: 82,6 ms, total: 458 ms Tiempo de
pared: 160 ms

Esta única línea de código NumPy simula todos los valores y los almacena en un objeto ndarray .

La vectorización es un concepto poderoso para escribir código conciso, fácil


de leer y fácil de mantener en finanzas y comercio algorítmico. Con NumPy,
el código vectorizado no sólo hace que el código sea más conciso, sino que
también puede acelerar considerablemente la ejecución del código (por un
factor de aproximadamente ocho en la simulación de Monte Carlo, por ejemplo).

Es seguro decir que NumPy ha contribuido significativamente al éxito de Python en ciencia y finanzas.
Muchos otros paquetes populares de Python de la llamada pila científica de Python se basan en NumPy
como una estructura de datos eficiente y funcional para almacenar y manejar datos numéricos. De hecho,
NumPy es una consecuencia del proyecto del paquete SciPy , que proporciona una gran cantidad de
funciones que se necesitan con frecuencia en la ciencia. El proyecto SciPy reconoció la necesidad de una
estructura de datos numéricos más poderosa y

4 | Capítulo 1: Python y el comercio algorítmico


Machine Translated by Google

Consolidó proyectos antiguos como Numeric y NumArray en esta área en uno nuevo y unificador en
forma de NumPy.

En el comercio algorítmico, una simulación de Monte Carlo puede no ser el caso de uso más importante
para un lenguaje de programación. Sin embargo, si ingresa al espacio del comercio algorítmico, la gestión
de conjuntos de datos de series temporales financieras más grandes, o incluso grandes, es un caso de
uso muy importante. Basta pensar en las pruebas retrospectivas de estrategias comerciales (intradiarias)
o en el procesamiento de flujos de datos de ticks durante el horario comercial. Aquí es donde entra en
juego el paquete de análisis de datos pandas .

pandas y la clase DataFrame El desarrollo de

pandas comenzó en 2008 por Wes McKinney, quien en aquel entonces trabajaba en AQR Capital
Management, un gran fondo de cobertura que operaba en Greenwich, Connecticut. Como ocurre con
cualquier otro fondo de cobertura, trabajar con datos de series temporales es de suma importancia para
AQR Capital Management, pero en aquel entonces Python no ofrecía ningún tipo de soporte atractivo
para este tipo de datos. La idea de Wes era crear un paquete que imitara las capacidades del lenguaje
estadístico R (http://r­project.org) en esta área. Esto se refleja, por ejemplo, al nombrar la clase principal
DataFrame, cuya contraparte en R se llama data.frame. Al no considerarse lo suficientemente cercano al
negocio principal de la administración del dinero, AQR Capital Management abrió el proyecto pandas en
2009, lo que marca el comienzo de una importante historia de éxito en análisis financiero y datos basados
en código abierto.

En parte debido a los pandas, Python se ha convertido en una fuerza importante en el análisis financiero
y de datos. Muchas personas que adoptan Python, provenientes de otros lenguajes diversos, citan a los
pandas como una de las principales razones de su decisión. En combinación con fuentes de datos
abiertos como Quandl, pandas incluso permite a los estudiantes realizar análisis financieros sofisticados
con las barreras de entrada más bajas jamás vistas: basta con un ordenador portátil normal con conexión
a Internet.

Supongamos que un operador algorítmico está interesado en operar con Bitcoin, la criptomoneda con
mayor capitalización de mercado. Un primer paso podría ser recuperar datos sobre el tipo de cambio
histórico en USD. Utilizando datos de Quandl y pandas, esta tarea se realiza en menos de un minuto. La
Figura 1­1 muestra el gráfico que resulta del siguiente código Python, que tiene (omitiendo algunas
parametrizaciones relacionadas con el estilo de trazado) solo cuatro líneas. Aunque pandas no se importa
explícitamente, el paquete contenedor Quandl Python devuelve por defecto un objeto DataFrame que
luego se usa para agregar un promedio móvil simple (SMA) de 100 días, así como para visualizar los
datos sin procesar junto con el SMA:

En [3]: %matplotlib en línea


de pylab import mpl, plt
plt.style.use('seaborn')
mpl.rcParams['savefig.dpi'] = 300

Python para finanzas | 5


Machine Translated by Google

mpl.rcParams['font.family'] = 'serif'

En [4]: importar configparser


c = configparser.ConfigParser()
c.read('../pyalgo.cfg')
Fuera[4]: ['../pyalgo.cfg']

En [5]: importar quandl como q


q.ApiConfig.api_key = c['quandl']['api_key'] d =
q.get('BCHAIN/MKPRU')
d['SMA'] = d['Value' ].rolling(100).mean()
d.loc['2013­1­1':].plot(title=' tipo de cambio BTC/USD',
tamaño de higo=(10, 6));

Importa y configura el paquete de trazado.

Importa el módulo configparser y lee las credenciales.

Importa el paquete contenedor Quandl Python y proporciona la clave API.

Recupera datos diarios del tipo de cambio de Bitcoin y devuelve datos de pandas
Objeto de marco con una sola columna.

Calcula la SMA de 100 días de forma vectorizada.

Selecciona datos a partir del 1 de enero de 2013 y los traza.

Obviamente, NumPy y pandas contribuyen de manera mensurable al éxito de Python en


finanzas. Sin embargo, el ecosistema Python tiene mucho más que ofrecer en forma de funciones adicionales.
Paquetes Python opcionales que resuelven problemas bastante fundamentales y, a veces, especi
los alizados. Este libro hará uso de paquetes para la recuperación y almacenamiento de datos (por
ejemplo, PyTables, TsTables, SQLite) y para aprendizaje automático y profundo (por ejemplo,
ejemplo, scikit­learn, TensorFlow), por nombrar solo dos categorías. En el camino, lo haremos
También implemente clases y módulos que harán que cualquier proyecto de comercio algorítmico
más eficiente. Sin embargo, los principales paquetes utilizados serán NumPy y
pandas.

6 | Capítulo 1: Python y el comercio algorítmico


Machine Translated by Google

Figura 1­1. Tipo de cambio histórico de Bitcoin en USD desde principios de 2013 hasta mediados
de 2020

Mientras que NumPy proporciona la estructura de datos básica para almacenar


datos numéricos y trabajar con ellos, pandas aporta potentes capacidades de
gestión de series temporales. También hace un gran trabajo al integrar la
funcionalidad de otros paquetes en una API fácil de usar. El ejemplo de Bitcoin
que se acaba de describir muestra que una sola llamada a un método en un
objeto DataFrame es suficiente para generar un gráfico con dos series de
tiempo financieras visualizadas. Al igual que NumPy, pandas permite un código
vectorizado bastante conciso que generalmente también se ejecuta bastante
rápido debido al uso intensivo de código compilado bajo el capó.

Comercio algorítmico
El término comercio algorítmico no está definido de forma única ni universal. En un nivel bastante
básico, se refiere a la negociación de instrumentos financieros basándose en algún algoritmo formal.
Un algoritmo es un conjunto de operaciones (matemáticas, técnicas) que se deben realizar en una
secuencia determinada para lograr un objetivo determinado. Por ejemplo, existen algoritmos
matemáticos para resolver un cubo de Rubik.4 Un algoritmo de este tipo puede resolver el problema
en cuestión mediante un procedimiento paso a paso, a menudo perfectamente. Otro ejemplo son los algoritmos para

4 Véase Matemáticas del cubo de Rubik o Algoritmos para resolver el cubo de Rubik.

Comercio algorítmico | 7
Machine Translated by Google

encontrar la(s) raíz(es) de una ecuación, si es que existe(n). En ese sentido, el objetivo de un algoritmo
matemático suele estar bien especificado y a menudo se espera una solución óptima.

Pero ¿qué pasa con el objetivo de los algoritmos de comercio financiero? Esta pregunta no es tan fácil de
responder en general. Podría ser útil dar un paso atrás por un momento y considerar los motivos generales
para comerciar. En Dorn et al. (2008) escribe:

El comercio en los mercados financieros es una actividad económica importante. Las


operaciones son necesarias para entrar y salir del mercado, poner dinero innecesario en el
mercado y volver a convertirlo en efectivo cuando se necesita el dinero. También son
necesarios para mover dinero dentro del mercado, intercambiar un activo por otro, gestionar
el riesgo y explotar información sobre futuros movimientos de precios.

La opinión expresada aquí es de naturaleza más técnica que económica y se centra principalmente en el
proceso en sí y sólo en parte en por qué las personas inician el comercio en primer lugar.
Para nuestros propósitos, una lista no exhaustiva de motivos de negociación financiera de personas e
instituciones financieras que administran dinero propio o para terceros incluye lo siguiente:

Negociación
beta Ganar primas de riesgo de mercado invirtiendo, por ejemplo, en fondos cotizados en bolsa (ETF)
que replican el rendimiento del S&P 500.

Generación alfa
Ganar primas de riesgo independientes del mercado, por ejemplo, vendiendo en corto acciones
cotizadas en el S&P 500 o ETF en el S&P 500.

Cobertura
estática Cobertura contra riesgos de mercado comprando, por ejemplo, opciones de venta fuera del
dinero en el S&P 500.

Cobertura dinámica
Cobertura contra riesgos de mercado que afectan a las opciones sobre el S&P 500, por ejemplo,
negociando dinámicamente futuros sobre el S&P 500 e instrumentos apropiados de efectivo, mercado
monetario o tasas.

Administración de la responsabilidad de los bienes

Negociar acciones y ETF del S&P 500 para poder cubrir los pasivos resultantes de, por ejemplo, la
suscripción de pólizas de seguro de vida.

Creación de
mercado Proporcionar, por ejemplo, liquidez a opciones sobre el S&P 500 mediante la compra y venta
de opciones a diferentes precios de oferta y demanda.

Todos estos tipos de operaciones pueden implementarse mediante un enfoque discrecional, en el que los
comerciantes humanos toman decisiones principalmente por sí mismos, así como basándose en algoritmos
que apoyan al comerciante humano o incluso los reemplazan por completo en el futuro.

8 | Capítulo 1: Python y el comercio algorítmico


Machine Translated by Google

proceso de toma de decisiones. En este contexto, por supuesto, la informatización del comercio
financiero juega un papel importante. Mientras que al comienzo del comercio financiero, el comercio
en el piso con un gran grupo de personas gritándose entre sí ("grito abierto") era la única forma de
ejecutar operaciones, la informatización y la llegada de Internet y las tecnologías web han
revolucionado el comercio en el sector financiero. industria. La cita al comienzo de este capítulo
ilustra esto de manera impresionante en términos del número de personas que participaron
activamente en la negociación de acciones de Goldman Sachs en 2000 y en 2016. Es una
tendencia que se previó hace 25 años, como Solomon y Corso (1991). señalar:

Las computadoras han revolucionado el comercio de valores y el mercado de valores se encuentra


actualmente en medio de una transformación dinámica. Está claro que el mercado del futuro no se
parecerá a los mercados del pasado.

La tecnología ha hecho posible que la información sobre los precios de las acciones se envíe a
todo el mundo en segundos. Actualmente, las computadoras enrutan órdenes y ejecutan pequeñas
operaciones directamente desde la terminal de la firma de corretaje a la bolsa. Actualmente, las
computadoras conectan varias bolsas de valores, una práctica que está contribuyendo a crear un
mercado global único para el comercio de valores. Las continuas mejoras en la tecnología harán
posible ejecutar operaciones a nivel mundial mediante sistemas de comercio electrónico.

Curiosamente, uno de los algoritmos más antiguos y utilizados se encuentra en la cobertura


dinámica de opciones. Ya con la publicación de los artículos fundamentales sobre la fijación de
precios de las opciones europeas de Black y Scholes (1973) y Merton (1973), el algoritmo, llamado
cobertura delta, estuvo disponible mucho antes de que comenzaran las operaciones computarizadas
y electrónicas. La cobertura delta como algoritmo comercial muestra cómo protegerse de todos los
riesgos del mercado en un mundo modelo simplificado, perfecto y continuo. En el mundo real, con
costos de transacción, comercio discreto, mercados imperfectamente líquidos y otras fricciones
(“imperfecciones”), el algoritmo también ha demostrado, de manera algo sorprendente tal vez, su
utilidad y solidez. Puede que no permita cubrir perfectamente los riesgos de mercado que afectan
a las opciones, pero es útil para acercarse al ideal y, por lo tanto, todavía se utiliza a gran escala
en la industria financiera.5 Este libro se centra en el comercio algorítmico en

el contexto de alfa. generando estrategias.


Aunque existen definiciones más sofisticadas de alfa, para los propósitos de este libro, alfa se
considera la diferencia entre el rendimiento de una estrategia comercial durante un período de
tiempo y el rendimiento del punto de referencia (acción única, índice, criptomoneda, etc.). Por
ejemplo, si el S&P 500 arroja un rendimiento del 10 % en 2018 y una estrategia algorítmica arroja
un rendimiento del 12 %, entonces el alfa es de +2 % puntos. Si la estrategia arroja un rendimiento
del 7%, entonces el alfa es ­3% de puntos. En general, dichas cifras no se ajustan al riesgo, y otras
características de riesgo, como la reducción máxima (período), generalmente se consideran de
importancia de segundo orden, en todo caso.

5 Véase Hilpisch (2015) para un análisis detallado de las estrategias de cobertura delta para opciones europeas y americanas.
usando Python.

Comercio algorítmico | 9
Machine Translated by Google

Este libro se centra en estrategias generadoras de alfa, o estrategias que intentan


generar rendimientos positivos (por encima de un punto de referencia)
independientemente del desempeño del mercado. Alfa se define en este libro (de
la forma más sencilla) como el exceso de rendimiento de una estrategia sobre el
rendimiento del instrumento financiero de referencia.

Hay otras áreas en las que los algoritmos relacionados con el comercio desempeñan un papel
importante. Uno es el espacio del comercio de alta frecuencia (HFT), donde la velocidad suele ser la
disciplina en la que compiten los jugadores.6 Los motivos para el HFT son diversos, pero la creación de
mercado y la generación de alfa probablemente desempeñen un papel destacado. Otro es la ejecución
de operaciones, donde se implementan algoritmos para ejecutar de manera óptima ciertas operaciones no estándar.
Los motivos en esta área podrían incluir la ejecución (al mejor precio posible) de órdenes grandes o la
ejecución de una orden con el menor impacto posible en el mercado y en el precio.
Un motivo más sutil podría ser disfrazar una orden ejecutándola en varios intercambios diferentes.

Queda por abordar una pregunta importante: ¿hay alguna ventaja en utilizar algoritmos para el comercio
en lugar de investigación, experiencia y discreción humanas? Esta pregunta difícilmente puede
responderse de manera generalizada. Sin duda, hay operadores humanos y administradores de carteras
que han ganado, en promedio, más que su punto de referencia para los inversores durante períodos de
tiempo más largos. El ejemplo más destacado a este respecto es Warren Buffett. Por otro lado, los
análisis estadísticos muestran que la mayoría de los gestores de carteras activos rara vez superan de
forma consistente los índices de referencia relevantes. Refiriéndose al año 2015, Adam Shell escribe:

El año pasado, por ejemplo, cuando el índice bursátil Standard & Poor's 500 registró un rendimiento total
insignificante del 1,4% con dividendos incluidos, el 66% de los fondos de acciones de grandes empresas
"administrados activamente" registraron rendimientos menores que el índice... El panorama es igual de
sombrío: el 84% de los fondos de gran capitalización generaron rendimientos inferiores a los del S&P 500
en el último período de cinco años y el 82% se quedó por debajo de ellos en los últimos 10 años, según
el estudio.7

En un estudio empírico publicado en diciembre de 2016, Harvey et al. escribir:

Analizamos y contrastamos el desempeño de los fondos de cobertura discrecionales y sistemáticos.


Los fondos sistemáticos utilizan estrategias basadas en reglas, con poca o ninguna intervención humana
diaria... Encontramos que, para el período 1996­2014, los administradores de acciones sistemáticos
tienen un desempeño inferior a sus contrapartes discrecionales en términos de rendimientos (brutos) no
ajustados, pero que después Al ajustar por exposiciones a factores de riesgo bien conocidos, el
rendimiento ajustado al riesgo es similar. En el caso macro, los fondos sistemáticos superan a los fondos
discrecionales, tanto en términos no ajustados como ajustados al riesgo.

6 Consulte el libro de Lewis (2015) para obtener una introducción no técnica a HFT.

7 Fuente: “El 66% de los administradores de fondos no pueden igualar los resultados de S&P”. EE.UU. Hoy en día, 14 de marzo de 2016.

10 | Capítulo 1: Python y el comercio algorítmico


Machine Translated by Google

La tabla 1­0 reproduce los principales hallazgos cuantitativos del estudio de Harvey et al.
(2016).8 En la tabla, los factores incluyen los tradicionales (acciones, bonos, etc.),
(valor, impulso, etc.) y volatilidad (compra de opciones de compra y venta al precio del dinero).
El índice de evaluación del rendimiento ajustado divide alfa por la volatilidad del rendimiento ajustado. Para
Para más detalles y antecedentes, ver el estudio original.

Los resultados del estudio ilustran que los fondos de cobertura macro sistemáticos (“algorítmicos”) por
forman mejor como categoría, tanto en términos no ajustados como ajustados por riesgo. Generan un
alfa anualizado de 4,85 puntos% durante el período estudiado. Estos son fondos de cobertura
implementar estrategias que suelen ser globales, abarcan todos los activos y a menudo implican
elementos políticos y macroeconómicos. Los fondos de cobertura de acciones sistemáticos sólo superaron a sus
contrapartidas discrecionales sobre la base del ratio de valoración de rentabilidad ajustado (0,35
frente a 0,25).

Macro sistémica Macro discrecional Patrimonio sistemático Patrimonio discrecional

5,01% 2,86% 2,88% 4,09%


Promedio de retorno

Retorno atribuido a 0,15% 1,28% 1,77% 2,86%

factores

1,57% 1,11% 1,22%


Adj. rentabilidad media (alfa) 4,85%

0,93% 5,10% 3,18% 4,79%


Adj. volatilidad del rendimiento

0,44 0,31 0,35 0,25


Adj. tasa de tasación de retorno

En comparación con el S&P 500, el rendimiento general de los fondos de cobertura fue bastante pobre durante el
año 2017. Mientras que el índice S&P 500 obtuvo un rendimiento del 21,8%, los fondos de cobertura solo obtuvieron un rendimiento del 8,5%

a inversores (ver este artículo en Investopedia). Esto ilustra lo difícil que es, incluso con
presupuestos multimillonarios para investigación y tecnología, para generar alfa.

Python para el comercio algorítmico


Python se utiliza en muchos rincones de la industria financiera, pero se ha vuelto particularmente
Popular en el espacio comercial algorítmico. Hay algunas buenas razones para esto:

Capacidades de análisis de datos


Un requisito importante para todo proyecto de comercio algorítmico es la capacidad de gestionar
envejecer y procesar datos financieros de manera eficiente. Python, en combinación con paquetes
como NumPy y pandas, hace la vida más fácil en este sentido para cada algorítmico
trader que la mayoría de los otros lenguajes de programación.

8 Rendimiento anualizado (por encima de la tasa de interés a corto plazo) y medidas de riesgo para categorías de fondos de cobertura
que comprende un total de 9.000 fondos de cobertura durante el período comprendido entre junio de 1996 y diciembre de 2014.

Python para el comercio algorítmico | 11


Machine Translated by Google

Manejo de API modernas Las


plataformas comerciales en línea modernas, como las de FXCM y Oanda , ofrecen interfaces de
programación de aplicaciones (API) RESTful y API de socket (streaming) para acceder a datos
históricos y en vivo. En general, Python es muy adecuado para interactuar eficientemente con
dichas API.

Paquetes dedicados
Además de los paquetes estándar de análisis de datos, hay varios paquetes disponibles que
están dedicados al espacio comercial algorítmico, como PyAlgoTrade y Zipline para realizar
pruebas retrospectivas de estrategias comerciales y Pyfolio para realizar análisis de riesgo y
cartera.

Paquetes patrocinados por


proveedores Cada vez más proveedores en el espacio lanzan paquetes Python de código
abierto para facilitar el acceso a sus ofertas. Entre ellos se encuentran plataformas de comercio
en línea como Oanda, así como proveedores de datos líderes como Bloomberg y Refinitiv.

Plataformas dedicadas
Quantopian, por ejemplo, ofrece un entorno de backtesting estandarizado como una plataforma
basada en web donde el lenguaje elegido es Python y donde las personas pueden intercambiar
ideas con otras personas con ideas afines a través de diferentes funciones de redes sociales.
Desde su fundación hasta 2020, Quantopian ha atraído a más de 300.000 usuarios.

Adopción del lado de compra


y venta Cada vez más actores institucionales han adoptado Python para agilizar los esfuerzos
de desarrollo en sus departamentos comerciales. Esto, a su vez, requiere cada vez más
personal competente en Python, lo que hace que valga la pena aprender Python.
inversión.

Educación, formación y libros.


Los requisitos previos para la adopción generalizada de una tecnología o lenguaje de
programación son programas de educación y capacitación académica y profesional en
combinación con libros especializados y otros recursos. El ecosistema Python ha experimentado
un enorme crecimiento en este tipo de ofertas recientemente, educando y capacitando a cada
vez más personas en el uso de Python para las finanzas. Se puede esperar que esto refuerce
la tendencia de adopción de Python en el espacio comercial algorítmico.

En resumen, es bastante seguro decir que Python ya juega un papel importante en el comercio
algorítmico y parece tener un fuerte impulso para volverse aún más importante en el futuro. Por lo
tanto, es una buena opción para cualquiera que intente ingresar a este espacio, ya sea como un
comerciante "minorista" ambicioso o como un profesional empleado por una institución financiera
líder dedicada al comercio sistemático.

12 | Capítulo 1: Python y el comercio algorítmico


Machine Translated by Google

Enfoque y requisitos previos

El foco de este libro está en Python como lenguaje de programación para el comercio algorítmico. El
libro supone que el lector ya tiene cierta experiencia con Python y los paquetes populares de Python
utilizados para el análisis de datos. Buenos libros introductorios son, por ejemplo, Hilpisch (2018),
McKinney (2017) y VanderPlas (2016), que se pueden consultar para construir una base sólida en
Python para el análisis de datos y las finanzas. También se espera que el lector tenga cierta
experiencia con las herramientas típicas utilizadas para el análisis interactivo con Python, como
IPython, a la que VanderPlas (2016) también proporciona una introducción.

Este libro presenta y explica el código Python que se aplica a los temas en cuestión, como realizar
pruebas retrospectivas de estrategias comerciales o trabajar con transmisión de datos. No puede
proporcionar una introducción completa a todos los paquetes utilizados en diferentes lugares. Sin
embargo, intenta resaltar aquellas capacidades de los paquetes que son centrales para la exposición
(como la vectorización con NumPy).

El libro tampoco puede proporcionar una introducción exhaustiva y una descripción general de todos
los aspectos financieros y operativos relevantes para el comercio algorítmico. En cambio, el enfoque
se centra en el uso de Python para construir la infraestructura necesaria para sistemas de comercio
algorítmico automatizados. Por supuesto, la mayoría de los ejemplos utilizados provienen del espacio
comercial algorítmico. Sin embargo, cuando se trata, por ejemplo, de estrategias de impulso o de
reversión a la media, se utilizan más o menos simplemente sin proporcionar verificación (estadística)
o una discusión en profundidad de sus complejidades. Siempre que parece apropiado, se dan
referencias que señalan al lector fuentes que abordan cuestiones que quedaron abiertas durante la
exposición.

Considerándolo todo, este libro está escrito para lectores que tienen cierta experiencia tanto con
Python como con el comercio (algorítmico). Para dicho lector, el libro es una guía práctica para la
creación de sistemas comerciales automatizados utilizando Python y paquetes adicionales.

Este libro utiliza una serie de enfoques de programación Python (por


ejemplo, programación orientada a objetos) y paquetes (por ejemplo,
scikit­learn) que no se pueden explicar en detalle. La atención se
centra en aplicar estos enfoques y paquetes a diferentes pasos de un
proceso de negociación algorítmica. Por lo tanto, se recomienda que
aquellos que aún no tengan suficiente experiencia en Python (para
finanzas) consulten además textos más introductorios de Python.

Estrategias comerciales

A lo largo de este libro, se utilizan como ejemplos cuatro estrategias comerciales algorítmicas
diferentes. Se presentan brevemente en las siguientes secciones y con más detalle en el Capítulo 4.
Todas estas estrategias comerciales se pueden clasificar como principalmente búsqueda de alfa.

Enfoque y requisitos previos | 13


Machine Translated by Google

estrategias, ya que su principal objetivo es generar retornos positivos superiores a los del mercado
independientemente de la dirección del mercado. Los ejemplos canónicos a lo largo del libro, cuando
se trata de instrumentos financieros negociados, son un índice bursátil, una acción única o una
criptomoneda (denominada en moneda fiduciaria). El libro no cubre estrategias que involucran
múltiples instrumentos financieros al mismo tiempo (estrategias de negociación de pares, estrategias
basadas en cestas, etc.). También cubre solo estrategias cuyas señales comerciales se derivan de
datos estructurados de series de tiempo financieras y no, por ejemplo, de fuentes de datos no
estructurados como noticias o redes sociales. Esto mantiene las discusiones y las implementaciones
de Python concisas y más fáciles de entender, en línea con el enfoque (discutido anteriormente) de
centrarse en Python para el comercio algorítmico.9 El resto de este capítulo

ofrece una descripción general rápida de las cuatro estrategias comerciales utilizadas en este libro. .

Medias móviles simples El primer

tipo de estrategia comercial se basa en medias móviles simples (SMA) para generar señales
comerciales y posicionamientos en el mercado. Estas estrategias comerciales han sido popularizadas
por los llamados analistas técnicos o chartistas. La idea básica es que una SMA a corto plazo que
tenga un valor mayor que una SMA a más largo plazo indica una posición larga en el mercado y el
escenario opuesto indica una posición neutral o corta en el mercado.

Impulso
La idea básica detrás de las estrategias de impulso es que se supone que un instrumento financiero
se comportará de acuerdo con su desempeño reciente durante algún tiempo adicional. Por ejemplo,
cuando un índice bursátil ha tenido un rendimiento negativo en promedio durante los últimos cinco
días, se supone que su rendimiento también será negativo mañana.

Reversión media
En las estrategias de reversión a la media, se supone que un instrumento financiero revierte a algún
nivel medio o de tendencia si actualmente está lo suficientemente lejos de ese nivel. Por ejemplo,
supongamos que una acción cotiza 10 USD por debajo de su nivel SMA de 200 días de 100. Entonces
se espera que el precio de la acción vuelva a su nivel SMA pronto.

9 Consulte el libro de Kissel (2013) para obtener una descripción general de los temas relacionados con el comercio algorítmico, el libro de Chan
(2013) para una discusión en profundidad sobre las estrategias de impulso y reversión a la media, o el libro de Narang
(2013) para una cobertura del comercio cuantitativo y HFT en general.

14 | Capítulo 1: Python y el comercio algorítmico


Machine Translated by Google

Aprendizaje automático y profundo Con

los algoritmos de aprendizaje automático y profundo, generalmente se adopta un enfoque más de caja
negra para predecir los movimientos del mercado. Por simplicidad y reproducibilidad, los ejemplos de este
libro se basan principalmente en observaciones de rendimientos históricos como características para
entrenar algoritmos de aprendizaje profundo y automático para predecir los movimientos del mercado de valores.

Este libro no presenta el comercio algorítmico de forma sistemática.


Dado que la atención se centra en la aplicación de Python en este
fascinante campo, los lectores que no estén familiarizados con el
comercio algorítmico deben consultar recursos dedicados al tema,
algunos de los cuales se citan en este capítulo y en los siguientes.
Pero tenga en cuenta el hecho de que el mundo del comercio
algorítmico en general es reservado y que casi todos los que tienen
éxito son naturalmente reacios a compartir sus secretos para proteger
sus fuentes de éxito (es decir, su alfa).

Conclusiones
Python ya es una fuerza en las finanzas en general y está en camino de convertirse en una fuerza
importante en el comercio algorítmico. Hay varias buenas razones para usar Python para el comercio
algorítmico, entre ellas el poderoso ecosistema de paquetes que permite un análisis de datos eficiente
o el manejo de API modernas. También hay una serie de buenas razones para aprender Python para
el comercio algorítmico, la principal de ellas el hecho de que algunas de las instituciones de compra y
venta más importantes hacen un uso intensivo de Python en sus operaciones comerciales y buscan
constantemente profesionales experimentados en Python.

Este libro se centra en la aplicación de Python a las diferentes disciplinas del comercio algorítmico,
como realizar pruebas retrospectivas de estrategias comerciales o interactuar con plataformas
comerciales en línea. No puede reemplazar una introducción completa al propio Python ni al comercio en general.
Sin embargo, combina sistemáticamente estos dos mundos fascinantes para proporcionar una valiosa
fuente para la generación de alfa en los competitivos mercados financieros y de criptomonedas
actuales.

Referencias y recursos adicionales


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

Black, Fischer y Myron Scholes. 1973. “La fijación de precios de opciones y pasivos corporativos”.
Revista de Economía Política 81 (3): 638­659.

Chan, Ernesto. 2013. Comercio algorítmico: estrategias ganadoras y su justificación.


Hoboken y otros: John Wiley & Sons.

Conclusiones | 15
Machine Translated by Google

Dorn, Anne, Daniel Dorn y Paul Sengmueller. 2008. “¿Por qué la gente comercia?”
Revista de Finanzas Aplicadas (otoño/invierno): 37­50.

Harvey, Campbell, Sandy Rattray, Andrew Sinclair y Otto Van Hemert. 2016.
"Hombre versus máquina: comparación del desempeño de los fondos de cobertura discrecionales
y sistemáticos". Libro blanco del Journal of Portfolio Management, Man Group.

Hilpisch, Yves. 2015. Análisis de derivados con Python: análisis de datos, modelos, simulación,
calibración y cobertura. Finanzas Wiley. Recursos en http://dawp.tpq.io.

. 2018. Python para finanzas: dominar las finanzas basadas en datos. 2da ed. Sebasto
Pol: O'Reilly. Recursos en https://py4fi.pqp.io.

. 2020. Inteligencia artificial en finanzas: una guía basada en Python. Sebastopol: O'Reilly.
Recursos en https://aiif.pqp.io.

Kissel, Robert. 2013. La ciencia del comercio algorítmico y la gestión de carteras.


Ámsterdam y otros: Elsevier/Academic Press.

Luis, Miguel. 2015. Flash Boys: descifrando el código del dinero. Nueva York, Londres: WW
Norton y compañía.

McKinney, Wes. 2017. Python para análisis de datos: gestión de datos con Pandas, NumPy e
IPython. 2da ed. Sebastopol: O'Reilly.

Merton, Roberto. 1973. “Teoría de la fijación de precios de opciones racionales”. Bell Journal de
Economía y Ciencias de la Gestión 4: 141­183.

Narang, Rishi. 2013. Dentro de la caja negra: una guía sencilla para el comercio cuantitativo y de
alta frecuencia. Hoboken y otros: John Wiley & Sons.

Salomón, Lewis y Louise Corso. 1991. “El impacto de la tecnología en la negociación de valores: el
mercado global emergente y las implicaciones para la regulación”.
Revisión de la ley de John Marshall 24 (2): 299­338.

Vander Plas, Jake. 2016. Manual de ciencia de datos de Python: herramientas esenciales para
trabajar con datos. Sebastopol: O'Reilly.

16 | Capítulo 1: Python y el comercio algorítmico


Machine Translated by Google

CAPITULO 2

Infraestructura de Python

Al construir una casa existe el problema de la selección de la madera.


Es fundamental que el objetivo del carpintero sea llevar un equipo que corte bien y, cuando tenga
tiempo, afilarlo.

—Miyamoto Musashi (El libro de los cinco anillos)

Para alguien nuevo en Python, la implementación de Python puede parecer casi sencilla.
Lo mismo se aplica a la gran cantidad de bibliotecas y paquetes que se pueden instalar opcionalmente.
En primer lugar, no existe un solo Python. Python viene en muchas versiones diferentes, como CPython,
Jython, IronPython o PyPy. Luego todavía existe la división entre Python 2.7 y el mundo 3.x. Este capítulo
se centra en CPython, la versión más popular del lenguaje de programación Python, y en la versión 3.8.

Incluso cuando nos centramos en CPython 3.8 (en adelante simplemente “Python”), la implementación
se dificulta por varias razones:

• El intérprete (una instalación estándar de CPython) sólo viene con el llamado


Biblioteca estándar (por ejemplo, que cubre funciones matemáticas típicas).

• Los paquetes opcionales de Python deben instalarse por separado y hay cientos
de ellos.

• Compilar (“construir”) estos paquetes no estándar por su cuenta puede ser complicado
debido a dependencias y requisitos específicos del sistema operativo.

• Cuidar dichas dependencias y la coherencia de la versión a lo largo del tiempo (mantenimiento).


finanzas) suele ser tedioso y requiere mucho tiempo.

• Las actualizaciones y mejoras para ciertos paquetes pueden causar la necesidad de volver a compilar
una multitud de otros paquetes.

17
Machine Translated by Google

• Cambiar o reemplazar un paquete puede causar problemas en (muchos) otros lugares. • Migrar de

una versión de Python a otra en algún momento posterior podría


ampliar todas las cuestiones anteriores.

Afortunadamente, existen herramientas y estrategias disponibles que ayudan con el problema de


implementación de Python. Este capítulo cubre los siguientes tipos de tecnologías que ayudan con la
implementación de Python:

Administrador de
paquetes Los administradores de paquetes como pip o conda ayudan con la instalación, actualización
y eliminación de paquetes de Python. También ayudan con la coherencia de las versiones de
diferentes paquetes.

Responsable del entorno virtual


Un administrador de entorno virtual como virtualenv o conda permite administrar múltiples instalaciones
de Python en paralelo (por ejemplo, tener una instalación de Python 2.7 y 3.8 en una sola máquina o
probar la versión de desarrollo más reciente de un elegante paquete de Python sin riesgo). .1

Los

contenedores Docker representan sistemas de archivos completos que contienen todas las piezas de
un sistema necesarias para ejecutar un determinado software, como código, tiempo de ejecución o
herramientas del sistema. Por ejemplo, puede ejecutar un sistema operativo Ubuntu 20.04 con una
instalación de Python 3.8 y los códigos Python respectivos en un contenedor Docker alojado en una
máquina que ejecuta Mac OS o Windows 10. Un entorno de contenedores de este tipo también se
puede implementar más adelante en el nube sin mayores cambios.

Instancia en la
nube La implementación de código Python para aplicaciones financieras generalmente requiere
alta disponibilidad, seguridad y rendimiento. Por lo general, estos requisitos solo pueden
cumplirse mediante el uso de infraestructura de almacenamiento y computación profesional que
hoy en día está disponible en condiciones atractivas en forma de instancias de nube desde
bastante pequeñas hasta realmente grandes y potentes. Una ventaja de una instancia en la
nube (servidor virtual) en comparación con un servidor dedicado alquilado a largo plazo es que
a los usuarios generalmente se les cobra solo por las horas de uso real. Otra ventaja es que
dichas instancias en la nube están disponibles literalmente en uno o dos minutos si es necesario,
lo que ayuda con un desarrollo ágil y escalabilidad.

La estructura de este capítulo es la siguiente. “Conda como administrador de paquetes” en la página 19


presenta conda como administrador de paquetes para Python. “Conda como entorno virtual

1 Un proyecto reciente llamado pipenv combina las capacidades del administrador de paquetes pip con las del
administrador de entorno virtual virtualenv. Consulte https://github.com/pypa/pipenv.

18 | Capítulo 2: Infraestructura de Python


Machine Translated by Google

Manager” en la página 27 se centra en las capacidades de Conda para la gestión de entornos


virtuales. “Uso de contenedores Docker” en la página 30 ofrece una breve descripción general de
Docker como tecnología de contenedorización y se centra en la creación de un contenedor basado
en Ubuntu con instalación de Python 3.8. “Uso de instancias de nube” en la página 36 muestra
cómo implementar Python y Jupyter Lab, un potente conjunto de herramientas basado en navegador
para el desarrollo y la implementación de Python en la nube.

El objetivo de este capítulo es tener una instalación adecuada de Python con las herramientas más
importantes, así como paquetes numéricos, de análisis de datos y visualización, disponibles en una
infraestructura profesional. Luego, esta combinación sirve como columna vertebral para implementar
e implementar los códigos Python en capítulos posteriores, ya sea código de análisis financiero
interactivo o código en forma de scripts y módulos.

Conda como administrador de paquetes

Aunque conda se puede instalar solo, una forma eficiente de hacerlo es a través de Miniconda, una
distribución mínima de Python que incluye conda como paquete y administrador de entorno virtual.

Instalación de Miniconda

Puede descargar las diferentes versiones de Miniconda en la página de Miniconda. A continuación


se asume la versión Python 3.8 de 64 bits, que está disponible para Linux, Windows y Mac OS. El
ejemplo principal en esta subsección es una sesión en un contenedor Docker basado en Ubuntu,
que descarga el instalador de Linux de 64 bits a través de wget y luego instala Miniconda. El código
que se muestra también debería funcionar (quizás con modificaciones menores) en cualquier otra
máquina basada en Linux o Mac OS:2

$ ventana acoplable ejecutar ­ti ­h pyalgo ­p 11111:11111 ubuntu:latest /bin/bash

root@pyalgo:/# apt­get update; apt­obtener actualización ­y


...
root@pyalgo:/# apt­get install ­y gcc wget
...
root@pyalgo:/# cd root
root@pyalgo:~# wget \ > https://
repo.anaconda.com/miniconda/Miniconda3­latest­Linux­x86_64.sh \ > ­O miniconda.sh

...
Solicitud HTTP enviada, esperando respuesta... 200 OK
Longitud: 93052469 (89M) [application/x­sh]
Guardando en: 'miniconda.sh'

2 En Windows, también puede ejecutar exactamente los mismos comandos en un contenedor Docker (consulte https://oreil.ly/GndRR).
Trabajar directamente en Windows requiere algunos ajustes. Consulte, por ejemplo, el libro de Matthias y Kane (2018) para obtener
más detalles sobre el uso de Docker.

Conda como administrador de paquetes | 19


Machine Translated by Google

miniconda.sh 100%[=============>] 88,74 millones 1,60 MB/s en 2m 15s

2020­08­25 11:01:54 (3,08 MB/s) ­ 'miniconda.sh' guardado [93052469/93052469]

root@pyalgo:~# bash miniconda.sh

Bienvenido a Miniconda3 py38_4.8.3

Para continuar con el proceso de instalación, revise el acuerdo de licencia.

Por favor, presione ENTER para continuar


>>>

Simplemente presionando la tecla ENTER se inicia el proceso de instalación. Después de revisar


el acuerdo de licencia, apruebe los términos respondiendo sí:

...
Última actualización 25 de febrero de 2020

¿Aceptas los términos de la licencia? [sí|no] [no] >>> sí

Miniconda3 ahora se instalará en esta ubicación: /root/miniconda3

­ Presione ENTER para confirmar la ubicación


­ Presione CTRL­C para cancelar la instalación.
­ O especifique una ubicación diferente a continuación

[/root/miniconda3] >>>
PREFIX=/root/miniconda3
Desempaquetando carga útil...
Recopilación de metadatos del paquete (current_repodata.json): hecho
Entorno de resolución: hecho

## Plan de paquete ##

ubicación del entorno: /root/miniconda3


...
pitón paquetes/main/linux­64::python­3.8.3­hcff3b4d_0
...
Preparando transacción: hecho
Ejecutando transacción: hecho instalación
finalizada.

Después de haber aceptado los términos de la licencia y haber confirmado la ubicación de


instalación, debe permitir que Miniconda anteponga la nueva ubicación de instalación de Miniconda al
variable de entorno PATH respondiendo sí una vez más:

¿Desea que el instalador inicialice Miniconda3 ejecutando conda


init? [sí|no] [no] >>> sí

20 | Capítulo 2: Infraestructura de Python


Machine Translated by Google

...
ningún cambio /root/miniconda3/etc/profile.d/conda.csh /root/.bashrc
modificado

==> Para que los cambios surtan efecto, cierre y vuelva a abrir su shell actual. <==

Si prefiere que el entorno base de conda no se active al inicio, establezca el parámetro


auto_activate_base en falso:

configuración de conda ­­set auto_activate_base falso

¡Gracias por instalar Miniconda3! raíz@pyalgo:~#

Después de eso, es posible que desees actualizar conda ya que el instalador de Miniconda en general no se
actualiza con tanta regularidad como el propio conda :

root@pyalgo:~# export PATH="/root/miniconda3/bin/:$PATH"


root@pyalgo:~# actualización de conda ­y conda
...
root@pyalgo:~# echo ". /root/miniconda3/etc/profile.d/conda.sh" >> ~/.bashrc root@pyalgo:~# bash
(base) root@pyalgo:~#

Después de este procedimiento de instalación bastante simple, ahora hay disponibles una instalación básica de
Python y conda . La instalación básica de Python ya viene con algunas buenas baterías incluidas, como el motor
de base de datos SQLite3 . Puedes probar si puedes iniciar Python en una nueva instancia de shell o después
de agregar la ruta relevante a la variable de entorno respectiva (como se hizo en el ejemplo anterior):

(base) root@pyalgo:~# python


Python 3.8.3 (predeterminado, 19 de mayo de 2020, 18:47:26)
[GCC 7.3.0] :: Anaconda, Inc. en Linux Escriba
"ayuda", "derechos de autor", "créditos" o "licencia" para obtener más información. >>>
print('Hola Python para el mundo del comercio algorítmico.')
Hola Python para el mundo del comercio algorítmico.
>>> salir()
(base) raíz@pyalgo:~#

Operaciones básicas con Conda conda se

puede utilizar para manejar eficientemente, entre otras cosas, la instalación, actualización y eliminación de
paquetes de Python. La siguiente lista proporciona una descripción general de las funciones principales:

Instalación de Python xx
conda install python=xx

Actualizando Python
conda actualizar Python

Conda como administrador de paquetes | 21


Machine Translated by Google

Instalación de un paquete

instalación de conda $PACKAGE_NAME

Actualizando un paquete

actualización de conda $PACKAGE_NAME

Quitar un paquete

conda eliminar $PACKAGE_NAME

Actualizando la propia conda

actualización de conda

Buscando paquetes

búsqueda de conda $SEARCH_TERM

Listado de paquetes instalados


lista de condas

Dadas estas capacidades, instalar, por ejemplo, NumPy (como uno de los más importantes
paquetes de la llamada pila científica) es un único comando. Cuando la instalación
La operación se realiza en una máquina con procesador Intel, el procedimiento se realiza automáticamente.
instala la biblioteca Intel Math Kernel mkl, que acelera las operaciones numéricas no
sólo para NumPy en máquinas Intel pero también para algunos otros paquetes científicos de Python:3

(base) root@pyalgo:~# conda instalar numpy


Recopilación de metadatos del paquete (current_repodata.json): hecho
Entorno de resolución: hecho

## Plan de paquete ##

ubicación del entorno: /root/miniconda3

especificaciones agregadas/actualizadas:

­ entumecido

Se descargarán los siguientes paquetes:

compilación del paquete |


­­­­­­­­­­­­­­­­­­­­­­|­­­­­­­­­­­­­­­­­
blas­1.0 mkl 6 KB
intel­openmp­2020.1 | | 217 | 217 | py38he904b0f_0 780KB
mkl­2020.1 | py38h23d657b_0 129,0MB
mkl­service­2.3.0 62KB
mkl_fft­1.1.0 150KB

3 La instalación del metapaquete nomkl, como en conda install numpy nomkl, evita la instalación automática
y uso de mkl y otros paquetes relacionados.

22 | Capítulo 2: Infraestructura de Python


Machine Translated by Google

mkl_random­1.1.1 | py38h0573a6f_0 | 341KB


numpy­1.19.1 py38hbc911f0_0 | 21KB
numpy­base­1.19.1 py38hfa32c7d_0 4,2MB
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ ­­­­­­­­­­

Total: 134,5MB

Se INSTALARÁN los siguientes paquetes NUEVOS:

blas paquetes/main/linux­64::blas­1.0­mkl
intel­openmp mkl paquetes/main/linux­64::intel­openmp­2020.1­217
mkl­ paquetes/main/linux­64::mkl­2020.1­217
service mkl_fft paquetes/main/linux­64::mkl­service­2.3.0­py38he904b0f_0
mkl_random paquetes/main/linux­64::mkl_fft­1.1.0­py38h23d657b_0
numpy numpy­ paquetes/main/linux­64::mkl_random­1.1.1­py38h0573a6f_0
base paquetes/main/linux­64::numpy­1.19.1­py38hbc911f0_0
paquetes/main/linux­64::numpy­base­1.19.1­py38hfa32c7d_0

¿Continuar ([sí]/n)? y

Descarga y extracción de paquetes


base­numpy­1.19.1 | 4,2 MB | ############################## | 100%
blas­1.0 | 6 KB | ############################## | 100%
mkl_fft­1.1.0 | 150 KB | ############################## | 100%
mkl­servicio­2.3.0 | 62 KB | ############################## | 100%
numpy­1.19.1 | 21 KB | ############################## | 100%
mkl­2020.1 | 129,0 MB | ############################## | 100%
mkl_random­1.1.1 | 341 KB | ############################## | 100%
Intel­openmp­2020.1 | 780 KB | ############################## | 100%
Preparando transacción: hecho
Verificando transacción: hecho
Ejecutando transacción: hecho
(base) raíz@pyalgo:~#

Conda como administrador de paquetes | 23


Machine Translated by Google

También se pueden instalar varios paquetes a la vez. El indicador ­y indica que todos (potencialmente)
cial) las preguntas se responderán con sí:

(base) root@pyalgo:~# conda install ­y ipython matplotlib pandas \


> pytables scikit­aprende scipy
...
Recopilación de metadatos del paquete (current_repodata.json): hecho
Entorno de resolución: hecho

## Plan de paquete ##

ubicación del entorno: /root/miniconda3

especificaciones agregadas/actualizadas:

­ipython
­matplotlib
­ pandas
­ pytables
­ scikit­aprende
­ picante

Se descargarán los siguientes paquetes:

compilación del paquete |


­­­­­­­­­­­­­­­­­­­­­­|­­­­­­­­­­­­­­­­­
devolución de llamada­0.2.0 py_0 | 15 KB
...
zstd­1.4.5 | h9ceee32_0 619KB
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ ­­­­­­­­­­

Total: 144,9MB

Se INSTALARÁN los siguientes paquetes NUEVOS:

devolución de llamada paquetes/main/noarch::backcall­0.2.0­py_0


bloque paquetes/main/linux­64::blosc­1.20.0­hd408876_0
...
zstd paquetes/main/linux­64::zstd­1.4.5­h9ceee32_0

Descarga y extracción de paquetes


glib­2.65.0 | 2,9MB | ############################## | 100%
...
snappy­1.1.8 | 40 KB | ############################## | 100%
Preparando transacción: hecho
Verificando transacción: hecho
Ejecutando transacción: hecho
(base) raíz@pyalgo:~#

24 | Capítulo 2: Infraestructura de Python


Machine Translated by Google

Tras el procedimiento de instalación resultante, además de las estándar, están disponibles algunas de las
bibliotecas más importantes para análisis financiero:

IPython
Un shell Python interactivo mejorado

matplotlib
La biblioteca de trazado estándar para Python

NumPy
Manejo eficiente de matrices numéricas.

pandas
Gestión de datos tabulares, como datos de series de tiempo financieras.

PyTables
Un contenedor de Python para la biblioteca HDF5

aprendizaje­scikit

Un paquete para aprendizaje automático y tareas relacionadas.

ciencia ficción

Una colección de clases y funciones científicas.

Esto proporciona un conjunto de herramientas básico para el análisis de datos en general y el análisis
financiero en particular. El siguiente ejemplo usa IPython y dibuja un conjunto de números pseudoaleatorios
con NumPy:

(base) root@pyalgo:~# ipython


Python 3.8.3 (predeterminado, 19 de mayo de 2020, 18:47:26)
Escriba 'copyright', 'créditos' o 'licencia' para obtener más información IPython
7.16.1 : un Python interactivo mejorado. Tipo '?' por ayuda.

En [1]: importar numpy como np

En [2]: np.random.seed(100)

En [3]: np.random.standard_normal((5, 4))


Fuera[3]:
, 1.1530358
matriz([[­1.74976547, 0.3426804 , ­0.25243604], [ 0.98132079, 0.51421884,
0.22117967 , ­1.07004333], [­0.18949583 , 0.25500144, ­0.4
5802699, 0,43516349], [­0,58359505, 0,81684707, 0,67272081, ­
0,10441114], [­0,53128038, 1,02973269, ­0,43813562, ­1,11831825]])

En [4]: salir
(base) root@pyalgo:~#

Conda como administrador de paquetes | 25


Machine Translated by Google

La ejecución de la lista conda muestra qué paquetes están instalados:

(base) root@pyalgo:~# lista de conda


# paquetes en el entorno en /root/miniconda3:
#
# Nombre Versión Construir canal
_libgcc_mutex 0.1 principal

retrollamada 0.2.0 py_0


blas 1.0 mkl
blosc 1.20.0 hd408876_0
...
zlib 1.2.11 h7b6447c_3
zstd 1.4.5 h9ceee32_0
(base) raíz@pyalgo:~#

En caso de que un paquete ya no sea necesario, se elimina eficientemente con conda remove:

(base) root@pyalgo:~# conda eliminar matplotlib


Recopilación de metadatos del paquete (repodata.json): hecho
Entorno de resolución: hecho

## Plan de paquete ##

ubicación del entorno: /root/miniconda3

especificaciones eliminadas:

­matplotlib

Los siguientes paquetes serán ELIMINADOS:

Los siguientes paquetes serán ELIMINADOS:

ciclador­0.10.0­py38_0
...
tornado­6.0.4­py38h7b6447c_1

¿Continuar ([sí]/n)? y

Preparando transacción: hecho


Verificando transacción: hecho
Ejecutando transacción: hecho
(base) raíz@pyalgo:~#

conda como administrador de paquetes ya es bastante útil. Sin embargo, su potencia total sólo
se vuelve evidente cuando se agrega la gestión del entorno virtual a la mezcla.

26 | Capítulo 2: Infraestructura de Python


Machine Translated by Google

conda como administrador de paquetes hace que la instalación, actualización


y eliminación de paquetes de Python sea una experiencia agradable. No es
necesario encargarse de construir y compilar paquetes por su cuenta, lo
que a veces puede ser complicado dada la lista de dependencias que
especifica un paquete y los detalles específicos que se deben considerar
en diferentes sistemas operativos.

Conda como gestor de entorno virtual


Tener instalado Miniconda con conda incluido proporciona una instalación predeterminada de Python
dependiendo de la versión de Miniconda que se haya elegido. Las capacidades de gestión del entorno
virtual de conda permiten, por ejemplo, agregar a una instalación predeterminada de Python 3.8 una
instalación completamente separada de Python 2.7.x. Para ello, conda ofrece la siguiente funcionalidad:

Creando un entorno virtual conda


create ­­name $ENVIRONMENT_NAME

Activando un entorno conda


activar $ENVIRONMENT_NAME

Desactivando un entorno conda


desactivar $ENVIRONMENT_NAME

Eliminar un entorno
conda env eliminar ­­nombre $ENVIRONMENT_NAME

Exportar a un archivo de entorno


Exportación de entorno conda > $FILE_NAME

Crear un entorno a partir de un archivo


conda entorno crear ­f $FILE_NAME

Listado de todos los entornos


información de conda ­­envs

A modo de ilustración sencilla, el código de ejemplo que sigue crea un entorno llamado py27, instala IPython
y ejecuta una línea de código Python 2.7.x. Aunque el soporte para Python 2.7 ha finalizado, el ejemplo
ilustra cómo se puede ejecutar y probar fácilmente el código Python 2.7 heredado:

(base) root@pyalgo:~# conda create ­­name py27 python=2.7


Recopilación de metadatos del paquete (current_repodata.json):
terminado Solución del entorno: falló con repodata de current_repodata.json, lo volveré
a intentar con la siguiente fuente de repodata.
Recopilación de metadatos del paquete (repodata.json):
hecho Entorno de resolución: hecho

Conda como gestor de entorno virtual | 27


Machine Translated by Google

## Plan de paquete ##

ubicación del entorno: /root/miniconda3/envs/py27

especificaciones agregadas/actualizadas:

­ pitón=2.7

Se descargarán los siguientes paquetes:

compilación del paquete |


­­­­­­­­­­­­­­­­­­­­­­|­­­­­­­­­­­­­­­­­
certificado­2019.11.28 py27_0 py27_0 h15b4118_1 py27_0 py27_0 |
153KB
pip­19.3.1 |
1,7 megas

python­2.7.18 |
9,9MB
herramientas de |
512KB
configuración­44.0.0 rueda­0.33.6 |
42KB
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ ­­­­­­­­­­

Total: 12,2MB

Se INSTALARÁN los siguientes paquetes NUEVOS:

_libgcc_mutex paquetes/main/linux­64::_libgcc_mutex­0.1­main
certificados ca paquetes/main/linux­64::ca­certificates­2020.6.24­0
...
zlib paquetes/main/linux­64::zlib­1.2.11­h7b6447c_3

¿Continuar ([sí]/n)? y

Descarga y extracción de paquetes


certificado­2019.11.28 | 153 KB pitón­2.7.18 | ############################### | 100%
| 9,9 MB pip­19.3.1 | 1,7 MB herramientas | ############################### | 100%
de configuración­44.0.0 | 512 KB | ############################### | 100%
rueda­0.33.6 | 42 KB Preparando | ############################### | 100%
transacción: hecho | ############################### | 100%

Verificando transacción: hecho


Ejecutando transacción: hecho
#
# Para activar este entorno, utilice
#
# $ conda activar py27
#
# Para desactivar un entorno activo, utilice
#
# $ conda desactivar

(base) raíz@pyalgo:~#

28 | Capítulo 2: Infraestructura de Python


Machine Translated by Google

Observe cómo el mensaje cambia para incluir (py27) después de activar el entorno:

(base) root@pyalgo:~# conda enable py27 (py27)


root@pyalgo:~# pip install ipython DEPRECACIÓN: Python
2.7 llegará al final de su vida útil el 1 de enero de 2020.
...
Ejecutando transacción: hecho (py27)
root@pyalgo:~#

Finalmente, esto permite usar IPython con la sintaxis de Python 2.7:

(py27) root@pyalgo:~# ipython Python


2.7.18 |Anaconda, Inc.| (predeterminado, 23 de abril de 2020, 22:42:48)
Escriba "copyright", "créditos" o "licencia" para obtener más información.

IPython 5.10.0: un Python interactivo mejorado.


­> Introducción y descripción general de las características de IPython. ?
%quickref ­> Referencia rápida. ayuda ­>
Sistema de ayuda propio de Python. ¿objeto? ­> Detalles
sobre 'objeto', utilice '¿objeto?' para detalles adicionales.

En [1]: imprima "Hola Python para el mundo del comercio algorítmico".


Hola Python para el mundo del comercio algorítmico.

En [2]: salir (py27)


root@pyalgo:~#

Como lo demuestra este ejemplo, conda como administrador de entorno virtual permite instalar
diferentes versiones de Python una al lado de la otra. También permite instalar diferentes versiones
de ciertos paquetes. La instalación predeterminada de Python no se ve influenciada por dicho
procedimiento, ni tampoco otros entornos que puedan existir en la misma máquina. Todos los
entornos disponibles se pueden mostrar a través de conda info ­­envs:

(py27) root@pyalgo:~# lista de entornos de conda #


entornos de conda:

# base /root/miniconda3 * /root/


py27 miniconda3/envs/py27

(py27) raíz@pyalgo:~#

A veces es necesario compartir información del entorno con otros o utilizar información del
entorno en varias máquinas, por ejemplo. Para este fin, se puede exportar la lista de paquetes
instalados a un archivo con conda env export. Sin embargo, esto sólo funciona correctamente de
forma predeterminada para el mismo sistema operativo, ya que las versiones de compilación se
especifican en el archivo yaml resultante . Sin embargo, se pueden eliminar para especificar solo
la versión del paquete mediante el indicador ­­no­builds :

(py27) root@pyalgo:~# conda desactivar (base)


root@pyalgo:~# conda env export ­­no­builds > base.yml (base) root@pyalgo:~# cat base.yml
nombre: base

Conda como gestor de entorno virtual | 29


Machine Translated by Google

canales: ­
dependencias
predeterminadas:
­ _libgcc_mutex=0.1 ­
backcall=0.2.0 ­
blas=1.0 ­
blosc=1.20.0
...
­zlib=1.2.11
­zstd=1.4.5
prefijo: /root/miniconda3
(base) root@pyalgo:~#

A menudo, los entornos virtuales, que técnicamente no son mucho más que una determinada estructura
de (sub)carpetas, se crean para realizar algunas pruebas rápidas.4 En tal caso, un entorno se elimina
fácilmente (después de la desactivación) mediante conda env eliminar:

(base) root@pyalgo:~# conda env remove ­n py27

Elimine todos los paquetes en el entorno /root/miniconda3/envs/py27:

(base) raíz@pyalgo:~#

Con esto concluye la descripción general de conda como administrador de entorno virtual.

conda no solo ayuda a administrar paquetes, sino que también es


un administrador de entorno virtual para Python. Simplifica la
creación de diferentes entornos Python, permitiendo tener
múltiples versiones de Python y paquetes opcionales disponibles
en la misma máquina sin que se influyan entre sí de ninguna
manera. conda también permite exportar información del entorno
para replicarla fácilmente en varias máquinas o compartirla con otros.

Usando contenedores Docker


Los contenedores Docker han arrasado en el mundo de la TI (consulte Docker). Aunque la tecnología es todavía
relativamente joven, se ha establecido como uno de los puntos de referencia para el desarrollo e implementación
eficiente de casi cualquier tipo de aplicación de software.

Para nuestros propósitos, basta con pensar en un contenedor Docker como un sistema de archivos
separado (“en contenedores”) que incluye un sistema operativo (por ejemplo, Ubuntu 20.04 LTS para
servidor), un tiempo de ejecución (Python), un sistema adicional y herramientas de desarrollo. herramientas, y

4 En la documentación oficial encontrará la siguiente explicación: “Los entornos virtuales de Python permiten
Los paquetes de Python se instalarán en una ubicación aislada para una aplicación en particular, en lugar de instalarse
globalmente”. Consulte la página Creación de entornos virtuales.

30 | Capítulo 2: Infraestructura de Python


Machine Translated by Google

más bibliotecas y paquetes (Python) según sea necesario. Un contenedor Docker de este tipo podría
ejecutarse en una máquina local con Windows 10 Professional de 64 bits o en una instancia en la
nube con un sistema operativo Linux, por ejemplo.

Esta sección profundiza en los interesantes detalles de los contenedores Docker. Es una ilustración
concisa de lo que la tecnología Docker puede hacer en el contexto de la implementación de Python.5

Imágenes y contenedores de Docker Antes

de pasar a la ilustración, es necesario distinguir dos términos fundamentales cuando se habla de


Docker. La primera es una imagen de Docker, que se puede comparar con una clase de Python. El
segundo es un contenedor Docker, que se puede comparar con una instancia de la clase Python
respectiva.

A un nivel más técnico, encontrarás la siguiente definición de imagen Docker en el glosario de Docker:

Las imágenes de Docker son la base de los contenedores. Una imagen es una colección ordenada de cambios en
el sistema de archivos raíz y los parámetros de ejecución correspondientes para su uso dentro de un tiempo de
ejecución de contenedor. Una imagen normalmente contiene una unión de sistemas de archivos en capas apilados
uno encima del otro. Una imagen no tiene estado y nunca cambia.

De manera similar, encontrará la siguiente definición para un contenedor Docker en el glosario de


Docker, lo que hace transparente la analogía con las clases de Python y las instancias de dichas clases:

Un contenedor es una instancia en tiempo de ejecución de una imagen de Docker.

Un contenedor Docker consta de

• Una imagen de Docker

• Un entorno de ejecución

• Un conjunto estándar de instrucciones

El concepto está tomado de los contenedores de envío, que definen un estándar para enviar mercancías a nivel
mundial. Docker define un estándar para enviar software.

Dependiendo del sistema operativo, la instalación de Docker es algo diferente.


Es por eso que esta sección no entra en los detalles respectivos. Más información y enlaces adicionales
se encuentran en la página Obtener Docker.

Creación de una imagen de Docker de Ubuntu y Python Esta

subsección ilustra la creación de una imagen de Docker basada en la última versión de Ubuntu que
incluye Miniconda, así como algunos paquetes importantes de Python. En

5 Véase Matthias y Kane (2018) para obtener una introducción completa a la tecnología Docker.

Uso de contenedores Docker | 31


Machine Translated by Google

Además, realiza algunas tareas domésticas de Linux actualizando el índice de paquetes de Linux,
actualizando los paquetes si es necesario e instalando ciertas herramientas adicionales del sistema.
Para ello se necesitan dos guiones. Uno es un script Bash que hace todo el trabajo a nivel de Linux.6
El otro es el llamado Dockerfile, que controla el procedimiento de construcción de la imagen misma.

El script Bash del Ejemplo 2­1, que realiza la instalación, consta de tres partes principales. La
primera parte se ocupa de la limpieza de Linux. La segunda parte instala Miniconda, mientras que la
tercera parte instala paquetes Python opcionales. También hay comentarios más detallados en línea:

Ejemplo 2­1. Script que instala Python y paquetes opcionales

#!/bin/bash#

# Script para instalar


# Herramientas del sistema Linux y
# Componentes básicos de Python #

# Python para el comercio algorítmico # (c) Dr. Yves J.


Hilpisch # The Python Quants GmbH #

#LINUX GENERAL

apt­get update # actualiza el caché del índice de paquetes apt­get Upgrade ­y #


actualiza paquetes # instala herramientas del sistema apt­get
install ­y bzip2 gcc git # herramientas
del sistema apt­get install ­y htop screen vim wget # herramientas del sistema apt­
get update ­y bash # actualiza bash si es necesario apt­get clean # limpia el caché del índice
del paquete

# INSTALAR MINICONDA #
descargas Miniconda wget https://

repo.anaconda.com/miniconda/Miniconda3­latest­Linux­x86_64.sh ­O \
Miniconda.sh
bash Miniconda.sh ­b # lo instala rm ­rf Miniconda.sh #
elimina el instalador export PATH="/root/miniconda3/bin:$PATH" # antepone

la nueva ruta

# INSTALAR BIBLIOTECAS PYTHON

conda install ­y pandas # instala pandas conda install ­y ipython # instala


IPython shell

# PERSONALIZACIÓN

6 Consulte el libro de Robbins (2016) para obtener una introducción concisa y una descripción general rápida de las secuencias de comandos Bash .
Véase también ver GNU Bash.

32 | Capítulo 2: Infraestructura de Python


Machine Translated by Google

cd /root/ wget
http://hilpisch.com/.vimrc # Configuración de Vim

El Dockerfile del Ejemplo 2­2 utiliza el script Bash del Ejemplo 2­1 para crear una nueva imagen de
Docker. También tiene sus partes principales comentadas en línea:

Ejemplo 2­2. Dockerfile para construir la imagen

#
# Construyendo una imagen de Docker con
# la última versión de Ubuntu y
# Instalación básica de Python #

# Python para el comercio algorítmico # (c) Dr. Yves J.


Hilpisch # The Python Quants GmbH #

# última versión de Ubuntu


DESDE ubuntu:último

# información sobre el mantenedor


MANTENEDOR yves

# agregar el script bash ADD


install.sh / # cambiar los
derechos del script RUN chmod u+x /install.sh
# ejecutar el script bash RUN /
install.sh # anteponer la nueva
ruta ENV PATH /root/
miniconda3/bin:$ CAMINO

# ejecutar IPython cuando se ejecuta el contenedor


CMD ["ipython"]

Si estos dos archivos están en una sola carpeta y Docker está instalado, entonces la creación de la
nueva imagen de Docker es sencilla. Aquí, la etiqueta pyalgo:basic se utiliza para la imagen. Esta
etiqueta es necesaria para hacer referencia a la imagen, por ejemplo, cuando se ejecuta un
contenedor basado en ella:

(base) pro:Docker yves$ docker build ­t pyalgo:basic.


Envío de contexto de compilación al demonio Docker 4.096kB Paso
1/7: DESDE ubuntu:latest ­­­> 4e2eef94cd6b

Paso 2/7 : MANTENEDOR yves


­­­> Ejecutando en 859db5550d82
Extracción del contenedor intermedio 859db5550d82
­­­> 40adf11b689f Paso
3/7: AGREGAR install.sh / ­­­>
34cd9dc267e0

Uso de contenedores Docker | 33


Machine Translated by Google

Paso 4/7: EJECUTAR chmod u+x /install.sh


­­­> Ejecutando en 08ce2f46541b
Extracción del contenedor intermedio 08ce2f46541b
­­­> 88c0adc82cb0
Paso 5/7: EJECUTAR /install.sh
­­­> Ejecutando en 112e70510c5b
...
Extracción del contenedor intermedio 112e70510c5b
­­­> 314dc8ec5b48
Paso 6/7: RUTA ENV /root/miniconda3/bin:$RUTA
­­­> Ejecutando en 82497aea20bd
Extracción del contenedor intermedio 82497aea20bd
­­­> 5364f494f4b4
Paso 7/7: CMD ["ipython"]
­­­> Ejecutando en ff434d5a3c1b
Extracción del contenedor intermedio ff434d5a3c1b
­­­> a0bb86daf9ad
A0bb86daf9ad construido con éxito
Pyalgo etiquetado con éxito: básico
(base) pro:Docker yves$

Las imágenes de Docker existentes se pueden enumerar mediante imágenes de Docker. La nueva imagen debe ser
en la parte superior de la lista:

(base) pro:Docker yves$ imágenes acoplables


REPOSITORIO ETIQUETA ID DE CREADO TAMAÑO

IMAGEN pyalgo basic a0bb86daf9ad ubuntu último 4e2eef94cd6b (base) pro:Docker Hace 2 minutos 1,79GB
yves$ Hace 5 días 73,9MB

Habiendo construido la imagen pyalgo:basic con éxito, uno puede ejecutar una respectiva
Contenedor acoplable con ejecución acoplable. La combinación de parámetros ­ti es necesaria para
procesos interactivos que se ejecutan dentro de un contenedor Docker, como un proceso de shell de

IPython (consulte la página de referencia de ejecución de Docker):

(base) pro:Docker yves$ docker run ­ti pyalgo:basic


Python 3.8.3 (predeterminado, 19 de mayo de 2020, 18:47:26)
Escriba 'copyright', 'créditos' o 'licencia' para obtener más información
IPython 7.16.1: Python interactivo mejorado. Tipo '?' por ayuda.

En [1]: importar numpy como np

En [2]: np.random.seed(100)

En [3]: a = np.random.standard_normal((5, 3))

En [4]: importar pandas como pd

En [5]: df = pd.DataFrame(a, columnas=['a', 'b', 'c'])

En [6]: gl
Fuera[6]:

34 | Capítulo 2: Infraestructura de Python


Machine Translated by Google

a b C
0­1,749765 0,342680 1,153036
1 ­0,252436 0,981321 0,514219
2 0,221180 ­1,070043 ­0,189496
3 0,255001 ­0,458027 0,435163
4­0,583595 0,816847 0,672721

Al salir de IPython, también se saldrá del contenedor, ya que es la única aplicación en ejecución.
dentro del contenedor. Sin embargo, puede separarse de un contenedor mediante lo siguiente:

Ctrl+p ­­> Ctrl+q

Después de haberse desconectado del contenedor, el comando docker ps muestra la ejecución


ning contenedor (y tal vez otros contenedores actualmente en ejecución):

(base) pro:Docker yves$ docker ps


IMAGEN DE IDENTIFICACIÓN DEL CONTENEDOR COMANDO CREADO ... NOMBRES

e93c4cbd8ea8 pyalgo:basic "ipython" Hace aproximadamente un minuto jolly_rubin


(base) pro:Docker yves$

La conexión al contenedor Docker se realiza mediante Docker adjuntar $CONTENEDOR_ID.


Tenga en cuenta que unas pocas letras del ID DEL CONTENEDOR son suficientes:

(base) pro:Docker yves$ docker adjuntar e93c


En [7]: df.info()
<clase 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entradas, 0 a 4
Columnas de datos (un total de 3 columnas):

# Columna Tipo D de recuento no nulo


­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 un 5 no nulo flotador64
1 b 5 no nulos 2 c 5 tipos no nulos: flotador64
float64(3) flotador64

uso de memoria: 248,0 bytes

El comando de salida finaliza IPython y con ello detiene el contenedor Docker, como
Bueno. Se puede eliminar mediante Docker rm:

En [8]: salir
(base) pro:Docker yves$ docker rm e93c
e93c
(base) pro:Docker yves$

De manera similar, la imagen de Docker pyalgo:basic se puede eliminar a través de Docker rmi si no
necesita más. Si bien los contenedores son relativamente livianos, las imágenes individuales pueden
consumir bastante espacio de almacenamiento. En el caso de la imagen pyalgo:basic , el tamaño es
cerca de 2 GB. Es por eso que es posible que desees limpiar periódicamente la lista de Docker.
imágenes:

(base) pro:Docker yves$ docker rmi a0bb86


Sin etiqueta: pyalgo:básico
Eliminado: sha256:a0bb86daf9adfd0ddf65312ce6c1b068100448152f2ced5d0b9b5adef5788d88

Uso de contenedores Docker | 35


Machine Translated by Google

...
Eliminado: sha256:40adf11b689fc778297c36d4b232c59fedda8c631b4271672cc86f505710502d (base) pro:Docker yves$

Por supuesto, hay mucho más que decir sobre los contenedores Docker y sus beneficios en ciertos
escenarios de aplicaciones. Para los propósitos de este libro, proporcionan un enfoque moderno para
implementar Python, realizar el desarrollo de Python en un entorno completamente separado (en
contenedores) y enviar códigos para el comercio algorítmico.

Si aún no utiliza contenedores Docker, debería considerar comenzar a


utilizarlos. Proporcionan una serie de beneficios cuando se trata de
esfuerzos de implementación y desarrollo de Python, no solo cuando se
trabaja localmente sino también, en particular, cuando se trabaja con
instancias remotas en la nube y servidores que implementan código para
el comercio algorítmico.

Usar instancias en la nube


Esta sección muestra cómo configurar una infraestructura Python completa en una instancia de nube
de DigitalOcean . Existen muchos otros proveedores de nube, entre ellos Amazon Web Services (AWS)
como proveedor líder. Sin embargo, DigitalOcean es bien conocido por su simplicidad y tarifas
relativamente bajas para instancias de nube más pequeñas, a las que llama Droplet. El Droplet más
pequeño, que generalmente es suficiente para fines de exploración y desarrollo, solo cuesta 5 USD al
mes o 0,007 USD por hora.
El uso se cobra por hora, de modo que uno puede (por ejemplo) hacer girar fácilmente una Droplet
durante dos horas, destruirla y cobrar solo 0,014 USD.7

El objetivo de esta sección es configurar un Droplet en DigitalOcean que tenga una instalación de
Python 3.8 más los paquetes típicamente necesarios (como NumPy y pandas) en combinación con un
Jupyter Lab protegido con contraseña y cifrado con Secure Sockets Layer (SSL). instalación del
servidor.8 Como conjunto de herramientas basado en web, Jupyter Lab proporciona varias herramientas
que se pueden utilizar a través de un navegador normal:

Jupyter Notebook
Este es uno de los entornos de desarrollo interactivos basados en navegador más populares (si
no el más popular) que presenta una selección de diferentes núcleos de lenguaje como Python, R
y Julia.

7 Para aquellos que aún no tienen una cuenta con un proveedor de nube, en http://bit.ly/do_sign_up, los nuevos usuarios obtienen una
Crédito inicial de 10 USD para DigitalOcean.

8 Técnicamente, Jupyter Lab es una extensión de Jupyter Notebook. Sin embargo, ambas expresiones son a veces
usado indistintamente.

36 | Capítulo 2: Infraestructura de Python


Machine Translated by Google

Consola Python
Esta es una consola basada en IPython que tiene una interfaz gráfica de usuario diferente de la apariencia
de la implementación estándar basada en terminal.

Terminal

Esta es una implementación de shell del sistema accesible a través del navegador que permite no solo
todas las tareas típicas de administración del sistema, sino también el uso de herramientas útiles como
Vim para edición de código o git para control de versiones.

Editor

Otra herramienta importante es un editor de archivos de texto basado en navegador con resaltado de
sintaxis para muchos lenguajes de programación y tipos de archivos diferentes, así como capacidades
típicas de edición de texto/código.

Administrador
de archivos Jupyter Lab también proporciona un administrador de archivos completo que permite
operaciones típicas de archivos, como cargar, descargar y cambiar el nombre.

Tener Jupyter Lab instalado en un Droplet permite realizar el desarrollo y la implementación de Python a través
del navegador, evitando la necesidad de iniciar sesión en la instancia de la nube a través del acceso Secure
Shell (SSH).

Para lograr el objetivo de esta sección, se necesitan varios scripts:

Script de configuración
del servidor Este script organiza todos los pasos necesarios, como copiar otros archivos al Droplet y
ejecutarlos en el Droplet.

Script de instalación de Python y Jupyter


Este script instala Python, paquetes adicionales, Jupyter Lab e inicia el servidor de Jupyter Lab .

Archivo de configuración de Jupyter Notebook


Este archivo es para la configuración del servidor de Jupyter Lab , por ejemplo, con respecto a la
protección con contraseña.

Archivos de clave pública y privada RSA


Estos dos archivos son necesarios para el cifrado SSL de la comunicación con el servidor de Jupyter Lab .

La siguiente sección trabaja hacia atrás a través de esta lista de archivos, ya que aunque el script de instalación
se ejecuta primero, los demás archivos deben haberse creado previamente.

Uso de instancias en la nube | 37


Machine Translated by Google

Claves públicas y privadas RSA


Para lograr una conexión segura al servidor de Jupyter Lab a través de un navegador arbitrario, se
necesita un certificado SSL que consta de claves públicas y privadas RSA (consulte la página de
Wikipedia de RSA) . En general, se esperaría que un certificado de este tipo procediera de la
denominada Autoridad de certificación (CA). Sin embargo, para los propósitos de este libro, un
certificado autogenerado es
“suficientemente bueno”. 9 Una herramienta popular para generar pares de claves RSA es OpenSSL. La
breve sesión interactiva a continuación genera un certificado apropiado para usar con un servidor de
Jupyter Lab (consulte los documentos de Jupyter Notebook):

(base) pro:cloud yves$ openssl req ­x509 ­nodes ­days 365 ­newkey rsa:2048 \ > ­keyout mykey.key
­out mycert.pem Generando una clave privada
RSA

.......+++++
.....+++++
+++++
escribiendo una nueva clave privada en 'mykey.key'
­­­­­
Está a punto de que se le solicite que ingrese información que se incorporará en su solicitud de
certificado.
Lo que está a punto de ingresar es lo que se llama un Nombre Distinguido o DN.
Hay bastantes campos pero puedes dejar algunos en blanco.
Para algunos campos habrá un valor predeterminado. Si
ingresa '.', el campo quedará en blanco.
­­­­­
Nombre del país (código de 2 letras) [AU]:DE
Nombre del estado o provincia (nombre completo) [Algún estado]:Saarland
Nombre de la localidad (p. ej., ciudad) []:Voelklingen
Nombre de la organización (p. ej., empresa) [Internet Widgits Pty Ltd ]:Nombre de la unidad
organizativa de TPQ GmbH (p. ej., sección) []:Nombre común de comercio algorítmico
(p. ej., FQDN del servidor o SU nombre) []:Dirección de correo electrónico de
Jupyter Lab []:[email protected] (base)
pro:cloud yves $

Los dos archivos mykey.key y mycert.pem deben copiarse en el Droplet y el archivo de configuración de
Jupyter Notebook debe hacer referencia a ellos. Este archivo se presenta proximamente.

Archivo de configuración del portátil Jupyter

Se puede implementar de forma segura un servidor público de Jupyter Lab , como se explica en los
documentos de Jupyter Notebook. Entre otras cosas, Jupyter Lab estará protegido con contraseña. Para
este fin, hay disponible una función de generación de código hash de contraseña llamada passwd()

9 Con un certificado autogenerado de este tipo, es posible que deba agregar una excepción de seguridad cuando se lo solicite el

navegador. En Mac OS, incluso puedes registrar explícitamente el certificado como confiable.

38 | Capítulo 2: Infraestructura de Python


Machine Translated by Google

en el subpaquete notebook.auth . El siguiente código genera un código hash de contraseña donde jupyter
es la contraseña misma:

En [1]: desde notebook.auth importar contraseña

En [2]: contraseña('jupyter')
Fuera[2]: 'sha1:da3a3dfc0445:052235bb76e56450b38d27e41a85a136c3bf9cd7'

En [3]: salir

Este código hash debe colocarse en el archivo de configuración de Jupyter Notebook como se presenta
en el Ejemplo 2­3. El archivo de configuración supone que los archivos de clave RSA se han copiado en
el Droplet a la carpeta /root/.jupyter/ .

Ejemplo 2­3. Archivo de configuración de Jupyter Notebook

#
# Archivo de configuración de Jupyter Notebook #

# Python para el comercio algorítmico # (c) Dr. Yves J.


Hilpisch # The Python Quants GmbH #

# CIFRADO SSL
# reemplace los siguientes nombres de archivos (y archivos utilizados) por los archivos/su elección
c.NotebookApp.certfile = u'/root/.jupyter/mycert.pem' c.NotebookApp.keyfile = u'/
root/.jupyter/mykey. llave'

# DIRECCIÓN IP Y PUERTO
# establezca ip en '*' para vincular todas las direcciones IP de la instancia de nube c.NotebookApp.ip =
'0.0.0.0' # es una buena idea configurar un
puerto predeterminado fijo y conocido para el acceso al servidor c.NotebookApp.port = 8888

# PROTECCIÓN DE CONTRASEÑA

# aquí: 'jupyter' como contraseña # reemplaza


el código hash con el de tu contraseña c.NotebookApp.password = \

'sha1:da3a3dfc0445:052235bb76e56450b38d27e41a85a136c3bf9cd7'

# SIN OPCIÓN DE NAVEGADOR

# evitar que Jupyter intente abrir un navegador c.NotebookApp.open_browser


= False

# ACCESO RAÍZ
# permitir que Jupyter se ejecute desde el usuario root
c.NotebookApp.allow_root = True

El siguiente paso es asegurarse de que Python y Jupyter Lab estén instalados en el Droplet.

Uso de instancias en la nube | 39


Machine Translated by Google

La implementación de Jupyter Lab en la nube genera una serie de problemas de


seguridad, ya que es un entorno de desarrollo completo al que se puede acceder
a través de un navegador web. Por lo tanto, es de suma importancia utilizar las
medidas de seguridad que un servidor Jupyter Lab proporciona de forma
predeterminada, como protección con contraseña y cifrado SSL. Pero esto es
sólo el comienzo y es posible que se recomienden medidas de seguridad
adicionales dependiendo de lo que se haga exactamente en la instancia de la nube.

Script de instalación para Python y Jupyter Lab El script

bash para instalar Python y Jupyter Lab es similar al presentado en la sección “Uso de
contenedores Docker” en la página 30 para instalar Python a través de Miniconda en un
contenedor Docker. Sin embargo, el script del ejemplo 2­4 también debe iniciar el servidor
de Jupyter Lab . Todas las partes y líneas de código principales están comentadas en línea.

Ejemplo 2­4. Script Bash para instalar Python y ejecutar el servidor Jupyter Notebook

#!/bin/bash
#
# Script para instalar
# Herramientas del sistema Linux y componentes básicos de Python # así
como a
# Inicie el servidor de Jupyter Lab
#
# Python para el comercio algorítmico # (c) Dr. Yves J.
Hilpisch # The Python Quants GmbH #

#LINUX GENERAL
apt­get update # actualiza el caché del índice de paquetes apt­get update ­y #
actualiza paquetes # instala herramientas del sistema apt­
get install ­y build­essential git #
herramientas del sistema apt­get install ­y screen htop vim wget # herramientas del
sistema apt ­get update ­y bash # actualiza bash si es necesario apt­get clean # limpia
el caché del índice del paquete

# INSTALANDO MINICONDA
wget https://repo.anaconda.com/miniconda/Miniconda3­latest­Linux­x86_64.sh \
­O Miniconda.sh bash
Miniconda.sh ­b # instala Miniconda rm ­rf Miniconda.sh # elimina
el instalador # antepone la nueva ruta para la exportación de la sesión
actual PATH="/root/miniconda3/bin:$PATH" # antepone el nuevo ruta
en la configuración del shell cat >> ~/.profile <<EOF export
PATH="/root/miniconda3/bin:$PATH"

EOF

40 | Capítulo 2: Infraestructura de Python


Machine Translated by Google

# INSTALACIÓN DE BIBLIOTECAS PYTHON conda

install ­y jupyter # análisis de datos interactivos en el navegador conda install ­y jupyterlab # entorno de Jupyter Lab conda install ­y

numpy # paquete de computación numérica conda install ­y pytables # contenedor para almacenamiento

binario HDF5 conda install ­y pandas # paquete de análisis de datos conda install ­y scipy # paquete de

cálculos científicos conda install ­y matplotlib # biblioteca de trazado estándar conda install ­y seaborn # biblioteca de

trazado estadístico conda install ­y quandl # contenedor para la API de datos de Quandl conda

install ­y scikit­learn # biblioteca de aprendizaje automático conda install ­y openpyxl # paquete para la interacción

con Excel conda install ­y xlrd xlwt # paquetes para la interacción con Excel conda install ­y pyyaml # paquete

para administrar archivos yaml

pip install ­­upgrade pip # actualizando el administrador de paquetes pip install q # registro y depuración pip install

plotly # trazados D3.js interactivos pip install cufflinks # combinando plotly

con pandas pip install tensorflow # biblioteca de aprendizaje profundo pip install keras

# biblioteca de aprendizaje profundo pip instalar eikon # contenedor de Python para Refinitiv Eikon Data

API # contenedor de Python para API de Oanda pip install git+git://github.com/yhilpisch/

tpqoa

# COPIAR ARCHIVOS Y CREAR DIRECTORIOS mkdir ­p /root/.jupyter/

custom wget http://hilpisch.com/custom.css mv custom.css /

root/.jupyter/custom mv /root/jupyter_notebook_config.py /root/.

jupyter/ mv /root/mycert.pem /root/.jupyter mv /root/mykey.key /

root/.jupyter mkdir /root/notebook cd /root/notebook

# INICIANDO JUPYTER LAB jupyter lab &

Este script debe copiarse en el Droplet y debe iniciarse mediante el script de orquestación,
como se describe en la siguiente subsección.

Script para organizar la configuración del Droplet El

segundo script bash, que configura el Droplet, es el más corto (consulte el Ejemplo 2­5).
Básicamente copia todos los demás archivos al Droplet para los cuales se espera la
dirección IP respectiva como parámetro. En la última línea, inicia el script bash install.sh ,
que a su vez realiza la instalación e inicia Jupyter Lab.
servidor.

Uso de instancias en la nube | 41


Machine Translated by Google

Ejemplo 2­5. Script Bash para configurar el Droplet

#!/bin/bash
#

# Configurar un Droplet de DigitalOcean # con Basic Python


Stack # y Jupyter Notebook #

# Python para el comercio algorítmico # (c) Dr. Yves J


Hilpisch
# Python Quants GmbH #

# DIRECCIÓN IP DEL PARÁMETRO

IP_MAESTRA=$1

# COPIAR LOS ARCHIVOS

scp install.sh raíz@${MASTER_IP}: scp mycert.pem


mykey.key jupyter_notebook_config.py raíz@${MASTER_IP}:

# EJECUTANDO EL SCRIPT DE INSTALACIÓN ssh root@$

{MASTER_IP} bash /root/install.sh

Ahora todo está junto para probar el código de configuración. En DigitalOcean, cree un nuevo Droplet con opciones similares a
estas:

Sistema operativo
Ubuntu 20.04 LTS x64 (la versión más reciente disponible al momento de escribir este artículo)

Tamaño

SSD de dos núcleos, 2 GB y 60 GB (Droplet estándar)

Región del centro de datos


Frankfurt (ya que su autor vive en Alemania)

clave SSH
Agregue una (nueva) clave SSH para iniciar sesión sin contraseña10

Nombre de la gota

Nombre preespecificado o algo así como pyalgo

Finalmente, al hacer clic en el botón Crear se inicia el proceso de creación del Droplet, que generalmente demora
aproximadamente un minuto. El resultado principal al continuar con el procedimiento de configuración es la dirección IP, que
podría ser, por ejemplo, 134.122.74.144 cuando

10 Si necesita ayuda, visite Cómo usar claves SSH con DigitalOcean Droplets o Cómo usar SSH
Claves con PuTTY en DigitalOcean Droplets (usuarios de Windows).

42 | Capítulo 2: Infraestructura de Python


Machine Translated by Google

ha elegido Frankfurt como ubicación de su centro de datos. Configurar Droplet ahora es tan fácil como
sigue:

(base) pro:nube yves$ bash setup.sh 134.122.74.144

Sin embargo, el proceso resultante puede tardar un par de minutos. Finaliza cuando aparece un mensaje
del servidor de Jupyter Lab que dice algo como lo siguiente:

[I 12:02:50.190 LabApp] Servir cuadernos desde el directorio local: /root/notebook [I 12:02:50.190


LabApp] Jupyter Notebook 6.1.1 se ejecuta en: [I 12:02:50.190 LabApp] https://
pyalgo:8888/

En cualquier navegador actual, al visitar la siguiente dirección se accede al Jupyter en ejecución


Servidor portátil (tenga en cuenta el protocolo https ):

https://134.122.74.144:8888

Después de tal vez agregar una excepción de seguridad, debería aparecer la pantalla de inicio de
sesión de Jupyter Notebook solicitando una contraseña (en nuestro caso, jupyter) . Ahora todo está
listo para iniciar el desarrollo de Python en el navegador a través de Jupyter Lab, a través de la
consola basada en IPython y a través de una ventana de terminal o el editor de archivos de texto.
También están disponibles otras capacidades de administración de archivos, como cargar archivos,
eliminar archivos o crear carpetas.

Las instancias en la nube, como las de DigitalOcean y Jupyter Lab


(impulsadas por el servidor Jupyter Notebook ) son una combinación
poderosa para que el desarrollador de Python y el practicante de comercio
algorítmico trabajen y hagan uso de una infraestructura de almacenamiento
y computación profesional. Los proveedores profesionales de centros de
datos y nube se aseguran de que sus máquinas (virtuales) sean físicamente
seguras y tengan alta disponibilidad. El uso de instancias en la nube
también mantiene la fase de exploración y desarrollo a costos bastante
bajos, ya que el uso generalmente se cobra por horas sin necesidad de
celebrar acuerdos a largo plazo.

Conclusiones
Python es el lenguaje de programación y la plataforma tecnológica elegidos no sólo para este libro sino
también para casi todas las instituciones financieras líderes. Sin embargo, la implementación de Python
puede ser, en el mejor de los casos, complicada y, a veces, incluso tediosa y estresante.
Afortunadamente, hoy en día hay tecnologías disponibles (casi todas tienen menos de diez años) que
ayudan con el problema de la implementación. El software de código abierto conda ayuda tanto con el
paquete Python como con la gestión del entorno virtual. Los contenedores Docker van aún más lejos en
el sentido de que se pueden crear fácilmente sistemas de archivos completos y entornos de ejecución en
un “sandbox” técnicamente protegido, o el contenedor. Yendo incluso un paso más allá, los proveedores
de nube como DigitalOcean ofrecen capacidad de computación y almacenamiento en

Conclusiones | 43
Machine Translated by Google

Centros de datos seguros y administrados profesionalmente en cuestión de minutos y facturados por


horas. Esto, en combinación con una instalación de Python 3.8 y una instalación segura del servidor
Jupyter Notebook/Lab, proporciona un entorno profesional para el desarrollo y la implementación de
Python en el contexto de Python para proyectos de comercio algorítmico.

Referencias y recursos adicionales


Para la gestión de paquetes de Python, consulte los siguientes recursos:

• página del administrador de paquetes

pip • página del administrador de paquetes

conda • página oficial de instalación de paquetes

Para la gestión del entorno virtual, consulte estos recursos:

• Página del administrador de entorno virtualenv •

Página de administración de entornos de conda

• Paquete pipenv y administrador de entorno

Puede encontrar información sobre los contenedores Docker, entre otros lugares, en la página de inicio
de Docker, así como en lo siguiente:

• Matías, Karl y Sean Kane. 2018. Docker: en funcionamiento. 2da ed. Sebasto
Pol: O'Reilly.

Robbins (2016) proporciona una introducción concisa y una descripción general del lenguaje de
programación Bash :

• Robbins, Arnold. 2016. Referencia de bolsillo de Bash. 2da ed. Sebastopol: O'Reilly.

En The Jupyter Notebook Docs se explica cómo ejecutar de forma segura un servidor público de Jupyter
Notebook/Lab . También está disponible JupyterHub , que permite la gestión de múltiples usuarios para
un servidor Jupyter Notebook (consulte JupyterHub).

Para registrarse en DigitalOcean con un saldo inicial de 10 USD en su nueva cuenta, visite http://bit.ly/
do_sign_up. Esto paga por dos meses de uso para el Droplet más pequeño.

44 | Capítulo 2: Infraestructura de Python


Machine Translated by Google

CAPÍTULO 3

Trabajar con datos financieros

Claramente, los datos superan a los algoritmos. Sin datos completos, se tiende a obtener
predicciones no completas.
—Rob Thomas (2016)

En el comercio algorítmico, generalmente hay que tratar con cuatro tipos de datos, como se ilustra
en la Tabla 3­1. Aunque simplifica el mundo de los datos financieros, distinguir entre datos históricos
versus en tiempo real y estructurados versus no estructurados a menudo resulta útil en entornos
técnicos.

Tabla 3­1. Tipos de datos financieros (ejemplos)


Estructurado No estructurado

Precios históricos de cierre al final del día Artículos de noticias financieras

Precios de oferta/demanda en tiempo real para FX Publicaciones en Twitter

Este libro se ocupa principalmente de datos estructurados (datos numéricos, tabulares) tanto de
tipo histórico como en tiempo real. Este capítulo en particular se centra en datos históricos
estructurados, como los valores de cierre al final del día para las acciones de SAP SE negociadas
en la Bolsa de Frankfurt. Sin embargo, esta categoría también incluye datos intradía, como los
datos de barra de 1 minuto de las acciones de Apple, Inc. que cotizan en la bolsa de valores
NASDAQ. El procesamiento de datos estructurados en tiempo real se trata en el Capítulo 7.

Un proyecto de comercio algorítmico generalmente comienza con una idea o hipótesis comercial
que debe probarse (retrospectivamente) en función de datos financieros históricos. Este es el
contexto de este capítulo, cuyo plan es el siguiente. “Lectura de datos financieros de diferentes
fuentes” en la página 46 utiliza pandas para leer datos de diferentes fuentes basadas en archivos
y web. “Trabajar con fuentes de datos abiertas” en la página 52 presenta a Quandl como una
plataforma popular de fuentes de datos abiertas. “API de datos Eikon” en la página 55 presenta el
contenedor Python para API de datos Eikon de Refinitiv. Finalmente, "almacenar datos financieros de manera eficiente"

45
Machine Translated by Google

en la página 65 se muestra brevemente cómo almacenar datos históricos estructurados de manera eficiente con
pan das basado en el formato de almacenamiento binario HDF5 .

El objetivo de este capítulo es tener datos financieros disponibles en un formato con el que se pueda implementar
de manera efectiva la prueba retrospectiva de ideas e hipótesis comerciales. Los tres temas principales son la
importación de datos, el manejo de los datos y su almacenamiento.
Este capítulo y los siguientes asumen una instalación de Python 3.8 con paquetes de Python instalados como se
explica en detalle en el Capítulo 2. Por el momento, aún no es relevante en qué infraestructura se proporciona
exactamente este entorno Python. Para obtener más detalles sobre operaciones eficientes de entrada y salida con
Python, consulte Hilpisch (2018, cap. 9).

Lectura de datos financieros de diferentes fuentes


Esta sección hace un uso intensivo de las capacidades de pandas, el popular paquete de análisis de
datos para Python (consulte la página de inicio de pandas ). pandas admite de manera integral las
tres tareas principales de las que se ocupa este capítulo: leer datos, manejar datos y almacenar
datos. Uno de sus puntos fuertes es la lectura de datos de diferentes tipos de fuentes, como se ilustra
en el resto de esta sección.

El conjunto de datos

En esta sección, trabajamos con un conjunto de datos bastante pequeño para el precio de las acciones de Apple
Inc. (con símbolo AAPL y Reuters Instrument Code o RIC AAPL.O) recuperados de la API de datos de Eikon para
abril de 2020.

Dado que dichos datos financieros históricos se han almacenado en un archivo CSV en el disco, se puede utilizar
Python puro para leer e imprimir su contenido:

En [1]: fn = '../data/AAPL.csv'

En [2]: con open(fn, 'r') como f: en rango(5):


para _ print(f.readline(),
end='')
Fecha,ALTO,CIERRE,BAJO,ABRIR,CUENTA,VOLUMEN
2020­04­01,248.72,240.91,239.13,246.5,460606.0,44054638.0
2020­04­02,245.15,244.93,236.9,240.34,380294.0 , 41483493.0
2020­04­03.245,7.241,41 ,238.9741,242.8,293699.0,32470017.0
2020­04­06,263.11,262.47,249.38.250.9,486681.0,50455071.0

Abre el archivo en el disco (ajuste la ruta y el nombre del archivo si es necesario).

Configura un bucle for con cinco iteraciones.

Imprime las primeras cinco líneas del archivo CSV abierto.

Este enfoque permite una inspección simple de los datos. Uno aprende que hay una línea de encabezado y que los
puntos de datos individuales por fila representan Fecha, ABIERTO, ALTO,

46 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

BAJO, CERRAR, CONTAR y VOLUMEN, respectivamente. Sin embargo, los datos aún no están
disponibles en la memoria para su uso posterior con Python.

Lectura de un archivo CSV con Python Para

trabajar con datos almacenados como un archivo CSV, el archivo debe analizarse y los datos
deben almacenarse en una estructura de datos de Python. Python tiene un módulo integrado
llamado csv que admite la lectura de datos de un archivo CSV. El primer método produce un
objeto de lista que contiene otros objetos de lista con los datos del archivo:

En [3]: importar csv

En [4]: csv_reader = csv.reader(open(fn, 'r'))

En [5]: datos = lista (csv_reader)

En [6]: datos[:5]
Salida[6]: [['Fecha', 'ALTO', 'CERRAR', 'BAJO', ' ABRIR', 'CONTAR', 'VOLUMEN'],
['2020­04­01',
'248.72',
'240.91',
'239.13',
'246.5',
'460606.0',
'44054638.0'],
['2020­04­02',
'245.15',
'244.93' ,
'236.9',
'240.34',
'380294.0',
'41483493.0'],
['2020­04­03',
'245.7',
'241.41' ,
'238.9741',
'242.8',
'293699.0',
'32470017.0' ],
['2020­04­06',
'263.11',
'262.47',
'249.38',
'250.9',
'486681.0',
'50455071.0']]

Lectura de datos financieros de diferentes fuentes | 47


Machine Translated by Google

Importa el módulo csv .

Crea una instancia de un objeto iterador csv.reader .

Una lista por comprensión que agrega cada línea del archivo CSV como un objeto de lista al objeto de
lista resultante .

Imprime los primeros cinco elementos del objeto de lista .

Trabajar con un objeto de lista anidado de este tipo (para el cálculo del precio de cierre promedio, por ejemplo)
es posible en principio, pero no es realmente eficiente ni intuitivo.
El uso de un objeto iterador csv.DictReader en lugar del objeto csv.reader estándar hace que estas tareas
sean un poco más manejables. Luego, cada fila de datos en el archivo CSV (aparte de la fila del encabezado)
se importa como un objeto dict para que se pueda acceder a los valores individuales a través de la clave
respectiva:

En [7]: csv_reader = csv.DictReader(open(fn, 'r'))

En [8]: datos = lista (csv_reader)

En [9]: datos[:3]
Salida[9]: [{'Fecha': '2020­04­01', 'ALTO':
'248.72', 'CERRAR':
'240.91', 'BAJO':
'239.13', 'ABIERTO':
'246.5' , 'CONTAR':
'460606.0', 'VOLUMEN':
'44054638.0'},
{'Fecha': '2020­04­02',
'ALTO': '245,15',
'CERRAR': '244,93',
'BAJO': '236,9',
'ABIERTO': '240,34',
'CONTAR': ' 380294.0',
'VOLUMEN': '41483493.0'},
{'Fecha': '2020­04­03',
'ALTO': '245.7',
'CERRAR': '241.41',
'BAJO': '238.9741', '
ABIERTO': '242.8',
'CONTADOR': '293699.0',
'VOLUMEN': '32470017.0'}]

Aquí, se crea una instancia del objeto iterador csv.DictReader , que lee cada fila de datos en un objeto
dict , dada la información en la fila del encabezado.

48 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

Basado en objetos de dictado único , las agregaciones ahora son algo más fáciles de realizar.
plato. Sin embargo, todavía no se puede hablar de una forma conveniente de calcular la media de
el precio de cierre de las acciones de Apple al inspeccionar el código Python respectivo:

En [10]: suma([float(l['CLOSE']) para l en datos]) / len(datos)


Fuera[10]: 272.38619047619045

Primero, se genera un objeto de lista mediante una lista por comprensión con todos los valores de cierre;
en segundo lugar, se suman todos estos valores; tercero, la suma resultante se divide
por el número de valores de cierre.

Esta es una de las principales razones por las que los pandas han ganado tanta popularidad en el mundo.
Comunidad de Python. Hace que la importación de datos y el manejo de, por ejemplo,
conjuntos de datos de series de tiempo financieras más convenientes (y a menudo también considerablemente más rápidos)
que Python puro.

Lectura de un archivo CSV con pandas


A partir de este momento, esta sección utiliza pandas para trabajar con los datos del precio de las acciones de Apple.
colocar. La función principal utilizada es read_csv(), que permite una serie de personalizaciones.
ciones a través de diferentes parámetros (consulte la referencia de API read_csv() ). read_csv() produce
Como resultado del procedimiento de lectura de datos, se crea un objeto DataFrame , que es el elemento central.
Medios para almacenar datos (tabulares) con pandas. La clase DataFrame tiene muchos poderosos
métodos que son particularmente útiles en aplicaciones financieras (consulte el DataFrame
Referencia API):

En [11]: importar pandas como pd

En [12]: datos = pd.read_csv(fn, index_col=0,


parse_dates=Verdadero)

En [13]: data.info() <clase


'pandas.core.frame.DataFrame'>
DatetimeIndex: 21 entradas, 2020­04­01 al 2020­04­30
Columnas de datos (un total de 6 columnas):
# Columna Tipo D de recuento no nulo
­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 ALTO 21 no nulo flotador64


1 CERRAR 21 no nulo flotador64
2 BAJO 21 no nulo flotador64
3 ABIERTO 21 no nulo flotador64
4 CUENTA 21 no nula flotador64
5 VOLUMEN 21 no nulo flotador64
tipos de datos: float64(6)
Uso de memoria: 1,1 KB

En [14]: datos.tail()
Fuera[14]: ALTO CIERRE BAJO ABIERTO CONTAR VOLUMEN
Fecha

Lectura de datos financieros de diferentes fuentes | 49


Machine Translated by Google

2020­04­24 283.01 282.97 277.00 277.20 306176.0 31627183.0 2020­04­27 284.54 283.17
279.95 281.80 300771.0 29271893.0
2020­04­28 285,83 278,58 278,20 285,08 285384,0 28001187,0
2020­04­29 289,67 287,73 283,89 284,73 324890,0 34320204,0
2020­04­30 294,53 293,80 288,35 289,96 471129,0 45765968,0

Se importa el paquete pandas .

Esto importa los datos del archivo CSV, lo que indica que la primera columna se tratará como la
columna de índice y permitirá que las entradas de esa columna se interpreten como información
de fecha y hora.

Esta llamada al método imprime metainformación sobre el objeto DataFrame resultante .

El método data.tail() imprime de forma predeterminada las cinco filas de datos más recientes.

Calcular la media de los valores de cierre de las acciones de Apple ahora es solo una llamada al
método:

En [15]: datos['CLOSE'].media()
Fuera[15]: 272.38619047619056

El Capítulo 4 presenta más funciones de pandas para el manejo de datos financieros.


Para obtener detalles sobre cómo trabajar con pandas y la poderosa clase DataFrame , consulte
también la página de documentación oficial de pandas y McKinney (2017).

Aunque la biblioteca estándar de Python proporciona capacidades para


leer datos de archivos CSV, pandas en general simplifica y acelera
significativamente dichas operaciones. Un beneficio adicional es que las
capacidades de análisis de datos de pandas están disponibles de
inmediato ya que read_csv() devuelve un objeto DataFrame .

Exportar a Excel y JSON pandas

también destaca en la exportación de datos almacenados en objetos DataFrame cuando estos datos
deben compartirse en un formato no específico de Python. Además de poder exportar a archivos CSV,
pandas también permite exportar en forma de archivos de hojas de cálculo de Excel y archivos JSON,
los cuales son formatos de intercambio de datos populares en la industria financiera. Un procedimiento
de exportación de este tipo normalmente necesita una única llamada a un método:

En [16]: data.to_excel('data/aapl.xls', 'AAPL')

En [17]: data.to_json('datos/aapl.json')

En [18]: ls ­n datos/ total 24

50 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

­rw­r­­r­­ 1 501 20 3067 25 de agosto 11:47 aapl.json


­rw­r­­r­­ 1 501 20 5632 25 de agosto 11:47 aapl.xls

Exporta los datos a un archivo de hoja de cálculo de Excel en el disco.

Exporta los datos a un archivo JSON en el disco.

En particular, cuando se trata de la interacción con archivos de hojas de cálculo de Excel, existen
formas más elegantes que simplemente realizar un volcado de datos en un archivo nuevo. xlwings, por ejemplo, es
un potente paquete Python que permite una interacción eficiente e inteligente
entre Python y Excel (visite la página de inicio de xlwings ).

Lectura de Excel y JSON


Ahora que los datos también están disponibles en forma de archivo de hoja de cálculo Excel y JSON
archivo de datos, los pandas también pueden leer datos de estas fuentes. El enfoque es como
sencillo como con los archivos CSV:

En [19]: data_copy_1 = pd.read_excel('data/aapl.xls', 'AAPL',


índice_col=0)

En [20]: data_copy_1.head()
Fuera[20]: ALTO CIERRE BAJO CUENTA ABIERTA VOLUMEN
Fecha
2020­04­01 248.72 240.91 239.1300 246.50 460606 44054638
2020­04­02 245.15 244.93 236.9000 240.34 380294 41483493
2020­04­03 245,70 241,41 238,9741 242,80 293699 32470017
2020­04­06 263.11 262.47 249.3800 250.90 486681 50455071
2020­04­07 271,70 259,43 259,0000 270,80 467375 50721831

En [21]: data_copy_2 = pd.read_json('data/aapl.json')

En [22]: data_copy_2.head()
Fuera[22]: ALTO CIERRE BAJO CUENTA ABIERTA VOLUMEN
2020­04­01 248.72 240.91 239.1300 246.50 460606 44054638
2020­04­02 245.15 244.93 236.9000 240.34 380294 41483493
2020­04­03 245,70 241,41 238,9741 242,80 293699 32470017
2020­04­06 263.11 262.47 249.3800 250.90 486681 50455071
2020­04­07 271,70 259,43 259,0000 270,80 467375 50721831

En [23]: !rm datos/*

Esto lee los datos del archivo de hoja de cálculo de Excel en un nuevo objeto DataFrame .

Se imprimen las primeras cinco filas de la primera copia de los datos en memoria.

Esto lee los datos del archivo JSON en otro objeto DataFrame .

Lectura de datos financieros de diferentes fuentes | 51


Machine Translated by Google

Luego, esto imprime las primeras cinco filas de la segunda copia en memoria de los datos.

pandas resulta útil para leer y escribir datos financieros desde y hacia diferentes tipos de archivos de datos.
A menudo, la lectura puede ser complicada debido a formatos de almacenamiento no estándar (como un
“;” en lugar de un “,” como separador), pero pandas generalmente proporciona el conjunto correcto de
combinaciones de parámetros para hacer frente a tales casos. Aunque todos los ejemplos de esta sección
utilizan únicamente un pequeño conjunto de datos, se puede esperar operaciones de entrada­salida de
alto rendimiento de los pandas en los escenarios más importantes cuando los conjuntos de datos son
mucho más grandes.

Trabajar con fuentes de datos abiertas


En gran medida, el atractivo del ecosistema Python se debe al hecho de que casi todos los paquetes
disponibles son de código abierto y se pueden utilizar de forma gratuita. Sin embargo, el análisis financiero
en general y el comercio algorítmico en particular no pueden vivir únicamente con software y algoritmos de
código abierto; Los datos también juegan un papel vital, como lo enfatiza la cita al comienzo del capítulo.
La sección anterior utiliza un pequeño conjunto de datos de una fuente de datos comerciales. Si bien ha
habido fuentes útiles de datos (financieros) abiertos disponibles durante algunos años (como las
proporcionadas por Yahoo! Finance o Google Finance), no quedan demasiadas al momento de escribir
este artículo en 2020. Una de las fuentes más obvias Las razones de esta tendencia podrían ser los
términos siempre cambiantes de los acuerdos de licencia de datos.

La única excepción notable para los propósitos de este libro es Quandl, una plataforma que agrega una
gran cantidad de fuentes de datos abiertas y premium (es decir, de pago). Los datos se proporcionan a
través de una API unificada para la cual hay disponible un paquete contenedor de Python.

El paquete contenedor de Python para la API de datos de Quandl (consulte la página contenedora de
Python en Quandl y la página de GitHub del paquete) se instala con conda a través de conda install quandl.
El primer ejemplo muestra cómo recuperar los precios promedio históricos para el tipo de cambio BTC/
USD desde la introducción de Bitcoin como criptomoneda. Con Quandl, las solicitudes siempre esperan
una combinación de la base de datos y el conjunto de datos específico deseado. (En el ejemplo, BCHAIN
y MKPRU). Esta información generalmente se puede buscar en la plataforma Quandl. Por ejemplo, la
página relevante en Quandl es BCHAIN/MKPRU.

De forma predeterminada, el paquete quandl devuelve un objeto Pandas DataFrame . En el ejemplo, la


columna Valor también se presenta de forma anualizada (es decir, con valores de fin de año). Tenga en
cuenta que el número que se muestra para 2020 es el último valor disponible en el conjunto de datos (de
mayo de 2020) y no necesariamente el valor de fin de año.

Si bien una gran parte de los conjuntos de datos de la plataforma Quandl son gratuitos, algunos de ellos
requieren una clave API. Esta clave es necesaria después de un cierto límite de API gratuita.

52 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

llamadas también. Cada usuario obtiene dicha clave registrándose para obtener una cuenta gratuita de Quandl en
la página de registro de Quandl. Las solicitudes de datos que requieren una clave API esperan que la clave sea
proporcionado como parámetro api_key. En el ejemplo, la clave API (que se encuentra en
la página de configuración de la cuenta) se almacena como una cadena en la variable quandl_api_key. El
El valor concreto de la clave se lee desde un archivo de configuración a través del configparser.
módulo:

En [24]: importar configparser


config = configparser.ConfigParser()
config.read('../pyalgo.cfg')
Fuera[24]: ['../pyalgo.cfg']

En [25]: importar quandl como q

En [26]: datos = q.get('BCHAIN/MKPRU', api_key=config['quandl']['api_key'])

En [27]: datos.info()
<clase 'pandas.core.frame.DataFrame'>
DatetimeIndex: 4254 entradas, 2009­01­03 al 2020­08­26
Columnas de datos ( 1 columna en total):
# Columna Tipo D de recuento no nulo
­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 Valor 4254 float64 no nulo


tipos de datos: float64(1)
uso de memoria: 66,5 KB

En [28]: datos['Valor'].resample('A').último()
Fuera[28]: Fecha
2009­12­31 0.000000
2010­12­31 0,299999
2011­12­31 4.995000
2012­12­31 13.590000
2013­12­31 731.000000
2014­12­31 317.400000
2015­12­31 428.000000
2016­12­31 952.150000
2017­12­31 13215.574000
2018­12­31 3832.921667
2019­12­31 7385.360000
2020­12­31 11763.930000
Frecuencia: A­DEC, Nombre: Valor, tipo d: float64

Trabajar con fuentes de datos abiertas | 53


Machine Translated by Google

Importa el paquete contenedor de Python para Quandl.

Lee datos históricos para el tipo de cambio BTC/USD.

Selecciona la columna Valor y vuelve a muestrearla, desde los valores diarios originales hasta los anuales.
valores y define la última observación disponible como la relevante.

Quandl también proporciona, por ejemplo, diversos conjuntos de datos para acciones individuales, como
precios de las acciones, fundamentos de las acciones o conjuntos de datos relacionados con opciones negociadas en un determinado
existencias:

En [29]: datos = q.get('FSE/SAP_X', fecha_inicio='2018­1­1',


fecha_final='2020­05­01',
api_key=config['quandl']['api_key'])

En [30]: datos.info()
<clase 'pandas.core.frame.DataFrame'>
DatetimeIndex: 579 entradas, 2018­01­02 al 2020­04­30
Columnas de datos (un total de 10 columnas):
# Columna Tipo D de recuento no nulo
­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 abierto 257 no nulo 579 flotador64


1 alto no nulo 579 no flotador64
2 bajo nulo 579 no nulo flotador64
3 Cerrar flotador64
4 cambio 0 no nulo 533 objeto
5 Volumen negociado no nulo 533 no flotador64
6 facturación nulo flotador64
7 Último precio del día 0 no nulo 8 Unidades negociadas objeto
diariamente 0 no nulo 9 Tipos de facturación diaria: float64(6), objeto
object(4) 0 no nulo objeto

Uso de memoria: 49,8+ KB

La clave API también se puede configurar permanentemente con el contenedor de Python a través de
siguiente:

q.ApiConfig.api_key = 'TU_API_KEY'

La plataforma Quandl también ofrece conjuntos de datos premium para los cuales se requiere una suscripción o tarifa.
requerido. La mayoría de estos conjuntos de datos ofrecen muestras gratuitas. El ejemplo recupera la opción
volatilidades implícitas para las acciones de Microsoft Corp. El conjunto de datos de muestra gratuito es bastante
grande, con más de 4100 filas y muchas columnas (solo se muestra un subconjunto). El
Las últimas líneas de código muestran los valores de volatilidad implícita a 30, 60 y 90 días para los cinco
últimos días disponibles:

En [31]: q.ApiConfig.api_key = config['quandl']['api_key']

En [32]: vol = q.get('VOL/MSFT')

54 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

En [33]: vol.iloc[:, :10].info()


<clase 'pandas.core.frame.DataFrame'>
DatetimeIndex: 1006 entradas, 2015­01­02 al 2018­12­31
Columnas de datos (un total de 10 columnas):
# Columna Tipo D de recuento no nulo
­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 Hv10 1006 flotador no nulo64


1Hv20 1006 flotador no nulo64
2Hv30 1006 flotador no nulo64
3 Hv60 1006 flotador no nulo64
4 Hv90 1006 flotador no nulo64
5 Hv120 1006 flotador no nulo64
6 Hv150 1006 flotador no nulo64
7 Hv180 1006 flotador no nulo64
8 Phv10 1006 flotador no nulo64
9 Phv20 1006 flotador no nulo64
tipos de datos: float64(10)
uso de memoria: 86,5 KB

En [34]: vol[['IvMean30', 'IvMean60', 'IvMean90']].tail()


Salida[34]: IvMean30 IvMean60 IvMean90
Fecha
2018­12­24 0.4310 0.4112 0.3829
2018­12­26 0.4059 0.3844 0.3587
2018­12­27 0,3918 0,3879 0.3618
2018­12­28 0,3940 0,3736 0.3482
2018­12­31 0,3760 0,3519 0.3310

Con esto concluye la descripción general del paquete contenedor de Python quandl para Quandl.
API de datos. La plataforma y el servicio de Quandl están creciendo rápidamente y demuestran ser una
fuente valiosa de datos financieros en un contexto comercial algorítmico.

El software de código abierto es una tendencia que comenzó hace muchos años. Tiene
redujo las barreras de entrada en muchas áreas y también en algorítmicas
comercio. Una nueva tendencia que se refuerza en este sentido son las fuentes de datos abiertos.
ces. En algunos casos, como en el caso de Quandl, incluso proporcionan una alta
conjuntos de datos de calidad. No se puede esperar que los datos abiertos compitan
reemplazar completamente las suscripciones de datos profesionales en el corto plazo, pero
Representan un medio valioso para comenzar con algorítmica.
comerciar de manera rentable.

API de datos de Eikon

Las fuentes de datos abiertas son una bendición para los operadores algorítmicos que desean iniciarse en el
espacio y querer poder probar rápidamente hipótesis e ideas basadas en datos financieros reales.
conjuntos de datos especiales. Sin embargo, tarde o temprano, los conjuntos de datos abiertos ya no serán suficientes para
satisfacer las necesidades de comerciantes y profesionales más ambiciosos.

API de datos de Eikon | 55


Machine Translated by Google

Refinitiv es uno de los mayores proveedores de noticias y datos financieros del mundo. Su actual
producto estrella de escritorio es Eikon, que es el equivalente al Terminal de Bloomberg, el principal
competidor en el campo de los servicios de datos. La Figura 3­1 muestra una captura de pantalla de
Eikon en la versión basada en navegador. Eikon proporciona acceso a petabytes de datos a través
de un único punto de acceso.

Figura 3­1. Versión de navegador del terminal Eikon

Recientemente, Refinitiv ha simplificado su panorama de API y ha lanzado un paquete contenedor


de Python, llamado eikon, para la API de datos de Eikon, que se instala mediante pip install eikon.
Si tiene una suscripción a los servicios de datos de Refinitiv Eikon, puede utilizar el paquete Python
para recuperar mediante programación datos históricos, así como transmitir datos estructurados y
no estructurados, desde la API unificada. Un prerrequisito técnico es que se esté ejecutando una
aplicación de escritorio local que proporcione una sesión API de escritorio. La última aplicación de
escritorio de este tipo en el momento de escribir este artículo se llama Workspace (consulte la Figura
3­2).

Si es suscriptor de Eikon y tiene una cuenta para las páginas de la comunidad de desarrolladores, encontrará
una descripción general de la biblioteca de secuencias de comandos Python Eikon en Inicio rápido.

56 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

Figura 3­2. Aplicación de espacio de trabajo con servicios API de escritorio

Para utilizar la API de datos de Eikon, es necesario configurar Eikon app_key . Lo obtienes a través de la
aplicación App Key Generator (APPKEY) en Eikon o Workspace:

En [35]: importar eikon como ek

En [36]: ek.set_app_key(config['eikon']['app_key'])

En [37]: ayuda(ek)
Ayuda sobre el paquete eikon:

NOMBRE

eikon ­ # codificación: utf­8

CONTENIDOS DEL PAQUETE

Perfil
data_grid
eikonError
json_requests
news_request
streaming_session (paquete) simbología
herramientas
time_series

SUBMÓDULOS

caché

escritorio_sesión

istream_callback

API de datos de Eikon | 57


Machine Translated by Google

flujo de elementos
sesión
arroyo
conexión_transmisión
precio de transmisión
streamingprice_callback
precios de streaming

VERSIÓN
1.1.5

ARCHIVO

/Users/yves/Python/envs/py38/lib/python3.8/site­packages/eikon/__init__
.py

Importa el paquete eikon como ek.

Establece app_key.

Muestra el texto de ayuda del módulo principal.

Recuperar datos estructurados históricos

La recuperación de datos de series de tiempo financieras históricas es tan sencilla como con el
otros envoltorios utilizados antes:

En [39]: símbolos = ['AAPL.O', 'MSFT.O', 'GOOG.O']

En [40]: datos = ek.get_timeseries(símbolos,


start_date='2020­01­01',
end_date='2020­05­01',
intervalo='diario',
campos=['*'])

En [41]: datos.claves()
Fuera[41]: MultiIndex([('AAPL.O', 'ALTO'),
('AAPL.O', 'CERRAR'),
('AAPL.O', 'BAJO'),
('AAPL.O', 'ABIERTO'),
('AAPL.O', 'CONTAR'),
('AAPL.O', 'VOLUMEN'),
('MSFT.O', 'ALTO'),
('MSFT.O', 'CERRAR'),
('MSFT.O', 'BAJO'),
('MSFT.O', 'ABIERTO'),
('MSFT.O', 'CONTAR'),
('MSFT.O', 'VOLUMEN'),
('GOOG.O', 'ALTO'),
('GOOG.O', 'CERRAR'),

58 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

('GOOG.O', 'BAJO'),
('GOOG.O', 'ABIERTO'),
('GOOG.O', 'CONTAR'),
('GOOG.O', 'VOLUMEN')],
)

En [42]: escriba(datos['AAPL.O'])
Fuera[42]: pandas.core.frame.DataFrame

En [43]: datos['AAPL.O'].info()
<clase 'pandas.core.frame.DataFrame'>
DatetimeIndex: 84 entradas, 2020­01­02 al 2020­05­01
Columnas de datos (un total de 6 columnas):
# Columna Tipo D de recuento no nulo
­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 ALTO 84 no nulo flotador64


1 CERRAR 84 no nulo flotador64
2 LOW 84 no nulo 3 OPEN 84 no nulo flotador64
4 COUNT 84 no nulo 5 VOLUME 84 flotador64
tipos no nulos: Int64(2), float64(4) Int64
Int64

uso de memoria: 4,8 KB

En [44]: datos['AAPL.O'].tail()
Fuera[44]: ALTO CIERRE RECUENTO ABIERTO BAJO VOLUMEN
Fecha
2020­04­27 284,54 283,17 279,95 281,80 300771 29271893
2020­04­28 285,83 278,58 278,20 285,08 285384 28001187
2020­04­29 289,67 287,73 283,89 284,73 324890 34320204
2020­04­30 294,53 293,80 288,35 289,96 471129 45765968
2020­05­01 299,00 289,07 285,85 286,25 558319 60154175

Define algunos símbolos como un objeto de lista .

La línea central de código que recupera datos para el primer símbolo...

…para la fecha de inicio indicada y…

…la fecha de finalización indicada.

El intervalo de tiempo se elige aquí para que sea diario.

Todos los campos son solicitados.

La función get_timeseries() devuelve un objeto DataFrame de múltiples índices .

Los valores correspondientes a cada nivel son objetos DataFrame normales .

API de datos de Eikon | 59


Machine Translated by Google

Esto proporciona una descripción general de los datos almacenados en el objeto DataFrame .

Se muestran las últimas cinco filas de datos.

La belleza de trabajar con una API de servicio de datos profesional se hace evidente cuando
uno desea trabajar con múltiples símbolos y en particular con un nivel granular diferente.
Cuidad de los datos financieros (es decir, otros intervalos de tiempo):

En [45]: %%tiempo
datos = ek.get_timeseries(symbols,
start_date='2020­08­14',
end_date='2020­08­15',
intervalo='minuto',
campos='*')
Tiempos de CPU: usuario 58,2 ms, sistema: 3,16 ms, total: 61,4 ms
Tiempo de pared: 2,02 s

En [46]: imprimir(datos['GOOG.O'].loc['2020­08­14 16:00:00':


'2020­08­14 16:04:00'])

ALTO BAJO ABIERTO CERRAR VOLUMEN DE CONTEO


Fecha

2020­08­14 16:00:00 1510.7439 1509.220 1509.940 1510.5239 48 1362


2020­08­14 16:01:00 1511.2900 1509.980 1510.500 1511.2900 2020­08­14 16:02:00 52 1002
1513.0000 1510.964 1510.964 1512.8600 72 1762
2020­08­14 16:03:00 1513.6499 1512.160 1512.990 1513.2300 108 4534
2020­08­14 16:04:00 1513.6500 1511.540 1513.418 1512.7100 40 1364

En [47]: para sym en símbolos:


print('\n' + sim + '\n', datos[sym].iloc[­300:­295])

AAPL.O
ALTO BAJO ABIERTO CERRAR VOLUMEN DE CONTEO
Fecha
2020­08­14 19:01:00 457.1699 456.6300 457.14 456.83 1457 104693
2020­08­14 19:02:00 456.9399 456.4255 456.81 456.45 1178 79740
2020­08­14 19:03:00 456.8199 456.4402 456.45 456.67 908 68517
2020­08­14 19:04:00 456.9800 456.6100 456.67 456.97 2020­08­14 19:05:00 457.1900 665 53649
456.9300 456.98 457.00 679 49636

MSFT.O
ALTO BAJO ABIERTO CERRAR VOLUMEN DE CONTEO
Fecha

2020­08­14 19:01:00 208.6300 208.5083 208.5500 208.5674 333 21368


2020­08­14 19:02:00 208.5750 208.3550 208.5501 208.3600 513 37270
2020­08­14 19:03:00 208.4923 208.3000 208.3600 208.4000 303 23903
2020­08­14 19:04:00 208.4200 208.3301 208.3901 208.4099 222 15861
2020­08­14 19:05:00 208.4699 208.3600 208.3920 208.4069 235 9569

60 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

GOOG.O
ALTO BAJO ABRIR CERRAR CONTAR VOLUMEN
Fecha

2020­08­14 19:01:00 1510.42 1509.3288 1509.5100 1509.8550 47 1577 2020­08­14 19:02:00 1510.30
1508.8000 1509.7559 1508.8647 71 2950 2020­08­14 19:03:00 1510.21 1508.7200 1508.7200 1509.8100 33
603
2020­08­14 19:04:00 1510.21 1508.7200 1509.8800 1509.8299 41 934
2020­08­14 19:05:00 1510.21 1508.7300 1509.5500 1509.6600 30 445

Los datos se recuperan para todos los símbolos a la vez.

El intervalo de tiempo…

…se acorta drásticamente.

La llamada a la función recupera barras de minutos para los símbolos.

Imprime cinco filas del conjunto de datos de Google, LLC.

Imprime tres filas de datos de cada objeto DataFrame .

El código anterior ilustra lo conveniente que es recuperar datos históricos de series de


tiempo financieras de la API de Eikon con Python. De forma predeterminada, la función
get_times series() proporciona las siguientes opciones para el parámetro de intervalo : tick,
minuto, hora, diario, semanal, mensual, trimestral y anual. Esto brinda toda la flexibilidad
necesaria en un contexto comercial algorítmico, particularmente cuando se combina con
las capacidades de remuestreo de pandas como se muestra en el siguiente código:

En [48]: %%tiempo
datos = ek.get_timeseries(símbolos[0],
start_date='2020­08­14 15:00:00',
end_date='2020­08­14 15:30:00', intervalo='tick',
campos=['*'])

Tiempos de CPU: usuario 257 ms, sistema: 17,3 ms, total: 274 ms Tiempo
de pared: 2,31 s

En [49]: data.info() <clase


'pandas.core.frame.DataFrame'> DatetimeIndex: 47346
entradas, 2020­08­14 15:00:00.019000 al 2020­08­14
15:29:59.987000
Columnas de datos (un total de 2 columnas):
# Columna Tipo D de recuento no nulo
­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 VALOR 47311 float64 no nulo


1 VOLUMEN 47346 no nulo Int64
dtypes: Int64(1), float64(1) uso de
memoria: 1,1 MB

API de datos de Eikon | 61


Machine Translated by Google

En [50]: datos.head()
Fuera[50]: VALOR VOLUMEN
Fecha
2020­08­14 15:00:00.019 453.2499 2020­08­14 60
15:00:00.036 453.2294 3
2020­08­14 15:00:00.146 453.2100 5
2020­08­14 15:00:00.146 453.2100 100
2020­08­14 15:00:00.236 453.2100 2

En [51]: resampled = data.resample('30s', label='right').agg(


{'VALOR': 'último', 'VOLUMEN': 'suma'})

En [52]: resampled.tail()
Fuera[52]: VALOR VOLUMEN
Fecha
2020­08­14 15:28:00 453.9000 29746
2020­08­14 15:28:30 454.2869 86441
2020­08­14 15:29:00 454.3900 49513
2020­08­14 15:29:30 454.7550 98520
2020­08­14 15:30:00 454.6200 55592

Un intervalo de tiempo de…

…se elige una hora (debido a los límites de recuperación de datos).

El parámetro de intervalo está configurado para marcar.

Se recuperan cerca de 50.000 ticks de precios durante el intervalo.

El conjunto de datos de series temporales muestra longitudes de intervalos muy irregulares (heterogéneos)
entre dos garrapatas.

Los datos de tick se vuelven a muestrear en un intervalo de 30 segundos de duración (tomando el último valor
y la suma, respectivamente)…

…que se refleja en el DatetimeIndex del nuevo objeto DataFrame .

Recuperar datos históricos no estructurados


Una de las principales ventajas de trabajar con la API de Eikon a través de Python es la fácil recuperación de
datos no estructurados, que luego se pueden analizar y analizar con paquetes Python para el procesamiento del lenguaje
natural (NLP). Este procedimiento es tan simple y directo como
ward como para los datos de series de tiempo financieras.

62 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

El código que sigue recupera titulares de noticias durante un intervalo de tiempo fijo que incluye
Apple Inc. como empresa y "Macbook" como palabra. Los cinco éxitos más recientes son dis
jugado como máximo:

En [53]: titulares = ek.get_news_headlines(query='R:AAPL.O macbook', count=5,

date_from='2020­4­1',
date_to='2020­5­1')

En [54]: titulares
Fuera[54]: versiónCreada \
2020­04­20 21:33:37.332 2020­04­20 21:33:37.332000+00:00
2020­04­20 10:20:23.201 2020­04­20 10:20:23.201000+00:00
2020­04­20 02:32:27.721 2020­04­20 02:32:27.721000+00:00
2020­04­15 12:06:58.693 2020­04­15 12:06:58.693000+00:00
2020­04­09 21:34:08.671 2020­04­09 21:34:08.671000+00:00

texto \
2020­04­20 21:33:37.332 Apple dijo que lanzará nuevos AirPods, MacBook Pro 2020­04­20 ...
10:20:23.201 Apple podría lanzar AirPods actualizados, M de 13 pulgadas ...
2020­04­20 02:32:27.721 Apple supuestamente lanzará nuevos AirPods junto con...
2020­04­15 12:06:58.693 Apple presenta una patente para iPhones, MacBook indu...
2020­04­09 21:34:08.671 Apple lanza una nueva actualización de software para MacBoo...

ID de historia \
2020­04­20 21:33:37.332 urn:newsml:reuters.com:20200420:nNRAble9rq:1
2020­04­20 10:20:23.201 urna:newsml:reuters.com:20200420:nNRAbl8eob:1
2020­04­20 02:32:27.721 urn:newsml:reuters.com:20200420:nNRAbl4mfz:1
2020­04­15 12:06:58.693 urn:newsml:reuters.com:20200415:nNRAbjvsix:1
2020­04­09 21:34:08.671 urn:newsml:reuters.com:20200409:nNRAbi2nbb:1

código fuente
2020­04­20 21:33:37.332 NS:TIMIND
2020­04­20 10:20:23.201 NS:BUSSTA
2020­04­20 02:32:27.721 NS:HINDUT
2020­04­15 12:06:58.693 NS:HINDUT
2020­04­09 21:34:08.671 NS:TIMIND

En [55]: historia = titulares.iloc[0]

En [56]: historia
Fuera[56]: versiónCreada 2020­04­20 21:33:37.332000+00:00
texto Apple dijo que lanzará nuevos AirPods y MacBook Pro ...
storyId urna:newsml:reuters.com:20200420:nNRAble9rq:1
nombre del NS:TIMENTE
código fuente: 2020­04­20 21:33:37.332000, tipo d: objeto

En [57]: noticias_texto = ek.get_news_story(historia['storyId'])

En [58]: desde IPython.display importar HTML

API de datos de Eikon | 63


Machine Translated by Google

En [59]: HTML(texto_noticia)
Salida[59]: < objeto IPython.core.display.HTML >

NUEVA DELHI: Apple lanzó recientemente su tan esperado teléfono inteligente asequible iPhone SE. Ahora
parece que la empresa se está preparando para otro lanzamiento.
Se dice que Apple lanzará la próxima generación de AirPods y la nueva MacBook Pro de 13 pulgadas el
próximo mes.

En febrero, un informe en línea reveló que el gigante tecnológico con sede en Cupertino está trabajando en
AirPods Pro Lite. Ahora, un tweet del informante Job Posser ha revelado que Apple pronto presentará
nuevos AirPods y MacBook Pro.
Jon Posser tuiteó: "Los nuevos AirPods (que se suponía que estarían en el evento de marzo) ya
están listos para funcionar.

Probablemente junto con el MacBook Pro el próximo mes." Sin embargo, no hay muchos detalles disponibles
sobre los próximos productos en este momento. Se suponía que la compañía lanzaría estos productos
en el evento de marzo junto con el iPhone SE.

Pero debido a la actual pandemia de coronavirus, el evento fue cancelado.


Se espera que Apple lance los AirPods Pro Lite y el MacBook Pro de 13 pulgadas tal como lanzó el iPhone SE.
Mientras tanto, Apple ha programado su conferencia anual de desarrolladores WWDC para junio.

Este año la compañía ha decidido realizar un evento únicamente online debido al brote de coronavirus.
Los informes sugieren que este año la compañía planea lanzar los nuevos AirTags y un par de auriculares
Bluetooth premium en el evento. Con Apple AirTags, los usuarios podrán localizar elementos del mundo real,
como llaves o maletas, en la aplicación Find My.

Los AirTags también tendrán capacidades de búsqueda fuera de línea que la compañía introdujo en el núcleo
de iOS 13. Aparte de esto, también se dice que Apple presentará sus auriculares Bluetooth de alta gama. Se
espera que los auriculares Bluetooth ofrezcan una mejor calidad de sonido y batería de respaldo en comparación
con los AirPods.

Para derechos de reimpresión: timescontent.com

Copyright (c) 2020 BENNETT, COLEMAN & CO.LTD.

El parámetro de consulta para la operación de recuperación.

Establece el número máximo de visitas en cinco.

Define el intervalo...

…para buscar titulares de noticias.

Proporciona el objeto de resultados (salida abreviada).

Se elige un titular en particular...

64 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

…y se muestra el story_id .

Esto recupera el texto de la noticia como código html.

En Jupyter Notebook, por ejemplo, el código html...

…se puede renderizar para una mejor lectura.

Con esto concluye la ilustración del paquete contenedor de Python para la API de datos de Refinitiv
Eikon.

Almacenamiento de datos financieros de manera eficiente

En el comercio algorítmico, uno de los escenarios más importantes para la gestión de conjuntos de datos es
"recuperar una vez y utilizar varias veces". O desde una perspectiva de entrada­salida (IO), es "escribir una vez,
leer varias veces". En el primer caso, los datos podrían recuperarse de un servicio web y luego usarse para
probar una estrategia varias veces basándose en una copia temporal en memoria del conjunto de datos. En el
segundo caso, los datos de ticks que se reciben continuamente se escriben en el disco y luego se usan
nuevamente varias veces para ciertas manipulaciones (como agregaciones) en combinación con un procedimiento
de backtesting.

Esta sección asume que la estructura de datos en memoria para almacenar los datos es un objeto Pandas
DataFrame , sin importar de qué fuente se adquieran los datos (de un archivo CSV, un servicio web, etc.).

Para tener disponible un conjunto de datos algo significativo en términos de tamaño, la sección utiliza un conjunto
de datos financieros de muestra generado mediante el uso de números pseudoaleatorios. “Secuencias de
comandos de Python” en la página 78 presenta el módulo de Python con una función llamada generate_sam
ple_data() que realiza la tarea.

En principio, esta función genera un conjunto de datos financieros de muestra en forma tabular de tamaño
arbitrario (la memoria disponible, por supuesto, establece un límite):

En [60]: desde sample_data importar generate_sample_data

En [61]: imprimir (generar_sample_data (filas = 5, columnas = 4))


No0 No1 No2 Numero 3

2021­01­01 00:00:00 100.000000 100.000000 100.000000 100.000000


2021­01­01 00:01:00 100.019641 99.950661 100.052993 99.913841 2021­01­01 00:02:00
99.998164 99.796667 100.109971 99.955398
2021­01­01 00:03:00 100.051537 99.660550 100.136336 100.024150
2021­01­01 00:04:00 99.984614 99.729158 100.210888 99.976584

Importa la función desde el script de Python.

Imprime un conjunto de datos financieros de muestra con cinco filas y cuatro columnas.

Almacenamiento de datos financieros de forma eficiente | sesenta y cinco


Machine Translated by Google

Almacenamiento de objetos de marco de datos

El almacenamiento de un objeto Pandas DataFrame en su conjunto se simplifica gracias a los pandas.


Funcionalidad contenedora HDFStore para el estándar de almacenamiento binario HDF5 . permite uno
para volcar objetos DataFrame completos en un solo paso a un objeto de base de datos basado en archivos.
Para ilustrar la implementación, el primer paso es crear un conjunto de datos de muestra de medias.
tamaño pequeño. Aquí el tamaño del DataFrame generado es de aproximadamente 420 MB:

En [62]: %time data = generate_sample_data(rows=5e6, cols=10).round(4)


Tiempos de CPU: usuario 3,88 s, sistema: 830 ms, total: 4,71 s
Tiempo de pared: 4,72 s

En [63]: datos.info()
<clase 'pandas.core.frame.DataFrame'>
DatetimeIndex: 5000000 entradas, 2021­01­01 00:00:00 a 2030­07­05
05:19:00
Frecuencia: T

Columnas de datos (un total de 10 columnas):


# Tipo de columna
­­­ ­­­­­­ ­­­­­

0 No0 flotador64
1 número 1 flotador64
2 No2 flotador64
3 No3 flotador64
4 número 4 flotador64
5 No5 flotador64
6 No6 flotador64
7 No7 flotador64
8 No8 flotador64
9 número 9 flotador64
tipos de datos: float64(10)
uso de memoria: 419,6 MB

Se genera un conjunto de datos financieros de muestra con 5.000.000 de filas y diez columnas; el
La generación tarda un par de segundos.

El segundo paso es abrir un objeto HDFStore (es decir, un archivo de base de datos HDF5) en el disco.
y escribir el objeto DataFrame en él.1 El tamaño en disco de aproximadamente 440 MB es un poco
mayor que el del objeto DataFrame en memoria . Sin embargo, la velocidad de escritura es aproximadamente
cinco veces más rápido que la generación en memoria del conjunto de datos de muestra.

1 Por supuesto, también se pueden almacenar varios objetos DataFrame en un único objeto HDFStore .

66 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

Trabajar en Python con almacenes binarios como archivos de bases de datos HDF5 normalmente te hace escribir.
velocidades cercanas al máximo teórico del hardware disponible:2

En [64]: h5 = pd.HDFStore('datos/datos.h5', 'w')

En [65]: %tiempo h5['datos'] = datos


Tiempos de CPU: usuario 356 ms, sistema: 472 ms, total: 828 ms
Tiempo de pared: 1,08 s

En [66]: h5
Salida [66]: <clase 'pandas.io.pytables.HDFStore'>
Ruta del archivo: datos/datos.h5

En [67]: ls ­n datos/datos.*
­rw­r­­r­­@ 1 501 20 440007240 25 de agosto 11:48 data/data.h5

En [68]: h5.cerrar()

Esto abre el archivo de base de datos en el disco para escribir (y sobrescribe un archivo potencialmente
archivo existente con el mismo nombre).

Escribir el objeto DataFrame en el disco lleva menos de un segundo.

Esto imprime metainformación para el archivo de base de datos.

Esto cierra el archivo de base de datos.

El tercer paso es leer los datos del objeto HDFStore basado en archivos . Leyendo también
generalmente se produce cerca de la velocidad máxima teórica:

En [69]: h5 = pd.HDFStore('datos/datos.h5', 'r')

En [70]: %tiempo data_copy = h5['datos']


Tiempos de CPU: usuario 388 ms, sistema: 425 ms, total: 813 ms
Tiempo de pared: 812 ms

En [71]: copia_datos.info()
<clase 'pandas.core.frame.DataFrame'>
DatetimeIndex: 5000000 entradas, 2021­01­01 00:00:00 a 2030­07­05
05:19:00
Frecuencia: T

Columnas de datos (un total de 10 columnas):


# Tipo de columna
­­­ ­­­­­­ ­­­­­

0 No0 flotador64
1 número 1 flotador64

2 Todos los valores reportados aquí son del MacMini del autor con procesador Intel i7 hexa core (12 subprocesos), 32 GB
de memoria de acceso aleatorio (RAM DDR4) y una unidad de estado sólido (SSD) de 512 GB.

Almacenamiento de datos financieros de forma eficiente | 67


Machine Translated by Google

2 No2 flotador64
3 No3 flotador64
4 número 4 flotador64
5 No5 flotador64
6 No6 flotador64
7 No7 flotador64
8 No8 flotador64
9 número 9 flotador64
tipos de datos: float64(10)
uso de memoria: 419,6 MB

En [72]: h5.close()

En [73]: datos rm /datos.h5

Abre el archivo de base de datos para su lectura.

La lectura lleva menos de medio segundo.

Existe otra forma algo más flexible de escribir los datos desde un DataFrame.
objeto a un objeto HDFStore . Para este fin, se puede utilizar el método to_hdf() del
objeto DataFrame y establezca el parámetro de formato en tabla (consulte la referencia de la API to_hdf ).
página de referencia). Esto permite agregar nuevos datos al objeto de tabla en el disco y
también, por ejemplo, la búsqueda de datos en el disco, lo cual no es posible con el
primer enfoque. El precio a pagar son velocidades de escritura y lectura más lentas:

En [74]: %time data.to_hdf('data/data.h5', 'data', format='table')


Tiempos de CPU: usuario 3,25 s, sistema: 491 ms, total: 3,74 s
Tiempo de pared: 3,8 s

En [75]: ls ­n datos/datos.*
­rw­r­­r­­@ 1 501 20 446911563 25 de agosto 11:48 data/data.h5

En [76]: %tiempo data_copy = pd.read_hdf('datos/datos.h5', 'datos')


Tiempos de CPU: usuario 236 ms, sistema: 266 ms, total: 502 ms
Tiempo de pared: 503 ms

En [77]: copia_datos.info()
<clase 'pandas.core.frame.DataFrame'>
DatetimeIndex: 5000000 entradas, 2021­01­01 00:00:00 a 2030­07­05
05:19:00
Frecuencia: T

Columnas de datos (un total de 10 columnas):


# Tipo de columna
­­­ ­­­­­­ ­­­­­

0 No0 flotador64
1 número 1 flotador64
2 No2 flotador64
3 No3 flotador64
4 número 4 flotador64
5 No5 flotador64

68 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

6 No6 flotador64
7 No7 flotador64
8 No8 flotador64
9 número 9 flotador64
tipos de datos: float64(10)
uso de memoria: 419,6 MB

Esto define que el formato de escritura será de tipo tabla. La escritura se vuelve más lenta
ya que este tipo de formato implica un poco más de gastos generales y conduce a un
aumento del tamaño del archivo.

La lectura también es más lenta en este escenario de aplicación.

En la práctica, la ventaja de este enfoque es que se puede trabajar con table_frame


objeto en el disco como con cualquier otro objeto de tabla del paquete PyTables que se utiliza
por los pandas en este contexto. Esto proporciona acceso a ciertas capacidades básicas del
Paquete PyTables , como agregar filas a un objeto de tabla :

En [78]: importar tablas como tb

En [79]: h5 = tb.open_file('datos/datos.h5', 'r')

En [80]: h5
Salida[80]: Archivo(nombre de archivo=datos/datos.h5, título='', modo='r', root_uep='/',
filtros = Filtros (nivel completo = 0, aleatorio = Falso, bitshuffle = Falso,
fletcher32=Falso, dígito_menos_significativo=Ninguno))
/ (Grupo raíz) ''
/datos (Grupo) ''
/datos/tabla (Tabla(5000000,)) ''
descripción := {
"índice": Int64Col(forma=(), dflt=0, pos=0),
"values_block_0": Float64Col(forma=(10,), dflt=0.0, pos=1)}
orden de bytes : = 'pequeño'
forma de trozo := (2978,)
índice automático := Verdadero

colíndices := {
"índice": Índice(6, medio, aleatorio, zlib(1)).is_csi=False}

En [81]: h5.root.data.table[:3]
Fuera[81]: matriz([(1609459200000000000, [100. 100. 100. , 100. , 100. , 100. ]), ,
100. , 100. , , , 100. , 100.
(1609459260000000000, [100.0752, 100.1164, 100.0224, 100.0073,
100.1142, 100.0474, 99.9329, 100.0254, 100.1009, 100.066 ]),
(1609459320000000000, [100.1593, 100.1721, 100.0519, 100.0933,
100.1578, 100.0301, 99.92 , 100.0965, 100.1441, 100.0717])],
dtype=[('índice', '<i8'), ('valores_bloque_0', '<f8', (10,))])

En [82]: h5.close()

En [83]: datos rm /datos.h5

Almacenamiento de datos financieros de forma eficiente | 69


Machine Translated by Google

Importa el paquete PyTables .

Abre el archivo de base de datos para su lectura.

Muestra el contenido del archivo de base de datos.

Imprime las primeras tres filas de la tabla.

Cierra la base de datos.

Aunque este segundo enfoque proporciona más flexibilidad, no abre las puertas a todas las capacidades del paquete
PyTables . Sin embargo, los dos enfoques presentados en esta subsección son convenientes y eficientes cuando se
trabaja con conjuntos de datos más o menos inmutables que caben en la memoria. Hoy en día, sin embargo, el trading
algorítmico tiene que lidiar en general con conjuntos de datos en constante y rápido crecimiento, como por ejemplo,
datos de ticks sobre precios de acciones o tipos de cambio.

Para hacer frente a los requisitos de tal escenario, podrían resultar útiles enfoques alternativos.

Al utilizar el contenedor HDFStore para el estándar de almacenamiento binario


HDF5, pandas puede escribir y leer datos financieros casi a la velocidad máxima
que permite el hardware disponible. Las exportaciones a otros formatos basados
en archivos, como CSV, son generalmente alternativas mucho más lentas.

Uso de TsTables El

paquete PyTables , con las tablas de nombres de importación, es un contenedor para la biblioteca de almacenamiento
binario HDF5 que también utiliza pandas para su implementación HDFStore presentada en la subsección anterior. El
paquete TsTables (consulte la página de GitHub para conocer el paquete) a su vez está dedicado al manejo eficiente
de grandes conjuntos de datos de series de tiempo financieras basados en la biblioteca de almacenamiento binario
HDF5. Es efectivamente una mejora del paquete PyTables y agrega soporte para datos de series de tiempo a sus
capacidades.
Implementa un enfoque de almacenamiento jerárquico que permite una recuperación rápida de subconjuntos de datos
seleccionados proporcionando fechas y horas de inicio y finalización, respectivamente. El escenario principal admitido
por TsTables es "escribir una vez, recuperar varias veces".

La configuración ilustrada en esta subsección es que los datos se recopilan continuamente de una fuente web, un
proveedor de datos profesional, etc. y se almacenan de forma provisional y en memoria en un objeto DataFrame .
Después de un tiempo o de una cierta cantidad de puntos de datos recuperados, los datos recopilados se almacenan
en un objeto de tabla TsTables en una base de datos HDF5.

70 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

Primero, aquí está la generación de los datos de muestra:

En [84]: %%tiempo
datos = generar_muestra_datos (filas = 2.5e6, columnas = 5,
frecuencia='1s').ronda(4)
Tiempos de CPU: usuario 915 ms, sistema: 191 ms, total: 1,11 s
Tiempo de pared: 1,14 s

En [85]: datos.info()
<clase 'pandas.core.frame.DataFrame'>
DatetimeIndex: 2500000 entradas, 2021­01­01 00:00:00 a 2021­01­29
22:26:39
Frecuencia: S

Columnas de datos (un total de 5 columnas):


# Tipo de columna
­­­ ­­­­­­ ­­­­­

0 No0 flotador64
1 número 1 flotador64
2 No2 flotador64
3 No3 flotador64
4 número 4 flotador64
tipos de datos: float64(5)
uso de memoria: 114,4 MB

Esto genera un conjunto de datos financieros de muestra con 2.500.000 filas y cinco columnas.
con una frecuencia de un segundo; los datos de la muestra se redondean a dos dígitos.

En segundo lugar, algunas importaciones más y la creación del objeto de tabla TsTables . El
La mayor parte es la definición de la clase desc , que proporciona la descripción del
estructura de datos del objeto de tabla :

Actualmente, TsTables solo funciona con la versión anterior de pandas 0.19.


Está disponible una bifurcación amigable que funciona con versiones más nuevas de pandas .
en http://github.com/yhilpisch/tstables que se puede instalar
con lo siguiente:

pip instalar git+https://github.com/yhilpisch/tstables.git

En [86]: importar tstables

En [87]: importar tablas como tb

En [88]: clase desc(tb.IsDescription):


'''
Descripción de la estructura de la tabla TsTables.
'''

marca de tiempo = tb.Int64Col(pos=0)


No0 = tb.Float64Col(pos=1)
No1 = tb.Float64Col(pos=2)
No2 = tb.Float64Col(pos=3)
No3 = tb.Float64Col(pos=4)

Almacenamiento de datos financieros de forma eficiente |


71
Machine Translated by Google

No4 = tb.Float64Col(pos=5)

En [89]: h5 = tb.open_file('datos/datos.h5ts', 'w')

En [90]: ts = h5.create_ts('/', 'datos', desc)

En [91]: h5
Salida[91]: Archivo(nombre de archivo=data/data.h5ts, título='', modo='w', root_uep='/',
filtros=Filtros(complevel=0, shuffle=False, bitsshuffle=Falso, fletcher32=Falso,
dígito_menos_significativo=Ninguno))
/ (Grupo raíz) '' /
data (Grupo/Serie temporal) '' /
data/y2020 (Grupo) '' /data/
y2020/m08 (Grupo) '' /data/
y2020/m08/d25 (Grupo) '' /data /
y2020/m08/d25/ts_data (Tabla(0,)) '' descripción :=
{ "marca de tiempo":
Int64Col(forma=(), dflt=0, pos=0), "No0":
Float64Col(forma=( ), dflt=0.0, pos=1), "No1":
Float64Col(forma=(), dflt=0.0, pos=2), "No2":
Float64Col(forma=(), dflt=0.0, pos=3) , "No3":
Float64Col(forma=(), dflt=0.0, pos=4), "No4":
Float64Col(forma=(), dflt=0.0, pos=5)} byteorder :=
'pequeño' fragmento de
forma := (1365,)

TsTables (instalado desde https://github.com/yhilpisch/tstables)…

… Se importan PyTables .

La primera columna de la tabla es una marca de tiempo representada como un valor int .

Todas las columnas de datos contienen valores flotantes .

Esto abre un nuevo archivo de base de datos para escribir.

La tabla TsTables se crea en el nodo raíz, con datos de nombre y se le proporciona la


descripción basada en clases .

La inspección del archivo de la base de datos revela el principio básico detrás de la estructuración jerárquica en años,
meses y días.

En tercer lugar está la escritura de los datos de muestra almacenados en un objeto DataFrame en el objeto de tabla en el disco.

Uno de los principales beneficios de TsTables es la conveniencia con la que se realiza esta operación, es decir, mediante una
simple llamada a un método. Aún mejor, aquí la comodidad va unida a la velocidad. Con respecto a la estructura de la base de
datos, TsTables divide los datos en subconjuntos de un solo día. En el caso de ejemplo donde el

72 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

La frecuencia se establece en un segundo, lo que se traduce en 24 x 60 x 60 = 86 400 filas de datos por día
completo de datos:

En [92]: %tiempo ts.append(datos)


Tiempos de CPU: usuario 476 ms, sistema: 238 ms, total: 714 ms Tiempo
de pared: 739 ms

En [93]: #h5

Archivo(nombre de archivo=data/data.h5ts, título='', modo='w', root_uep='/',


filtros=Filtros(complevel=0, shuffle=False, bitshuffle=False, fletcher32=False,
less_significant_digit=Ninguno))
/ (Grupo raíz) '' /
data (Grupo/Serie temporal) '' /
data/y2020 (Grupo) '' /data/
y2021 (Grupo) '' /data/
y2021/m01 (Grupo) '' /data/
y2021/m01 /d01 (Grupo) '' /data/
y2021/m01/d01/ts_data (Tabla(86400,)) ''
descripción :=
{ "marca de tiempo": Int64Col(forma=(), dflt=0, pos=0),
"No0": Float64Col(forma=(), dflt=0.0, pos=1), "No1":
Float64Col( forma=(), dflt=0.0, pos=2), "No2":
Float64Col(forma=(), dflt=0.0, pos=3), "No3":
Float64Col(forma=(), dflt=0.0, pos =4), "No4":
Float64Col(forma=(), dflt=0.0, pos=5)} orden de bytes :=
forma de trozo 'pequeño' :=
(1365,) /data/y2021/m01/
d02 (Grupo) '' /data/y2021/m01/d02/
ts_data (Tabla(86400,)) ''
descripción :=
{ "marca de tiempo": Int64Col(forma=(), dflt=0, pos=0),
"No0": Float64Col(forma=(), dflt=0.0, pos=1), "No1":
Float64Col( forma=(), dflt=0.0, pos=2), "No2":
Float64Col(forma=(), dflt=0.0, pos=3), "No3":
Float64Col(forma=(), dflt=0.0, pos =4), "No4":
Float64Col(forma=(), dflt=0.0, pos=5)} orden de bytes :=
forma de trozo 'pequeño' :=
(1365,) /data/y2021/m01/
d03 (Grupo) '' /data/y2021/m01/d03/
ts_data (Tabla(86400,)) ''
descripción :=
{ "marca de tiempo": Int64Col(forma=(), dflt=0, pos=0),
...

Esto agrega el objeto DataFrame mediante una simple llamada a un método.

El objeto de tabla muestra 86.400 filas por día después de la operación append() .

La lectura de subconjuntos de datos de un objeto de tabla TsTables generalmente es muy rápida, ya que
para eso está optimizado en primer lugar. En este sentido, TsTables apoya

Almacenamiento de datos financieros de forma eficiente | 73


Machine Translated by Google

porta aplicaciones comerciales algorítmicas típicas, como backtesting, bastante bien. Otro
factor que contribuye es que TsTables ya devuelve los datos como un objeto DataFrame , por
lo que en general no son necesarias conversiones adicionales:

En [94]: importar fecha y hora

En [95]: inicio = fecha y hora. fecha y hora (2021, 1, 2)

En [96]: fin = fecha y hora. fecha y hora (2021, 1, 3)

En [97]: % subconjunto de tiempo = ts.read_range(inicio, fin)


Tiempos de CPU: usuario 10,3 ms, sistema: 3,63 ms, total: 14 ms Tiempo
de pared: 12,8 ms

En [98]: inicio = fecha y hora. fecha y hora (2021, 1, 2, 12, 30, 0)

En [99]: fin = fechahora.fechahora(2021, 1, 5, 17, 15, 30)

En [100]: % subconjunto de tiempo = ts.read_range(inicio, fin)


Tiempos de CPU: usuario 28,6 ms, sistema: 18,5 ms, total: 47,1 ms Tiempo de
pared: 46,1 ms

En [101]: subset.info() <clase


'pandas.core.frame.DataFrame'> DatetimeIndex:
276331 entradas, 2021­01­02 12:30:00 a 2021­01­05
17:15:30
Columnas de datos (un total de 5 columnas):
# Columna Tipo D de recuento no nulo
­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 No0 276331 flotante no nulo64 276331


1 número 1 flotante no nulo64 276331 flotante
2 No2 no nulo64 276331 flotante no nulo64
3 No3 276331 flotante no nulo64
4 número 4
dtypes: float64(5) uso de
memoria: 12,6 MB

En [102]: h5.cerrar()

En [103]: datos rm/*

Esto define la fecha de inicio y…

…fecha de finalización de la operación de recuperación de datos.

El método read_range() toma las fechas de inicio y finalización como entrada; leer aquí
es solo cuestión de milisegundos.

Los datos nuevos que se recuperan durante un día se pueden agregar al objeto de tabla
TsTables , como se ilustra anteriormente. Por lo tanto, el paquete es una valiosa adición a la

74 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

capacidades de pandas en combinación con objetos HDFStore cuando se trata de


almacenamiento y recuperación eficientes de (grandes) conjuntos de datos de series temporales financieras a lo largo del tiempo.

Almacenamiento de datos con SQLite3

Los datos de series de tiempo financieras también se pueden escribir directamente desde un objeto DataFrame a un
Base de datos relacional como SQLite3. El uso de una base de datos relacional podría resultar útil en
escenarios donde se aplica el lenguaje de consulta SQL para implementar soluciones más sofisticadas
análisis. En cuanto a la velocidad y también al uso del disco, las bases de datos relacionales no pueden, sin embargo,
nunca, compárelo con otros enfoques que se basan en formatos de almacenamiento binario como
HDF5.

La clase DataFrame proporciona el método to_sql() (consulte la referencia de API to_sql()


página) para escribir datos en una tabla en una base de datos relacional. El tamaño en disco con más de 100 MB
indica que hay bastante sobrecarga al usar bases de datos relacionales:

En [104]: %tiempo de datos = generate_sample_data(1e6, 5, '1min').ronda(4)


Tiempos de CPU: usuario 342 ms, sistema: 60,5 ms, total: 402 ms
Tiempo de pared: 405 ms

En [105]: data.info() <clase


'pandas.core.frame.DataFrame'>
DatetimeIndex: 1000000 entradas, 2021­01­01 00:00:00 al 2022­11­26
10:39:00
Frecuencia: T

Columnas de datos (un total de 5 columnas):


# Recuento de columnas no nulas tipo D
­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­
0 No0 1000000 flotador no nulo64
1 número 1 1000000 flotador no nulo64
2 No2 1000000 flotador no nulo64
3 No3 1000000 flotador no nulo64
4 número 4 1000000 flotador no nulo64
tipos de datos: float64(5)
uso de memoria: 45,8 MB

En [106]: importar sqlite3 como sq3

En [107]: con = sq3.connect('datos/datos.sql')

En [108]: %time data.to_sql('datos', estafa)


Tiempos de CPU: usuario 4,6 s, sistema: 352 ms, total: 4,95 s
Tiempo de pared: 5,07 s

En [109]: ls ­n datos/datos.*
­rw­r­­r­­@ 1 501 20 105316352 25 de agosto 11:48 data/data.sql

Almacenamiento de datos financieros de forma eficiente | 75


Machine Translated by Google

El conjunto de datos financieros de muestra tiene 1.000.000 de filas y cinco columnas; El uso de memoria es de
aproximadamente 46 MB.

Esto importa el módulo SQLite3 .

Se abre una conexión a un nuevo archivo de base de datos.

Escribir los datos en la base de datos relacional lleva un par de segundos.

Una fortaleza de las bases de datos relacionales es la capacidad de implementar tareas de análisis (sin memoria) basadas
en declaraciones SQL estandarizadas. Como ejemplo, considere una consulta
que selecciona para la columna No1 todas aquellas filas donde el valor en esa fila se encuentra entre
105 y 108:

En [110]: consulta = 'SELECCIONAR * DE datos DONDE No1 > 105 y No2 < 108'

En [111]: %time res = con.execute(query).fetchall()


Tiempos de CPU: usuario 109 ms, sistema: 30,3 ms, total: 139 ms Tiempo
de pared: 138 ms

En [112]: res[:5]
Fuera[112]: [('2021­01­03 19:19:00', 103.6894, 105.0117, 103.9025, 95.8619, 93.6062),
('2021­01­03
19:20:00', 103.6724, 105.0654, 10 3.9277 , 95.8915, 93.5673), ('2021­01­03
19:21:00',
103.6213, 105.1132, 103.8598, 95.7606, 93.5618), ('2021­01­03 19:22:00',
103.6724,
105.1896, 103.8704 , 95.7302, 93.4139), ('2021­01­03 19:23:00', 103.8115,
105.1152,
103.8342, 95.706, 93.4436)]

En [113]: len(res)
Fuera[113]: 5035

En [114]: con.cerrar()

En [115]: datos rm/*

La consulta SQL como un objeto str de Python .

La consulta ejecutada para recuperar todas las filas de resultados.

Se imprimieron los primeros cinco resultados.

La longitud del objeto de la lista de resultados .

76 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

Es cierto que este tipo de consultas simples también son posibles con pandas si el conjunto de datos
cabe en la memoria. Sin embargo, el lenguaje de consulta SQL ha demostrado ser útil y poderoso
durante décadas y debería estar en el arsenal de armas de datos del comerciante algorítmico.

pandas también admite conexiones de bases de datos a través de SQLAlchemy,


un paquete de capa de abstracción de Python para diversas bases de datos
relacionales (consulte la página de inicio de SQLAlchemy ). Esto a su vez permite
el uso de, por ejemplo, MySQL como backend de la base de datos relacional.

Conclusiones
Este capítulo cubre el manejo de datos de series de tiempo financieras. Ilustra la lectura de dichos
datos de diferentes fuentes basadas en archivos, como archivos CSV. También muestra cómo
recuperar datos financieros de servicios web, como el de Quandl, para datos de opciones y de fin de
día. Las fuentes abiertas de datos financieros son una valiosa adición al panorama financiero. Quandl
es una plataforma que integra miles de conjuntos de datos abiertos bajo el paraguas de una API
unificada.

Otro tema importante cubierto en este capítulo es el almacenamiento eficiente de objetos DataFrame
completos en disco, así como de los datos contenidos en dicho objeto en memoria en bases de datos.
Los tipos de bases de datos utilizados en este capítulo incluyen el estándar de base de datos HDF5 y
la base de datos relacional liviana SQLite3. Este capítulo sienta las bases para el Capítulo 4, que
aborda el backtesting vectorizado; Capítulo 5, que cubre el aprendizaje automático y el aprendizaje
profundo para la predicción del mercado; y el Capítulo 6, que analiza las pruebas retrospectivas de
estrategias comerciales basadas en eventos.

Conclusiones | 77
Machine Translated by Google

Referencias y recursos adicionales

Puede encontrar más información sobre Quandl en el siguiente enlace:

• http://quandl.org

La información sobre el paquete utilizado para recuperar datos de esa fuente se encuentra aquí:

• Página contenedora de Python en Quandl •

Página GitHub del contenedor Quandl Python

Debe consultar las páginas de documentación oficial para obtener más información sobre los paquetes utilizados
en este capítulo:

• página de inicio de pandas

• página de inicio de PyTables

• Bifurcación de TsTables en GitHub

• Página de inicio de SQLite

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

Hilpisch, Yves. 2018. Python para finanzas: dominar las finanzas basadas en datos. 2da ed.
Sebastopol: O'Reilly.

McKinney, Wes. 2017. Python para análisis de datos: gestión de datos con Pandas, NumPy e IPython. 2da ed.
Sebastopol: O'Reilly.

Tomás, Rob. "Los malos pronósticos del día de las elecciones asestan un golpe a la ciencia de datos: los modelos
de predicción sufrieron datos limitados, algoritmos defectuosos y debilidades humanas".
Wall Street Journal, 9 de noviembre de 2016.

Secuencias de comandos de Python

El siguiente script de Python genera datos de series de tiempo financieras de muestra basados en una simulación
de Monte Carlo para un movimiento browniano geométrico; para obtener más información, consulte Hilpisch (2018,
cap. 12):

# Módulo Python para generar un # conjunto de


datos financieros de muestra # # Python para

operaciones algorítmicas # (c) Dr. Yves J. Hilpisch #


The Python Quants GmbH

78 | Capítulo 3: Trabajar con datos financieros


Machine Translated by Google

importar numpy como


np importar pandas como pd

r = 0,05 # tasa corta constante


sigma = 0,5 # factor de volatilidad

def generar_sample_data(filas, columnas, frecuencia='1min'):


'''

Función para generar datos financieros de muestra.

Parámetros
==========
filas: int
número de filas para generar cols: int
número de
columnas para generar frecuencia: cadena
de frecuencia
str para DatetimeIndex

Devoluciones
=======

df: marco de datos


Objeto DataFrame con los datos de muestra.
'''

filas = int(filas) cols =


int(cols) # generar un
objeto DatetimeIndex dado el índice de frecuencia = pd.date_range('2021­1­1',
periods=rows, freq=freq) # determinar el delta de tiempo en fracciones de año dt =
(index[1] ­ index[0]) / pd.Timedelta(value='365D') # generar
nombres de columnas columns = ['No%d' % i for i in range(cols)] # generar rutas de
muestra para movimiento browniano
geométrico raw = np.exp(np.cumsum((r ­ 0.5 * sigma ** 2) * dt +
sigma * np.sqrt(dt) * np.random.standard_normal((filas, columnas)), eje= 0))

# normalizar los datos para que comiencen en 100 raw


= raw / raw[0] * 100 # generar el
objeto DataFrame df = pd.DataFrame(raw,
index=index, columns=columns) return df

si __nombre__ == '__principal__':
filas = 5 # número de filas columnas =
3 # número de columnas frecuencia = 'D' #
frecuencia diaria
print(generate_sample_data(filas, columnas, frecuencia))

Secuencias de comandos de Python | 79


Machine Translated by Google
Machine Translated by Google

CAPÍTULO 4

Dominar el backtesting vectorizado

[E]ron lo suficientemente tontos como para pensar que se puede mirar el pasado para predecir el futuro.1

­El economista

Desarrollar ideas e hipótesis para un programa de comercio algorítmico es generalmente la parte más
creativa y, a veces, incluso divertida de la etapa de preparación. Probarlos exhaustivamente suele ser la
parte más técnica y que requiere más tiempo. Este capítulo trata sobre el backtesting vectorizado de
diferentes estrategias comerciales algorítmicas. Cubre los siguientes tipos de estrategias (consulte también
“Estrategias comerciales” en la página 13):

Estrategias basadas en promedios móviles simples (SMA)


La idea básica del uso de SMA para la generación de señales de compra y venta ya tiene décadas. Las
SMA son una herramienta importante en el llamado análisis técnico de los precios de las acciones. Una
señal se deriva, por ejemplo, cuando una SMA definida en una ventana de tiempo más corta (por
ejemplo, 42 días) cruza una SMA definida en una ventana de tiempo más larga (por ejemplo, 252 días).

Estrategias de impulso
Estas son estrategias que se basan en la hipótesis de que el desempeño reciente persistirá durante
algún tiempo adicional. Por ejemplo, se supone que una acción que tiene una tendencia a la baja lo
hará durante más tiempo, razón por la cual dicha acción debe venderse en corto.

Estrategias de reversión a la
media El razonamiento detrás de las estrategias de reversión a la media es que los precios de las
acciones o de otros instrumentos financieros tienden a revertir a algún nivel medio o a algún nivel de

tendencia cuando se han desviado demasiado de dichos niveles.

1 Fuente: “¿El pasado predice el futuro?” The Economist, 23 de septiembre de 2009.

81
Machine Translated by Google

El capítulo sigue de la siguiente manera. “Hacer uso de la vectorización” en la página 82 presenta la


vectorización como un enfoque técnico útil para formular y probar estrategias comerciales. “Estrategias
basadas en promedios móviles simples” en la página 88 es el núcleo de este capítulo y cubre con cierta
profundidad el backtesting vectorizado de estrategias basadas en SMA. “Estrategias basadas en el
impulso” en la página 98 presenta y prueba estrategias comerciales basadas en el llamado impulso de
series de tiempo (“rendimiento reciente”) de una acción. “Estrategias basadas en la reversión a la media”
en la página 107 finaliza el capítulo con una cobertura de las estrategias de reversión a la media.
Finalmente, “Espionaje y sobreajuste de datos” en la página 111 analiza los peligros del espionaje y
sobreajuste de datos en el contexto de las pruebas retrospectivas de estrategias comerciales algorítmicas.

El objetivo principal de este capítulo es dominar el enfoque de implementación vectorizada, que permiten
paquetes como NumPy y pandas , como una herramienta de backtesting rápida y eficiente. Con este fin,
los enfoques presentados parten de una serie de suposiciones simplificadoras para centrar mejor la
discusión en el tema principal de la vectorización.

Se debe considerar el backtesting vectorizado en los siguientes casos:

Estrategias comerciales
simples El enfoque de backtesting vectorizado claramente tiene límites cuando se trata del
modelado de estrategias comerciales algorítmicas. Sin embargo, muchas estrategias simples y
populares se pueden probar de forma vectorizada.

Exploración estratégica interactiva


El backtesting vectorizado permite una exploración ágil e interactiva de las estrategias comerciales
y sus características. Por lo general, unas pocas líneas de código son suficientes para obtener los
primeros resultados, y se pueden probar fácilmente diferentes combinaciones de parámetros.

La visualización como objetivo principal


El enfoque se presta bastante bien para visualizaciones de los datos, estadísticas, señales y
resultados de rendimiento utilizados. Por lo general, unas pocas líneas de código Python son
suficientes para generar gráficos atractivos y reveladores.

Programas integrales de backtesting


El backtesting vectorizado es bastante rápido en general, lo que permite probar una gran variedad
de combinaciones de parámetros en un corto período de tiempo. Cuando la velocidad es clave, se
debe considerar el enfoque.

Haciendo uso de la vectorización


La vectorización, o programación de matrices, se refiere a un estilo de programación en el que
las operaciones en escalares (es decir, números enteros o de punto flotante) se generalizan a
vectores, matrices o incluso matrices multidimensionales. Considere un vector de números
enteros representado en Python como un objeto de lista v = [1, 2, 3, 4, 5]. Calculando el
producto escalar de tal vector y, digamos, el número 2 requiere en puro

82 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

Python un bucle for o algo similar, como una lista de comprensión, que es simplemente
sintaxis diferente para un bucle for :

En [1]: v = [1, 2, 3, 4, 5]

En [2]: sm = [2 * i para i en v]

En [3]: sm
Fuera[3]: [2, 4, 6, 8, 10]

En principio, Python permite multiplicar un objeto de lista por un número entero, pero Python
El modelo de datos devuelve otro objeto de lista en el caso de ejemplo que contiene dos tiempos.
los elementos del objeto original:

En [4]: 2 * v
Fuera[4]: [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]

Vectorización con NumPy


El paquete NumPy para computación numérica (ver página de inicio de NumPy ) introduce vectores
rización a Python. La clase principal proporcionada por NumPy es la clase ndarray , que
significa matriz n­dimensional. Se puede crear una instancia de tal objeto, por ejemplo
Por ejemplo, sobre la base del objeto de lista v. Multiplicación escalar, transformación lineal.
ciones y operaciones similares del álgebra lineal funcionan como se desea:

En [5]: importar numpy como np

En [6]: a = np.array(v)

En [7]: un
Fuera[7]: matriz([1, 2, 3, 4, 5])

En [8]: escriba (a)


Fuera[8]: numpy.ndarray

En [9]: 2 * a
Fuera[9]: matriz([ 2, 4, 6, 8, 10])

En [10]: 0,5 * un + 2
Salida[10]: matriz([2.5, 3. , 3.5, 4. , 4.5])

Importa el paquete NumPy .

Crea una instancia de un objeto ndarray basado en el objeto de lista .

Imprime los datos almacenados como objeto ndarray .

Busca el tipo de objeto.

Haciendo uso de la vectorización | 83


Machine Translated by Google

Logra una multiplicación escalar de forma vectorizada.

Logra una transformación lineal de forma vectorizada.

La transición de una matriz unidimensional (un vector) a una matriz bidimensional (un
matriz) es natural. Lo mismo se aplica a las dimensiones superiores:

En [11]: a = np.arange(12).reshape((4, 3))

En [12]: un
Fuera[12]: matriz([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])

En [13]: 2 * a
Fuera[13]: matriz([[ 0, 2, 4],
[ 6, 8, 10],
[12, 14, 16],
[18, 20, 22]])

En [14]: un ** 2
Fuera[14]: matriz([[ 0, 1, 4],
[ 9, 16, 25],
[ 36, 49, 64],
[ 81, 100, 121]])

Crea un objeto ndarray unidimensional y le da nueva forma a dos dimensiones.

Calcula el cuadrado de cada elemento del objeto de forma vectorizada.

Además, la clase ndarray proporciona ciertos métodos que permiten operaciones vectorizadas.
ciones. A menudo también tienen su equivalente en forma de las llamadas funciones universales.
que NumPy proporciona:

En [15]: a.media()
Fuera[15]: 5,5

En [16]: np.media(a)
Fuera[16]: 5,5

En [17]: a.media(eje=0)
Fuera[17]: matriz([4.5, 5.5, 6.5])

En [18]: np.media(a, eje=1)


Fuera[18]: matriz([ 1., 4., 7., 10.])

Calcula la media de todos los elementos mediante una llamada a un método.

Calcula la media de todos los elementos mediante una función universal.

84 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

Calcula la media a lo largo del primer eje.

Calcula la media a lo largo del segundo eje.

Como ejemplo financiero, considere la función generate_sample_data() en “Secuencias de comandos


de Python” en la página 78 que utiliza una discretización de Euler para generar rutas de muestra para
un movimiento browniano geométrico. La implementación utiliza múltiples operaciones vectorizadas
que se combinan en una sola línea de código.

Consulte el Apéndice para obtener más detalles sobre la vectorización con NumPy. Consulte Hilpisch
(2018) para conocer una multitud de aplicaciones de vectorización en un contexto financiero.

El conjunto de instrucciones estándar y el modelo de datos de Python generalmente no


permiten operaciones numéricas vectorizadas. NumPy introduce poderosas técnicas de
vectorización basadas en la clase de matriz regular ndarray que conducen a un código
conciso cercano a la notación matemática en, por ejemplo, álgebra lineal con respecto
a vectores y matrices.

Vectorización con pandas El paquete

pandas y la clase central DataFrame hacen un uso intensivo de NumPy y la clase ndarray . Por lo tanto,
la mayoría de los principios de vectorización vistos en el contexto de NumPy se trasladan a los pandas.
La mecánica se explica mejor con un ejemplo concreto. Para empezar, primero defina un objeto ndarray
bidimensional :

En [19]: a = np.arange(15).reshape(5, 3)

En [20]: un
Fuera[20]: matriz([[ 0, 1, 2], [ 3, 4,
5], [ 6, 7, 8],
[ 9, 10, 11], [12,
13 , 14]])

Para la creación de un objeto DataFrame , genere un objeto de lista con nombres de columnas y un
objeto DatetimeIndex a continuación, ambos del tamaño apropiado dado el objeto ndarray :

En [21]: importar pandas como pd

En [22]: columnas = lista('abc')

En [23]: columnas
Fuera[23]: ['a', 'b', 'c']

En [24]: índice = pd.date_range('2021­7­1', periodos=5, frecuencia='B')

En [25]: índice

Haciendo uso de la vectorización | 85


Machine Translated by Google

Fuera[25]: DatetimeIndex(['2021­07­01', '2021­07­02', '2021­07­05',


'2021­07­06',
'2021­07­07'],
dtype='fechahora64[ns]', frecuencia='B')

En [26]: df = pd.DataFrame(a, columnas=columnas, índice=índice)

En [27]: gl
Fuera[27]: aBC
2021­07­01 0 1 2
2021­07­02 3 4 5
2021­07­05 6 7 8
2021­07­06 9 10 11
2021­07­07 12 13 14

Importa el paquete pandas .

Crea un objeto de lista a partir del objeto str .

Se crea un objeto pandas DatetimeIndex que tiene una frecuencia de "día hábil"
y abarca cinco períodos.

Se crea una instancia de un objeto DataFrame en función del objeto ndarray con una columna
etiquetas y valores de índice especificados.

En principio, la vectorización ahora funciona de manera similar a los objetos ndarray . Una diferencia es
que las operaciones de agregación de forma predeterminada muestran resultados en columnas:

En [28]: 2 * gl
Fuera[28]: aBC
2021­07­01 0 2 4
2021­07­02 6 8 10
2021­07­05 12 14 16
2021­07­06 18 20 22
2021­07­07 24 26 28

En [29]: df.sum()
Fuera[29]: ab 30
35
C 40
tipo de letra: int64

En [30]: np.media(df)
Fuera[30]: ab 6.0
7.0
C 8.0
tipo de letra: float64

86 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

Calcula el producto escalar del objeto DataFrame (tratado como una matriz).

Calcula la suma por columna.

Calcula la media por columna.

Las operaciones por columnas se pueden implementar haciendo referencia a la columna respectiva
nombres, ya sea mediante la notación entre corchetes o la notación con puntos:

En [31]: df['a'] + df['c']


Fuera[31]: 2021­07­01 2
2021­07­02 8
2021­07­05 14
2021­07­06 20
2021­07­07 26
Frecuencia: B, tipo d: int64

Entra [32]: 0.5 * df.a + 2 * df.b ­ df.c Salida [32]: 2021­07­01


0.0
2021­07­02 4.5
2021­07­05 9.0
2021­07­06 13.5
2021­07­07 18.0
Frecuencia: B, tipo d: float64

Calcula la suma de elementos de las columnas a y c.

Calcula una transformación lineal que involucra las tres columnas.

De manera similar, las condiciones que producen vectores de resultados booleanos y selecciones tipo SQL basadas
en tales condiciones son fáciles de implementar:

En [33]: df['a'] > 5


Fuera[33]: 2021­07­01 FALSO
2021­07­02 FALSO
2021­07­05 Verdadero

2021­07­06 Verdadero

2021­07­07 Verdadero

Frecuencia: B, Nombre: a, tipo d: bool

En [34]: df[df['a'] > 5]


Fuera[34]: abc
2021­07­05 6 7 8
2021­07­06 9 10 11
2021­07­07 12 13 14

¿Qué elemento de la columna a es mayor que cinco?

Seleccione todas aquellas filas donde el elemento de la columna a sea mayor que cinco.

Haciendo uso de la vectorización | 87


Machine Translated by Google

Para una prueba retrospectiva vectorizada de estrategias comerciales, comparaciones entre dos columnas
o más son típicos:

En [35]: df['c'] > df['b']


Fuera[35]: 2021­07­01 Verdadero

2021­07­02 Verdadero

2021­07­05 Verdadero

2021­07­06 Verdadero

2021­07­07 Verdadero

Frecuencia: B, tipo d: bool

En [36]: 0.15 * df.a + df.b > df.c Salida [36]: 2021­07­01


Falso
2021­07­02 FALSO
2021­07­05 FALSO
2021­07­06 Verdadero

2021­07­07 Verdadero

Frecuencia: B, tipo d: bool

¿Para qué fecha el elemento de la columna c es mayor que el de la columna b?

Condición que compara una combinación lineal de las columnas a y b con la columna c.

La vectorización con pandas es un concepto poderoso, en particular para la implementación


de algoritmos financieros y el backtesting vectorizado, como se ilustra en el resto
de este capítulo. Para obtener más información sobre los conceptos básicos de la vectorización con pandas y finanzas
Para ejemplos, consulte Hilpisch (2018, cap. 5).

Mientras que NumPy aporta enfoques generales de vectorización a los números


En el mundo informático de Python, pandas permite la vectorización sobre
datos de series de tiempo. Esto es realmente útil para la implementación de
Algoritmos financieros y pruebas retrospectivas del comercio algorítmico.
estrategias. Al utilizar este enfoque, puede esperar un código conciso, ya que
así como una ejecución de código más rápida, en comparación con Python estándar
código, haciendo uso de bucles for y modismos similares para lograr
el mismo objetivo.

Estrategias basadas en medias móviles simples


El comercio basado en promedios móviles simples (SMA) es una estrategia de décadas de antigüedad que ha
sus orígenes en el mundo del análisis técnico de acciones. Brock y cols. (1992), por ejemplo,
investigar empíricamente tales estrategias de manera sistemática. Escriben:

El término "análisis técnico" es un título general para una gran cantidad de técnicas comerciales.
preguntas….En este artículo, exploramos dos de las reglas técnicas más simples y populares:
oscilador de media móvil y ruptura del rango de negociación (niveles de resistencia y soporte). En
En el primer método, las señales de compra y venta se generan mediante dos medias móviles, una larga

88 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

período, y un período corto... Nuestro estudio revela que el análisis técnico ayuda a predecir los cambios
en las acciones.

Entrando en los conceptos

básicos Esta subsección se centra en los conceptos básicos de las estrategias comerciales de
backtesting que utilizan dos SMA. El ejemplo siguiente funciona con datos de cierre de fin de día
(EOD) para el tipo de cambio EUR/USD, como se proporciona en el archivo csv en el archivo de
datos EOD. Los datos del conjunto de datos provienen de la API de datos de Refinitiv Eikon y
representan valores de EOD para los respectivos instrumentos (RIC):

En [37]: raw = pd.read_csv('http://hilpisch.com/pyalgo_eikon_eod_data.csv', index_col=0, parse_dates=True).dropna()

En [38]: raw.info() <clase


'pandas.core.frame.DataFrame'> DatetimeIndex: 2516
entradas, 2010­01­04 a 2019­12­31 Columnas de datos ( 12 columnas en total): #
Columna No Tipo D de recuento nulo

­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 AAPL.O 2516 flotante no nulo64 1 MSFT.O 2516


flotante no nulo64 2 INTC.O 2516 flotante no nulo64 3
AMZN.O 2516 flotante no nulo64 4 GS.N 2516 flotante no
nulo64

5 SPY 2516 flotante no nulo64 6 .SPX 2516 flotante no


nulo64 7 .VIX 2516 flotante no nulo64 8 EUR= 2516
flotante no nulo64 9 XAU= 2516 flotante no nulo64

10 GDX 2516 float64 no nulo 11 GLD 2516 float64 no


nulo dtypes: float64(12) uso de memoria: 255,5 KB

En [39]: datos = pd.DataFrame(raw['EUR='])

En [40]: data.rename(columnas={'EUR=': 'precio'}, inplace=True)

En [41]: data.info() <clase


'pandas.core.frame.DataFrame'> DatetimeIndex: 2516
entradas, 2010­01­04 a 2019­12­31 Columnas de datos ( 1 columna en total): #
Columna No Tipo D de recuento nulo

­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 precio 2516 tipos de dtypes float64 no nulos: float64(1)


uso de memoria: 39,3 KB

Estrategias basadas en medias móviles simples | 89


Machine Translated by Google

Lee los datos del archivo CSV almacenado de forma remota .

Muestra la metainformación del objeto DataFrame .

Transforma el objeto Serie en un objeto DataFrame .

Cambia el nombre de la única columna a precio.

Muestra la metainformación del nuevo objeto DataFrame .

El cálculo de las SMA se simplifica mediante el método Rolling() , en combinación con una operación de cálculo
diferido:

En [42]: datos['SMA1'] = datos['precio'].rolling(42).mean()

En [43]: datos['SMA2'] = datos['precio'].rolling(252).mean()

En [44]: datos.tail()
Fuera[44]: precio SMA1 SMA2

Fecha 2019­12­24 1.1087 1.107698 1.119630


2019­12­26 1.1096 1.107740 1.119529
2019­12­27 1.1175 1.107924 1.119428
2019­12­30 1.1197 1.108131 1.119333
2019­12­31 1.1210 1.108279 1.119231

Crea una columna con 42 días de valores de SMA. Los primeros 41 valores serán NaN.

Crea una columna con 252 días de valores de SMA. Los primeros 251 valores serán NaN.

Imprime las últimas cinco filas del conjunto de datos.

Una visualización de los datos de la serie temporal original en combinación con las SMA ilustra mejor los resultados
(consulte la Figura 4­1):

En [45]: %matplotlib en línea desde


pylab import mpl, plt
plt.style.use('seaborn')
mpl.rcParams['savefig.dpi'] = 300
mpl.rcParams['font.family'] = 'serif'

En [46]: data.plot(title='EUR/USD | SMA de 42 y 252 días', figsize=(10,


6));

El siguiente paso es generar señales, o más bien posicionamientos de mercado, basados en la relación entre las
dos SMA. La regla es ir en largo siempre que la SMA más corta esté por encima de la más larga y viceversa. Para
nuestros propósitos, indicamos una posición larga con 1 y una posición corta con –1.

90 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

Figura 4­1. El tipo de cambio EUR/USD con dos SMA

Ser capaz de comparar directamente dos columnas del objeto DataFrame hace que la implementación
de la regla sea una cuestión de una sola línea de código. El posicionamiento a lo largo del tiempo se
ilustra en la Figura 4­2:

En [47]: datos['posición'] = np.where(datos['SMA1'] > datos['SMA2'],


1, ­1)

En [48]: datos.dropna(inplace=True)

En [49]: datos['posición'].plot(ylim=[­1.1, 1.1],


title='Posicionamiento en el
mercado', figsize=(10, 6));

Implementa la regla comercial de forma vectorizada. np.where() produce +1 para filas donde la
expresión es Verdadera y ­1 para filas donde la expresión es Falsa.

Elimina todas las filas del conjunto de datos que contienen al menos un valor NaN .

Traza el posicionamiento en el tiempo.

Estrategias basadas en medias móviles simples | 91


Machine Translated by Google

Figura 4­2. Posicionamiento de mercado basado en la estrategia con dos SMA

Para calcular el rendimiento de la estrategia, a continuación calcule los rendimientos logarítmicos basándose en la
serie de tiempo financiera original. El código para hacer esto vuelve a ser bastante conciso debido a la vectorización.
La Figura 4­3 muestra el histograma de los retornos del registro:

En [50]: datos['devoluciones'] = np.log(datos['precio'] / datos['precio'].shift(1))

En [51]: datos['devoluciones'].hist(bins=35, figsize=(10, 6));

Calcula los rendimientos logarítmicos de forma vectorizada sobre la columna de precios .

Traza los resultados del registro como un histograma (distribución de frecuencia).

Para derivar los rendimientos de la estrategia, multiplique la columna de posición (desplazada un día de negociación)
por la columna de rendimientos . Dado que los rendimientos logarítmicos son aditivos, calcular la suma de las
columnas rendimientos y estrategia proporciona una primera comparación del rendimiento de la estrategia en
relación con la inversión base misma.

92 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

Figura 4­3. Distribución de frecuencia de las rentabilidades logarítmicas del EUR/USD

La comparación de los rendimientos muestra que la estrategia logra una victoria sobre el banco pasivo.
inversión en marcas:

En [52]: datos['estrategia'] = datos['posición'].shift(1) * datos['devoluciones']

En [53]: datos[['devoluciones', 'estrategia']].sum()


Out[53]: estrategia de ­0.176731
retorno 0.253121
tipo de letra: float64

En [54]: datos[['devoluciones', 'estrategia']].sum().apply(np.exp)


Fuera[54]: devuelve 0,838006
estrategia 1.288039
tipo de letra: float64

Deriva los rendimientos logarítmicos de la estrategia dados los posicionamientos y los rendimientos del mercado.

Resume los valores de rendimiento logarítmicos únicos tanto para la acción como para la estrategia (para ilustrar).
ración únicamente).

Aplica la función exponencial a la suma de los rendimientos logarítmicos para calcular el


rendimiento bruto.

Calcular la suma acumulada en el tiempo con cumsum y, en base a esto, el cumu

Los rendimientos relativos al aplicar la función exponencial np.exp() dan una visión más completa.
imagen completa de cómo se compara la estrategia con el desempeño del sistema financiero base.

Estrategias basadas en medias móviles simples | 93


Machine Translated by Google

instrumento a lo largo del tiempo. La Figura 4­4 muestra los datos gráficamente e ilustra el resultado.
rendimiento en este caso particular:

En [55]: datos[['devoluciones', 'estrategia']].cumsum(


).apply(np.exp).plot(figsize=(10, 6));

Figura 4­4. Rendimiento bruto del EUR/USD en comparación con la estrategia basada en SMA

Las estadísticas promedio anualizadas de riesgo­rendimiento tanto para la acción como para la estrategia son fáciles
calcular:

En [56]: datos[['devoluciones', 'estrategia']].mean() * 252 Fuera[56]:


devuelve ­0.019671
tipo de 0.028174
estrategia: float64

En [57]: np.exp(data[['returns', 'strategy']].mean() * 252) ­ 1 Salida[57]: devuelve


­0.019479
tipo de 0.028575
estrategia: float64

En [58]: datos[['devoluciones', 'estrategia']].std() * 252 ** 0.5 Salida[58]:


devuelve 0.085414
tipo de 0.085405
estrategia: float64

En [59]: (data[['returns', 'strategy']].apply(np.exp) ­ 1).std() * 252 ** 0.5 Out[59]: devuelve


0.085405
tipo de 0.085373
estrategia: float64

94 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

Calcula el rendimiento medio anualizado tanto en espacio logarítmico como regular.

Calcula la desviación estándar anualizada tanto en espacio logarítmico como regular.

Otras estadísticas de riesgo que suelen ser de interés en el contexto del desempeño de la estrategia comercial
son la reducción máxima y el período de reducción más largo. Una estadística de ayuda para
uso en este contexto es el rendimiento bruto máximo acumulado calculado por el
Método cummax() aplicado al rendimiento bruto de la estrategia. La figura 4­5 muestra
las dos series temporales para la estrategia basada en SMA:

En [60]: datos['cumret'] = datos['estrategia'].cumsum().apply(np.exp)

En [61]: datos['cummax'] = datos['cumret'].cummax()

En [62]: datos[['cumret', 'cummax']].dropna().plot(figsize=(10, 6));

Define una nueva columna, cumret, con el rendimiento bruto a lo largo del tiempo.

Define otra columna más con el valor máximo acumulado del bruto
actuación.

Traza las dos nuevas columnas del objeto DataFrame .

Figura 4­5. Rendimiento bruto y rendimiento máximo acumulado de la estrategia basada en SMA

Estrategias basadas en medias móviles simples | 95


Machine Translated by Google

La reducción máxima se calcula entonces simplemente como el máximo de la diferencia entre las dos
columnas relevantes. La reducción máxima en el ejemplo es de aproximadamente 18 puntos
porcentuales:

En [63]: reducción = datos['cummax'] ­ datos['cumret']

En [64]: reducción.max()
Fuera[64]: 0,17779367070195917

Calcula la diferencia por elementos entre las dos columnas.

Selecciona el valor máximo de todas las diferencias.

La determinación del período de reducción más largo es un poco más complicada. Requiere aquellas
fechas en las que el rendimiento bruto iguala su máximo acumulado (es decir, donde se fija un nuevo
máximo). Esta información se almacena en un objeto temporal. Luego se calculan las diferencias en
días entre todas esas fechas y se selecciona el período más largo. Estos períodos pueden durar sólo
un día o más de 100 días. En este caso, el período de retiro más largo dura 596 días, un período
bastante largo:2

En [65]: temp = reducción[drawdown == 0]

En [66]: periodos = (temp.index[1:].to_pydatetime() ­


temp.index[:­1].to_pydatetime())

En [67]: puntos[12:15]
Fuera[67]: matriz([datetime.timedelta(days=1), datetime.timedelta(days=1),
datetime.timedelta(días=10)], dtype=objeto)

En [68]: periodos.max()
Fuera[68]: fechahora.timedelta(días=596)

¿Dónde las diferencias son iguales a cero?

Calcula los valores delta de tiempo entre todos los valores del índice.

Selecciona el valor máximo delta de tiempo .

El backtesting vectorizado con pandas es generalmente una tarea bastante eficiente debido a las
capacidades del paquete y la clase principal DataFrame . Sin embargo, el enfoque interactivo ilustrado
hasta ahora no funciona bien cuando se desea implementar un programa de backtesting más amplio
que, por ejemplo, optimice los parámetros de una estrategia basada en SMA. Para ello es aconsejable
un enfoque más general.

2 Para obtener más información sobre los objetos de fecha , hora y delta de tiempo , consulte el Apéndice C de Hilpisch (2018).

96 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

pandas demuestra ser una poderosa herramienta para el análisis vectorizado de


estrategias comerciales. Muchas estadísticas de interés, como los rendimientos
logarítmicos, los rendimientos acumulados, los rendimientos anualizados y la
volatilidad, la reducción máxima y el período máximo de reducción, pueden en
general calcularse con una sola línea o con unas pocas líneas de código. Ser capaz
de visualizar los resultados mediante una simple llamada a un método es un beneficio adicional.

Generalización del enfoque “Clase de

backtesting de SMA” en la página 115 presenta un código Python que contiene una clase para el backtesting
vectorizado de estrategias comerciales basadas en SMA. En cierto sentido, es una generalización del enfoque
introducido en la subsección anterior. Permite definir una instancia de la clase SMAVectorBacktester proporcionando
los siguientes parámetros:

• símbolo: RIC (datos del instrumento) que se utilizará

• SMA1: para la ventana de tiempo en días para la SMA más corta • SMA2:

para la ventana de tiempo en días para la SMA más larga

• inicio: para la fecha de inicio de la selección de datos

• fin: para la fecha de finalización de la selección de datos

La aplicación en sí se ilustra mejor con una sesión interactiva que haga uso de la clase. El ejemplo primero replica
el backtest implementado anteriormente basado en datos del tipo de cambio EUR/USD. Luego optimiza los
parámetros de SMA para obtener el máximo rendimiento bruto. Sobre la base de los parámetros óptimos, traza el
rendimiento bruto resultante de la estrategia en comparación con el instrumento base durante el período de tiempo
relevante:

En [69]: importar SMAVectorBacktester como SMA

En [70]: smabt = SMA.SMAVectorBacktester('EUR=', 42, 252,


'2010­1­1', '2019­12­31')

En [71]: smabt.run_strategy()
Fuera[71]: (1,29, 0,45)

En [72]: %%tiempo
smabt.optimize_parameters((30, 50, 2),
(200, 300, 2))
Tiempos de CPU: usuario 3,76 s, sistema: 15,8 ms, total: 3,78 s Tiempo de
pared: 3,78 s

Fuera[72]: (matriz([ 48., 238.]), 1.5)

En [73]: smabt.plot_results()

Esto importa el módulo como SMA.

Estrategias basadas en medias móviles simples | 97


Machine Translated by Google

Se crea una instancia de la clase principal.

Realiza una prueba retrospectiva de la estrategia basada en SMA, dados los parámetros durante la creación de instancias.

El método optimizar_parameters() toma como parámetros de entrada rangos con tamaños de


paso y determina la combinación óptima mediante un enfoque de fuerza bruta.

El método plot_results() traza el rendimiento de la estrategia en comparación con el instrumento


de referencia, dados los valores de los parámetros almacenados actualmente (aquí del
procedimiento de optimización).

El rendimiento bruto de la estrategia con la parametrización original es 1,24 o 124%. La estrategia


optimizada produce un rendimiento absoluto de 1,44 o 144% para la combinación de parámetros
SMA1 = 48 y SMA2 = 238. La Figura 4­6 muestra gráficamente el rendimiento bruto a lo largo del
tiempo, nuevamente en comparación con el rendimiento del instrumento base, que representa el punto
de referencia.

Figura 4­6. Rendimiento bruto del EUR/USD y la estrategia SMA optimizada

Estrategias basadas en el impulso


Hay dos tipos básicos de estrategias de impulso. El primer tipo son las estrategias de impulso
transversal. Al seleccionar entre un conjunto más grande de instrumentos, estas estrategias compran
aquellos instrumentos que recientemente han tenido un desempeño superior en relación con sus pares
(o un índice de referencia) y venden aquellos instrumentos que han tenido un desempeño inferior. La
idea básica es que los instrumentos continúan teniendo un desempeño superior y inferior, respectivamente, en

98 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

al menos durante un período de tiempo determinado. Jegadeesh y Titman (1993, 2001) y Chan et al.
(1996) estudian este tipo de estrategias comerciales y sus posibles fuentes de ganancias.

Las estrategias de impulso transversal tradicionalmente han funcionado bastante bien. Jegadeesh y
Titman (1993) escriben:

Este documento documenta que las estrategias que compran acciones que han tenido un buen desempeño
en el pasado y venden acciones que han tenido un mal desempeño en el pasado generan retornos positivos
significativos durante períodos de tenencia de 3 a 12 meses.

El segundo tipo son las estrategias de impulso de series temporales. Estas estrategias compran aquellos
instrumentos que recientemente han tenido un buen desempeño y venden aquellos que recientemente
han tenido un mal desempeño. En este caso, el punto de referencia son los rendimientos pasados del
propio instrumento. Moskowitz et al. (2012) analizan en detalle este tipo de estrategia de impulso en una
amplia gama de mercados. Escriben:

En lugar de centrarse en los rendimientos relativos de los valores en la sección transversal, el impulso de las
series de tiempo se centra exclusivamente en el rendimiento pasado del propio valor... Nuestro hallazgo del
impulso de las series de tiempo en prácticamente todos los instrumentos que examinamos parece desafiar la
hipótesis del “camino aleatorio”. lo que en su forma más básica implica que saber si un precio subió o bajó en
el pasado no debería ser informativo sobre si subirá o bajará en el futuro.

Entrando en lo básico
Considere los precios de cierre al final del día para el precio del oro en USD (XAU=):

En [74]: datos = pd.DataFrame(raw['XAU='])

En [75]: data.rename(columnas={'XAU=': 'precio'}, inplace=True)

En [76]: datos['devoluciones'] = np.log(datos['precio'] / datos['precio'].shift(1))

La estrategia de impulso de series de tiempo más simple es comprar la acción si el último rendimiento fue
positivo y venderla si fue negativo. Con NumPy y pandas esto es fácil de formalizar; simplemente tome el
signo del último rendimiento disponible como posición de mercado.
La Figura 4­7 ilustra el desempeño de esta estrategia. La estrategia tiene un rendimiento significativamente
inferior al instrumento base:

En [77]: datos['posición'] = np.sign(datos['devoluciones'])

En [78]: datos['estrategia'] = datos['posición'].shift(1) * datos['devoluciones']

En [79]: datos[['devoluciones', 'estrategia']].dropna().cumsum(


).apply(np.exp).plot(figsize=(10, 6));

Define una nueva columna con el signo (es decir, 1 o –1) del retorno de registro relevante; los
valores resultantes representan las posiciones de mercado (largas o cortas).

Estrategias basadas en el impulso | 99


Machine Translated by Google

Calcula los rendimientos del registro de estrategia dadas las posiciones en el mercado.

Traza y compara el desempeño de la estrategia con el instrumento de referencia.

Figura 4­7. Rendimiento bruto del precio del oro (USD) y estrategia de impulso (solo último retorno)

Utilizando una ventana de tiempo móvil, la estrategia de impulso de la serie temporal se puede generalizar a
algo más que el último rendimiento. Por ejemplo, se puede utilizar el promedio de los últimos tres retornos para
generar la señal para el posicionamiento. La Figura 4­8 muestra que la estrategia en este caso funciona mucho
mejor, tanto en términos absolutos como en relación con el instrumento base:

En [80]: datos['posición'] = np.sign(datos['devoluciones'].rolling(3).mean())

En [81]: datos['estrategia'] = datos['posición'].shift(1) * datos['devoluciones']

En [82]: datos[['devoluciones',
'estrategia']].dropna().cumsum( ).apply(np.exp).plot(figsize=(10, 6));

Esta vez se toma el rendimiento medio durante un período móvil de tres días.

Sin embargo, el rendimiento es bastante sensible al parámetro de la ventana de tiempo. Elegir, por ejemplo, los
dos últimos retornos en lugar de tres conduce a un desempeño mucho peor, como se muestra en la Figura 4­9.

100 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

Figura 4­8. Rendimiento bruto del precio del oro (USD) y estrategia de impulso (últimos tres
rendimientos)

Figura 4­9. Rendimiento bruto del precio del oro (USD) y estrategia de impulso (dos últimos
rendimientos)

Estrategias basadas en el impulso | 101


Machine Translated by Google

También se podría esperar un impulso de las series temporales durante el día. En realidad, uno lo haría
Se espera que sea más pronunciado intradiario que interdiario. La figura 4­10 muestra el valor bruto
desempeño de cinco estrategias de impulso de series de tiempo para uno, tres, cinco, siete y
nueve observaciones de retorno, respectivamente. Los datos utilizados son datos de precios de acciones intradía para
Apple Inc., recuperado de Eikon Data API. La figura se basa en el código que
sigue. Básicamente, todas las estrategias superan a la acción durante el transcurso de este intradía.
ventana de tiempo, aunque algunas sólo ligeramente:

En [83]: fn = '../data/AAPL_1min_05052020.csv'
# fn = '../data/SPX_1min_05052020.csv'

En [84]: datos = pd.read_csv(fn, index_col=0, parse_dates=True)

En [85]: data.info() <clase


'pandas.core.frame.DataFrame'>
DatetimeIndex: 241 entradas, 2020­05­05 16:00:00 a 2020­05­05 20:00:00
Columnas de datos (un total de 6 columnas):
# Columna Tipo D de recuento no nulo
­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 ALTO 241 no nulo 241 no nulo flotador64


1 BAJO flotador64
2 ABIERTO 241 no nulo flotador64
3 CERRAR 241 no nulo flotador64
4 COUNT 241 no nulo 5 VOLUMEN flotador64
241 tipos no nulos: float64(6) flotador64

uso de memoria: 13,2 KB

En [86]: datos['devoluciones'] = np.log(datos['CLOSE'] /


datos['CERRAR'].shift(1))

En [87]: to_plot = ['devoluciones']

En [88]: para m en [1, 3, 5, 7, 9]:


datos['posición_%d' % m] = np.sign(datos['returns'].rolling(m).mean()) datos['strategy_%d' % m] =
(datos['posición_%d' % m].shift(1) *
datos['devoluciones'])
to_plot.append('estrategia_%d' % m)

En [89]: datos[to_plot].dropna().cumsum().apply(np.exp).plot(
title='AAPL intradía 05 de mayo de 2020',
figsize=(10, 6), estilo=['­', '­­', '­­', '­­', '­­', '­­']);

Lee los datos intradía de un archivo CSV .

Calcula los rendimientos del registro intradiario.

Define un objeto de lista para seleccionar las columnas que se trazarán más adelante.

Deriva posicionamientos según el parámetro de la estrategia de impulso.

102 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

Calcula los retornos del registro de estrategia resultante.

Agrega el nombre de la columna al objeto de lista .

Traza todas las columnas relevantes para comparar el desempeño de las estrategias con el desempeño
del instrumento de referencia.

Figura 4­10. Rendimiento bruto intradía de las acciones de Apple y cinco estrategias de impulso (último,
tres, cinco, siete y nueve rendimientos)

La figura 4­11 muestra el desempeño de las mismas cinco estrategias para el índice S&P 500.
Nuevamente, las cinco configuraciones de estrategias superan al índice y todas muestran un rendimiento
positivo (antes de los costos de transacción).

Estrategias basadas en el impulso | 103


Machine Translated by Google

Figura 4­11. Rendimiento intradiario bruto del índice S&P 500 y cinco estrategias de impulso (últimos
rendimientos uno, tres, cinco, siete y nueve)

Generalización del enfoque “Clase de

backtesting de impulso” en la página 118 presenta un módulo de Python que contiene la clase MomVectorBacktester ,
que permite realizar un backtesting un poco más estandarizado de estrategias basadas en impulso. La clase tiene
los siguientes atributos:

• símbolo: RIC (datos del instrumento) que se utilizará

• inicio: para la fecha de inicio de la selección de datos

• fin: para la fecha de finalización de la selección de datos

• monto: por el monto inicial a invertir

• tc: para los costos de transacción proporcionales por operación

En comparación con la clase SMAVectorBacktester , ésta introduce dos generalizaciones importantes: la cantidad
fija que se invertirá al comienzo del período de backtesting y los costos de transacción proporcionales para
acercarse a las realidades del mercado en términos de costos.
En particular, la adición de costos de transacción es importante en el contexto de estrategias de impulso de series
temporales que a menudo conducen a una gran cantidad de transacciones a lo largo del tiempo.

104 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

La aplicación es tan sencilla y cómoda como antes. El ejemplo primero replica los resultados de la
sesión interactiva anterior, pero esta vez con una inversión inicial de 10.000 USD. La Figura 4­12
visualiza el desempeño de la estrategia, tomando la media de los últimos tres retornos para generar
señales para el posicionamiento. El segundo caso cubierto es uno con costos de transacción
proporcionales del 0,1% por operación. Como ilustra la Figura 4­13 , incluso los costos de transacción
pequeños deterioran significativamente el desempeño en este caso. El factor determinante en este
sentido es la frecuencia relativamente alta de operaciones que requiere la estrategia:

En [90]: importar MomVectorBacktester como mamá

En [91]: mombt = Mom.MomVectorBacktester('XAU=', '2010­1­1',


'2019­12­31', 10000, 0.0)

En [92]: mombt.run_strategy(momentum=3)
Fuera[92]: (20797.87, 7395.53)

En [93]: mombt.plot_results()
En [94]: mombt = Mom.MomVectorBacktester('XAU=', '2010­1­1',
'2019­12­31', 10000, 0,001)

En [95]: mombt.run_strategy(momentum=3)
Fuera[95]: (10749,4, ­2652,93)

En [96]: mombt.plot_results()

Importa el módulo como mamá.

Crea una instancia de un objeto de la clase de backtesting que define el capital inicial como 10
000 USD y los costos de transacción proporcionales como cero.

Realiza una prueba retrospectiva de la estrategia de impulso en función de una ventana de tiempo
de tres días: la estrategia supera la inversión pasiva de referencia.

Esta vez, se asumen costos de transacción proporcionales del 0,1% por operación.

En ese caso, la estrategia básicamente pierde todo el rendimiento superior.

Estrategias basadas en el impulso | 105


Machine Translated by Google

Figura 4­12. Rendimiento bruto del precio del oro (USD) y la estrategia de impulso
(últimos tres retornos, sin costos de transacción)

Figura 4­13. Evolución bruta del precio del oro (USD) y la estrategia de impulso (últimos
tres retornos, costos de transacción del 0,1%)

106 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

Estrategias basadas en la reversión a la media


En términos generales, las estrategias de reversión a la media se basan en un razonamiento opuesto a las
estrategias de impulso. Si un instrumento financiero ha tenido un desempeño “demasiado bueno” en
relación con su tendencia, está en corto y viceversa. Para decirlo de otra manera, mientras que las
estrategias de impulso (series temporales) suponen una correlación positiva entre los rendimientos, las
estrategias de reversión a la media suponen una correlación negativa. Balvers et al. (2000) escribe:

La reversión a la media se refiere a una tendencia de los precios de los activos a volver a una tendencia.

Trabajando con una media móvil simple (SMA) como indicador de una “senda de tendencia”, una estrategia
de reversión a la media en, por ejemplo, el tipo de cambio EUR/USD se puede realizar una prueba
retrospectiva de manera similar a las pruebas retrospectivas de la SMA y el impulso. ­Estrategias basadas
en. La idea es definir un umbral para la distancia entre el precio actual de las acciones y la SMA, que indica
una posición larga o corta.

Entrando en lo básico
Los ejemplos que siguen son para dos instrumentos financieros diferentes para los cuales se esperaría una
reversión media significativa, ya que ambos se basan en el precio del oro:

• GLD es el símbolo de SPDR Gold Shares, que es el mayor fondo cotizado en bolsa (ETF) con respaldo
físico para oro (consulte la página de inicio de SPDR Gold Shares). • GDX es el símbolo

del ETF de VanEck Vectors Gold Miners, que invierte en productos de acciones para seguir el índice
NYSE Arca Gold Miners (consulte la página de descripción general de VanEck Vectors Gold Miners).

El ejemplo comienza con GDX e implementa una estrategia de reversión a la media sobre la base de una
SMA de 25 días y un valor umbral de 3,5 para que la desviación absoluta del precio actual se desvíe de la
SMA para señalar un posicionamiento. La Figura 4­14 muestra las diferencias entre el precio actual de
GDX y el SMA, así como el valor umbral positivo y negativo para generar señales de compra y venta,
respectivamente:

En [97]: datos = pd.DataFrame(raw['GDX'])

En [98]: data.rename(columnas={'GDX': 'precio'}, inplace=True)

En [99]: datos['devoluciones'] = np.log(datos['precio'] /


datos['precio'].shift(1))

En [100]: SMA = 25

En [101]: datos['SMA'] = datos['precio'].rolling(SMA).mean()

En [102]: umbral = 3,5

En [103]: datos['distancia'] = datos['precio'] ­ datos['SMA']

Estrategias basadas en la reversión a la media | 107


Machine Translated by Google

En [104]: datos['distancia'].dropna().plot(figsize=(10, 6), legend=True) plt.axhline(umbral,


color='r') plt.axhline(­umbral, color ='r')
plt.axhline(0, color='r');

El parámetro SMA está definido...

…y se calcula la SMA (“trayectoria de tendencia”).

Se define el umbral para la generación de señal.

La distancia se calcula para cada punto en el tiempo.

Se trazan los valores de distancia.

Figura 4­14. Diferencia entre el precio actual de GDX y SMA, así como los valores umbral para generar
señales de reversión a la media

A partir de las diferencias y de los valores umbral fijados se pueden obtener de nuevo posicionamientos de
forma vectorizada. La Figura 4­15 muestra las posiciones resultantes:

En [105]: datos['posición'] = np.where(datos['distancia'] > umbral,


­1, np.nan)

En [106]: datos['posición'] = np.where(datos['distancia'] < ­umbral,


1, datos ['posición'])

En [107]: datos['posición'] = np.dónde(datos['distancia'] *

108 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

datos['distancia'].shift(1) < 0, 0, datos['posición'])

En [108]: datos['posición'] = datos['posición'].ffill().fillna(0)

En [109]: datos['posición'].iloc[SMA:].plot(ylim=[­1.1, 1.1], figsize=(10, 6));

Si el valor de la distancia es mayor que el valor umbral, vaya corto (establezca –1 en la nueva posición de la
columna); de lo contrario, establezca NaN.

Si el valor de la distancia es inferior al valor del umbral negativo, vaya en largo (conjunto 1); de lo contrario,
mantenga la posición de la columna sin cambios.

Si hay un cambio en el signo del valor de la distancia, vaya al mercado neutral (establezca 0); de lo contrario,
mantenga la posición de la columna sin cambios.

Rellene hacia adelante todas las posiciones NaN con los valores anteriores; reemplace todos los valores de NaN
restantes por 0.

Trace las posiciones resultantes a partir de la posición del índice SMA en adelante.

Figura 4­15. Posicionamientos generados para GDX basados en la estrategia de reversión a la media

El último paso es derivar los rendimientos de la estrategia que se muestran en la Figura 4­16. La estrategia supera con
creces al ETF GDX , aunque la particular parametrización conduce a largos periodos con una posición neutral (ni larga
ni corta).
Estas posiciones neutrales se reflejan en las partes planas de la curva de estrategia en la Figura 4­16:

Estrategias basadas en la reversión a la media | 109


Machine Translated by Google

En [110]: datos['estrategia'] = datos['posición'].shift(1) * datos['devoluciones']

En [111]: datos[['devoluciones',
'estrategia']].dropna().cumsum( ).apply(np.exp).plot(figsize=(10, 6));

Figura 4­16. Rentabilidad bruta del ETF GDX y la estrategia de reversión a la media (SMA =
25, umbral = 3,5)

Generalizando el enfoque Como

antes, el backtesting vectorizado es más eficiente de implementar basándose en una clase


de Python respectiva. La clase MRVectorBacktester presentada en “Clase de backtesting de
reversión a la media” en la página 120 hereda de la clase MomVectorBacktester y simplemente
reemplaza el método run_strategy() para adaptarse a los detalles específicos de la estrategia
de reversión a la media.

El ejemplo ahora utiliza GLD y establece los costos de transacción proporcionales en 0,1%.
El importe inicial a invertir vuelve a fijarse en 10.000 USD. La SMA es 43 esta vez y el valor
umbral está establecido en 7,5. La Figura 4­17 muestra el desempeño de la estrategia de
reversión a la media en comparación con el ETF GLD :

En [112]: importar MRVectorBacktester como MR

En [113]: mrbt = MR.MRVectorBacktester('GLD', '2010­1­1', '2019­12­31',


10000, 0,001)

En [114]: mrbt.run_strategy(SMA=43, umbral=7,5)


Fuera[114]: (13542.15, 646.21)

110 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

En [115]: mrbt.plot_results()

Importa el módulo como MR.

Crea una instancia de un objeto de la clase MRVectorBacktester con un capital inicial de


10 000 USD y un costo de transacción proporcional del 0,1 % por operación; En este caso,
la estrategia supera significativamente al instrumento de referencia.

Realiza una prueba retrospectiva de la estrategia de reversión a la media con un valor SMA de 43 y un
valor umbral de 7,5.

Traza el rendimiento acumulado de la estrategia frente al instrumento base.

Figura 4­17. Rendimiento bruto del ETF GLD y la estrategia de reversión a la media (SMA = 43,
umbral = 7,5, costos de transacción del 0,1%)

Espionaje de datos y sobreajuste


El énfasis en este capítulo, así como en el resto de este libro, está en la implementación
tecnológica de conceptos importantes en el comercio algorítmico mediante el uso de Python.
Las estrategias, parámetros, conjuntos de datos y algoritmos utilizados a veces se eligen
arbitrariamente y otras veces se eligen intencionalmente para dejar claro un punto. Sin duda,
cuando se habla de métodos técnicos aplicados a las finanzas, resulta más apasionante y
motivador ver ejemplos que muestran “buenos resultados”, aunque no sean generalizables a
otros instrumentos financieros o períodos de tiempo, por ejemplo.

Espionaje de datos y sobreajuste | 111


Machine Translated by Google

La capacidad de mostrar ejemplos con buenos resultados a menudo tiene como coste el espionaje de
datos. Según White (2000), el espionaje de datos se puede definir de la siguiente manera:

El espionaje de datos ocurre cuando un determinado conjunto de datos se utiliza más de una vez con fines
de inferencia o selección de modelos.

En otras palabras, un determinado enfoque podría aplicarse varias o incluso muchas veces al mismo
conjunto de datos para llegar a números y gráficos satisfactorios. Esto, por supuesto, es intelectualmente
deshonesto en la investigación de estrategias comerciales porque pretende que una estrategia comercial
tiene algún potencial económico que podría no ser realista en un contexto del mundo real. Dado que el
objetivo de este libro es el uso de Python como lenguaje de programación para el comercio algorítmico,
el enfoque de espionaje de datos podría estar justificado. Esto es una analogía con un libro de
matemáticas que, a modo de ejemplo, resuelve una ecuación que tiene una solución única que puede
identificarse fácilmente. En matemáticas, estos ejemplos sencillos son más bien la excepción que la
regla, pero aun así se utilizan con frecuencia con fines didácticos.

Otro problema que surge en este contexto es el sobreajuste. El sobreajuste en un contexto comercial se
puede describir de la siguiente manera (consulte el Man Institute on Overfitting):

El sobreajuste ocurre cuando un modelo describe ruido en lugar de señal. El modelo puede tener un buen
rendimiento con los datos sobre los que se probó, pero poco o ningún poder predictivo sobre nuevos datos
en el futuro. El sobreajuste puede describirse como encontrar patrones que en realidad no existen. Hay un
costo asociado con el sobreajuste: una estrategia sobreadaptada tendrá un rendimiento inferior en el futuro.

Incluso una estrategia simple, como la basada en dos valores de SMA, permite realizar pruebas
retrospectivas de miles de combinaciones de parámetros diferentes. Es casi seguro que algunas de
esas combinaciones mostrarán buenos resultados de rendimiento. Como señalan Bailey et al. (2015)
analizan en detalle, esto conduce fácilmente a un sobreajuste del backtesting y las personas responsables
del backtesting a menudo ni siquiera son conscientes del problema. Señalan:

Los avances recientes en la investigación algorítmica y la computación de alto rendimiento han hecho que
sea casi trivial probar millones y miles de millones de estrategias de inversión alternativas en un conjunto de
datos finito de series de tiempo financieras... [E]s una práctica común utilizar este poder computacional para
calibrar los parámetros de una estrategia de inversión para maximizar su rendimiento. Pero debido a que la
relación señal­ruido es tan débil, a menudo el resultado de dicha calibración es que los parámetros se eligen
para aprovechar el ruido pasado en lugar de la señal futura. El resultado es una prueba retrospectiva
sobreadaptada.

El problema de la validez de los resultados empíricos, en un sentido estadístico, por supuesto, no se


limita al backtesting estratégico en un contexto financiero.

112 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

Ioannidis (2005), refiriéndose a publicaciones médicas, enfatiza consideraciones probabilísticas y


estadísticas al juzgar la reproducibilidad y validez de los resultados de la investigación:

Existe una creciente preocupación de que en la investigación moderna, los hallazgos falsos puedan
constituir la mayoría o incluso la gran mayoría de las afirmaciones de las investigaciones publicadas. Sin
embargo, esto no debería sorprender. Se puede demostrar que la mayoría de los hallazgos de la
investigación son falsos... Como se ha demostrado anteriormente, la probabilidad de que un hallazgo de la
investigación sea realmente cierto depende de la probabilidad previa de que sea cierto (antes de realizar
el estudio), el poder estadístico del estudio y el nivel de significación estadística.

En este contexto, si se demuestra que una estrategia comercial en este libro funciona bien dado un determinado
conjunto de datos, una combinación de parámetros y tal vez un algoritmo de aprendizaje automático específico,
esto no constituye ningún tipo de recomendación para la configuración particular ni la permite. sacar conclusiones
más generales sobre la calidad y el potencial de desempeño de la configuración de la estrategia en cuestión.

Por supuesto, le animamos a utilizar el código y los ejemplos presentados en este libro para explorar sus propias
ideas de estrategias comerciales algorítmicas e implementarlas en la práctica basándose en los resultados,
validaciones y conclusiones de sus propias pruebas retrospectivas. Después de todo, lo que compensarán los
mercados financieros es una investigación estratégica adecuada y diligente, no el espionaje y el sobreajuste de
datos impulsados por la fuerza bruta.

Conclusiones
La vectorización es un concepto poderoso en la informática científica, así como en el análisis financiero, en el
contexto del backtesting de estrategias comerciales algorítmicas. Este capítulo presenta la vectorización tanto con
NumPy como con pandas y la aplica a tres tipos de estrategias comerciales de respaldo: estrategias basadas en
promedios móviles simples, impulso y reversión a la media. Es cierto que el capítulo hace una serie de
suposiciones simplificadoras, y una prueba retrospectiva rigurosa de las estrategias comerciales debe tener en
cuenta más factores que determinan el éxito comercial en la práctica, como cuestiones de datos, cuestiones de
selección, evitar el sobreajuste o elementos de microestructura del mercado. Sin embargo, el objetivo principal
del capítulo es centrarse en el concepto de vectorización y lo que puede hacer en el comercio algorítmico desde
un punto de vista tecnológico y de implementación. Con respecto a todos los ejemplos y resultados concretos
presentados, es necesario considerar los problemas de espionaje de datos, sobreajuste y significancia estadística.

Referencias y recursos adicionales


Para conocer los conceptos básicos de la vectorización con NumPy y pandas, consulte estos libros:

McKinney, Wes. 2017. Python para análisis de datos. 2da ed. Sebastopol: O'Reilly.

Vander Plas, Jake. 2016. Manual de ciencia de datos de Python. Sebastopol: O'Reilly.

Conclusiones | 113
Machine Translated by Google

Para conocer el uso de NumPy y pandas en un contexto financiero, consulte estos libros:

Hilpisch, Yves. 2015. Análisis de derivados con Python: análisis de datos, modelos, simulación
calibración, calibración y cobertura. Finanzas Wiley.

. 2017. Derivados cotizados de volatilidad y varianza: una guía basada en Python. wiley
Finanzas.

. 2018. Python para finanzas: dominar las finanzas basadas en datos. 2da ed. Sebastopol: O'Reilly.

Para conocer los temas de espionaje y sobreajuste de datos, consulte estos artículos:

Bailey, David, Jonathan Borwein, Marcos López de Prado y Qiji Jim Zhu. 2015.
"La probabilidad de sobreajuste del backtest". Revista de Finanzas Computacionales 20, (4): 39­69.
https://oreil.ly/sOHlf.

Ioánnidis, John. 2005. "Por qué la mayoría de los resultados de las investigaciones publicadas son falsos".
Medicina PLoS 2, (8): 696­701.

Blanco, Halbert. 2000. "Una verificación de la realidad del espionaje de datos". Econométrica 68, (5):
1097­1126.

Para obtener más información general y resultados empíricos sobre estrategias comerciales basadas en
promedios móviles simples, consulte estas fuentes:

Brock, William, Josef Lakonishok y Blake LeBaron. 1992. “Reglas técnicas simples de negociación y
propiedades estocásticas de los rendimientos de las acciones”. Revista de Finanzas 47, (5): 1731­1764.

Droke, Clif. 2001. Medias móviles simplificadas. Columbia: Libros del mercado.

El libro de Ernest Chan cubre en detalle estrategias comerciales basadas en el impulso, así como en la
reversión a la media. El libro también es una buena fuente para conocer los peligros de las estrategias
comerciales de backtesting:

Chan, Ernesto. 2013. Comercio algorítmico: estrategias ganadoras y su justificación.


Hoboken y otros: John Wiley & Sons.

Estos artículos de investigación analizan las características y fuentes de ganancias de las estrategias de
impulso transversal, el enfoque tradicional del comercio basado en impulso:

Chan, Louis, Narasimhan Jegadeesh y Josef Lakonishok. 1996. “Impulso


Estrategias." Revista de Finanzas 51, (5): 1681­1713.

Jegadeesh, Narasimhan y Sheridan Titman. 1993. “Rendimientos de los ganadores que compran y los
perdedores que venden: implicaciones para la eficiencia del mercado de valores”. Revista de Finanzas
48, (1): 65­91.

114 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

Jegadeesh, Narasimhan y Sheridan Titman. 2001. "Rentabilidad de las estrategias de impulso: una evaluación
de explicaciones alternativas". Revista de Finanzas 56, (2): 599­720.

El artículo de Moskowitz et al. proporciona un análisis de las llamadas estrategias de impulso de series
temporales:

Moskowitz, Tobias, Yao Hua Ooi y Lasse Heje Pedersen. 2012. “Serie temporal
Impulso." Revista de Economía Financiera 104: 228­250.

Estos artículos analizan empíricamente la reversión media de los precios de los activos:

Balvers, Ronald, Yangru Wu y Erik Gilliland. 2000. “Reversión media en los mercados bursátiles
nacionales y estrategias de inversión paramétricas contrarias”. Journal of Finance 55, (2): 745­772.

Kim, Myung Jig, Charles Nelson y Richard Startz. 1991. “¿Reversión media de los precios de las
acciones? Una reevaluación de la evidencia empírica”. Revista de Estudios Económicos 58:
515­528.

Spierdijk, Laura, Jacob Bikker y Peter van den Hoek. 2012. “Reversión a la media en los mercados
bursátiles internacionales: un análisis empírico del siglo XX”. Revista de dinero y finanzas
internacionales 31: 228­249.

Secuencias de comandos de Python

Esta sección presenta los scripts de Python a los que se hace referencia y se utilizan en este capítulo.

Clase de backtesting de SMA

A continuación se presenta el código Python con una clase para el backtesting vectorizado de
estrategias basadas en promedios móviles simples:
#

# Módulo Python con clase # para backtesting


vectorizado # de estrategias basadas en SMA #
# Python para comercio algorítmico # (c)

Dr. Yves J. Hilpisch # The Python Quants GmbH

#
importar numpy como
np importar pandas como
pd desde scipy.optimize importar bruto

clase SMAVectorBacktester(objeto):

Secuencias de comandos de Python | 115


Machine Translated by Google

''' Clase para el backtesting vectorizado de estrategias comerciales basadas en SMA.

Atributos
==========
símbolo: cadena
Símbolo RIC con el que trabajar
SMA1:
ventana de tiempo int en días para SMA más corto
SMA2: entero
ventana de tiempo en días para un inicio más largo de
SMA: str

fecha de inicio para la recuperación de datos


fin: str fecha
de finalización para la recuperación de datos

Métodos
=======
get_data:
recupera y prepara el conjunto de datos base set_parameters:
establece uno o dos
nuevos parámetros SMA run_strategy:

ejecuta la prueba retrospectiva para la estrategia basada en SMA


plot_results: traza
el rendimiento de la estrategia en comparación con el símbolo update_and_run: actualiza
los parámetros de
SMA y devuelve el rendimiento absoluto (negativo) optimización_parameters: implementa una optimización
de fuerza bruta para los dos
parámetros de SMA
'''

def __init__(self, símbolo, SMA1, SMA2, inicio, fin):


self.symbol = símbolo
self.SMA1 = SMA1
self.SMA2 = SMA2
self.start = iniciar self.end
= finalizar self.results
= Ninguno self.get_data()

def get_data(yo):
'''
Recupera y prepara los datos.
'''

raw = pd.read_csv('http://hilpisch.com/pyalgo_eikon_eod_data.csv',
index_col=0, parse_dates=True).dropna() raw =
pd.DataFrame(raw[self.symbol]) raw =
raw.loc[self.start:self.end]
raw.rename(columns={self.symbol: 'precio'}, inplace=True) raw['return'] = np.log(raw /
raw.shift(1)) raw['SMA1'] = raw['price'].rolling(self.SMA1).
media() cruda['SMA2'] = cruda['precio'].rolling(self.SMA2).media() self.data =
cruda

116 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

def set_parameters(self, SMA1=Ninguno, SMA2=Ninguno):


''' Actualiza los parámetros de SMA y resp. series de tiempo.
'''

si SMA1 no es Ninguno:
self.SMA1 = SMA1
self.data['SMA1'] = self.data['price'].rolling( self.SMA1).mean()

si SMA2 no es Ninguno:
self.SMA2 = SMA2
self.data['SMA2'] = self.data['precio'].rolling(self.SMA2).mean()

def ejecutar_estrategia(yo):
''' Realiza una prueba retrospectiva de la estrategia comercial.
'''

datos = self.data.copy().dropna() datos['posición']


= np.where(datos['SMA1'] > datos['SMA2'], 1, ­1) datos['estrategia'] = datos['posición'].shift(1) *
datos['retorno'] datos.dropna(inplace=True) datos['creturns'] =
datos['retorno'].cumsum().apply(np.exp)
data['cstrategy'] = data['strategy'].cumsum().apply(np.exp) self.results = data #
rendimiento bruto de la estrategia aperf = data['cstrategy'].iloc[­1] # Rendimiento superior
o inferior de la estrategia
operf = aperf ­ data['creturns'].iloc[­1] return
round(aperf, 2), round(operf, 2)

def resultados_trama(yo):
''' Traza el rendimiento acumulado de la estrategia comercial en comparación con el
símbolo.
'''

si self.results es Ninguno:
print(' Aún no hay resultados para trazar. Ejecute una estrategia.')
título = '%s | SMA1=%d, SMA2=%d' % (símbolo propio,
auto.SMA1, auto.SMA2)
self.results[['creturns', 'cstrategy']].plot(title=title, figsize=(10, 6))

def update_and_run(self, SMA): ''' Actualiza


los parámetros de SMA y devuelve un rendimiento absoluto negativo (para el algoritmo de minimización).

Parámetros
==========
SMA: tupla
Tupla de parámetros SMA
'''

self.set_parameters(int(SMA[0]), int(SMA[1])) devuelve


­self.run_strategy()[0]

def optimizar_parametros(self, SMA1_range, SMA2_range):

Secuencias de comandos de Python | 117


Machine Translated by Google

''' Encuentra el máximo global dados los rangos de parámetros de SMA.

Parámetros
==========
SMA1_range, SMA2_range: tupla tuplas
de la forma (inicio, fin, tamaño de paso)
'''

opt = brute(self.update_and_run, (SMA1_range, SMA2_range), terminar=Ninguno) return opt,


­self.update_and_run(opt)

si __nombre__ == '__main__': smabt


= SMAVectorBacktester('EUR=', 42, 252,
'2010­1­1', '2020­12­31')
print(smabt.run_strategy())
smabt.set_parameters(SMA1=20, SMA2=100)
print(smabt.run_strategy())
print(smabt.optimize_parameters((30, 56, 4), (200, 300, 4)) )

Clase de backtesting de impulso


A continuación se presenta el código Python con una clase para el backtesting vectorizado de
estrategias basadas en el impulso de series de tiempo:

#
# Módulo Python con Clase # para
Backtesting Vectorizado # de Estrategias
Basadas en Momentum #

# Python para el comercio algorítmico # (c) Dr. Yves J.


Hilpisch # The Python Quants GmbH #
importar numpy como np importar pandas

como pd

class MomVectorBacktester(object): ''' Clase para


el backtesting vectorizado de estrategias comerciales basadas
en impulso.

Atributos
==========
símbolo: cadena
RIC (instrumento financiero) para trabajar con inicio: str

fecha de inicio para la selección de datos


fin: str fecha
de finalización para la selección de
datos monto: int, monto
flotante a invertir al principio tc: flotante

118 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

Costos de transacción proporcionales (por ejemplo, 0,5% = 0,005) por operación.

Métodos
=======
get_data:
recupera y prepara el conjunto de datos base
ejecutar_estrategia:
ejecuta la prueba retrospectiva para la estrategia basada en
el impulso
plot_results: traza el rendimiento de la estrategia en comparación con el símbolo
'''

def __init__(self, símbolo, inicio, fin, cantidad, tc):


self.symbol = símbolo self.start
= inicio self.end = final
self.amount =
cantidad self.tc = tc self.results
= Ninguno
self.get_data()

def get_data(yo):
'''
Recupera y prepara los datos.
'''

raw = pd.read_csv('http://hilpisch.com/pyalgo_eikon_eod_data.csv',
index_col=0, parse_dates=True).dropna() raw =
pd.DataFrame(raw[self.symbol]) raw =
raw.loc[self.start:self.end]
raw.rename(columns={self.symbol: 'precio'}, inplace=True) raw['return'] = np.log(raw /
raw.shift(1)) self.data = raw

def run_strategy(self, impulso=1):


''' Realiza una prueba retrospectiva de la estrategia comercial.
'''

self.momentum = datos de impulso


= self.data.copy().dropna() datos['posición'] =
np.sign(datos['retorno'].rolling(momentum).mean()) datos['estrategia' ] = datos['posición'].shift(1) *
datos['retorno'] # determinar cuándo se realiza una operación data.dropna(inplace=True)
trades = datos['posición'].diff().fillna( 0) != 0 # restar los
costos de transacción del retorno
cuando se realiza el comercio data['strategy'][trades] ­= self.tc
data['creturns'] = self.amount * data['return'].cumsum() .apply(np.exp) datos['cstrategy'] =
self.amount * \

data['strategy'].cumsum().apply(np.exp) self.results = data


# rendimiento absoluto de la
estrategia aperf = self.results['cstrategy'].iloc[­1] #
rendimiento superior/bajo de estrategia operf = aperf ­
self.results['creturns'].iloc[­1]

Secuencias de comandos de Python | 119


Machine Translated by Google

ronda de retorno (aperf, 2), ronda (operf, 2)

def resultados_trama(yo):
''' Traza el rendimiento acumulado de la estrategia comercial en comparación con el
símbolo.
'''

si self.results es Ninguno:
print(' Aún no hay resultados para trazar. Ejecute una estrategia.')
título = '%s | TC = %.4f' % (self.symbol, self.tc) self.results[['creturns',
'cstrategy']].plot(title=title, figsize=(10, 6))

if __name__ == '__main__': mombt =


MomVectorBacktester('XAU=', '2010­1­1', '2020­12­31', 10000, 0.0)

print(mombt.run_strategy())
print(mombt.run_strategy(momentum=2)) mombt =
MomVectorBacktester('XAU=', '2010­1­1', '2020­12­31', 10000, 0.001)

print( mombt.run_strategy(momento=2))

Clase de backtesting de reversión a la media A

continuación se presenta el código Python con una clase para el backtesting vectorizado de
estrategias basadas en la reversión a la media:.

# Módulo Python con clase # para backtesting


vectorizado # de estrategias de reversión a la
media # # Python para comercio algorítmico # (c)

Dr. Yves J. Hilpisch # The Python Quants GmbH

#
desde la importación de MomVectorBacktester *

clase MRVectorBacktester (MomVectorBacktester):


''' Clase para el backtesting vectorizado de estrategias
comerciales basadas en reversión a la media.

Atributos
==========

símbolo: cadena
Símbolo RIC con el que trabajar
inicio: cadena
fecha de inicio para la recuperación de
datos fin: str
fecha de finalización de la cantidad de
recuperación de datos: int, float

120 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

cantidad a invertir al principio tc: costos de transacción


proporcionales
flotantes (por ejemplo, 0,5% = 0,005) por operación

Métodos
=======
get_data:
recupera y prepara el conjunto de datos base
run_strategy:
ejecuta la prueba retrospectiva para la estrategia basada en reversión de
la media.
plot_results: traza el rendimiento de la estrategia en comparación con el símbolo.
'''

def run_strategy(self, SMA, umbral): ''' Realiza una


prueba retrospectiva de la estrategia comercial.
'''

datos = self.data.copy().dropna() datos['sma'] =


datos['precio'].rolling(SMA).mean() datos['distancia'] = datos['precio'] ­
datos ['sma'] data.dropna(inplace=True) # datos de señales de
venta['posición'] =

np.where(datos['distancia'] > umbral, ­1, np.nan)

# señales de
compra data['position'] = np.where(data['distance'] < ­threshold, 1, data['position']) #
cruce del precio actual y los
datos SMA (distancia cero) ['position'] = np.where(datos['distancia'] *
datos['distancia'].shift(1) < 0, 0, datos['posición'])

datos['posición'] = datos['posición'].ffill().fillna(0) datos['estrategia'] =


datos['posición'].shift(1) * datos['retorno'] # determinar cuándo se realiza una transacción
trades = data['position'].diff().fillna(0) != 0 # restar los
costos de transacción del retorno cuando se realiza la transacción
data['strategy'][trades] ­= self.tc data[ 'creturns'] = self.cantidad * \

datos['retorno'].cumsum().apply(np.exp)
datos['cstrategy'] = self.cantidad * \
data['strategy'].cumsum().apply(np.exp) self.results = data
# rendimiento absoluto de la
estrategia aperf = self.results['cstrategy'].iloc[­1] #
rendimiento superior/bajo de estrategia operf = aperf ­
self.results['creturns'].iloc[­1] return round(aperf, 2),
round(operf, 2)

si __nombre__ == '__main__': mrbt =


MRVectorBacktester('GDX', '2010­1­1', '2020­12­31', 10000, 0.0)

Secuencias de comandos de Python | 121


Machine Translated by Google

print(mrbt.run_strategy(SMA=25, umbral=5)) mrbt =


MRVectorBacktester('GDX', '2010­1­1', '2020­12­31', 10000, 0.001)

print(mrbt.run_strategy(SMA=25, umbral=5)) mrbt =


MRVectorBacktester('GLD', '2010­1­1', '2020­12­31', 10000, 0.001)

print(mrbt.run_strategy(SMA =42, umbral=7,5))

122 | Capítulo 4: Dominar el backtesting vectorizado


Machine Translated by Google

CAPÍTULO 5

Predecir los movimientos del mercado con


Aprendizaje automático

Skynet comienza a aprender a un ritmo geométrico. Se vuelve consciente de sí mismo a las 2:14 am, hora del
este, del 29 de agosto.

—El Terminator (Terminator 2)

En los últimos años se han producido enormes avances en las áreas de aprendizaje automático,
aprendizaje profundo e inteligencia artificial. La industria financiera en general y los operadores
algorítmicos de todo el mundo en particular también intentan beneficiarse de estos avances tecnológicos.

Este capítulo presenta técnicas de estadística, como la regresión lineal, y de aprendizaje automático,
como la regresión logística, para predecir movimientos futuros de precios en función de rendimientos
pasados. También ilustra el uso de redes neuronales para predecir los movimientos del mercado de
valores. Este capítulo, por supuesto, no puede reemplazar una introducción exhaustiva al aprendizaje
automático, pero puede mostrar, desde el punto de vista de un profesional, cómo aplicar concretamente
ciertas técnicas al problema de predicción de precios. Para más detalles, consulte Hilpisch (2020).1

Este capítulo cubre los siguientes tipos de estrategias comerciales:

Estrategias basadas en regresión lineal


Estas estrategias utilizan la regresión lineal para extrapolar una tendencia o derivar la dirección del
movimiento futuro de precios de un instrumento financiero.

1 Los libros de Guido y Müller (2016) y VanderPlas (2016) proporcionan introducciones prácticas y generales a
Aprendizaje automático con Python.

123
Machine Translated by Google

Estrategias basadas en aprendizaje automático


En el comercio algorítmico generalmente es suficiente predecir la dirección del movimiento de un instrumento
financiero en lugar de la magnitud absoluta de ese movimiento. Con este razonamiento, el problema de predicción
se reduce básicamente a un problema de clasificación que consiste en decidir si habrá un movimiento hacia arriba
o hacia abajo. Se han desarrollado diferentes algoritmos de aprendizaje automático para atacar estos problemas
de clasificación. Este capítulo presenta la regresión logística, como un algoritmo de referencia típico, para la
clasificación.

Estrategias basadas en el aprendizaje profundo

El aprendizaje profundo ha sido popularizado por gigantes tecnológicos como Facebook.


De manera similar a los algoritmos de aprendizaje automático, los algoritmos de aprendizaje profundo basados
en redes neuronales permiten atacar los problemas de clasificación que se enfrentan en la predicción de los
mercados financieros.

El capítulo está organizado de la siguiente forma. “Uso de la regresión lineal para predecir el movimiento del mercado”
en la página 124 presenta la regresión lineal como una técnica para predecir los niveles del índice y la dirección de los
movimientos de precios. “Uso del aprendizaje automático para la predicción del movimiento del mercado” en la página
139 se centra en el aprendizaje automático e introduce scikit­learn sobre la base de la regresión lineal. Cubre
principalmente la regresión logística como un modelo lineal alternativo aplicable explícitamente a problemas de
clasificación. “Uso del aprendizaje profundo para la predicción del movimiento del mercado” en la página 153 presenta
a Keras para predecir la dirección de los movimientos del mercado de valores basándose en algoritmos de redes
neuronales.

El objetivo principal de este capítulo es proporcionar enfoques prácticos para predecir movimientos futuros de precios
en los mercados financieros basándose en rendimientos pasados. El supuesto básico es que la hipótesis del mercado
eficiente no se cumple universalmente y que, similar al razonamiento detrás del análisis técnico de los gráficos de
precios de acciones, la historia podría proporcionar algunas ideas sobre el futuro que pueden extraerse con técnicas
estadísticas. En otras palabras, se supone que ciertos patrones en los mercados financieros se repiten de modo que
las observaciones pasadas pueden aprovecharse para predecir movimientos futuros de precios. Se tratan más detalles
en Hilpisch (2020).

Uso de la regresión lineal para la predicción del movimiento del mercado


Los mínimos cuadrados ordinarios (MCO) y la regresión lineal son técnicas estadísticas de décadas de antigüedad que
han demostrado ser útiles en muchas áreas de aplicación diferentes. Esta sección utiliza la regresión lineal con fines
de predicción de precios. Sin embargo, comienza con una revisión rápida de los conceptos básicos y una introducción
al enfoque básico.

124 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

Una revisión rápida de la regresión lineal


Antes de aplicar la regresión lineal, una revisión rápida del enfoque basada en algunos
los datos aleatorios podrían ser útiles. El código de ejemplo usa NumPy para generar primero un
Objeto ndarray con datos para la variable independiente x. Con base en estos datos, al azar
Se generan datos personalizados (“datos ruidosos”) para la variable dependiente y . NumPy proporciona dos
funciones, polyfit y polyval, para una implementación conveniente de la regresión OLS
basado en monomios simples. Para una regresión lineal, el grado más alto para el mono
Los valores a utilizar se establecen en 1. La Figura 5­1 muestra los datos y la línea de regresión:

En [1]: importar sistema operativo

importar aleatoriamente
importar numpy como
np desde pylab importar mpl, plt
plt.style.use ('seaborn')
mpl.rcParams['savefig.dpi'] = 300
mpl.rcParams['font.family'] = 'serif'
os.environ['PYTHONHASHSEED'] = '0'

En [2]: x = np.linspace(0, 10)

En [3]: def set_seeds(semilla=100):


semilla.aleatoria(semilla)
np.random.seed(semilla)
conjunto_semillas()

En [4]: y = x + np.random.standard_normal(len(x))

En [5]: reg = np.polyfit(x, y, grados=1)

En [6]: reg
Salida [6]: matriz ([0.94612934, 0.22855261])

En [7]: plt.figure(figsize=(10, 6)) plt.plot(x,


y, 'bo', label='data') plt.plot(x, np.polyval(reg,
x), 'r', lw=2,5,
label='regresión lineal')
plt.legend(loc=0);

Importa NumPy.

Importa matplotlib.

Genera una cuadrícula de flotadores espaciados uniformemente para los valores de x entre 0 y 10.

Corrige los valores iniciales para todos los generadores de números aleatorios relevantes.

Genera los datos aleatorios para los valores de y .

Uso de la regresión lineal para la predicción del movimiento del mercado | 125
Machine Translated by Google

Se realiza una regresión MCO de grado 1 (es decir, regresión lineal).

Muestra los valores óptimos de los parámetros.

Crea un nuevo objeto de figura.

Traza el conjunto de datos original como puntos.

Traza la línea de regresión.

Crea la leyenda.

Figura 5­1. Regresión lineal ilustrada basada en datos aleatorios

El intervalo para la variable dependiente x es x 0, 10 . Ampliar el intervalo a, digamos, x 0, 20 permite “predecir”


valores para la variable dependiente y más allá del dominio del conjunto de datos original mediante una
extrapolación dados los parámetros de regresión óptimos. La Figura 5­2 visualiza la extrapolación:

En [8]: plt.figure(figsize=(10, 6)) plt.plot(x,


y, 'bo', label='data') xn = np.linspace(0,
20) plt.plot(xn , np.polyval(reg,
xn), 'r', lw=2.5, label='regresión lineal') plt.legend(loc=0);

Genera un dominio ampliado para los valores de x .

126 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

Figura 5­2. Predicción (extrapolación) basada en regresión lineal

La idea básica para la predicción de precios

La predicción de precios basada en datos de series temporales tiene que abordar una
característica especial: la ordenación de los datos en función del tiempo. Generalmente, el
orden de los datos no es importante para la aplicación de la regresión lineal. En el primer
ejemplo de la sección anterior, los datos sobre los que se implementa la regresión lineal
podrían haberse compilado en ordenes completamente diferentes, manteniendo constantes
los pares x e y . Independientemente del orden, los parámetros de regresión óptimos habrían sido los mismos.

Sin embargo, en el contexto de predecir el nivel del índice de mañana, por ejemplo, parece
ser de suma importancia tener los niveles históricos del índice en el orden correcto. Si este es
el caso, entonces se intentaría predecir el nivel del índice de mañana dado el nivel del índice
de hoy, ayer, anteayer, etc. El número de días utilizados como entrada generalmente se
denomina rezagos. Por lo tanto, utilizar el nivel del índice actual y los dos más anteriores se
traduce en tres rezagos.

El siguiente ejemplo vuelve a colocar esta idea en un contexto bastante simple. Los datos que
utiliza el ejemplo son los números del 0 al 11:

En [9]: x = np.arange(12)

En [10]: x
Fuera[10]: matriz([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])

Supongamos tres rezagos para la regresión. Esto implica tres variables independientes para
la regresión y una dependiente. Más concretamente, 0, 1 y 2 son valores de

Uso de la regresión lineal para la predicción del movimiento del mercado | 127
Machine Translated by Google

variables independientes, mientras que 3 sería el valor correspondiente a la variable dependiente.


Avanzando paso (“en el tiempo”), los valores son 1, 2 y 3, además de 4.
La combinación final de valores es 8, 9 y 10 con 11. El problema, por lo tanto, es plasmar esta idea
formalmente en una ecuación lineal de la forma A ∙ x = b donde A es una matriz y x y b son vectores:

En [11]: rezagos = 3

En [12]: m = np.zeros((retrasos + 1, len(x) ­ retrasos))

En [13]: m[retrasos] = x[retrasos:]


para i en rango(retrasos):
m[i] = x[i:i ­ retrasos]

En [14]: mT
Salida[14]: matriz([[ 0., 1., 2., 3.], [ 1., 2., 3.,
4.], [ 2., 3., 4 ., 5.], [ 3.,
4., 5., 6.], [ 4., 5., 6., 7.],
[ 5., 6., 7., 8.], [ 6 ., 7., 8.,
9.], [ 7., 8., 9., 10.], [ 8.,
9., 10., 11.]])

Define el número de retrasos.

Crea una instancia de un objeto ndarray con las dimensiones adecuadas.

Define los valores objetivo (variable dependiente).

Itera sobre los números desde 0 hasta retrasos ­ 1.

Define los vectores base (variables independientes)

Muestra la transpuesta del objeto ndarray m.

En el objeto ndarray transpuesto m, las primeras tres columnas contienen los valores de las tres
variables independientes. Juntos forman la matriz A. La cuarta y última columna representa el
vector b. Como resultado, la regresión lineal produce el vector faltante x. Como ahora hay más
variables independientes, polifit y polival ya no funcionan. Sin embargo, hay una función en el
subpaquete NumPy para álgebra lineal (linalg) que permite resolver problemas generales de
mínimos cuadrados: lstsq. Sólo se necesita el primer elemento de la matriz de resultados ya que
contiene los parámetros de regresión óptimos:

En [15]: reg = np.linalg.lstsq(m[:lags].T, m[lags], rcond=None)[0]

128 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

En [16]: reg
Salida [16]: matriz ([­0.66666667, 0.33333333, 1.33333333])

En [17]: np.dot(m[:lags].T, reg)


Fuera[17]: matriz([ 3., 4., 5., 6., 7., 8., 9., 10., 11.])

Implementa la regresión lineal MCO.

Imprime los parámetros óptimos.

El producto escalar produce los resultados de la predicción.

Esta idea básica se traslada fácilmente a los datos de series temporales financieras del mundo real.

Predicción de niveles de índice

El siguiente paso es traducir el enfoque básico a los datos de series de tiempo para una situación financiera real.
instrumento, como el tipo de cambio EUR/USD:

En [18]: importar pandas como pd

En [19]: raw = pd.read_csv('http://hilpisch.com/pyalgo_eikon_eod_data.csv',


index_col=0, parse_dates=True).dropna()

En [20]: raw.info() <clase


'pandas.core.frame.DataFrame'>
DatetimeIndex: 2516 entradas, 2010­01­04 al 2019­12­31
Columnas de datos (un total de 12 columnas):
# Columna Tipo D de recuento no nulo
­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 AAPL.O 2516 float64 no nulo


1 MSFT.O 2516 flotante no nulo64
2 INTC.O 2516 flotante no nulo64
3 AMZN.O 2516 flotante no nulo64
4 GS.N 2516 flotador no nulo64
5 ESPÍA 2516 flotador no nulo64
6 .SPX 2516 flotante no nulo64
7 .VIX 2516 flotante no nulo64
8 EUR= 2516 flotación no nula64
9 XAU= 2516 flotante no nulo64
10 GDX 2516 flotador no nulo64
11 GLD 2516 flotador no nulo64
tipos de datos: float64(12)
Uso de memoria: 255,5 KB

En [21]: símbolo = 'EUR='

En [22]: datos = pd.DataFrame(raw[símbolo])

En [23]: data.rename(columnas={símbolo: 'precio'}, inplace=True)

Uso de la regresión lineal para la predicción del movimiento del mercado | 129
Machine Translated by Google

Importa el paquete pandas .

Recupera datos del final del día (EOD) y los almacena en un objeto DataFrame .

Los datos de la serie temporal para el símbolo especificado se seleccionan de los datos originales.
Marco.

Cambia el nombre de la columna única a precio.

Formalmente, apenas es necesario cambiar el código Python del ejemplo simple anterior para implementar el
enfoque de predicción basado en regresión. Sólo es necesario reemplazar el objeto de datos:

En [24]: rezagos = 5

En [25]: cols = [] para


retraso en rango(1, retrasos + 1): col
= f'lag_{lag}' datos[col]
= datos['precio'].shift(lag) cols.append( col)

datos.dropna(inplace=True)

En [26]: reg = np.linalg.lstsq(datos[cols], datos['precio'],


rsegundo=Ninguno)[0]

En [27]: reg
Salida [27]: matriz ([ 0.98635864, 0.02292172, ­0.04769849, 0.05037365,
­0.01208135])

Toma la columna de precios y la desplaza por desfase.

Los parámetros de regresión óptimos ilustran lo que normalmente se denomina hipótesis del paseo aleatorio.
Esta hipótesis establece que los precios de las acciones o los tipos de cambio, por ejemplo, siguen un camino
aleatorio con la consecuencia de que el mejor predictor del precio de mañana es el precio de hoy. Los
parámetros óptimos parecen apoyar tal hipótesis ya que el precio de hoy explica casi por completo el nivel de
precios previsto para mañana. Los otros cuatro valores apenas tienen peso asignado.

La Figura 5­3 muestra el tipo de cambio EUR/USD y los valores previstos. Debido a la gran cantidad de datos
para la ventana temporal de varios años, las dos series temporales son indistinguibles en el gráfico:

En [28]: datos['predicción'] = np.dot(datos[cols], reg)

En [29]: datos[['precio', 'predicción']].plot(figsize=(10, 6));

130 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

Calcula los valores de predicción como el producto escalar .

Traza las columnas de precio y predicción .

Figura 5­3. Tipo de cambio EUR/USD y valores previstos basados en regresión lineal (cinco
rezagos)

Hacer zoom al trazar los resultados para una ventana de tiempo mucho más corta permite distinguir
mejor las dos series de tiempo. La Figura 5­4 muestra los resultados para un período de tres meses.
Este gráfico ilustra que la predicción para la tasa de mañana es aproximadamente la tasa de hoy.
La predicción es más o menos un desplazamiento del tipo original hacia la derecha un día de
negociación:

En [30]: datos[['precio', 'predicción']].loc['2019­10­1':].plot(


tamaño de higo=(10, 6));

La aplicación de la regresión lineal MCO para predecir los tipos del EUR/USD
basándose en los tipos históricos respalda la hipótesis del paseo aleatorio. Los
resultados del ejemplo numérico muestran que la tasa de hoy es el mejor
predictor de la tasa de mañana en mínimos cuadrados.
sentido.

Uso de la regresión lineal para la predicción del movimiento del mercado | 131
Machine Translated by Google

Figura 5­4. Tipo de cambio EUR/USD y valores previstos basados en regresión lineal (cinco rezagos,
solo tres meses)

Predicción de rendimientos futuros

Hasta ahora, el análisis se basa en niveles de tipos absolutos. Sin embargo, los rendimientos (logarítmicos)
podrían ser una mejor opción para este tipo de aplicaciones estadísticas debido, por ejemplo, a su
característica de hacer que los datos de las series temporales sean estacionarios. El código para aplicar la
regresión lineal a los datos de retorno es casi el mismo que antes. Esta vez no sólo el rendimiento de hoy
es relevante para predecir el rendimiento de mañana, sino que los resultados de la regresión también son
de naturaleza completamente diferente:

En [31]: datos['retorno'] = np.log(datos['precio'] /


datos['precio'].shift(1))

En [32]: datos.dropna(inplace=True)

En [33]: cols = [] para


retraso en rango(1, retrasos + 1): col =
f'lag_{lag}' data[col] =
data['return'].shift(lag) cols.append( col)

datos.dropna(inplace=True)

En [34]: reg = np.linalg.lstsq(datos[cols], datos['retorno'],


rsegundo=Ninguno)[0]

En [35]: registro

132 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

Fuera[35]: matriz([­0.015689 , 0.00890227, ­0.03634858, 0.01290924, ­0.00636023])

Calcula los retornos del registro.

Elimina todas las líneas con valores NaN .

Toma la columna de retornos para los datos retrasados.

La Figura 5­5 muestra los datos de retorno y los valores de predicción. Como lo ilustra impresionantemente la
figura, la regresión lineal obviamente no puede predecir la magnitud de los rendimientos futuros en una medida
significativa:

En [36]: datos['predicción'] = np.dot(datos[cols], reg)

En [37]: data[['return', 'prediction']].iloc[lags:].plot(figsize=(10, 6));

Figura 5­5. Rentabilidad logarítmica del EUR/USD y valores previstos basados en regresión lineal (cinco
retrasos)

Desde el punto de vista comercial, se podría argumentar que lo relevante no es la magnitud del rendimiento
previsto, sino más bien si la dirección se pronostica correctamente o no. Para ello, basta con un simple cálculo
para obtener una visión general. Siempre que la regresión lineal toma la dirección correcta, lo que significa que
el signo del rendimiento previsto es correcto, el producto del rendimiento del mercado y el rendimiento previsto
es positivo y, en caso contrario, negativo.

Uso de la regresión lineal para la predicción del movimiento del mercado | 133
Machine Translated by Google

En el caso de ejemplo, la predicción es 1.250 veces correcta y 1.242 veces incorrecta, lo que
se traduce en una tasa de aciertos de alrededor del 49,9%, o casi exactamente el 50%:

En [38]: visitas = np.sign(datos['retorno'] *


datos['predicción']).value_counts()

Entra [39]: golpea


Fuera[39]: 1,0 ­1,0 1250
1242
0.0 13
tipo de letra: int64

En [40]: hits.values[0] / suma(hits)


Fuera[40]: 0.499001996007984

Calcula el producto del mercado y el rendimiento previsto, toma el signo de la


resultados y cuenta los valores.

Imprime los recuentos de los dos valores posibles.

Calcula la tasa de aciertos definida como el número de predicciones correctas dadas todas
predicciones

Predecir la dirección futura del mercado


La pregunta que surge es si se puede mejorar la tasa de aciertos directamente
implementar la regresión lineal basada en el signo de los rendimientos logarítmicos que sirven como
los valores de la variable dependiente. Al menos en teoría, esto simplifica el problema desde el punto de vista previo.
dictando un valor de retorno absoluto al signo del valor de retorno. El único cambio en
el código Python para implementar este razonamiento es utilizar los valores de signo (es decir, 1.0 o
­1.0 en Python) para el paso de regresión. De hecho, esto aumenta el número de visitas a
1.301 y el índice de aciertos a aproximadamente el 51,9%, una mejora de dos puntos porcentuales:

En [41]: reg = np.linalg.lstsq(datos[cols], np.sign(datos['retorno']),


rsegundo=Ninguno)[0]

En [42]: registro
Fuera[42]: matriz([­5.11938725, ­2.24077248, ­5.13080606, ­3.03753232,
­2.14819119])

En [43]: datos['predicción'] = np.sign(np.dot(datos[cols], reg))

En [44]: datos['predicción'].value_counts()
Out[44]: 1.0 ­1.0 1300
1205
Nombre: predicción, tipo d: int64

En [45]: visitas = np.sign(datos['retorno'] *


datos['predicción']).value_counts()

134 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

En [46]: visitas
Fuera[46]: 1,0 ­1,0 1301
1191
0.0 13
tipo de letra: int64

En [47]: hits.values[0] / suma(hits)


Fuera[47]: 0,5193612774451097

Esto utiliza directamente el signo del rendimiento que se va a predecir para la regresión.

Además, para el paso de predicción, sólo el signo es relevante.

Backtesting vectorizado de estrategia basada en regresión


El índice de aciertos por sí solo no dice demasiado sobre el potencial económico de una empresa comercial.
estrategia que utiliza regresión lineal en la forma presentada hasta ahora. Es bien sabido que el
diez mejores y peores días en los mercados durante un período de tiempo determinado influyen considerablemente
influyen en el rendimiento general de las inversiones.2 En un mundo ideal, un operador largo­corto
Intentaría, por supuesto, aprovechar tanto los mejores como los peores días yendo largo y tendido.
respectivamente, sobre la base de indicadores adecuados de sincronización del mercado. Traducido a
En el contexto actual, esto implica que, además del ratio de acierto, la calidad de la
La sincronización del mercado importa. Por lo tanto, un backtesting siguiendo las líneas del enfoque en
El capítulo 4 puede dar una mejor idea del valor de la regresión para la predicción.

Teniendo en cuenta los datos que ya están disponibles, el backtesting vectorizado se reduce a dos líneas
del código Python, incluida la visualización. Esto se debe al hecho de que el valor de predicción
Las cotizaciones ya reflejan las posiciones del mercado (largas o cortas). La Figura 5­6 muestra que, en la
muestra, la estrategia bajo los supuestos actuales supera al mercado.
significativamente (ignorando, entre otras cosas, los costos de transacción):

En [48]: datos.head()
Fuera[48]: precio retraso_1 retraso_2 retraso_3 retraso_4 retraso_5 \
Fecha
2010­01­20 1.4101 ­0.005858 ­0.008309 ­0.000551 0.001103 ­0.001310
2010­01­21 1.4090 ­0.013874 ­0.005858 ­0.008309 ­0.000551 0.001103
2010­01­22 1.4137 ­0.000780 ­0.013874 ­0.005858 ­0.008309 ­0.000551
2010­01­25 1.4150 0.003330 ­0.000780 ­0.013874 ­0.005858 ­0.008309
2010­01­26 1.4073 0.000919 0.003330 ­0.000780 ­0.013874 ­0.005858

predicción devolver
Fecha
2010­01­20 1,0 ­0,013874
2010­01­21 1.0 ­0.000780

2 Véase, por ejemplo, la discusión en El cuento de 10 días.

Uso de la regresión lineal para la predicción del movimiento del mercado | 135
Machine Translated by Google

2010­01­22 1,0 0,003330


2010­01­25 1,0 0,000919
2010­01­26 1,0 ­0,005457

En [49]: datos['estrategia'] = datos['predicción'] * datos['retorno']

En [50]: datos[['retorno', 'estrategia']].sum().apply(np.exp)


Fuera[50]: devuelve 0,784026
estrategia 1.654154
tipo de letra: float64

En [51]: datos[['retorno', 'estrategia']].dropna().cumsum(


).apply(np.exp).plot(figsize=(10, 6));

Multiplica los valores de predicción (posicionamientos) por los retornos del mercado.

Calcula el rendimiento bruto del instrumento base y la estrategia.

Traza el rendimiento bruto del instrumento base y la estrategia a lo largo del tiempo.
(en muestra, sin costos de transacción).

Figura 5­6. Rentabilidad bruta del EUR/USD y la estrategia basada en regresión (cinco rezagos)

136 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

La tasa de acierto de una estrategia basada en predicciones es sólo una cara de la


moneda cuando se trata del rendimiento general de la estrategia. El otro lado es qué
tan bien la estrategia acierta en el momento adecuado del mercado. Una estrategia
que predice correctamente los mejores y peores días durante un determinado período
de tiempo podría superar al mercado incluso con un índice de acierto inferior al 50%.
Por otro lado, una estrategia con una tasa de acierto muy por encima del 50% podría
aún tener un rendimiento inferior al instrumento base si realiza mal los grandes
movimientos raros.

Generalizando el enfoque “Clase de

backtesting de regresión lineal” en la página 167 presenta un módulo de Python que contiene una
clase para el backtesting vectorizado de la estrategia comercial basada en regresión en el espíritu
del Capítulo 4. Además de permitir una cantidad arbitraria para invertir y costos de transacción
proporcionales, permite el ajuste dentro de la muestra del modelo de regresión lineal y la evaluación
fuera de la muestra. Esto significa que el modelo de regresión se ajusta en función de una parte del
conjunto de datos, digamos para los años 2010 a 2015, y se evalúa en base a otra parte del conjunto
de datos, digamos para los años 2016 y 2019. Para todas las estrategias que implican un paso de
optimización o ajuste, esto proporciona una visión más realista del rendimiento en la práctica, ya que
ayuda a evitar los problemas que surgen del espionaje de datos y el sobreajuste de modelos (consulte
también “Espionaje y sobreajuste de datos” en la página 111).

La Figura 5­7 muestra que la estrategia basada en regresión basada en cinco rezagos supera al
instrumento base EUR/USD para la configuración particular también fuera de la muestra y antes de
contabilizar los costos de transacción:

En [52]: importar LRVectorBacktester como LR

En [53]: lrbt = LR.LRVectorBacktester('EUR=', '2010­1­1', '2019­12­31', 10000, 0.0)

En [54]: lrbt.run_strategy('2010­1­1', '2019­12­31', '2010­1­1',


'2019­12­31', retrasos = 5)
Fuera[54]: (17166.53, 9442.42)

En [55]: lrbt.run_strategy('2010­1­1', '2017­12­31', '2018­1­1',


'2019­12­31', retrasos = 5)
Fuera[55]: (10160.86, 791.87)

En [56]: lrbt.plot_results()

Importa el módulo como LR.

Crea una instancia de un objeto de la clase LRVectorBacktester .

Entrena y evalúa la estrategia en el mismo conjunto de datos.

Uso de la regresión lineal para la predicción del movimiento del mercado | 137
Machine Translated by Google

Utiliza dos conjuntos de datos diferentes para los pasos de capacitación y evaluación.

Traza el desempeño de la estrategia fuera de la muestra en comparación con el mercado.

Figura 5­7. Rendimiento bruto del EUR/USD y la estrategia basada en regresión (cinco rezagos, fuera de la
muestra, antes de los costos de transacción)

Considere el ETF de GDX . La configuración de estrategia elegida muestra un rendimiento superior fuera de la
muestra y después de tener en cuenta los costos de transacción (ver Figura 5­8):

En [57]: lrbt = LR.LRVectorBacktester('GDX', '2010­1­1', '2019­12­31', 10000, 0.002)

En [58]: lrbt.run_strategy('2010­1­1', '2019­12­31', '2010­1­1',


'2019­12­31', retrasos = 7)
Fuera[58]: (23642.32, 17649.69)

En [59]: lrbt.run_strategy('2010­1­1', '2014­12­31', '2015­1­1',


'2019­12­31', retrasos = 7)
Fuera[59]: (28513.35, 14888.41)

En [60]: lrbt.plot_results()

Cambios en los datos de la serie temporal para GDX.

138 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

Figura 5­8. Rendimiento bruto del ETF GDX y la estrategia basada en regresión (siete rezagos, fuera
de muestra, después de los costos de transacción)

Uso del aprendizaje automático para predecir el movimiento del mercado

Hoy en día, el ecosistema Python proporciona varios paquetes en el campo del aprendizaje automático.
El más popular de ellos es scikit­learn (consulte la página de inicio de scikit­learn ), que también es uno
de los paquetes mejor documentados y mantenidos. Esta sección presenta primero la API del paquete
basada en regresión lineal, replicando algunos de los resultados de la sección anterior. Luego pasa a
utilizar la regresión logística como algoritmo de clasificación para atacar el problema de predecir la
dirección futura del mercado.

Regresión lineal con scikit­learn Para presentar la

API scikit­learn , es fructífero revisar la idea básica detrás del enfoque de predicción presentado en este
capítulo. La preparación de datos es la misma que con NumPy únicamente:

En [61]: x = np.arange(12)

En [62]: x
Fuera[62]: matriz([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])

En [63]: rezagos = 3

En [64]: m = np.zeros((retrasos + 1, len(x) ­ retrasos))

Uso del aprendizaje automático para la predicción del movimiento del mercado | 139
Machine Translated by Google

En [65]: m[retrasos] = x[retrasos:]


para i en rango (retrasos):
m[i] = x[i:i ­ retrasos]

El uso de scikit­learn para nuestros propósitos consta principalmente de tres pasos:

1. Selección de modelo: se debe seleccionar y crear una instancia de un modelo.

2. Ajuste del modelo: el modelo debe ajustarse a los datos disponibles.

3. Predicción: dado el modelo ajustado, se realiza la predicción.

Para aplicar la regresión lineal, esto se traduce en el siguiente código que hace uso de
el subpaquete linear_model para modelos lineales generalizados (ver scikit­learn lin
página de modelos de orejas). De forma predeterminada, el modelo LinearRegression se ajusta a un valor de intersección:

En [66]: de sklearn importar modelo_lineal

En [67]: lm = linear_model.LinearRegression()

En [68]: lm.fit(m[:retrasos].T, m[retrasos])


Fuera[68]: Regresión lineal()

En [69]: lm.coef_
Salida[69]: matriz([0.33333333, 0.33333333, 0.33333333])

En [70]: lm.intercept_
Salida [70]: 2,0

En [71]: lm.predict(m[:retrasos].T)
Fuera[71]: matriz([ 3., 4., 5., 6., 7., 8., 9., 10., 11.])

Importa las clases de modelo lineal generalizado.

Crea una instancia de un modelo de regresión lineal.

Ajusta el modelo a los datos.

Imprime los parámetros de regresión óptimos.

Imprime los valores de intersección.

Predice los valores buscados dado el modelo ajustado.

Establecer el parámetro fit_intercept en False da exactamente los mismos resultados de regresión


como con NumPy y polyfit():

En [72]: lm = linear_model.LinearRegression(fit_intercept=False)

En [73]: lm.fit(m[:retrasos].T, m[retrasos])

140 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

Salida[73]: Regresión lineal(fit_intercept=False)

En [74]: lm.coef_
Salida[74]: matriz([­0.66666667, 0.33333333, 1.33333333])

En [75]: lm.intercept_ Salida


[75]: 0,0

En [76]: lm.predict(m[:retrasos].T)
Fuera[76]: matriz([ 3., 4., 5., 6., 7., 8., 9., 10., 11.])

Fuerza un ajuste sin valor de intersección.

Este ejemplo ya ilustra bastante bien cómo aplicar scikit­learn al problema de predicción. Debido a su
diseño API consistente, el enfoque básico también se aplica a otros modelos.

Un problema de clasificación simple En un

problema de clasificación, hay que decidir a cuál de un conjunto limitado de categorías (“clases”)
pertenece una nueva observación. Un problema clásico estudiado en el aprendizaje automático es la
identificación de dígitos escritos a mano del 0 al 9. Tal identificación conduce a un resultado correcto,
digamos 3. O conduce a un resultado incorrecto, digamos 6 u 8, donde todos esos resultados
incorrectos están igualmente equivocados. En el contexto de un mercado financiero, predecir el precio
de un instrumento financiero puede llevar a un resultado numérico muy alejado del correcto o
eso está bastante cerca de eso. Al predecir la dirección del mercado de mañana, sólo puede haber un
resultado correcto o (“completamente”) incorrecto. Este último es un problema de clasificación con el
conjunto de categorías limitado a, por ejemplo, "arriba" y "abajo" o "+1" y "­1" o "1" y "0". Por el
contrario, el primer problema es un problema de estimación.

Un ejemplo simple de un problema de clasificación se encuentra en Wikipedia en Regresión logística.


El conjunto de datos relaciona el número de horas estudiadas para prepararse para un examen por
parte de un número de estudiantes con el éxito de cada estudiante al aprobar o no el examen.
Si bien el número de horas estudiadas es un número real ( objeto flotante), la aprobación del examen
es Verdadero o Falso (es decir, 1 o 0 en números). La Figura 5­9 muestra los datos gráficamente:

En [77]: horas = np.array([0.5, 0.75, 1., 1.25, 1.5, 1.75, 1.75, 2., 2.25, 2.5, 2.75, 3., 3.25, 3.5,
4., 4.25, 4.5, 4.75, 5., 5.5])

En [78]: éxito = np.array([0, 0, 0 , 0 , 0 , 0 , 1 , 0, 1 , 0, 1, 0 , 1, 0 , 1, 1, 1, 1, 1 , 1])

En [79]: plt.figure(figsize=(10, 6)) plt.plot(horas,


éxito, 'ro') plt.ylim(­0.2, 1.2);

Uso del aprendizaje automático para la predicción del movimiento del mercado | 141
Machine Translated by Google

El número de horas estudiadas por los diferentes estudiantes (la secuencia importa).

El éxito de cada alumno al aprobar el examen (la secuencia importa).

Traza el conjunto de datos tomando horas como valores de x y éxito como valores de y .

Ajusta los límites del eje y.

Figura 5­9. Datos de ejemplo para el problema de clasificación.

La pregunta básica que normalmente se plantea en tal contexto es: dada una cierta cantidad de horas estudiadas
por un estudiante (no en el conjunto de datos), ¿aprobará el examen o no? ¿Qué respuesta podría dar la regresión
lineal? Probablemente no sea uno que sea satisfactorio, como muestra la Figura 5­10 . Dado un número diferente
de horas estudiadas, la regresión lineal proporciona valores (de predicción) principalmente entre 0 y 1, así como
menores y mayores. Pero sólo puede haber fracaso o éxito como resultado de realizar el examen:

En [80]: reg = np.polyfit(horas, éxito, grados=1)

En [81]: plt.figure(figsize=(10, 6))


plt.plot(horas, éxito, 'ro') plt.plot(horas,
np.polyval(reg, horas), 'b') plt. ylim(­0,2, 1,2);

Implementa una regresión lineal en el conjunto de datos.

Traza la línea de regresión además del conjunto de datos.

142 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

Figura 5­10. Regresión lineal aplicada al problema de clasificación.

Aquí es donde entran en juego los algoritmos de clasificación, como la regresión logística y las
máquinas de vectores de soporte. A modo de ilustración, la aplicación de la regresión logística
es suficiente (ver James et al. (2013, cap. 4) para obtener más información general). La clase
respectiva también se encuentra en el subpaquete linear_model . La Figura 5­11 muestra el
resultado del siguiente código Python. Esta vez, hay un valor claro (predicción) para cada valor
de entrada diferente. El modelo predice que los estudiantes que estudiaron de 0 a 2 horas
reprobarán. Para todos los valores iguales o superiores a 2,75 horas, el modelo predice que
un estudiante aprueba el examen:

En [82]: lm = linear_model.LogisticRegression(solver='lbfgs')

En [83]: horas = horas.reshape(1, ­1).T

En [84]: lm.fit(horas, éxito)


Fuera[84]: Regresión logística()

En [85]: predicción = lm.predict(horas)

En [86]: plt.figure(figsize=(10, 6))


plt.plot(horas, éxito, 'ro', etiqueta='datos') plt.plot(horas,
predicción, 'b', etiqueta=' predicción') plt.legend(loc=0)
plt.ylim(­0.2, 1.2);

Uso del aprendizaje automático para la predicción del movimiento del mercado | 143
Machine Translated by Google

Crea una instancia del modelo de regresión logística.

Cambia la forma del objeto ndarray unidimensional a uno bidimensional (requerido por scikit­learn).

Implementa el paso de adaptación.

Implementa el paso de predicción dado el modelo ajustado.

Figura 5­11. Regresión logística aplicada al problema de clasificación.

Sin embargo, como muestra la Figura 5­11 , no hay garantía de que 2,75 horas o más conduzcan al éxito.
Simplemente es “más probable” tener éxito después de tantas horas que fracasar.
Este razonamiento probabilístico también se puede analizar y visualizar en función de la misma instancia del
modelo, como lo ilustra el siguiente código. La línea discontinua en la Figura 5­12 muestra la probabilidad de
tener éxito (aumentando monótonamente). La línea de puntos y guiones muestra la probabilidad de fallar
(decreciente monótonamente):

En [87]: prob = lm.predict_proba(horas)

En [88]: plt.figure(figsize=(10, 6)) plt.plot(horas,


éxito, 'ro') plt.plot(horas, predicción,
'b') plt.plot(horas, prob.T [0], 'm­­',
label='$p(h)$ para cero') plt.plot(horas,
prob.T[1], 'g­.', label='$p(h) $
por uno')

144 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

plt.ylim(­0.2, 1.2)
plt.legend(loc=0);

Predice probabilidades de éxito y fracaso, respectivamente.

Traza las probabilidades de fallar.

Traza las probabilidades de éxito.

Figura 5­12. Probabilidades de éxito y fracaso, respectivamente, basadas en regresión


logística

scikit­learn hace un buen trabajo al brindar acceso a una gran variedad


de modelos de aprendizaje automático de forma unificada. Los
ejemplos muestran que la API para aplicar la regresión logística no
difiere de la de la regresión lineal. scikit­learn, por lo tanto, es muy
adecuado para probar una serie de modelos de aprendizaje automático
apropiados en un determinado escenario de aplicación sin alterar
mucho el código Python.

Equipado con los conceptos básicos, el siguiente paso es aplicar la regresión logística al problema
de predecir la dirección del mercado.

Uso del aprendizaje automático para la predicción del movimiento del mercado | 145
Machine Translated by Google

Uso de la regresión logística para predecir la dirección del mercado En el aprendizaje

automático, generalmente se habla de características en lugar de variables independientes o explicativas como en un


contexto de regresión. El ejemplo de clasificación simple tiene una única característica: el número de horas estudiadas.
En la práctica, a menudo uno tiene más de una característica que puede usarse para la clasificación. Dado el enfoque de
predicción presentado en este capítulo, se puede identificar una característica por un retraso. Por lo tanto, trabajar con
tres rezagos de los datos de series temporales significa que hay tres características. Como posibles resultados o
categorías, sólo hay +1 y ­1 para un movimiento ascendente y descendente, respectivamente. Aunque la redacción
cambia, el formalismo sigue siendo el mismo, particularmente con respecto a la derivación de la matriz, ahora llamada
matriz de características.

El siguiente código presenta una alternativa a la creación de una “matriz de características” basada en
Pandas DataFrame a la que el procedimiento de tres pasos se aplica igualmente bien, si no de una
manera más pitónica. La matriz de características ahora es un subconjunto de las columnas del conjunto
de datos original:

En [89]: símbolo = 'GLD'

En [90]: datos = pd.DataFrame(raw[símbolo])

En [91]: data.rename(columnas={símbolo: 'precio'}, inplace=True)

En [92]: datos['retorno'] = np.log(datos['precio'] / datos['precio'].shift(1))

En [93]: datos.dropna(inplace=True)

En [94]: rezagos = 3

En [95]: cols = [] para


retraso en rango(1, retrasos + 1): col
= 'lag_{}'.format(lag) data[col] =
data['return'].shift(lag) cols .añadir(col)

En [96]: datos.dropna(inplace=True)

Crea una instancia de un objeto de lista vacío para recopilar nombres de columnas.

Crea un objeto str para el nombre de la columna.

Agrega una nueva columna al objeto DataFrame con los datos de retraso respectivos.

Agrega el nombre de la columna al objeto de lista .

Se asegura de que el conjunto de datos esté completo.

146 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

La regresión logística mejora la tasa de aciertos en comparación con la regresión lineal en más de
un punto porcentual hasta alrededor del 54,5%. La Figura 5­13 muestra el desempeño de la estrategia.
basado en predicciones basadas en regresión logística. Aunque la proporción de aciertos es mayor, el
el rendimiento es peor que con la regresión lineal:

En [97]: desde sklearn.metrics importa precision_score

En [98]: lm = linear_model.LogisticRegression(C=1e7, solver='lbfgs',


multi_class='automático',
iter_máx=1000)

En [99]: lm.fit(datos[cols], np.sign(datos['return']))


Fuera[99]: Regresión logística(C=10000000.0, max_iter=1000)

En [100]: datos['predicción'] = lm.predict(datos[cols])

En [101]: datos['predicción'].value_counts()
Fuera[101]: 1,0 1983
­1.0 529
Nombre: predicción, tipo d: int64

En [102]: hits = np.sign(data['return'].iloc[lags:] *


datos['predicción'].iloc[lags:]
).value_counts()

En [103]: visitas
Fuera[103]: 1,0 1338
­1.0 1159
0.0 12
tipo de letra: int64

En [104]: exactitud_score(datos['predicción'],
np.sign(datos['retorno']))
Fuera[104]: 0,5338375796178344

En [105]: datos['estrategia'] = datos['predicción'] * datos['retorno']

En [106]: datos[['retorno', 'estrategia']].sum().apply(np.exp)


Fuera[106]: estrategia 1.289478
de retorno 2.458716
tipo de letra: float64

En [107]: datos[['retorno', 'estrategia']].cumsum().apply(np.exp).plot(


tamaño de higo=(10, 6));

Crea una instancia del objeto modelo usando un valor C que le da menos peso a la regularidad.
término de zación (consulte la página Modelos lineales generalizados).

Se ajusta al modelo en función del signo de los rendimientos a predecir.

Uso del aprendizaje automático para la predicción del movimiento del mercado | 147
Machine Translated by Google

Genera una nueva columna en el objeto DataFrame y escribe en ella los valores de predicción.

Muestra el número de posiciones largas y cortas resultantes, respectivamente.

Calcula el número de predicciones correctas e incorrectas.

La precisión (índice de aciertos) es en este caso del 53,3%.

Sin embargo, el desempeño bruto de la estrategia…

…es mucho mayor en comparación con la inversión pasiva de referencia.

Figura 5­13. Rendimiento bruto de GLD ETF y la estrategia basada en regresión logística (3 rezagos, dentro de
la muestra)

Aumentar el número de rezagos utilizados de tres a cinco disminuye la tasa de aciertos, pero mejora en cierta
medida el desempeño bruto de la estrategia (en la muestra, antes de los costos de transacción). La Figura 5­14
muestra el rendimiento resultante:

En [108]: datos = pd.DataFrame(raw[símbolo])

En [109]: data.rename(columnas={símbolo: 'precio'}, inplace=True)

En [110]: datos['retorno'] = np.log(datos['precio'] / datos['precio'].shift(1))

En [111]: rezagos = 5

148 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

En [112]: columnas = []
para retraso en el rango (1, retrasos + 1):
col = 'lag_%d' % retraso
datos[col] = datos['precio'].shift(lag)
cols.append(col)

En [113]: datos.dropna(inplace=True)

En [114]: lm.fit(datos[cols], np.sign(datos['return']))


Fuera[114]: Regresión logística(C=10000000.0, max_iter=1000)

En [115]: datos['predicción'] = lm.predict(datos[cols])

En [116]: datos['predicción'].value_counts()
Fuera[116]: 1,0 2047
­1.0 464
Nombre: predicción, tipo d: int64

En [117]: hits = np.sign(data['return'].iloc[lags:] *


datos['predicción'].iloc[lags:]
).value_counts()

En [118]: visitas
Fuera[118]: 1,0 ­1,0 1331
1163
0.0 12
tipo de letra: int64

En [119]: exactitud_score(datos['predicción'],
np.sign(datos['retorno']))
Fuera[119]: 0,5312624452409399

En [120]: datos['estrategia'] = datos['predicción'] * datos['retorno']

En [121]: datos[['retorno', 'estrategia']].sum().apply(np.exp)


Fuera[121]: regresar 1.283110
estrategia 2.656833
tipo de letra: float64

En [122]: datos[['retorno', 'estrategia']].cumsum().apply(np.exp).plot(


tamaño de higo=(10, 6));

Aumenta el número de rezagos a cinco.

Se adapta al modelo basado en cinco rezagos.

Con la nueva parametrización ahora hay muchas más posiciones cortas.

La precisión (índice de aciertos) disminuye al 53,1%.

El rendimiento acumulado también aumenta significativamente.

Uso del aprendizaje automático para la predicción del movimiento del mercado | 149
Machine Translated by Google

Figura 5­14. Rendimiento bruto de GLD ETF y la estrategia basada en regresión logística (cinco
rezagos, dentro de la muestra)

Hay que tener cuidado de no caer en la trampa del sobreajuste. Se obtiene


una imagen más realista mediante un enfoque que utiliza datos de
entrenamiento (= datos dentro de la muestra) para el ajuste del modelo y
datos de prueba (= datos fuera de la muestra) para la evaluación del desempeño de la estrategia.
Esto se hace en la siguiente sección, cuando el enfoque se generaliza
nuevamente en la forma de una clase de Python.

Generalización del enfoque “Clase de

backtesting de algoritmos de clasificación” en la página 170 presenta un módulo de Python con una clase
para el backtesting vectorizado de estrategias basadas en modelos lineales de scikit­learn. Aunque sólo se
implementan regresión lineal y logística, el número de modelos se incrementa fácilmente. En principio, la
clase ScikitVectorBacktester podría heredar métodos seleccionados de LRVectorBacktester , pero se
presenta de forma autónoma. Esto hace que sea más fácil mejorar y reutilizar esta clase para aplicaciones
prácticas.

Basado en ScikitBacktesterClass, es posible una evaluación fuera de muestra de la estrategia basada en


regresión logística. El ejemplo utiliza el tipo de cambio EUR/USD como instrumento base.

150 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

La Figura 5­15 ilustra que la estrategia supera al instrumento base durante el período fuera de la
muestra (que abarca el año 2019), sin embargo, sin considerar los costos de transacción como antes:

En [123]: importe ScikitVectorBacktester como SCI

En [124]: scibt = SCI.ScikitVectorBacktester('EUR=', '2010­1­1',


'2019­12­31', 10000, 0.0,
'logístico')

En [125]: scibt.run_strategy('2015­1­1', '2019­12­31', '2015­1­1',


'2019­12­31', retrasos=15)
Fuera[125]: (12192.18, 2189.5)

En [126]: scibt.run_strategy('2016­1­1', '2018­12­31', '2019­1­1',


'2019­12­31', retrasos=15)
Fuera[126]: (10580.54, 729.93)

En [127]: scibt.plot_results()

Figura 5­15. Rendimiento bruto del S&P 500 y la estrategia basada en regresión logística fuera
de muestra (15 rezagos, sin costos de transacción)

Como otro ejemplo, considere la misma estrategia aplicada al ETF GDX , para el cual se muestra un
rendimiento superior fuera de la muestra (durante el año 2018) en la Figura 5­16 (antes de costos de
transacción):

En [128]: scibt = SCI.ScikitVectorBacktester('GDX', '2010­1­1',


'2019­12­31', 10000, 0.00,
'logístico')

Uso del aprendizaje automático para la predicción del movimiento del mercado | 151
Machine Translated by Google

En [129]: scibt.run_strategy('2013­1­1', '2017­12­31', '2018­1­1',


'2018­12­31', retrasos=10)
Fuera[129]: (12686.81, 4032.73)

En [130]: scibt.plot_results()

Figura 5­16. Rendimiento bruto de GDX ETF y la estrategia basada en regresión logística (10
retrasos, fuera de muestra, sin costos de transacción)

La Figura 5­17 muestra cómo el rendimiento bruto disminuye (llevando incluso a una pérdida neta)
cuando se tienen en cuenta los costos de transacción, manteniendo todos los demás parámetros.
constante:

En [131]: scibt = SCI.ScikitVectorBacktester('GDX', '2010­1­1',


'2019­12­31', 10000, 0.0025,
'logístico')

En [132]: scibt.run_strategy('2013­1­1', '2017­12­31', '2018­1­1',


'2018­12­31', retrasos=10)
Fuera[132]: (9588.48, 934.4)

En [133]: scibt.plot_results()

152 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

Figura 5­17. Rendimiento bruto de GDX ETF y la estrategia basada en regresión logística (10
rezagos, fuera de muestra, con costos de transacción)

La aplicación de técnicas sofisticadas de aprendizaje automático a la predicción


del mercado de valores a menudo produce resultados prometedores desde el
principio. En varios ejemplos, las estrategias respaldadas superan
significativamente al instrumento base dentro de la muestra. Muy a menudo,
estos desempeños estelares se deben a una combinación de supuestos
simplificadores y también a un sobreajuste del modelo de predicción. Por
ejemplo, probar la misma estrategia en lugar de hacerlo dentro de la muestra en
un conjunto de datos fuera de la muestra y agregar costos de transacción (como
dos formas de obtener una imagen más realista) a menudo muestra que el
desempeño de la estrategia considerada " repentinamente” sigue el desempeño
del instrumento base o se convierte en una pérdida neta.

Uso del aprendizaje profundo para la predicción del movimiento del mercado

Desde el código abierto y la publicación de Google, la biblioteca de aprendizaje profundo TensorFlow


ha atraído mucho interés y una amplia aplicación. Esta sección aplica TensorFlow de la misma
manera que la sección anterior aplicó scikit­learn a la predicción de movimientos del mercado de
valores modelados como un problema de clasificación.
Sin embargo, TensorFlow no se utiliza directamente; más bien se utiliza a través del igualmente
popular paquete de aprendizaje profundo Keras . Se puede considerar que Keras proporciona una
abstracción de nivel superior al paquete TensorFlow con una API más fácil de entender y usar.

Uso del aprendizaje profundo para la predicción del movimiento del mercado | 153
Machine Translated by Google

Las bibliotecas se instalan mejor mediante pip install tensorflow y pip install keras. scikit­learn
también ofrece clases para aplicar redes neuronales a problemas de clasificación.

Para obtener más información general sobre el aprendizaje profundo y Keras, consulte Goodfellow
et al. (2016) y Chollet (2017), respectivamente.

El problema de la clasificación simple revisado


Para ilustrar el enfoque básico de la aplicación de redes neuronales a problemas de clasificación, el
problema de clasificación simple presentado en la sección anterior vuelve a resultar útil:

En [134]: horas = np.array([0.5, 0.75, 1., 1.25, 1.5, 1.75, 1.75, 2., 2.25, 2.5, 2.75, 3., 3.25, 3.5, 4., 4.25,
4.5, 4.75, 5., 5.5])

En [135]: éxito = np.array([0, 0, 0 , 0, 0, 0, 1 , 0, 1, 0 , 1, 0 , 1, 0, 1 , 1 , 1 , 1 , 1 , 1])

En [136]: datos = pd.DataFrame({'horas': horas, 'éxito': éxito})

En [137]: data.info() <clase


'pandas.core.frame.DataFrame'> RangeIndex: 20
entradas, 0 a 19 columnas de datos ( 2
columnas en total):
# Columna Tipo D de recuento no nulo
­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 horas 20 no nulos 1 éxito 20 tipos flotador64


no nulos: float64(1), int64(1) uso de int64
memoria: 448,0 bytes

Almacena los dos subconjuntos de datos en un objeto DataFrame .

Imprime la metainformación del objeto DataFrame .

Con estos preparativos, el MLPClassifier de scikit­learn se puede importar y aplicar directamente.3


“MLP” en este contexto significa perceptrón multicapa, que es otra expresión para red neuronal
densa. Como antes, la API para aplicar redes neuronales con scikit­learn es básicamente la misma:

En [138]: desde sklearn.neural_network importe MLPClassifier

En [139]: modelo = MLPClassifier(hidden_layer_sizes=[32], max_iter=1000,


random_state=100)

3 Para más detalles, consulte https://oreil.ly/hOwsE.

154 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

Importa el objeto MLPClassifier de scikit­learn.

Crea una instancia del objeto MLPClassifier .

El siguiente código se ajusta al modelo, genera las predicciones y traza los resultados, como
como se muestra en la Figura 5­18:

En [140]: model.fit(datos['horas'].valores.reshape(­1, 1), datos['éxito'])


Fuera[140]: MLPClassifier(hidden_layer_sizes=[32], max_iter=1000,
estado_aleatorio=100)

En [141]: datos['predicción'] = model.predict(datos['horas'].valores.reshape(­1, 1))

En [142]: datos.tail()
Out[142]: horas de predicción de éxito
15 4.25 1 1
16 4,50 17 1
4,75 11 1
18 5.00 1 1
19 5,50 1 1

En [143]: data.plot(x='horas', y=['éxito', 'predicción'],


estilo=['ro', 'b­'], ylim=[­.1, 1.1],
tamaño de higo=(10, 6));

Se adapta a la red neuronal para su clasificación.

Genera los valores de predicción basados en el modelo ajustado.

Traza los datos originales y los valores de predicción.

Este sencillo ejemplo muestra que la aplicación del enfoque de aprendizaje profundo es bastante
similar al enfoque con scikit­learn y el modelo LogisticRegression
objeto. La API es básicamente la misma; Sólo los parámetros son diferentes.

Uso del aprendizaje profundo para la predicción del movimiento del mercado | 155
Machine Translated by Google

Figura 5­18. Base de datos y resultados de predicción con MLPClassifier para la clasificación simple.
ejemplo de ficación

Uso de redes neuronales profundas para predecir la dirección del mercado

El siguiente paso es aplicar el enfoque a los datos del mercado de valores en forma de rendimientos logarítmicos.
de una serie de tiempo financiera. Primero, es necesario recuperar y preparar los datos:

En [144]: símbolo = 'EUR='

En [145]: datos = pd.DataFrame(raw[símbolo])

En [146]: data.rename(columnas={símbolo: 'precio'}, inplace=True)

En [147]: datos['retorno'] = np.log(datos['precio'] /


datos['precio'].shift(1))

En [148]: datos['dirección'] = np.donde(datos['retorno'] > 0, 1, 0)

En [149]: rezagos = 5

En [150]: columnas = []
para retraso en el rango (1, retrasos
+ 1): col = f'lag_{lag}'
datos[col] = datos['retorno'].shift(lag)
cols.append(col)
datos.dropna(en el lugar=Verdadero)

156 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

En [151]: datos.round(4).tail()
Fuera[151]:
precio dirección de retorno lag_1 lag_2 lag_3 lag_4 lag_5
Fecha
2019­12­24 1.1087 0.0001 2019­12­26 1 0,0007 ­0,0038 0,0008 ­0,0034 0,0006 1 0,0001 0,0007
1.1096 0.0008 ­0,0038 0,0008 ­0,0034
2019­12­27 1.1175 0.0071 1 0,0008 0,0001 0,0007 ­0,0038 0,0008
2019­12­30 1.1197 0.0020 1 0,0071 0,0008 0,0001 0,0007 ­0,0038
2019­12­31 1.1210 0.0012 1 0,0020 0,0071 0,0008 0,0001 0,0007

Lee los datos del archivo CSV .

Selecciona la columna de serie temporal única de interés.

Cambia el nombre de la única columna a precio.

Calcula los retornos del registro y define la dirección como una columna binaria.

Crea los datos retrasados.

Crea nuevas columnas de DataFrame con los retornos de registro desplazados por el número respectivo de
retrasos.

Elimina filas que contienen valores NaN .

Imprime las últimas cinco filas que indican los "patrones" que emergen en las cinco columnas de
características.

El siguiente código utiliza una red neuronal densa (DNN) con el paquete Keras4 , define subconjuntos de datos de
prueba y entrenamiento, define las columnas de características y etiqueta y ajusta el clasificador. En el backend,
Keras utiliza el paquete TensorFlow para realizar la tarea. La Figura 5­19 muestra cómo cambia la precisión del
clasificador DNN para los conjuntos de datos de entrenamiento y validación durante el entrenamiento. Como
conjunto de datos de validación, se utiliza el 20% de los datos de entrenamiento (sin barajar):

En [152]: importar tensorflow como tf


de keras.models importa Secuencial de keras.layers
importa Denso de keras.optimizers importa
Adam, RMSprop

En [153]: optimizador = Adam (tasa_de aprendizaje = 0,0001)

En [154]: def set_seeds(semilla=100):


semilla.aleatoria(semilla)
np.semilla.aleatoria(semilla)

4 Para obtener más información, consulte https://keras.io/layers/core/.

Uso del aprendizaje profundo para la predicción del movimiento del mercado | 157
Machine Translated by Google

tf.random.set_seed(100)

En [155]: set_seeds()
modelo = Sequential()
model.add(Dense(64, activación='relu',
input_shape=(lags,)))
model.add(Dense(64, activación='relu'))
model.add(Dense(1, activación='sigmoid'))
model.compile(optimizer=optimizer,
pérdida = 'binary_crossentropy',
métricas = ['precisión'])

En [156]: corte = '2017­12­31'

En [157]: datos_entrenamiento = datos[datos.índice < corte].copiar()

En [158]: mu, std = Training_data.mean(), Training_data.std()

En [159]: datos_entrenamiento_ = (datos_entrenamiento ­ mu) / std

En [160]: test_data = datos[datos.index >= corte].copiar()

En [161]: test_data_ = (test_data ­ mu) / std

En [162]: %%tiempo
model.fit(training_data[cols],
datos_entrenamiento['dirección'],
épocas = 50, detallado = Falso,
validación_split=0.2, shuffle=Falso)
Tiempos de CPU: usuario 4,86 s, sistema: 989 ms, total: 5,85 s
Tiempo de pared: 3,34 s

Fuera[162]: <tensorflow.python.keras.callbacks.History en 0x7f996a0a2880>

En [163]: res = pd.DataFrame(modelo.historia.historia)

En [164]: res[['accuracy', 'val_accuracy']].plot(figsize=(10, 6), style='­­');

Importa el paquete TensorFlow .

Importa el objeto modelo requerido de Keras.

Importa el objeto de capa relevante de Keras.

Se crea una instancia de un modelo secuencial .

Se definen las capas ocultas y la capa de salida.

Compila el objeto del modelo secuencial para su clasificación.

158 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

Define la fecha límite entre los datos de entrenamiento y prueba.

Define los conjuntos de datos de entrenamiento y prueba.

Normaliza los datos de las características mediante la normalización gaussiana.

Ajusta el modelo al conjunto de datos de entrenamiento.

Figura 5­19. Precisión del clasificador DNN en datos de entrenamiento y validación por paso de entrenamiento

Equipado con el clasificador ajustado, el modelo puede generar predicciones sobre el conjunto de datos de entrenamiento. La

Figura 5­20 muestra el desempeño bruto de la estrategia en comparación con el instrumento base (en la muestra):

En [165]: model.evaluate(training_data_[cols], Training_data['dirección'])


63/63 [==============================] ­ 0s 586us/paso ­ pérdida: 0,7556 ­ precisión:
0,5152

Fuera[165]: [0.7555528879165649, 0.5151968002319336]

En [166]: pred = np.where(model.predict(training_data_[cols]) > 0.5, 1, 0)

En [167]: pred[:30].aplanar()
Fuera[167]: matriz([0, 0, 0, 0, 0, 1 , 1 , 1, 1 , 0 , 0 , 0 , 1, 1, 1, 0 , 0 , 0, 1, 1 , 0, 0, 0, 1 , 0, 1, 0, 1, 0,
0])

En [168]: datos_entrenamiento['predicción'] = np.where(pred > 0, 1, ­1)

Uso del aprendizaje profundo para la predicción del movimiento del mercado | 159
Machine Translated by Google

En [169]: datos_entrenamiento['estrategia'] = (datos_entrenamiento['predicción'] *


datos_entrenamiento['retorno'])

En [170]: datos_entrenamiento[['retorno', 'estrategia']].sum().apply(np.exp)


Fuera[170]: retorno 0.826569
tipo de 1.317303
estrategia: float64

En [171]: datos_entrenamiento[['retorno', 'estrategia']].cumsum(


).apply(np.exp).plot(figsize=(10, 6));

Predice la dirección del mercado en la muestra.

Transforma las predicciones en posiciones largas­cortas, +1 y ­1.

Calcula los rendimientos de la estrategia dadas las posiciones.

Traza y compara el desempeño de la estrategia con el desempeño de referencia (en la muestra).

Figura 5­20. Rendimiento bruto del EUR/USD en comparación con la estrategia basada en aprendizaje profundo
egy (en muestra, sin costos de transacción)

La estrategia parece funcionar algo mejor que el instrumento base en el


conjunto de datos de entrenamiento (en muestra, sin costos de transacción). Sin embargo, cuanto más interés
La pregunta más importante es cómo se desempeña en el conjunto de datos de prueba (fuera de muestra). Después de un tambaleo

Desde el principio, la estrategia también supera al instrumento base fuera de la muestra, como se muestra en la Figura 5­21.

160 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

ilustra. Esto a pesar de que la precisión del clasificador es sólo ligeramente superior al 50% en el conjunto
de datos de prueba:

En [172]: model.evaluate(test_data_[cols], test_data['dirección'])


16/16 [===============================] ­ 0s 676us/paso ­ pérdida: 0,7292 ­
precisión: 0,5050

Fuera[172]: [0.7292129993438721, 0.5049701929092407]

En [173]: pred = np.where(model.predict(test_data_[cols]) > 0.5, 1, 0)

En [174]: test_data['predicción'] = np.where(pred > 0, 1, ­1)

En [175]: test_data['predicción'].value_counts()
Fuera[175]: ­1 1 368
135
Nombre: predicción, tipo d: int64

En [176]: test_data['estrategia'] = (test_data['prediction'] * test_data['return'])

En [177]: test_data[['retorno', 'estrategia']].sum().apply(np.exp)


Salida [177]: retorno 0.934478 1.109065 tipo de
float64 estrategia:

En [178]: test_data[['return',
'strategy']].cumsum( ).apply(np.exp).plot(figsize=(10, 6));

Figura 5­21. Rendimiento bruto del EUR/USD en comparación con la estrategia basada en aprendizaje
profundo (fuera de muestra, sin costos de transacción)

Uso del aprendizaje profundo para la predicción del movimiento del mercado | 161
Machine Translated by Google

Agregar diferentes tipos de funciones


Hasta ahora, el análisis se centra principalmente en los retornos de registros directamente. Por supuesto, es posible
no sólo para agregar más clases/categorías sino también para agregar otros tipos de características al
mezcla, como las basadas en medidas de impulso, volatilidad o distancia. El código que
A continuación se derivan las características adicionales y se agregan al conjunto de datos:

En [179]: datos['momentum'] = datos['return'].rolling(5).mean().shift(1)

En [180]: datos['volatilidad'] = datos['retorno'].rolling(20).std().shift(1)

En [181]: datos['distancia'] = (datos['precio'] ­


datos['precio'].rolling(50).media()).shift(1)

En [182]: datos.dropna(inplace=True)

En [183]: cols.extend(['impulso', 'volatilidad', 'distancia'])

En [184]: imprimir(datos.ronda(4).cola())

precio dirección de retorno lag_1 lag_2 lag_3 lag_4 retraso_5


Fecha

2019­12­24 1.1087 0.0001 1 0,0007 ­0,0038 0,0008 ­0,0034 0,0006


2019­12­26 1.1096 0.0008 1 0,0001 0,0007 ­0,0038 0,0008 ­0,0034
2019­12­27 1.1175 0.0071 1 0,0008 0,0001 0,0007 ­0,0038 0,0008
2019­12­30 1.1197 0.0020 1 0,0071 0,0008 0,0001 0,0007 ­0,0038
2019­12­31 1.1210 0.0012 1 0,0020 0,0071 0,0008 0,0001 0,0007

distancia de volatilidad del impulso


Fecha
2019­12­24 ­0.0010 0.0024 0.0005
2019­12­26 ­0.0011 0,0024 0.0004
2019­12­27 ­0.0003 0,0024 0.0012
2019­12­30 0.0010 0.0028 0.0089
2019­12­31 0.0021 0.0028 0.0110

La característica basada en el impulso.

La característica basada en la volatilidad.

La característica basada en la distancia.

Los próximos pasos son redefinir los conjuntos de datos de entrenamiento y prueba para normalizar las características.
datos y actualizar el modelo para reflejar las nuevas columnas de características:

En [185]: datos_entrenamiento = datos[datos.índice < corte].copiar()

En [186]: mu, std = Training_data.mean(), Training_data.std()

En [187]: datos_entrenamiento_ = (datos_entrenamiento ­ mu) / std

162 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

En [188]: test_data = datos[datos.index >= corte].copiar()

En [189]: test_data_ = (test_data ­ mu) / std

En [190]: set_seeds() modelo


= Sequential()
model.add(Dense(32, activación='relu',
input_shape=(len(cols),)))
model.add(Dense(32, activación='relu ')) model.add(Dense(1,
activación='sigmoide')) model.compile(optimizador=optimizador,
pérdida='binary_crossentropy', metrics=['accuracy'])

El parámetro input_shape se ajusta para reflejar la nueva cantidad de funciones.

Según el conjunto de funciones enriquecido, se puede entrenar el clasificador. El rendimiento de la


estrategia dentro de la muestra es bastante mejor que antes, como se ilustra en la Figura 5­22:

En [191]: %%tiempo
model.fit(training_data_[cols], Training_data['dirección'], detallado=False, épocas=25)

Tiempos de CPU: usuario 2,32 s, sistema: 577 ms, total: 2,9 s Tiempo de
pared: 1,48 s

Fuera[191]: <tensorflow.python.keras.callbacks.History en 0x7f996d35c100>

En [192]: model.evaluate(training_data_[cols], Training_data['dirección'])


62/62 [==============================] ­ 0s 649us/paso ­ pérdida: 0,6816 ­ precisión: 0,5646

Fuera[192]: [0.6816270351409912, 0.5646397471427917]

En [193]: pred = np.where(model.predict(training_data_[cols]) > 0.5, 1, 0)

En [194]: datos_entrenamiento['predicción'] = np.where(pred > 0, 1, ­1)

En [195]: datos_entrenamiento['estrategia'] = (datos_entrenamiento['predicción'] *


datos_entrenamiento['retorno'])

En [196]: datos_entrenamiento[['retorno', 'estrategia']].sum().apply(np.exp)


Salida [196]: retorno 0.901074 2.703377 tipo de
float64 estrategia:

En [197]: datos_entrenamiento[['retorno',
'estrategia']].cumsum( ).apply(np.exp).plot(figsize=(10, 6));

Uso del aprendizaje profundo para la predicción del movimiento del mercado | 163
Machine Translated by Google

Figura 5­22. Rendimiento bruto del EUR/USD en comparación con la estrategia basada en aprendizaje profundo
(en la muestra, características adicionales)

El paso final es la evaluación del clasificador y la derivación del desempeño de la estrategia fuera de la muestra.
El clasificador también funciona significativamente mejor, ceteris paribus, en comparación con el caso sin
características adicionales. Como antes, el comienzo es un poco inestable (ver Figura 5­23):

En [198]: model.evaluate(test_data_[cols], test_data['dirección'])


16/16 [==============================] ­ 0s 800us/paso ­ pérdida: 0,6931 ­ precisión:
0,5507

Fuera[198]: [0.6931276321411133, 0.5506958365440369]

En [199]: pred = np.where(model.predict(test_data_[cols]) > 0.5, 1, 0)

En [200]: test_data['predicción'] = np.where(pred > 0, 1, ­1)

En [201]: test_data['predicción'].value_counts()
Fuera[201]: ­1 1 335
168
Nombre: predicción, tipo d: int64

En [202]: test_data['estrategia'] = (test_data['prediction'] * test_data['return'])

En [203]: test_data[['retorno', 'estrategia']].sum().apply(np.exp)


Salida [203]: estrategia 0.934478
de retorno 1.144385
tipo d: float64

164 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

En [204]: test_data[['return',
'strategy']].cumsum( ).apply(np.exp).plot(figsize=(10, 6));

Figura 5­23. Rendimiento bruto del EUR/USD en comparación con la estrategia basada en aprendizaje
profundo (fuera de muestra, características adicionales)

El paquete Keras , en combinación con el paquete TensorFlow como backend, permite utilizar los avances
más recientes en aprendizaje profundo, como los clasificadores de redes neuronales profundas (DNN), para
el comercio algorítmico. La aplicación es tan sencilla como aplicar otros modelos de aprendizaje automático
con scikit­learn. El enfoque ilustrado en esta sección permite una mejora sencilla con respecto a los diferentes
tipos de funciones utilizadas.

Como ejercicio, vale la pena codificar una clase de Python (en el espíritu
de la “Clase de backtesting de regresión lineal” en la página 167 y la “Clase
de backtesting de algoritmos de clasificación” en la página 170) que permita
un uso más sistemático y realista del Paquete Keras para la predicción de
los mercados financieros y el backtesting de las respectivas estrategias
comerciales.

Uso del aprendizaje profundo para la predicción del movimiento del mercado | 165
Machine Translated by Google

Conclusiones
Predecir los movimientos futuros del mercado es el santo grial en las finanzas. Significa encontrar la verdad.
Significa superar los mercados eficientes. Si uno puede hacerlo con una ventaja considerable, la consecuencia
serán excelentes retornos de inversión y operaciones. Este capítulo presenta técnicas estadísticas de los
campos de la estadística tradicional, el aprendizaje automático y el aprendizaje profundo para predecir la
dirección futura del mercado en función de rendimientos pasados o cantidades financieras similares. Algunos
primeros resultados dentro de la muestra son prometedores, tanto para la regresión lineal como para la logística.
Sin embargo, se obtiene una impresión más confiable cuando se evalúan dichas estrategias fuera de la muestra
y se tienen en cuenta las transacciones.
costos.

Este capítulo no pretende haber encontrado el santo grial. Más bien ofrece un vistazo a técnicas que podrían
resultar útiles en su búsqueda. La API unificada de scikit­learn también facilita la sustitución, por ejemplo, de
un modelo lineal por otro.
En ese sentido, ScikitBacktesterClass se puede utilizar como punto de partida para explorar más modelos de
aprendizaje automático y aplicarlos a la predicción de series de tiempo financieras.

La cita al comienzo del capítulo de la película Terminator 2 de 1991 es bastante optimista en cuanto a qué tan
rápido y en qué medida los ordenadores podrían aprender y adquirir conciencia. No importa si se cree que las
computadoras reemplazarán a los seres humanos en la mayoría de las áreas de la vida o no, o si algún día se
vuelven conscientes de sí mismas, han demostrado ser útiles para los seres humanos como dispositivos de
apoyo en casi cualquier área de la vida. Y algoritmos como los utilizados en el aprendizaje automático, el
aprendizaje profundo o la inteligencia artificial encierran al menos la promesa de permitirles convertirse en
mejores operadores algorítmicos en un futuro próximo. Una descripción más detallada de estos temas y
consideraciones se encuentra en Hilpisch (2020).

Referencias y recursos adicionales


Los libros de Guido y Müller (2016) y VanderPlas (2016) proporcionan introducciones prácticas al aprendizaje
automático con Python y scikit­learn. El libro de Hilpisch (2020) se centra exclusivamente en la aplicación de
algoritmos de aprendizaje automático y profundo al problema de identificar ineficiencias estadísticas y explotar
las ineficiencias económicas a través del comercio algorítmico:

Guido, Sarah y Andreas Müller. 2016. Introducción al aprendizaje automático con


Python: una guía para científicos de datos. Sebastopol: O'Reilly.

Hilpisch, Yves. 2020. Inteligencia artificial en finanzas: una guía basada en Python. Sebasto
Pol: O'Reilly.

Vander Plas, Jake. 2016. Manual de ciencia de datos de Python: herramientas esenciales para trabajar con
datos. Sebastopol: O'Reilly.

166 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

Los libros de Hastie et al. (2008) y James et al. (2013) proporcionan una descripción matemática
exhaustiva de las técnicas y algoritmos populares de aprendizaje automático:

Hastie, Trevor, Robert Tibshirani y Jerome Friedman. 2008. Los elementos de estadística.
Aprendizaje tico. 2da ed. Nueva York: Springer.
James, Gareth, Daniela Witten, Trevor Hastie y Robert Tibshirani. 2013. Introducción
ción al aprendizaje estadístico. Nueva York: Springer.

Para obtener más información general sobre el aprendizaje profundo y Keras, consulte estos libros:

Chollet, Francois. 2017. Aprendizaje profundo con Python. Isla refugio: Manning.

Goodfellow, Ian, Yoshua Bengio y Aaron Courville. 2016. Aprendizaje profundo. Leva­
puente: MIT Press. http://deeplearningbook.org.

Secuencias de comandos de Python

Esta sección presenta los scripts de Python a los que se hace referencia y se utilizan en este capítulo.

Clase de backtesting de regresión lineal


A continuación se presenta código Python con una clase para el backtesting vectorizado de
estrategias basadas en regresión lineal utilizadas para la predicción de la dirección del mercado.
movimientos:

# Módulo Python con clase # para backtesting


vectorizado # de estrategias basadas en
regresión lineal # # Python para comercio algorítmico # (c) Dr. Yves

J. Hilpisch # The Python Quants GmbH

#
importar numpy como np
importar pandas como pd

clase LRVectorBacktester(objeto):
''' Clase para el backtesting vectorizado de estrategias
comerciales basadas en regresión lineal.

Atributos
==========
símbolo: cadena
TR RIC (instrumento financiero) con el que trabajar
inicio: cadena
fecha de inicio para la selección de datos
fin: str

Secuencias de comandos de Python | 167


Machine Translated by Google

fecha de finalización para la


selección de datos
monto: int, monto flotante que se invertirá al
principio
tc: costos de transacción proporcionales flotantes (por ejemplo, 0,5% = 0,005) por operación

Métodos
=======
get_data:
recupera y prepara el conjunto de datos base
select_data:
selecciona un subconjunto de datos
prepare_lags:
prepara los datos retrasados para la regresión
fit_model:
implementa el paso de regresión
ejecutar_estrategia:
ejecuta la prueba retrospectiva para la estrategia basada en
regresión
plot_results: traza el rendimiento de la estrategia en comparación con el símbolo
'''

def __init__(self, símbolo, inicio, fin, cantidad, tc):


self.symbol = símbolo self.start
= inicio self.end = final
self.amount =
cantidad self.tc = tc self.results
= Ninguno
self.get_data()

def get_data(yo):
'''
Recupera y prepara los datos.
'''
raw = pd.read_csv('http://hilpisch.com/pyalgo_eikon_eod_data.csv',
index_col=0, parse_dates=True).dropna() raw
= pd.DataFrame(raw[self.symbol]) raw =
raw.loc[self.start:self.end]
raw.rename(columns={self.symbol: 'precio'}, inplace=True)
raw['devoluciones'] = np.log(raw / raw.shift(1))
self.data = raw.dropna()

def select_data(self, start, end): '''


Selecciona subconjuntos de datos financieros.
'''
datos = self.data[(self.data.index >= inicio) &
(self.data.index <= final)].copiar()
devolver datos

def prepare_lags(self, start, end): prepara


'''
los datos retrasados para los pasos de regresión y predicción.
'''

168 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

data = self.select_data(start, end) self.cols = [] para


retraso en el rango(1,
self.lags + 1): col = f'lag_{lag}' data[col] =
data['returns'] .shift(lag)
self.cols.append(col) data.dropna(inplace=True)
self.lagged_data = datos

def fit_model(self, inicio, fin):


''' Implementa el paso de regresión.
'''

self.prepare_lags(inicio, fin) reg =


np.linalg.lstsq(self.lagged_data[self.cols], np.sign(self.lagged_data['returns']),
rcond=Ninguno)[0]

self.reg = registro

def run_strategy(self, start_in, end_in, start_out, end_out, retrasos=3):


''' Realiza una prueba retrospectiva de la estrategia comercial.
'''

self.lags = retrasos
self.fit_model(start_in, end_in) self.results =
self.select_data(start_out, end_out).iloc[lags:] self.prepare_lags(start_out, end_out) predicción
= np.sign(np.dot(self .lagged_data[self.cols], self.reg))
self.results['prediction'] = predicción self.results['strategy'] = self.results['prediction'] * \

self.results['returns'] # determina
cuándo se realiza una transacción trades =
self.results['prediction'].diff().fillna(0) != 0 # resta los costos de transacción de la
devolución cuando se realiza la transacción self.results ['estrategia'][operaciones] ­= self.tc
self.results['creturns'] = self.amount * \

self.resultados['devoluciones'].cumsum().apply(np.exp)
self.results['cstrategy'] = self.cantidad * \
self.resultados['estrategia'].cumsum().apply(np.exp)
# rendimiento bruto de la estrategia aperf =
self.results['cstrategy'].iloc[­1] # rendimiento superior o inferior
de la estrategia operf = aperf ­
self.results['creturns'].iloc[­1] return round( aperf, 2), redondo(operf, 2)

def resultados_trama(yo):
''' Traza el rendimiento acumulado de la estrategia comercial en comparación con el
símbolo.
'''

si self.results es Ninguno:
print(' Aún no hay resultados para trazar. Ejecute una estrategia.')
título = '%s | TC = %.4f' % (self.symbol, self.tc) self.results[['creturns',
'cstrategy']].plot(title=title, figsize=(10, 6))

Secuencias de comandos de Python | 169


Machine Translated by Google

si __nombre__ == '__principal__':
lrbt = LRVectorBacktester('.SPX', '2010­1­1', '2018­06­29', 10000, 0.0) print(lrbt.run_strategy('2010­1­1',
'2019­12­31' , '2010­1­1', '2019­12­31')) print(lrbt.run_strategy('2010­1­1',
'2015­12­31', '2016­1­1', '2019 ­12­31'))

lrbt = LRVectorBacktester('GDX', '2010­1­1', '2019­12­31', 10000, 0.001) print(lrbt.run_strategy('2010­1­1',


'2019­12­31', '2010­1­1', '2019­12­31', retrasos = 5))
print(lrbt.run_strategy('2010­1­1', '2016­12­31',
'2017­1­1' , '2019­12­31', retrasos = 5))

Clase de backtesting del algoritmo de clasificación

A continuación se presenta el código Python con una clase para el backtesting vectorizado de
estrategias basadas en regresión logística, como algoritmo de clasificación estándar, utilizado
para la predicción de la dirección de los movimientos del mercado:

# Módulo Python con clase # para backtesting


vectorizado # de estrategias basadas en
aprendizaje automático # # Python para comercio algorítmico #

(c) Dr. Yves J. Hilpisch # The Python Quants GmbH

#
importar numpy como np
importar pandas como pd
desde sklearn importar linear_model

clase ScikitVectorBacktester(objeto): ''' Clase para el


backtesting vectorizado de estrategias comerciales basadas en
aprendizaje automático.

Atributos
==========

símbolo: cadena
TR RIC (instrumento financiero) con el que trabajar
inicio: cadena
fecha de inicio para la selección de datos
fin: str fecha
de finalización para la selección de datos
monto: int, monto flotante
que se invertirá al principio tc: costos de transacción
proporcionales
flotantes (por ejemplo, 0,5% = 0,005) por operación
modelo: str
ya sea 'regresión' o 'logística'

170 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

Métodos
=======
get_data:
recupera y prepara el conjunto de datos base
select_data:
selecciona un subconjunto de datos
prepare_features:
prepara los datos de características para el ajuste del
modelo
fit_model: implementa el paso de ajuste
ejecutar_estrategia:
ejecuta la prueba retrospectiva para la estrategia basada en
regresión
plot_results: traza el rendimiento de la estrategia en comparación con el símbolo
'''

def __init__(self, símbolo, inicio, fin, cantidad, tc, modelo): self.symbol = símbolo
self.start = inicio self.end =
final self.cantidad =
cantidad self.tc = tc
self.resultados = Ninguno

si modelo == 'regresión':
self.model = modelo_lineal.Regresiónlineal()
modelo elif == 'logístico':
self.model = modelo_lineal.Regresión logística (C = 1e6,
solucionador='lbfgs', multi_class='ovr', max_iter=1000)
else:
elevar ValueError('Modelo desconocido o aún no implementado.') self.get_data()

def get_data(yo):
'''
Recupera y prepara los datos.
'''

raw = pd.read_csv('http://hilpisch.com/pyalgo_eikon_eod_data.csv',
index_col=0, parse_dates=True).dropna() raw =
pd.DataFrame(raw[self.symbol]) raw =
raw.loc[self.start:self.end]
raw.rename(columns={self.symbol: 'precio'}, inplace=True) raw['devoluciones'] =
np.log(raw / raw.shift(1)) self.data = raw.dropna()

def select_data(self, start, end): ''' Selecciona


subconjuntos de datos financieros.
'''

datos = self.data[(self.data.index >= inicio) & (self.data.index <=


final)].copiar()
devolver datos

def prepare_features(self, inicio, fin):

Secuencias de comandos de Python | 171


Machine Translated by Google

'''
Prepara las columnas de características para los pasos de regresión y predicción.
'''
self.data_subset = self.select_data(start, end)
self.feature_columns = [] para
el retraso en el rango(1, self.lags + 1): col
= 'lag_{}'.format(lag)
self.data_subset[col] = self.data_subset['devoluciones'].shift(lag)
self.feature_columns.append(col)
self.data_subset.dropna(inplace=True)

def fit_model(self, inicio, fin):


''' Implementa el paso de adaptación.
'''
self.prepare_features(inicio, fin)
self.model.fit(self.data_subset[self.feature_columns],
np.sign(self.data_subset['devoluciones']))

def run_strategy(self, start_in, end_in, start_out, end_out, retrasos=3):


''' Realiza una prueba retrospectiva de la estrategia comercial.
'''
self.lags = retrasos
self.fit_model(start_in, end_in) # datos
= self.select_data(start_out, end_out)
self.prepare_features(start_out, end_out) predicción
=
self.model.predict( self.data_subset[self.feature_columns])
self .data_subset['prediction'] = predicción
self.data_subset['strategy'] = (self.data_subset['prediction'] *
self.data_subset['returns'])
# determinar cuándo se realiza una
transacción trades = self.data_subset['prediction'].diff().fillna(0) != 0 # restar
los costos de transacción del retorno cuando se realiza la transacción
self.data_subset['strategy'][trades] ­= self.tc
self.data_subset['creturns'] = (self.cantidad *
self.data_subset['devoluciones'].cumsum().apply(np.exp))
self.data_subset['cstrategy'] = (self.cantidad *
self.data_subset['strategy'].cumsum().apply(np.exp)) self.results
= self.data_subset # rendimiento
absoluto de la estrategia aperf =
self.results['cstrategy'].iloc[­1] # Rendimiento
superior o inferior de la estrategia operf =
aperf ­ self.results['creturns'].iloc[­1] return round(aperf, 2),
round(operf, 2)

def resultados_trama(yo):
''' Traza el rendimiento acumulado de la estrategia comercial en
comparación con el símbolo.
'''
si self.results es Ninguno:
print(' Aún no hay resultados para trazar. Ejecute una estrategia.')
título = '%s | TC = %.4f' % (self.symbol, self.tc)
self.results[['creturns', 'cstrategy']].plot(title=title,

172 | Capítulo 5: Predecir los movimientos del mercado con aprendizaje automático
Machine Translated by Google

tamaño de higo=(10, 6))

if __name__ == '__main__':
scibt = ScikitVectorBacktester('.SPX', '2010­1­1', '2019­12­31', 10000, 0.0,
'regresión')
print(scibt.run_strategy('2010­ 1­1', '2019­12­31', '2010­1­1',
'2019­12­31'))
print(scibt.run_strategy('2010­1­1', '2016­12­31 ', '2017­1­1',
'2019­12­31')) scibt =
ScikitVectorBacktester('.SPX', '2010­1­1', '2019­12­31', 10000, 0.0, 'logística ')
print(scibt.run_strategy('2010­1­1',
'2019­12­31', '2010­1­1', '2019­12­31'))
print(scibt.run_strategy('2010­
1­1', '2016­12­31', '2017­1­1', '2019­12­31')) scibt =
ScikitVectorBacktester('.SPX',
'2010­1­1', '2019­ 12­31', 10000, 0.001, 'logística') print(scibt.run_strategy('2010­1­1',
'2019­12­31', '2010­1­1',
'2019­12­31' , retrasos = 15)) print(scibt.run_strategy('2010­1­1',
'2013­12­31', '2014­1­1', '2019­12­31',
retrasos = 15))

Secuencias de comandos de Python | 173


Machine Translated by Google
Machine Translated by Google

CAPÍTULO 6

Clases de construcción para


Backtesting basado en eventos

Las tragedias reales de la vida no guardan relación con las ideas preconcebidas de uno. Al final, uno
siempre queda desconcertado por su sencillez, la grandeza de su diseño y por ese elemento de
extrañeza que parece inherente a ellos.

—Jean Cocteau

Por un lado, el backtesting vectorizado con NumPy y pandas es generalmente conveniente y eficiente de
implementar debido al código conciso, y es rápido de ejecutar debido a que estos paquetes están optimizados
para tales operaciones. Sin embargo, el enfoque no puede hacer frente a todos los tipos de estrategias
comerciales ni a todos los fenómenos que la realidad comercial presenta a un operador algorítmico. Cuando
se trata de backtesting vectorizado, las posibles deficiencias del enfoque son las siguientes:

Sesgo de anticipación

Las pruebas retrospectivas vectorizadas se basan en el conjunto completo de datos disponibles y no


tienen en cuenta que los nuevos datos llegan de forma incremental.

Simplificación
Por ejemplo, los costos fijos de transacción no pueden modelarse mediante la vectorización, que se
basa principalmente en rendimientos relativos. Además, los montos fijos por operación o la no
divisibilidad de instrumentos financieros individuales (por ejemplo, una acción) no se pueden modelar
adecuadamente.

Los algoritmos de no

recursividad, que incorporan estrategias comerciales, pueden recurrir a variables estatales a lo largo del
tiempo, como pérdidas y ganancias hasta un determinado momento o estadísticas similares dependientes
de la trayectoria. La vectorización no puede hacer frente a tales características.

175
Machine Translated by Google

Por otro lado, el backtesting basado en eventos permite abordar estos problemas mediante un enfoque más
realista para modelar las realidades comerciales. En un nivel básico, un evento se caracteriza por la llegada de
nuevos datos. Al realizar una prueba retrospectiva de una estrategia comercial para las acciones de Apple Inc.
basada en los datos del final del día, un evento sería un nuevo precio de cierre para las acciones de Apple.
También puede ser un cambio en la tasa de interés o el logro de un nivel de límite de pérdidas.
Las ventajas del enfoque de backtesting basado en eventos generalmente son las siguientes:

Enfoque incremental Como


en la realidad del trading, el backtesting se realiza bajo la premisa de que los nuevos datos llegan de
forma incremental, tick a tick y cotización a cotización.

Modelado realista Uno


tiene total libertad para modelar aquellos procesos que se desencadenan por un evento nuevo y específico.

Dependencia de la
ruta Es sencillo realizar un seguimiento de las estadísticas condicionales, recursivas o dependientes de
la ruta, como el precio máximo o mínimo visto hasta ahora, e incluirlas en el algoritmo de negociación.

Reusabilidad
Realizar pruebas retrospectivas de diferentes tipos de estrategias comerciales requiere una funcionalidad
básica similar que pueda implementarse y unificarse mediante programación orientada a objetos.

Cerca del comercio


Ciertos elementos de un sistema de backtesting basado en eventos a veces también se pueden utilizar
para la implementación automatizada de la estrategia comercial.

En lo que sigue, un nuevo evento generalmente se identifica mediante una barra, que representa una unidad
de datos nuevos. Por ejemplo, los eventos pueden ser barras de un minuto para una estrategia de negociación
intradía o barras de un día para una estrategia de negociación basada en precios de cierre diarios.

El capítulo está organizado de la siguiente forma. “Clase base de backtesting” en la página 177 presenta una
clase base para el backtesting de estrategias comerciales basado en eventos. La “Clase de backtesting de solo
largo” en la página 182 y la “Clase de backtesting de largo y corto” en la página 185 utilizan la clase base para
implementar clases de backtesting de solo largo y corto, respectivamente.

Los objetivos de este capítulo son comprender el modelado basado en eventos, crear clases que permitan un
backtesting más realista y tener una infraestructura de backtesting fundamental disponible como punto de
partida para futuras mejoras y refinamientos.

176 | Capítulo 6: Creación de clases para backtesting basado en eventos


Machine Translated by Google

Clase base de backtesting


Cuando se trata de construir la infraestructura (en forma de una clase de Python) para realizar pruebas
retrospectivas basadas en eventos, se deben cumplir varios requisitos:

Recuperar y preparar datos


La clase base se encargará de la recuperación de datos y posiblemente de la preparación para el
backtesting en sí. Para mantener la discusión enfocada, los datos del final del día (EOD) leídos desde
un archivo CSV son el tipo de datos que la clase base permitirá.

Funciones auxiliares y de conveniencia


Proporcionará un par de funciones auxiliares y convenientes que facilitarán el backtesting. Algunos
ejemplos son funciones para trazar datos, imprimir variables de estado o devolver información de fecha
y precio para una barra determinada.

Colocación de
órdenes La clase base cubrirá la colocación de órdenes básicas de compra y venta. Para simplificar,
sólo se modelan las órdenes de compra y venta de mercado.

Cierre de posiciones Al
final de cualquier backtesting, es necesario cerrar todas las posiciones de mercado. La clase
base se encargará de este comercio final.

Si la clase base cumple con estos requisitos, las clases respectivas para probar estrategias basadas en
promedios móviles simples (SMA), impulso o reversión a la media (ver Capítulo 4), así como en predicción
basada en aprendizaje automático (ver Capítulo 5), se puede construir sobre él. “Clase base de backtesting”
en la página 191 presenta una implementación de dicha clase base llamada BacktestBase. A continuación
se muestra un recorrido por los métodos individuales de esta clase para obtener una descripción general de
su diseño.

Con respecto al método especial __main__, solo hay algunas cosas dignas de mención.
Primero, la cantidad inicial disponible se almacena dos veces, tanto en un atributo privado _cantidad que se
mantiene constante como en un atributo regular cantidad que representa el saldo corriente. El supuesto
predeterminado es que no hay costos de transacción:

def __init__(self, símbolo, inicio, fin, cantidad,


ftc=0.0, ptc=0.0, detallado=True): self.symbol =
símbolo self.start = iniciar
self.end = finalizar

self.monto_inicial = monto self.monto =


monto self.ftc = ftc

self.ptc = ptc
self.unidades = 0
self.posición = 0
self.trades = 0

Clase base de backtesting | 177


Machine Translated by Google

self.verbose = detallado
self.get_data()

Almacena el monto inicial en un atributo privado.

Establece el valor del saldo de efectivo inicial.

Define los costos de transacción fijos por operación.

Define costos de transacción proporcionales por operación.

Unidades del instrumento (por ejemplo, número de acciones) en la cartera inicialmente.

Establece la posición inicial en mercado neutral.

Establece el número inicial de operaciones en cero.

Establece self.verbose en True para obtener un resultado completo.

Durante la inicialización, se llama al método get_data , que recupera datos EOD de un archivo CSV para el
símbolo proporcionado y el intervalo de tiempo dado. También calcula los retornos del registro. El código
Python que sigue se ha utilizado ampliamente en los Capítulos 4 y 5. Por lo tanto, no es necesario explicarlo
en detalle aquí:

def get_data(yo):
'''
Recupera y prepara los datos.
'''
raw = pd.read_csv('http://hilpisch.com/pyalgo_eikon_eod_data.csv',
index_col=0, parse_dates=True).dropna() raw
= pd.DataFrame(raw[self.symbol]) raw =
raw.loc[self.start:self.end]
raw.rename(columns={self.symbol: 'precio'}, inplace=True) raw['return']
= np.log(raw / raw.shift(1)) self.data = raw.dropna()

El método .plot_data() es simplemente un método auxiliar simple para trazar los valores (cercanos ajustados)
para el símbolo proporcionado:

def plot_data(self, cols=Ninguno):


''' Traza los precios de cierre del símbolo.
'''
si cols es Ninguno: cols
= ['precio']
self.data['precio'].plot(figsize=(10, 6), title=self.symbol)

Un método que se llama con frecuencia es .get_date_price(). Para una barra determinada, devuelve la fecha y
la información del precio:

def get_date_price(self, bar): fecha


'''
de regreso y precio de la barra.

178 | Capítulo 6: Creación de clases para backtesting basado en eventos


Machine Translated by Google

'''
fecha = str(self.data.index[barra])[:10]
precio = self.data.price.iloc[barra]
fecha de regreso , precio

.print_balance() imprime el saldo de efectivo actual dada una determinada barra,


mientras que .print_net_wealth() hace lo mismo para la riqueza neta (= saldo actual más
valor de la posición comercial):

def print_balance(self, barra):


'''
Imprima la información del saldo de efectivo actual.
'''
fecha, precio = self.get_date_price(bar)
print(f'{fecha} | saldo actual {self.amount:.2f}')

def print_net_wealth(self, barra):


'''
Imprima la información del saldo de efectivo actual.
'''
fecha, precio = self.get_date_price(bar)
riqueza_neta = auto.unidades * precio + auto.monto
print(f'{fecha} | riqueza neta actual {net_wealth:.2f}')

Dos métodos principales son .place_buy_order() y .place_sell_order(). Ellos permiten


la compra y venta emulada de unidades de un instrumento financiero. Primero es
el método .place_buy_order() , que se comenta detalladamente:

def place_buy_order(self, bar, unidades=Ninguno, cantidad=Ninguno):


''' Realizar una orden de compra.
'''
fecha, precio = self.get_date_price(bar) si las unidades
son Ninguna: unidades =
int(cantidad / precio)
self.importe ­= (unidades * precio) * (1 + self.ptc) + self.ftc self.units += unidades

self.trades += 1 if
self.verbose:
print(f'{fecha} | vendiendo {unidades} unidades a {precio:.2f}') self.print_balance(bar)
self.print_net_wealth(bar)

Se recupera la información de fecha y precio de la barra determinada.

Si no se da ningún valor para las unidades ...

…el número de unidades se calcula teniendo en cuenta el valor del importe. (Tenga en cuenta que uno
debe indicarse.) El cálculo no incluye los costos de transacción.

El saldo de caja corriente se reduce por los desembolsos de efectivo de las unidades del
instrumento a comprar más los costos de transacción fijos y proporcionales. Nota
que no se comprueba si hay suficiente liquidez disponible o no.

Clase base de backtesting | 179


Machine Translated by Google

El valor de las unidades propias aumenta según el número de unidades compradas.

Esto aumenta el contador del número de operaciones en uno.

Si self.verbose es Verdadero...

…imprimir información sobre la ejecución comercial…

…el saldo de caja actual…

…y la riqueza neta actual.

En segundo lugar, el método .place_sell_order() , que solo tiene dos ajustes menores en comparación con
el método .place_buy_order() :

def place_sell_order(self, bar, unidades=Ninguno, cantidad=Ninguno):


''' Realizar una orden de venta.
'''

fecha, precio = self.get_date_price(bar) si las unidades


son Ninguna: unidades =
int(cantidad / precio)
self.monto += (unidades * precio) * (1 ­ self.ptc) ­ self.ftc self.units ­= unidades self.trades
+= 1

if self.verbose:
print(f'{fecha} | vendiendo {unidades} unidades a {precio:.2f}') self.print_balance(bar)
self.print_net_wealth(bar)

El saldo de efectivo actual se incrementa con el producto de la venta menos los costos de transacción.

El valor de las unidades propias se reduce según el número de unidades vendidas.

No importa qué tipo de estrategia comercial se pruebe, la posición al final del período de prueba debe
cerrarse. El código de la clase BacktestBase supone que la posición no se liquida sino que se contabiliza
con su valor de activo para calcular e imprimir las cifras de rendimiento:

def close_out(self, bar): ''' Cerrar una


posición larga o corta.
'''

fecha, precio = self.get_date_price(bar) self.monto +=


self.units * precio self.units = 0 self.trades += 1

if self.verbose:
print(f'{fecha} | inventario {self.units} unidades a {price:.2f}') print('=' * 55)

180 | Capítulo 6: Creación de clases para backtesting basado en eventos


Machine Translated by Google

print('Saldo final [$] {:.2f}'.format(self.monto)) rendimiento = ((self.monto ­ self.monto_inicial) /


self.monto_inicial * 100)

print(' Rendimiento neto [%] {:.2f}'.format(perf)) print('Operaciones ejecutadas [#]


{:.2f}'.format(self.trades)) print('=' * 55)

Al final no se restan costos de transacción.

El saldo final consiste en el saldo de caja actual más el valor de la posición comercial.

Esto calcula el rendimiento neto en porcentaje.

La parte final del script de Python es la sección __main__ , que se ejecuta cuando el archivo se
ejecuta como un script:

if __name__ == '__main__': bb =
BacktestBase('AAPL.O', '2010­1­1', '2019­12­31', 10000) print(bb.data.info()) print(bb. datos.tail())
bb.plot_data()

Crea una instancia de un objeto basado en la clase BacktestBase . Esto conduce automáticamente a
la recuperación de datos para el símbolo proporcionado. La Figura 6­1 muestra el gráfico resultante.
El siguiente resultado muestra la metainformación para el objeto DataFrame respectivo y las cinco
filas de datos más recientes:

En [1]: %run BacktestBase.py <class


'pandas.core.frame.DataFrame'> DatetimeIndex: 2515
entradas, 2010­01­05 a 2019­12­31 Columnas de datos (2 columnas en total): # Columna
no Tipo D de recuento nulo

­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 precio 2515 float64 no nulo 1 retorno 2515 float64 no nulo

dtypes: float64(2) uso de


memoria: 58,9 KB Ninguno

precio devolver
Fecha
2019­12­24 284,27 0,000950
2019­12­26 289,91 0,019646
2019­12­27 289,80 ­0,000380 2019­12­30
291,52 0,005918 2019­12­31 293,65 0,007280

En 2]:

Clase base de backtesting | 181


Machine Translated by Google

Figura 6­1. Gráfico de datos recuperados para el símbolo por la clase BacktestBase

Las dos secciones siguientes presentan clases para realizar pruebas retrospectivas de estrategias
comerciales largas y cortas. Dado que estas clases se basan en la clase base presentada en esta
sección, la implementación de las rutinas de backtesting es bastante concisa.

El uso de programación orientada a objetos permite construir una


infraestructura básica de backtesting en forma de una clase de Python. Esta
clase pone a disposición la funcionalidad estándar necesaria durante las
pruebas retrospectivas de diferentes tipos de estrategias comerciales
algorítmicas de una manera no redundante y fácil de mantener. También es
sencillo mejorar la clase base para proporcionar más funciones de forma
predeterminada que podrían beneficiar a una multitud de otras clases creadas sobre ella.

Clase de backtesting de larga duración

Ciertas preferencias o regulaciones de los inversores pueden prohibir las ventas en corto como parte
de una estrategia comercial. Como consecuencia, a un comerciante o administrador de cartera solo
se le permite ingresar posiciones largas o estacionar capital en forma de efectivo o activos similares
de bajo riesgo, como cuentas del mercado monetario. “Clase de backtesting de solo largo” en la
página 194 muestra el código de una clase de backtesting para estrategias de solo largo llamada
BacktestLongOnly. Dado que depende de la clase BacktestBase y la hereda , el código para
implementar las tres estrategias basadas en SMA, impulso y reversión a la media es bastante conciso.

182 | Capítulo 6: Creación de clases para backtesting basado en eventos


Machine Translated by Google

El método .run_mean_reversion_strategy() implementa el proceso de backtesting.


durante la estrategia basada en reversión a la media. Este método se comenta en detalle,
ya que podría ser un poco más complicado desde el punto de vista de la implementación. Las ideas básicas,
sin embargo, transfiera fácilmente a los métodos que implementan las otras dos estrategias:

def run_mean_reversion_strategy(self, SMA, umbral):


''' Realizar pruebas retrospectivas de una estrategia basada en la reversión a la media.

Parámetros
==========
SMA: entero
media móvil simple en días
umbral: flotar
valor absoluto para la señal basada en desviación relativa a SMA
'''
'
msg = f'\n\nEjecución de estrategia de reversión a la media |
mensaje += f'SMA={SMA} & thr={umbral}'
'
msg += f'\ncostos fijos {self.ftc} | msg += f'costos
proporcionales {self.ptc}'

imprimir(mensaje) imprimir('=' * 55)


self.position = 0 self.trades
= 0 self.monto =
self.initial_amount

self.data['SMA'] = self.data['precio'].rolling(SMA).mean()

para barra en rango (SMA, len (self.data)): si self.position


== 0: if (self.data['price'].iloc[bar]
<
self.data['SMA'].iloc[bar] ­ umbral): self.place_buy_order(bar,
cantidad=self.amount) self.position = 1 elif self.position == 1:

if self.data['price'].iloc[bar] >= self.data['SMA'].iloc[bar]: self.place_sell_order(bar,


unidades=self.units) self.position = 0

self.close_out(barra)

Al principio, este método imprime una descripción general de los parámetros principales para
el backtesting.

La posición se establece en mercado neutral, lo que se hace aquí para mayor claridad y
debería ser el caso de todos modos.

El saldo de efectivo actual se restablece al monto inicial en caso de que se realice otra prueba retrospectiva.
run ha sobrescrito el valor.

Esto calcula los valores de SMA necesarios para la implementación de la estrategia.

Clase de backtesting de larga duración | 183


Machine Translated by Google

El valor inicial SMA garantiza que haya valores SMA disponibles para comenzar a implementar y
realizar pruebas retrospectivas de la estrategia.

La condición comprueba si la posición es neutral al mercado.

Si la posición es neutral en el mercado, se comprueba si el precio actual es lo suficientemente bajo


en relación con la SMA como para activar una orden de compra y entrar en largo.

Esto ejecuta la orden de compra por el monto del saldo de efectivo actual.

La posición del mercado se prevé larga.

La condición comprueba si la posición es larga en el mercado.

Si ese es el caso, se verifica si el precio actual ha vuelto al nivel SMA o superior.

En tal caso, se realiza una orden de venta para todas las unidades del instrumento financiero.

La posición del mercado vuelve a ser neutral.

Al final del período de backtesting, la posición de mercado se cierra si hay alguna abierta.

La ejecución del script de Python en la “Clase de backtesting de solo duración” en la página 194 produce
resultados de backtesting, como se muestra a continuación. Los ejemplos ilustran la influencia de los costos
de transacción fijos y proporcionales. Primero, afectan el desempeño en general. En cualquier caso, tener
en cuenta los costos de transacción reduce el rendimiento. En segundo lugar, sacan a la luz la importancia
del número de operaciones que una determinada estrategia desencadena a lo largo del tiempo. Sin costos
de transacción, la estrategia de impulso supera significativamente a la estrategia basada en SMA. Con los
costos de transacción, la estrategia basada en SMA supera a la estrategia de impulso ya que se basa en
menos operaciones:

Ejecución de la estrategia SMA | SMA1=42 y SMA2=252 costos


fijos 0.0 | costos proporcionales 0.0
==================================================== =====

Saldo final [$] 56204,95 Rendimiento neto


[%] 462,05
==================================================== =====

Ejecución de estrategia de impulso | Costos fijos de 60


días 0,0 | costos proporcionales 0.0
==================================================== =====

Saldo final [$] 136716,52 Rendimiento neto


[%] 1267,17
==================================================== =====

184 | Capítulo 6: Creación de clases para backtesting basado en eventos


Machine Translated by Google

Ejecución de una estrategia de reversión a la media | SMA=50 y thr=5 costos


fijos 0,0 | costos proporcionales 0.0
==================================================== =====

Saldo final [$] 53907,99 Rendimiento neto


[%] 439,08
==================================================== =====

Ejecución de la estrategia SMA | SMA1=42 y SMA2=252 costos


fijos 10,0 | costos proporcionales 0.01
==================================================== =====

Saldo final [$] 51959,62 Rendimiento neto


[%] 419,60
==================================================== =====

Ejecución de estrategia de impulso | Costos fijos de 60


días 10,0 | costos proporcionales 0.01
==================================================== =====

Saldo final [$] 38074,26 Rendimiento neto


[%] 280,74
==================================================== =====

Ejecución de una estrategia de reversión a la media | SMA=50 y thr=5 costos


fijos 10,0 | costos proporcionales 0.01
==================================================== =====

Saldo final [$] 15375,48 Rendimiento neto


[%] 53,75
==================================================== =====

El capítulo 5 enfatiza que hay dos caras de la moneda del desempeño: la


tasa de acierto para la predicción correcta de la dirección del mercado y el
momento del mercado (es decir, cuándo exactamente la predicción es
correcta). Los resultados que se muestran aquí ilustran que hay incluso un
"tercer lado": el número de operaciones activadas por una estrategia. Una
estrategia que exige una mayor frecuencia de transacciones tiene que
soportar costos de transacción más altos que fácilmente devoran un
supuesto rendimiento superior a otra estrategia con costos de transacción
bajos o nulos. Entre otras cosas, esto suele justificar estrategias de
inversión pasiva de bajo coste basadas, por ejemplo, en fondos cotizados en bolsa (ETF).

Clase de backtesting larga y corta


“Clase de Backtesting Long­Short” en la página 197 presenta la clase
BacktestLongShort , que también hereda de la clase BacktestBase . Además de
implementar los respectivos métodos para el backtesting de las diferentes estrategias, implementa dos

Clase de backtesting larga y corta | 185


Machine Translated by Google

métodos adicionales para ir en largo y en corto, respectivamente. Sólo el método .go_long()


está comentado en detalle, ya que el método .go_short() hace exactamente lo mismo en
la dirección opuesta:

def go_long(self, bar, unidades=Ninguno, cantidad=Ninguno):


si posición propia == ­1:
self.place_buy_order(barra, unidades=­self.units) si unidades:

self.place_buy_order(barra, unidades=unidades)
monto elif : si
monto == 'todos':
cantidad = auto.monto
self.place_buy_order(barra, monto=monto)

def go_short(self, bar, unidades=Ninguno, cantidad=Ninguno):


si posición propia == 1:
self.place_sell_order(barra, unidades=self.units)
si unidades:
self.place_sell_order(barra, unidades=unidades)
cantidad elif :
si cantidad == 'todo':
cantidad = auto.monto
self.place_sell_order(barra, monto=monto)

Además de la barra, los métodos esperan un número para las unidades del objeto negociado
instrumento o una cantidad de moneda.

En el caso de .go_long() , primero se comprueba si hay una posición corta.

Si es así, esta posición corta se cierra primero.

Luego se comprueba si se dan las unidades ...

…lo que desencadena una orden de compra en consecuencia.

Si se da cantidad , pueden darse dos casos.

Primero, el valor es todo, lo que se traduce en…

…todo el efectivo disponible en el saldo de caja actual.

En segundo lugar, el valor es un número que luego simplemente se toma para colocar el respectivo
orden de compra. Tenga en cuenta que no se comprueba si hay suficiente liquidez o no.

186 | Capítulo 6: Creación de clases para backtesting basado en eventos


Machine Translated by Google

Para mantener la implementación concisa en todo momento, hay muchos


simplificaciones en las clases de Python que transfieren responsabilidad a
el usuario. Por ejemplo, las clases no se preocupan de si hay
Hay suficiente liquidez o no para ejecutar una operación. Esta es una economía
simplificación ya que, en teoría, se podría suponer suficiente o incluso
Crédito ilimitado para el comerciante algorítmico. Como otro ejemplo,
ciertos métodos esperan que al menos uno de dos parámetros (ya sea
unidades o cantidad) . No hay ningún código que capte el caso.
donde ambos no están establecidos. Esta es una simplificación técnica.

A continuación se presenta el bucle central de .run_mean_reversion_strategy()


método de la clase BacktestLongShort . Nuevamente, la estrategia de reversión a la media es
elegido ya que la implementación es un poco más complicada. Por ejemplo, es el único
estrategia que también conduce a posiciones intermedias neutrales en el mercado. Esto requiere
más controles en comparación con las otras dos estrategias, como se ve en “Backtesting largo­corto
Clase” en la página 197:

para barra en rango (SMA, len (self.data)):


si self.position == 0: if
(self.data['precio'].iloc[bar] <
self.data['SMA'].iloc[bar] ­ umbral): self.go_long(bar,
cantidad=self.initial_amount) self.position = 1 elif
(self.data['price'].iloc[bar]
>
self.data['SMA'].iloc[bar] + umbral):
self.go_short(barra, cantidad=self.initial_amount)
auto.posicion = ­1 elif
auto.posicion == 1:
if self.data['price'].iloc[bar] >= self.data['SMA'].iloc[bar]: self.place_sell_order(bar,
unidades=self.units) self.position = 0 elif self. posición
== ­1: si
self.data['precio'].iloc[bar] <=
self.data['SMA'].iloc[bar]: self.place_buy_order(bar, unidades=­self.units)
self .posición = 0

self.close_out(barra)

La primera condición de nivel superior comprueba si la posición es neutral al mercado.

Si esto es cierto, se comprueba si el precio actual es lo suficientemente bajo en relación


a la SMA.

En tal caso, se llama al método .go_long() ...

…y la posición del mercado se perfila como larga.

Clase de backtesting larga y corta | 187


Machine Translated by Google

Si el precio actual es lo suficientemente alto en relación con el SMA, se llama al


método .go_short() ...

…y la posición del mercado se sitúa en corto.

La segunda condición de nivel superior verifica una posición larga en el mercado.

En tal caso, se vuelve a comprobar si el precio actual se encuentra nuevamente en el nivel SMA o por encima de él.

Si es así, la posición larga se cierra vendiendo todas las unidades de la cartera.

La posición del mercado se restablece a neutral.

Finalmente, la tercera condición de nivel superior busca una posición corta.

Si el precio actual está en o por debajo de la SMA...

…se activa una orden de compra para todas las unidades cortas para cerrar la posición corta.

A continuación, la posición del mercado se restablece a neutral.

La ejecución del script Python en “Clase de backtesting largo y corto” en la página 197 produce resultados de rendimiento
que arrojan más luz sobre las características de la estrategia. Uno podría inclinarse a suponer que agregar flexibilidad para
vender en corto un instrumento financiero produce mejores resultados. Sin embargo, la realidad demuestra que esto no es
necesariamente cierto. Todas las estrategias funcionan peor sin y después de los costos de transacción. Algunas
configuraciones incluso acumulan pérdidas netas o incluso una posición de deuda. Aunque estos son sólo resultados
específicos, ilustran que en tal contexto es arriesgado sacar conclusiones demasiado pronto y no tener en cuenta los límites
para la acumulación de deuda:

Ejecución de la estrategia SMA | SMA1=42 y SMA2=252 costos


fijos 0.0 | costos proporcionales 0.0
==================================================== =====

Saldo final [$] 45631,83 Rendimiento neto


[%] 356,32
==================================================== =====

Ejecución de estrategia de impulso | Costos fijos de 60


días 0,0 | costos proporcionales 0.0
==================================================== =====

Saldo final [$] 105236,62 Rendimiento neto


[%] 952,37
==================================================== =====

188 | Capítulo 6: Creación de clases para backtesting basado en eventos


Machine Translated by Google

Ejecución de una estrategia de reversión a la media | SMA=50 y thr=5 costos


fijos 0,0 | costos proporcionales 0.0
==================================================== =====

Saldo final [$] 17279,15 Rendimiento neto


[%] 72,79
==================================================== =====

Ejecución de la estrategia SMA | SMA1=42 y SMA2=252 costos


fijos 10,0 | costos proporcionales 0.01
==================================================== =====

Saldo final [$] 38369,65 Rendimiento neto


[%] 283,70
==================================================== =====

Ejecución de estrategia de impulso | Costos fijos de 60


días 10,0 | costos proporcionales 0.01
==================================================== =====

Saldo final [$] 6883,45 Rendimiento neto


[%] ­31,17
==================================================== =====

Ejecución de una estrategia de reversión a la media | SMA=50 y thr=5 costos


fijos 10,0 | costos proporcionales 0.01
==================================================== =====

Saldo final [$] ­5110,97 Rendimiento neto


[%] ­151,11
==================================================== =====

Las situaciones en las que la negociación podría consumir todo el capital


inicial e incluso conducir a una posición de deuda surgen, por ejemplo,
en el contexto de la negociación de contratos por diferencias (CFD). Estos
son productos altamente apalancados para los cuales el operador sólo
necesita depositar, digamos, el 5% del valor de la posición como margen
inicial (cuando el apalancamiento es 20). Si el valor de la posición cambia,
digamos, un 10%, es posible que el operador deba cumplir con el ajuste
de margen correspondiente. Para una posición larga de 100.000 USD, se
requiere un capital de 5.000 USD. Si la posición cae a 90.000 USD, el
capital desaparece y el operador debe depositar 5.000 USD más para
cubrir las pérdidas. Esto supone que no existen límites de margen que
cerrarían la posición tan pronto como el capital restante caiga a 0 USD.

Clase de backtesting larga y corta | 189


Machine Translated by Google

Conclusiones
Este capítulo presenta clases para el backtesting de estrategias comerciales basado en eventos.
En comparación con el backtesting vectorizado, el backtesting basado en eventos hace un uso intensivo
y intencional de bucles e iteraciones para poder abordar cada evento nuevo (generalmente, la llegada de
nuevos datos) de forma individual. Esto permite un enfoque más flexible que puede, entre otras cosas,
hacer frente fácilmente a costos de transacción fijos o estrategias más complejas (y variaciones de las
mismas).

“Clase base de backtesting” en la página 177 presenta una clase base con ciertos métodos útiles para el
backtesting de una variedad de estrategias comerciales. La “Clase de backtesting solo largo” en la página
182 y la “Clase de backtesting largo­corto” en la página 185 se basan en esta infraestructura para
implementar clases que permiten realizar pruebas retrospectivas de estrategias comerciales solo largas
y cortas. Principalmente por razones de comparación, las implementaciones incluyen las tres estrategias
presentadas formalmente en el Capítulo 4. Tomando las clases de este capítulo como punto de partida,
se pueden lograr mejoras y refinamientos fácilmente.

Referencias y recursos adicionales


Los capítulos anteriores presentan las ideas y conceptos básicos con respecto a las tres estrategias
comerciales cubiertas en este capítulo. Este capítulo, por primera vez, hace un uso más sistémico de las
clases de Python y la programación orientada a objetos (POO). Una buena introducción a la programación
orientada a objetos con Python y el modelo de datos de Python se encuentra en Ramalho (2021). Una
introducción más concisa a la POO aplicada a las finanzas se encuentra en Hilpisch (2018, cap. 6):

Hilpisch, Yves. 2018. Python para finanzas: dominar las finanzas basadas en datos. 2da ed.
Sebastopol: O'Reilly.

Ramalho, Luciano. 2021. Python fluido: programación clara, concisa y eficaz.


2da ed. Sebastopol: O'Reilly.

El ecosistema Python proporciona una serie de paquetes opcionales que permiten realizar pruebas
retrospectivas de estrategias comerciales algorítmicas. Cuatro de ellos son los siguientes:

• por cierto

• Operador atrasado

• PyAlgoTrade •

Tirolesa

Zipline, por ejemplo, impulsa la popular plataforma Quantopian para realizar pruebas retrospectivas de
estrategias comerciales algorítmicas, pero también se puede instalar y utilizar localmente.

190 | Capítulo 6: Creación de clases para backtesting basado en eventos


Machine Translated by Google

Aunque estos paquetes podrían permitir una prueba retrospectiva más exhaustiva de las estrategias comerciales
algorítmicas que las clases bastante simples presentadas en este capítulo, el objetivo principal de este libro es capacitar
al lector y al comerciante algorítmico para implementar el código Python de forma autónoma. Incluso si posteriormente
se utilizan paquetes estándar para realizar las pruebas retrospectivas reales, una buena comprensión de los diferentes
enfoques y sus mecanismos es beneficiosa, si no necesaria.

Secuencias de comandos de Python

Esta sección presenta los scripts de Python a los que se hace referencia y se utilizan en este capítulo.

Clase base de backtesting


El siguiente código Python contiene la clase base para el backtesting basado en eventos:

#
# Script Python con clase base # para backtesting
basado en eventos #

# Python para el comercio algorítmico # (c) Dr. Yves J.


Hilpisch # The Python Quants GmbH #

importar numpy como np


importar pandas como pd
desde pylab importar mpl, plt
plt.style.use('seaborn')
mpl.rcParams['font.family'] = 'serif'

clase BacktestBase (objeto):


'''
Clase base para pruebas retrospectivas de estrategias comerciales basadas en eventos.

Atributos
==========

símbolo: cadena
TR RIC (instrumento financiero) a utilizar
inicio: cadena
fecha de inicio para la selección de datos
fin: str fecha
de finalización para la selección de datos
monto: monto
flotante que se invertirá una vez o por operación ftc: costos de
transacción
fijos flotantes por operación (compra o venta) ptc: costos de transacción
proporcionales
flotantes por operación (compra o venta) )

Métodos
=======

Secuencias de comandos de Python | 191


Machine Translated by Google

get_data:
recupera y prepara el conjunto de datos base
plot_data:
traza el precio de cierre para el símbolo
get_date_price:
devuelve la fecha y el precio de la barra dada
print_balance:
imprime el saldo actual (en efectivo)
print_net_wealth:
imprime la riqueza neta actual
place_buy_order:
coloca una orden de
compra
place_sell_order: coloca
una orden
de venta close_out: cierra una posición larga o corta
'''

def __init__(self, símbolo, inicio, fin, cantidad, ftc=0.0, ptc=0.0,


detallado=True): self.symbol = símbolo self.start
= inicio self.end = final
self.initial_amount =
cantidad self.amount
= cantidad self.ftc = ftc self.ptc = ptc
self.units = 0 self.position = 0
self.trades = 0
self.verbose =
detallado
self.get_data()

def get_data(yo):
'''
Recupera y prepara los datos.
'''
raw = pd.read_csv('http://hilpisch.com/pyalgo_eikon_eod_data.csv',
index_col=0, parse_dates=True).dropna() raw
= pd.DataFrame(raw[self.symbol]) raw =
raw.loc[self.start:self.end]
raw.rename(columns={self.symbol: 'precio'}, inplace=True) raw['return']
= np.log(raw / raw.shift(1)) self.data = raw.dropna()

def plot_data(self, cols=Ninguno):


''' Traza los precios de cierre del símbolo.
'''
si cols es Ninguno: cols
= ['precio']
self.data['precio'].plot(figsize=(10, 6), title=self.symbol)

def get_date_price(self, barra):

192 | Capítulo 6: Creación de clases para backtesting basado en eventos


Machine Translated by Google

'''
Fecha de devolución y precio de la barra.
'''
fecha = str(self.data.index[bar])[:10] precio =
self.data.price.iloc[bar] fecha de retorno ,
precio

def print_balance(self, bar): imprime


'''
la información del saldo de efectivo actual.
'''
fecha, precio = self.get_date_price(bar)
print(f'{fecha} | saldo actual {self.amount:.2f}')

def print_net_wealth(self, bar): imprime


'''
la información del saldo de efectivo actual.
'''
fecha, precio = self.get_date_price(bar)
riqueza_neta = self.units * precio + self.amount
print(f'{fecha} | riqueza neta actual {net_wealth:.2f}')

def place_buy_order(self, barra, unidades=Ninguno, monto=Ninguno): '''


Realizar una orden de compra.
'''
fecha, precio = self.get_date_price(bar) si las unidades
son Ninguna: unidades =
int(cantidad / precio)
self.amount ­= (unidades * precio) * (1 + self.ptc) + self.ftc self.units += unidades
self.trades += 1 if
self.verbose:
print(f'{fecha} |
vendiendo {unidades } unidades a {precio:.2f}') self.print_balance(bar)
self.print_net_wealth(bar)

def place_sell_order(self, bar, unidades=Ninguno, cantidad=Ninguno):


''' Realizar una orden de venta.
'''
fecha, precio = self.get_date_price(bar) si las unidades
son Ninguna: unidades =
int(cantidad / precio)
self.amount += (unidades * precio) * (1 ­ self.ptc) ­ self.ftc self.units ­= unidades
self.trades += 1 if
self.verbose:
print(f'{fecha} |
vendiendo {unidades } unidades a {precio:.2f}') self.print_balance(bar)
self.print_net_wealth(bar)

def close_out(self, bar): '''


Cerrar una posición larga o corta.
'''
fecha, precio = self.get_date_price(bar)
self.monto += self.units * precio

Secuencias de comandos de Python | 193


Machine Translated by Google

self.units = 0
self.trades += 1 if
self.verbose:
print(f'{fecha} | inventario {self.units} unidades a {price:.2f}') print('=' * 55)

print('Saldo final [$] {:.2f}'.format(self.monto)) rendimiento = ((self.monto ­


self.monto_inicial) / self.monto_inicial * 100)

print(' Rendimiento neto [%] {:.2f}'.format(perf)) print('Operaciones


ejecutadas [#] {:.2f}'.format(self.trades)) print('=' * 55)

if __name__ == '__main__': bb =
BacktestBase('AAPL.O', '2010­1­1', '2019­12­31', 10000) print(bb.data.info()) print(bb.
datos.tail()) bb.plot_data()

Clase de backtesting de larga duración

A continuación se presenta el código Python con una clase para el backtesting basado en eventos de
estrategias de solo largo plazo, con implementaciones para estrategias basadas en SMA, impulso y
reversión a la media:

#
# Script Python con clase larga únicamente # para
backtesting basado en eventos
#
# Python para el comercio algorítmico # (c) Dr. Yves J.
Hilpisch # The Python Quants GmbH #

desde la importación de BacktestBase *

clase BacktestLongOnly (BacktestBase):

def run_sma_strategy(self, SMA1, SMA2):


''' Backtesting de una estrategia basada en SMA.

Parámetros
==========
SMA1, SMA2: media
móvil simple int de corto y largo plazo (en días)
'''

msg = f'\n\nEjecutando estrategia SMA | SMA1={SMA1} & SMA2={SMA2}' msg += f'\ncostos


'
fijos {self.ftc} | msg += f'costos proporcionales
{self.ptc}' print(msg) print('=' * 55) self.position = 0 #
posición
neutral inicial

194 | Capítulo 6: Creación de clases para backtesting basado en eventos


Machine Translated by Google

self.trades = 0 # aún no hay transacciones


self.amount = self.initial_amount # restablecer el capital inicial self.data['SMA1'] =
self.data['price'].rolling(SMA1).mean() self.data[ 'SMA2'] = self.data['precio'].rolling(SMA2).mean()

para barra en rango (SMA2, len (self.data)): si self.position


== 0:
if self.data['SMA1'].iloc[bar] > self.data['SMA2'].iloc[bar]: self.place_buy_order(bar,
cantidad=self.amount) self.position = 1 # posición larga

elif auto.posición == 1:
if self.data['SMA1'].iloc[bar] < self.data['SMA2'].iloc[bar]: self.place_sell_order(bar,
unidades=self.units) self.position = 0 # mercado neutral

self.close_out(barra)

def run_momentum_strategy(self, impulso):


''' Realizar pruebas retrospectivas de una estrategia basada en el impulso.

Parámetros
==========
impulso: int
número de días para el cálculo del rendimiento medio
'''

msg = f'\n\nEstrategia de impulso en ejecución | {momentum} días' msg += f'\ncostos


'
fijos {self.ftc} | msg += f'costos proporcionales
{self.ptc}' print(msg) print('=' * 55) self.position = 0 # posición
neutral inicial
self.trades = 0 # aún
no hay transacciones self.amount = self.initial_amount # restablecer
el capital inicial self.data['momentum'] =
self.data['return'].rolling(momentum).mean() for bar in range(momentum, len(self.data)): if
self.position = = 0:

si self.data['momentum'].iloc[bar] > 0:
self.place_buy_order(barra, cantidad=self.amount) self.position = 1 #
posición larga
elif self.position == 1: si
self.data['momentum'].iloc[bar] < 0:
self.place_sell_order(bar, unidades=self.units) self.position = 0 #
mercado neutral
self.close_out(barra)

def run_mean_reversion_strategy(self, SMA, umbral): ''' Prueba retrospectiva de


una estrategia basada en reversión a la media.

Parámetros
==========
SMA:
promedio móvil simple int en días umbral:
flotante

Secuencias de comandos de Python | 195


Machine Translated by Google

valor absoluto para la señal basada en desviación relativa a SMA


'''
'
msg = f'\n\nEjecución de estrategia de reversión a la media | msg
+= f'SMA={SMA} & thr={threshold}' msg += f'\ncostos
'
fijos {self.ftc} | msg += f'costos proporcionales
{self.ptc}' print(msg) print('=' * 55) self.position = 0
self.trades = 0
self.amount =
self.initial_amount

self.data['SMA'] = self.data['precio'].rolling(SMA).mean()

para barra en rango (SMA, len(self.data)): if self.position


== 0: if (self.data['price'].iloc[bar]
< self.data['SMA'].iloc[ barra] ­ umbral):
self.place_buy_order(barra, importe=self.importe)
self.position = 1 elif self.position == 1:

if self.data['price'].iloc[bar] >= self.data['SMA'].iloc[bar]: self.place_sell_order(bar,


unidades=self.units) self.position = 0

self.close_out(barra)

if __name__ == '__main__': def


run_strategies():
lobt.run_sma_strategy(42, 252)
lobt.run_momentum_strategy(60)
lobt.run_mean_reversion_strategy(50, 5) lobt =
BacktestLongOnly('AAPL.O', '2010­1­ 1', '2019­12­31', 10000,
detallado = Falso)
run_strategies() #
costos de transacción: 10 USD fijos, 1% de lobt variable =
BacktestLongOnly('AAPL.O', '2010­1­1', '2019­12­31', 10000, 10.0, 0.01, False)

ejecutar_estrategias()

196 | Capítulo 6: Creación de clases para backtesting basado en eventos


Machine Translated by Google

Clase de backtesting larga y corta

El siguiente código Python contiene una clase para el backtesting basado en eventos de
estrategias largas y cortas, con implementaciones para estrategias basadas en SMA, impulso y
reversión media:

#
# Script Python con clase larga­corta # para backtesting basado
en eventos # # Python para comercio algorítmico
#

(c) Dr. Yves J. Hilpisch # Python Quants GmbH # de


importación de BacktestBase *

clase BacktestLongShort (BacktestBase):

def go_long(self, bar, unidades=Ninguno, cantidad=Ninguno):


si posición propia == ­1:
self.place_buy_order(barra, unidades=­self.units) si unidades:

self.place_buy_order(barra, unidades=unidades)
monto elif : si
monto == 'todos': monto =
self.monto
self.place_buy_order(barra, monto=monto)

def go_short(self, bar, unidades=Ninguno, cantidad=Ninguno):


si posición propia == 1:
self.place_sell_order(barra, unidades=self.units) si unidades:

self.place_sell_order(barra, unidades=unidades)
monto elif : si
monto == 'todos': monto =
self.monto
self.place_sell_order(barra, monto=monto)

def run_sma_strategy(self, SMA1, SMA2): msg =


f'\n\nEjecutando la estrategia SMA | SMA1={SMA1} & SMA2={SMA2}' msg += f'\ncostos
'
fijos {self.ftc} | msg += f'costos proporcionales
{self.ptc}' print(msg) print('=' * 55) self.position = 0 #
posición
neutral inicial
self.trades = 0 # aún no hay transacciones self.amount =
self.initial_amount # restablecer el capital inicial
self.data['SMA1'] = self.data['price'].rolling(SMA1).mean() self.data['SMA2'] =
self.data['price'].rolling( SMA2).media()

Secuencias de comandos de Python | 197


Machine Translated by Google

para barra en rango (SMA2, len(self.data)):


si posición propia en [0, ­1]:
si self.data['SMA1'].iloc[bar] > self.data['SMA2'].iloc[bar]:
self.go_long(bar, cantidad='todos')
self.position = 1 # posición larga
si self.position en [0, 1]: if
self.data['SMA1'].iloc[bar] < self.data['SMA2'].iloc[bar]:
self.go_short(bar, cantidad='todos')
self.position = ­1 # posición corta
self.close_out(barra)

def run_momentum_strategy(self, impulso): msg =


f'\n\nEstrategia de impulso en ejecución | {momentum} días' msg +=
'
f'\ncostos fijos {self.ftc} | msg += f'costos
proporcionales {self.ptc}' print(msg) print('=' * 55)
self.position
= 0 # posición
neutral inicial self.trades = 0 # aún no hay transacciones
self.amount = self.initial_amount #
restablecer el capital inicial self.data['momentum'] =
self.data['return'].rolling(momentum).mean() for bar in range(momentum, len(self.data)):
si self.position está en [0, ­1]: if
self.data['momentum'].iloc[bar] > 0:
self.go_long(bar, cantidad='all') self.position = 1 #
posición larga

if self.position in [0, 1]: if


self.data['momentum'].iloc[bar] <= 0: self.go_short(bar,
cantidad='all') self.position = ­1 # posición
corta
self.close_out(barra)

def run_mean_reversion_strategy(self, SMA, umbral): msg =


'
f'\n\nEstrategia de reversión a la media en ejecución |
msg += f'SMA={SMA} & thr={threshold}' msg
'
+= f'\ncostos fijos {self.ftc} | msg += f'costos
proporcionales {self.ptc}' print(msg) print('=' * 55)
self.position
= 0 # posición
neutral inicial self.trades = 0 # aún no hay transacciones
self.amount = self.initial_amount #
restablecer el capital inicial

self.data['SMA'] = self.data['precio'].rolling(SMA).mean()

para barra en rango (SMA, len(self.data)): if


self.position == 0: if
(self.data['price'].iloc[bar] <
self.data['SMA'].iloc[ bar] ­ umbral): self.go_long(bar,
importe=self.initial_amount) self.position = 1 elif
(self.data['precio'].iloc[bar]
>

198 | Capítulo 6: Creación de clases para backtesting basado en eventos


Machine Translated by Google

self.data['SMA'].iloc[bar] + umbral):
self.go_short(bar, cantidad=self.initial_amount)
self.position = ­1 elif
self.position == 1:
if self.data['price'].iloc[bar] >= self.data['SMA'].iloc[bar]: self.place_sell_order(bar,
unidades=self.units) self.position = 0 elif self. posición
== ­1: si
self.data['precio'].iloc[bar] <=
self.data['SMA'].iloc[bar]: self.place_buy_order(bar, unidades=­self.units)
self .posición = 0

self.close_out(barra)

if __name__ == '__main__': def


run_strategies():
lsbt.run_sma_strategy(42, 252)
lsbt.run_momentum_strategy(60)
lsbt.run_mean_reversion_strategy(50, 5) lsbt =
BacktestLongShort('EUR=', '2010­1­1 ', '2019­12­31', 10000, detallado = Falso)

run_strategies() #
costos de transacción: 10 USD fijos, 1% variable lsbt
= BacktestLongShort('AAPL.O', '2010­1­1', '2019­12­31', 10000, 10.0,
0.01, False)
ejecutar_estrategias()

Secuencias de comandos de Python | 199


Machine Translated by Google
Machine Translated by Google

CAPÍTULO 7

Trabajar con datos y sockets en tiempo real

Si quieres encontrar los secretos del universo, piensa en términos de energía, frecuencia y vibración.

­Nikola Tesla

Desarrollar ideas comerciales y probarlas es un proceso bastante asincrónico y no crítico durante el cual hay
múltiples pasos que pueden repetirse o no, durante el cual no hay capital en juego y durante el cual el
rendimiento y la velocidad no son los requisitos más importantes. Recurrir a los mercados para implementar una
estrategia comercial cambia las reglas considerablemente. Los datos llegan en tiempo real y normalmente en
cantidades masivas, lo que hace que sea necesario procesarlos en tiempo real y tomar decisiones en tiempo
real basadas en la transmisión de datos. Este capítulo trata sobre cómo trabajar con datos en tiempo real para
los cuales los enchufes son, en general, la herramienta tecnológica preferida.

En este contexto, he aquí algunas palabras sobre términos técnicos centrales:

Conector de red

Punto final de una conexión en una red informática, también abreviado simplemente conector.

Dirección del zócalo

Combinación de una dirección de Protocolo de Internet (IP) y un número de puerto.

Protocolo de socket
Un protocolo que define y maneja la comunicación de socket, como el Protocolo de control de transferencia
(TCP).

Par de sockets
Combinación de un socket local y uno remoto que se comunican entre sí.

API de socket

La interfaz de programación de aplicaciones que permite el control de enchufes y su comunicación.

201
Machine Translated by Google

Este capítulo se centra en el uso de ZeroMQ como una biblioteca de programación de sockets liviana, rápida y
escalable. Está disponible en múltiples plataformas con contenedores para los lenguajes de programación más
populares. ZeroMQ admite diferentes patrones para la comunicación por socket. Uno de esos patrones es el llamado
patrón editor­suscriptor (PUB­SUB) , donde un único socket publica datos y varios sockets los recuperan
simultáneamente. Esto es similar a una estación de radio que transmite su programa y es escuchado simultáneamente
por miles de personas a través de dispositivos de radio.

Dado el patrón PUB­SUB , un escenario de aplicación fundamental en el comercio algorítmico es la


recuperación de datos financieros en tiempo real de un intercambio, una plataforma comercial o un
proveedor de servicios de datos. Supongamos que ha desarrollado una idea de negociación intradiaria
basada en el par de divisas EUR/USD y la ha probado minuciosamente. Al implementarlo, debe
poder recibir y procesar los datos de precios en tiempo real. Esto encaja exactamente con ese patrón
PUB­SUB . Una instancia central transmite los nuevos datos de ticks a medida que están disponibles
y usted, al igual que probablemente miles de personas más, los recibe y procesa al mismo tiempo.1

Este capítulo está organizado de la siguiente forma. “Ejecución de un servidor de datos de ticks simple” en la página
203 describe cómo implementar y ejecutar un servidor de datos de ticks para obtener datos financieros de muestra.
“Conexión de un cliente de datos de ticks simple” en la página 206 implementa un cliente de datos de ticks para
conectarse al servidor de datos de ticks. “Generación de señales en tiempo real” en la página 208 muestra cómo
generar señales comerciales en tiempo real basadas en datos del servidor de datos de ticks.
Finalmente, “Visualización de datos de transmisión con Plotly” en la página 211 presenta el paquete de trazado
Plotly como una manera eficiente de trazar datos de transmisión en tiempo real.

El objetivo de este capítulo es tener un conjunto de herramientas y enfoques disponibles para poder trabajar con
datos en streaming en el contexto del comercio algorítmico.

El código de este capítulo hace un uso intensivo de los puertos a través


de los cuales se realiza la comunicación por socket y requiere la ejecución
simultánea de dos o más scripts al mismo tiempo. Por lo tanto, se
recomienda ejecutar los códigos de este capítulo en diferentes instancias
de terminal, ejecutando diferentes núcleos de Python. La ejecución dentro
de un único Jupyter Notebook, por ejemplo, no funciona en general. Sin
embargo, lo que funciona es la ejecución del script del servidor de datos
de ticks (“Ejecución de un servidor de datos de ticks simple” en la página
203) en una terminal y la recuperación de datos en un Jupyter Notebook
(“Visualización de la transmisión de datos con Plotly” en página 211).

1 Cuando se habla de simultáneamente o al mismo tiempo, se entiende en un sentido teórico e idealizado. En aplicaciones
prácticas, las diferentes distancias entre los sockets de envío y recepción, las velocidades de la red y otros factores
afectan el tiempo de recuperación exacto por socket de suscriptor.

202 | Capítulo 7: Trabajar con datos y sockets en tiempo real


Machine Translated by Google

Ejecutar un servidor de datos de tick simple


Esta sección muestra cómo ejecutar un servidor de datos de ticks simple basado en precios de
instrumentos financieros simulados. El modelo utilizado para la generación de datos es el movimiento
browniano geométrico (sin dividendos) para el cual se dispone de una discretización de Euler exacta,
como se muestra en la Ecuación 7­1. Aquí, S es el precio del instrumento, r es la tasa corta constante,
σ es el factor de volatilidad constante y z es una variable aleatoria normal estándar. Δt es el intervalo
entre dos observaciones discretas del precio del instrumento.

Ecuación 7­1. Discretización de Euler del movimiento browniano geométrico

σ2
St = St − Δt ∙ exp r − Δt + σ Δtz
2

Haciendo uso de este modelo, “Servidor de datos de ticks de muestra” en la página 218 presenta un
script de Python que implementa un servidor de datos de ticks usando ZeroMQ y una clase llamada
Instrument Price para publicar datos de ticks nuevos y simulados de forma aleatoria. La publicación es
aleatoria de dos maneras. En primer lugar, el valor del precio de las acciones se basa en una simulación
de Monte Carlo. En segundo lugar está la duración del intervalo de tiempo entre dos eventos de
publicación que asignó al azar. El resto de esta sección explica en detalle las partes principales del guión.

La primera parte del siguiente script realiza algunas importaciones, entre otras cosas, para el contenedor
Python de ZeroMQ. También crea instancias de los objetos principales necesarios para abrir un socket
de tipo PUB :

importar zmq
importar
matemáticas
importar tiempo importar aleatorio

contexto = zmq.Context() socket =


contexto.socket(zmq.PUB) socket.bind('tcp://
0.0.0.0:5555')

Esto importa el contenedor de Python para la biblioteca ZeroMQ .

Se crea una instancia de un objeto Context . Es el objeto central de la comunicación del socket.
catión.

El socket en sí se define en función del tipo de socket PUB (“patrón de comunicación”).

El socket se vincula a la dirección IP local (0.0.0.0 en Linux y Mac OS, 127.0.0.1 en Windows) y
al número de puerto 5555.

Ejecución de un servidor de datos de ticks simple | 203


Machine Translated by Google

La clase InstrumentPrice es para la simulación de valores de precios de instrumentos a lo largo del tiempo.
Como atributos, existen los parámetros principales para el movimiento browniano geométrico, además del
símbolo del instrumento y el momento en que se crea una instancia. El único método .simulate_value()
genera nuevos valores para el precio de las acciones dado el tiempo transcurrido desde que se llamó por
última vez y un factor aleatorio:

clase PrecioInstrumento(objeto): def


__init__(self): self.symbol =
'SYMBOL' self.t = time.time()
self.value = 100. self.sigma =
0.4 self.r = 0.01

def simular_valor(yo):
'''
Genera un precio de acción nuevo y aleatorio.
'''

t = tiempo.tiempo() dt
= (t ­ self.t) / (252 * 8 * 60 * 60) dt *= 500

self.t = t
self.value *= math.exp((self.r ­ 0.5 * self.sigma ** 2) * dt + self.sigma * math.sqrt(dt) *
random.gauss(0, 1) )
devolver valor propio

El atributo t almacena el tiempo de la inicialización.

Cuando se llama al método .simulate_value() , se registra la hora actual.

dt representa el intervalo de tiempo entre la hora actual y la almacenada en self.t en fracciones de


año (comercial).

Para tener movimientos más grandes en el precio de los instrumentos, esta línea de código escala la
variable dt (por un factor arbitrario).

El atributo t se actualiza con la hora actual, que representa el punto de referencia para la próxima
llamada del método.

Basado en un esquema de Euler para el movimiento browniano geométrico, se simula el precio de un


nuevo instrumento.

La parte principal del script consiste en la creación de instancias de un objeto de tipo InstrumentPrice y un
bucle while infinito . Durante el ciclo while , se simula el precio de un nuevo instrumento y se crea, imprime
y envía un mensaje a través del socket.

204 | Capítulo 7: Trabajar con datos y sockets en tiempo real


Machine Translated by Google

Finalmente, la ejecución se detiene durante un período de tiempo aleatorio:

ip = PrecioInstrumento()

mientras que Verdadero:

msj = '{} {:.2f}'.format(ip.symbol, ip.simulate_value()) print(msg) socket.send_string(msg)

time.sleep(random.random() * 2)

Esta línea crea una instancia de un objeto InstrumentPrice .

Se inicia un bucle while infinito .

El texto del mensaje se genera en función del atributo del símbolo y del valor del precio de las acciones
recientemente simulado.

El objeto de cadena de mensaje se imprime en la salida estándar.

También se envía a sockets suscritos.

La ejecución del bucle se pausa durante un período de tiempo aleatorio (entre 0 y 2 segundos), simulando la
llegada aleatoria de nuevos datos de ticks a los mercados.

Al ejecutar el script se imprimen los mensajes de la siguiente manera:

(base) pro:ch07 yves$ Python TickServer.py SÍMBOLO 100.00

SÍMBOLO 99.65
SÍMBOLO 99.28
SÍMBOLO 99.09
SÍMBOLO 98.76
SÍMBOLO 98.83
SÍMBOLO 98.82
SÍMBOLO 98,92
SÍMBOLO 98,57
SÍMBOLO 98.81
SÍMBOLO 98.79
SÍMBOLO 98.80

En este punto, aún no se puede verificar si el script también envía el mismo mensaje a través del socket vinculado a
tcp://0.0.0.0:5555 (tcp://127.0.0.1:5555 en Windows). Para este fin, se necesita otro socket que se suscriba al socket
de publicación para completar el par de sockets.

Ejecución de un servidor de datos de ticks simple | 205


Machine Translated by Google

A menudo, la simulación Monte Carlo de los precios de los instrumentos


financieros se basa en intervalos de tiempo homogéneos (como “un día de
negociación”). En muchos casos, ésta es una aproximación “suficientemente
buena” cuando se trabaja, por ejemplo, con precios de cierre de fin de día en horizontes más largos.
En el contexto de los datos de ticks intradiarios, la llegada aleatoria de los datos
es una característica importante que debe tenerse en cuenta.
El script Python para el servidor de datos de ticks implementa tiempos de llegada
aleatorios mediante intervalos de tiempo aleatorios durante los cuales pausa la
ejecución.

Conexión de un cliente de datos de tick simple

El código para el servidor de datos de ticks ya es bastante conciso, y la clase de simulación


InstrumentPrice representa la parte más larga. El código para un cliente de datos de ticks respectivo,
como se muestra en “Cliente de datos de ticks” en la página 219, es aún más conciso. Son solo unas
pocas líneas de código las que crean una instancia del objeto Contexto principal , se conectan al socket
de publicación y se suscriben al canal SYMBOL , que resulta ser el único canal disponible aquí. En el
bucle while , el mensaje basado en cadenas se recibe e imprime.
Eso lo convierte en un guión bastante corto.

La parte inicial del siguiente script es casi simétrica al script del servidor de datos de ticks:

importar zmq

contexto = zmq.Context()
socket = contexto.socket(zmq.SUB)
socket.connect('tcp://0.0.0.0:5555')
socket.setsockopt_string(zmq.SUBSCRIBE, 'SYMBOL')

Esto importa el contenedor de Python para la biblioteca ZeroMQ .

Para el cliente, el objeto principal también es una instancia de zmq.Context.

A partir de aquí, el código es diferente; el tipo de conector está configurado en SUB.

Este socket se conecta a la combinación de puerto y dirección IP respectiva.

Esta línea de código define el llamado canal al que se suscribe el socket.


Aquí solo hay uno, pero aun así se requiere una especificación. Sin embargo, en aplicaciones del
mundo real, es posible que reciba datos para una multitud de símbolos diferentes a través de una
conexión de socket.

206 | Capítulo 7: Trabajar con datos y sockets en tiempo real


Machine Translated by Google

El bucle while se reduce a recuperar los mensajes enviados por el socket del servidor e imprimirlos:

mientras que

Verdadero: datos = socket.recv_string() imprimir(datos)

Este socket recibe datos en un bucle infinito.

Esta es la línea principal de código donde se reciben los datos (mensaje basado en cadenas).

los datos se imprimen en la salida estándar.

La salida del script Python para el cliente de socket es exactamente la misma que la del script Python
para el servidor de socket:

(base) pro:ch07 yves$ Python TickClient.py SÍMBOLO 100.00 SÍMBOLO 99.65

SÍMBOLO 99.28

SÍMBOLO 99.09

SÍMBOLO 98,76

SÍMBOLO 98,83

SÍMBOLO 98,82

SÍMBOLO 98.92

SÍMBOLO 98.57

SÍMBOLO 98,81

SÍMBOLO 98,79

SÍMBOLO 98,80

Recuperar datos en forma de mensajes basados en cadenas a través de comunicación por socket es solo un
requisito previo para las tareas que se deben realizar en función de los datos, como generar señales comerciales
en tiempo real o visualizar los datos. Esto es lo que cubren las dos siguientes secciones.

ZeroMQ también permite la transmisión de otros tipos de objetos. Por ejemplo, existe una opción para enviar

un objeto Python a través de un socket.

Para este fin, el objeto se serializa y deserializa, de forma predeterminada,


con pickle. Los métodos respectivos para lograr esto son .send_pyobj()
y .recv_pyobj() (consulte La API de PyZMQ). En la práctica, sin embargo,
las plataformas y los proveedores de datos atienden a un conjunto
diverso de entornos, siendo Python sólo uno entre muchos lenguajes.
Por lo tanto, la comunicación por socket basada en cadenas se utiliza a
menudo, por ejemplo, en combinación con formatos de datos estándar como
como JSON.

Conexión de un cliente de datos de tick simple | 207


Machine Translated by Google

Generación de señal en tiempo real


Un algoritmo en línea es un algoritmo basado en datos que se reciben de forma incremental (bit a bit) a lo largo del
tiempo. Un algoritmo de este tipo sólo conoce los estados actuales y anteriores de las variables y parámetros
relevantes, pero nada sobre el futuro. Se trata de un escenario realista para los algoritmos de negociación financiera
en los que debe excluirse cualquier elemento de previsión (perfecta). Por el contrario, un algoritmo fuera de línea
conoce el conjunto de datos completo desde el principio. Muchos algoritmos en informática entran en la categoría
de algoritmos fuera de línea, como un algoritmo de clasificación sobre una lista de números.

Para generar señales en tiempo real sobre la base de un algoritmo en línea, es necesario recopilar y procesar datos
a lo largo del tiempo. Consideremos, por ejemplo, una estrategia comercial basada en el impulso de la serie
temporal de los últimos tres intervalos de cinco segundos (consulte el Capítulo 4). Los datos de ticks deben
recopilarse y luego volverse a muestrear, y el impulso debe calcularse en función del conjunto de datos
remuestreado. Con el paso del tiempo se produce una actualización continua e incremental. “Algoritmo en línea de
impulso” en la página 219 presenta un script de Python que implementa la estrategia de impulso, como se describió
anteriormente como algoritmo en línea. Técnicamente, hay dos partes principales además de manejar la
comunicación del socket. Primero están la recuperación y almacenamiento de los datos de ticks:

df = pd.DataFrame() mamá =
3 min_length

= mamá + 1

mientras que Verdadero:

datos = socket.recv_string() t =
datetime.datetime.now() sym, valor =
data.split() df =
df.append(pd.DataFrame({sym: float(valor)}, index=[t]) )

Crea una instancia de un DataFrame de pandas vacío para recopilar los datos de tick.

Define el número de intervalos de tiempo para el cálculo del impulso.

Especifica la longitud mínima (inicial) para que se active la generación de señal.

La recuperación de los datos del tick a través de la conexión de socket.

Se genera una marca de tiempo para la recuperación de datos.

El mensaje basado en cadenas se divide en el símbolo y el valor numérico (aquí sigue siendo un objeto str ).

Esta línea de código primero genera un objeto DataFrame temporal con los nuevos datos y luego lo agrega al
objeto DataFrame existente .

208 | Capítulo 7: Trabajar con datos y sockets en tiempo real


Machine Translated by Google

En segundo lugar está el remuestreo y procesamiento de los datos, como se muestra en el siguiente código
Python. Esto sucede en función de los datos de ticks recopilados hasta un momento determinado. Durante
este paso, los retornos logarítmicos se calculan en función de los datos remuestreados y se deriva el
impulso. El signo del impulso define el posicionamiento a tomar en el instrumento financiero:

dr = df.resample('5s', label='right').last() dr['returns'] = np.log(dr /


dr.shift(1)) if len(dr) > min_length: min_length + = 1
dr['momentum'] =

np.sign(dr['returns'].rolling(mom).mean()) print('\n' + '=' * 51) print('NUEVA SEÑAL | {}


'.format(datetime.datetime.now()))
print('=' * 51) print(dr.iloc[:­1].tail()) if dr['momentum'].iloc[­2] = = 1,0:

print('\ nPosición larga en el mercado.') #


realizar alguna acción (por ejemplo, realizar una orden de compra)
elif dr['momentum'].iloc[­2] == ­1.0: print('\nPosición
corta en el mercado.') # realizar alguna acción
(por ejemplo, realizar una orden de venta)

Los datos de tick se vuelven a muestrear en un intervalo de cinco segundos, tomando el último valor
de tick disponible como relevante.

Esto calcula los retornos del registro en intervalos de cinco segundos.

Esto aumenta en uno la longitud mínima del objeto DataFrame remuestreado.

El impulso y, según su signo, el posicionamiento se derivan de los retornos logarítmicos de tres


intervalos de tiempo remuestreados.

Esto imprime las últimas cinco filas del objeto DataFrame remuestreado .

Un valor de impulso de 1,0 significa una posición larga en el mercado. En producción, la primera señal
o un cambio en la señal desencadena ciertas acciones, como realizar una orden con el corredor. Tenga
en cuenta que se utiliza el segundo pero último valor de la columna de impulso , ya que el último valor

se basa en esta etapa en datos incompletos para el intervalo de tiempo relevante (aún no terminado).
Técnicamente, esto se debe al uso del método pan das .resample() con la parametrización label='right' .

De manera similar, un valor de impulso de ­1,0 implica una posición corta en el mercado y
potencialmente ciertas acciones que podrían desencadenarse, como una orden de venta con un corredor.
Nuevamente, se utiliza el segundo pero último valor de la columna de impulso .

Cuando se ejecuta el script, lleva algún tiempo, dependiendo de los parámetros elegidos, hasta que haya
suficientes datos (remuestreados) disponibles para generar la primera señal.

Generación de Señal en Tiempo Real | 209


Machine Translated by Google

A continuación se muestra un ejemplo intermedio del script del algoritmo de comercio en línea:

(base) yves@pro ch07 $ python OnlineAlgorithm.py

==================================================== =

NUEVA SEÑAL | 2020­05­23 11:33:31.233606


==================================================== =

SÍMBOLO ... impulso


2020­05­23 11:33:15 98.65... 2020­05­23 11:33:20 Yaya
98.53... 2020­05­23 11:33:25 98.83... Yaya
Yaya
2020­05­23 11:33:30 99,33... 1.0

[4 filas x 3 columnas]

Posición larga en el mercado.

==================================================== =

NUEVA SEÑAL | 2020­05­23 11:33:36.185453


==================================================== =

SÍMBOLO ... impulso


2020­05­23 11:33:15 98,65... Yaya
2020­05­23 11:33:20 98,53... Yaya
2020­05­23 11:33:25 98.83... 2020­05­23 11:33:30 Yaya
99.33... 2020­05­23 11:33:35 97.76... 1.0
­1.0

[5 filas x 3 columnas]

Posición corta en el mercado.

==================================================== =

NUEVA SEÑAL | 2020­05­23 11:33:40.077869


==================================================== =

SÍMBOLO... impulso
2020­05­23 11:33:20 98.53... Yaya
2020­05­23 11:33:25 98,83... Yaya
2020­05­23 11:33:30 99,33... 1.0
2020­05­23 11:33:35 97,76... 2020­05­23 11:33:40 ­1.0
98,51... ­1.0

[5 filas x 3 columnas]

Posición corta en el mercado.

Es un buen ejercicio para implementar, basado en el tick presentado.


script de cliente, tanto una estrategia basada en SMA como una reversión a la media
estrategia como algoritmo en línea.

210 | Capítulo 7: Trabajar con datos y sockets en tiempo real


Machine Translated by Google

Visualización de datos en streaming con Plotly


La visualización de datos en tiempo real es generalmente una tarea exigente. Afortunadamente, hoy
en día existen bastantes tecnologías y paquetes de Python disponibles que simplifican significativamente
dicha tarea. A continuación, trabajaremos con Plotly, que es a la vez una tecnología y un servicio que
se utiliza para generar gráficos interactivos y atractivos para datos estáticos y de transmisión. Para
seguir adelante, es necesario instalar el paquete plotly . Además, es necesario instalar varias
extensiones de Jupyter Lab cuando se trabaja con Jupyter Lab. Se debe ejecutar el siguiente comando
en la terminal:

conda instalar plotly ipywidgets jupyter


labextension instalar jupyterlab­plotly jupyter labextension instalar
@jupyter­widgets/jupyterlab­manager jupyter labextension instalar plotlywidget

Los basicos
Una vez instalados los paquetes y la extensión necesarios, la generación de un gráfico de transmisión
es bastante eficiente. El primer paso es la creación de un widget de figura de Plotly:

En [1]: importar zmq


desde fecha y hora importar fecha y hora
importar plotly.graph_objects como funciona

En [2]: símbolo = 'SÍMBOLO'

En [3]: fig = go.FigureWidget()


fig.add_scatter() fig .

Salida[3]: FigureWidget({ 'data':


[{'type': 'scatter', 'uid': 'e1a65f25­287d­4021 ­a210­
c2f41f32426a'}], 'diseño': {'t…

Esto importa los objetos gráficos de plotly.

Esto crea una instancia de un widget de figura Plotly dentro de Jupyter Notebook.

El segundo paso es configurar la comunicación del socket con el servidor de datos de ticks de muestra,
que debe ejecutarse en la misma máquina en un proceso Python separado. Los datos entrantes se
enriquecen con una marca de tiempo y se recopilan en objetos de lista . Estos objetos de lista, a su
vez, se utilizan para actualizar los objetos de datos del widget de figura (consulte la Figura 7­1):

En [4]: contexto = zmq.Context()

En [5]: socket = contexto.socket(zmq.SUB)

En [6]: socket.connect('tcp://0.0.0.0:5555')

Visualización de datos en streaming con Plotly | 211


Machine Translated by Google

En [7]: socket.setsockopt_string(zmq.SUBSCRIBE, 'SÍMBOLO')

En [8]: tiempos = lista() precios


= lista()

En [9]: para _ en rango(50):


mensaje = socket.recv_string()
t = datetime.now()
times.append(t) _,
precio = msg.split()
precios.append(flotante(precio))
fig.data[0].x = veces
fig.data[0].y = precios

objeto de lista para las marcas de tiempo.

objeto de lista para los precios en tiempo real.

Genera una marca de tiempo y la agrega.

Actualiza el objeto de datos con los conjuntos de datos x (veces) e y (precios) modificados .

Figura 7­1. Gráfico de datos de precios en streaming, recuperados en tiempo real a través de una conexión de socket

Tres transmisiones en tiempo real

Un gráfico de transmisión con Plotly puede tener múltiples objetos de gráfico. Esto viene muy bien
cuando, por ejemplo, dos promedios móviles simples (SMA) se visualicen en tiempo real
tiempo además de los ticks de precios. El siguiente código crea nuevamente una instancia de una figura.
widget, esta vez con tres objetos dispersos . Los datos de ticks de los datos de ticks de muestra.

212 | Capítulo 7: Trabajar con datos y sockets en tiempo real


Machine Translated by Google

El servidor se recopila en un objeto Pandas DataFrame . Los dos SMA se calculan después de cada
actualización del socket. Los conjuntos de datos modificados se utilizan para actualizar el objeto de
datos del widget de figura (consulte la Figura 7­2):

En [10]: fig = go.FigureWidget()


fig.add_scatter(nombre='SYMBOL')
fig.add_scatter(nombre='SMA1', línea=dict(ancho=1, guión='punto'),
modo='líneas+marcadores')
fig.add_scatter(nombre='SMA2', línea=dict(ancho=1, guión='guión'),
modo='líneas+marcadores')
higo
Fuera[10]: FiguraWidget({
'datos': [{'nombre': 'SÍMBOLO', 'tipo': 'dispersión', 'uid': 'bcf83157­f015­411b­a834­
d5fd6ac509ba…

En [11]: importar pandas como pd

En [12]: df = pd.DataFrame()

En [13]: para _ en rango(75):


msg = socket.recv_string() t =
datetime.now() sym,
precio = msg.split() df =
df.append(pd.DataFrame({sym: float(price)}, index=[t])) df ['SMA1'] =
df[sym].rolling(5).mean() df['SMA2'] =
df[sym].rolling(10).mean() fig.data[0].x = df. índice
fig.data[1].x = df.index
fig.data[2].x = df.index
fig.data[0].y = df[sym] fig.data[1].y
= df[' SMA1'] fig.data[2].y =
df['SMA2']

Recopila los datos de tick en un objeto DataFrame .

Agrega los dos SMA en columnas separadas al objeto DataFrame .

Nuevamente, es un buen ejercicio combinar el trazado de datos de ticks


en tiempo real y las dos SMA con la implementación de un algoritmo de
comercio en línea basado en las dos SMA. En este caso, se debería
añadir el remuestreo a la implementación, ya que dichos algoritmos de
negociación casi nunca se basan en datos de ticks, sino en barras de
longitud fija (cinco segundos, un minuto, etc.).

Visualización de datos en streaming con Plotly | 213


Machine Translated by Google

Figura 7­2. Gráfico de datos de precios de transmisión y dos SMA calculados en tiempo real

Tres subtramas para tres corrientes


Al igual que con los gráficos de Plotly convencionales, los gráficos de transmisión basados en widgets de
figuras también pueden tener múltiples subtramas. El siguiente ejemplo crea una trama continua con tres
subtramas. El primero traza los datos de ticks en tiempo real. El segundo traza los datos de retorno del registro.
El tercero traza el impulso de la serie temporal basándose en los datos de retornos logarítmicos. La Figura
7­3 muestra una instantánea de todo el objeto de la figura:

En [14]: desde plotly.subplots importa make_subplots

En [15]: f = make_subplots(filas=3, cols=1, share_xaxes=True)


f.append_trace(go.Scatter(nombre='SÍMBOLO'), fila=1, col=1)
f.append_trace(go.Scatter(nombre='RETURN', línea=dict(ancho=1, guión='punto' ), modo='líneas+marcadores',
marcador={'símbolo': 'triángulo arriba'}), fila=2, col=1)

f.append_trace(go.Scatter(nombre='MOMENTUM', línea=dict (ancho = 1, guión = 'guión'),


modo='líneas+marcadores', marcador={'símbolo': 'x'}), fila=3, columna=1)
# f.update_layout(altura=600)

En [16]: fig = go.FigureWidget(f)

En [17]: fig
Out[17]: FigureWidget({ 'data':
[{'name': 'SYMBOL', 'type': 'scatter', 'uid':
'c8db0cac…

En [18]: importar numpy como np

En [19]: df = pd.DataFrame()

214 | Capítulo 7: Trabajar con datos y sockets en tiempo real


Machine Translated by Google

En [20]: para _ en rango(75):


msg = socket.recv_string() t =
datetime.now() sym,
precio = msg.split() df =
df.append(pd.DataFrame({sym: float(price)}, index=[t])) df ['RET'] = np.log(df[sym] /
df[sym].shift(1)) df['MOM'] = df['RET'].rolling(10).mean()
fig.data [0].x = df.index fig.data[1].x = df.index
fig.data[2].x = df.index
fig.data[0].y = df[sym]
fig.data[ 1].y = df['RET']
fig.data[2].y = df['MOM']

Crea tres subtramas que comparten el eje x.

Crea el primer subgráfico para los datos de precios.

Crea la segunda subtrama para los datos de retorno del registro.

Crea la tercera subtrama para los datos de impulso.

Ajusta la altura del objeto de figura.

Figura 7­3. Transmisión de datos de precios, rendimientos de registros e impulso en diferentes subtramas

Transmisión de datos como barras

No todos los datos de transmisión se visualizan mejor como una serie de tiempo ( objeto disperso). Algunos
datos de transmisión se visualizan mejor como barras con altura cambiante. “Servidor de datos de muestra para
gráfico de barras” en la página 220 contiene un script de Python que proporciona datos de muestra adecuados
para una visualización basada en barras. Un único conjunto de datos (mensaje) consta de ocho puntos flotantes.

Visualización de datos en streaming con Plotly | 215


Machine Translated by Google

números. El siguiente código Python genera un diagrama de barras de transmisión (consulte la Figura 7­4).
En este contexto, los datos x normalmente no cambian. Para que funcione el siguiente código, el script
BarsServer.py debe ejecutarse en una instancia local separada de Python:

En [21]: socket = contexto.socket(zmq.SUB)

En [22]: socket.connect('tcp://0.0.0.0:5556')

En [23]: socket.setsockopt_string(zmq.SUBSCRIBE, '')

En [24]: para _ en rango(5): msj =


socket.recv_string() print(msg) 60.361
53.504 67.782
64.165 35.046 94.227 20.221 54.716
79.508 48.210 84.163 73.430 53.288 38.673 4.962 78.920
53.316 80.139 73.733 55.549 21.015 20.556 49.090 29.630
86.664 93.919 33.762 82.095 3.108 92.122 84.194 36.666 37.192 85.305 48.397
36.903 81.835 98.691 61.818 87.121

En [25]: fig = go.FigureWidget() fig.add_bar()


fig . Salida[25]:

FigureWidget({ 'data': [{'type': 'bar',


'uid': '51c6069f­4924­458d ­a1ae­
c5b5b5f3b07f'}], 'diseño': {'templo...

En [26]: x = list('abcdefgh') fig.data[0].x = x


for in range(25):
_
msg = socket.recv_string() y =
msg.split() y = [float(n)
para n en y] fig.data[0].y = y

216 | Capítulo 7: Trabajar con datos y sockets en tiempo real


Machine Translated by Google

Figura 7­4. Transmisión de datos como barras con altura cambiante

Conclusiones
Hoy en día, el comercio algorítmico tiene que lidiar con diferentes tipos de datos de transmisión (en
tiempo real). El tipo más importante a este respecto son los datos de ticks para instrumentos financieros
que, en principio, se generan y publican las 24 horas del día.2 Los sockets son la herramienta tecnológica
preferida para manejar los datos en streaming. Una biblioteca poderosa y al mismo tiempo fácil de usar en
este sentido es ZeroMQ, que se utiliza en este capítulo para crear un servidor de datos de ticks simple que
emite sin cesar datos de ticks de muestra.

Se presentan y explican diferentes clientes de datos de ticks para generar señales comerciales en tiempo
real basadas en algoritmos en línea y para visualizar los datos de ticks entrantes mediante gráficos en
tiempo real usando Plotly. Plotly hace que la visualización de transmisiones dentro de Jupyter Notebook
sea un asunto eficiente, permitiendo, entre otras cosas, múltiples transmisiones al mismo tiempo, tanto en
una sola trama como en diferentes subtramas.

Con base en los temas tratados en este capítulo y en los anteriores, ahora puede trabajar tanto con datos
estructurados históricos (por ejemplo, en el contexto del backtesting de estrategias comerciales) como con
datos de transmisión en tiempo real (por ejemplo, en el contexto de la generación de señales comerciales
en tiempo real). Esto representa un hito importante en el esfuerzo por construir una operación comercial
algorítmica y automatizada.

2 No todos los mercados están abiertos las 24 horas, los 7 días de la semana y, desde luego, no todos los instrumentos
financieros se negocian las 24 horas del día. Sin embargo, los mercados de criptomonedas, por ejemplo el de Bitcoin,
funcionan las 24 horas del día, creando constantemente nuevos datos que los actores activos en estos mercados deben digerir en tiempo real.

Conclusiones | 217
Machine Translated by Google

Referencias y recursos adicionales

El mejor punto de partida para una descripción general completa de ZeroMQ es la página de inicio de ZeroMQ.
La página del tutorial Aprendizaje de ZeroMQ con Python proporciona una descripción general del patrón PUB­SUB
basado en el contenedor de Python para la biblioteca de comunicación de socket.

Un buen lugar para empezar a trabajar con Plotly es la página de inicio de Plotly y, en particular, la página de introducción
a Plotly para Python.

Secuencias de comandos de Python

Esta sección presenta los scripts de Python a los que se hace referencia y se utilizan en este capítulo.

Servidor de datos de ticks de muestra El

siguiente es un script que ejecuta un servidor de datos de ticks de muestra basado en ZeroMQ. Hace uso de la simulación
de Monte Carlo para el movimiento browniano geométrico:

#
# Script Python para simular un
# Servidor de datos de ticks financieros
#
# Python para el comercio algorítmico # (c) Dr. Yves J.
Hilpisch # The Python Quants GmbH #

importar zmq
importar
matemáticas
importar tiempo importar aleatorio

contexto = zmq.Context() socket


= contexto.socket(zmq.PUB) socket.bind('tcp://
0.0.0.0:5555')

clase PrecioInstrumento(objeto): def


__init__(self): self.symbol =
'SYMBOL' self.t = time.time()
self.value = 100. self.sigma =
0.4 self.r = 0.01

def simular_valor(yo):
'''
Genera un precio de acción nuevo y aleatorio.
'''

t = tiempo.tiempo() dt
= (t ­ self.t) / (252 * 8 * 60 * 60) dt *= 500

218 | Capítulo 7: Trabajar con datos y sockets en tiempo real


Machine Translated by Google

self.t = t self.value

*= math.exp((self.r ­ 0.5 * self.sigma ** 2) * dt + self.sigma * math.sqrt(dt) * random.gauss(0, 1) )

devolver valor propio

ip = PrecioInstrumento()

mientras que Verdadero:

msj = '{} {:.2f}'.format(ip.symbol, ip.simulate_value()) print(msg) socket.send_string(msg)

time.sleep(random.random() * 2)

Cliente de datos de marca

El siguiente es un script que ejecuta un cliente de datos de ticks basado en ZeroMQ. Se conecta al servidor
de datos de ticks desde “Servidor de datos de ticks de muestra” en la página 218:

# Python Script # con


cliente Tick Data
#

# Python para el comercio algorítmico # (c) Dr. Yves J.


Hilpisch # The Python Quants GmbH #

importar zmq

contexto = zmq.Context() socket =


contexto.socket(zmq.SUB) socket.connect('tcp://
0.0.0.0:5555') socket.setsockopt_string(zmq.SUBSCRIBE,
'SYMBOL')

mientras que

Verdadero: datos = socket.recv_string()


imprimir(datos)

Algoritmo en línea Momentum El siguiente es

un script que implementa una estrategia comercial basada en el impulso de series de tiempo como un
algoritmo en línea. Se conecta al servidor de datos de ticks desde “Servidor de datos de ticks de muestra”
en la página 218:

# Python Script # con


algoritmo de comercio en línea # # Python para el

comercio algorítmico # (c) Dr. Yves J. Hilpisch # The


Python Quants GmbH

Secuencias de comandos de Python | 219


Machine Translated by Google

importar zmq
importar fecha y hora
importar numpy como np
importar pandas como pd

contexto = zmq.Context() socket =


contexto.socket(zmq.SUB) socket.connect('tcp://
0.0.0.0:5555') socket.setsockopt_string(zmq.SUBSCRIBE,
'SYMBOL')

df = pd.DataFrame() mamá = 3

min_length = mamá + 1

mientras que

Verdadero: datos = socket.recv_string() t =


datetime.datetime.now() sym, valor = data.split()
df = df.append(pd.DataFrame({sym:
float(valor)}, index=[ t])) dr = df.resample('5s', label='right').last() dr['returns'] = np.log(dr / dr.shift(1)) if
len(dr) > min_length: min_length += 1 dr['momentum'] =
np.sign(dr['returns'].rolling(mom).mean()) print('\n' + '=' * 51)
print('NUEVO SEÑAL |

{}'.format(datetime.datetime.now())) print('=' * 51) print(dr.iloc[:­1].tail()) if dr['momentum'].iloc[ ­2] == 1,0:

print('\ nPosición larga en el mercado.') # realizar alguna


acción (por ejemplo, realizar una orden de compra)
elif dr['momentum'].iloc[­2] == ­1.0: print('\nPosición corta en el
mercado.') # realizar alguna acción (por ejemplo, realizar
una orden de venta)

Servidor de datos de muestra para gráfico de barras

El siguiente es un script de Python que genera datos de muestra para un gráfico de barras de transmisión:

# Script de Python para servir # Datos de


barras aleatorias # # Python

para el comercio algorítmico # (c) Dr. Yves J. Hilpisch #


The Python Quants GmbH

importar zmq
importar
matemáticas
importar tiempo importar aleatorio

220 | Capítulo 7: Trabajar con datos y sockets en tiempo real


Machine Translated by Google

contexto = zmq.Context()
conector = contexto.socket (zmq.PUB)
socket.bind('tcp://0.0.0.0:5556')

mientras que Verdadero:

barras = [random.random() * 100 para dentro del _ rango(8)]


' '.join([f'{bar:.3f}' para barra en barras])
mensaje =

imprimir (mensaje)

socket.send_string(msj)
tiempo.dormir(aleatorio.aleatorio() * 2)

Secuencias de comandos de Python | 221


Machine Translated by Google
Machine Translated by Google

CAPÍTULO 8

Negociación de CFD con Oanda

Hoy en día, incluso las entidades pequeñas que comercian con instrumentos complejos o que reciben suficiente
apalancamiento pueden amenazar al sistema financiero global.

—Paul Cantante

Hoy en día, es más fácil que nunca empezar a operar en los mercados financieros. Existe una gran cantidad de
plataformas comerciales en línea (brokers) disponibles entre las que un operador algorítmico puede elegir. La
elección de una plataforma puede verse influenciada por múltiples factores:

Instrumentos

El primer criterio que me viene a la mente es el tipo de instrumento que le interesa negociar. Por ejemplo, uno
podría estar interesado en negociar acciones, fondos cotizados en bolsa (ETF), bonos, divisas, materias
primas, opciones o futuros.

Estrategias
Algunos operadores están interesados en estrategias únicamente largas, mientras que otros también requieren
ventas cortas. Algunos se centran en estrategias de un solo instrumento, mientras que otros se centran en
aquellas que involucran múltiples instrumentos al mismo tiempo.

Costos

Los costos de transacción fijos y variables son un factor importante para muchos comerciantes.
Incluso podrían decidir si una determinada estrategia es rentable o no (véanse, por ejemplo, los capítulos 4 y
6).

223
Machine Translated by Google

Tecnología La
tecnología se ha convertido en un factor importante en la selección de plataformas comerciales.
En primer lugar, están las herramientas que las plataformas ofrecen a los comerciantes. Las herramientas
comerciales están disponibles, en general, para computadoras de escritorio/portátiles, tabletas y teléfonos inteligentes.
En segundo lugar, están las interfaces de programación de aplicaciones (API) a las que los comerciantes pueden
acceder mediante programación.

Jurisdicción
El comercio financiero es un campo fuertemente regulado con diferentes marcos legales
vigentes para diferentes países o regiones. Esto podría prohibir a determinados comerciantes
utilizar determinadas plataformas y/o instrumentos financieros dependiendo de su residencia.

Este capítulo se centra en Oanda, una plataforma comercial en línea que es muy adecuada para implementar
estrategias comerciales algorítmicas y automatizadas, incluso para comerciantes minoristas. La siguiente es una breve
descripción de Oanda junto con los criterios descritos anteriormente:

Instrumentos

Oanda ofrece una amplia gama de productos denominados contratos por diferencia (CFD) (consulte también
“Contratos por diferencia (CFD)” en la página 225 y “Descargo de responsabilidad” en la página 249). Las
principales características de los CFD son que están apalancados (por ejemplo, 10:1 o 50:1) y se negocian con
margen, de modo que las pérdidas pueden exceder el capital inicial.

Estrategias
Oanda permite operar en largo (comprar) y en corto (vender) CFD. Hay diferentes tipos de órdenes disponibles,
como órdenes de mercado o de límite, con o sin objetivos de ganancias y/o stop loss (trailing).

Costos

No existen costos de transacción fijos asociados con la negociación de CFD en Oanda. Sin embargo, existe un
diferencial entre oferta y demanda que genera costos de transacción variables al operar con CFD.

Tecnología
Oanda proporciona la aplicación de negociación fxTrade (Practice), que recupera datos en tiempo real y permite
la negociación (manual, discrecional) de todos los instrumentos (consulte la Figura 8­1). También hay disponible
una aplicación comercial basada en navegador (consulte la Figura 8­2). Una fortaleza importante de la plataforma
son las API RESTful y de transmisión (consulte API Oanda v20) a través de las cuales los operadores pueden
acceder mediante programación a datos históricos y de transmisión, realizar órdenes de compra y venta o
recuperar información de la cuenta. Hay disponible un paquete contenedor de Python (consulte v20 en PyPi).
Oanda ofrece cuentas comerciales en papel gratuitas que brindan acceso completo a todas las capacidades
tecnológicas,

224 | Capítulo 8: Negociación de CFD con Oanda


Machine Translated by Google

lo cual es realmente útil para comenzar a utilizar la plataforma. Esto también simplifica la transición del
comercio en papel al comercio real.

Jurisdicción
Dependiendo de la residencia del titular de la cuenta, la selección de CFD con los que se puede negociar
cambia. Los CFD relacionados con divisas están disponibles básicamente en todos los lugares donde
Oanda está activo. Por ejemplo, es posible que los CFD sobre índices bursátiles no estén disponibles en
determinadas jurisdicciones.

Figura 8­1. Aplicación comercial de Oanda fxTrade Practice

Contratos por diferencia (CFD)


Para obtener más detalles sobre los CFD, consulte la página de CFD de Investopedia o la
página de CFD de Wikipedia más detallada. Hay CFD disponibles sobre pares de divisas (por
ejemplo, EUR/USD), materias primas (por ejemplo, oro), índices bursátiles (por ejemplo, el
índice bursátil S&P 500), bonos (por ejemplo, el Bund alemán a 10 años) y más. Se puede
pensar en una gama de productos que básicamente permita implementar estrategias macro
globales. Desde el punto de vista financiero, los CFD son productos derivados que obtienen su
rentabilidad en función de la evolución de los precios de otros instrumentos. Además, la
actividad comercial (liquidez) influye en el precio de los CFD. Aunque un CFD puede estar
basado en el índice S&P 500, es un producto completamente diferente emitido, cotizado y
respaldado por Oanda (o un proveedor similar).

Negociación de CFD con Oanda | 225


Machine Translated by Google

Esto conlleva ciertos riesgos que los comerciantes deben tener en cuenta. Un evento reciente que
ilustra este problema es el evento del franco suizo que provocó una serie de insolvencias en el espacio
de los corredores en línea. Véase, por ejemplo, el artículo Los corredores de divisas caen como fichas
de dominó tras la decisión del SNB sobre el franco suizo.

Figura 8­2. Aplicación comercial basada en navegador de Oanda

El capítulo está organizado de la siguiente forma. “Configuración de una cuenta” en la página 227 explica
brevemente cómo configurar una cuenta. “La API de Oanda” en la página 229 ilustra los pasos necesarios
para acceder a la API. Según el acceso a la API, “Recuperación de datos históricos” en la página 230
recupera y trabaja con datos históricos para un determinado CFD. “Trabajar con transmisión de datos” en la
página 236 presenta la API de transmisión de Oanda para la recuperación y visualización de datos.
“Implementación de estrategias comerciales en tiempo real” en la página 239 implementa una estrategia
comercial algorítmica y automatizada en tiempo real. Finalmente, “Recuperación de información de la
cuenta” en la página 244 trata de recuperar datos sobre la cuenta misma, como el saldo actual o las
operaciones recientes. En todo momento, el código utiliza una clase contenedora de Python llamada tpqoa
(consulte el repositorio de GitHub).

El objetivo de este capítulo es hacer uso de los enfoques y tecnologías presentados en capítulos anteriores
para operar automáticamente en la plataforma Oanda.

226 | Capítulo 8: Negociación de CFD con Oanda


Machine Translated by Google

Configurar una cuenta


El proceso para configurar una cuenta en Oanda es sencillo y eficiente. Puede elegir entre
una cuenta real y una cuenta de demostración gratuita (“práctica”), que es absolutamente
suficiente para implementar lo que sigue (consulte las Figuras 8­3 y 8­4).

Figura 8­3. Registro de cuenta Oanda (tipos de cuenta)

Si el registro se realiza correctamente y ha iniciado sesión en la cuenta de la plataforma,


debería ver una página de inicio, como se muestra en la Figura 8­5. En el medio, encontrará
un enlace de descarga para la aplicación fxTrade Practice para escritorio , que debe instalar.
Una vez que se está ejecutando, se ve similar a la captura de pantalla que se muestra en la
Figura 8­1.

Configurar una cuenta | 227


Machine Translated by Google

Figura 8­4. Registro de cuenta Oanda (formulario de registro)

Figura 8­5. Página de inicio de la cuenta Oanda

228 | Capítulo 8: Negociación de CFD con Oanda


Machine Translated by Google

La API de Oanda
Después del registro, obtener acceso a las API de Oanda es fácil. Los principales ingredientes necesarios son el
número de cuenta y el token de acceso (clave API). Encontrará el número de cuenta, por ejemplo, en el área
Gestionar fondos. El token de acceso se puede generar en el área Administrar acceso a la API (consulte la Figura

8­6).1

De ahora en adelante, el módulo configparser se utiliza para administrar las credenciales de la cuenta. El módulo
espera un archivo de texto (con un nombre de archivo, digamos, pyalgo.cfg) en el siguiente formato para usar con
una cuenta de práctica de Oanda:

[oanda]
account_id = YOUR_ACCOUNT_ID
access_token = YOUR_ACCESS_TOKEN
account_type = práctica

Figura 8­6. Página de gestión de acceso a la API de Oanda

Para acceder a la API a través de Python, se recomienda utilizar el paquete contenedor de Python tpqoa (ver
repositorio de GitHub) que a su vez depende del paquete v20 de Oanda (ver repositorio de GitHub).

1 La denominación de ciertos objetos no es completamente coherente en el contexto de las API de Oanda. Por ejemplo, la
clave API y el token de acceso se usan indistintamente. Además, el ID de cuenta y el número de cuenta se refieren al mismo
número.

La API de Oanda | 229


Machine Translated by Google

Se instala con el siguiente comando:

pip instalar git+https://github.com/yhilpisch/tpqoa.git

Con estos requisitos previos, puedes conectarte a la API con una sola línea de código:

En [1]: importar tpqoa

En [2]: api = tpqoa.tpqoa('../pyalgo.cfg')

Ajuste la ruta y el nombre del archivo si es necesario.

Este es un hito importante: estar conectado a la API de Oanda permite la recuperación de datos históricos, la
realización programática de pedidos y más.

La ventaja de utilizar el módulo configparser es que simplifica el almacenamiento


y la gestión de las credenciales de la cuenta. En el comercio algorítmico, la
cantidad de cuentas necesarias puede crecer rápidamente. Algunos ejemplos
son una instancia o servidor en la nube, un proveedor de servicios de datos,
una plataforma de comercio en línea, etc.
La desventaja es que la información de la cuenta se almacena en forma de texto
plano, lo que representa un riesgo de seguridad considerable, especialmente
porque la información sobre varias cuentas se almacena en un solo archivo. Por
lo tanto, al pasar a producción, debe aplicar, por ejemplo, métodos de cifrado
de archivos para mantener seguras las credenciales.

Recuperar datos históricos


Un beneficio importante de trabajar con la plataforma Oanda es que se puede acceder al historial completo de
precios de todos los instrumentos de Oanda a través de la API RESTful. En este contexto, la historia completa
se refiere a los diferentes CFD en sí, no a los instrumentos subyacentes en los que se definen.

Búsqueda de instrumentos disponibles para negociar Para obtener una

descripción general de qué instrumentos se pueden negociar para una cuenta determinada, utilice el
método .get_instruments() . Solo recupera los nombres para mostrar y los instrumentos técnicos, nombres de la
API. Hay más detalles disponibles a través de la API, como el tamaño mínimo de la posición:

En [3]: api.get_instruments()[:15]
Salida[3]: [('AUD/CAD', 'AUD_CAD'),
('AUD/CHF', 'AUD_CHF'),
('AUD/HKD', 'AUD_HKD'),
('AUD/JPY', ' AUD_JPY'),
('AUD/NZD', 'AUD_NZD'),
('AUD/SGD', 'AUD_SGD'),

230 | Capítulo 8: Negociación de CFD con Oanda


Machine Translated by Google

('AUD/USD', 'AUD_USD'),
("Australia 200", "AU200_AUD"),
('Petróleo crudo Brent', 'BCO_USD'),
('Bund', 'DE10YB_EUR'),
('CAD/CHF', 'CAD_CHF'),
('CAD/HKD', 'CAD_HKD'),
('CAD/JPY', 'CAD_JPY'),
('CAD/SGD', 'CAD_SGD'),
('CHF/HKD', 'CHF_HKD')]

Prueba retrospectiva de una estrategia Momentum en barras de minutos

El siguiente ejemplo utiliza el instrumento EUR_USD basado en el par de divisas EUR/USD. El objetivo
es realizar pruebas retrospectivas de estrategias basadas en el impulso en barras de un minuto. Los
datos utilizados son de dos días de mayo de 2020. El primer paso es recuperar los datos sin procesar
de Oanda:

En [4]: ayuda (api.get_history)


Ayuda sobre el método get_history en el módulo tpqoa.tpqoa:

método get_history(instrumento, inicio, fin, granularidad, precio, localización=True) de la instancia


tpqoa.tpqoa.tpqoa
Recupera datos históricos del instrumento.

Parámetros
==========

instrumento: cadena
nombre de instrumento válido
inicio, fin: fecha y hora, cadena
Objetos de cadena o fecha y hora de Python para granularidad inicial y final:
encadene una cadena como
'S5', 'M1' o 'D' precio: encadene uno de 'A'
(preguntar), 'B'
(oferta) o 'M' (medio )

Devoluciones
=======

datos: pd.DataFrame
pandas objeto DataFrame con datos

En [5]: instrumento = 'EUR_USD' inicio =


'2020­08­10' fin = '2020­08­12'

granularidad = 'M1' precio


= 'M'

En [6]: datos = api.get_history(instrumento, inicio, fin, granularidad, precio)

En [7]: datos.info()

Recuperar datos históricos | 231


Machine Translated by Google

<clase 'pandas.core.frame.DataFrame'>
DatetimeIndex: 2814 entradas, 2020­08­10 00:00:00 a 2020­08­11
23:59:00
Columnas de datos (un total de 6 columnas):
# Columna Tipo D de recuento no nulo
­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 oh 1 2814 flotador no nulo64


2814 flotador no nulo64
2l 2814 flotador no nulo64
3c4 2814 flotador no nulo64
volumen 5 2814 int64 no nulo
completo 2814 bool no nulo
tipos de datos: bool(1), float64(4), int64(1)
Uso de memoria: 134,7 KB

En [8]: datos[['c', 'volumen']].head()


Salida[8]: volumen c
tiempo
2020­08­10 00:00:00 1.17822 2020­08­10 18
00:01:00 1.17836 2020­08­10 00:02:00 32
1.17828 25
2020­08­10 00:03:00 1.17834 13
2020­08­10 00:04:00 1.17847 43

Muestra la cadena de documentación (texto de ayuda) para el método .get_history() .

Define los valores de los parámetros.

Recupera los datos sin procesar de la API.

Muestra la metainformación del conjunto de datos recuperado.

Muestra las primeras cinco filas de datos para dos columnas.

El segundo paso es implementar el backtesting vectorizado. La idea es simultáneamente


Pruebe brevemente un par de estrategias de impulso. El código es sencillo y
conciso (ver también el Capítulo 4).

Para simplificar, el siguiente código utiliza valores cercanos (c) de precios medios únicamente:2

En [9]: importar numpy como np

En [10]: datos['devoluciones'] = np.log(datos['c'] / datos['c'].shift(1))

En [11]: columnas = []

2 Esto implícitamente ignora los costos de transacción en forma de diferenciales entre oferta y demanda al vender y comprar unidades del
instrumento, respectivamente.

232 | Capítulo 8: Negociación de CFD con Oanda


Machine Translated by Google

En [12]: para impulso en [15, 30, 60, 120]: col =


'position_{}'.format(momentum) data[col] =
np.sign(data['returns'].rolling(momentum) .mean()) cols.append(col)

Calcula los rendimientos logarítmicos en función de los valores de cierre de los precios medios.

Crea una instancia de un objeto de lista vacío para recopilar nombres de columnas.

Define el intervalo de tiempo en barras de minutos para la estrategia de impulso.

Define el nombre de la columna que se utilizará para el almacenamiento en el objeto DataFrame .

Agrega los posicionamientos de la estrategia como una nueva columna.

Agrega el nombre de la columna al objeto de lista .

El paso final es la derivación y trazado del desempeño absoluto de los diferentes


estrategias de impulso. El gráfico de la Figura 8­7 muestra el desempeño de los
estrategias basadas en impulso gráficamente y las compara con el desempeño de
el instrumento base en sí:

En [13]: de pylab import plt


plt.style.use('nacido en el mar')
importar matplotlib como mpl
mpl.rcParams['savefig.dpi'] = 300
mpl.rcParams['font.family'] = 'serif'

En [14]: estratos = ['devoluciones']

En [15]: para col en cols: strat


= 'strategy_{}'.format(col.split('_')[1]) data[strat] =
data[col].shift(1) * data[' devuelve'] strats.append(strat)

En [16]: datos[strats].dropna().cumsum(
).apply(np.exp).plot(figsize=(10, 6));

Define otro objeto de lista para almacenar los nombres de las columnas que se trazarán más adelante.

Itera sobre columnas con las posiciones de las diferentes estrategias.

Deriva el nombre de la nueva columna en la que se muestra el rendimiento de la estrategia.


almacenado.

Calcula los retornos de registro para las diferentes estrategias y los almacena como nuevos.
columnas.

Recuperar datos históricos | 233


Machine Translated by Google

Agrega los nombres de las columnas al objeto de lista para su posterior trazado.

Traza los desempeños acumulados del instrumento y las estrategias.

Figura 8­7. Rendimiento bruto de diferentes estrategias de impulso para el instrumento EUR_USD (barras
de minutos)

Teniendo en cuenta el apalancamiento y el margen

En general, cuando se compra una acción por, digamos, 100 USD, los cálculos de pérdidas y ganancias
(P&L) son sencillos: si el precio de la acción aumenta 1 USD, se gana 1 USD (beneficio no realizado). ); si
el precio de las acciones cae 1 USD, usted pierde 1 USD (pérdida no realizada). Si compras 10 acciones,
simplemente multiplica el resultado por 10.

Operar con CFD en la plataforma de Oanda implica apalancamiento y margen. Esto influye significativamente
en el cálculo de P&L. Para obtener una introducción y una descripción general de este tema, consulte las
Reglas de márgenes de Oanda fxTrade. Un ejemplo sencillo puede ilustrar los aspectos más importantes
en este contexto.

Considere que un operador algorítmico basado en EUR quiere negociar el instrumento EUR_USD en la
plataforma Oanda y quiere obtener una exposición larga de 10.000 EUR a un precio de venta de 1,1. Sin
apalancamiento ni margen, el comerciante (o el programa Python) compraría

234 | Capítulo 8: Negociación de CFD con Oanda


Machine Translated by Google

10.000 unidades del CFD.3 Si el precio del instrumento (tipo de cambio) sube a 1,105 (como tipo medio
entre los precios de oferta y demanda), el beneficio absoluto es 10.000 x 0,005 = 50 o 0,5%.

¿Qué impacto tienen el apalancamiento y el margen? Supongamos que el operador algorítmico elige un
ratio de apalancamiento de 20:1, lo que se traduce en un margen del 5% (= 100% / 20). Esto, a su vez,
implica que el operador sólo necesita aportar un margen inicial de 10.000 EUR x 5% = 500 EUR para
obtener la misma exposición. Si el precio del instrumento sube a 1,105, el beneficio absoluto permanece
igual en 50 EUR, pero el beneficio relativo aumenta a 50 EUR / 500 EUR = 10%. El rendimiento se amplifica
considerablemente en un factor de 20; Este es el beneficio del apalancamiento cuando las cosas van según
lo deseado.

¿Qué pasa si las cosas van mal? Supongamos que el precio del instrumento cae a 1,08 (como el punto
medio entre los precios de oferta y demanda), lo que genera una pérdida de 10.000 x (1,08 ­ 1,1) = ­200
EUR. La pérdida relativa ahora es ­200 EUR / 500 EUR = ­40%. Si a la cuenta con la que opera el operador
algorítmico le quedan menos de 200 EUR en capital/efectivo, es necesario cerrar la posición ya que los
requisitos de margen (regulatorios) ya no se pueden cumplir. Si las pérdidas consumen el margen por
completo, es necesario asignar fondos adicionales como margen para mantener viva la operación.4 La
Figura 8­8 muestra el efecto amplificador

sobre el desempeño de las estrategias de impulso para una relación de apalancamiento de 20:1. El margen
inicial del 5% es suficiente para cubrir posibles pérdidas, ya que no se consume ni siquiera en el peor de
los casos descrito:

En [17]: datos[strats].dropna().cumsum().apply( lambda x: x


* 20).apply(np.exp).plot(figsize=(10, 6));

Multiplica los rendimientos logarítmicos por un factor de 20 según el ratio de apalancamiento supuesto.

El comercio apalancado no sólo amplifica las ganancias potenciales, sino que


también amplifica las pérdidas potenciales. Con operaciones apalancadas
basadas en un factor 10:1 (margen del 10%), un movimiento adverso del 10%
en el instrumento base ya elimina todo el margen. En otras palabras, un
movimiento del 10% conlleva una pérdida del 100%. Por lo tanto, debe
asegurarse de comprender completamente todos los riesgos que implica el
comercio apalancado. También debe asegurarse de aplicar medidas de riesgo
adecuadas, como órdenes de stop loss, que estén en consonancia con su perfil de riesgo y su apetito.

3 Tenga en cuenta que para algunos instrumentos, una unidad significa 1 USD, como en el caso de los CFD relacionados con divisas. Para otros, como para

CFD relacionados con índices (por ejemplo, DE30_EUR), una unidad significa una exposición de divisas al precio (oferta/
demanda) del CFD (por ejemplo, 11.750 EUR).

4 Los cálculos simplificados no tienen en cuenta, por ejemplo, los costos financieros que podrían adeudarse por el comercio apalancado.

Recuperar datos históricos | 235


Machine Translated by Google

Figura 8­8. Rendimiento bruto de las estrategias de impulso para el instrumento EUR_USD con
apalancamiento de 20:1 (barras de minutos)

Trabajar con datos en streaming


Trabajar con transmisión de datos nuevamente se vuelve simple y directo gracias al paquete contenedor de
Python tpqoa. El paquete, en combinación con el paquete v20 , se encarga de la comunicación del socket de
manera que el comerciante algorítmico sólo necesita decidir qué hacer con los datos de streaming:

En [18]: instrumento = 'EUR_USD'

En [19]: api.stream_data(instrumento, parada=10)


2020­08­19T14:39:13.560138152Z 1.19131 1.1915
2020­08­19T14:39:14.088511060Z 1.19134 1.19152
2020­08­19T14:39:14.390081879Z 1.19124 1.19145
2020­08­19T14:39:15.105974700Z 1.19129 1.19144
2020­08­19T14:39:15.375370451Z 1.19128 1.19144
2020­08­19T14:39:15.501380756Z 1.1912 1.19141
2020­08­19T14:39:15.951793928Z 1.1912 1.19138
2020­08­19T14:39:16.354844135Z 1.19123 1.19138
2020­08­19T14:39:16.661440356Z 1.19118 1.19133
2020­08­19T14:39:16.912150908Z 1.19112 1.19132

El parámetro de parada detiene la transmisión después de recuperar una cierta cantidad de ticks.

236 | Capítulo 8: Negociación de CFD con Oanda


Machine Translated by Google

Colocar órdenes de mercado

De manera similar, es sencillo colocar órdenes de compra o venta de mercado con el


método create_order() :

En [20]: ayuda (api.create_order)


Ayuda sobre el método create_order en el módulo tpqoa.tpqoa:

create_order(instrumento, unidades, precio=Ninguno, sl_distance=Ninguno,


tsl_distance=Ninguno, tp_price=Ninguno, comentario=Ninguno, touch=False, suprimir=False,
ret=False) método de la instancia tpqoa.tpqoa.tpqoa
Realiza pedidos con Oanda.

Parámetros
==========

instrumento: cadena
nombre de instrumento válido
unidades: int
número de unidades de instrumento que se comprarán ( int
positivo , por ejemplo, 'unidades = 50') o se
venderán ( int negativo, por ejemplo, 'unidades = ­100')
precio: precio de
orden flotante límite, precio de orden táctil sl_distance:
precio de distancia de stop
loss flotante , obligatorio , por ejemplo, en Alemania tsl_distance: distancia de
stop loss flotante tp_price:
precio de ganancia flotante que se
utilizará para la
operación
comentario: str
toque
de cuerda: booleano
orden market_if_touched (requiere que se establezca el precio) suprimir:
booleano si se suprime
imprimir ret: booleano

si se debe devolver el objeto del pedido

En [21]: api.create_order(instrumento, 1000)

{'id': '1721', 'hora': '2020­08­19T14:39:17.062399275Z', 'ID de usuario': 13834683, 'ID de cuenta':


'101­004­13834683­001', 'ID de lote': '1720', 'requestID': '24716258589170956', 'tipo':
'ORDER_FILL', 'orderID': '1720', 'instrumento': 'EUR_USD', 'unidades': '1000.0',
'gainQuoteHomeConversionFactor': '0.835288642787 ',
'lossQuoteHomeConversionFactor': '0.843683503518', 'precio': 1.19131,
'fullVWAP': 1.19131, 'fullPrice': {'tipo': 'PRECIO', 'ofertas': [{'precio': 1.1911, 'liquidez' : '10000000'}],
'pregunta': [{'precio': 1.19131, 'liquidez': '10000000'}], 'liquidación': 1.1911, 'liquidez': 1.19131}, 'motivo':
'MARKET_ORDER', 'pl': '0.0', 'financiación': '0.0',

Colocación de órdenes de mercado | 237


Machine Translated by Google

'comisión': '0.0', 'guaranteedExecutionFee': '0.0', 'accountBalance':


'98510.7986', 'tradeOpened': {'tradeID': '1721', 'units': '1000.0', 'price': 1.19131 ,
'guaranteedExecutionFee': '0.0', 'halfSpreadCost': '0.0881', 'initialMarginRequired':
'33.3'}, 'halfSpreadCost': '0.0881'}

En [22]: api.create_order(instrumento, ­1500)

{'id': '1723', 'hora': '2020­08­19T14:39:17.200434462Z', 'ID de usuario': 13834683,


'ID de cuenta': '101­004­13834683­001', 'ID de lote': '1722', 'requestID':
'24716258589171315', 'tipo': 'ORDER_FILL', 'orderID': '1722', 'instrumento':
'EUR_USD', 'unidades': '­1500.0', 'gainQuoteHomeConversionFactor':
' 0.835288642787', 'lossQuoteHomeConversionFactor':
'0.843683503518', 'precio': 1.1911, 'fullVWAP': 1.1911, 'fullPrice': {'tipo': 'PRECIO',
'ofertas': [{'precio': 1.1911, 'liquidez' ': '10000000'}], 'pregunta': [{'precio': 1.19131,
'liquidez': '9999000'}], 'liquidación': 1.1911, 'liquidez': 1.19131}, 'razón':
'MARKET_ORDER' , 'pl': '­0.1772', 'financiamiento': '0.0', 'comisión': '0.0',
'guaranteedExecutionFee': '0.0', 'accountBalance': '98510.6214', 'tradeOpened':
{'tradeID' : '1723', 'unidades': '­500.0', 'precio': 1.1911,
'guaranteedExecutionFee': '0.0', 'halfSpreadCost': '0.0441', 'initialMarginRequired':
'16.65'}, 'tradesClosed': [ {'tradeID': '1721', 'unidades': '­1000.0', 'precio': 1.1911,
'realizedPL': '­0.1772', 'financiamiento': '0.0', 'guaranteedExecutionFee': '0.0', '
halfSpreadCost': '0.0881'}], 'halfSpreadCost': '0.1322'}

En [23]: api.create_order(instrumento, 500)

{'id': '1725', 'hora': '2020­08­19T14:39:17.348231507Z', 'ID de usuario': 13834683,


'ID de cuenta': '101­004­13834683­001', 'ID de lote': '1724', 'requestID':
'24716258589171775', 'tipo': 'ORDER_FILL', 'orderID': '1724', 'instrumento':
'EUR_USD', 'unidades': '500.0', 'gainQuoteHomeConversionFactor':
'0.835313189428 ', 'lossQuoteHomeConversionFactor':
'0.84370829686', 'precio': 1.1913, 'fullVWAP': 1.1913, 'fullPrice': {'tipo': 'PRECIO',
'ofertas': [{'precio': 1.19104, 'liquidez' : '9998500'}], 'pregunta': [{'precio': 1.1913, 'liquidez':
'9999000'}], 'liquidación': 1.19104, 'liquidez': 1.1913}, 'motivo':
'MARKET_ORDER', 'pl': '­0.0844', 'financiamiento': '0.0', 'comisión': '0.0',
'guaranteedExecutionFee': '0.0', 'accountBalance': '98510.537', 'tradesClosed':
[{'tradeID' : '1723', 'unidades': '500.0', 'precio': 1.1913, 'realizedPL': '­0.0844',
'financiamiento': '0.0', 'guaranteedExecutionFee': '0.0', 'halfSpreadCost': '0.0546 '}],
'halfSpreadCost': '0.0546'}

Muestra todas las opciones para realizar órdenes de mercado, de límite y de mercado si se tocan.

Abre una posición larga mediante orden de mercado.

238 | Capítulo 8: Negociación de CFD con Oanda


Machine Translated by Google

Se pone corto después de cerrar la posición larga mediante orden de mercado.

Cierra la posición corta mediante orden de mercado.

Aunque la API de Oanda permite la realización de diferentes tipos de órdenes, este capítulo
y el siguiente capítulo se centra principalmente en las órdenes de mercado para ir instantáneamente en largo o en corto
cada vez que aparece una nueva señal.

Implementación de estrategias comerciales en tiempo real


Esta sección presenta una clase personalizada que negocia automáticamente el instrumento EUR_USD
en la plataforma Oanda basándose en una estrategia de impulso. Se llama MomentumTrader
y se presenta en “Python Script” en la página 247. A continuación se explica el
clase línea por línea, comenzando con el método 0 . La clase misma hereda de la
clase tpqoa :

importar tpqoa
importar numpy como np
importar pandas como pd

clase MomentumTrader(tpqoa.tpqoa):
def __init__(self, conf_file, instrumento, bar_length, impulso, unidades,
*argumentos, **kwargs):
super(MomentumTrader, self).__init__(conf_file)
self.position = 0
self.instrument = instrumento
self.momentum = impulso
self.bar_length = bar_length self.units =
unidades
self.raw_data = pd.DataFrame()
self.min_length = self.momentum + 1

Valor de la posición inicial (neutral al mercado).

Instrumento a negociar.

Longitud de la barra para el remuestreo de los datos del tick.

Número de intervalos para el cálculo del impulso.

Número de unidades a negociar.

Un objeto DataFrame vacío que se rellenará con datos de tick.

La longitud mínima inicial de la barra para el inicio de la negociación en sí.

Implementación de estrategias comerciales en tiempo real | 239


Machine Translated by Google

El método principal es el método .on_success() , que implementa la lógica comercial.


para la estrategia de impulso:

def on_success(self, tiempo, oferta, demanda):


'''
''' Toma medidas cuando llegan nuevos datos de ticks.
print(self.ticks, end=' ') self.raw_data =
self.raw_data.append(pd.DataFrame(
{'oferta': oferta, 'preguntar': preguntar}, index=[pd.Timestamp(time)]))
self.data = self.raw_data.resample(
self.bar_length, etiqueta='derecha').last().ffill().iloc[:­1]
self.data['mid'] = self.data.mean(axis=1) self.data['returns'] =
np.log(self.data['mid'] /
self.data['mid'].shift(1))
self.data['posición'] = np.sign(
self.data['devoluciones'].rolling(self.momentum).mean())

if len(self.data) > self.min_length: self.min_length +=


1 if self.data['position'].iloc[­1]
== 1: if self.position == 0: self.create_order(self .instrumento,
auto.unidades)

elif auto.posición == ­1:


self.create_order(self.instrumento, self.units * 2) self.position = 1 elif
self.data['position'].iloc[­1]
== ­1: if self.position == 0: self.create_order (auto.instrumento,
­auto.unidades)

elif auto.posición == 1:
self.create_order(self.instrumento, ­self.units * 2) self.position = ­1

Este método se llama cada vez que llegan nuevos datos de ticks.

Se imprime el número de ticks recuperados.

Los datos de ticks se recopilan y almacenan.

Luego, los datos de tick se vuelven a muestrear a la longitud de barra adecuada.

Los precios medios se calculan...

…en función del cual se derivan los retornos del registro.

La señal (posicionamiento) se deriva en función del parámetro/atributo de impulso .


(a través de un algoritmo en línea).

Cuando hay datos suficientes o nuevos, se aplica la lógica comercial y el mínimo


La longitud aumenta en uno cada vez.

240 | Capítulo 8: Negociación de CFD con Oanda


Machine Translated by Google

Comprueba si el último posicionamiento (“señal”) es 1 (largo).

Si la posición actual del mercado es 0 (neutral)…

… se inicia una orden de compra de unidades propias .

Si es ­1 (corto)…

… se inicia una orden de compra de 0 .

La posición de mercado self.position se establece en +1 (largo).

Comprueba si el último posicionamiento (“señal”) es ­1 (corto).

Si la posición actual del mercado es 0 (neutral)…

… se inicia una orden de venta para ­self.units .

Si es +1 (largo)…

… se inicia una orden de venta de 0 .

La posición de mercado self.position se establece en ­1 (corta).

Según esta clase, comenzar con el comercio algorítmico automatizado son solo cuatro
líneas de código. El código Python que sigue inicia una sesión comercial automatizada:

En [24]: importar MomentumTrader como MT

En [25]: mt = MT.MomentumTrader('../pyalgo.cfg',
instrumento=instrumento,
bar_length='10s',
impulso=6,
unidades=10000)

En [26]: mt.stream_data(mt.instrument, parada=500)

El archivo de configuración con las credenciales.

Se especifica el parámetro del instrumento .

Se proporciona el parámetro bar_length para el remuestreo.

Se define el parámetro de impulso , que se aplica a los datos remuestreados entre sí.
vals.

Implementación de estrategias comerciales en tiempo real | 241


Machine Translated by Google

Se establece el parámetro de unidades , que especifica el tamaño de la posición para posiciones


largas y cortas.

Esto inicia la transmisión y con ello el comercio; se detiene después de 100 tics.

El código anterior proporciona el siguiente resultado:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
101 102 103 104 105 106 107 108 109 110 1 11 112 113 114 115 116 117 118 119 120
121 122 123 124 125 126 127 128 129 130 131
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
149 150 151 152 153

{'id': '1727', 'hora': '2020­08­19T14:40:30.443867492Z', 'ID de usuario': 13834683, 'ID


de cuenta': '101­004­13834683­001', 'ID de lote': '1726', 'requestID':
'42730657405829101', 'tipo': 'ORDER_FILL', 'orderID': '1726', 'instrumento':
'EUR_USD', 'unidades': '10000.0', 'gainQuoteHomeConversionFactor':
'0.8350012403 ', 'lossQuoteHomeConversionFactor':
'0.843393212565', 'precio': 1.19168, 'fullVWAP': 1.19168, 'fullPrice': {'tipo': 'PRECIO',
'ofertas': [{'precio': 1.19155, 'liquidez' : '10000000'}], 'pregunta': [{'precio': 1.19168, 'liquidez':
'10000000'}], 'liquidación': 1.19155, 'liquidez': 1.19168}, 'motivo': 'MARKET_ORDER',
'pl': '0.0', 'financiamiento': '0.0', 'comisión': '0.0', 'guaranteedExecutionFee': '0.0',
'accountBalance': '98510.537', 'tradeOpened': {'tradeID': ' 1727', 'unidades': '10000.0',
'precio': 1.19168, 'guaranteedExecutionFee': '0.0', 'halfSpreadCost':
'0.5455', 'initialMarginRequired': '333.0'}, 'halfSpreadCost': '0.5455'}

154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223

{'id': '1729', 'hora': '2020­08­19T14:41:11.436438078Z', 'ID de usuario': 13834683, 'ID


de cuenta': '101­004­13834683­001', 'ID de lote': '1728', 'requestID':
'42730657577912600', 'tipo': 'ORDER_FILL', 'orderID': '1728', 'instrumento':
'EUR_USD', 'unidades': '­20000.0', 'gainQuoteHomeConversionFactor':
' 0.83519398913', 'lossQuoteHomeConversionFactor':
'0.843587898569', 'precio': 1.19124, 'fullVWAP': 1.19124, 'fullPrice': {'tipo': 'PRECIO',
'ofertas': [{'precio': 1.19124, 'liquidez ': '10000000'}], 'pregunta': [{'precio': 1.19144, 'liquidez':
'10000000'}], 'liquidación': 1.19124, 'liquidez': 1.19144}, 'razón': 'MARKET_ORDER' ,
'pl': '­3.7118', 'financiamiento': '0.0', 'comisión': '0.0', 'guaranteedExecutionFee': '0.0',
'accountBalance': '98506.8252', 'tradeOpened': {'tradeID' : '1729', 'unidades':
'­10000.0', 'precio': 1.19124, 'guaranteedExecutionFee': '0.0', 'halfSpreadCost':
'0.8394', 'initialMarginRequired': '333.0'},

242 | Capítulo 8: Negociación de CFD con Oanda


Machine Translated by Google

'tradesClosed': [{'tradeID': '1727', 'units': '­10000.0', 'price': 1.19124, 'realizedPL':


'­3.7118', 'financiamiento': '0.0', 'guaranteedExecutionFee': '0.0',
'halfSpreadCost': '0.8394'}], 'halfSpreadCost': '1.6788'}

224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
245 246 247 248 249 250 251 252 253 254 255 256 257 258
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
333 334 335 336 337 338 339 340 341 342 343
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394

{'id': '1731', 'hora': '2020­08­19T14:42:20.525804142Z', 'ID de usuario': 13834683, 'ID


de cuenta': '101­004­13834683­001', 'ID de lote': '1730', 'requestID':
'42730657867512554', 'tipo': 'ORDER_FILL', 'orderID': '1730', 'instrumento':
'EUR_USD', 'unidades': '20000.0', 'gainQuoteHomeConversionFactor':
'0.835400847964 ', 'lossQuoteHomeConversionFactor':
'0.843796836386', 'precio': 1.19111, 'fullVWAP': 1.19111, 'fullPrice': {'tipo': 'PRECIO',
'ofertas': [{'precio': 1.19098, 'liquidez' : '10000000'}], 'pregunta': [{'precio': 1.19111, 'liquidez':
'10000000'}], 'liquidación': 1.19098, 'liquidez': 1.19111}, 'motivo': 'MARKET_ORDER',
'pl': '1.086', 'financiamiento': '0.0', 'comisión': '0.0', 'guaranteedExecutionFee': '0.0',
'accountBalance': '98507.9112', 'tradeOpened': {'tradeID': ' 1731', 'unidades': '10000.0',
'precio': 1.19111, 'guaranteedExecutionFee': '0.0', 'halfSpreadCost':
'0.5457', 'initialMarginRequired': '333.0'}, 'tradesClosed': [{'tradeID ': '1729', 'unidades':
'10000.0', 'precio': 1.19111, 'realizedPL': '1.086', 'financiamiento': '0.0',
'guaranteedExecutionFee': '0.0', 'halfSpreadCost': '0.5457 '}], 'halfSpreadCost':
'1.0914'}

395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
416 417 418 419 420 421 422 423 424 425 426 427 428 429
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500

Finalmente, cierre la posición final:


En [27]: oo = mt.create_order(instrumento, unidades=­mt.position * mt.units, ret=Verdadero,
suprimir=Verdadero)
oh

Salida[27]: {'id': '1733', 'hora':


'2020­08­19T14:43:17.107985242Z', 'ID de usuario':
13834683, 'ID de
cuenta': '101­004­13834683­001' , 'ID de lote':
'1732',

Implementación de estrategias comerciales en tiempo real | 243


Machine Translated by Google

'requestID': '42730658106750652', 'tipo':


'ORDER_FILL', 'orderID':
'1732', 'instrumento':
'EUR_USD', 'unidades':
'­10000.0',
'gainQuoteHomeConversionFactor': '0.835327206922', '
lossQuoteHomeConversionFactor': '0.843722455232', 'precio':
1.19109, 'fullVWAP':
1.19109, 'fullPrice': {'tipo':
'PRECIO', 'ofertas': [{'precio': 1.19109,
'liquidez': '10000000 '}], 'pregunta': [{'precio': 1.19121, 'liquidez':
'10000000'}], 'closeoutBid': 1.19109, 'closeoutAsk': 1.19121}, 'motivo':
'MARKET_ORDER', 'pl' :
'­0.1687', 'financiamiento':
'0.0', 'comisión': '0.0',

'guaranteedExecutionFee':
'0.0', 'accountBalance':
'98507.7425', 'tradesClosed': [{'tradeID':
'1731 ',

'unidades': '­10000.0',
'precio': 1.19109,
'realizedPL': '­0.1687',
'financiamiento': '0.0',
'guaranteedExecutionFee': '0.0',
'halfSpreadCost': '0.5037'}],
'halfSpreadCost': '0.5037'}

Cierra la posición final.

Recuperar información de la cuenta


Con respecto a la información de la cuenta, el historial de transacciones y similares,
también es conveniente trabajar con la API RESTful de Oanda. Por ejemplo, después
de ejecutar la estrategia de impulso de la sección anterior, es posible que el operador
algorítmico desee inspeccionar el saldo actual de la cuenta comercial. Esto es posible
mediante el método .get_account_summary() :
En [28]: api.get_account_summary()
Salida[28]: {'id': '101­004­13834683­001', 'alias':
'Primario', 'moneda':
'EUR', 'saldo':
'98507.7425', 'createdByUserID':
13834683, 'createdTime':
'2020­03­19T06:08:14.363139403Z',
'guaranteedStopLossOrderMode': 'DISABLED', 'pl': '
­1273.126 ', '
resettablePL': '­1273.126',
'resettablePLTime': '0' ,
'financiación': '­219.1315',

244 | Capítulo 8: Negociación de CFD con Oanda


Machine Translated by Google

'comisión': '0.0',
'guaranteedExecutionFees': '0.0',
'marginRate': '0.0333',
'openTradeCount': 1,
'openPositionCount': 1,
'pendingOrderCount': 0,
'hedgingEnabled': False,
'unrealizedPL ': '929.8862', 'NAV':
'99437.6287',
'marginUsed': '377.76',
'marginAvailable': '99064.4945',
'positionValue': '3777.6',
'marginCloseoutUnrealizedPL': '935.8183',
'marginCloseoutNAV': '99443.5608',
'marginCloseoutMarginUsed': '377.76',
'marginCloseoutPercent': '0.0019',
'marginCloseoutPositionValue': '3777.6',
'withdrawalLimit': ' 98507.7425 ',
'marginCallMarginUsed': '377.76',
'marginCallPercent': ' 0.0038 ',
'últimaID de transacción': '1733'}

La información sobre las últimas operaciones se recibe con el método .get_transactions() :

En [29]: api.get_transactions(tid=int(oo['id']) ­ 2)
Salida[29]: [{'id': '1732', 'time':
'2020­08­19T14:43:17.107985242Z', 'userID':
13834683, 'accountID':
'101­004­13834683­001 ', 'batchID': '1732',
'requestID':
'42730658106750652', 'tipo':
'MARKET_ORDER',
'instrumento': 'EUR_USD',
'unidades': '­10000.0',
'timeInForce': 'FOK' ,
'positionFill': 'DEFAULT', 'motivo':
'CLIENT_ORDER'}, {'id': '1733',

'hora': '2020­08­19T14:43:17.107985242Z', 'ID de


usuario': 13834683, 'ID
de cuenta': '101­004­13834683­001', 'ID de
lote': '1732', 'ID de
solicitud': ' 42730658106750652', 'tipo':
'ORDER_FILL', 'orderID':
'1732', 'instrumento':
'EUR_USD', 'unidades':
'­10000.0',
'gainQuoteHomeConversionFactor': '0.835327206922',
'lossQuoteHomeConversionFactor': '0.843722 455232 ', 'precio':
1.19109, 'fullVWAP':
1.19109, 'fullPrice': {'tipo':
'PRECIO',

Recuperar información de la cuenta | 245


Machine Translated by Google

'ofertas': [{'precio': 1,19109, 'liquidez': '10000000'}], 'preguntas':


[{'precio': 1,19121, 'liquidez': '10000000'}], 'liquidación de liquidación':
1,19109, 'closeoutAsk':
1.19121}, 'motivo':
'MARKET_ORDER', 'pl':
'­0.1687',
'financiamiento': '0.0',
'comisión': '0.0',
'guaranteedExecutionFee': '0.0',
'accountBalance ': '98507.7425',
'tradesClosed': [{'tradeID': '1731',
'unidades': '­10000.0',
'precio': 1.19109,
'realizedPL': '­0.1687',
'financiamiento': '0.0',
'guaranteedExecutionFee': '0.0',
'halfSpreadCost': '0.5037'}],
'halfSpreadCost': '0.5037'}]

Para una descripción general concisa, también está disponible el método .print_transactions() :

En [30]: api.print_transactions(tid=int(oo['id']) ­ 18)


1717 | 2020­08­19T14:37:00.803426931Z | EUR_USD | ­10000.0 | 0,0 1719 |
2020­08­19T14:38:21.953399006Z | EUR_USD | 10000.0 | 6.8444 1721 |
2020­08­19T14:39:17.062399275Z | EUR_USD | 1000,0 | 0,0 1723 |
2020­08­19T14:39:17.200434462Z | EUR_USD | ­1500.0 | ­0,1772 1725 |
2020­08­19T14:39:17.348231507Z | EUR_USD | 500,0 | ­0.0844 1727 |
2020­08­19T14:40:30.443867492Z | EUR_USD | 10000.0 | 0,0 1729 |
2020­08­19T14:41:11.436438078Z | EUR_USD | ­20000.0 | ­3.7118 1731 |
2020­08­19T14:42:20.525804142Z | EUR_USD | 20000,0 | 1.086 1733 |
2020­08­19T14:43:17.107985242Z | EUR_USD | ­10000.0 | ­0.1687

Conclusiones
La plataforma Oanda permite una entrada fácil y directa al mundo del comercio algorítmico automatizado.
Oanda se especializa en los llamados contratos por diferencia (CFD). Dependiendo del país de
residencia del comerciante, existe una gran variedad de instrumentos con los que se puede negociar.

Una ventaja importante de Oanda desde un punto de vista tecnológico son las API modernas y potentes
a las que se puede acceder fácilmente a través de un paquete contenedor de Python dedicado (v20).
Este capítulo muestra cómo configurar una cuenta, cómo conectarse a las API con Python, cómo
recuperar datos históricos (barras de un minuto) para realizar pruebas retrospectivas, cómo recuperar
datos de transmisión en tiempo real, cómo operar automáticamente con un CFD basado en una
estrategia de impulso y cómo recuperar información de la cuenta y el historial detallado de transacciones.

246 | Capítulo 8: Negociación de CFD con Oanda


Machine Translated by Google

Referencias y recursos adicionales

Visite las páginas de ayuda y soporte de Oanda en Ayuda y soporte para obtener más información sobre
la plataforma de Oanda y aspectos importantes del comercio de CFD.

El portal para desarrolladores de Oanda Getting Started proporciona una descripción detallada de las API.

Secuencia de comandos de Python

El siguiente script de Python contiene una clase de streaming personalizada de Oanda que intercambia
automáticamente una estrategia de impulso:

#
# Python Script # con
Momentum Trading Class # para Oanda v20 #

# Python para el comercio algorítmico # (c) Dr. Yves J.


Hilpisch # The Python Quants GmbH #

importar tpqoa
importar numpy como
np importar pandas como pd

clase MomentumTrader(tpqoa.tpqoa):
def __init__(self, conf_file, instrumento, bar_length, impulso, unidades, *args, **kwargs):

super(MomentumTrader, self).__init__(conf_file) self.position = 0


self.instrument =
instrumento self.momentum = impulso

self.bar_length = bar_length self.units =


unidades self.raw_data =
pd.DataFrame() self.min_length =
self.momentum + 1

def on_success(self, tiempo, oferta, demanda):


'''
''' Toma medidas cuando llegan nuevos datos de ticks.
print(self.ticks, end=' ') self.raw_data =
self.raw_data.append(pd.DataFrame(
{'oferta': oferta, 'preguntar': preguntar}, index=[pd.Timestamp(time)])) self.data =
self.raw_data.resample( self.bar_length,
label='right').last() .ffill().iloc[:­1]
self.data['mid'] = self.data.mean(axis=1) self.data['returns'] =
np.log(self.data['mid'] / self.data['mid']. cambio(1))

self.data['posición'] =
np.sign( self.data['returns'].rolling(self.momentum).mean())

Referencias y recursos adicionales | 247


Machine Translated by Google

if len(self.data) > self.min_length: self.min_length += 1


if self.data['position'].iloc[­1]
== 1: if self.position == 0: self.create_order(self .instrumento,
auto.unidades)

elif auto.posición == ­1:


self.create_order(self.instrumento, self.units * 2) self.position = 1 elif
self.data['position'].iloc[­1]
== ­1: if self.position == 0: self.create_order (auto.instrumento,
­auto.unidades)

elif auto.posición == 1:
self.create_order(self.instrumento, ­self.units * 2) self.position = ­1

si __nombre__ == '__principal__': estrato


=2
si estrato == 1:
mamá = MomentumTrader('../pyalgo.cfg', 'DE30_EUR', '5s', 3, 1)
mamá.stream_data(mom.instrument, stop=100)
mom.create_order(mom.instrument, unidades=­mom. posición * mom.units) elif strat == 2: mom =
MomentumTrader('../
pyalgo.cfg', instrumento='EUR_USD',
bar_length='5s', impulso=6, unidades=100000)
mom.stream_data(mom.instrument, stop=100)
mom.create_order(mom.instrument, unidades=­mom.position * mom.units) else: print(' Estrategia

desconocida.')

248 | Capítulo 8: Negociación de CFD con Oanda


Machine Translated by Google

CAPÍTULO 9

Comercio de divisas con FXCM

A las instituciones financieras les gusta llamar trading a lo que hacen. Seamos honestos. No es comercio;
es apostar.

—Graydon Carter

Este capítulo presenta la plataforma comercial de FXCM Group, LLC (“FXCM” en adelante), con
su interfaz de programación de aplicaciones (API) RESTful y streaming, así como el paquete
contenedor Python fcxmpy. Al igual que Oanda, es una plataforma muy adecuada para el
despliegue de estrategias comerciales algorítmicas y automatizadas, incluso para comerciantes
minoristas con posiciones de capital más pequeñas. FXCM ofrece a los comerciantes minoristas e
institucionales una serie de productos financieros que se pueden negociar tanto a través de
aplicaciones comerciales tradicionales como programáticamente a través de su API. Los productos
se centran en pares de divisas, así como contratos por diferencia (CFD) sobre, entre otros, los
principales índices bursátiles y materias primas. En este contexto, consulte también “Contratos por
diferencia (CFD)” en la página 225 y “Descargo de responsabilidad” en la página 249.

Descargo de responsabilidad

Operar con Forex/CFD con margen conlleva un alto nivel de riesgo y puede no ser adecuado para
todos los inversores, ya que podría sufrir pérdidas superiores a los depósitos. El apalancamiento
puede jugar en su contra. Los productos están destinados a clientes minoristas y profesionales.
Debido a ciertas restricciones impuestas por las leyes y regulaciones locales, los clientes minoristas
residentes en Alemania podrían sufrir una pérdida total de los fondos depositados, pero no están
sujetos a obligaciones de pago posteriores más allá de los fondos depositados. Sea consciente y
comprenda plenamente todos los riesgos asociados con el mercado y el comercio. Antes de
comercializar cualquier producto, considere cuidadosamente su situación financiera y su nivel de
experiencia. Cualquier opinión, noticia, investigación, análisis, precio u otra información se
proporciona como comentario general del mercado y no constituye asesoramiento de inversión. El
comentario del mercado no ha sido preparado de acuerdo con los requisitos legales diseñados para promover la independencia.

249
Machine Translated by Google

de investigación de inversiones y, por lo tanto, no está sujeto a ninguna prohibición de


negociar antes de su difusión. Ni las plataformas comerciales ni el autor aceptarán
responsabilidad por ninguna pérdida o daño, incluida, entre otras, cualquier pérdida de
ganancias, que pueda surgir directa o indirectamente del uso o la confianza en dicha información.

Con respecto a los criterios de plataforma discutidos en el Capítulo 8, FXCM ofrece lo siguiente:

Instrumentos

Productos FX (por ejemplo, la negociación de pares de divisas), contratos por diferencias (CFD) sobre índices
bursátiles, materias primas o productos de tasas.

Estrategias
FXCM permite, entre otras cosas, posiciones largas y cortas (apalancadas), órdenes de entrada al mercado y
órdenes de stop loss y objetivos de obtención de beneficios.

Costos

Además del diferencial entre oferta y demanda, generalmente se debe pagar una tarifa fija por cada operación
con FXCM. Hay diferentes modelos de precios disponibles.

Tecnología
FXCM proporciona al operador algorítmico una API RESTful moderna a la que se puede acceder, por ejemplo,
mediante el uso del paquete contenedor fxcmpy de Python. También están disponibles aplicaciones comerciales
estándar para computadoras de escritorio, tabletas y teléfonos inteligentes.

Jurisdicción
FXCM está activo en varios países a nivel mundial (por ejemplo, en el Reino Unido o Alemania). Dependiendo
del país, es posible que ciertos productos no estén disponibles/ofrecidos debido a regulaciones y restricciones.

Este capítulo cubre las funcionalidades básicas de la API comercial FXCM y el paquete fxcmpy Python necesarios
para implementar una estrategia comercial algorítmica y automatizada mediante programación. Está estructurado de
la siguiente manera. “Primeros pasos” en la página 251 muestra cómo configurar todo para que funcione con la API
REST de FXCM para operaciones algorítmicas.
“Recuperación de datos” en la página 251 muestra cómo recuperar y trabajar con datos financieros (hasta el nivel de
marca). “Trabajar con la API” en la página 256 es fundamental porque ilustra tareas típicas implementadas utilizando
la API RESTful, como recuperar datos históricos y de transmisión, realizar pedidos o buscar información de cuentas.

250 | Capítulo 9: Comercio de divisas con FXCM


Machine Translated by Google

Empezando
Encontrará documentación detallada de la API de FXCM en https://oreil.ly/Df_7e. Para instalar el paquete contenedor
de Python fxcmpy, ejecute lo siguiente en el shell:

instalación de pip fxcmpy

La documentación del paquete fxcmpy se encuentra en http://fxcmpy.tpq.io.

Para comenzar con la API comercial de FXCM y el paquete fxcmpy , es suficiente una cuenta de
demostración gratuita con FXCM. Se puede abrir una cuenta de este tipo en la cuenta demo de FXCM.
1 El siguiente paso es crear un token API único (por ejemplo, YOUR_FXCM_API_TOKEN) desde la
cuenta de demostración. A continuación se abre una conexión con la API, por ejemplo de la siguiente manera:

importar fxcmpy
api = fxcmpy.fxcmpy(access_token=YOUR_FXCM_API_TOKEN, log_level='error')

Alternativamente, puede usar el archivo de configuración creado en el Capítulo 8 para conectarse a la API.
El contenido de este archivo debe modificarse de la siguiente manera:

[FXCM]
log_level = error log_file
= PATH_TO_AND_NAME_OF_LOG_FILE access_token
= YOUR_FXCM_API_TOKEN

Luego uno puede conectarse a la API a través de lo siguiente:

importar fxcmpy
api = fxcmpy.fxcmpy(config_file='pyalgo.cfg')

De forma predeterminada, el servidor se conecta al servidor de demostración. Sin embargo, mediante el uso del
parámetro del servidor , se puede realizar la conexión al servidor de operaciones en vivo (si existe dicha cuenta):

api = fxcmpy.fxcmpy(config_file='pyalgo.cfg', servidor='demo') api =


fxcmpy.fxcmpy(config_file='pyalgo.cfg', servidor='real')

Se conecta al servidor de demostración.

Se conecta al servidor comercial en vivo.

Recuperando datos
FXCM brinda acceso a conjuntos de datos históricos de precios de mercado, como datos de ticks, en una variante
preempaquetada. Esto significa que se pueden recuperar, por ejemplo, archivos comprimidos de los servidores FXCM
que contienen datos de ticks para el tipo de cambio EUR/USD de la semana.

1 Tenga en cuenta que las cuentas demo de FXCM solo se ofrecen para ciertos países.

Empezando | 251
Machine Translated by Google

10 de 2020. La recuperación de datos históricos de velas de la API se explica en la sección siguiente.

Recuperar datos de ticks

Para varios pares de divisas, FXCM proporciona datos históricos de ticks. El paquete fxcmpy facilita la
recuperación de dichos datos de ticks y el trabajo con ellos. Primero, algunas importaciones:

En [1]: tiempo de importación


importar numpy como np
importar pandas como pd
importar fecha y hora como dt
desde pylab importar mpl, plt
plt.style.use('seaborn')
mpl.rcParams['savefig.dpi'] = 300
mpl.rcParams['font.family'] = 'serifa'

El segundo es un vistazo a los símbolos disponibles (pares de divisas) para los cuales hay datos de tick
disponibles:

En [2]: desde fxcmpy importe fxcmpy_tick_data_reader como tdr

En [3]: imprimir(tdr.get_available_symbols())
('AUDCAD', 'AUDCHF', 'AUDJPY', 'AUDNZD', 'CADCHF', 'EURAUD', 'EURCHF',
'EURGBP', 'EURJPY', 'EURUSD', 'GBPCHF', 'GBPJPY', 'GBPNZD', 'GBPUSD',
'GBPCHF', 'GBPJPY', 'GBPNZD', 'NZDCAD', 'NZDCHF', 'NZDJPY', 'NZDUSD',
'USDCAD', 'USDCHF', 'USDJPY')

El siguiente código recupera los datos de ticks de una semana para un solo símbolo. El objeto pandas
DataFrame resultante tiene más de 4,5 millones de filas de datos:

En [4]: inicio = dt.datetime(2020, 3, 25)


detener = dt.datetime(2020, 3, 30)

En [5]: td = tdr('EURUSD', inicio, parada)

En [6]: td.get_raw_data().info() <clase


'pandas.core.frame.DataFrame'> Índice: 4504288
entradas, 22/03/2020 21:12:02.256 al 27/03/2020
20:59:00.022
Columnas de datos (un total de 2 columnas):
# Tipo de columna
­­­ ­­­­­­ ­­­­­

0 Oferta float64 1 Pregunte


a float64 dtypes: float64(2)
uso de memoria: 103,1+
MB

En [7]: td.get_data().info()
<clase 'pandas.core.frame.DataFrame'> DatetimeIndex:
4504288 entradas, 2020­03­22 21:12:02.256000 a

252 | Capítulo 9: Comercio de divisas con FXCM


Machine Translated by Google

2020­03­27 20:59:00.022000
Columnas de datos (un total de 2 columnas):
# Tipo de columna
­­­ ­­­­­­ ­­­­­

0 Oferta flotante64
1 Pregunta a float64
tipos de datos: float64(2)
uso de memoria: 103,1 MB

En [8]: td.get_data().head()
Fuera[8]: Oferta Preguntar

2020­03­22 21:12:02.256 1.07006 1.07050


2020­03­22 21:12:02.258 1.07002 1.07050
2020­03­22 21:12:02.259 1.07003 1.07033
2020­03­22 21:12:02.653 1.07003 1.07034
2020­03­22 21:12:02.749 1.07000 1.07034

Esto recupera el archivo de datos, lo descomprime y almacena los datos sin procesar en un DataFrame .
objeto (como un atributo del objeto resultante).

El método .get_raw_data() devuelve el objeto DataFrame con los datos sin procesar
para los cuales los valores de índice siguen siendo objetos str .

El método .get_data() devuelve un objeto DataFrame para el cual el índice tiene


se ha transformado en un DatetimeIndex. 2

Dado que los datos de ticks se almacenan en un objeto DataFrame , es sencillo seleccionar un subconjunto de
datos e implementar tareas típicas de análisis financiero en él. Figura 9­1
muestra un gráfico de los precios medios derivados para el subconjunto y un promedio móvil simple
(AME):

En [9]: sub = td.get_data(start='2020­03­25 12:00:00',


fin = '2020­03­25 12:15:00')

En [10]: sub.head()
Fuera[10]: Oferta Preguntar

2020­03­25 12:00:00.067 1.08109 1.0811


2020­03­25 12:00:00.072 1.08110 1.0811
2020­03­25 12:00:00.074 1.08109 1.0811
2020­03­25 12:00:00.078 1.08111 1.0811
2020­03­25 12:00:00.121 1.08112 1.0811

En [11]: sub['Medio'] = sub.media(eje=1)

En [12]: sub['SMA'] = sub['Mid'].rolling(1000).mean()

2 La conversión de DatetimeIndex lleva mucho tiempo, por lo que existen dos métodos diferentes relacionados con
Recuperación de datos de marca.

Recuperar datos | 253


Machine Translated by Google

En [13]: sub[['Mid', 'SMA']].plot(figsize=(10, 6), lw=1.5);

Elige un subconjunto del conjunto de datos completo.

Calcula los precios medios a partir de los precios de oferta y demanda.

Deriva valores de SMA en intervalos de 1000 ticks.

Figura 9­1. Precios históricos de tick medio para EUR/USD y SMA

Recuperación de datos de velas

Además, FXCM proporciona acceso a datos históricos de velas (más allá de la API). Los datos de velas son datos
para ciertos intervalos de tiempo homogéneos (“barras”) con valores de apertura, máximo, mínimo y cierre para
precios de oferta y demanda.

Primero, un vistazo a los símbolos disponibles para los cuales se proporcionan datos de velas:

En [14]: desde fxcmpy importe fxcmpy_candles_data_reader como cdr

En [15]: imprimir(cdr.get_available_symbols())
('AUDCAD', 'AUDCHF', 'AUDJPY', 'AUDNZD', 'CADCHF', 'EURAUD', 'EURCHF',
'EURGBP', 'EURJPY', 'EURUSD', 'GBPCHF', 'GBPJPY', 'GBPNZD', 'GBPUSD',
'GBPCHF', 'GBPJPY', 'GBPNZD', 'NZDCAD', 'NZDCHF', 'NZDJPY', 'NZDUSD',
'USDCAD', 'USDCHF', 'USDJPY')

254 | Capítulo 9: Comercio de divisas con FXCM


Machine Translated by Google

En segundo lugar, la propia recuperación de datos. Es similar a la recuperación de datos de ticks. La única diferencia
La diferencia es que es necesario especificar un valor de período , o la longitud de la barra (por ejemplo, m1
durante un minuto, H1 durante una hora o D1 durante un día):

En [16]: inicio = dt.datetime(2020, 4, 1)


detener = dt.fechahora(2020, 5, 1)

En [17]: período = 'H1'

En [18]: velas = cdr('EURUSD', inicio, parada, punto)

En [19]: datos = velas.get_data()

En [20]: datos.info()
<clase 'pandas.core.frame.DataFrame'>
DatetimeIndex: 600 entradas, 2020­03­29 21:00:00 a 2020­05­01 20:00:00
Columnas de datos (un total de 8 columnas):
# Columna Tipo D de recuento no nulo
­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 BidOpen 600 no nulo flotador64


1 oferta alta 600 no nula flotador64
2 BidLow 600 no nulo flotador64
3 BidClose 600 no nulo flotador64
4 AskOpen 600 no nulo flotador64
5 AskHigh 600 no nulo flotador64
6 AskLow 600 no nulo flotador64
7 AskClose 600 no nulo flotador64
tipos de datos: float64(8)
uso de memoria: 42,2 KB

En [21]: datos[datos.columnas[:4]].tail()
Fuera[21]: Oferta Oferta abierta Oferta alta Oferta baja Oferta cerrada
2020­05­01 16:00:00 1.09976 1.09996 1.09850 1.09874
2020­05­01 17:00:00 1.09874 1.09888 1.09785 1.09818
2020­05­01 18:00:00 1.09818 1.09820 1.09757 1.09766
2020­05­01 19:00:00 1.09766 1.09816 1.09747 1.09793
2020­05­01 20:00:00 1.09793 1.09812 1.09730 1.09788

En [22]: datos[datos.columnas[4:]].tail()
Fuera[22]: PreguntarAbrir PreguntarAlto PreguntarBajo PreguntarCerrar
2020­05­01 16:00:00 1.09980 1.09998 1.09853 1.09876
2020­05­01 17:00:00 1.09876 1.09891 1.09786 1.09818
2020­05­01 18:00:00 1.09818 1.09822 1.09758 1.09768
2020­05­01 19:00:00 1.09768 1.09818 1.09748 1.09795
2020­05­01 20:00:00 1.09795 1.09856 1.09733 1.09841

Especifica el valor del período .

Valores de apertura, máximo, mínimo y cierre de los precios de oferta.

Valores de apertura, máximo, mínimo y cierre para los precios de venta.

Recuperar datos | 255


Machine Translated by Google

Para concluir esta sección, el código Python que sigue y calcula los precios de cierre medio, calcula
dos SMA y traza los resultados (consulte la Figura 9­2):

En [23]: datos['MidClose'] = datos[['BidClose', 'AskClose']].mean(axis=1)

En [24]: datos['SMA1'] = datos['MidClose'].rolling(30).mean()


datos['SMA2'] = datos['MidClose'].rolling(100).mean()

En [25]: datos[['MidClose', 'SMA1', 'SMA2']].plot(figsize=(10, 6));

Calcula los precios de cierre medio a partir de los precios de oferta y cierre.

Calcula dos SMA: uno para un intervalo de tiempo más corto y otro para uno más largo.

Figura 9­2. Precios históricos de cierre medio por hora para EUR/USD y dos SMA

Trabajando con la API


Mientras que las secciones anteriores recuperan datos históricos de ticks y velas preempaquetados
desde los servidores FXCM, esta sección muestra cómo recuperar datos históricos a través de la
API. Sin embargo, se necesita un objeto de conexión a la API de FXCM. Por lo tanto, primero, aquí
está la importación del paquete fxcmpy , la conexión a la API (basada en el token API único) y un
vistazo a los instrumentos disponibles. Es posible que haya más instrumentos disponibles en
comparación con los conjuntos de datos preempaquetados:

En [26]: importar fxcmpy

En [27]: fxcmpy.__version__
Fuera[27]: '1.2.6'

256 | Capítulo 9: Comercio de divisas con FXCM


Machine Translated by Google

En [28]: api = fxcmpy.fxcmpy(config_file='../pyalgo.cfg')

En [29]: instrumentos = api.get_instruments()

En [30]: imprimir(instrumentos)
['EUR/USD', 'USD/JPY', 'GBP/USD', 'USD/CHF', 'EUR/CHF', 'AUD/USD', 'USD/CAD',
'NZD/USD', ' EUR/GBP', 'EUR/JPY', 'GBP/JPY', 'CHF/JPY', 'GBP/CHF', 'EUR/AUD',
'EUR/CAD', 'AUD/CAD', 'AUD/ JPY', 'CAD/JPY', 'NZD/JPY', 'GBP/CAD', 'GBP/NZD',
'GBP/AUD', 'AUD/NZD', 'USD/SEK', 'EUR/SEK' , 'EUR/NOK', 'USD/NOK', 'USD/
MXN', 'AUD/CHF', 'EUR/NZD', 'USD/ZAR', 'USD/HKD', 'ZAR/JPY', ' USD/TRY', 'EUR/
TRY', 'NZD/CHF', 'CAD/CHF', 'NZD/CAD', 'TRY/JPY', 'USD/ILS', 'USD/CNH',
'AUS200' , 'ESP35', 'FRA40', 'GER30', 'HKG33', ' JPN225', 'NAS100', 'SPX500',
'UK100', 'US30', 'Cobre', 'CHN50', 'EUSTX50', ' USDOLLAR', 'US2000', 'USOil',
'UKOil', ' SOYF', 'NGAS', 'USOilSpot', 'UKOilSpot', 'WHEATF', 'CORNF', 'Bund', 'XAU/
USD', ' XAG/USD', 'EMBasket', 'JPYBasket', 'BTC/USD', 'BCH/USD', 'ETH/USD', 'LTC/
USD', 'XRP/USD', 'CryptoMajor', 'EOS/ USD', 'XLM/USD', 'ESPORTS', 'BIOTECH',
'CANNABIS', 'FAANG', 'CHN.TECH', 'CHN.ECOMM', 'USEquities']

Esto se conecta a la API; ajuste la ruta/nombre de archivo.

Recuperación de datos históricos

Una vez conectado, la recuperación de datos para intervalos de tiempo específicos se logra mediante
una única llamada a un método. Cuando se utiliza el método .get_candles() , el período del parámetro
puede ser uno de m1, m5, m15, m30, H1, H2, H3, H4, H6, H8, D1, W1 o M1. La Figura 9­3 muestra
los precios de cierre de la barra de un minuto para el instrumento EUR/USD (par de divisas):

En [31]: velas = api.get_candles('USD/JPY', periodo='D1', número=10)

En [32]: velas[velas.columnas[:4]]
Fuera[32]: oferta oferta abierta oferta cerrada oferta alta oferta baja
fecha
2020­08­07 21:00:00 105.538 105.898 106.051 105.452 2020­08­09
21:00:00 105.871 105.846 105.871 105.844
2020­08­10 21:00:00 105.846 105.914 106.197 105.702
2020­08­11 21:00:00 105.914 106.466 106.679 105.870
2020­08­12 21:00:00 106.466 106.848 107.009 106.434 2020­08­13
21:00:00 106.848 106.893 107.044 106.560 2020­08­14 21:00:00 106.893
106.535 107.033 106.429
2020­08­17 21:00:00 106.559 105.960 106.648 105.937
2020­08­18 21:00:00 105.960 105.378 106.046 105.277
2020­08­19 21:00:00 105.378 105.528 105.599 105.097

En [33]: velas[velas.columnas[4:]]
Fuera[33]: preguntarabrir preguntarcerrar preguntarpreguntar altotick bajocantidad
fecha
2020­08­07 21:00:00 105.557 105.969 106.062 105.484 253759 2020­08­09 21:00:00
105.983 105.952 105.989 105.925 20
2020­08­10 21:00:00 105.952 105.986 106.209 105.715 161841

Trabajar con la API | 257


Machine Translated by Google

2020­08­11 21:00:00 105.986 106.541 106.689 105.929 243813


2020­08­12 21:00:00 106.541 106.950 107.022 106.447 248989
2020­08­13 21:00:00 106.950 106.983 107.056 106.572 214735
2020­08­14 21:00:00 106.983 106.646 107.044 106.442 164244
2020­08­17 21:00:00 106.680 106.047 106.711 105.948 163629
2020­08­18 21:00:00 106.047 105.431 106.101 105.290 215574
2020­08­19 21:00:00 105.431 105.542 105.612 105.109 151255

En [34]: inicio = dt.datetime(2019, 1, 1) fin = dt.datetime(2020,


6, 1)

En [35]: velas = api.get_candles('EUR/GBP', period='D1',


inicio=inicio, parada=fin)

En [36]: velas.info() <clase


'pandas.core.frame.DataFrame'>
DatetimeIndex: 438 entradas, 2019­01­02 22:00:00 a 2020­06­01 21:00:00
Columnas de datos (un total de 9 columnas):
# Columna Tipo D de recuento no nulo
­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 bidopen 438 no nulo 1 bidclose 438 flotador64


no nulo 2 bidhigh 438 no nulo 3 bidlow flotador64
438 no nulo 4 Askopen 438 no nulo 5 flotador64
Askclose 438 no nulo flotador64
flotador64
flotador64
6 Askhigh 438 no nulo 7 Asklow 438 no flotador64
nulo 8 tickqty 438 tipos no nulos: flotador64
float64(8), int64(1) int64

uso de memoria: 34,2 KB

En [37]: velas = api.get_candles('EUR/USD', periodo='m1', número=250)

En [38]: velas['askclose'].plot(figsize=(10, 6))

Recupera los 10 precios de fin de día más recientes.

Recupera los precios de fin de día de todo un año.

Recupera los precios de barra de un minuto más recientes disponibles.

Los datos históricos recuperados de la API RESTful de FXCM pueden cambiar


con el modelo de fijación de precios de la cuenta. En particular, el promedio
Los diferenciales entre oferta y demanda pueden ser mayores o menores para diferentes modelos de precios.

ofrecido por FXCM a diferentes grupos de comerciantes.

258 | Capítulo 9: Comercio de divisas con FXCM


Machine Translated by Google

Figura 9­3. Precios históricos de cierre de venta para EUR/USD (barras de minutos)

Recuperación de datos en tiempo real Si

bien los datos históricos son importantes, por ejemplo, para realizar pruebas retrospectivas de estrategias
comerciales algorítmicas, se requiere acceso continuo a datos en tiempo real o en tiempo real (durante el horario
comercial) para implementar y automatizar estrategias comerciales algorítmicas. Al igual que la API de Oanda,
la API de FXCM también permite la suscripción a flujos de datos en tiempo real para todos los instrumentos. El
paquete contenedor fxcmpy admite esta funcionalidad porque permite proporcionar funciones definidas por el
usuario (las llamadas funciones de devolución de llamada) para procesar el flujo de datos en tiempo real suscrito.

El siguiente código Python presenta una función de devolución de llamada tan simple (solo imprime elementos
seleccionados del conjunto de datos recuperados) y la utiliza para procesar los datos recuperados en tiempo
real, después de una suscripción al instrumento deseado (aquí EUR/ USD):

En [39]: salida def (datos, marco de datos):


imprimir('%3d | %s | %s | %6.5f, %6.5f'
% (len(marco de datos), datos['Símbolo'],
pd.to_datetime(int(datos['Actualizado']), unidad='ms'),
datos['Tarifas'][0], datos['Tarifas'][1]))

En [40]: api.subscribe_market_data('EUR/USD', (salida,))


2 | EUR/USD | 2020­08­19 14:32:36.204000 | 1.19319, 1.19331 3 | EUR/
USD | 2020­08­19 14:32:37.005000 | 1.19320, 1.19331 4 | EUR/USD |
2020­08­19 14:32:37.940000 | 1.19323, 1.19333 5 | EUR/USD |
2020­08­19 14:32:38.429000 | 1.19321, 1.19332 6 | EUR/USD |
2020­08­19 14:32:38.915000 | 1.19323, 1.19334 7 | EUR/USD |
2020­08­19 14:32:39.436000 | 1.19321, 1.19332

Trabajar con la API | 259


Machine Translated by Google

8 | EUR/USD | 2020­08­19 14:32:39.883000 | 1.19317, 1.19328 9 | EUR/


USD | 2020­08­19 14:32:40.437000 | 1.19317, 1.19328 10 | EUR/USD |
2020­08­19 14:32:40.810000 | 1.19318, 1.19329

En [41]: api.get_last_price('EUR/USD')
Salida[41]: Oferta 1.19318 Oferta Alta
Baja 1.19329
1.19534
1.19217 Nombre:

2020­08­19 14:32:40.810000, tipo d: float64

11 | EUR/USD | 2020­08­19 14:32:41.410000 | 1.19319, 1.19329

En [42]: api.unsubscribe_market_data('EUR/USD')

Esta es la función de devolución de llamada que imprime ciertos elementos de los datos recuperados.
colocar.

Aquí está la suscripción a un flujo de datos específico en tiempo real. Los datos se procesan de forma
asincrónica siempre que no exista un evento de "cancelación de suscripción".

Durante la suscripción, el método .get_last_price() devuelve el último conjunto de datos disponible.

Esto se da de baja del flujo de datos en tiempo real.

Funciones de devolución de llamada

Las funciones de devolución de llamada son una forma flexible de procesar datos de
transmisión en tiempo real basados en una función de Python o incluso en varias funciones de este tipo.
Se pueden utilizar para tareas simples, como la impresión de datos entrantes, o tareas
complejas, como generar señales comerciales basadas en algoritmos comerciales en línea.

Colocación de órdenes

La API de FXCM permite la colocación y gestión de todo tipo de órdenes que también están disponibles a
través de la aplicación comercial de FXCM (como órdenes de entrada u órdenes de trailing stop loss).3 Sin
embargo, el siguiente código ilustra la compra y venta básica del mercado. órdenes únicamente, ya que
generalmente son suficientes para al menos comenzar con el comercio algorítmico.

3 Consulte la documentación en http://fxcmpy.tpq.io.

260 | Capítulo 9: Comercio de divisas con FXCM


Machine Translated by Google

El siguiente código primero verifica que no haya posiciones abiertas y luego abre posiciones diferentes.
diferentes posiciones a través del método .create_market_buy_order() :

En [43]: api.get_open_positions()
Out[43]: Marco de datos vacío
Columnas: []
Índice: []

En [44]: orden = api.create_market_buy_order('EUR/USD', 100)

En [45]: sel = ['tradeId', 'montoK', 'moneda',


'brutoPL', 'isBuy']

En [46]: api.get_open_positions()[sel]
Fuera[46]: cantidad de tradeIdK moneda brutaPL esComprar
0 169122817 100 EUR/USD ­9,21945 Verdadero

En [47]: orden = api.create_market_buy_order('EUR/GBP', 50)

En [48]: api.get_open_positions()[sel]
Fuera[48]: cantidad de tradeIdK moneda brutaPL esComprar
0 169122817 100 EUR/USD ­8,38125 Verdadero
1 169122819 50 EUR/GBP ­9,40900 Verdadero

Muestra las posiciones abiertas para la cuenta conectada (predeterminada).

Abre una posición de 100.000 en el par de divisas EUR/USD.4

Muestra las posiciones abiertas solo para elementos seleccionados.

Abre otra posición de 50.000 en el par de divisas EUR/GBP .

Mientras .create_market_buy_order() abre o aumenta posiciones, .cre


ate_market_sell_order() permite cerrar o disminuir posiciones. También hay
métodos más generales que permiten el cierre de posiciones, como el siguiente código
ilustra:

En [49]: orden = api.create_market_sell_order('EUR/USD', 25)

En [50]: orden = api.create_market_buy_order('EUR/GBP', 50)

En [51]: api.get_open_positions()[sel]
Fuera[51]: cantidad de tradeIdK moneda brutaPL esComprar
0 169122817 100 EUR/USD ­7,54306 Verdadero

4 Las cantidades están en miles del instrumento para pares de divisas. Además, tenga en cuenta que diferentes cuentas pueden tener
diferentes ratios de apalancamiento. Esto implica que la misma posición podría requerir más o menos capital (margen)
dependiendo del ratio de apalancamiento relevante. Ajuste las cantidades del ejemplo a valores más bajos si es necesario. Ver
https://oreil.ly/xUHMP.

Trabajar con la API | 261


Machine Translated by Google

1 169122819 2 50 EUR/GBP ­11,62340 Verdadero


169122834 25 EUR/USD ­2,30463 Falso
3 169122835 50 EUR/GBP ­9,96292 Verdadero

En [52]: api.close_all_for_symbol('EUR/GBP')

En [53]: api.get_open_positions()[sel]
Fuera[53]: cantidad de tradeIdK moneda brutaPL esComprar
0 169122817 1 100 EUR/USD ­5,02858 Verdadero
169122834 25 EUR/USD ­3,14257 Falso

En [54]: api.close_all()

En [55]: api.get_open_positions()
Out[55]: Marco de datos vacío
Columnas: []
Índice: []

Reduce la posición en el par de divisas EUR/USD .

Aumenta la posición en el par de divisas EUR/GBP .

Para el EUR/GBP ahora hay dos posiciones largas abiertas; al contrario del EUR/USD
posición, no se compensa.

El método .close_all_for_symbol() cierra todas las posiciones para el especificado


símbolo.

El método .close_all() cierra todas las posiciones abiertas a la vez.

De forma predeterminada, FXCM configura cuentas de demostración como cuentas de cobertura. Este

significa que ir en largo, digamos EUR/USD, con 10.000 y ir en corto


el mismo instrumento con 10.000 conduce a dos posiciones abiertas diferentes.
ciones. Lo predeterminado con Oanda son cuentas netas que netan pedidos y
posiciones para el mismo instrumento.

Información de la cuenta

Más allá, por ejemplo, de las posiciones abiertas, la API de FXCM permite recuperar más posiciones genéricas.
información general de la cuenta, también. Por ejemplo, se puede buscar la cuenta predeterminada.
(si hay varias cuentas) o una descripción general de la situación del capital y el margen:

En [56]: api.get_default_account()
Fuera[56]: 1233279

En [57]: api.get_accounts().T Fuera[57]:


0
t 6

262 | Capítulo 9: Comercio de divisas con FXCM


Machine Translated by Google

tasaPrecisión 0
cuentaId 1233279
saldo 47555.2
usdMr 0
mc norte

mcFecha
Nombre de cuenta 01233279
usdMr3 0
cobertura Y
utilizable Margen 3 47555.2
utilizable Margen Perc 100
utilizable Margen 3 Perc 100
capital 47555.2
utilizable Margen 47555.2
bus 1000
día PL 653.16
bruto PL 0

Muestra el valor predeterminado de accountId .

Muestra para todas las cuentas la situación financiera y algunos parámetros.

Conclusiones
Este capítulo trata sobre la API RESTful de FXCM para el comercio algorítmico y cubre
los siguientes temas:

• Configurar todo para el uso de API

• Recuperar datos históricos de ticks

• Recuperar datos históricos de velas

• Recuperar datos de transmisión en tiempo real

• Colocar órdenes de compra y venta en el mercado.

• Buscar información de la cuenta

Más allá de estos aspectos, la API FXCM y el paquete contenedor fxcmpy proporcionan, por supuesto
Por supuesto, más funcionalidad. Sin embargo, los temas de este capítulo son la construcción básica.
bloques necesarios para comenzar con el comercio algorítmico.

Con Oanda y FXCM, los operadores algorítmicos tienen dos plataformas comerciales (brokers)
disponibles que proporcionan un amplio espectro de instrumentos financieros y
API adecuadas para implementar estrategias comerciales algorítmicas y automatizadas. Algunas importantes
Estos aspectos se añaden a la mezcla en el Capítulo 10.

Conclusiones | 263
Machine Translated by Google

Referencias y recursos adicionales


Los siguientes recursos cubren la API comercial de FXCM y el paquete contenedor de
Python:

• API comercial: https://fxcm.github.io/rest­api­docs


• Paquete fxcmpy : http://fxcmpy.tpq.io

264 | Capítulo 9: Comercio de divisas con FXCM


Machine Translated by Google

CAPÍTULO 10

Automatización de operaciones comerciales

A la gente le preocupa que las computadoras se vuelvan demasiado inteligentes y se apoderen del mundo,
pero el verdadero problema es que son demasiado estúpidas y ya se han apoderado del mundo.

—Pedro Domingos

"¿Ahora que?" tú puedes pensar. La plataforma comercial que permite recuperar datos históricos y
datos en tiempo real está disponible. Permite realizar órdenes de compra y venta y comprobar el
estado de la cuenta. En este libro se han introducido varios métodos diferentes para derivar
estrategias comerciales algorítmicas prediciendo la dirección de los movimientos de los precios del
mercado. Quizás se pregunte: "¿Cómo, después de todo, se puede combinar todo esto para que
funcione de forma automatizada?" Esto no puede responderse de manera generalizada. Sin embargo,
este capítulo aborda una serie de temas que son importantes en este contexto. El capítulo supone
que se implementará una única estrategia comercial algorítmica y automatizada. Esto simplifica, por
ejemplo, aspectos como la gestión del capital y del riesgo.

El capítulo cubre los siguientes temas. “Gestión de capital” en la página 266 analiza el criterio de
Kelly. Dependiendo de las características de la estrategia y del capital comercial disponible, el criterio
de Kelly ayuda a dimensionar las operaciones. Para ganar confianza en una estrategia comercial
algorítmica, es necesario realizar una prueba retrospectiva exhaustiva de la estrategia con respecto
a las características de rendimiento y riesgo. La “Estrategia comercial basada en ML” en la página
277 realiza una prueba retrospectiva de una estrategia de ejemplo basada en un algoritmo de
clasificación de aprendizaje automático (ML), como se presenta en “Estrategias comerciales” en la
página 13. Para implementar la estrategia comercial algorítmica para el comercio automatizado, es
necesario traducirse en un algoritmo en línea que funcione con datos de transmisión entrantes en
tiempo real. “Algoritmo en línea” en la página 291 cubre la transformación de un algoritmo fuera de
línea en un algoritmo en línea.

“Infraestructura e implementación” en la página 296 luego se propone garantizar que la estrategia


comercial algorítmica y automatizada se ejecute de manera sólida y confiable en la nube. No se
pueden cubrir en detalle todos los temas relevantes, pero la implementación de la nube parece ser la

265
Machine Translated by Google

única opción viable desde el punto de vista de disponibilidad, rendimiento y seguridad en este
contexto. “Registro y monitoreo” en la página 297 cubre el registro y el monitoreo.
El registro es importante para poder analizar el historial y ciertos eventos durante el despliegue
de una estrategia comercial automatizada. La monitorización a través de comunicación por
socket, como se presentó en el Capítulo 7, permite observar eventos de forma remota en tiempo
real. El capítulo concluye con una “Descripción general visual paso a paso” en la página 299,
que proporciona un resumen visual de los pasos principales para la implementación automatizada
de estrategias comerciales algorítmicas en la nube.

La gestión del capital


Una cuestión central en el comercio algorítmico es cuánto capital desplegar en una estrategia
comercial algorítmica determinada, dado el capital total disponible. La respuesta a esta pregunta
depende del objetivo principal que se intenta alcanzar mediante el comercio algorítmico. La
mayoría de las personas y las instituciones financieras estarán de acuerdo en que la maximización
de la riqueza a largo plazo es un buen objetivo candidato. Esto es lo que Edward Thorp tenía en
mente cuando derivó el criterio de Kelly para la inversión, como lo describen Rotando y Thorp (1992).
En pocas palabras, el criterio de Kelly permite un cálculo explícito de la fracción del capital disponible
que un operador debería invertir en una estrategia, dadas sus características estadísticas de
rendimiento.

Criterio de Kelly en un entorno binomial La

forma común de introducir la teoría del criterio de Kelly en la inversión es sobre la base de un
juego de lanzamiento de moneda o, más generalmente, un entorno binomial (sólo son posibles
dos resultados). Esta sección sigue ese camino. Supongamos que un jugador está jugando al
lanzamiento de una moneda contra un banco o casino infinitamente rico. Supongamos además
que la probabilidad de obtener cara es algún valor p para el cual se cumple lo siguiente:

1
2 <p<1

La probabilidad de cruz se define por lo siguiente:

1
q=1−p< 2

El jugador puede realizar apuestas b > 0 de tamaño arbitrario, por lo que gana la misma cantidad
si acierta y lo pierde todo si se equivoca. Dadas las suposiciones sobre las probabilidades, el
jugador, por supuesto, querría apostar a cara.

266 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

Por lo tanto, el valor esperado para este juego de apuestas B (es decir, la variable aleatoria que
representa este juego) en una configuración de una sola vez es el siguiente:

B=p∙b−q∙b=p−q∙b>0

A un jugador neutral al riesgo y con fondos ilimitados le gustaría apostar la mayor cantidad posible, ya
que esto maximizaría el beneficio esperado. Sin embargo, el comercio en los mercados financieros no
es un juego de una sola vez en general. Es un juego repetido. Por tanto, supongamos que bi representa
la cantidad que se apuesta el día i y que c0 representa el capital inicial. El capital c1 al final del día uno
depende del éxito de las apuestas ese día y podría ser c0 + b1 o c0 − b1. El valor esperado para una
apuesta que se repite n veces es el siguiente:

norte

Bn = c0 + ∑
yo = 1
p − q ∙ bi

En la teoría económica clásica, con agentes maximizadores de la utilidad esperada neutrales al riesgo,
un jugador intentaría maximizar la expresión anterior. Se ve fácilmente que se maximiza apostando
todos los fondos disponibles, bi = ci − 1, como en el escenario único.
Sin embargo, esto a su vez implica que una sola pérdida acabará con todos los fondos disponibles y
conducirá a la ruina (a menos que sea posible pedir préstamos ilimitados). Por tanto, esta estrategia no
conduce a una maximización de la riqueza a largo plazo.

Si bien apostar el máximo capital disponible puede conducir a una ruina repentina, no apostar nada
evita cualquier tipo de pérdida pero tampoco se beneficia de la ventajosa apuesta. Aquí es donde entra
en juego el criterio de Kelly ya que de él se deriva la fracción óptima f* del capital disponible para
apostar por ronda de apuestas. Supongamos que n = h + t donde h representa el número de caras
observadas durante n rondas de apuestas y donde t representa el número de cruces. Con estas
definiciones, el capital disponible después de n rondas es el siguiente:

h t
cn = c0 ∙ 1 + f ∙1−f

Gestión de capital | 267


Machine Translated by Google

En tal contexto, la maximización de la riqueza a largo plazo se reduce a maximizar la


tasa de crecimiento geométrico promedio por apuesta que se da de la siguiente manera:

1/n
cn
r
gramo

= iniciar sesión
c0

h t 1/n
c0 ∙ 1 + f = ∙1−f
registro
c0

h t 1/n

= registro 1 + f 1−f
h t
=
iniciar sesión 1 + f + iniciar sesión 1 ­ f
norte norte

Entonces, formalmente el problema es maximizar la tasa promedio esperada de crecimiento en


eligiendo f de manera óptima. Con h = n ∙ p y t = n ∙ q, se obtiene:

gr h t
=
iniciar sesión 1 + f + iniciar sesión 1 ­ f
norte norte

= p Iniciar sesión 1 + f + q Iniciar sesión 1 − f

= p Iniciar sesión 1 + f + q Iniciar sesión 1 − f

‫ק‬Gf

Ahora se puede maximizar el término eligiendo la fracción óptima f * según


la condición de primer orden. La primera derivada viene dada por lo siguiente:

pag − q
GRAMO
f=1 1−f

= + fp − pf − q − qf
1+f1−f

= pag ­ q ­ f
1+f1−f

De la condición de primer orden se obtiene lo siguiente:

!
Gf= 0 f * = pag − q

Si uno confía en que esto sea el máximo (y no el mínimo), este resultado implica que
lo óptimo es invertir una fracción f * = p − q por ronda de apuestas. Con, por ejemplo,
p = 0,55, se tiene f* = 0,55 ­ 0,45 = 0,1, o que la fracción óptima es el 10%.

268 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

El siguiente código Python formaliza estos conceptos y resultados mediante simulación.


Primero, algunas importaciones y configuraciones:

En [1]: importar matemáticas


tiempo de importación

importar numpy como np


importar pandas como pd
importar fecha y hora como dt
desde pylab importar plt, mpl

En [2]: np.random.seed(1000)
plt.style.use('nacido en el mar')
mpl.rcParams['savefig.dpi'] = 300
mpl.rcParams['font.family'] = 'serif'

La idea es simular, por ejemplo, 50 series con 100 lanzamientos de moneda por serie. El
El código Python para esto es sencillo:

En [3]: p = 0,55

En [4]: f = p ­ (1 ­ p)

En [5]: f
Salida [5]: 0,10000000000000009

En [6]: I = 50

En [7]: n = 100

Corrige la probabilidad de que salga cara.

Calcula la fracción óptima según el criterio de Kelly.

El número de series a simular.

El número de ensayos por serie.

La parte principal es la función run_simulation() de Python, que logra la simulación.


ción de acuerdo con los supuestos anteriores. La Figura 10­1 muestra la simulación.
resultados:

En [8]: def ejecutar_simulación(f):


c = np.zeros((n, I)) c[0] = 100
para i en
rango(I): para t en rango(1,
n): o = np.random.binomial(1,
p) si o > 0: c[t, i] = (1 + f) * c[t ­ 1, i] más:

c[t, i] = (1 ­ f) * c[t ­ 1, i]

Gestión de capital | 269


Machine Translated by Google

volver c

En [9]: c_1 = ejecutar_simulación(f)

En [10]: c_1.round(2)
Fuera[10]: matriz([[100. , [ 90. 100. , 100. , ..., 100. , 100. , 100. ],
[ 99. , 110. , 90. , ..., 110. , 90. , 110. ],
, 121. , 99. , ..., 121. , 81. , 121. ],
...,
[226,35, 338,13, 413,27, ..., 123,97, 123,97, 123,97],
[248,99, 371,94, 454,6 , ..., 136,37, 136,37, 136,37],
[273.89, 409.14, 409.14, ..., 122.73, 150.01, 122.73]])

En [11]: plt.figure(tamañofig=(10, 6))


plt.plot(c_1, 'b', lw=0.5)
plt.plot(c_1.mean(axis=1), 'r', lw=2.5);

Crea una instancia de un objeto ndarray para almacenar los resultados de la simulación.

Inicializa el capital inicial con 100.

Bucle exterior para las simulaciones en serie.

Bucle interior para la propia serie.

Simula el lanzamiento de una moneda.

Si 1 o cara...

…luego suma la victoria al capital.

Si es 0 o cruz...

…resta la pérdida del capital.

Esto ejecuta la simulación.

Traza las 50 series.

Traza el promedio de las 50 series.

270 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

Figura 10­1. 50 series simuladas con 100 ensayos cada una (línea roja = promedio)

El siguiente código repite la simulación para diferentes valores de f . Como se muestra en la Figura
10­2, una fracción más baja conduce a una tasa de crecimiento más baja en promedio. Valores más
altos podrían llevar a un capital promedio más alto al final de la simulación (f =0,25) o a un capital
promedio mucho más bajo (f =0,5]). En ambos casos donde la fracción f es mayor, la volatilidad
aumenta considerablemente:

En [12]: c_2 = ejecutar_simulación(0.05)

En [13]: c_3 = ejecutar_simulación(0.25)

En [14]: c_4 = ejecutar_simulación(0.5)

En [15]: plt.figure(figsize=(10, 6))


plt.plot(c_1.mean(axis=1), 'r', label='$f^*=0.1$')
plt.plot( c_2.mean(eje=1), 'b', etiqueta='$f=0.05$')
plt.plot(c_3.mean(eje=1), 'y', etiqueta='$f=0.25$')
plt.plot(c_4.mean(axis=1), 'm', label='$f=0.5$') plt.legend(loc=0);

Simulación con f = 0,05.

Simulación con f = 0,25.

Simulación con f = 0,5.

Gestión de capital | 271


Machine Translated by Google

Figura 10­2. Capital promedio a lo largo del tiempo para diferentes valores de f

Criterio de Kelly para acciones e índices Supongamos

ahora un entorno de mercado de valores en el que la acción relevante (índice) sólo puede adquirir dos valores
después de un período de un año a partir de hoy, dado su valor conocido hoy.
El escenario es nuevamente binomial, pero esta vez un poco más cercano en el lado del modelado a las realidades
del mercado de valores.1 Específicamente, supongamos que lo siguiente es cierto:

S 1
pr =μ+σ=Pr S=μ−σ=
2

S
estándar de = μ > 0 es el rendimiento esperado de la acción durante un año, y σ > 0 es aquí, r la desviación
los rendimientos (volatilidad). En un escenario de un período, se obtiene lo siguiente para el capital disponible
después de un año (con c0 yf definidos como antes):

S
cf = c0 ∙ 1 + 1 − f ∙ r + f ∙ r

1 La exposición sigue a Hung (2010).

272 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

Aquí, r es la tasa corta constante obtenida con el efectivo no invertido en la acción. maximiz
Calcular la tasa de crecimiento geométrico significa maximizar el término:

cf
G f = log c0

Supongamos ahora que hay n días comerciales relevantes en el año, de modo que para cada uno de ellos
día de negociación i se cumple lo siguiente:

σ σ 1
S
= µ + S
= µ − =
pri norte norte
= Pri norte norte 2

Tenga en cuenta que la volatilidad aumenta con la raíz cuadrada del número de días de negociación. Bajo
estos supuestos, los valores diarios aumentan a los anuales de antes y uno
obtiene lo siguiente:

r
norte

S
cn f = c0 ∙ ∏ 1+1−f∙ + f∙ ri
yo = 1 norte

Ahora hay que maximizar la siguiente cantidad para lograr la máxima rentabilidad a largo plazo.
riqueza al invertir en acciones:

cn f
Gn f = registro
c0

r
norte

S
=∑ iniciar sesión 1 + 1 − f ∙ + f∙ ri
yo = 1 norte

1 r σ
norte

= ∑ µ +
2 yo = 1
iniciar sesión 1 + 1 − f ∙
norte
+f∙ norte norte

r µ −
σ
+ Iniciar sesión 1 + 1 − f ∙
norte
+f∙ norte norte

2
r µ F 2σ2
=
norte

2 iniciar sesión 1 + 1 − f ∙ norte
+f∙ norte norte

Utilizando un desarrollo en serie de Taylor, finalmente se llega a lo siguiente:

σ2 1
2+
Gn f = r + μ − r ∙ f − ∙ f 2 norte

Gestión de capital | 273


Machine Translated by Google

O para una cantidad infinita de puntos de negociación en el tiempo (es decir, para una negociación
continua), se llega a lo siguiente:

σ2 2
G∞ f = r + μ − r ∙ f − ∙ f 2

La fracción óptima f * entonces viene dada por la condición de primer orden mediante la siguiente
expresión:

= μ−r
f*
σ2

Esto representa el exceso de rendimiento esperado de la acción sobre la tasa libre de riesgo dividido
por la varianza de los rendimientos. Esta expresión parece similar a la relación de Sharpe pero es
diferente.

Un ejemplo del mundo real ilustrará la aplicación de la fórmula anterior y su papel en el apalancamiento
del capital desplegado en las estrategias comerciales. La estrategia comercial considerada es
simplemente una posición larga pasiva en el índice S&P 500. Para ello, los datos base se recuperan
rápidamente y las estadísticas necesarias se derivan fácilmente:

En [16]: raw = pd.read_csv('http://hilpisch.com/pyalgo_eikon_eod_data.csv', index_col=0,


parse_dates=True)

En [17]: símbolo = '.SPX'

En [18]: datos = pd.DataFrame(raw[símbolo])

En [19]: datos['retorno'] = np.log(datos / datos.shift(1))

En [20]: datos.dropna(inplace=True)

En [21]: datos.tail()
Fuera[21]: .SPX devolver
Fecha
2019­12­23 3224.01 0.000866
2019­12­24 3223.38 ­0.000195
2019­12­27 3240.02 0.000034
2019­12­30 3221.29 ­0.005798
2019­12­31 3230.78 0.002942

Las propiedades estadísticas del índice S&P 500 durante el período cubierto sugieren que se invertirá
una fracción óptima de aproximadamente 4,5 en la posición larga del índice. Es decir, por cada dólar
disponible se invertirán 4,5 dólares, lo que implica un ratio de apalancamiento de 4,5 de acuerdo con la
fracción de Kelly óptima o, en este caso, el factor de Kelly óptimo.

274 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

En igualdad de condiciones, el criterio de Kelly implica un mayor apalancamiento cuando el


el rendimiento esperado es mayor y la volatilidad (varianza) es menor:

En [22]: mu = datos['retorno'].mean() * 252

En [23]: mu
Fuera [23]: 0.09992181916534204

En [24]: sigma = datos['retorno'].std() * 252 ** 0,5

En [25]: sigma
Fuera [25]: 0,14761569775486563

En [26]: r = 0,0

En [27]: f = (mu ­ r) / sigma ** 2

En [28]: f
Fuera [28]: 4.585590244019818

Calcula el rendimiento anualizado.

Calcula la volatilidad anualizada.

Establece la tasa libre de riesgo en 0 (por simplicidad).

Calcula la fracción de Kelly óptima a invertir en la estrategia.

El siguiente código Python simula la aplicación del criterio de Kelly y el


ratio de apalancamiento óptimo. Por motivos de simplicidad y comparación, el patrimonio inicial se fija
a 1 mientras que el capital total invertido inicialmente se fija en 1∙f*. Dependiendo del rendimiento
Mientras que el capital desplegado en la estrategia, el capital total en sí se ajusta diariamente
según el patrimonio disponible. Después de una pérdida, el capital se reduce; después de una ganancia, el
se aumenta el capital. La evolución de la posición patrimonial respecto al propio índice
se muestra en la Figura 10­3:

En [29]: ecus = []

En [30]: def kelly_strategy(f):


igualdad global
equ = 'equidad_{:.2f}'.formato(f)
equs.append(equ)
tapa = 'capital_ {:.2f}'.formato(f)
datos[equ] = 1
datos[cap] = datos[equ] * f
para i, t en enumerar(data.index[1:]):
t_1 = datos.index[i]
datos.loc[t, cap] = datos[cap].loc[t_1] * \
math.exp(datos['retorno'].loc[t])
datos.loc[t, equ] = datos[cap].loc[t] ­ \

Gestión de capital | 275


Machine Translated by Google

datos[cap].loc[t_1] + \
datos[equ].loc[t_1]
datos.loc[t, cap] = datos[equ].loc[t] * f

En [31]: kelly_strategy(f * 0,5)

En [32]: kelly_strategy(f * 0,66)

En [33]: kelly_strategy(f)

En [34]: imprimir(datos[equs].tail())
equidad_2.29 equidad_3.03 equidad_4.59
Fecha
2019­12­23 6.628865 9.585294 14.205748
2019­12­24 6.625895 9.579626 14.193019
2019­12­27 6.626410 9.580610 14.195229
2019­12­30 6.538582 9.412991 13.818934
2019­12­31 6.582748 9.496919 14.005618

En [35]: ax = data['return'].cumsum().apply(np.exp).plot(figsize=(10, 6))


datos[equs].plot(ax=ax, legend=True);

Genera una nueva columna para equidad y establece el valor inicial en 1.

Genera una nueva columna para capital y establece el valor inicial en 1 ∙ f *.

Elige el valor DatetimeIndex correcto para los valores anteriores.

Calcula la nueva posición de capital dada la rentabilidad.

Ajusta el valor del patrimonio de acuerdo con el desempeño de la posición de capital.

Ajusta la posición de capital dada la nueva posición patrimonial y el apalancamiento fijo


relación.

Simula la estrategia basada en el criterio de Kelly para la mitad de f...

…durante dos tercios de f…

…y f mismo.

276 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

Figura 10­3. Rendimiento bruto del S&P 500 en comparación con la posición accionaria dados diferentes
valores de f

Como ilustra la Figura 10­3 , la aplicación del apalancamiento óptimo de Kelly conduce a una evolución
bastante errática de la posición accionaria (alta volatilidad), lo cual es intuitivamente plausible, dado el
índice de apalancamiento de 4,59. Se esperaría que la volatilidad de la posición accionaria aumentara al
aumentar el apalancamiento. Por lo tanto, los profesionales a menudo no utilizan “Kelly completo” (4.6),
sino “medio Kelly” (2.3). En el ejemplo actual, esto se reduce a:

1
2 • f * ≈ 2,3

En este contexto, la Figura 10­3 también muestra la evolución de la posición patrimonial para valores
inferiores al “Kelly completo”. De hecho, el riesgo se reduce con valores más bajos de látexmatemáticas:
[$f$].

Estrategia comercial basada en ML

El Capítulo 8 presenta la plataforma comercial Oanda, su API RESTful y el paquete contenedor Python
tpqoa. Esta sección combina un enfoque basado en ML para predecir la dirección de los movimientos de
los precios del mercado con datos históricos de la API RESTful de Oanda v20 para probar una estrategia
comercial algorítmica para el par de divisas EUR/USD. Utiliza backtesting vectorizado, teniendo en cuenta
esta vez el diferencial de oferta y demanda como costos de transacción proporcionales. También agrega,
en comparación con el vectorizado simple

Estrategia comercial basada en ML | 277


Machine Translated by Google

enfoque de backtesting presentado en el Capítulo 4, un análisis más profundo del riesgo


características de la estrategia comercial probada.

Backtesting vectorizado
El backtest se basa en datos intradía, más concretamente en barras de 10 minutos en
longitud. El siguiente código se conecta a la API de Oanda v20 y recupera 10 minutos
datos de la barra durante una semana. La Figura 10­4 visualiza los precios de cierre medio durante el período para
qué datos se recuperan:

En [36]: importar tpqoa

En [37]: %tiempo api = tpqoa.tpqoa('../pyalgo.cfg')


Tiempos de CPU: usuario 893 μs, sistema: 198 μs, total: 1,09 ms
Tiempo de pared: 1,04 ms

En [38]: instrumento = 'EUR_USD'

En [39]: raw = api.get_history(instrumento,


inicio = '2020­06­08',
fin = '2020­06­13',
granularidad = 'M10',
precio = 'M')

En [40]: raw.tail()
Fuera[40]: oh h yo c volumen completo
tiempo
2020­06­12 20:10:00 1.12572 1.12593 1.12532 1.12568 221 Verdadero

2020­06­12 20:20:00 1.12569 1.12578 1.12532 1.12558 163 Verdadero

2020­06­12 20:30:00 1.12560 1.12573 1.12534 1.12543 192 Verdadero

2020­06­12 20:40:00 1.12544 1.12594 1.12528 1.12542 2020­06­12 20:50:00 219 Verdadero

1.12544 1.12624 1.12541 1.12554 296 Verdadero

En [41]: raw.info()
<clase 'pandas.core.frame.DataFrame'>
DatetimeIndex: 701 entradas, 2020­06­08 00:00:00 a 2020­06­12 20:50:00
Columnas de datos (un total de 6 columnas):
# Columna Tipo D de recuento no nulo
­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0o 701 no nulo flotador64


1 hora 2 701 no nulo 701 flotador64
litros no nulo 701 no flotador64
3c4 nulo 701 no nulo flotador64
volumen int64
5 tipos completos 701 no nulos: bool(1), booleano

float64(4), int64(1)
Uso de memoria: 33,5 KB

En [42]: diferencial = 0,00012

En [43]: media = raw['c'].media()

278 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

En [44]: ptc = dispersión / media


ptc
Fuera[44]: 0.00010599557439495706

En [45]: raw['c'].plot(figsize=(10, 6), legend=True);

Se conecta a la API y recupera los datos.

Especifica el diferencial promedio entre oferta y demanda.

Calcula el precio medio de cierre del conjunto de datos.

Calcula los costos de transacción proporcionales promedio dado el diferencial promedio


y el precio promedio de cierre medio.

Figura 10­4. Tipo de cambio EUR/USD (barras de 10 minutos)

La estrategia basada en ML utiliza una serie de características de series temporales, como el retorno de registros.
y el mínimo y el máximo del precio de cierre. Además, las características
los datos están retrasados. En otras palabras, el algoritmo ML aprenderá de patrones históricos.
tal como lo reflejan los datos de características rezagadas:

En [46]: datos = pd.DataFrame(raw['c'])

En [47]: datos.columnas = [instrumento,]

En [48]: ventana = 20
datos['retorno'] = np.log(datos / datos.shift(1))

Estrategia comercial basada en ML | 279


Machine Translated by Google

datos['vol'] = datos['retorno'].rolling(ventana).std() datos['mom'] =


np.sign(datos['retorno'].rolling(ventana).mean()) datos ['sma'] =
datos[instrumento].rolling(ventana).media() datos['min'] =
datos[instrumento].rolling(ventana).min() datos['max'] =
datos[instrumento] .rolling(ventana).max()

En [49]: datos.dropna(inplace=True)

En [50]: rezagos = 6

En [51]: características = ['return', 'vol', 'mom', 'sma', 'min', 'max']

En [52]: columnas = []
para f en características:
para retraso en el rango (1, retrasos + 1):
col = f'{f}_lag_{lag}'
datos[col] = datos[f].shift(lag) cols.append(col)

En [53]: datos.dropna(inplace=True)

En [54]: datos['dirección'] = np.donde(datos['retorno'] > 0, 1, ­1)

En [55]: datos[cols].iloc[:lags, :lags]


Fuera[55]:
retorno_lag_1 retorno_lag_2 retorno_lag_3 retorno_lag_4 \
tiempo
2020­06­08 04:20:00 0.000097 0.000018 ­0.000452 0.000035
2020­06­08 04:30:00 ­0.000115 0.000097 0.000018 ­0.000452
2020­06­08 04:40:00 0.000027 ­0.000115 0.000097 0.000018
2020­06­08 04:50:00 ­0,000142 0,000027 ­0,000115 0.000097
2020­06­08 05:00:00 0,000035 ­0,000142 0,000027 ­0.000115
2020­06­08 05:10:00 ­0,000159 0,000035 ­0,000142 0.000027

retorno_lag_5 retorno_lag_6
tiempo
2020­06­08 04:20:00 0,000000 0.000009
2020­06­08 04:30:00 0,000035 0.000000
2020­06­08 04:40:00 ­0.000452 0.000035
2020­06­08 04:50:00 0.000018 ­0.000452
2020­06­08 05:00:00 0.000097 0.000018
2020­06­08 05:10:00 ­0.000115 0.000097

Especifica la longitud de la ventana para determinadas funciones.

Calcula los rendimientos logarítmicos a partir de los precios de cierre.

Calcula la volatilidad móvil.

Deriva el impulso de la serie temporal como la media de los rendimientos logarítmicos recientes.

280 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

Calcula la media móvil simple.

Calcula el valor máximo móvil.

Calcula el valor mínimo móvil.

Agrega los datos de características retrasadas al objeto DataFrame .

Define los datos de las etiquetas como la dirección del mercado (+1 o arriba y ­1 o abajo).

Muestra un pequeño subconjunto de los datos de entidades retrasadas resultantes.

Dadas las características y los datos de las etiquetas, ahora se podrían utilizar diferentes algoritmos de aprendizaje supervisado.
se aplicado. A continuación se utiliza el algoritmo de clasificación denominado AdaBoost.
del paquete scikit­learn ML (consulte AdaBoostClassifier). La idea de impulsar
en el contexto de la clasificación es utilizar un conjunto de clasificadores base para llegar a una
predictor superior que se supone que es menos propenso al sobreajuste (consulte “Data Snoop
ing y sobreajuste” en la página 111). Como clasificador base, una clasificación de árbol de decisión.
Se utiliza el algoritmo de scikit­learn (consulte DecisionTreeClassifier).

El código entrena y prueba la estrategia comercial algorítmica basándose en una división secuencial de prueba de
tren. Las puntuaciones de precisión del modelo para los datos de entrenamiento y prueba son significativas.
significativamente por encima del 50%. En lugar de puntuaciones de precisión, también se hablaría en un sentido financiero.
contexto comercial de la proporción de aciertos de la estrategia comercial (es decir, el número de ganancias
operaciones en comparación con todas las operaciones). Dado que la tasa de aciertos es significativamente mayor que el 50%,
esto podría indicar, en el contexto del criterio de Kelly, una ventaja estadística en comparación
a una configuración de paseo aleatorio:

En [56]: desde sklearn.metrics importa precision_score


desde sklearn.tree importar DecisionTreeClassifier
desde sklearn.ensemble importar AdaBoostClassifier

En [57]: n_estimadores=15
estado_aleatorio=100

profundidad_máxima=2
min_samples_leaf=15 submuestra=0,33

En [58]: dtc = DecisionTreeClassifier(random_state=random_state,


profundidad_max=profundidad_max,

min_samples_leaf=min_samples_leaf)

En [59]: modelo = AdaBoostClassifier(base_estimator=dtc,


n_estimadores=n_estimadores,
estado_aleatorio=estado_aleatorio)

En [60]: dividir = int(len(datos) * 0,7)

Estrategia comercial basada en ML | 281


Machine Translated by Google

En [61]: tren = data.iloc[:split].copiar()

En [62]: mu, std = tren.mean(), tren.std()

En [63]: tren_ = (tren ­ mu) / std

En [64]: model.fit(train_[cols], train['dirección'])


Fuera[64]: AdaBoostClassifier(algoritmo='SAMME.R',
base_estimator=Clasificador de árbol de decisiones(ccp_alpha=0.0,
class_weight=Ninguno,
criterio='gini',
profundidad_max=2,
max_features=Ninguno,
max_leaf_nodes=Ninguno,
min_impurity_decrease=0.0,
min_impurity_split=Ninguno,
min_samples_leaf=15,
min_samples_split=2,
min_weight_fraction_leaf=0.0,
presort='obsoleto',
estado_aleatorio=100,
divisor = 'mejor'),
tasa_de_aprendizaje=1.0, n_estimadores=15, estado_aleatorio=100)

En [65]: precision_score(train['dirección'], model.predict(train_[cols]))


Fuera[65]: 0.8050847457627118

En [66]: prueba = data.iloc[split:].copiar()

En [67]: prueba_ = (prueba ­ mu) / std

En [68]: prueba['posición'] = modelo.predict(prueba_[cols])

En [69]: exactitud_score(prueba['dirección'], prueba['posición'])


Fuera[69]: 0,5665024630541872

Especifica los parámetros principales para el algoritmo ML (consulte las referencias para el
clases de modelo proporcionadas anteriormente).

Crea una instancia del algoritmo de clasificación base (árbol de decisión).

Crea una instancia del algoritmo de clasificación AdaBoost.

Aplica la normalización gaussiana al conjunto de datos de características de entrenamiento.

Se ajusta al modelo según el conjunto de datos de entrenamiento.

Muestra la precisión de las predicciones del modelo entrenado en la muestra (entrenamiento


conjunto de datos).

282 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

Aplica la normalización gaussiana al conjunto de datos de características de prueba (utilizando los parámetros del
conjunto de datos de características de entrenamiento).

Genera las predicciones para el conjunto de datos de prueba.

Muestra la precisión de las predicciones del modelo entrenado fuera de la muestra (conjunto de datos de prueba).

Es bien sabido que el índice de aciertos es sólo una cara de la moneda del éxito en el comercio financiero. El otro lado
comprende, entre otras cosas, realizar correctamente las operaciones importantes, así como los costos de transacción
implicados por la estrategia comercial.2 Con este fin, sólo un enfoque formal de backtesting vectorizado permite juzgar
la calidad de la estrategia comercial. El siguiente código tiene en cuenta los costos de transacción proporcionales según
el diferencial promedio entre oferta y demanda. La Figura 10­5 compara el desempeño de la estrategia comercial
algorítmica (sin y con costos de transacción proporcionales) con el desempeño de la inversión pasiva de referencia:

En [70]: prueba['estrategia'] = prueba['posición'] * prueba['retorno']

En [71]: suma(prueba['posición'].diff() != 0)
Fuera[71]: 77

En [72]: prueba['estrategia_tc'] = np.where(prueba['posición'].diff() != 0, prueba['estrategia'] ­


ptc, prueba['estrategia'])

En [73]: prueba[['return', 'strategy', 'strategy_tc']].sum( ).apply(np.exp) 0.990182 1.015827


1.007570
Salida [73]: estrategia
de retorno
estrategia_tc
tipo d: float64

En [74]: test[['return', 'strategy', 'strategy_tc']].cumsum( ).apply(np.exp).plot(figsize=(10,


6));

2 Es un hecho empírico estilizado que es de suma importancia para el desempeño de la inversión y el comercio acertar los
mayores movimientos del mercado (es decir, los mayores movimientos de ganadores y perdedores). Este aspecto
se ilustra claramente en la Figura 10­5, que muestra que la estrategia comercial logra un gran movimiento a la baja en
el instrumento subyacente correcto, lo que lleva a un salto mayor para la estrategia comercial.

Estrategia comercial basada en ML | 283


Machine Translated by Google

Deriva los retornos de registro para la estrategia comercial algorítmica basada en ML.

Calcula el número de operaciones que implica la estrategia comercial en función de los cambios en
la posición.

Cada vez que se realiza una operación, los costos de transacción proporcionales se restan del
rendimiento logarítmico de la estrategia ese día.

Figura 10­5. Rendimiento bruto del tipo de cambio EUR/USD y estrategia de negociación algorítmica
(antes y después de los costes de transacción)

El backtesting vectorizado tiene sus límites con respecto a qué tan cerca de
las realidades del mercado se pueden probar las estrategias. Por ejemplo,
no permite incluir directamente los costos de transacción fijos por operación.
Como aproximación, se podría tomar un múltiplo de los costos de
transacción proporcionales promedio (basados en el tamaño promedio de
las posiciones) para contabilizar indirectamente los costos de transacción
fijos. Sin embargo, esto no sería exacto en general. Si se requiere un mayor
grado de precisión, se deben aplicar otros enfoques, como el backtesting
basado en eventos (ver Capítulo 6) con bucles explícitos sobre cada barra
de los datos de precios.

284 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

Apalancamiento óptimo

Equipado con los datos de retorno del registro de la estrategia comercial, los valores de media y varianza
se puede calcular para derivar el apalancamiento óptimo según el criterio de Kelly.
río. El código que sigue escala los números a valores anualizados, aunque esto
no cambia los valores de apalancamiento óptimos según el criterio de Kelly ya que el
rentabilidad media y la escala de varianza con el mismo factor:

En [75]: media = prueba[['return', 'strategy_tc']].media() * len(datos) * 52


significar

Salida [75]: retorno ­1.705965


estrategia_tc 1.304023
tipo d: float64

En [76]: var = prueba[['return', 'strategy_tc']].var() * len(data) * 52


var
Salida [76]: retorno 0.011306
estrategia_tc 0.011370
tipo d: float64

En [77]: vol = var vol ** 0,5

Salida [77]: retorno 0.106332


estrategia_tc 0.106631
tipo d: float64

En [78]: media / var


Salida [78]: retorno ­150.884961
estrategia_tc 114.687875
tipo d: float64

En [79]: media / var * 0,5


Salida [79]: retorno ­75.442481
estrategia_tc 57.343938
tipo d: float64

Rentabilidad media anualizada.

Variaciones anualizadas.

Volatilidades anualizadas.

Apalancamiento óptimo según el criterio de Kelly (“full Kelly”).

Apalancamiento óptimo según el criterio de Kelly (“mitad Kelly”).

Utilizando el criterio de "mitad Kelly", el apalancamiento óptimo para la estrategia comercial está por encima
50. Con una serie de corredores, como Oanda, y ciertos instrumentos financieros, como
como pares de divisas y contratos por diferencias (CFD), tales ratios de apalancamiento

Estrategia comercial basada en ML | 285


Machine Translated by Google

son factibles, incluso para los comerciantes minoristas. La Figura 10­6 muestra, en comparación, el desempeño de la estrategia

comercial con costos de transacción para diferentes valores de apalancamiento:

En [80]: to_plot = ['return', 'strategy_tc']

En [81]: para lev en [10, 20, 30, 40, 50]: etiqueta =


'lstrategy_tc_%d' % lev prueba[label]
= prueba['strategy_tc'] * lev to_plot.append(label)

En [82]: test[to_plot].cumsum().apply(np.exp).plot(figsize=(10, 6));

Escala los rendimientos de la estrategia para diferentes valores de apalancamiento.

Figura 10­6. Rendimiento bruto de la estrategia comercial algorítmica para diferentes valores de apalancamiento

El apalancamiento aumenta significativamente los riesgos asociados con las


estrategias comerciales. Los comerciantes deben leer atentamente las exenciones
de responsabilidad y las regulaciones sobre riesgos. Un desempeño positivo en el
backtesting tampoco es garantía alguna para desempeños futuros. Todos los
resultados mostrados son solo ilustrativos y están destinados a demostrar la
aplicación de enfoques de programación y análisis. En algunas jurisdicciones,
como Alemania, los ratios de apalancamiento están limitados para los comerciantes
minoristas basados en diferentes grupos de instrumentos financieros.

286 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

Análisis de riesgo

Dado que el apalancamiento aumenta el riesgo asociado con una determinada estrategia comercial,
Por supuesto, parece necesario realizar un análisis de riesgos más profundo. El análisis de riesgo que sigue
asume un ratio de apalancamiento de 30. Primero, la reducción máxima y la reducción más larga
Se calculará el período de inactividad. La reducción máxima es la mayor pérdida (caída) después de un
máximo reciente. En consecuencia, el período de retiro más largo es el período más largo que el
La estrategia comercial debe volver a un máximo reciente. El análisis supone que el inicio
La posición inicial de capital es de 3.333 EUR, lo que lleva a un tamaño de posición inicial de 100.000 EUR para
un ratio de apalancamiento de 30. También se supone que no hay ajustes con respecto al
equidad a lo largo del tiempo, sin importar cuál sea el desempeño:

En [83]: patrimonio = 3333

En [84]: riesgo = pd.DataFrame(prueba['lstrategy_tc_30'])

En [85]: riesgo['equity'] = riesgo['lstrategy_tc_30'].cumsum(


).apply(np.exp) * equidad

En [86]: riesgo['cummax'] = riesgo['equity'].cummax()

En [87]: riesgo['drawdown'] = riesgo['cummax'] ­ riesgo['equity']

En [88]: riesgo['reducción'].max()
Fuera[88]: 511.38321383258017

En [89]: t_max = riesgo['drawdown'].idxmax() t_max

Salida[89]: Marca de tiempo('2020­06­12 10:30:00')

El patrimonio inicial.

El registro relevante devuelve series de tiempo...

…escalado por el patrimonio inicial.

Los valores máximos acumulados a lo largo del tiempo.

Los valores de reducción a lo largo del tiempo.

El valor máximo de reducción.

El momento en el que sucede.

Estrategia comercial basada en ML | 287


Machine Translated by Google

Técnicamente, un nuevo máximo se caracteriza por un valor de reducción de 0. La reducción


El período es el tiempo entre dos de esos máximos. La Figura 10­7 visualiza tanto el máximo
reducción y los períodos de reducción:

En [90]: temp = riesgo['reducción'][riesgo['reducción'] == 0]

En [91]: periodos = (temp.index[1:].to_pydatetime() ­


temp.index[:­1].to_pydatetime())

En [92]: periodos[20:30]
Fuera[92]: matriz([datetime.timedelta(segundos=600),
fechahora.timedelta(segundos=1200),
datetime.timedelta(segundos=1200), datetime.timedelta(segundos=1200)],
tipo d=objeto)

En [93]: t_per = periodos.max()

En [94]: t_per
Salida[94]: datetime.timedelta(segundos=26400)

En [95]: t_per.segundos / 60 / 60 Fuera


[95]: 7.333333333333333

En [96]: riesgo[['equity', 'cummax']].plot(figsize=(10, 6))


plt.axvline(t_max, c='r', alfa=0.5);

Identifica máximos para los cuales la reducción debe ser 0.

Calcula los valores delta de tiempo entre todos los máximos.

El período de reducción más largo en segundos...

…transformado en horas.

Otra medida de riesgo importante es el valor en riesgo (VaR). Se cotiza como moneda.
importe y representa la pérdida máxima que se puede esperar dado tanto un tiempo determinado
horizonte y un nivel de confianza.

288 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

Figura 10­7. Reducción máxima (línea vertical) y períodos de reducción (horizontal


líneas)

El siguiente código deriva valores VaR basados en los rendimientos logarítmicos de la posición de capital.
para la estrategia comercial apalancada a lo largo del tiempo para diferentes niveles de confianza. El tiempo
El intervalo se fija en la duración del compás de diez minutos:

En [97]: importar scipy.stats como scs

En [98]: perc = [0,01, 0,1, 1, 2,5, 5,0, 10,0]

En [99]: riesgo['retorno'] = np.log(riesgo['capital'] /


riesgo['capital'].shift(1))

En [100]: VaR = scs.scoreatpercentile(capital * riesgo['retorno'], percs)

En [101]: def print_var():


imprimir('{} {}'.format('Nivel de confianza', 'Valor en riesgo'))
imprimir(33 * '­')
para par en zip(percs, VaR):
print('{:16.2f} {:16.3f}'.format(100 ­ par[0], ­par[1]))

En [102]: imprimir_var()
Nivel de confianza Valor en riesgo
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
99,99 162.570
99,90 161.348
99.00 132.382

Estrategia comercial basada en ML | 289


Machine Translated by Google

97,50 122.913
95,00 100.950
90.00 62.622

Define los valores percentiles que se utilizarán.

Calcula los valores VaR dados los valores percentiles.

Traduce los valores percentiles a niveles de confianza y los valores VaR (negativos).
valores positivos) a valores positivos para la impresión.

Finalmente, el siguiente código calcula los valores VaR para un horizonte temporal de una hora mediante

remuestreo del objeto DataFrame original . En efecto, los valores del VaR aumentan para
todos los niveles de confianza:

En [103]: hora = riesgo.resample('1H', etiqueta='derecha').last()

En [104]: hora['retorno'] = np.log(hora['equity'] /


cada hora['equidad'].shift(1))

En [105]: VaR = scs.scoreatpercentile(capital * por hora['retorno'], percs)

En [106]: imprimir_var()
Nivel de confianza Valor en riesgo
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
99,99 252.460
99,90 251.744
99.00 244.593
97,50 232.674
95,00 125.498
90,00 61.701

Vuelve a muestrear los datos de barras de 10 minutos a 1 hora.

Calcula los valores VaR dados los valores percentiles.

Persistir el objeto modelo


Una vez que se acepta la estrategia comercial algorítmica basada en el backtesting, el apalancamiento
ing, y los resultados del análisis de riesgos, el objeto del modelo y otros componentes del algoritmo relevantes.
Los elementos pueden conservarse para su uso posterior en la implementación. Ahora incorpora la tecnología basada en ML.
estrategia comercial o el algoritmo comercial.

En [107]: importar pepinillo

En [108]: algoritmo = {'model': modelo, 'mu': mu, 'std': std}

En [109]: pickle.dump(algoritmo, open('algorithm.pkl', 'wb'))

290 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

Algoritmo en línea
El algoritmo comercial probado hasta ahora es un algoritmo fuera de línea. Estos algoritmos utilizan
un conjunto de datos completo para resolver un problema en cuestión. El problema ha sido entrenar
un algoritmo de clasificación AdaBoost basado en un árbol de decisión como clasificador base, varias
características de series de tiempo diferentes y datos de etiquetas direccionales. En la práctica, al
implementar el algoritmo comercial en los mercados financieros, debe consumir datos pieza por pieza
a medida que llegan para predecir la dirección del movimiento del mercado para el siguiente intervalo
de tiempo (barra). Esta sección utiliza el objeto de modelo persistente de la sección anterior y lo
incorpora en un contexto de transmisión de datos.

El código que transforma el algoritmo de comercio fuera de línea en un algoritmo de comercio en línea
aborda principalmente los siguientes problemas:

Datos de
tick Los datos de tick llegan en tiempo real y deben procesarse en tiempo real, por ejemplo, para
recopilarse en un objeto DataFrame .

Remuestreo
Los datos de tick se deben remuestrear a la longitud de barra adecuada según el algoritmo
comercial. A modo de ilustración, se utiliza una longitud de barra más corta para el remuestreo
que para el entrenamiento y el backtesting.

Predicción
El algoritmo comercial genera una predicción de la dirección del movimiento del mercado durante
el intervalo de tiempo relevante que por naturaleza se encuentra en el futuro.

Órdenes

Dada la posición actual y la predicción (“señal”) generada por el algoritmo, se realiza una orden o la posición se
mantiene sin cambios.

El Capítulo 8, y en particular “Trabajar con datos de streaming” en la página 236, muestra cómo
recuperar datos de ticks de la API de Oanda en tiempo real. El enfoque básico es redefinir el
método .on_success() de la clase tpqoa.tpqoa para implementar la lógica comercial.

Primero, se carga el algoritmo de comercio persistente; representa la lógica comercial a seguir.


Consiste en el modelo entrenado en sí y los parámetros para la normalización de los datos de
características, que son partes integrales del algoritmo:

En [110]: algoritmo = pickle.load(open('algorithm.pkl', 'rb'))

En [111]: algoritmo['modelo']
Fuera[111]: AdaBoostClassifier(algoritmo='SAMME.R',
base_estimator=DecisionTreeClassifier(ccp_alpha=0.0,
class_weight=Ninguno,
criterio='gini',
max_profundidad=2,

Algoritmo en línea | 291


Machine Translated by Google

max_features=Ninguno,
max_leaf_nodes=Ninguno,
min_impurity_decrease=0.0,
min_impurity_split=Ninguno,
min_samples_leaf=15,
min_samples_split=2,
min_weight_fraction_leaf=0.0,
presort='obsoleto',
random_state=100,
splitter='mejor'),
learning_rate=1.0, n_estimators =15, estado_aleatorio=100)

En el siguiente código, la nueva clase MLTrader, que hereda de tpqoa.tpqoa y que, a


través de .on_success() y métodos auxiliares adicionales, transforma el algoritmo
comercial en un contexto en tiempo real. Se trata de la transformación del algoritmo
offline a un llamado algoritmo online:
En [112]: clase MLTrader(tpqoa.tpqoa):
def __init__(self, config_file, algoritmo):
super(MLTrader, self).__init__(config_file) self.model =
algoritmo['model'] self.mu = algoritmo['mu']
self.std = algoritmo['std'] self.units =
100000

self.posición = 0 self.bar
= '5s'
self.window = 2
self.lags = 6
self.min_length = self.lags + self.window + 1 self.features = ['return',
'sma', 'min', 'max', 'vol', 'mom '] self.raw_data = pd.DataFrame() def prepare_features(self):
self.data['return'] = np.log(self.data['mid'] /
self.data['mid'].shift( 1)) self.data['sma']
= self.data['mid'].rolling(self.window).mean() self.data['min'] =
self.data['mid'].rolling (self.window).min()
self.data['mamá'] = np.sign(

self.data['return'].rolling(self.window).mean()) self.data['max'] =
self.data['mid'].rolling(self.window).max() self. datos['vol'] = self.data['return'].rolling(

self.window).std()
self.data.dropna(inplace=True)
self.data[self.features] ­= self.mu self.data[self.features] /
= self.std self.cols = [] para f en self.features:

para retraso en rango(1, self.lags + 1): col = f'{f}


_lag_{lag}' self.data[col] =
self.data[f].shift(lag) self.cols.append( col) def on_success(self,
tiempo, oferta, demanda): df =
pd.DataFrame({'oferta': float(oferta), 'preguntar':
float(preguntar)},

292 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

index=[pd.Timestamp(time).tz_localize(None)]) self.raw_data =
self.raw_data.append(df) self.data =
self.raw_data.resample(self.bar,
label='right').last().ffill() self.data =
self.data.iloc[:­1] if len(self.data) >
self.min_length: self.min_length +=1 self.data[ 'mid']
= (self.data['oferta'] +

self.data['preguntar']) / 2
self.prepare_features()
características =
self.data[ self.cols].iloc[­1].values.reshape(1, ­1)
señal = self.model.predict(características)[0] print(f'NUEVA
SEÑAL: {señal}', end='\r') si self.position en [0, ­1] y señal
== 1: imprimir ('*** VA LARGO ***')
self.create_order(self.stream_instrument,
unidades=(1 ­ self.position) * self.units) self.position = 1

elif self.position en [0, 1] y señal == ­1: print('*** GOING SHORT


***') self.create_order(self.stream_instrument,
unidades=­(1 + self.position) * self .unidades)

auto.posición = ­1

El objeto modelo AdaBoost entrenado y los parámetros de normalización.

El número de unidades comercializadas.

La posición inicial y neutral.

La longitud de la barra en la que se implementa el algoritmo.

La longitud de la ventana para las funciones seleccionadas.

El número de retrasos (debe estar en línea con el entrenamiento del algoritmo).

El método que genera los datos de características retrasadas.

El método redefinido que encarna la lógica comercial.

Compruebe si hay una señal larga y una operación larga.

Compruebe si hay una señal corta y una operación corta.

Con la nueva clase MLTrader, el comercio automatizado se simplifica. Unas pocas líneas de código son suficientes en un
contexto interactivo. Los parámetros están configurados de tal manera que el primer pedido se realiza al poco tiempo. En
realidad, sin embargo, todos los parámetros deben, por supuesto, estar en

Algoritmo en línea | 293


Machine Translated by Google

en línea con los originales de la fase de investigación y backtesting. Por ejemplo, también podrían persistir
en el disco y leerse con el algoritmo:

En [113]: mlt = MLTrader('../pyalgo.cfg', algoritmo)

En [114]: mlt.stream_data(instrumento, stop=500) print('***


CIERRE ***')
mlt.create_order(mlt.stream_instrument,
unidades=­mlt.posición * mlt.unidades)

Crea una instancia del objeto comercial.

Inicia la transmisión, el procesamiento de datos y el comercio.

Cierra la posición abierta final.

El código anterior genera una salida similar a la siguiente:

*** VA A LARGO ***

{'id': '1735', 'hora': '2020­08­19T14:46:15.552233563Z', 'ID de usuario': 13834683, 'ID


de cuenta': '101­004­13834683­001', 'ID de lote': '1734', 'requestID':
'42730658849646182', 'tipo': 'ORDER_FILL', 'orderID': '1734', 'instrumento':
'EUR_USD', 'unidades': '100000.0', 'gainQuoteHomeConversionFactor':
'0.835983419025 ', 'lossQuoteHomeConversionFactor':
'0.844385262432', 'precio': 1.1903, 'fullVWAP': 1.1903, 'fullPrice': {'tipo': 'PRECIO',
'ofertas': [{'precio': 1.19013, 'liquidez' : '10000000'}], 'pregunta': [{'precio': 1.1903, 'liquidez':
'10000000'}], 'liquidación': 1.19013, 'liquidez': 1.1903}, 'motivo': 'MARKET_ORDER',
'pl': '0.0', 'financiamiento': '0.0', 'comisión': '0.0', 'guaranteedExecutionFee': '0.0',
'accountBalance': '98507.7425', 'tradeOpened': {'tradeID': ' 1735', 'unidades': '100000.0',
'precio': 1.1903, 'guaranteedExecutionFee': '0.0', 'halfSpreadCost':
'7.1416', 'initialMarginRequired': '3330.0'}, 'halfSpreadCost': '7.1416'}

*** EN CORTO ***

{'id': '1737', 'hora': '2020­08­19T14:48:10.510726213Z', 'ID de usuario': 13834683, 'ID


de cuenta': '101­004­13834683­001', 'ID de lote': '1736', 'requestID':
'42730659332312267', 'tipo': 'ORDER_FILL', 'orderID': '1736', 'instrumento':
'EUR_USD', 'unidades': '­200000.0', 'gainQuoteHomeConversionFactor':
' 0.835885095595', 'lossQuoteHomeConversionFactor':
'0.844285950827', 'precio': 1.19029, 'fullVWAP': 1.19029, 'fullPrice': {'tipo': 'PRECIO',
'ofertas': [{'precio': 1.19029, 'liquidez ': '10000000'}], 'pregunta': [{'precio': 1.19042, 'liquidez':
'10000000'}], 'liquidación': 1.19029, 'liquidez': 1.19042}, 'razón': 'MARKET_ORDER' ,
'pl': '­0.8443', 'financiamiento': '0.0', 'comisión': '0.0', 'guaranteedExecutionFee': '0.0',
'accountBalance': '98506.8982', 'tradeOpened': {'tradeID' : '1737',

294 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

'unidades': '­100000.0', 'precio': 1.19029, 'guaranteedExecutionFee': '0.0',


'halfSpreadCost': '5.4606', 'initialMarginRequired': '3330.0'}, 'tradesClosed': [{'tradeID':
'1735', 'unidades': '­100000.0', 'precio': 1.19029, 'realizedPL': '­0.8443', 'financiamiento':
'0.0', 'guaranteedExecutionFee': '0.0', 'halfSpreadCost': '5.4606 '}],
'halfSpreadCost': '10.9212'}

*** VA A LARGO ***

{'id': '1739', 'hora': '2020­08­19T14:48:15.529680632Z', 'ID de usuario': 13834683, 'ID


de cuenta': '101­004­13834683­001', 'ID de lote': '1738', 'requestID':
'42730659353297789', 'tipo': 'ORDER_FILL', 'orderID': '1738', 'instrumento':
'EUR_USD', 'unidades': '200000.0', 'gainQuoteHomeConversionFactor':
'0.835835944263 ', 'lossQuoteHomeConversionFactor':
'0.844236305512', 'precio': 1.1905, 'fullVWAP': 1.1905, 'fullPrice': {'tipo': 'PRECIO',
'ofertas': [{'precio': 1.19035, 'liquidez' : '10000000'}], 'pregunta': [{'precio': 1.1905, 'liquidez':
'10000000'}], 'liquidación': 1.19035, 'liquidez': 1.1905}, 'motivo': 'MARKET_ORDER',
'pl': '­17.729', 'financiamiento': '0.0', 'comisión': '0.0', 'guaranteedExecutionFee': '0.0',
'accountBalance': '98489.1692', 'tradeOpened': {'tradeID': '1739', 'unidades':
'100000.0', 'precio': 1.1905, 'guaranteedExecutionFee': '0.0', 'halfSpreadCost':
'6.3003', 'initialMarginRequired': '3330.0'}, 'tradesClosed': [{' tradeID': '1737', 'units':
'100000.0', 'price': 1.1905, 'realizedPL': '­17.729', 'financiamiento': '0.0',
'guaranteedExecutionFee': '0.0', 'halfSpreadCost': '6.3003'}], 'halfSpreadCost':
'12.6006'}

*** CERRANDO ***

{'id': '1741', 'hora': '2020­08­19T14:49:11.976885485Z', 'ID de usuario': 13834683, 'ID


de cuenta': '101­004­13834683­001', 'ID de lote': '1740', 'requestID':
'42730659588338204', 'tipo': 'ORDER_FILL', 'orderID': '1740', 'instrumento':
'EUR_USD', 'unidades': '­100000.0', 'gainQuoteHomeConversionFactor':
' 0.835730636848', 'lossQuoteHomeConversionFactor':
'0.844129939731', 'precio': 1.19051, 'fullVWAP': 1.19051, 'fullPrice': {'tipo': 'PRECIO',
'ofertas': [{'precio': 1.19051, 'liquidez ': '10000000'}], 'pregunta': [{'precio': 1.19064, 'liquidez':
'10000000'}], 'liquidación': 1.19051, 'liquidez': 1.19064}, 'razón': 'MARKET_ORDER' ,
'pl': '0.8357', 'financiamiento': '0.0', 'comisión': '0.0', 'guaranteedExecutionFee': '0.0',
'accountBalance': '98490.0049', 'tradesClosed': [{'tradeID' : '1739', 'unidades':
'­100000.0', 'precio': 1.19051, 'realizedPL': '0.8357', 'financiamiento': '0.0',
'guaranteedExecutionFee': '0.0', 'halfSpreadCost': '5.4595 '}], 'halfSpreadCost': '5.4595'}

Algoritmo en línea | 295


Machine Translated by Google

Infraestructura e implementación
Implementar una estrategia comercial algorítmica automatizada con fondos reales requiere una
infraestructura adecuada. Entre otras cosas, la infraestructura debe cumplir con lo siguiente:

Confiabilidad
La infraestructura en la que implementar una estrategia comercial algorítmica debe permitir
una alta disponibilidad (por ejemplo, 99,9% o más) y debe cuidar la confiabilidad (copias de
seguridad automáticas, redundancia de unidades y conexiones web, etc.).

Rendimiento
Dependiendo de la cantidad de datos que se procesen y la demanda computacional que
generen los algoritmos, la infraestructura debe tener suficientes núcleos de CPU, memoria de
trabajo (RAM) y almacenamiento (SSD). Además, las conexiones web deben ser lo
suficientemente rápidas.

Seguridad
El sistema operativo y las aplicaciones que se ejecutan en él deben estar protegidos con
contraseñas seguras, así como con cifrado SSL y cifrado del disco duro. El hardware debe
estar protegido contra incendios, agua y acceso físico no autorizado.

Básicamente, estos requisitos sólo se pueden cumplir alquilando una infraestructura adecuada a
un centro de datos profesional o a un proveedor de nube. Las propias inversiones en infraestructura
física para satisfacer los requisitos mencionados anteriormente sólo pueden ser justificadas por los
actores más grandes, o incluso los más grandes, de los mercados financieros.

Desde el punto de vista del desarrollo y las pruebas, incluso el Droplet (instancia de nube) más
pequeño de DigitalOcean (http://digitalocean.com) es suficiente para comenzar. Al momento de
escribir este artículo, un Droplet de este tipo cuesta 5 USD por mes y se factura por hora, se crea
en minutos y se destruye en segundos.3

Cómo configurar un Droplet con DigitalOcean se explica en detalle en el Capítulo 2 (específicamente


en “Uso de instancias de la nube” en la página 36), con scripts Bash que se pueden ajustar para
reflejar los requisitos individuales con respecto a los paquetes de Python, por ejemplo.

3 Utilice el enlace http://bit.ly/do_sign_up para obtener un bono de 10 USD en DigitalOcean al registrarse para un nuevo
cuenta.

296 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

Aunque el desarrollo y prueba de estrategias comerciales algorítmicas


automatizadas es posible desde una computadora local (computadora
de escritorio, portátil o similar), no es apropiado para la implementación
de estrategias automatizadas que operan con dinero real. Una simple
pérdida de la conexión web o un breve corte de energía podrían hacer
caer todo el algoritmo, dejando, por ejemplo, posiciones abiertas no
deseadas en la cartera. Como otro ejemplo, haría que uno se perdiera
datos de ticks en tiempo real y terminaría con conjuntos de datos
corruptos, lo que podría generar señales incorrectas y operaciones y posiciones no deseadas.

Registro y monitoreo
Supongamos ahora que la estrategia de comercio algorítmico automatizado se implementará en un
servidor remoto (instancia de nube virtual o servidor dedicado). Supongamos además que se han
instalado todos los paquetes de Python necesarios (consulte “Uso de instancias en la nube” en la página
36) y que, por ejemplo, Jupyter Lab se está ejecutando de forma segura (consulte Ejecución de un
servidor portátil). ¿Qué más se debe considerar desde el punto de vista de los traders algorítmicos si no
quieren sentarse todo el día frente a la pantalla mientras inician sesión en el servidor?

Esta sección aborda dos temas importantes a este respecto: el registro y el monitoreo en tiempo real.
El registro conserva la información y los eventos en el disco para su posterior inspección. Es una
práctica estándar en el desarrollo e implementación de aplicaciones de software. Sin embargo, aquí la
atención podría centrarse en el aspecto financiero, registrando datos financieros importantes e
información de eventos para su posterior inspección y análisis. Lo mismo se aplica a la monitorización
en tiempo real mediante comunicación por socket. A través de sockets, se puede crear un flujo
constante en tiempo real de aspectos financieros importantes que luego se pueden recuperar y procesar
en una computadora local, incluso si la implementación se realiza en la nube.

“Estrategia comercial automatizada” en la página 305 presenta una secuencia de comandos Python que
implementa todos estos aspectos y hace uso del código de “Algoritmo en línea” en la página 291. La
secuencia de comandos le da al código una forma que permite, por ejemplo, la implementación del
algoritmo. estrategia comercial rítmica, basada en el objeto algorítmico persistente, en un servidor
remoto. Agrega capacidades de registro y monitoreo basadas en una función personalizada que, entre
otras cosas, hace uso de ZeroMQ (ver http://zeromq.org) para la comunicación por socket. En
combinación con el breve script de “Monitoreo de estrategia” en la página 308, esto permite un monitoreo
remoto en tiempo real de la actividad en un servidor remoto.4

Cuando se ejecuta el script de "Estrategia comercial automatizada" en la página 305 , ya sea local o
remotamente, la salida que se registra y envía a través del socket tiene el siguiente aspecto:

4 El método de registro utilizado aquí es bastante simple y tiene la forma de un archivo de texto simple. Es fácil cambiar el registro y
la persistencia de, por ejemplo, los datos financieros relevantes en forma de una base de datos o formatos de almacenamiento
binario apropiados, como HDF5 (ver Capítulo 3).

Registro y monitoreo | 297


Machine Translated by Google

2020­06­15 17:04:14.298653
==================================================== ===============================

NÚMERO DE TICKS: 147 | NÚMERO DE BARRAS: 49

==================================================== ===============================

DATOS MÁS RECIENTES

return_lag_1 return_lag_2 ... max_lag_5 max_lag_6 ­0.125253 ... ­1.703276 ­1.700746


2020­06­15 15:04:06 0.026508
2020­06­15 15:04:08 ­0.049373 0,026508 ... ­1,694419 ­1,703276
2020­06­15 15:04:10 ­0,077828 ­0,049373 ... ­1,694419 ­1,694419 ­0,077828 ... ­1,705807
2020­06­15 15:04:12 0,064448 ­1,694419 0,064448 ... ­1,710869 ­1,705807
2020­06­15 15:04:14 ­0,020918

[5 filas x 36 columnas]

==================================================== ===============================

características:
[[­0.02091774 0.06444794 ­0.07782834 ­0.04937258 0.02650799 ­0.12525265 ­2.06428556 ­1.96568848 ­2.16288147
­2.08071843 ­1.9492569 2­2.19574189
0,92939697 0,92939697 ­1,07368691 0,92939697 ­1,07368691 ­1,07368691 ­1,41861822 ­1,42605902 ­1,4294412
­1,42470615 ­1,4274119 ­1,42 470615 ­1.05508516 ­1.06879043 ­1.06879043 ­1.0619378 ­1.06741991 ­1.06741991
­1.70580717 ­1.70707253 ­1.71339931 ­1.7108686 ­1.7108686 ­ 1.70580717]] posición: 1 señal: 1

2020­06­15 17:04:14.402154
==================================================== ===============================

*** NO SE REALIZA COMERCIO ***

***FIN DE CICLO***

2020­06­15 17:04:16.199950
==================================================== ===============================

==================================================== ===============================

*** VOLVIÉNDOSE NEUTRAL ***

{'id': '979', 'hora': '2020­06­15T15:04:16.138027118Z', 'ID de usuario': 13834683, 'ID de cuenta': '101­004­13834683­001',
'ID de lote': '978', 'requestID': '60721506683906591', 'tipo': 'ORDER_FILL', 'orderID':
'978', 'instrumento': 'EUR_USD', 'unidades': '­100000.0', 'gainQuoteHomeConversionFactor': ' 0.882420762903',
'lossQuoteHomeConversionFactor': '0.891289313284', 'precio': 1.12751,
'fullVWAP': 1.12751, 'fullPrice': {'tipo': 'PRECIO', 'ofertas': [{'precio': 1.12751,
'liquidez ': '10000000'}], 'pregunta': [{'precio': 1.12765, 'liquidez': '10000000'}],
'liquidación': 1.12751, 'liquidez': 1.12765}, 'razón': 'MARKET_ORDER' , 'pl': '­3.5652', 'financiación': '0.0',
'comisión': '0.0',

298 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

'guaranteedExecutionFee': '0.0', 'accountBalance': '99259.7485', 'tradesClosed': [{'tradeID': '975',


'units': '­100000.0', 'price': 1.12751, 'realizedPL': ' ­3.5652', 'financiamiento': '0.0',
'guaranteedExecutionFee': '0.0', 'halfSpreadCost': '6.208'}], 'halfSpreadCost': '6.208'}

==================================================== ===============================

La ejecución local del script de “Monitoreo de estrategia” en la página 308 permite la recuperación y
el procesamiento en tiempo real de dicha información. Por supuesto, es fácil ajustar el registro y la
transmisión de datos a los propios requisitos.5 Además, el guión comercial y toda la lógica se pueden
ajustar para incluir elementos tales como detener pérdidas o objetivos de obtención de ganancias
mediante programación.

Operar con pares de divisas y/o CFD está asociado con una serie de riesgos
financieros. La implementación de una estrategia de negociación algorítmica
para dichos instrumentos conlleva automáticamente una serie de riesgos
adicionales. Entre ellos se encuentran fallas en la lógica de negociación y/o
ejecución, así como riesgos técnicos que incluyen problemas asociados con la
comunicación por socket, recuperación retrasada o incluso pérdida de datos
de ticks durante la implementación. Por lo tanto, antes de implementar una
estrategia comercial de manera automatizada, uno debe asegurarse de que
todos los riesgos asociados de mercado, de ejecución, operativos, técnicos y
de otro tipo hayan sido identificados, evaluados y abordados adecuadamente.
El código presentado en este capítulo es sólo para fines de ilustración técnica.

Descripción general visual paso a paso

Esta sección final proporciona una descripción general paso a paso en capturas de pantalla. Si bien
las secciones anteriores se basan en la plataforma comercial FXCM, la descripción visual se basa en
la plataforma comercial Oanda.

Configuración de la cuenta de Oanda

El primer paso es configurar una cuenta con Oanda (o cualquier otra plataforma comercial para este
fin) y establecer el índice de apalancamiento correcto para la cuenta de acuerdo con el criterio de
Kelly y como se muestra en la Figura 10­8.

5 Tenga en cuenta que la comunicación por socket, tal como se implementa en los dos scripts, no está cifrada y envía texto sin formato
a través de la web, lo que podría representar un riesgo de seguridad en producción.

Descripción general visual paso a paso | 299


Machine Translated by Google

Figura 10­8. Establecer influencia sobre Oanda

Configuración del hardware El

segundo paso es crear una gota de DigitalOcean, como se muestra en la Figura 10­9.

Figura 10­9. Gota del océano digital

300 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

Configuración del entorno Python El tercer paso es colocar

todo el software en el droplet (consulte la Figura 10­10) para configurar la infraestructura. Cuando todo funcione bien,
podrá crear un nuevo Jupyter Notebook e iniciar su sesión interactiva de Python (consulte la Figura 10­11).

Figura 10­10. Instalación de Python y paquetes

Figura 10­11. Pruebas del laboratorio Jupyter

Descripción general visual paso a paso | 301


Machine Translated by Google

Carga del código El cuarto

paso es cargar los scripts de Python para el comercio automatizado y el monitoreo en tiempo real, como
se muestra en la Figura 10­12. También es necesario cargar el archivo de configuración con las credenciales
de la cuenta.

Figura 10­12. Cargando archivos de código Python

Ejecutar el código El quinto

paso es ejecutar el script Python para el comercio automatizado, como se muestra en la Figura 10­13. La
Figura 10­14 muestra una operación que ha iniciado el script Python.

302 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

Figura 10­13. Ejecutando el script Python

Figura 10­14. Un comercio iniciado por el script Python

Descripción general visual paso a paso | 303


Machine Translated by Google

Monitoreo en tiempo real El último

paso es ejecutar el script de monitoreo localmente (siempre que haya configurado la IP correcta en el script
local), como se ve en la Figura 10­15. En la práctica, esto significa que puede monitorear localmente en
tiempo real qué está sucediendo exactamente en su instancia en la nube.

Figura 10­15. Monitoreo local en tiempo real a través de socket

Conclusiones
Este capítulo trata sobre el despliegue de una estrategia comercial algorítmica de forma automatizada,
basada en un algoritmo de clasificación del aprendizaje automático para predecir la dirección de los
movimientos del mercado. Aborda temas tan importantes como la gestión de capital (basada en el criterio
de Kelly), el backtesting vectorizado de rendimiento y riesgo, la transformación de algoritmos comerciales
fuera de línea a en línea, una infraestructura adecuada para la implementación y el registro y monitoreo
durante la implementación.

El tema de este capítulo es complejo y requiere un amplio conjunto de habilidades por parte del practicante
del comercio algorítmico. Por otro lado, tener disponibles API RESTful para trading algorítmico, como la de
Oanda, simplifica considerablemente la tarea de automatización, ya que la parte central se reduce
principalmente a hacer uso de las capacidades del paquete contenedor de Python tpqoa para la recuperación
de datos de ticks. y realización de pedidos. Alrededor de este núcleo se deberían agregar, en la medida de
lo apropiado y posible, elementos para mitigar los riesgos operativos y técnicos.

304 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

Referencias y recursos adicionales

Artículos citados en este capítulo:

Rotando, Louis y Edward Thorp. 1992. “El criterio de Kelly y el mercado de valores”. El Mensual
Matemático Estadounidense 99 (10): 922­931.

Colgado, Jane. 2010. "Apostar con el criterio de Kelly". http://bit.ly/betting_with_kelly.

Secuencia de comandos de Python

Esta sección contiene scripts de Python utilizados en este capítulo.

Estrategia comercial automatizada


El siguiente script de Python contiene el código para la implementación automatizada del
Estrategia comercial basada en ML, como se analiza y se prueba en este capítulo:

#
# Estrategia comercial automatizada basada en ML para Oanda
# Algoritmo en línea, registro, monitoreo
#
# Python para el comercio algorítmico # (c) Dr. Yves J.
Hilpisch #

importar zmq
importar tpqoa
importar pickle
importar numpy como
np importar pandas como
pd importar fecha y hora como dt

log_file = 'estrategia_automatizada.log'

# carga el objeto de algoritmo persistente algoritmo


= pickle.load(open('algorithm.pkl', 'rb'))

# configura la comunicación del socket a través de ZeroMQ (aquí: "editor") contexto =


zmq.Context() socket =
contexto.socket(zmq.PUB)

# esto vincula la comunicación del socket a todas las direcciones IP de la máquina socket.bind('tcp://
0.0.0.0:5555')

# recreando el archivo de registro


con open(log_file, 'w') como f: f.write('***
NUEVO ARCHIVO DE REGISTRO ***\n')
f.write(str(dt.datetime.now()) + '\n\n\n')

Referencias y recursos adicionales | 305


Machine Translated by Google

def logger_monitor(message, time=True, sep=True): función


'''
personalizada de registrador y monitor.
'''

con open(log_file, 'a') como f: t =


str(dt.datetime.now())
''
msj = si
hora: msg

+= '\n' + t + '\n' si sep: msj += 80 *


'=' + '\n'

msg += mensaje + '\n\n' #


envía el mensaje a través del socket
socket.send_string(msg) #
escribe el mensaje en el archivo de registro
f.write(msg)

clase MLTrader(tpqoa.tpqoa): def


__init__(self, config_file, algoritmo):
super(MLTrader, self).__init__(config_file) self.model =
algoritmo['model'] self.mu = algoritmo['mu']
self.std = algoritmo['std'] self.units =
100000

self.position = 0 self.bar
= '2s' self.window = 2
self.lags = 6
self.min_length =
self.lags + self.window + 1 self.features = ['return', 'vol', 'mom ',
'sma', 'min', 'max'] self.raw_data = pd.DataFrame()

def prepare_features(yo):
self.data['return'] = np.log( self.data['mid'] /
self.data['mid'].shift(1)) self.data['vol'] = self.data['
return'].rolling(self.window).std() self.data['mamá'] = np.sign(

self.data['return'].rolling(self.window).mean()) self.data['sma'] =
self.data['mid'].rolling(self.window).mean() self. datos['min'] = self.data['mid'].rolling(self.window).min()
self.data['max'] = self.data['mid'].rolling(self.window) .max() self.data.dropna(inplace=True)
self.data[self.features] ­= self.mu self.data[self.features] /= self.std self.cols = [] para f en
self .características:

para retraso en rango(1, self.lags + 1): col = f'{f}


_lag_{lag}' self.data[col] =
self.data[f].shift(lag) self.cols.append( columna)

def report_trade(self, pos, orden):

306 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

'''
Imprime, registra y envía datos comerciales.
'''

salida = '\n\n' + 80 * '=' + '\n' salida += '***


GOING {} *** \n'.format(pos) + '\n' salida += str( orden) + '\n' salida +=
80 * '=' + '\n' logger_monitor(salida)
print(salida)

def on_success(self, tiempo, oferta, demanda):


print(self.ticks, 20 * ' ', end='\r') df = pd.DataFrame({'bid':
float(bid), 'ask': float(ask)}, index=[pd.Timestamp (tiempo).tz_localize(Ninguno)])
self.raw_data = self.raw_data.append(df) self.data =
self.raw_data.resample(

self.bar, label='right').last().ffill() self.data = self.data.iloc[:­1]


if len(self.data) > self.min_length:

logger_monitor('NÚMERO DE TICKS: {} | '.format(self.ticks) + 'NÚMERO DE BARRAS:


{}'.format(self.min_length)) self.min_length += 1 self.data['mid'] =
(self.data['bid'] +
self.data['ask']) / 2 características self.prepare_features() =

self.data[self.cols].iloc[­1].values.reshape(1, ­ 1) señal = self.model.predict(features)[0] #


registra y envía información financiera importante
logger_monitor(' DATOS MÁS RECIENTES\n' +

str(self.data[self.cols].tail()), Falso)

logger_monitor('características:\n' + str(características) + '\n' + 'posición: ' +


str(self.position) + '\n' + 'señal: + str(señal), False) if self.
'
posición en [0, ­1] y señal == 1: # ¿va en largo?

orden = self.create_order(self.stream_instrument,
unidades=(1 ­ self.position) * self.units,
suprimir=True,
ret=True) self.report_trade('LONG',
orden) self.position = 1

elif self.position en [0, 1] y señal == ­1: # ¿se queda corto? orden =


self.create_order(self.stream_instrument,
unidades=­(1 + self.position) * self.units,
suprimir=True,
ret=True) self.report_trade('SHORT',
orden) self.position = ­1 else: # no trade
logger_monitor('*** NO SE
HA REALIZADO
COMERCIO ***')

logger_monitor('*** FIN DEL CICLO ***\n\n', Falso, Falso)

Secuencia de comandos de Python | 307


Machine Translated by Google

if __name__ == '__main__': mlt =


MLTrader('../pyalgo.cfg', algoritmo) mlt.stream_data('EUR_USD',
stop=150) orden = mlt.create_order(mlt.stream_instrument,

unidades = ­mlt.position * mlt.units, suprimir =


Verdadero, ret = Verdadero)
mlt.position = 0
mlt.report_trade('NEUTRAL', orden)

Monitoreo de estrategia El

siguiente script de Python contiene código para monitorear de forma remota la ejecución del
script de Python de “Estrategia comercial automatizada” en la página 305.
#
# Estrategia comercial automatizada basada en ML para Oanda # Monitoreo de
estrategia a través de comunicación de socket # # Python para comercio

algorítmico # (c) Dr. Yves J. Hilpisch

#
importar zmq

# configura la comunicación del socket a través de ZeroMQ (aquí: "suscriptor") contexto =


zmq.Context() socket =
context.socket(zmq.SUB)

# ajustamos la dirección IP para reflejar la ubicación remota socket.connect('tcp://


134.122.70.51:5555')

# dirección IP local utilizada para las pruebas #


socket.connect('tcp://0.0.0.0:5555')

# configura el socket para recuperar cada mensaje


socket.setsockopt_string(zmq.SUBSCRIBE, '')

mientras es
Verdadero: msg = socket.recv_string()
print(msg)

308 | Capítulo 10: Automatización de operaciones comerciales


Machine Translated by Google

APÉNDICE

Python, NumPy, matplotlib, pandas

Hablar es barato. Muéstrame el código.


­Linus Torvalds

Python se ha convertido en un poderoso lenguaje de programación y ha desarrollado un vasto


ecosistema de paquetes útiles en los últimos años. Este apéndice proporciona una descripción
general concisa de Python y tres de los pilares principales de la denominada pila científica o de
ciencia de datos:

• NumPy (ver https://numpy.org) • matplotlib

(ver https://matplotlib.org) • pandas (ver https://

pandas.pydata.org)

NumPy proporciona operaciones de matriz de alto rendimiento en conjuntos de datos numéricos grandes y homogéneos,
mientras que pandas está diseñado principalmente para manejar datos tabulares, como datos de series de tiempo
financieras, de manera eficiente.

Un apéndice introductorio de este tipo, que sólo aborda temas seleccionados relevantes para el resto del contenido de
este libro, no puede, por supuesto, reemplazar una introducción exhaustiva a Python y los paquetes cubiertos. Sin
embargo, si eres bastante nuevo en Python o en la programación en general, es posible que obtengas una primera
visión general y una idea de lo que se trata Python. Si ya tiene experiencia en otro lenguaje típicamente utilizado en
finanzas cuantitativas (como Matlab, R, C++ o VBA), verá cómo son las estructuras de datos, los paradigmas de
programación y los modismos típicos en Python.

Para obtener una descripción general completa de Python aplicado a las finanzas, consulte Hilpisch (2018).
Otras introducciones más generales al lenguaje con un enfoque científico y de análisis de datos son VanderPlas (2017)
y McKinney (2017).

309
Machine Translated by Google

Conceptos básicos de Python

Esta sección presenta los tipos y estructuras de datos básicos de Python, las estructuras de control,
y algunos modismos de Python.

Tipos de datos

Cabe señalar que Python es generalmente un sistema de tipo dinámico, lo que significa
que los tipos de objetos se infieren a partir de sus contextos. Empecemos con los números:

En [1]: a = 3

En [2]: escriba (a)


Fuera[2]: int

En [3]: a.bit_length()
Fuera[3]: 2

En [4]: b = 5.

En [5]: escriba (b)


Fuera[5]: flotar

Asigna al nombre de la variable un valor entero de 3.

Busca el tipo de a.

Busca el número de bits utilizados para almacenar el valor entero.

Asigna al nombre de la variable b un valor de coma flotante de 5,0.

Python puede manejar números enteros arbitrariamente grandes, lo cual es bastante beneficioso para los números.
aplicaciones teóricas, por ejemplo:

En [6]: c = 10 ** 100

En [7]: c
Fuera[7]: 10000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000

En [8]: c.bit_length()
Fuera[8]: 333

Asigna un valor entero "enorme".

Muestra el número de bits utilizados para la representación de números enteros.

310 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

Las operaciones aritméticas sobre estos objetos funcionan como se esperaba:

En [9] : 3/5 .
Fuera[9]: 0,6

Entrada [10]: a * b
Salida [10]: 15,0

Entrada [11]: a ­ b
Salida [11]: ­2,0

Entrada [12]: b + a
Salida [12]: 8,0

Entrada [13]: a ** b
Salida [13]: 243,0

División.

Multiplicación.

Suma.

Diferencia.

Fuerza.

Muchas funciones matemáticas de uso común se encuentran en el módulo de matemáticas , que


es parte de la biblioteca estándar de Python:

En [14]: importar matemáticas

En [15]: math.log(a)
Fuera[15]: 1.0986122886681098

En [16]: matemática.exp(a)
Fuera[16]: 20.085536923187668

En [17]: matemáticas.sin(b)
Fuera[17]: ­0,9589242746631385

Importa el módulo de matemáticas de la biblioteca estándar.

Calcula el logaritmo natural.

Calcula el valor exponencial.

Calcula el valor del seno.

Python, NumPy, matplotlib, pandas | 311


Machine Translated by Google

Otro tipo de datos básico importante es el objeto de cadena (str):

En [18]: s = 'Python para comercio algorítmico'.

En [19]: tipo(s)
Fuera[19]: str

En [20]: en inferior()
Out[20]: 'python para comercio algorítmico'.

En [21]: en superior()
Out[21]: 'PYTHON PARA COMERCIO ALGORITMICO.'

En [22]: s[0:6]
Fuera[22]: 'Python'

Asigna un objeto str al nombre de variable s.

Transforma todos los caracteres a minúsculas.

Transforma todos los caracteres a mayúsculas.

Selecciona los primeros seis caracteres.

Estos objetos también se pueden combinar utilizando el operador + . El valor del índice –1 repre
envía el último carácter de una cadena (o el último elemento de una secuencia en general):

En [23]: st = s[0:6] + s[­9:­1]

En [24]: imprimir(st)
Comercio de Python

Combina subconjuntos del objeto str en uno nuevo.

Imprime el resultado.

Los reemplazos de cadenas se utilizan a menudo para parametrizar la salida de texto:

En [25]: repl = 'Mi nombre es %s, tengo %d años y %4.2f m de altura.'

En [26]: imprimir (reemplazar % ('Gordon Gekko', 43, 1,78))


Mi nombre es Gordon Gekko, tengo 43 años y mido 1,78 m.

En [27]: repl = 'Mi nombre es {:s}, tengo {:d} años y {:4.2f} m de altura.'

En [28]: print(repl.format('Gordon Gekko', 43, 1.78))


Mi nombre es Gordon Gekko, tengo 43 años y mido 1,78 m.

En [29]: nombre, edad, altura = 'Gordon Gekko', 43, 1,78

En [30]: print(f'Mi nombre es {nombre:s}, tengo {edad:d} años y \

312 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

{altura:4.2f}m de altura.')
Mi nombre es Gordon Gekko, tengo 43 años y mido 1,78 m .

Define una plantilla de cadena a la manera "antigua".

Imprime la plantilla con los valores reemplazados de la forma "antigua".

Define una plantilla de cadena de la forma "nueva".

Imprime la plantilla con los valores reemplazados de forma “nueva”.

Define variables para su uso posterior durante el reemplazo.

Hace uso de la llamada cadena f para el reemplazo de cadenas (introducida en Python 3.6).

Estructuras de datos

Los objetos tupla son estructuras de datos livianas. Estas son colecciones inmutables de otros objetos y están
construidas por objetos separados por comas, con o sin paréntesis:

En [31]: t1 = (a, b, st)

En [32]: t1
Salida [32]: (3, 5.0, 'Comercio con Python')

En [33]: escriba (t1)


Fuera[33]: tupla

En [34]: t2 = st, b, a

En [35]: t2
Salida [35]: ('Python Trading', 5.0, 3)

En [36]: escriba (t2)


Fuera[36]: tupla

Construye un objeto tupla entre paréntesis.

Imprime la representación str .

Construye un objeto tupla sin paréntesis.

También son posibles estructuras anidadas:

En [37]: t = (t1, t2)

En [38]: t

Python, NumPy, matplotlib, pandas | 313


Machine Translated by Google

Fuera[38]: ((3, 5.0, 'Comercio con Python'), ('Comercio con Python', 5.0, 3))

En [39]: t[0][2]
Fuera[39]: 'Comercio con Python'

Construye un objeto tupla a partir de otros dos.

Accede al tercer elemento del primer objeto.

Los objetos de lista son colecciones mutables de otros objetos y generalmente se construyen
proporcionando una colección de objetos separados por comas entre paréntesis:

En [40]: l = [a, b, st]

En [41]: l
Fuera[41]: [3, 5.0, 'Comercio con Python']

En [42]: escriba (l)


Fuera[42]: lista

En [43]: l.append(s.split()[3])

En [44]: l
Fuera[44]: [3, 5.0, 'Comercio con Python', 'Comercio'.

Genera un objeto de lista usando corchetes.

Agrega un nuevo elemento (palabra final de s) al objeto de lista .

Ordenar es una operación típica en objetos de lista , que también se puede construir usando el constructor
de listas (aquí aplicado a un objeto tupla):

En [45]: l = lista(('Z', 'Q', 'D', 'J', 'E', 'H', '5.', 'a'))

En [46]: l
Fuera[46]: ['Z', 'Q', 'D', 'J', 'E', 'H', '5.', 'a']

En [47]: l.sort()

En [48]: l
Fuera[48]: ['5.', 'D', 'E', 'H', 'J', 'Q', 'Z', 'a']

Crea un objeto de lista a partir de una tupla.

Ordena todos los elementos in situ (es decir, cambia el objeto mismo).

Los objetos de diccionario (dict) son los llamados almacenes de valores clave y generalmente se
construyen con llaves:

En [49]: d = {'int_obj': a, 'float_obj': b, 'string_obj': st}

314 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

En [50]: escriba (d)


Fuera[50]: dict

En [51]: d
Salida[51]: {'int_obj': 3, 'float_obj': 5.0, 'string_obj': 'Python Trading'}

En [52]: d['float_obj']
Fuera[52]: 5,0

En [53]: d['int_obj_long'] = 10 ** 20

En [54]: d
Fuera[54]: {'int_obj': 3,
'obj_flotante': 5.0,
'string_obj': 'Comercio con Python',
'int_obj_long': 100000000000000000000}

En [55]: d.claves()
Fuera[55]: dict_keys(['int_obj', 'float_obj', 'string_obj', 'int_obj_long'])

En [56]: d.valores()
Fuera[56]: dict_values([3, 5.0, 'Python Trading', 100000000000000000000])

Crea un objeto dict usando llaves y pares clave­valor.

Accede al valor dada una clave.

Agrega un nuevo par clave­valor.

Selecciona y muestra todas las claves.

Selecciona y muestra todos los valores.

Estructuras de Control

Las iteraciones son operaciones muy importantes en la programación en general y financiera.


análisis en particular. Muchos objetos de Python son iterables, lo que resulta bastante convencional.
niente en muchas circunstancias. Considere el rango de objetos iteradores especiales :

En [57]: rango(5)
Fuera[57]: rango(0, 5)

En [58]: rango(3, 15, 2)


Fuera[58]: rango(3, 15, 2)

En [59]: para i en el rango(5):


print(i ** 2, end=' ')
0 1 4 9 16
En [60]: para i en el rango (3, 15, 2):
imprimir(yo, fin=' ')

Python, NumPy, matplotlib, pandas | 315


Machine Translated by Google

3 5 7 9 11 13
En [61]: l = ['a', 'b', 'c', 'd', 'e']

En [62]: para _ en l:
imprimir(_)
a
b
C
d
mi

En [63]: s = 'Comercio con Python'

En [64]: para c en s:
imprimir(c + '|', fin='')
P|y|t|h|o|n| |T|r|a|d|i|n|g|

`objeto dado un solo parámetro (valor final + 1).

Crea un objeto de rango con valores de parámetros de inicio, fin y paso .

Itera sobre un objeto de rango e imprime los valores al cuadrado.

Itera sobre un objeto de rango usando parámetros de inicio, fin y paso .

Itera sobre un objeto de lista .

Itera sobre un objeto str .

Los bucles while son similares a sus homólogos en otros idiomas:

En [65]: i = 0

En [66]: mientras i < 5:


imprimir(yo ** 0.5, fin=' ') yo += 1

0,0 1,0 1,4142135623730951 1,7320508075688772 2,0

Establece el valor del contador en 0.

Siempre que el valor de i sea menor que 5...

…imprime la raíz cuadrada de i y…

…aumentar el valor de i en 1.

316 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

Modismos de Python

Python en muchos lugares se basa en una serie de modismos especiales. Comencemos con una
bastante popular, la lista de comprensión:

En [67]: lc = [i ** 2 para i en el rango (10)]

En [68]: lc
Fuera[68]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

En [69]: escriba (lc)


Fuera[69]: lista

Crea un nuevo objeto de lista basado en la sintaxis de comprensión de la lista ( bucle for entre
paréntesis).

Las llamadas funciones lambda o anónimas son útiles en muchos lugares:

En [70]: f = lambda x: math.cos(x)

En [71]: f(5)
Fuera[71]: 0,2836621854632263

En [72]: lista(mapa(lambda x: math.cos(x), rango(10)))


Fuera[72]: [1.0,
0.5403023058681398,
­0.4161468365471424,
­0.9899924966004454,
­0.6536436208636119,
0.2836621854632263 ,
0.96017028665 03661,
0,7539022543433046 ,
­0,14550003380861354 ,
­0,9111302618846769]

Define una nueva función f mediante la sintaxis lambda .

Evalúa la función f para un valor de 5.

Asigna la función f a todos los elementos del objeto de rango y crea un objeto de lista con los
resultados, que se imprime.

En general, se trabaja con funciones regulares de Python (a diferencia de las funciones lambda), que
se construyen de la siguiente manera:

En [73]: def f(x):


devuelve math.exp(x)

En [74]: f(5)
Fuera[74]: 148.4131591025766

Python, NumPy, matplotlib, pandas | 317


Machine Translated by Google

En [75]: def f(*args): para arg en


args: print(arg)

regresar Ninguno

En [76]: f(l) ['a', 'b',


'c', 'd', 'e']

Las funciones regulares utilizan la declaración def para la definición.

Con la declaración de retorno , se define lo que se devuelve cuando la ejecución/


la evaluación es exitosa; Son posibles varias declaraciones de devolución (por ejemplo, para
casos diferentes).

0 permite pasar múltiples argumentos como un objeto iterable (por ejemplo,


objeto de lista ).

Itera sobre los argumentos.

Hace algo con cada argumento: aquí, imprimiendo.

Devuelve algo: aquí, Ninguno; No es necesario para una función Python válida.

Pasa el objeto de lista l a la función f, que lo interpreta como una lista de


argumentos.

Considere la siguiente definición de función, que devuelve diferentes valores/cadenas


Basado en una estructura de control if­elif­else :

En [77]: importación aleatoria

En [78]: a = aleatorio.randint(0, 1000)

En [79]: print(f'El número aleatorio es {a}')


El número aleatorio es 188.

En [80]: def número_decide(número):


si un < 10:
devuelve "El número es de un solo dígito".
elif 10 <= a < 100: devuelve
"El número es de dos dígitos".
else:
devuelve "El número es de tres dígitos".

En [81]: número_decide(a)
Out[81]: 'El número es de tres dígitos'.

318 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

Importa el módulo aleatorio para extraer números aleatorios.

Dibuja un número entero aleatorio entre 0 y 1000.

Imprime el valor del número sorteado.

Comprueba si hay un número de un solo dígito y, si es falso...

…comprueba si hay un número de dos dígitos; si también es falso…

…el único caso que queda es el de los tres dígitos.

Llama a la función con el valor de número aleatorio a.

NumPy
Muchas operaciones en finanzas computacionales se llevan a cabo sobre grandes conjuntos de datos numéricos.
NumPy es un paquete de Python que permite el manejo y operación eficiente de dichas estructuras de datos.
Aunque es un paquete bastante poderoso con una gran cantidad de funcionalidades, para los propósitos de este
libro es suficiente cubrir los conceptos básicos de NumPy. Un interesante libro en línea que está disponible de
forma gratuita sobre NumPy es From Python to NumPy. Cubre muchos aspectos importantes en detalle que se
omiten en las siguientes secciones.

Objeto ndarray regular


El caballo de batalla es la clase NumPy ndarray , que proporciona la estructura de datos para objetos de matriz
de n dimensiones. Puede generar un objeto ndarray , por ejemplo, a partir de un objeto de lista :
En [82]: importar numpy como np

En [83]: a = np.array(rango(24))

En [84]: un
Fuera[84]: matriz([ 0, 1, 2, 3, 4, 5 , 6 , 7, 8, 9, 10, 11, 12, 13, 14,

15, 16,
17, 18, 19, 20, 21, 22, 23])

En [85]: b = a.reshape((4, 6))

En [86]: b
Fuera[86]: matriz([[ 0, 1, 2, 3, 4, 5], [ 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17], [18, 19,
20, 21, 22, 23]])

Python, NumPy, matplotlib, pandas | 319


Machine Translated by Google

En [87]: c = a.reshape((2, 3, 4))

En [88]: c
Fuera[88]: matriz([[[ 0, 1, 2, 3], [ 4, 5, 6,
7], [ 8, 9, 10, 11]],

[[12, 13, 14, 15], [16,


17, 18, 19], [20, 21,
22, 23]]])

En [89]: b = np.array(b, dtype=np.float)

En [90]: b
Fuera[90]: matriz([[ 0., 1., 2., 3., 4., 5.], [ 6., 7., 8., 9., 10.,
11.], [12., 13., 14., 15., 16., 17.], [18.,
19., 20., 21., 22., 23.]])

Importa NumPy como np por convención.

Crea una instancia de un objeto ndarray a partir del objeto de rango ; np.arange también podría
usarse, por ejemplo.

Imprime los valores.

Cambia la forma del objeto a uno bidimensional...

…e imprime el resultado.

Cambia la forma del objeto a uno tridimensional...

…e imprime el resultado.

Esto cambia el tipo de objeto a np.float y…

…muestra el nuevo conjunto de números (ahora de punto flotante).

320 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

Muchas estructuras de datos de Python están diseñadas para ser bastante


generales. Un ejemplo son los objetos de lista mutables que se pueden
manipular fácilmente de muchas maneras (agregando y eliminando
elementos, almacenando otras estructuras de datos complejas, etc.). La
estrategia de NumPy con el objeto ndarray regular es proporcionar una
estructura de datos más especializada para la cual todos los elementos sean
del mismo tipo atómico y que a su vez permita el almacenamiento contiguo
en la memoria. Esto hace que el objeto ndarray sea mucho mejor para
resolver problemas en ciertas configuraciones, como cuando se opera con
conjuntos de datos numéricos más grandes, o incluso grandes. En el caso
de NumPy, esta especialización también conlleva comodidad para el
programador, por un lado, y, a menudo, mayor velocidad, por otro.

Operaciones vectorizadas

Una de las principales fortalezas de NumPy son las operaciones vectorizadas:

En [91]: 2 * b
Fuera[91]: matriz([[ 0., 2., 4., 6., 8., 10.], [12., 14., 16., 18., 20 ., 22.],
[24., 26., 28., 30., 32., 34.], [36., 38., 40., 42.,
44., 46.]])

En [92]: b ** 2
Salida[92]: matriz([[ 0., 1., 4., 9., 16., 25.], [ 36., 49., 64.,
81., 100., 121.], [144., 169., 196., 225., 256., 289 .],
[324., 361., 400., 441., 484., 529.]])

En [93]: f = lambda x: x ** 2 ­ 2 * x + 0,5

En [94]: f(a)
Fuera[94]: matriz([ 0,5, ­0,5, 0,5, 3,5, 8,5 , 15,5, 24,5, 35,5,
48,5,
63,5, 80,5, 99,5, 120,5, 143,5 , 168,5, 195,5, 224,5, 255,5,
288,5, 323,5, 360,5, 399,5, 440,5, 483,5])

Implementa una multiplicación escalar en el objeto ndarray unidimensional (vector).

Calcula el cuadrado de cada número de b de forma vectorizada.

Define una función f mediante un constructor lambda .

Aplica f al objeto ndarray a usando vectorización.

Python, NumPy, matplotlib, pandas | 321


Machine Translated by Google

En muchos escenarios, sólo una (pequeña) parte de los datos almacenados en un objeto ndarray es de
interés. NumPy admite cortes básicos y avanzados y otras funciones de selección:

En [95]: a[2:6]
Fuera[95]: matriz([2, 3, 4, 5])

En [96]: b[2, 4]
Fuera[96]: 16,0

En [97]: b[1:3, 2:4]


Fuera[97]: matriz([[ 8., 9.],
[14., 15.]])

Selecciona los elementos tercero a sexto.

Selecciona la tercera fila y la quinta (última) fila.

Selecciona el cuadrado del medio del objeto b .

Operaciones booleanas

Las operaciones booleanas también se admiten en muchos lugares:

En [98]: b > 10
Fuera[98]: matriz([[Falso, Falso, Falso, Falso, Falso, Falso],
[Falso, Falso, Falso, Falso, Falso, Verdadero],
[ Verdadero, Verdadero, Verdadero, Verdadero, Verdadero, Verdadero],
[ Verdadero, Verdadero, Verdadero, Verdadero, Verdadero, Verdadero]])

En [99]: b[b > 10]


Fuera[99]: matriz([11., 12., 13., 14., 15. , 16. , 17., 18., 19., 20., 21., 22.,
23.])

¿Qué números son mayores que 10?

Devuelve todos aquellos números mayores que 10.

Métodos ndarray y funciones NumPy

Además, los objetos ndarray tienen múltiples métodos (convenientes) ya integrados:

En [100]: a.suma()
Fuera[100]: 276

En [101]: b.media()
Fuera[101]: 11,5

En [102]: b.media(eje=0)
Fuera[102]: matriz([ 9., 10., 11., 12., 13., 14.])

322 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

En [103]: b.media(eje=1)
Fuera[103]: matriz([ 2.5, 8.5, 14.5, 20.5])

En [104]: c.std()
Fuera[104]: 6.922186552431729

La suma de todos los elementos.

La media de todos los elementos.

La media a lo largo del primer eje.

La media a lo largo del segundo eje.

La desviación estándar de todos los elementos.

De manera similar, existe una gran cantidad de las llamadas funciones universales que el paquete NumPy proporciona.
vídeos. Son universales en el sentido de que se pueden aplicar en general a NumPy.
objetos ndarray y tipos de datos numéricos estándar de Python. Para obtener más información, consulte Universidad
funciones sal (ufunc):

En [105]: np.sum(a)
Fuera[105]: 276

En [106]: np.media(b, eje=0)


Fuera[106]: matriz([ 9., 10., 11., 12., 13., 14.])

En [107]: np.sin(b).ronda(2)
Fuera[107]: matriz([[ 0. , 0,84, 0,91, 0,14, ­0,76, ­0,96],
[­0,28, 0,66, 0,99, 0,41, ­0,54, ­1. ],
[­0,54, 0,42, 0,99, 0,65, ­0,29, ­0,96],
[­0,75, 0,15, 0,91, 0,84, ­0,01, ­0,85]])

En [108]: np.sin(4.5)
Fuera[108]: ­0,977530117665097

La suma de todos los elementos.

La media a lo largo del primer eje.

El valor del seno para todos los elementos redondeado a dos dígitos.

El valor seno de un objeto flotante de Python.

Python, NumPy, matplotlib, pandas | 323


Machine Translated by Google

Sin embargo, debe tener en cuenta que aplicar las funciones universales de NumPy al estándar
Los tipos de datos de Python generalmente conllevan una carga de rendimiento significativa:

En [109]: %tiempo l = [np.sin(x) para x en el rango(1000000)]


Tiempos de CPU: usuario 1,21 s, sistema: 22,9 ms, total: 1,24 s Tiempo de
pared: 1,24 s

En [110]: %tiempo l = [math.sin(x) para x en el rango(1000000)]


Tiempos de CPU: usuario 215 ms, sistema: 22,9 ms, total: 238 ms Tiempo
de pared: 239 ms

Comprensión de listas utilizando la función universal NumPy en objetos flotantes de Python .

Comprensión de listas usando funciones matemáticas en objetos flotantes de Python .

Por otro lado, usar las operaciones vectorizadas de NumPy en objetos ndarray es más rápido que las dos
alternativas anteriores que dan como resultado objetos de lista . Sin embargo, la ventaja de la velocidad a
menudo tiene el costo de una huella de memoria mayor, o incluso enorme:

En [111]: % tiempo a = np.sin(np.arange(1000000))


Tiempos de CPU: usuario 20,7 ms, sistema: 5,32 ms, total: 26 ms Tiempo
de pared: 24,6 ms

En [112]: sistema de importación

En [113]: sys.getsizeof(a)
Fuera[113]: 8000096

Entrada [114]: a.nbytes


Salida [114]: 8000000

Cálculo vectorizado de los valores de los senos con NumPy, que es mucho más rápido en general.

Importa el módulo sys con muchas funciones relacionadas con el sistema.

Muestra el tamaño de un objeto en la memoria.

Muestra el número de bytes utilizados para almacenar los datos en un objeto .

La vectorización a veces es un enfoque muy útil para escribir código conciso


que a menudo también es mucho más rápido que el código Python. Sin
embargo, tenga en cuenta la huella de memoria que la vectorización puede
tener en muchos escenarios relevantes para las finanzas. A menudo, hay
implementaciones alternativas de algoritmos disponibles que son eficientes
en memoria y que, al usar bibliotecas de rendimiento como Numba o Cython,
pueden ser incluso más rápidas. Véase Hilpisch (2018, cap. 10).

324 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

Creación de ndarray

Aquí utilizamos el constructor de objetos ndarray np.arange(), que produce un objeto ndarray.
objeto de números enteros. El siguiente es un ejemplo sencillo:

En [115]: ai = np.arange(10)

En [116]: ai
Fuera[116]: matriz([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

En [117]: ai.dtype Fuera


[117]: dtype('int64')

En [118]: af = np.arange(0.5, 9.5, 0.5)

En [119]: af
Fuera[119]: matriz([0.5, 1. 6.5, , 1,5, 2. , 2,5, 3. , 3.5, 4. , 4,5, 5. , 5.5, 6. ,

7. , 7,5, 8. , 8.5, 9. ])

En [120]: af.dtype Salida


[120]: dtype('float64')

En [121]: np.linspace(0, 10, 12)


Fuera[121]: matriz([ 0. , 0.90909091, 1.81818182, 2.72727273,
3.63636364,
4.54545455, 5.45454545, 6.36363636, 7.27272727, 8.18181818,
9.09090909, 10. ])

Crea una instancia de un objeto ndarray a través del constructor np.arange() .

Imprime los valores.

El tipo de archivo resultante es np.int64.

Utiliza arange() nuevamente, pero esta vez con parámetros de inicio, fin y paso .

Imprime los valores.

El tipo de archivo resultante es np.float64.

Utiliza el constructor linspace() , que espacia uniformemente el intervalo entre 0 y


10 en 11 intervalos, devolviendo un objeto ndarray con 12 valores.

Python, NumPy, matplotlib, pandas | 325


Machine Translated by Google

Números al azar
En análisis financiero, a menudo se necesitan números aleatorios1. NumPy proporciona muchas funciones
ciones para tomar muestras de diferentes distribuciones. Los que se necesitan regularmente en términos cuantitativos
Las finanzas son la distribución normal estándar y la distribución de Poisson. El

Las funciones respectivas se encuentran en el subpaquete numpy.random:

En [122]: np.random.standard_normal(10)
Fuera[122]: matriz([­1.06384884, ­0.22662171, 1.2615483 ­1.23231112, , ­0.45626608,

­1,51309987, 1,23938439, 0,22411366, ­0,84616512, ­1,09923136])

En [123]: np.random.poisson(0.5, 10)


Fuera[123]: matriz([0, 1, 1, 0, 0, 1, 0, 0, 2, 0])

En [124]: np.random.seed(1000)

En [125]: datos = np.random.standard_normal((5, 100))

En [126]: datos[:, :3]


Fuera[126]: matriz([[­0.8044583 , 0.32093155, ­0.02548288],
[­0,39031935, ­0,58069634, 1,94898697],
[­1.11573322, ­1.34477121, 0.75334374],
[ 0,42400699, ­1,56680276, 0,76499895],
[­1,74866738, ­0,06913021, 1,52621653]])

En [127]: datos.media()
Fuera[127]: ­0,02714981205311327

En [128]: datos.std()
Fuera[128]: 1.0016799134894265

En [129]: datos = datos ­ datos.media()

En [130]: datos.media()
Fuera[130]: 3.552713678800501e­18

En [131]: datos = datos / datos.std()

En [132]: datos.std()
Fuera[132]: 1,0

Dibuja diez números aleatorios estándar distribuidos normalmente.

Dibuja diez números aleatorios distribuidos por Poisson.

Corrige el valor inicial del generador de números aleatorios para mayor repetibilidad.

1 Tenga en cuenta que las computadoras sólo pueden generar números pseudoaleatorios como aproximaciones a números verdaderamente aleatorios.

326 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

Genera un objeto ndarray bidimensional con números aleatorios.

Imprime una pequeña selección de los números.

La media de todos los valores es cercana a 0 pero no exactamente 0.

La desviación estándar es cercana a 1 pero no exactamente 1.

El primer momento se corrige de forma vectorizada.

La media ahora es “casi igual” a 0.

El segundo momento se corrige de forma vectorizada.

La desviación estándar ahora es exactamente 1.

matplotlib
En este punto, tiene sentido introducir el trazado con matplotlib, el método de trazado
caballo de batalla en el ecosistema Python. Usamos matplotlib con la configuración de otro.
biblioteca en todas partes, a saber, seaborn. Esto da como resultado un estilo de trazado más moderno.
El siguiente código genera la Figura A­1:

En [133]: importar matplotlib.pyplot como plt

En [134]: plt.style.use('seaborn')

En [135]: importar matplotlib como mpl

En [136]: mpl.rcParams['savefig.dpi'] = 300


mpl.rcParams['font.family'] = 'serif' %matplotlib
en línea

En [137]: datos = np.random.standard_normal((5, 100))

En [138]: plt.figure(figsize=(10, 6))


plt.plot(data.cumsum())
Fuera[138]: [<matplotlib.lines.Line2D en 0x7faceaaeed30>]

Importa la biblioteca de trazado principal.

Establece nuevos valores predeterminados de estilo de trama.

Importa el módulo de nivel superior.

Establece la resolución en 300 DPI (para guardar) y la fuente en serif.

Python, NumPy, matplotlib, pandas | 327


Machine Translated by Google

Genera un objeto ndarray con números aleatorios.

Crea una instancia de un nuevo objeto de figura .

Primero calcula la suma acumulada de todos los elementos del objeto ndarray y
luego traza el resultado.

Figura A­1. Gráfico de líneas con matplotlib

También es fácil generar múltiples gráficos de líneas en un objeto de una sola figura (consulte la Figura A­2):

En [139]: plt.figure(figsize=(10, 6));


plt.plot(data.T.cumsum(axis=0), label='line')
plt.legend(loc=0);
plt.xlabel(' punto de datos')
plt.ylabel('valor'); plt.title('
serie aleatoria');

Crea una instancia de un nuevo objeto de figura y define el tamaño.

Traza cinco líneas calculando la suma acumulada a lo largo del primer eje y define
una etiqueta.

Pone una leyenda en la posición óptima (loc=0).

Agrega una etiqueta al eje x.

328 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

Agrega una etiqueta al eje y.

Agrega un título a la figura.

Figura A­2. Trazar con múltiples líneas

Otros tipos de trazado importantes son los histogramas y los gráficos de barras. En la Figura A­3 se muestra un histograma

para los 500 valores del objeto de datos . En el código, el método .flatten() se utiliza para generar una matriz unidimensional
a partir de una bidimensional:

En [140]: plt.figure(figsize=(10, 6))


plt.hist(data.flatten(), bins=30);

Traza el histograma con 30 contenedores (grupos de datos).

Finalmente, considere el gráfico de barras presentado en la Figura A­4, generado por el siguiente
código:

En [141]: plt.figure(figsize=(10, 6))


plt.bar(np.arange(1, 12) ­ 0,25, datos[0,
:11], ancho=0,5);

Traza un gráfico de barras basado en un pequeño subconjunto del conjunto de datos original.

Python, NumPy, matplotlib, pandas | 329


Machine Translated by Google

Figura A­3. Histograma de datos aleatorios

Figura A­4. Gráfico de barras de datos aleatorios.

Para concluir la introducción a matplotlib, considere la regresión de mínimos cuadrados ordinarios (OLS) de los
datos de muestra que se muestran en la Figura A­5. NumPy proporciona las dos funciones de conveniencia polifit
y polival para implementar OLS basado

330 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

en monomios simples, x, x2 , x3 , . . ., xn . Para fines ilustrativos, considere lineal,


cúbica y regresión MCO de noveno grado (ver Figura A­5):

En [142]: x = np.arange(len(data.cumsum()))

En [143]: y = 0,2 * datos.cumsum() ** 2

En [144]: rg1 = np.polyfit(x, y, 1)

En [145]: rg3 = np.polyfit(x, y, 3)

En [146]: rg9 = np.polyfit(x, y, 9)

En [147]: plt.figure(figsize=(10, 6)) plt.plot(x,


y, 'r', label='data') plt.plot(x, np.polyval(rg1,
x), 'b­­', etiqueta='lineal') plt.plot(x, np.polyval(rg3, x), 'b­.',
etiqueta='cúbico') plt.plot(x, np.polyval(rg9 , x), 'b:', label='9no
grado') plt.legend(loc=0);

Crea un objeto ndarray para los valores de x .

Define los valores de y como la suma acumulada del objeto de datos .

Regresión lineal.

Regresión cúbica.

Regresión de noveno grado.

El nuevo objeto figura .

Los datos base.

Se visualizan los resultados de la regresión.

Coloca una leyenda.

Python, NumPy, matplotlib, pandas | 331


Machine Translated by Google

Figura A­5. Regresión lineal, cúbica y de noveno grado

pandas
pandas es un paquete con el que se pueden gestionar y operar datos de series temporales y
otras estructuras de datos tabulares de manera eficiente. Permite la implementación de soluciones incluso sofisticadas.
ted tareas de análisis de datos en conjuntos de datos bastante grandes en memoria. Si bien el foco está en
operaciones en memoria, también hay múltiples opciones para operaciones sin memoria (en disco)
operaciones. Aunque pandas proporciona varias estructuras de datos diferentes, incorpora
Compuesto por clases potentes, la estructura más utilizada es la clase DataFrame .
que se asemeja a una tabla típica de una base de datos relacional (SQL) y se utiliza para administrar,
por ejemplo, datos de series de tiempo financieras. Esto es en lo que nos centramos en esta sección.

Clase de marco de datos

En su forma más básica, un objeto DataFrame se caracteriza por un índice, columna


nombres y datos tabulares. Para hacer esto más específico, considere el siguiente ejemplo
conjunto de datos:

En [148]: importar pandas como pd

En [149]: np.random.seed(1000)

En [150]: raw = np.random.standard_normal((10, 3)).cumsum(axis=0)

En [151]: index = pd.date_range('2022­1­1', period=len(raw), freq='M')

332 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

En [152]: columnas = ['no1', 'no2', 'no3']

En [153]: df = pd.DataFrame(sin formato, índice=índice, columnas=columnas)

En [154]: gl
Fuera[154]: no1 no2 Numero 3

2022­01­31 ­0.804458 0.320932 ­0.025483


2022­02­28 ­0.160134 0.020135 0.363992
2022­03­31 ­0.267572 ­0.459848 0.959027
2022­04­30 ­0.732239 0.207433 0.152912
2022­05­31 ­1.928309 ­0.198527 ­0.029466
2022­06­30 ­1.825116 ­0.336949 0.676227
2022­07­31 ­0.553321 ­1.323696 0.341391
2022­08­31 ­0.652803 ­0.916504 1.260779
2022­09­30 ­0.340685 0.616657 0.710605
2022­10­31 ­0.723832 ­0.206284 2.310688

Importa el paquete pandas .

Corrige el valor inicial del generador de números aleatorios de NumPy.

Crea un objeto ndarray con números aleatorios.

Define un objeto DatetimeIndex con algunas fechas.

Define un objeto de lista que contiene los nombres de las columnas (etiquetas).

Crea una instancia de un objeto DataFrame .

Muestra la representación str (HTML) del nuevo objeto.

Los objetos DataFrame han incorporado una multitud de funciones básicas, avanzadas y convenientes.
métodos, algunos de los cuales se ilustran en el código Python que aparece a continuación:

En [155]: df.head()
Fuera[155]: no1 no2 no3
2022­01­31 ­0.804458 0.320932 ­0.025483
2022­02­28 ­0.160134 0.020135 0.363992
2022­03­31 ­0.267572 ­0.459848 0.959027
2022­04­30 ­0.732239 0.207433 0.152912
2022­05­31 ­1.928309 ­0.198527 ­0.029466

En [156]: df.tail()
Fuera[156]: no1 no2 Numero 3

2022­06­30 ­1.825116 ­0.336949 0.676227


2022­07­31 ­0.553321 ­1.323696 0.341391
2022­08­31 ­0.652803 ­0.916504 1.260779
2022­09­30 ­0.340685 0.616657 0.710605
2022­10­31 ­0.723832 ­0.206284 2.310688

Python, NumPy, matplotlib, pandas | 333


Machine Translated by Google

En [157]: df.index
Salida[157]: DatetimeIndex(['2022­01­31', '2022­02­28', '2022­03­31',
'2022­04­30',
'2022­05­31', '2022­06­30', '2022­07­31', '2022­08­31',
'2022­09­30', '2022­10­31'],
dtype='fechahora64[ns]', frecuencia='M')

En [158]: df.columns
Fuera[158]: Índice(['no1', 'no2', 'no3'], dtype='objeto')

En [159]: df.info() <clase


'pandas.core.frame.DataFrame'>
DatetimeIndex: 10 entradas, 2022­01­31 al 2022­10­31
Frecuencia: M

Columnas de datos (un total de 3 columnas):


# Columna Tipo D de recuento no nulo
­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 no1 1 10 tipos no nulos flotador64


no2 10 no nulos 10 flotador64
2 no 3 tipos no nulos: flotador64
float64(3)
uso de memoria: 320,0 bytes

En [160]: df.describe()
Fuera[160]: no1 no2 Numero 3

contar 10.000000 10.000000 10.000000


media ­0,798847 ­0,227665 0,672067
estándar 0,607430 0,578071 0,712430
mín. ­1,928309 ­1,323696 ­0,029466
25% ­0,786404 ­0,429123 0,200031
50% ­0,688317 ­0,202406 0,520109
75% ­0,393844 0,160609 0,896922
máximo ­0,160134 0,616657 2,310688

Muestra las primeras cinco filas de datos.

Muestra las últimas cinco filas de datos.

Imprime el atributo de índice del objeto.

Imprime el atributo de columna del objeto.

Muestra algunos metadatos sobre el objeto.

Proporciona estadísticas resumidas seleccionadas sobre los datos.

334 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

Mientras que NumPy proporciona una estructura de datos especializada para


matrices multidimensionales (con datos numéricos en general), pandas toma
la especialización un paso más allá hacia los datos tabulares (bidimensionales)
con la clase DataFrame . En particular, los pandas son fuertes en la mano.
datos de series de tiempo financieras, como lo ilustran los ejemplos siguientes.

Operaciones numéricas
Las operaciones numéricas son en general tan fáciles con objetos DataFrame como con NumPy
objetos ndarray . También son bastante parecidos en términos de sintaxis:

En [161]: imprimir(df * 2)
no1 no2 no3
2022­01­31 ­1.608917 0.641863 ­0.050966
2022­02­28 ­0.320269 0.040270 0.727983
2022­03­31 ­0.535144 ­0.919696 1.918054
2022­04­30 ­1.464479 0.414866 0.305823
2022­05­31 ­3.856618 ­0.397054 ­0.058932
2022­06­30 ­3.650232 ­0.673898 1.352453
2022­07­31 ­1.106642 ­2.647393 0.682782
2022­08­31 ­1.305605 ­1.833009 2.521557
2022­09­30 ­0.681369 1.233314 1.421210
2022­10­31 ­1.447664 ­0.412568 4.621376

En [162]: df.std()
Fuera[162]: no1 0,607430
número 2 0,578071
nº3 0,712430
tipo de letra: float64

En [163]: df.media()
Fuera[163]: no1 ­0.798847
no2 ­0.227665
Numero 3 0.672067
tipo de letra: float64

En [164]: df.media(eje=1)
Fuera[164]: 2022­01­31 ­0.169670
2022­02­28 0.074664
2022­03­31 0.077202
2022­04­30 ­0.123965
2022­05­31 ­0.718767
2022­06­30 ­0.495280
2022­07­31 ­0.511875
2022­08­31 ­0.102843
2022­09­30 0.328859
2022­10­31 0.460191
Frecuencia: M, tipo d: float64

En [165]: np.media(df)
Fuera[165]: no1 ­0.798847

Python, NumPy, matplotlib, pandas | 335


Machine Translated by Google

no2 ­0.227665
nº3 0,672067
tipo de letra: float64

Multiplicación escalar (vectorizada) de todos los elementos.

Calcula la desviación estándar por columnas...

…y valor medio. Con los objetos DataFrame , las operaciones por columnas son las
por defecto.

Calcula el valor medio por valor de índice (es decir, por filas).

Aplica una función de NumPy al objeto DataFrame .

Selección de datos

Los datos se pueden buscar a través de diferentes mecanismos:

En [166]: df['no2']
Fuera[166]: 2022­01­31 0.320932
2022­02­28 0.020135
2022­03­31 ­0.459848
2022­04­30 0.207433
2022­05­31 ­0.198527
2022­06­30 ­0.336949
2022­07­31 ­1.323696
2022­08­31 ­0.916504
2022­09­30 0.616657
2022­10­31 ­0.206284
Frecuencia: M, Nombre: no2, tipo d: float64

En [167]: df.iloc[0]
Fuera[167]: no1 ­0.804458
no2 0.320932
no3 ­0.025483
Nombre: 2022­01­31 00:00:00, tipo d: float64

En [168]: df.iloc[2:4]
Fuera[168]: no1 no2 Numero 3

2022­03­31 ­0.267572 ­0.459848 0.959027


2022­04­30 ­0.732239 0.207433 0.152912

En [169]: df.iloc[2:4, 1]
Fuera[169]: 2022­03­31 ­0.459848
2022­04­30 0.207433
Frecuencia: M, Nombre: no2, tipo d: float64

En [170]: df.no3.iloc[3:7]
Fuera[170]: 2022­04­30 0.152912

336 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

2022­05­31 ­0.029466
2022­06­30 0.676227
2022­07­31 0.341391
Frecuencia: M, Nombre: no3, tipo d: float64

En [171]: df.loc['2022­3­31']
Fuera[171]: no1 ­0.267572
no2 ­0.459848
Numero 3 0.959027
Nombre: 2022­03­31 00:00:00, tipo d: float64

En [172]: df.loc['2022­5­31', 'no3']


Fuera[172]: ­0,02946577492329111

En [173]: df['no1'] + 3 * df['no3']


Fuera[173]: 2022­01­31 ­0.880907
2022­02­28 0.931841
2022­03­31 2.609510
2022­04­30 ­0.273505
2022­05­31 ­2.016706
2022­06­30 0.203564
2022­07­31 0.470852
2022­08­31 3.129533
2022­09­30 1.791130
2022­10­31 6.208233
Frecuencia: M, tipo d: float64

Selecciona una columna por nombre.

Selecciona una fila por posición de índice.

Selecciona dos filas por posición de índice.

Selecciona dos valores de fila de una columna por posiciones de índice.

Utiliza la sintaxis de búsqueda de puntos para seleccionar una columna.

Selecciona una fila por valor de índice.

Selecciona un único punto de datos por valor de índice y nombre de columna.

Implementa una operación aritmética vectorizada.

Operaciones booleanas
La selección de datos basada en operaciones booleanas también es un punto fuerte de los pandas:

En [174]: df['no3'] > 0,5 Fuera[174]:


2022­01­31 Falso
2022­02­28 Falso

Python, NumPy, matplotlib, pandas | 337


Machine Translated by Google

2022­03­31 Verdadero

2022­04­30 FALSO
2022­05­31 FALSO
2022­06­30 Verdadero

2022­07­31 FALSO
2022­08­31 Verdadero

2022­09­30 Verdadero

2022­10­31 Verdadero

Frecuencia: M, Nombre: no3, tipo d: bool

En [175]: df[df['no3'] > 0,5]


Fuera[175]: no1 no2 Numero 3

2022­03­31 ­0.267572 ­0.459848 0.959027


2022­06­30 ­1.825116 ­0.336949 0.676227
2022­08­31 ­0.652803 ­0.916504 1.260779
2022­09­30 ­0.340685 0.616657 0.710605
2022­10­31 ­0.723832 ­0.206284 2.310688

En [176]: df[(df.no3 > 0.5) & (df.no2 > ­0.25)]


Fuera[176]: no3 no1 no2
2022­09­30 ­0.340685 0.616657 0.710605
2022­10­31 ­0.723832 ­0.206284 2.310688

En [177]: df[df.index > '2022­5­15']


Fuera[177]: no1 no2 Numero 3

2022­05­31 ­1.928309 ­0.198527 ­0.029466


2022­06­30 ­1.825116 ­0.336949 0.676227
2022­07­31 ­0.553321 ­1.323696 0.341391
2022­08­31 ­0.652803 ­0.916504 1.260779
2022­09­30 ­0.340685 0.616657 0.710605
2022­10­31 ­0.723832 ­0.206284 2.310688

En [178]: df.query('no2 > 0.1')


Fuera[178]: no1 no2 Numero 3

2022­01­31 ­0.804458 0.320932 ­0.025483


2022­04­30 ­0.732239 0.207433 0.152912
2022­09­30 ­0.340685 0.616657 0.710605

En [179]: a = ­0,5

En [180]: df.query('no1 > @a')


Fuera[180]: no1 no2 Numero 3

2022­02­28 ­0.160134 0.020135 0.363992


2022­03­31 ­0.267572 ­0.459848 0.959027
2022­09­30 ­0.340685 0.616657 0.710605

¿Qué valores en la columna número 3 son mayores que 0,5?

Seleccione todas las filas cuya condición sea Verdadera.

338 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

Combina dos condiciones con el operador & (bit a bit y) ; | es el operador bit a bit o .

Selecciona todas las filas con valores de índice mayores (posteriores) que '2020­5­15' (aquí, según la
clasificación de objetos str ).

Utiliza el método .query() para seleccionar filas dadas las condiciones como objetos str .

Trazar con pandas pandas está

bien integrado con el paquete de trazado matplotlib , lo que lo hace conveniente para trazar datos almacenados
en objetos DataFrame . En general, una sola llamada a un método ya es suficiente (consulte la Figura A­6):

En [181]: df.plot(figsize=(10, 6));

Traza los datos como un diagrama de líneas (por columnas) y fija el tamaño de la figura.

Figura A­6. Trama lineal con pandas

pandas se encarga del formato adecuado de los valores del índice, en este caso las fechas. Esto solo funciona
correctamente para un DatetimeIndex . Si la información de fecha y hora está disponible solo como objetos str , el
constructor DatetimeIndex() se puede utilizar para transformar la información de fecha y hora fácilmente:

Python, NumPy, matplotlib, pandas | 339


Machine Translated by Google

En [182]: índice = ['2022­01­31', '2022­02­28', '2022­03­31', '2022­04­30', '2022­05­31', '2022


­06­30', '2022­07­31', '2022­08­31', '2022­09­30', '2022­10­31']

En [183]: pd.DatetimeIndex(df.index)
Fuera[183]: DatetimeIndex(['2022­01­31', '2022­02­28', '2022­03­31',
'2022­04­30',
'2022­05­31', '2022­06­30', '2022­07­31', '2022­08­31',
'2022­09­30', '2022­10­31'],
dtype='datetime64[ns]', freq='M')

Datos de índice de fecha y hora como un objeto de lista de objetos str .

Genera un objeto DatetimeIndex fuera del objeto de la lista .

Los histogramas también se generan de esta manera. En ambos casos, pandas se encarga del manejo de las
columnas individuales y genera automáticamente líneas individuales (con las respectivas entradas de leyenda,
consulte la Figura A­6) y genera las respectivas subgráficas con tres histogramas diferentes (como en la Figura
A­7). ):

En [184]: df.hist(figsize=(10, 6));

Genera un histograma para cada columna.

Figura A­7. Histogramas con pandas

340 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

Operaciones de entrada­salida

Otro punto fuerte de los pandas es la exportación e importación de datos hacia y desde
diversos formatos de almacenamiento de datos (ver también el Capítulo 3). Considere el caso de la separación por comas.
Archivos de valores almacenados (CSV):

En [185]: df.to_csv('datos.csv')

En [186]: con open('data.csv') como f:


para la línea en f.readlines():
imprimir(línea,
fin='') ,no1,no2,no3
2022­01­31,­0.8044583035248052,0.3209315470898572,
,­0.025482880472072204
2022­02­28,­0.16013447509799061,0.020134874302836725,0.363991673815235
2022­03­31,­0.26757177678888727,­0.4598482010579319,0.9590271758917923
2022­04­30,­0.7322393029842283,0.2074331059300848,0.15291156544935125
2022­05­31,­1.9283091368170622,­0.19852705542997268,
,­0.02946577492329111
2022­06­30,­1.8251162427820806,­0.33694904401573555,0.6762266000356951
2022­07­31,­0.5533209663746153,­1.3236963728130973,0.34139114682415433
2022­08­31,­0.6528026643843922,­0.9165042724715742,1.2607786860286034
2022­09­30,­0.34068465431802875,0.6166567928863607,0.7106048210003031
2022­10­31,­0.7238320652023266,­0.20628417055270565,2.310688189060956

En [187]: from_csv = pd.read_csv('data.csv', index_col=0,


parse_dates=True)

En [188]: from_csv.head() # Fuera[188]: no1


no2 Numero 3

2022­01­31 ­0.804458 0.320932 ­0.025483


2022­02­28 ­0.160134 0.020135 0.363992
2022­03­31 ­0.267572 ­0.459848 0.959027
2022­04­30 ­0.732239 0.207433 0.152912
2022­05­31 ­1.928309 ­0.198527 ­0.029466

Escribe los datos en el disco como un archivo CSV.

Abre ese archivo e imprime el contenido línea por línea.

Lee los datos almacenados en el archivo CSV en un nuevo objeto DataFrame .

Define la primera columna como la columna de índice .

La información de fecha y hora en la columna de índice se transformará en Marca de tiempo

objetos.

Imprime las primeras cinco filas del nuevo objeto DataFrame .

Python, NumPy, matplotlib, pandas | 341


Machine Translated by Google

Sin embargo, en general, almacenaría los objetos DataFrame en el disco de forma más eficiente.
formatos binarios como HDF5. pandas en este caso envuelve la funcionalidad de PyTables
paquete. La función constructora que se utilizará es HDFStore:

En [189]: h5 = pd.HDFStore('data.h5', 'w')

En [190]: h5['df'] = df

En [191]: h5
Fuera [191]: <clase 'pandas.io.pytables.HDFStore'>
Ruta del archivo: datos.h5

En [192]: desde_h5 = h5['df']

En [193]: h5.cerrar()

En [194]: from_h5.tail()
Fuera[194]: no1 no2 Numero 3

2022­06­30 ­1.825116 ­0.336949 0.676227


2022­07­31 ­0.553321 ­1.323696 0.341391
2022­08­31 ­0.652803 ­0.916504 1.260779
2022­09­30 ­0.340685 0.616657 0.710605
2022­10­31 ­0.723832 ­0.206284 2.310688

En [195]: !rm datos.csv datos.h5

Abre un objeto HDFStore .

Escribe el objeto DataFrame (los datos) en HDFStore.

Muestra la estructura/contenido del archivo de base de datos.

Lee los datos en un nuevo objeto DataFrame .

Cierra el objeto HDFStore .

Muestra las últimas cinco filas del nuevo objeto DataFrame .

Elimina los archivos CSV y HDF5.

342 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

Caso de estudio

Cuando se trata de datos financieros, hay funciones útiles de importación de datos disponibles
en el paquete pandas (ver también el Capítulo 3). El siguiente código lee datos históricos
diarios para el índice S&P 500 y el índice de volatilidad VIX de un archivo CSV almacenado
en un servidor remoto usando la función pd.read_csv() :

En [196]: raw = pd.read_csv('http://hilpisch.com/pyalgo_eikon_eod_data.csv', index_col=0, parse_dates=True).dropna()

En [197]: spx = pd.DataFrame(raw['.SPX'])

En [198]: spx.info() <clase


'pandas.core.frame.DataFrame'> DatetimeIndex: 2516
entradas, 2010­01­04 a 2019­12­31 Columnas de datos ( 1 columna en total): #
Columna No Tipo D de recuento nulo

­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 .SPX 2516 tipos de dtypes float64 no nulos: float64(1)


uso de memoria: 39,3 KB

En [199]: vix = pd.DataFrame(raw['.VIX'])

En [200]: vix.info() <class


'pandas.core.frame.DataFrame'> DatetimeIndex: 2516
entradas, 2010­01­04 a 2019­12­31 Columnas de datos ( 1 columna en total):

# Columna Tipo D de recuento no nulo


­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 .VIX 2516 tipos de dtypes float64 no nulos: float64(1)


uso de memoria: 39,3 KB

Importa el paquete pandas .

Lee datos históricos del índice bursátil S&P 500 desde un archivo CSV (datos de Refinitiv
Eikon Data API).

Muestra la metainformación del objeto DataFrame resultante .

Lee datos históricos del índice de volatilidad VIX.

Muestra la metainformación del objeto DataFrame resultante .

Combinemos las respectivas columnas Cerrar en un solo objeto DataFrame . Son posibles
varias formas de lograr este objetivo:

Python, NumPy, matplotlib, pandas | 343


Machine Translated by Google

En [201]: spxvix = pd.DataFrame(spx).join(vix)

En [202]: spxvix.info()
<clase 'pandas.core.frame.DataFrame'> DatetimeIndex: 2516
entradas, 2010­01­04 a 2019­12­31 Columnas de datos ( 2 columnas en total):

# Columna Tipo D de recuento no nulo


­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 .SPX 2516 float64 no nulo 1 .VIX 2516 float64 no nulo


tipos d: float64(2) uso de memoria: 139,0 KB

En [203]: spxvix = pd.merge(spx, vix, left_index=True, #


fusionar en el índice izquierdo right_index=True, # fusionar en el
índice derecho )

En [204]: spxvix.info()
<clase 'pandas.core.frame.DataFrame'> DatetimeIndex: 2516
entradas, 2010­01­04 a 2019­12­31 Columnas de datos ( 2 columnas en total):

# Columna Tipo D de recuento no nulo


­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 .SPX 2516 float64 no nulo 2516 tipos de


1.VIX float64 no nulos: float64(2) uso de

memoria: 139,0 KB

En [205]: spxvix = pd.DataFrame({'SPX': spx['.SPX'], 'VIX': vix['.VIX']}, index=spx.index)

En [206]: spxvix.info()
<clase 'pandas.core.frame.DataFrame'> DatetimeIndex: 2516
entradas, 2010­01­04 a 2019­12­31 Columnas de datos ( 2 columnas en total):

# Columna Tipo D de recuento no nulo


­­­ ­­­­­­ ­­­­­­­­­­­­­­ ­­­­­

0 SPX 2516 float64 no nulo 2516 tipos de


1 VIX float64 no nulos: float64(2) uso de

memoria: 139,0 KB

Utiliza el método de unión para combinar los subconjuntos de datos relevantes.

Utiliza la función de fusión para la combinación.

Utiliza el constructor DataFrame en combinación con un objeto dict como entrada.

344 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

Tener disponibles los datos combinados en un solo objeto hace que el análisis visual sea sencillo.
adelante (ver Figura A­8):

En [207]: spxvix.plot(figsize=(10, 6), subtramas=True);

Traza los dos subconjuntos de datos en subtramas separadas.

Figura A­8. Valores históricos de cierre al final del día para el S&P 500 y VIX

pandas también permite operaciones vectorizadas en objetos DataFrame completos . La siguiente


El código calcula los retornos del registro en las dos columnas del objeto spxvix simultáneamente.
damente de forma vectorizada. El método de desplazamiento desplaza el conjunto de datos según el número de
valores del índice según lo previsto (en este caso particular, por un día hábil):

En [208]: rets = np.log(spxvix / spxvix.shift(1))

En [209]: rets = rets.dropna()

En [210]: rets.head()
Fuera[210]: SPX VIX
Fecha
2010­01­05 0.003111 ­0.035038
2010­01­06 0.000545 ­0.009868
2010­01­07 0.003993 ­0.005233
2010­01­08 0.002878 ­0.050024
2010­01­11 0.001745 ­0.032514

Python, NumPy, matplotlib, pandas | 345


Machine Translated by Google

Calcula los rendimientos logarítmicos de las dos series temporales de forma totalmente vectorizada.

Elimina todas las filas que contienen valores NaN (“no es un número”).

Muestra las primeras cinco filas del nuevo objeto DataFrame .

Considere el gráfico de la Figura A­9 que muestra los rendimientos del registro VIX frente a los
rendimientos del registro SPX en un diagrama de dispersión con una regresión lineal. Ilustra una
fuerte correlación negativa entre los dos índices:

En [211]: rg = np.polyfit(rets['SPX'], rets['VIX'], 1)

En [212]: rets.plot(kind='scatter', x='SPX', y='VIX', style='.',


figsize=(10, 6))
plt.plot(rets['SPX'], np.polyval(rg, rets['SPX']), 'r­');

Implementa una regresión lineal en los dos conjuntos de datos de retorno logarítmico.

Crea un diagrama de dispersión de los retornos del registro.

Traza la línea de regresión lineal en el diagrama de dispersión existente.

Figura A­9. Gráfico de dispersión de los rendimientos logarítmicos del S&P 500 y VIX con línea de regresión lineal

346 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

Tener datos de series de tiempo financieras almacenados en un objeto Pandas DataFrame hace que la
La cálculo de estadísticas típicas es sencillo:

En [213]: ret = rets.media() * 252

En [214]: retirarse
Fuera[214]: SPX 0,104995
VIX ­0.037526
tipo de letra: float64

En [215]: vol = rets.std() * math.sqrt(252)

En [216]: vol.
Fuera[216]: SPX 0,147902
VIX 1.229086
tipo de letra: float64

Entrar [217]: (ret ­ 0,01) / vol


Salir[217]: SPX 0,642279
VIX ­0.038667
tipo de letra: float64

Calcula el rendimiento medio anualizado de los dos índices.

Calcula la desviación estándar anualizada.

Calcula el ratio de Sharpe para una tasa corta libre de riesgo del 1%.

La reducción máxima, que sólo calculamos para el índice S&P 500, es un poco
más involucrado. Para su cálculo utilizamos el método .cummax() , que registra el
Máximo histórico en ejecución de la serie temporal hasta una fecha determinada. Considere lo siguiente
Código siguiente que genera el gráfico en la Figura A­10:

En [218]: plt.figure(figsize=(10, 6))


spxvix['SPX'].plot(label='S&P 500')
spxvix['SPX'].cummax().plot(label='ejecutando máximo')
plt.legend(loc=0);

Crea una instancia de un nuevo objeto de figura .

Traza los valores de cierre históricos del índice S&P 500.

Calcula y traza el máximo actual a lo largo del tiempo.

Coloca una leyenda en el lienzo.

Python, NumPy, matplotlib, pandas | 347


Machine Translated by Google

Figura A­10. Precios históricos de cierre del índice S&P 500 y máximo actual

La reducción máxima absoluta es la mayor diferencia entre el máximo actual y el nivel del índice
actual. En nuestro caso particular, se trata de 580 puntos de índice.
La reducción máxima relativa a veces puede ser un poco más significativa. Aquí hay un valor de
alrededor del 20%:

En [219]: adrawdown = spxvix['SPX'].cummax() ­ spxvix['SPX']

En [220]: arawdown.max()
Fuera[220]: 579.6500000000001

En [221]: rdrawdown = ((spxvix['SPX'].cummax() ­ spxvix['SPX']) /


spxvix['SPX'].cummax())

En [222]: rdrawdown.max()
Fuera[222]: 0.1977821376780688

Deriva la reducción máxima absoluta.

Deriva la reducción máxima relativa.

El período de retiro más largo se calcula de la siguiente manera. El siguiente código selecciona todos
aquellos puntos de datos donde la reducción es cero (donde se alcanza un nuevo máximo). Luego
calcula la diferencia entre dos valores de índice consecutivos (fechas de negociación) para los cuales
la reducción es cero y toma el valor máximo. Dado el conjunto de datos que estamos analizando, el
período de retiro más largo es de 417 días:

348 | Apéndice: Python, NumPy, matplotlib, pandas


Machine Translated by Google

En [223]: temp = arawdown[adrawdown == 0]

En [224]: period_spx = (temp.index[1:].to_pydatetime() ­


temp.index[:­1].to_pydatetime())

En [225]: periodos_spx[50:60]
Fuera[225]: matriz([datetime.timedelta(días=67), datetime.timedelta(días=1),
datetime.timedelta(días=1), datetime.timedelta(días=1),
datetime.timedelta(días= 301), datetime.timedelta(días=3),
datetime.timedelta(días=1), datetime.timedelta(días=2),
datetime.timedelta(días=12), datetime.timedelta(días=2)], dtype
=objeto)

En [226]: máx(periodos_spx)
Fuera[226]: fechahora.timedelta(días=417)

Selecciona todas las posiciones del índice donde la reducción es 0.

Calcula los valores delta de tiempo entre todas esas posiciones de índice.

Muestra algunos de estos valores.

Selecciona el valor máximo para el resultado.

Conclusiones
Este apéndice proporciona una descripción general introductoria y concisa de temas seleccionados
relevantes para el uso de Python, NumPy, matplotlib y pandas en el contexto del comercio algorítmico. Por
supuesto, no puede sustituir una formación exhaustiva y una experiencia práctica, pero ayuda a aquellos
que quieren empezar rápidamente y que están dispuestos a profundizar en los detalles cuando sea
necesario.

Recursos adicionales
Una fuente valiosa y gratuita para los temas tratados en este apéndice son las notas de clase de Scipy
que están disponibles en múltiples formatos electrónicos. También está disponible gratuitamente el libro
en línea From Python to NumPy de Nicolas Rougier.

Libros citados en este apéndice:

Hilpisch, Yves. 2018. Python para finanzas. 2da ed. Sebastopol: O'Reilly.

McKinney, Wes. 2017. Python para análisis de datos. 2da ed. Sebastopol: O'Reilly.

Vander Plas, Jake. 2017. Manual de ciencia de datos de Python. Sebastopol: O'Reilly.

Python, NumPy, matplotlib, pandas | 349


Machine Translated by Google
Machine Translated by Google

Índice

monitoreo en tiempo real, 304 código

Una reducción máxima absoluta, 348 algoritmo en ejecución, 302 carga

AdaBoost, 281 operador de suma de código, 302 descripción

(+), 312 índice de evaluación de general visual paso a paso, 299­304

rendimiento ajustado, 11 ventajas de comercio


algorítmico (generalmente), 10 conceptos B
básicos, 7­11 estrategias,
backtesting
13­15 estrategias
basado en promedios móviles simples, 88­98
de búsqueda alfa, 13
Scripts de Python para pruebas retrospectivas de algoritmos
alfa, definido , 9 funciones anónimas,
de clasificación, 170
317 clave API, para
Scripts de Python para la clase de backtesting de regresión
conjuntos de datos, 52­54 precios de
lineal, 167 vectorizados
acciones intradía de Apple, Inc., 102
(ver backtesting vectorizado)
lectura de datos
Clase BacktestLongShort, 185, 197 gráficos de
de precios de acciones de diferentes barras, 329 diagramas
fuentes, 46­52 recuperación de datos históricos no estructurados
de barras (ver Plotly; diagrama de barras en tiempo real)
sobre, 63­65
clase base, para pruebas retrospectivas basadas en eventos, 177­182,
app_key, para Eikon Data API, 57 AQR Capital 191
Management, 5
Script Bash, 32 para
operaciones aritméticas, 311 programación de
configuración de Droplet, 41­43 para
matrices, 82
instalación de Python/Jupyter Lab, 40­41
Bitcoin, 5, 52

operaciones booleanas
(ver también vectorización)
NumPy, 322
operaciones comerciales automatizadas, 265­308
pandas, 337
administración de capital, 266­277
configuración de la cuenta de Oanda, 299
configuración de hardware,
300 infraestructura e implementación, 296 registro Funciones de devolución de

y monitoreo, 297­299 llamada C , 259 operaciones

Estrategia comercial basada en ML, 277­290 comerciales automatizadas de gestión de capital y, 266­277

algoritmo en línea, 291­294 Criterio de Kelly para acciones e índices, 272­277


Configuración del entorno Python, 301
Scripts de Python para, 305­308 Criterio de Kelly en configuración binomial, 266­271

351
Machine Translated by Google

Carter, Graydon, 249 almacenamiento de datos

CFD (contratos por diferencia) SQLite3 para, 75­77

riesgos de negociación algorítmica, 299 almacenar datos de manera eficiente,


definidos, 225 65­77 almacenar objetos DataFrame, 66­70
riesgos de pérdidas, 189 Paquete TsTables para 70­75
riesgos de negociación con margen, 249 estructuras de datos, 313­315

negociación con Oanda, 223­247 (ver Clase de marco de datos, 5­7, 49, 332­335
también Oanda) Creación de objetos
problemas de clasificación DataFrame, 85
aprendizaje automático para, 141­145 almacenamiento,
redes neuronales para, 154­155 66­70 datismo, ix

Scripts de Python para backtesting vectorizado, Constructor DatetimeIndex(), 339 algoritmo de


170 clasificación de árboles de decisión, 281 aprendizaje
Método .close_all(), 262 instancias profundo que
de nube, 36­43 agrega características al análisis, 162­165
script de instalación para Python y Jupyter problema de clasificación, 154­155 redes
Laboratorio, 40­41 neuronales profundas para predecir la dirección del mercado,
Archivo de configuración de Jupyter Notebook, 38 156­165 predicción del

Claves públicas/privadas RSA, 38 movimiento del mercado, 153­165 estrategias


script para orquestar la configuración de Droplet, 41­43 comerciales y, 15 redes neuronales
Cocteau, Jean, 175 profundas, 156­165 cobertura delta, 9
archivos de valores separados por comas (CSV) (ver CSV redes neuronales
densas (DNN), 154, 157 objetos de diccionario (dict),
archivos) 48, 314
conda como administrador de Instancias en la
paquetes, 19­27 como administrador de entorno virtual, nube de DigitalOcean,

27­30 operaciones básicas, configuración de 36 a 43 gotas, 300


21­27 instalación de Miniconda, 19­21 DNN (red neuronal densa), 154, 157
eliminación de conda, Contenedores Docker, 30­36

26 módulo configparser, 229 creación de una imagen Docker de Ubuntu y Python,


contenedores (ver contenedores Docker) 31­36 definidos,
contratos por diferencia (ver CFD ) estructuras 31
de control, 315 CPython, 1, 17 Imágenes de Docker versus, 31
Imágenes de Docker
método .create_market_buy_order(), 261 definidas, 31
método .create_order(), 237­239 estrategias Contenedores Docker versus 31

de impulso transversal, 98 archivos CSV Archivo Docker, 32­33

Domingos, Pedro, 265


operaciones de entrada­salida, 341­342 lectura Droplet, 36
de un archivo CSV con pandas, 49 lectura de un archivo costos, 296

CSV con Python, 47­49 método .cummax(), 347 pares de script para orquestar la configuración, 41­43
divisas, 299 (ver también tipo de cobertura dinámica, 9
cambio EUR/USD)
comercio algorítmico riesgos, 299
E

hipótesis del mercado eficiente, 124


API de datos de Eikon, 55­65

recuperar datos históricos estructurados, 58­62


Pila de ciencia de datos D , recuperar datos históricos no estructurados, 62­65
309 espionaje de datos, 112

352 | Índice
Machine Translated by Google

Discretización de Euler, 2 lectura de un archivo CSV con Python, 47­49


Estrategia de impulso de prueba almacenamiento de datos de manera
retrospectiva del tipo de cambio EUR/USD en barras de eficiente, 65­77
minutos, 231­234 método .flatten(), 329 comercio de divisas (ver
evaluación de la estrategia basada en regresión, 137 comercio
factorización del apalancamiento/margen, 234­235 de divisas; FXCM) rendimientos futuros,
desempeño bruto versus estrategia basada en predicción, 132­134
aprendizaje profundo, 159­161, 163­164 comercio de divisas, 249­264 ( ver también tipo de cambio EUR/USD)
pregunta histórica precios de cierre, 257­258 FXCM
datos históricos de velas para, 256 Comercio de divisas,
datos históricos de ticks para, 253 249­264 cómo comenzar,
implementación de estrategias comerciales en tiempo 251 realizar pedidos, 260­262
real, 239­244 recuperar información de la cuenta, 262
estrategias basadas en regresión logística, 150 recuperar datos de velas, 254­256
realizar pedidos, 260­262 recuperar datos, 251­256
predecir, 129­131 predecir recuperar datos históricos, 257­258
rendimientos futuros, 132­134 predecir recuperar datos de transmisión, 259
niveles de índice, 129­131 recuperar recuperar datos de ticks, 252­254
datos de transmisión, 259 recuperar trabajando con la API, 256­263 funciones
información de cuentas comerciales, 244­246 de devolución de llamada del
paquete contenedor fxcmpy,
Cálculo de SMA, 89­98 259 instalación,
pruebas retrospectivas vectorizadas de estrategia 251 recuperación de datos
comercial basada en de ticks, 252 fxTrade, 224
ML, 278­284 pruebas retrospectivas vectorizadas de
estrategia
G
basada en regresión, 135 pruebas
GDX (VanEck Vectors Gold Miners ETF) estrategias
retrospectivas
basadas en regresión logística, 151 estrategias de
basadas en eventos, 175­197
reversión a la media, 107­111 estrategias
ventajas, 176 clase base, 177­182,
basadas en regresión, 138
191 clases de construcción para, 175­197 clase de
generate_sample_data(), 65
backtesting solo largo, 182­185, 194 clase de backtesting largo­corto, 185­189, 197
método .get_account_summary(), 244
Scripts de Python para, 191­197
Excel método .get_candles(), 257 .
método get_data(), 178, 253
exportando datos financieros a, 50
método .get_date_price(), 178
leyendo datos financieros de, 51
método .get_instruments(), 230
método .get_last_price(), 260
método .get_raw_data(), 253 función
F presenta get_timeseries(), 61
la adición de diferentes tipos, 162­165 método .get_transactions(), 245 GLD
retrasos y, 146 (SPDR Gold Shares) estrategias
datos financieros, trabajando con, 45­78 basadas en regresión logística, 147­150 estrategias de
conjuntos de datos para ejemplos, 46 reversión a la media, 107­111 estrategias de
Eikon Data API, 55­65 reversión a
exportar a Excel/JSON, 50 fuentes la media del precio del oro, 107­109 estrategia
de datos abiertas, 52­55 leer de impulso y, 99­102, 105­105 Goldman Sachs, 1, 9 .
datos de diferentes fuentes, 46­52 leer datos de Excel/ método go_long(), 186
JSON, 51 leer desde un archivo CSV con
pandas, 49

Índice | 353
Machine Translated by Google

H predecir niveles de índice, 129­131

criterio de mitad de Kelly, 285 predicción de precios basada en datos de series de


tiempo,
Harari, Yuval Noah, ix
127­129 revisión
Biblioteca de almacenamiento binario HDF5, 70­75
de, 125 scikit­learn y, 139
Envoltorio HDFStore, 66­70
operaciones de alta frecuencia (HFQ), 10 backtesting vectorizado de estrategia basada en

histogramas, índice regresión, 135, 167


de aciertos 329 , definido, 281 comprensión de listas, 317
constructor de listas, 314
objetos de lista, 47, 314, 321
registro de operaciones comerciales automatizadas,
I estructura de control if­elif­else, 318 ajuste 297­299
en muestra, 137 niveles de regresión logística
índice, predicción, 129­131 script de generalizando el enfoque, 150­153 predicción
instalación de infraestructura (ver infraestructura de de la dirección del mercado, 146­150
Python), Python/Jupyter Lab, 40­41 Script Python para backtesting vectorizado,
Biblioteca Intel Math Kernel, 22 170
iteraciones, 315
clase de backtesting larga y corta, 182­185, 194 clase
de backtesting larga y corta, 185­189, 197 período de

j reducción más largo, 287

JSON
exportando datos financieros a, 50
leyendo datos financieros de, 51 Problema de clasificación
Script de de aprendizaje automático M , 141­145
instalación de Jupyter Lab para 40­41 regresión lineal con scikit­learn, 139 predicción del
Claves públicas/privadas RSA para, 38 movimiento del mercado, 139­153
herramientas incluidas, 36
Estrategia comercial basada en ML, 277­290
Cuaderno Jupyter, 38 Scripts de Python, 167
estrategias comerciales y, 15

k uso de regresión logística para predecir la dirección del


mercado, 146­150
Criterio de Kelly en
fondos de cobertura macro, comercio algorítmico y, 11 método
configuración binomial, 266­271
__main__, 177 comercio de
apalancamiento óptimo, 285­286
acciones e índices, 272­277 margen, 249 predicción

Keras, 153, 157, 165


de la dirección del mercado, 134 predicción
del movimiento del mercado aprendizaje
almacenes clave­valor, 314
profundo para, 153 ­165 redes
claves, público/privado, 38
neuronales profundas para, 156­165
regresión lineal para, 124­138 regresión
L lineal con scikit­learn, 139 regresión logística para
retrasos, 127, 146 predecir la dirección del mercado, 146­150 aprendizaje
funciones lambda, 317 automático para,
LaTeX, 2 139­153 predecir la dirección futura del
operaciones apalancadas, riesgos de, 235, 249, 286 mercado, 134 predecir rendimientos futuros,
regresión lineal que 132­134 predecir niveles de índice, 129­131
generaliza el enfoque, 137 predicción del predicción de precios basada en datos
movimiento del mercado, 124­138 predicción de la de series temporales, 127­129
dirección futura del mercado, 134 predicción de
rendimientos futuros, 132­134

354 | Índice
Machine Translated by Google

backtesting vectorizado de estrategia basada en regresión, clase ndarray, 83­85


135 órdenes de objetos ndarray, 3, 322­324
mercado, colocación, 237­239 módulo creación, 325
matemático, 311 funciones regresión lineal y, 125 regular,
matemáticas, 311 matplotlib, 327­331, 319 estructuras
339­340 reducción máxima, 287, 348 anidadas, 313 PNL
(procesamiento de lenguaje natural), 62 np.arange(),
McKinney, Wes, 5 325 números,
estrategias de reversión a la media, 3, 107­111 tipificación de datos de, 310
conceptos básicos, operaciones numéricas, pandas, 335 NumPy,
107­111 generalización del enfoque, 110 3­5, 319­327 operaciones
Código Python con una clase para backtesting booleanas, 322 creación de
vectorizado, 118 ndarray, 325 métodos de
Miniconda, 19­21 mkl ndarray, 322­324 números
(Biblioteca Intel Math Kernel), 22 aleatorios, 326 objetos de
Estrategias basadas en ML, 277­290 ndarray regulares, 319 funciones
apalancamiento óptimo, 285­286 universales, 323 vectorización,
persistir el objeto modelo, 290 83­85 operaciones
Secuencia de comandos de vectorizadas , 321 subpaquete
Python para, 305 análisis de numpy.random, 326 Índice NYSE Arca Gold
riesgos, 287­290 backtesting vectorizado, 278­284 Miners, 107
Clasificador MLP, 154
Clase MLTrader, 292­294
oh
estrategias de impulso, 14 backtesting
en barras de minutos, 231­234 conceptos básicos,
Configuración de cuenta Oanda, 299
99­103 generalización
configuración de cuenta, 227
del enfoque, 104
Acceso a API, 229­230
Código Python con una clase para backtesting
estrategia de impulso de prueba retrospectiva en barras de
vectorizado, 118
minutos, 231­234
Script de Python para clases de streaming personalizadas,
247 Operaciones con CFD, 223­247
teniendo en cuenta el apalancamiento/margen con datos
Script Python para algoritmo en línea de impulso, 219
históricos, 234­235
backtesting
implementar estrategias comerciales en tiempo real,
vectorizado de, 98­105
239­244
Clase MomentumTrader, 239­244
buscar instrumentos disponibles para negociar, 230 colocar
Clase MomVectorBacktester, 104 monitoreo
órdenes de
de operaciones
mercado, 237­239
comerciales automatizadas, 297­299, 304
Script de Python para clases de streaming personalizadas,
Scripts de Python para monitoreo de estrategias, 308
247
Servidor de datos de ticks de
recuperar información de la cuenta, 244­246 recuperar
muestra de simulación de Monte Carlo,
datos históricos, 230­235 trabajar con datos de
basado en 218 datos de series
transmisión, 236
temporales, 78 motivos, para comercio, 8
API RESTful de Oanda v20, 229, 277­290, 278 algoritmo
Clase MRVectorBacktester, 110 perceptrón
fuera de línea definido,
multicapa, 154
208 transformación
Musashi, Miyamoto, 17 años
a algoritmo en línea, 292
Regresión OLS (mínimos cuadrados ordinarios), 330 operaciones
N comerciales
procesamiento del lenguaje natural (PNL), 62 automatizadas de algoritmos en línea, 291­294

Índice | 355
Machine Translated by Google

definido, 208 estructuras de datos, 313­315

Script Python para algoritmo en línea de impulso, 219 tipos de datos, 310­313
generación dificultades de implementación, 17
de señales en tiempo real, 208­210 transformación modismos, 317­319

del algoritmo fuera de línea a, 292 método .on_success(), NumPy y vectorización, 3­5 obstáculos
240, 291 fuentes de datos abiertas, 52­55 para la adopción en la industria financiera,
mínimos cuadrados ordinarios 1

(OLS ) regresión, 330 evaluación fuera de muestra, 137 orígenes, 1


sobreajuste, 112 pandas y clase DataFrame, 5­7 pseudocódigo
versus, 2 lectura de un
archivo CSV, 47­49
Infraestructura Python, 17­44 conda
como administrador de paquetes, 19­27
Administrador de paquetes P , conda as,
conda como administrador de entorno virtual,
19­27 pandas, 5­7, 332­342 27­30
Operaciones booleanas, 337
Contenedores Docker, 30­36 que
estudio de caso, 343­349
utilizan instancias en la nube, 36­43
selección de datos, 336­337
Scripts de Python
Clase DataFrame, 332­335
para operaciones comerciales automatizadas, 302, 305­308
exportar datos financieros a Excel/JSON, 50 operaciones
clase base de backtesting, 191
de entrada y salida, 341­342 operaciones
clase de transmisión personalizada que
numéricas, 335 trazar, 339­340
comercializa una estrategia de
leer datos financieros
impulso, 247 clase de backtesting de regresión
de Excel/JSON, 51 leer desde un archivo CSV, 49
lineal, 167 clase de backtesting solo largo,
almacenar DataFrame objetos, 66­70
194 clase de backtesting largo­corto, 197
vectorización, 85­88
datos en tiempo real manejo, 218­220
conjunto de datos de series de tiempo
protección con contraseña, para el laboratorio de
de muestra, 78 monitoreo de
Jupyter, 38 método .place_buy_order(),
estrategias, 308 carga para operaciones comerciales
179 método .place_sell_order(), 179
automatizadas,

302 backtesting vectorizado, 115­120


Conceptos básicos
de Plotly, 211 múltiples transmisiones en
tiempo real para, 212 múltiples subtramas q
para transmisiones, 214 transmisión Conjuntos
de datos como barras, 215 visualización de de datos premium de Quandl,
transmisión de datos, 211­216 trazado, 54 trabajando con fuentes de datos abiertas, 52­55
con pandas, 339­340
método .plot_data(), 178 funciones de conveniencia polyfit()/
R
polyval(), 330 predicción de precios, basada en datos
números aleatorios, 326
de series de tiempo, 127­129
hipótesis de paseo aleatorio, 130 rango
Método .print_balance(), 179
(objeto iterador), 315 función
Método .print_net_wealth(), 179
read_csv(), 49 datos en tiempo
Método .print_transactions(), 246
real, 201­220
pseudocódigo, Python versus, 2 patrones
Script Python para manejo, 218­220 generación
editor­suscriptor (PUB­SUB), 202 Ventajas de Python
de señales en tiempo real, 208­210 cliente de datos
(generalmente), 11
de ticks para, 206 servidor de
conceptos básicos,
datos de ticks para, 203­206, 218
1­16 estructuras
de control, 315

356 | Índice
Machine Translated by Google

conjuntos de datos de series de tiempo


visualización de datos de transmisión con Plotly,
211­216 pandas y vectorización, 88 predicción
monitoreo en tiempo real, 304 de precios basada en, 127­129 script Python
Refinitiv, 56 para generar un conjunto de muestras, 78 SQLite3 para
reducción máxima relativa, 348 rendimientos, almacenamiento de, 75­77 TsTables
predicción del futuro, 132­134 análisis de para almacenar, 70­75 estrategias
riesgo, para estrategia comercial basada en ML, 287­290 de impulso de series temporales, 99 (ver
también estrategias de impulso) .to_hdf
Claves públicas/privadas RSA, () método, paquete
método 38 .run_mean_reversion_strategy(), 183, contenedor 68 tpqoa, 229, 236 plataformas
187 comerciales, factores que influyen en la elección de,
Método .run_simulation(), 269 223

estrategias comerciales, 13­15


(ver también estrategias específicas)
S
implementadas en tiempo real con Oanda, 239­244
S&P 500, 8­11
estrategias basadas en regresión logística y 150
aprendizaje automático/aprendizaje profundo,
estrategias de impulso, 103
15 reversión de media,
posiciones largas pasivas, 274­277 objetos
3 impulso, 14
dispersos, 212 pila
promedios móviles simples, 14
científica, 4, 309 scikit­learn,
operaciones, motivos para, 8
139
costos de transacción, 184, 284
Clase ScikitBacktester, 150­152
Paquete TsTables, 70­75
Proyecto de paquete SciPy, 4
objetos tupla, 313
biblioteca seaborn, 327­331
promedios móviles simples (SMA), 5, 14
estrategias comerciales basadas en, 88­98 Ud.

visualización con ticks de precios, 212 Ubuntu, 31­36


Método .simulate_value(), 204 Singer, funciones universales, NumPy, 323
Paul, 223 sockets,
datos en tiempo real y, 201­220 objetos de lista
de clasificación, 314 SQLite3,
Paquete contenedor V v20, 229, 277­290, 278 valor
75­77 Certificado
en riesgo (VAR), 288­290 vectorización,
SSL, 38 almacenamiento
3, 107­111 espionaje y
(ver almacenamiento de datos)
sobreajuste de datos de
gráficos de barras de transmisión,
backtesting vectorizados, 111­113
215, 220 transmisión
Estrategia comercial basada en ML, 278­284
de datos Oanda y,
estrategias comerciales basadas en impulso, 98­105
236 visualización con Plotly, 211­216 objetos
deficiencias potenciales, 175
de cadena (str), 312­313 evento del
Código Python con una clase para vectorizado.
franco suizo, 226 fondos de
backtesting de estrategias comerciales de reversión
cobertura macro sistemáticos, 11
a la media, 118
Scripts de Python para, 115­120, 167
t estrategia basada en regresión, 135
TensorFlow, 153, 157 estrategias comerciales basadas en promedios móviles
Thomas, Rob, 45 simples, 88­98
Thorp, Edward, cliente de vectorización con NumPy, 83­85
datos de 266 ticks, vectorización con pandas, 85­88
servidor de datos de 206 ticks, 203­206, 218 operaciones vectorizadas, 321

Índice | 357
Machine Translated by Google

gestión del entorno virtual, 27­30 z


CeroMQ, 202
W
bucles while, 316

358 | Índice
Machine Translated by Google

Sobre el Autor
El Dr. Yves J. Hilpisch es fundador y director ejecutivo de The Python Quants, un grupo que se centra en
el uso de tecnologías de código abierto para la ciencia de datos financieros, la inteligencia artificial, el
comercio algorítmico y las finanzas computacionales. También es fundador y director ejecutivo de The AI
Machine, una empresa centrada en el comercio algorítmico impulsado por IA a través de una plataforma
patentada de ejecución de estrategias.

Además de este libro, es autor de los siguientes libros:

• Inteligencia artificial en finanzas (O'Reilly, 2020) • Python

para finanzas (2.a ed., O'Reilly, 2018) • Análisis de

derivados con Python (Wiley, 2015) • Derivados cotizados

de volatilidad y varianza (Wiley, 2017)

Yves es profesor adjunto de finanzas computacionales y da conferencias sobre comercio algorítmico en


el Programa CQF. También es el director de los primeros programas de formación en línea que conducen
a certificados universitarios en Python para comercio algorítmico y Python para finanzas computacionales.

Yves escribió la biblioteca de análisis financiero DX Analytics y organiza reuniones, conferencias y


campamentos de entrenamiento sobre Python para finanzas cuantitativas y comercio algorítmico en
Londres, Frankfurt, Berlín, París y Nueva York. Ha pronunciado discursos de apertura en conferencias de
tecnología en Estados Unidos, Europa y Asia.
Machine Translated by Google

Colofón
El animal en la portada de Python for Algorithmic Trading es una culebra barrada común (Natrix helvetica).
Esta serpiente no venenosa se encuentra en o cerca de agua dulce en Europa occidental.

La culebra barrada común, originalmente miembro de Natrix natrix antes de su reclasificación como especie
distinta, tiene un cuerpo gris verdoso con bandas distintivas a lo largo de sus flancos y puede crecer hasta
un metro de largo. Es un nadador prodigioso y se alimenta principalmente de anfibios como sapos y ranas.
Debido a que necesitan regular su temperatura corporal como todos los reptiles, la culebra barrada común
normalmente pasa sus inviernos bajo tierra, donde la temperatura es más estable.

El estado de conservación de esta serpiente es actualmente de “Preocupación menor” y actualmente está


protegida en Gran Bretaña bajo la Ley de Vida Silvestre y Campo. Muchos de los animales que aparecen
en las portadas de O'Reilly están en peligro de extinción; Todos ellos son importantes para el mundo.

La ilustración de la portada es de José Marzán, basada en un grabado en blanco y negro de la Enciclopedia


Inglesa de Historia Natural. Las fuentes de portada son Gilroy Semibold y Guardian Sans. La fuente del
texto es Adobe Minion Pro; la fuente del título es Adobe Myriad Condensed; y la fuente del código es
Ubuntu Mono de Dalton Maag.

Common questions

Con tecnología de IA

El aprendizaje automático se utiliza para predecir movimientos del mercado a través de algoritmos de regresión lineal y regresión logística. La regresión lineal se aplica para predecir rendimientos futuros basándose en datos de devolución históricos, mientras que la regresión logística se emplea como un algoritmo de clasificación para predecir la dirección del mercado. Scikit-learn es el paquete de Python principalmente utilizado para implementar estos algoritmos, facilitando tanto el entrenamiento de modelos como la evaluación de su rendimiento .

El libro presenta cuatro estrategias comerciales algorítmicas que se pueden clasificar como principalmente búsqueda de alfa: estrategias basadas en SMA (Simple Moving Average), de impulso, de reversión a la media y posiblemente otras configuraciones específicas. Estas estrategias suelen aplicarse en el contexto de pruebas retrospectivas utilizando Python y destacan por su simplicidad y reproducibilidad en un entorno algorítmico .

El libro cubre el backtesting principalmente a través de estrategias basadas en medias móviles simples (SMA), impulso y reversión a la media mediante un enfoque vectorizado que permite una exploración ágil y rápida de las estrategias con pocas líneas de código . Para implementar el backtesting en Python, se utilizan paquetes como NumPy y pandas para manejar los datos financieros y realizar operaciones vectorizadas, lo que facilita el análisis de datos y la prueba de estrategias . Esta implementación puede incluir la limpieza de datos, cálculo de retornos, eventos de cruce de medias móviles, y ejecución simulada con control de gestión de riesgos y apalancamiento . El uso de la programación orientada a objetos y la integración con plataformas de comercio como Oanda y FXCM también se recomienda para la conexión y manejo de datos en tiempo real .

Python es ventajoso para el comercio algorítmico debido a su ecosistema de potentes paquetes que facilitan el análisis de datos eficientes, como pandas para la gestión de datos y scikit-learn para aplicar aprendizaje automático . Además, Python permite el manejo de API modernas, lo cual es clave para plataformas de negociación en línea . La capacidad de Python para realizar pruebas retrospectivas de estrategias comerciales con un código más conciso y comprensible es otro beneficio significativo . Su amplia adopción en la industria financiera asegura una gran demanda de profesionales que dominan este lenguaje .

La regresión lineal se utiliza en la predicción de movimientos de mercado para extrapolar tendencias y prever la dirección futura de los precios basándose en datos pasados . Este método es parte de una estrategia más amplia basada en la idea de que los patrones históricos pueden predecir los movimientos futuros, asumiendo que el mercado no es totalmente eficiente . Sin embargo, presenta limitaciones significativas. Por un lado, la regresión lineal no siempre maneja adecuadamente la naturaleza secuencial y temporal de los datos financieros, ya que los modelos tradicionales no consideran la importancia del orden de los datos . Además, si bien puede predecir la dirección de los movimientos, no es eficaz para estimar la magnitud de los cambios . Por último, en muchos casos, simplemente predice que los precios de hoy son los mejores indicadores para los de mañana, lo que refleja la hipótesis de un paseo aleatorio en los precios .

La gestión del riesgo en el contexto de la automatización de operaciones comerciales algorítmicas se aborda a través de varios métodos y herramientas. Uno de los métodos comunes es el cálculo del Value at Risk (VaR), que estima la pérdida máxima esperada en un horizonte temporal particular y bajo un cierto nivel de confianza . Además, el backtesting riguroso de estrategias comerciales es fundamental para evaluar las características de rendimiento y riesgo antes de implementarlas en un entorno de producción . Por ejemplo, el backtesting vectorizado permite una representación concisa del código y un buen rendimiento, mientras que el backtesting basado en eventos proporciona un modelado más granular y realista de las características del mercado . También se menciona la importancia del criterio de Kelly para la asignación de capital, lo que ayuda a dimensionar adecuadamente las operaciones según el riesgo percibido . Estos enfoques se combinan con el uso de tecnologías como Python, que facilitan el manejo de datos financieros y API modernas para la interacción con plataformas de comercio algorítmico .

Para aquellos sin experiencia en Python pero interesados en el comercio algorítmico, se recomienda comenzar por consultar textos introductorios de Python que sean más básicos, como los de Hilpisch (2018), McKinney (2017) y VanderPlas (2016). Estos libros ofrecen una base sólida en Python para el análisis de datos y las finanzas. Es importante desarrollar habilidades en el manejo de paquetes como NumPy y pandas, que son esenciales para el tradeo algorítmico debido a sus capacidades de análisis de datos y manejo de series temporales . Además, aprender sobre automatización y backtesting es esencial. El comercio algorítmico requiere una infraestructura de Python y el uso de plataformas comerciales y sus APIs para realizar órdenes, lo cual se destaca en los materiales disponibles .

Para manejar la falta de datos completos en la implementación de estrategias basadas en machine learning, es recomendable utilizar técnicas de vectorización y programación de arrays para optimizar el uso de los datos disponibles. La vectorización permite procesar los datos de manera eficiente, reduciendo el tiempo y recursos necesarios para el análisis . Además, es crucial considerar la validación rigurosa de los modelos para evitar problemas como el sobreajuste y asegurar la robustez del modelo a pesar de la falta de datos. Se sugiere utilizar enfoques de validación cruzada y ajuste de parámetros para evaluar la efectividad de las estrategias, incluso cuando los datos son incompletos . Otro enfoque es emplear métodos de imputación para completar los datos faltantes, utilizando modelos predictivos para estimar los valores faltantes basándose en patrones del conjunto de datos disponible .

El módulo 'tpqoa' es crucial para la implementación de estrategias comerciales automatizadas en la plataforma Oanda. Sirve como un paquete contenedor que facilita el acceso y el manejo de la API de Oanda, permitiendo la ejecución de estrategias algorítmicas en tiempo real. Además, el módulo tpqoa se integra con el paquete v20 de Oanda, lo cual optimiza el proceso de conexión y operación con la plataforma . También es una herramienta esencial para desarrollar aplicaciones con Python que permiten interactuar con plataformas como Oanda y ofrecer capacidades tales como la recuperación de datos de cuenta y la interacción en tiempo real para el comercio con API .

La vectorización en las pruebas retrospectivas de estrategias comerciales se implementa utilizando paquetes como NumPy y pandas, que permiten la programación de matrices para generalizar operaciones en vectores y matrices . Esto facilita la exploración ágil e interactiva de estrategias, permitiendo probar una gran variedad de combinaciones de parámetros en un corto período de tiempo . La vectorización es adecuada para estrategias comerciales simples, como las basadas en promedios móviles simples, impulso y reversión a la media . A través de la vectorización, se pueden obtener resultados con solo unas pocas líneas de código Python, permitiendo también generar visualizaciones atractivas y reveladoras .

También podría gustarte