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%
11.1 KB · 367 lines typescript
Raw Blame History
1/*2 * =============================================================================3 *  VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 *  File:      server/services/python/optionsPricing.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 OptionsPricingInput {32  symbol?: string;  // NEW: Automatically fetch data from FMP API33  stock_price?: number;  // ALTERNATIVE: Manual mode34  strike_price: number;35  time_to_maturity: number;  // in years36  risk_free_rate?: number;   // default 0.05 (5%)37  volatility?: number;        // annualized volatility (e.g., 0.3 for 30%)38  option_type?: 'call' | 'put';39}4041export interface OptionGreeks {42  delta: number;43  gamma: number;44  theta: number;45  vega: number;46  rho: number;47}4849export interface OptionResult {50  price: number;51  intrinsic_value: number;52  time_value: number;53  greeks: OptionGreeks;54  moneyness: string;55}5657export interface OptionsPricingResult {58  success: boolean;59  error?: string;60  parameters?: {61    stock_price: number;62    strike_price: number;63    time_to_maturity: number;64    risk_free_rate: number;65    volatility: number;66    option_type: string;67  };68  call_option?: OptionResult;69  put_option?: OptionResult;70  parity_check?: {71    call_minus_put: number;72    stock_minus_pv_strike: number;73    parity_holds: boolean;74  };75}7677export interface OptionsPricingExecutionResult {78  success: boolean;79  result?: OptionsPricingResult;80  code: string;81  error?: string;82}8384/**85 * Validate options pricing input86 */87function validateOptionInput(input: OptionsPricingInput): { valid: boolean; error?: string } {88  // Check if either symbol or stock_price is provided89  if (!input.symbol && !input.stock_price) {90    return { valid: false, error: 'Either symbol or stock_price must be provided' };91  }9293  // If stock_price provided, validate it94  if (input.stock_price !== undefined && (typeof input.stock_price !== 'number' || input.stock_price <= 0)) {95    return { valid: false, error: 'stock_price must be a positive number' };96  }9798  // If symbol provided, validate it99  if (input.symbol && typeof input.symbol !== 'string') {100    return { valid: false, error: 'symbol must be a string' };101  }102103  if (typeof input.strike_price !== 'number' || input.strike_price <= 0) {104    return { valid: false, error: 'strike_price must be a positive number' };105  }106107  if (typeof input.time_to_maturity !== 'number' || input.time_to_maturity <= 0) {108    return { valid: false, error: 'time_to_maturity must be a positive number (in years)' };109  }110111  if (input.risk_free_rate !== undefined && (typeof input.risk_free_rate !== 'number' || input.risk_free_rate < 0)) {112    return { valid: false, error: 'risk_free_rate must be a non-negative number' };113  }114115  if (input.volatility !== undefined && (typeof input.volatility !== 'number' || input.volatility <= 0)) {116    return { valid: false, error: 'volatility must be a positive number' };117  }118119  if (input.option_type && !['call', 'put'].includes(input.option_type)) {120    return { valid: false, error: 'option_type must be "call" or "put"' };121  }122123  return { valid: true };124}125126/**127 * Execute Black-Scholes options pricing calculation128 */129export async function executeOptionsPricing(130  input: OptionsPricingInput131): Promise<OptionsPricingExecutionResult> {132  try {133    // Validate input134    const validation = validateOptionInput(input);135    if (!validation.valid) {136      return {137        success: false,138        code: generateOptionsPricingCode(input),139        error: validation.error140      };141    }142143    const pythonScriptPath = path.join(__dirname, 'optionsPricingService.py');144    const inputJson = JSON.stringify(input);145146    return new Promise((resolve) => {147      const pythonProcess = spawn(PYTHON_PATH, [pythonScriptPath], {148        stdio: ['pipe', 'pipe', 'pipe'],149        env: process.env150      });151152      let stdout = '';153      let stderr = '';154155      pythonProcess.stdout.on('data', (data) => {156        stdout += data.toString();157      });158159      pythonProcess.stderr.on('data', (data) => {160        stderr += data.toString();161      });162163      pythonProcess.on('close', (code) => {164        if (code !== 0 || (stderr && !stdout)) {165          resolve({166            success: false,167            code: generateOptionsPricingCode(input),168            error: stderr || `Python process exited with code ${code}`169          });170          return;171        }172173        try {174          const result = JSON.parse(stdout) as OptionsPricingResult;175          resolve({176            success: result.success,177            result,178            code: generateOptionsPricingCode(input),179            error: result.error180          });181        } catch (parseError) {182          resolve({183            success: false,184            code: generateOptionsPricingCode(input),185            error: 'Failed to parse Python output: ' + (parseError instanceof Error ? parseError.message : 'Unknown error')186          });187        }188      });189190      pythonProcess.on('error', (error) => {191        resolve({192          success: false,193          code: generateOptionsPricingCode(input),194          error: 'Failed to start Python process: ' + error.message195        });196      });197198      pythonProcess.stdin.write(inputJson);199      pythonProcess.stdin.end();200    });201  } catch (error) {202    return {203      success: false,204      code: generateOptionsPricingCode(input),205      error: error instanceof Error ? error.message : 'Unknown error occurred'206    };207  }208}209210/**211 * Generate readable Python code for display212 */213function generateOptionsPricingCode(input: OptionsPricingInput): string {214  const K = input.strike_price;215  const T = input.time_to_maturity;216  const r = input.risk_free_rate || 0.05;217  const optionType = input.option_type || 'call';218219  if (input.symbol) {220    // Generate code for symbol-based mode221    const sigmaCode = input.volatility222      ? `sigma = ${input.volatility}  # Provided volatility`223      : `# Calculate 30-day historical volatility224recent_prices = closing_prices[-30:] if len(closing_prices) >= 30 else closing_prices225sigma = calculate_historical_volatility(recent_prices, annualize=True)226print(f"Calculated 30-day historical volatility: {sigma*100:.2f}%")`;227228    return `import math229from scipy import stats230from fmpClient import get_historical_prices, get_stock_quote, calculate_historical_volatility231232# Fetch market data for ${input.symbol}233symbol = '${input.symbol}'234print(f"Fetching market data for {symbol}...")235236# Get current stock price237quote = get_stock_quote(symbol)238S = quote['price']239print(f"Current stock price: $\\{S:.2f}")240241# Get historical prices242hist_data = get_historical_prices(symbol)243closing_prices = [h['close'] for h in reversed(hist_data['historical'])]244245# Calculate or use provided volatility246${sigmaCode}247248# Black-Scholes Option Pricing Parameters249K = ${K}  # Strike price250T = ${T}  # Time to maturity (years)251r = ${r}  # Risk-free rate252253# Calculate d1 and d2254d1 = (math.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * math.sqrt(T))255d2 = d1 - sigma * math.sqrt(T)256257# Calculate Call Option Price258call_price = S * stats.norm.cdf(d1) - K * math.exp(-r * T) * stats.norm.cdf(d2)259260# Calculate Put Option Price261put_price = K * math.exp(-r * T) * stats.norm.cdf(-d2) - S * stats.norm.cdf(-d1)262263# Calculate Greeks264# Delta265call_delta = stats.norm.cdf(d1)266put_delta = call_delta - 1267268# Gamma (same for call and put)269gamma = stats.norm.pdf(d1) / (S * sigma * math.sqrt(T))270271# Vega (same for call and put)272vega = S * stats.norm.pdf(d1) * math.sqrt(T) / 100273274# Theta275call_theta = (-(S * stats.norm.pdf(d1) * sigma) / (2 * math.sqrt(T))276              - r * K * math.exp(-r * T) * stats.norm.cdf(d2)) / 365277278put_theta = (-(S * stats.norm.pdf(d1) * sigma) / (2 * math.sqrt(T))279             + r * K * math.exp(-r * T) * stats.norm.cdf(-d2)) / 365280281# Rho282call_rho = K * T * math.exp(-r * T) * stats.norm.cdf(d2) / 100283put_rho = -K * T * math.exp(-r * T) * stats.norm.cdf(-d2) / 100284285print(f"Call Option Price: $\\{call_price:.4f}")286print(f"Put Option Price: $\\{put_price:.4f}")287print(f"\\nCall Greeks:")288print(f"  Delta: \\{call_delta:.4f}")289print(f"  Gamma: \\{gamma:.4f}")290print(f"  Theta: \\{call_theta:.4f}")291print(f"  Vega: \\{vega:.4f}")292print(f"  Rho: \\{call_rho:.4f}")293print(f"\\nPut Greeks:")294print(f"  Delta: \\{put_delta:.4f}")295print(f"  Gamma: \\{gamma:.4f}")296print(f"  Theta: \\{put_theta:.4f}")297print(f"  Vega: \\{vega:.4f}")298print(f"  Rho: \\{put_rho:.4f}")`;299  } else {300    // Generate code for manual mode301    const S = input.stock_price;302    const sigma = input.volatility || 0.3;303304    return `import math305from scipy import stats306307# Black-Scholes Option Pricing Parameters308S = ${S}  # Current stock price309K = ${K}  # Strike price310T = ${T}  # Time to maturity (years)311r = ${r}  # Risk-free rate312sigma = ${sigma}  # Volatility (annualized)313314# Calculate d1 and d2315d1 = (math.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * math.sqrt(T))316d2 = d1 - sigma * math.sqrt(T)317318# Calculate Call Option Price319call_price = S * stats.norm.cdf(d1) - K * math.exp(-r * T) * stats.norm.cdf(d2)320321# Calculate Put Option Price322put_price = K * math.exp(-r * T) * stats.norm.cdf(-d2) - S * stats.norm.cdf(-d1)323324# Calculate Greeks325# Delta326call_delta = stats.norm.cdf(d1)327put_delta = call_delta - 1328329# Gamma (same for call and put)330gamma = stats.norm.pdf(d1) / (S * sigma * math.sqrt(T))331332# Vega (same for call and put)333vega = S * stats.norm.pdf(d1) * math.sqrt(T) / 100334335# Theta336call_theta = (-(S * stats.norm.pdf(d1) * sigma) / (2 * math.sqrt(T))337              - r * K * math.exp(-r * T) * stats.norm.cdf(d2)) / 365338339put_theta = (-(S * stats.norm.pdf(d1) * sigma) / (2 * math.sqrt(T))340             + r * K * math.exp(-r * T) * stats.norm.cdf(-d2)) / 365341342# Rho343call_rho = K * T * math.exp(-r * T) * stats.norm.cdf(d2) / 100344put_rho = -K * T * math.exp(-r * T) * stats.norm.cdf(-d2) / 100345346print(f"Call Option Price: $\\{call_price:.4f}")347print(f"Put Option Price: $\\{put_price:.4f}")348print(f"\\nCall Greeks:")349print(f"  Delta: \\{call_delta:.4f}")350print(f"  Gamma: \\{gamma:.4f}")351print(f"  Theta: \\{call_theta:.4f}")352print(f"  Vega: \\{vega:.4f}")353print(f"  Rho: \\{call_rho:.4f}")354print(f"\\nPut Greeks:")355print(f"  Delta: \\{put_delta:.4f}")356print(f"  Gamma: \\{gamma:.4f}")357print(f"  Theta: \\{put_theta:.4f}")358print(f"  Vega: \\{vega:.4f}")359print(f"  Rho: \\{put_rho:.4f}")`;360  }361}362363// ============================================================================364// GARCH Volatility Model365// ============================================================================366367