""" app.py -- Gradio UI for Value at Risk Analysis. """ import os import pandas as pd import gradio as gr from src.logger import logger from src.config import TICKERS, LOOKBACK_DAYS, STRESS_START_DATE, STRESS_END_DATE, STRESS_LABEL from src.historical import historical_var_es_pipeline from src.parametric import parametric_var_es_pipeline def calculate_var_analysis( ticker: str, end_date_str: str, portfolio_value: float, n_days: int, var_confidence_label: str, es_confidence_label: str, method: str, ): """Calculate Value at Risk analysis based on Gradio inputs and delegate to the analysis pipeline.""" logger.debug( f"Analysis requested: {ticker} | VaR={var_confidence_label} ES={es_confidence_label} | {method} | N={n_days} | Date={end_date_str} | PV=${portfolio_value:,.0f}" ) var_confidence = float(var_confidence_label.strip().replace("%", "")) / 100.0 es_confidence = float(es_confidence_label.strip().replace("%", "")) / 100.0 var_conf_pct = var_confidence_label.strip() es_conf_pct = es_confidence_label.strip() today = pd.Timestamp.today().normalize() try: end_date = pd.to_datetime(end_date_str, errors="raise").normalize() except Exception: gr.Warning("Invalid date selection. Please try again.") return reset_analysis_results(n_days, var_confidence_label, es_confidence_label, method) if end_date >= today: gr.Warning( "Invalid date selection. VaR estimation requires historical data, so please choose a date prior to today." ) return reset_analysis_results(n_days, var_confidence_label, es_confidence_label, method) fy_start = pd.Timestamp(year=today.year - 1, month=4, day=1) if end_date < fy_start: gr.Warning(f"VaR Date must be after {fy_start.strftime('%Y-%m-%d')}") return reset_analysis_results(n_days, var_confidence_label, es_confidence_label, method) if portfolio_value <= 0: gr.Warning("Portfolio value must be positive.") return reset_analysis_results(n_days, var_confidence_label, es_confidence_label, method) pipeline = historical_var_es_pipeline if method == "Historical VaR" else parametric_var_es_pipeline result = pipeline( ticker, var_confidence, es_confidence, LOOKBACK_DAYS, int(n_days), portfolio_value, end_date, stress_start=STRESS_START_DATE, stress_end=STRESS_END_DATE, stress_label=STRESS_LABEL, ) logger.info( f"Analysis complete: {ticker} | VaR=${result['var_nd']:,.2f} | ES=${result['es_nd']:,.2f}" ) return ( gr.update( label=f"{int(n_days)}-day {var_conf_pct} VaR", value=f"${result['var_nd']:,.2f}", ), gr.update( label=f"{int(n_days)}-day {es_conf_pct} ES", value=f"${result['es_nd']:,.2f}", ), gr.update( label=f"{int(n_days)}-day {var_conf_pct} Stressed VaR", value=f"${result['stressed_var_nd']:,.2f}", ), gr.update( label=f"{int(n_days)}-day {es_conf_pct} Stressed ES", value=f"${result['stressed_es_nd']:,.2f}", ), gr.update(value=result["fig_dist"], visible=True), gr.update(value=result["excel_path"], visible=True), ) def reset_analysis_results(n_days: float, var_confidence_label: str, es_confidence_label: str, method: str): """Reset and hide analysis results when input parameters are modified.""" var_conf_pct = var_confidence_label.strip() es_conf_pct = es_confidence_label.strip() method_short = "Historical" if method == "Historical VaR" else "Parametric" return ( gr.update(value="", label=f"{int(n_days)}-day {var_conf_pct} VaR"), gr.update(value="", label=f"{int(n_days)}-day {es_conf_pct} ES"), gr.update(value="", label=f"{int(n_days)}-day {var_conf_pct} Stressed VaR"), gr.update(value="", label=f"{int(n_days)}-day {es_conf_pct} Stressed ES"), gr.update(value=None, visible=False), gr.update(visible=False), ) def enable_run_button_for_method(method: str): """Enable the Run Analysis button only for fully implemented VaR methods.""" return gr.update(interactive=(method in ("Historical VaR", "Parametric VaR"))) # ------------------------------------------------------------------ # UI builder # ------------------------------------------------------------------ CUSTOM_CSS = """ .form { border: none !important; box-shadow: none !important; gap: 0 !important; } .form .block, .form .row, .form > * { border: none !important; box-shadow: none !important; } #excel-btn, #excel-btn.primary { background: #f97316 !important; background-color: #f97316 !important; color: white !important; border-color: #9a3412 !important; border: 1px solid #9a3412 !important; } #excel-btn:hover, #excel-btn.primary:hover { background: #ea580c !important; background-color: #ea580c !important; border-color: #9a3412 !important; border: 1px solid #9a3412 !important; } #portfolio-hr { margin: 2rem 0 0.5rem !important; border: none !important; border-top: 1px solid #e5e7eb !important; } #portfolio-footer { width: 100% !important; max-width: 100% !important; } #portfolio-text { text-align: center !important; margin: 0 auto !important; padding: 0.75rem 0 !important; width: 100% !important; } #portfolio-text a { color: #ffffff !important; font-weight: 500 !important; font-size: 0.8rem !important; font-family: 'Inter', 'Segoe UI', system-ui, sans-serif !important; letter-spacing: 0.05em !important; text-decoration: none !important; cursor: pointer !important; } #portfolio-text a:hover { color: #d1d5db !important; } """ def build_app() -> gr.Blocks: """Construct and return the Gradio Blocks application.""" with gr.Blocks( title="VaR Engine", ) as app: with gr.Row(): with gr.Column(scale=3): gr.Markdown("# VaR Engine") with gr.Column(scale=1, min_width=260): download_file = gr.DownloadButton( "Download Excel Report", variant="primary", visible=False, elem_id="excel-btn", ) with gr.Row(): # Sidebar with gr.Column(scale=1, min_width=260): gr.Markdown("### Inputs") with gr.Group(): ticker_dd = gr.Dropdown( choices=TICKERS, value=TICKERS[0], label="Ticker", ) end_date_input = gr.DateTime( include_time=False, type="datetime", label="VaR Date", ) portfolio_val_input = gr.Number( value=1000000, label="Portfolio Value ($)", ) n_days_slider = gr.Slider( minimum=1, maximum=15, step=1, value=10, label="N Days for VaR", ) with gr.Row(): confidence_dd = gr.Dropdown( choices=["99%", "97.5%", "95%"], value="99%", label="VaR Confidence", min_width=100, ) es_confidence_dd = gr.Dropdown( choices=["99%", "97.5%", "95%"], value="99%", label="ES Confidence", min_width=100, ) method_radio = gr.Radio( choices=[ "Historical VaR", "Parametric VaR", ], value="Historical VaR", label="Method", ) run_btn = gr.Button("Run Analysis", variant="primary") # Enable/disable run button based on method availability method_radio.change( fn=enable_run_button_for_method, inputs=method_radio, outputs=run_btn, ) with gr.Column(scale=3): gr.Markdown("### Results") with gr.Row(): with gr.Column(scale=1): var_box = gr.Textbox( label="10-day 99% VaR", interactive=False, ) with gr.Column(scale=1): es_box = gr.Textbox( label="10-day 99% ES", interactive=False, ) with gr.Row(): with gr.Column(scale=1): stressed_var_box = gr.Textbox( label="10-day 99% Stressed VaR", interactive=False, ) with gr.Column(scale=1): stressed_es_box = gr.Textbox( label="10-day 99% Stressed ES", interactive=False, ) plot_dist = gr.Plot(show_label=False, visible=False) # Wiring all_outputs = [var_box, es_box, stressed_var_box, stressed_es_box, plot_dist, download_file] run_btn.click( fn=calculate_var_analysis, inputs=[ ticker_dd, end_date_input, portfolio_val_input, n_days_slider, confidence_dd, es_confidence_dd, method_radio, ], outputs=all_outputs, ) all_inputs = [ ticker_dd, end_date_input, portfolio_val_input, n_days_slider, confidence_dd, es_confidence_dd, method_radio, ] label_inputs = [n_days_slider, confidence_dd, es_confidence_dd, method_radio] for comp in all_inputs: if comp is n_days_slider: comp.release( fn=reset_analysis_results, inputs=label_inputs, outputs=all_outputs, ) else: comp.change( fn=reset_analysis_results, inputs=label_inputs, outputs=all_outputs, ) method_radio.change( fn=enable_run_button_for_method, inputs=method_radio, outputs=run_btn ) gr.HTML( '
' '

' '' "\u00a9 kameshcodes" "

", elem_id="portfolio-footer", ) return app # ------------------------------------------------------------------ # Entry point # ------------------------------------------------------------------ if __name__ == "__main__": port = int(os.environ.get("PORT", 7860)) application = build_app() application.launch(server_name="0.0.0.0", server_port=port, share=False, theme=gr.themes.Base(), css=CUSTOM_CSS)