<jupyter_start><jupyter_text>Modelos e Estratégias de Trading Comportamento do Retorno dos AtivosUirá Caiado. 07 de Agosto, 2016  1. Estatísticas de Retornos FinanceirosNesta primeira parte o objetivo é estudar as características estatísticas das distribuições de retornos logarítmicos de cinco diferentes índices de ações. Como não consegui obter estes dados para o *FTSE All Share*, vou seguir apenas com os outros 4 índices. Para começar, vou baixar os dados do *Yahoo! Finance* e orgnizá-los em uma tabela:<jupyter_code># importa bibliotecas necessarias
from pandas_datareader import data, wb
import pandas as pd
import datetime
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from scipy import stats
# recupera dados do yahoo
# NOTE: nem o yahoo e nem o google finance voltam dados do FTAS
d_data = {}
l_names = ["S&P 500", "DAX", "Nikkey 225", "Ibovespa"]
l_codes = ['^GSPC','^GDAXI', '^N225', '^BVSP']
ts_start = datetime.datetime(1980, 1, 1)
ts_end = datetime.datetime(2016, 8, 8)
for s_key, s_name in zip(l_codes, l_names):  #, '^FTAS']:
    df = data.get_data_yahoo(s_key, ts_start, ts_end)
    # guarda apenas preco ajustado em dicionario
    d_data[s_key[1:]] = df['Adj Close']
    print u"a primeira data do {} é {:%d/%m/%Y}".format(s_name, df.index[0])
# coloca todas as bases para comecar na mesma data
df_prices = pd.DataFrame(d_data)
df_prices.dropna(inplace=True)
df_prices.head()<jupyter_output><empty_output><jupyter_text>Apesar de ter alguns dados desde 1980, os dados do Ibovespa que tenho acesso começam em 1993. Então, para efeito de comparação, excluí todas os dados dos outros Índices anteriores a este ano. Abaixo vou calcular a mediana, a média, o desvio padrão, o ponto mínimo, o ponto máximo, a assimetria, curtose, o desvio médio absoluto (MAD) e a distância interquartil (IQR) de cada uma das bases a amostra com frequência diária, semanal, mensal e anual:<jupyter_code>import scipy.stats.stats as st

def calc_stats(df):
    '''
    Return a dictionary with the median, mean, std deviation,
    minimum value, maximum value, skewness, kurtosis, MAD
    and IQR
    :param df: dataframe. time series of log-returns of
        different assets
    '''
    d_rtn = {}
    # Panda's std() method uses degrees of freedom of 1 by default
    # calcula estatisticas basicas
    d_rtn['Count'] = df.count().to_dict()
    d_rtn['Median'] = df.median().to_dict()
    d_rtn['Mean'] = df.mean().to_dict()
    d_rtn['StdDev'] = df.std(ddof=0).to_dict()
    # recupera min e max
    d_rtn['Min'] = df.min().to_dict()
    d_rtn['Max'] = df.max().to_dict()
    # calcula assimetria
    d_rtn['Skew'] = dict(zip(df_aux.columns, st.skew(df_aux)))
    # calcula curtose
    d_rtn['Kutosis'] = dict(zip(df_aux.columns, st.kurtosis(df_aux)))
    # calcula MAD e IQR
    d_rtn['MAD'] = df.mad()    
    d_rtn['IQR'] = df.quantile(0.75) - df.quantile(0.25)
    # reorganiza dados
    df_rtn = pd.DataFrame(d_rtn).T
    l_idx = ['Count', 'Median', 'Mean', 'StdDev', 'Min', 'Max',
             'Skew', 'Kutosis', 'MAD', 'IQR']
    return df_rtn.ix[l_idx, :]
    
# calcula estatisticas 
l_name = ['1. daily', '2. weekly', '3. monthly', '4. yearly']
d_logrtn = {}
d_logrtn_all = {}
df_rtn = None
for s_frq, s_name in zip(['1D', 'W-FRI', '1M', 'A'], l_name):
    # modifica frequencia dos dados e calcula log retornos
    df_aux = df_prices.resample(s_frq).last()
    df_aux = np.log(df_aux/df_aux.shift()).dropna()
    # calcula estatisticas e guarda resultados na mesma estrutura
    df_test = calc_stats(df_aux)
    df_iqr = df_test.ix['IQR', :]
    l_idx = df_test.index
    df_test.index = [[s_name] * len(l_idx), df_test.index]    
    if s_frq == '1D':
        df_rtn = df_test.copy()
    else:
        df_rtn = pd.concat([df_rtn, df_test])
    #guarda dados sem outliers (1.5 * IQR)
    l_no_out_liers = ((df_aux <= df_aux.quantile(0.75) + 1.5 * df_iqr) & 
                      (df_aux >= df_aux.quantile(0.25) - 1.5 * df_iqr)).sum(axis=1)==4
    d_logrtn_all[s_name] = df_aux
    d_logrtn[s_name] = df_aux[l_no_out_liers]
