import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from pandas.tools.plotting import table
from datetime import datetime
%matplotlib inline
#plt.style.use('ggplot')
year = datetime.now().year # Presente agno
url_data = 'https://rawgit.com/collabmarket/data_afp/master/data/VC-MODELO.csv'
afp_name = url_data.split('-')[-1].split('.')[0] # Nombre de la afp
karg_csv = dict(delimiter=';', decimal=',', index_col=0, parse_dates=True)
afp = pd.read_csv(url_data, **karg_csv) # Crea DataFrame con los datos
afp.dropna(inplace=True) # Elimina filas con datos faltantes (elimina C antes multifondos)
# Grafico solo fondos A y E
afp.loc[:,['A','E']].plot(title='Multifondos %s'%(afp_name))
<matplotlib.axes._subplots.AxesSubplot at 0x6a73c1f0>
El modelo se sustenta en la medida que existan períodos en que el valor cuota de A crece más que E, y después viene un período de corrección, en que el valor cuota de A es menor que E. Existen varios supuestos cuya demostración escapa del alcance del modelo, pero se debe tener en cuenta para entender como funciona el mercado financiero
Incialmente el valor cuota de A y E no es necesariamente el mismo, pero la diferencia entre los dos es señal del valor relativo de uno con respecto al otro.
# Delta es Valor relativo (diferencia o resta) entre valores cuota de A y E
afp['delta'] = afp.A - afp.E
print "Describe delta %s:\n"%(afp_name)
print afp.delta.describe()
Describe delta MODELO: count 2774.000000 mean -598.525829 std 1822.776027 min -4161.220000 25% -1892.615000 50% -1138.270000 75% 600.532500 max 5254.820000 Name: delta, dtype: float64
Incialmente el valor cuota de A y E no es necesariamente el mismo, por lo que el nivel de indiferencia no es cero, por lo cual se eligen dos niveles de indiferencia uno de mediano plazo y uno de largo plazo
# Elegimos un nivel de indiferencia de largo plazo
# No se usa en los calculos solo para visualizacion, dato ex-post
# Media historica de delta
zero = afp.delta.mean()
print "Zero media %s: %s"%(afp_name, zero)
Zero media MODELO: -598.525829128
# Parametros: ['median',365] (elejimos mediana movil 365 dias)
afp['zero'] = afp.delta.rolling(window=365,center=False).median()
# Supuestos:
# Zero representa valor relativo fondo A comparable con E
# fondo E refugio A es como una pseudo Inverse exchange-traded cuando hay alta volatilidad
# Cambios entre renta variable (fondo A) y renta fija (fondo E)
# Estrategia objetivo "comprar barato y vender caro"
# Otro indice valore relativo entre A y E
# No se usa en los calculos solo informativo
# rho = (A-E)/E
afp['rho'] = afp.delta / afp.E
print "Rho media %s: %s"%(afp_name, afp.rho.mean())
Rho media MODELO: -0.0211375225474
# Grafico de tendencia Delta diferencia entre A y E con nivel de indiferencia Zero
afp[['zero','delta']].plot(title='Diferencia A-E %s'%(afp_name))
plt.axhline(y=zero, color='r', linestyle='--')
<matplotlib.lines.Line2D at 0x69418770>
Se observa que hay una tendencia en que 'delta' converge a valor cercano a 0, cada vez es menos atractiva la estrategia de cambio de fondo entre A y E, pues variabilidad de 'delta' tienden a ser mas frecuentes y de menor aplitud.
Estrategia "Evitar perdidas" (ejemplo: Felices y Forrados) Es muy dificil pues caidas son mas abruptas que subidas, consiste en mantenerse en A y refujiar en E implica aplicar "stop lost" algo muy dificil de implementar por la lentitud de los plazos cambio de fondo de las AFP.
Dado que subida es mas lenta que caida mas acorde con lentitud de cambios de fondo, consiste en mantenerse en E y despues de una gran caida entrar a A para aprovechar recuperacion, implica aplicar un criterio arbitrario de "entrada" que define gran caida e inicio recuperacion.
Luego del periodo de recuperacion se sale de A mientras la probabilidad de recuperacion sea alta, implica aplicar "stop gain" se debe elegir un criterio arbitrario de "salida".
Warren Buffett dice “cuando todo el mundo es ambicioso, se precavido. Cuando todo el mundo es precavido, se ambicioso”.
Dado que variaciones de delta tienden a ser mas frecuentes y de menor aplitud, esta estrategia tambien es cada vez mas dificil de implementar por la lentitud de cambios de fondo de las AFP.
Entre el 2004 y el 2012 esta estrategia pudo aprovecharse ya que variacion tendencia de delta, fue en ciclos grandes de gran amplitud, a partir del 2012 variacion tendencia delta es de ciclos cortos y baja amplitud, por lo que solo la estrategia de aprovechar recuperacion es atractiva, pero tambien tiende a ser menos rentable.
En teoria se podria hacer ambas estrategias evitar caidas del A y aprovechar recuperaciones del A, si alguien pudiera "predecir" cambios de tendencia en los mercados en el mediano plazo (semanas, meses) seria factible, pero nadie a demostrato un metodo consistente para lograr eso, el "market timing" funciona entre menor sea el plazo hasta llegar a casos extremos como el High-frequency trading (HFT).
Compara delta con respecto a zero:
# Categoria (etiqueta) 'valorA'
afp['valorA'] = pd.np.where(afp['delta'] < afp['zero'], 'bajo', 'alto')
afp['bajo'] = afp.delta[afp.delta<afp.zero]
afp['alto'] = afp.delta[afp.delta>afp.zero]
Después de una caida de A la probabilidad de ganacia E->A es alta (recuperacion), pues se supone que renta variable en promedio genera mas valor que renta fija, pero para eso es necesario detectar minimo local de valor de A.
Warning: "Probabilidad de ganancia E->A" != 1-"Probabilidad de perdida de A"
Antes de grandes caidas, hay mucha volatilidad con recuperaciones "aparentes", que preceden grandes caidas, por eso es necesario ademas tener datos de riesgo sistemico. Casos:
# Cuenta dias en que valor de A es alto y bajo con respecto a E
afp.groupby('valorA').delta.count().plot(kind='bar', title='Valor de A relativo a E en %s'%(afp_name))
<matplotlib.axes._subplots.AxesSubplot at 0x693bd310>
Del gráfico se puede observar que es más frecuente que valor fondo A sea mayor con respecto a el fondo E, entonces es posible suponer que las recuperaciones demoran más tiempo que las caídas. Se tiene más tiempo para aprovechar eventos puntuales de recuperacion valores minimos de A con respecto a E.
afp.loc[:,['bajo','alto','zero']].plot(title='Delta clasificado por \n Valor de A relativo a E en %s'%(afp_name))
plt.axhline(y=zero, color='r', linestyle=':')
<matplotlib.lines.Line2D at 0x693f1e30>
# Zoom desde dos agnos al presente
init = str(year - 2)
titulo= 'Delta clasificado por \n Valor de A relativo a E en %s desde %s'%(afp_name,init)
afp.loc[init:,['bajo','alto','zero']].plot(title=titulo)
plt.axhline(y=zero, color='r', linestyle=':')
<matplotlib.lines.Line2D at 0x691e0230>
"Aprovechar recuperacion" y "Evitar perdidas" con stop gain
## Estrategia "Aprovechar recuperacion" y "Evitar perdidas" con stop gain
# Criterios de Entrada a renta variable
# Parametros: ['quantile',0.1,365] (elejimos cuantil 10% ventana 365 dias)
afp['entra'] = afp.delta.rolling(window=365,center=False).quantile(quantile=0.05)
# Criterios de Salida a renta fija
# Parametros: ['quantile',0.9,365] (elejimos cuantil 90% ventana 365 dias)
afp['sale'] = afp.delta.rolling(window=365,center=False).quantile(quantile=0.95)
fig, ax = plt.subplots(figsize=(10,10))
afp.loc[:,['entra','sale']].plot(title='AFP %s'%(afp_name), style=['--r','--b'], ax=ax)
afp.loc[:,['bajo','alto','zero']].plot(title='AFP %s'%(afp_name), style=['b','g','r'], ax=ax)
ax.axhline(y=zero, color='r', linestyle=':')
ax.set_frame_on(False) # no visible frame
# Segnal concavidad delta corto plazo
# Parametros segunda derivada delta de corto plazo (concavidad)
afp['rate'] = afp.delta.pct_change(30).pct_change(15)
# Calcula log(positivo)
afp['subida'] = afp.rate.apply(pd.np.log)
# Calcula log(-negativo)
afp['caida'] = afp.rate.multiply(-1.).apply(pd.np.log)
# Criterio concavidad delta de corto plazo
init = str(2003)
fig, ax = plt.subplots(ncols=2, figsize=(18,6))
afp.loc[init:, ['A','caida']].plot(secondary_y=['caida'], ax=ax[0], color='br', style='.-')
afp.loc[init:, ['A','subida']].plot(secondary_y=['subida'], ax=ax[1], color='bg', style='.-')
<matplotlib.axes._subplots.AxesSubplot at 0x68ddddf0>
# Indicador de valor relativo entre A y E, con rho = (A-E)/E
init = str(2003)
fig, ax = plt.subplots(ncols=2, figsize=(18,6))
ax1 = afp.loc[init:, ['A','rho']].plot(secondary_y=['A'], ax=ax[0], color='br', style='.-')
afp.loc[init:, ['rho']].plot(kind='kde', ax=ax[1], color='r')
ax[0].axhline(y=afp.rho.mean(), color='r', linestyle=':')
<matplotlib.lines.Line2D at 0x68956af0>
Crea la categoría 'estrategia' donde se van a clasificar los días como 'E', 'A', 'E->A', 'A->E' y 'S/I' (Sin información)
# Caso base se marca como desconocido, Sin Informacion (S/I)
afp['estrategia'] = 'S/I'
# Segnales de largo plazo
afp.loc[afp.delta < afp.zero,'estrategia'] = 'E'
afp.loc[afp.delta > afp.zero,'estrategia'] = 'A'
# Grafico estrategia base
gb = afp[['A','B','C','E','estrategia']].groupby('estrategia')
fig, ax = plt.subplots()
gb.A.plot(style='.', title='Estrategia vista sobre Fondo A en %s'%(afp_name),ax=ax)
plt.legend(loc=2)
<matplotlib.legend.Legend at 0x69061f30>
# Segnales de corto plazo
# Fuerte subida cuando el 'valorA' es barato con respecto a E
# (afp.subida > 0. ) convexa
afp.loc[(afp.delta > afp.entra) & (afp.delta < afp.zero) & (afp.subida > 0),'estrategia'] = 'E->A'
# Fuerte caida cuando 'valorA' es muy caro con respeto a E
# (afp.caida > 0.) concava
afp.loc[(afp.delta > afp.sale) & (afp.delta > afp.zero) & (afp.caida > 0),'estrategia'] = 'A->E'
# Clasifica de acuerdo a 'estrategia' y cuenta numeros de dias en cada
afp.groupby('estrategia').count()['zero'].plot(kind='bar', title='Tiempo estado estrategia en %s'%(afp_name))
<matplotlib.axes._subplots.AxesSubplot at 0x691e05d0>
El algoritmo NO entrega señales 100% claras, por lo que NO es recomendable su uso para gestionanr fondos AFP, sin una correcta interpretación de las tendencias y datos de riesgo sistémico (Por ejemplo en Oct 2008 y Agosto 2016 DJI fin de ciclo alcista de largo plazo, que hace explotar burbujas)
# Agrupar por la categoria 'estrategia'
gb = afp.loc[:, ['A','B','C','E','estrategia']].groupby('estrategia')
# Grafica 'estrategia' sobre A
fig, ax = plt.subplots()
gb.A.plot(style='.', figsize=(10,10), title='Estrategia historica sobre Fondo A en %s'%(afp_name),ax=ax)
plt.legend(loc=2)
ax.set_frame_on(False) # no visible frame
fig.savefig('result/A-E_%s-large.png'%(afp_name))
# Grafica estrategia sobre fondo='A' desde init al presente
init = str(year - 2)
fondo = 'A'
fig, ax = plt.subplots()
for k, group in gb:
df = group.loc[init:, fondo]
if len(df) != 0: # Evita error Empty 'DataFrame'
df.plot(style='.', figsize=(10, 8), title='Estrategia 2 agnos sobre Fondo %s %s'%(fondo, afp_name), ax=ax, label=k)
ax.legend(loc=2)
ax.set_frame_on(False) # no visible frame
# Only show ticks on the left and bottom spines
ax.yaxis.set_ticks_position('left')
ax.xaxis.set_ticks_position('bottom')
fig.savefig('result/A-E_%s.png'%(afp_name))
El algoritmo no entrega señales 100% deterministas, por lo que no es recomendable aún su uso para gestionanr fondos AFP. Requiere hacer un método estocástico para "filtrar" las señales de cambio de fondo de las caídas pequeñas, no así con las caídas grandes.
# Tabla ultimos 15 dias de estrategia
df = afp.iloc[-15:][['A','E','rho','estrategia']].round(3)
df.index = [item.strftime('%Y-%m-%d') for item in df.index]
fig, ax = plt.subplots(figsize=(9, 8)) # set size frame
ax.xaxis.set_visible(False) # hide the x axis
ax.yaxis.set_visible(False) # hide the y axis
ax.set_frame_on(False) # no visible frame
# loc='best' raise ValueError("posx and posy should be finite values")
tabla = table(ax, df, loc='center', colWidths=[0.1]*len(df.columns))
tabla.scale(1.8, 1.8)
plt.title('Ultimos 15 dias de estrategia %s'%(afp_name))
plt.savefig('result/A-E_%s_table.png'%(afp_name))
# Tabla ultimos 45 dias de estrategia
afp.iloc[-60:][['A','E','rho','estrategia']].round(3)
A | E | rho | estrategia | |
---|---|---|---|---|
Fecha | ||||
2018-02-05 | 44598.04 | 40196.26 | 0.110 | A |
2018-02-06 | 43918.24 | 40149.59 | 0.094 | A |
2018-02-07 | 43471.20 | 40186.26 | 0.082 | A |
2018-02-08 | 43116.24 | 40153.26 | 0.074 | A |
2018-02-09 | 42885.14 | 40186.92 | 0.067 | A |
2018-02-10 | 42885.14 | 40186.92 | 0.067 | A |
2018-02-11 | 42885.14 | 40186.92 | 0.067 | A |
2018-02-12 | 42620.09 | 40210.47 | 0.060 | A |
2018-02-13 | 42740.13 | 40206.62 | 0.063 | A |
2018-02-14 | 42576.56 | 40217.23 | 0.059 | A |
2018-02-15 | 42830.99 | 40258.59 | 0.064 | A |
2018-02-16 | 43087.52 | 40243.80 | 0.071 | A |
2018-02-17 | 43087.52 | 40243.80 | 0.071 | A |
2018-02-18 | 43087.52 | 40243.80 | 0.071 | A |
2018-02-19 | 43190.56 | 40264.00 | 0.073 | A |
2018-02-20 | 43310.04 | 40293.07 | 0.075 | A |
2018-02-21 | 43246.99 | 40313.14 | 0.073 | A |
2018-02-22 | 43277.19 | 40336.00 | 0.073 | A |
2018-02-23 | 43280.41 | 40342.58 | 0.073 | A |
2018-02-24 | 43280.41 | 40342.58 | 0.073 | A |
2018-02-25 | 43280.41 | 40342.58 | 0.073 | A |
2018-02-26 | 43414.30 | 40363.48 | 0.076 | A |
2018-02-27 | 43434.11 | 40375.54 | 0.076 | A |
2018-02-28 | 43208.45 | 40333.06 | 0.071 | A |
2018-03-01 | 43136.77 | 40332.15 | 0.070 | A |
2018-03-02 | 42988.23 | 40348.57 | 0.065 | A |
2018-03-03 | 42988.23 | 40348.57 | 0.065 | A |
2018-03-04 | 42988.23 | 40348.57 | 0.065 | A |
2018-03-05 | 42876.71 | 40382.00 | 0.062 | A |
2018-03-06 | 43030.33 | 40399.60 | 0.065 | A |
2018-03-07 | 43057.76 | 40378.52 | 0.066 | A |
2018-03-08 | 43219.63 | 40395.25 | 0.070 | A |
2018-03-09 | 43474.86 | 40361.82 | 0.077 | A |
2018-03-10 | 43474.86 | 40361.82 | 0.077 | A |
2018-03-11 | 43474.86 | 40361.82 | 0.077 | A |
2018-03-12 | 43764.32 | 40366.85 | 0.084 | A |
2018-03-13 | 43992.89 | 40430.76 | 0.088 | A |
2018-03-14 | 43868.36 | 40445.43 | 0.085 | A |
2018-03-15 | 43703.23 | 40460.34 | 0.080 | A |
2018-03-16 | 43742.51 | 40493.46 | 0.080 | A |
2018-03-17 | 43742.51 | 40493.46 | 0.080 | A |
2018-03-18 | 43742.51 | 40493.46 | 0.080 | A |
2018-03-19 | 43878.62 | 40547.78 | 0.082 | A |
2018-03-20 | 43685.16 | 40540.59 | 0.078 | A |
2018-03-21 | 43667.44 | 40518.85 | 0.078 | A |
2018-03-22 | 43556.58 | 40488.77 | 0.076 | A |
2018-03-23 | 43184.70 | 40484.09 | 0.067 | A |
2018-03-24 | 43184.70 | 40484.09 | 0.067 | A |
2018-03-25 | 43184.70 | 40484.09 | 0.067 | A |
2018-03-26 | 42690.90 | 40514.39 | 0.054 | E |
2018-03-27 | 42929.55 | 40553.24 | 0.059 | E |
2018-03-28 | 42899.75 | 40592.98 | 0.057 | E |
2018-03-29 | 42610.63 | 40628.81 | 0.049 | E |
2018-03-30 | 42610.63 | 40628.81 | 0.049 | E |
2018-03-31 | 42610.63 | 40628.81 | 0.049 | E |
2018-04-01 | 42610.63 | 40628.81 | 0.049 | E |
2018-04-02 | 42892.52 | 40647.16 | 0.055 | E |
2018-04-03 | 42776.64 | 40641.16 | 0.053 | E |
2018-04-04 | 42862.57 | 40657.61 | 0.054 | E |
2018-04-05 | 42816.16 | 40709.74 | 0.052 | E |
#%qtconsole