Artículo cedido por Óscar Cagigas de Onda4. Si te gusta este artículo, te recomiendo que visites su web, en la que encontrarás información de gran calidad para iniciarte en los sistemas de trading.
En el último informe vimos la forma en la que podemos contrastar medias de una forma estadísticamente correcta. El objetivo es saber si las mejoras que le hacemos a un sistema de trading están avaladas por un cierto poder predictivo de las reglas que aplicamos, o por el contrario simple-mente sale bien por casualidad.
Esto se probó sobre un sistema para opciones del SPX, un Iron Condor 0DTE que tras añadir un filtro basado en el gap de apertura mejoraba su ganancia promedio, pasando de 55 dólares por operación hasta los $89.
Al terminar de escribir el informe se me ocurrió que quizás podría utilizar Cadenas de Markov para intentar mejorar las estadísticas de este sistema, que de partida ya es suficientemente atractivo.
Escribí unos artículos al respecto en 2021 que tuvieron mucha acogida, de hecho, la primera parte fue publicado en la revista Traders’. Por si no los conoce, o necesita refrescar la memoria aquí tiene los enlaces a los artículos mencionados:
Cadenas de Markov Como Herramienta de Diseño de Sistemas
La metodología es prácticamente la misma que en el segundo artículo; sin embargo, he tenido que hacer alguna consideración adicional porque en este caso de las opciones la salida es el resultado de ganancia o pérdida de un Iron Condor 0DTE, y por tanto esta variable no participa en la entrada, como era el caso cuando utilizábamos retornos.
Resulta que, en las cadenas de Markov el estado actual de un proceso solamente depende de valores anteriores del mismo proceso, así que la metodología que voy a aplicar no es exactamente una cadena de Markov al uso, sino que es el mismo modo de encontrar patrones en el precio que mostré en el segundo artículo.
Adicionalmente, he tenido que filtrar algunos datos pues solamente tenemos vencimientos de opciones diarias para los martes y jueves desde el año pasado (vea debajo las fechas), así que los datos que arroja Amibroker pueden indicar fechas en las que no se pudo hacer un Iron Condor 0DTE. Esto lo he solucionado en Excel haciendo cuadrar las fechas de las variables de entrada y las fechas en las que OptionOmega da resultados, la salida.
Por lo anterior, he desarrollado una metodología propia que me parece que tiene sentido y donde entran en juego variables intermedias, pues ya sabe que con opciones hay volatilidad, strikes, paso del tiempo, y todo tipo de variables que influyen en el resultado final. La metodología es la siguiente:
- Hago una lista de las variables que yo creo que pueden tener influencia sobre la rentabilidad de un Iron Condor 0DTE
- Hago percentiles de los valores que toman estas variables, de forma que todo se simplifica a unos estados, en este caso tendremos tres estados: 0,1,2
- Con lo anterior, genero códigos que indiquen el estado de las variables. Siempre de menor a mayor. Así el código 210 significaría que la primera variable está en su tercio superior, la segunda variable está en un estado intermedio y la tercera variable está en su percentil inferior
- Pruebo un Iron Condor sin ningún tipo de filtro en la entrada, y registro las fechas en las que opera. Con esto me aseguro de que en estas fechas se puede abrir la posición.
- Asocio la rentabilidad del Iron Condor con los estados de las variables de entrada que influyen (o asumo que influyen) en los resultados
- Busco aquellos estados de entrada que generen más ganancias que pérdidas.
- Escribo las reglas en Amibroker para que me genere las fechas en las que se operaría con esos estados del paso anterior.
- Hago un Backtest en OptionOmega abriendo la posición solamente en las fechas obtenidas en el paso anterior.
- Evalúo resultados y los comparo con no filtrar
En realidad, es más sencillo de lo que parece. Trabajar sobre el ejemplo hará que se entienda bien.
Lo primero es la lista de variables. En mi opinión, esto influye en el resultado de un Iron Condor 0DTE:
- El gap con el que abre hoy el SPX
- El valor del VIX al cierre de ayer
- El gap con el que abre hoy el VIX
Si escogí las variables anteriores es porque todas ellas afectan a la prima de las opciones, y la prima de las opciones tiene influencia en el resultado final.
De estas tres variables he decidido hacer que salgan 3 estados correspondientes a lo que delimitan los percentiles del 33.33 y 66.66%; es decir: el estado 0 es por debajo del percentil del 33.33%. El estado 1 es entre el 33.33% y el 66.66%. Y el estado 2 es para cualquier lectura mayor del percentil del 66.66%. Así que tendremos 33 = 27 combinaciones para la entrada.
Para que quede todo claro le muestro la forma en la que está programado el primer caso (gap de apertura del SPX) y luego lo vemos sobre un gráfico.
Con la función Percentile() todo se simplifica. Notar que en el artículo 2 lo hacía en Excel, pero ahora hago los percentiles directamente en Amibroker, que es más rápido.
Este código genera tres variables: Gap0, Gap1 y Gap2, que toman el valor de 1 si el gap de la barra actual está en la categoría correspondiente. Por tanto, un fuerte hueco bajista de apertura del SPX pondrá la variable gap0 a 1. Un fuerte hueco alcista pondrá la variable Gap2 a 1, y un hueco intermedio pondrá la variable Gap1 a 1.
También he creado una variable para el GapStatus (GapSt) que tiene un número de 0 a 2 con el que reflejar el estado y poder construir un código. Los tres dígitos del código son: SPX + VIX + VixGap. Por ejemplo:
CÓDIGO: 201
Significa que en la apertura hubo un fuerte hueco alcista, el VIX estaba en un valor bajo y el Gap de apertura del VIX era un valor intermedio. Siempre de menor (0) a mayor (2).
El código Amibroker de la página anterior que genera todo lo relacionado con el Gap de apertura del SPX lo repito con ligeras modificaciones para crear los percentiles y códigos del VIX y Gap del VIX. El código completo lo anexo al final de este informe. Cuando hago Explore sale lo siguiente:
Que nos muestra que, por ejemplo, el 17 de enero el SPX abrió con un hueco intermedio (0.19 puntos), con un valor bajo del VIX (18.35) y también con un hueco alcista del VIX de 1.54 puntos, generando un código de 102.
En la imagen de debajo pongo un cursor vertical en la misma fecha del 17 de enero. Tenga en cuenta que el nivel del VIX que anoto en azul para ese día (18.35) es el correspondiente al del día antes, ya que en el momento de la apertura USA no se tienen más datos que el nivel de la apertura y el cierre de ayer del VIX.
Debajo, donde pone “Niveles” en azul, muestro los niveles de Gap, Vix y VixGap que separan los distintos percentiles. El gap del SPX se calcula con una ventana de 100 datos y lo referente al VIX con 20 datos (un mes aproximada-mente), así que pueden salir niveles muy próximos unos de otros. Por ejemplo, el percentil del 33% de niveles del VIX es de 21.23, y el del 66% es de 22.
Lo siguiente es obtener las fechas reales en las que se puede operar un Iron Condor 0DTE. Esto se hace con un backtest de la estrategia a la que no se le añade ningún filtro de entrada. Posteriormente se exportan los resultados en un fichero separado por comas CSV.
Debajo muestro en marrón los datos que saca Amibroker; y en azul vemos las fechas y resultado de ganancias y pérdidas de un Iron Condor sin filtrar que se obtiene de OptionOmega. Utilizo la función BUSCARV() de Excel para asociar cada variable de entrada con el beneficio (P/L) de ese día. Por ejemplo, el 17 de enero el código es 102 y el resultado de un Iron Condor es una ganancia de 757 dólares.
La salida también la pongo en forma de percentil, pero en este caso solo utilizo dos estados, la mitad inferior (<50%) y la mitad superior (>=50%).
Supongo que ahora se ve claro que lo único que hay que hacer es encontrar los códigos que generan la salida de 1 (mitad superior). Esto se ve bien si se pinta como una matriz de códigos.
Debajo muestro la matriz de códigos. Utilizo la función CONTAR.SI.CONJUNTO para que Excel cuente las ocurrencias de cada código. Por ejemplo, el código 000 tiene 5 resultados en la mitad baja del rango y 6 en la mitad alta.
En realidad, lo que me interesa es encontrar códigos que tengan muchas ocurrencias en el 1 y pocas en el 0, porque así sé que es una regla ganadora. Es como decir: muchas ganancias y pocas pérdidas. Así que en una columna aparte (G) calculo la diferencia. Resulta que me interesan los siguientes 9 códigos: 001, 002, 011, 022, 101, 102, 120 , 121 y 210. Los he marcado en verde.
Los códigos de color verde son aquellos que producen una diferencia favorable entre los resultados buenos (1 o mitad superior) y los malos (0 o mitad inferior). Escogí el 30% superior mediante un filtro condicional en Excel que colorea en rojo las casillas. De nuevo, es como decir: más ganancias que pérdidas.
Estos códigos los paso a Amibroker diciendo que compre en los días que esos códigos valgan 1:
Y es que así puedo filtrar por Filter = Buy y con eso por fin tengo el listado de operaciones para llevar a OptionOmega. Este listado son las operaciones que generan las 9 reglas anteriores. En la imagen de la página 6 Vd. puede ver las operaciones que generan estos códigos, ya que las entradas tienen un círculo verde.
En la imagen de la página anterior (matriz de códigos) he pasado una prueba estadística (chi cuadrado) para ver si la cuenta de resultados por código son anomalías esta-dísticas, o por el contrario son puro azar. Parece haber una probabilidad del 99.85% de que estas ineficiencias del mercado no son azar. Más detalles en el Anexo1.
Y ahora cargo el fichero con las fechas en las que hay que operar la estrategia en OptionOmega. El resultado es el que vemos debajo. Me llama la atención que el promedio de ganancia por operación ha subido a $135; y el ratio MAR ahora tiene una lectura muy elevada, de 8.6 (antes 6.6). Estos resultados son muy superiores a todas las pruebas que hice previamente sobre la estrategia cambiando los parámetros que se pueden cambiar (hueco de apertura, nivel absoluto del VIX, etc).
Esta metodología parece arrojar muy buenos resultados, aunque habrá que estudiar con detenimiento si hay algún error, está muy sobreoptimizado o tiene cualquier otro vicio oculto. En datos nuevos parece ir bien, pero no hay muchos datos disponibles por el tema de los nuevos vencimientos solamente desde el 2022.
Finalmente, he realizado una prueba más, que consiste en repetir el procedimiento anterior, pero quedarme solamente con los mejores códigos, que son: 001, 002, 101 y 102, y que tienen las mayores diferencias, al menos de 10 ocurrencias, tal y como vemos a continuación:
En este caso, resultan menos operaciones (ahora 188) pero mejores estadísticas, con un 61% de aciertos, ratio MAR de 14.5 y ganancia promedio por operación de $167.
ANEXO 1. PRUEBA CHI CUADRADO
La prueba Chi Cuadrado es muy útil para saber si unos datos difieren de su valor esperado. Para calcularlo, añado una matriz a la derecha que contiene los resultados esperados (el promedio) para ese código. Por ejemplo, en la primera fila tenemos 7 + 17 = 24 ocurrencias, así que esperaríamos 12 ocurrencias en cada columna si todo fuera azar.
La fórmula para calcular el estadístico Chi-sq es la siguiente: suma[ (DatosMx – MediaMx)^2/MediaMx] donde la terminación Mx se refiere a matrices. En la primera matriz de mejores códigos sale un resultado de chi-sq = 19.90.
La probabilidad asociada se obtiene mediante la fórmula =DISTR.CHICUAD.CD(ChiValue, (N1-1)*(N2-1)) donde N1 y N2 son el número de filas y columnas, 4 y 2, respectivamente.
Los mejores y los peores códigos tienen una probabilidad muy elevada (99.98%) de que los resultados sean distintos del valor promedio o esperado.
Por otro lado, la matriz “mediocres” tiene una probabilidad de solamente el 3.24% de que la muestra sea distinta del azar. Eso quiere decir que, aunque los códigos mostrados en gris dieran ganancias no serían fiables para ser considerados.
ANEXO 2. CÓDIGO AMIBROKER
//################################################
// SISTEMA MARKOV DE 3 VARIABLES
// ESTE CÓDIGO ES PARA HACER UN EXPLORE
// Y GENERAR LOS DATOS QUE PROCESAMOS EN EXCEL
// OSCAR G. CAGIGAS
// 23 ENERO 2023
//################################################
SetBacktestMode(backtestRegular);
//SETTINGS
Short = Cover = 0; //SOLO LARGOS
ShortPrice = CoverPrice = C;
SetTradeDelays(0,0,0,0);
//VARIOS//
SetOption("InitialEquity", 100000);
PositionSize = MarginDeposit = 1;
PointValue = 100; //FUTURO MINI
// * DEFINICIONES. SIEMPRE DE MENOR (0) A MAYOR (2) ****
//PRIMERA VARIABLE -> GAP DE APERTURA DEL SPX CON TRES ESTADOS: 0,1,2
OpenGap = Open - Ref(C,-1);
gap0pct = Percentile( OpenGap, 100, 33.33);
gap2pct = Percentile( OpenGap, 100, 66.33);
gap0 = OpenGap < gap0pct; gap1 = OpenGap >= gap0pct AND OpenGap < gap2pct; gap2 = OpenGap > gap2pct;
//Gap Status
GapSt = IIf( gap0, 0, IIf(gap1, 1, 2) );
//SEGUNDA VARIABLE -> VALOR DEL VIX CON TRES ESTADOS: 0,1,2
VIX = Ref(Foreign("$VIX","C"),-1); //valor del cierre de ayer
//VIX DE UN MES (20 BARRAS) CON TRES ESTADOS
Vix0pct = Percentile( VIX, 20, 33.33);
Vix2pct = Percentile( VIX, 20, 66.66);
Vix0 = VIX < Vix0pct; Vix1 = VIX >= Vix0pct AND VIX < Vix2pct; Vix2 = VIX > Vix2pct;
//VIX STATUS
VixSt = IIf( Vix0, 0, IIf(Vix1, 1, 2 ) );
//TERCERA VARIABLE -> GAP DE APERTURA DEL VIX CON TRES ESTADOS: 0,1,2
VIXo = Foreign("$VIX","O"); //valor de apertura de hoy
VixGap = VIXo-VIX;
//VIX DE UN MES (20 BARRAS) CON TRES ESTADOS
VixGap0pct = Percentile( VixGap, 20, 33.33);
VixGap2pct = Percentile( VixGap, 20, 66.66);
VixGap0 = VixGap < VixGap0pct; VixGap1 = VixGap >= VixGap0pct AND VixGap < VixGap2pct; VixGap2 = VixGap > VixGap2pct;
//VIX STATUS
VixGapSt = IIf( VixGap0, 0, IIf(VixGap1, 1, 2 ) );
//-----------------------
//SETUP PARA LA ENTRADA
cond1 = gap0 AND Vix0 AND VixGap1; //001
cond2 = gap0 AND Vix0 AND VixGap2; //002
cond3 = gap0 AND Vix1 AND VixGap1; //011
cond4 = gap0 AND Vix2 AND VixGap2; //022
cond5 = gap1 AND Vix0 AND VixGap1; //101
cond6 = gap1 AND Vix0 AND VixGap2; //102
cond7 = gap1 AND Vix2 AND VixGap0; //120
cond8 = gap1 AND Vix2 AND VixGap1; //121
cond9 = gap2 AND Vix1 AND VixGap0; //210
//ENTRADA
Buy = Cond1 OR cond2 OR cond3
OR cond4 OR cond5 OR cond6 OR cond7 OR cond8 OR cond9;
BuyPrice = Open;
//SALIDA
Sell = Buy;
SellPrice = Close;
//--------------------------------------------------
//ELIMINAMOS SEÑALES EXTRA//
Buy = ExRem( Buy, Sell );
Sell = ExRem( Sell, Buy );
Short = ExRem( Short, Cover );
Cover = ExRem( Cover, Short );
//LARGO/CORTO//
largo=Flip(Buy,Sell); Corto=Flip(Short,Cover);
//-----------cosmetics--------------------------
//CHART//
SetChartOptions(0,chartShowDates);
Title = StrFormat("{{NAME}} - {{INTERVAL}} {{DATE}} Open %g, Hi %g, Lo %g, Close %g (%.1f%%) {{VALUES}}", O, H, L, C, SelectedValue( ROC( C, 1 ) ) )
+ "n"+ FullName()+"n"+ EncodeColor(colorBlue)+"SISTEMA MARKOV"
+ "n"+ "OpenGap: " + NumToStr(OpenGap,1.2) + "n" + "Vix: " + NumToStr(VIX,1.2) + + "n"+ "VixGap: " + NumTo-Str(VixGap,1.2)
+ "n" + "Niveles Gap: " + NumToStr(gap0pct,1.2) +", " + NumToStr(gap2pct,1.2)
+ "n" + "Niveles Vix: " + NumToStr(Vix0pct,1.2) +", " + NumToStr(Vix2pct,1.2)
+ "n" + "Niveles VixGap: " + NumToStr(VixGap0pct,1.2) +", " + NumToStr(VixGap2pct,1.2)
+ "n"+ EncodeColor(colorRed) + GapSt + VixSt + VixGapSt + "n"+ WriteIf(Cond1, "Cond1","");
//CIRCULOS EN PANTALLA//
Plot(C,"Last ",colorgrey50,styleBar|styleThick);
PlotShapes(IIf(Buy,shapeCircle,shapeNone),colorGreen,0,BuyPrice,0);
//Filter = Buy;
Filter = 1;
AddColumn( OpenGap, "OpenGap", 1.2);
AddColumn( VIX, "VIX", 1.2);
AddColumn( VixGap, "VixGap", 1.2);
AddColumn( GapSt, "GapSt", 1);
AddColumn( VixSt, "VixSt", 1);
AddColumn( VixGapSt, "VixGapSt", 1);