0% found this document useful (0 votes)
3 views38 pages

Jariri Python Code

The document outlines a Streamlit application for analyzing quantitative data, allowing users to upload CSV or Excel files for processing. It includes features for data cleaning, performance metrics calculations, and visualization of results through various plots and KPI banners. The application also provides options for selecting analysis parameters and displaying the results in a user-friendly format.

Uploaded by

hitheshburugala
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views38 pages

Jariri Python Code

The document outlines a Streamlit application for analyzing quantitative data, allowing users to upload CSV or Excel files for processing. It includes features for data cleaning, performance metrics calculations, and visualization of results through various plots and KPI banners. The application also provides options for selecting analysis parameters and displaying the results in a user-friendly format.

Uploaded by

hitheshburugala
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

import streamlit as st

import numpy as np

import pandas as pd

import [Link] as plt

import io, math, datetime as dt, re

import openai

from openpyxl import Workbook

from [Link] import Font, PatternFill, Alignment, Border, Side

from [Link] import get_column_letter

# ====== PDF imports (reportlab) ======

from [Link] import A4, landscape

from [Link] import (

SimpleDocTemplate, LongTable, TableStyle, Paragraph, Spacer,


PageBreak, Image as RLImage

from [Link] import colors

from [Link] import getSampleStyleSheet, ParagraphStyle

from [Link] import TA_LEFT, TA_CENTER, TA_RIGHT

#
============================================
=========

# 1. PAGE SETUP & DATA LOADING

#
============================================
=========

st.set_page_config(page_title="Jarir Quant Analyzer", layout="wide")

[Link]("Jarir Quant Analysis with AI assistance")


uploaded_file = st.file_uploader("Please upload the raw quant file to
begin.", type=["csv", "xlsx"])

if uploaded_file is None:

[Link]("Please upload the raw quant file to begin.")

[Link]()

@st.cache_data

def load_and_clean_base(file):

df = pd.read_csv(file) if [Link](".csv") else


pd.read_excel(file)

[Link] = [Link]()

if "Data" in [Link]: [Link](columns={"Data": "Date"},


inplace=True)

df["Date"] = pd.to_datetime(df["Date"], format='%b-%y',


errors="coerce")

if "Year" in [Link]:

df['Date'] = [Link](lambda x: x['Date'].replace(year=int(x['Year']))


if [Link](x['Date']) else x['Date'], axis=1)

mgr_cols = [c for c in [Link] if c not in ["Date", "Year"]]

last_idx = df[mgr_cols].dropna(how='all').[Link]()

return [Link][:last_idx + 1].sort_values("Date").reset_index(drop=True)

df_raw = load_and_clean_base(uploaded_file)

#
============================================
=========

# 2. SIDEBAR SETTINGS

#
============================================
=========
[Link]("Analysis Settings")

available_dates = df_raw["Date"].dropna().sort_values().unique()

date_labels = [[Link]('%b-%Y') for d in available_dates]

col_s1, col_s2 = [Link](2)

with col_s1:

start_label = [Link]("Start Month", options=date_labels, index=0)

with col_s2:

end_label = [Link]("End Month", options=date_labels,


index=len(date_labels)-1)

start_dt = pd.to_datetime(start_label, format='%b-%Y')

end_dt = pd.to_datetime(end_label, format='%b-%Y')

df_filtered = df_raw[(df_raw["Date"] >= start_dt) & (df_raw["Date"] <=


end_dt)].reset_index(drop=True)

rfr_target = "13 Wk US Treasury Bills"

default_rfr_idx = df_filtered.columns.get_loc(rfr_target) if rfr_target in


df_filtered.columns else 0

rfr_col = [Link]("Risk-Free Rate Column",


options=df_filtered.columns, index=default_rfr_idx)

all_mgrs = [c for c in df_filtered.columns if c not in ["Date", "Year", rfr_col]]

manager_cols = [Link]("Select Managers",


options=all_mgrs, default=all_mgrs[:5])

if not manager_cols:

[Link]("Please select managers.")

[Link]()
#
============================================
=========

# 3. DATA CLEANING

#
============================================
=========

cleaned_df = df_filtered.copy()

for col in manager_cols + [rfr_col]:

s = cleaned_df[col].astype(str).[Link]()

is_pct = [Link]("%", regex=False)

s = [Link](["", "nan", "-", "–", " - "], [Link])\

.[Link]("%", "", regex=False)\

.[Link](r"\((.*?)\)", r"-\1", regex=True)\

.astype(float)

[Link][is_pct] = [Link][is_pct] / 100

cleaned_df[col] = s

#
============================================
=========

# 4. CALCULATION HELPERS

#
============================================
=========

def get_cap(s_m, s_b):

combined = [Link]({'m': s_m, 'b': s_b}).dropna()

up = combined[combined['b'] > 0]; dn = combined[combined['b'] < 0]

u_cap = ([Link](1+up['m'])/[Link](1+up['b']))*100 if not [Link]


else [Link]

d_cap = ([Link](1+dn['m'])/[Link](1+dn['b']))*100 if not [Link]


else [Link]
return u_cap, d_cap

def slice_series(series, years=None):

s = [Link]()

if years is None: return s

months = int(years * 12)

return [Link](months) if len(s) >= months else None

def get_ann_ret(s):

if s is None or len(s) < 12: return [Link]

return ([Link](1 + s)) ** (1 / (len(s) / 12)) - 1

def get_ann_vol(s):

if s is None or len(s) < 2: return [Link]

return [Link](ddof=1) * [Link](12)

def get_downside_dev(s):

if s is None or len(s) < 12: return [Link]

return [Link]([Link]([Link](s, 0) ** 2)) * [Link](12)

def get_upside_dev(s):

if s is None or len(s) < 12: return [Link]

return [Link]([Link]([Link](s, 0) ** 2)) * [Link](12)

def get_max_drawdown(s):

if s is None or len(s) == 0: return [Link]

cum = (1 + s).cumprod()

return ((cum - [Link]()) / [Link]()).min()


#
============================================
=========

# 5. ANALYTICS ENGINE (extended horizons: 1–10, 15, 20)

#
============================================
=========

horizons = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20]

h_labels = [f"{y} Year" for y in horizons] + ["Since Inception"]

metrics = ["Annualized Return (%)", "Annualized Volatility (%)",

"Upward Deviation (%)", "Downward Deviation (%)",

"Sharpe Ratio", "Sortino Ratio", "Max Drawdown (%)"]

results = {m: [Link](index=h_labels, columns=manager_cols)


for m in metrics}

for yrs, lbl in zip(horizons + [None], h_labels):

for mgr in manager_cols:

s_m = slice_series(cleaned_df[mgr], yrs)

s_rf = slice_series(cleaned_df[rfr_col], yrs)

if s_m is None or s_rf is None: continue

ann_ret = get_ann_ret(s_m)

ann_rfr = get_ann_ret(s_rf)

ann_vol = get_ann_vol(s_m)

ann_ddev = get_downside_dev(s_m)

ann_udev = get_upside_dev(s_m)

exc_ret = ann_ret - ann_rfr

results["Annualized Return (%)"].at[lbl, mgr] = ann_ret * 100 if not


[Link](ann_ret) else [Link]
results["Annualized Volatility (%)"].at[lbl, mgr] = ann_vol * 100 if not
[Link](ann_vol) else [Link]

results["Upward Deviation (%)"].at[lbl, mgr] = ann_udev * 100 if


not [Link](ann_udev) else [Link]

results["Downward Deviation (%)"].at[lbl, mgr] = ann_ddev * 100 if


not [Link](ann_ddev) else [Link]

results["Sharpe Ratio"].at[lbl, mgr] = exc_ret / ann_vol if ann_vol >


0 else [Link]

results["Sortino Ratio"].at[lbl, mgr] = exc_ret / ann_ddev if ann_ddev


> 0 else [Link]

results["Max Drawdown (%)"].at[lbl, mgr] = get_max_drawdown(s_m)


* 100

#
============================================
=========

# 6. PERFORMANCE SNAPSHOT + KPI BANNER

#
============================================
=========

# ── KPI Banner Cards with selectable horizon ──

[Link]("### 📊 Performance Summary")

banner_horizon = [Link]("Select Horizon for Summary Cards",


options=h_labels, index=h_labels.index("Since Inception"),
key="banner_h")

si_ret = results["Annualized Return


(%)"].loc[banner_horizon].apply(pd.to_numeric, errors='coerce')

si_vol = results["Annualized Volatility


(%)"].loc[banner_horizon].apply(pd.to_numeric, errors='coerce')

si_sharpe = results["Sharpe
Ratio"].loc[banner_horizon].apply(pd.to_numeric, errors='coerce')
si_sort = results["Sortino
Ratio"].loc[banner_horizon].apply(pd.to_numeric, errors='coerce')

si_dd = results["Max Drawdown


(%)"].loc[banner_horizon].apply(pd.to_numeric, errors='coerce')

# All cards use same clean dark navy — no vague colour variety

CARD_BG = "#1C2B4A"

banner_cards = [

("🏆 Best Sharpe", si_sharpe.idxmax(), f"{si_sharpe.max():.2f}"),

("📈 Highest Ann. Return", si_ret.idxmax(), f"{si_ret.max():.2f}%"),

("📉 Lowest Ann. Return", si_ret.idxmin(), f"{si_ret.min():.2f}%"),

("💪 Lowest Drawdown", si_dd.idxmax(), f"{si_dd.max():.2f}%"),

("⚠️Highest Drawdown", si_dd.idxmin(), f"{si_dd.min():.2f}%"),

("📊 Highest Volatility", si_vol.idxmax(), f"{si_vol.max():.2f}%"),

("🎯 Lowest Volatility", si_vol.idxmin(), f"{si_vol.min():.2f}%"),

("⚡ Best Sortino", si_sort.idxmax(), f"{si_sort.max():.2f}"),

[Link]("""

<style>

.banner-grid{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:20px}

.banner-card{

flex:1 1 160px;border-radius:10px;padding:14px 12px;text-align:center;

box-shadow:0 2px 8px rgba(0,0,0,.20);

.banner-card .label{font-size:11px;font-weight:600;opacity:.80;margin-
bottom:4px;letter-spacing:.4px;text-transform:uppercase}

.banner-card .manager{font-size:13px;font-weight:700;margin-
bottom:4px}

.banner-card .value{font-size:20px;font-weight:800;letter-spacing:-.5px}
</style>""", unsafe_allow_html=True)

cards_html = '<div class="banner-grid">'

for lbl_b, mgr_b, val_b in banner_cards:

cards_html += (

f'<div class="banner-card" style="background:


{CARD_BG};color:white;">'

f'<div class="label">{lbl_b}</div>'

f'<div class="manager">{mgr_b}</div>'

f'<div class="value">{val_b}</div>'

f'</div>'

cards_html += '</div>'

[Link](cards_html, unsafe_allow_html=True)

[Link]()

#
============================================
=========

# 7. STYLE HELPER

#
============================================
=========

def style_df(df_in, pct=True):

fmt = "{:.2f}%" if pct else "{:.2f}"

return df_in.apply(pd.to_numeric).style \

.map(lambda x: 'color:red;text-align:center;font-weight:normal'

if ([Link](x) and x < 0)

else ('text-align:center;color:black;font-weight:normal' if
[Link](x) else '')) \

.format(fmt, na_rep="") \
.set_properties(**{'text-align': 'center'}) \

.set_table_styles([

{'selector': 'th.row_heading', 'props': [('font-weight','bold'),


('color','black'),('text-align','left'),('white-space','nowrap')]},

{'selector': 'th.col_heading', 'props': [('font-weight','bold'),


('color','black'),('text-align','center')]},

{'selector': '[Link]', 'props': [('text-align','center')]},

])

#
============================================
=========

# 8. METRIC TABLES

#
============================================
=========

for m in metrics:

is_p = "Ratio" not in m

with [Link](f"View: {m}", expanded=True):

[Link](style_df(results[m], pct=is_p), width="stretch",


height=(len(h_labels)+1)*35+15)

#
============================================
=========

# 9. RISK–RETURN + UPSIDE–DOWNSIDE PLOTS

#
============================================
=========

[Link]()

viz_h = [Link]("Select Horizon for Plots", options=h_labels,


index=len(h_labels)-2)
col_v1, col_v2 = [Link](2)

def smart_scatter(ax, x_vals, y_vals, labels, dot_color, xlabel, ylabel,


reflines=None):

"""Scatter with repulsion-based label placement — no overlaps."""

import numpy as np

[Link](x_vals, y_vals, color=dot_color, s=90, zorder=5)

if not labels:

ax.set_xlabel(xlabel, fontsize=9)

ax.set_ylabel(ylabel, fontsize=9)

[Link](True, ls=':', alpha=0.6)

return

x_arr = [Link](x_vals, dtype=float)

y_arr = [Link](y_vals, dtype=float)

n = len(labels)

x_range = max(x_arr.max() - x_arr.min(), 1e-6)

y_range = max(y_arr.max() - y_arr.min(), 1e-6)

# Start label positions offset from dots

lx = x_arr + x_range * 0.04

ly = y_arr + y_range * 0.04

# Iterative repulsion between labels

for _ in range(300):

for i in range(n):
fx, fy = 0.0, 0.0

for j in range(n):

if i == j: continue

dx = (lx[i] - lx[j]) / x_range

dy = (ly[i] - ly[j]) / y_range

dist2 = dx*dx + dy*dy + 1e-9

if dist2 < 0.08:

fx += dx / dist2

fy += dy / dist2

# Also repel from original dot positions

for j in range(n):

dx = (lx[i] - x_arr[j]) / x_range

dy = (ly[i] - y_arr[j]) / y_range

dist2 = dx*dx + dy*dy + 1e-9

if dist2 < 0.06:

fx += 0.5 * dx / dist2

fy += 0.5 * dy / dist2

step = 0.0008

lx[i] += fx * step * x_range

ly[i] += fy * step * y_range

for i, lbl in enumerate(labels):

[Link](

lbl,

xy=(x_arr[i], y_arr[i]),

xytext=(lx[i], ly[i]),

fontsize=8.5, fontweight='bold', color='#1a1a2e',

bbox=dict(boxstyle='round,pad=0.3', fc='white', ec='#cccccc',


alpha=0.85, lw=0.6),
arrowprops=dict(arrowstyle='-', color='#999999', lw=0.8),

zorder=10

ax.set_xlabel(xlabel, fontsize=9)

ax.set_ylabel(ylabel, fontsize=9)

[Link](True, ls=':', alpha=0.6)

if reflines:

for axis, val, lw in reflines:

if axis == 'h': [Link](val, color='black', lw=lw)

if axis == 'v': [Link](val, color='black', lw=lw)

with col_v1:

[Link](f"**Risk-Return Plot ({viz_h})**")

rr_ui = [Link]({

"Return (%)": results["Annualized Return (%)"].loc[viz_h],

"Volatility (%)": results["Annualized Volatility (%)"].loc[viz_h]

}).apply(pd.to_numeric).dropna()

fig_rr, ax_rr = [Link](figsize=(8, 5))

smart_scatter(ax_rr, rr_ui["Volatility (%)"].tolist(), rr_ui["Return


(%)"].tolist(),

rr_ui.[Link](), '#0d6e8a', "Volatility (%)", "Return (%)")

plt.tight_layout(); [Link](fig_rr)

with col_v2:

[Link](f"**Upside–Downside Plot ({viz_h})**")

ud_ui = [Link]({

"Return (%)": results["Annualized Return (%)"].loc[viz_h],

"Downside Dev (%)": results["Downward Deviation (%)"].loc[viz_h]


}).apply(pd.to_numeric).dropna()

fig_ud, ax_ud = [Link](figsize=(8, 5))

smart_scatter(ax_ud, ud_ui["Downside Dev (%)"].tolist(), ud_ui["Return


(%)"].tolist(),

ud_ui.[Link](), '#c0392b',

"Downside Deviation (%) — Bad Volatility",

"Annualized Return (%) — Reward",

reflines=[('h', 0, 0.8), ('v', 0, 0.8)])

plt.tight_layout(); [Link](fig_ud)

# Capture scatter

[Link]()

col_v3, col_v4 = [Link](2)

with col_v3:

b_cap = [Link]("Benchmark for Capture Scatter",


options=manager_cols, index=len(manager_cols)-1)

[Link](f"**Capture Matrix ({viz_h}) — Benchmark: {b_cap}**")

caps_ui = []

for m in manager_cols:

y_c = int(viz_h.split()[0]) if "Year" in viz_h else None

s_m_ = cleaned_df[m].tail(y_c*12) if y_c else cleaned_df[m]

s_b_ = cleaned_df[b_cap].tail(y_c*12) if y_c else cleaned_df[b_cap]

u, d = get_cap(s_m_.dropna(), s_b_.dropna())

caps_ui.append({"Manager": m, "Upside": u, "Downside": d})

cap_ui_df = [Link](caps_ui).set_index("Manager").dropna()

fig_cap, ax_cap = [Link](figsize=(8, 5))

smart_scatter(ax_cap, cap_ui_df["Downside"].tolist(),
cap_ui_df["Upside"].tolist(),

cap_ui_df.[Link](), '#8e1a0e',

"Downside Capture (%)", "Upside Capture (%)",


reflines=[('h', 100, 1), ('v', 100, 1)])

plt.tight_layout(); [Link](fig_cap)

#
============================================
=========

# 10. CALENDAR RETURNS & ALPHA

#
============================================
=========

[Link]()

cal_base = cleaned_df.set_index('Date')[manager_cols]

cal_ret = cal_base.groupby(cal_base.[Link]).apply(

lambda x: ([Link](x + 1, axis=0) - 1) *


100).sort_index(ascending=False)

[Link]("**Calendar Returns (%)**")

[Link](style_df(cal_ret), width="stretch")

bench_diff = [Link]("Select Alpha Benchmark",


options=manager_cols, index=len(manager_cols)-1)

cal_diff = cal_ret.subtract(cal_ret[bench_diff], axis=0)

[Link](f"**Calendar Difference (Alpha vs {bench_diff}) %**")

[Link](style_df(cal_diff), width="stretch")

[Link]()

alpha_fund = [Link]("Select Fund for Alpha Matrix",


options=manager_cols, index=0)

alpha_bench = [Link]("Select Benchmark for Alpha Matrix",


options=manager_cols, index=len(manager_cols)-1)

[Link](f"**Alpha Over Benchmark (Yearly View): {alpha_fund} vs


{alpha_bench} (%)**")
alpha_matrix = [Link](index=cleaned_df.index)

for y in range(1, 21):

f_r = cleaned_df[alpha_fund].rolling(window=y*12).apply(

lambda x: ([Link](x+1)**(12/len(x))-1)*100)

b_r = cleaned_df[alpha_bench].rolling(window=y*12).apply(

lambda x: ([Link](x+1)**(12/len(x))-1)*100)

alpha_matrix[f"{y}Y"] = f_r - b_r

alpha_matrix['Date'] = cleaned_df['Date']

alpha_disp_year =
alpha_matrix.groupby(alpha_matrix['Date'].[Link]).tail(1).copy()

alpha_disp_year.set_index(alpha_disp_year['Date'].[Link], inplace=True)

alpha_disp_year =
alpha_disp_year.drop(columns=['Date']).sort_index(ascending=False)

[Link](style_df(alpha_disp_year), width="stretch", height=600)

[Link]()

[Link]("36-Monthly Returns of the selected funds")

m_36_ui = cleaned_df.set_index('Date')[manager_cols].tail(36).iloc[::-1]

m_36_ui.index = m_36_ui.[Link]('%b-%Y')

[Link](style_df(m_36_ui * 100), width="stretch", height=1300)

#
============================================
=========

# 11. CAPTURE TABLE

#
============================================
=========

[Link]()

[Link]("📋 Upside / Downside Capture Table")


cap_bench = [Link]("Benchmark for Capture Table",
options=manager_cols,

index=len(manager_cols)-1, key="cap_tbl_bench")

cap_horizons_lbl = [f"{y} Year" for y in [1, 3, 5, 8, 10]] + ["Since


Inception"]

cap_rows = []

for mgr in manager_cols:

row = {"Manager": mgr}

for lbl_c in cap_horizons_lbl:

yrs_c = int(lbl_c.split()[0]) if "Year" in lbl_c else None

sm = cleaned_df[mgr].dropna(); sb =
cleaned_df[cap_bench].dropna()

if yrs_c:

if len(sm) < yrs_c*12:

row[f"{lbl_c} Up"] = [Link]; row[f"{lbl_c} Dn"] = [Link];


continue

sm = [Link](yrs_c*12); sb = [Link](yrs_c*12)

u, d = get_cap(sm, sb)

row[f"{lbl_c} Up"] = round(u, 2) if u is not None else [Link]

row[f"{lbl_c} Dn"] = round(d, 2) if d is not None else [Link]

cap_rows.append(row)

cap_tbl = [Link](cap_rows).set_index("Manager")

def style_capture(df_in):

def color_cell(val):

if [Link](val): return ''

return 'color:green;font-weight:bold;text-align:center' if val >= 100


else 'color:red;text-align:center'

return df_in.[Link](color_cell).format("{:.1f}",
na_rep="").set_table_styles([
{'selector': 'th.row_heading', 'props': [('font-weight','bold'),
('color','black'),('text-align','left')]},

{'selector': 'th.col_heading', 'props': [('font-weight','bold'),


('color','black'),('text-align','center'),('font-size','11px')]},

])

[Link](style_capture(cap_tbl), width="stretch")

#
============================================
=========

# 12. WIN RATE VS BENCHMARK

#
============================================
=========

[Link]()

[Link]("🏅 Win Rate vs Benchmark (% of months outperformed)")

wr_bench = [Link]("Select Benchmark for Win Rate",


options=manager_cols,

index=len(manager_cols)-1, key="wr_bench")

wr_horizons = [1, 3, 5, 8, 10]

wr_rows = []

for mgr in [m for m in manager_cols if m != wr_bench]:

row = {"Manager": mgr}

for yrs_w in wr_horizons:

lbl_w = f"{yrs_w}Y"

sm = cleaned_df[mgr].dropna(); sb = cleaned_df[wr_bench].dropna()

if len(sm) < yrs_w*12: row[lbl_w] = [Link]; continue

sm = [Link](yrs_w*12); sb = [Link](yrs_w*12)

combined = [Link]({'m': [Link], 'b': [Link]}).dropna()

row[lbl_w] = round((combined['m'] > combined['b']).mean()*100, 1)

wr_rows.append(row)
wr_df = [Link](wr_rows).set_index("Manager")

def style_winrate(df_in):

def color_wr(val):

if [Link](val): return ''

if val >= 55: return 'color:green;font-weight:bold;text-align:center'

if val <= 45: return 'color:red;text-align:center'

return 'color:orange;text-align:center'

return df_in.[Link](color_wr).format("{:.1f}%",
na_rep="").set_table_styles([

{'selector': 'th.row_heading', 'props': [('font-weight','bold'),


('color','black'),('text-align','left')]},

{'selector': 'th.col_heading', 'props': [('font-weight','bold'),


('color','black'),('text-align','center')]},

])

[Link](style_winrate(wr_df), width="stretch")

[Link]("🟢 ≥55% consistent outperformance | 🟠 45–55% neutral | 🔴


≤45% underperformance")

#
============================================
=========

# 13. MANAGER COMPARISON CARD

#
============================================
=========

[Link]()

[Link]("🔍 Manager Comparison Card")

col_c1, col_c2 = [Link](2)

with col_c1: mgr_a = [Link]("Manager A", options=manager_cols,


index=0, key="cmp_a")
with col_c2: mgr_b = [Link]("Manager B", options=manager_cols,
index=min(1,len(manager_cols)-1), key="cmp_b")

cmp_horizon = [Link]("Comparison Horizon", options=h_labels,


index=len(h_labels)-1, key="cmp_h")

cmp_metrics = {

"Annualized Return (%)": ("{:.2f}%", True),

"Annualized Volatility (%)": ("{:.2f}%", False),

"Sharpe Ratio": ("{:.2f}", True),

"Sortino Ratio": ("{:.2f}", True),

"Max Drawdown (%)": ("{:.2f}%", False),

"Upward Deviation (%)": ("{:.2f}%", True),

"Downward Deviation (%)": ("{:.2f}%", False),

[Link]("""

<style>

.cmp-wrap{display:flex;gap:12px;margin-top:10px}

.cmp-box{flex:1;border-
radius:10px;padding:18px;background:#f8f9fa;border:1px solid #dee2e6}

.cmp-title{font-size:18px;font-weight:800;color:#1a1a2e;margin-
bottom:14px;border-bottom:3px solid #1a5276;padding-bottom:6px}

.cmp-row{display:flex;justify-content:space-between;padding:6px
0;border-bottom:1px solid #eee;font-size:13px}

.cmp-label{color:#555;font-weight:500}

.cmp-val-good{color:#1e8449;font-weight:700}

.cmp-val-bad{color:#922b21;font-weight:700}

.cmp-val-neu{color:#333;font-weight:700}

.cmp-winner{background:#eafaf1;border:2px solid #1e8449}

</style>""", unsafe_allow_html=True)
def get_cmp_val(mgr, metric):

try: return float(results[metric].at[cmp_horizon, mgr])

except: return [Link]

def render_cmp_box(mgr, other, is_winner):

box_class = "cmp-box cmp-winner" if is_winner else "cmp-box"

rows_html = ""

for metric, (fmt, higher_better) in cmp_metrics.items():

v = get_cmp_val(mgr, metric); o = get_cmp_val(other, metric)

if [Link](v): val_str, cls = "N/A", "cmp-val-neu"

else:

val_str = [Link](v)

if [Link](o): cls = "cmp-val-neu"

elif higher_better: cls = "cmp-val-good" if v > o else ("cmp-val-


bad" if v < o else "cmp-val-neu")

else: cls = "cmp-val-good" if v < o else ("cmp-val-bad" if v


> o else "cmp-val-neu")

rows_html += f'<div class="cmp-row"><span class="cmp-


label">{metric}</span><span class="{cls}">{val_str}</span></div>'

badge = " 🏆" if is_winner else ""

return f'<div class="{box_class}"><div class="cmp-title">{mgr}


{badge}</div>{rows_html}</div>'

sharpe_a = get_cmp_val(mgr_a, "Sharpe Ratio"); sharpe_b =


get_cmp_val(mgr_b, "Sharpe Ratio")

a_wins = (not [Link](sharpe_a)) and ([Link](sharpe_b) or sharpe_a >=


sharpe_b)

[Link](f'<div class="cmp-
wrap">{render_cmp_box(mgr_a,mgr_b,a_wins)}
{render_cmp_box(mgr_b,mgr_a,not a_wins)}</div>',

unsafe_allow_html=True)
[Link]("🟢 = better value for that metric | 🏆 = overall winner by
Sharpe Ratio")

#
============================================
=========

# 14. ROLLING SHARPE & VOLATILITY

#
============================================
=========

[Link]()

[Link]("📈 Rolling Sharpe Ratio & Volatility")

roll_col1, roll_col2 = [Link](2)

with roll_col1: roll_window = [Link]("Rolling Window", options=[12,


24, 36], index=0, key="roll_win")

with roll_col2: roll_mgrs = [Link]("Managers to Plot",


options=manager_cols, default=manager_cols[:3], key="roll_mgrs")

if roll_mgrs:

fig_roll, (ax_rs, ax_rv) = [Link](2, 1, figsize=(12, 8), sharex=True)

colors_list = [Link]

rfr_series = cleaned_df[rfr_col].fillna(method='ffill')

for i, mgr in enumerate(roll_mgrs):

s = cleaned_df[mgr]; dates = cleaned_df['Date']

roll_sharpe = [Link](roll_window).apply(

lambda x: (([Link](x+1)**(12/len(x))-1) -

([Link](rfr_series.iloc[[Link][-roll_window:] if
len([Link])>=roll_window else [Link]]+1)**(12/len(x))-1))

/ ([Link](ddof=1)*[Link](12)) if [Link]() > 0 else [Link])

roll_vol = [Link](roll_window).std(ddof=1) * [Link](12) * 100

c = colors_list[i % len(colors_list)]

ax_rs.plot(dates, roll_sharpe, label=mgr, color=c, lw=1.5)


ax_rv.plot(dates, roll_vol, label=mgr, color=c, lw=1.5)

ax_rs.axhline(0, color='black', lw=0.8, ls='--')

ax_rs.set_ylabel("Rolling Sharpe"); ax_rs.legend(fontsize=8);


ax_rs.grid(True, ls=':')

ax_rs.set_title(f"{roll_window}-Month Rolling Sharpe Ratio")

ax_rv.set_ylabel("Rolling Volatility (%)"); ax_rv.legend(fontsize=8);


ax_rv.grid(True, ls=':')

ax_rv.set_title(f"{roll_window}-Month Rolling Annualized Volatility")

plt.tight_layout(); [Link](fig_roll)

#
============================================
=========

# 15. DRAWDOWN TIMELINE

#
============================================
=========

[Link]()

[Link]("🌊 Drawdown Timeline (Underwater Chart)")

dd_mgrs = [Link]("Managers for Drawdown Chart",


options=manager_cols,

default=manager_cols[:4], key="dd_mgrs")

if dd_mgrs:

n_mgrs = len(dd_mgrs)

fig_dd, axes = [Link](n_mgrs, 1, figsize=(12, 3*n_mgrs),


sharex=True)

if n_mgrs == 1: axes = [axes]

colors_dd = [Link]

for i, mgr in enumerate(dd_mgrs):

s = cleaned_df[mgr].fillna(0)

cum = (1+s).cumprod(); roll_max = [Link]()

dd_vals = (cum/roll_max - 1)*100


ax = axes[i]

ax.fill_between(cleaned_df['Date'], dd_vals, 0, color=colors_dd[i


%len(colors_dd)], alpha=0.5)

[Link](cleaned_df['Date'], dd_vals, color=colors_dd[i


%len(colors_dd)], lw=1)

[Link](0, color='black', lw=0.8); ax.set_ylabel("Drawdown (%)")

ax.set_title(mgr, fontweight='bold', fontsize=10); [Link](True, ls=':',


alpha=0.5)

max_dd_val = dd_vals.min()

max_dd_date = cleaned_df['Date'].iloc[dd_vals.argmin()]

[Link](f"{max_dd_val:.1f}%", xy=(max_dd_date, max_dd_val),

xytext=(max_dd_date, max_dd_val-2), fontsize=8,


color='red', fontweight='bold')

plt.tight_layout(); [Link](fig_dd)

#
============================================
=========

# 16. AI STRATEGIC CONVERSATION

#
============================================
=========

[Link]()

[Link]("Jarir AI Strategic Advisor")

full_results_text = ""

for metric_name, df_res in [Link]():

full_results_text += f"\n--- {metric_name} ---\n{df_res.to_string()}\n"

chat_context = f"""

Portfolio Window: {start_label} to {end_label}

Managers: {', '.join(manager_cols)}


Alpha Check: {alpha_fund} vs {alpha_bench}

[DATASET]\n{full_results_text}\n{alpha_disp_year.to_string()}

"""

with [Link]("Activate AI Conversation", expanded=False):

key_input = st.text_input("Enter OpenAI API Key", type="password")

if [Link](" Generate insights"):

if not key_input:

[Link]("Please provide an API key.")

else:

with [Link]("Generating Insights..."):

try:

client = [Link](api_key=key_input)

sys_role = "You are acting as a senior investment analyst


performing a comprehensive evaluation of fund manager performance for
institutional portfolio decisions. I will provide manager-level performance
data including returns, volatility, upside and downside deviations, Sharpe
and Sortino ratios, drawdowns, capture ratios, calendar returns, alpha
metrics, and risk-return visualizations. Your objective is to interpret the
data the way an experienced investment analyst would—by identifying
what is really driving performance, when it occurred, and whether it is
repeatable. Examine whether returns are generated through genuine
manager skill or through exposure to favorable market regimes, elevated
risk-taking, or beta concentration. Assess return consistency across time
by identifying periods of performance concentration, regime dependence,
and month-year-specific inflection points such as market stress,
drawdowns, recoveries, or rallies. Evaluate downside risk by analyzing
drawdowns, downside deviation, recovery speed, and Sortino behavior to
determine capital preservation capability. Analyze upside versus downside
capture to understand payoff asymmetry and to distinguish convex return
profiles from leveraged or directional exposure. Use the risk-return
positioning to identify efficiency, dominance, and risk-adjusted
attractiveness relative to peers. Leverage alpha and relative metrics to
test persistence, robustness, and benchmark independence, flagging
statistically fragile or benchmark-hugging strategies. Synthesize these
findings into actionable conclusions by classifying managers as core,
satellite, tactical, or unsuitable; identifying complementary pairings based
on risk and asymmetry; highlighting red flags and monitoring triggers; and
presenting insights in clear, decision-focused language suitable for
investment committee review, explicitly referencing relevant time periods
and market contexts rather than relying on generic performance
summaries."

user_prompt = f"Using this EXACT DATA: {chat_context}\n\


nTask: {sys_role}"

response = [Link](

model="gpt-4-turbo",

messages=[{"role":"system","content":sys_role},
{"role":"user","content":user_prompt}])

st.session_state.insights =
[Link][0].[Link]

st.session_state.chat_history =
[{"role":"assistant","content":st.session_state.insights}]

except Exception as e: [Link](f"AI Error: {e}")

if 'insights' in st.session_state:

[Link](st.session_state.insights)

[Link]()

[Link]("**Chat with Jarir AI to get more insights:**")

if "chat_history" not in st.session_state: st.session_state.chat_history


= []

for msg in st.session_state.chat_history:

with st.chat_message(msg["role"]): [Link](msg["content"])

if user_query := st.chat_input("Ask about the data..."):

st.session_state.chat_history.append({"role":"user","content":user_query}
)

with st.chat_message("user"): [Link](user_query)

with st.chat_message("assistant"):

placeholder = [Link](); [Link]("🤓 Investment


Analyst is thinking...")
try:

client = [Link](api_key=key_input)

chat_resp = [Link](

model="gpt-4-turbo",

messages=[{"role":"system","content":f"Senior Analyst.
Context: {chat_context}"},

*st.session_state.chat_history])

reply = chat_resp.choices[0].[Link]

[Link](); [Link](reply)

st.session_state.chat_history.append({"role":"assistant","content":reply})

except Exception as e: [Link](f"Chat Error: {e}")

#
============================================
=========

# 17. EXCEL EXPORT

#
============================================
=========

def generate_excel(res, mgrs, cal_ret_df, cal_diff_df, alpha_df, cap_tbl_df,


wr_df_ex):

wb = Workbook(); [Link]([Link])

hdr_font = Font(name='Arial', bold=True, color='FFFFFF', size=10)

hdr_fill = PatternFill('solid', start_color='1A5276')

idx_font = Font(name='Arial', bold=True, size=10)

num_font = Font(name='Arial', size=10)

red_font = Font(name='Arial', size=10, color='FF0000')

center = Alignment(horizontal='center', vertical='center')

left_al = Alignment(horizontal='left', vertical='center')

thin = Side(style='thin', color='CCCCCC')


bdr = Border(left=thin, right=thin, top=thin, bottom=thin)

alt_fill = PatternFill('solid', start_color='EBF5FB')

def write_sheet(sheet_name, df, fmt="{:.2f}%"):

ws = wb.create_sheet(title=sheet_name[:31]); ws.freeze_panes =
'B2'

[Link](1,1,"Horizon").font = hdr_font; [Link](1,1).fill = hdr_fill

[Link](1,1).alignment = center; [Link](1,1).border = bdr

for ci, col in enumerate([Link], 2):

c = [Link](1,ci,str(col))

[Link] = hdr_font; [Link] = hdr_fill; [Link] = center; [Link] =


bdr

ws.column_dimensions[get_column_letter(ci)].width = max(14,
len(str(col))+4)

ws.column_dimensions['A'].width = 18; ws.row_dimensions[1].height


= 22

for ri, (idx, row) in enumerate([Link](), 2):

ic = [Link](ri,1,str(idx)); [Link] = idx_font; [Link] = left_al;


[Link] = bdr

if ri%2==0: [Link] = alt_fill

for ci, val in enumerate(row, 2):

cell = [Link](ri,ci); [Link] = bdr; [Link] = center

if ri%2==0: [Link] = alt_fill

try:

v = float(val)

[Link] = v/100 if "%" in fmt else v

cell.number_format = '0.00%' if "%" in fmt else '0.00'

[Link] = red_font if v < 0 else num_font

except: [Link] = ""; [Link] = num_font

for metric in res:


fmt = "{:.2f}%" if "Ratio" not in metric else "{:.2f}"

write_sheet(metric[:31], res[metric].apply(pd.to_numeric,
errors='coerce'), fmt)

write_sheet("Calendar Returns", cal_ret_df.apply(pd.to_numeric,


errors='coerce'))

write_sheet("Calendar Difference", cal_diff_df.apply(pd.to_numeric,


errors='coerce'))

write_sheet("Alpha Matrix", alpha_df.apply(pd.to_numeric,


errors='coerce'))

# Capture table

ws_cap = wb.create_sheet("Capture Table"); ws_cap.freeze_panes =


'B2'

ws_cap.cell(1,1,"Manager").font = hdr_font; ws_cap.cell(1,1).fill =


hdr_fill

ws_cap.cell(1,1).alignment = center; ws_cap.cell(1,1).border = bdr

ws_cap.column_dimensions['A'].width = 22

for ci, col in enumerate(cap_tbl_df.columns, 2):

c = ws_cap.cell(1,ci,str(col)); [Link] = hdr_font; [Link] = hdr_fill

[Link] = center; [Link] = bdr

ws_cap.column_dimensions[get_column_letter(ci)].width = 12

for ri, (idx, row) in enumerate(cap_tbl_df.iterrows(), 2):

ws_cap.cell(ri,1,str(idx)).font = idx_font; ws_cap.cell(ri,1).alignment


= left_al; ws_cap.cell(ri,1).border = bdr

for ci, val in enumerate(row, 2):

cell = ws_cap.cell(ri,ci); [Link] = bdr; [Link] = center

try:

v = float(val); [Link] = v; cell.number_format = '0.0'

[Link] = Font(name='Arial',size=10,color='1E8449' if v>=100


else 'FF0000',bold=(v>=100))

except: [Link] = ""; [Link] = num_font


# Win Rate

ws_wr = wb.create_sheet("Win Rate vs Benchmark");


ws_wr.freeze_panes = 'B2'

ws_wr.cell(1,1,"Manager").font = hdr_font; ws_wr.cell(1,1).fill = hdr_fill

ws_wr.cell(1,1).alignment = center; ws_wr.cell(1,1).border = bdr

ws_wr.column_dimensions['A'].width = 22

for ci, col in enumerate(wr_df_ex.columns, 2):

c = ws_wr.cell(1,ci,str(col)); [Link] = hdr_font; [Link] = hdr_fill

[Link] = center; [Link] = bdr

ws_wr.column_dimensions[get_column_letter(ci)].width = 10

for ri, (idx, row) in enumerate(wr_df_ex.iterrows(), 2):

ws_wr.cell(ri,1,str(idx)).font = idx_font; ws_wr.cell(ri,1).alignment =


left_al; ws_wr.cell(ri,1).border = bdr

for ci, val in enumerate(row, 2):

cell = ws_wr.cell(ri,ci); [Link] = bdr; [Link] = center

try:

v = float(val); [Link] = v/100; cell.number_format = '0.0%'

if v>=55: [Link] =
Font(name='Arial',size=10,color='1E8449',bold=True)

elif v<=45: [Link] =


Font(name='Arial',size=10,color='FF0000')

else: [Link] = Font(name='Arial',size=10,color='E67E22')

except: [Link] = ""; [Link] = num_font

buf = [Link](); [Link](buf); [Link](0); return buf

#
============================================
=========

# 18. PRETTY PDF EXPORT


#
============================================
=========

def _fmt_is_pct(metric_name): return metric_name.endswith("(%)")

def _format_val(v, is_pct=False):

if [Link](v) or v == "": return ""

try:

f = float(v)

return f"{f:,.2f}" + ("%" if is_pct else "")

except: return str(v)

def _is_negative(v):

try: return float(v) < 0

except: return False

def _wrap_label(name, max_word_len=14):

name = [Link](r"[/\-]", " / ", name)

def chunk(tok): return tok if len(tok)<=max_word_len else "\


u200b".join([tok[i:i+max_word_len] for i in
range(0,len(tok),max_word_len)])

return " ".join([chunk(t) for t in [Link]()])

def _fig_to_rl_image(fig, max_w, max_h, dpi=160):

buf = [Link](); [Link](buf, format="png", dpi=dpi,


bbox_inches="tight"); [Link](0)

img = RLImage(buf); w, h = [Link], [Link]

scale = min(max_w/float(w), max_h/float(h), 1.0)

[Link] = w*scale; [Link] = h*scale; return img

def generate_pdf_pretty(results_dict, mgrs, rfr_name, start_l, end_l,


metrics_order, fig_rr_=None, fig_ud_=None, viz_h_="",

max_cols_per_page=7):

buffer = [Link]()

doc = SimpleDocTemplate(buffer, pagesize=landscape(A4),

leftMargin=24, rightMargin=24, topMargin=36,


bottomMargin=36)

styles = getSampleStyleSheet()

page_w, page_h = landscape(A4)

avail_w = page_w - [Link] - [Link]

avail_h = page_h - [Link] - [Link]

elems = []

hdr_style = ParagraphStyle("hdr", parent=styles["Normal"],


fontName="Helvetica-Bold",

fontSize=8, leading=9.2, alignment=TA_CENTER)

hor_style = ParagraphStyle("hor", parent=styles["Normal"],


fontName="Helvetica",

fontSize=8, leading=9.2, alignment=TA_LEFT)

header_bg = [Link]("#ECF0F4")

grid_col = [Link]("#D1D7DF")

row_bg2 = [Link]("#FAFBFD")

# Cover

now_str = [Link]().strftime("%Y-%m-%d %H:%M")

[Link](Paragraph(f"Jarir Investments — Quant Performance


Report: {start_l} to {end_l}", styles["Title"]))

[Link](Paragraph(f"Generated: {now_str} | Risk-Free Rate:


<b>{rfr_name}</b>", styles["Normal"]))

[Link](Paragraph(f"Managers ({len(mgrs)}): {',


'.join(mgrs[:10])}{'…' if len(mgrs)>10 else ''}", styles["Normal"]))
[Link](Spacer(1,10))

# Charts

if fig_rr_ is not None:

[Link](PageBreak())

[Link](Paragraph(f"Risk–Return (Volatility vs Return) —


{viz_h_}", styles["Heading2"]))

[Link](Spacer(1,6))

[Link](_fig_to_rl_image(fig_rr_, avail_w, avail_h-40))

if fig_ud_ is not None:

[Link](PageBreak())

[Link](Paragraph(f"Upside–Downside (Downside Dev vs


Return) — {viz_h_}", styles["Heading2"]))

[Link](Spacer(1,6))

[Link](_fig_to_rl_image(fig_ud_, avail_w, avail_h-40))

if fig_rr_ is not None or fig_ud_ is not None:

[Link](PageBreak())

# Metric tables (chunked, pretty)

for mi, metric in enumerate(metrics_order):

dfm = results_dict[metric].apply(pd.to_numeric,
errors='coerce').round(2)

ncols = [Link][1]

chunks = max(1, [Link](ncols / max_cols_per_page))

for ci in range(chunks):

start_c = ci*max_cols_per_page; end_c =


min((ci+1)*max_cols_per_page, ncols)

sub = [Link][:, start_c:end_c].replace({[Link]: ""})

chunk_mgrs = ", ".join([_wrap_label(c) for c in [Link]])

[Link](Paragraph(metric, styles["Heading2"]))
[Link](Paragraph(f"<font size=9><b>Managers:</b>
{chunk_mgrs}</font>", styles["Normal"]))

[Link](Spacer(1,4))

is_pct = _fmt_is_pct(metric)

hdr_row = [Paragraph("Horizon", hdr_style)] +


[Paragraph(_wrap_label(str(c)), hdr_style) for c in [Link]]

data = [hdr_row]

for idx_, row_ in [Link]():

r = [Paragraph(str(idx_), hor_style)]

r += [_format_val(v, is_pct) for v in row_.values]

[Link](r)

ncols_t = len(data[0])

rest_ = ncols_t - 1

horizon_w = 120; avail_rest = max(120, avail_w - horizon_w)

w_each = min(max(avail_rest/rest_, 70), 115) if rest_ > 0 else


avail_rest

col_widths = [horizon_w] + [w_each]*rest_

tbl = LongTable(data, repeatRows=1, colWidths=col_widths,


hAlign="LEFT")

ts = TableStyle([

("BACKGROUND",(0,0),(-1,0), header_bg),

("TEXTCOLOR",(0,0),(-1,0), [Link]("#1F2328")),

("FONTNAME",(0,0),(-1,0), "Helvetica-Bold"),

("FONTSIZE",(0,0),(-1,0), 8.5),

("FONTSIZE",(0,1),(-1,-1), 8),

("VALIGN",(0,0),(-1,-1), "MIDDLE"),

("ALIGN",(0,0),(0,-1), "LEFT"),
("ALIGN",(1,0),(-1,-1), "RIGHT"),

("ROWBACKGROUNDS",(0,1),(-1,-1), [[Link], row_bg2]),

("GRID",(0,0),(-1,-1),0.25, grid_col),

("LEFTPADDING",(0,0),(-1,-1), 5),

("RIGHTPADDING",(0,0),(-1,-1), 5),

("TOPPADDING",(0,0),(-1,-1), 3),

("BOTTOMPADDING",(0,0),(-1,-1),3),

])

for r_idx in range(1, len(data)):

for c_idx in range(1, len(data[0])):

if _is_negative([Link][r_idx-1, c_idx-1]):

[Link]("TEXTCOLOR",(c_idx,r_idx),(c_idx,r_idx),
[Link]("#C0342B"))

[Link](ts); [Link](tbl)

[Link](Spacer(1,8) if ci < chunks-1 else (PageBreak() if mi


< len(metrics_order)-1 else Spacer(1,6)))

# Calendar, Alpha, 36M

def add_simple_table(df_p, title, is_pct=True):

[Link](Paragraph(title, styles["Heading2"]))

num_cols = len(df_p.columns)+1

font_size = 7 if num_cols<=10 else 5.5

index_w = 80 if num_cols<=10 else 60

col_widths = [index_w] + [(avail_w-index_w)/(num_cols-


1)]*(num_cols-1) if num_cols > 1 else [avail_w]

data = [[""] + list(df_p.columns)]

style_ = [('BACKGROUND',(0,0),(-1,0),[Link]("#4F6228")),

('TEXTCOLOR',(0,0),(-1,0),[Link]),

('GRID',(0,0),(-1,-1),0.5,[Link]),

('FONTSIZE',(0,0),(-1,-1),font_size),
('ALIGN',(0,0),(-1,-1),'CENTER'),

('FONTNAME',(0,0),(0,-1),'Helvetica-Bold'),

('TEXTCOLOR',(0,1),(0,-1),[Link])]

for r_i,(idx_,row_) in enumerate(df_p.iterrows(),1):

row_vals = [str(idx_)]

for c_i,v in enumerate(row_,1):

if [Link](v): row_vals.append("")

else:

row_vals.append(f"{v:.2f}%" if is_pct else f"{v:.2f}")

if v<0: style_.append(('TEXTCOLOR',(c_i,r_i),
(c_i,r_i),[Link]))

[Link](row_vals)

t = LongTable(data, repeatRows=1, colWidths=col_widths)

[Link](TableStyle(style_)); [Link](t);
[Link](Spacer(1,15))

[Link](PageBreak())

add_simple_table(cal_ret.apply(pd.to_numeric,errors='coerce').round(2),
"Calendar Year Returns (%)")

add_simple_table(cal_diff.apply(pd.to_numeric,errors='coerce').round(2),
f"Calendar Difference vs {bench_diff} (%)")

[Link](PageBreak())

add_simple_table(alpha_disp_year.apply(pd.to_numeric,errors='coerce').ro
und(2),

f"Alpha Matrix: {alpha_fund} vs {alpha_bench} (%)")

[Link](PageBreak())

m_36_p = cleaned_df.set_index('Date')[mgrs].tail(36).iloc[::-1]

m_36_p.index = m_36_p.[Link]('%b-%Y')
add_simple_table((m_36_p*100).apply(pd.to_numeric,errors='coerce').rou
nd(2), "Trailing 36-Month Returns (%)")

# Page numbers

def add_footer(canvas, doc_):

[Link]()

[Link]("Helvetica", 8)

[Link](doc_.pagesize[0]-24, 20, f"Page


{doc_.page}")

[Link]()

[Link](elems, onFirstPage=add_footer, onLaterPages=add_footer)

[Link](0); return [Link]()

#
============================================
=========

# 19. SIDEBAR EXPORT BUTTONS

#
============================================
=========

[Link]()

# CSV

csv_data = [Link](results, axis=0).to_csv().encode("utf-8")

[Link].download_button("📄 Download CSV", data=csv_data,

file_name=f"{[Link]()}_jarir_results.csv",
mime="text/csv")

[Link]()

if [Link]("📥 Generate Excel Report"):


with [Link]("Building Excel workbook..."):

xl_buf = generate_excel(

{m: results[m].apply(pd.to_numeric, errors='coerce') for m in


metrics},

manager_cols, cal_ret, cal_diff, alpha_disp_year, cap_tbl, wr_df)

[Link].download_button(

"⬇️Download Excel", data=xl_buf,

file_name=f"{[Link]()}_jarir_quant_analysis.xlsx",

mime="application/[Link]-
[Link]")

[Link]()

if [Link]("📑 Generate Master PDF"):

with [Link]("Building professional PDF report..."):

pdf_bytes = generate_pdf_pretty(

results_dict=results, mgrs=manager_cols, rfr_name=rfr_col,

start_l=start_label, end_l=end_label, metrics_order=metrics,

fig_rr_=fig_rr, fig_ud_=fig_ud, viz_h_=viz_h)

[Link].download_button(

"⬇️Download PDF", data=pdf_bytes,

file_name=f"{[Link]()}_jarir_quant_analysis.pdf",

mime="application/pdf")

You might also like