import streamlit as st
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
import textwrap
# Custom Stylesheet Application helper (mirrors app.py custom style)
def apply_custom_style():
st.markdown("""
""", unsafe_allow_html=True)
# Helper function to get badge class based on recommendation
def get_badge_class(rec):
rec_clean = str(rec).lower().strip()
if "strong buy" in rec_clean:
return "badge-strong-buy"
elif "buy" in rec_clean:
return "badge-buy"
elif "hold" in rec_clean:
return "badge-hold"
elif "reduce" in rec_clean:
return "badge-reduce"
else:
return "badge-avoid"
# ----------------------------------------------------
# TECHNICAL REQUIREMENTS: load_data()
# ----------------------------------------------------
@st.cache_data
def load_data():
"""Loads and processes all recommendation evolution datasets."""
summary_path = "data/processed/recommendation_summary.parquet"
evolution_path = "data/processed/recommendation_evolution.parquet"
master_path = "data/processed/investment_intelligence_master.parquet"
summary = pd.read_parquet(summary_path)
evolution = pd.read_parquet(evolution_path)
master = pd.read_parquet(master_path)
# Map 'Avoid' to 'Sell' globally
for df in [summary, evolution, master]:
for col in ["historical_recommendation", "Recommendation", "Recommendation_Final", "investment_signal"]:
if col in df.columns:
df[col] = df[col].replace("Avoid", "Sell")
# Add Sector from master to summary
sector_map = master.set_index("Symbol")["Sector"].to_dict()
summary["Sector"] = summary["Symbol"].map(sector_map).fillna("Unknown")
# Advanced Score Calculations
# 1. Scaled Metrics
min_evol = summary["evolution_score"].min()
max_evol = summary["evolution_score"].max()
if max_evol != min_evol:
summary["scaled_evolution"] = (summary["evolution_score"] - min_evol) / (max_evol - min_evol) * 100
else:
summary["scaled_evolution"] = 50.0
min_conf = summary["confidence_score"].min()
max_conf = summary["confidence_score"].max()
if max_conf != min_conf:
summary["scaled_confidence"] = (summary["confidence_score"] - min_conf) / (max_conf - min_conf) * 100
else:
summary["scaled_confidence"] = 50.0
# Trend Direction Value Mapping (Improving = 100, Stable = 50, Deteriorating = 0)
def map_trend_dir(val):
v = str(val).lower().strip()
if "improving" in v:
return 100.0
elif "deteriorating" in v or "weakening" in v:
return 0.0
else:
return 50.0
summary["trend_value"] = summary["trend_direction"].apply(map_trend_dir)
# A. Recommendation Momentum Score
summary["Recommendation_Momentum_Score"] = (
(summary["scaled_evolution"] * 0.5) +
(summary["scaled_confidence"] * 0.3) +
(summary["trend_value"] * 0.2)
).clip(0.0, 100.0)
# B. Recommendation Quality Score
# Stability Score is already a percentage (93-96), let's keep it raw or scale it to exaggerate differences.
min_stab = summary["stability_score"].min()
max_stab = summary["stability_score"].max()
if max_stab != min_stab:
summary["scaled_stability"] = (summary["stability_score"] - min_stab) / (max_stab - min_stab) * 100
else:
summary["scaled_stability"] = 50.0
summary["Recommendation_Quality_Score"] = (
(summary["scaled_stability"] * 0.4) +
(summary["scaled_confidence"] * 0.4) +
(summary["scaled_evolution"] * 0.2)
).clip(0.0, 100.0)
# C. Analyst Conviction Score
summary["Analyst_Conviction_Score"] = (
(summary["scaled_confidence"] * 0.6) +
(summary["evolution_percentile"] * 0.4)
).clip(0.0, 100.0)
return summary, evolution, master
# ----------------------------------------------------
# TECHNICAL REQUIREMENTS: show_evolution_overview()
# ----------------------------------------------------
def show_evolution_overview(summary, master):
"""Renders the top KPI metrics bar."""
st.markdown('
', unsafe_allow_html=True)
avg_evo = summary["evolution_score"].mean()
improving_count = len(summary[summary["trend_direction"].str.lower().str.strip() == "improving"])
weakening_count = len(summary[summary["trend_direction"].str.lower().str.strip() == "deteriorating"])
avg_stab = summary["stability_score"].mean()
avg_conf = summary["confidence_score"].mean()
# Find Highest Conviction Stock based on Analyst Conviction Score
best_idx = summary["Analyst_Conviction_Score"].idxmax()
highest_conviction_stock = summary.loc[best_idx, "Symbol"]
highest_conviction_val = summary.loc[best_idx, "Analyst_Conviction_Score"]
col1, col2, col3, col4, col5, col6 = st.columns(6)
with col1:
st.metric(label="Avg Evolution Score", value=f"{avg_evo:.3f}")
with col2:
st.metric(label="Improving Stocks", value=f"{improving_count}")
with col3:
st.metric(label="Weakening Stocks", value=f"{weakening_count}")
with col4:
st.metric(label="Avg Stability Score", value=f"{avg_stab:.2f}%")
with col5:
st.metric(label="Avg Confidence", value=f"{avg_conf:.1f}")
with col6:
st.metric(label="Highest Conviction", value=f"{highest_conviction_stock}", delta=f"{highest_conviction_val:.1f} Score")
# ----------------------------------------------------
# TECHNICAL REQUIREMENTS: show_stock_timeline()
# ----------------------------------------------------
def show_stock_timeline(selected_symbol, summary, evolution):
"""Renders the historical timeline chart for a specific selected stock."""
# Filter evolution records for selected stock
stock_evo = evolution[evolution["Symbol"] == selected_symbol].sort_values("Date").reset_index(drop=True)
stock_sum = summary[summary["Symbol"] == selected_symbol].iloc[0]
st.markdown(f"### π Recommendation Timeline for {selected_symbol}")
col1, col2, col3, col4 = st.columns(4)
with col1:
badge = get_badge_class(stock_sum["historical_recommendation"])
st.markdown(f"""
Current Recommendation
{stock_sum["historical_recommendation"]}
""", unsafe_allow_html=True)
with col2:
st.markdown(f"""
Current Confidence
{stock_sum["confidence_score"]:.1f}
""", unsafe_allow_html=True)
with col3:
trend = stock_sum["trend_direction"]
color = "#00E676" if "improving" in trend.lower() else ("#FF3D00" if "deteriorating" in trend.lower() else "#FFA000")
st.markdown(f"""
""", unsafe_allow_html=True)
with col4:
st.markdown(f"""
Evolution Score
{stock_sum["evolution_score"]:.4f}
""", unsafe_allow_html=True)
# Plotly Timeline Chart (Ordered Recommendation Level over Time)
rec_order = ["Sell", "Reduce", "Hold", "Buy", "Strong Buy"]
fig = px.line(
stock_evo,
x="Date",
y="historical_recommendation",
markers=True,
title=f"Historical Recommendation Path of {selected_symbol}",
category_orders={"historical_recommendation": rec_order},
color_discrete_sequence=["#00E676"]
)
fig.update_layout(
template="plotly_dark",
font_family="Outfit",
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
xaxis=dict(showgrid=False),
yaxis=dict(
showgrid=True,
gridcolor="rgba(255,255,255,0.08)",
categoryarray=rec_order
),
height=350,
margin=dict(l=10, r=10, t=50, b=10)
)
fig.update_traces(
line=dict(shape="hv", width=3),
marker=dict(size=8, symbol="circle"),
hovertemplate="Date: %{x}
Recommendation: %{y}"
)
st.plotly_chart(fig, use_container_width=True)
# ----------------------------------------------------
# TECHNICAL REQUIREMENTS: show_confidence_analysis()
# ----------------------------------------------------
def show_confidence_analysis(selected_symbol, summary, evolution):
"""Renders confidence analytics charts and metrics."""
st.markdown('', unsafe_allow_html=True)
# selected stock evolution records
stock_evo = evolution[evolution["Symbol"] == selected_symbol].sort_values("Date").reset_index(drop=True)
avg_c = stock_evo["confidence_score"].mean()
max_c = stock_evo["confidence_score"].max()
min_c = stock_evo["confidence_score"].min()
c_col1, c_col2, c_col3 = st.columns(3)
with c_col1:
st.metric(label="Average Confidence (Stock)", value=f"{avg_c:.1f}")
with c_col2:
st.metric(label="Highest Confidence (Stock)", value=f"{max_c:.1f}")
with c_col3:
st.metric(label="Lowest Confidence (Stock)", value=f"{min_c:.1f}")
col_chart1, col_chart2 = st.columns(2)
with col_chart1:
# Confidence Through Time
fig1 = px.line(
stock_evo,
x="Date",
y="confidence_score",
title=f"Confidence Score Evolution: {selected_symbol}",
color_discrete_sequence=["#00B0FF"]
)
fig1.update_layout(
template="plotly_dark",
font_family="Outfit",
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
xaxis=dict(showgrid=False),
yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)"),
height=300,
margin=dict(l=10, r=10, t=50, b=10)
)
st.plotly_chart(fig1, use_container_width=True)
with col_chart2:
# Confidence Distribution (All stocks)
fig2 = px.histogram(
summary,
x="confidence_score",
nbins=12,
title="System-Wide Confidence Score Distribution",
color_discrete_sequence=["#FFA000"]
)
fig2.update_layout(
template="plotly_dark",
font_family="Outfit",
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
xaxis=dict(showgrid=False, title="Confidence Score"),
yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)"),
bargap=0.05,
height=300,
margin=dict(l=10, r=10, t=50, b=10)
)
st.plotly_chart(fig2, use_container_width=True)
# Confidence vs Recommendation (Scatter Plot)
fig3 = px.scatter(
summary,
x="confidence_score",
y="historical_recommendation",
color="historical_recommendation",
hover_name="Symbol",
title="Confidence Score vs Recommendation Category",
category_orders={"historical_recommendation": ["Sell", "Reduce", "Hold", "Buy", "Strong Buy"]},
color_discrete_map={
"Strong Buy": "#00E676",
"Buy": "#00B0FF",
"Hold": "#FFA000",
"Reduce": "#E91E63",
"Sell": "#F44336"
}
)
fig3.update_layout(
template="plotly_dark",
font_family="Outfit",
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
xaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)", title="Confidence Score"),
yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)"),
height=320,
margin=dict(l=10, r=10, t=50, b=10)
)
st.plotly_chart(fig3, use_container_width=True)
# ----------------------------------------------------
# TECHNICAL REQUIREMENTS: show_upgrade_analysis()
# ----------------------------------------------------
def show_upgrade_analysis(selected_symbol, summary, evolution):
"""Renders upgrade and downgrade timelines and comparisons."""
st.markdown('', unsafe_allow_html=True)
stock_sum = summary[summary["Symbol"] == selected_symbol].iloc[0]
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric(label="Upgrade Count (Stock)", value=f"{stock_sum['upgrade_count']}")
with col2:
st.metric(label="Downgrade Count (Stock)", value=f"{stock_sum['downgrade_count']}")
with col3:
st.metric(label="Net Recommendation Trend", value=f"{stock_sum['net_recommendation_trend']}", delta=int(stock_sum['net_recommendation_trend']))
with col4:
st.metric(label="Evolution Category", value=stock_sum["evolution_category"])
# Filter upgrades / downgrades for visualization
# We can aggregate changes by Month or Date from evolution where change_direction != 'No Change'
changes_df = evolution[evolution["change_direction"].isin(["Upgrade", "Downgrade"])].copy()
changes_df["Month"] = changes_df["Date"].dt.to_period("M").astype(str)
monthly_changes = changes_df.groupby(["Month", "change_direction"]).size().reset_index(name="Count")
col_chart1, col_chart2 = st.columns(2)
with col_chart1:
# Upgrade / Downgrade over time
fig1 = px.line(
monthly_changes,
x="Month",
y="Count",
color="change_direction",
title="Monthly System-Wide Upgrades & Downgrades Trend",
color_discrete_map={"Upgrade": "#00E676", "Downgrade": "#FF3D00"}
)
fig1.update_layout(
template="plotly_dark",
font_family="Outfit",
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
xaxis=dict(showgrid=False, tickangle=-45),
yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)"),
height=300,
margin=dict(l=10, r=10, t=50, b=10)
)
st.plotly_chart(fig1, use_container_width=True)
with col_chart2:
# Aggregated Up vs Down comparison
overall_changes = changes_df["change_direction"].value_counts().reset_index()
overall_changes.columns = ["Change", "Count"]
fig2 = px.bar(
overall_changes,
x="Change",
y="Count",
color="Change",
title="System-Wide Upgrades vs Downgrades Comparison",
color_discrete_map={"Upgrade": "#00E676", "Downgrade": "#FF3D00"}
)
fig2.update_layout(
template="plotly_dark",
font_family="Outfit",
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
xaxis=dict(showgrid=False),
yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)"),
showlegend=False,
height=300,
margin=dict(l=10, r=10, t=50, b=10)
)
st.plotly_chart(fig2, use_container_width=True)
# ----------------------------------------------------
# Stability analysis
# ----------------------------------------------------
def show_stability_analysis(selected_symbol, summary, evolution):
st.markdown('', unsafe_allow_html=True)
stock_sum = summary[summary["Symbol"] == selected_symbol].iloc[0]
stock_evo = evolution[evolution["Symbol"] == selected_symbol].sort_values("Date")
# Calculate numerical volatility: map categories to numbers
mapping = {"Sell": 1, "Reduce": 2, "Hold": 3, "Buy": 4, "Strong Buy": 5}
mapped_vals = stock_evo["historical_recommendation"].map(mapping).fillna(3)
rec_vol = mapped_vals.std()
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric(label="Stability Score (Stock)", value=f"{stock_sum['stability_score']:.2f}%")
with col2:
st.metric(label="Recommendation Changes", value=f"{stock_sum['recommendation_changes']}")
with col3:
st.metric(label="Total Records", value=f"{stock_sum['total_records']}")
with col4:
st.metric(label="Recommendation Volatility", value=f"{rec_vol:.3f}")
col_chart1, col_chart2 = st.columns(2)
with col_chart1:
# Stability Distribution
fig1 = px.histogram(
summary,
x="stability_score",
nbins=10,
title="System-Wide Stability Distribution",
color_discrete_sequence=["#00B0FF"]
)
fig1.update_layout(
template="plotly_dark",
font_family="Outfit",
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
xaxis=dict(showgrid=False, title="Stability Score (%)"),
yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)"),
bargap=0.05,
height=300,
margin=dict(l=10, r=10, t=50, b=10)
)
st.plotly_chart(fig1, use_container_width=True)
with col_chart2:
# Changes Distribution
fig2 = px.histogram(
summary,
x="recommendation_changes",
nbins=8,
title="Recommendation Changes Distribution",
color_discrete_sequence=["#E91E63"]
)
fig2.update_layout(
template="plotly_dark",
font_family="Outfit",
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
xaxis=dict(showgrid=False, title="Number of Changes"),
yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)"),
bargap=0.05,
height=300,
margin=dict(l=10, r=10, t=50, b=10)
)
st.plotly_chart(fig2, use_container_width=True)
# Stability Ranking (Top 15 Stable Stocks)
stable_rank = summary.sort_values(by="stability_score", ascending=False).head(15)
fig3 = px.bar(
stable_rank,
x="Symbol",
y="stability_score",
title="Top 15 Most Stable Stocks (Stability Score)",
color="stability_score",
color_continuous_scale="Tealgrn"
)
fig3.update_layout(
template="plotly_dark",
font_family="Outfit",
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
xaxis=dict(showgrid=False),
yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)", range=[90, 100]),
coloraxis_showscale=False,
height=320,
margin=dict(l=10, r=10, t=50, b=10)
)
st.plotly_chart(fig3, use_container_width=True)
# ----------------------------------------------------
# TECHNICAL REQUIREMENTS: show_leaderboards()
# ----------------------------------------------------
def show_leaderboards(summary):
"""Renders tabbed ranking tables of the evolution dataset."""
st.markdown('', unsafe_allow_html=True)
tab1, tab2, tab3, tab4, tab5 = st.tabs([
"π₯ Most Improved Stocks",
"β‘ Strongly Improving",
"π‘οΈ Most Stable Recommendations",
"π― Highest Confidence",
"π Weakening Recommendations"
])
col_config = {
"Symbol": st.column_config.TextColumn("Symbol", width="small"),
"historical_recommendation": st.column_config.TextColumn("Recommendation"),
"confidence_score": st.column_config.NumberColumn("Confidence", format="%.1f"),
"evolution_score": st.column_config.NumberColumn("Evolution Score", format="%.4f"),
"trend_direction": st.column_config.TextColumn("Trend Direction"),
"stability_score": st.column_config.NumberColumn("Stability Score", format="%.2f%%"),
"Recommendation_Momentum_Score": st.column_config.ProgressColumn("Momentum Score", format="%.1f", min_value=0.0, max_value=100.0),
"Recommendation_Quality_Score": st.column_config.ProgressColumn("Quality Score", format="%.1f", min_value=0.0, max_value=100.0),
"Analyst_Conviction_Score": st.column_config.ProgressColumn("Conviction Score", format="%.1f", min_value=0.0, max_value=100.0)
}
display_cols = ["Symbol", "historical_recommendation", "confidence_score", "evolution_score", "trend_direction", "Recommendation_Momentum_Score", "Recommendation_Quality_Score", "Analyst_Conviction_Score"]
with tab1:
# Sort Evolution Score Desc
df1 = summary.sort_values(by="evolution_score", ascending=False).copy()
st.dataframe(df1[display_cols], column_config=col_config, hide_index=True, use_container_width=True)
with tab2:
# Strongly Improving Category
df2 = summary[summary["evolution_category"].str.lower().str.contains("improving") | (summary["trend_direction"].str.lower() == "improving")].sort_values(by="evolution_score", ascending=False).copy()
st.dataframe(df2[display_cols], column_config=col_config, hide_index=True, use_container_width=True)
with tab3:
# Sort Stability Score Desc
df3 = summary.sort_values(by="stability_score", ascending=False).copy()
st.dataframe(df3[display_cols + ["stability_score"]], column_config=col_config, hide_index=True, use_container_width=True)
with tab4:
# Sort Confidence Desc
df4 = summary.sort_values(by="confidence_score", ascending=False).copy()
st.dataframe(df4[display_cols], column_config=col_config, hide_index=True, use_container_width=True)
with tab5:
# Trend Direction Deteriorating
df5 = summary[summary["trend_direction"].str.lower().str.strip() == "deteriorating"].sort_values(by="evolution_score", ascending=True).copy()
if not df5.empty:
st.dataframe(df5[display_cols], column_config=col_config, hide_index=True, use_container_width=True)
else:
st.info("No recommendations currently classed as deteriorating.")
# ----------------------------------------------------
# Recommendation Momentum Analytics
# ----------------------------------------------------
def show_momentum_analytics(summary):
st.markdown('', unsafe_allow_html=True)
col_chart1, col_chart2 = st.columns(2)
with col_chart1:
# Evolution Score Distribution
fig1 = px.histogram(
summary,
x="evolution_score",
nbins=12,
title="Evolution Score Distribution",
color_discrete_sequence=["#00E676"]
)
fig1.update_layout(
template="plotly_dark",
font_family="Outfit",
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
xaxis=dict(showgrid=False, title="Evolution Score"),
yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)"),
bargap=0.05,
height=300,
margin=dict(l=10, r=10, t=50, b=10)
)
st.plotly_chart(fig1, use_container_width=True)
with col_chart2:
# Trend Direction Distribution
trend_c = summary["trend_direction"].value_counts().reset_index()
trend_c.columns = ["Trend Direction", "Count"]
fig2 = px.bar(
trend_c,
x="Trend Direction",
y="Count",
color="Trend Direction",
title="Trend Direction Distribution",
color_discrete_map={"Improving": "#00E676", "Stable": "#FFA000", "Deteriorating": "#FF3D00"}
)
fig2.update_layout(
template="plotly_dark",
font_family="Outfit",
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
xaxis=dict(showgrid=False),
yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)"),
showlegend=False,
height=300,
margin=dict(l=10, r=10, t=50, b=10)
)
st.plotly_chart(fig2, use_container_width=True)
col_chart3, col_chart4 = st.columns(2)
with col_chart3:
# Evolution Percentile Distribution
fig3 = px.histogram(
summary,
x="evolution_percentile",
nbins=10,
title="Evolution Percentile Distribution",
color_discrete_sequence=["#00B0FF"]
)
fig3.update_layout(
template="plotly_dark",
font_family="Outfit",
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
xaxis=dict(showgrid=False, title="Percentile Rank"),
yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)"),
bargap=0.05,
height=300,
margin=dict(l=10, r=10, t=50, b=10)
)
st.plotly_chart(fig3, use_container_width=True)
with col_chart4:
# Evolution Score by Sector
sec_evo = summary.groupby("Sector")["evolution_score"].mean().reset_index().sort_values("evolution_score")
fig4 = px.bar(
sec_evo,
x="evolution_score",
y="Sector",
orientation="h",
title="Average Evolution Score by Sector",
color="evolution_score",
color_continuous_scale="Viridis"
)
fig4.update_layout(
template="plotly_dark",
font_family="Outfit",
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
xaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)"),
yaxis=dict(showgrid=False, title=None),
coloraxis_showscale=False,
height=300,
margin=dict(l=10, r=10, t=50, b=10)
)
st.plotly_chart(fig4, use_container_width=True)
# ----------------------------------------------------
# Evolution Heatmaps
# ----------------------------------------------------
def show_heatmaps(summary):
st.markdown('', unsafe_allow_html=True)
# We will pivot Sector against different metrics to represent beautiful matrix heatmaps.
# Group by Sector and compute averages
sec_metrics = summary.groupby("Sector").agg({
"evolution_score": "mean",
"confidence_score": "mean",
"stability_score": "mean",
"Recommendation_Momentum_Score": "mean"
}).reset_index()
# Pivot metrics to create dynamic heatmap layout
sec_metrics_melted = sec_metrics.melt(id_vars="Sector", var_name="Metric", value_name="Value")
col1, col2 = st.columns(2)
with col1:
# Heatmap 1: Evolution Score by Sector
fig1 = px.density_heatmap(
sec_metrics,
x="Sector",
y="evolution_score",
z="evolution_score",
title="Evolution Score Heatmap",
color_continuous_scale="RdYlGn"
)
fig1.update_layout(
template="plotly_dark", font_family="Outfit", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
height=260, margin=dict(l=10, r=10, t=40, b=10)
)
st.plotly_chart(fig1, use_container_width=True)
# Heatmap 2: Confidence by Sector
fig2 = px.density_heatmap(
sec_metrics,
x="Sector",
y="confidence_score",
z="confidence_score",
title="Confidence Score Heatmap",
color_continuous_scale="Blues"
)
fig2.update_layout(
template="plotly_dark", font_family="Outfit", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
height=260, margin=dict(l=10, r=10, t=40, b=10)
)
st.plotly_chart(fig2, use_container_width=True)
with col2:
# Heatmap 3: Stability by Sector
fig3 = px.density_heatmap(
sec_metrics,
x="Sector",
y="stability_score",
z="stability_score",
title="Stability Heatmap",
color_continuous_scale="Purples"
)
fig3.update_layout(
template="plotly_dark", font_family="Outfit", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
height=260, margin=dict(l=10, r=10, t=40, b=10)
)
st.plotly_chart(fig3, use_container_width=True)
# Heatmap 4: Momentum / Trend Direction by Sector
fig4 = px.density_heatmap(
sec_metrics,
x="Sector",
y="Recommendation_Momentum_Score",
z="Recommendation_Momentum_Score",
title="Recommendation Momentum Score Heatmap",
color_continuous_scale="Viridis"
)
fig4.update_layout(
template="plotly_dark", font_family="Outfit", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
height=260, margin=dict(l=10, r=10, t=40, b=10)
)
st.plotly_chart(fig4, use_container_width=True)
# ----------------------------------------------------
# TECHNICAL REQUIREMENTS: show_ai_recommendation_report()
# ----------------------------------------------------
def show_ai_recommendation_report(summary):
"""Generates and displays AI Chief Strategist advisory report."""
st.markdown('', unsafe_allow_html=True)
# Compute stats for dynamic report
improving_stocks = summary[summary["trend_direction"].str.lower().str.strip() == "improving"]["Symbol"].tolist()
weakening_stocks = summary[summary["trend_direction"].str.lower().str.strip() == "deteriorating"]["Symbol"].tolist()
avg_stability = summary["stability_score"].mean()
improving_str = ", ".join(improving_stocks[:4]) if improving_stocks else "No stocks"
weakening_str = ", ".join(weakening_stocks[:4]) if weakening_stocks else "No stocks"
st.markdown(f"""
π§ Premium Recommendation Evolution Report
The algorithm reports stable conviction system-wide, with average signal stability registering at {avg_stability:.2f}%. High-conviction setups continue to display strong resilience across sectors.
""", unsafe_allow_html=True)
c1, c2 = st.columns(2)
with c1:
st.markdown(f"""
β‘ Recommendation Summary
Overall rating configurations show improving patterns in selection nodes. Most stock recommendations remain stable over the medium term, with confidence clusters consolidating around blue-chip leaders.
π Improving Opportunities
{improving_str} exhibit the strongest positive rating momentum, characterized by continuous upgrades and accelerating analyst conviction.
""", unsafe_allow_html=True)
with c2:
st.markdown(f"""
β οΈ Weakening Opportunities
{weakening_str} are currently under pressure with negative evolution vectors, demanding closer risk management and dynamic hedging implementation.
π‘οΈ Stability & Confidence
Stability scores remain above 95% across the main cohort, confirming reliable signals. Confidence levels remain elevated among top-ranked opportunities.
""", unsafe_allow_html=True)
# ----------------------------------------------------
# Export Features
# ----------------------------------------------------
def show_export_features(summary):
st.markdown('', unsafe_allow_html=True)
col_e1, col_e2, col_e3, col_e4 = st.columns(4)
with col_e1:
sum_csv = summary[["Symbol", "historical_recommendation", "confidence_score", "stability_score", "trend_direction"]].to_csv(index=False).encode("utf-8")
st.download_button(
label="Download Recommendation Summary",
data=sum_csv,
file_name="investiq_recommendation_summary.csv",
mime="text/csv",
key="dl-rec-sum",
use_container_width=True
)
with col_e2:
rank_csv = summary.sort_values("evolution_score", ascending=False)[["Symbol", "evolution_score", "Recommendation_Momentum_Score", "Recommendation_Quality_Score", "Analyst_Conviction_Score"]].to_csv(index=False).encode("utf-8")
st.download_button(
label="Download Evolution Rankings",
data=rank_csv,
file_name="investiq_evolution_rankings.csv",
mime="text/csv",
key="dl-evo-rank",
use_container_width=True
)
with col_e3:
imp_df = summary[summary["trend_direction"].str.lower().str.strip() == "improving"]
imp_csv = imp_df[["Symbol", "historical_recommendation", "confidence_score", "evolution_score", "stability_score"]].to_csv(index=False).encode("utf-8")
st.download_button(
label="Download Improving Opportunities",
data=imp_csv,
file_name="investiq_improving_opportunities.csv",
mime="text/csv",
key="dl-imp-opp",
use_container_width=True
)
with col_e4:
weak_df = summary[summary["trend_direction"].str.lower().str.strip() == "deteriorating"]
weak_csv = weak_df[["Symbol", "historical_recommendation", "confidence_score", "evolution_score", "stability_score"]].to_csv(index=False).encode("utf-8")
st.download_button(
label="Download Weakening Opportunities",
data=weak_csv,
file_name="investiq_weakening_opportunities.csv",
mime="text/csv",
key="dl-weak-opp",
use_container_width=True
)
# ----------------------------------------------------
# MAIN CALLABLE: show_recommendation_evolution_workstation()
# ----------------------------------------------------
def show_recommendation_evolution_workstation():
"""Main Streamlit execution point for Section 7 Page."""
apply_custom_style()
st.title("π Recommendation Evolution Workstation")
st.markdown("Track how investment recommendations have changed over time and identify improving opportunities before the market.
", unsafe_allow_html=True)
# Load data
try:
summary, evolution, master = load_data()
except Exception as e:
st.error(f"Error loading workstation data: {e}")
st.stop()
# 1. Evolution Overview
show_evolution_overview(summary, master)
st.markdown("---")
# 2. Stock Recommendation Timeline Selector
st.markdown('', unsafe_allow_html=True)
selected_symbol = st.selectbox("Select Asset to Audit Timeline & Performance", summary["Symbol"].unique())
show_stock_timeline(selected_symbol, summary, evolution)
st.markdown("---")
# 3. Confidence Intelligence
show_confidence_analysis(selected_symbol, summary, evolution)
st.markdown("---")
# 4. Upgrade / Downgrade Analysis
show_upgrade_analysis(selected_symbol, summary, evolution)
st.markdown("---")
# 5. Recommendation Stability Analysis
show_stability_analysis(selected_symbol, summary, evolution)
st.markdown("---")
# 6. Leaderboards
show_leaderboards(summary)
st.markdown("---")
# 7. Recommendation Momentum Analytics
show_momentum_analytics(summary)
st.markdown("---")
# 8. Evolution Heatmaps
show_heatmaps(summary)
st.markdown("---")
# 9. AI Recommendation Strategist
show_ai_recommendation_report(summary)
st.markdown("---")
# 10. Export Features
show_export_features(summary)