<jupyter_start><jupyter_text>Fama-french portfolios Author: Prof. <NAME> Imports<jupyter_code>import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import bql
bq = bql.Service()<jupyter_output><empty_output><jupyter_text>DataGrab the IBOV index and the CDI rate time series like this:<jupyter_code>start_date = '2007-12-31'
end_date = pd.to_datetime('today')
bbg_tickers = ['IBOV Index', 'BZACCETP Index']
fields = {'price' : bq.data.px_last(fill='PREV', start=start_date, end=end_date)}
req = bql.Request(bbg_tickers, fields)
res = bq.execute(req)
df = bql.combined_df(res).drop('CURRENCY', 1).pivot_table(index='DATE', columns= 'ID')
df.columns = ['CDI', 'Ibov']
df.to_csv('IBOV_time_series.csv') # This is if you want to save it in a .csv file
df.head()<jupyter_output><empty_output><jupyter_text>Grab IBOV's members at every month end:<jupyter_code>bql_item = bq.data.id()
month_ends = [df.index[i-1] for i in range(1,df.shape[0]) if df.index[i-1].month != df.index[i].month]
ibov_composition = pd.DataFrame()
for eom in month_ends:
    bql_universe = bq.univ.members('IBOV Index', dates=eom)
    bql_request = bql.Request(bql_universe, bql_item)
    bql_response = bq.execute(bql_request)
    ddf = bql.combined_df(bql_response).set_index(['ID()', 'DATE']).drop('ORIG_IDS', 1)
    ibov_composition = ibov_composition.append(ddf)<jupyter_output><empty_output><jupyter_text>Grab Total Return series for every member:<jupyter_code>all_tickers = ibov_composition.index.get_level_values('ID()').unique()
bql_request = f"get(TOTAL_RETURN_INDEX(DATES=RANGE({start_date}, {end_date.date()}))) for(["
for x in all_tickers:
    bql_request += f"'{x}',"    
bql_request = bql_request[:-1] + "])"
bql_response = bq.execute(bql_request)
tr_df = bql.combined_df(bql_response)
tr_df.to_csv('tr_df.csv')
ibov_composition.to_csv('ibov_composition.csv')<jupyter_output><empty_output><jupyter_text>Momentum signalsLet's now calculate a momentum signal for every stock at every month end. We will use a 12M-1M momentum signal:<jupyter_code>k = 12 # months
mom_signals = pd.Series(index=ibov_composition.index)
signal_start_date = mom_signals.index.get_level_values(1).unique()[1 + k]
for stock, d in mom_signals.index:    
    if stock in tr_df.index and d>=signal_start_date:
        p1 = tr_df.loc[stock].set_index('DATE').shift(21).loc[:d].iloc[-1,0]
        p0 = tr_df.loc[stock].set_index('DATE').shift(21).loc[:d].iloc[-k*21,0]
        mom_signals.loc[stock, d] = p1/p0 -1
mom_signals.index = mom_signals.index.swaplevel(0,1)<jupyter_output><empty_output><jupyter_text>Momentum portfoliosLet's now pick the top (20%) and bottom (20%) stocks according to our momentum signal:<jupyter_code>mom_portfolios = {}
for eom in mom_signals.dropna().index.get_level_values('DATE').unique():
    stocks_on_date = mom_signals.loc[eom].dropna().rank()
    n = stocks_on_date.shape[0]
    date_port = {
        'top' : stocks_on_date[stocks_on_date>n-n/5].index,
        'bottom' : stocks_on_date[stocks_on_date<n/5].index
    }
    mom_portfolios[eom] = date_port<jupyter_output><empty_output><jupyter_text>Momentum portfolios returns<jupyter_code>def weights_on_date(d, port_name):
    w = ibov_composition.loc[[(x,d) for x in mom_portfolios[d][port_name]]]['Weights'].fillna(0)
    w = w/w.sum()
    w.index = w.index.droplevel(level='DATE')
    return w

def quant_on_date(d, port_name, notional):
    w = weights_on_date(d, port_name)
    ref_date = max([x for x in tr_df['DATE'].unique() if x < d])
    prices = tr_df[tr_df['DATE']==ref_date].loc[w.index].drop('DATE', 1).iloc[:,0]
    return notional * w / prices
    
    
calendar = [d for d in tr_df.DATE.unique() if d>=signal_start_date]
backtests = pd.DataFrame(index=calendar, columns=['top', 'bottom'])
backtests.iloc[0] = 100
quant = {
    'q_top' : quant_on_date(signal_start_date, 'top', backtests.iloc[0,0]),
    'q_bottom' : quant_on_date(signal_start_date, 'bottom', backtests.iloc[0,1]),
}

for tdy, yst in zip(calendar[1:], calendar[:-1]):
    # calculate pnl of the top stocks
    p1top = tr_df[tr_df['DATE']==tdy].loc[quant['q_top'].index].drop('DATE', 1).iloc[:,0]
    p0top = tr_df[tr_df['DATE']==yst].loc[quant['q_top'].index].drop('DATE', 1).iloc[:,0]
    toppnl = (quant['q_top'] * (p1top - p0top)).sum()
    backtests.loc[tdy, 'top'] = backtests.loc[yst, 'top'] + toppnl
    # calculate pnl of the bottom stocks
    p1bot = tr_df[tr_df['DATE']==tdy].loc[quant['q_bottom'].index].drop('DATE', 1).iloc[:,0]
    p0bot = tr_df[tr_df['DATE']==yst].loc[quant['q_bottom'].index].drop('DATE', 1).iloc[:,0]
    botpnl = (quant['q_bottom'] * (p1bot - p0bot)).sum()
    backtests.loc[tdy, 'bottom'] = backtests.loc[yst, 'bottom'] + botpnl
    
    if yst in mom_portfolios.keys(): # rebalance the portfolio
        quant = {
        'q_top' : quant_on_date(yst, 'top', backtests.loc[tdy, 'top']),
        'q_bottom' : quant_on_date(yst, 'bottom', backtests.loc[tdy, 'bottom']),
        }
backtests.head()
df2 = pd.concat([backtests, df], axis=1, sort=True).dropna().drop('CDI', 1).astype(float)
df2 = np.exp(np.log(df2).diff(1).fillna(0).cumsum())
df2.plot(figsize=(15,10), fontsize=16)
plt.title('Top and bottom 12M-1M momentum portfolios', fontsize=20)
plt.legend(fontsize=20)
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.16770420675344275, 'fraction_numerical': 0.017921146953405017, 'mean_word_length': 5.387951807228916, 'pattern_counts': {'":': 0, '<': 32, '<?xml version=': 0, '>': 33, 'https://': 0, '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': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '349448', 'license': 'MIT', 'n_tokens_mistral': 2159, 'n_tokens_neox': 2105, 'n_words': 362, 'path': 'Quantitative Finance Lectures/lecture02/fama_french_portfolios/uni_fama_french.ipynb', 'repo_name': 'Finance-Hub/FinanceHubMaterials', 'size': '5309'}