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/garchService.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"""17GARCH Model Service18Estimates GARCH volatility models using financial data from FMP API directly19"""20import json21import sys22import numpy as np23import pandas as pd24import os25from datetime import datetime, timedelta2627# Import our FMP client28sys.path.insert(0, os.path.dirname(__file__))29from fmpClient import get_historical_prices3031# Import GARCH from arch library32try:33 from arch import arch_model34 from arch.__future__ import reindexing35except ImportError:36 print("ERROR: arch library not installed. Please run: pip install arch", file=sys.stderr)37 sys.exit(1)383940def run_garch_analysis(data_json: str) -> dict:41 """42 Run GARCH volatility model analysis on financial data4344 Args:45 data_json: JSON string containing:46 - symbol: Stock ticker symbol (e.g., 'AAPL')47 - p: GARCH p parameter (default: 1)48 - q: GARCH q parameter (default: 1)49 - forecast_horizon: Number of days to forecast (default: 30)50 - data_period: Historical data period in days (default: 504 for ~2 years)5152 Returns:53 Dictionary containing GARCH model results and volatility forecasts54 """55 try:56 data = json.loads(data_json)5758 # Extract parameters59 symbol = data.get('symbol')60 if not symbol:61 return {62 "error": "Symbol is required",63 "success": False64 }6566 p = data.get('p', 1) # GARCH order for lagged variance67 q = data.get('q', 1) # GARCH order for lagged squared residuals68 forecast_horizon = data.get('forecast_horizon', 30)69 data_period = data.get('data_period', 504)7071 # Fetch historical prices from FMP API72 print(f"Fetching historical prices for {symbol} from FMP API...", file=sys.stderr)73 hist_data = get_historical_prices(symbol)7475 # Extract closing prices (most recent first, so reverse for chronological order)76 prices = [h['close'] for h in reversed(hist_data['historical'])]77 dates = [h['date'] for h in reversed(hist_data['historical'])]7879 # Limit to requested period80 if len(prices) > data_period:81 prices = prices[-data_period:]82 dates = dates[-data_period:]8384 print(f"Using {len(prices)} historical prices for GARCH analysis", file=sys.stderr)8586 if len(prices) < 30:87 return {88 "error": f"Need at least 30 historical prices for GARCH model (got {len(prices)})",89 "success": False90 }9192 # Convert to pandas Series with date index93 price_series = pd.Series(prices, index=pd.to_datetime(dates))9495 # Calculate percentage returns (multiply by 100 for better numerical stability)96 returns = 100 * price_series.pct_change().dropna()9798 print(f"Calculated {len(returns)} returns for GARCH model", file=sys.stderr)99100 # Fit GARCH model101 print(f"Fitting GARCH({p},{q}) model...", file=sys.stderr)102103 # Use constant mean model with GARCH volatility104 model = arch_model(105 returns,106 vol='Garch',107 p=p,108 q=q,109 mean='constant',110 dist='normal'111 )112113 # Fit the model114 model_fit = model.fit(disp='off', show_warning=False)115116 # Extract model parameters117 params = model_fit.params118119 # Get parameter names and values120 param_dict = {}121 for param_name in params.index:122 param_dict[param_name] = float(params[param_name])123124 # Extract GARCH-specific parameters125 omega = float(params.get('omega', 0))126 alpha = [float(params.get(f'alpha[{i+1}]', 0)) for i in range(q)]127 beta = [float(params.get(f'beta[{i+1}]', 0)) for i in range(p)]128129 # Calculate persistence (sum of alpha and beta)130 persistence = sum(alpha) + sum(beta)131132 # Get conditional volatility133 conditional_volatility = model_fit.conditional_volatility134135 # Annualize conditional volatility (from daily to annual)136 annualized_volatility = conditional_volatility * np.sqrt(252) / 100 # Convert back from percentage137138 # Get recent volatility values139 recent_volatility = annualized_volatility.tail(30).tolist()140 recent_dates = annualized_volatility.tail(30).index.strftime('%Y-%m-%d').tolist()141142 # Current volatility143 current_volatility = float(annualized_volatility.iloc[-1])144145 # Forecast volatility146 print(f"Forecasting volatility for {forecast_horizon} days...", file=sys.stderr)147 forecasts = model_fit.forecast(horizon=forecast_horizon, reindex=False)148149 # Get variance forecasts and convert to volatility150 forecast_variance = forecasts.variance.values[-1, :] # Last row contains the forecasts151 forecast_volatility = np.sqrt(forecast_variance) * np.sqrt(252) / 100 # Annualize and convert from percentage152153 # Generate forecast dates154 last_date = pd.to_datetime(dates[-1])155 forecast_dates = [(last_date + timedelta(days=i+1)).strftime('%Y-%m-%d') for i in range(forecast_horizon)]156157 # Model statistics158 aic = float(model_fit.aic)159 bic = float(model_fit.bic)160 log_likelihood = float(model_fit.loglikelihood)161162 # Calculate summary statistics163 avg_volatility = float(annualized_volatility.mean())164 min_volatility = float(annualized_volatility.min())165 max_volatility = float(annualized_volatility.max())166 std_volatility = float(annualized_volatility.std())167168 # Return results169 results = {170 "success": True,171 "symbol": symbol,172 "model_specification": {173 "type": f"GARCH({p},{q})",174 "p": p,175 "q": q,176 "mean_model": "constant",177 "distribution": "normal"178 },179 "parameters": {180 "all_params": param_dict,181 "omega": omega,182 "alpha": alpha,183 "beta": beta,184 "persistence": persistence,185 "mean": float(params.get('mu', 0))186 },187 "model_fit": {188 "aic": aic,189 "bic": bic,190 "log_likelihood": log_likelihood,191 "num_observations": len(returns)192 },193 "volatility_statistics": {194 "current_volatility": current_volatility,195 "average_volatility": avg_volatility,196 "min_volatility": min_volatility,197 "max_volatility": max_volatility,198 "std_volatility": std_volatility199 },200 "historical_volatility": {201 "dates": recent_dates,202 "values": recent_volatility203 },204 "volatility_forecast": {205 "dates": forecast_dates,206 "values": forecast_volatility.tolist(),207 "horizon": forecast_horizon208 },209 "interpretation": {210 "is_mean_reverting": persistence < 1,211 "persistence_level": "high" if persistence > 0.95 else "moderate" if persistence > 0.85 else "low",212 "shock_impact": "high" if sum(alpha) > 0.2 else "moderate" if sum(alpha) > 0.1 else "low",213 "volatility_clustering": "strong" if persistence > 0.9 else "moderate" if persistence > 0.7 else "weak"214 }215 }216217 return results218219 except Exception as e:220 import traceback221 error_details = traceback.format_exc()222 print(f"ERROR: {error_details}", file=sys.stderr)223 return {224 "error": str(e),225 "success": False226 }227228229if __name__ == "__main__":230 # Read input from stdin231 input_data = sys.stdin.read()232233 # Run GARCH analysis234 result = run_garch_analysis(input_data)235236 # Output result as JSON237 print(json.dumps(result))238