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%
8.4 KB · 238 lines python
Raw Blame History
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