Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/models/portfolio_csv_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -987,10 +987,13 @@ def cumulative_split_factor(ticker, from_date, to_date):

sec = self.securities.get(ticker)

first_purchase = first_purchase_dates.get(ticker)

results.append({
'ticker': ticker,
'shares': qty,
'currency': currency,
'first_purchase_date': pd.Timestamp(first_purchase).date() if first_purchase else None,

# REPORTING COLUMNS (Native)
'current_price': current_price,
Expand Down
20 changes: 10 additions & 10 deletions src/views/holdings_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ def render_holdings_table(holdings_data: pd.DataFrame):
display_data = holdings_data.copy()

if not holdings_data.empty:
# Select columns to show (keep numeric types for proper sorting)
# These keys must match the output of create_table_holdings in portfolio_csv_builder.py
# Select columns to show
cols = [
'ticker',
'currency',
'shares',
'shares',
'first_purchase_date', # ✅ ADDED COLUMN
'holding_weight',
'current_price',
'avg_price',
Expand All @@ -54,6 +54,7 @@ def render_holdings_table(holdings_data: pd.DataFrame):
'ticker': 'Ticker',
'currency': 'Currency',
'shares': 'Shares',
'first_purchase_date': 'First Purchase Date', # ✅ RENAME
'holding_weight': 'Weight (%)',
'current_price': 'Price',
'avg_price': 'Avg Cost',
Expand All @@ -65,19 +66,20 @@ def render_holdings_table(holdings_data: pd.DataFrame):
'total_return': 'Total Return ($)',
'total_return_pct': 'Total Return (%)',
'annualized_return_pct': 'Annualized Return (%)',
'sector': 'sector',
'sector': 'Sector',
'asset_class': 'Asset Class',
'status': 'Status'
})

# Use column_config to format while retaining numeric dtype for correct sorting
# Render table
st.dataframe(
df_show,
use_container_width=True,
column_config={
'Ticker': st.column_config.TextColumn('Ticker'),
'Currency': st.column_config.TextColumn('Currency'),
'Shares': st.column_config.NumberColumn('Shares', format='%.2f'),
'First Purchase Date': st.column_config.DateColumn('First Purchase Date'), # ✅ CONFIG
'Weight (%)': st.column_config.NumberColumn('Weight (%)', format='%.2f%%'),
'Price': st.column_config.NumberColumn('Price', format='$%.2f'),
'Avg Cost': st.column_config.NumberColumn('Avg Cost', format='$%.2f'),
Expand All @@ -89,25 +91,23 @@ def render_holdings_table(holdings_data: pd.DataFrame):
'Total Return ($)': st.column_config.NumberColumn('Total Return ($)', format='$%.2f'),
'Total Return (%)': st.column_config.NumberColumn('Total Return (%)', format='%.2f%%'),
'Annualized Return (%)': st.column_config.NumberColumn('Annualized Return (%)', format='%.2f%%'),
'Sector': st.column_config.TextColumn('sector'),
'Sector': st.column_config.TextColumn('Sector'),
'Asset Class': st.column_config.TextColumn('Asset Class'),
'Status': st.column_config.TextColumn('Status'),
},
hide_index=True
)

st.info("💡 **Note**: All values are in the native currency of the asset (CAD or USD). Weights are normalized to portfolio total.")
else:
st.info("No holdings data available")


def render_holdings_summary(holdings_data: pd.DataFrame):
if not holdings_data.empty:
st.subheader("Holdings Summary")
col1, col2, col3, col4 = st.columns(4)

# Calculate summary stats (be careful with mixed currencies - these are rough approximations if not normalized)
# Note: If we want strict correctness, we should sum the 'market_value_cad' if it existed, but we dropped it.
# For simple metrics like count and size distribution, raw numbers are okayish or we can re-derive if needed.

with col1:
st.metric("Total Positions", len(holdings_data))
with col2:
Expand Down