diff --git "a/src/pages/1_\360\237\223\210_Basic_Stats.py" "b/src/pages/1_\360\237\223\210_Basic_Stats.py" index fe9e18d..60885bf 100644 --- "a/src/pages/1_\360\237\223\210_Basic_Stats.py" +++ "b/src/pages/1_\360\237\223\210_Basic_Stats.py" @@ -5,6 +5,7 @@ PLT_CONFIG, PLT_CONFIG_NO_LOGO, FAVICON, + DICT_GROUPBY_LEVELS, ) from utils import ( aggregate_by_ticker, @@ -86,16 +87,11 @@ options=["Macro Asset Classes", "Asset Classes", "Ticker"], horizontal=True, ) - dict_group_by = { - "Macro Asset Classes": "macro_asset_class", - "Asset Classes": "asset_class", - "Ticker": "ticker", - } df_pivot_ = get_portfolio_pivot( df=df_j, df_dimensions=df_anagrafica, pf_actual_value=pf_actual_value, - aggregation_level=dict_group_by[group_by], + aggregation_level=DICT_GROUPBY_LEVELS[group_by], ) df_pivot_.index += 1 st.table( diff --git "a/src/pages/2_\360\237\221\251\342\200\215\360\237\224\254_Advanced_Stats.py" "b/src/pages/2_\360\237\221\251\342\200\215\360\237\224\254_Advanced_Stats.py" index 8962143..330e8af 100644 --- "a/src/pages/2_\360\237\221\251\342\200\215\360\237\224\254_Advanced_Stats.py" +++ "b/src/pages/2_\360\237\221\251\342\200\215\360\237\224\254_Advanced_Stats.py" @@ -1,17 +1,14 @@ import streamlit as st -from var import ( - GLOBAL_STREAMLIT_STYLE, - # PLT_CONFIG, - # PLT_CONFIG_NO_LOGO, - FAVICON, +from var import GLOBAL_STREAMLIT_STYLE, PLT_CONFIG_NO_LOGO, FAVICON, DICT_GROUPBY_LEVELS + +from utils import ( + get_max_common_history, + write_disclaimer, + get_daily_returns, ) -# from utils import ( -# sharpe_ratio, -# get_risk_free_rate_last_value, -# get_risk_free_rate_history, -# ) +from plot import plot_correlation_map st.set_page_config( page_title="PFN | Advanced Stats", @@ -21,15 +18,70 @@ ) st.markdown(GLOBAL_STREAMLIT_STYLE, unsafe_allow_html=True) -st.warning("Work in progress! Please, come back later", icon="🚧") -# df_common_history = get_max_common_history(ticker_list=ticker_list) +if "data" in st.session_state: + df_transactions = st.session_state["data"] + df_registry = st.session_state["dimensions"] +else: + st.error("Oops... there's nothing to display. Go through 🏠 first to load the data") + st.stop() + +ticker_list = df_transactions["ticker_yf"].unique().tolist() +df_common_history = get_max_common_history(ticker_list=ticker_list) + +st.markdown("## Correlation of daily returns") + +col_l_up, col_r_up = st.columns([1, 1], gap="small") + +level = col_l_up.radio( + label="Aggregate by:", + options=["Macro Asset Classes", "Asset Classes", "Ticker"], + horizontal=True, + index=2, +) + +enhance_corr = col_r_up.radio( + "Kind of correlation to enhance:", + options=["Positive", "Null", "Negative"], + horizontal=True, +) + +st.markdown("") +col_l_mid, col_r_mid = st.columns([1, 0.3], gap="small") -# weights = [ -# df_pivot[df_pivot["ticker_yf"].eq(x_)]["weight_pf"].values[0] -# for x_ in df_common_history.columns -# ] -# weighted_average = pd.Series(np.average(df_common_history, weights=weights, axis=1)) +first_transaction = df_transactions["transaction_date"].sort_values().values[0] +first_day, last_day = col_l_mid.select_slider( + "Select a time slice:", + options=df_common_history.index, + value=[first_transaction, df_common_history.index[-1]], + format_func=lambda value: str(value)[:10], + label_visibility="collapsed", +) + +df_daily_rets = get_daily_returns( + df=df_common_history.loc[first_day:last_day, :], + df_registry=df_registry, + level=DICT_GROUPBY_LEVELS[level], +) + +fig = plot_correlation_map( + df=df_daily_rets.corr(), + enhance_correlation=enhance_corr.lower(), + lower_triangle_only=True, +) +st.plotly_chart(fig, use_container_width=True, config=PLT_CONFIG_NO_LOGO) + +# st.markdown("## Distribution of Daily Returns") + +# diff_prev_day = get_wealth_history( +# df_transactions=df_transactions, +# df_prices=get_max_common_history(ticker_list=ticker_list), +# )['diff_previous_day'] + +# import plotly.express as px +# st.plotly_chart(px.histogram(diff_prev_day), use_container_width=True, config=PLT_CONFIG_NO_LOGO) + +################################# # returns = np.log(weighted_average.div(weighted_average.shift(1))).fillna(0) @@ -46,3 +98,5 @@ # ) # st.write(sr) + +write_disclaimer() diff --git a/src/plot.py b/src/plot.py index 9b88d0f..488d327 100644 --- a/src/plot.py +++ b/src/plot.py @@ -53,11 +53,11 @@ def plot_pnl_by_asset_class( def plot_wealth(df: pd.DataFrame) -> go.Figure: fig = px.area( - data_frame=df, - x=df.index, - y="ap_daily_value", + data_frame=df, x=df.index, y="ap_daily_value", custom_data=["diff_previous_day"] + ) + fig.update_traces( + hovertemplate="%{x}: %{y:,.0f}€ Gain/Loss on previous day: %{customdata:,.0f}€" ) - fig.update_traces(hovertemplate="%{x}: %{y:,.0f}€") fig.update_layout( autosize=False, height=550, @@ -70,3 +70,45 @@ def plot_wealth(df: pd.DataFrame) -> go.Figure: showlegend=False, ) return fig + + +def plot_correlation_map( + df: pd.DataFrame, + enhance_correlation: Literal["positive", "null", "negative"], + lower_triangle_only: bool = False, +): + if lower_triangle_only: + mask = np.triu(np.ones_like(df, dtype=bool)) + + if enhance_correlation == "positive": + cuts = [-1, 0, 1] + colors = ["white", "white", "darkred"] + elif enhance_correlation == "null": + cuts = [-1, -0.3, 0, 0.3, 1] + colors = ["white", "white", "darkgreen", "white", "white"] + elif enhance_correlation == "negative": + cuts = [-1, 0, 1] + colors = ["darkblue", "white", "white"] + colorscale = [[(cut_ + 1) / 2, col_] for cut_, col_ in zip(cuts, colors)] + + fig = go.Figure( + go.Heatmap( + z=df.mask(mask) if lower_triangle_only else df, + x=df.columns, + y=df.columns, + colorscale=colorscale, + zmin=-1, + zmax=1, + ) + ) + fig.update_traces(hovertemplate="%{z:.2f} ") + fig.update_layout( + height=500, + hoverlabel_font_size=PLT_FONT_SIZE, + margin=dict(l=0, r=0, t=30, b=0), + yaxis=dict(autorange="reversed", showgrid=False), + # paper_bgcolor="rgba(0,0,0,0)", + # plot_bgcolor="rgba(0,0,0,0)", + ) + + return fig diff --git a/src/utils.py b/src/utils.py index 60c6180..3531ef5 100644 --- a/src/utils.py +++ b/src/utils.py @@ -274,12 +274,14 @@ def get_wealth_history( df_asset_allocation.loc[data, ticker] += total_shares df_asset_allocation = df_asset_allocation.cumsum() - df_wealth = ( + df_wealth = pd.DataFrame( df_asset_allocation.multiply(df_prices.loc[begin_date:]) .fillna(method="ffill") .sum(axis=1) .rename("ap_daily_value") ) + df_wealth["diff_previous_day"] = df_wealth.diff() + return df_wealth @@ -341,3 +343,25 @@ def write_disclaimer() -> None: ', unsafe_allow_html=True, ) + + +@st.cache_data(ttl=CACHE_EXPIRE_SECONDS, show_spinner=False) +def get_daily_returns( + df: pd.DataFrame, + df_registry: pd.DataFrame, + level: Literal["ticker", "asset_class", "macro_asset_class"], +): + df_daily_rets = df.pct_change()[1:] + + if level == "ticker": + return df_daily_rets + else: + classes = df_registry[level].unique() + df_daily_rets_classes = pd.DataFrame(columns=classes) + for class_ in classes: + cols_to_sum = df_registry[df_registry[level].eq(class_)][ + "ticker_yf" + ].to_list() + + df_daily_rets_classes[class_] = df_daily_rets[cols_to_sum].sum(axis=1) + return df_daily_rets_classes diff --git a/src/var.py b/src/var.py index b20e76e..8f5fb52 100644 --- a/src/var.py +++ b/src/var.py @@ -8,7 +8,7 @@ os.path.join(*split_script_running_path[0 : len(split_script_running_path) - 1]) ) -APP_VERSION = "0.1.0" +APP_VERSION = "0.1.1" # Data/images @@ -44,3 +44,8 @@ # Others TRADING_DAYS_YEAR = 252 +DICT_GROUPBY_LEVELS = { + "Macro Asset Classes": "macro_asset_class", + "Asset Classes": "asset_class", + "Ticker": "ticker", +}