# imprime resultados das estatisticas por periodo
df_rtn<jupyter_output><empty_output><jupyter_text>Olhando os números dispostos desta maneira, não fica tão fácil a comparação. Para ajudar na análise, vou criar um *heatmap* de cada uma das estatísticas calculadas acima.<jupyter_code># arruma estrutura os dados para usar em plot
df_rtn2 = df_rtn.stack().reset_index()
df_rtn2.columns = ['Sample', 'Stat', 'Index', 'Value']
df_rtn2 = df_rtn2[df_rtn2.Stat != 'Count']

# cria funcao para fazer cada heatmap
def facet(data, color):
    data = data.pivot(index="Sample", columns='Index', values='Value')
    g = sns.heatmap(data, annot=True, linewidths=.5, cmap='Blues')

#plota o heatmap de cada estatistica
with sns.plotting_context(font_scale=5.5):
    g = sns.FacetGrid(df_rtn2, col="Stat", col_wrap=3, size=4, aspect=1)
    g = g.map_dataframe(facet)
    g.add_legend()
    g.set_xlabels('Index')
    g.set_ylabels('Sample Frequency')<jupyter_output><empty_output><jupyter_text>Esperava observar um aumento nos valores de cada estatística quando diminuímos a frequência dos dados (diário para semanal, por exemplo), porém isso não aconteceu para a curtóse e para a assimetria. Também olhando para os dados desta maneira é possível perceber algums fatos curiosos:- A mediana anual mais alta foi fo índice DAX- Todas as médias do *Nikkey 225* são negativas, independente da frequência (e sua volatilidade anual não é baixa)- O pior retorno anual de todos os Indices foi semelhante- A assimetria de nenhuma distribuição ficou perto de 0 (zero)- O desvio médio absoluto, no geral, é inferior ao desvio padrão Agora, vou checar visualmente a distribuição dos retornos e sobrepor como seria a distribuição normal deles se considerasse o mesmo desvio e média. Note que os eixos X e Y tem amplitudes diferentes em cada gráfico.<jupyter_code># reorganiza dados para plotagem de heatmap
df_plot = None
for s_name in l_name:
    df_aux = d_logrtn[s_name].copy()
    df_aux.index = [[s_name] * len(df_aux.index), df_aux.index]
    if s_frq == '1D':
        df_plot = df_aux.copy()
    else:
        df_plot = pd.concat([df_plot, df_aux])

df_plot = df_plot.stack().reset_index()
df_plot.columns = ['Freq', 'Date', 'Index', 'Return']
# pltando dados sem out-liers
g = sns.FacetGrid(df_plot, col="Index",
                  row='Freq', sharex=False,
                  sharey=False)
