Machine Translated by Google Dado que anteriormente creamos una función auxiliar que calcula la puntuación de impulso, ahora podemos aplicar esta función a todo el conjunto de precios históricos, nuevamente en una sola fila. La siguiente línea es muy inteligente y representa una gran parte de cómo funciona Python. De la fila anterior, tenemos un objeto que contiene precios históricos de aproximadamente 500 acciones. La línea siguiente dará como resultado una tabla ordenada de puntuación de clasificación para todos ellos. # Crear tabla de clasificación de impulso ranking_table = hist.apply(momentum_score).sort_values(ascending=False) Eso significa que ahora tenemos los análisis que necesitamos para determinar nuestra cartera. Tenemos la tabla de clasificación de impulso, y esa es la parte central de la lógica. Con los modelos de cartera, generalmente es más fácil determinar primero en qué salir y luego en qué entrar. De esa manera, sabemos cuánto necesitamos comprar para reemplazar las posiciones cerradas. Por lo tanto, recorreremos todas las posiciones abiertas, si las hay, y veremos cuáles cerrar y cuáles conservar. """ Vender lógica Primero comprobamos si alguna posición existente debería venderse. * Vender si la acción ya no forma parte del índice. * Vender si la acción tiene un valor de impulso demasiado bajo. """ posiciones_mantenidas = lista(context.portfolio.positions.keys()) para seguridad en contexto.portfolio.positions: if (seguridad no en el universo_hoy): order_target_percent(seguridad, 0.0) posiciones_mantenidas.remove(seguridad) elif ranking_table[seguridad] < mínimo_momentum : order_target_percent(seguridad, 0.0) keep_positions.remove(seguridad) Ahora hemos vendido los puestos que ya no calificaban. Observe cómo primero reaccionamos con una lista de todas las acciones en la cartera y luego, cuando alguna acción cayó del índice o no logró mantener el impulso suficiente, la cerramos y la eliminamos de la lista de acciones mantenidas. Después de haber cerrado posiciones que ya no queremos, llega el momento de decidir cuáles Se deben agregar existencias. """ Lógica de selección de acciones Compruebe cuántas existencias mantenemos del mes pasado. Complete desde la parte superior de la lista de clasificación, hasta alcanzar el número total deseado de participaciones en cartera. """ Machine Translated by Google acciones_de_reemplazo = tamaño_cartera ­ len(posiciones_mantenidas) lista_compra = tabla_clasificación.loc[ ~ranking_table.index.isin(kept_positions)][:replacement_stocks] new_portfolio = pd.concat( (lista_de_compra, ranking_table.loc[ranking_table.index.isin(kept_positions)]) ) La lógica de selección de acciones anterior primero verifica cuántas acciones necesitamos encontrar. Esa sería la diferencia entre el tamaño objetivo de la cartera de 30 y la cantidad de acciones que mantenemos del mes anterior. Una de esas filas puede requerir una explicación adicional. Esta fila a continuación, dividida en dos filas para que se muestre mejor en este libro, utiliza algunos trucos inteligentes de Python. lista_compra = ranking_table.loc[ ~ranking_table.index.isin(kept_positions)][:replacement_stocks] La variable stock_reemplazo s , calculado justo antes, nos dice cuántas acciones necesitamos comprar para reemplazar las que se vendieron. La variable ranking_table e contiene una lista de todas las acciones del índice, ordenadas por puntuación de impulso. Lo que queremos hacer es seleccionar acciones de la parte superior de la lista de clasificación, pero sin seleccionar acciones que ya poseemos y queremos conservar para el próximo mes. La primera parte de esta fila filtra ranking_table e para eliminar las acciones que pretendemos conservar, de modo que no las seleccionemos nuevamente. Lo hacemos con el signo de tilde ~, que es el equivalente lógico de no. Eso significa que ranking_table.loc[~ranking_table.index.isin(kept_positions) ] nos dará las acciones en la tabla de clasificación ordenada, pero sin las que se deben conservar. A partir de ahí, simplemente lo cortamos como antes, usando la lógica de objeto[inicio:parada:paso] , comenzando desde el principio y deteniéndonos en la cantidad de acciones que queremos. Fácil, ahora que sabes cómo, ¿verdad? Ahora obtenemos las acciones mejor clasificadas en la lista de puntuación de impulso, excluyendo las que ya tenemos, hasta que hayamos llenado las 30 acciones objetivo. Luego combiné la lista de compra con las acciones que ya poseemos y listo, ahí está nuestra nueva cartera objetivo. Pero espera, aún no hemos negociado. No, y ni siquiera hemos calculado el tamaño de las operaciones. Bueno, esa es la única pieza que queda en este modelo. Machine Translated by Google Necesitamos calcular los pesos objetivo para cada acción y realizar el pedido. Recuerde que estamos utilizando la volatilidad inversa para escalar el tamaño de las posiciones. Cada mes recalculamos todos los pesos y reequilibramos todas las posiciones. Eso significa que también estamos ajustando el tamaño a las existencias existentes. El paso número uno sería crear una tabla de volatilidad para todas las acciones, utilizando la función auxiliar de volatilidad que vimos antes. Después de invertir estos números, podemos calcular qué tan grande debería ser cada posición, como una fracción de la volatilidad inversa a la suma de la volatilidad inversa de todas las acciones seleccionadas. Y ahora tenemos una tabla con pesos objetivo. """ Calcule la volatilidad inversa de las acciones y establezca ponderaciones de posiciones objetivo. """ vola_table = hist[new_portfolio.index].apply(volatilidad) inv_vola_table = 1 / vola_table sum_inv_vola = np.sum(inv_vola_table) vola_target_weights = inv_vola_table / sum_inv_vola por seguridad, clasifique en new_portfolio.iteritems(): peso = vola_target_weights[seguridad] si la seguridad está en posiciones_mantenidas: order_target_percent(seguridad, peso) más: si ranking_table[seguridad] > mínimo_momentum: order_target_percent(seguridad, peso) Habiendo creado una tabla de ponderaciones objetivo, ahora podemos repasar las acciones seleccionadas una por una y realizar las operaciones necesarias. Para las acciones que ya poseemos, simplemente reequilibramos el tamaño. Si la acción es una nueva entrada en la cartera, primero comprobamos si tiene suficiente impulso. Si es así, lo compramos. Si no, lo saltamos y dejamos el peso calculado en efectivo. Calculamos las ponderaciones objetivo basándonos en llegar a una asignación del 100% para las 30 acciones. Pero como se puede ver aquí, es posible que no terminemos comprando los 50, si algunos no cumplen el criterio de impulso. Eso es por diseño. El espacio en la cartera reservado para una acción que no logra reducir el impulso simplemente se deja en efectivo. Esto actúa como un mecanismo de reducción de riesgos durante los mercados bajistas. Si no hay suficientes acciones con buen rendimiento para comprar, este modelo comenzará a reducir la exposición y puede llegar hasta una exposición cero durante períodos prolongados de mercado bajista. Aquí está el código fuente completo de todo el modelo. %matplotlib en línea Machine Translated by Google importar zipline desde zipline.api importar order_target_percent, símbolo, \ set_commission, set_slippage, Schedule_function, \ date_rules, time_rules desde datetime importar fecha y hora importar pytz importar matplotlib.pyplot como plt importar pyfolio como pf importar pandas como pd importar numpy como np desde scipy importar estadísticas desde zipline.finance.commission importe PerDollar desde zipline.finance.slippage importe VolumeShare Slippage, Fixed Slippage """ Configuración del modelo """ cartera_inicial = 100000 ventana_impulso = 125 momento_mínimo = 40 tamaño_cartera = 30 ventana_vola = 20 """ Configuración de comisiones y deslizamientos """ enable_commission = Verdadero Commission_pct = 0.001 enable_slippage = Verdadero slippage_volume_limit = 0.025 slippage_impact = 0.05 """ Funciones auxiliares. """ def puntuación_impulso(ts): """ Entrada: Serie temporal de precios. Salida: Pendiente de regresión exponencial anualizada, """ multiplicada por el R2 # Haz una lista de números consecutivos x = np.arange(len(ts)) # Obtener registros log_ts = np.log(ts) # Calcular los valores de regresión pendiente, intersección, r_value, p_value, std_err = stats.linregress(x, log_ts) # Anualizar porcentaje pendiente_anualizada = (np.power(np.exp(pendiente), 252) ­ 1) * 100 #Ajustar por puntaje de condición física = pendiente_anualizada * (valor_r ** 2) puntaje de retorno def volatilidad (ts): devuelve ts.pct_change().rolling(vola_window).std().iloc[­1] def salida_progreso(contexto): """ Machine Translated by Google Genera algunos números de rendimiento durante la ejecución del backtest. Este código simplemente imprime el rendimiento del mes pasado para que tengamos algo que mirar mientras se ejecuta el backtest. """ # Obtener la fecha de hoy hoy = zipline.api.get_datetime().date() # Calcular la diferencia porcentual desde el mes pasado perf_pct = (context.portfolio.portfolio_value / context.last_month) ­ 1 # Rendimiento de impresión, formato como porcentaje con dos decimales. print("{} ­ Resultado del último mes: {:.2%}".format(hoy, perf_pct)) # Recuerde el valor de la cartera de hoy para el cálculo del próximo mes context.last_month = context.portfolio.portfolio_value """ Lógica de inicialización y negociación. """ def inicializar (contexto): # Establecer comisión y deslizamiento. si enable_commission: comm_model = Por dólar (coste = comisión_pct) más: comm_model = Por dólar (coste = 0,0) set_commission(modelo_comunicación) si enable_slippage: slippage_model=VolumeShare Slippage(volume_limit=slippage_volume_limit, price_impact=slippage_impact) else: slippage_model=Fixed Slippage(spread=0.0) set_slippage(slippage_model) # Se usa solo para salida de progreso. contexto.último_mes = cartera_inicial # Almacenar membresía del índice context.index_members = pd.read_csv('sp500.csv', index_col=0, parse_dates=[0]) #Programar reequilibrio mensual. función_programación( func=reequilibrio, fecha_rule=date_rules.month_start(), time_rule=time_rules.market_open() ) def reequilibrio (contexto, datos): Machine Translated by Google # Escribir algún resultado de progreso durante la prueba retrospectiva output_progress(context) # Ok, averigüemos qué acciones se pueden negociar hoy. # Primero, obtenga la fecha de hoy = zipline.api.get_datetime() # En segundo lugar, obtenga la composición del índice de todos los días anteriores a hoy. all_prior = contexto.index_members.loc[context.index_members.index <hoy] # Ahora tomemos la primera columna de la última entrada, es decir, la última. último_día = all_prior.iloc[­1,0] # Dividir la cadena de texto con tickers en una lista list_of_tickers = last_day.split(',') # Finalmente, obtenga los símbolos Zipline para los tickers todays_universe = [symbol(ticker) for ticker in list_of_tickers] # Ahí está tu universo diario. Pero, por supuesto, podríamos haberlo hecho de una vez. """ # Esta línea a continuación hace lo mismo, # usando la misma lógica para recuperar las acciones de hoy. todays_universe = [símbolo(ticker) para ticker en context.index_members.loc[context.index_members.index <hoy].iloc[­1,0].split(',') ] """ # Obtener datos históricos hist = data.history(todays_universe, "close", impulse_window, "1d") # Crear tabla de clasificación de impulso ranking_table = hist.apply(momentum_score).sort_values(ascending=False) """ Vender lógica Primero comprobamos si alguna posición existente debería venderse. * Vender si la acción ya no forma parte del índice. * Vender si la acción tiene un valor de impulso demasiado bajo. """ posiciones_mantenidas = lista(context.portfolio.positions.keys()) para seguridad en contexto.portfolio.positions: if (seguridad no en el universo_hoy): order_target_percent(seguridad, 0.0) Machine Translated by Google posiciones_mantenidas.remove(seguridad) elif ranking_table[seguridad] <momento_mínimo: order_target_percent(seguridad, 0.0) keep_positions.remove(seguridad) """ Lógica de selección de acciones Compruebe cuántas existencias mantenemos del mes pasado. Complete desde la parte superior de la lista de clasificación, hasta alcanzar el número total deseado de participaciones en cartera. """ acciones_de_reemplazo = tamaño_cartera ­ len(posiciones_mantenidas) lista_compra = tabla_clasificación.loc[ ~ranking_table.index.isin(kept_positions)][:replacement_stocks] new_portfolio = pd.concat( (lista_de_compra, ranking_table.loc[ranking_table.index.isin(kept_positions)]) ) """ Calcule la volatilidad inversa de las acciones y establezca ponderaciones de posiciones objetivo. """ vola_table = hist[new_portfolio.index].apply(volatilidad) inv_vola_table = 1 / vola_table sum_inv_vola = np.sum(inv_vola_table) vola_target_weights = inv_vola_table / sum_inv_vola por seguridad, clasifique en new_portfolio.iteritems(): peso = vola_target_weights[seguridad] si la seguridad está en posiciones_mantenidas: order_target_percent(seguridad, peso) más: si ranking_table[seguridad] > mínimo_momentum: order_target_percent(seguridad, peso) def analizar (contexto, rendimiento): rendimiento['max'] = rendimiento.portfolio_value.cummax() rendimiento['dd'] = (perf.portfolio_value / rendimiento['max']) ­ 1 maxdd = rendimiento['dd'].min() ann_ret = (np.power((perf.portfolio_value.iloc[­1] / perf.portfolio_value.iloc[0]),(252 / len(perf)))) ­ 1 print("Retorno anualizado: {:.2%} Reducción máxima: {:.2%}".format(ann_ret, maxdd)) devolver Machine Translated by Google inicio = fecha y hora (1997, 1, 1, 8, 15, 12, 0, pytz.UTC) fin = fecha y hora (2018, 12, 31, 8, 15, 12, 0, pytz.UTC) rendimiento = zipline.run_algorithm ( inicio=inicio, fin=fin, inicializar=inicializar, analizar=analizar, capital_base=intial_portfolio, data_frequency = 'diario', paquete='ac_equities_db') Actuación Aquí es donde pretendo decepcionarte, querido lector. Sí, has llegado hasta aquí, hasta aquí en el libro, y ahora te ocultaré parte de la información. No te daré el tipo habitual de datos de rendimiento para este modelo. No mostraré una tabla con rendimiento anualizado, reducción máxima, índice de Sharpe y lo habitual. Pero tengo buenas razones para ello. Y no tiene nada que ver con ser reservado, sino con querer ayudarte de verdad. Escúchame aquí. Si desea aprender de verdad, debe hacer el trabajo y calcular estos números usted mismo. Es necesario comprender adecuadamente lo que está pasando. Necesita aprender cada detalle sobre cómo funciona un modelo, cómo se desempeña, a qué es sensible o no, cómo se puede modificar y mucho más. Simplemente confiar en mi palabra e intercambiar las reglas que describí anteriormente sería una muy mala idea. No aprenderías nada. Pondré algunos gráficos y tablas con datos de rendimiento, y podrás deducir algunos a partir de ellos. Pero me abstendré de las estadísticas de rendimiento habituales, al menos por ahora, con la esperanza de que tengas la curiosidad de probarlo. Después de todo, en este libro le estoy dando todo lo que necesita para replicar toda esta investigación. Este es un libro práctico, destinado a enseñarle cómo hacer las cosas usted mismo. Obtienes el código fuente completo y explicaciones completas. Eso es más de lo que la mayoría de los libros comerciales pueden afirmar. Pero este no es un libro en el que se supone que uno simplemente debe confiar en las afirmaciones del autor y seguir sus reglas comerciales. Para este y otros modelos del libro, le daré una idea de cómo funciona. Les mostraré el perfil general del desempeño y discutiré las características de las devoluciones. Esto le dará una comprensión suficiente del rendimiento del modelo. Machine Translated by Google Evitar desde el principio a estos sospechosos habituales de las cifras de rendimiento también sirve para reducir los riesgos de otra trampa clásica. Si publicara estos números, algunos lectores inmediatamente comenzarían a comparar. En primer lugar, por supuesto, cuál de los modelos de este libro funciona mejor. Si existe tal cosa. A continuación, ¿cómo se comparan estos modelos con otros libros? Ninguna de esas comparaciones sería útil en este momento. No importa quién 'ganaría'. Semejante comparación simplemente sería perder el argumento. Comparar modelos en diferentes libros de diferentes autores, probablemente utilizando una metodología muy diferente, no tendría sentido. Comparar dentro del libro probablemente le lleve a estar más interesado en un modelo que en otro, por todos los errores razones. Las pruebas retrospectivas se pueden ajustar hacia arriba y hacia abajo para ajustarse a los números de retorno que está buscando. Y tenga en cuenta que podría fácilmente aumentar o disminuir números como el rendimiento anualizado, simplemente eligiendo en qué año comenzar y finalizar la prueba retrospectiva. El perfil general y las características de las devoluciones son mucho más interesantes y te ayudarán a comprender correctamente el modelo. Bien, habiendo dicho todo eso, obtendrás una comparación entre modelos, incluidos los números que probablemente estés buscando, más adelante en este libro. Preferiría no distraerte con eso en este momento. Y lo que es aún más importante, este libro ofrece un método excelente para evaluar el rendimiento del modelo comercial. Pero eso no está escrito por mí. Mi colega autor y amigo Robert Carver ha contribuido amablemente con un capítulo invitado sobre ese tema, el capítulo 22. Por ahora, sólo tenemos que averiguar si toda la molestia de este capítulo valió la pena o no. Si parece haber algún valor en este tipo de enfoque de negociación, o si simplemente deberíamos abandonarlo y seguir adelante. Resultados del modelo Equity Momentum Empezando por echar un primer vistazo a las cifras de rentabilidad mensual, como se muestra en el Cuadro 12.1, parece prometedor. Podemos ver que una abrumadora cantidad de años son positivos. Muchos muestran cifras muy sólidas. Hay años negativos de dos dígitos, lo que es de esperar de un modelo largo sólo de renta variable, pero no años de desastre. Machine Translated by Google También puede ver claramente que este modelo, si bien tuvo un desempeño bastante bueno en los últimos años, tuvo un desempeño bastante mejor en los primeros años de esta prueba retrospectiva. Este puede ser un tema de investigación interesante para aquellos que están en casa y no les importa hacer algunos deberes adicionales. Tabla 12.1 Rentabilidad mensual de Equity Momentum Año Ene Feb Mar Abr May Jun Jul Ago Sep Oct Nov Dic Año 1997 +7,9 ­1,4 ­3,0 +5,4 +5,9 +4,0 +10,7 ­0,5 +5,2 ­7,9 +0,2 +0,1 +28,0 1998 ­1,5 +4,1 +5,9 +1,2 ­2,1 +8,0 +1,4 ­13,5 +10,3 ­1,0 +1,3 +5,8 +19,1 1999 +2,8 ­4,1 +5,2 +1,3 ­1,0 +9,2 ­1,6 +0,1 ­1,8 +0,6 +12,9 +14,6 +42,9 2000 ­2,3 +26,2 ­0,4 ­3,3 ­6,6 +12,3 ­6,1 +4,4 +1,5 +1,4 ­4,6 +9,3 +30,9 2001 ­7,4 +2,9 ­2,0 +5,1 +3,2 ­0,7 ­0,5 ­2,9 ­11,0 ­1,1 +3,5 +0,8 ­10,9 2002 ­0,7 +1,1 +2,1 ­0,9 ­0,0 ­3,1 ­12,0 ­2,5 ­0,2 +0,4 +0,7 ­0,6 ­15,3 2003 ­6,3 ­0,7 +0,5 +13,8 +11,1 +1,8 +1,6 +4,7 ­1,1 +10,0 +4,7 ­1,9 +43,0 2004 +5,6 +3,7 ­0,6 ­2,9 +2,1 +3,4 ­3,5 ­1,6 +2,7 +3,1 +8,1 +0,3 +21,8 2005 ­3,0 +4,8 ­2,4 ­5,6 +3,5 +5,4 +3,2 ­0,6 +3,8 ­5,5 +3,9 +1,6 +8,5 2006 +8,8 ­1,7 +3,9 ­1,1 ­6,5 ­0,7 ­3,3 +1,2 +0,1 +1,6 +2,4 +0,9 +5,0 2007 +3,4 ­0,3 +2,3 +1,6 +4,4 ­1,0 ­2,4 +1,3 +5,3 +3,7 ­4,8 ­0,3 +13,6 2008 ­9,7 +1,0 ­0,8 +2,6 +1,2 ­0,8 ­10,2 ­3,6 ­8,3 ­6,6 ­0,3 +0,1 ­31,1 2009 ­0,0 ­0,4 +0,5 ­1,0 ­1,0 +0,2 +10,1 +5,1 +4,5 ­5,2 +5,1 +7,6 +27,4 2010 ­4,5 +6,7 +9,8 +2,2 ­6,8 ­5,1 +3,2 ­1,6 +4,6 +1,2 +0,5 +2,7 +12,1 2011 ­0,5 +4,0 +3,0 ­0,1 ­2,8 ­1,7 ­1,5 ­2,1 ­2,9 +1,2 +0,2 ­0,3 ­3,6 2012 +0,3 +3,0 +4,6 +0,7 ­10,0 +3,7 +0,1 ­0,4 +1,1 ­3,4 +1,8 +1,5 +2,3 2013 +6,4 +0,4 +7,8 ­0,2 +6,3 ­1,8 +5,6 ­1,8 +5,5 +6,2 +4,0 +1,8 +47,4 2014 +0,0 +6,6 ­0,8 +1,0 +1,6 +2,8 ­2,1 +5,2 ­2,4 +0,6 +3,4 +0,8 +17,7 2015 +0,4 +4,2 +0,3 ­3,7 +2,8 ­0,6 +0,5 ­3,8 ­0,0 +4,3 ­0,4 ­0,8 +2,8 2016 ­2,3 +0,2 +2,7 ­2,1 +0,6 +6,1 +2,7 ­3,9 +1,7 ­4,1 +7,1 ­0,2 +8,2 2017 +1,5 +3,0 ­2,4 +0,3 +4,2 ­2,7 +2,1 +1,3 +0,8 +4,3 +3,3 +1,1 +17,7 2018 +6,5 ­3,8 ­0,2 +0,9 +0,5 +0,3 +0,5 +5,1 ­0,2 ­8,1 +1,4 ­8,1 ­6.1 La curva de acciones del backtest, que se muestra en la Figura 12­4, ofrece una imagen amplia de cómo se comporta este modelo. Las dos líneas superiores muestran el valor de la cartera de prueba retrospectiva a lo largo del tiempo, así como el índice de rendimiento total S&P 500, a modo de comparación. El panel del gráfico central traza la caída y en la parte inferior encontrará una correlación continua de seis meses entre la estrategia y el índice. Machine Translated by Google No debes cometer el error de simplemente comprobar si la estrategia supera al índice. Esa no es la información más importante aquí. Hay otros detalles que te dirán mucho más sobre esta estrategia. Una cosa que hay que observar es cómo la curva de rendimiento se nivela en ocasiones. Esto es cuando la exposición se reduce, cuando no hay suficiente número de acciones disponibles que cumplan el criterio de impulso. En segundo lugar, observe el patrón de reducción. Como era de esperar, las reducciones corresponden bastante bien a períodos conocidos de dificultades del mercado. Eso no debería ser demasiado sorprendente. Al fin y al cabo, estamos ante una estrategia larga únicamente en renta variable. Y eso lleva al panel del gráfico inferior, que es vital comprender aquí. El panel inferior muestra la correlación continua con el índice. Y como ves, este suele ser muy alto. Esto es algo de lo que realmente no se puede escapar cuando se construyen estrategias de cartera de acciones a largo plazo. Eso significa que no es realista esperar que tengan un buen desempeño en tiempos de dificultades en el mercado, ni tampoco es realista esperar una desviación demasiado grande del mercado. Figura 12 4 Curva de rendimiento del modelo de impulso de las acciones Machine Translated by Google Otra forma de tener una idea del desempeño a largo plazo de una estrategia es hacer un mapa del período de tenencia, como en la Tabla 12.2. Esto muestra cuál habría sido su rendimiento anualizado si hubiera comenzado esta estrategia al comienzo de un año determinado, como se muestra en la columna más a la izquierda, y la hubiera mantenido durante una cierta cantidad de años. Los valores se redondean al porcentaje más cercano, principalmente para que quepan en las páginas de este libro. Si, por ejemplo, el modelo hubiera comenzado en 1997 y se hubiera comercializado durante 10 años, habría visto una ganancia anualizada compuesta del 16% anual. Nada mal. Pero, por otro lado, si hubiera comenzado en enero de 2001 y hubiera continuado durante 10 años, sólo habría visto una ganancia anualizada del 5%. Este tipo de tabla puede ayudarle a obtener una perspectiva diferente de los rendimientos que la que puede mostrar un gráfico simple. Más adelante en este libro mostraré cómo calcular dicha tabla. Cuadro 12.2 Rentabilidad del período de tenencia para el modelo de impulso de las acciones Años 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 1997 +28 +23 +30 +30 +21 +14 +17 +18 +17 +16 +15 +11 +12 +12 +11 +10 +12 +12 +12 +12 +12 +11 1998 +19 +30 +31 +19 +11 +16 +17 +16 +14 +14 +9 +11 +11 +10 +9 +11 +12 +11 +11 +11 +10 1999 +43 +37 +19 +9 +15 +16 +15 +14 +14 +8 +10 +10 +9 +8 +11 +11 +11 +10 +11 +10 2000 +31 +8 ­0 +9 +11 +11 +10 +11 +5 +7 +7 +6 +6 +9 +9 +9 +9 +9 +8 2001 ­11 ­13 +3 +7 +7 +7 +8 +2 +5 +5 +4 +4 +7 +8 +7 +8 +8 +7 2002 ­15 +10 +14 +12 +11 +11 +4 +7 +7 +6 +6 +9 +9 +9 +9 +9 +8 2003 +43 +32 +24 +19 +18 +8 +10 +10 +9 +8 +11 +12 +11 +11 +11 +10 2004 +22 +15 +12 +12 +2 +6 +6 +5 +5 +8 +9 +9 +9 +9 +8 2005 +9 +7 +9 ­3 +3 +4 +3 +3 +7 +8 +8 +8 +8 +7 2006 +5 +9 ­6 +1 +3 +2 +2 +7 +8 +8 +8 +8 +7 2007 +14 ­12 ­0 +3 +2 +2 +7 +8 +8 +8 +9 +7 2008 ­31 ­6 ­1 ­1 ­1 +6 +8 +7 +7 +8 +7 2009 +27 +20 +11 +9 +16 +16 +14 +13 +14 +12 2010 +12 +4 +3 +13 +14 +12 +11 +12 +10 2011 ­4 ­1 +13 +14 +12 +11 +12 +10 2012 +2 +23 +21 +16 +15 +15 +12 2013 +47 +32 +21 +18 +18 +13 2014 +18 +10 +9 +11 +8 2015 +3 +5 +9 +5 2016 +8 +13 +6 2017 +18 +5 2018­6 _ Machine Translated by Google Modelos de futuros Los futuros son contratos para comprar o vender un activo en un momento específico en el futuro. Pero sólo una parte cada vez menor de los participantes en el mercado de futuros utiliza estos contratos para tales cosas. La idea original con los futuros era brindar a los agricultores una mayor seguridad económica vendiendo su cosecha futura a los precios conocidos actuales. Como ocurre con la mayoría de las innovaciones financieras, rápidamente se convirtió en otra parte del gran casino que existe y se convirtió en un vehículo popular para los comerciantes. Hay algunas propiedades excelentes de los futuros que vamos a utilizar en este libro, y también hay algunos potenciales peligrosos. La forma peligrosa de ver los futuros es como un apalancamiento fácil. El hecho de que puedas asumir un riesgo enorme en los mercados de futuros no significa necesariamente que sea una buena idea hacerlo. entonces. En los mercados de futuros se pueden tomar posiciones mucho mayores que en los mercados de valores. Si tiene una cuenta de cien mil, fácilmente podría colocar una posición de un millón o más. Esa es una excelente manera de borrar su cuenta rápidamente, y esta característica de los mercados de futuros tiende a atraer al tipo de juego. Como en un casino, puedes duplicar tu dinero en un día o perderlo todo en un minuto. No es así como usaremos los futuros en este libro. Este autor diría que los mercados de futuros son el espacio más interesante para el comercio sistemático, debido principalmente al enorme potencial de diversificación. Por supuesto, la única debilidad de los mercados de futuros son los requisitos de capital bastante altos para jugar este juego. El tipo más interesante de modelos sistemáticos requiere una base de capital mayor que la que tendrán disponible la mayoría de los lectores de este libro. Pero antes de dejar de leer para redactar una carta solicitando un reembolso, sepa que casi nadie gana dinero interesante comercializando su propio capital. El dinero real está en la gestión de activos y en los honorarios que se pueden obtener haciendo un buen trabajo gestionando el dinero de otras personas. Conceptos básicos de futuros Machine Translated by Google Los futuros son los únicos derivados que pretendo cubrir en este libro. Son bastante sencillos, fáciles de entender y comercializar. Ciertamente no quiero decir que sea fácil ganar dinero con ellos, pero tienen muy poco en común con el altamente complejo mundo del comercio de opciones. La única característica realmente importante de los futuros es la estandarización. Esto es lo más importante que debes entender sobre los futuros. La verdadera razón por la que se hicieron tan populares entre los traders sistemáticos profesionales, como la ahora prolífica industria que sigue las tendencias, es la estandarización. Debido a que están estandarizados, puede operar en una amplia variedad de mercados de la misma manera. Puede negociar con algodón y cerdos magros de la misma manera que negocia con índices bursátiles y divisas. La misma mecánica, las mismas matemáticas, la misma terminología. Esto hace las cosas mucho más fáciles. Existen algunas diferencias menores entre los mercados de futuros, pero para la mayoría de los propósitos funcionan más o menos igual. Los mercados de futuros pueden abrirle un mundo completamente nuevo. Si está acostumbrado a ser un operador de acciones o de Forex, ahora de repente puede operar con todo de la misma manera. En realidad, hay muy pocas razones para elegir una sola clase de activos para negociar, cuando se pueden obtener tantos beneficios de diversificación al operar con todos ellos. Y los mercados de futuros son perfectos para esto. Un contrato de futuros es una obligación mutua de realizar una transacción sobre un activo específico en una fecha futura específica, a un precio específico. Esa es la definición del libro de texto y, si bien es importante comprender esta parte, funciona un poco diferente desde un punto de vista práctico. El propósito original de los futuros es la cobertura. El ejemplo más simple puede ser el de un agricultor que tendrá una cosecha de maíz lista para enviar en tres meses y le gustaría fijar el precio. Luego podrá vender contratos de futuros con entrega en tres meses y asegurarse de fijar los niveles de precios actuales. Es posible que una empresa de cereales para el desayuno necesite comprar ese maíz en tres meses y también le gustaría evitar el riesgo de precio en los próximos meses. Toman el lado opuesto de esa posición de futuros, fijando los niveles de precios actuales. El mismo ejemplo podría aplicarse a la mayoría de los mercados de futuros, ya sea que el subyacente sea petróleo, divisas o bonos, el argumento de la cobertura funciona en teoría. Por supuesto, en realidad una parte abrumadora de todas las operaciones de futuros es especulación, no cobertura. Compra el contrato de futuros de oro si cree que el oro está subiendo. Si Si piensas que el yen está a punto de bajar, te pones corto en ese contrato. Machine Translated by Google Como se mencionó, los contratos de futuros son una obligación mutua de recibir o realizar la entrega. Esa es la teoría, pero no exactamente la realidad. Algunos mercados de futuros tienen entrega física, mientras que otros se liquidan en efectivo. Por ejemplo, las materias primas y las divisas generalmente se liquidan físicamente. Eso significa que si mantiene una posición larga en futuros de ganado vivo hasta que entren en entrega, puede encontrarse con un camión de bovinos ligeramente confundidos afuera de su oficina. En teoria. Hoy en día, prácticamente ningún banco o corredor le permitirá mantener una posición física de futuros después de la fecha de entrega, a menos que sea un operador comercial con acuerdos específicos que permitan transacciones físicas. Si tiene una cuenta de corretaje normal, no hay riesgo de sufrir sorpresas inesperadas. Su corredor cerrará sus posiciones a la fuerza en el último momento, si usted no lo hace. No por motivos altruistas, por supuesto. Ellos no están más interesados que usted en lidiar con un acuerdo físico. Sin embargo, normalmente se permite la entrega de futuros de divisas, así que tenga mucho cuidado con ellos. Si tiene un contrato de futuros de divisas abierto al vencimiento, es posible que obtenga conversiones al contado mayores de lo esperado en sus cuentas. Eso puede haberle sucedido o no a un servidor hace mucho tiempo, pero negaré rotundamente tales rumores. Otro punto muy importante en la estandarización de los contratos de futuros es la contraparte. Cuando compras un contrato de futuros de oro, eso significa que alguien más está en corto en el mismo contrato. Pero no hay necesidad de preocuparse por quién es esa persona y si su contraparte rechazará el trato o no pagará a tiempo. Con los futuros, su contraparte es la cámara de compensación, no el individuo u organización del otro lado de la operación. No tienes riesgo de contraparte hacia quien esté del otro lado. Mecánica y terminología de futuros Machine Translated by Google Si está acostumbrado al mercado de valores, lo primero que notará en los futuros es su vida útil limitada. No se compran simplemente futuros de oro. Compra un contrato específico, como el contrato de futuros de oro de marzo de 2017, por ejemplo. Ese contrato ya no existirá en abril de 2017, por lo que no puedes simplemente comprar y mantener futuros como lo harías con las acciones. Siempre debes asegurarte de mantener una posición en el contrato correcto y seguir manteniéndola. La palabra "correcto" es peligrosa, por supuesto, pero la mayoría de las veces el contrato correcto sería el que se negocia más activamente en este momento. Para la mayoría de los mercados de futuros, sólo existe un contrato altamente líquido y negociado activamente en un momento dado. En algún momento antes del vencimiento, los comerciantes comenzarán a pasar a otro contrato y habrá un nuevo contrato activo. El proceso de mover posiciones de un mes a otro se conoce como balanceo. Otro aspecto clave que hay que entender acerca de los futuros es el procedimiento de valoración a precios de mercado. Cuando compras un contrato de futuros, en realidad no lo pagas por adelantado. Es necesario aportar un margen, una especie de garantía, pero hablaremos de eso más adelante. A diferencia de las acciones y los bonos, en realidad no paga el monto total, solo necesita tener disponible una pequeña parte para asegurarse de poder cubrir pérdidas potenciales. El procedimiento de valoración a precios de mercado liquida todas las posiciones al final del día. Cuando tenga posiciones de futuros abiertas, verá un flujo de caja diario. Si perdió dos mil dólares ese día, se deducirán automáticamente de su cuenta de efectivo al final del día. Si obtuvo una ganancia, se acreditará. Por lo tanto, el valor de una posición de futuros siempre es cero al final del día. Ha sido marcado para el mercado. Cada mercado de futuros representa una determinada cantidad del subyacente, no sólo una unidad. Por ejemplo, un contrato de oro equivale a 100 onzas, un contrato de petróleo crudo a 1.000 barriles y el franco suizo a 125.000 CHF. Esta propiedad es lo que se llama tamaño del contrato. Para la mayoría de los contratos, el tamaño del contrato también es el mismo que el valor en puntos de propiedad más importante. Hay algunas excepciones a esa regla, como los bonos, pero la mayoría de las veces son iguales. La razón por la que el valor en puntos de la propiedad es más importante para nosotros es que esto es lo que necesita multiplicar para calcular su ganancia o pérdida. El valor en puntos, a veces denominado valor en puntos grandes, le indica cuánto dinero gana o pierde si el contrato de futuros mueve una unidad monetaria completa. Siempre debe ser consciente del valor en puntos del mercado en el que opera. Machine Translated by Google Tomemos el oro como ejemplo. Compras un único contrato de oro de marzo de 2017 a 1.400 dólares el lunes. A la hora de cierre de ese día, el precio era de 1.412 dólares, exactamente 12 dólares más que el día anterior. Dado que cada movimiento completo de un dólar representa 100, el valor en puntos para el mercado del oro, ahora aparecerán mágicamente 1.200 dólares en su cuenta de efectivo por la noche. Al día siguiente, el precio de los futuros del oro baja a 1.408 dólares y se deducirán 400 dólares de su cuenta. El miércoles cierras la posición, vendes el mismo contrato, a $1,410, y se acreditan $200 dólares a tu cuenta. Y así es, en pocas palabras, cómo funcionan los futuros. Tabla 13.1 Propiedades de los futuros Propiedad Descripción El Corazón código base del contrato de futuros. Por ejemplo, GC para Comex Gold. Lamentablemente, esto no está estandarizado y diferentes proveedores de datos pueden utilizar diferentes tickers para el mismo contrato. Si utiliza varios proveedores de datos de mercado, puede que valga la pena crear su propia tabla de búsqueda para poder traducir fácilmente entre los diferentes esquemas de código. Mes El mes de entrega se expresa como una sola letra y aquí afortunadamente la nomenclatura es la misma para todos los proveedores. De enero a diciembre se designan, en orden, con las letras F, G, H, J, K, M, N, Q, U, V, X y Z. Año Un número para indicar en qué año vence el contrato. A menudo se trata de un solo dígito, y la década se considera la siguiente posible. Algunos proveedores de datos utilizan un valor de dos dígitos para mayor claridad. Código El código completo es la combinación de las tres propiedades anteriores. Por lo tanto, Comex Gold con mes de entrega en junio de 2019 generalmente se designaría como GCM9 o GCM19, dependiendo de si se utiliza el año de uno o dos dígitos. Meses de vencimiento La fecha exacta en la que expira el contrato, ya sea para la liquidación financiera o la entrega real. Para un comerciante, esta fecha sólo es relevante para los futuros financieros, no para las materias primas ni para cualquier cosa que realmente se pueda entregar. Para los contratos entregables es necesario salir mucho antes. Último día de negociación Esta es la fecha a la que debes prestar atención. Las reglas son diferentes para diferentes mercados y pueden usar terminología ligeramente diferente para esta fecha (día del primer aviso, etc.), pero todos los contratos de futuros tienen un último día de negociación predeterminado para los especuladores. Para los contratos entregables físicamente, corre el riesgo de verse obligado a aceptar o realizar la entrega si se mantiene más allá de este punto. En la práctica esto no es probable que suceda Machine Translated by Google Sin embargo, la mayoría de los corredores no le permitirán realizar la entrega y cerrarán su posición a la fuerza ese día a menos que usted lo haga primero. Sin embargo, no quieres que eso suceda, así que será mejor que te asegures de cerrar o cambiar tu posición a tiempo. Tamaño del contrato Esto le indica lo que representa un contrato en términos del mundo real. El petróleo crudo ligero Nymex, por ejemplo, representa 1.000 barriles, mientras que el futuro del franco suizo en el ICE representa 125.000 CHF. Multiplicador Para la mayoría de los contratos de futuros, el tamaño del contrato y el multiplicador son exactamente los mismos. Sin embargo, cuando se trata de futuros de activos cruzados, se encontrará con algunas excepciones a esta regla y eso requiere una forma estándar de calcular sus pérdidas y ganancias, riesgo, etc. Necesita una forma de saber exactamente a cuánto ascendería la ganancia o pérdida si el contrato de futuros se mueve un punto completo. Para los futuros de bonos, la respuesta suele ser el tamaño del contrato dividido por 100. Con los futuros del mercado monetario es necesario dividir por 100 y ajustar por la duración. Así, el futuro de eurodólar a 3 meses con un tamaño de contrato de un millón termina con un multiplicador de 2.500 (1.000.000 / 100 / 4). Asegúrese de tener una tabla de búsqueda adecuada de multiplicadores para todos los contratos que desee negociar. El multiplicador también se conoce como valor en puntos o valor en puntos grande. Divisa Para que el multiplicador tenga sentido, necesita saber en qué moneda se negocia el futuro y luego traducirlo a la moneda base de su cartera. Margen inicial El margen inicial lo determina el intercambio y le indica exactamente cuánto efectivo necesita aportar como garantía para cada contrato de un futuro determinado. Sin embargo, si la posición va en tu contra, necesitarás dejar más margen, por lo que será mejor no navegar demasiado cerca del viento. Su corredor cerrará su posición si no mantiene suficiente garantía en su cuenta. Margen de mantenimiento La cantidad de dinero necesaria en su cuenta para mantener un contrato. Si su cuenta cae por debajo de esta cantidad, deberá cerrar la posición o reponer fondos en su cuenta. Interes abierto La mayoría de los instrumentos financieros comparten los campos de datos históricos de apertura, máximo, mínimo, cierre y volumen, pero el interés abierto es exclusivo de los derivados. Esto le indica cuántos contratos abiertos tienen actualmente los participantes del mercado. Al ser los futuros un juego de suma cero, alguien siempre está corto y otro está largo, pero cada contrato se cuenta solo una vez. Sector (clase de activo) Si bien hay muchas formas de dividir los futuros en sectores, en este libro utilizo un esquema amplio que tiene mucho sentido para nuestras necesidades. Dividiré los mercados de futuros en divisas, Machine Translated by Google acciones, tasas, materias primas agrícolas y productos no agrícolas. Cada contrato de futuros individual tiene un código específico. A partir de este código, usted Puede ver el mercado, el mes de entrega y el año de entrega. El código estándar para un contrato de futuros son simplemente esas tres cosas, en ese orden. La marzo de 2017 El oro, por ejemplo, tendría el código GCH7, o GCH17 si el año es de dos dígitos. Se utiliza la convención. Normalmente el año se designa con un solo dígito, pero algunos datos de mercado Los proveedores y vendedores de software utilizan dos dígitos. En este libro usaremos dos dígitos, ya que ese es el formato requerido para el software de backtesting Zipline. Cuando trabajes con futuros por un tiempo, la sintaxis de los tickers cambiará. convertirse en una segunda naturaleza. Si aún no estás muy familiarizado con este instrumento tipo, debería tomarse un momento para mirar la Tabla 13.2. Cualquier persona que comercie o El modelaje en esta área debe conocer las cartas de entrega mensuales. Tabla 13.2 Códigos de entrega de futuros Mes Código Enero Febrero Marzo F Abril j Puede k Junio Julio GRAMO h METRO norte q Agosto Septiembre U Octubre V noviembre k z Diciembre Futuros y exposición a divisas Cuando se trata de exposición a divisas, los futuros difieren bastante de cepo. Por supuesto, este tema sólo es relevante si opera en mercados denominados en diferentes monedas. En el caso de las acciones, usted paga su compra por adelantado. Eso significa que EE.UU. Un inversor radicado que compra acciones por valor de un millón de euros en Francia tiene que cambiar sus dólares por euros. Entonces tendría una exposición cambiaria abierta de un millón, a menos que decida cubrirlo. Machine Translated by Google En el caso de las acciones internacionales, en realidad tendrías dos posiciones. Uno en acciones y otro en moneda. Al modelar y realizar pruebas retrospectivas de estrategias, este puede ser un factor importante. Por otro lado, en el caso de los futuros, el impacto cambiario funciona de manera un poco diferente. Recuerde que no pagamos por contratos de futuros de la misma manera que pagamos por acciones. Simplemente ponemos un margen y luego calculamos la ganancia o pérdida diaria nuestra cuenta. Eso significa que su exposición cambiaria para futuros internacionales se limita a las pérdidas y ganancias. Si compra un contrato de futuros británico, no tiene exposición cambiaria inicial. Si al final del primer día obtiene una ganancia de mil libras, esa cantidad se pagará a su cuenta en libras esterlinas. Ahora tiene una exposición monetaria abierta de mil, pero no sobre el importe nominal total del contrato. Si pierde la misma cantidad al día siguiente, ahora no tendrá exposición cambiaria. Por lo tanto, la exposición cambiaria es un problema mucho menor con los futuros, y uno que casi se puede aproximar. Futuros y apalancamiento El hecho de que sólo necesitemos aportar un margen y no pagar los futuros en su totalidad puede tener algunas implicaciones interesantes. Esto significa que, en teoría, podemos asumir posiciones muy grandes. Rara vez vale la pena preguntarse qué tan grandes serían las posiciones que podría asumir si quisiera maximizar su riesgo. De todos modos, puedes asumir puestos mucho más importantes de los que deberías asumir. Y los comerciantes minoristas a menudo hacen mal uso de esa posibilidad. Si su motivación para negociar futuros es que puede asumir un riesgo mayor que con las acciones, debe quedarse con las acciones. Usar futuros para obtener apalancamiento barato es una muy mala idea. Pero la posibilidad de asumir posiciones de tan enorme tamaño tiene una ventaja muy clara. Cuando se trata de mercados al contado, como las acciones, se está más o menos limitado a una exposición nocional del 100%. Seguro que puedes aprovechar las acciones, pero eso es caro y restrictivo. Pero con los futuros, puede comenzar por decidir qué nivel de riesgo está buscando y utilizarlo para guiar el tamaño de sus posiciones. No hay necesidad de preocuparse realmente por la exposición nocional de su cartera, siempre y cuando tenga un ojo firme en el lado del riesgo. Machine Translated by Google Esto significa, entre otras cosas, que podemos tomar posiciones significativas en mercados de muy lento movimiento. El mercado de intereses a corto plazo, por ejemplo, donde los movimientos diarios superiores al 0,1% son raros, no sería de mucho interés si estuviéramos limitados a una exposición nocional del 100%. Simplemente no podría tener un impacto notable en la cartera. Pero este es un claro beneficio de los futuros. Podemos utilizar el riesgo como guía, en lugar de exposición nocional. El apalancamiento puede acabar con usted, pero si se utiliza correcta y deliberadamente, permite le permitirá construir modelos comerciales mucho más interesantes. Modelado de futuros y backtesting Las estrategias de backtesting para acciones y futuros son dos cosas muy diferentes. Incluso si sus reglas comerciales conceptuales de alto nivel son similares, la lógica y el código del modelo no lo son necesariamente. La diferencia más importante proviene del hecho de que los contratos de futuros tienen una vida útil limitada. Esto tiene múltiples implicaciones. El más obvio es que tenemos que realizar un seguimiento de qué entrega de contrato exacta negociar y cuándo cambiar a otra. Pero esto es sólo una muestra de la superficie de esta cuestión. Técnicamente hablando, no tenemos series temporales a largo plazo en el mundo de los futuros. Cualquier contrato de futuros tiene una vida útil limitada y sólo tiene suficiente liquidez para ser importante durante unos pocos meses como máximo. Sin embargo, los modelos comerciales cuantitativos normalmente necesitan basarse en series temporales sustancialmente más largas para analizar. Tomemos como ejemplo algo tan simple como una media móvil a largo plazo. Simplemente no es posible calcularlo utilizando datos reales del mercado. Como ejemplo, mire la Figura 14­1, que muestra el precio y el interés abierto del contrato de futuros de petróleo crudo de febrero de 2003. Recuerde que el interés abierto le indica cuántos contratos de futuros están abiertos en este momento. Cuando compras un contrato, ese número aumenta en uno. Cuando lo vuelves a vender, el número baja en uno. Esta es una buena indicación de cuán líquido es un contrato. Este contrato comenzó a negociarse, o al menos era posible negociarlo a partir del otoño de 2000, pero como se puede ver en el gráfico, realmente no tuvo transacciones hasta finales de 2002. Aunque tenemos precios para el contrato durante años, la mayoría de esos datos son irrelevantes. Como no se negoció en ningún volumen significativo, no podemos confiar en dicha serie de precios ni sacar ninguna conclusión de ella. Los datos históricos del contrato sólo se vuelven interesantes cuando hay suficiente participación en el mercado para garantizar un precio adecuado y suficiente liquidez para la negociación real. Machine Translated by Google Figura 14 1 Contrato único ­ CLG03 Como se ve en la Figura 14­1, el contrato de petróleo crudo de febrero de 2003 sólo es realmente relevante por un período de tiempo muy breve. Así es como se ve normalmente. En el siguiente gráfico, Figura 14 2, debería tener una mejor visión de lo que realmente está sucediendo aquí. Esto muestra cinco entregas consecutivas de petróleo crudo, durante el mismo período de tiempo. Aquí puede ver cómo avanza la liquidez, de contrato en contrato, a lo largo del tiempo. En el panel inferior del gráfico se ve cómo el contrato de octubre de 2002 tiene la mayor liquidez, pero cómo lentamente cede terreno al contrato de noviembre. Después de un mes, el contrato de noviembre se desvanece y el de diciembre toma el relevo. Este círculo se seguirá repitiendo, con un patrón bastante predecible. Machine Translated by Google Figura 14 2 Liquidez pasando de un contrato a otro Es importante entender que, desde un punto de vista analítico, cada contrato sólo importa durante ese breve tiempo en el que es el rey de la colina, el que tiene mayor liquidez. Eso nos deja con una gran cantidad de series temporales cortas, de diferentes contratos, y sin un historial a largo plazo. Un error sorprendentemente común de los novatos es hacer una serie temporal a largo plazo simplemente colocando un contrato tras otro. Simplemente usando las mismas series de tiempo tal como están y cambiando entre contratos a medida que avanzamos. El problema con esto es que este método creará una representación errónea de lo que realmente sucedió en el mercado. Será simple y llanamente incorrecto. El precio del crudo de noviembre y del crudo de diciembre serán diferentes. Simplemente no es lo mismo y no se puede comparar sobre la misma base. Uno es para entrega en noviembre y el otro para entrega en diciembre. El precio justo de estos dos contratos siempre será diferente. Machine Translated by Google En la Figura 14­3 se puede ver cómo el mismo activo subyacente, en este caso todavía petróleo crudo, tiene un precio diferente el mismo día, dependiendo de la fecha de entrega. Este tipo de diferencia es normal y tiene razones del mundo real. Tiene que ver con el costo de transporte, ya que representa el costo de cobertura y, por lo tanto, el precio justo. El costo de financiamiento y el costo de almacenamiento son factores importantes aquí. Figura 14 3 Comparación de contratos Algunos proveedores de datos de mercado todavía proporcionan series temporales en las que los contratos se colocan uno tras otro, sin ningún ajuste. Incluso algunos de los proveedores de datos de mercado extremadamente caros, los que cobran alrededor de dos mil dólares al mes, hacen esto. El hecho de que alguien le venda datos a un alto costo no significa que los datos sean precisos o útiles. El efecto de utilizar una serie temporal de contratos a largo plazo simplemente colocados uno detrás del otro, sin ajuste, es la introducción de falsas brechas. A medida que esta llamada continuación cambia de un contrato a otro, parecerá como si el mercado saltara hacia arriba o hacia abajo. Pero en realidad no se produjo tal movimiento. Si hubiera durado mucho el primer contrato, lo hubiera vendido y pasado al siguiente contrato, no habría experimentado la ganancia o pérdida de una brecha que implicaría esta continuación defectuosa. Así que tenga mucho cuidado con sus datos cuando se trata de series temporales de futuros a largo plazo. Continuaciones Machine Translated by Google El método más utilizado para construir continuaciones de futuros a largo plazo es el ajuste hacia atrás. Lo que eso significa es que cada vez que cambias de contrato, ajustas toda la serie hacia atrás en el tiempo. Generalmente esto se hace preservando las proporciones. Esto es para hacer que los movimientos porcentuales de la continuación reflejen los movimientos porcentuales reales. El objetivo de este método es llegar a una serie de tiempo que refleje aproximadamente la experiencia que habría tenido un operador de la vida real si hubiera comprado y renovado la posición, mes tras mes y año tras año. Esta continuación se utiliza para el análisis de precios a largo plazo, pero claramente no se puede comercializar por sí sola. No es una serie real, simplemente está construida con fines analíticos. No se puede ejecutar una media móvil de 200 días en un solo contrato, pero se puede hacerlo en una continuación construida adecuadamente. Lo que hará la mayoría del software de backtesting es intercambiar esta continuación construida artificialmente y pretender que es un activo real. Esa ha sido la solución al problema de los futuros en el sector minorista durante décadas, y también es bastante común en el sector profesional. Si bien es una solución suficientemente buena para la mayoría de las tareas, tiene algunos inconvenientes. Un problema con este enfoque estándar es que cuanto más retroceda en el tiempo, más se alejará la continuación calculada del precio de mercado real. Si continúa agregando ajuste tras ajuste durante un largo período de tiempo, terminará con un precio bastante alejado del precio de mercado en ese momento. El método habitual para realizar dicha continuación significa que la serie completa se calcula el día en que ejecuta el backtest. Esto a menudo resulta en el tipo de discrepancias que vemos en la Figura 14­4. La línea sólida en la parte superior, la que sube hasta casi 700, es la serie ajustada hacia atrás, mientras que la inferior, que nunca se mueve mucho por encima de 100, es la serie no ajustada. No se puede decir que ninguna de estas dos series sea correcta, al igual que ninguna de ellas está realmente equivocada. Dependiendo de tu punto de vista. La serie ajustada pretende reflejar los movimientos porcentuales que habría experimentado un comerciante o inversor con una exposición larga a lo largo del tiempo. Pero claramente, el precio del petróleo nunca estuvo alrededor de los 700. Esta distorsión es el efecto del ajuste por la brecha de base durante muchos años. Machine Translated by Google Figura 14 4 Continuación ajustada versus no ajustada Si el propósito de la continuación es medir la tendencia, por ejemplo, eso no supondría un gran problema. Este tipo de finalidad es el motivo para realizar una continuación. Pero tal como funciona la mayoría del software de backtesting minorista, el programa asumirá que la continuación es un instrumento negociable real. Comprará y venderá la continuación misma, lo que claramente no es posible en la vida real. Eso conduce a algunos problemas potenciales. Los futuros del petróleo crudo tienen un multiplicador de 1000, lo que significa que un movimiento de precio de un dólar afectará su valoración de mercado en 1000 dólares. Esto es algo que normalmente se tiene en cuenta al decidir el tamaño de las posiciones y puede llevar a conclusiones potencialmente erróneas para carteras más pequeñas. Supongamos por un momento que su backtest ejecuta una cartera de un millón de dólares y que su objetivo es una variación diaria por posición del 0,2%. Es decir, desea que cada posición tenga un impacto diario aproximado en su cartera de $2000. El backtest intenta comprar una posición en petróleo, que como se mencionó tiene un valor en puntos, o multiplicador de 1.000, y que tiene una variación de precio promedio diaria de alrededor del uno por ciento. Al realizar su prueba retrospectiva en la serie ajustada hace algunos años, el precio ajustado del petróleo fue de $400 y tiende a variar alrededor de $4 por día. Como el contrato tiene un multiplicador de 1000, cada contrato fluctúa por un valor de $4000 por día. Pero queríamos tomar una posición que fluctúe $2.000 por día y, por lo tanto, la conclusión es que no podemos operar. Simplemente no es lo suficientemente granular. Machine Translated by Google Pero si miramos el contrato real en ese momento, se negoció alrededor de $40, en términos no ajustados. Con un cambio diario de aproximadamente el uno por ciento, eso significa que cada contrato fluctúa alrededor de $400 por día, y podemos comprar 5 contratos completos. Esto es algo con lo que hay que tener cuidado. Por lo general, es posible encontrar formas de compensar estos problemas, pero eso significa que es necesario tener en cuenta qué tipo de distorsiones son posibles y encontrar formas de abordarlas. Otro aspecto problemático del uso de este tipo de continuaciones es que no son estáticas. La forma tradicional de utilizar la continuación implica recalcular toda la serie, ajustando todos los puntos en el tiempo cuando pasamos a un nuevo contrato. Por lo general, esto se hace aplicando una proporción a toda la serie temporal existente. Deténgase y piense en las implicaciones de eso por un momento. Puede tener algunas implicaciones bastante preocupantes. Considere un modelo comercial a largo plazo que construyó hace un año. En aquel entonces, realizó una prueba retrospectiva completa del modelo y quedó satisfecho con los resultados. Ahora, un año después, realiza otra prueba retrospectiva de las mismas reglas en los mismos mercados. Y descubres que los resultados ya no son los mismos. Es muy posible que esto suceda, dependiendo nuevamente de qué análisis se aplique a la serie temporal y de si se ha tenido en cuenta o no este fenómeno. Un colega profesional del mercado que tuvo la amabilidad de dar comentarios y sugerencias iniciales para este libro señaló que estoy siendo demasiado duro con el uso de continuaciones, y probablemente tenga razón. Si realmente sabe lo que está haciendo y comprende completamente cómo se calculan sus continuaciones y las implicaciones de esos cálculos, puede asegurarse de que el código de su modelo no haga nada tonto. Pero sigo manteniendo que se puede lograr un mayor nivel de realismo utilizando contratos individuales o con continuaciones calculadas dinámicamente. Y en el futuro en este libro usaremos ambos. Comportamiento de continuación de tirolesa Al menos, la forma en que Zipline maneja los futuros es razón suficiente para elegir este backtester sobre otros. Me gustaría atribuirme algo de crédito por esto, ya que aconsejé a Quantopian hace unos años sobre cómo manejar mejor los futuros, pero sospecho que simplemente me estaban siguiendo la corriente y que ya tenían planes en esta dirección. Machine Translated by Google Su motor de backtesting Zipline tiene una buena solución al antiguo problema de la continuación. Las continuaciones de Zipline se generan sobre la marcha, cada día del backtest. Eso significa que la continuación se verá como se vería si se calculara ese día. Cada día que se procese el backtest, las continuaciones se generarán recientemente a partir de ese día, de modo que los precios "actuales" en la continuación reflejen los precios actuales reales en el mercado. La forma habitual, o tal vez deberíamos llamarla la vieja escuela, de calcular una continuación, se realiza por adelantado, generalmente a partir del momento de la vida real en que se realiza el backtest. Zipline tiene una solución inteligente para esto, calculando una continuación cada vez que se solicita en el backtest, basándose únicamente en los datos disponibles hasta ese momento. Los resultados son más realistas y menos propensos a errores. Zipline también resuelve otro problema común relacionado con las continuaciones. Como se mencionó, la mayoría de las soluciones de backtesting disponibles para los comerciantes minoristas pretenderán negociar la continuación misma. Claramente, eso no es posible en el mundo real, ya que la continuación no es más un instrumento negociable real que una media móvil. Puede haber algunas implicaciones inesperadas al simular el comercio en la propia continuación, y esto puede tener un impacto real en el realismo y la precisión de su backtest si no tiene cuidado. Zipline resuelve esto negociando sólo contratos reales, tal como lo haría en la realidad. Claramente, esta es la mejor solución, pero también significa que debe proporcionar al motor de backtesting datos históricos para cada contrato individual. Podrían terminar siendo bastantes de ellos. Para los fines de este libro, utilicé una base de datos de alrededor de veinte mil contratos individuales. Esta forma de trabajar con futuros es muy superior a la que normalmente están disponibles para los comerciantes minoristas. No sólo es más realista, sino que abre nuevas posibilidades, como los diferenciales de calendario o el comercio basado en estructuras de plazos. Sí, simplemente voy a decir esas palabras y dejarte pensar en ello por un momento. La otra cara, por supuesto, es que se vuelve un poco más complicado. Necesitamos proporcionar sustancialmente más datos al backtester. No sólo los datos de series temporales históricas, sino también los metadatos. Para cada contrato, necesitamos saber cosas como la fecha de vencimiento, la última fecha de negociación y más. Al crear una prueba retrospectiva, debemos realizar un seguimiento de qué contrato negociar en un momento dado. Y, por supuesto, debemos realizar un seguimiento de cuándo es el momento de pasar al siguiente contrato. Puede parecer mucho más complicado, pero en realidad no es tan difícil de configurar. Machine Translated by Google Contratos, Continuaciones y Rolling En la sección de equidad de este libro, ya debería estar familiarizado con cómo acceder a los datos en un backtest de Zipline. Hay pocas cosas nuevas a este respecto cuando se trata de futuros, y tiene que ver con el hecho de que en el terreno de los futuros tenemos que lidiar tanto con continuaciones como con contratos individuales. Se puede crear fácilmente una continuación proporcionando un símbolo raíz y algunas configuraciones básicas. La siguiente línea muestra cómo crear una continuación para los grandes futuros del S&P 500, símbolo raíz SP. sp_continuation = continuo_futuro('SP', desplazamiento=0, roll='volumen', ajuste='mul') Con este objeto de continuación, podemos solicitar series de tiempo históricas, verificar qué contrato está activo actualmente e incluso podemos extraer la cadena completa de contratos negociados, como haremos en capítulos posteriores. Puede solicitar el historial de series temporales de la misma manera que lo hicimos anteriormente para las acciones. continuación_hist = datos.historial( sp_continuation, campos=['open','high','low','close'], frecuencia='1d', bar_count=100, ) Esta serie de tiempo es la que normalmente se usaría para análisis. Aquí recuperamos la serie ajustada en el tiempo, utilizando solo los datos disponibles en el momento en que el backtest realizó la solicitud. Pero para poder comerciar, necesitamos conseguir un contrato individual real. La mayoría de los modelos de futuros, aunque ciertamente no todos, negocian el contrato más activo. Si ese es el caso, podemos simplemente preguntarle a la continuación qué contrato subyacente está utilizando en este momento. contrato = datos.actual(cont, 'contrato') Una vez que tenemos un contrato, realizar pedidos funciona de la misma manera que vimos anteriormente para las acciones. Podemos ordenar una cantidad determinada de contratos, apuntar a un monto nocional fijo o apuntar a un cierto porcentaje de exposición. order(contract, 100) # Compra 100 contratos order_value(contract, 1000000) # Compra un valor nominal de un millón de dólares, o lo más cercano. order_target(contract, 10000) # Ajusta la posición actual, si la hay, a 10,000 contratos order_target_value(contract, 1000000) # Ajusta para apuntar a un valor nominal de un millón de dólares. order_target_percent(contract, 0.2) # Ajusta la posición actual para apuntar al 20% de exposición Machine Translated by Google Como se mencionó anteriormente, el problema de negociar contratos individuales es que tienen una vida útil corta. No se trata sólo de decidir cuál elegir cuando entremos en el puesto. También tenemos que realizar un seguimiento de cuándo el contrato que tenemos ya no está activo. En ese momento, debemos pasar al siguiente contrato. Si la intención es quedarse con el contrato más activo, la forma más sencilla es realizar una verificación diaria, comparando los contratos actualmente mantenidos con los que las continuaciones utilizan en este momento. def roll_futures(contexto, datos): open_orders = zipline.api.get_open_orders() para contrato_retenido en contexto.cartera.posiciones: # No descarte posiciones que están configuradas para cambiar según la lógica central si se mantiene_contrato en órdenes_abiertas: continuar # Hacer una continuación continuación = continuo_futuro (hold_contract.root_symbol, offset=0, roll='volume', ajuste='mul') # Obtener el contrato actual de la continuación continuación_contrato = data.current(continuación, 'contrato') if continuation_contract !=held_contract: # Comprobar cuántos contratos tenemos pos_size = context.portfolio.positions[held_contract].amount # Cerrar la posición actual order_target_percent(held_contract, 0.0) # Abrir nueva posición order_target(continuation_contract, pos_size) Como verá en los modelos de futuros, usaremos este código o variaciones del mismo para asegurarnos de seguir ejecutando los contratos cuando sea necesario y permanecer con el activo. Seguimiento de tendencias de futuros Machine Translated by Google Hace algunos años escribí un libro llamado Siguiendo la tendencia (Clenow, Siguiendo la tendencia, 2013). En ese libro presenté un modelo de seguimiento de tendencias bastante estándar, que utilicé para explicar qué es el seguimiento de tendencias y cómo funciona. Demostré que la mayor parte de los rendimientos de la industria de futuros gestionados se puede explicar mediante un modelo de seguimiento de tendencias muy simple. En retrospectiva, lo único que lamento es no haber simplificado aún más el modelo de demostración. Quería incluir varias características en el modelo para demostrar cómo funcionan y qué impacto pueden tener. Si hubiera mantenido las reglas del modelo aún más simples, podría haber evitado el malentendido ocasional de que este modelo era una especie de súper sistema secreto de seguimiento de tendencias . Las reglas que presenté no eran exactamente secretas, ya que se conocían décadas antes de mi libro. Lo que intenté hacer fue explicarlo de una manera, con suerte, nueva y darle a las personas ajenas a nuestra industria una idea de qué es lo que hacemos para ganarnos la vida. Hay demasiados mitos inexactos en torno al negocio. Más adelante en este libro corregiré ese error presentando un modelo de seguimiento de tendencias increíblemente simple. Uno que, con razón, debería sorprender a la gente. Suena tan tonto que la mayoría lo descartaría sin más. Pero todavía muestra rendimientos bastante buenos en una prueba retrospectiva. Pero supongo que las personas que lean mi primer libro querrán saber qué está pasando con el Modelo Central, como lo llamé en ese libro. Es justo preguntar si esas reglas han funcionado, o si fracasaron y se quemaron. La historia corta es que el enfoque funcionó bien. Sospecho que muchos de los lectores de este libro también leyeron el primero. Aún así, no quiero asumir que todos ustedes lo hicieron, así que me aseguraré de incluir suficiente información aquí para que no se vean obligados a comprar el otro libro. Por supuesto, no voy a repetirlo todo, pero intentaré repetir todo lo que sea necesario para seguir las lecciones de este libro. El modelo de Siguiendo la tendencia tuvo un recorrido bastante interesante. Resulta que mi libro se publicó justo cuando comenzaban unos años difíciles para seguir tendencias. El último año cubierto en el libro fue 2011, que fue un año generalmente pobre para las estrategias de seguimiento de tendencias. Por supuesto, 2012 y 2013 también fueron años bastante pobres para los modelos de tendencia. Cada vez que el seguimiento de tendencias tiene uno o dos años malos, hay expertos que salen de la nada para declarar que la estrategia está muerta. Realmente no importa cuantas veces esto suceda y cuantas veces se equivoquen. Machine Translated by Google La cuestión es que seguir una tendencia, como cualquier otra estrategia, tiene años buenos y años malos. Si encuentra una estrategia que no ha tenido una mala ejecución, probablemente haya algo que haya pasado por alto. La única persona que nunca tuvo pérdidas comerciales es Bernie M. Pero luego llegó el gran 2014, cuando el seguimiento de tendencias funcionó realmente bien. Las pérdidas de los últimos tres años se borraron y se alcanzaron nuevos máximos históricos. Fue un año realmente bueno y las personas que anunciaron el fin del seguimiento de tendencias no aparecían por ninguna parte. Al seguir tendencias, siempre debes tener en cuenta que es una estrategia a largo plazo. Es posible que vea varios años seguidos de pérdidas, tal como sucederá en la mayoría de las estrategias. Esta es la razón por la que deberías operar en un tamaño razonable. Tarde o temprano, cualquier estrategia tendrá un par de años consecutivos de pérdidas. Cada vez que el seguimiento de tendencias tiene algunos años malos, muchos jugadores quedan eliminados. Quienes mantienen el rumbo siempre han sido recompensados a largo plazo. correr. Principios de seguimiento de tendencias La idea de seguir tendencias es realmente bastante simple. Las ideas comerciales más sólidas son bastante simples. Por supuesto, esto no significa que sea sencillo de implementar, sencillo para ganar dinero o incluso necesariamente sencillo de codificar. Simple, significa que la idea en sí es simple. El concepto se entiende y explica fácilmente. Esto es algo que vale la pena considerar al diseñar una estrategia. Si no puede explicar la idea detrás de su estrategia comercial de una manera simple, breve y comprensible, entonces existe un riesgo claro de que tenga reglas demasiado complicadas y ajustadas para hacer coincidir los datos, y que haya poco o ningún valor predictivo. El seguimiento de tendencias se basa en la observación empírica de que los precios a menudo se mueven en la misma dirección durante un período de tiempo sostenido. La estrategia apunta a capturar la mayor parte de dichos movimientos, sin intentar de ninguna manera cronometrar valles o picos. Simplemente espera a que los precios comiencen a moverse en una determinada dirección y luego salta en la misma dirección, intentando seguir la tendencia. Por lo general, se emplea un trailing stop loss, lo que significa que esperamos hasta que el mercado comience a moverse en nuestra contra antes de salir. Machine Translated by Google Las operaciones que siguen la tendencia fallan la mayor parte del tiempo. Pero eso esta bien. El seguimiento de tendencias tiende a tener una cantidad bastante grande de operaciones perdedoras, a menudo hasta el 70 por ciento. No es una estrategia para aquellos a quienes les gusta tener la razón todo el tiempo. Sin embargo, lo importante es la evolución del valor a largo plazo de la cartera en su conjunto. El seguimiento de tendencias tiende a tener una gran cantidad de pequeñas pérdidas y una pequeña cantidad de grandes ganancias. Mientras el valor neto esperado de esto resulte en un número positivo, todos estaremos bien. Hay innumerables métodos para elegir al diseñar e implementar un modelo de seguimiento de tendencias. Si reúne a los cincuenta mejores comerciantes que siguen tendencias para revelar sus secretos, lo más probable es que tengan cincuenta conjuntos de reglas muy , diferentes. Pero como demostré en Siguiendo la tendencia, la mayor parte de los rendimientos que siguen una tendencia se pueden explicar mediante reglas muy simples. En ese libro, mostré cómo un conjunto de reglas extremadamente simple tiene una correlación muy alta con los rendimientos de los principales fondos de cobertura que siguen la tendencia mundial. No hice eso para disminuir el impresionante trabajo de ninguna de estas personas. Lo hice para explicar el seguimiento de tendencias como un fenómeno y educar a los lectores sobre de dónde provienen los retornos y, potencialmente, cómo replicarlos. En la práctica, seguir tendencias puede ser una estrategia comercial muy frustrante. Siempre entrarás tarde en una ruptura y comprarás después de que el precio ya haya subido. Muy a menudo, el precio simplemente vuelve a caer después de que usted ingresa, lo que resulta en una pérdida rápida. Cuando el precio despega y tiene una tendencia para usted, la lógica del trailing stop garantiza que siempre perderá dinero desde la lectura máxima de su posición, hasta que se alcance el stop. Y, por supuesto, a veces, seguir tendencias simplemente no funciona muy bien durante un año, o incluso varios años. Bueno, todo eso es sólo una parte del costo de hacer negocios en el campo de seguimiento de tendencias. No es para todos. No es una estrategia sorprendente sin pérdidas y para hacerse rico rápidamente en la que todos deberían apostar todo su dinero. Pero es una estrategia que empíricamente ha funcionado notablemente bien durante décadas. Revisando el modelo de tendencias centrales El modelo central que presenté hace algunos años en Siguiendo la tendencia tiene como objetivo capturar las tendencias a mediano plazo en todos los sectores del mercado. Es un tipo de modelo intermedio muy deliberado. No hay nada particularmente destacable en este modelo. Lo destacable es el hecho de que no sólo es rentable, sino bastante rentable. El hecho de que el flujo de retorno esté muy correlacionado con la tendencia que siguen los fondos de cobertura del mundo es lo que debería llamar su atención aquí. Machine Translated by Google Lo que esto debería indicarle es que la mayor parte de los rendimientos que siguen la tendencia se pueden capturar con modelos bastante simplistas. Construir una simulación que funcione de manera similar a algunos de los mejores fondos de cobertura del mundo no es tan difícil. Por supuesto, la implementación es una historia diferente, pero por ahora nos centraremos en el modelado. Construir un modelo de tendencia básico como este es un buen ejercicio, en parte para aprender a escribir el código pero, más importante aún, para comprender la lógica central del seguimiento de tendencias. Propósito del modelo La idea del modelo de tendencia central es garantizar la participación en cualquier tendencia a medio y largo plazo en todos los principales sectores de futuros. Ha habido mucha mística en torno al seguimiento de tendencias y una gran cantidad de personas sin experiencia en la industria ganaban dinero vendiendo reglas del sistema a comerciantes minoristas crédulos. Quería presentarles al hombre detrás de la cortina. Pero no confunda la simplicidad de las reglas con una receta para enriquecerse rápidamente. Comprender el seguimiento de tendencias e implementarlas con éxito son dos cosas muy diferentes. Por un lado, el proverbial diablo tiende a estar en los detalles. Si bien se puede replicar la mayor parte de los rendimientos que siguen la tendencia con reglas simples, es posible que se requiera mayor complejidad para controlar la volatilidad, mantener un riesgo aceptable y desarrollar una estrategia que pueda comercializarse y financiarse con éxito. Luego, por supuesto, está el pequeño y molesto detalle de los requisitos de capital. En , Siguiendo la tendencia, sostuve que se necesita un tamaño de cuenta de al menos un millón de dólares para implementar un modelo diversificado de seguimiento de tendencias. Esa es probablemente la frase de ese libro que generó la mayor cantidad de correos electrónicos. Ni siquiera puedo empezar a contar cuántas personas me enviaron correos electrónicos preguntándome si se podía hacer con una cuenta mucho más pequeña. Seré generoso y diré que tal vez puedas hacerlo con la mitad del tamaño de esa cuenta. Tal vez. Incluso admitiré que, en teoría, podrías hacerlo con mucho menos dinero simplemente fingiendo que tienes el dinero. Sí, fingiendo. Cuando opera con modelos de futuros, en realidad no necesita aportar todo el efectivo, como lo haría en el comercio de acciones. Entonces, en teoría, podrías tener una cuenta de cien mil dólares y simplemente fingir que tienes un millón. Opere como si el tamaño de la cuenta base fuera un millón. Machine Translated by Google Pero eso sería una muy mala idea. Una reducción del diez por ciento le aniquilaría. Éste es el problema de negociar modelos de futuros diversificados. Que requieren un tamaño de cuenta bastante grande. Esto se debe al hecho de que la mayoría de los contratos de futuros son bastante grandes. Si bien se puede comprar una sola acción por unos pocos dólares, la exposición nocional en un contrato de futuros puede ser de decenas o cientos de miles. Incluso si actualmente no tiene un millón de dólares en su cuenta o no tiene planes realistas de adquirirlo en las próximas horas, aún puede beneficiarse al comprender el seguimiento de tendencias. Así que no dejes de leer todavía. Después de todo, ya pagaste por este libro. Universo de inversión En igualdad de condiciones, un gran universo de inversión suele favorecer los resultados finales. Puede resultar difícil comerciar y gestionar un universo grande, pero en la práctica es la mejor manera de hacerlo. Es por eso que debes tener mucho cuidado cuando comienzas a reducir el universo para adaptarlo a lo que puedes intercambiar. Usaremos unos cuarenta mercados en esta demostración. Este universo de inversión cubre productos agrícolas, metales, energías, acciones, divisas, renta fija y mercados monetarios. Usaré sólo mercados denominados en USD. No porque eso sea de alguna manera mejor, porque realmente no lo es. Es evidente que hay mercados más interesantes para operar en USD que en cualquier otra moneda, pero incluir los mercados internacionales supone un claro beneficio. Puede ser de gran ayuda con la diversificación y mejorar los resultados a largo plazo. No, la razón para utilizar aquí únicamente mercados de futuros basados en dólares es que el backtester utilizado para la demostración en este libro, Zipline, aún no admite futuros internacionales. No existe ningún mecanismo para tener en cuenta los efectos cambiarios. cuenta. Con los futuros esto es un problema mucho menor que con las acciones. Cuando se trata de futuros internacionales, no se tiene una exposición cambiaria abierta sobre el monto nocional. El valor nominal de su contrato en moneda extranjera no afecta su exposición cambiaria. Recuerde que en realidad no ha pagado por este contrato, como ocurre con las acciones. Sólo tienes un requisito de margen. Machine Translated by Google Frecuencia de negociación El modelo opera con datos diarios y verifica todos los días las señales comerciales. Puede que no haya nada con qué negociar cada día, pero lo comprobamos diariamente para asegurarnos. No se toman medidas intradía y no se colocan órdenes stop en el mercado. Toda la lógica opera sobre los precios de cierre y la negociación siempre ocurre un día después de la señal. Tenga en cuenta que cada día se realizan dos tareas independientes. Primero comprobamos si alguna posición debe cerrarse o abrirse. Después de eso, comprobamos si se debe rodar alguna posición. La lógica de la tirada y el motivo se explican en el capítulo 14. Asignación de posiciones Los tamaños de las posiciones tomadas para este modelo tienen como objetivo asumir la misma cantidad de riesgo por posición. Y no, el riesgo no tiene nada que ver con la cantidad de dinero que pierde si se activa su punto de stop loss. Desafortunadamente, esa es una explicación común del riesgo que se encuentra en muchos libros comerciales, pero en realidad no tiene nada que ver con el riesgo tal como se usa la palabra en la industria financiera. Riesgo también es una palabra un tanto controvertida dentro del negocio. En este contexto, estoy usando una definición ligeramente simplificada pero aún válida, basada en el impacto de pérdida o ganancia promedio diario esperado. Dado que realmente no tenemos motivos para creer que una posición es más importante o será más rentable que otra, este modelo pretende poner más o menos el mismo riesgo en cada posición. Lo primero y obvio que debemos señalar es que no podemos simplemente comprar la misma cantidad de cada uno. Esto ni siquiera funciona para instrumentos razonablemente similares como las acciones. Los futuros pueden mostrar diferencias extremas en su forma de moverse, y si mantienes una cantidad igual de cada uno, tendrías un riesgo muy sesgado. En algunos mercados, como el del petróleo, por ejemplo, ocurren con bastante frecuencia movimientos del 3% en un día. Hoy en día, apenas se ve un titular en las noticias sobre tal medida. Es demasiado común. Pero para que los futuros del Tesoro estadounidense a 10 años se movieran un 3% en un día, sería necesario un evento catastrófico. Si colocara 100.000 dólares en cada uno, claramente tendría un riesgo significativamente mayor en la posición de petróleo que en la posición de tesorería. Machine Translated by Google Un método común para resolver esto es observar la volatilidad reciente de cada mercado y escalar posiciones en función de eso. La volatilidad diaria es un indicador lejano del riesgo en este contexto. Lo que queremos lograr es tener un impacto diario aproximadamente igual en la cartera general de cada posición. Simplemente estamos tratando de dar a cada posición el mismo voto. La misma capacidad de impactar el resultado final de pérdidas y ganancias de la cartera general. La volatilidad se puede medir de muchas maneras. En Siguiendo la tendencia, , solía el rango verdadero promedio (ATR) es una medida de cuánto tiende a subir y bajar un mercado en un día promedio. Como siempre estoy a favor de ampliar el conjunto de herramientas, voy a utilizar aquí una medida diferente. Por favor, no me envíes un correo electrónico para preguntarme cuál es mejor, porque eso sería perder el punto. No soy la persona que te dará instrucciones exactas sobre cómo hacer las cosas de la mejor manera. Sobre todo porque ese tipo de consejos me parecen perjudiciales, en el mejor de los casos. Quiero ayudarlo a desarrollar un conjunto de habilidades y comprender varios tipos de métodos y herramientas. Una base sobre la que puede construir y aprender cómo funcionan los métodos, cómo incorporarlos a sus ideas comerciales y, con suerte, cómo construir sus propias herramientas. En lugar de usar ATR, esta vez usaré la desviación estándar. No la desviación estándar anualizada habitual en porcentaje, sino la desviación estándar de los cambios de precios. Es una medida no tan diferente del ATR. Como solo utiliza precios de cierre, tendremos un poco menos de datos con los que preocuparnos y una lógica un poco más simple. La desviación estándar aquí se calcula en función de los cambios diarios de dólares y centavos de un día a otro, durante un lapso de 40 días. Tenga en cuenta que estamos hablando de cambios de precio, no de precio. Utilizar una desviación estándar de los niveles de precios reales produciría un resultado bastante absurdo en este contexto. 40 días aquí, mide aproximadamente la volatilidad de los últimos dos meses. Siéntase libre de experimentar con otras configuraciones. Por suerte para nosotros, calcular la desviación estándar de los cambios de precios es realmente sencillo en Python. Puedes hacerlo en una sola línea, si quieres. El siguiente código es todo lo que necesita para calcular la desviación estándar de los cambios de precios durante los . e. últimos dos meses, asumiendo que tiene un DataFram e con una columna llamada close std_dev = df.close.diff()[­40:].std() Machine Translated by Google Dividamos esa fila en sus partes para asegurarnos de que comprende lo que está sucediendo aquí. La variable df aquí es un DataFrame con una columna llamada clos. La . primera parte de la declaración simplemente obtiene los cambios día a día, usando la mi función diff(). . Eso por sí solo nos da otra serie de tiempo, con la diferencia entre cada día. Después de eso, dividimos los últimos 40 días y solicitamos un cálculo de desviación estándar basado en eso, con la función incorporada .std() . Ahora intente replicar esta línea de código en su otro lenguaje de programación favorito y verá rápidamente cuánto más simple puede ser Python. Supongamos que nos gustaría que cada posición de la cartera tuviera un impacto diario promedio equivalente al 0,2% del valor total de la cartera. El número 0,2% en este ejemplo se denomina factor de riesgo. Este es un número completamente arbitrario que podemos alternar hacia arriba o hacia abajo para aumentar o disminuir el riesgo. Es nuestra principal herramienta de orientación sobre riesgos para el modelo de demostración de este capítulo. Aquí está la fórmula que aplicaríamos para calcular el tamaño de la posición basada en la volatilidad. Esta fórmula supone que todo está en la misma moneda. Si se trata de futuros internacionales, también necesitará convertir monedas extranjeras a su moneda base. Supongamos que tenemos una cartera con un saldo total de $5.000.000 y nos gustaría aplicar un factor de riesgo de 20 puntos básicos. La expresión puntos básicos en este contexto se refiere a fracciones de porcentaje, por lo que 20 puntos básicos equivalen a 0,2%. Ahora estamos a punto de comprar petróleo crudo, que en nuestro ejemplo se cotiza actualmente a 43,62 dólares y tiene una desviación estándar de 40 días de 1,63 dólares. Aquí está nuestra fórmula. Machine Translated by Google En este caso terminamos comprando sólo 6 contratos, para una cartera de 5 millones de dólares. Bueno, ya te advertí que estas podrían no ser las reglas exactas que implementarás. Quédate conmigo. Este modelo está destinado a enseñarle cómo funcionan las cosas. Lo que es clave en este momento es que comprenda la lógica detrás de este tipo de dimensionamiento de posiciones. La idea es que, en este caso, queremos que cada posición tenga un impacto promedio diario en la cartera de aproximadamente $10,000 y, a partir de ahí, calcularemos cuántos contratos necesitamos negociar. Hay formas alternativas de realizar la asignación de paridad de volatilidad, que es el nombre elegante de lo que realmente estamos haciendo aquí. Los otros métodos que pueda tener en mente probablemente funcionen bien. Pero el concepto es importante. El código para el cálculo del tamaño de la posición se puede encontrar a continuación. Los datos de entrada son el valor de mercado actual de la cartera, la desviación estándar y el valor en puntos. El factor de riesgo ya está definido en la sección de configuración del modelo, como verás en el código completo más adelante. La producción es la cantidad de contratos que debemos negociar. def tamaño_posición(valor_cartera, estándar, valor_punto): variación_objetivo = valor_cartera * factor_riesgo variación_contrato = estándar * valor_punto contratos = variación_objetivo / variación_contrato return int(np.nan_to_num(contratos))Filtro de tendencias Para este modelo se utiliza una buena media móvil dual, pero no para la tendencia. señales. En cambio, esto se utiliza como filtro para determinar la dirección de la tendencia. Estamos utilizando una combinación de una media móvil exponencial de 40 días y una más larga de 80 días. Si el promedio de 40 días es mayor que el promedio de 80 días, determinamos que la dirección de la tendencia es positiva. Si es al revés, con la EMA más rápida más baja que la más lenta, llamaremos a la tendencia negativa. Esto por sí solo no desencadena ninguna transacción. Simplemente lo usaremos como filtro. Sólo aceptaremos señales de entrada largas si la tendencia es positiva y, a la inversa, sólo aceptaremos señales de entrada cortas si la tendencia es negativa. En caso de que se pregunte por qué se seleccionaron los números 40 y 80, la respuesta es, tal vez no demasiado inesperada en este punto, que se eligieron de manera bastante frívola. Estas cifras son razonables, al igual que muchas otras. No dudes en probar otras combinaciones. Sólo tenga en cuenta el propósito general de determinar si el mercado se está moviendo hacia arriba o hacia abajo en este momento. Machine Translated by Google Quizás también se pregunte por qué se seleccionó una media móvil exponencial, en lugar de una media móvil simple normal. La motivación principal para seleccionar esto aquí es mostrarle cómo se calcula una media móvil exponencial usando Python. La Figura 15­1 demuestra la idea de este filtro de tendencias. Como la EMA más rápida es más alto, la tendencia se considera positiva; en caso contrario, negativa. Figura 15 1 Filtro de tendencias Reglas de entrada Las reglas comerciales para este modelo son simétricas. Eso significa que estamos tratando el lado largo y corto por igual, simplemente invirtiendo el signo de la lógica comercial. Quizás esa no sea la mejor manera de comerciar. Las tendencias alcistas y bajistas tienden a comportarse de manera muy diferente y pueden requerir conjuntos de parámetros diferentes. Pero por ahora, mantendremos el modelo simple y directo. Las reglas de entrada se basan en una lógica de ruptura simple. Cuando un mercado alcance un nuevo extremo de 50 días en la dirección de la tendencia, entraremos. Machine Translated by Google Para una entrada larga, primero requerimos que el filtro de tendencia sea positivo. El promedio exponencial de 40 días debe ser mayor que el equivalente de 80 días. Eso significa que tenemos luz verde para recibir señales de entrada largas cuando lleguen. Cuando el precio alcanza un nuevo máximo de 50 días, entramos en posición larga en el siguiente día. Para descubrir las entradas cortas, simplemente invierte la lógica. Si el filtro de tendencia es negativo y conseguimos un nuevo mínimo de 50 días, nos ponemos cortos. Reglas de salida Este modelo de tendencia simple utiliza un trailing stop. Eso significa que el stop se mueve junto con el precio, asegurándose de que no devolvamos demasiado después de una buena corrida. Lo que queremos hacer aquí es aspirar a una cierta cantidad de devolución de ganancias. Tenga en cuenta que aquí estamos ante un modelo de seguimiento de tendencias. Los modelos de tendencia siempre devuelven parte del beneficio antes del cierre. Estos modelos no apuntan a comprar desde abajo y vender desde arriba. De eso no se trata el seguimiento de tendencias. Otros tipos de estrategias pueden intentar eso, pero lo que estamos haciendo aquí es bastante diferente. Buscamos comprar caro y vender más alto. No comprar barato y vender caro. Para este modelo, no tenemos ningún objetivo de beneficios. Queremos estar en esta posición el mayor tiempo posible. Simplemente permanecemos en la posición mientras siga moviéndose en nuestra dirección. Si hace un movimiento significativo contra nosotros, lo cerramos. Naturalmente, la palabra clave aquí es significativo . Usar un porcentaje de pérdida en la posición no le ayudará. Recuerde que estamos tratando con muchos mercados diferentes en muchas clases de activos. El nivel de volatilidad base será bastante diferente, por lo que no podrás comparar porcentajes. Para el petróleo y el platino, un movimiento del 2% no es gran cosa. Pasa todo el tiempo. Pero para los futuros del Tesoro estadounidense a 2 años, se trata de un gran paso. En el caso de los mercados monetarios, esto es prácticamente inaudito. No, necesitamos algo mejor que porcentajes para medir los movimientos. Necesitamos normalizar los movimientos hacia la volatilidad, para que puedan compararse. Machine Translated by Google Anteriormente analizamos la desviación estándar en el contexto del dimensionamiento de posiciones. Esa medida también funciona bien para su uso en este contexto. Lo que nos dice la desviación estándar es aproximadamente cuánto tiende a moverse un mercado en un día, en promedio. La regla de salida aquí se basa en este valor de desviación estándar. A partir de la lectura máxima de la posición, que es en el momento de la mayor ganancia no realizada, dejamos que la posición pierda tres veces el valor de la desviación estándar antes de cerrarla. No se trata de un stop intradiario colocado en los mercados. Este es un disparador basado en los precios diarios, y la operación real se ejecuta al día siguiente. Esto sólo para mantener las reglas simples y fáciles de replicar. La mayoría de ustedes probablemente puedan obtener datos diarios con poco o ningún problema, mientras que las series históricas intradiarias pueden resultar costosas. En este caso, existe un beneficio adicional de utilizar un nivel de parada basado en la desviación estándar. Anteriormente en el ejemplo, utilizamos un factor de riesgo de 20 puntos básicos para establecer el tamaño de la posición. La fórmula, como se explicó anteriormente, busca que la variación promedio diaria de la posición impacte la cartera en alrededor de 20 puntos básicos. Entonces, si el mercado se mueve una desviación estándar, el impacto general de la cartera será del 0,2%. Y dado que ahora fijamos el tope en tres veces el valor de la desviación estándar, ahora sabemos que cada posición perderá aproximadamente el 0,6% del valor de la cartera antes de cerrarse. Es decir, perder 60 puntos básicos desde la lectura máxima. Esa es la cantidad de ganancias que devolveremos antes de que se cierre una posición, si hubiera alguna ganancia que devolver. Costos y deslizamiento Zipline tiene un modelado incorporado bastante complejo de costos y deslizamientos, mucho más que incluso la mayoría de las soluciones de nivel comercial. Si profundiza en los detalles, encontrará una variedad de algoritmos altamente configurables para tener en cuenta estas importantes variables. Le recomiendo mucho que pruebe variaciones, modele diferentes suposiciones y vea cómo afecta sus resultados. Para este modelo de demostración, he elegido un modelo de deslizamiento que tiene en cuenta el volumen negociado. En este caso, se asegurará de que nunca negociemos más del 20% del volumen diario y modelará el deslizamiento en base a eso. Por supuesto, este modelo de deslizamiento funciona aún mejor con datos intradía. Machine Translated by Google Para los costes, he fijado una comisión asumida por contrato de 0,85 dólares, así como una comisión de cambio de 1,5 dólares. Suponiendo que opere con un corredor en línea de bajo costo, estas suposiciones de costos deberían ser razonablemente realistas. He dejado configuraciones en el código, para que puedas activar o desactivar el deslizamiento y comisiones y experimentar el impacto que tienen estas cosas. Intereses sobre Liquidez Como ya debería saber, los futuros se negocian con margen. La mayoría de los gestores de futuros profesionales tienden a estar en el rango del 10 al 20 por ciento de margen sobre capital, y eso significa que alrededor del 80 al 90 por ciento de su capital no es realmente necesario en este momento. Los administradores de futuros profesionales utilizan ese exceso de efectivo para comprar títulos gubernamentales a corto plazo. Hay dos razones para esto. La más obvia es que puede generar intereses y, al mismo tiempo, ser la definición misma de libre de riesgos. En los viejos tiempos del comercio de futuros, los años 80, 90 e incluso parte de los 2000, este interés podía tener un impacto sustancial en el resultado de la estrategia. Y, por supuesto, la mejor parte aquí es que, como hedge, se le paga una tarifa de rendimiento sobre el rendimiento que genera para sus clientes. Incluso si parte de ese rendimiento procediera de intereses libres de riesgo. Sí, esos eran los días. Desafortunadamente, hoy en día se obtiene tan poco rendimiento de estos instrumentos que Realmente no tiene ningún impacto en el rendimiento. Pero todavía lo haces. Por la otra razón. La otra razón tiene que ver con la responsabilidad fiduciaria. Si su banco o corredor quiebra de la noche a la mañana, es probable que sus tenencias de efectivo desaparezcan. Claro, cuentas segregadas y demás, pero en realidad lo más probable es que su efectivo se esté arruinando. Sin embargo, es muy probable que sus valores le sean devueltos, tarde o temprano. Si se trata de una situación de fraude, todas las apuestas están canceladas, pero si se trata de un caso normal de codicia, irresponsabilidad y cierta corrupción corporativa común y corriente, es probable que en algún momento le devuelvan sus valores. Aquí mantendré las simulaciones simples y ignoraré esta parte. Al implementar modelos de futuros en la realidad, lo que realmente conviene es examinar más de cerca la gestión del efectivo. Tanto por motivos fiduciarios, como por motivos económicos. Si se tuvieran en cuenta adecuadamente el impacto histórico de la gestión del efectivo, los resultados de la simulación serían un poco mayores. Algunos años, cuando los rendimientos libres de riesgo eran altos, marcó una diferencia sustancial. Machine Translated by Google Código fuente del modelo de tendencia La estructura general de una simulación de Zipline ya debería resultarle familiar, a menos, por supuesto, que se haya saltado la parte de este libro sobre equidad. Como verá en este código, la principal diferencia con los futuros se reduce a lidiar con el concepto dual de continuaciones versus contratos. Una continuación es una serie temporal calculada, basada en series temporales de contratos individuales. Contrato tras contrato se unen para formar una serie temporal a largo plazo, con el objetivo de acercarse lo más posible al impacto real en el precio de la tenencia a largo plazo del mercado de futuros. Como no tenemos series reales a largo plazo en el espacio de futuros, esto es lo mejor que podemos hacer para crear algo en lo que podamos ejecutar análisis de series temporales a más largo plazo. Pero no se puede negociar una continuación. Es sólo un cálculo. Para negociar, necesitamos determinar qué contrato es relevante para negociar en este momento. Por lo general, eso significa el contrato más líquido. Lo que hacemos aquí es construir una continuación que siempre usa el contrato más negociado como actual, y luego, cuando operamos, le pedimos a la continuación que nos diga qué contrato exacto está usando en este momento. Ése es el que cambiaremos. Como los contratos de futuros tienen una vida útil limitada, esto también significa que debemos comprobar todos los días si es necesario renovar alguna posición. La lógica comercial no es demasiado complicada. Comenzamos calculando el filtro de tendencia y la medida de volatilidad. Luego repasamos cada mercado, uno por uno. Si ya tenemos una posición abierta en un mercado, comprobamos si se cumple alguna de las dos posibles condiciones de stop. Ya sea si el precio se movió en nuestra contra tres veces la desviación estándar o si la dirección de la tendencia cambió. Si no se mantiene ninguna posición, comprobamos si hay un nuevo extremo de 50 días en la dirección de la tendencia y, de ser así, operamos para abrir. Como antes, les mostraré las partes clave del código poco a poco, con explicaciones y, al final de esta sección, obtendrá la fuente completa de una vez. En la parte superior, tenemos varias declaraciones de importación, como es habitual. Gran parte de esto le resultará familiar si lee el capítulo sobre equidad y estudia ese código. Esta vez, sin embargo, se importan algunas funciones específicas de futuros. %matplotlib en línea importar tirolesa desde zipline.api importar futuro_symbol, \ set_commission, set_slippage, función_programación, reglas_fecha, \ Machine Translated by Google time_rules,continue_future,order_target desde fecha y hora importar fecha y hora importar pytz importar matplotlib.pyplot como plt importar pyfolio como pf importar pandas como pd importar numpy como np desde zipline.finance.commission importar PerTrade, PerContract desde zipline.finance.slippage importar VolumeShare Slippage, \ Fixpage , VolatilidadVolumenCompartir En el modelo de impulso de las acciones, estábamos generando una gran cantidad de texto mientras se ejecutaba el modelo, principalmente para tener algo que mirar durante los minutos que lleva ejecutar la prueba retrospectiva. Esto no afecta nada más que aumentar el aburrimiento para aquellos de nosotros con poca capacidad de atención. En este modelo, pensé en mostrarles otra forma de hacer lo mismo. Siguiendo el tema de este libro sobre el aumento incremental de la complejidad, esta vez le mostraré cómo actualizar la salida de texto, sin imprimir una nueva fila cada vez. El modelo de impulso de las acciones imprime una fila por mes, lo que da como resultado bastantes líneas de texto para desplazarse hacia abajo al final. El enfoque aquí actualizará el mismo texto, dinámicamente. Primero debemos configurarlo, importando las bibliotecas necesarias y creando una variable de salida que podamos actualizar durante la ejecución de la prueba retrospectiva. # Estas líneas son para los informes de texto dinámico de IPython.display import display import ipywidgets as widgets out = widgets.HTML() display(out) Ahora que esto está configurado, podemos cambiar dinámicamente el texto de salida configurando out.valu, e, lo cual haremos en una función separada mientras se ejecuta la prueba retrospectiva. La siguiente función se llamará todos los días. Hemos visto antes que el objeto Zipline llamado contexto t puede almacenar casi cualquier cosa que desee durante la ejecución de la prueba retrospectiva. En este caso, lo usaremos para realizar un seguimiento de cuántos meses hemos estado operando hasta ahora. Para esto, todo lo que necesitamos hacer es establecer context.months = 0 en . inicializ e. En la misma rutina de inicio, programaremos un informe de salida mensual. # Simplemente usaremos esto para el resultado del progreso # durante la prueba retrospectiva. No impacta nada. contexto.meses = 0 # Programar la salida del informe mensual función_programa( Machine Translated by Google func=report_result, date_rule=date_rules.month_start(), time_rule=time_rules.market_open() La rutina de informes en sí sólo tiene unas pocas líneas de código. Actualizamos el número de meses negociados, calculamos el rendimiento anualizado hasta el momento y actualizamos el texto. Eso es todo lo que se necesita para tener una actualización de texto dinámica mientras ejecutamos el backtest. def report_result(contexto, datos): contexto.meses += 1 hoy = zipline.api.get_datetime().date() # Calcular el rendimiento anualizado hasta el momento ann_ret = np.power(context.portfolio.portfolio_value / Starting_portfolio, 12 / contexto.meses) ­ 1 # Actualizar el texto out.value = """{} Hemos negociado <b>{}</b> meses y el rendimiento anualizado es <b>{:.2%}</b>""".format( hoy, contexto.meses, ann_ret) Hasta ahora, todo esto son solo declaraciones de importación y un poco de informes, pero estamos llegando a la lógica del modelo real. Al igual que con los modelos anteriores, tenemos la rutina de inicialización , donde configuramos la comisión, el deslizamiento y varias otras configuraciones. Con el modelo de acciones anterior, necesitábamos un universo de inversión dinámico que reflejara las acciones que, de manera realista, habrían estado en su radar en el pasado. Con los futuros, se vuelve más fácil. Aquí simplemente definimos los mercados en los que queremos operar. Es una suposición válida que habría elegido más o menos los mismos mercados hace diez años. En el modelo de acciones del capítulo 12, utilizamos una lista de objetos simbólicos como nuestro universo de inversión. En la lógica Zipline, el objeto símbolo se aplica sólo a las acciones. Para los futuros, tenemos dos conceptos relacionados. Tenemos futuro_símbolo y continuo_futuro . El primero se refiere a un contrato de futuros específico, mientras que el segundo se refiere a una continuación calculada del precio a largo plazo basada en muchos contratos individuales. Como puede ver en la inicialización a continuación, lo que hacemos aquí es hacer una lista de objetos continuos_futuros , uno para cada mercado. def inicializar (contexto): """ Configuración de costos """ si enable_commission: comm_model = PerContract(coste=0,85, exchange_fee=1,5) más: Machine Translated by Google comm_model = Por operación (coste = 0,0) set_commission(us_futures=comm_model) si enable_slippage: slippage_model=VolatilityVolumeShare(volume_limit=0.2) else: slippage_model=Fixed Slippage(spread=0.0) set_slippage(us_futures=modelo_deslizamiento) """ Mercados para comerciar """ monedas = [ 'ANUNCIO', 'BP', 'CD', 'CU', 'DX', 'JY', 'NORDESTE', 'SF', ] agrícola = [ 'LICENCIADO EN DERECHO', '_C', 'CONNECTICUT', 'FC', 'KC', 'LR', 'LS', '_O', '_S', 'SB', 'SM', '_W', ] no agrícola = [ 'CL', 'GC', 'HG', 'HO', 'LG', 'NG', 'PENSILVANIA', 'PL', 'RB', 'SI', ] acciones = [ 'ES', 'NK', Machine Translated by Google 'NQ', 'TW', 'VX', 'YM', ] tarifas = [ 'DE', 'FV', 'TU', 'TY', 'A NOSOTROS', ] # Lista de todos los mercados = monedas + agrícola + no agrícola + acciones + tipos # Haga una lista de todas las continuaciones context.universe = [continue_future(market, offset=0, roll='volume', ajuste='mul') para el mercado en los mercados ] # Los usaremos para realizar un seguimiento de la mejor lectura de posición. # Se utilizan para calcular los puntos de parada. context.highest_in_position = {mercado: 0 para mercado en mercados} context.lowest_in_position = {mercado: 0 para mercado en mercados} # Programe la función de programación de operaciones diaria (daily_trade, date_rules.every_day(), time_rules.market_close()) # Simplemente usaremos esto para el resultado del progreso # durante la prueba retrospectiva. No impacta nada. contexto.meses = 0 # Programar la salida del informe mensual función_programa( func=report_result, date_rule=date_rules.month_start(), time_rule=time_rules.market_open() ) Tenemos algunas funciones auxiliares aquí, tal como hemos visto en el capítulo sobre equidad. Existe la lógica para comprobar si es necesario renovar algún contrato, que se analizó en el capítulo anterior. La lógica del tamaño de la posición también se acaba de explicar en la sección anterior sobre asignación de posiciones. Ahora, lo que probablemente esté más ansioso por ver es el código para la lógica de negociación diaria. Comenzamos la rutina daily_trad e obteniendo aproximadamente un año de datos continuos para todos los mercados. # Obtener datos de continuación Machine Translated by Google hist = data.history( contexto.universo, campos=['cerrar','volumen'], frecuencia='1d', bar_count=250, ) A continuación calcularemos la tendencia y, como mencionamos anteriormente, llamaremos alcista si la EMA de 40 días es superior a la EMA de 80 días, tal como se define en nuestra configuración. # Calcular tendencia hist['tendencia'] = hist['cerrar'].ewm(span=fast_ma).mean() > hist['cerrar'].ewm(span=slow_ma).mean() Una vez que tengamos los datos y la información de tendencias, podemos iterar cada mercado, uno por uno. Cuando iteramos mercado por mercado, primero comprobamos si hay una posición activa o no. Si se mantiene una posición, comprobamos si es larga o corta y ejecutamos la lógica aplicable. Si no se mantiene una posición, comprobamos si hay un mercado alcista o bajista en este momento y ejecutamos el código correspondiente para ver si se debe abrir una posición o no. Aquí está, poco a poco. Primero iniciamos el ciclo y preparamos los datos que necesitamos, incluyendo un cálculo de desviación estándar. # Iterar mercados, buscar operaciones para continuar en context.universe: # Obtener el símbolo raíz de la continuación root = continuation.root_symbol # Cortar el historial solo para este mercado h = hist.xs(continuación, 2) # Obtener la desviación estándar std = h.close.diff()[­vola_window:].std() Luego procesamos posiciones largas. si es root en open_pos: # La posición está abierta # Obtener posición p = context.portfolio.positions[open_pos[root]] si p.amount > 0: # La posición es larga if context.highest_in_position[root] == 0: # Primer día ocupando el puesto contexto.posición_más alta[raíz] = p.cost_basis else: contexto.posición_más alta[raíz] = max( h['cerrar'].iloc[­1], contexto.posición_más alta[raíz] ) Machine Translated by Google # Calcular el punto de parada stop = context.highest_in_position[root] ­ (std * stop_distance) # Comprobar si se alcanza la parada si h.iloc[­1]['close'] < stop: contract = open_pos[root] order_target(contract, 0) context.highest_in_position[root] = 0 # Comprobar si la tendencia ha invertido elif h['trend'].iloc[­1] == Falso: contract = open_pos[root] order_target (contrato, 0) contexto.highest_in_position[raíz] = 0 Procesar posiciones cortas. else: # La posición es corta if context.lowest_in_position[root] == 0: # Primer día que ocupa el puesto context.lowest_in_position[root] = p.cost_basis else: context.lowest_in_position[root] = min( h['close'].iloc[­1] , contexto.posición_más_baja[raíz] ) # Calcular el punto de parada stop = context.lowest_in_position[root] + (std * stop_distance) # Comprobar si se alcanza la parada si h.iloc[­1]['close'] > stop: contract = open_pos[root] order_target(contract, 0) context.lowest_in_position[root] = 0 # Comprobar si la tendencia ha invertido elif h['trend'].iloc[­1] == Verdadero: contract = open_pos[root] order_target (contrato, 0) contexto.lowest_in_position[raíz] = 0 Si no hay ninguna posición, nos enfrentamos a un escenario de mercado alcista. else: # Sin posición en if h['tendencia'].iloc[­1]: # Tendencia alcista # Compruebe si acabamos de alcanzar un nuevo máximo if h['close'][­1] == h[­breakout_window:]['close'].max(): contract = data.current(continuation, 'contract') contratos_para_comerciar = tamaño_posición( \ contexto.portfolio.portfolio_value, \std, \contrato.price_multiplier) # Limite el tamaño al 20% del promedio. volumen diario contratos_cap = int(h['volumen'][­20:].mean() * 0.2) contratos_para_comerciar = min(contratos_para_comerciar, contratos_cap) # Hacer el pedido Machine Translated by Google order_target(contrato, contratos_para_comerciar) Y, por último, nos ocupamos de los mercados bajistas. más: # Tendencia bajista # Compruebe si acabamos de alcanzar un nuevo mínimo if h['close'][­1] == h[­breakout_window:]['close'].min(): contract = data.current(continuation, 'contract') contratos_para_comerciar = tamaño_posición( \ contexto.portfolio.portfolio_value, \std, \contrato.price_multiplier) # Limite el tamaño al 20% del promedio. volumen diario contratos_cap = int(h['volumen'][­20:].mean() * 0.2) contratos_para_comerciar = min(contratos_para_comerciar, contratos_cap) # Realizar el pedido order_target(contract, ­1 * contracts_to_trade) Finalmente, aún en la rutina daily_trad e , si hay posiciones abiertas, ejecutamos la función para verificar si es necesario rodar algo. Esto se explicó con más detalle en el capítulo 14. # Si tenemos posiciones abiertas, verificamos los rolls si len(open_pos) > 0: roll_futures(context, data) Como sus habilidades con Python deberían mejorar continuamente a lo largo de este libro, confiaré cada vez más en los comentarios en el código y no explicaré cada línea con texto. Si lo hiciera, este libro pasaría de unas 500 páginas al doble. En cambio, me concentro en explicar nuevos conceptos y segmentos importantes. Ahora que hemos visto las partes individuales del código, es posible que desees echar un vistazo al código del modelo completo a continuación. Al igual que con el resto del código de este libro, puede descargarlo desde el sitio web del libro, www.followingthetrend.com/trading­evolved . %matplotlib en línea importar tirolesa desde zipline.api importar futuro_symbol, \ set_commission, set_slippage, Schedule_function, date_rules, \ time_rules, continuo_future, order_target de fecha y hora importar fecha y hora importar pytz importar matplotlib.pyplot como plt importar pyfolio como pf importar pandas como pd importar numpy como np desde zipline.finance.commission importar PerTrade, PerContract desde zipline .finance.slippage importar VolumeShare Slippage, \ Machine Translated by Google Deslizamiento fijo, volatilidad, volumen compartido # Estas líneas son para los informes de texto dinámico de IPython.display import display import ipywidgets as widgets out = widgets.HTML() display(out) """ Configuración del modelo """ cartera_inicial = 50000000 factor_riesgo = 0.0015 distancia_parada = 3 ventana_ruptura = 50 ventana_vola = 40 ma_lenta = 80 ma_rápida = 40 enable_commission = Verdadero enable_slippage = Verdadero def report_result(contexto, datos): contexto.meses += 1 hoy = zipline.api.get_datetime().date() # Calcular el rendimiento anualizado hasta el momento ann_ret = np.power(context.portfolio.portfolio_value / Starting_portfolio, 12 / contexto.meses) ­ 1 # Actualizar el texto out.value = """{} Hemos negociado <b>{}</b> meses y el rendimiento anualizado es <b>{:.2%}</b>""".format( hoy, contexto.meses, ann_ret) def roll_futures(contexto, datos): open_orders = zipline.api.get_open_orders() para contrato_retenido en contexto.cartera.posiciones: # No descarte posiciones que están configuradas para cambiar según la lógica central si se mantiene_contrato en órdenes_abiertas: continuar # Ahorre algo de tiempo comprobando únicamente las listas de # contratos que dejarán de operar en los próximos días días_to_auto_close = (held_contract.auto_close_date.date() ­ data.current_session.date() ).days if days_to_auto_close > 5: continuar # Hacer una continuación continuación =continue_future(held_contract.root_symbol, offset=0, roll='volume', ajuste='mul' Machine Translated by Google ) # Obtener el contrato actual de la continuación continuación_contrato = data.current(continuación, 'contrato') if continuation_contract !=held_contract: # Comprobar cuántos contratos tenemos pos_size = context.portfolio.positions[held_contract].amount # Cerrar la posición actual order_target(held_contract, 0) # Abrir nueva posición order_target(continuation_contract, pos_size) def tamaño_posición(valor_cartera, estándar, valor_punto): variación_objetivo = valor_cartera * factor_riesgo variación_contrato = estándar * valor_punto contratos = variación_objetivo / variación_contrato return int(np.nan_to_num(contratos)) def inicializar (contexto): """ Configuración de costos """ si enable_commission: comm_model = PerContract(coste=0,85, exchange_fee=1,5) más: comm_model = Por operación (coste = 0,0) set_commission(us_futures=comm_model) si enable_slippage: slippage_model=VolatilityVolumeShare(volume_limit=0.2) else: slippage_model=Fixed Slippage(spread=0.0) set_slippage(us_futures=modelo_deslizamiento) """ Mercados para comerciar """ monedas = [ 'ANUNCIO', 'BP', 'CD', 'CU', 'DX', 'JY', 'NORDESTE', 'SF', ] Machine Translated by Google agrícola = [ 'LICENCIADO EN DERECHO', '_C', 'CONNECTICUT', 'FC', 'KC', 'LR', 'LS', '_O', '_S', 'SB', 'SM', '_W', ] no agrícola = [ 'CL', 'GC', 'HG', 'HO', 'LG', 'NG', 'PENSILVANIA', 'PL', 'RB', 'SI', ] acciones = [ 'ES', 'NK', 'NQ', 'TW', 'VX', 'YM', ] tarifas = [ 'DE', 'FV', 'TU', 'TY', 'A NOSOTROS', ] # Haga una lista de todos los mercados = monedas + agrícola + no agrícola + acciones + tasas # Haga una lista de todas las continuaciones context.universe = [continue_future(market, offset=0, roll='volume', ajuste='mul') para el mercado en los mercados ] # Los usaremos para realizar un seguimiento de la mejor lectura de posición. # Se utilizan para calcular los puntos de parada. context.highest_in_position = {mercado: 0 para mercado en mercados} Machine Translated by Google context.lowest_in_position = {mercado: 0 para mercado en mercados} # Programe la función de programación de operaciones diaria (daily_trade, date_rules.every_day(), time_rules.market_close()) # Simplemente usaremos esto para el resultado del progreso # durante la prueba retrospectiva. No impacta nada. contexto.meses = 0 # Programar la salida del informe mensual función_programa( func=report_result, date_rule=date_rules.month_start(), time_rule=time_rules.market_open() ) def analizar (contexto, rendimiento): devoluciones, posiciones, transacciones = pf.utils.extract_rets_pos_txn_from_zipline(perf) pf.create_returns_tear_sheet(devoluciones, benchmark_rets=None) def daily_trade(context, data): # Obtener datos de continuación hist = data.history( context.universe, campos=['close','volume'], frecuencia='1d', bar_count=250, ) # Calcular tendencia hist['tendencia'] = hist['cerrar'].ewm(span=fast_ma).mean() > hist['cerrar'].ewm(span=slow_ma).mean() # Hacer diccionario de posiciones abiertas open_pos = { pos.root_symbol: pos para pos en context.portfolio.positions } # Iterar mercados, buscar operaciones para continuar en context.universe: # Obtener el símbolo raíz de la continuación root = continuation.root_symbol # Cortar el historial solo para este mercado h = hist.xs(continuación, 2) # Obtener la desviación estándar std = h.close.diff()[­vola_window:].std() Machine Translated by Google si es root en open_pos: # La posición está abierta # Obtener posición p = context.portfolio.positions[open_pos[root]] si p.amount > 0: # La posición es larga if context.highest_in_position[root] == 0: # Primer día ocupando el puesto contexto.posición_más alta[raíz] = p.cost_basis else: contexto.posición_más alta[raíz] = max( h['cerrar'].iloc[­1], contexto.posición_más alta[raíz] ) # Calcular el punto de parada stop = context.highest_in_position[root] ­ (std * stop_distance) # Comprobar si se alcanza la parada si h.iloc[­1]['close'] < stop: contract = open_pos[root] order_target(contract, 0) context.highest_in_position[root] = 0 # Comprobar si la tendencia ha invertido elif h['trend'].iloc[­1] == Falso: contract = open_pos[root] order_target (contrato, 0) contexto.highest_in_position[raíz] = 0 else: # La posición es corta if context.lowest_in_position[root] == 0: # Primer día que ocupa el puesto context.lowest_in_position[root] = p.cost_basis else: context.lowest_in_position[root] = min( h['close'].iloc[­1] , contexto.posición_más_baja[raíz] ) # Calcular el punto de parada stop = context.lowest_in_position[root] + (std * stop_distance) # Comprobar si se alcanza la parada si h.iloc[­1]['close'] > stop: contract = open_pos[root] order_target(contract, 0) context.lowest_in_position[root] = 0 # Comprobar si la tendencia ha invertido elif h['trend'].iloc[­1] == Verdadero: contract = open_pos[root] order_target (contrato, 0) contexto.lowest_in_position[raíz] = 0 else: # Sin posición en if h['tendencia'].iloc[­1]: # Tendencia alcista # Comprueba si acabamos de alcanzar un nuevo máximo if h['close'][­1] == h[­breakout_window:]['close'].max(): Machine Translated by Google contrato = datos.actual(continuación, 'contrato') contratos_para_comerciar = tamaño_posición( \ contexto.portfolio.portfolio_value, \std, \contrato.price_multiplier) # Limite el tamaño al 20% del promedio. volumen diario contratos_cap = int(h['volumen'][­20:].mean() * 0.2) contratos_para_comerciar = min(contratos_para_comerciar, contratos_cap) # Realizar el pedido order_target(contrato, contratos_para_trade) más: # Tendencia bajista # Compruebe si acabamos de alcanzar un nuevo mínimo if h['close'][­1] == h[­breakout_window:]['close'].min(): contract = data.current(continuation, 'contract') contratos_para_comerciar = tamaño_posición( \ contexto.portfolio.portfolio_value, \std, \contrato.price_multiplier) # Limite el tamaño al 20% del promedio. volumen diario contratos_cap = int(h['volumen'][­20:].mean() * 0.2) contratos_para_comerciar = min(contratos_para_comerciar, contratos_cap) # Realizar el pedido order_target(contract, ­1 * contracts_to_trade) # Si tenemos posiciones abiertas, verificamos los rolls si len(open_pos) > 0: roll_futures(context, data) inicio = fecha y hora (2001, 1, 1, 8, 15, 12, 0, pytz.UTC) fin = fecha y hora (2019, 1, 31, 8, 15, 12, 0, pytz.UTC) rendimiento = zipline.run_algorithm (inicio = inicio, fin = fin, inicializar = inicializar, analizar = analizar, capital_base = cartera_inicial, frecuencia_datos = 'diario', paquete = 'futuros') Resultados del modelo de tendencias principales Machine Translated by Google Después de ejecutar el modelo, lo primero que hay que hacer es comprobar la forma general de la curva de renta variable. Por supuesto, esa no es una base válida o mensurable para tomar decisiones, pero sí brinda una visión general de si vale la pena investigar más a fondo un modelo. Descubrirá que a menudo puede descartar ideas y conceptos después de echar un vistazo a la curva de acciones. A veces le dará mucha más información sobre lo que hará una estrategia que los ratios habituales, como Sharpe, rendimiento anualizado o reducciones máximas. Tabla 15.1 ­ Rentabilidades Mensuales de Tendencia Principal Año Ene Feb Mar Abr May Jun Jul Ago Sep Oct Nov Dic Año 2001 ­4,6 +0,8 +9,3 ­9,3 +0,5 ­2,8 +0,5 +1,9 +6,1 +7,4 ­3,9 ­1,3 +3,1 2002 ­3,3 ­2,1 ­3,5 ­1,8 +7,4 +12,6 +6,1 +0,3 +4,5 ­6,1 ­2,2 +6,4 +18,0 2003 +3,5 +7,2 ­8,4 +1,1 +4,2 ­2,9 +1,3 ­4,0 +2,4 +8,3 ­3,4 +8,5 +17,4 2004 +0,2 +3,5 +0,6 ­7,1 +0,9 ­0,4 +1,9 ­2,4 +0,3 +3,1 +3,9 ­1,6 +2,3 2005 ­6,7 +0,1 +0,7 ­0,4 ­0,2 ­4,2 +1,8 +1,5 +2,4 ­1,9 +7,4 ­1,2 ­1,2 2006 +7,6 ­1,6 +1,9 +9,6 +2,1 ­4,4 ­3,8 +7,1 ­2,2 +2,7 +2,4 ­4,6 +16,8 2007 ­0,1 ­5,7 ­3,0 +6,4 +0,0 +5,4 ­1,5 +1,9 +8,2 +2,0 +2,7 +1,8 +18,8 2008 +5,7 +21,0 ­11,6 +0,9 +3,2 +0,5 ­8,9 +6,0 +12,0 +28,4 +3,5 +3,3 +75,9 2009 ­2,2 +2,7 ­8,4 ­1,4 +14,9 ­9,9 +1,1 +5,0 +2,6 ­5,1 +5,5 ­3,0 ­0,7 2010 ­2,0 +0,3 +2,3 ­0,3 ­3,0 ­2,4 +1,4 +3,8 +9,4 +8,7 ­7,5 +9,3 +20,2 2011 +1,9 +1,3 ­0,4 +14,6 ­7,5 ­4,9 +3,1 ­2,7 +2,5 ­8,8 ­0,5 ­1,8 ­5,0 2012 +0,4 +4,2 +1,3 ­1,8 +13,5 ­7,7 +9,1 +1,2 ­0,5 ­7,3 ­1,0 +0,8 +10,9 2013 +1,3 ­2,2 +1,3 +0,5 +3,3 +2,4 ­5,4 ­3,4 ­2,3 ­0,9 +1,5 +3,8 ­0,5 2014 ­7,5 +2,5 ­4,5 ­2,2 +0,7 +0,0 +2,4 +13,3 +20,7 ­2,9 +8,3 +9,9 +44,1 2015 +6,7 ­7,0 ­2,6 ­2,8 ­0,2 ­1,0 +4,4 ­0,7 +0,1 ­5,3 +2,7 +0,2 ­6,2 2016 +1,7 +1,0 ­3,5 +3,1 ­2,3 +0,2 ­0,9 ­2,7 ­2,4 +1,9 +13,8 +0,6 +9,6 2017 ­6,9 +1,6 ­5,2 ­1,8 ­2,2 ­1,5 +4,2 +0,3 ­2,5 +7,0 +3,5 +3,0 ­1,3 2018 +16,7 ­2,2 ­5,1 +0,8 ­0,8 +3,8 ­0,6 +3,7 ­0,6 ­2,7 +1,5 +8,6 +23,6 La Figura 15­2 muestra el desempeño de la prueba retrospectiva de este modelo de tendencia simple, en comparación con el del S&P 500 Total Return Index. Esta descripción visual básica proporciona algunos datos importantes. La más obvia es que parece que esta estrategia tiene un mayor rendimiento a largo plazo que el S&P 500. Pero eso es casi irrelevante. Incluso se podría argumentar que, para empezar, es una comparación irrelevante. Después de todo, esta no es de ninguna manera una estrategia relacionada con la renta variable. Machine Translated by Google El simple hecho de tener un mayor rendimiento a largo plazo no es una medida de calidad. Tenga en cuenta que en una simulación como esta, podríamos simplemente aumentar o reducir los tamaños de riesgo de las posiciones para cambiar las cifras de rendimiento acumuladas finales. Comparar únicamente los puntos inicial y final no tiene mucho sentido. Necesitamos considerar cómo llegamos allí. A continuación podemos ver en la misma figura cómo la rentabilidad suele llegar en momentos diferentes a los de los mercados bursátiles. Hubo dos mercados bajistas importantes durante este período y en ambas ocasiones la estrategia de futuros funcionó bien. Esto realmente no debería ser una sorpresa para nadie que haya seguido la tendencia del espacio. Históricamente, este enfoque ha funcionado muy bien durante períodos de crisis de mercado. Esto también significa que el modelo de tendencia a veces tiene un rendimiento muy inferior durante los mercados alcistas, y eso puede ser un problema mayor de lo que uno podría pensar. En esta figura se utiliza como referencia el mercado de valores, aunque la estrategia realmente tiene muy poco que ver con ese sector. La cuestión es que el público en general e incluso los profesionales financieros tienden a comparar todo con los mercados de valores. Si administras el dinero de otras personas, esto es algo que notarás rápidamente. Incluso si tienes una estrategia que realmente no debería estar relacionada con los mercados de valores, siempre te compararán con ella. Si usted pierde el diez por ciento y el mercado de valores pierde el 15 por ciento, nadie se quejará. Si usted gana un diez por ciento y el mercado de valores gana un 15 por ciento, es posible que algunos clientes no estén contentos. Nos guste o no, el mercado de valores es el punto de referencia de facto. Machine Translated by Google Figura 15 2 – Curva de acciones del modelo de tendencia central Si bien tener una baja correlación con los mercados de valores durante los mercados alcistas puede ser problemático, tener una baja correlación durante los mercados bajistas es de vital importancia. Aquí es donde realmente brilla una estrategia como ésta. A la gente le gusta ganar dinero, pero no hay nada como ganar dinero mientras el vecino pierde el suyo. El gráfico de rentabilidad también muestra un aspecto un tanto preocupante. Un vistazo rápido le indica que los rendimientos parecen estar bajando. Estamos viendo reducciones más profundas y duraderas. Sí, es cierto. Y hay buenas razones para ello. El entorno para seguir tendencias no ha sido óptimo en los últimos años. La situación de baja volatilidad es un problema. El seguimiento de tendencias prospera en mercados de alta volatilidad y se ha desempeñado notablemente bien durante mercados bajistas volátiles. Pero hemos visto un mercado alcista de diez años con una volatilidad bastante baja, y eso ha reducido el potencial de ganancias para las estrategias de futuros diversificadas. El entorno de bajos tipos de interés también ha sido motivo de preocupación. Históricamente, el seguimiento de tendencias generó retornos descomunales simplemente por estar a largo plazo en bonos, bonos del Tesoro y futuros del mercado monetario, capitalizando los rendimientos que decrecían lentamente. A medida que los rendimientos cayeron a niveles históricamente bajos y se estancaron, esa fuente de ingresos disminuyó. Machine Translated by Google Sin embargo, sería un error considerar los últimos años como un signo de tendencia tras la desaparición. Del mismo modo que fue un error tomar los rendimientos extremos de 2008 y extrapolarlos al futuro. Lo que sí sabemos es que este tipo de estrategia altamente simplificada ha funcionado bien durante décadas. Ha superado los enfoques de inversión tradicionales y ha mostrado un rendimiento muy sólido durante los mercados bajistas y en tiempos de dificultades en los mercados. Sería razonable suponer que tendrá un buen desempeño durante el próximo mercado bajista. Tabla 15.2 Modelo de tendencia central de rendimientos del período de tenencia Años 1 2 34 5 6 7 8 9 10 11 12 13 14 15 16 17 18 2001 +3 +10 +13 +10 +8 +9 +10 +17 +15 +15 +13 +13 +12 +14 +13 +12 +12 +12 2002 +18 +18 +12 +9 +10 +12 +19 +17 +17 +15 +14 +13 +15 +13 +13 +12 +13 2003 +17 +10 +6 +9 +10 +19 +16 +17 +14 +14 +12 +15 +13 +13 +12 +12 2004 +2 +1 +6 +9 +20 +16 +17 +14 +13 +12 +15 +13 +12 +11 +12 2005 ­1 +7 +11 +25 +19 +19 +15 +15 +13 +16 +14 +13 +12 +13 2006 +17 +18 +35 +25 +24 +18 +17 +15 +18 +15 +15 +13 +14 2007 +19 +45 +28 +26 +19 +17 +15 +18 +15 +14 +13 +14 2008 +76 +32 +28 +19 +17 +14 +18 +15 +14 +12 +13 2009 ­1 +9 +4 +6 +5 +10 +8 +8 +7 +9 2010 +20 +7 +8 +6 +13 +9 +9 +8 +10 2011 ­5 +3 +2 +11 +7 +8 +6 +8 2012 +11 +5 +17 +11 +10 +8 +10 2013 ­1 +20 +10 +10 +8 +10 2014 +44 +16 +14 +10 +13 2015 ­6 +1 +0 +6 2016 +10 +4 +10 2017 ­1 +10 2018 +24 La descripción general de los rendimientos del período de tenencia en la tabla 15.2 proporciona una forma diferente de ver los rendimientos. Esto le muestra cuál sería su rendimiento anualizado si comprara al comienzo de un año determinado y lo mantuviera durante una determinada cantidad de años. Si quiere ver qué hubiera sucedido si hubiera iniciado esta estrategia en enero de 2004 y la hubiera mantenido durante cuatro años, vaya a la fila con ese año y cuatro columnas afuera. Eso le indicará que habría obtenido una ganancia anualizada del 9% durante este período. Si bien este gráfico muestra poco sobre detalles importantes, como la volatilidad y las caídas necesarias para obtener esos rendimientos, puede ayudar a brindar una perspectiva a largo plazo. Machine Translated by Google Modelo de tendencia de retorno de tiempo El modelo de tendencia de retorno del tiempo es probablemente el modelo de tendencia más simple que jamás haya visto. Puede que sea el modelo comercial más simple de cualquier tipo que pueda encontrar. Al menos si sólo contamos los modelos con un rendimiento razonablemente bueno. No hay nada malo con los resultados de este modelo. No es el modelo más práctico para operar, pero los rendimientos han sido nada menos que estelares, dado lo simples que son realmente las reglas. Y por supuesto, estoy a punto de daros todo el código fuente. El objetivo de este modelo es capturar tendencias a largo plazo evitando al mismo tiempo el problema común de la interrupción anticipada. Con demasiada frecuencia, los modelos clásicos de seguimiento de tendencias sufren por detenerse demasiado pronto en retrocesos de corto plazo, sólo para ver que los precios regresan nuevamente a la tendencia. Si bien estos modelos de tendencia clásicos tienden a mostrar un sólido desempeño a largo plazo a pesar de este problema, el modelo de tendencia de retorno en el tiempo no es susceptible al problema en absoluto. Las reglas son tan simples que es fácil descartar este modelo a primera vista. Aunque te insto a que te lo tomes en serio. Hay mucho que aprender al comprender este modelo. Debes tener en cuenta desde el principio que este modelo en particular no es muy práctico para operar. Para la mayoría de los lectores, simplemente no es posible intercambiarlo. Para implementar realmente este modelo, necesitará una suma sustancial de dinero, una gran cantidad de posiciones estarán abiertas en un momento dado y constantemente tendrá una relación margen­capital muy alta. Pero este modelo sigue siendo una herramienta de aprendizaje muy valiosa. Si entiendes la mecánica, podrás adaptar el modelo y hacerlo más práctico. Esta versión que te mostraré es para aprender, no para que tú la implementes. Universo de inversión Al igual que en el modelo anterior, utilizaremos un universo de inversión muy amplio. Un modelo como este depende completamente de la diversificación y no funcionará bien en un universo de inversión pequeño. Si prueba reglas como esta en cualquier mercado determinado, o en un pequeño número de mercados, probablemente verá resultados que van desde pobres hasta mediocres. El efecto proviene de operar en un amplio conjunto de mercados, todos a la vez. Machine Translated by Google Para este modelo, negociaremos el mismo conjunto de mercados de futuros amplios que utilizamos en el modelo anterior. Cubrimos índices de acciones, materias primas, divisas, tesorerías y mercados monetarios. Frecuencia de negociación Con este modelo en particular, sólo operamos una vez al mes. Ignoramos por completo cualquier cosa que suceda durante ese mes. No importa cuán grandes sean los movimientos, no se toma ninguna medida durante el mes. Sin punto de parada, sin objetivos de ganancias. Simplemente haga sus operaciones a principios de mes y salga a pescar un rato. En realidad, hay una cosa que debemos hacer entre los puntos comerciales mensuales. Como estamos tratando con futuros aquí, necesitamos verificar las tiradas diariamente. Es decir, debemos asegurarnos de mantener el contrato correcto, ya que la liquidez pasa de una entrega de contrato a otra. Al igual que con cualquier modelo de futuros, esto es algo de lo que no nos libraremos. Asignación de posiciones Para este modelo, utilizaremos la misma lógica de asignación de posiciones que empleamos para el modelo anterior. Medimos la volatilidad utilizando la desviación estándar y establecemos tamaños de posiciones según la lógica de volatilidad inversa. Es decir, tenemos una cantidad de valor nominal más bajo de algo más volátil, y viceversa, en un intento de apuntar a una volatilidad, o riesgo si se quiere, aproximadamente igual, por posición. Para mantener la lógica simple aquí, no se realiza ningún reequilibrio regular de los tamaños de las posiciones, ni se emplea ninguna técnica de orientación de volatilidad más compleja. Ambos tienen más sentido para la gestión de cartera institucional que para la gestión individual. cuentas. Reglas comerciales Como ya se mencionó, este modelo se comercializa solo una vez al mes. En ese momento comprobamos sólo dos cosas. ¿El precio es mayor o menor que hace un año? ¿El precio es mayor o menor que hace medio año? Machine Translated by Google Eso es todo lo que nos importa aquí. Para este modelo, no hay indicadores de ruptura, ni promedios móviles ni indicadores de ningún tipo. Se trata, a propósito, de un modelo de tendencia extremadamente simple. Tenga en cuenta que aquí hacemos los cálculos en función de las continuaciones, como hicimos en el modelo anterior, no de contratos individuales. Generalmente así es como necesitamos trabajar con los modelos de futuros, ya que los contratos individuales tienen un historial de series temporales muy limitado. Gráfico de rendimiento dinámico Dado que este modelo es bastante simple, este parece un buen lugar para presentar un nuevo y pequeño truco que enseñarte. Si ha replicado los modelos y realizado las pruebas retrospectivas de los capítulos anteriores, ya habrá notado que a veces lleva un poco de tiempo terminarlos. Anteriormente les mostré formas sencillas de generar texto, como el rendimiento del mes pasado durante la ejecución. Pero esto no es bonito ni muy informativo. ¿No sería bueno si pudiéramos obtener un gráfico que se actualiza dinámicamente mientras esperamos? Un gráfico que muestra el rendimiento continuo de la simulación, tal como sucede. Es un truco bien conocido que a la gente le molestan menos los tiempos de espera si hay algo que mirar o escuchar. Al fin y al cabo, por eso tienen espejos en los ascensores. Resulta que no sólo es posible, sino incluso bastante sencillo, tener una visualización de gráfico dinámica mientras se ejecuta el backtest. De ahora en adelante en este libro, utilizaré esta forma para generar resultados a medida que se realicen las pruebas retrospectivas. Para configurar estos gráficos dinámicos en el entorno Jupyter Notebook , debemos hacer tres cosas simples. Primero, hasta ahora hemos estado usando la línea %matplotlib inlin e en la parte superior de nuestro código. Eso le dice a la biblioteca de gráficos, matplotlib , que muestre los gráficos en la salida como imágenes cuando nuestro código los solicite. Pero su uso no permite gráficos interactivos. Para poder obtener gráficos interactivos, debemos cambiar esa línea a % matplotlib cuaderno k . En segundo lugar, necesitamos agregar un código breve para crear un DataFrame para almacenar los datos del gráfico, así como inicializar el gráfico. # DataFame para almacenar y actualizar los datos que queremos graficar Machine Translated by Google resultados_dinámicos = pd.DataFrame() # Figura inicial fig = plt.figure(figsize=(10, 6)) ax = fig.add_subplot(111) ax.set_title('Rendimiento de retorno de tiempo') Ahora lo único que queda es actualizar los datos a medida que avanzamos en el backtest. Para mantenerlo limpio, agregué una función programada separada en este ejemplo. Ten en cuenta que si buscas pura velocidad, utilizar un gráfico dinámico te ralentizará, y más cuanto más a menudo lo actualices. Agregué esta fila a nuestra función de inicialización , que actualizará el gráfico una vez al mes. Si tienes muy poca capacidad de atención, puedes actualizar esto diariamente, pero el rendimiento se verá afectado. función_programación(update_chart, date_rules.month_start(), time_rules.market_close()) Y luego esta nueva función para almacenar y actualizar el gráfico. def update_chart(contexto,datos): # Esta función actualiza continuamente el gráfico durante el backtest hoy = data.current_session.date()dynamic_results.loc[today, 'PortfolioValue'] = context.portfolio.portfolio_value if ax.lines: # Actualizar la línea existente ax.lines[0].set_xdata(dynamic_results.index) ax.lines[0].set_ydata(dynamic_results.PortfolioValue) else: # Crear nueva línea ax.semilogy(dynamic_results) # Actualizar escalas min/max ax.set_ylim(dynamic_results.PortfolioValue.min(),dynamic_results.PortfolioValue.max() ) ax.set_xlim( resultados_dinámicos.index.min(), resultados_dinámicos.index.max() ) # Redibujar el gráfico fig.canvas.draw() Código fuente de retorno de tiempo El código para este modelo es realmente bastante simple. Incluso si realmente no sabes nada sobre programación, aún puedes resolverlo. En comparación con la mayoría de los lenguajes de programación, Python es bastante fácil de leer y comprender. Machine Translated by Google Como de costumbre, en la parte superior encontrará algunas declaraciones de importación que le indican a nuestro código qué bibliotecas pretendemos utilizar. Luego hay algunos parámetros definidos. Siéntete libre de jugar con estos. Puede cambiar el factor de riesgo, la ventana de cálculo de volatilidad, el filtro de liquidez y las ventanas de tendencia. Notarás que hay dos configuraciones para la ventana de tendencias. Uno corto y otro largo. Uno está fijado en 125 días y el otro en 250, o aproximadamente medio año y un año completo respectivamente. Como puede ver, las reglas requieren un rendimiento positivo en ambos períodos de tiempo para una posición larga, o negativo en ambos períodos para una posición corta. En la función Inicializar podemos habilitar o deshabilitar el costo y el deslizamiento. Pruébelo nuevamente y vea cómo cambia las cosas. No tendría mucho sentido para mí simplemente decírtelo. Créame, aprenderá mucho más si ejecuta este código localmente y lo comprueba usted mismo. Luego, en la misma función, el universo de inversión se define antes de que configure programadores para ejecutar el reequilibrio mensual y la verificación de futuros diariamente. En el reequilibrio mensual, primero comprobamos si una posición ya está abierta o no para cada mercado. Si no hay ninguna posición, comprobamos si el precio de ayer es más alto que hace un año y medio año. Si es así, compre. Si es inferior a esos dos puntos en el tiempo, vaya corto. Y además, no ocupar ningún puesto. cuaderno% matplotlib importar tirolesa desde zipline.api importar futuro_symbol, \ set_commission, set_slippage, Schedule_function, date_rules, \ time_rules, continuo_future, order_target de fecha y hora importar fecha y hora importar pytz importar matplotlib.pyplot como plt importar matplotlib importar pyfolio como pf importar pandas como pd importar numpy como np desde zipline.finance.commission importar PerShare, PerTrade , PerContract de zipline.finance.slippage importar VolumeShare Slippage, \ Fixed Slippage, VolatilityVolumeShare """ Configuración del modelo """ cartera_inicial = 10000000 factor_riesgo = 0.0015 ventana_vola = 60 ventana_tendencia_corta = 125 ventana_tendencia_larga = 250 """ Machine Translated by Google Prepárese para el gráfico dinámico """ Dynamic_results = pd.DataFrame() fig = plt.figure(figsize=(10, 6)) ax = fig.add_subplot(111) ax.set_title('Rendimiento de retorno de tiempo') def inicializar (contexto): """ Configuración de costos """ context.enable_commission = Verdadero context.enable_slippage = Verdadero si context.enable_commission: comm_model = PerContract(coste=0,85, exchange_fee=1,5) else: comm_model = PerTrade(coste=0,0) set_commission(us_futures=comm_model) si context.enable_slippage: slippage_model=VolatilityVolumeShare(volume_limit=0.2) else: slippage_model=Fixed Slippage(spread=0.0) set_slippage(us_futures=modelo_deslizamiento) monedas = [ 'ANUNCIO', 'BP', 'CD', 'CU', 'DX', 'JY', 'NORDESTE', 'SF', ] agrícolas = [ 'LICENCIADO EN DERECHO', 'BO', '_C', 'CC', 'CONNECTICUT', 'FC', 'KC', 'LB', 'LC', 'LR', 'LS', '_O', '_S', 'SB', Machine Translated by Google '_W', ] no agrícolas = [ 'CL', 'GC', 'HG', 'HO', 'LG', 'NG', 'PENSILVANIA', 'PL', 'RB', 'SI', ] acciones = [ 'ES', 'NK', 'NQ', 'TW', 'VX', 'YM', ] tarifas = [ 'DE', 'FV', 'TU', 'TY', 'A NOSOTROS', ] # Unir listas de sectores en una lista de mercados = monedas + agrícolas + no agrícolas + acciones + tasas # Haga una lista de todas las continuaciones context.universe = [continue_future(market, offset=0, roll='volume', ajuste='mul') para el mercado en los mercados ] # Programar operaciones diarias Schedule_function(rebalance, date_rules.month_start(), time_rules.market_close()) # Programar verificación de rollo diaria Schedule_function(roll_futures,date_rules.every_day(), time_rules.market_close()) # Programar actualización mensual del gráfico Schedule_function(update_chart,date_rules.month_start(), time_rules.market_close()) def update_chart(contexto,datos): # Esta función actualiza continuamente el gráfico durante el backtest hoy = data.current_session.date()dynamic_results.loc[today, 'PortfolioValue'] = context.portfolio.portfolio_value Machine Translated by Google if ax.lines: # Actualizar la línea existente ax.lines[0].set_xdata(dynamic_results.index) ax.lines[0].set_ydata(dynamic_results.PortfolioValue) else: # Crear nueva línea ax.semilogy(dynamic_results) # Actualizar escalas min/max ax.set_ylim(dynamic_results.PortfolioValue.min(),dynamic_results.PortfolioValue.max() ) ax.set_xlim( resultados_dinamicos.index.min(), resultados_dinamicos.index.max() ) # Redibujar el gráfico fig.canvas.draw() def roll_futures(context,data): hoy = data.current_session.date() open_orders = zipline.api.get_open_orders() para holding_contract en contexto.portfolio.positions: si holding_contract en open_orders: continuar días_to_auto_close = (held_contract.auto_close_date.date() ­ hoy).días si días_to_auto_close > 10: continuar # Hacer una continuación continuación = continuo_futuro (hold_contract.root_symbol, offset=0, roll='volume', ajuste='mul') contrato_continuación = datos.actual(continuación, 'contrato') si contrato_continuación! = contrato_retenido: pos_size = contexto.cartera.posiciones[contrato_retenido].cantidad objetivo_pedido(contrato_retenido, 0) objetivo_pedido(contrato_continuación, tamaño_pos) def tamaño_posición(valor_cartera, estándar, pv, volumen_promedio): variación_objetivo = valor_cartera * factor_riesgo variación_contrato = estándar * pv contratos = variación_objetivo / variación_contrato return int(np.nan_to_num(contratos)) def rebalance(context, data): # Obtener el historial hist = data.history( context.universe, campos=['close', 'volume'], frecuencia='1d', Machine Translated by Google bar_count=ventana_tendencia_larga, ) # Hacer un diccionario de posiciones abiertas open_pos = {pos.root_symbol: pos for pos in context.portfolio.positions} # Recorrer todos los mercados para continuar en context.universe: # Cortar el historial de este mercado h = hist.xs(continuation, 2) root = continuation.root_symbol # Calcular la volatilidad std = h.close.diff()[­vola_window:].std() si es root en open_pos: # La posición ya está abierta p = context.portfolio.positions[open_pos[root]] if p.amount > 0: # Posición larga if h.close[­1] < h.close[­long_trend_window]: # Tendencia lenta perdida, posición cerrada order_target(open_pos [raíz], 0) elif h.close[­1] < h.close[­short_trend_window]: # Tendencia rápida perdida, posición cerrada order_target(open_pos[root], 0) else: # Posición corta si h.close[­1] > h.close[­long_trend_window]: # Tendencia lenta perdida, posición cerrada order_target(open_pos[root] ], 0) elif h.cerrar[­1] > h.cerrar[­short_trend_window]: # Tendencia rápida perdida, posición cerrada order_target(open_pos[root], 0) else: # Aún no hay ninguna posición abierta. if (h.close[­1] > h.close[­long_trend_window]) \ y\ (h.close[­1] > h.close[­short_trend_window]): # Comprar nuevo contrato de posición = data.current(continuación, 'contrato') contract_to_trade = position_size( \ contexto.portfolio.portfolio_value, \ std, \ contract.price_multiplier, \ h['volumen'][­20:].mean()) order_target(contrato, contratos_para_comerciar) elif (h.close[­1] < h.close[­long_trend_window]) \ y\ (h.close[­1] < h.close[­short_trend_window]): # Nuevo contrato de posición corta = data.current(continuación, 'contrato') contract_to_trade = position_size( \ contexto.cartera.valor_cartera, \ Machine Translated by Google std, \ contract.price_multiplier, \ h['volumen'][­20:].mean()) order_target(contrato, contratos_para_comerciar *­1) inicio = fecha y hora (2001, 1, 1, 8, 15, 12, 0, pytz.UTC) fin = fecha y hora (2018, 12, 31, 8, 15, 12, 0, pytz.UTC) rendimiento = zipline.run_algorithm (inicio = inicio, fin = fin, inicialización = inicialización, capital_base = cartera_inicial, frecuencia_datos = 'diario', paquete = 'futuros') Rendimiento del modelo de retorno de tiempo Nuestro modelo es tan increíblemente simple que no podría mostrar ningún tipo de rendimiento interesante. ¿Bien? Bueno, resulta que seguir tendencias no tiene por qué ser complejo. Claro, este modelo todavía está demasiado simplificado y se puede mejorar fácilmente, pero incluso en este estado simple sigue funcionando. Un vistazo rápido a la tabla de rentabilidad mensual y al gráfico a largo plazo debería decirnos que al menos no debemos descartar este tipo de enfoque de plano. Podemos ver algunas características interesantes. En primer lugar, tiende a funcionar bien durante los mercados bajistas. Sólo tuvimos dos mercados bajistas severos durante este período, pero el modelo los manejó muy bien. En segundo lugar, podemos ver que el modelo parece estar mostrando un rendimiento a largo plazo bastante sólido, con reducciones aceptables. Machine Translated by Google Figura 16 1: Rendimiento del momento del tiempo Tabla 16.1 ­ Retorno de Tiempo, Cifras Mensuales Año Ene Feb Mar Abr May Jun Jul Ago Sep Oct Nov Dic Año 2001 ­0,9 +8,0 +6,3 ­5,7 +2,4 +1,6 ­4,6 +2,2 +9,8 +4,8 ­3,4 ­1,6 +19,1 2002 +2,6 +1,8 ­9,7 ­1,4 +8,0 +6,5 +2,4 +5,4 +9,8 ­2,6 ­0,4 +13,4 +39,3 2003 +8,9 +6,7 ­7,6 +0,9 +6,7 ­1,7 ­7,8 +0,7 +6,0 +11,7 +5,5 +3,3 +36,0 2004 +4,2 +9,4 +2,3 ­11,8 ­3,1 ­2,9 +2,3 ­1,4 +4,4 +5,0 +3,7 ­0,6 +10,4 2005 ­2,4 +3,2 +1,3 ­3,5 ­4,4 ­0,5 ­2,7 +2,7 +0,6 ­6,7 +8,0 +4,3 ­1,1 2006 +9,7 ­2,4 +6,9 +2,4 ­6,5 ­3,9 ­0,1 +4,0 ­3,3 +1,5 +1,2 ­2,0 +6,6 2007 +1,2 ­2,2 ­0,8 +8,0 +1,2 +4,2 ­1,5 +4,7 +13,0 +9,4 +2,3 +2,3 +49,4 2008 +8,9 +32,7 ­17,4 ­1,1 +1,5 +15,9 ­17,2 ­13,0 ­3,1 +16,3 +11,2 +1,7 +27,6 2009 +2,2 +3,3 ­4,2 ­5,5 ­9,6 ­1,0 ­0,6 +5,8 +5,6 ­0,9 +10,3 +0,8 +4,6 2010 ­5,4 +2,9 +2,5 +1,9 ­14,0 ­4,9 ­4,4 +4,8 +6,1 +13,7 ­2,4 +21,1 +18,8 2011 +5,5 +8,7 ­0,6 +8,1 ­7,8 ­7,4 +7,8 +2,2 ­13,1 ­6,3 +4,7 +0,8 ­0,3 2012 +1,4 ­4,3 ­1,2 +0,8 ­4,9 ­8,2 +3,3 ­6,2 ­4,2 ­3,5 +1,7 ­2,7 ­25,1 2013 +6,0 ­2,7 +1,5 +4,3 ­3,0 ­0,3 ­1,2 ­0,5 +1,8 +1,5 +2,3 +0,6 +10,3 2014 ­4,3 +0,1 ­1,1 +3,0 ­0,3 +5,9 ­1,3 +4,1 +5,3 +7,7 +13,2 +3,8 +41,1 Machine Translated by Google Año Ene Feb Mar Abr May Jun Jul Ago Sep Oct Nov Dic Año 2015 +8,1 ­4,9 +12,5 ­8,1 +7,8 ­7,1 +12,5 ­3,1 +3,8 ­6,8 +8,6 ­3,3 +18,0 2016 +5,5 +2,0 ­7,4 ­3,3 ­3,1 +10,8 ­1,8 ­2,6 +3,5 ­5,0 ­4,3 ­0,4 ­7,3 2017 ­1,3 +1,6 ­5,0 ­0,8 ­1,6 +2,5 ­1,4 ­0,5 +0,6 +5,6 +2,4 +3,4 +5,2 2018 +8,2 ­6,1 +0,6 +4,1 ­3,6 +1,3 +0,1 +3,9 +1,7 ­8,9 ­0,9 ­11,1 ­11,7 Lo que tenemos aquí ciertamente no es el mejor modelo posible, pero tampoco es una locura. La volatilidad es bastante alta y probablemente demasiado alta para el apetito de la mayoría de las personas. Pero este tipo de desempeño con reglas tan extremadamente simples debería decirle algo sobre la naturaleza del seguimiento de tendencias. Aquí no se utiliza ningún indicador. No se requieren promedios móviles, RSI, estocásticos, MACD ni términos de análisis técnico de ningún tipo. Todo lo que estamos haciendo es comprobar dos puntos de precio. Y solo lo hacemos una vez al mes. Tenga esto en cuenta la próxima vez que alguien le ofrezca venderle algún increíble sistema de seguimiento de tendencias. Tabla 16.2 Análisis del período de tenencia Años 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 2001 +19 +29 +31 +26 +20 +17 +22 +22 +20 +20 +18 +14 +13 +15 +15 +14 +13 +12 2002 +39 +38 +28 +20 +17 +22 +23 +20 +20 +18 +13 +13 +15 +15 +13 +13 +11 2003 +36 +23 +14 +12 +19 +20 +18 +18 +16 +11 +11 +13 +13 +12 +11 +10 2004 +10 +4 +5 +15 +17 +15 +16 +13 +8 +9 +11 +12 +10 +10 +8 2005 ­1 +3 +16 +19 +16 +16 +14 +8 +8 +11 +12 +10 +10 +8 2006 +7 +26 +27 +21 +20 +17 +9 +10 +13 +13 +11 +11 +9 2007 +49 +38 +26 +24 +19 +10 +10 +13 +14 +12 +11 +9 2008 +28 +16 +17 +12 +3 +5 +9 +10 +8 +8 +6 2009 +5 +11 +7 ­2 +0 +6 +8 +6 +6 +4 2010 +19 +9 ­4 ­1 +7 +8 +6 +6 +4 2011 ­0 ­14 ­6 +4 +7 +4 +4 +2 2012 ­25 ­9 +5 +8 +5 +5 +2 2013 +10 +25 +22 +14 +12 +8 2014 +41 +29 +16 +13 +7 2015 +18 +5 +5 +0 2016 ­7 ­1 ­5 2017 +5 ­4 2018­12 _ Machine Translated by Google Pero hay una observación más que ya debería haber hecho. ¿No habíamos visto antes este perfil de retorno? ¿No es esto un poco similar a algo que hemos visto antes? Sí, querido lector. Y ese es exactamente el punto que estoy tratando de señalar con cierta lentitud. Los rendimientos que siguen las tendencias provienen de un concepto simple. La mayoría de los modelos de seguimiento de tendencias tienen una correlación muy alta entre sí. O en otras palabras, no hay muchas formas diferentes de seguir las tendencias. Y es por eso que la mayoría de los modelos comerciales de seguimiento de tendencias tienen una alta correlación, y por qué la mayoría de los fondos de cobertura que siguen las tendencias se ven muy similares con el tiempo. Los mayores diferenciadores son la combinación de activos y el marco temporal, y en este caso mantuve ambos modelos más o menos iguales a propósito. Como se ve en la Figura 16­2, estos dos modelos muy diferentes tienen una correlación que dista mucho de ser perfecta, pero con el tiempo tienden a igualarse y ofrecer más o menos el mismo resultado. Se podrían crear fácilmente decenas de modelos de tendencias aparentemente diferentes, y probablemente todos se verán muy similares si se comparan en igualdad de condiciones. Figura 16 2 Comparación de modelos de tendencia Machine Translated by Google La primera y principal conclusión que podemos sacar de esto es que seguir una tendencia, como fenómeno, es bastante simple. La mayor parte de los rendimientos que siguen las tendencias se pueden capturar con reglas que podrían resumirse en una sola oración. En cuanto a los rendimientos, si nos fijamos únicamente en las cifras anuales, son notablemente sólidos. Desde 2000, sólo tres años terminaron en números rojos. Ningún año perdió más del 25%, mientras que el mejor año duplicó el dinero. Si hubiera colocado 100.000 en una estrategia de este tipo en enero de 2000, tendría más de dos millones y medio en 2016. Pero, por supuesto, nadie puede negociar con dinero real en simulaciones históricas. Si bien esta estrategia puede parecer bastante atractiva cuando se analiza una simulación a largo plazo, está lejos de ser una estrategia pulida. La volatilidad de este modelo está en ocasiones muy por encima de niveles aceptables. Siempre es necesario tener en cuenta la volatilidad al analizar un modelo comercial, y en el capítulo 22 obtendrá una mejor idea de cómo hacerlo. La reducción de 2008 también es un poco preocupante. Lo que sucedió allí fue que primero el modelo obtuvo grandes ganancias y luego gran parte de ellas fueron devueltas. Al observar una simulación, esto puede no parecer gran cosa. Fácil viene, fácil se va, ¿verdad? Quizás sea así, si tiene el beneficio de la retrospectiva al elegir su entrada en la estrategia. Si hubiera invertido en esta estrategia justo después de las ganancias, habría visto las pérdidas y le habría llevado tres años recuperarlas. En realidad, nunca se sabe cuándo llegará la reducción. Suponga que sucederá en el peor momento posible. Porque así es la realidad. Antes de profundizar demasiado en el análisis de los rendimientos de este simple año modelo de retorno, deberíamos identificar y corregir un par de deficiencias. Reequilibrio Se trata de un modelo comercial a muy largo plazo con un período de tenencia promedio de aproximadamente medio año. Un modelo así necesita absolutamente un mecanismo de reequilibrio. Sin uno, el riesgo de la posición y, en extensión, el riesgo de la cartera, acabarían siendo bastante aleatorios. Machine Translated by Google La asignación de posiciones para este modelo se basa en la volatilidad reciente. Recuerde cómo analizamos la volatilidad pasada y calculamos el tamaño de las posiciones con el objetivo de asignar una cantidad equivalente de riesgo a cada posición. De manera óptima, nos gustaría que todas las posiciones tuvieran la misma posibilidad de impactar la cartera a diario. Eso significa comprar más en los mercados de movimiento lento y comprar menos en los mercados más volátiles. Pero, por supuesto, los mercados no van a mantener cortésmente el mismo nivel de volatilidad sólo para facilitar los cálculos. La volatilidad no es estática y por lo tanto su asignación tampoco puede serlo. La solución a esto es restablecer los tamaños de las posiciones de forma regular. Si no lo hace, perderá el control sobre el riesgo de la posición y eso provocará algunos picos desagradables en la curva de acciones. Hay dos formas en que el riesgo de posición real puede cambiar con el tiempo. La volatilidad del mercado puede cambiar y cambiará con el tiempo. Si desea aprovechar este modelo y mejorarlo, la primera orden del día probablemente debería ser reequilibrar y mantener los objetivos de riesgo. Comercio de tendencia contraria Quizás seguir tendencias no sea lo tuyo. Después de todo, a veces puede ser un tipo de estrategia bastante frustrante. Incluso si los resultados a largo plazo tienden a ser buenos, cualquiera que haya seguido una tendencia seria en el trading puede decirle cómo a menudo se siente como si pudiera ganar dinero haciendo exactamente lo contrario. Una cosa en particular que la mayoría de los seguidores de tendencias ven una y otra vez es cómo los precios vuelven a caer hasta los topes dinámicos, sólo para volver a la tendencia poco después de que usted se detenga. Por necesidad, los modelos que siguen tendencias deben dejar de funcionar en algún momento. Es algo inevitable para ese tipo de estrategia. Puede pensar que la solución es simplemente alejar la distancia del trailing stop de los modelos de tendencia, pero no es tan fácil. Esto dará como resultado un modelo de tendencia a más largo plazo y seguirá teniendo el mismo problema en un horizonte temporal diferente. Machine Translated by Google Se trata de un fenómeno de mercado bastante interesante y fácilmente observable. Como de costumbre, sólo podemos especular sobre por qué sucede esto y no importa qué tan buena explicación se nos ocurra, nunca podremos estar seguros de si es la verdadera razón o no. A menos que instalemos dispositivos de escucha en los hogares y lugares de trabajo de todos los comerciantes del mundo, simplemente no podemos saber por qué los participantes del mercado reaccionan de la forma en que lo hacen, y parecería como si Amazon Alexa ya estuviera muy por delante de nosotros en ese juego. Pero la incapacidad de verificar o incluso hacer uso práctico de una teoría del origen de un fenómeno de mercado no debería impedirnos fabricar alguna. Un modelo comercial exitoso a menudo tiene una buena historia detrás. A veces, esa historia puede ayudar a generar confianza en el enfoque y, a veces, puede ayudar a recaudar los activos necesarios para comercializarlo. A menudo, un modelo comercial sólido comienza con esa teoría, con una idea amplia de lo que se quiere aprovechar y por qué. Por sorprendente que parezca, en realidad puede ser útil si hay una razón lógica para sus operaciones. En este caso, se nos ocurre una posible explicación. La industria que sigue tendencias alguna vez fue una variante comercial de nicho que pocos tomaban muy en serio. A medida que el éxito de esta estrategia se volvió difícil de ignorar, atrajo a cantidades cada vez mayores de nuevos comerciantes e inversores. En este momento, se gestionan más de un cuarto de billón de dólares con este tipo de estrategias. Sería razonable suponer que este tipo de tamaño tiene un impacto grave en el mercado. Los seguidores de tendencias alguna vez fueron seguidores pasivos del mercado. Ahora bien, se podría argumentar que no se limitan a seguir los movimientos de los precios del mercado, sino que los crean. Contrariamente a la percepción pública, ser lo suficientemente grande como para mover los precios del mercado rara vez es una ventaja. No es que puedas chasquear los dedos y bajar los precios antes de querer comprar. Es más probable que desee comprar, pero a medida que comienza a hacerlo, el tamaño de sus órdenes eleva los precios y le otorga un precio de ejecución significativamente peor. Puede ser un verdadero problema tener demasiado dinero en este negocio. Si por casualidad David Harding estuviera leyendo este libro, estoy perfectamente dispuesto a ser un buen amigo y ayudar haciéndome cargo de algunos de esos activos, Dave. En mi primer libro, demostré que la mayoría de los fondos de cobertura que siguen tendencias emplean estrategias muy similares. Esto implicaría que todos tienen mecanismos similares a los trailing stop loss. Si esto es cierto, significaría que todos cotizan en momentos muy similares. Machine Translated by Google Si tuviéramos una buena tendencia alcista en el mercado del petróleo, por ejemplo, sería razonable suponer que la mayoría, si no todos, los seguidores de la tendencia están en largo. Ahora imagine que el precio del petróleo comienza a bajar por cualquier motivo. En algún momento, los seguidores de la tendencia comienzan a salir. Y eso puede hacer que el precio baje un poco más, tal vez incluso provocando paradas para otros seguidores de la tendencia. Dado el gran tamaño de la tendencia que sigue al capital, esto puede crear una situación en la que una pequeña corrección del mercado se vea amplificada por sus órdenes de stop loss. Esto podría resultar en dos cosas. Primero, que el mercado retrocede más de lo que lo haría de otra manera. En segundo lugar, cuando los seguidores de la tendencia dejen de detenerse, podría haber un retroceso, ya que los precios habían sido deprimidos artificialmente por las paradas. Quizás podríamos modelar esto y ver si hay algún dinero en explotar tal fenómeno. Lógica del modelo de contador La idea general aquí es invertir la lógica de los modelos de seguimiento de tendencias. Lo que queremos hacer es descubrir más o menos dónde terminan los modelos de tendencia y luego ingresar posiciones en ese momento. Este modelo de estilo de reversión a la media intentará entrar durante esos retrocesos, apostando a que el precio vuelva a subir. Claramente, en este tipo de modelo, el momento de entrada es el elemento más importante. Al explorar ideas comerciales, a menudo puede resultar útil intentar aislar la parte de la estrategia que parece más importante, como prueba inicial. Eso es lo que vamos a intentar hacer aquí. Vamos a explorar si existe un valor predictivo para entrar en el tipo de retrocesos en los que los seguidores de tendencias suelen detenerse. Como ocurre con todos los modelos comerciales de este libro, quiero enfatizar una vez más que estoy tratando de enseñar y demostrar conceptos. Estos no son modelos de producción y no están destinados a serlo. El punto aquí es que usted aprenda a construir modelos, y darle reglas exactas para copiar e intercambiar frustraría en gran medida ese propósito. Estudie estos modelos, aprenda de ellos y construya sus propios modelos de producción. Los parámetros no están optimizados ni seleccionados para mostrar el mejor resultado posible. Son, de forma bastante deliberada, unas configuraciones intermedias. Se eligen más o menos al azar, entre una gama de valores razonables. Machine Translated by Google Entonces, habiendo dejado eso de lado, ahora eche un vistazo a las reglas de entrada. Sólo buscamos comprar caídas en los mercados alcistas. En mi opinión, la dinámica de los repuntes del mercado bajista tiende a ser tan diferente de la de las caídas del mercado alcista que no podemos utilizar la misma lógica, o al menos no los mismos escenarios. Para mantener las cosas razonablemente simples, nos centraremos en las caídas del mercado alcista. Eso significa que necesitamos saber si hay un mercado alcista. Necesitamos definir qué es un mercado alcista. Definiré aquí un mercado alcista como cuando la media móvil exponencial de 40 días está por encima de la media móvil exponencial de 80 días. Los promedios móviles exponenciales son bastante populares entre los analistas técnicos porque se dice que reaccionan más rápido. Lo que hacen es ponderar las observaciones recientes en comparación con las más antiguas. Lo uso aquí, no porque sea mejor, sino porque puede ser una herramienta útil y, tarde o temprano, alguien preguntará cómo calcular la media móvil exponencial en Python. Figura 17 1 Media móvil exponencial Sin embargo, hay algo muy importante que entender cuando se trabaja con medias móviles exponenciales. Esta es una fuente clásica de confusión con muchos indicadores de análisis técnico. La vieja pregunta de por qué su media móvil exponencial no es la misma que la mía. Si bien puede parecer que una media móvil exponencial de 40 días sólo utiliza y necesita 40 cierres diarios, ese no es el caso. La ponderación exponencial comienza con el valor anterior, y eso significa que en realidad nunca elimina puntos de datos. Simplemente los reduce al olvido con el tiempo. Machine Translated by Google Lo que es importante entender aquí es que obtendrá un valor ligeramente diferente para un promedio móvil exponencial de 40 días si lo aplica a un historial de series temporales de medio año para un mercado, que si lo aplica a un historial de diez años para el mismo mercado. Supongo que lo que intento decir aquí es que por favor absténganse de enviarme correos electrónicos explicando por qué su EMA es un poco diferente a la mía. Cuantificar los retrocesos Una vez que sepamos que estamos en un mercado alcista, buscaremos retrocesos. Una manera fácil de cuantificar los retrocesos es medir la distancia desde un máximo reciente, pero esto debe ponerse en algún tipo de contexto. Claramente no tendría sentido activar una operación ante un retroceso de diez dólares, por ejemplo, ya que un retroceso de diez dólares en el oro es bastante diferente del mismo en la gasolina. Tampoco tendrían sentido los retrocesos porcentuales, dada la volatilidad muy variable en los mercados de futuros. No, lo que debemos hacer es estandarizar el retroceso a la volatilidad de cada mercado. Si lo piensas bien, ya tenemos una buena herramienta para esto. Hemos estado utilizando la desviación estándar como indicador de la volatilidad en modelos anteriores con el fin de dimensionar las posiciones. No hay nada que nos impida reutilizar esta analítica para estandarizar los retrocesos. Entonces, en este modelo usaremos una desviación estándar de 40 días de los cambios de precios tanto para propósitos de dimensionamiento de posiciones como para retrocesos. Un retroceso se medirá como la diferencia entre el precio actual y el cierre más alto de los últimos veinte días, dividida por la desviación estándar. Eso nos dirá a cuántas desviaciones estándar estamos del precio más alto en aproximadamente un mes. Esto da como resultado un análisis que se puede comparar entre mercados. Podrías utilizar el mismo proceso para acciones, bonos, materias primas y otros mercados, ya que tiene en cuenta la volatilidad. En este modelo, compraremos para abrir si estamos en un mercado alcista, como lo definen los promedios móviles descritos anteriormente, y vemos un retroceso de tres veces la desviación estándar. En el código Python, calculamos este retroceso con solo unas pocas líneas. El siguiente fragmento es del código fuente del modelo, que encontrará completo más adelante en este capítulo. Como ves, este código primero comprueba si tenemos una tendencia positiva. Si es así, calculamos la diferencia entre el último valor de cierre y el más alto de hace 20 días, la variable high_window , y lo dividimos por la desviación estándar. Machine Translated by Google if h['trend'].iloc[­1]: pullback = ( h['close'].values[­1] ­ np.max(h['close'].values[­high_window:]) ) / enfermedad de transmisión sexual En cuanto a la salida, vamos a utilizar una lógica realmente simple, sólo para demostrar la idea de entrada. Quiero mostrar que lo importante en este tipo de modelo es la entrada. Eso no significa que todo lo demás carezca de importancia, pero un modelo de estilo de reversión a la media como este depende en gran medida de una lógica de entrada sólida. Ese no es el caso de muchos otros tipos de estrategias. Para ver si hay algún valor predictivo en el método de entrada descrito, vamos a utilizar dos criterios de salida simples. En primer lugar, si la tendencia definida por las dos medias móviles se vuelve bajista, saldremos al día siguiente. En segundo lugar, si eso no sucede, mantendremos la posición durante 20 días hábiles, aproximadamente un mes. Luego salimos. Probablemente se esté preguntando por qué no hay un punto de stop loss ni un precio de salida objetivo aquí. Es muy posible que esas cosas tengan sentido y te animo a que las pruebes. El objetivo aquí es enseñarle conceptos y brindarle ideas para futuras investigaciones. Replica lo que se muestra aquí, pruébalo, modifícalo y conviértelo en tu propio. Resumen de reglas Se permiten posiciones largas si la media móvil exponencial de 40 días está por encima de la media móvil exponencial de 80 días. Si el precio en un mercado alcista cae tres veces su desviación estándar del precio de cierre más alto de los últimos 20 días, compramos para abrir. Si la tendencia se vuelve bajista, salimos. Si la posición se mantiene durante 20 días hábiles, salimos. El tamaño de la posición es la paridad de volatilidad, basada en la desviación estándar. Código fuente de tendencia contraria En el modelo anterior, el modelo de retorno en el tiempo, aprendimos cómo hacer un gráfico que se actualiza dinámicamente durante el backtest, mostrando el valor de la cartera a medida que se calcula. Esta vez lo mantendré, pero también agregaré un gráfico actualizado para mostrar cómo cambia la exposición con el tiempo. importar tirolesa desde zipline.api importar futuro_symbol, \ set_commission, set_slippage, función_programación, reglas_fecha, \ Machine Translated by Google time_rules, continuo_future, order_target de fecha y hora importar fecha y hora importar pytz importar pyfolio como pf importar pandas como pd importar numpy como np desde zipline.finance.commission importe PerTrade, PerContract desde zipline.finance.slippage importe Fixedslippage, VolatilityVolumeShare # Estas líneas son para los informes de texto dinámico de IPython.display import display import ipywidgets as widgets out = widgets.HTML() display(out) """ Configuración del modelo """ cartera_inicial = 20000000 vola_window = 40 slow_ma = 80 fast_ma = 40 factor_de_riesgo = 0.0015 ventana_alta = 20 días_para_mantener = 20 dip_buy = ­3 def report_result(contexto, datos): contexto.meses += 1 hoy = zipline.api.get_datetime().date() # Calcular el rendimiento anualizado hasta el momento ann_ret = np.power(context.portfolio.portfolio_value / Starting_portfolio, 12 / contexto.meses) ­ 1 # Actualizar el texto out.value = """{} Hemos negociado <b>{}</b> meses y el rendimiento anualizado es <b>{:.2%}</b>""".format( hoy, contexto.meses, ann_ret) def inicializar (contexto): """ Configuración de costos """ context.enable_commission = Verdadero context.enable_slippage = Verdadero si contexto.enable_commission: comm_model = PorContrato(costo=0,85, tarifa_intercambio=1,5) más: comm_model = PorTrade(costo=0,0) set_commission(us_futures=comm_model) si context.enable_slippage: slippage_model=VolatilityVolumeShare(volume_limit=0.3) else: slippage_model=Fixed Slippage(spread=0.0) Machine Translated by Google set_slippage(us_futures=modelo_deslizamiento) agrícola = [ 'LICENCIADO EN DERECHO', 'CC', 'CONNECTICUT', 'FC', 'KC', 'LB', 'LR', 'DO', 'RR', '_S', 'SB', 'LC', 'LS', ] no agrícola = [ 'CL', 'GC', 'HG', 'HO', 'LG', 'PENSILVANIA', 'PL', 'RB', 'SI', 'NG', 'LO', ] monedas = [ 'ANUNCIO', 'BP', 'CD', 'CU', 'DX', 'NORDESTE', 'SF', 'JY', ] acciones = [ 'ES', 'NK', 'NQ', 'YM', ] tarifas = [ 'DE', 'FV', 'TU', 'TY', 'A NOSOTROS', ] mercados = agrícola + no agrícola + monedas + acciones + tipos Machine Translated by Google contexto.universo = \ [continuo_futuro(mercado, compensación=0, roll='volumen', ajuste='mul') \ para mercado en mercados ] # Diccionario utilizado para realizar un seguimiento de cuántos días ha estado abierta una posición. context.bars_held = {market.root_symbol: 0 para el mercado en context.universe} # Programar operaciones diarias Schedule_function(daily_trade, date_rules.every_day(), time_rules.market_close()) # Simplemente usaremos esto para el resultado del progreso # durante la prueba retrospectiva. No impacta nada. contexto.meses = 0 # Programar la salida del informe mensual función_programa( func=report_result, date_rule=date_rules.month_start(), time_rule=time_rules.market_open() ) def roll_futures(contexto, datos): open_orders = zipline.api.get_open_orders() para contrato_retenido en contexto.cartera.posiciones: # no descarte posiciones que están configuradas para cambiar según la lógica central si se mantiene_contrato en open_orders: continuar # Ahorre algo de tiempo verificando solo las listas de # contratos que vencen en la próxima semana días_para_auto_cerrar = (held_contract.auto_close_date.date() ­ data.current_session.date() ).días si días_para_auto_cerrar > 5: continuar # Hacer una continuación continuación = continuo_futuro (hold_contract.root_symbol, offset=0, roll='volume', ajuste='mul') # Obtener el contrato actual de la continuación continuación_contrato = data.current(continuación, 'contrato') Machine Translated by Google if continuation_contract !=held_contract: # Comprobar cuántos contratos tenemos pos_size = context.portfolio.positions[held_contract].amount # Cerrar la posición actual order_target(held_contract, 0) # Abrir nueva posición order_target(continuation_contract, pos_size) def tamaño_posición(valor_cartera, estándar, pv): target_variation = valor_cartera * factor_riesgo variación_contrato = std * pv contratos = variación_objetivo / variación_contrato # Número de retorno redondeado hacia abajo. return int(np.nan_to_num(contratos)) def daily_trade(contexto, datos): open_pos = {pos.root_symbol: pos para pos en context.portfolio.positions} hist = data.history( contexto.universo, campos=['cerrar', 'volumen'], frecuencia='1d', bar_count=250, ) # Calcular la tendencia hist['trend'] = hist['close'].ewm(span=fast_ma).mean() > hist['close'].ewm(span=slow_ma).mean() para continuar en context.universe: root = continuación.root_symbol # Cortar el historial de este mercado h = hist.xs(continuación, 2) # Calcular la volatilidad std = h.close.diff()[­vola_window:].std() si es root en open_pos: # Verifique primero las posiciones abiertas. context.bars_held[root] += 1 # Un día más retenido if context.bars_held[root] >= 20: # Retenido por un mes, contrato de salida = open_pos[root] order_target(contract, 0) elif h['tendencia'].iloc[­1] == Falso: # Tendencia cambiada, salir. contrato = open_pos[raíz] order_target(contrato, 0) Machine Translated by Google else: # Comprobar si hay nuevas entradas si h['trend'].iloc[­1]: # Calcular el retroceso pullback = ( h['close'].values[­1] ­ np.max(h['close'].values[­high_window:]) ) / std si retroceso < dip_buy: # Obtener el contrato actual contrato = datos.actual(continuación, 'contrato') # Calcular el tamaño de los contratos_a_trade = position_size( \ contexto.portfolio.portfolio_value, \std, \contrato.price_multiplier) # Comerciar order_target(contrato, contratos_para_comerciar) # Restablecer el recuento de barras a cero context.bars_held[root] = 0 # Comprueba si necesitamos rodar. si len(open_pos) > 0: roll_futures(contexto, datos) inicio = fecha y hora (2001, 1, 1, 8, 15, 12, 0, pytz.UTC) fin = fecha y hora (2018, 12, 31, 8, 15, 12, 0, pytz.UTC) rendimiento = zipline.run_algorithm (inicio = inicio, fin = fin, inicialización = inicialización, capital_base = cartera_inicial, frecuencia_datos = 'diario', paquete = 'futuros') Resultados de contratendencia Una vez más vemos que un conjunto simple de reglas puede mostrar resultados bastante buenos, siempre que exista una premisa subyacente sólida. Hay algunos meses negativos preocupantes, como se ve en la Tabla 17.1, pero dada la lógica de parada inestable en este modelo de demostración, eso no debería ser demasiado sorprendente. Lo que vemos es una estrategia que puede volverse bastante volátil en ocasiones, pero que ha generado fuertes retornos ajustados al riesgo a lo largo del tiempo. Machine Translated by Google Nada mal para un modelo tan sencillo. La conclusión aquí es que parece haber un valor en este tipo de lógica de entrada. La conclusión no es que deba acudir a su corredor y apostar todo su dinero en esto, sino que podría ser un tema de investigación interesante para desarrollar más. Si esta lógica simple puede generar retornos bastante interesantes, seguramente puedes mejorarla, adaptarla a tus propias necesidades, agregarle algunas características y crear un modelo de grado de producción adecuado. Tabla 17.1 ­ Rentabilidades Mensuales Contra Tendencia Año Ene Feb Mar Abr May Jun Jul Ago Sep Oct Nov Dic Año 2001 ­2,0 ­1,1 +1,2 ­2,0 ­1,5 ­1,3 +1,3 ­2,1 +3,8 +3,1 ­1,0 ­1,4 ­3,3 2002 ­1,6 +4,6 ­2,5 +2,5 +4,3 +4,3 +1,9 +8,4 +2,9 +1,8 ­2,1 +11,3 +41,2 2003 +14,5 ­2,9 ­4,3 ­2,1 +0,4 ­0,8 ­0,6 +6,9 ­0,1 ­11,7 +9,6 +2,6 +9,3 2004 +5,8 +11,9 +0,6 ­12,9 ­2,1 ­2,6 +2,8 +2,5 +7,4 +3,2 ­0,9 +1,4 +16,1 2005 ­2,1 +6,0 +0,9 ­4,9 ­2,1 ­0,4 +0,5 +8,8 +5,8 ­1,5 +4,8 +1,3 +17,4 2006 +2,9 ­2,2 ­2,4 +4,3 ­3,7 ­5,4 +0,3 +1,0 ­1,1 ­0,7 +3,3 ­2,4 ­6,4 2007 ­0,9 +2,1 +1,8 +5,4 +0,1 +2,9 +1,1 ­5,6 +5,9 +14,6 ­4,8 +6,1 +30,9 2008 +9,2 +19,0 ­3,7 +4,5 +2,3 +5,3 ­4,8 ­9,5 ­2,1 ­1,6 +2,4 ­0,2 +19,3 2009 +0,1 ­1,7 +1,3 +0,1 +4,8 +0,1 +2,0 +3,5 +7,9 +8,7 +14,4 ­4,0 +42,2 2010 ­1,9 +7,0 ­2,1 +2,5 ­26,1 +1,5 +1,9 +2,6 +8,4 +12,2 ­5,9 +15,5 +9,2 2011 +6,8 +9,5 +0,5 +8,1 ­1,4 ­4,7 +7,7 ­4,8 ­11,5 +1,0 +3,2 ­0,8 +12,2 2012 +6,6 ­1,5 ­0,5 ­1,5 ­12,0 ­0,7 +6,4 ­0,2 +1,6 ­5,8 +0,5 +0,3 ­7,9 2013 +2,8 ­4,3 +1,8 +0,1 ­6,7 ­2,1 +3,3 +0,7 +3,8 +6,1 +0,4 +0,6 +5,9 2014 ­0,0 +9,2 +1,4 +7,2 +1,0 +9,2 ­8,0 +4,4 ­0,4 ­0,4 +1,6 ­2,1 +24,0 2015 +7,4 ­0,0 +0,3 ­0,1 +0,2 +2,1 ­4,3 ­5,0 +2,6 ­0,3 ­2,5 ­1,6 ­1,9 2016 ­5,7 +2,1 +3,4 +5,3 ­3,7 +12,4 ­0,4 ­4,1 +6,9 ­4,3 +1,6 ­1,3 +11,1 2017 +0,5 +4,3 ­2,5 +1,1 +1,6 +2,7 +5,1 +4,1 ­7,0 ­0,2 +6,0 +1,1 +17,2 La curva de acciones de la Figura 17­2 nos muestra una estrategia con un claro sesgo positivo. Suele tener una correlación bastante baja con los mercados de valores y, por lo general, tiene un rendimiento inferior durante los mercados alcistas a largo plazo. Sin embargo, eso no es necesariamente un problema, ya que muestra un sólido desempeño durante los mercados bajistas y un desempeño superior sustancial en el largo plazo. Machine Translated by Google Figura 17 2 Resultados de la simulación de contratendencia En la Tabla 17.2 puedes ver lo que habría pasado si hubieras iniciado esto estrategia en un año determinado y siguió operando con ella durante varios años a partir de ese momento. Esta puede ser una forma útil de obtener rápidamente una visión general de cómo funciona la mirada inicial. A veces, el tiempo puede ser muy importante para un modelo comercial. Los números muestran rendimiento anualizado sobre el número de años mostrados en el eje x. Si bien el rendimiento a largo plazo parece atractivo, este tipo de gráfico puede rápidamente muéstranos que hay años iniciales en los que quizás no hayas sido tan feliz. Si Si hubieras iniciado esta estrategia en 2011, solo habrías anualizado alrededor del 4% después de 8 años. Pero, por otro lado, si hubieras empezado en 2007, habría visto una ganancia anualizada del 16% después del mismo número de años. Tabla 17.2 Análisis del período de tenencia para el modelo de contratendencia Años 1 2 345 6 7 8 9 10 11 12 13 14 15 16 17 18 2001 ­3 +17 +14 +15 +15 +11 +14 +15 +17 +17 +16 +14 +13 +14 +13 +13 +13 +11 2002 +41 +24 +21 +20 +15 +17 +17 +20 +19 +18 +16 +15 +15 +14 +14 +14 +12 2003 +9 +13 +14 +9 +13 +14 +18 +16 +16 +13 +13 +14 +12 +12 +13 +10 2004 +16 +17 +8 +14 +15 +19 +18 +17 +14 +13 +14 +13 +12 +13 +10 2005 +17 +5 +13 +14 +20 +18 +17 +14 +13 +14 +12 +12 +13 +10 2006 ­6 +11 +14 +20 +18 +17 +13 +12 +13 +12 +12 +12 +9 Machine Translated by Google 2007 +31 +25 +30 +25 +22 +17 +15 +16 +14 +14 +14 +11 2008 +19 +30 +23 +20 +14 +12 +14 +12 +12 +12 +9 2009 +42 +25 +20 +13 +11 +13 +11 +11 +12 +8 2010 +9 +11 +4 +5 +8 +6 +7 +8 +5 2011 +12 +2 +3 +8 +6 +7 +8 +4 2012 ­8 ­1 +7 +4 +6 +8 +3 2013 +6 +15 +9 +9 +11 +5 2014 +24 +10 +11 +12 +5 2015 ­2 +4 +9 +1 2016 +11 +14 +2 2017 +17 ­2 2018­19 _ Este modelo mostró claramente rendimientos más bajos en los últimos años. El último año del backtest ciertamente tuvo un impacto, ya que terminó con alrededor del 15 por ciento de números rojos. Esto puede sorprender a algunos lectores, ya que los libros tienden a mostrar modelos que tuvieron un desempeño notablemente bueno durante todo el período, o al menos en los años más recientes. Habría sido fácil para mí modificar la configuración del backtest hasta lograr ese resultado, pero eso sería realmente contrario a las instrucciones de este libro. Si desea ajustar la curva a esta prueba retrospectiva, continúe y modifique la configuración hasta que obtenga los resultados que desea ver. Sólo tenga en cuenta que no es muy probable que resulte en ningún valor predictivo significativo. Las configuraciones utilizadas aquí no se eligen en función de los resultados de la prueba retrospectiva que arrojan, sino por su simetría con el modelo de tendencia mostrado anteriormente. La razón de esto es simplemente que hace que sea más fácil explicar el fundamento subyacente de este enfoque comercial. Si bien el ajuste de curvas es una mala idea, eso no significa que debas seguir adelante y cambiar estas configuraciones exactas tampoco. Lo que hay que hacer es pensar detenidamente sobre lo que se quiere lograr con un modelo. No se trata sólo de aspirar a obtener altos rendimientos. Es cuestión de encontrar el perfil de rentabilidad deseado, y eso no siempre significa la mayor rentabilidad posible. De hecho, muy rara vez significa eso. No, una forma mucho más interesante de ver un modelo de reversión a la media como este es como un componente de la cartera, negociado junto con modelos de seguimiento de tendencias. Pero me estoy adelantando. Más sobre eso más adelante. Operando con la curva Machine Translated by Google Cuando me propuse escribir este libro, mi objetivo principal era ampliar el horizonte de los comerciantes minoristas. Mostrar cosas de las que tal vez no sean conscientes, hacerles ver nuevas posibilidades y formas de trabajar que de otro modo no habrían pensado. Es de esperar que el concepto de este capítulo sirva de lección y muestre un tipo de negociación que muchos lectores probablemente no habían considerado. Lo que pretendo demostrar aquí es un método para negociar futuros sin utilizar ningún dato histórico. Sí, seguiremos utilizando el comercio algorítmico sistemático, pero no necesitaremos ningún historial de precios para la lógica comercial. Todo lo que necesitamos son los precios actuales. Calcularemos el coste de carry y lo utilizaremos como único dato para la selección de operaciones. Esto significa que debemos considerar no sólo el contrato inicial, sino también los contratos más lejanos. La mayoría de los modelos de negociación basados en futuros negocian sólo el contrato frontal y nada más. Para este modelo aquí, no cambiaremos el frente en absoluto, solo los puntos más alejados. El término que uso aquí es comercio de curvas, y así es como me refiero a este enfoque comercial. Para ser claros, lo que haremos aquí es negociar carry. El costo de transporte está implícito en la forma de la curva de estructura de plazos. Dependiendo de la terminología a la que esté acostumbrado, es posible que opine que operar en curva significa tomar posiciones compensadas en la curva, largas un mes y cortas otro. Se trata de un concepto muy relacionado y, si comprende el modelo presentado en este capítulo, podrá ampliarlo fácilmente a modelos de tipo calendario extendido, como suelen denominarse más adelante. Pero por ahora, simplemente estamos negociando carry. Conceptos básicos de la estructura de términos Algunos lectores ya saben exactamente hacia dónde me dirijo con esto, pero otros tal vez prefieran recibir primero un pequeño curso de actualización sobre estructuras de términos. Los contratos de futuros tienen una vida útil limitada. Hay una fecha en la que cada contrato deja de existir. Esto significa que no existe un solo contrato de futuros de oro, sino una cantidad bastante grande de ellos. En un momento dado, habrá un contrato específico que tenga la mayor liquidez. La mayoría de la gente negociaría el contrato que esté más cerca de expirar, siempre y cuando no haya pasado la fecha del primer aviso. Machine Translated by Google La primera fecha de notificación, generalmente un mes antes de su vencimiento, se refiere a la fecha en que un contrato se puede entregar y se puede pedir a alguien que aún lo tenga para que lo haga o lo reciba. Como comerciantes, realmente no queremos ser parte de eso, pero en realidad no es un riesgo. De todos modos, su corredor no le permitirá mantener posiciones abiertas después de esa fecha. La fecha de primer aviso es un concepto principalmente en futuros de materias primas, y ese es el único sector con el que negociaremos en este capítulo. Es en el sector de las materias primas donde el comercio de estructuras temporales resulta de mayor interés. La Figura 18­1 muestra cómo se veía la estructura temporal de la soja al momento de escribir este artículo. Como ocurre con cualquier dato financiero, esto, por supuesto, cambia constantemente. Esta figura muestra las fechas de vencimiento en el eje x y el precio correspondiente y el interés abierto para cada contrato. Estos son los contratos que se negocian actualmente, nuevamente al momento de escribir esto. Figura 18 1 Estructura de términos en Contango En este ejemplo, puedes ver que cada punto consecutivo de la curva está un poco más arriba que el anterior. Es decir, los contratos son más caros cuanto más tiempo falta para Lo su vencimiento. Esa situación se llama contango . contrario, si cada punto se abaratara, se llamaría forwardation , que es lo que se ve en la Figura 18­2. , Machine Translated by Google Figura 18 2 Gasoducto en Contango Los contratos más cercanos tienden a negociarse bastante cerca del activo subyacente. Cuanto menos tiempo queda para el vencimiento, más cercano tiende a estar el precio. La razón es simple. Al vencimiento, el valor del contrato es igual al valor del subyacente, ya que se liquidará por él. Pero cuando queda mucho tiempo, otros factores toman el control. Hay múltiples razones por las cuales el precio en el futuro es diferente, pero como traders cuantitativos rara vez necesitamos profundizar en estas razones. Los factores en juego incluyen las tasas de interés, el costo de almacenamiento y la estacionalidad. Lo que importa es cómo interpretar este patrón. He aquí una forma sencilla de pensar en ello. Si la estructura de plazos está en contango, como en la Figura 18­1, hay un sesgo bajista. ¿Por qué? Porque cuanto más se acerque el vencimiento de un contrato, más cerca estará el precio del subyacente. Entonces, si el subyacente permanece exactamente igual, cada punto de la curva de la Figura 18­1 tendría que moverse lentamente hacia abajo hasta que finalmente alcance el precio del activo subyacente. Por lo tanto, ocurre lo contrario con el forwardation, que tiene un sesgo alcista incorporado. El subyacente necesitaría moverse hacia abajo simplemente para que los puntos de la estructura de forwardation permanezcan igual. Si el subyacente no se mueve, los contratos deben subir para cumplirlo. Ahora bien, si entiendes la idea general, probablemente ya estés pensando en cómo cuantificar este efecto. Cuantificación del efecto de la estructura del término Machine Translated by Google Si bien existen diferentes formas de cuantificar la estructura de términos, demostrar una metodología que creo que tiene sentido intuitivo. Esto involucra calculando un rendimiento anual implícito, o el costo de transporte, por así decirlo. Necesita ser anualizado, de modo que el número resultante pueda compararse entre diferentes fechas de entrega. Como siempre en las finanzas, el tiempo importa. Si un contrato de tres meses de antelación Se negocia con un descuento del 2% que es más significativo que si fuera un contrato de 12 meses. out se comercializa con el mismo descuento del 2%. Así como es preferible hacer un cien dólares hoy que dentro de un año. Usaremos los mismos datos que para la Figura 18­1 en este ejemplo. primero lo haré Le mostraremos cómo calcular un punto manualmente y luego veremos cómo calcularlo. Haga esto fácilmente para toda la curva. El primer punto de la curva, el contrato más cercano, vence el 14 de marzo de 2019. Ese décimo de contrato, el SH9, se cotiza actualmente a 907,50 céntimos por almud. No, realmente no es necesario saber mucho sobre bushels para comercializar soja. El próximo contrato que sale es el SK9, que vence el 14 de mayo y se el mismo año, y eso es cotiza a 921,50 centavos por bushel. El SK9 caduca 61 días después del SH9. Si el subyacente, la soja al contado, no no se mueve en absoluto, el SK9 necesitaría moverse de 921,50 a 907,50 en el los próximos 61 días. Esto supone una pérdida del 1,52%. Una pérdida del 1,52% en 61 días, equivaldría a una pérdida anualizada del 8,75%. Esto nos da un número con el que podemos relacionarnos y comparar entre mercados y fechas de vencimiento. Disponemos de un rendimiento anual cuantificable y comparable número. Hacer este tipo de cosas rápidamente en una cadena de futuros es muy sencillo. Primero comenzaremos con un Pandas DataFrame estándar con los precios y fechas de caducidad. Los datos de la tabla 18.1 son los mismos que utilizamos para la estructura de términos. Figura justo antes, y muestra un mercado en contango. Si no te saltaste el Introducción a Python en la primera parte de este libro, no debería tener ningún problema . obtener estos datos en un Pandas DataFrame Tabla 18.1 Datos de estructura de términos expiración precio interes abierto 0 14 de marzo de 2019 907,50 295,414 1 14 mayo 2019 921.50 206,154 2 12 julio 2019 935.00 162.734 3 14 agosto 2019 940.25 14.972 4 13 de septiembre de 2019 943.50 7.429 Machine Translated by Google 5 14 de noviembre de 2019 952.00 6 14 de enero de 2020 961.50 75.413 7.097 Suponiendo que tiene la tabla anterior en un DataFrame llamado simplemente df , podrías obtener la analítica que buscamos paso a paso así. df['day_diff'] = (df['expiry'] ­ df.iloc[0]['expiry']) / np.timedelta64(1, 'D') df['pct_diff'] = (df.iloc[0].precio / df.precio) ­ 1 df['carry_anualizado'] = (np.power(df['pct_diff'] + 1, (365 / df.day_diff))) – 1 Como ya deberías haber descubierto Python en este punto, podrías Por supuesto, haz todo esto en una sola fila si lo deseas. La capacidad de Python para funcionar. múltiples operaciones complejas en una sola fila a veces pueden ser excelentes, pero también tiende a hacer que el código sea más difícil de seguir. Ejecutar este código y agregar estas columnas debería dar como resultado un DataFrame como el siguiente en la Tabla 18.2. Ahora tenemos algo útil. Esta es una tabla que podemos usar directamente para la lógica comercial. Tabla 18.2 Carry anualizado expiración precio open_interest día_diff pct_diff anualizado_carry 0 14 de marzo de 2019 907,50 295.414 0,00% 0,00% 0 1 14 mayo 2019 921,50 206.154 61 ­1,52% ­8,75% 2 12 julio 2019 935.00 162.734 120 ­2,94% ­8,68% 3 14 agosto 2019 940.25 14.972 153 ­3,48% ­8,11% 4 13 de septiembre de 2019 943.50 5 14 de noviembre de 2019 952.00 6 14 de enero de 2020 961.50 7.429 75.413 7.097 183 ­3,82% ­7,47% 245 ­4,67% ­6,88% 306 ­5,62% ­6,66% Una tabla como esta le indica en qué parte de la curva teóricamente obtendría el mejor retorno. Pero también hay que tener en cuenta la liquidez. A menudo lo harás encontrar que se puede obtener una ganancia teórica sustancial operando muy lejos en la curva, pero que simplemente no hay liquidez disponible. Pero en este caso, vemos que parece haber un rendimiento negativo de casi el 9%. en el contrato de mayo, que es la segunda entrega más cercana y parece haber suficiente liquidez para considerar. Esta lógica forma la base de lo que hará nuestro próximo modelo comercial. Lógica del modelo de curva Machine Translated by Google Vamos a hacer un modelo bastante simple, basado en la lógica que acabamos de describir. Vamos a negociar sólo con materias primas, y sólo con un conjunto de materias primas bastante líquidas. Esta no es una estrategia que pueda usarse en cualquier mercado, y de poco serviría probar la misma metodología en futuros de divisas o mercados de baja liquidez. En esta versión, intentamos encontrar un contrato dentro de un año en la curva para negociar. Es decir, un contrato que vence un año más tarde que el contrato frontal actual. Las operaciones solo se realizan una vez por semana, momento en el que repasamos cada mercado que cubrimos, identificamos el contrato dentro de un año y calculamos el carry anualizado. Habiendo calculado esto para todos los mercados que cubrimos, el modelo clasifica los mercados según el carry y elige los 5 principales forwardation para ir en largo y los 5 principales contango para ir en corto. De forma predeterminada, intentamos negociar un monto nocional igual de cada contrato. Quizás se pregunten por qué, después de mis anteriores peroratas sobre este libro sobre el valor de la asignación de paridad de volatilidad. Dos razones. Primero, pensé que tal vez ya había confundido bastante a los lectores con esta forma tan diferente de construir un modelo y quiero mantener la lógica simple y fácil de seguir. Y en segundo lugar, para este enfoque en particular, no supondrá una gran diferencia. Sólo operamos con una clase de activos y las diferencias de volatilidad no serán enormes. A diferencia de otros modelos de este libro, ahora nos enfrentamos a mercados de liquidez potencialmente baja, y eso requiere una consideración adicional. Incluso si el contrato frontal de oro se negocia intensamente, el contrato dentro de un año probablemente no lo será. Esto significa que debemos asegurarnos de que nuestro modelo, o más bien nuestro backtester, pueda manejarlo de manera realista. Simplemente no podemos asumir que nos llenaremos abiertos en cualquier tamaño. Afortunadamente, Zipline es bastante bueno en esto y el modelo utilizará una condición de deslizamiento para garantizar que nunca negociemos más del 20% del volumen diario. Luego, el modelo tomará un máximo del 20% del volumen y dejará el resto de la orden abierta para el siguiente día de negociación. Los pedidos grandes se distribuirán a lo largo de varios días, cuando sea necesario. Pero también pondré una condición de liquidez adicional, para estar seguro. Considere si, por ejemplo, el contrato de café dentro de un año tiene un volumen diario promedio de 100 contratos y su modelo decide que es una buena idea realizar un pedido de 1000 contratos. La lógica de Zipline modelará los llenados cada día, y es posible que le lleve un par de semanas o más llenarse por completo. Tal vez sea realista, pero eso no es lo que queremos ver aquí. Machine Translated by Google Por esa razón, pondremos la condición de verificar el volumen diario promedio del contrato que queremos negociar y nos abstendremos de realizar cualquier orden superior al 25% de ese volumen. A veces todavía puede tomar un par de días llenarse, pero no debería tomar más. Esta condición limitará el tamaño máximo de los pedidos. Como verá en el código fuente, la versión predeterminada que le muestro aquí intentará generar una exposición de 150% larga y 150% corta, para una exposición neta cercana a cero. Por supuesto, no se puede esperar ser “neutral en delta” teniendo, por ejemplo, una posición corta en soja que compense una posición larga en petróleo crudo. El término delta neutral proviene de los mercados de opciones y significa que, al menos en teoría, carece de exposición direccional a los mercados. No espere una cobertura perfecta de ningún tipo, pero, por supuesto, ayuda un poco con el riesgo general tener una parte larga y otra corta. En definitiva, el nivel de exposición bruta del 300% no es desproporcionado para un modelo de futuros razonablemente diversificado. Como anteriormente, todas las configuraciones principales están en la parte superior del código, por lo que puede probar fácilmente sus propias variaciones una vez que haya configurado su entorno de modelado Python local. Código fuente de comercio de curvas A estas alturas ya deberías estar bastante familiarizado con la estructura general del código de prueba retrospectiva de Zipline. Primero te mostraré las partes clave de este modelo en particular y luego, como de costumbre, al final de esta sección encontrarás el código fuente completo. Primero, la configuración del modelo. # configuración spread_months = 12 pos_per_side = 5 target_exposure_per_side = 1,5 inicial_portfolio_millones = 1 volumen_order_cap = 0,25 Con estas configuraciones podemos cambiar un poco las cosas y probar variaciones fácilmente. , s, establece qué tan lejos de la curva nos dirigimos. En el caso La primera configuración, spread_month predeterminado, buscamos contratos con un mes de anticipación. La segunda configuración, pos_per_sid e , Por ahora, ¿en cuántos mercados queremos estar en largo y en cuántos queremos estar en corto? En la implementación actual, este número establece posiciones tanto largas como cortas. Machine Translated by Google , e establece el porcentaje de exposición por lado. Con La siguiente configuración, target_exposure_per_sid el valor predeterminado de 1,5, estamos intentando crear una exposición del 150 % a largo plazo y del 150 % a corto plazo. A continuación, establecemos con cuántos millones queremos comenzar y, por último, establecemos un límite de pedido por volumen. Esa última configuración puede ser bastante importante para este modelo en particular. Operar con estos contratos menos líquidos con un vencimiento más largo significa que debemos tener cuidado con la liquidez. No podemos comerciar demasiado y siempre debemos asegurarnos de que haya suficiente capacidad. Con el valor predeterminado de 0,25 para volume_order_ca p , nuestro código comercial limitará las órdenes al 25% del volumen diario promedio. Esto asegurará que no intentemos colocar órdenes mucho más grandes de lo que el mercado puede manejar y terminemos pasando semanas tratando de ejecutarlas. La lógica comercial se realiza una vez a la semana, que es el único momento en el que ajustamos la composición de nuestra cartera. Dado que aquí estamos negociando contratos específicos y no tratando de quedarnos con el contrato frontal como es común, no hay necesidad de lógica de rollo. Veamos la lógica paso a paso, para ver qué operaciones se realizan cada mes para este modelo. Primero simplemente creamos un DataFrame vacío , usando la lista de mercados en nuestro universo de inversión. En un momento completaremos este DataFrame con nuestro análisis de carry calculado. defweekly_trade(context, data): # Marco de datos vacío para completar más tarde. carry_df = pd.DataFrame (índice = contexto.universo) Una vez que tengamos ese DataFrame vacío , Vamos a recorrer todos los mercados que cubrimos, obtener el contrato frontal y el contrato dentro de un año, calcular el carry y . Aquí está, paso a paso. almacenarlo en el DataFrame . Esta primera parte inicia el ciclo, obtiene la cadena de contrato y la transforma en un DataFrame con contratos y fechas de vencimiento correspondientes. para continuar en context.universe: # Obtener la cadena cadena = datos.cadena_actual(continuación) # Transformar la cadena en marco de datos df = pd.DataFrame(index = chain) para contrato en cadena: df.loc[contract, 'future'] = contract df.loc[contract, 'expiration_date'] = contract.expiration_date Machine Translated by Google A continuación localizamos el contrato más cercano a nuestra fecha objetivo. Como la configuración predeterminada aquí es negociar con 12 meses de anticipación, buscamos un contrato que venza en un año. Si bien es bastante probable que encontremos un contrato dentro de 12 meses, no es seguro que todos los mercados tengan un contrato dentro de 3 o 9 meses y, por lo tanto, esta lógica garantiza que el modelo pueda manejar tales variaciones. fecha_de_expiración_más cercana = df.iloc[0].fecha_de_expiración fecha_de_expiración_objetivo = fecha_de_expiración_más cercana + relativadelta(meses=+meses_diferenciados) df['días_para_objetivo'] = abs(df.fecha_de_expiración ­ fecha_de_expiración_objetivo) contrato_objetivo = df.loc[df.días_para_objetivo == df.días_para_objetivo .min()] , El contrato que ubicamos allí, target_contrac t es el que vence más cerca de lo que estamos buscando. La lógica anterior primero verifica qué fecha exacta es X meses desde el vencimiento del contrato frontal, donde X es 12 de forma predeterminada. Luego hacemos una columna para todos los contratos de la cadena que muestra cuántos días de diferencia tienen hasta esa fecha objetivo. Finalmente, elegimos el contrato con la menor diferencia. Ahora necesitamos obtener los últimos precios de cierre para el contrato frontal y el contrato objetivo, y calcular el carry anualizado. # Obtener precios para el contrato frontal y los precios del contrato objetivo = data.current( [ df.index[0], target_contract.index[0] ], 'cerrar' ) # Verifique la diferencia de días exacta entre los contratos days_to_front = int( (target_contract.expiration_date ­ close_expiration_date)[0].days ) # Calcular el carry anualizadoanualized_carry = (np.power((prices[0]/prices[1]), (365/ days_to_front))) ­ 1 Si ha prestado atención hasta ahora al libro, es posible que haya notado que la fórmula para calcular el rendimiento anualizado le resulta un poco familiar. Por supuesto, se trata de la misma lógica que utilizamos para calcular las puntuaciones de impulso anualizadas en el capítulo 12. Ahora tenemos los datos que necesitamos para completar el DataFrame que creamos al principio. Para comparar todos los mercados y elegir las situaciones de carry más atractivas, necesitamos saber cuál es el contrato frontal, cuál es el contrato objetivo y cuál es el carry anualizado. Machine Translated by Google carry_df.loc[continuación, 'front'] = df.iloc[0].future carry_df.loc[continuación, 'next'] = target_contract.index[0] carry_df.loc[continuación, 'carry'] = transporte_anualizado Ahora ordénelos y elimine las filas potencialmente vacías. # Ordenar por acarreo carry_df.sort_values('carry', inplace=True, ascending=False) carry_df.dropna(inplace=True) Este objeto DataFrame , llamado carry_df en el código, ahora contiene los análisis que necesitamos para decidir qué intercambiar. Ahora podemos comenzar eligiendo los cinco contratos superiores e inferiores, los que deberíamos ser largos y cortos. # Selección de contrato para i en np.arange(0, pos_per_side): j = ­(i+1) # Comprar arriba, abajo corto long_contract = carry_df.iloc[i].next short_contract = carry_df.iloc[j].next new_longs.append(contrato_largo) new_shorts.append(contrato_corto) Lo que hace este código es recorrer los números del 0 al 4, seleccionando cinco contratos de la parte superior y cinco de la parte inferior del mazo. Ahora que sabemos qué contratos queremos que sean largos y cuáles queremos que sean cortos, todo lo que tenemos que hacer es calcular cuánto de cada uno negociar y luego ejecutar. Para resolver esto, necesitamos obtener algunos datos históricos. Como dijimos anteriormente, vamos a limitar nuestros pedidos al 25% del volumen diario promedio, por lo que obviamente necesitamos saber cuál es el volumen diario promedio. # Obtener datos para la nueva cartera new_portfolio = new_longs + new_shorts hist = data.history(new_portfolio, campos=['close','volume'], frecuencia='1d', bar_count=10,) También dijimos que estamos haciendo una asignación ponderada equitativa. Si está dispuesto a hacer pequeños retoques, la parte de asignación es algo con lo que quizás quiera trabajar más por su cuenta. # Target_weight simple igual ponderado = ( target_exposure_per_side * context.portfolio.portfolio_value ) / pos_per_side Machine Translated by Google En este punto, estamos listos para recorrer los mercados que queremos mantener durante la próxima semana. Esto es lo que vamos a hacer. Para cada mercado, calculamos los contratos objetivo a mantener, aplicamos un límite basado en el volumen promedio y ejecutamos. # Negociar por contrato en new_portfolio: # Historial de sectores para contrato h = hist.xs(contrato, 2) # Igual ponderación, con límite basado en el volumen. contratos_a_trade = peso_objetivo / \ contrato.precio_multiplicador / \ h.close[­1] # Límite de tamaño de posición contract_cap = int(h['volume'].mean() * volume_order_cap) # Limite el tamaño de la operación al límite de tamaño de la posición. contratos_para_comerciar = min(contratos_para_comerciar, contratos_cap) # Posición negativa para cortos si contrato en new_shorts: contratos_para_comerciar *= ­1 # Ejecutar order_target(contrato, contratos_para_comerciar) Eso deja sólo una última parte de la limpieza. ¿Puedes pensar en lo que no hicimos todavía? Analizamos los mercados, seleccionamos contratos, construimos una nueva cartera, calculamos el tamaño de las posiciones y negociamos. Pero aún no hemos cerrado las antiguas posiciones. Lo único que queda es recorrer todas las posiciones abiertas y cerrar aquellas que no forman parte de la cartera de la próxima semana. # Cerrar cualquier otra posición abierta para pos en context.portfolio.positions: si pos no está en new_portfolio: order_target(pos, 0.0) Al igual que con los modelos anteriores, mostraré el código completo del modelo utilizado a continuación. Como verá, utilicé el gráfico de actualización dinámica durante la ejecución de la simulación, como vimos anteriormente. Esta vez, agregué un gráfico de reducción, solo para mostrar cómo se hace. cuaderno% matplotlib importar tirolesa Machine Translated by Google desde zipline.api importe símbolo_futuro, \ set_commission, set_slippage, función_programación, \ reglas_fecha, reglas_tiempo, futuro_continuo, destino_pedido desde fecha y hora importar fecha y hora importar pytz importar matplotlib.pyplot como plt importar pyfolio como pf importar pandas como pd importar numpy como np desde zipline.finance.commission importe PerTrade, PerContract desde zipline.finance.slippage importe Fixedslippage, VolatilityVolumeShare # Usaremos esto para encontrar una fecha futura, con X meses de antelación. desde dateutil.relativedelta importar relativodelta # configuración spread_months = 12 pos_per_side = 5 target_exposure_per_side = 1,5 inicial_portfolio_millones = 1 volumen_order_cap = 0,25 # DataFame para almacenar y actualizar los datos que queremos graficardynamic_results = pd.DataFrame() fig = plt.figure(figsize=(10, 6)) ax = fig.add_subplot(211) ax.set_title('Comercio de curvas') ax2 = fig.add_subplot(212) ax2.set_title('Drawdown') def inicializar (contexto): """ Configuraciones de fricción """ context.enable_commission = Verdadero context.enable_slippage = Verdadero si context.enable_commission: comm_model = PerContract(coste=0.85, exchange_fee=1.5) else: comm_model = PerTrade(coste=0.0) set_commission(us_futures=comm_model) si context.enable_slippage: slippage_model=VolatilityVolumeShare(volume_limit=0.3) else: slippage_model=Fixed Slippage(spread=0.0) set_slippage(us_futures=modelo_deslizamiento) """ Mercados para comerciar """ Machine Translated by Google most_liquid_commods = [ 'CL','HO','RB','NG','GC','LC','_C','_S','_W','SB', 'HG', 'CT', 'KC ' ] context.universe = [continue_future(market, offset=0, roll='volume', ajuste='mul') para el mercado en most_liquid_commods] función_programación(comercio_semanal, reglas_fecha.inicio_semana(), reglas_hora.mercado_cierre()) función_programación(update_chart,date_rules.month_start(), time_rules.market_close()) def update_chart(contexto,datos): # Esta función actualiza continuamente el gráfico durante el backtest today = data.current_session.date() pv = context.portfolio.portfolio_value exp = context.portfolio.positions_exposuredynamic_results.loc[today, 'PortfolioValue'] = pv reducción = (pv /dynamic_results['PortfolioValue'].max()) ­ 1 exposición = exp / pvdynamic_results.loc[today, 'Drawdown'] = reducción si ax.lines: ax.lines[0].set_xdata(dynamic_results.index) ax.lines[0].set_ydata(dynamic_results.PortfolioValue) ax2.lines[0].set_xdata(dynamic_results.index) ax2.lines[0] .set_ydata(dynamic_results.Drawdown) más: ax.plot(resultados_dinámicos.PortfolioValue) ax2.plot(resultados_dinámicos.Drawdown) ax.set_ylim( resultados_dinámicos.PortfolioValue.min(), resultados_dinámicos.PortfolioValue.max() ) ax.set_xlim( resultados_dinamicos.index.min(), resultados_dinamicos.index.max() ) ax2.set_ylim( resultados_dinámicos.Drawdown.min(), resultados_dinámicos.Drawdown.max() ) ax2.set_xlim( resultados_dinámicos.index.min(), resultados_dinámicos.index.max() ) figura.canvas.draw() Machine Translated by Google defweekly_trade(context, data): # Marco de datos vacío para completar más tarde. carry_df = pd.DataFrame (índice = contexto.universo) para continuar en context.universe: # Obtener la cadena cadena = datos.cadena_actual(continuación) # Transformar la cadena en marco de datos df = pd.DataFrame(index = chain) para contrato en cadena: df.loc[contract, 'future'] = contract df.loc[contract, 'expiration_date'] = contract.expiration_date # Localice el contrato más cercano a la fecha objetivo. # X meses fuera del contrato frontal. fecha_de_expiración_más cercana = df.iloc[0].fecha_de_expiración fecha_de_expiración_objetivo = fecha_de_expiración_más cercana + relativadelta(meses=+meses_diferenciados) df['días_para_objetivo'] = abs(df.fecha_de_expiración ­ fecha_de_expiración_objetivo) contrato_objetivo = df.loc[df.días_para_objetivo == df.días_para_objetivo .min()] # Obtener precios para el contrato frontal y los precios del contrato objetivo = data.current( [ df.index[0], target_contract.index[0] ], 'cerrar' ) # Verifique la diferencia de días exacta entre los contratos days_to_front = int( (target_contract.expiration_date ­ close_expiration_date)[0].days ) # Calcular el carry anualizadoanualized_carry = (np.power((prices[0]/prices[1]), (365/days_to_front))) ­1 carry_df.loc[continuación, 'front'] = df.iloc[0].future carry_df.loc[continuación, 'next'] = target_contract.index[0] carry_df.loc[continuación, 'carry'] = transporte_anualizado # Ordenar por acarreo carry_df.sort_values('carry', inplace=True, ascending=False) carry_df.dropna(inplace=True) nueva_cartera = [] nuevos_largos = [] nuevos_cortos = [] Machine Translated by Google # Selección de contrato para i en np.arange(0, pos_per_side): j = ­ (i+1) # Comprar arriba, abajo corto long_contract = carry_df.iloc[i].next short_contract = carry_df.iloc[j].next new_longs.append(contrato_largo) new_shorts.append(contrato_corto) # Obtener datos para la nueva cartera new_portfolio = new_longs + new_shorts hist = data.history(new_portfolio, campos=['close','volume'], frecuencia='1d', bar_count=10,) # Target_weight simple igual ponderado = ( target_exposure_per_side * context.portfolio.portfolio_value ) / pos_per_side # Negociar por contrato en new_portfolio: # Historial de sectores para contrato h = hist.xs(contrato, 2) # Igual ponderación, con límite basado en el volumen. contratos_a_trade = peso_objetivo / \ contrato.precio_multiplicador / \ h.close[­1] # Límite de tamaño de posición contract_cap = int(h['volume'].mean() * volume_order_cap) # Limite el tamaño de la operación al límite de tamaño de la posición. contratos_para_comerciar = min(contratos_para_comerciar, contratos_cap) # Posición negativa para cortos si contrato en new_shorts: contratos_para_comerciar *= ­1 # Ejecutar order_target(contrato, contratos_para_comerciar) # Cerrar cualquier otra posición abierta para pos en context.portfolio.positions: si pos no está en new_portfolio: order_target(pos, 0.0) Machine Translated by Google inicio = fecha y hora (2001, 1, 1, 8, 15, 12, 0, pytz.UTC) fin = fecha y hora (2018, 12, 30, 8, 15, 12, 0, pytz.UTC) rendimiento = zipline.run_algorithm (inicio = inicio, fin = fin, inicialización = inicialización, capital_base = cartera_inicial_millones * 1000000, frecuencia_datos = 'diario', paquete = 'futuros') Resultados comerciales de curvas Este es un modelo de negociación algorítmico que no utiliza series de precios históricas. Esto es bastante inusual, y si no está familiarizado previamente con el concepto de estructura de plazos o calendario de negociación, puede parecerle una idea muy extraña. Pero como verá en los resultados de la prueba retrospectiva, incluso una implementación simple como esta puede funcionar bastante bien. Primero eche un vistazo a la tabla mensual para tener una idea de cómo se comporta la estrategia a lo largo del tiempo. Para un modelo sin datos históricos, sin stop loss, sin objetivos, sin indicadores y con una simple ponderación igual, en realidad no es tan malo. Tabla 18.3 Rentabilidades mensuales de operaciones con curvas Año Ene Feb Mar Abr May Jun Jul Ago Sep Oct Nov Dic Año 2001 +4,2 +1,4 +5,8 +2,5 +2,7 +2,5 ­3,0 +7,7 ­3,1 2002 +0,6 +3,0 ­6,0 +6,1 ­2,1 ­1,0 +4,1 +0,8 +0,9 ­3,7 ­2,6 +2,2 +1,7 2003 +5,1 +11,4 ­5,5 ­1,5 +5,6 +0,3 ­0,8 ­3,8 +0,3 ­0,3 ­2,0 +0,1 +8,1 2004 ­4,2 +4,2 ­0,3 +5,3 ­0,5 +3,1 +15,3 ­2,1 +12,9 +11,7 ­2,0 ­5,7 +41,2 2005 +5,7 ­0,6 +10,6 ­0,2 +3,5 +4,5 +0,5 +6,0 +4,3 ­0,2 +7,3 +1,2 +51,1 2006 ­1,6 +0,4 +3,1 +3,1 ­1,2 +7,5 ­1,5 ­4,7 ­0,7 ­1,9 +1,2 +1,0 +4,2 2007 +1,2 +2,5 +1,1 +4,8 ­2,6 +0,4 ­1,6 +5,3 +1,9 +5,1 +2,2 +2,0 +24,2 2008 ­1,0 +3,8 +5,2 +2,7 +15,2 ­1,1 ­11,4 ­4,5 +5,9 ­6,8 +8,9 +9,7 +26,3 2009 +5,9 +6,0 ­5,4 +0,1 ­6,7 +1,2 +1,6 +10,5 ­1,3 ­4,8 +0,8 +0,8 +7,3 2010 +6,4 ­3,0 +0,4 ­1,6 +6,0 +3,0 ­8,3 +6,8 +7,9 +14,3 ­5,2 +6,6 +36,1 2011 +4,2 +2,5 +0,3 ­2,9 +0,5 +6,6 +3,6 +1,2 +0,0 +2,8 +0,0 +3,0 +23,9 2012 +0,3 +6,4 +3,4 +1,5 ­6,5 +5,7 +1,6 +1,6 ­3,3 +1,2 +3,1 ­0,1 +15,2 2013 +2,2 ­2,0 +1,7 ­0,8 +3,7 +2,3 +3,5 +2,0 ­2,1 +1,4 +0,0 +1,2 +13,6 2014 +0,2 ­8,4 ­3,1 ­2,5 +6,8 +2,6 ­0,5 ­1,5 +5,8 ­13,5 ­2,9 +1,4 ­16,1 2015 ­1,2 +0,0 +6,7 ­1,8 +2,9 ­5,6 +4,8 +3,0 ­0,8 +3,1 +0,0 +7,3 +19,4 2016 ­3,9 +2,9 ­3,3 ­8,5 ­3,6 +1,2 +8,3 ­4,3 ­0,3 ­1,5 +2,0 ­3,5 ­14,5 2017 ­0,5 +0,9 +0,4 +4,7 ­2,3 ­1,5 ­4,6 +5,2 +0,1 +7,7 +4,2 +4,5 +19,6 ­7,0 ­2,3 ­0,1 +10,7 Machine Translated by Google Año Ene Feb Mar Abr May Jun Jul Ago Sep Oct Nov Dic Año 2018 +1,6 +1,1 +4,4 +3,1 +3,7 +9,0 ­1,3 +8,1 +3,4 ­8,6 ­6,0 +0,8 +19,1 Si observa la curva de acciones en la Figura 18­3, verá que es sorprendentemente suave. Sin embargo, no debería sorprender que no muestre ninguna correlación discernible con el índice bursátil. Esta estrategia es en gran medida la llamada estrategia no correlacionada. Ese término se utiliza mucho en la industria y se refiere a estrategias que, al menos en teoría, no están correlacionadas con los mercados de valores. Figura 18 3 Rentabilidades comerciales en curva La tabla 18.4 muestra los rendimientos del período de tenencia, o cómo habría sido su rendimiento porcentual si hubiera iniciado esta estrategia a principios de un año determinado y la hubiera mantenido durante un cierto número de años. Lo que ve aquí son cifras a largo plazo bastante buenas para la mayoría de los puntos de partida. Incluso eligiendo los peores años iniciales posibles, se ve cómo la recuperación no lleva demasiado tiempo y pronto supera los enfoques de inversión tradicionales. Tabla 18.4 Rentabilidad del período de tenencia: negociación con curvas Machine Translated by Google Años 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 2001 11 6 7 14 21 18 19 20 18 20 20 20 19 16 17 14 15 15 2002 2 5 16 24 20 20 21 19 21 21 21 20 17 17 15 15 15 2003 8 24 32 25 24 25 22 24 24 23 22 18 18 16 16 16 2004 41 46 31 29 28 25 26 26 25 24 19 19 16 16 17 2005 51 25 25 25 22 24 24 23 22 17 17 14 15 15 2006 4 14 18 15 19 20 19 18 14 15 12 12 13 2007 24 25 19 23 23 22 21 15 16 12 13 13 2008 26 16 23 23 21 20 14 15 11 12 12 2009 7 21 22 20 19 12 13 9 10 11 2010 36 30 25 22 13 14 10 11 12 2011 24 19 17 8 10 6 8 9 2012 15 14 3 7 2 5 7 2013 14 ­2 4 ­1 3 6 2014 ­16 0 ­5 1 4 2015 19 1 7 10 2016­14 1 7 2017 20 19 2018 19 Consideraciones del modelo No se puede enfatizar demasiado que este modelo es muy sensible a la liquidez. Como ha visto en el código fuente de este modelo, sólo se utilizó un millón como base para el comercio. Esa es una cantidad extremadamente pequeña para negociar modelos de futuros, pero este es un tipo de modelo en el que tienes una pequeña ventaja. Es posible obtener rendimientos bastante altos con menores inversiones de capital, pero el juego cambia dramáticamente si quieres operar con nueve cifras. Por supuesto, me doy cuenta de que muchos lectores se resisten a mi variedad y que un millón es una poquita cantidad de dinero. Por supuesto que es una enorme cantidad de dinero, en el mundo real. Pero en lo que respecta al comercio de futuros profesional, en realidad no es mucho. Este modelo se puede ampliar y reducir un poco. Se puede negociar con un poco menos y con un poco más y aun así mostrar rendimientos interesantes. Pero el dinero de tamaño institucional serio tendría dificultades. La implementación de este modelo aquí es muy simple, nuevamente para enseñar un concepto y demostrar una forma de abordar el modelado algorítmico. El código fuente puesto a su disposición aquí debería permitirle experimentar con sus propias versiones, adaptándolo y mejorándolo en función de su propia situación y requisitos. Machine Translated by Google En términos generales, encontrará una liquidez un poco mejor más cerca de la curva, hacia el lado izquierdo. Esta implementación se comercializa dentro de 12 meses y, por lo general, el comercio allí es bastante limitado. Si opera, por ejemplo, con tres meses de antelación, encontrará una liquidez un poco mejor. Sus suposiciones de deslizamiento y sus preferencias sobre qué tan agresivo desea operar afectarán en gran medida sus resultados. Tenga cuidado con su modelado aquí. Sería muy fácil cambiar un par de números en el código y terminar con un rendimiento del 50 por ciento anual o más. Pero una simulación es tan buena como las suposiciones que la componen, y no es probable que usted obtenga tales retornos en el trading real. Otra área de investigación sería buscar combinar esta información de estructura de términos con otros análisis. El enfoque que se muestra aquí se centra únicamente en el rendimiento implícito o el coste de mantenimiento, pero no hay ninguna razón por la que sus modelos deban ser tan puros en la vida real. Sí, voy a tirar eso y dejarte hacer la tarea. Comparar y combinar modelos En lo que respecta a los futuros, hemos analizado algunos enfoques diferentes. Primero, un modelo de tendencia estándar con filtro de tendencia, stop dinámico y lógica de ruptura. En segundo lugar, un modelo de rentabilidad temporal simple que sólo compara un precio mensual con el del año y medio año anterior. En tercer lugar, el enfoque de contratendencia o reversión a la media, que tiene como objetivo entrar cuando los seguidores de la tendencia dejan de salir, operando en un período de tiempo más corto. Y, finalmente, un modelo de negociación basado en carry, que se centra únicamente en la forma de la curva de las estructuras de plazo. También comenzamos con un modelo sistemático de impulso de las acciones, que negocia sólo el lado largo de las acciones y debería tener un perfil de rendimiento bastante diferente al de los modelos de futuros de rendimiento absoluto. En el capítulo sobre cada uno de estos modelos, probablemente habrás notado que no mostré las estadísticas de retorno habituales. Esto fue muy a propósito, ya que me he dado cuenta de que muchos lectores se fijan demasiado en esas figuras y se pierden el panorama general. Es un poco como entregar impresiones de diapositivas antes de una presentación en vivo. Nadie escuchará lo que tengas que decir después de eso. Pero ahora que presumiblemente ya ha recorrido los capítulos anteriores, debería ser lo suficientemente seguro como para mostrarle las estadísticas. Los datos que está buscando se encuentran en la Tabla 19.1, donde se enumeran las estrategias que analizamos anteriormente, así como las mismas estadísticas para el índice de retorno total S&P 500, todas cubriendo el período de pruebas retrospectivas desde principios de 2001 hasta finales de 2018. Machine Translated by Google Tabla 19.1 Estadísticas de estrategias de futuros Anualizado Devolver máx. Reducción Anualizado Volatilidad Sharpe calmar sortino Relación Relación Relación modelo_tendencia 12,12% ­25,48% 19,35% 0,69 0,48 0,98 tendencia contraria 11,00% ­30,09% 18,55% 0,66 0,37 0,92 comercio_curva 14,89% ­23,89% 18,62% 0,84 0,62 1.22 tiempo_retorno 11,78% ­40,31% 21,09% 0,63 0,29 0,9 impulso_sistemático 7,84% ­39,83% 16,48% 0,54 0,2 0,76 SPXTR 5,60% ­55,25% 18,92% 0,38 0.1 0,5 Claramente el modelo de comercio de curvas es el mejor, ¿verdad? y el impulso ¿No vale la pena molestarse? Bueno, conclusiones como esa son la razón por la que lo hice. No mostrar estas estadísticas simples antes. Evaluar los modelos comerciales es una tarea más tarea más compleja que simplemente mirar una mesa como ésta. Necesitas estudiar los detalles y estudiar el perfil de rentabilidad a largo plazo. Y por supuesto escalabilidad. En Al final del negocio, a menudo se busca un comportamiento específico en el retorno. perfil, a menudo en relación con otros factores. La respuesta a qué modelo es más. Lo prometedor depende de lo que estés buscando en este momento y qué encajaría o complementaría su cartera actual de modelos. Todos estos modelos son modelos de demostración simples. Son herramientas de enseñanza, no modelos de grado de producción. Pero todos muestran potencial y se pueden pulir. hasta convertirse en modelos de grado de producción. También puedes ver que todos ellos son órdenes de magnitud más atractivos. que un enfoque bursátil de comprar y mantener. Algunos lectores pueden sorprenderse al veamos cuán magros son los rendimientos de los mercados bursátiles a lo largo del tiempo. En este periodo, De 2001 a 2018, el S&P 500 obtuvo menos del 6% anual, incluso con dividendos incluidos e incluso con los últimos diez años de mercado alcista incluidos. Y eso fue una reducción máxima de más de la mitad. Otro punto que puede sorprender a algunos es el nivel de los ratios de Sharpe. Ninguno supera 1. Existe la desafortunada idea errónea de que un Sharpe inferior a 1 uno es pobre. Eso no es necesariamente así. De hecho, para estrategias sistemáticas es Es inusual ver ratios de Sharpe realizados superiores a uno. Machine Translated by Google Figura 19 1 Comparación de modelos de futuros La Figura 19­1 muestra el desarrollo a largo plazo de estas cinco estrategias, en comparación con el del mercado de valores. En una escala temporal tan larga, la comparación del índice no parece justa. Pero el hecho es que, a corto plazo, siempre te compararán con él. Ésta es la maldición del negocio. Recuerde que la razón por la que estos backtests comienzan en 2001 es que un El problema actual y, con suerte, pronto solucionado en Zipline hace que sea complicado utilizar datos anteriores al 2000. El hecho de que el índice de acciones comience con una caída en picada podría hacer que esta comparación sea un poco injusta y, por esa razón, también les mostraré el mismo gráfico a partir de 2003, el punto más bajo del mercado bajista. No haré uno desde el fondo del mercado bajista de 2008­2009. Eso sería simplemente una tontería. Comparar el momento perfecto del mercado en el mercado alcista más duradero de una generación con estrategias alternativas no tiene ningún sentido. Machine Translated by Google Figura 19 2 Comparación, a partir de 2003 Incluso si hubiéramos tenido la previsión de comprar el índice en el momento perfecto en el momento más bajo de la crisis tecnológica, el índice aún habría mostrado un rendimiento menor y caídas más profundas. Combinando los modelos Todo el mundo sabe que la diversificación es beneficiosa. Al menos todo el mundo debería saber eso. Pero la mayoría de la gente piensa en la diversificación sólo en términos de mantener múltiples posiciones. Todo eso está muy bien, pero también puede encontrar valor agregado al diversificar los estilos comerciales. Piense en un modelo comercial único como un componente de la cartera. Lo que puede encontrar es que una cartera general de modelos puede funcionar significativamente mejor que cualquiera de las estrategias individuales que la integran. Lo demostraré con una cartera simple, que consta de los cinco modelos comerciales que hemos visto hasta ahora. Como tenemos cinco modelos, asignaremos un peso igual del 20% de nuestro capital a cada uno. El período de reequilibrio es mensual, lo que significa que necesitaríamos ajustar todas las posiciones en consecuencia cada mes, restableciendo el peso al objetivo del 20%. Esta frecuencia de reequilibrio a nivel de modelo puede resultar difícil y consumir mucho tiempo para cuentas más pequeñas, pero es perfectamente razonable a mayor escala. Si lo deseas, puedes repetir este experimento con datos anuales. Hacer cálculos de cartera como este es un área donde Python brilla en comparación con otros lenguajes. Machine Translated by Google Puede leer sobre cómo se calculó esta combinación de cartera y ver el código correspondiente en el capítulo 20. Cuadro 19.2 Portafolio de modelos de futuros Anualizado Devolver máx. Reducción Anualizado Volatilidad Sharpe calmar sortino Relación Relación Relación modelo_tendencia 12,12% ­25,48% 19,35% 0,69 0,48 0,98 tendencia contraria 11,00% ­30,09% 18,55% 0,66 0,37 0,92 comercio_curva 14,89% ­23,89% 18,62% 0,84 0,62 1.22 tiempo_retorno 11,78% ­40,31% 21,09% 0,63 0,29 0,9 7,84% ­39,83% 16,48% 0,54 0,2 0,76 14,92% ­17,55% 11,81% 1.24 0,85 1,79 impulso_sistemático Conjunto La Tabla 19.2 muestra una comparación del desempeño de cada individuo. modelo, así como el mercado de valores en general, al de la cartera combinada. Estas cifras deberían ser muy claras. La cartera combinada lejos superó a cada estrategia individual, con menor volatilidad. tenemos un mayor rendimiento anualizado, una reducción máxima más baja, menor volatilidad, mayor Sharpe, etc. Espero que esto ayude a aclarar mi insistencia en que es necesario mirar el perfil de retorno detallado al evaluar un nuevo modelo comercial. No es necesariamente el retorno per se que busca, sino más bien su perfil y qué tan bien encaja sus modelos existentes. Es posible que encuentre un modelo con un rendimiento esperado bajo a lo largo del tiempo, pero que también tiene una correlación baja o negativa con otros modelos y, por lo tanto, puede ayude a su cartera general y combinada de modelos comerciales. Machine Translated by Google Figura 19 3 Cartera de modelos comerciales También verá en la Figura 19 3 y en la Tabla 19.3 que el total El perfil de retorno parece significativamente más atractivo, una vez que se combinan los modelos. Como los modelos individuales a menudo tienen sus ganancias y pérdidas en diferentes momentos unos de otros, se complementan bien y ayudan a suavizar los problemas a largo plazo. volatilidad. Las reducciones se vuelven moderadas, lo que resulta en una mayor rentabilidad a largo plazo. devolver. Si bien estuvo muy cerca algunos años, al final, ni un solo año de este La cartera combinada terminó perdiendo dinero. Tabla 19.3 Análisis del período de tenencia para el modelo combinado Años 1 2 345 6 7 8 9 10 11 12 13 14 15 16 17 18 2001 8 13 17 18 17 16 18 19 19 20 18 17 17 17 17 16 16 15 2002 18 22 22 20 17 19 21 20 21 20 18 18 18 17 16 16 15 2003 27 24 21 17 20 21 21 21 20 18 18 18 17 16 16 15 2004 21 18 14 18 20 20 21 19 17 17 17 17 16 15 14 2005 15 11 17 20 20 21 19 16 16 17 16 15 15 14 2006 7 18 21 21 22 19 17 16 17 16 15 15 14 2007 29 29 26 26 22 18 18 19 17 16 16 14 2008 29 24 24 20 16 16 17 16 15 14 13 2009 20 22 17 13 14 15 14 13 13 12 2010 25 16 11 12 14 13 12 12 11 2011 8 5 2012 1 8 13 12 10 11 9 8 12 11 10 10 2013 16 20 16 13 13 11 9 Machine Translated by Google 2014 24 16 12 12 10 2015 9 6 2016 4 8 2017 13 8 7 6 7 2018 2 Implementación de una cartera de modelos Si bien una demostración como esta puede parecer una solución simple para todos sus compromisos de inversión, la implementación puede no ser tan fácil. Cada uno de estos modelos requiere millones para comercializarse. Es evidente que para negociar con todos ellos se necesitan aún más millones. Reconozco que no todos los lectores de este libro tienen cien millones de sobra para comercializar. Pero incluso si usted es uno de esos pocos desafortunados no multimillonarios, comprender el poder de combinar diferentes enfoques puede resultar de gran ayuda. La insuficiencia de fondos no es el único problema potencial al crear carteras de modelos. En la práctica, puede resultar muy complejo implementar la combinación de modelos que se muestra en este capítulo. A medida que aumenta la complejidad, le falta la descripción general simple que es posible con un solo modelo y es posible que necesite un software más sofisticado para realizar un seguimiento de las posiciones, señales, asignación de riesgos, etc. Una organización comercial profesional puede desarrollar la capacidad de negociar combinaciones tan complejas, monitorear el riesgo y generar informes y análisis adecuados. Para los comerciantes individuales, esto puede no ser una posibilidad. Por supuesto, hay otra forma de verlo. Comprender cómo se pueden construir e implementar carteras complejas de modelos puede ayudarle a adquirir las habilidades necesarias para conseguir un buen trabajo en la industria. Trabajar en lo más destacado de la industria tiene el potencial de hacerle ganar mucho más dinero del que podría ganar negociando su propia cartera. Nunca olvide que el dinero interesante en este negocio se obtiene negociando el dinero de otras personas. Si usted personalmente tiene o no el dinero para intercambiar dichos modelos no es la parte importante. Bueno, sería bueno tener eso, por supuesto. Pero aún puedes hacer uso de este tipo de conocimiento y aún puedes beneficiarte de él. Si consigue un buen trabajo en un fondo de cobertura o similar, probablemente le pagarán mucho más trabajando para ellos que negociando con su propio dinero de todos modos. Machine Translated by Google Visualización del rendimiento y Combinaciones En los apartados anteriores has visto varias tablas y gráficos que muestran el rendimiento de los backtests. Al ser un libro totalmente transparente y todo eso, les mostraré cómo se hicieron esas visualizaciones. Estas son cosas realmente simples de hacer en Python. Todo lo que necesitamos es una serie de tiempo con la que trabajar y podemos crear todo tipo de gráficos, análisis, tablas y otros resultados. Almacenamiento de resultados del modelo En el capítulo 8 analizamos algunas formas de extraer resultados de una prueba retrospectiva de Zipline y construir algunos análisis y gráficos basados en eso. Aquí se aplican los mismos principios, pero la diferencia es que sería útil almacenar las pruebas retrospectivas para realizar análisis posteriores. Como ya habrá visto, cuando ejecutamos una prueba retrospectiva de Zipline, recibimos los resultados. En los modelos de muestra anteriores en este libro, esto tiende a verse más o menos así. rendimiento = zipline.run_algorithm (inicio = inicio, fin = fin, inicializar = inicializar, analizar = analizar, capital_base = millones_traded * 1000000, frecuencia_datos = 'diario', paquete = 'futuros') En este caso, una vez completada la ejecución del backtest, la variable per f contendrá todos los resultados. Este es un DataFrame , que consta de una gran cantidad de datos diferentes que se recopilaron o calcularon durante la ejecución. Si desea almacenar solo el valor de la cartera para cada día en el backtest, puede hacerlo muy fácilmente. Esto se almacena en la columna portfolio_valu e y eso significa que simplemente podemos guardarlo como un archivo separado por comas en esta única fila de código. perf.portfolio_value.to_csv('model_rendimiento.csv') Eso es todo lo que se necesita, y esa línea guardará el valor de la cartera, a menudo llamado curva de acciones, del backtest en un archivo con el nombre especificado. Si buscas analizar otros aspectos de los backtests, puedes guardar las operaciones realizadas y . cualquier otro dato que encuentres en per f Machine Translated by Google Este truco, guardar un DataFrame en un archivo separado por comas, puede resultar muy útil en muchos casos. Otra herramienta algo relacionada que puede ser particularmente útil en ocasiones durante la depuración es .to_clipboard() . En lugar de guardarlo en el disco, esto colocará el DataFrame en la memoria, en el portapapeles. Estará en el formato correcto para que puedas pegarlo directamente en Excel, si esa es tu intención. Poder copiar rápidamente algo a Excel para una inspección visual puede resultar muy útil a la hora de depurar. Cómo se realizó el análisis de rendimiento del modelo Para calcular y visualizar los análisis de rendimiento de cada uno de los capítulos del modelo, comencé con un Jupyter Notebook. Como se analizó anteriormente en el libro, tiene sentido separar diferentes piezas de lógica en diferentes celdas del Cuaderno. Las celdas inferiores pueden acceder a los datos que obtuvo o calculó en las de nivel superior. Después de ejecutar las pruebas retrospectivas, guardé el valor histórico de la cartera en un archivo CSV local. Luego, el cuaderno de análisis de rendimiento lee uno de estos archivos CSV, así como los datos de referencia del índice de rendimiento total S&P 500 para comparar. Esto es lo que hace la primera celda de mi cuaderno. %matplotlib en línea importar matplotlib.pyplot como plt importar pandas como pd # Donde los datos son ruta = 'datos/' # Establecer un punto de referencia para comparar con bm = 'SPXTR' bm_name = 'S&P 500 Total Return' # Estos son los archivos csv de rendimiento guardados de nuestros modelos de libros. strat_names = { "trend_model": "Estrategia de tendencia central", "time_return": "Estrategia de retorno de tiempo", "counter_trend": "Estrategia de contratendencia", "curve_trading": "Estrategia de comercio de curvas", "systematic_momentum": "Impulso de la equidad Estrategia", } # Elija uno para analizar strat = 'curve_trading' # Busca el nombre Machine Translated by Google nombre_estrato = nombres_estrato[estrato] # Leer la estrategia df = pd.read_csv(path + strat + '.csv', index_col=0, parse_dates=True, nombres=[strat]) # Leer el punto de referencia df[bm_name] = pd.read_csv(bm + '.csv', index_col=0, parse_dates=[0] ) # Limitar el historial hasta finales de 2018 para el libro df = df.loc[:'2018­12­31'] # Imprimir la confirmación de que todo está hecho print("Fetched: {}".format(strat_name)) Una vez hecho esto, tenemos los datos que necesitamos de forma ordenada. Lo Marco de datos . siguiente que quería hacer era crear una tabla bonita de rendimientos mensuales. El código Python para agregar rendimiento en frecuencias mensuales y anuales no requiere mucho trabajo. Como suele ocurrir, alguien más ya ha escrito código para esto y no hay necesidad de reinventar la rueda. En este caso, usaré la biblioteca Empyrical , ya que ya debería estar instalada en tu computadora si seguiste los capítulos anteriores. Viene incluido con la biblioteca PyFolio que usamos anteriormente en el capítulo 8. Por lo tanto, calcular los rendimientos mensuales y anuales requiere solo una línea de código cada uno. El resto de la siguiente celda trata sobre la construcción de una tabla formateada ordenada. Para eso, decidí usar HTML antiguo, solo para asegurarme de que se vea como quiero para este libro. Si simplemente desea volcar los valores mensuales en texto, la mayor parte de la siguiente celda es redundante. La mayor parte del siguiente código trata simplemente de formatear una tabla HTML para una visualización bonita. # Se utiliza para cálculos de rendimiento. Importar empírico como em. # Se utiliza para mostrar contenido con formato HTML en el cuaderno desde IPython.core.display import display, HTML # Utilice Empyrico para agregar en períodos mensuales y anuales Monthly_data = em.aggregate_returns(df[strat].pct_change(),'monthly') Yearly_data = em.aggregate_returns(df[strat].pct_change(),'yearly') # Comience una tabla HTML para mostrar """ table = <table id='monthlyTable' class='table table­hover table­condensed table­striped'> <thead> <tr> <th style="text­align:right">Año</th> <th style="text­align:right">enero</th> <th style="text­align:right">febrero</th > <th style="text­align:right">Mar</th> <th style="text­ align:right">Abr</th> <th style="text­ align:right">Mayo</ th> <th style="text­ align:right">junio</th> <th style="text­ align:right">julio</th> Machine Translated by Google <th style="text­align:right">ago</th> <th style="text­align:right">septiembre</th> <th style="text­align:right">octubre</th > <th style="text­align:right">Nov</th> <th style="text­align:right">Dic</th> <th style="text­align:right">Año</ th> </tr> </ thead> <tbody> <tr>""" primer_año = Verdadero primer_mes = Verdadero año = 0 mes = 0 # Mire mes a mes y agregue a la tabla HTML m, val en Monthly_data.iteritems(): yr = m[0] mnth = m[1] # Si es el primer mes del año, agregue la etiqueta del año a la tabla. if(primer_mes): tabla += "<td align='right'><b>{}</b></ td>\n".format(año) primer_mes = False # rellenar meses vacíos para el primer año si sim no comienza en enero if(first_year): first_year = False if(mnth > 1): for i in range(1, mnth): table += "<td align='right '>­</td>\n" # Agregar la tabla de rendimiento mensual += "<td align='right'>{:+.1f}</td>\n".format(val * 100) # Verifique diciembre, agregue el número anual if(mnth==12): table += "<td align='right'><b>{:+.1f}</b></td>\n".format (datos_anuales[año] * 100) tabla += '</ tr>\n <tr> \n' primer_mes = Verdadero # agregar relleno para los meses vacíos y el valor del año pasado if(mes!= 12): for i in range(mes+1, 13): table += "<td align='right'>­</td>\n" if(i==12): tabla += "<td align='right'><b>{:+.1f}</b></td>\n".format( datos_anuales[año] * 100 ) tabla += '</tr>\n <tr> \n' # Finalizar tabla tabla += '</tr>\n </tbody> \n </table>' # Y muéstralo. mostrar(HTML(tabla)) Machine Translated by Google Esto debería generar una tabla similar a las que ha visto varias veces en este libro, para cada uno de los capítulos de análisis estratégico. A continuación está el gráfico de rendimiento. En las secciones anteriores, utilicé un gráfico logarítmico para comparar cada estrategia con el índice de acciones. Puede que no tenga mucho sentido comparar su estrategia con el S&P 500, pero es muy probable que otros lo comparen con ella, sea lógico o no. En la misma imagen del gráfico, también tenía un gráfico de reducción y una correlación móvil de 6 meses. Son muy fáciles de calcular y ya deberías saber cómo hacer las gráficas. También hay algo de código para hacer que el siguiente sea realmente grande y que las líneas sean negras y grises para el libro. importar matplotlib # Días de negociación supuestos en un año yr_periods = 252 # Formato para la visualización del libro font = {'familia': 'eurostile', 'peso': 'normal', 'tamaño': 16} matplotlib.rc('font', **font) # Rebase a la primera fila con una sola línea de código df = df / df.iloc[0] # Calcular la correlación df['Correlation'] = df[strat].pct_change().rolling(window=int(yr_periods / 2)).corr(df[bm_name].pct_change()) # Calcular la reducción acumulada df['Drawdown'] = (df[strat] / df[strat].cummax()) ­ 1 # Asegúrese de que no haya valores NA allí df.fillna(0, inplace=True) # Iniciar una trama figure fig = plt.figure(figsize=(15, 12)) # Primer gráfico ax = fig.add_subplot(311) ax.set_title('Comparaciones de estrategias') ax.semilogy(df[strat], '­',label=strat_name, color='black') ax.semilogy(df[bm_name ] , '­­', color='gris') ax.legend() # Segundo gráfico ax = fig.add_subplot(312) ax.fill_between(df.index, df['Drawdown'], label='Drawdown', color='black') ax.legend() # Tercer gráfico ax = fig.add_subplot(313) ax.fill_between(df.index,df['Correlation'], label='6M Rolling Correlation', color='grey') ax.legend() Machine Translated by Google Finalmente, para cada capítulo hice una tabla llamada período de tenencia, que muestra su rendimiento porcentual si comenzó en enero de un año determinado y lo mantuvo durante una cierta cantidad de años completos. Nuevamente elegí usar salida HTML para asegurar que se pueda mostrar bien en este libro. Dado que el ancho de estas páginas es limitado, también redondeé los números al porcentaje completo. def holding_period_map(df): # Rendimientos anuales agregados yr = em.aggregate_returns(df[strat].pct_change(), 'anual') año_inicio = 0 #Comenzar desde la tabla table = "<table class='table table­hover table­condensed table­striped'>" table += "<tr><th>Años</th>" # Construye la primera fila de la tabla para i en range(len(yr)): table += "<th>{}</th>".format(i+1) table += "</tr>" # Iterar años para the_year, valor en yr.iteritems(): # Nueva tabla de filas de tabla += "<tr><th>{}</th>".format(the_year) # Iterar los años retenidos para yrs_held in (range(1, len(yr)+1)): # Iterar los años retenidos si yrs_held <= len(yr[yr_start:yr_start + yrs_held]): ret = em.annual_return(yr[yr_start :yr_start + yrs_held], 'yearly' ) tabla += "<td>{: +.0f}</td>".format(ret * 100) table += "</tr>" yr_start+=1 tabla de retorno tabla = holding_period_map(df) display(HTML(tabla)) Cómo se realizó el Análisis de Cartera Combinada Machine Translated by Google En el capítulo 19 analizamos los beneficios de diversificación de combinar modelos y negociar carteras de modelos. El método utilizado en ese capítulo fue reequilibrar al comienzo de cada mes, restableciendo el peso de cada estrategia en ese intervalo. En el código que se muestra aquí, también le daré otro método de reequilibrio. Como verá en el siguiente segmento de código, también puede activar el reequilibrio en caso de divergencia porcentual, si los desarrollos del mercado han impulsado alguna estrategia a tener un descuento de más de un cierto porcentaje. Este es un tema algo avanzado y realmente va más allá de lo que planeaba mostrar en este libro. Incluyo este código fuente aquí de todos modos, en aras de ser transparente. Sin embargo, evitaré la discusión potencialmente larga sobre cómo se construye este código y el motivo. Utiliza algunos trucos para mejorar el rendimiento, utilizando Numpy para acelerar las cosas. Una vez que se sienta cómodo con Python y las pruebas retrospectivas, es posible que desee profundizar en este tema. Cómo utilizarlo para optimizar operaciones complejas y acelerar el código. Pero está fuera del alcance de este libro. importar pandas como pd importar numpy como np base_path = '../Pruebas retrospectivas/' # Reequilibrio en la clase de divergencia porcentual PercentRebalance(objeto): def __init__(self, percent_target): self.rebalance_count = 0 self.percent_target = percent_target def reequilibrio(self, fila, pesos, fecha): total = fila.sum() reequilibrado = fila reequilibrada = np.multiply(total, pesos) if np.any(np.abs((fila­ reequilibrada)/reequilibrada) > (self.percent_target/100.0)): self.rebalance_count = self.rebalance_count + 1 devolución reequilibrada en caso contrario: fila de retorno # Reequilibrio en el calendario clase MonthRebalance(objeto): def __init__(self, meses): self.month_to_rebalance = meses self.rebalance_count = 0 self.last_rebalance_month = 0 def reequilibrio (auto, fila, pesos, fecha): mes_actual = fecha.mes if self.last_rebalance_month!= current_month: total = fila.sum() reequilibrado = np.multiply(pesos, total) self.rebalance_count = self.rebalance_count + 1 Machine Translated by Google self.last_rebalance_month = fecha.mes retorno reequilibrado más: fila de retorno # Calcular la combinación reequilibrada def calc_rebalanced_returns(devoluciones, reequilibrador, pesos): devoluciones = devoluciones.copia() + 1 # crear un ndarray numpy para contener los rendimientos acumulativos cumulative = np.zeros(returns.shape) cumulative[0] = np.array(weights) # también convierte los retornos a un ndarray para un acceso más rápido rets = return.values # usando ndarrays, toda la multiplicación ahora es manejada por numpy para i en rango(1, len(acumulativo)): np.multiply(acumulativo[i­1], rets[i], out=acumulativo[i]) acumulativo[ i] = rebalancer.rebalance(acumulativo[i], pesos, retornos.index[i]) # convertir los retornos acumulativos nuevamente en un marco de datos cumulativeDF = pd.DataFrame(acumulativo, index=returns.index, columns=returns.columns) # Averiguar cuántas veces ocurre el reequilibrio es un ejercicio interesante print ("Reequilibrado {} veces".format(rebalancer.rebalance_count)) # convertir los valores acumulados nuevamente en retornos diarios rr = cumulativeDF.pct_change() + 1 rebalanced_return = rr.dot(weights) ­ 1 retorno rebalanced_return def get_strat(estrato): df = pd.read_csv(base_path + strat + '.csv', index_col=0, parse_dates=True, nombres=[strat] ) return df # Usar reequilibrador mensual, reequilibrador de intervalo de un mes = MonthRebalance(1) # Definir estrategias y ponderaciones de cartera = { 'trend_model': 0.2, 'counter_trend': 0.2, 'curve_trading': 0.2, 'time_return': 0.2, 'systematic_momentum': 0.2, } # Leer todos los archivos en un DataFrame df = pd.concat( [ pd.read_csv('{}{}.csv'.format( ruta_base, estrato ), index_col=0, parse_dates=True, nombres=[estrato] ).pct_change().dropna() para estrato en lista(portfolio.keys()) Machine Translated by Google ], eje=1 ) # Calcular la cartera combinada df['Combined'] = calc_rebalanced_returns( df, reequilibrador, pesos = lista (cartera.valores ())) df.dropna(en el lugar=Verdadero) # Hacer Graph import matplotlib import matplotlib.pyplot como plt include_combined = Verdadero include_benchmark = Verdadero punto de referencia = 'SPXTR' si include_benchmark: devuelve[benchmark] = get_strat(benchmark).pct_change() #devoluciones = devuelve['2003­1­1':] normalizado = (devoluciones+1).cumprod() fuente = {'familia': 'eurostilo', 'peso': 'normal', 'tamaño': 16} matplotlib.rc('fuente', **fuente) higo = plt.figura(tamañohigo=(15, 8)) # Primer gráfico ax = fig.add_subplot(111) ax.set_title('Comparaciones de estrategias') dashstyles = ['­','­­','­.','.­.', '­'] i = 0 para estrategia en normalizado: si estrato == 'Combinado': si no, include_combined: continuar clr = 'black' guión = '­' ancho = 5 elif strat == punto de referencia: si no, include_benchmark: continuar clr = 'negro' guión = '­' ancho = 2 #elif strat == 'equity_momentum': # continuar demás: clr = guión 'gris' = estilos de guión[i] Machine Translated by Google ancho = yo + 1 yo += 1 ax.semilogy(normalizado[estrato], guión, etiqueta=estrato, color=clr, ancho de línea=ancho) hacha.leyenda() No puedes vencer a todos los monos todo el tiempo. No deberías preocuparte si un primate te supera. Nos pasa a los mejores. Pero tomemos esto desde el principio. En 1973 se publicó el icónico libro A Random Walk Down Wall Street , escrito por Burton Malkiel. Hay muchas cosas en este libro que no han resistido muy bien la prueba del tiempo, pero también hay algunas partes que han sido bastante proféticas. Lo irónico de esto es, por supuesto, que si bien gran parte de su análisis fue acertado, las conclusiones resultaron un poco diferentes. El libro es el principal defensor de la llamada Hipótesis del Mercado Eficiente. La teoría académica que afirma que los precios de los activos reflejan plenamente toda la información conocida y que es imposible ganarle al mercado con el tiempo. El movimiento de los precios de las acciones cuando no se ha puesto a disposición nueva información es puramente aleatorio y, por tanto, impredecible. Por supuesto, esta teoría tiene tanto valor como un artículo académico que demuestre que los abejorros no pueden volar. La observación empírica ha seguido atacando este modelo académico durante décadas, y sólo los académicos de carrera lo toman en serio. Pero no escribo esto para desacreditar las teorías del profesor Malkiel. Después de todo, Warren Buffett ya ha hecho un trabajo bastante bueno en ese frente. No, menciono este libro por una cita famosa. Uno que resultó ser más cierto de lo que el autor probablemente esperaba o deseaba. "Un mono con los ojos vendados que lanzara dardos a las páginas financieras de un periódico podría seleccionar una cartera que funcionaría tan bien como una cuidadosamente seleccionada por expertos". Machine Translated by Google Ahora estamos hablando. Esto es absolutamente cierto. El error está en marginar a los monos y desacreditar sus capacidades. Y, por supuesto, no veo los beneficios de vendarle los ojos al pobre mono, aparte de hacerlo lucir gracioso. La capacidad de los primates para superar al mercado de valores es indiscutible. La pregunta es qué hacemos con este conocimiento. La conclusión del profesor Malkiel fue que deberíamos abandonar toda esperanza de competir con los pequeños peludos y simplemente comprar fondos mutuos. Puede que eso hubiera parecido una buena idea en los años 70, pero eso fue antes de que tuviéramos pruebas concluyentes del fracaso de la industria de los fondos mutuos. Los chimpancés tendrán más posibilidades de generar rendimiento que un fondo mutuo. Señalar que los fondos mutuos son una mala idea no es exactamente traer nada nuevo a la mesa. Los informes trimestrales S&P Indices versus Active (SPIVA, 2019) publicados por S&P Dow Jones Indices son devastadores. Alrededor del 80% de todos los fondos mutuos no logran igualar su índice de referencia en un período determinado de 3 o 5 años. Los que superan su punto de referencia tienden a ser diferentes cada año, lo que indica el impacto del puro azar. Los fondos mutuos están diseñados para generar el máximo de ingresos para los administradores de fondos y los bancos, al tiempo que garantizan un rendimiento inferior por un margen lo suficientemente pequeño como para que la mayoría de los ahorradores minoristas no se den cuenta de lo que está pasando. Es un buen modelo de negocio. Lanza un fondo que invierte casi todos los activos como una composición de índice determinada. Se permiten pequeñas desviaciones, pero dentro de presupuestos de error de seguimiento muy estrictos. El diseño pretende evitar cualquier desviación notable del índice. Los fondos mutuos cobran una tarifa de gestión fija anual, como porcentaje de los activos, pero, por supuesto, esa no es la base total de ingresos. La mayoría de los bancos que ofrecen fondos mutuos también tienen departamentos de corretaje y, por supuesto, aquí es donde cotiza el fondo. La información del flujo es valiosa, sabiendo cuándo y cómo se reequilibrarán los fondos y qué órdenes realizan. Hay muchas maneras de ganar mucho dinero con los fondos mutuos y todo lo que necesita hacer es asegurarse de que no se desvíen mucho del índice de referencia. En los años 70, cuando se escribió el libro en cuestión, este patrón probablemente no era tan visible. Los fondos mutuos debieron parecer una muy buena idea en aquel entonces. Lo que sabemos ahora es diferente y es muy poco probable que un fondo mutuo supere al proverbial primate. Los fondos mutuos ni siquiera intentan hacer eso. No hay rentabilidad en tal esfuerzo, al menos en comparación con ir a lo seguro, mostrar un pequeño rendimiento inferior y sacar provecho de grandes comisiones. Machine Translated by Google Pero yo divago. Nada de esto es información nueva. Después de todo, señalar que los fondos mutuos no son grandes inversiones es, a estas alturas, mucho más que vencer a un caballo muerto. Se parece más a gritarle a la lata de comida para perros en el supermercado donde finalmente terminó el caballo. Comprar fondos mutuos no es la respuesta. Quizás deberíamos simplemente renunciar y contratar a un chimpancé para la oficina. Sí, soy consciente de que Malkiel pidió un mono, pero usar un simio apenas parece una trampa. Después de todo, los chimpancés tienen claras ventajas en comparación con el reclutamiento en la industria de los fondos de cobertura, como salarios más bajos, mejor comportamiento y temperamento más tranquilo. Sin embargo, no todo el mundo tiene la suerte de poder contar con los servicios de un primate cooperativo. La buena noticia es que tenemos una forma de simular las habilidades de estas extraordinarias criaturas. Generadores de números aleatorios. El señor Bubbles va a Wall Street Antes de comenzar a diseñar estrategias comerciales para los mercados de valores, debe ser consciente de lo que se esperaría que lograra un chimpancé promedio, con un comportamiento razonablemente bueno. El quid de la cuestión es que un enfoque aleatorio de selección de acciones generará buenos beneficios con el tiempo. Por supuesto, no estoy sugiriendo que elija sus acciones al azar, pero este conocimiento tiene implicaciones. Tal vez usted diseñe un modelo de selección de acciones que muestre resultados bastante buenos a largo plazo. Usted ha estado trabajando en ello durante mucho tiempo, construyendo un método complejo para combinar indicadores técnicos, ratios fundamentales y diseñar reglas exactas. Ahora tus simulaciones muestran claramente que ganarás dinero con estas reglas. La pregunta es, por supuesto, si ganará más o menos dinero que con un enfoque aleatorio y con mayor o menor riesgo. Es bastante fácil construir un modelo que funcione más o menos igual que un enfoque aleatorio y, en ese caso, debes preguntarte si realmente agregaste algún valor. Para demostrar el principio, dejaré que nuestro chimpancé imaginario de oficina, el Sr. Las burbujas muestran algunas de sus mejores estrategias. Empezaremos con un clásico sencillo. The Equal Random 50. Las reglas son realmente simples. Al comienzo de cada mes, Bubbles lanzará dardos imaginarios a una lista igualmente imaginaria de acciones, eligiendo 50 acciones de aquellas que formaban parte del índice S&P 500 el día de la selección. Toda la cartera será liquidada y reemplazada por 50 nuevas acciones el primero de cada mes. Machine Translated by Google Ahora no malinterpreten lo que estamos haciendo aquí. Sólo un chimpancé comerciaría de esta manera. Estamos haciendo esto para conocer el comportamiento de las acciones. Guarda ese teclado. Tendrás mucho tiempo para enviarme correos enojados más tarde. Habiendo seleccionado 50 acciones al azar, simplemente compramos una cantidad igual de cada una. No el número de acciones, por supuesto, sino su valor. En este caso, partimos de 100.000 dólares e invertimos 2.000 en cada acción. Por eso llamamos a la estrategia Equal Random 50. Tamaño igual, selección aleatoria, 50 posiciones. Cuando lleguen los dividendos, se dejarán en efectivo en la cuenta hasta el próximo reequilibrio mensual, cuando se utilizará para comprar acciones nuevamente. Esta estrategia siempre implica una inversión total, a excepción de estos dividendos en efectivo. A esta estrategia del mono no le importa en absoluto qué acciones está comprando o qué está haciendo el mercado en general. ¿Cómo crees que funcionaría tal estrategia? La respuesta intuitiva, que sería tan razonable como incorrecta, es que los resultados se centrarían en torno al índice. Muy parecido a lanzar dardos a un tablero. Si apuntas al centro y lanzas un número suficiente de veces, en teoría deberías obtener una distribución bastante uniforme alrededor del centro. A menos que tengas algún tipo de brazo extraño que tire hacia la izquierda, por supuesto. El error lógico con esa respuesta es suponer que el índice es una especie de promedio. Que no es. El índice es una estrategia comercial sistemática completamente diferente. Y uno mal diseñado además. Dado que las simulaciones aquí utilizan números aleatorios, por supuesto no tendría sentido hacerlo sólo una vez. Después de todo, si das vueltas a la mesa de la ruleta una o dos veces, puede pasar cualquier cosa. Incluso podrías ganar dinero. Pero si lo giras suficientes veces, el resultado final es muy predecible y prácticamente no hay posibilidad de que salgas del Bellagio con dinero en efectivo. Por cada estrategia aleatoria en este capítulo, mostraré 50 iteraciones. Algunos lectores que estudiaron estadística probablemente estén gritando a las páginas en este momento, pero no hay necesidad de preocuparse. He hecho las mismas simulaciones con 500 iteraciones cada una y los resultados no diferirán lo suficiente como para justificar el desorden visual de mostrarte 500 carreras. Machine Translated by Google La Figura 21 1 muestra el resultado de estas 50 iteraciones. No es necesario entrecerrar los ojos para intentar distinguir las diferencias entre todas las líneas. Lo más importante que se debe leer en este gráfico es dónde se compara el índice con los demás. El índice, en este caso el S&P 500 Total Return Index, se muestra como una línea negra gruesa para distinguirlo de los demás. Figura 21 1 Selección aleatoria de acciones Lo primero y más obvio que debes notar en esta imagen es que casi todas las simulaciones de selección aleatoria de acciones terminaron con más dinero que el índice. La segunda cosa que usted podría notar es que el enfoque de selección aleatoria de acciones comenzó a ganarle al índice apenas a principios de siglo. En los años 90, el índice estaba a la cabeza. Volveremos sobre el motivo de esto más adelante. Machine Translated by Google Figura 21 2 Rentabilidades aleatorias de 50 frente a volatilidad Por supuesto, los mayores rendimientos generales no son gratuitos. Las devoluciones rara vez lo son. La Figura 21­2 muestra un diagrama de dispersión de rendimientos versus volatilidad. Esto pinta un panorama algo diferente. Como puede ver aquí, la volatilidad es mayor para todas las estrategias aleatorias. Para algunas de las corridas aleatorias, tenemos rendimientos aproximadamente iguales a largo plazo con mayor volatilidad, y eso claramente no es muy atractivo, pero para muchos de ellos los rendimientos son lo suficientemente altos como para compensar la volatilidad adicional. Quizás notes que todavía no he mencionado los costos comerciales. ¿Qué pasa con los deslizamientos y las comisiones? Hasta el momento no he aplicado ninguno. No, no, no es por la razón que crees. No es para que la selección de monos se vea mejor. Al menos esa no es la razón principal. En realidad, la razón es que, en este caso, no sería una comparación justa si incluyéramos estos costos. No estamos tratando de diseñar un plan comercial práctico aquí, haciendo una contabilidad realista de los costos, deslizamientos y demás para llegar a un número de retorno esperado. Estamos dejando que un chimpancé lance dardos a un tablero y hay muy poco realismo en ello. No, aquí estamos comparando el concepto de índice con el concepto del Sr. Las habilidades de Bubble para lanzar dardos. Si incluimos costos comerciales en su estrategia, también debemos incluir costos comerciales en el índice S&P. El índice no incluye dichas tarifas. El índice no está construido como una especie de estrategia comercial realista con deslizamientos y comisiones. Asumen que usted puede comprar y vender instantáneamente en el mercado por cualquier monto sin ningún cargo ni deslizamiento. Así que es justo que el Sr. Bubbles haga lo mismo. Machine Translated by Google Otros modelos de este libro utilizan costes comerciales realistas, pero en este capítulo más teórico operaremos de forma gratuita. Por ahora, sólo intento señalar algo sobre los mercados de valores. Es evidente que esta estrategia aparentemente ridícula está superando al mercado en el largo plazo. Bueno, al menos tiene una probabilidad superior a la media de terminar con mayores rendimientos, con una volatilidad ligeramente mayor. Como mínimo, podemos concluir que, en comparación con la metodología del índice, los resultados no son del todo escandalosos. Aquí no hay ningún truco en juego. Un modelo de selección aleatoria de acciones habría tenido una alta probabilidad de superar al índice en términos de rentabilidad durante las últimas décadas. Por supuesto, hay una razón para esto, pero vamos a lanzar algunos dardos más antes de llegar al punto real de este capítulo. El modelo que acabamos de probar utilizaba el mismo tamaño. Quizás ese sea el truco aquí. Por las dudas, eliminaremos ese factor de la ecuación. Ingrese el modelo Random Random 50. Random Random 50 es prácticamente igual que Equal Random 50. Reemplaza toda la cartera mensualmente en función de selecciones aleatorias de los miembros del índice. La única diferencia es que no asigna el mismo peso a las posiciones. En cambio, asigna un peso aleatorio. Siempre utilizamos el 100% del efectivo disponible, pero la cantidad que compramos de cada acción es completamente aleatoria. Esto tiene sentido. Después de todo, ¿por qué un chimpancé compraría una cantidad igual de cada acción? Lo que ve aquí, en la Figura 21­3, es un patrón muy similar. Nuevamente vemos un desempeño deficiente a finales de los años 1990, seguido de un desempeño muy superior durante una década y media después. Aquí hay un intervalo bastante amplio entre la mejor y la peor iteración aleatoria, pero incluso con el retraso de los años 90, casi todas superan el índice. Tenga en cuenta cómo se vería este gráfico si, en cambio, trazara los fondos mutuos reales de la vida real frente al índice de la misma manera. De 50 fondos mutuos seleccionados al azar, uno o dos estarían estadísticamente por encima del índice. No mas. Todo lo contrario de lo que muestran las estrategias de los chimpancés. Invierta en un fondo mutuo y tendrá un 20% de posibilidades de superar el índice de referencia. Invierte con un chimpancé y tendrás un 80% de posibilidades. Machine Translated by Google Figura 21 3 Selección aleatoria, tamaño aleatorio En cuanto al diagrama de dispersión, nuevamente parece bastante similar. Tenemos un alcance más amplio, tanto en términos de volatilidad como de rentabilidad. Esto sería de esperar, dados los tamaños aleatorios de las posiciones y los efectos extremos de asignación que podrían traer. Figura 21 4 Selección aleatoria, tamaño aleatorio, rendimientos versus Volatilidad Machine Translated by Google Nuevamente tenemos algunas iteraciones que son claramente peores que el índice. Muestran una mayor volatilidad para rendimientos más bajos, o rendimientos similares para una mayor volatilidad. Pero la mayoría de las iteraciones muestran retornos más fuertes en el largo plazo. Podríamos llevar este pequeño juego aún más lejos y agregar también un aleatorizador para la cantidad de acciones a elegir. Pero realmente no importaría mucho. El resultado final sería predecible. Veríamos una amplitud aún mayor tanto en los rendimientos como en la volatilidad, y aún veríamos un rendimiento esperado más alto para el modelo aleatorio. No, creo que saltaremos al punto de este capítulo. El problema está en el índice. Bien, ahora sabemos que la selección aleatoria de acciones supera al índice, al menos en el sentido de que tiene una probabilidad muy alta de mostrar rendimientos superiores a largo plazo. El efecto se produce con tamaños iguales, con tamaños aleatorios e incluso con un número aleatorio de acciones. Mientras tengamos una cantidad razonable de acciones y evitemos volvernos completamente locos con la asignación, parece que tendremos un desempeño superior. Al menos después de los años 1990. Entonces, ¿cuál es el trato aquí? ¿Acabo de hacerte perder el tiempo leyendo sobre estrategias sin sentido o hay algo que destacar aquí? La cuestión, que es muy fácil de pasar por alto, es con qué nos estamos comparando. ¿Existe realmente una buena razón para utilizar el índice como punto de referencia? Para comprender por qué la selección aleatoria de acciones parece favorable, es necesario comprender qué es realmente el índice. Sólo otro modelo comercial. Un índice de mercado no es una medida objetiva de los mercados financieros. No estoy seguro de si tal cosa existe o incluso de cómo crear una medición de mercado justa. De hecho, nunca he visto una metodología de índice que no tenga fallas de una forma u otra. Sin embargo, eso no es culpa del proveedor del índice. No importa cómo se construya un índice, necesariamente estará orientado hacia algún factor. La mayoría de los índices de mercado modernos se basan en dos factores principales del mercado. Impulso a largo plazo y capitalización de mercado. Veamos primero por qué afirmo que estos índices se basan en el impulso, y luego llegaremos a la cuestión más obvia de la capitalización de mercado. Machine Translated by Google ¿Qué se necesita para que una acción se una al índice S&P 500? La selección real la realiza un comité de forma discrecional, pero cualquier acción que se considere debe cumplir ciertos criterios. El más importante de ellos es que las acciones deben tener un valor total de mercado superior a 5 mil millones de dólares. También existen reglas relativas a la liquidez y el free float, para garantizar que las acciones puedan negociarse a gran escala. Las empresas con un valor de 5 mil millones de dólares no surgen de la nada. Comenzaron siendo pequeños y crecieron. A medida que el precio de las acciones subió, se volvieron más valiosas. En algún momento, se volvieron lo suficientemente valiosos como para ser incluidos en algún índice. Entonces, en un sentido muy real, las acciones del S&P 500 se incluyen principalmente porque tuvieron un fuerte desempeño de precios en el pasado. Esto es lo que convierte al índice en una especie de estrategia de impulso. Quizás no sea una gran estrategia de impulso, pero sigue siendo un factor importante en la selección de acciones. El segundo factor está estrechamente relacionado. La capitalización de mercado actual. Sin embargo, en este caso, el problema está en la ponderación. La mayoría de los índices principales se ponderan según la capitalización de mercado. La excepción más obvia es el Dow Jones, que se pondera exclusivamente según el nivel de precios. El problema con la capitalización de mercado es importante. Si estabas buscando el truco que hace que las estrategias de los chimpancés anteriores parezcan funcionar, aquí lo tienes. El modelo de ponderación de capitalización de mercado de la mayoría de los principales índices tiene el efecto secundario no deseado de destruir la diversificación. La mayoría de la gente asumiría que un índice amplio como el S&P 500 está muy diversificado. Después de todo, cubre las 500 empresas más grandes de Estados Unidos. El problema es que la evolución del precio del índice parecería casi idéntica si simplemente se omitieran las 300 acciones inferiores. Este no es un índice diversificado. Está extremadamente concentrado en unas pocas empresas masivas. Las tres empresas más grandes del índice tienen un peso combinado de alrededor del 10,5%. Eso es aproximadamente el mismo peso que las 230 acciones inferiores. La empresa de mayor peso tiene en este momento un peso de casi el 4%, mientras que la de menor peso es inferior al 0,01%. Eso no es diversificación. Lo que significa es que cuando inviertes en un índice tradicional, en realidad estás comprando una estrategia de factores basada en la capitalización de mercado. Está invirtiendo todo su dinero en un pequeño número de empresas extremadamente grandes. El resto de las acciones del índice realmente no importan. Por eso los chimpancés nos están engañando. no es que lo estén haciendo algo particularmente correcto. Es más como si el índice estuviera haciendo algo mal. Machine Translated by Google En ocasiones, las acciones de gran capitalización superan a las de pequeña capitalización. Eso sucedió entre mediados y finales de los años 1990. Sin embargo, la mayoría de las veces, las empresas de gran capitalización tienen un rendimiento inferior al de las de mediana y pequeña capitalización. Este efecto puede verse incluso dentro de un índice como el S&P 500, donde todas las acciones son, por definición, de gran capitalización. Hay una diferencia bastante grande entre una acción que vale unos pocos miles de millones y otra lo suficientemente grande como para hacer que el PIB de Suiza parezca un error de redondeo. Las empresas más grandes del mundo llegaron a ser tan grandes porque tenían un buen desempeño en empresas de mediana y gran capitalización. Pero una vez que se tiene una capitalización de mercado equivalente al PIB de América del Sur, la pregunta es cuántas veces se puede duplicar a partir de ahí. Por necesidad, estos gigantes se vuelven lentos después de un tiempo. Tendrán mucho más potencial negativo que positivo. Sin embargo, estos índices altamente concentrados son muy populares. Una razón es, por supuesto, que muchas personas no comprenden completamente cuán concentradas están, pero eso es sólo una pequeña parte de la historia. El factor más importante en juego es el pensamiento grupal. Hay un axioma industrial muy antiguo que resulta muy apropiado en este contexto. “Nunca despidieron a nadie por comprar IBM”. Este proverbio proviene de la época en que IBM era una de las empresas más grandes del mundo. El equivalente a Apple o Microsoft ahora. Si compras la proverbial IBM, estás haciendo lo que todos los demás hacen, y no se te puede culpar por eso. La mayoría de las inversiones se realizan en nombre de otras personas. Gestoras de fondos y gestoras de activos de diversa índole. Desde esa perspectiva, no ser culpado es una prioridad. Te pagan mediante tarifas. Si pierdes dinero cuando todos los demás pierden dinero, estarás bien. Nadie puede culparte por eso. Pero si intenta hacer lo mejor que puede y corre el riesgo de fracasar, es posible que lo despidan. Así que le conviene comprar IBM. La razón del rendimiento aparentemente superior de las estrategias aleatorias es que no utilizaron ponderación de capitalización de mercado. Esta es también la razón por la cual tuvieron un desempeño inferior en los años 1990. Durante la crisis informática de 2000 a 2002, las empresas más grandes sufrieron la mayor paliza. Por eso la mayoría de las estrategias aleatorias no lo hicieron. El punto aquí, supongo, es que debes saber cuál es el índice. También debe decidir si su objetivo es superar el índice o si su objetivo es independiente del índice. En la industria financiera hablamos de estrategias relativas y estrategias de retorno absoluto. Con demasiada frecuencia, estos enfoques tan diferentes se mezclan. Una estrategia relativa siempre debe compararse con su índice de referencia, mientras que una estrategia de retorno absoluto no. Machine Translated by Google A menudo, esto puede ser un problema en la industria. El hecho de que la mayoría de la gente no sea consciente de la distinción. Si tiene una estrategia de retorno absoluto y termina un año con un 15 por ciento más, normalmente se consideraría un buen año. Si los mercados de valores cayeron un 20 por ciento ese año, sus clientes confiarán en usted para cuidar a sus hijos durante el fin de semana. Pero si los mercados bursátiles subieran un 25 por ciento, rápidamente lo sacarían de la lista de tarjetas navideñas. Buscando al Sr. Burbujas En caso de que quieras probar esto en casa, te proporcionaré el código fuente utilizado para este experimento. Gran parte del código aquí es el mismo que el del modelo de impulso, pero sin la elegante lógica de asignación e impulso. Antes de mostrar el código completo, veamos las partes interesantes. Además de las mismas declaraciones de importación que tuvimos la última vez, también necesitamos obtener una biblioteca que pueda generar números aleatorios para nosotros. # Para generar números aleatorios a partir de importación aleatoria random, seed, randrange Estamos usando esto para seleccionar acciones aleatorias. La lista real de acciones elegibles se obtiene de la misma manera que antes. # Verifique las acciones elegibles todays_universe = [symbol(ticker) para el ticker en context.index_members.loc[context.index_members.index < today].iloc[­1,0].split(',') ] La selección real se realiza mediante un bucle que se ejecuta una vez al mes. Aquí hacemos un bucle una vez para cada acción que necesitaríamos seleccionar y usamos la biblioteca aleatoria para generar un número. Nos aseguramos de que este número esté entre cero y la longitud de la lista de acciones, menos uno. Recuerde que las listas están basadas en cero. También verá el uso de pop() aquí, que es una forma conveniente de seleccionar un elemento de una lista y eliminarlo de la lista al mismo tiempo. Esto garantiza que no elegiremos la misma acción dos veces. para i en np.arange(1, número_de_acciones +1): num = randrange(0, len(hoy_universo) ­1) compras.append(hoy_universo.pop(num)) La única otra parte aquí que es realmente nueva es cómo ejecutamos una prueba retrospectiva varias veces con los resultados almacenados. En este caso, lo único que me interesaba era el valor de la cartera a lo largo del tiempo, así que eso es todo lo que almacené en este bucle. Machine Translated by Google # Ejecute las pruebas retrospectivas para i en np.arange(1, number_of_runs ' print('Ejecución de procesamiento + 1): + str(i)) resultado = zipline.run_algorithm (inicio = inicio, fin = fin, inicialización = inicialización, base_capital = 100000, frecuencia_datos = 'diario', paquete = 'ac_equities_db') df[i] = resultado['valor_cartera'] Al final, tendremos un DataFrame con el desarrollo del valor de la cartera a lo largo del tiempo, una columna para cada ejecución de backtest. Ahora todo lo que necesitas hacer es hacer algunos gráficos bonitos o calcular algunos análisis útiles. La fuente completa del modelo de selección aleatoria de acciones se encuentra a continuación. %matplotlib en línea importar tirolesa desde zipline.api importar order_target_percent, símbolo, set_commission, \ set_slippage, función_programación, reglas_fecha, reglas_hora desde zipline.finance.commission importe PerTrade, PerDollar desde zipline.finance.slippage importe VolumeShareDeslizamiento, FijoDeslizamiento desde fecha y hora importar fecha y hora importar pytz importar pandas como pd importar numpy como np # Para generar números aleatorios a partir de importación aleatoria random, seed, randrange """ Ajustes """ number_of_runs = 2 random_portfolio_size = False number_of_stocks = 50 # tamaño de la cartera, si no es aleatorio sizing_method = 'equal' # igual o aleatorio enable_commission = Falso Commission_pct = 0.001 enable_slippage = Falso slippage_volume_limit = 0.025 slippage_impact = 0.05 def inicializar (contexto): # Obtener y almacenar membresía de índice context.index_members = pd.read_csv('../data/index_members/sp500.csv', index_col=0, parse_dates=[0]) # Establecer comisión y deslizamiento. Machine Translated by Google si enable_commission: comm_model = Por dólar (coste = comisión_pct) más: comm_model = Por dólar (coste = 0,0) set_commission(modelo_comunicación) si enable_slippage: slippage_model=VolumeShareDeslizamiento(volume_limit=slippage_volume_limit, price_impact=slippage_impact) de lo contrario: slippage_model=Fixed Slippage(spread=0.0) set_slippage(slippage_model) función_programación( func=reequilibrio, fecha_rule=date_rules.month_start(), time_rule=time_rules.market_open() ) def reequilibrio (contexto, datos): hoy = zipline.api.get_datetime() # Verifique las acciones elegibles todays_universe = [symbol(ticker) para el ticker en context.index_members.loc[context.index_members.index < today].iloc[­1,0].split(',') ] # Haga una lista de acciones para comprar = [] # Para modificar la variable global y no crear una nueva global number_of_stocks # Si se seleccionan acciones aleatorias if random_portfolio_size: # Compre entre 5 y 200 acciones. número_de_existencias = rango aleatorio(5, 200) # Seleccione acciones para i en np.arange(1, número_de_acciones +1): num = randrange(0, len(hoy_universo) ­1) compras.append(hoy_universo.pop(num)) # Vender posiciones que ya no se desean. para seguridad en context.portfolio.positions: if (seguridad no en compras): order_target_percent(seguridad, 0.0) Machine Translated by Google #Crear un DataFrame vacío para mantener los tamaños de las posiciones objetivo buy_size = pd.DataFrame(index=buys) # Obtener tamaños aleatorios, si está habilitado. si size_method == 'aleatorio': buy_size['rand'] = [randrange(1,100) para x en buy_size.iterrows()] buy_size['target_weight'] = buy_size['rand'] / buy_size['rand'].sum() elif size_method == 'equal': buy_size['target_weight'] = 1.0 / número_de_acciones # Enviar órdenes de compra por seguridad en compras: order_target_percent(security, buy_size.loc[security, 'target_weight']) inicio = fecha y hora (1996, 1, 1, tzinfo=pytz.UTC) fin = fecha y hora (2018, 12, 31, tzinfo=pytz.UTC) # Marco de datos vacío para contener los resultados df = pd.DataFrame() # Ejecute las pruebas retrospectivas para i en np.arange(1, number_of_runs ' print('Ejecución de procesamiento + 1): + str(i)) resultado = zipline.run_algorithm (inicio = inicio, fin = fin, inicialización = inicialización, base_capital = 100000, frecuencia_datos = 'diario', paquete = 'ac_equities_db') df[i] = resultado['valor_cartera'] print('Todo listo. Listo para analizar.') Una vez finalizado este código, los datos resultantes están en el objeto df y puede guardarlos en el disco o utilizar las técnicas descritas en el capítulo anterior para representarlos o analizarlos. Capítulo invitado: Medición relativa Actuación Machine Translated by Google Robert Carver es un operador de futuros sistemático independiente, escritor y consultor de investigación; y actualmente es profesor invitado en Queen Mary, Universidad de Londres. Es autor de "Comercio sistemático: un nuevo método único para diseñar sistemas de comercio e inversión" y "Carteras inteligentes: una guía práctica para crear y mantener carteras de inversión inteligentes". Hasta 2013, Robert trabajó para AHL, un gran fondo de cobertura sistemático y parte del Man Group. Fue responsable de la creación de la estrategia macro global fundamental de AHL y luego gestionó la cartera de renta fija multimillonaria del fondo. Antes de eso, Robert negoció derivados exóticos para el banco de inversión Barclays. Robert tiene una licenciatura en Economía de la Universidad de Manchester y una maestría, también en Economía, del Birkbeck College de la Universidad de Londres. Python es una gran herramienta para realizar pruebas retrospectivas y proporcionar abundante información de diagnóstico sobre el rendimiento potencial de sus estrategias comerciales. Pero las herramientas sólo son útiles si sabes cómo utilizarlas correctamente; de lo contrario, pueden ser peligrosas. Intente cortar un trozo de madera con una motosierra con los ojos vendados y vea qué tan bien funciona. De manera similar, un backtest superficialmente maravilloso puede resultar en un daño grave a su patrimonio, si resulta que la estrategia que ha desarrollado no es sólida. En este capítulo me centraré en analizar el rendimiento relativo : una estrategia comercial frente a otra. Hay varias situaciones diferentes en las que el rendimiento relativo importa. En primer lugar, todas las estrategias deberían compararse con algún tipo de punto de referencia. Por ejemplo, si su estrategia se centra en comprar acciones seleccionadas del S&P 500, entonces debería poder superar al índice S&P 500. Otra comparación importante es entre diferentes variaciones de la misma estrategia. Encontrar la variación óptima de la estrategia a veces se conoce como ajuste . Considere el modelo de seguimiento de tendencias de futuros que Andreas presentó en el capítulo 21. La regla de entrada tiene tres parámetros: las dos longitudes de promedio móvil y el número de días que miramos hacia atrás para detectar una ruptura. Andreas utiliza valores de 40 días, 80 días y 50 días para estos parámetros. Probablemente estos no sean malos valores de parámetros (ignore su exterior jocoso: Andreas no es ningún idiota). ¿Pero tal vez podríamos hacerlo mejor? Para probar esto, podríamos generar pruebas retrospectivas para diferentes conjuntos de valores de parámetros y elegir el mejor. Machine Translated by Google Las comparaciones entre estrategias también pueden ser importantes si está ejecutando varias estrategias y necesita decidir cuánto de su valioso efectivo asignar a cada una. Una estrategia que tenga mejores resultados en el backtest probablemente merezca una asignación mayor. La comparación final que quizás desee hacer es entre una versión más antigua y una más nueva de su estrategia comercial. Una vez que tienes una estrategia de trading, surge una necesidad casi insoportable de modificarla y mejorarla. En poco tiempo se encontrará con un backtest ligeramente mejorado. Sin embargo, se requiere una comparación adecuada para ver si vale la pena actualizar al último modelo o seguir con la confiable versión original. Una cuestión clave con todas estas diferentes comparaciones es la importancia . Su estrategia debería ser significativamente mejor que un punto de referencia. Si va a elegir una variación de una estrategia comercial, con un conjunto particular de parámetros, entonces debería ser significativamente mejor que las alternativas. Al asignar capital entre estrategias, si va a poner muchos huevos en una canasta determinada, debe estar seguro de que esa canasta es significativamente mejor que otros contenedores de huevos disponibles. Finalmente, si está pensando en cambiar de una estrategia a otra, debe determinar si la mejora en los retornos es lo suficientemente significativa como para justificar el trabajo. Otro tema importante es la comparabilidad . Si no ha elegido un punto de referencia apropiado para su estrategia, sus resultados no tendrán sentido. No tiene mucho sentido que un seguidor de tendencias de futuros diversificados utilice el índice S&P 500 como punto de referencia, aunque a menudo verá esta comparación en los materiales de marketing. El índice de referencia y la estrategia también deberían tener un riesgo similar, o la estrategia más riesgosa tendrá una ventaja. En este capítulo me centraré en comparar los rendimientos del modelo Equity Momentum de Andreas con un punto de referencia: el índice S&P 500. Sin embargo, las ideas generales también serán aplicables en otras situaciones. Algunos de estos tipos de comparaciones se pueden realizar automáticamente utilizando herramientas como pyfolio, pero en este capítulo le mostraré cómo hacer sus propios cálculos a partir de los primeros principios. Esto le permitirá comprender mejor lo que está sucediendo. Primero veamos las devoluciones. Puede descargar estos archivos desde el sitio web de Andreas, junto con este código fuente. Los archivos que necesita son SPXTR.csv y equidad_momentum.csv, ambos ubicados en el archivo de descarga del código fuente, en la carpeta Backtests. Encontrará los archivos en www.followingthetrend.com/trading­evolved . Machine Translated by Google importar pandas como pd data_path = '../Backtests/' strat = "equity_momentum" bench = 'SPXTR' benchmark = pd.read_csv("{}{}.csv".format(data_path, bench), index_col=0, header =Ninguno, parse_dates=Verdadero) estrategia = pd.read_csv("{} {}.csv".format(data_path, strat), index_col=0, encabezado=Ninguno, parse_dates=Verdadero) Al comparar dos estrategias, debemos asegurarnos de que los rendimientos cubran el mismo periodo de tiempo: primera_fecha = pd.Series([benchmark.index[0], estrategia.index[0]]).max() punto de referencia = punto de referencia[primera_fecha:] estrategia = estrategia[primera_fecha:] Tambien debemos asegurarnos de que nuestras devoluciones se produzcan al mismo intervalos, haciendo coincidir los rendimientos del índice de referencia con la estrategia mediante el uso de relleno directo para reemplazar cualquier dato faltante: punto de referencia = punto de referencia.reindex(estrategia.índice).ffill() Ahora vamos a trazar las curvas de cuenta de las dos estrategias. yo prefiero para hacer esto utilizando rendimientos porcentuales acumulados. El primer paso es calcular los rendimientos porcentuales: benchmark_perc_returns = benchmark.diff()/benchmark.shift(1) estrategia_perc_returns = estrategia.diff()/strategy.shift(1) Ahora acumulamos estos: benchmark_cum_returns = benchmark_perc_returns.cumsum() estrategia _cum_returns = estrategia_perc_returns.cumsum() ambos_cum_returns = pd.concat([benchmark_cum_returns, estrategia_cum_returns], eje=1) ambos_cum_returns.columns=['benchmark', 'estrategia'] ambos_cum_returns.plot() ' tiempo ffil l ': Machine Translated by Google Figura 22 1 Rentabilidades acumuladas Por cierto, esto equivale a trazar las curvas de cuenta en una escala logarítmica. Con este tipo de gráfico es más fácil ver el rendimiento a lo largo de todo el historial de la curva de la cuenta, ya que una ganancia o pérdida del 10% tiene exactamente la misma escala independientemente del período de tiempo que estemos analizando. A primera vista, parece que la estrategia es significativamente mejor que la punto de referencia. Veamos la diferencia en el rendimiento. diff_cum_returns = estrategia_cum_returns ­ benchmark_cum_returns diff_cum_returns.plot() Machine Translated by Google Figura 22 2 Diferencia en rendimientos acumulados Ahora podemos ver más claramente que el rendimiento superior se limita a los primeros siete años aproximadamente del backtest. Después de eso, el rendimiento relativo es estable. ¿Es significativo este cambio en el rendimiento? Esa es una buena pregunta y volveremos a ella más adelante, cuando tengamos las herramientas necesarias para responderla. Antes de hacer eso, comencemos con un análisis básico. ¿Cuánto dinero genera cada estrategia? Personalmente, me gusta calcular los rendimientos anuales. Los rendimientos son diarios, por lo que debemos multiplicarlos por el número de días de cada año. Nuestras devoluciones excluyen los fines de semana y otros días no comerciales. Hay aproximadamente 256 días hábiles en un año (de hecho, hay otra razón por la que uso este número específico): imprimir(estrategia_perc_returns.mean()*256) 0.124743 imprimir (benchmark_perc_returns.mean()*256) 0.094309 Se trata de un rendimiento superior bastante decente para la estrategia, 12,5% anual frente al 9,4% del índice de referencia: poco más del 3%. Pero no sería una comparación justa si la estrategia fuera mucho más riesgosa que el punto de referencia. Comprobémoslo, utilizando la desviación estándar anual de los rendimientos como nuestra medida de riesgo. Esa no es una medida perfecta del riesgo, ya que supone que nuestros rendimientos siguen una distribución estadística particular conocida como normal gaussiana. Sin embargo, servirá por ahora. Machine Translated by Google imprimir(estrategia_perc_returns.std()*16) 0.189826 imprimir(benchmark_perc_returns.std()*16) 0.192621 Observe que para anualizar las desviaciones estándar multiplicamos por la raíz cuadrada del número de días de un año. La raíz cuadrada de 256 es exactamente 16. Ahora puedes ver por qué me gusta tanto este número 256. El riesgo de la estrategia es muy similar pero no igual. Para compensar esto necesitamos . fuera más riesgosa, un inversor podría comprar usar retornos ajustados al riesgo. Si una estrategia el índice de referencia, agregar un poco de apalancamiento y terminar con un nivel de riesgo comparable al riesgo, y si una estrategia fuera menos riesgosa como Es aquí donde el inversor podría comprar una cantidad menor del índice de referencia para igualarlo y tener algo de efectivo excedente para ganar intereses. Estrictamente hablando, deberíamos calcular los rendimientos ajustados al riesgo utilizando . El índice de Sharpe representa la tasa de interés que tenemos que pagar el índice de Sharpe. cuando utilizamos el apalancamiento, o cualquier interés que ganemos por tener un exceso de efectivo. Sin embargo, para simplificar las cosas en este capítulo, usaremos una versión simplificada del índice de Sharpe, que es igual al rendimiento dividido por la desviación estándar. def simple_sharpe_ratio(perc_returns): retorno (perc_returns.mean()*256) / (perc_returns.std()*16) simple_sharpe_ratio (estrategia_perc_returns) 0.657144 simple_sharpe_ratio (benchmark_perc_returns) 0.489609 Incluso después de tener en cuenta el riesgo, la estrategia parece mejor. Otra forma de demostrar esto es ajustar el índice de referencia para que tenga el mismo riesgo que la estrategia. ajustado_benchmark_perc_returns = benchmark_perc_returns * estrategia_perc_returns.std() / benchmark_perc_returns.std() ajustado_benchmark_cum_returns = ajustado_benchmark_perc_returns.cumsum() Recuerde que esto ignora el costo de apalancar la estrategia para igualar el riesgo del índice de referencia, o cualquier interés ganado por el exceso de efectivo porque no necesitamos poner el 100% de nuestro dinero en el índice de referencia para igualar el riesgo de la estrategia. Esto favorece ligeramente la estrategia. Volvamos a calcular el rendimiento superior, esta vez anualizando la diferencia promedio en rendimientos: Machine Translated by Google diff_cum_returns = estrategia_cum_returns ­ ajuste_benchmark_cum_returns diff_returns = diff_cum_returns.diff() diff_returns .mean()*256 0.032538 Si se ajusta el menor riesgo de la estrategia, la mejora anual es ligeramente superior: 3,3% anual. Suena muy bien. Sin embargo, ¿podemos realmente creerlo? La suerte juega un papel importante en el trading y siempre deberíamos preguntarnos si los beneficios del trading son sólo una casualidad. Existe una forma formal de probar esto conocida como "prueba T". La prueba T calcula la probabilidad de que la media de los rendimientos de la estrategia sea mayor que la media de los rendimientos del índice de referencia. La prueba T se encuentra en otra biblioteca de Python, scipy : desde scipy.stats importe ttest_rel ttest_rel(strategy_perc_returns, ajustad_benchmark_perc_returns, nan_policy='omit') Ttest_relResult(estadística=masked_array(datos = [1.0347721604748643], máscara = [Falso], valor_relleno = 1e+20), pvalue=matriz_enmascarada(datos = 0.30082053402226544, máscara = Falso, valor_relleno = 1e+20) ) Los números clave aquí son el estadístico T (1,03477) y el valor p (0,3082). Un estadístico T alto hace que sea más probable que la estrategia sea mejor que el punto de referencia. El valor p cuantifica la probabilidad. Un valor p de 0,3 indica que existe un 30% de posibilidades de que la estrategia tenga el mismo rendimiento promedio que el índice de referencia. Esto es un poco mejor que la suerte (que sería un 50% de posibilidades), pero no mucho más. Cualquier valor inferior al 5% normalmente se considera un resultado significativo: por lo tanto, esta estrategia no ha pasado la prueba de significancia. Cuando utilice la regla del 5%, tenga en cuenta la siguiente advertencia: si prueba 20 estrategias comerciales, es probable que descubra al menos una que sea significativamente (5%) mejor que el punto de referencia. Si ha considerado y descartado muchas estrategias, entonces debería utilizar un valor p más estricto. Necesito hacer otra nota de precaución: la prueba T hace algunas suposiciones sobre la distribución estadística de los rendimientos. Supuestos que normalmente se violan violentamente cuando se utilizan datos financieros. Para dar cuenta de esto necesitamos hacer algo diferente, con un nombre bastante grandioso: 'Prueba T no paramétrica'. Machine Translated by Google Esta prueba se realizará utilizando una técnica de 'Monte Carlo'. No tiene nada que ver con el juego, aunque el comercio y el juego son primos cercanos, pero tiene mucho que ver con la aleatoriedad. Vamos a generar aleatoriamente una gran cantidad de 'historias alternativas'. Cada historial tendrá la misma longitud que los datos reales probados y constará de puntos extraídos aleatoriamente del backtest. Para cada historial medimos la diferencia de rentabilidad entre el índice de referencia y la estrategia. Luego observamos la distribución de todas las diferencias. Si menos del 5% de ellos son negativos (la estrategia tiene un rendimiento inferior al del índice de referencia), entonces la estrategia pasará la prueba T. importar numpy como np monte_carlo_runs = 5000 # agranda esto si tu computadora puede soportar length_returns = len(diff_returns.index) bootstraps = [[int(np.random.uniform(high=length_returns)) for _not_used1 in range(length_returns)] for _not_used2 in range( monte_carlo_runs)] def Average_given_bootstrap(one_bootstrap, diff_returns): subset_returns = diff_returns.iloc[one_bootstrap] Average_for_bootstrap = np.float(subset_returns.mean()*256) return Average_for_bootstrap bootstrapped_return_differences = [average_given_bootstrap(one_bootstrap, diff_returns) para one_bootstrap en bootstraps] bootstrapped_return_differences = pd.Series(bootstrapped_return_differences) bootstrapped_return_differences.plot.hist(bins=50) Aquí hay una visualización de la distribución: Figura 22 3 Distribución El promedio de esta distribución es de alrededor del 3%: en línea con el rendimiento superior promedio de la estrategia. Pero parte de la distribución está por debajo del 0%, lo que sugiere que existe una posibilidad razonable de que la diferencia en los rendimientos sea en realidad cero o negativa. Podemos comprobar esto matemáticamente: Machine Translated by Google suma(bootstrapped_return_differences<0)/float(len(bootstrapped_return_differences)) 0.1466 No se preocupe si obtiene un número ligeramente diferente debido a la aleatoriedad involucrada. Podemos interpretar esto como que hay una probabilidad del 14,7% de que la estrategia no esté realmente superando al índice de referencia. Este es un mejor resultado que el 30% que obtuvimos antes, usando la prueba T estándar de pantano, aunque aún no llega al valor crítico del 5% que se usa normalmente. Esta técnica de arranque se puede utilizar con cualquier tipo de estadística: correlación, volatilidad, Beta... simplemente reemplace el cálculo en la función Average_given_bootstra p y podrá tener una idea realista de con qué precisión podemos medir realmente la estadística relevante. Un método alternativo para comparar una estrategia y un punto de referencia es . calculan su alfa y beta en el Disculpas por dejar caer una profusión de letras griegas, texto. Estos valores surgen de la idea de que los inversores sólo deberían preocuparse por el rendimiento superior después de deducir el rendimiento que se obtiene al estar expuesto al índice de referencia del mercado. La exposición al índice de referencia del mercado se mide por beta. El rendimiento superior restante se denomina entonces alfa. Para ser técnicos, la beta es la covarianza de la estrategia con respecto al índice de referencia. Para ser menos técnico, una beta de 1.0 indica que espera obtener aproximadamente la misma cantidad de rendimiento que el punto de referencia. Una beta superior a 1,0 implica que espera obtener un mayor rendimiento porque su estrategia es más riesgosa. Una beta inferior a 1,0 sugiere que se espera un rendimiento menor debido a una menor exposición al índice de referencia, ya sea porque su estrategia tiene una desviación estándar más baja que el índice de referencia o porque tiene una correlación relativamente baja con el índice de referencia. Para medir la beta necesitamos utilizar otra técnica estadística sofisticada: la regresión lineal . El primer paso es comprobar que nuestro punto de referencia es sensato para nuestra estrategia. Lo que buscamos aquí es una buena relación lineal entre la estrategia y los rendimientos del índice de referencia. Dejando a un lado algunos valores atípicos, eso es principalmente lo que vemos. Hay formas más sofisticadas de hacer esto, pero para mí no hay nada mejor que mirar los datos. ambos_retornos = pd.concat([estrategia_perc_returns, punto de referencia_perc_returns], eje=1) ambos_pesos.columnas = ambos_retornos.columnas = ['estrategia', 'punto de referencia'] Both_returns.plot.scatter(x="benchmark", y="estrategia") Machine Translated by Google Figura 22 4 Dispersión Debido a que la regresión lineal tiene en cuenta diferentes niveles de riesgo, podemos utilizar la rendimientos originales no ajustados aquí. importar statsmodels.formula.api como smf lm = smf.ols(fórmula= 'estrategia ~ punto de referencia' , datos=both_returns).fit() lm.params Interceptar 0.000227 punto de referencia 0,706171 La primera cifra, denominada intersección, es el alfa. La segunda figura, denominado punto de referencia, es el valor de beta. El alfa es positivo, lo cual es bueno. y equivale a alrededor del 5,8% anual. ¿Pero es esto sólo suerte o estadísticamente? ¿significativo? Vamos a revisar: lm.summary() Resultados de la regresión MCO Dep. Variable: estrategia R­cuadrado: 0.513 Modelo: OLS Adj. R­cuadrado: 0.513 Método: Mínimos cuadrados Estadística F: 5840. Fecha: viernes, 07 de junio de 2019 Prob (estadística F): 0.00 Tiempo: 07:47:09 Logística de probabilidad: 18687. N° Observaciones: 5536 AIC: ­3.737e+04 Machine Translated by Google Residuales Df: 5534 Modelo Df: 1 Tipo de covarianza: no robusto coef Interceptar 0.000 2 punto de referencia 0.706 k 2 t P>|t| [0,025 0,000 2,041 0,04 1 8.97e­06 enfermedad de transmisión sexual errar 0.009 921.67 7 General: Problema (ómnibus) : ­3.736e+04 BIC: 0.000 76,42 0.00 2 0 Durbin­Watson: 0,688 0.975 ] 0.000 0,724 1.921 Jarque­Bera 17845.37 (JB): 4 Sesgar: ­0,145 Problema(JB): 0.00 Curtosis: 11.791 Cond. No. 83.1 Son muchas estadísticas y no voy a tratar de explicárselas todas. aquí. Pero la clave es 'P>|t|' columna encima de la fila 'Interceptar'. Eso muestra el valor p para una prueba T de que el valor de la intersección es positivo. El valor de 0,041, o 4,1%, sugiere que sólo hay un 4,1% de posibilidades de que la intersección sea en realidad cero o negativo. Esto está por debajo del valor crítico del 5%, por lo que podemos estar razonablemente seguro de que la estrategia es mejor que el punto de referencia. En realidad, estos resultados se basan en aún más supuestos: los rendimientos de ambos la estrategia y el punto de referencia deben tener buenas distribuciones estadísticas (lo cual es poco probable), y necesitan tener una relación lineal (recuerde que verificamos esto visualmente, y parecía que era cierto). En principio podríamos utilizar un bootstrap para hacer esta prueba de forma no paramétrica, pero eso está fuera del alcance de esto capítulo. Volvamos ahora a una pregunta que hicimos antes: ¿es el plano relativo? ¿Significa el desempeño de la estrategia después de 2005? Podemos comprobar esto usando otro tipo de prueba t, que no requiere que los dos conjuntos de datos que se están probando emparejar. Machine Translated by Google de scipy.stats import ttest_ ind split_date = pd.datetime(2006,1,1) ttest_ind(diff_returns[diff_returns.index<split_date], diff_returns[diff_returns.index>=split_date], nan_policy='omit') Ttest_indResult(estadística=masked_array(datos = [1.6351376708112633], máscara = [Falso], valor_relleno = 1e+20), pvalue=matriz_enmascarada(datos = 0.10207707408174886, máscara = Falso, valor_relleno = 1e+20) ) A estas alturas deberías ser un experto en interpretar estos números. El valor p (0,102) es razonablemente bajo (aunque no inferior al 5%), lo que sugiere que hay muchas posibilidades de que el rendimiento después de enero de 2006 sea inferior al rendimiento anterior. ¿Cómo podemos utilizar esta información? Quizás estés pensando: "Está bien, necesito modificar la estrategia para que siga teniendo mejores resultados después de 2006". ¡No hagas esto! Este es un ejemplo de lo que yo llamo ajuste implícito . El ajuste implícito es donde cambia su estrategia comercial después de observar todos los resultados del backtest. ¡Esto es hacer trampa! Se supone que un backtest nos muestra cómo podríamos haberlo hecho en el pasado. Pero cuando empezamos a operar en 1997, no podríamos haber sabido qué pasaría con la estrategia después de 2006, a menos que tuviéramos acceso a una máquina del tiempo. El ajuste implícito conduce a dos problemas graves. En primer lugar, probablemente terminaremos con una estrategia comercial demasiado complicada, probablemente sobreadaptada y, por lo tanto, no funcionará bien en el futuro. En segundo lugar, el desempeño pasado de nuestra estrategia parecerá mejor de lo que realmente podría ser. Como no podemos modificar la estrategia, pensemos en un escenario simple en el que intentamos decidir qué parte de nuestra cartera asignar a la estrategia comercial y cuánto poner en el índice de referencia. Primero vamos a utilizar el ajuste implícito. Asignamos el 100% de nuestro capital a la estrategia hasta enero de 2006 (ya que la estrategia es mucho mejor), y posteriormente ponemos el 50% en cada una de las estrategias y en el índice de referencia (ya que parecen funcionar igualmente bien después de eso). peso_estrategia = pd.Series([0.0]*len(strategy_perc_returns), índice= estrategia_perc_returns .index) peso_benchmark = pd.Series([0.0]*len(benchmark_perc_returns), índice=benchmark_perc_returns.index) peso_estrategia[peso_estrategia.index<fecha_split] = 1,0 peso_benchmark[benchmark_weight.index<fecha_split]=0,0 peso_estrategia[peso_estrategia.index>=fecha_dividida] = 0,5 Machine Translated by Google benchmark_weight[benchmark_weight.index>=split_date]=0.5 ambos_pesos = pd.concat([estrategia_peso,benchmark_weight], eje=1) ambos_retornos = pd.concat([estrategia_perc_returns, punto de referencia_perc_returns], eje=1) ambos_pesos.columnas = ambos_retornos.columnas = ['estrategia', 'punto de referencia'] retornos_ajuste_implícitos = ambos_pesos*ambos_retornos retornos_ajuste_implícitos = retornos_ajuste_implícitos.sum(eje=1) Ahora vamos a hacer las cosas correctamente; tomar decisiones de asignación utilizando solo datos pasados, sin acceso a una máquina del tiempo. Asignar capital es un negocio bastante complicado, así que para simplificar las cosas voy a utilizar una regla en la que asignamos en proporción al rendimiento anual histórico de cada activo, en relación con un rendimiento anual del 11% (que es aproximadamente el rendimiento promedio tanto en la estrategia como en el índice de referencia). Como sabemos que el rendimiento relativo de las estrategias se degrada con el tiempo, usaremos los rendimientos de los últimos 5 años para determinar esa media histórica (para ser precisos, estamos usando un promedio móvil ponderado exponencialmente con una vida media de 2,5 años). , que es el equivalente más suave de una media móvil simple de 5 años). Usar un período más corto daría como resultado pesas demasiado ruidosas. Rolling_means = pd.ewma(both_returns, halflife = 2.5*256) Rolling_means = Rolling_means + (0.16/256) Rolling_means[rolling_means<0]=0.000001 total_mean_to_normalise = Rolling_means.sum(axis=1) total_mean_to_normalise = pd.concat([total_mean_to_normalise] *2, eje=1) media_total_para_normalizar.columnas = medios_rodantes.columnas pesos_rodantes = medias_rodantes / media_total_para_normalizar pesos_rodantes.plot() Figura 22 5 Machine Translated by Google Estos pesos no se ven tan diferentes de los pesos implícitos de trampa; Pusimos la mayor parte de nuestro capital en la estrategia antes de 2006, y tenemos algo cercano a una división equitativa después de 2011. Sin embargo, en el período entre 2006 y 2011 la media móvil todavía se está ajustando al cambio en el rendimiento relativo, por lo que terminamos poniendo más en la estrategia que el ajuste implícito. balanceo _fit_returns = balanceo_pesos*ambos_retornos balanceo _fit_returns = balanceo_fit_returns.sum(axis=1) compare_returns = pd.concat([rolling_fit_returns, implicit_fit_returns], eje=1) compare_returns.columns = ['rolling', 'implícito'] compare_returns.cumsum().plot() Figura 22 6 diff_compare = implícito_fit_returns ­ Rolling_fit_returns diff_compare.cumsum().plot() Figura 22 7 Machine Translated by Google No sorprende que el ajuste implícito tenga mejores resultados que el ajuste rodante, y todo el rendimiento superior se produjo antes de 2008, cuando hace trampa al asignar todo a la estrategia. Después de 2009, ambos métodos están haciendo prácticamente lo mismo (en 2008, el ajuste implícito funciona ligeramente mejor, ya que todavía tiene un peso mayor en la estrategia que supera al punto de referencia). En cifras concretas, si comparamos la rentabilidad ajustada al riesgo: simple_sharpe_ratio(rolling_fit_returns) 0.669280152423306 simple_sharpe_ratio(retornos_ajuste_implícitos) 0.6708715830455622 Con sólo hacer un poco de trampa hemos conseguido aumentar el ratio de Sharpe. ¿Imagínese cuánto más obtendríamos si hiciéramos algunas modificaciones serias? En poco tiempo, podría tener un índice de Sharpe de 2,0 y creer que es un operador genio que puede utilizar con seguridad un apalancamiento excesivo y permitirse pagar altos costos comerciales a su corredor. Estas son suposiciones peligrosas que tendrán un resultado final deprimente: una cuenta comercial vacía. Tenga mucho cuidado de no seguir el camino del ajuste implícito. Utilice únicamente datos que realmente hubieran estado disponibles en el pasado para modificarlos, ajustarlos o asignarlos a estrategias comerciales. Importando tus datos El error más común cuando las personas intentan configurar su primer entorno de backtesting de Python es conectar los datos. Esta no es una tarea trivial, al menos si tienes que resolverla por ti mismo. Es por eso que dedicaré un poco de tiempo al tema y les mostraré algo de código fuente que, con suerte, les ayudará y les ahorrará algunos dolores de cabeza. El primer paso es, por supuesto, adquirir los datos reales. Necesita encontrar un proveedor de datos donde pueda obtener los datos que necesita a un precio con el que esté satisfecho. En lugar de dar una sugerencia aquí, escribiré sobre los proveedores de datos en mi sitio web, donde es más fácil actualizar y agregar dicha información. Si simplemente desea continuar y aprender sobre el backtesting de Python de inmediato, puede descargar datos generados aleatoriamente desde mi sitio web, en www.followingthetrend.com/ trading­evolved , donde publicaré datos y códigos relacionados con este libro. Machine Translated by Google En este libro, utilizamos el motor de backtesting Zipline para todo el código de muestra. Tuve que elegir un backtester para el libro y me pareció una buena opción. La conexión de datos a un backtester funciona de manera diferente para cada software de backtesting. Si desea utilizar un software diferente, también deberá descubrir cómo conectar los datos. Los datos sobre acciones y futuros funcionan de manera bastante diferente, y eso significa que funcionan un poco diferente cuando importamos dichos datos. Necesita un código ligeramente diferente para las dos clases de activos y comenzaré importando datos de acciones. Hay dos formas principales de abordar la gestión de datos local. Cuál de ellos elija depende en gran medida de su propia situación y de cómo planea trabajar con sus datos. Puede importar datos directamente desde cualquier formato que le proporcione su proveedor de datos a Zipline. O puede organizar todos sus datos en una base de datos local y usarla como una capa entre su proveedor de datos y Zipline. Si recién estás comenzando en el mundo del backtesting, probablemente recién desea importar los datos y comenzar. Rapido y Facil. Si, por el contrario, está más avanzado o aspira a estarlo, puede ver los beneficios de utilizar una base de datos de valores local. De esa manera, puede combinar datos de múltiples proveedores de datos en un formato uniforme. Puede acceder a los datos directamente a través de varias herramientas que ahora puede crear con Python y puede agregar todo tipo de información de metadatos útil. Hacer un paquete Si bien crear un paquete puede parecer algo que hace un comerciante en un gran día en el mercado, en realidad se refiere al proceso de importar sus datos a Zipline. Esta parte es muy específica de Zipline y, si decide utilizar un motor de backtesting diferente, esta parte no será aplicable. Otras bibliotecas de backtesting utilizan sus propios métodos diferentes para conectar los datos. Podría decirse que el método utilizado por Zipline es más complejo, pero ese es el costo de tener la funcionalidad, escalabilidad y conjunto de características adicionales de este backtester. En este capítulo, veremos primero cómo hacer un paquete de acciones y, segundo, cómo hacer uno de futuros. Hay bastantes similitudes, pero hay algunos puntos importantes en los que las cosas difieren. Machine Translated by Google Para los paquetes de muestra aquí, solo obtenemos el historial diario, pero Zipline También admite resolución a nivel de minutos y es bastante rápido para procesarla. Hasta ahora en este libro, hemos estado escribiendo código solo en Jupyter Notebook . Es . un excelente entorno para escribir, ejecutar y analizar pruebas retrospectivas, pero necesitamos algo más para construir nuestro paquete. Hay otro programa que ya estaba instalado en su computadora junto con el paquete Anaconda . Debería poder encontrar un programa en su Este es un computadora llamado Spyder . editor de código Python de propósito general, donde puedes escribir, ejecutar y depurar código y aquí es donde escribiremos el paquete. Al hacer un nuevo paquete, hay dos partes que hacer. La primera parte es escribir el paquete, y esa es la mayor parte de la tarea y lo que vamos a repasar en detalle. La segunda parte, que es fácil pero que no debe olvidarse, es que hay que registrar el nuevo paquete. Eso implica agregar un poquito de texto en un archivo de texto, pero si no se hace, Zipline no sabrá que hay un nuevo paquete en la ciudad. Escribir su primer paquete puede parecer un poco desalentador, en particular porque puede ser difícil encontrar documentación clara. Como ocurre con la mayoría de las cosas, una vez que sabes cómo, no es tan difícil. Primero, una breve descripción de lo que implica un paquete. Claramente, un paquete de datos necesita leer datos de algún lugar. Esa es probablemente la parte más sencilla. La biblioteca Pandas es tu amiga aquí y puede leer fácilmente datos de archivos de texto locales, archivos de bases de datos, servidores de bases de datos, llamadas web o cualesquiera que sean tus preferencias y necesidades. Su paquete debe tener una firma de función específica. Eso simplemente significa que debe haber un conjunto específico de argumentos para su función de ingesta. Si eres bastante nuevo en todo esto, no te preocupes mucho por esto. Podrías usar mi código de muestra y cambiar las pocas cosas que probablemente deban cambiarse para leer tus propios datos. Siguiendo el programa, no cuente el principio rector de este libro, profundicemos en cómo se puede crear un paquete simple pero completamente funcional para leer datos históricos de precios de acciones desde el disco. Mostraré y explicaré poco a poco y luego, al final de esta sección, mostraré todo el código a la vez. Primero las habituales declaraciones de importación. importar pandas como pd desde os import listdir Machine Translated by Google Necesitamos Pandas para leer los archivos csv del disco y para el tipo habitual de vudú DataFrame , y necesitamos la función listdi r para comprobar qué archivos están disponibles. A continuación estableceremos la ruta donde están los datos. Los programadores experimentados señalarán que codificar una cadena como esa no es una buena práctica de codificación, pero este ejemplo no se trata de una buena práctica de codificación. Quiero mostrarte formas sencillas de hacer las cosas y, una vez que te sientas más seguro de cómo funciona, podrás seguir adelante y mejorarlas. # Cambie la ruta a donde tiene su ruta de datos = 'C:\ \yourdatapath\\data\\random_stocks\\' Ahora, aquí es donde comienza lo real. A continuación vamos a iniciar nuestra función de ingesta. Mencioné brevemente que dicha función debe tener una firma específica, y eso es lo que verá en esta definición de función. """ La función de ingesta debe tener esta firma exacta, lo que significa que estos argumentos se pasan, como se muestra a continuación. """ def datos_stock_aleatorios(entorno, escritor_base_activo, escritor_barra_minutos, escritor_barra_diaria, escritor_ajuste, calendario, sesión_inicio, sesión_final, caché, show_progress, dir_salida): La siguiente parte es una demostración de una forma sencilla de trabajar en Python. Mire el segmento de código a continuación y cuánto podemos hacer en una breve línea de código. Esta línea lee todos los archivos en la carpeta especificada, elimina los últimos cuatro caracteres y devuelve una lista de estos nombres de archivos recortados. Por supuesto, esto se basa en nuestra suposición aquí de que todos los archivos en la carpeta especificada son archivos csv, con el nombre de un símbolo bursátil, seguido de .csv. Es decir, IBM.csv o AAPL.csv, etc. # Obtener la lista de archivos de la ruta # Cortar la última parte # 'example.csv'[:­4] = 'ejemplo' símbolos = [f[:­4] for f in listdir(path)] En caso de que haya olvidado poner datos en esta carpeta, generemos un error. si no son símbolos: genere ValueError ("No se encontraron símbolos en la carpeta"). Machine Translated by Google Ahora prepararemos tres DataFrame que necesitaremos llenar con datos en un momento. Por ahora, solo estamos creando la estructura de estos DataFrame . s. # Prepare un DataFrame vacío para dividendos divs = pd.DataFrame(columns=['sid', 'amount', 'ex_date', 'record_date', 'declared_date', 'pay_date'] ) # Preparar un DataFrame vacío para divisiones splits = pd.DataFrame(columns=['sid', 'ratio', ' Effective_date'] ) # Prepare un DataFrame vacío para metadatos metadata = pd.DataFrame(columns=('start_date', 'end_date', 'auto_close_date', 'symbol' 'exchange') ) El backtester de Zipline hizo cumplir el cumplimiento de los calendarios de intercambio. Si bien la mayoría de los backtesters simplemente usan los datos que se les proporcionan, Zipline sabrá qué días fueron días comerciales válidos para un intercambio determinado y requerirá que esos días y ningún otro estén llenos de datos históricos. En realidad, los datos que los proveedores le proporcionan pueden mostrar una ligera desviación de reglas tan estrictas. Es posible que falten datos para una determinada acción en un día de intercambio válido, o que se proporcionen datos por error en un día no válido. Claro, esto no debería suceder, pero si alguna vez encuentra un proveedor de datos perfecto, hágamelo saber. Seguro que no. Definiremos qué calendario de intercambio debe seguir nuestro paquete cuando lo registremos más adelante, y este código de paquete será consciente del calendario. Si vuelve a mirar la firma de definición de función, verá que el calendario se proporcionó allí. A partir de ahí, ahora podemos comprobar qué días son válidos. # Verifique las fechas de negociación válidas, según las sesiones del calendario de intercambio seleccionadas = calendar.sessions_in_range(start_session, end_session) Machine Translated by Google Ahora todo lo que tenemos que hacer es recuperar los datos, alinear las fechas con el calendario, procesar dividendos y metadatos. Prácticamente la mayor parte del paquete real. Pero aquí es donde se pone interesante. Hasta ahora hemos estado analizando solo una . hay tres líneas más para esta función. función, a la que llamamos random_stock_dat a. Solo # Obtener datos de todas las acciones y escribir en Zipline daily_bar_writer.write (process_stocks (símbolos, sesiones, metadatos, divs)) # Escribir los metadatos active_db_writer.write(equities=metadatos) # Escribir divisiones y dividendos ajuste_escritor.write(splits=splits, dividends=divs) Si observa esas filas, debería darse cuenta de que la magia debe estar sucediendo en una función diferente, una llamada Process_stock s. Esta es la llamada .función generadora, que iterará nuestras existencias, procesará los datos y completará los DataFrames que necesitamos. Esta función devolverá los datos históricos necesarios para el escritor de barras diarias y completará los metadatos y los dividendos que necesitamos. También proporcionamos el marco de datos dividido vacío que creamos anteriormente, ya que nuestros datos ya están ajustados para divisiones, como tiende a ser la mayoría de los datos. Eso deja la función generadora, que es donde se obtienen y procesan los datos. Después de definir la función y pasar la lista de símbolos, los días de negociación válidos, los metadatos y los objetos de dividendos, iniciamos un bucle de todos los símbolos bursátiles. El objetivo de utilizar enumerat e para recorrer las acciones es que automáticamente obtendremos un número para cada acción, en orden. El primero será 0, luego 1 y así sucesivamente. Necesitamos un ID de seguridad único (SID) para cada acción, y siempre que sea un número y único, todo está bien. Estos números que aumentan automáticamente y que nos da la enumeración funcionarán perfectamente. """ Función de generador para iterar acciones, crear datos históricos, metadatos y datos de dividendos. """ def Process_Stocks (símbolos, sesiones, metadatos, divs): # Realizar un bucle en las acciones, estableciendo un ID de seguridad (SID) único para sid, símbolo en enumerar (símbolos): Machine Translated by Google El resto de la función está dentro de ese bucle, lo que significa que el resto del código se ejecutará una vez por cada símbolo bursátil disponible en nuestra carpeta de datos. Dependiendo de la cantidad de datos que proporcione, esto puede tardar unos minutos. Siempre que una tarea pueda llevar algún tiempo, puede resultar útil generar algún tipo de progreso para que sepamos que todo avanza como debería. Si bien esto se puede hacer de manera mucho más bonita, aquí usaré una declaración impresa simple. print('Cargando {}....'.formato(símbolo)) # Leer los datos bursátiles del archivo csv. df = pd.read_csv('{}/{}.csv'.format(ruta, símbolo), index_col=[0], parse_dates=[0]) Como puede ver en ese código, leemos los datos del archivo usando la biblioteca Pandas , especificando la ruta que establecimos anteriormente y agregando el símbolo para crear un nombre de archivo. Configuramos la primera columna como índice y le pedimos a Pandas que analice el formato de fecha. A continuación nos aseguraremos de que nuestros datos se ajusten al calendario de intercambio especificado. Como nota al margen, una vez que te sientas más cómodo con Python y Zipline, puedes intentar editar calendarios o crear el tuyo propio. Al alinear las fechas con el calendario, debe decidir qué hacer en caso de discrepancia. Esperemos que no haya ninguna diferencia, pero es necesario que haya algún tipo de manejo si eso ocurre. En el código siguiente, como ejemplo, le dije a Pandas que reenviara los días faltantes y luego eliminara cualquier posible valor none como medida de seguridad. Tenga en cuenta que el siguiente código verifica la primera y la última fecha de los datos reales que leemos del disco y luego reindexa estos datos para usar los días de intercambio válidos en ese rango. # Verifique la primera y última fecha. fecha_inicio = df.index[0] fecha_final = df.index[­1] # Sincronizar con el calendario oficial de intercambio df = df.reindex(sessions.tz_localize(None))[start_date:end_date] # Rellenar hacia adelante los datos faltantes df.fillna(method='ffill', inplace=True) # Eliminar el NaN restante df.dropna(inplace=True) Machine Translated by Google Ahora tenemos suficiente información sobre el stock para los metadatos. De hecho, podría agregar más información a los metadatos si lo desea, como el nombre de la empresa, por ejemplo, pero tenemos todo lo necesario para que todo funcione. Simplemente agregamos al metadato un DataFrame que pasamos en la definición de la función. Un error común es pasar por alto el campo de intercambio. Después de todo, realmente no necesitamos esa información y no la usaremos para nada. Sin embargo, el problema es que si omites ese campo, Zipline no podrá usar tu paquete. Aquí simplemente lo codificamos para evitar el problema. # La fecha de cierre automático es el día después de la última operación. ac_date = end_date + pd.Timedelta(días=1) # Agregue una fila al DataFrame de metadatos. metadata.loc[sid] = fecha_inicio, fecha_final, fecha_ac, símbolo, "NYSE" La última tarea antes de devolver los datos es comprobar los dividendos. Como es posible que tenga o no datos sobre dividendos disponibles, agregué una verificación para verificar si dicha columna está en el archivo de datos. Si es así, lo procesamos y agregamos los datos al DataFrame del .div. # Si hay datos de dividendos, agréguelos al marco de datos de dividendos si hay 'dividendos' en df.columns: # Cortar los días con dividendos tmp = df[df['dividend'] != 0.0]['dividend'] div = pd.DataFrame(data=tmp.index.tolist(), columns=['ex_date']) # Proporcione columnas vacías ya que no tenemos estos datos por ahora div['record_date'] = pd.NaT div['declared_date'] = pd.NaT div['pay_date'] = pd.NaT # Almacenar los dividendos y establecer el ID de seguridad div['amount'] = tmp.tolist() div['sid'] = sid # Comience a numerar donde lo dejamos la última vez ind = pd.Index(range(divs.shape[0], divs.shape[0] + div.shape[0])) div.set_index(ind, inplace=True) # Agregue los dividendos de esta acción a la lista de todos los dividendos divs = divs.append(div) Y eso deja sólo el pequeño detalle final de pasar los datos históricos. Volvamos a la persona que llama, y dado que esta es una función generadora, usamos yiel d . Machine Translated by Google rendimiento sid, df Ese es el paquete completo. Si bien admito que descubrir cómo hacer esto simplemente leyendo la documentación en línea puede parecer un poco desalentador si aún no eres un programador de Python, usar mi ejemplo aquí y adaptarlo a tus propios datos debería ser muy sencillo. Aquí está nuevamente el código completo para este paquete de datos. importar pandas como pd desde os import listdir # Cambie la ruta a donde tiene su ruta de datos = 'C:\\Users\ \Andreas Clenow\\BookSamples\\BookModels\\data\\random_stocks\\' """ La función de ingesta debe tener esta firma exacta, lo que significa que estos argumentos se pasan, como se muestra a continuación. """ def datos_stock_aleatorios(entorno, escritor_base_activo, escritor_barra_minutos, escritor_barra_diaria, escritor_ajuste, calendario, sesión_inicio, sesión_final, caché, show_progress, dir_salida): # Obtener la lista de archivos de la ruta # Cortar la última parte # 'example.csv'[:­4] = símbolos 'example' = [f[:­4] for f in listdir(path)] si no son símbolos: genere ValueError ("No se encontraron símbolos en la carpeta"). # Prepare un DataFrame vacío para dividendos divs = pd.DataFrame(columns=['sid', 'amount', 'ex_date', 'record_date', 'declared_date', 'pay_date'] ) # Preparar un DataFrame vacío para divisiones splits = pd.DataFrame(columns=['sid', 'ratio', Machine Translated by Google 'Fecha efectiva'] ) # Prepare un DataFrame vacío para metadatos metadata = pd.DataFrame(columns=('start_date', 'end_date', 'auto_close_date', 'symbol', 'exchange') ) # Verifique las fechas de negociación válidas, según las sesiones del calendario de intercambio seleccionadas = calendar.sessions_in_range(start_session, end_session) # Obtener datos de todas las acciones y escribir en Zipline daily_bar_writer.write (process_stocks (símbolos, sesiones, metadatos, divs)) # Escribir los metadatos active_db_writer.write(equities=metadatos) # Escribir divisiones y dividendos ajuste_escritor.write(splits=splits, dividends=divs) """ Función de generador para iterar acciones, crear datos históricos, metadatos y datos de dividendos. """ def Process_Stocks (símbolos, sesiones, metadatos, divs): # Realizar un bucle en las acciones, estableciendo un ID de seguridad (SID) único para sid, símbolo en enumerar (símbolos): print('Cargando {}....'.formato(símbolo)) # Leer los datos bursátiles del archivo csv. df = pd.read_csv('{}/{}.csv'.format(ruta, símbolo), index_col=[0], parse_dates=[0]) # Verifique la primera y última fecha. fecha_inicio = df.index[0] fecha_final = df.index[­1] # Sincronizar con el calendario oficial de intercambio df = df.reindex(sessions.tz_localize(None))[start_date:end_date] # Rellenar hacia adelante los datos faltantes df.fillna(method='ffill', inplace=True) Machine Translated by Google # Eliminar el NaN restante df.dropna(inplace=True) # La fecha de cierre automático es el día después de la última operación. ac_date = end_date + pd.Timedelta(días=1) # Agregue una fila al DataFrame de metadatos. No olvide agregar un campo de intercambio. metadata.loc[sid] = fecha_inicio, fecha_final, fecha_ac, símbolo, "NYSE" # Si hay datos de dividendos, agréguelos al marco de datos de dividendos si hay 'dividendos' en df.columns: # Cortar los días con dividendos tmp = df[df['dividend'] != 0.0]['dividend'] div = pd.DataFrame(data=tmp.index.tolist(), columns=['ex_date']) # Proporcione columnas vacías ya que no tenemos estos datos por ahora div['record_date'] = pd.NaT div['declared_date'] = pd.NaT div['pay_date'] = pd.NaT # Almacenar los dividendos y establecer el ID de seguridad div['amount'] = tmp.tolist() div['sid'] = sid # Comience a numerar donde lo dejamos la última vez ind = pd.Index(range(divs.shape[0], divs.shape[0] + div.shape[0])) div.set_index(ind, inplace=True) # Agregue los dividendos de esta acción a la lista de todos los dividendos divs = divs.append(div) rendimiento sid, df Este código debe guardarse como un archivo .py en la carpeta de su paquete Zipline. La ubicación exacta del suyo en su computadora depende de su propia instalación y configuración local. Debería encontrarlo fácilmente buscando, pero como referencia, el mío se encuentra en la ruta que se muestra justo debajo. Para el ejemplo aquí, asumiré que guardaste Puede llamar al este paquete como random_stock_data.py . archivo como desee, pero debemos consultarlo en el momento en que registremos el paquete. C:\ProgramData\Anaconda3\envs\zip35\Lib\site­packages\zipline\data\bundles Machine Translated by Google Ahora que hemos creado el paquete, debemos registrarlo en Zipline. Esto se hace en un archivo llamado extensión.py que debería encontrar en su directorio de inicio, en /.ziplin e . Si está . c:/users/username/.zipline/extension.py y si Si es un en Windows, probablemente estaría en sistema operativo de estilo UNIX, sería ~/.zipline/extension.py . Si el archivo no está allí, créelo. En este archivo, debemos importar el paquete y registrarlo, según la sintaxis siguiente, asumiendo que guardó el código del paquete en la carpeta correcta, usando el nombre random_stock_data.py . desde el registro de importación zipline.data.bundles, registro random_stock_data(' random_stock_data ', random_stock_data . random_stock_data calendar_name='NYSE') , Como puede ver aquí, aquí es donde especificamos el calendario de intercambio, que como vimos anteriormente, define qué días son días comerciales válidos. Quizás se pregunte por qué repetimos el nombre dos veces, random_stock_data.random_stock_dat a . Bueno, eso simplemente porque en este ejemplo, el nombre del archivo y el nombre de la función en el archivo resultan ser el mismo. También nombraremos el paquete con el mismo nombre aquí, para que sea sencillo. Una vez modificado y guardado este archivo, estamos listos para ingerir el paquete. El proceso de ingesta se refiere al proceso de ejecutar el código del paquete para importar los datos a Zipline. Ahora está listo para incorporar su nuevo paquete. ¿Recuerdas cómo hicimos eso antes, en el capítulo 7? Abra una ventana de terminal para su entorno zip3 5 . Puedes hacerlo a través de Anaconda Navigator . Luego ingiera el nuevo paquete como antes. ingesta de tirolesa ­ b random_equities Si todo funcionó como debería, verá cómo Zipline extrae los datos, stock por stock, y los almacena. Cuando se complete este proceso, estará listo para comenzar a construir algunas pruebas retrospectivas serias, aunque algo aleatorias. Como ha visto en capítulos anteriores, cada código de prueba retrospectiva para cada modelo especifica de qué paquete extrae datos. Así que ahora puedes simplemente modificar esa parte del código, generalmente cerca de la parte inferior, para usar tu paquete aleatorio. Datos de tirolesa y futuros Machine Translated by Google El panorama de los motores de backtesting de Python está cambiando rápidamente, y quizás para cuando leas esto haya ocurrido algo revolucionario, dejando obsoleto parte de este libro. Pero al momento de escribir esto, en mi opinión, Zipline está liderando la carrera en términos de proporcionar el entorno de backtesting de Python más robusto y rico en funciones para el futuro. Ya discutimos esto un poco en el capítulo 14. Sin embargo, requiere un poco de trabajo para configurarlo correctamente en una instalación local. Pero no temas, te guiaré a través de ello. Dado que llegó hasta este capítulo, probablemente ya haya aprendido cómo crear un paquete Zipline para acciones, como comentamos anteriormente en este capítulo. Gran parte de lo que aprendimos allí también es válido para el futuro, pero hay algunos puntos difíciles de abordar. Lo que siempre debes tener en cuenta en el mundo Python es que, desafortunadamente, nada es software realmente terminado y pulido. A menudo encontrará situaciones en las que se preguntará por qué alguien dejó algo aparentemente sin terminar, o por qué necesitaría editar el código fuente de otra persona sólo para que su solución funcione. Ese es el mundo de Python para ti. Pero por otro lado, todo es gratis. Como descripción general para empezar, esto es lo que debemos hacer para poder estar en funcionamiento con las pruebas retrospectivas futuras para Zipline: Construya un paquete de futuros. Proporcione metadatos de futuros precisos para agrupar. Proporcione metadatos adicionales para los símbolos raíz de futuros. Registre el paquete de futuros en extension.py . Edite constantes.py para asegurarse de que todos sus mercados tengan definiciones predeterminadas de deslizamiento y tarifas de cambio. Algunos errores con los que hay que tener cuidado: Zipline espera una sintaxis muy específica para el símbolo de futuros. Asegúrese de , un símbolo raíz de dos caracteres, M formatear el símbolo como RRMYY , donde RR es es un código de mes de un carácter e YY es un año de dos dígitos. Ejemplo: CLF02 para el contrato de crudo de enero de 2002. Tenga en cuenta que se requiere un símbolo raíz de dos caracteres. No uno, tres o cuatro, como puede ser el caso en la realidad. Por lo tanto, es necesario aplicar la sintaxis de dos caracteres. Machine Translated by Google Todos los símbolos raíz del mercado deben enumerarse en el archivo constantes.py con una tarifa y un deslizamiento predeterminados. Si desea incluir un mercado que aún no está en ese archivo, debe agregarlo. En cuanto a los paquetes de acciones, Zipline espera recibir un DataFrame para ajustes de dividendos y divisiones. Claramente eso no tiene mucho sentido, pero siempre y cuando simplemente proporcionemos un DataFrame vacío , el backtester está contento. Zipline espera que los datos se proporcionen en los días exactos especificados en su calendario de días festivos, y la ingesta de datos fallará si a sus datos les faltan días o tienen días sobrantes. Es posible e incluso probable que este sea el caso, por lo que debes utilizar Pandas para asegurarte de que tus fechas coincidan con las fechas esperadas. Esto es lo mismo que para el paquete de acciones que vimos antes y la solución es la misma. La versión actual de Zipline, que es 1.3 al momento de escribir este artículo, tiene algunos problemas con los datos anteriores al año 2000. Restringir los datos a posteriores al 2000 simplifica el proceso. Si tiene datos disponibles para el día del primer aviso, es posible que desee incorporarlos en el paquete. Como muchos lectores probablemente no lo hagan, lo simplificaré y lo omitiré por ahora. Puede aproximar el día del primer aviso para la mayoría de los productos básicos configurando el día de cierre automático un mes antes de la última fecha de negociación. Paquete de datos de futuros En gran medida, un paquete de futuros funciona igual que un paquete de acciones, pero preste atención a las consideraciones especiales para esta clase de activos. En el paquete de muestra aquí, leeremos los datos aleatorios que puede descargar del sitio web del libro. Proporciono estos datos aleatorios para ayudarlo a comenzar a funcionar más rápido. Al tener este conjunto de datos de muestra aleatorio y los paquetes correspondientes, debería poder ver la lógica, probarla y modificarla cuando sea necesario para que sus propios datos funcionen. Machine Translated by Google Para ejecutar esta muestra, necesitará descargar mis datos de muestra aleatoria del sitio web del libro, www.followingthetrend.com/trading­evolved/ o modificar el código para usar sus propios, datos. Además de los datos aleatorios, también hay un archivo de búsqueda de metadatos que puede descargar del sitio, que contiene información importante sobre futuros, como valor de puntos, sector y más. Este paquete también utiliza ese archivo, proporcionando la información al marco Zipline. También les presentaré un pequeño truco nuevo en este ejemplo de código. Cargar los datos puede llevar un tiempo y mostrar una nueva fila de texto para cada mercado como lo hicimos en el paquete de acciones es un poco primitivo. Esta vez, obtendrás una pequeña barra de progreso. Comenzando con el código, primero tenemos las declaraciones de importación habituales, incluida la biblioteca tqdm que usaremos para la barra de progreso. importar pandas como pd desde os importar listdir desde tqdm importar tqdm # Usado para la barra de progreso Como se mencionó, se proporcionan datos históricos aleatorios y búsqueda de metadatos. La siguiente parte del código especifica dónde se pueden ubicar esos datos y luego lee la búsqueda de metadatos en la memoria, para que podamos acceder más tarde. Cambie la ruta para que coincida con la ubicación donde colocó los datos descargados. # Cambia la ruta donde tienes tus datos base_path = "C:/your_path/data/" data_path = base_path + 'random_futures/' meta_path = 'futures_meta/meta.csv' Futures_lookup = pd.read_csv(base_path + meta_path, index_col= 0) El diseño de mi tabla de búsqueda de futuros se muestra en la Tabla 23.1. La mayoría de estos campos se explican por sí mismos, como el símbolo raíz, la descripción y el sector. Pero quizás te preguntes acerca del multiplicador y el ajuste cambiario menor. El multiplicador, a veces denominado valor en puntos o tamaño del contrato, es una propiedad clave de un contrato de futuros. Define cuántos dólares gana o pierde si el precio del contrato cambia en un dólar. De ahí el nombre multiplicador. El factor menor de ajuste cambiario es algo que incluí como recordatorio. Algunos mercados de futuros de EE. UU. se cotizan en centavos estadounidenses, en lugar de dólares estadounidenses, y si no ajusta esto, puede obtener algunos resultados extraños. Debe consultar con su proveedor de datos local para ver si ofrece precios en USD o USc para dichos mercados. Tabla 23.1 Tabla de búsqueda de futuros 0 root_symbol multiplicador minor_fx_adj descripción intercambio 100000 1 AUD/USD CME ANUNCIO sector Divisa Machine Translated by Google 1 B.O. 2 PA 600 62500 0.01 Aceite de Soja 1 GBP/USD CME TCC Agrícola Divisa Comenzamos la función de ingesta con la misma firma que vio en el paquete de muestra de acciones. """ La función de ingesta debe tener esta firma exacta, lo que significa que estos argumentos se pasan, como se muestra a continuación. """ def datos_futuros_aleatorios(entorno, active_db_writer, minute_bar_writer, daily_bar_writer, ajuste_writer, calendario, inicio_sesión, fin_sesión, caché, show_progress, salida_dir): Hasta ahora, no es realmente diferente de lo que vimos antes. El siguiente segmento aquí también es muy similar, pero tenga en cuenta que debemos proporcionar algunos campos adicionales en los metadatos. # Prepare un DataFrame vacío para dividendos divs = pd.DataFrame(columns=['sid', 'amount', 'ex_date', 'record_date', 'declared_date', 'pay_date'] ) # Preparar un DataFrame vacío para divisiones splits = pd.DataFrame(columns=['sid', 'ratio', ' Effective_date'] ) # Prepare un DataFrame vacío para metadatos metadata = pd.DataFrame(columns=('start_date', 'end_date', 'auto_close_date', 'symbol', 'root_symbol', 'expiration_date', 'notice_date', 'tick_size', 'exchange ') ) Machine Translated by Google Para contratos de futuros, necesitamos proporcionar información sobre el símbolo raíz, fecha de vencimiento, fecha de aviso y tamaño de marca, y lo haremos en los metadatos. El siguiente segmento no contiene nuevas sorpresas, y aquí es donde obtenemos los días comerciales válidos para el calendario seleccionado, llamamos a la función que buscará y procesará los datos y escribimos el resultado. Por supuesto, es en esa función particular donde ocurre la parte interesante y pronto la veremos más de cerca. Tenga en cuenta que también tenemos que escribir los datos vacíos para divisiones y dividendos. Claramente no tenemos tal información sobre los mercados de futuros, así que simplemente proporcione marcos vacíos con los encabezados esperados. # Verifique las fechas de negociación válidas, según las sesiones del calendario de intercambio seleccionadas = calendar.sessions_in_range(start_session, end_session) # Obtener datos de todas las acciones y escribir en Zipline daily_bar_writer.write (process_futures (símbolos, sesiones, metadatos)) ajuste_escritor.write(divisiones=divisiones, dividendos=divs) Quizás recuerdes antes que el último paso fue escribir los metadatos. Esto funciona casi igual aquí y, como antes, la función generadora, aquí llamada Process_future s, ha , contrato para nosotros. preparado los metadatos del Ahora necesitamos preparar metadatos en el nivel raíz y escribirlos en el marco Zipline. Podemos utilizar la tabla de búsqueda de futuros que obtuvimos anteriormente, casi tal como está. Solo necesitamos agregar una columna con un root_symbol_i d único , así como eliminar el campo minor_fx_ad j, ahora innecesario. # Preparar metadatos a nivel raíz root_symbols = Futures_lookup.copy() root_symbols['root_symbol_id'] = root_symbols.index.values del root_symbols['minor_fx_adj'] #escribir los metadatos active_db_writer.write(futures=metadata, root_symbols=root_symbols) Esa es toda la función de ingesta, pero todavía no hemos analizado la función que realmente lee y procesa los datos. La estructura es la misma que para el paquete de acciones, pero preste atención a las consideraciones especiales con los futuros. Primero definimos la función e iniciamos un ciclo de todos los símbolos. Observe cómo usamos tqdm aquí cuando iniciamos el ciclo. Eso es todo lo que necesitamos hacer para que se muestre una pequeña barra de progreso durante este ciclo. Machine Translated by Google def process_futures(símbolos, sesiones, metadatos): # Realizar un bucle en las acciones, estableciendo un ID de seguridad (SID) único sid = 0 # Repita los símbolos con la barra de progreso, usando tqdm para el símbolo en tqdm(symbols, desc='Cargando datos...'): lado += 1 # Leer los datos bursátiles del archivo csv. df = pd.read_csv('{}/{}.csv'.format(data_path, símbolo), index_col=[0], parse_dates=[0]) Ahora que hemos leído los datos del disco, podemos comenzar a procesarlos. Primero, comprobaré el factor de ajuste cambiario menor y multiplicaré todos los precios por él. # Verificar cotizaciones de divisas menores ajuste_factor = futuros_lookup.loc[ futuros_lookup['root_symbol'] == df.iloc[0]['root_symbol'] ] ['minor_fx_adj'].iloc[0] df['open'] *= factor_ajuste df['alto'] *= factor_ajuste df['bajo'] *= factor_ajuste df['cerrar'] *= factor_ajuste , La sintaxis utilizada arriba del valor *= x valor += x es lo mismo que valor = valor + x es lo mismo que valor = valor * X , al igual que . Obtener datos perfectos puede ser una empresa casi inútil. Considero que un problema muy común con los datos de futuros es que de vez en cuando se obtiene un valor alto por debajo del cercano, o algún problema similar. Sólo para mostrarle un método para evitar que un pequeño error como ese haga estallar su código, incluí este segmento a continuación. # Evite posibles errores de datos altos/bajos en el conjunto de datos # Y aplique ajustes de moneda menores para las cotizaciones en dólares estadounidenses df['high'] = df[['high', 'close']].max(axis=1) df['low '] = df[['bajo', 'cerrar']].min(eje=1) df['alto'] = df[['alto', 'abierto']].max(eje=1) df[ 'bajo'] = df[['bajo', 'abierto']].min(eje=1) Ahora volvemos a indexar las fechas para que coincidan con las fechas válidas de la sesión y cortamos las fechas. antes del año 2000 para evitar rápidamente un problema actual con Zipline con tales fechas. # Sincronizar con el calendario oficial de intercambio df = df.reindex(sessions.tz_localize(None))[df.index[0]:df.index[­1] ] # Rellenar hacia adelante los datos faltantes df.fillna(method='ffill', inplace=True) # Eliminar el NaN restante Machine Translated by Google df.dropna(en el lugar=Verdadero) # Cortar fechas anteriores al 2000, evitando el problema de Zipline df = df['2000­01­01':] Necesitamos recopilar algunos metadatos para cada contrato y, para que el código sea más fácil de leer y administrar, lo subcontraté a una función separada, a la que llamamos aquí. Llegaremos a los detalles de esa función en un momento. # Preparar metadatos del contrato make_meta(sid, metadata, df, sesiones) Finalmente, finalizamos este bucle de símbolos así como la función eliminando los campos que ya no necesitamos y devolver la identificación de seguridad y los datos. del df['openinterest'] del df['expiration_date'] del df['root_symbol'] del df['symbol'] rendimiento sid, df Eso solo deja la construcción de metadatos, que dejamos en una función separada. Esta función agrega una fila a nuestro metadato, un DataFrame, para cada contrato individual. def make_meta(sid, metadata, df, sesiones): # Verifique la primera y la última fecha. fecha_inicio = df.index[0] fecha_final = df.index[­1] # La fecha de cierre automático es el día después de la última operación. ac_date = end_date + pd.Timedelta(días=1) símbolo = df.iloc[0]['symbol'] root_sym = df.iloc[0]['root_symbol'] exchng = futuros_lookup.loc[futures_lookup['root_symbol'] == root_sym ]['exchange'].iloc[ 0] fecha_exp = fecha_finalización # Agregue el día del aviso si es así. # Consejo para mejorar: establezca la fecha de aviso en un mes antes del # vencimiento para los mercados de productos básicos. Notice_date = ac_date tick_size = 0.0001 # Marcador de posición # Agregue una fila al DataFrame de metadatos. metadata.loc[sid] = fecha_inicio, fecha_final, fecha_ac, símbolo, \ root_sym, fecha_exp, fecha_notificación, tamaño_tick, intercambio Machine Translated by Google Esta función, make_met a , completa los valores para cada contrato, agregando el símbolo raíz, las fechas de inicio y finalización, etc. Esta es una función bastante sencilla, pero hay un punto interesante que mencionar aquí. Para simplificar las cosas, en este código simplemente establezco la fecha del primer aviso en la misma fecha que la fecha de vencimiento. Si sólo se negocian futuros financieros, eso no es realmente un problema, pero en los mercados de materias primas podría serlo. Si su proveedor de datos tiene las fechas reales de los primeros avisos, simplemente indíquelas. Pero para la mayoría de los lectores, probablemente ese no sea el caso. Así que les haré una sugerencia y les daré un poco de tarea. Si llegaste hasta aquí en el libro, deberías poder resolver este. Lo que puede hacer para aproximar la fecha del primer aviso es establecerla un mes antes del vencimiento, si se trata de un mercado de productos básicos. Entonces, lo que tendría que hacer es verificar el sector en la tabla de búsqueda y, si es un producto básico, deducir un mes de la fecha de vencimiento. Lo resolverás. Para mayor comodidad, aquí está el código fuente completo de este paquete de futuros. importar pandas como pd desde os importar listdir desde tqdm importar tqdm # Usado para la barra de progreso # Cambia la ruta donde tienes tus datos base_path = "C:/ your_path/data/" data_path = base_path + 'random_futures/' meta_path = 'futures_meta/ meta.csv' Futures_lookup = pd.read_csv(base_path + meta_path, index_col= 0) """ La función de ingesta debe tener esta firma exacta, lo que significa que estos argumentos se pasan, como se muestra a continuación. """ def datos_futuros_aleatorios(entorno, active_db_writer, minute_bar_writer, daily_bar_writer, ajuste_writer, calendario, inicio_sesión, fin_sesión, caché, show_progress, salida_dir): # Obtener la lista de archivos de la ruta # Cortar la última parte # 'example.csv'[:­4] = símbolos 'example' = [f[:­4] for f in listdir(data_path)] Machine Translated by Google si no son símbolos: genere ValueError ("No se encontraron símbolos en la carpeta"). # Prepare un DataFrame vacío para dividendos divs = pd.DataFrame(columns=['sid', 'amount', 'ex_date', 'record_date', 'declared_date', 'pay_date'] ) # Preparar un DataFrame vacío para divisiones splits = pd.DataFrame(columns=['sid', 'ratio', ' Effective_date'] ) # Prepare un DataFrame vacío para metadatos metadata = pd.DataFrame(columns=('start_date', 'end_date', 'auto_close_date', 'symbol', 'root_symbol', 'expiration_date', 'notice_date', 'tick_size', 'exchange ' ) ) # Verifique las fechas de negociación válidas, según las sesiones del calendario de intercambio seleccionadas = calendar.sessions_in_range(start_session, end_session) # Obtener datos de todas las acciones y escribir en Zipline daily_bar_writer.write (process_futures (símbolos, sesiones, metadatos)) ajuste_escritor.write(divisiones=divisiones, dividendos=divs) # Preparar metadatos a nivel raíz root_symbols = Futures_lookup.copy() root_symbols['root_symbol_id'] = root_symbols.index.values del root_symbols['minor_fx_adj'] #escribir los metadatos active_db_writer.write(futures=metadata, root_symbols=root_symbols) def process_futures(símbolos, sesiones, metadatos): # Realizar un bucle en las acciones, estableciendo un ID de seguridad (SID) único sid = 0 Machine Translated by Google # Repita los símbolos con la barra de progreso, usando tqdm para el símbolo en tqdm(symbols, desc='Cargando datos...'): lado += 1 # Leer los datos bursátiles del archivo csv. df = pd.read_csv('{}/{}.csv'.format(data_path, símbolo), index_col=[0], parse_dates=[0]) # Verificar cotizaciones de divisas menores ajuste_factor = futuros_lookup.loc[ futuros_lookup['root_symbol'] == df.iloc[0]['root_symbol'] ] ['minor_fx_adj'].iloc[0] df['open'] *= factor_ajuste df['alto'] *= factor_ajuste df['bajo'] *= factor_ajuste df['cerrar'] *= factor_ajuste # Evite posibles errores de datos altos/bajos en el conjunto de datos # Y aplique ajustes de moneda menores para las cotizaciones en dólares estadounidenses df['high'] = df[['high', 'close']].max(axis=1) df['low '] = df[['bajo', 'cerrar']].min(eje=1) df['alto'] = df[['alto', 'abierto']].max(eje=1) df[ 'bajo'] = df[['bajo', 'abierto']].min(eje=1) # Sincronizar con el calendario oficial de intercambio df = df.reindex(sessions.tz_localize(None))[df.index[0]:df.index[­1] ] # Rellenar hacia adelante los datos faltantes df.fillna(method='ffill', inplace=True) # Eliminar el NaN restante df.dropna(inplace=True) # Cortar fechas anteriores al 2000, evitando el problema de Zipline df = df['2000­01­01':] # Preparar metadatos del contrato make_meta(sid, metadata, df, sesiones) del df['openinterest'] del df['expiration_date'] del df['root_symbol'] del df['symbol'] rendimiento sid, df def make_meta(sid, metadata, df, sesiones): # Verifica la primera y la última fecha. fecha_inicio = df.index[0] fecha_final = df.index[­1] Machine Translated by Google # La fecha de cierre automático es el día después de la última operación. ac_date = end_date + pd.Timedelta(días=1) símbolo = df.iloc[0]['symbol'] root_sym = df.iloc[0]['root_symbol'] exchng = futuros_lookup.loc[futures_lookup['root_symbol'] == root_sym ]['exchange'].iloc[ 0] fecha_exp = fecha_finalización # Agregue el día del aviso si es así. # Consejo para mejorar: establezca la fecha de aviso en un mes antes del # vencimiento para los mercados de productos básicos. Notice_date = ac_date tick_size = 0.0001 # Marcador de posición # Agregue una fila al DataFrame de metadatos. metadata.loc[sid] = fecha_inicio, fecha_final, fecha_ac, símbolo, \ root_sym, fecha_exp, fecha_notificación, tamaño_tick, intercambio También necesitamos registrar este paquete, lo cual, tal como vimos anteriormente con el paquete de acciones, lo hacemos en la extensión de archivo.py . Suponiendo que ya creó o modificó este archivo anteriormente para el paquete de acciones aleatorio, su nuevo archivo debería verse así, dado que acaba de guardar el paquete de futuros como random_futures.py en la carpeta de paquetes de Zipline. desde zipline.data.bundles registro de importación, random_equities, random_futures registro('random_equities', random_equities.random_bundle, calendar_name='NYSE') registro('random_futures', random_futures.futures_bundle, calendar_name='us_futures') Esto registrará el paquete para que podamos ingerirlo. Es decir, ahora podemos extraer los datos de los archivos separados por comas en el disco y ponerlos a disposición para que Zipline los use. Si está leyendo esto en edición de bolsillo, debe escribir algo aquí. O simplemente puede ir a www.followingthetrend.com/trading­evolved y descargarlo. Si estás leyendo esto en un Kindle, deberías poder copiar y pegar el texto, y si estás leyendo esto en una copia pirata en PDF, haré uso de mi conjunto muy particular de habilidades, adquiridas a lo largo de una carrera muy larga, y Te encontraré. Pandas puede leer fácilmente este archivo, como muestra el código fuente del paquete, y utilizarlo para proporcionar a Zipline la información requerida sobre cada mercado. Machine Translated by Google Finalmente, a menos que este problema se haya solucionado cuando lea esto, busque el archivo constantes.py Será . más rápido para usted buscar el archivo que escribir una ruta de este libro. En este archivo, tiene dos diccionarios que necesitan modificación. El primero, llamado FUTURES_EXCHANGE_FEES_BY_SYMBO L , enumera la raíz de cada futuro y la tarifa de cambio correspondiente. Asegúrese de que todos los mercados que desea cubrir estén enumerados allí. Si no, agréguelos. El segundo diccionario en el mismo archivo se llama ROOT_SYMBOL_TO_ET A y afecta las condiciones de deslizamiento. Nuevamente, asegúrese de que todos los mercados que desea cubrir en su backtesting estén enumerados aquí. Ahora está listo para ingerir el paquete. ingesta de tirolesa ­b random_futures Parcheando el marco Al momento de escribir esto, falta una línea de código en el marco Zipline que impedirá que se carguen sus datos futuros. Es de esperar que esto ya esté solucionado cuando leas este libro, pero de lo contrario tendrás que hacer las modificaciones necesarias tú mismo. No te preocupes, no es difícil una vez que sabes qué hacer. Lo que hay que hacer es localizar un archivo llamado run_algo.py , que puede ser y que se encuentra debajo de su instalación de Zipline, en la subcarpeta , hacer un archivo menor util s mod. Busque este archivo si no puede encontrarlo de inmediato, ya que la ruta puede ser ligeramente diferente en su computadora que en la mía. Abra este archivo en Spyder y desplácese hacia abajo hasta la línea número 160, donde encontrará este código. datos = DataPortal( env.asset_finder, trading_calendar=trading_calendar, first_trading_day=first_trading_day, equidad_minuto_reader=bundle_data.equity_ Minute_bar_reader, equidad_daily_reader=bundle_data.equity_daily_bar_reader, ajuste_reader=bundle_data.adjustment_reader, ) Necesitamos una línea para este código, para que se parezca al código siguiente. Después de eso, Zipline y sus datos de futuros deberían llevarse muy bien. datos = DataPortal( env.asset_finder, trading_calendar=trading_calendar, Machine Translated by Google first_trading_day=first_trading_day, equidad_minuto_reader=bundle_data.equity_minuto_bar_reader, equidad_daily_reader=bundle_data.equity_daily_bar_reader, futuro_daily_reader=bundle_data.equity_daily_bar_reader, ajuste_reader=bundle_data.adjustment_reader, ) Bueno, te advertí desde el principio que nada está realmente terminado en el mundo de Python. Es posible que deba modificar el código de otra persona en ocasiones para que todo funcione como se espera. Datos y bases de datos Los datos son probablemente el mayor problema cuando se trata de modelos financieros y pruebas retrospectivas. No importa qué tan bueno sea su algoritmo comercial, si se basa en datos erróneos, es una pérdida de tiempo. Hay dos aspectos principales de los datos. Está la calidad de los datos y está la cobertura de los datos. La calidad se refiere a lo confiable que es. En términos generales, los datos gratuitos serán de menor calidad que los datos caros. Los problemas de calidad pueden ser de diferentes tipos y gravedad. Podrían haberse introducido errores, haciendo que parezca que hubo un aumento masivo en el precio, cuando en realidad no ocurrió nada, o tal vez se introdujo un valor cero para que pareciera que una acción quebró repentinamente. Es posible que falten datos o que de repente se le proporcione un valor NaN . Quizás a veces faltan ajustes para las divisiones o estos son incorrectos, lo que hace que su algoritmo se vuelva loco. Potencialmente, podría haber cualquier tipo de problema. Si bien este tipo de problemas de calidad son más comunes en fuentes de datos disponibles gratuitamente, no son desconocidos en bases de datos de series temporales costosas y seleccionadas. La búsqueda de datos de calidad es una lucha constante. El otro aspecto es algo más fácil de abordar, ya que normalmente sólo requiere dinero. El otro aspecto es la cobertura, tanto en términos de instrumentos cubiertos como en términos de tipo de datos disponibles. Es principalmente en ese segundo aspecto donde pronto se dará cuenta de que las fuentes de datos disponibles gratuitamente no son suficientes. Machine Translated by Google Los datos sobre acciones son el mejor ejemplo de por qué es así. Bien puede parecer que las fuentes de datos en línea gratuitas tienen una cobertura bastante buena de los datos sobre acciones. Incluso después de que Yahoo y Google decidieran cerrar su acceso a la API, todavía hay bastantes lugares donde puede obtener al menos el historial diario de las acciones. Pero si miras un poco más de cerca y consideras lo que realmente necesitas, encontrarás que estas fuentes gratuitas no tienen lo que se necesita. Los datos gratuitos o de bajo coste suelen ajustarse a escisiones y acciones corporativas. Después de todo, sin ajustar esto, los datos serían completamente inútiles. La mayoría de estas bases de datos gratuitas o de bajo costo carecen de información sobre dividendos y, si bien eso puede no parecer gran cosa, puede tener un gran impacto. A largo plazo, los efectos de los dividendos pueden ser bastante sustanciales. Pero incluso a corto plazo, puedes tener algunas distorsiones reales en tus modelos. Si su backtest mantiene una acción mientras está ex­div y usted carece de los datos o la lógica para manejar eso, su backtester pensará que acaba de perder dinero. La información sobre dividendos y la lógica para manejarla son absolutamente necesarias para realizar una simulación de acciones adecuada. Pero por muy importantes que sean los dividendos, hay otro punto mucho más importante. Para crear pruebas retrospectivas realistas, es necesario tener datos disponibles, tal como se remontan al pasado. Una cosa de la que tienden a carecer estas fuentes gratuitas y de bajo costo son las acciones que se eliminan de la lista, se fusionan o se modifican de alguna otra manera. Las acciones que están disponibles actualmente son las que obtuvieron buenos resultados para sobrevivir. De ahí el término sesgo de supervivencia. Recuerde cuántas acciones tecnológicas siguieron el camino del dodo a finales de los años 90. Aquellas acciones que se arruinaron en aquel entonces ahora desaparecerán. Purgado de la realidad, en lo que a fuentes de datos de bajo coste se refiere. Nunca existieron. Claramente, eso creará un sesgo en sus pruebas retrospectivas. Las únicas acciones disponibles para negociar son aquellas que tuvieron un desempeño lo suficientemente bueno como para existir hoy. Es casi seguro que ignorar este problema le dará una falsa sensación de seguridad, ya que el backtest mostrará resultados mucho mejores que los que mostraría en realidad. Por lo tanto, es necesario incluir todas las acciones excluidas de la lista, el llamado cementerio. Una cuestión estrechamente relacionada con esto es algún tipo de información para decidir qué acciones probablemente habría considerado en el pasado. Al momento de escribir esto, Apple es la empresa más grande del mundo. Todo el mundo sabe que se trata de una empresa muy grande que tuvo un desempeño estelar en el pasado. Pero si el backtest se remonta a veinte o treinta años atrás, la situación era bastante diferente. Machine Translated by Google Tengo edad suficiente para recordar cuando Apple estuvo a punto de quebrar y tuvo que pedirle ayuda a Bill Gates para pagar las cuentas. En aquel entonces, cuando Apple parecía un pequeño fabricante de computadoras de poca importancia, probablemente no habría estado en su radar. Asumir que su backtest lo negociaría hace décadas, solo porque ahora es una acción grande, es un error. Hay dos formas comunes de construir un universo de inversión dinámico para aliviar este problema. La primera sería utilizar datos históricos sobre capitalización de mercado y/o liquidez. Al usar esto, puede identificar qué acciones eran lo suficientemente grandes o líquidas como para haber sido posibles candidatas en un momento lejano en el tiempo, y su código puede luego crear una lista dinámica de acciones para negociar. El otro método sería elegir un índice y observar los miembros históricos de ese índice. Para ello, no se necesitan datos adicionales de liquidez o capitalización de mercado de las acciones, sino sólo información sobre las entradas y salidas de un índice. Es probable que el método del índice sea más fácil para los lectores de este libro y encontrará un resultado final más o menos equivalente, y lo utilicé para el modelo de cartera de acciones en este libro. Lo que quiero decir con todo esto es que muy rápidamente llegarás al final de la línea de lo que se puede hacer con datos gratuitos. Eso le deja con la tarea de determinar qué fuente de datos desea utilizar y conectarla a su backtester. En el capítulo anterior, analizamos formas de leer datos personalizados de archivos de texto locales. Esta es una buena entrada al mundo de los datos personalizados, pero a medida que avanza, es probable que adquiera necesidades más sofisticadas. Una vez que se adentra en aspectos más avanzados de los datos, y tal vez comience a combinar datos de varias fuentes diferentes, tiene sentido configurar su propia base de datos de valores local. Por supuesto, esto no es de ninguna manera un requisito, y si todavía estás luchando contra algo de tecnofobia, puedes saltarte este capítulo con seguridad. Su propia base de datos de valores Lo que recomendaría es almacenar sus datos en una base de datos adecuada, independientemente del formato que le proporcione el proveedor de datos elegido. Ahora vamos un poco más allá del tema central del trading y nos adentramos en un tema un poco más técnico. Pero déjame explicarte por qué. Machine Translated by Google Supongamos que su proveedor de datos, por ejemplo, le entrega diariamente archivos planos CSV con diez campos por stock en un diseño particular. Ahora puede hacer que su backtester lea estos datos, explicándole exactamente dónde se encuentra este archivo, cuáles son los esquemas de nombres y cómo están dispuestos los campos. Probablemente tengas otras herramientas de software en tu computadora que quieras usar para acceder a estos datos, y podrías hacer lo mismo allí, hacer que lean los archivos directamente. Su backtester probablemente no sea el único software que utilizará para acceder a estos datos. El problema llega el día que tu proveedor de datos cambia algo, o cuando decides cambiar de proveedor. E incluso si nada de eso sucede, es posible que tenga problemas cuando desee agregar un segundo proveedor de datos o agregar información como búsqueda de código, información del sector y otros metadatos. Lo que probablemente quiera hacer en su lugar es colocar su propia base de datos de valores entre los proveedores de datos y sus herramientas. Y sí, querido lector, eso significa que debes aprender algunos conceptos básicos sobre cómo funcionan las bases de datos. Claro, pensabas que se trataba de un libro sobre comercio, pero ahora que te he engañado para que leas un libro de tecnología hasta ahora, no tienes más remedio que morder y continuar. Si tiene su propia base de datos de valores local, tendrá un control mucho mejor sobre sus datos. Será mucho más sencillo manejar los datos, solucionar problemas, agregar fuentes de datos, etc. Afortunadamente, existe un servidor de base de datos perfectamente funcional y de gran capacidad disponible de forma gratuita. Si tiene demasiado dinero haciendo un agujero en su bolsillo, siga adelante y seleccione una de las alternativas comerciales, pero para nuestros propósitos no verá ningún beneficio con ese enfoque. Lo que vamos a utilizar aquí es MySQL , que no sólo es gratuito sino que también está disponible para una amplia gama de sistemas operativos. Instalación del servidor MySQL Los ejemplos de este libro utilizarán la edición gratuita MySQL Community Edition. Si . prefiere otra base de datos, casi todo debería funcionar más o menos de la misma manera. Si recién está comenzando y aprendiendo sobre bases de datos, le recomendamos que se ciña a MySQL Community Edition por ahora. Puedes descargarlo a través de la siguiente URL, que con suerte no habrán cambiado cuando leas esto. https://dev.mysql.com/downloads/mysql/ Machine Translated by Google La instalación es bastante sencilla, utilizando una instalación visual estándar. programa. Sólo asegúrese de que los conectores Python estén instalados, lo cual debería ser la predeterminada y no olvide la contraseña de root que deberá configurar. . En mis ejemplos aquí, mantendré esa contraseña simplemente como root Una vez finalizada la instalación del software, podemos comenzar a configurar las cosas. arriba. Hay un programa instalado con la instalación del servidor MySQL llamado Banco de trabajo MySQL , que podemos utilizar para configurar bases de datos y tablas. Inicie MySQL Workbench y abra una conexión a su base de datos servidor. Suponiendo que instaló el servidor en la misma computadora en la que está trabajando, puede conectarse al nombre de host 127.0.0.1 el loopback, estándar para localhost Ingrese . el nombre de usuario y contraseña que seleccionó en la instalación, y ya está todo listo. Una vez que tenga una conexión abierta a su nuevo servidor de base de datos, necesitará para crear una nueva base de datos en el servidor. Un servidor MySQL puede ejecutar múltiples bases de datos, y si está utilizando el servidor para muchas cosas, generalmente es una buena idea de mantener separadas las tareas no relacionadas. En nuestro caso, estamos creando una bolsa de valores. base de datos que contendrá series temporales y metadatos para los mercados financieros. Las bases de datos en un servidor de bases de datos a menudo se denominan esquemas, y eso es lo que a MySQL Workbench también le gusta llamarlos. Crear una nueva base de datos, o esquema si lo desea, y asígnele un nombre inteligente. Si no has gastado demasiado tiempo en el lado tecnológico de las cosas, puede que le sorprenda cuánto esfuerzo dedican los técnicos tienden a esforzarse al nombrar y lo inteligentes que se sienten cuando descubren una buena esquema de nomenclatura. Mi base de datos, como ve en la Figura 24 1, se llama mimisbrunn r . esto es de Por supuesto, una referencia obvia al Pozo de la Sabiduría donde Odín sacrificó su…. Ah bien. A nadie le importa. Hacia adelante. Machine Translated by Google Figura 24 1 Banco de trabajo MySQL Elaboración de una tabla de series temporales de acciones Ahora tenemos una base de datos, pero lo que todavía nos faltan son tablas. A medida que se sienta más cómodo trabajando con bases de datos, se dará cuenta de que puede resultar muy útil tener algunas tablas diferentes para diferentes cosas. El ejemplo más sencillo sería tener una tabla de series temporales de acciones para datos históricos y metadatos para información como el nombre de la empresa, la moneda, el sector, la industria y varios esquemas de códigos. Sin embargo, por ahora, todo lo que necesitamos es una tabla simple de series temporales para instrumentos de tipo accionario. Cree una nueva tabla y llámela Equity_histor y .En nuestro ejemplo, esta tabla contendrá los campos habituales de series de tiempo, como apertura, máximo, mínimo, cierre y volumen, pero también un par de otros campos importantes. Machine Translated by Google También agregaré un campo para dividendos y un campo para membresía en el índice. El campo de dividendo será cero la mayoría de los días y solo tendrá un valor si la acción quedó ex­div ese día. Como se mencionó anteriormente, es muy importante tener en cuenta los dividendos para realizar un backtesting adecuado. Esperemos que ya haya obtenido una fuente de datos confiable que contenga esta información. El segundo campo algo diferente que incluiré en esta tabla es in_sp50 . Este campo tendrá un valor de 0 si la acción no era miembro de la 0 Índice S&P 500 de un día determinado, o el valor 1 si formara parte del índice. De esta manera, más adelante en nuestras pruebas retrospectivas podemos limitar nuestras operaciones a acciones que formaban parte del índice en un día determinado. Aquellos lectores que ya están bastante familiarizados con las bases de datos ahora gritan a las páginas que esta no es exactamente una buena manera de almacenar dichos datos. Es probable que en este momento haya lectores que estén furiosos por cómo se almacenan aquí los datos de dividendos y membresía del índice. Y sí, por supuesto que tienen razón. Esta no es la mejor manera de hacerlo. Pero es lo más sencillo. Mantendré este formato por ahora, ya que facilita la implementación y la explicación de cómo funcionan las cosas. Nos permitirá avanzar más rápido y volver a centrarnos en el comercio real. Si se siente lo suficientemente cómodo con las bases de datos, no dude en crear tablas de búsqueda separadas para información sobre dividendos y membresía en índices. Hacerlo facilitará la escala de esta lógica, se expandirá para cubrir más índices y hará que el almacenamiento sea más eficiente. Pero realmente no importa mucho para los propósitos de este libro. Machine Translated by Google Figura 24 2 Tabla de historial de acciones Como muestra la Figura 24 2, configuré los dos primeros campos, trade_dat e y ticker , como clave principal e índice único. Esto ayudará a acelerar las consultas y evitar entradas duplicadas. Como esta tabla está pensada para datos diarios, sólo debe haber Habrá una fila posible para cada acción y día. Continúe y cree la tabla. Ahora podemos completarlo con algunos datos. Llenando la base de datos Afortunadamente, Pandas hace que hablar con nuestra base de datos sea muy sencillo. Leyendo y escribir datos es muy sencillo usando la biblioteca Pandas . Para establecer una conexión con , la base de datos a través de Pandas se puede utilizar una biblioteca auxiliar llamada SqlAlchemy . útil, así que comencemos instalándolo. La instalación de esta biblioteca se realiza utilizando los mismos métodos que utilizamos. anteriormente en este libro para instalar las bibliotecas de Python. Puede abrir una terminal desde el Anaconda Navigator e instálelo desde allí. Asegúrate de seleccionar el zip3 5 entorno, para que podamos usar esta biblioteca con nuestro código Zipline. Machine Translated by Google Conda instala sqlalchemy Una vez instalada esta biblioteca, ahora estamos listos para hablar con nuestra nueva base de datos a través de Python. Dependiendo del proveedor de datos con el que esté tratando y del tipo de opción de entrega que haya elegido con ellos, sus datos pueden estar en varios formatos diferentes. Resulta que Pandas es bastante competente para lidiar con esto. Como ejemplo, veremos cómo convertir un archivo plano separado por comas en una tabla de base de datos ordenada. A partir de este ejemplo, debería poder adaptar el código para tratar cualquier formato de los datos que reciba de su proveedor. Este tipo de tarea es algo que probablemente necesitará con bastante frecuencia. Es importante entender qué está pasando aquí y cómo funciona, así que lo iremos paso a paso. Vamos a crear una función que lea un archivo CSV del disco e inserte esos datos en una base de datos MySQL . Luego, recorreremos los archivos CSV en una carpeta y los procesaremos uno por uno. Como primer paso, leeremos un archivo CSV y comprobaremos qué contiene. Si no tiene archivos CSV listos para usar y desea probar esto, puede usar los datos generados aleatoriamente que se pueden descargar del sitio web del libro, www.followingthetrend.com/trading­evolved . Este ejemplo supone que ha descargado los datos aleatorios de a mí. Además, no olvide importar pandas como pd , tal como lo hemos estado haciendo antes. Mientras aprende, puede resultar útil escribir parte del código y luego detenerse para generar resultados. De esa manera, puede asegurarse de que el resultado sea el esperado y podrá identificar problemas potenciales desde el principio. Pruebe primero el siguiente código. Asegúrese de actualizar la variable data_path para que apunte a dónde están sus datos. Siempre puedes descargar mi conjunto o datos aleatorios si no tienes otros datos a mano. importar pandas como pd ruta_datos = '../datos/existencias_aleatorias' def import_file(símbolo): ruta = '{}/{}.csv'.format(data_path,symbol) df = pd.read_csv(ruta, index_col=[0], parse_dates=True) return df df = import_file('A') df.head() Machine Translated by Google Si todo funcionó bien y tenía un archivo llamado A.cs v en la ruta especificada en el código, deberías ver algo como esto. trade_date apertura alto bajo cierre volumen dividendo in_sp500 1999­11­18 100,5 100,5 100,5 100,5 1000000,0 0,0 0 1999­11­19 100,0 100,0 100,0 100,0 1000000,0 0,0 0 1999­11­22 99,5 99,5 99,5 99,5 1000000,0 0,0 0 1999­11­23 100,0 100,0 100,0 100,0 1000000,0 0,0 0 1999­11­24 100,5 100,5 100,5 100,5 1000000,0 0,0 0 Mira lo que hace este código. Llamamos a la función import_fil e y proporcionamos la cadena 'A ' como simbolo. La función construye una cadena de ruta, apuntando a donde debería estar el archivo. Luego, Pandas lee el archivo CSV y le indicamos que la primera columna, comenzando con el número 0, es el índice y también una columna de datos. Decirle a los pandas que analizar la fecha por nosotros significa que podemos proporcionar prácticamente cualquier tipo de fecha formato, y descubrirá cuál es el día, el mes y el año para nosotros. Finalmente nosotros devolver el DataFrame terminado . La última fila del código imprime las primeras diez filas del DataFrame. Esta es una forma útil de comprobar lo que hay dentro y asegurarse de que todo funcionó bien. Si Si prefiere imprimir las últimas diez filas, puede llamar a .tail() en lugar de .head() . Hasta ahora, todo bien. A continuación, vamos a agregar el código para conectarnos al base de datos y escribir los datos en ella. Desde el punto de vista del flujo, lo que queremos hacer aquí es lo siguiente. Compruebe qué archivos de datos están disponibles en el disco. Lea un archivo a la vez, usando Pandas. Construya una declaración de inserción SQL con los datos leídos. Envíe esa declaración de inserción al servidor. Para esta tarea, necesitamos importar solo tres bibliotecas. Necesitamos el sistema operativo biblioteca para enumerar los archivos disponibles, Pandas para leer los datos y SqlAlchemy para hablar con la base de datos. Además de esto, importé tqdm_notebook , que simplemente proporciona una barra de progreso visual para el entorno de Jupiter Notebook mientras esperamos. . Machine Translated by Google importar sistema operativo importar pandas como pd desde sqlalchemy importar create_engine desde tqdm importar tqdm_notebook motor = create_engine('mysql+mysqlconnector://root:root@localhost/mimisbrunnr') ubicación_datos = '../datos/existencias_aleatorias/' A continuación, haremos una única función que toma un símbolo bursátil como entrada, lee los datos del disco, crea una declaración SQL y la ejecuta. Sí, casi toda la lógica que necesitamos, en una sola función. Con esta función, quiero mostrarles lo útil que puede ser Python para simplificar operaciones aparentemente complejas. Lo que voy a hacer aquí es construir una declaración de inserción gigante, en lugar de hacer miles de pequeñas. Podríamos ejecutar una declaración de inserción por día y símbolo, pero eso sería tremendamente lento. En su lugar, enviaremos una declaración de inserción por símbolo bursátil. La sintaxis que queremos para esta declaración de inserción se muestra a continuación. En el siguiente ejemplo verá tres días de muestra, pero podemos agregar una gran cantidad de días de esta manera. Como puede ver, primero definimos el diseño del encabezado de la fila y luego los valores para cada día. La parte final del comunicado trata de lo que sucede en caso de duplicados. Agregué esto, ya que probablemente lo necesites. Si intenta importar datos de una acción en un día en el que su base de datos ya tiene datos, obtendrá un error. Con esta instrucción agregada al final de la instrucción de inserción, especificamos que en caso de duplicados, simplemente sobrescribimos con los datos más recientes. insertar en valores de Equity_history (fecha_comercial, ticker, apertura, máximo, mínimo, cierre, volumen, dividendo, in_sp500) ('2000­01­01', 'ABC', 10, 11, 9, 10, 1000, 0, 1), ('2000­01­02', 'ABC', 10, 11, 9, 10, 1000, 0, 1), ('2000­01­02', 'ABC', 10, 11, 9, 10, 1000, 0, 1), en actualización de clave duplicada open=values(open), high=values(high), low=values( bajo), cerrar=valores(cerrar), volumen=valores(volumen), dividendo=valores(dividendo), in_sp500=valores(in_sp500); Armados con este conocimiento, echemos un vistazo a cómo nuestra función de importación construye esta cadena de texto potencialmente gigante. La primera parte, leer los datos, ya debería resultarle muy familiar. Después de eso, agregamos la parte inicial de la declaración de inserción. # Primera parte de la declaración insertada Machine Translated by Google insert_init = """insertar en valores de Equity_history (fecha_comercial, ticker, apertura, máximo, mínimo, cierre, volumen, dividendo, in_sp500) """ Hasta ahora, nada fuera de lo común, pero la siguiente línea de código es donde las cosas se ponen inteligentes. Aquí es donde creamos una cadena de texto gigante de valores separados por comas, con cada día entre paréntesis. Esta fila tiene mucho que digerir. La fila comienza con una cadena de texto de un solo carácter, seguida de una función de unión . La lógica de la unión es que usted establece un delimitador, proporciona una lista y recupera una cadena delimitada. Por ejemplo, “­“.join(['a','b','c' ] devolverá ab­c . Después de eso es donde creamos la lista, que puede ver mediante el uso de corchetes. Iteramos cada fila, usando df.iterrows( ) , lo que nos dará el índice de fila y los valores de fila para cada día. Luego armamos una cadena de texto para cada día, donde la función .forma t insertará cada valor en su posición designada entre llaves. Esta forma de trabajar con Python es muy común y si tiene dificultades con la lógica de esto, le aconsejo que se tome un momento y estudie la siguiente fila. Pruébelo usted mismo, haga algunos cambios y vea qué sucede. # Agregue valores para todos los días a la declaración de inserción vals = ",".join(["""('{}', '{}', {}, {}, {}, {}, {}, {} , {})""".formato( str(día), símbolo, fila.abierta, fila.alta, fila.baja, fila.cerrar, fila.volumen, fila.dividendo, fila.in_sp500) para el día, fila en df.iterrows()]) Como ya hemos completado la mayor parte de la lógica, todo lo que tenemos que hacer es agregar la parte final de la declaración de inserción. Es decir, la parte que actualiza los valores duplicados y finalmente juntamos las tres partes de la declaración de inserción. # Manejar duplicados: evitar errores si ya tiene algunos datos # en su tabla insert_end = en """ actualización de clave duplicada open=values(open), high=values(high), low=values(low), close=values(close ), volumen=valores(volumen), dividendo=valores(dividendo), in_sp500=valores(in_sp500);""" # Junte las piezas Machine Translated by Google consulta = insertar_init + vals + insertar Eso es todo. Entonces estamos listos para enviar esta declaración al servidor. # Activar instrucción de inserción motor.execute (consulta) Ese es todo el código que necesitamos para convertir un archivo csv con datos bursátiles en filas de la base de datos. Pero eso fue solo para una acción única, por lo que claramente necesitamos llamar a esta función más de una vez. Lo que haremos es usar la biblioteca del sistema operativo para enumerar los archivos en la carpeta dada y luego recorrer archivo por archivo. Como se mencionó, usaremos la biblioteca tqdm para crear una barra de progreso visual. """ Función: get_symbols Propósito: Devuelve nombres de archivos en el directorio de datos. """ def símbolos_proceso(): # ¿Recuerdas cortar? Eliminemos los últimos cuatro # caracteres, que serán '.csv' # Usando [] para hacer una lista de todos los símbolos símbolos = [s[:­4] para s en os.listdir(data_location)] para símbolo en tqdm_notebook(symbols, desc='Importando acciones...'): import_file (símbolo) símbolos_proceso() Eso es todo lo que necesitamos. Como de costumbre, mostraré el código completo a continuación. importar sistema operativo importar pandas como pd desde sqlalchemy importar create_engine desde tqdm importar tqdm_notebook motor = create_engine('mysql+mysqlconnector://root:root@localhost/mimisbrunnr') ubicación_datos = '../datos/existencias_aleatorias/' """ Función: import_file Propósito: Lee un archivo CSV y almacena los datos en una base de datos. """ def import_file(símbolo): ruta = ubicación_datos + '{}.csv'.format(símbolo) df = pd.read_csv(ruta, index_col=[0], parse_dates=[0]) # Primera parte de la declaración insert_init = """insertar en valores de Equity_history (trade_date, ticker, apertura, máximo, mínimo, cierre, volumen, dividendo, in_sp500) """ Machine Translated by Google # Agregue valores para todos los días a la declaración de inserción vals = ",".join(["""('{}', '{}', {}, {}, {}, {}, {}, {} , {})""".formato( str(día), símbolo, fila.abierto, fila.alta, fila.baja, fila.cerrar, fila.volumen, fila.dividendo, fila.in_sp500) para el día, fila en df.iterrows()]) # Manejar duplicados: evitar errores si ya tiene algunos datos # en su tabla insert_end = en actualización """ de clave duplicada open=values(open), high=values(high), low=values(low), close=values(close ), volumen=valores(volumen), dividendo=valores(dividendo), in_sp500=valores(in_sp500);""" # Juntar las partes query = insert_init + vals + insert # Declaración de inserción de fuego motor.ejecutar (consulta) """ Función: get_symbols Propósito: Devuelve nombres de archivos en el directorio de datos. """ def símbolos_proceso(): # ¿Recuerdas cortar? Eliminemos los últimos cuatro # caracteres, que serán '.csv' # Usando [] para hacer una lista de todos los símbolos símbolos = [s[:­4] para s en os.listdir(data_location)] para símbolo en tqdm_notebook(symbols, desc='Importing...'): importar_archivo(símbolo) símbolos_proceso() Consultando la base de datos Ahora que tenemos una buena base de datos de series temporales, podemos acceder a ella fácil y rápidamente. Si bien necesitamos usar SQL para hablar con nuestra base de datos, realmente no necesitamos habilidades SQL más profundas. Será suficiente una comprensión muy básica de este lenguaje de consulta. Machine Translated by Google Para demostrarlo, escribiremos un código para recuperar series temporales de la base de datos y mostrar un gráfico. Cosas fáciles. La sintaxis básica para solicitar información a la base de datos es algo así como este. SELECCIONE campos DE la tabla DONDE condiciones Por supuesto, SQL puede hacer mucho más si así lo desea. Una razón por la que recomiendo utilizar una base de datos adecuada en lugar de tratar directamente con archivos planos es que tienes muchas más posibilidades. A medida que profundice y amplíe sus conocimientos, descubrirá que utilizar una base de datos MySQL le resultará de gran ayuda cuando empiece a crear modelos más complejos o a ampliar su cobertura de datos. El siguiente código recuperará el historial de series temporales del ticker AAPL y lo trazará, utilizando las mismas técnicas que aprendimos anteriormente en este libro. Tenga en cuenta que también incluí un ejemplo de una nueva forma de dividir un Pandas DataFrame . En este código, en la penúltima línea, selecciono todos los datos entre 2014 y 2015 y los selecciono para los gráficos. %matplotlib importa pandas en línea como pd desde matplotlib importa pyplot como plt, rc desde sqlalchemy importa create_engine motor = create_engine('mysql+mysqlconnector://root:root@localhost/mimisbrunnr') # Formato de gráfico, utilizado para la imagen del libro font = {'family' : 'eurostile', 'weight' : 'normal', 'size' : 16} rc('font', **font) # Función para recuperar el historial del historial de db def (símbolo): query = """seleccione trade_date, cierre, volumen de equidad_history donde ticker='{}' """.format(symbol) print('Esta es la declaración SQL que enviamos: \n {}'.format(query)) df = pd.read_sql_query(query, motor, index_col='trade_date', parse_dates=[' fecha_comercio']) retorno df # Obtener datos para el símbolo bursátil = 'AAPL' hist = historial (ticker) # Configurar el gráfico fig = plt.figure(figsize=(15, 8)) ax = fig.add_subplot(111) ax.grid(True) ax.set_title('Gráfico para {}'.format(ticker)) # Tenga en cuenta cómo puede utilizar rangos de fechas para dividir la serie temporal Machine Translated by Google plot_data = hist['2014­01­01':'2015­01­01'] # Trazar los datos de cierre ax.plot(plot_data.close, linestyle='­', label=ticker, linewidth=3.0, color='black') ax.set_ylabel("Price") # Crea un segundo eje Y, compartiendo el mismo X ax2 = ax.twinx() ax2.set_ylabel("Volumen") # Trazar el volumen como barras ax2.bar(plot_data.index, plot_data.volume, color='grey') Este código generará la consulta SQL en sí, solo para mostrarle claramente cómo se ve la cadena de consulta terminada. Esta es la declaración SQL que enviamos: select trade_date, close, volume from equidad_history donde ticker='AAPL' Finalmente generará un gráfico que ahora le resultará familiar. Agregué un segundo y­ eje con información de volumen también, solo para mostrar cómo se puede hacer eso. Si se pregunta por qué el precio de AAPL se ve un poco diferente de lo que recuerda, hay una buena razón para ello. En este ejemplo, utilicé datos aleatorios de mi sitio web para permitir que los lectores comiencen a trabajar y aprender incluso si aún no tienen una fuente de datos adecuada. Se trata de datos generados aleatoriamente, utilizando una metodología de Random Walk . Figura 24 3 Manzanas al azar Como puede ver aquí, simplemente solicitar a la base de datos el historial de series temporales de un ticker determinado es muy sencillo. Por el momento, eso es realmente todo el SQL que necesita saber. Existe un claro beneficio al comprender un poco más de SQL, pero con este capítulo simplemente quiero que se interese en el tema. Machine Translated by Google Hacer un paquete de base de datos La lógica para importar datos de una base de datos de valores a Zipline ya debería resultarle bastante familiar. El código que aparece a continuación es prácticamente el mismo que cuando leíamos datos de archivos csv anteriormente. La única diferencia real es que ahora leemos datos de la base de datos, en lugar de archivos planos. Cuando leemos archivos csv del disco, usamos la función pd.read_csv( ) y la principal diferencia aquí es que en su lugar usamos pd.read_sql_query( ) para recuperar datos. Proporcionaremos a esta función una conexión a la base de datos y una consulta SQL simple para especificar qué datos queremos recuperar. Dado que completamos nuestra nueva base de datos con acciones anteriormente en este capítulo, ahora podemos simplemente preguntarle a la base de datos qué acciones exactas están disponibles. La siguiente función muestra cómo se puede hacer eso con un código mínimo. desde sqlalchemy importar create_engine motor = create_engine('mysql+mysqlconnector://root:root@localhost/mimisbrunnr') def available_stocks(): symbol_query = "seleccione un ticker distinto de equidad_history ordenar por ticker" símbolos = pd.read_sql_query(symbol_query, motor) return símbolos.ticker # Devuelve una lista de tickers El método para obtener una lista de acciones disponibles es una de las dos partes en las que el código difiere del paquete de acciones csv que vimos anteriormente. La segunda parte que difiere es cómo leemos los datos de cada acción. Donde antes leíamos un archivo por acción, ahora lanzamos una consulta a la base de datos por acción. # Hacer una consulta a la base de datos query = """seleccione trade_date como fecha, apertura, máximo, mínimo, cierre, volumen, dividendo de equidad_history donde ticker='{}' ordene por trade_date; """.format(symbol) # Solicite los datos a la base de datos df = pd.read_sql_query(query, motor, index_col='date', parse_dates=['date']) Eso es realmente lo único que difiere. Aunque es materialmente lo mismo, incluyo el código fuente completo del paquete de base de datos a continuación. Naturalmente, también podrías hacer lo mismo con los datos de futuros. importar pandas como pd desde tqdm importar tqdm # Usado para la barra de progreso desde sqlalchemy importar create_engine motor = create_engine('mysql+mysqlconnector://root:root@localhost/mimisbrunnr') Machine Translated by Google def available_stocks(): symbol_query = "seleccione un ticker distinto de equidad_history ordenar por ticker" símbolos = pd.read_sql_query(symbol_query, motor) return símbolos.ticker # Devuelve una lista de tickers """ La función de ingesta debe tener esta firma exacta, lo que significa que estos argumentos se pasan, como se muestra a continuación. """ def paquete_base_datos(entorno, activo_db_writer, minuto_bar_writer, daily_bar_writer, ajuste_writer, calendario, inicio_sesión, fin_sesión, caché, show_progress, salida_dir): # Obtener una lista de archivos de la ruta # Cortar la última parte # 'example.csv'[:­4] = símbolos 'example' = stocks_disponibles() # Prepare un DataFrame vacío para dividendos divs = pd.DataFrame(columns=['sid', 'amount', 'ex_date', 'record_date', 'declared_date', 'pay_date'] ) # Preparar un DataFrame vacío para divisiones splits = pd.DataFrame(columns=['sid', 'ratio', ' Effective_date'] ) # Prepare un DataFrame vacío para metadatos metadata = pd.DataFrame(columns=('start_date', 'end_date', 'auto_close_date', 'symbol', 'exchange') ) # Verifique las fechas de negociación válidas, según las sesiones del calendario de intercambio seleccionadas = calendar.sessions_in_range(start_session, end_session) Machine Translated by Google # Obtener datos de todas las acciones y escribir en Zipline daily_bar_writer.write (process_stocks (símbolos, sesiones, metadatos, divs)) # Escribir los metadatos active_db_writer.write(equities=metadatos) # Escribir divisiones y dividendos ajuste_escritor.write(splits=splits, dividends=divs) """ Función de generador para iterar acciones, crear datos históricos, metadatos y datos de dividendos. """ def Process_Stocks (símbolos, sesiones, metadatos, divs): # Realizar un bucle en las acciones, estableciendo un ID de seguridad (SID) único sid = 0 para símbolo en tqdm(símbolos): sid += 1 # Hacer una consulta a la base de datos query = """seleccione trade_date como fecha, apertura, máximo, mínimo, cierre, volumen, dividendo de equidad_history donde ticker='{}' ordene por trade_date; """.format(symbol) # Solicite los datos a la base de datos df = pd.read_sql_query(query, motor, index_col='date', parse_dates=['date']) # Verifique la primera y última fecha. fecha_inicio = df.index[0] fecha_final = df.index[­1] # Sincronizar con el calendario oficial de intercambio df = df.reindex(sessions.tz_localize(None))[start_date:end_date] # Rellenar hacia adelante los datos faltantes df.fillna(method='ffill', inplace=True) # Eliminar el NaN restante df.dropna(inplace=True) Machine Translated by Google # La fecha de cierre automático es el día después de la última operación. ac_date = end_date + pd.Timedelta(días=1) # Agregue una fila al DataFrame de metadatos. metadata.loc[sid] = fecha_inicio, fecha_final, fecha_ac, símbolo, 'NYSE' # Si hay datos de dividendos, agréguelos al marco de datos de dividendos si hay 'dividendos' en df.columns: # Cortar los días con dividendos tmp = df[df['dividend'] != 0.0]['dividend'] div = pd.DataFrame(data=tmp.index.tolist(), columns=['ex_date']) # Proporcione columnas vacías ya que no tenemos estos datos por ahora div['record_date'] = pd.NaT div['declared_date'] = pd.NaT div['pay_date'] = pd.NaT # Almacenar los dividendos y establecer el ID de seguridad div['amount'] = tmp.tolist() div['sid'] = sid # Comience a numerar donde lo dejamos la última vez ind = pd.Index(range(divs.shape[0], divs.shape[0] + div.shape[0])) div.set_index(ind, inplace=True) # Agregue los dividendos de esta acción a la lista de todos los dividendos divs = divs.append(div) rendimiento sid, df Palabras finales: camino a seguir Este ha sido un libro bastante largo y, con suerte, repleto de información nueva para la mayoría de los lectores. Si lees el libro hasta el final y te cuesta absorber todo su contenido, no te preocupes. Para la mayoría de los lectores, este libro contiene una cantidad sustancial de información nueva y esperaría que necesitaran leerlo varias veces y probar el código de muestra antes de comprenderlo completamente. Es mucho para asimilar a la vez. Como se mencionó en la introducción de este libro, no hay sustituto para la experiencia práctica real. Para aprovechar al máximo este libro, le recomiendo encarecidamente que configure un entorno como se describe en este libro. Instale el software, importe sus datos y replique los modelos. Machine Translated by Google Quizás su interés no esté en absoluto en el tipo de modelos comerciales presentados en este libro. Eso está perfectamente bien y probablemente será el caso de muchos lectores. Pero si comienza replicando estos modelos, tendrá una base sólida sobre la cual construir cuando pase a codificar sus propias ideas. Es imposible que un libro de esta naturaleza le enseñe todo, ya sea sobre comercio o sobre Python. Solo puede arañar la superficie de ambos y, con suerte, despertar su interés en seguir adelante por su cuenta. Construye tus propios modelos Probablemente tenga algunas ideas comerciales interesantes. Quizás haya construido modelos antes en uno de los sospechosos habituales en la línea de software de comercio minorista que desea probar en Python. Traducir un modelo existente desde una plataforma diferente es un gran ejercicio. Si ya tiene un modelo de este tipo, comience traduciéndolo a Python. De esa manera, puede comparar fácilmente paso a paso para asegurarse de obtener los resultados que espera. Al aprender, la depuración resulta mucho más fácil si ya sabes qué resultados estás buscando. Si aún no ha creado ningún modelo en otras plataformas, probablemente todavía tenga ideas comerciales. Puede ser una gran revelación la primera vez que intenta formular reglas precisas para sus ideas comerciales, y puede ser una experiencia muy valiosa. Lo que le sucede a la mayoría de las personas es que te obliga a definir las reglas con mucho más detalle de lo que esperabas. Es posible que haya estado negociando estas reglas durante años sin darse cuenta de que tenían algún componente discrecional. Una vez que lo reduzcas a matemáticas exactas, sin duda descubrirás si este es el caso, y eso puede ser un gran paso para convertirte en un operador sistemático de pleno derecho. Otros motores de backtesting Machine Translated by Google Todo este libro se ha centrado en un único motor de backtesting, y sospecho que eso me hará ganar algunas reseñas de una estrella en Amazon. Sería bueno poder proporcionar instrucciones y códigos de muestra para muchos paquetes de software de backtesting diferentes, pero eso simplemente no sería factible en un libro como este. O haría que el libro fuera cinco veces más largo o reduciría el modelado real a una breve descripción general. Consideré cubrir solo dos motores de backtesting, ya que en este momento hay dos de ellos compitiendo en la carrera por el backtester más avanzado. El otro motor en ese caso habría sido LEAN, construido y mantenido por QuantConnect. LEAN es un motor de backtesting de código abierto, muy parecido a Zipline, y también tiene una versión en línea donde también obtienes acceso gratuito a los datos. Así como puede utilizar el sitio web de Quantopian para ejecutar pruebas retrospectivas en su entorno alojado con sus datos, puede hacer lo mismo en el sitio web de QuantConnect. Sin embargo, desde el punto de vista técnico, ambos motores son extremadamente diferentes. Si bien Zipline es una solución nativa de Python, LEAN está desarrollado en C# y le permite elegir entre múltiples lenguajes de programación para escribir algo. El proceso de instalación y configuración de LEAN es muy diferente al de Zipline y está fuera del alcance de este libro en particular. Sin embargo, este es un motor de backtesting sólido y espero escribir un poco más sobre él en mi sitio web. Claramente, la capacidad de escribir algos en varios idiomas es una gran ventaja para QuantConnect, pero la otra cara de la moneda es que se está perdiendo la experiencia nativa de Python. Si desea probar LEAN sin invertir demasiado tiempo por adelantado, puede ir al sitio web de QuantConnect (QuantConnect, 2019) y probar un código de muestra con sus datos. Otro motor de backtesting muy utilizado es Backtrader (Backtrader, 2019), que además es de código abierto y completamente gratuito. Existe una comunidad Backtrader activa y debería poder encontrar código de muestra y ayuda en foros en línea. ¿Pero necesitas probar otros motores de backtesting? Bueno, todo eso depende de sus propios estilos, ideas y requisitos comerciales. Para muchos traders sistemáticos, Zipline puede hacer cualquier cosa que quieran. Es posible que otros operadores ya hayan encontrado un factor decisivo en los capítulos introductorios. Machine Translated by Google Quizás sólo le interesen las estrategias de opciones o el mercado de divisas al contado. Quizás necesite soporte de activos multidivisa, o quizás haya otras razones para buscar un motor de backtesting diferente. En mi opinión, ningún motor de backtesting es perfecto. No existe una ventanilla única. Si bien considero que Zipline es un gran software, esto puede ser cierto o no para usted y su situación. Después de leer este libro, debería tener suficientes conocimientos sobre Python y backtesting, para poder determinar si necesita algo diferente y cómo configurar otro backtester. Cómo ganar dinero en los mercados Para concluir este libro, me gustaría reiterar un punto que trato de resaltar cuando hablo en conferencias alrededor del mundo. La mayoría de las personas que quieren ganar dinero en los mercados financieros no han entendido dónde está el dinero real. Y no se trata de operar con su propia cuenta. Muy pocas personas se han vuelto financieramente independientes operando con su propia cuenta. Te vuelves financieramente independiente intercambiando el dinero de otras personas. La esencia de este argumento es que si intercambias tu propio dinero, tienes una ventaja limitada y asumes todo el riesgo. Es probable que un operador profesional altamente calificado obtenga rendimientos en el rango del 12 al 18% anual a lo largo del tiempo, con reducciones ocasionales de aproximadamente tres veces más. Esperar rentabilidades de tres dígitos con bajo riesgo no se basa en la realidad. Sin embargo, si intercambia el dinero de otras personas, tal vez junto con el suyo propio, tendrá ventajas casi ilimitadas y desventajas limitadas. Puede crecer administrando más dinero, multiplicando su potencial de ingresos. En comparación, la decisión de gestionar sólo su propio dinero equivale a una mala decisión comercial. No menciono esto simplemente para provocar que pienses en una dirección diferente. Este es un consejo genuino. Intercambiar los activos de otras personas para ganarse la vida es el camino a seguir, desde una perspectiva financiera. De ahí proviene el dinero interesante del trading. Los tipos de modelos descritos en este libro se seleccionan deliberadamente teniendo esto en cuenta. Los modelos de futuros, por ejemplo, pueden resultar difíciles de implementar con una cuenta privada pequeña, pero pueden resultar bastante interesantes para la gestión profesional de activos. Machine Translated by Google Si estás al principio de tu carrera y recién estás comenzando con todo esto modelos financieros y backtesting, mi consejo número uno para usted es considerar seguir la ruta profesional y tratar de administrar el dinero de otras personas para un viviendo. Referencias Descargar naconda . (Dakota del Norte). Obtenido de https://www.anaconda.com/download/ comerciante .(2019, junio). Obtenido de https://www.backtrader.com/ Arver, R. (2015). Comercio sistemático. Casa Harriman. Arver, R. (2019). Comercio apalancado. Casa Harriman. lenow, A. (2013). Siguiendo la tendencia. Wiley. lenow, A. (2015). Acciones en movimiento. Rinold, R. (1999). Gestión Activa de Cartera. Wiley. Ilpisch, Y. (2018). Python para finanzas. Medios O'Reilly. Lewis, M. (1989). Póquer del mentiroso. McKinney, W. (2017). Python para análisis de datos. uantConnect . (2019, junio). Obtenido de https://quantconnect.com PIVA . (2019). Obtenido de https://us.spindices.com/spiva/ Versiones de software utilizadas Esta sección está destinada a aquellos que buscan replicar exactamente entorno que utilicé para este libro. Eso no debería ser necesario para la mayoría de usted, pero lo proporciono aquí por si acaso. Los modelos de libros fueron escritos en una máquina de desarrollo con Windows. 10 instalados. También fue probado en otra máquina con Windows 7 y algunos diferentes configuraciones. A continuación se muestran las versiones de los paquetes de Python utilizados para entorno zip35 en el libro. Se utilizó la versión 3.5 de Python y la versión 4.5.10 de Conda. # paquetes en el entorno en C:\ProgramData\Anaconda3_new\envs\zip35: # # Construir canal Versión 0.4.0 Nombre py35_1 0.7.10 py35_0 0.7.7 py35_0 Quantópico _nb_ext_conf alabastro0.3.6 alambique alfalén py_0 conda­forja Machine Translated by Google cliente­anaconda 1.6.14 py35_0 0.24.0 py35_0 1.5.3 py35_0 2.5.0 py35_0 0.1.0 py35_0 0.12.1 np111py35_0 Cuántópico mkl 1.0 2.1.3 py35_0 1.14.3 he51fdeb_0 1.2.1 py35h452e1ab_1 1.0.6 hfa6e2cd_5 botella bzip2 certificados 2019.3.9 hecc5488_0 conda­forja ca 2018.8.24 py35_1001 conda­forja 1.11.5 py35h74b6da3_1 3.0.4 py35_0 6,7 py35h10df73f_0 1.2.2 py35h3cd9751_1 certificado 0.3.9 py35h32a752f_0 cffi chardet 0.5.5 py35h0a97e54_0 2.3.1 haga clic en py35h74b6da3_0 0.10.0 clyent py35_0 0.2.2 colorama py35_0 Quantópico 0.28.3 py35hfa6e2cd_0 contextlib2 4.3.0 py35_0 0,14 py35_0 0,5,0 criptografía py35_0 cuantotópico 0,2,3 ciclador py35hb91ced9_2 2,9,1 ha9979f8_1 0,4 py35_0 1,10,2 hac2f561_1 cyordereddict cython decorador docutils puntos de entrada empíricos funciones de tipo libre hdf5 html5lib 1,0,1 py35h047fa9f_0 asn1crypto astroid babel backcall bcolz blas lejía blosc cuello de 2017.0.4 58.2 2,7 0.7.1 tamaño de 2018.0.3 imagen intel­ openmp 2.1.0 4.8.2 intervalotree 6.4.0 ipykernel 0.2.0 ipython 7.2.1 ipython_genutils ipywidgets 4.2.15 isort icc_rt icu idna jedi jinja2 jpeg jsonschema jupyter_client jupyter_core kiwisolver lazy­object­proxy libiconv libpng libsodium libxml2 libxslt logbook lru­dict lxml lzo 0.12.0 2.10 9b 2.6.0 5.2.3 4.4.0 1.0.1 1.3.1 1,15 1.6.37 1.0.16 2.9.8 1.1.32 0.12.5 1.1.4 4.2.5 2.10 h97af966_0 ha66f8fd_1 py35_0 py35_0 0 py35_0 Quantópico py35_0 py35_0 py35ha709e79_0 py35_0 py35_0 py35_1 py35hdf652bb_0 hb83a4c4_2 py35h27d56d3_0 py35_0 py35h629ba7f_0 py35hc605aed_0 py35_0 h1df5818_7 h7602738_0 conda­forja h9d3ae62_0 hadb2253_1 hf6f1972_0 py35_0 Quantópico py35_0 Quantópico py35hef2cd61_0 h6df0209_2 Machine Translated by Google 6 m2w64­gcc­libgfortran 5.3.0 m2w64­ gcc­libs 5.3.0 m2w64­gcc­libs­core 7 5.3.0 m2w64­gmp 6.1.0 m2w64­ libwinpthread­git 5.0.0.4634.697f757 2 7 2 mako py35ha146b58_0 1.0.7 1.0 marcado seguro py35hc253e08_1 2.2.2 matplotlib py35had4c4a9_2 sintonizar 0.8.3 py35hfa6e2cd_1 mkl2018.0.3 1 simulacro 2.0.0 py35h0f49239_0 msys2­conda­época 20160418 1 multipledispatch 0.5.0 mysql­ connector­python 2.0.4 nb_anacondacloud nb_conda nb_conda_kernels nbconvert nbformat nbpresent networkx norgatedata notebook con parámetros de nariz numexpr numpy 2.2.0 2.1.0 5.3.1 py35h98d6c46_0 py35h908c9d9_0 py35_0 1.11 0.1.45 py35h097edc8_0 py35h39e3cac_0 norgatedata <pip> 0.6.0 5.5.0 2.6.8 1.11.3 1.11.3 0.7.0 1.0.2s pandas pandas­datareader 0.22.0 0.6.0 pandoc pandocfilters 1.19.2.1 1.4.2 0.2.1 0.5.0 patsy pbr 4.0.4 pickleshare pip 0.7.4 19.2.3 pip 9.0.1 2.0.11 pycodestyle pycparser pyflakes py35_0 py35_0 4.4.0 3.0.2 numpydoc openssl plotly inmediata_toolkit psutil py35_0 py35_0 1.4.0 numpy­base parso py35_0 py35_0 py35h9ef55f4_0 py35h53ece5f_10 py35h8128ebf_10 py35_0 he774522_0 py35h6538335_0 py35_0 hb2460c7_1 py35h978f723_1 py35_0 py35_0 py35_0 1.0.15 5.2.2 2.3.1 2.19 1.6.0 py35h2f9f535_0 <pip> py35_1 py35_0 py35h89c7cb4_0 py35_0 py35_0 py35_0 py35_0 <pip> pyfolio pygments 0.9.0 2.2.0 py35h24c0941_0 pylint pyopenssl 1.7.2 18.0.0 py35_0 py35_0 pyparsing pyqt 2.2.0 5.9.2 pysocks 1.6.8 pytables python 3.4.4 3.5.5 python­dateutil pytz 2.7.3 2018.4 py35_0 py35h6538335_2 py35_0 py35he6f6034_0 h0c2934d_2 py35_0 py35_0 pywinpty pyyaml 0.5.4 3.12 py35_0 py35h4bf9689_1 pyzmq 17.0.0 py35hfa6e2cd_1 Machine Translated by Google 5.9.6 vc14h1e9a669_2 0.4.4 py35_0 4.3.1 qtconsole py35_0 1.3.1 qtpy py35_0 2.14.2 solicitudes py35_0 1.4.3 solicitudes­ py35_0 0.3.1 archivo py35_0 0.9.4 py35_1 solicitudes­ftp 0.19.1 py35h2037775_0 1.1.0 cuerda py35hc28095f_0 scikit­learn 0.8.1 py35hc73483e_0 scipy seaborn 1.5.0 py35_0 36.4.0 send2trash py35_1 0.8.1 herramientas de py35_2 3.4.0.3 configuración py35_0 4.19.8 py35h6538335_1000 conda­forja simplegeneric singledispatch 1.11.0sip six py35hc1da2df_1 1.1.7 h777316e_3 snappy snowballstemmer 1.2.1 py35_0 contenedores 1.4.4 py35_0 Quantópico 1.6.3 py35_0 1.0 ordenados py35_0 esfinge sphinxcontrib sphinxcontrib­ py35_0 websupport 1.0.1 spyder py35_0 1.2.8 3.2.3 py35hfa6e2cd_0 3.28.0 hfa6e2cd_0 conda­forja sqlalchemy 0.9.0 py35h452e1ab_0 sqlite 0.8.1 py35_1 statsmodels 6.2.0 <pip> 0.3.1 terminado py35h06cf69e_0 8.6.7 hcb92d03_3 0.9.0 py35_0 5.0.2 py35_0 qt qtawesome testfixtures testpath tk toolz py35h28b3542_0 tornado tqdm 4.26.0 trading­ py35_0 cuantotópico calendars 1.0.1 tracelets py35h09b975b_0 4.3.2 urllib3 1.23 14.1 py35_0 vc h0510ff6_3 3 15.5.2 vs2015_runtime wcwidth 0.1.7 py35h6e80d8a_0 0.5.1 webencodings py35h5d527fb_1 rueda 0.29.0 py35_0 3.2.1 widgetsnbextension py35_0 1.0.1 win_inet_pton py35_1 0.5 win_unicode_console py35h56988b5_0 wincertstore 0.2 winpty 0.4.3 py35_0 4 1.10.11 wrapt yaml zeromq zipline zlib py35_0 0.1.7 hc54c509_2 4.2.5 hc6251cf_0 1.3.0 np111py35_0 cuantopico 1.2.11 h8395fce_2 Machine Translated by Google Índice Asignación, 20 Anaconda, 39 años. Paquete Anaconda, 54 análisis, 106 Análisis de los resultados del backtest, 117 Carry anualizado, 309 Rentabilidad anualizada, 137 rango, 185 modelo de asignación de activos, 161 Clase de activo, 18 Rango verdadero promedio, 187 Pruebas retrospectivas, 84 Plataforma de backtesting, 84 Retroceso, 301 Sobornar al autor, 88 Paquetes, 372 CAGR, 137 Código, bloque de, 44 Coeficiente de determinación, 179. Combinando modelos, 320 Instalación de línea de comando, 94 Tasa de crecimiento anual compuesta, 137 Lógica condicional, 50 contango, 301 Tamaño del contrato, 215 Copiar al portapapeles, 80 Correlación, 71 Machine Translated by Google Creando un nuevo entorno, 93 Datos CSI, 85 Producto acumulado, 67 Exposición cambiaria, 219 Análisis personalizado, 130 Datos, 17, 371 Datos utilizados, 116 Bases de datos, 395 Marco de datos, 59 Diccionario, 47 dividendos, 170 Documentación, 61 Gráfico de rendimiento dinámico, 270 Empírico, 330 Reglas de entrada, 20 Ambientes, 92 Universo de Inversión en Renta Variable, 170 ETF, 143 Modelos de negociación de ETF, 159 Excel, 60 Fondos cotizados en bolsa, 143 Notas negociadas en bolsa, 145 Reglas de salida, 20 Pendiente de regresión exponencial, 179 Riesgo financiero. Ver Riesgo primer algoritmo comercial, 102 Definición de función, 72 Paquete de futuros, 382 Continuaciones de futuros, 227 apalancamiento_bruto, 140 Machine Translated by Google manejar_datos, 104 Cabeza, 80 historia, 104 si declaración, 105 Error de sangría, 52 Sangría, 45 Componentes del índice, 171 Rastreadores de índice, 143 Ingerir, 98 Instalación de bibliotecas de Python, 54 Instalación de Python, 38 Instalación de tirolesa, 91 Lenguaje interpretado, 36 Universo de Inversión, 19 iterrows(), 113 Júpiter, 46 Cuaderno Jupyter, 40 Apalancamiento, 220 Pendiente de regresión lineal, 180 Lista, 44 posiciones, 112 Bucle, 44 Margen, 217 Marca al mercado, 25 MatPlotLib , 56 Reducción máxima, 138 Impulso, 175 Puntuación de impulso, 179 Gestión del dinero. Ver Falacias de riesgo Monos, 337 Machine Translated by Google Media móvil, 58 Multiplicador, 217 MySQL, 398 nb_conda, 100 Datos de Norgate, 85 Numerosos, 65 order_target, 105 order_target_percent, 103 pandas , 57 Pct_Change, 67 Trama, 68 Trazado, 76 Backtest de cartera, 109 valor_cartera, 140 Pullback, 288 PyFolio, 117 Pyfolio Tear Sheets, 124 Pyramiding. Consulte Paquete Quandl de falacias de riesgo, 98 QuantCon, 88 Quantopian, 88 QuantQuote, 85 range(), 152 read_sql_query, 412 Lectura de archivos CSV, 58 reequilibrio, 21 Fuerza relativa, 80 RightEdge, 85 Riesgo, 23 Falacias de riesgo, 26 Machine Translated by Google Riesgo por operación . Ver falacias de riesgo Rodando, 231 Correlación móvil, 75 Media rodante, 191 Media rodante, 58 Ventana de tiempo móvil, 138 run_algorithm, 103 scipy, 185 Base de datos de valores, 398 turno, 67 ETF cortos, 150 Rebanar, 74 Instantánea, 132 Declaración de inserción SQL, 405 Desviación estándar, 187 Estandarización, 213 Concatenación de cadenas, 49 Soporte, 6 símbolo, 103 cola, 80 Estructura de términos, 299 to_clipboard, 329 to_csv, 328 Seguimiento de tendencias, 234 Asignación basada en volatilidad, 186 Tirolesa, 88