Uploaded by Elias Knaus

Trading Evolved Anyone can Build Killer Trading Strategies in Python parte 2 (1)

advertisement
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
Download