import pandas as pd import os from plotly.subplots import make_subplots import plotly.graph_objects as go from datetime import datetime, timedelta, date import streamlit as st import sys from math import ceil from modules import tables import io import boto3 from Data.credentials import credentials_s3 as creds3 def save_s3(key, secret_key, bucket, df, path): with io.BytesIO() as output: with pd.ExcelWriter(output, engine='xlsxwriter') as writer: df.to_excel(writer, 'sheet_name') data = output.getvalue() s3 = boto3.resource('s3', aws_access_key_id=key, aws_secret_access_key=secret_key) s3.Bucket(bucket).put_object(Key=path, Body=data) @st.experimental_memo def read_excel_s3(key, secret_key, bucket, path): s3_client = boto3.client('s3', aws_access_key_id=key, aws_secret_access_key=secret_key) response = s3_client.get_object(Bucket=bucket, Key=path) data = response["Body"].read() df = pd.read_excel(io.BytesIO( data), sheet_name='sheet_name', index_col='date') return df def sectors_lv1_dicc(): sectors_dicc = {} company_db = pd.read_excel( 'Data/Company_Base_Definitivo.xlsx', sheet_name='Compilado') sectors = list(company_db['LV1'].unique()) for sector in sectors: sectors_dicc[sector] = list(set(list(company_db[company_db['LV1'] == sector]['Country'].unique())) .intersection(['Argentina', 'Brazil', 'Chile', 'Colombia', 'Mexico', 'Peru'])) return sectors_dicc def colores_corporativos(colors=None): color_dict = {'green': (55, 95, 77), 'light_blue': (110, 162, 201), 'light_gray': (135, 146, 158), 'dark_purple': (119, 28, 95), 'red': (204, 0, 51), 'blue': (42, 83, 113), 'purple': (159, 37, 127), 'light_green': (122, 178, 153), 'gray': (66, 74, 82), 'yellow': (195, 195, 9),} for key in color_dict: color_dict[key] = tuple(v/255 for v in color_dict[key]) if colors is None: return color_dict else: aux = {col: color_dict[col] for col in colors} return aux def update_data(start=str(date.today()-timedelta(int(1.5*365))), today=str(date.today())): fields = ['IQ_CLOSEPRICE_ADJ', 'IQ_MARKETCAP', 'IQ_VALUE_TRADED'] key = creds3["S3_KEY_ID"] secret_key = creds3["S3_SECRET_KEY"] bucket = creds3["S3_BUCKET"] path = 'Momentum.xlsx' for f in fields: # Cargamos de Mongo data de empresas save_s3(key=key, secret_key=secret_key, bucket=bucket, df=tables.EquityMaster(field=f).query( start=start, end=today, rename=['asset']), path=f + '.xlsx') ud = pd.read_excel('Data/update_data.xlsx') ud = ud[ud['View'] != 'Mom Industrias'] today = date.today().strftime('%d-%m-%Y') ud = ud.append({"View": 'Mom Industrias', "Last_Update": today}, ignore_index=True) ud.to_excel('Data/update_data.xlsx', index=False) @st.cache(suppress_st_warning=True) def data_request(today, start, countries, criteria='LV1'): """ Hace las consultas básicas necesarias para poder visaulizar StN. :param today: fecha de hoy. Este parámetro está sólo para poder usar st.cache. :param start: desde cuándo se está haciendo la consulta. :param countries: países que solicita el usuario :param criteria: Criterio a usar en country_sector. :return: data_dict, company_db, country_sector) """"" key = creds3["S3_KEY_ID"] secret_key = creds3["S3_SECRET_KEY"] bucket = creds3["S3_BUCKET"] path = 'Momentum.xlsx' # Cargamos mapeos file = 'Data/Company_Base_Definitivo.xlsx' company_db = pd.read_excel(file, sheet_name='Compilado', index_col='ID_Quant', engine='openpyxl') # Pequeño arreglo de meli y globant company_db.loc[[7, 72], 'Country'] = 'Brazil' data_dict = {} paths = ['IQ_CLOSEPRICE_ADJ.xlsx', 'IQ_MARKETCAP.xlsx', 'IQ_VALUE_TRADED.xlsx'] for p in paths: data_dict[p[:-5]] = read_excel_s3(key=key, secret_key=secret_key, bucket=bucket, path=p) # Vemos solo las parejas de pais-sector que existen para ahorrar parejas country_sector = (company_db[['Country', criteria]].drop_duplicates(). sort_values(['Country', criteria])) country_sector = country_sector.loc[country_sector['Country'].isin( countries)] return data_dict, company_db, country_sector def mm_eval(series): return int(series.iloc[-1] > series.mean()) @st.cache(suppress_st_warning=True) def country_request(country, start, end): return tables.MacroMaster(country=country, instrument='INDEX').query(start=start, end=end) @st.cache(suppress_st_warning=True) def univ_request(company_db, c): return company_db.query(f"Country == '{c}'").index.astype(str) @st.cache(suppress_st_warning=True) def mm_sum(prices_c, p_list): return sum([prices_c.rolling(p * 20).apply(mm_eval) for p in p_list]) @st.experimental_memo def dictionaries_maker(start, countries, country_sector, criteria, company_db, data_dict): rel_rets = {} cs_ids = {} w_hist = {} bm_dict = {c: country_request(c, start, str(date.today())) for c in countries} for c in countries: bm_rets = tables.MacroMaster( country=c, instrument='INDEX').query(start=start) rel_ret_c = {} for s in country_sector.loc[country_sector['Country'] == c, criteria]: univ = (company_db.query(f"Country == '{c}' and {criteria} == '{s}'") .index.astype(str)) univ = list(set(univ & data_dict['IQ_MARKETCAP'].columns & data_dict['IQ_CLOSEPRICE_ADJ'].columns)) w = data_dict['IQ_MARKETCAP'][univ].ffill().fillna(0) w = w.div(w.sum(1), axis=0) w_hist[f'{c}-{s}'] = w p_ind = data_dict['IQ_CLOSEPRICE_ADJ'][univ].ffill() ret_ind = (w * p_ind.pct_change()).fillna(0).sum(1) rel_ret_c[s] = ret_ind - bm_rets cs_ids[f'{c}-{s}'] = univ rel_rets[c] = pd.DataFrame(rel_ret_c) return rel_rets, cs_ids, w_hist, bm_dict def signal_to_noise(): """ Despliega un formulario de streamlit y grafica lo indicado por el usuario. :return: None """ form_stn = st.form("StN") stn_view = form_stn.selectbox('¿Cómo desea visualizar StN?:', ('Nivel o Cambios por País', 'Grafico Agregado de StN')) if stn_view == 'Nivel o Cambios por País': n = form_stn.slider('Inserte para cuántas semanas desea visualizar cambios (Con 0 se muestra el nivel StN).', min_value=0, max_value=12) else: n = 0 # Países a visualizar countries = form_stn.multiselect('País : ', ('Todos', 'Argentina', 'Brazil', 'Chile', 'Colombia', 'Mexico', 'Peru')) if 'Todos' in countries: countries = ['Argentina', 'Brazil', 'Chile', 'Colombia', 'Mexico', 'Peru'] countries_q = len(countries) update_button = form_stn.form_submit_button("Actualizar datos") if update_button: update_data() accept = form_stn.form_submit_button('Visualizar') start = '2017' criteria = 'LV1' st.write("### Está visualizando: StN") colors = list(colores_corporativos().values()) colors2 = [] for i in range(len(colors)): colors2.append("rgb" + str(colors[i])) if accept: today = str(date.today()) # Acá cargamos los datos necesarios data = data_request(today, start, countries) data_dict = data[0] company_db = data[1] country_sector = data[2] st.write('Data desde ' + start + ' hasta ' + str(data_dict['IQ_CLOSEPRICE_ADJ'].index[-1].date())) rel_rets, cs_ids, w_hist, bm_dict = dictionaries_maker(start, countries, country_sector, criteria, company_db, data_dict) stn_p = 20 * 5 signal_to_noise_dict = {c: df.rolling(stn_p).sum() / df.abs().rolling(stn_p).sum().abs() for c, df in rel_rets.items()} if stn_view == 'Nivel o Cambios por País': index = list(range(len(colors2))) colors2 = dict(zip(index, colors2)) if n == 0: st.write('### Nivel de StN') fig = make_subplots(rows=1, cols=1, subplot_titles=countries) if countries_q == 1: for i, (c, df) in enumerate(signal_to_noise_dict.items()): fig.add_trace(go.Bar(x=df.iloc[-1].sort_values().array, y=df.iloc[-1].sort_values().index, orientation='h', showlegend=False, marker_color=colors2[3])) fig.update_yaxes(visible=True, showticklabels=True) fig.update_xaxes(visible=False, showticklabels=False) fig.update_layout(height=800) with st.container(): st.plotly_chart(fig, use_container_width=True) else: col1, col2 = st.columns(2) titles_1 = [] titles_2 = [] for k in range(countries_q): if k % 2 == 0: titles_1.append(countries[k]) else: titles_2.append(countries[k]) fig1 = make_subplots(rows=1 + (countries_q > 2) + (countries_q > 4), cols=1, subplot_titles=titles_1) fig2 = make_subplots(rows=1 + (countries_q > 2) + (countries_q > 4), cols=1, subplot_titles=titles_2) for i, (c, df) in enumerate(signal_to_noise_dict.items()): if i % 2 == 0: fig1.add_trace(go.Bar(x=df.iloc[-1].sort_values().array, y=df.iloc[-1].sort_values().index, orientation='h', showlegend=False, marker_color=colors2[3]), row=(i == 0) + 2 * (i == 2) + 3 * (i == 4), col=1) else: fig2.add_trace(go.Bar(x=df.iloc[-1].sort_values().array, y=df.iloc[-1].sort_values().index, orientation='h', showlegend=False, marker_color=colors2[3]), row=(i == 1) + 2 * (i == 3) + 3 * (i == 5), col=1) fig1.update_yaxes(visible=True, showticklabels=True) fig2.update_yaxes(visible=True, showticklabels=True) fig1.update_xaxes(visible=False, showticklabels=False) fig2.update_xaxes(visible=False, showticklabels=False) fig1.update_layout(height=800) fig2.update_layout(height=800) col1.plotly_chart(fig1, use_container_width=True) col2.plotly_chart(fig2, use_container_width=True) else: fig = make_subplots(rows=1, cols=1, subplot_titles=countries) if countries_q == 1: for i, (c, df) in enumerate(signal_to_noise_dict.items()): fig.add_trace(go.Bar(x=df.iloc[-1].sort_values().array, y=df.iloc[-1].sort_values().index, orientation='h', showlegend=False, marker_color=colors2[3])) fig.update_yaxes(visible=True, showticklabels=True) fig.update_xaxes(visible=False, showticklabels=False) fig.update_layout(height=800) with st.container(): st.plotly_chart(fig, use_container_width=True) else: st.write('### Cambios en ' + str(n) + ' Semanas') if countries_q == 1: fig = make_subplots( rows=1, cols=1, subplot_titles=countries) for i, (c, df) in enumerate(signal_to_noise_dict.items()): fig.add_trace(go.Bar(x=df.diff(5 * n).iloc[-1].sort_values().array, y=df.diff( 5 * n).iloc[-1].sort_values().index, orientation='h', showlegend=False, marker_color=colors2[3])) fig.update_yaxes(visible=True, showticklabels=True) fig.update_xaxes( visible=False, showticklabels=False) fig.update_layout(height=800) with st.container(): st.plotly_chart(fig, use_container_width=True) else: col1, col2 = st.columns(2) titles_1 = [] titles_2 = [] for k in range(countries_q): if k % 2 == 0: titles_1.append(countries[k]) else: titles_2.append(countries[k]) fig1 = make_subplots(rows=1 + (countries_q > 2) + (countries_q > 4), cols=1, subplot_titles=titles_1) fig2 = make_subplots(rows=1 + (countries_q > 2) + (countries_q > 4), cols=1, subplot_titles=titles_2) for i, (c, df) in enumerate(signal_to_noise_dict.items()): if i % 2 == 0: fig1.add_trace(go.Bar(x=df.diff(5 * n).iloc[-1].sort_values().array, y=df.diff( 5 * n).iloc[-1].sort_values().index, orientation='h', showlegend=False, marker_color=colors2[3]), row=(i == 0) + 2 * (i == 2) + 3 * (i == 4), col=1) else: fig2.add_trace(go.Bar(x=df.diff(5 * n).iloc[-1].sort_values().array, y=df.diff( 5 * n).iloc[-1].sort_values().index, orientation='h', showlegend=False, marker_color=colors2[3]), row=(i == 1) + 2 * (i == 3) + 3 * (i == 5), col=1) fig1.update_yaxes(visible=True, showticklabels=True) fig2.update_yaxes(visible=True, showticklabels=True) fig1.update_xaxes(visible=False, showticklabels=False) fig2.update_xaxes(visible=False, showticklabels=False) fig1.update_layout(height=1000, margin_b=20, margin_r=20, margin_l=20) fig2.update_layout(height=1000, margin_b=20, margin_r=20, margin_l=20) col1.plotly_chart(fig1, use_container_width=True) col2.plotly_chart(fig2, use_container_width=True) if stn_view == 'Grafico Agregado de StN': mc_th = 5000 aux_stn = pd.concat([df.rename(columns={s: f'{c}-{s}' for s in df.columns}) for c, df in signal_to_noise_dict.items() if c in countries], axis=1) mc_per_ind = pd.Series([data_dict['IQ_MARKETCAP'][cs_ids[cs]].rolling(60, 10).mean().iloc[-1].sum() for cs in aux_stn.columns], index=aux_stn.columns) st.markdown('### StN General ') col1, col2, col3 = st.columns(3) aux_stn = aux_stn.loc[:, (mc_per_ind > mc_th).values] # Ahora creamos los dataframes para cada margen de tiempo stn_general = aux_stn.iloc[-1].sort_values() # General stn_1week = aux_stn.diff(5).iloc[-1].sort_values() # 1 Week Chg stn_1month = aux_stn.diff(20).iloc[-1].sort_values() # 1 Month Chg # Procedemos a graficar fig1 = make_subplots(subplot_titles=['General']) fig2 = make_subplots(subplot_titles=['1W Chg']) fig3 = make_subplots(subplot_titles=['1M Chg']) fig1.add_trace( go.Bar(x=stn_general.array, y=stn_general.index, orientation='h', showlegend=False, marker_color=colors2[3])) fig2.add_trace( go.Bar(x=stn_1week.array, y=stn_1week.index, orientation='h', showlegend=False, marker_color=colors2[3])) fig3.add_trace( go.Bar(x=stn_1month.array, y=stn_1month.index, orientation='h', showlegend=False, marker_color=colors2[3])) fig1.update_xaxes(visible=False, showticklabels=False) fig2.update_xaxes(visible=False, showticklabels=False) fig3.update_xaxes(visible=False, showticklabels=False) col1.plotly_chart(fig1, use_container_width=True) col2.plotly_chart(fig2, use_container_width=True) col3.plotly_chart(fig3, use_container_width=True) def medias_moviles(): """ Despliega un formulario de streamlit y grafica lo indicado por el usuario. :return: None """ sectors_dict = sectors_lv1_dicc() select_sector = st.selectbox( 'Qué sector desea visualizar?', list(sectors_dict.keys())) form_mm = st.form("MM") start = form_mm.date_input( '¿Desde qué fecha desea visualizar?', value=date.today() - timedelta(365)) start = datetime.combine(start, datetime.min.time()) countries = form_mm.multiselect('¿Qué país(es) desea visualizar?', [ 'Todos'] + sectors_dict[select_sector]) if 'Todos' in countries: countries = sectors_dict[select_sector] update_button = form_mm.form_submit_button("Actualizar datos") if update_button: update_data() accept = form_mm.form_submit_button('Visualizar') criteria = 'LV1' st.write("### Está visualizando: Medias Moviles") colors = list(colores_corporativos().values()) colors2 = [] for i in range(len(colors)): colors2.append("rgb" + str(colors[i])) if accept: if not countries: countries = sectors_dict[select_sector] today = str(date.today()) # Acá cargamos los datos necesarios data = data_request(today, start, countries) data_dict = data[0] company_db = data[1] country_sector = data[2] st.write('Data desde ' + str(start.date()) + ' hasta ' + str(data_dict['IQ_CLOSEPRICE_ADJ'].index[-1].date())) rel_rets, cs_ids, w_hist, bm_dict = dictionaries_maker(start, countries, country_sector, criteria, company_db, data_dict) stn_p = 20 * 5 signal_to_noise_dict = { c: df.rolling(stn_p).sum() / df.abs().rolling(stn_p).sum().abs() for c, df in rel_rets.items()} st.write('Sector: ' + select_sector) ma_p = [20, 60, 250] aux_stn = pd.concat( [df.rename(columns={s: f'{c}-{s}' for s in df.columns}) for c, df in signal_to_noise_dict.items() if c in countries], axis=1) aux_rr = pd.concat( [df.rename(columns={s: f'{c}-{s}' for s in df.columns}) for c, df in rel_rets.items() if c in countries], axis=1) aux_rr = aux_rr[aux_stn.columns] if len(countries) > 1: col1, col2 = st.columns(2) titles_1 = [] titles_2 = [] dicc_tit_1 = {} dicc_tit_2 = {} for i, country in enumerate(countries): if i % 2 == 0: title_1=str(country) + ' - ' + str(select_sector) titles_1.append(title_1) dicc_tit_1[title_1] = make_subplots() else: title_2=str(country) + ' - ' + str(select_sector) titles_2.append(title_2) dicc_tit_2[title_2] = make_subplots() countries_q = len(countries) indices = list(range(ceil(countries_q / 2))) m = 0 for i, c in enumerate(countries): df = (aux_rr[c + '-' + select_sector] + 1).cumprod() - 1 df_mm = pd.DataFrame( {p: df.rolling(p, min_periods=1).mean() for p in ma_p}) df = df.to_frame() df[[f'MA_{p}' for p in ma_p]] = df_mm df = df.loc[df.index >= start] df.rename( columns={c + '-' + select_sector: 'Indice'}, inplace=True) df = df - df['Indice'][0] if i % 2 == 0: list_plot_1 =list(dicc_tit_1.keys()) for k in range(len(ma_p)): if k == 0: dicc_tit_1[list_plot_1[i//2 + i % 2]].add_trace( go.Scatter(x=df.index, y=df['Indice'], line=dict( color=colors2[len(ma_p) + 1]), name='Indice'), row=indices[m] + 1, col=1) dicc_tit_1[list_plot_1[i//2 + i % 2]].add_trace( go.Scatter(x=df.index, y=df['MA_' + str(ma_p[k])], line=dict(color=colors2[k]), name='MA_' + str(ma_p[k])), row=indices[m] + 1, col=1) dicc_tit_1[list_plot_1[i//2 + i % 2]].update_layout( height=350, width=400) dicc_tit_1[list_plot_1[i//2 + i % 2]].layout.update( title_text=list_plot_1[i//2 + i % 2], xaxis_rangeslider_visible=False, margin_b=20, margin_r=20, margin_l=20, legend=dict(orientation="h", yanchor="bottom", y=1.0, xanchor="right", x=1)) col1.plotly_chart(dicc_tit_1[list_plot_1[i//2 + i % 2]], use_container_width=True) else: list_plot_2 = list(dicc_tit_2.keys()) for k in range(len(ma_p)): if k == 0: dicc_tit_2[list_plot_2[i//2]].add_trace( go.Scatter(x=df.index, y=df['Indice'], line=dict( color=colors2[len(ma_p) + 1]), name='Indice'), row=indices[m] + 1, col=1) dicc_tit_2[list_plot_2[i//2]].add_trace( go.Scatter(x=df.index, y=df['MA_' + str(ma_p[k])], line=dict(color=colors2[k]), name='MA_' + str(ma_p[k])), row=indices[m] + 1, col=1) dicc_tit_2[list_plot_2[i//2]].update_layout( height=350, width=400) dicc_tit_2[list_plot_2[i//2]].layout.update( title_text=list_plot_2[i//2], xaxis_rangeslider_visible=False, margin_b=20, margin_r=20, margin_l=20, legend=dict(orientation="h", yanchor="bottom", y=1.0, xanchor="right", x=1)) col2.plotly_chart(dicc_tit_2[list_plot_2[i//2]], use_container_width=True) else: country = countries[0] titles = [str(country) + ' - ' + str(select_sector)] fig1 = make_subplots(rows=len(titles), cols=1, subplot_titles=titles) df = (aux_rr[country + '-' + select_sector] + 1).cumprod() - 1 df_mm = pd.DataFrame( {p: df.rolling(p, min_periods=1).mean() for p in ma_p}) df = df.to_frame() df[[f'MA_{p}' for p in ma_p]] = df_mm df = df.loc[df.index >= start] df.rename(columns={country + '-' + select_sector: 'Indice'}, inplace=True) df = df - df['Indice'][0] for k in range(len(ma_p)): fig1.add_trace(go.Scatter(x=df.index, y=df['MA_' + str(ma_p[k])], line=dict(color=colors2[k]), name='MA_' + str(ma_p[k]), showlegend=True), row=1, col=1) fig1.add_trace(go.Scatter(x=df.index, y=df['Indice'], line=dict(color=colors2[len(ma_p) + 1]), name='Indice', showlegend=True), row=1, col=1) fig1.update_layout(height=200, width=400, margin_b=0, margin_t=0, margin_r=0, margin_l=0) st.plotly_chart(fig1, use_container_width=True) def difusion(): """ Despliega un formulario de streamlit y grafica lo indicado por el usuario. :return: None """ start = '2017' form_dif = st.form("Difusión") countries = form_dif.multiselect('¿Qué países quiere visualizar?', ('Todos', 'Argentina', 'Brazil', 'Chile', 'Colombia', 'Mexico', 'Peru')) if 'Todos' in countries: countries = ['Argentina', 'Brazil', 'Chile', 'Colombia', 'Mexico', 'Peru'] update_button = form_dif.form_submit_button("Actualizar datos") if update_button: update_data() accept = form_dif.form_submit_button('Visualizar') criteria = 'LV1' st.write("### Está visualizando: Difusión") colores = list(colores_corporativos().values()) colores2 = [] for i in range(len(colores)): colores2.append("rgb" + str(colores[i])) if accept: today = str(date.today()) # Acá cargamos los datos necesarios data = data_request(today, start, countries) data_dict = data[0] company_db = data[1] country_sector = data[2] st.write('Data desde ' + str(date.today() - timedelta(365)) + ' hasta ' + str( data_dict['IQ_CLOSEPRICE_ADJ'].index[-1].date())) rel_rets, cs_ids, w_hist, bm_dict = dictionaries_maker(start, countries, country_sector, criteria, company_db, data_dict) p_list = [1, 3, 12] prices = data_dict['IQ_CLOSEPRICE_ADJ'].ffill() mm_countries = countries start = datetime.today() - timedelta(365) fig_mm = make_subplots(specs=[[{"secondary_y": True}]]*len(mm_countries), subplot_titles=mm_countries, rows=len(mm_countries), cols=1) for i, c in enumerate(mm_countries): univ = univ_request(company_db, c) univ = list(set(univ) & set(prices.columns)) prices_c = prices[univ].iloc[-500:] mm_sum_df = sum([prices_c.rolling(p * 20).apply(mm_eval) for p in p_list]) mm_sum_df = mm_sum_df.iloc[-252:].dropna(how='all') bull = (mm_sum_df == len(p_list)).sum(1) / mm_sum_df.notna().sum(1) bear = (mm_sum_df == 0).sum(1) / mm_sum_df.notna().sum(1) delta = (bull - bear).to_frame() delta.columns = [f'Bull-Bear {c}'] bm_rets = bm_dict[c] delta['aux'] = bm_rets delta[f'{c} Index'] = (1 + delta['aux']).cumprod() bull = bull.reindex(pd.to_datetime(bull.index)) bear = bear.reindex(pd.to_datetime(bear.index)) delta = delta.reindex(pd.to_datetime(delta.index)) bull = bull.loc[bull.index >= start] bear = bear.loc[bear.index >= start] delta = delta.loc[delta.index >= start] # Bull fig_mm.add_trace(go.Scatter(x=bull.index, y=bull.array, name='Bull', line=dict(color=colores2[5]), showlegend=(i == 0)), secondary_y=False, row=i + 1, col=1) # Bear fig_mm.add_trace(go.Scatter(x=bear.index, y=bear.array, name='Bear', line=dict(color=colores2[0]), showlegend=(i == 0)), secondary_y=False, row=i + 1, col=1) # Bull-Bear fig_mm.add_trace( go.Scatter(x=delta.index, y=delta[f'Bull-Bear {c}'], name='Bull - Bear', line=dict(color=colores2[6]), showlegend=(i == 0)), row=i + 1, col=1) # Indice fig_mm.add_trace(go.Scatter(x=delta.index, y=delta[f'{c} Index'], name='Index ', line=dict(color='black'), showlegend=(i == 0)), secondary_y=True, row=i + 1, col=1) fig_mm.update_yaxes(title_text="Valor", secondary_y=False, row=i + 1, col=1) fig_mm.update_layout(yaxis1={'tickformat': ',.0%'}) fig_mm.update_yaxes(title_text="Indice", secondary_y=True, row=i + 1, col=1) with st.container(): fig_mm.update_layout(height=400 * len(mm_countries)) st.plotly_chart(fig_mm, use_container_width=True)