diff --git a/data/in/demo.xlsx b/data/in/demo.xlsx index fad93d3..461036d 100644 Binary files a/data/in/demo.xlsx and b/data/in/demo.xlsx differ diff --git a/src/aggregation.py b/src/aggregation.py index 2136d48..6a1b1cd 100644 --- a/src/aggregation.py +++ b/src/aggregation.py @@ -114,10 +114,8 @@ def get_portfolio_pivot( .sum() .reset_index() ) - df_pivot["weight_pf"] = ( - (100 * df_pivot["position_value"].div(pf_actual_value)).astype(float).round(1) - ) - df_pivot["position_value"] = df_pivot["position_value"].astype(float).round(1) + df_pivot["weight_pf"] = 100 * df_pivot["position_value"].div(pf_actual_value) + df_pivot["position_value"] = df_pivot["position_value"] return df_pivot @@ -128,8 +126,6 @@ def get_pnl_by_asset_class( group_by: Literal["asset_class", "macro_asset_class"], ) -> pd.DataFrame: df_pnl = df.merge(df_dimensions, how="left", on="ticker_yf") - df_pnl["pnl"] = np.round( - ((df_pnl["price"] - df_pnl["dca"]) * df_pnl["shares"]).astype(float), 1 - ) + df_pnl["pnl"] = ((df_pnl["price"] - df_pnl["dca"]) * df_pnl["shares"]).astype(float) df_pnl = df_pnl.groupby(group_by)["pnl"].sum().reset_index().sort_values([group_by]) return df_pnl diff --git "a/src/pages/1_\360\237\216\257_Asset_Allocation_&_PnL.py" "b/src/pages/1_\360\237\216\257_Asset_Allocation_&_PnL.py" index f88276d..87b9d2b 100644 --- "a/src/pages/1_\360\237\216\257_Asset_Allocation_&_PnL.py" +++ "b/src/pages/1_\360\237\216\257_Asset_Allocation_&_PnL.py" @@ -51,18 +51,15 @@ consider_fees = st.checkbox("Take fees into account") pf_actual_value = (df_j["shares"] * df_j["price"]).sum() -if consider_fees: - pnl = pf_actual_value - expense - fees - pnl_perc = 100 * (pf_actual_value - expense - fees) / expense -else: - pnl = pf_actual_value - expense - pnl_perc = 100 * (pf_actual_value - expense) / expense +total_expense = expense + fees if consider_fees else expense +pnl = pf_actual_value - total_expense +pnl_perc = 100 * pnl / total_expense sign = "+" if pnl >= 0 else "" col_l.metric( label="Actual Portfolio Value", - value=f"{pf_actual_value: .1f} €", - delta=f"{pnl: .1f} € ({sign}{pnl_perc: .1f}%)", + value=f"{pf_actual_value: ,.1f} €", + delta=f"{pnl: ,.1f} € ({sign}{pnl_perc: .1f}%)", ) df_j["position_value"] = df_j["shares"] * df_j["price"] @@ -83,6 +80,7 @@ group_by = st.radio( label="Aggregate by:", options=["Macro Asset Classes", "Asset Classes", "Tickers"], + index=1, horizontal=True, ) df_pivot_ = get_portfolio_pivot( @@ -91,18 +89,24 @@ pf_actual_value=pf_actual_value, aggregation_level=DICT_GROUPBY_LEVELS[group_by], ) - df_pivot_.index += 1 - st.table( + st.dataframe( df_pivot_.rename( columns={ "macro_asset_class": "Macro Asset Class", "asset_class": "Asset Class", "ticker_yf": "Ticker", "name": "Name", - "position_value": "Position Value (€)", - "weight_pf": "Weight (%)", + "position_value": "Position Value", + "weight_pf": "Weight", + } + ).style.format( + { + "Position Value": "{:,.1f} €", + "Weight": "{:,.1f}%", } - ).style.format(precision=1) + ), + use_container_width=True, + hide_index=True, ) st.markdown("***") diff --git "a/src/pages/2_\360\237\223\210_Return_Analysis.py" "b/src/pages/2_\360\237\223\210_Return_Analysis.py" index 56793f6..9d27dac 100644 --- "a/src/pages/2_\360\237\223\210_Return_Analysis.py" +++ "b/src/pages/2_\360\237\223\210_Return_Analysis.py" @@ -28,7 +28,12 @@ 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_n_shares = ( + df_transactions.groupby("ticker_yf") + .agg(n_shares=("shares", "sum")) + .sort_values("n_shares", ascending=False) +) +ticker_list = df_n_shares.loc[~(df_n_shares == 0).all(axis=1)].index.unique().to_list() df_common_history = get_max_common_history(ticker_list=ticker_list) st.markdown("## Global settings") @@ -79,6 +84,7 @@ df_rets = get_period_returns( df=df_common_history.loc[first_day:last_day, :], df_registry=df_registry, + tickers_to_evaluate=ticker_list, period=DICT_FREQ_RESAMPLE[freq], level=DICT_GROUPBY_LEVELS[level], ) @@ -97,7 +103,7 @@ cols = st.multiselect( f"Choose the {level.lower()} to display:", options=default_objs, - default=default_objs[1], + default=default_objs[0], key="sel_lev_1", ) @@ -118,7 +124,7 @@ cols = col_l_lw.multiselect( f"Choose the {level.lower()} to display:", options=default_objs, - default=default_objs[1], + default=default_objs[0], key="sel_lev_2", ) @@ -132,6 +138,7 @@ df_roll_ret = get_rolling_returns( df_prices=df_common_history.loc[first_day:last_day, :].ffill(), df_registry=df_registry, + tickers_to_evaluate=ticker_list, level=DICT_GROUPBY_LEVELS[level], window=window, )[cols] diff --git "a/src/pages/3_\342\232\240\357\270\217_Risk_Analysis.py" "b/src/pages/3_\342\232\240\357\270\217_Risk_Analysis.py" index 8b86a98..9fa67e7 100644 --- "a/src/pages/3_\342\232\240\357\270\217_Risk_Analysis.py" +++ "b/src/pages/3_\342\232\240\357\270\217_Risk_Analysis.py" @@ -29,7 +29,12 @@ 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_n_shares = ( + df_transactions.groupby("ticker_yf") + .agg(n_shares=("shares", "sum")) + .sort_values("n_shares", ascending=False) +) +ticker_list = df_n_shares.loc[~(df_n_shares == 0).all(axis=1)].index.unique().to_list() df_common_history = get_max_common_history(ticker_list=ticker_list) st.markdown("## Global settings") @@ -73,6 +78,7 @@ df_rets = get_period_returns( df=df_common_history.loc[first_day:last_day, :], df_registry=df_registry, + tickers_to_evaluate=ticker_list, period=DICT_FREQ_RESAMPLE[freq], level=DICT_GROUPBY_LEVELS[level], ) @@ -81,7 +87,7 @@ cols = st.multiselect( f"Choose the {level.lower()} to display:", options=default_objs, - default=default_objs[1], + default=default_objs[0], key="sel_lev_3", ) diff --git a/src/plot.py b/src/plot.py index 95bfdfe..b993465 100644 --- a/src/plot.py +++ b/src/plot.py @@ -40,7 +40,7 @@ def plot_pnl_by_asset_class( hoverinfo="skip", ) ) - fig.update_traces(texttemplate="%{y:.1f}") + fig.update_traces(texttemplate="%{y:,.1f} €") fig.update_layout( autosize=False, height=550, diff --git a/src/returns.py b/src/returns.py index 849a55a..e8bb908 100644 --- a/src/returns.py +++ b/src/returns.py @@ -11,9 +11,12 @@ def get_period_returns( df: pd.DataFrame, df_registry: pd.DataFrame, + tickers_to_evaluate: list[str], period: Literal["Y", "Q", "M", "W", None], level: Literal["ticker", "asset_class", "macro_asset_class"], ): + # Filtra solo i ticker effettivamente in portafoglio + df_registry = df_registry[df_registry["ticker_yf"].isin(tickers_to_evaluate)] # Calcolo il ritorno df_rets = df.pct_change()[1:] # Se il periodo è None, la frequenza è giornaliera @@ -29,7 +32,6 @@ def get_period_returns( cols_to_sum = df_registry[df_registry[level].eq(class_)][ "ticker_yf" ].to_list() - df_rets_classes[class_] = df_rets[cols_to_sum].sum(axis=1) return df_rets_classes # Se il periodo non è None, faccio resampling al periodo desiderato @@ -52,9 +54,12 @@ def get_period_returns( def get_rolling_returns( df_prices: pd.DataFrame, df_registry: pd.DataFrame, + tickers_to_evaluate: list[str], window: int, level: Literal["ticker", "asset_class", "macro_asset_class"], ) -> pd.DataFrame: + # Filtra solo i ticker effettivamente in portafoglio + df_registry = df_registry[df_registry["ticker_yf"].isin(tickers_to_evaluate)] df_log_ret = np.log(df_prices.div(df_prices.shift(1))) df_roll_log_ret = df_log_ret.rolling(window=window).sum() diff --git a/src/var.py b/src/var.py index d82f948..ffbb8b0 100644 --- a/src/var.py +++ b/src/var.py @@ -9,7 +9,7 @@ os.path.join(*split_script_running_path[0 : len(split_script_running_path) - 1]) ) -APP_VERSION = "0.2.0" +APP_VERSION = "0.2.1" # Data/images