g.map(sns.distplot, "Return", bins=15, kde=False,
      fit=stats.norm);<jupyter_output><empty_output><jupyter_text>Para alguns dos gráficos acima, não ficou tão claro se realmente não podem ser considerados normalmente distribuídos. Abaixo, vou aplicar o teste [Jarque-Bera](https://en.wikipedia.org/wiki/Jarque–Bera_test) para testar a normalidade das distribuições e imprimir o p-valor dos teste para cada Índice/Frequência:<jupyter_code># imprime p-values dos testes
df_jarque_bera = df_plot.groupby(['Freq', 'Index']).aggregate(stats.jarque_bera)
pd.DataFrame([[x[0][1]] for x in df_jarque_bera.values], index=df_jarque_bera.index,
             columns=['p-value'])<jupyter_output><empty_output><jupyter_text>A hipótese nula é que a distribuição é normal. O teste consiste em testar se a assimetria e o excesso de curtose (curtose - 3) são zero, como é esperado por uma distribuição normal. Curiosamente, o teste não rejeitou a hipótese nula de que o *S&P 500* e o *DAX* na frequência diária sejam normais e que o *DAX* seja normal na frequência semanal (todos com 5% de confiança).  2 Replicação do Índice BovespaNesta segunda parte o objetivo é praticar a agregação transversal de retornos financeiros através da replicação de um índice de ações. Neste caso o objetivo é a replicação do Índice Bovespa (IBOV) através dos retornos individuais das ações compõem o índice através da fórmula:$$IBOV_t = IBOV_{t-1} \, \times \, \left(  1 + \sum_{i=1}^{N} \omega_{i, \, t} \times R_{i, \, t} \right)$$Onde $R_{i, \, t}$ é o retorno da ação $i$ na data $t$ e $\omega_{i, \, t}$ é o peso da ação $i$ na carteira do índice na data $t$. Vamos começar baixando os dados de cada ativo. Vou baixar os pelo Yahoo! Finance e, quando não disponíveis, baixarei pelo Google Finance. Porém, isso implicará em erro nos cálculos, uma vez o Google Finance não fornece o fechamento corrigido pels eventos, como o yahoo.<jupyter_code>import pandas as pd
l_assets = list(pd.read_excel('DadosReplicacao.xlsx', sheetname='LISTA').T.values[0])
df_redutor = pd.read_excel('DadosReplicacao.xlsx', sheetname='Redutor',
                           skiprows=1, index_col='Data')
df_qtd = pd.read_excel('DadosReplicacao.xlsx', sheetname='Qtd.Teorica', index_col='Data')
# recupera dados do yahoo
# NOTE: nem o yahoo e nem o google finance voltam dados do FTAS
d_assets = {}
ts_start = datetime.datetime(2015, 1, 5)
ts_end = datetime.datetime(2015, 8, 3)
for s_key, s_name in zip(l_assets, l_assets):  #, '^FTAS']:
    try:
        df = data.get_data_yahoo(s_key + '.SA', ts_start, ts_end)
        df = df['Adj Close']
    except:
        print "{} foi recuperado do Google Finance".format(s_key)
        df = data.get_data_google('BVMF:' + s_key, ts_start, ts_end)
        df = df['Close']
    # guarda apenas preco ajustado em dicionario
    d_assets[s_key] = df
#     print u"a primeira data do {} é {:%d/%m/%Y}".format(s_name, df.index[0])
print "Feito!"<jupyter_output>ALLL3 foi recuperado do Google Finance
KLBN11 foi recuperado do Google Finance
SANB11 foi recuperado do Google Finance
TBLE3 foi recuperado do Google Finance
Feito!<jupyter_text>Agora, preciso calcular o peso das ações no retorno do índice cada dia. Primeiro, vou replicar o índice usando as quantidades teóricas:<jupyter_code># redimensiona dados
df_assets = pd.DataFrame(d_assets)
df_assets2 = df_assets.ix[df_qtd.index, df_qtd.columns]
# os dados do yahoo em julho estao multiplicados por 10. Efetua correcao
df_assets2.ix[df_assets2.RUMO3>60., 'RUMO3'] /=10.
# calcula ibov
df_ibov = (df_qtd * df_assets2).sum(axis=1) / df_redutor['Redutor']
# plota comparacao
ax = df_redutor['Fechamento'].plot(label='observado', legend=True)
df_ibov.plot(ax=ax, label='calculado', legend=True)
ax.set_title("Fechamento do IBOV\n", fontsize=15);<jupyter_output><empty_output><jupyter_text>Além do deslocamento no eixo Y provavelmente por estar usando dados com eventos mais recentes descontados, há alguma inconsistência por volta de Abril/2015, aparentemente. Depois parece que foi corrigido. Consegui apenas localizar o erro relacionados às cotações da RUMO3. Como replicar o índice pelo método tradicional não é o objetivo deste trabalho, vou extrair o peso das ações destes dados mesmo.<jupyter_code>print u"dimensões da matriz de quantidade: ({}, {})".format(*df_qtd.shape)
print u"dimensões da matriz de preços: ({}, {})".format(*df_assets2.shape)
print u"dimensões da matriz do redutor: ({}, {})".format(df_redutor['Redutor'].shape[0], 1)
print u"dimensões da matriz do IBOV: ({}, {})".format(df_ibov.shape[0], 1)
# amostra dos dados calculados antes de serem acumulados
df_omega = ((df_qtd * df_assets2).T / (df_redutor['Redutor']*df_ibov)).T
df_aux = (1+(df_omega * df_assets2.pct_change()).sum(axis=1))
df_aux.head()
# usa como ponto inicial o fechamnto dado em planilha
df_ibov2 = (df_aux.cumprod() * df_redutor.Fechamento.iloc[0])
# plota comparacao
ax = df_redutor['Fechamento'].plot(label='observado', legend=True)
df_ibov2.plot(ax=ax, label='calculado', legend=True)
ax.set_title("Fechamento do IBOV\n", fontsize=15);<jupyter_output><empty_output><jupyter_text>Utilizando como ponto de partida o fechamento dado em planilha e o método de cálculo proposto no início desta seção, fica claro a diferença mencionada anteriormente. No início de Abril/15 aconteceu um rebalanceamento de carteira, que pode ter introduzido este erro nos cálculos. *Style notebook and change matplotlib defaults*<jupyter_code>#loading style sheet
from IPython.core.display import HTML
HTML(open('ipython_style.css').read())
#changing matplotlib defaults
%matplotlib inline
import seaborn as sns
sns.set_palette("deep", desat=.6)
sns.set_context(rc={"figure.figsize": (8, 4)})
sns.set_style("whitegrid")
sns.set_palette(sns.color_palette("Set2", 10))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11238250919493258, 'fraction_numerical': 0.013158970167552105, 'mean_word_length': 4.623161764705882, 'pattern_counts': {'":': 1, '<': 37, '<?xml version=': 0, '>': 38, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '377952', 'license': 'MIT', 'n_tokens_mistral': 4795, 'n_tokens_neox': 4624, 'n_words': 1336, 'path': 'AssetReturn_Behavor.ipynb', 'repo_name': 'ucaiado/AssetReturns_Behavior', 'size': '12235'}