From 958755dcec1f86115df783a21b7af6887b35ce8b Mon Sep 17 00:00:00 2001 From: Vincenzo Date: Sun, 23 Jul 2023 17:05:35 +0200 Subject: [PATCH 1/7] added daily variation in wealth plot --- src/plot.py | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/src/plot.py b/src/plot.py index 9b88d0f..596599e 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}€ %{customdata:,.0f}€" ) - fig.update_traces(hovertemplate="%{x}: %{y:,.0f}€") fig.update_layout( autosize=False, height=550, @@ -70,3 +70,34 @@ def plot_wealth(df: pd.DataFrame) -> go.Figure: showlegend=False, ) return fig + + +def plot_correlation_map(df: pd.DataFrame, lower_triangle_only: bool = False): + if lower_triangle_only: + mask = np.triu(np.ones_like(df, dtype=bool)) + + cuts = [-1, -0.3, 0.3, 0.7, 1] + colors = ["green", "darkblue", "darkorange", "red", "darkred"] + 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 From fa1c79beb07d28cdb5c3b9ac1146684161f7314a Mon Sep 17 00:00:00 2001 From: Vincenzo Date: Sun, 23 Jul 2023 17:05:47 +0200 Subject: [PATCH 2/7] added correlation heatmap --- ...200\215\360\237\224\254_Advanced_Stats.py" | 29 ++++++++++++++----- src/utils.py | 9 ++++-- 2 files changed, 28 insertions(+), 10 deletions(-) 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..7a6825c 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" @@ -3,15 +3,18 @@ from var import ( GLOBAL_STREAMLIT_STYLE, # PLT_CONFIG, - # PLT_CONFIG_NO_LOGO, + PLT_CONFIG_NO_LOGO, FAVICON, ) -# from utils import ( -# sharpe_ratio, -# get_risk_free_rate_last_value, -# get_risk_free_rate_history, -# ) +from utils import ( + get_max_common_history, + # 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,9 +24,19 @@ ) 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) + +fig = plot_correlation_map(df=df_common_history.corr(), lower_triangle_only=True) +st.plotly_chart(fig, use_container_width=True, config=PLT_CONFIG_NO_LOGO) # weights = [ # df_pivot[df_pivot["ticker_yf"].eq(x_)]["weight_pf"].values[0] diff --git a/src/utils.py b/src/utils.py index 60c6180..43c7dc8 100644 --- a/src/utils.py +++ b/src/utils.py @@ -174,7 +174,10 @@ def get_full_price_history(ticker_list: List[str]) -> Dict: for i, ticker_ in zip(range(len(ticker_list)), ticker_list): ticker_data = yf.Ticker(ticker_) - df_history[ticker_] = ticker_data.history(period="max", interval="1d",)[ + df_history[ticker_] = ticker_data.history( + period="max", + interval="1d", + )[ "Close" ].rename(ticker_) @@ -274,12 +277,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 From 3869ecbd7a1704269d31b4ff515c3b7ac32698fd Mon Sep 17 00:00:00 2001 From: Vincenzo Date: Sun, 23 Jul 2023 23:31:18 +0200 Subject: [PATCH 3/7] fix correlation map colorscale --- src/plot.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/plot.py b/src/plot.py index 596599e..488d327 100644 --- a/src/plot.py +++ b/src/plot.py @@ -56,7 +56,7 @@ def plot_wealth(df: pd.DataFrame) -> go.Figure: data_frame=df, x=df.index, y="ap_daily_value", custom_data=["diff_previous_day"] ) fig.update_traces( - hovertemplate="%{x}: %{y:,.0f}€ %{customdata:,.0f}€" + hovertemplate="%{x}: %{y:,.0f}€ Gain/Loss on previous day: %{customdata:,.0f}€" ) fig.update_layout( autosize=False, @@ -72,12 +72,23 @@ def plot_wealth(df: pd.DataFrame) -> go.Figure: return fig -def plot_correlation_map(df: pd.DataFrame, lower_triangle_only: bool = False): +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)) - cuts = [-1, -0.3, 0.3, 0.7, 1] - colors = ["green", "darkblue", "darkorange", "red", "darkred"] + 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( From 42beda795e3004314fa4a631d4ff83b8388cfc0e Mon Sep 17 00:00:00 2001 From: Vincenzo Date: Sun, 23 Jul 2023 23:31:40 +0200 Subject: [PATCH 4/7] added time-slice filters --- ...200\215\360\237\224\254_Advanced_Stats.py" | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) 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 7a6825c..fc67728 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" @@ -9,9 +9,8 @@ from utils import ( get_max_common_history, - # sharpe_ratio, - # get_risk_free_rate_last_value, - # get_risk_free_rate_history, + get_wealth_history, + write_disclaimer, ) from plot import plot_correlation_map @@ -35,14 +34,43 @@ ticker_list = df_transactions["ticker_yf"].unique().tolist() df_common_history = get_max_common_history(ticker_list=ticker_list) -fig = plot_correlation_map(df=df_common_history.corr(), lower_triangle_only=True) +st.markdown("## Correlation Matrix") + +col_l, col_c, col_r = st.columns([1, 0.15, 0.5], gap="small") + +first_transaction = df_transactions["transaction_date"].sort_values().values[0] +first_day, last_day = col_l.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", +) + +enhance_corr = col_r.radio( + "Kind of correlation to enhance:", + options=["Positive", "Null", "Negative"], + horizontal=True, +) + +fig = plot_correlation_map( + df=df_common_history.loc[first_day:last_day, :].corr(), + enhance_correlation=enhance_corr.lower(), + lower_triangle_only=True, +) st.plotly_chart(fig, use_container_width=True, config=PLT_CONFIG_NO_LOGO) -# 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)) +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) @@ -59,3 +87,5 @@ # ) # st.write(sr) + +write_disclaimer() From 3a46b47b3e5f99e5a9f38598986864ad5be86442 Mon Sep 17 00:00:00 2001 From: Vincenzo Date: Wed, 26 Jul 2023 22:51:45 +0200 Subject: [PATCH 5/7] fixed correlation map (now considering returns) --- ...200\215\360\237\224\254_Advanced_Stats.py" | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) 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 fc67728..a813cfe 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" @@ -34,7 +34,7 @@ ticker_list = df_transactions["ticker_yf"].unique().tolist() df_common_history = get_max_common_history(ticker_list=ticker_list) -st.markdown("## Correlation Matrix") +st.markdown("## Correlation of daily returns") col_l, col_c, col_r = st.columns([1, 0.15, 0.5], gap="small") @@ -53,8 +53,28 @@ horizontal=True, ) +df_daily_rets = df_common_history.loc[first_day:last_day, :].pct_change()[1:] + +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) + +# TODO Crea un selettore e una fuzione +import pandas as pd + +col_ = "asset_class" +macs = df_registry[col_].unique() +df_daily_rets_asset_class = pd.DataFrame(columns=macs) +for mac_ in macs: + cols_to_sum = df_registry[df_registry[col_].eq(mac_)]["ticker_yf"].to_list() + + df_daily_rets_asset_class[mac_] = df_daily_rets[cols_to_sum].sum(axis=1) + fig = plot_correlation_map( - df=df_common_history.loc[first_day:last_day, :].corr(), + df=df_daily_rets_asset_class.corr(), enhance_correlation=enhance_corr.lower(), lower_triangle_only=True, ) From 74a31d84529712c7e73a6ac7b3454e83a1141015 Mon Sep 17 00:00:00 2001 From: Vincenzo_Ventriglia Date: Tue, 1 Aug 2023 19:06:19 +0200 Subject: [PATCH 6/7] daily-returns correlation by asset class, code refactoring --- "src/pages/1_\360\237\223\210_Basic_Stats.py" | 8 +-- ...200\215\360\237\224\254_Advanced_Stats.py" | 58 ++++++++----------- src/utils.py | 27 +++++++-- src/var.py | 5 ++ 4 files changed, 54 insertions(+), 44 deletions(-) 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 a813cfe..f3e7391 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,16 +1,11 @@ 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, - get_wealth_history, write_disclaimer, + get_daily_returns, ) from plot import plot_correlation_map @@ -36,51 +31,46 @@ st.markdown("## Correlation of daily returns") -col_l, col_c, col_r = st.columns([1, 0.15, 0.5], gap="small") +col_l_up, col_r_up = st.columns([1, 1], gap="small") -first_transaction = df_transactions["transaction_date"].sort_values().values[0] -first_day, last_day = col_l.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", +level = col_l_up.radio( + label="Aggregate by:", + options=["Macro Asset Classes", "Asset Classes", "Ticker"], + horizontal=True, ) -enhance_corr = col_r.radio( +enhance_corr = col_r_up.radio( "Kind of correlation to enhance:", options=["Positive", "Null", "Negative"], horizontal=True, ) -df_daily_rets = df_common_history.loc[first_day:last_day, :].pct_change()[1:] +st.markdown("") +col_l_mid, col_r_mid = st.columns([1, 0.3], gap="small") -fig = plot_correlation_map( - df=df_daily_rets.corr(), - enhance_correlation=enhance_corr.lower(), - lower_triangle_only=True, +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", ) -st.plotly_chart(fig, use_container_width=True, config=PLT_CONFIG_NO_LOGO) - -# TODO Crea un selettore e una fuzione -import pandas as pd - -col_ = "asset_class" -macs = df_registry[col_].unique() -df_daily_rets_asset_class = pd.DataFrame(columns=macs) -for mac_ in macs: - cols_to_sum = df_registry[df_registry[col_].eq(mac_)]["ticker_yf"].to_list() - df_daily_rets_asset_class[mac_] = df_daily_rets[cols_to_sum].sum(axis=1) +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_asset_class.corr(), + 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") +# st.markdown("## Distribution of Daily Returns") # diff_prev_day = get_wealth_history( # df_transactions=df_transactions, diff --git a/src/utils.py b/src/utils.py index 43c7dc8..3531ef5 100644 --- a/src/utils.py +++ b/src/utils.py @@ -174,10 +174,7 @@ def get_full_price_history(ticker_list: List[str]) -> Dict: for i, ticker_ in zip(range(len(ticker_list)), ticker_list): ticker_data = yf.Ticker(ticker_) - df_history[ticker_] = ticker_data.history( - period="max", - interval="1d", - )[ + df_history[ticker_] = ticker_data.history(period="max", interval="1d",)[ "Close" ].rename(ticker_) @@ -346,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..191649f 100644 --- a/src/var.py +++ b/src/var.py @@ -44,3 +44,8 @@ # Others TRADING_DAYS_YEAR = 252 +DICT_GROUPBY_LEVELS = { + "Macro Asset Classes": "macro_asset_class", + "Asset Classes": "asset_class", + "Ticker": "ticker", +} From 4e7f902c3b8444fe9dcd8b2ce2965a6dd876e611 Mon Sep 17 00:00:00 2001 From: Vincenzo_Ventriglia Date: Tue, 1 Aug 2023 19:17:21 +0200 Subject: [PATCH 7/7] preparing release v0.1.1 --- ...0\237\221\251\342\200\215\360\237\224\254_Advanced_Stats.py" | 1 + src/var.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) 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 f3e7391..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" @@ -37,6 +37,7 @@ label="Aggregate by:", options=["Macro Asset Classes", "Asset Classes", "Ticker"], horizontal=True, + index=2, ) enhance_corr = col_r_up.radio( diff --git a/src/var.py b/src/var.py index 191649f..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