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