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/*2 * =============================================================================3 * VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 * File: server/services/python/garch.ts6 *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 * =============================================================================15 */1617import { spawn } from 'child_process';18import path from 'path';19import { fileURLToPath } from 'url';20import { dirname } from 'path';2122const __filename = fileURLToPath(import.meta.url);23const __dirname = dirname(__filename);2425// Determine Python executable path - use venv if available26const PYTHON_PATH = process.env.PYTHON_PATH ||27 path.join(process.cwd(), '.venv', 'bin', 'python') ||28 'python3';293031export interface GarchInput {32 symbol: string;33 p?: number; // GARCH p parameter (default: 1)34 q?: number; // GARCH q parameter (default: 1)35 forecast_horizon?: number; // Number of days to forecast (default: 30)36 data_period?: number; // Historical data period in days (default: 504)37}3839export interface GarchParameters {40 all_params: Record<string, number>;41 omega: number;42 alpha: number[];43 beta: number[];44 persistence: number;45 mean: number;46}4748export interface GarchVolatilityStats {49 current_volatility: number;50 average_volatility: number;51 min_volatility: number;52 max_volatility: number;53 std_volatility: number;54}5556export interface GarchHistoricalVolatility {57 dates: string[];58 values: number[];59}6061export interface GarchForecast {62 dates: string[];63 values: number[];64 horizon: number;65}6667export interface GarchInterpretation {68 is_mean_reverting: boolean;69 persistence_level: string;70 shock_impact: string;71 volatility_clustering: string;72}7374export interface GarchResult {75 success: boolean;76 error?: string;77 symbol?: string;78 model_specification?: {79 type: string;80 p: number;81 q: number;82 mean_model: string;83 distribution: string;84 };85 parameters?: GarchParameters;86 model_fit?: {87 aic: number;88 bic: number;89 log_likelihood: number;90 num_observations: number;91 };92 volatility_statistics?: GarchVolatilityStats;93 historical_volatility?: GarchHistoricalVolatility;94 volatility_forecast?: GarchForecast;95 interpretation?: GarchInterpretation;96}9798export interface GarchExecutionResult {99 success: boolean;100 result?: GarchResult;101 code: string;102 error?: string;103}104105/**106 * Validate GARCH input107 */108function validateGarchInput(input: GarchInput): { valid: boolean; error?: string } {109 if (!input.symbol || typeof input.symbol !== 'string') {110 return { valid: false, error: 'symbol must be a string' };111 }112113 if (input.p !== undefined && (input.p < 1 || input.p > 5)) {114 return { valid: false, error: 'p must be between 1 and 5' };115 }116117 if (input.q !== undefined && (input.q < 1 || input.q > 5)) {118 return { valid: false, error: 'q must be between 1 and 5' };119 }120121 if (input.forecast_horizon !== undefined && (input.forecast_horizon < 1 || input.forecast_horizon > 252)) {122 return { valid: false, error: 'forecast_horizon must be between 1 and 252' };123 }124125 if (input.data_period !== undefined && (input.data_period < 30 || input.data_period > 2520)) {126 return { valid: false, error: 'data_period must be between 30 and 2520' };127 }128129 return { valid: true };130}131132/**133 * Execute GARCH volatility model using Python134 */135export async function executeGarchModel(136 input: GarchInput137): Promise<GarchExecutionResult> {138 try {139 // Validate input140 const validation = validateGarchInput(input);141 if (!validation.valid) {142 return {143 success: false,144 code: generateGarchCode(input),145 error: validation.error146 };147 }148149 const pythonScriptPath = path.join(__dirname, 'garchService.py');150 const inputJson = JSON.stringify(input);151152 return new Promise((resolve) => {153 const pythonProcess = spawn(PYTHON_PATH, [pythonScriptPath], {154 stdio: ['pipe', 'pipe', 'pipe'],155 env: process.env156 });157158 let stdout = '';159 let stderr = '';160161 pythonProcess.stdout.on('data', (data) => {162 stdout += data.toString();163 });164165 pythonProcess.stderr.on('data', (data) => {166 stderr += data.toString();167 });168169 pythonProcess.on('close', (code) => {170 if (code !== 0 || (stderr && !stdout)) {171 resolve({172 success: false,173 code: generateGarchCode(input),174 error: stderr || `Python process exited with code ${code}`175 });176 return;177 }178179 try {180 const result = JSON.parse(stdout) as GarchResult;181 resolve({182 success: result.success,183 result,184 code: generateGarchCode(input),185 error: result.error186 });187 } catch (parseError) {188 resolve({189 success: false,190 code: generateGarchCode(input),191 error: 'Failed to parse Python output: ' + (parseError instanceof Error ? parseError.message : 'Unknown error')192 });193 }194 });195196 pythonProcess.on('error', (error) => {197 resolve({198 success: false,199 code: generateGarchCode(input),200 error: 'Failed to start Python process: ' + error.message201 });202 });203204 pythonProcess.stdin.write(inputJson);205 pythonProcess.stdin.end();206 });207 } catch (error) {208 return {209 success: false,210 code: generateGarchCode(input),211 error: error instanceof Error ? error.message : 'Unknown error occurred'212 };213 }214}215216/**217 * Generate readable Python code for display218 */219function generateGarchCode(input: GarchInput): string {220 const p = input.p || 1;221 const q = input.q || 1;222 const horizon = input.forecast_horizon || 30;223 const period = input.data_period || 504;224225 return `import numpy as np226import pandas as pd227from arch import arch_model228from fmpClient import get_historical_prices229230# Fetch historical prices for ${input.symbol}231symbol = '${input.symbol}'232print(f"Fetching historical prices for {symbol}...")233hist_data = get_historical_prices(symbol)234235# Extract closing prices236prices = [h['close'] for h in reversed(hist_data['historical'])]237dates = [h['date'] for h in reversed(hist_data['historical'])]238239# Limit to ${period} most recent days240if len(prices) > ${period}:241 prices = prices[-${period}:]242 dates = dates[-${period}:]243244# Convert to pandas Series245price_series = pd.Series(prices, index=pd.to_datetime(dates))246247# Calculate percentage returns248returns = 100 * price_series.pct_change().dropna()249250# Fit GARCH(${p},${q}) model251print(f"Fitting GARCH(${p},${q}) model...")252model = arch_model(253 returns,254 vol='Garch',255 p=${p},256 q=${q},257 mean='constant',258 dist='normal'259)260261model_fit = model.fit(disp='off')262263# Display model parameters264print("\\nModel Parameters:")265print(model_fit.params)266267# Extract GARCH parameters268omega = model_fit.params['omega']269alpha = [model_fit.params[f'alpha[{i+1}]'] for i in range(${q})]270beta = [model_fit.params[f'beta[{i+1}]'] for i in range(${p})]271272# Calculate persistence273persistence = sum(alpha) + sum(beta)274print(f"\\nPersistence: {persistence:.4f}")275print(f"Mean reverting: {persistence < 1}")276277# Get conditional volatility (annualized)278conditional_vol = model_fit.conditional_volatility * np.sqrt(252) / 100279current_volatility = conditional_vol.iloc[-1]280print(f"\\nCurrent Volatility: {current_volatility*100:.2f}%")281282# Forecast volatility283print(f"\\nForecasting volatility for ${horizon} days...")284forecasts = model_fit.forecast(horizon=${horizon})285forecast_variance = forecasts.variance.values[-1, :]286forecast_volatility = np.sqrt(forecast_variance) * np.sqrt(252) / 100287288print(f"\\nForecast Mean Volatility: {np.mean(forecast_volatility)*100:.2f}%")289print(f"Forecast Min Volatility: {np.min(forecast_volatility)*100:.2f}%")290print(f"Forecast Max Volatility: {np.max(forecast_volatility)*100:.2f}%")291292# Model fit statistics293print(f"\\nModel Fit:")294print(f" AIC: {model_fit.aic:.2f}")295print(f" BIC: {model_fit.bic:.2f}")296print(f" Log-Likelihood: {model_fit.loglikelihood:.2f}")`;297}298