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%
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