SPB Git

spb/vquant Public MIT

VibeQuant — AI-powered institutional-grade financial intelligence platform.

TypeScript 84.3% Python 11.7% JavaScript 1.6% CSS 1.5% HTML 0.7%
10.9 KB · 321 lines python
Raw Blame History
1#!/usr/bin/env python32# =============================================================================3#  VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4# -----------------------------------------------------------------------------5#  File:      server/services/varService.py6#7#  Author:    Simon-Pierre Boucher8#  Contact:   contact@spboucher.ai9#  Website:   https://www.spboucher.ai10#  Demo:      https://www.vquant.ai11#  License:   MIT (see LICENSE)12#13#  Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14# =============================================================================1516"""17Value at Risk (VaR) Calculator Service18Calculates VaR using multiple methodologies for risk assessment19"""20import json21import sys22import numpy as np23import pandas as pd24import os25from scipy import stats2627# Import our FMP client28sys.path.insert(0, os.path.dirname(__file__))29from fmpClient import get_historical_prices303132def calculate_historical_var(returns, confidence_level=0.95):33    """34    Calculate Historical VaR using the empirical distribution of returns3536    Args:37        returns: Array of historical returns38        confidence_level: Confidence level (default 0.95 for 95%)3940    Returns:41        VaR as a positive number (loss)42    """43    if len(returns) == 0:44        return 0.04546    # VaR is the negative of the percentile (we want loss, not gain)47    var = -np.percentile(returns, (1 - confidence_level) * 100)48    return float(var)495051def calculate_parametric_var(returns, confidence_level=0.95):52    """53    Calculate Parametric VaR assuming normal distribution5455    Args:56        returns: Array of historical returns57        confidence_level: Confidence level (default 0.95 for 95%)5859    Returns:60        VaR as a positive number (loss)61    """62    if len(returns) == 0:63        return 0.06465    mean = np.mean(returns)66    std = np.std(returns, ddof=1)6768    # Z-score for the confidence level69    z_score = stats.norm.ppf(1 - confidence_level)7071    # VaR = -(mean + z_score * std)72    var = -(mean + z_score * std)73    return float(var)747576def calculate_monte_carlo_var(returns, confidence_level=0.95, num_simulations=10000):77    """78    Calculate Monte Carlo VaR using simulated returns7980    Args:81        returns: Array of historical returns82        confidence_level: Confidence level (default 0.95 for 95%)83        num_simulations: Number of Monte Carlo simulations8485    Returns:86        VaR as a positive number (loss)87    """88    if len(returns) == 0:89        return 0.09091    mean = np.mean(returns)92    std = np.std(returns, ddof=1)9394    # Generate simulated returns assuming normal distribution95    simulated_returns = np.random.normal(mean, std, num_simulations)9697    # Calculate VaR from simulated returns98    var = -np.percentile(simulated_returns, (1 - confidence_level) * 100)99    return float(var)100101102def calculate_cvar(returns, confidence_level=0.95):103    """104    Calculate Conditional VaR (Expected Shortfall/CVaR)105    Average of losses beyond the VaR threshold106107    Args:108        returns: Array of historical returns109        confidence_level: Confidence level (default 0.95 for 95%)110111    Returns:112        CVaR as a positive number (loss)113    """114    if len(returns) == 0:115        return 0.0116117    # Get the VaR threshold118    var_threshold = -calculate_historical_var(returns, confidence_level)119120    # Calculate mean of returns below the VaR threshold121    tail_losses = returns[returns <= var_threshold]122123    if len(tail_losses) == 0:124        return float(calculate_historical_var(returns, confidence_level))125126    cvar = -np.mean(tail_losses)127    return float(cvar)128129130def run_var_analysis(data_json: str) -> dict:131    """132    Run comprehensive VaR analysis on financial data133134    Args:135        data_json: JSON string containing:136            - symbol: Stock ticker symbol (e.g., 'AAPL')137            - portfolio_value: Total portfolio value (default: 100000)138            - confidence_levels: List of confidence levels (default: [0.90, 0.95, 0.99])139            - time_horizon: Days to project (default: 1 for 1-day VaR)140            - num_simulations: Monte Carlo simulations (default: 10000)141            - data_period: Historical data period in days (default: 504 for ~2 years)142143    Returns:144        Dictionary containing VaR calculations using multiple methods145    """146    try:147        data = json.loads(data_json)148149        # Extract parameters150        symbol = data.get('symbol')151        if not symbol:152            return {153                "error": "Symbol is required",154                "success": False155            }156157        portfolio_value = data.get('portfolio_value', 100000)158        confidence_levels = data.get('confidence_levels', [0.90, 0.95, 0.99])159        time_horizon = data.get('time_horizon', 1)160        num_simulations = data.get('num_simulations', 10000)161        data_period = data.get('data_period', 504)162163        # Fetch historical prices from FMP API164        print(f"Fetching historical prices for {symbol} from FMP API...", file=sys.stderr)165        hist_data = get_historical_prices(symbol)166167        # Extract closing prices (most recent first, so reverse for chronological order)168        prices = np.array([h['close'] for h in reversed(hist_data['historical'])])169        dates = [h['date'] for h in reversed(hist_data['historical'])]170171        # Limit to requested period172        if len(prices) > data_period:173            prices = prices[-data_period:]174            dates = dates[-data_period:]175176        print(f"Using {len(prices)} historical prices for VaR analysis", file=sys.stderr)177178        if len(prices) < 30:179            return {180                "error": f"Need at least 30 historical prices for VaR calculation (got {len(prices)})",181                "success": False182            }183184        # Calculate daily returns185        returns = np.diff(prices) / prices[:-1]186187        # Scale returns for time horizon (assuming independent daily returns)188        if time_horizon > 1:189            returns_scaled = returns * np.sqrt(time_horizon)190        else:191            returns_scaled = returns192193        # Calculate VaR for each confidence level using all methods194        var_results = {}195196        for conf_level in confidence_levels:197            conf_pct = int(conf_level * 100)198199            # Historical VaR200            hist_var = calculate_historical_var(returns_scaled, conf_level)201            hist_var_dollar = hist_var * portfolio_value202203            # Parametric VaR204            param_var = calculate_parametric_var(returns_scaled, conf_level)205            param_var_dollar = param_var * portfolio_value206207            # Monte Carlo VaR208            mc_var = calculate_monte_carlo_var(returns_scaled, conf_level, num_simulations)209            mc_var_dollar = mc_var * portfolio_value210211            # Conditional VaR (CVaR)212            cvar = calculate_cvar(returns_scaled, conf_level)213            cvar_dollar = cvar * portfolio_value214215            var_results[f"{conf_pct}%"] = {216                "confidence_level": conf_level,217                "historical": {218                    "var_percentage": round(hist_var * 100, 4),219                    "var_dollar": round(hist_var_dollar, 2)220                },221                "parametric": {222                    "var_percentage": round(param_var * 100, 4),223                    "var_dollar": round(param_var_dollar, 2)224                },225                "monte_carlo": {226                    "var_percentage": round(mc_var * 100, 4),227                    "var_dollar": round(mc_var_dollar, 2)228                },229                "conditional_var": {230                    "cvar_percentage": round(cvar * 100, 4),231                    "cvar_dollar": round(cvar_dollar, 2)232                }233            }234235        # Calculate additional statistics236        mean_return = float(np.mean(returns))237        std_return = float(np.std(returns, ddof=1))238        skewness = float(stats.skew(returns))239        kurtosis = float(stats.kurtosis(returns))240241        # Annualize statistics242        annual_return = mean_return * 252243        annual_volatility = std_return * np.sqrt(252)244245        # Test for normality (Jarque-Bera test)246        jb_stat, jb_pvalue = stats.jarque_bera(returns)247        is_normal = bool(jb_pvalue > 0.05)248249        # Calculate worst historical loss250        worst_loss = float(-np.min(returns))251        worst_loss_dollar = worst_loss * portfolio_value252253        # Calculate best historical gain254        best_gain = float(np.max(returns))255        best_gain_dollar = best_gain * portfolio_value256257        # Return results258        results = {259            "success": True,260            "symbol": symbol,261            "parameters": {262                "portfolio_value": portfolio_value,263                "time_horizon": time_horizon,264                "time_horizon_description": f"{time_horizon}-day VaR",265                "num_simulations": num_simulations,266                "data_points": len(returns),267                "data_period_days": data_period268            },269            "var_by_confidence": var_results,270            "distribution_statistics": {271                "mean_daily_return": round(mean_return * 100, 4),272                "std_daily_return": round(std_return * 100, 4),273                "annual_return": round(annual_return * 100, 2),274                "annual_volatility": round(annual_volatility * 100, 2),275                "skewness": round(skewness, 4),276                "kurtosis": round(kurtosis, 4),277                "is_normally_distributed": is_normal,278                "jarque_bera_pvalue": round(jb_pvalue, 4)279            },280            "extreme_values": {281                "worst_daily_loss_pct": round(worst_loss * 100, 4),282                "worst_daily_loss_dollar": round(worst_loss_dollar, 2),283                "best_daily_gain_pct": round(best_gain * 100, 4),284                "best_daily_gain_dollar": round(best_gain_dollar, 2)285            },286            "interpretation": {287                "distribution_type": "normal" if is_normal else "non-normal",288                "tail_risk": "high" if abs(skewness) > 1 or kurtosis > 3 else "moderate" if abs(skewness) > 0.5 or kurtosis > 1 else "low",289                "recommended_method": "historical" if not is_normal else "parametric",290                "notes": [291                    "Historical VaR uses actual distribution of returns",292                    "Parametric VaR assumes normal distribution",293                    "Monte Carlo VaR uses simulated returns",294                    "CVaR shows average loss beyond VaR threshold",295                    f"Distribution is {'normal' if is_normal else 'non-normal'} (Jarque-Bera test)"296                ]297            }298        }299300        return results301302    except Exception as e:303        import traceback304        error_details = traceback.format_exc()305        print(f"ERROR: {error_details}", file=sys.stderr)306        return {307            "error": str(e),308            "success": False309        }310311312if __name__ == "__main__":313    # Read input from stdin314    input_data = sys.stdin.read()315316    # Run VaR analysis317    result = run_var_analysis(input_data)318319    # Output result as JSON320    print(json.dumps(result))321