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('
πŸ”„ Evolution Overview
', 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"""
Trend Direction
{trend}
""", 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('
🎯 Confidence Intelligence
', 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('
πŸ“ˆ Upgrade / Downgrade Analysis
', 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('
βš–οΈ Recommendation Stability Analysis
', 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('
πŸ† Evolution Leaderboards
', 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('
⚑ Recommendation Momentum Analytics
', 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('
πŸ—ΊοΈ Evolution Heatmaps
', 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('
🧠 AI Recommendation Strategist
', 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('
πŸ“₯ Export Station
', 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('
πŸ•°οΈ Stock Recommendation Timeline
', 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)